1 #include "first.h" 2 3 #include "base.h" 4 #include "fdevent.h" 5 #include "log.h" 6 #include "response.h" 7 8 #include "plugin.h" 9 #include <sys/types.h> 10 #include <sys/stat.h> 11 #include "sys-time.h" 12 13 #include <fcntl.h> 14 #include <stdlib.h> 15 #include <string.h> 16 #include <unistd.h> 17 #include <errno.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 fdevent_waitpid(p->rrdtool_pid, NULL, 0); 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 (fdevent_pipe_cloexec(to_rrdtool_fds, 4096)) { 91 log_perror(srv->errh, __FILE__, __LINE__, "pipe()"); 92 return 0; 93 } 94 if (fdevent_pipe_cloexec(from_rrdtool_fds, 4096)) { 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 const char * const path_rrdtool_bin = p->path_rrdtool_bin 101 ? p->path_rrdtool_bin->ptr 102 : "/usr/bin/rrdtool"; 103 *(const char **)&args[0] = path_rrdtool_bin; 104 *(const char **)&args[1] = "-"; 105 args[2] = NULL; 106 107 p->rrdtool_pid = fdevent_fork_execve(args[0], args, NULL, to_rrdtool_fds[0], from_rrdtool_fds[1], -1, -1); 108 109 if (-1 != p->rrdtool_pid) { 110 close(from_rrdtool_fds[1]); 111 close(to_rrdtool_fds[0]); 112 if (p->read_fd >= 0) close(p->read_fd); 113 if (p->write_fd >= 0) close(p->write_fd); 114 p->write_fd = to_rrdtool_fds[1]; 115 p->read_fd = from_rrdtool_fds[0]; 116 p->srv_pid = srv->pid; 117 return 1; 118 } else { 119 log_perror(srv->errh, __FILE__, __LINE__, 120 "fork/exec(%s)", path_rrdtool_bin); 121 close(to_rrdtool_fds[0]); 122 close(to_rrdtool_fds[1]); 123 close(from_rrdtool_fds[0]); 124 close(from_rrdtool_fds[1]); 125 return 0; 126 } 127 } 128 129 __attribute_noinline__ 130 static int mod_rrd_exec(server *srv, plugin_data *p) { 131 return (p->rrdtool_running = mod_rrd_create_pipe(srv, p)); 132 } 133 134 static void mod_rrd_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) { 135 switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */ 136 case 0: /* rrdtool.db-name */ 137 if (cpv->vtype == T_CONFIG_LOCAL) pconf->rrd = cpv->v.v; 138 break; 139 case 1: /* rrdtool.binary */ /* T_CONFIG_SCOPE_SERVER */ 140 break; 141 default:/* should not happen */ 142 return; 143 } 144 } 145 146 static void mod_rrd_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) { 147 do { 148 mod_rrd_merge_config_cpv(pconf, cpv); 149 } while ((++cpv)->k_id != -1); 150 } 151 152 static void mod_rrd_patch_config(request_st * const r, plugin_data * const p) { 153 p->conf = p->defaults; /* copy small struct instead of memcpy() */ 154 /*memcpy(&p->conf, &p->defaults, sizeof(plugin_config));*/ 155 for (int i = 1, used = p->nconfig; i < used; ++i) { 156 if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id)) 157 mod_rrd_merge_config(&p->conf, p->cvlist + p->cvlist[i].v.u2[0]); 158 } 159 } 160 161 SETDEFAULTS_FUNC(mod_rrd_set_defaults) { 162 static const config_plugin_keys_t cpk[] = { 163 { CONST_STR_LEN("rrdtool.db-name"), 164 T_CONFIG_STRING, 165 T_CONFIG_SCOPE_CONNECTION } 166 ,{ CONST_STR_LEN("rrdtool.binary"), 167 T_CONFIG_STRING, 168 T_CONFIG_SCOPE_SERVER } 169 ,{ NULL, 0, 170 T_CONFIG_UNSET, 171 T_CONFIG_SCOPE_UNSET } 172 }; 173 174 plugin_data * const p = p_d; 175 p->srv = srv; 176 if (!config_plugin_values_init(srv, p, cpk, "mod_rrdtool")) 177 return HANDLER_ERROR; 178 179 int activate = 0; 180 181 /* process and validate config directives 182 * (init i to 0 if global context; to 1 to skip empty global context) */ 183 for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) { 184 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0]; 185 for (; -1 != cpv->k_id; ++cpv) { 186 switch (cpv->k_id) { 187 case 0: /* rrdtool.db-name */ 188 if (!buffer_is_blank(cpv->v.b)) { 189 rrd_config *rrd = calloc(1, sizeof(rrd_config)); 190 force_assert(rrd); 191 rrd->path_rrd = cpv->v.b; 192 cpv->v.v = rrd; 193 cpv->vtype = T_CONFIG_LOCAL; 194 activate = 1; 195 } 196 break; 197 case 1: /* rrdtool.binary */ /* T_CONFIG_SCOPE_SERVER */ 198 if (!buffer_is_blank(cpv->v.b)) 199 p->path_rrdtool_bin = cpv->v.b; /*(store directly in p)*/ 200 break; 201 default:/* should not happen */ 202 break; 203 } 204 } 205 } 206 207 /* initialize p->defaults from global config context */ 208 if (p->nconfig > 0 && p->cvlist->v.u2[1]) { 209 const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0]; 210 if (-1 != cpv->k_id) 211 mod_rrd_merge_config(&p->defaults, cpv); 212 } 213 214 p->rrdtool_running = 0; 215 p->read_fd = -1; 216 p->write_fd = -1; 217 218 return (!activate || mod_rrd_exec(srv, p)) ? HANDLER_GO_ON : HANDLER_ERROR; 219 } 220 221 /* read/write wrappers to catch EINTR */ 222 223 /* write to blocking socket; blocks until all data is sent, write returns 0 or an error (apart from EINTR) occurs. */ 224 static ssize_t safe_write(int fd, const void *buf, size_t count) { 225 ssize_t res, sum = 0; 226 227 for (;;) { 228 res = write(fd, buf, count); 229 if (res >= 0) { 230 sum += res; 231 /* do not try again if res == 0 */ 232 if (res == 0 || (size_t) res == count) return sum; 233 count -= res; 234 buf = (const char*) buf + res; 235 continue; 236 } 237 switch (errno) { 238 case EINTR: 239 continue; 240 default: 241 return -1; 242 } 243 } 244 } 245 246 /* this assumes we get enough data on a successful read */ 247 static ssize_t safe_read(int fd, char *buf, size_t sz) { 248 ssize_t res; 249 250 do { 251 res = read(fd, buf, sz-1); 252 } while (-1 == res && errno == EINTR); 253 254 if (res >= 0) buf[res] = '\0'; 255 return res; 256 } 257 258 static int mod_rrdtool_create_rrd(server *srv, plugin_data *p, rrd_config *s, char *resp, size_t respsz) { 259 struct stat st; 260 261 /* check if DB already exists */ 262 if (0 == stat(s->path_rrd->ptr, &st)) { 263 /* check if it is plain file */ 264 if (!S_ISREG(st.st_mode)) { 265 log_error(srv->errh, __FILE__, __LINE__, 266 "not a regular file: %s", s->path_rrd->ptr); 267 return HANDLER_ERROR; 268 } 269 270 /* still create DB if it's empty file */ 271 if (st.st_size > 0) { 272 return HANDLER_GO_ON; 273 } 274 } 275 276 /* create a new one */ 277 buffer * const cmd = srv->tmp_buf; 278 buffer_clear(cmd); 279 buffer_append_str3(cmd, 280 CONST_STR_LEN("create "), 281 BUF_PTR_LEN(s->path_rrd), 282 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, BUF_PTR_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_clear(cmd); 335 buffer_append_str3(cmd, CONST_STR_LEN("update "), 336 BUF_PTR_LEN(s->path_rrd), 337 CONST_STR_LEN(" N:")); 338 buffer_append_int(cmd, s->bytes_read); 339 buffer_append_char(cmd, ':'); 340 buffer_append_int(cmd, s->bytes_written); 341 buffer_append_char(cmd, ':'); 342 buffer_append_int(cmd, s->requests); 343 buffer_append_char(cmd, '\n'); 344 345 if (-1 == safe_write(p->write_fd, BUF_PTR_LEN(cmd))) { 346 log_error(srv->errh, __FILE__, __LINE__, "rrdtool-write: failed"); 347 return mod_rrd_fatal_error(p); 348 } 349 350 if (-1 == safe_read(p->read_fd, resp, sizeof(resp))) { 351 log_error(srv->errh, __FILE__, __LINE__, "rrdtool-read: failed"); 352 return mod_rrd_fatal_error(p); 353 } 354 355 if (resp[0] == 'O' && resp[1] == 'K') { 356 s->requests = 0; 357 s->bytes_written = 0; 358 s->bytes_read = 0; 359 } 360 else if (!(strstr(resp, "(minimum one second step)") 361 && log_epoch_secs - srv->startup_ts < 3)) { 362 /* don't fail on this error if we just started (above condition) 363 * (graceful restart, the old one might have just updated too) */ 364 log_error(srv->errh, __FILE__, __LINE__, 365 "rrdtool-response: %s %s", cmd->ptr, resp); 366 return mod_rrd_fatal_error(p); 367 } 368 369 return 1; 370 } 371 372 static void mod_rrd_write_data_loop(server *srv, plugin_data *p) { 373 /* process and validate config directives 374 * (init i to 0 if global context; to 1 to skip empty global context) */ 375 for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) { 376 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0]; 377 for (; -1 != cpv->k_id; ++cpv) { 378 switch (cpv->k_id) { 379 case 0: /* rrdtool.db-name */ 380 if (cpv->vtype != T_CONFIG_LOCAL) continue; 381 mod_rrd_write_data(srv, p, cpv->v.v); 382 if (!p->rrdtool_running) return; 383 break; 384 } 385 } 386 } 387 } 388 389 TRIGGER_FUNC(mod_rrd_trigger) { 390 plugin_data *p = p_d; 391 /*(0 == p->rrdtool_pid if never activated; not used)*/ 392 if (0 == p->rrdtool_pid) return HANDLER_GO_ON; 393 394 /* write data once a minute */ 395 if ((log_epoch_secs % 60) != 0) return HANDLER_GO_ON; 396 397 if (!p->rrdtool_running) { 398 if (srv->pid != p->srv_pid) return HANDLER_GO_ON; 399 /* restart limited to once every 60 sec (above) */ 400 if (!mod_rrd_exec(srv, p)) return HANDLER_GO_ON; 401 } 402 403 mod_rrd_write_data_loop(srv, p); 404 return HANDLER_GO_ON; 405 } 406 407 static handler_t mod_rrd_waitpid_cb(server *srv, void *p_d, pid_t pid, int status) { 408 plugin_data *p = p_d; 409 if (pid != p->rrdtool_pid) return HANDLER_GO_ON; 410 if (srv->pid != p->srv_pid) return HANDLER_GO_ON; 411 412 p->rrdtool_running = 0; 413 p->rrdtool_pid = -1; 414 415 UNUSED(status); 416 return HANDLER_FINISHED; 417 } 418 419 REQUESTDONE_FUNC(mod_rrd_account) { 420 plugin_data *p = p_d; 421 /*(0 == p->rrdtool_pid if never activated; not used)*/ 422 if (0 == p->rrdtool_pid) return HANDLER_GO_ON; 423 424 mod_rrd_patch_config(r, p); 425 rrd_config * const rrd = p->conf.rrd; 426 if (NULL != rrd) { 427 ++rrd->requests; 428 rrd->bytes_written += http_request_stats_bytes_out(r); 429 rrd->bytes_read += http_request_stats_bytes_in(r); 430 } 431 return HANDLER_GO_ON; 432 } 433 434 int mod_rrdtool_plugin_init(plugin *p); 435 int mod_rrdtool_plugin_init(plugin *p) { 436 p->version = LIGHTTPD_VERSION_ID; 437 p->name = "rrd"; 438 439 p->init = mod_rrd_init; 440 p->cleanup = mod_rrd_free; 441 p->set_defaults= mod_rrd_set_defaults; 442 443 p->handle_trigger = mod_rrd_trigger; 444 p->handle_waitpid = mod_rrd_waitpid_cb; 445 p->handle_request_done = mod_rrd_account; 446 447 return 0; 448 } 449