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