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 ck_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 = ck_calloc(1, sizeof(rrd_config)); 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_char(cmd, ':'); 339 buffer_append_int(cmd, s->bytes_written); 340 buffer_append_char(cmd, ':'); 341 buffer_append_int(cmd, s->requests); 342 buffer_append_char(cmd, '\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) { 426 ++rrd->requests; 427 rrd->bytes_written += http_request_stats_bytes_out(r); 428 rrd->bytes_read += http_request_stats_bytes_in(r); 429 } 430 return HANDLER_GO_ON; 431 } 432 433 434 __attribute_cold__ 435 int mod_rrdtool_plugin_init(plugin *p); 436 int mod_rrdtool_plugin_init(plugin *p) { 437 p->version = LIGHTTPD_VERSION_ID; 438 p->name = "rrd"; 439 440 p->init = mod_rrd_init; 441 p->cleanup = mod_rrd_free; 442 p->set_defaults= mod_rrd_set_defaults; 443 444 p->handle_trigger = mod_rrd_trigger; 445 p->handle_waitpid = mod_rrd_waitpid_cb; 446 p->handle_request_done = mod_rrd_account; 447 448 return 0; 449 } 450