xref: /lighttpd1.4/src/mod_rrdtool.c (revision 66698469)
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