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-time.h" 11 12 #include <fcntl.h> 13 #include <stdlib.h> 14 #include <string.h> 15 #include <unistd.h> 16 #include <errno.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 (fdevent_pipe_cloexec(to_rrdtool_fds, 4096)) { 90 log_perror(srv->errh, __FILE__, __LINE__, "pipe()"); 91 return 0; 92 } 93 if (fdevent_pipe_cloexec(from_rrdtool_fds, 4096)) { 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 const char * const path_rrdtool_bin = p->path_rrdtool_bin 100 ? p->path_rrdtool_bin->ptr 101 : "/usr/bin/rrdtool"; 102 *(const char **)&args[0] = path_rrdtool_bin; 103 *(const char **)&args[1] = "-"; 104 args[2] = NULL; 105 106 p->rrdtool_pid = fdevent_fork_execve(args[0], args, NULL, to_rrdtool_fds[0], from_rrdtool_fds[1], -1, -1); 107 108 if (-1 != p->rrdtool_pid) { 109 close(from_rrdtool_fds[1]); 110 close(to_rrdtool_fds[0]); 111 if (p->read_fd >= 0) close(p->read_fd); 112 if (p->write_fd >= 0) close(p->write_fd); 113 p->write_fd = to_rrdtool_fds[1]; 114 p->read_fd = from_rrdtool_fds[0]; 115 p->srv_pid = srv->pid; 116 return 1; 117 } else { 118 log_perror(srv->errh, __FILE__, __LINE__, 119 "fork/exec(%s)", path_rrdtool_bin); 120 close(to_rrdtool_fds[0]); 121 close(to_rrdtool_fds[1]); 122 close(from_rrdtool_fds[0]); 123 close(from_rrdtool_fds[1]); 124 return 0; 125 } 126 } 127 128 __attribute_noinline__ 129 static int mod_rrd_exec(server *srv, plugin_data *p) { 130 return (p->rrdtool_running = mod_rrd_create_pipe(srv, p)); 131 } 132 133 static void mod_rrd_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) { 134 switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */ 135 case 0: /* rrdtool.db-name */ 136 if (cpv->vtype == T_CONFIG_LOCAL) pconf->rrd = cpv->v.v; 137 break; 138 case 1: /* rrdtool.binary */ /* T_CONFIG_SCOPE_SERVER */ 139 break; 140 default:/* should not happen */ 141 return; 142 } 143 } 144 145 static void mod_rrd_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) { 146 do { 147 mod_rrd_merge_config_cpv(pconf, cpv); 148 } while ((++cpv)->k_id != -1); 149 } 150 151 static void mod_rrd_patch_config(request_st * const r, plugin_data * const p) { 152 p->conf = p->defaults; /* copy small struct instead of memcpy() */ 153 /*memcpy(&p->conf, &p->defaults, sizeof(plugin_config));*/ 154 for (int i = 1, used = p->nconfig; i < used; ++i) { 155 if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id)) 156 mod_rrd_merge_config(&p->conf, p->cvlist + p->cvlist[i].v.u2[0]); 157 } 158 } 159 160 SETDEFAULTS_FUNC(mod_rrd_set_defaults) { 161 static const config_plugin_keys_t cpk[] = { 162 { CONST_STR_LEN("rrdtool.db-name"), 163 T_CONFIG_STRING, 164 T_CONFIG_SCOPE_CONNECTION } 165 ,{ CONST_STR_LEN("rrdtool.binary"), 166 T_CONFIG_STRING, 167 T_CONFIG_SCOPE_SERVER } 168 ,{ NULL, 0, 169 T_CONFIG_UNSET, 170 T_CONFIG_SCOPE_UNSET } 171 }; 172 173 plugin_data * const p = p_d; 174 p->srv = srv; 175 if (!config_plugin_values_init(srv, p, cpk, "mod_rrdtool")) 176 return HANDLER_ERROR; 177 178 int activate = 0; 179 180 /* process and validate config directives 181 * (init i to 0 if global context; to 1 to skip empty global context) */ 182 for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) { 183 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0]; 184 for (; -1 != cpv->k_id; ++cpv) { 185 switch (cpv->k_id) { 186 case 0: /* rrdtool.db-name */ 187 if (!buffer_is_blank(cpv->v.b)) { 188 rrd_config *rrd = calloc(1, sizeof(rrd_config)); 189 force_assert(rrd); 190 rrd->path_rrd = cpv->v.b; 191 cpv->v.v = rrd; 192 cpv->vtype = T_CONFIG_LOCAL; 193 activate = 1; 194 } 195 break; 196 case 1: /* rrdtool.binary */ /* T_CONFIG_SCOPE_SERVER */ 197 if (!buffer_is_blank(cpv->v.b)) 198 p->path_rrdtool_bin = cpv->v.b; /*(store directly in p)*/ 199 break; 200 default:/* should not happen */ 201 break; 202 } 203 } 204 } 205 206 /* initialize p->defaults from global config context */ 207 if (p->nconfig > 0 && p->cvlist->v.u2[1]) { 208 const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0]; 209 if (-1 != cpv->k_id) 210 mod_rrd_merge_config(&p->defaults, cpv); 211 } 212 213 p->rrdtool_running = 0; 214 p->read_fd = -1; 215 p->write_fd = -1; 216 217 return (!activate || mod_rrd_exec(srv, p)) ? HANDLER_GO_ON : HANDLER_ERROR; 218 } 219 220 /* read/write wrappers to catch EINTR */ 221 222 /* write to blocking socket; blocks until all data is sent, write returns 0 or an error (apart from EINTR) occurs. */ 223 static ssize_t safe_write(int fd, const void *buf, size_t count) { 224 ssize_t res, sum = 0; 225 226 for (;;) { 227 res = write(fd, buf, count); 228 if (res >= 0) { 229 sum += res; 230 /* do not try again if res == 0 */ 231 if (res == 0 || (size_t) res == count) return sum; 232 count -= res; 233 buf = (const char*) buf + res; 234 continue; 235 } 236 switch (errno) { 237 case EINTR: 238 continue; 239 default: 240 return -1; 241 } 242 } 243 } 244 245 /* this assumes we get enough data on a successful read */ 246 static ssize_t safe_read(int fd, char *buf, size_t sz) { 247 ssize_t res; 248 249 do { 250 res = read(fd, buf, sz-1); 251 } while (-1 == res && errno == EINTR); 252 253 if (res >= 0) buf[res] = '\0'; 254 return res; 255 } 256 257 static int mod_rrdtool_create_rrd(server *srv, plugin_data *p, rrd_config *s, char *resp, size_t respsz) { 258 struct stat st; 259 260 /* check if DB already exists */ 261 if (0 == stat(s->path_rrd->ptr, &st)) { 262 /* check if it is plain file */ 263 if (!S_ISREG(st.st_mode)) { 264 log_error(srv->errh, __FILE__, __LINE__, 265 "not a regular file: %s", s->path_rrd->ptr); 266 return HANDLER_ERROR; 267 } 268 269 /* still create DB if it's empty file */ 270 if (st.st_size > 0) { 271 return HANDLER_GO_ON; 272 } 273 } 274 275 /* create a new one */ 276 buffer * const cmd = srv->tmp_buf; 277 buffer_clear(cmd); 278 buffer_append_str3(cmd, 279 CONST_STR_LEN("create "), 280 BUF_PTR_LEN(s->path_rrd), 281 CONST_STR_LEN( 282 " --step 60 " 283 "DS:InOctets:ABSOLUTE:600:U:U " 284 "DS:OutOctets:ABSOLUTE:600:U:U " 285 "DS:Requests:ABSOLUTE:600:U:U " 286 "RRA:AVERAGE:0.5:1:600 " 287 "RRA:AVERAGE:0.5:6:700 " 288 "RRA:AVERAGE:0.5:24:775 " 289 "RRA:AVERAGE:0.5:288:797 " 290 "RRA:MAX:0.5:1:600 " 291 "RRA:MAX:0.5:6:700 " 292 "RRA:MAX:0.5:24:775 " 293 "RRA:MAX:0.5:288:797 " 294 "RRA:MIN:0.5:1:600 " 295 "RRA:MIN:0.5:6:700 " 296 "RRA:MIN:0.5:24:775 " 297 "RRA:MIN:0.5:288:797\n")); 298 299 if (-1 == (safe_write(p->write_fd, BUF_PTR_LEN(cmd)))) { 300 log_perror(srv->errh, __FILE__, __LINE__, "rrdtool-write: failed"); 301 return HANDLER_ERROR; 302 } 303 304 if (-1 == safe_read(p->read_fd, resp, respsz)) { 305 log_perror(srv->errh, __FILE__, __LINE__, "rrdtool-read: failed"); 306 return HANDLER_ERROR; 307 } 308 309 if (resp[0] != 'O' || resp[1] != 'K') { 310 log_error(srv->errh, __FILE__, __LINE__, 311 "rrdtool-response: %s %s", cmd->ptr, resp); 312 return HANDLER_ERROR; 313 } 314 315 return HANDLER_GO_ON; 316 } 317 318 __attribute_cold__ 319 static int mod_rrd_fatal_error(plugin_data *p) { 320 /* future: might send kill() signal to p->rrdtool_pid to trigger restart */ 321 p->rrdtool_running = 0; 322 return 0; 323 } 324 325 __attribute_noinline__ 326 static int mod_rrd_write_data(server *srv, plugin_data *p, rrd_config *s) { 327 char resp[4096]; 328 329 if (HANDLER_GO_ON != mod_rrdtool_create_rrd(srv, p, s, resp, sizeof(resp))) 330 return 0; 331 332 buffer * const cmd = srv->tmp_buf; 333 buffer_clear(cmd); 334 buffer_append_str3(cmd, CONST_STR_LEN("update "), 335 BUF_PTR_LEN(s->path_rrd), 336 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, BUF_PTR_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