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