1 #include "first.h" 2 3 #include "base.h" 4 #include "fdevent.h" 5 #include "log.h" 6 7 #include "plugin.h" 8 #include <sys/types.h> 9 #include <sys/stat.h> 10 #include <sys/wait.h> 11 12 #include <fcntl.h> 13 #include <stdlib.h> 14 #include <string.h> 15 #include <unistd.h> 16 #include <errno.h> 17 #include <time.h> 18 19 typedef struct { 20 buffer *path_rrdtool_bin; 21 buffer *path_rrd; 22 23 double requests, *requests_ptr; 24 double bytes_written, *bytes_written_ptr; 25 double bytes_read, *bytes_read_ptr; 26 } plugin_config; 27 28 typedef struct { 29 PLUGIN_DATA; 30 31 buffer *cmd; 32 buffer *resp; 33 34 int read_fd, write_fd; 35 pid_t rrdtool_pid; 36 pid_t srv_pid; 37 38 int rrdtool_running; 39 time_t rrdtool_startup_ts; 40 41 plugin_config **config_storage; 42 plugin_config conf; 43 } plugin_data; 44 45 INIT_FUNC(mod_rrd_init) { 46 plugin_data *p; 47 48 p = calloc(1, sizeof(*p)); 49 50 p->resp = buffer_init(); 51 p->cmd = buffer_init(); 52 53 return p; 54 } 55 56 FREE_FUNC(mod_rrd_free) { 57 plugin_data *p = p_d; 58 size_t i; 59 60 if (!p) return HANDLER_GO_ON; 61 62 if (p->config_storage) { 63 for (i = 0; i < srv->config_context->used; i++) { 64 plugin_config *s = p->config_storage[i]; 65 66 if (NULL == s) continue; 67 68 buffer_free(s->path_rrdtool_bin); 69 buffer_free(s->path_rrd); 70 71 free(s); 72 } 73 } 74 buffer_free(p->cmd); 75 buffer_free(p->resp); 76 77 free(p->config_storage); 78 79 if (p->read_fd >= 0) close(p->read_fd); 80 if (p->write_fd >= 0) close(p->write_fd); 81 82 if (p->rrdtool_pid > 0 && p->srv_pid == srv->pid) { 83 /* collect status */ 84 while (-1 == waitpid(p->rrdtool_pid, NULL, 0) && errno == EINTR) ; 85 } 86 87 free(p); 88 89 return HANDLER_GO_ON; 90 } 91 92 static int mod_rrd_create_pipe(server *srv, plugin_data *p) { 93 char *args[3]; 94 int to_rrdtool_fds[2]; 95 int from_rrdtool_fds[2]; 96 /* mod_rrdtool does not work with server.max-workers > 0 97 * since the data between workers is not aggregated, 98 * and it is not valid to send data to rrdtool more than once a sec 99 * (which would happen with multiple workers writing to same pipe) 100 * If pipes were to be shared, then existing pipes would need to be 101 * reused here, if they already exist (not -1), and after flushing 102 * existing contents (read and discard from read-end of pipes). */ 103 if (pipe(to_rrdtool_fds)) { 104 log_error_write(srv, __FILE__, __LINE__, "ss", 105 "pipe failed: ", strerror(errno)); 106 return -1; 107 } 108 if (pipe(from_rrdtool_fds)) { 109 log_error_write(srv, __FILE__, __LINE__, "ss", 110 "pipe failed: ", strerror(errno)); 111 return -1; 112 } 113 fdevent_setfd_cloexec(to_rrdtool_fds[1]); 114 fdevent_setfd_cloexec(from_rrdtool_fds[0]); 115 *(const char **)&args[0] = p->conf.path_rrdtool_bin->ptr; 116 *(const char **)&args[1] = "-"; 117 args[2] = NULL; 118 119 p->rrdtool_pid = fdevent_fork_execve(args[0], args, NULL, to_rrdtool_fds[0], from_rrdtool_fds[1], -1, -1); 120 121 if (-1 != p->rrdtool_pid) { 122 close(from_rrdtool_fds[1]); 123 close(to_rrdtool_fds[0]); 124 if (p->read_fd >= 0) close(p->read_fd); 125 if (p->write_fd >= 0) close(p->write_fd); 126 p->write_fd = to_rrdtool_fds[1]; 127 p->read_fd = from_rrdtool_fds[0]; 128 p->srv_pid = srv->pid; 129 return 0; 130 } else { 131 log_error_write(srv, __FILE__, __LINE__, "SBss", "fork/exec(", p->conf.path_rrdtool_bin, "):", strerror(errno)); 132 close(to_rrdtool_fds[0]); 133 close(to_rrdtool_fds[1]); 134 close(from_rrdtool_fds[0]); 135 close(from_rrdtool_fds[1]); 136 return -1; 137 } 138 } 139 140 /* read/write wrappers to catch EINTR */ 141 142 /* write to blocking socket; blocks until all data is sent, write returns 0 or an error (apart from EINTR) occurs. */ 143 static ssize_t safe_write(int fd, const void *buf, size_t count) { 144 ssize_t res, sum = 0; 145 146 for (;;) { 147 res = write(fd, buf, count); 148 if (res >= 0) { 149 sum += res; 150 /* do not try again if res == 0 */ 151 if (res == 0 || (size_t) res == count) return sum; 152 count -= res; 153 buf = (const char*) buf + res; 154 continue; 155 } 156 switch (errno) { 157 case EINTR: 158 continue; 159 default: 160 return -1; 161 } 162 } 163 } 164 165 /* this assumes we get enough data on a successful read */ 166 static ssize_t safe_read(int fd, buffer *b) { 167 ssize_t res; 168 169 buffer_string_prepare_copy(b, 4095); 170 171 do { 172 res = read(fd, b->ptr, b->size-1); 173 } while (-1 == res && errno == EINTR); 174 175 if (res >= 0) buffer_commit(b, res); 176 return res; 177 } 178 179 static int mod_rrdtool_create_rrd(server *srv, plugin_data *p, plugin_config *s) { 180 struct stat st; 181 182 /* check if DB already exists */ 183 if (0 == stat(s->path_rrd->ptr, &st)) { 184 /* check if it is plain file */ 185 if (!S_ISREG(st.st_mode)) { 186 log_error_write(srv, __FILE__, __LINE__, "sb", 187 "not a regular file:", s->path_rrd); 188 return HANDLER_ERROR; 189 } 190 191 /* still create DB if it's empty file */ 192 if (st.st_size > 0) { 193 return HANDLER_GO_ON; 194 } 195 } 196 197 /* create a new one */ 198 buffer_copy_string_len(p->cmd, CONST_STR_LEN("create ")); 199 buffer_append_string_buffer(p->cmd, s->path_rrd); 200 buffer_append_string_len(p->cmd, CONST_STR_LEN( 201 " --step 60 " 202 "DS:InOctets:ABSOLUTE:600:U:U " 203 "DS:OutOctets:ABSOLUTE:600:U:U " 204 "DS:Requests:ABSOLUTE:600:U:U " 205 "RRA:AVERAGE:0.5:1:600 " 206 "RRA:AVERAGE:0.5:6:700 " 207 "RRA:AVERAGE:0.5:24:775 " 208 "RRA:AVERAGE:0.5:288:797 " 209 "RRA:MAX:0.5:1:600 " 210 "RRA:MAX:0.5:6:700 " 211 "RRA:MAX:0.5:24:775 " 212 "RRA:MAX:0.5:288:797 " 213 "RRA:MIN:0.5:1:600 " 214 "RRA:MIN:0.5:6:700 " 215 "RRA:MIN:0.5:24:775 " 216 "RRA:MIN:0.5:288:797\n")); 217 218 if (-1 == (safe_write(p->write_fd, CONST_BUF_LEN(p->cmd)))) { 219 log_error_write(srv, __FILE__, __LINE__, "ss", 220 "rrdtool-write: failed", strerror(errno)); 221 222 return HANDLER_ERROR; 223 } 224 225 if (-1 == safe_read(p->read_fd, p->resp)) { 226 log_error_write(srv, __FILE__, __LINE__, "ss", 227 "rrdtool-read: failed", strerror(errno)); 228 229 return HANDLER_ERROR; 230 } 231 232 if (p->resp->ptr[0] != 'O' || 233 p->resp->ptr[1] != 'K') { 234 log_error_write(srv, __FILE__, __LINE__, "sbb", 235 "rrdtool-response:", p->cmd, p->resp); 236 237 return HANDLER_ERROR; 238 } 239 240 return HANDLER_GO_ON; 241 } 242 243 #define PATCH(x) \ 244 p->conf.x = s->x; 245 static int mod_rrd_patch_connection(server *srv, connection *con, plugin_data *p) { 246 size_t i, j; 247 plugin_config *s = p->config_storage[0]; 248 249 PATCH(path_rrdtool_bin); 250 PATCH(path_rrd); 251 252 p->conf.bytes_written_ptr = &(s->bytes_written); 253 p->conf.bytes_read_ptr = &(s->bytes_read); 254 p->conf.requests_ptr = &(s->requests); 255 256 /* skip the first, the global context */ 257 for (i = 1; i < srv->config_context->used; i++) { 258 data_config *dc = (data_config *)srv->config_context->data[i]; 259 s = p->config_storage[i]; 260 261 /* condition didn't match */ 262 if (!config_check_cond(srv, con, dc)) continue; 263 264 /* merge config */ 265 for (j = 0; j < dc->value->used; j++) { 266 data_unset *du = dc->value->data[j]; 267 268 if (buffer_is_equal_string(du->key, CONST_STR_LEN("rrdtool.db-name"))) { 269 PATCH(path_rrd); 270 /* get pointers to double values */ 271 272 p->conf.bytes_written_ptr = &(s->bytes_written); 273 p->conf.bytes_read_ptr = &(s->bytes_read); 274 p->conf.requests_ptr = &(s->requests); 275 } 276 } 277 } 278 279 return 0; 280 } 281 #undef PATCH 282 283 static int mod_rrd_exec(server *srv, plugin_data *p) { 284 if (mod_rrd_create_pipe(srv, p)) { 285 return -1; 286 } 287 288 p->rrdtool_running = 1; 289 p->rrdtool_startup_ts = srv->cur_ts; 290 return 0; 291 } 292 293 SETDEFAULTS_FUNC(mod_rrd_set_defaults) { 294 plugin_data *p = p_d; 295 size_t i; 296 int activate = 0; 297 298 config_values_t cv[] = { 299 { "rrdtool.binary", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ 300 { "rrdtool.db-name", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 1 */ 301 { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } 302 }; 303 304 if (!p) return HANDLER_ERROR; 305 306 force_assert(srv->config_context->used > 0); 307 p->config_storage = calloc(srv->config_context->used, sizeof(plugin_config *)); 308 309 for (i = 0; i < srv->config_context->used; i++) { 310 data_config const* config = (data_config const*)srv->config_context->data[i]; 311 plugin_config *s; 312 313 s = calloc(1, sizeof(plugin_config)); 314 s->path_rrdtool_bin = buffer_init(); 315 s->path_rrd = buffer_init(); 316 s->requests = 0; 317 s->bytes_written = 0; 318 s->bytes_read = 0; 319 320 cv[0].destination = s->path_rrdtool_bin; 321 cv[1].destination = s->path_rrd; 322 323 p->config_storage[i] = s; 324 325 if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) { 326 return HANDLER_ERROR; 327 } 328 329 if (i > 0 && !buffer_string_is_empty(s->path_rrdtool_bin)) { 330 /* path_rrdtool_bin is a global option */ 331 332 log_error_write(srv, __FILE__, __LINE__, "s", 333 "rrdtool.binary can only be set as a global option."); 334 335 return HANDLER_ERROR; 336 } 337 338 if (!buffer_string_is_empty(s->path_rrd)) activate = 1; 339 } 340 341 p->conf.path_rrdtool_bin = p->config_storage[0]->path_rrdtool_bin; 342 p->rrdtool_running = 0; 343 p->read_fd = -1; 344 p->write_fd = -1; 345 346 if (!activate) return HANDLER_GO_ON; 347 348 /* check for dir */ 349 350 if (buffer_string_is_empty(p->conf.path_rrdtool_bin)) { 351 log_error_write(srv, __FILE__, __LINE__, "s", 352 "rrdtool.binary has to be set"); 353 return HANDLER_ERROR; 354 } 355 356 return 0 == mod_rrd_exec(srv, p) ? HANDLER_GO_ON : HANDLER_ERROR; 357 } 358 359 static void mod_rrd_fatal_error(server *srv, plugin_data *p) { 360 /* future: might send kill() signal to p->rrdtool_pid to trigger restart */ 361 p->rrdtool_running = 0; 362 UNUSED(srv); 363 } 364 365 TRIGGER_FUNC(mod_rrd_trigger) { 366 plugin_data *p = p_d; 367 size_t i; 368 369 if (!p->rrdtool_running) { 370 /* limit restart to once every 5 sec */ 371 /*(0 == p->rrdtool_pid if never activated; not used)*/ 372 if (-1 == p->rrdtool_pid 373 && p->srv_pid == srv->pid 374 && p->rrdtool_startup_ts + 5 < srv->cur_ts) { 375 mod_rrd_exec(srv, p); 376 } 377 return HANDLER_GO_ON; 378 } 379 380 if ((srv->cur_ts % 60) != 0) return HANDLER_GO_ON; 381 382 for (i = 0; i < srv->config_context->used; i++) { 383 plugin_config *s = p->config_storage[i]; 384 385 if (buffer_string_is_empty(s->path_rrd)) continue; 386 387 /* write the data down every minute */ 388 389 if (HANDLER_GO_ON != mod_rrdtool_create_rrd(srv, p, s)) return HANDLER_GO_ON; 390 391 buffer_copy_string_len(p->cmd, CONST_STR_LEN("update ")); 392 buffer_append_string_buffer(p->cmd, s->path_rrd); 393 buffer_append_string_len(p->cmd, CONST_STR_LEN(" N:")); 394 buffer_append_int(p->cmd, s->bytes_read); 395 buffer_append_string_len(p->cmd, CONST_STR_LEN(":")); 396 buffer_append_int(p->cmd, s->bytes_written); 397 buffer_append_string_len(p->cmd, CONST_STR_LEN(":")); 398 buffer_append_int(p->cmd, s->requests); 399 buffer_append_string_len(p->cmd, CONST_STR_LEN("\n")); 400 401 if (-1 == safe_write(p->write_fd, CONST_BUF_LEN(p->cmd))) { 402 log_error_write(srv, __FILE__, __LINE__, "ss", 403 "rrdtool-write: failed", strerror(errno)); 404 405 mod_rrd_fatal_error(srv, p); 406 return HANDLER_GO_ON; 407 } 408 409 if (-1 == safe_read(p->read_fd, p->resp)) { 410 log_error_write(srv, __FILE__, __LINE__, "ss", 411 "rrdtool-read: failed", strerror(errno)); 412 413 mod_rrd_fatal_error(srv, p); 414 return HANDLER_GO_ON; 415 } 416 417 if (p->resp->ptr[0] != 'O' || 418 p->resp->ptr[1] != 'K') { 419 /* don't fail on this error if we just started (graceful restart, the old one might have just updated too) */ 420 if (!(strstr(p->resp->ptr, "(minimum one second step)") && (srv->cur_ts - srv->startup_ts < 3))) { 421 log_error_write(srv, __FILE__, __LINE__, "sbb", 422 "rrdtool-response:", p->cmd, p->resp); 423 424 mod_rrd_fatal_error(srv, p); 425 return HANDLER_GO_ON; 426 } 427 } 428 s->requests = 0; 429 s->bytes_written = 0; 430 s->bytes_read = 0; 431 } 432 433 return HANDLER_GO_ON; 434 } 435 436 static handler_t mod_rrd_waitpid_cb(server *srv, void *p_d, pid_t pid, int status) { 437 plugin_data *p = p_d; 438 if (pid != p->rrdtool_pid) return HANDLER_GO_ON; 439 if (srv->pid != p->srv_pid) return HANDLER_GO_ON; 440 441 p->rrdtool_running = 0; 442 p->rrdtool_pid = -1; 443 444 /* limit restart to once every 5 sec */ 445 if (p->rrdtool_startup_ts + 5 < srv->cur_ts) 446 mod_rrd_exec(srv, p); 447 448 UNUSED(status); 449 return HANDLER_FINISHED; 450 } 451 452 REQUESTDONE_FUNC(mod_rrd_account) { 453 plugin_data *p = p_d; 454 455 /*(0 == p->rrdtool_pid if never activated; not used)*/ 456 if (0 == p->rrdtool_pid) return HANDLER_GO_ON; 457 mod_rrd_patch_connection(srv, con, p); 458 459 *(p->conf.requests_ptr) += 1; 460 *(p->conf.bytes_written_ptr) += con->bytes_written; 461 *(p->conf.bytes_read_ptr) += con->bytes_read; 462 463 return HANDLER_GO_ON; 464 } 465 466 int mod_rrdtool_plugin_init(plugin *p); 467 int mod_rrdtool_plugin_init(plugin *p) { 468 p->version = LIGHTTPD_VERSION_ID; 469 p->name = buffer_init_string("rrd"); 470 471 p->init = mod_rrd_init; 472 p->cleanup = mod_rrd_free; 473 p->set_defaults= mod_rrd_set_defaults; 474 475 p->handle_trigger = mod_rrd_trigger; 476 p->handle_waitpid = mod_rrd_waitpid_cb; 477 p->handle_request_done = mod_rrd_account; 478 479 p->data = NULL; 480 481 return 0; 482 } 483