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