xref: /lighttpd1.4/src/mod_rrdtool.c (revision 49e9f0ac)
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/wait.h>
11 
12 #include <fcntl.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <unistd.h>
16 #include <errno.h>
17 #include <time.h>
18 
19 typedef struct {
20 	buffer *path_rrdtool_bin;
21 	buffer *path_rrd;
22 
23 	double requests, *requests_ptr;
24 	double bytes_written, *bytes_written_ptr;
25 	double bytes_read, *bytes_read_ptr;
26 } plugin_config;
27 
28 typedef struct {
29 	PLUGIN_DATA;
30 
31 	buffer *cmd;
32 	buffer *resp;
33 
34 	int read_fd, write_fd;
35 	pid_t rrdtool_pid;
36 	pid_t srv_pid;
37 
38 	int rrdtool_running;
39 	time_t rrdtool_startup_ts;
40 
41 	plugin_config **config_storage;
42 	plugin_config conf;
43 } plugin_data;
44 
45 INIT_FUNC(mod_rrd_init) {
46 	plugin_data *p;
47 
48 	p = calloc(1, sizeof(*p));
49 
50 	p->resp = buffer_init();
51 	p->cmd = buffer_init();
52 
53 	return p;
54 }
55 
56 FREE_FUNC(mod_rrd_free) {
57 	plugin_data *p = p_d;
58 	size_t i;
59 
60 	if (!p) return HANDLER_GO_ON;
61 
62 	if (p->config_storage) {
63 		for (i = 0; i < srv->config_context->used; i++) {
64 			plugin_config *s = p->config_storage[i];
65 
66 			if (NULL == s) continue;
67 
68 			buffer_free(s->path_rrdtool_bin);
69 			buffer_free(s->path_rrd);
70 
71 			free(s);
72 		}
73 	}
74 	buffer_free(p->cmd);
75 	buffer_free(p->resp);
76 
77 	free(p->config_storage);
78 
79 	if (p->read_fd >= 0) close(p->read_fd);
80 	if (p->write_fd >= 0) close(p->write_fd);
81 
82 	if (p->rrdtool_pid > 0 && p->srv_pid == srv->pid) {
83 		/* collect status */
84 		while (-1 == waitpid(p->rrdtool_pid, NULL, 0) && errno == EINTR) ;
85 	}
86 
87 	free(p);
88 
89 	return HANDLER_GO_ON;
90 }
91 
92 static int mod_rrd_create_pipe(server *srv, plugin_data *p) {
93 	char *args[3];
94 	int to_rrdtool_fds[2];
95 	int from_rrdtool_fds[2];
96 	/* mod_rrdtool does not work with server.max-workers > 0
97 	 * since the data between workers is not aggregated,
98 	 * and it is not valid to send data to rrdtool more than once a sec
99 	 * (which would happen with multiple workers writing to same pipe)
100 	 * If pipes were to be shared, then existing pipes would need to be
101 	 * reused here, if they already exist (not -1), and after flushing
102 	 * existing contents (read and discard from read-end of pipes). */
103 	if (pipe(to_rrdtool_fds)) {
104 		log_error_write(srv, __FILE__, __LINE__, "ss",
105 				"pipe failed: ", strerror(errno));
106 		return -1;
107 	}
108 	if (pipe(from_rrdtool_fds)) {
109 		log_error_write(srv, __FILE__, __LINE__, "ss",
110 				"pipe failed: ", strerror(errno));
111 		return -1;
112 	}
113 	fdevent_setfd_cloexec(to_rrdtool_fds[1]);
114 	fdevent_setfd_cloexec(from_rrdtool_fds[0]);
115 	*(const char **)&args[0] = p->conf.path_rrdtool_bin->ptr;
116 	*(const char **)&args[1] = "-";
117 	args[2] = NULL;
118 
119 	p->rrdtool_pid = fdevent_fork_execve(args[0], args, NULL, to_rrdtool_fds[0], from_rrdtool_fds[1], -1, -1);
120 
121 	if (-1 != p->rrdtool_pid) {
122 		close(from_rrdtool_fds[1]);
123 		close(to_rrdtool_fds[0]);
124 		if (p->read_fd >= 0) close(p->read_fd);
125 		if (p->write_fd >= 0) close(p->write_fd);
126 		p->write_fd = to_rrdtool_fds[1];
127 		p->read_fd = from_rrdtool_fds[0];
128 		p->srv_pid = srv->pid;
129 		return 0;
130 	} else {
131 		log_error_write(srv, __FILE__, __LINE__, "SBss", "fork/exec(", p->conf.path_rrdtool_bin, "):", strerror(errno));
132 		close(to_rrdtool_fds[0]);
133 		close(to_rrdtool_fds[1]);
134 		close(from_rrdtool_fds[0]);
135 		close(from_rrdtool_fds[1]);
136 		return -1;
137 	}
138 }
139 
140 /* read/write wrappers to catch EINTR */
141 
142 /* write to blocking socket; blocks until all data is sent, write returns 0 or an error (apart from EINTR) occurs. */
143 static ssize_t safe_write(int fd, const void *buf, size_t count) {
144 	ssize_t res, sum = 0;
145 
146 	for (;;) {
147 		res = write(fd, buf, count);
148 		if (res >= 0) {
149 			sum += res;
150 			/* do not try again if res == 0 */
151 			if (res == 0 || (size_t) res == count) return sum;
152 			count -= res;
153 			buf = (const char*) buf + res;
154 			continue;
155 		}
156 		switch (errno) {
157 		case EINTR:
158 			continue;
159 		default:
160 			return -1;
161 		}
162 	}
163 }
164 
165 /* this assumes we get enough data on a successful read */
166 static ssize_t safe_read(int fd, buffer *b) {
167 	ssize_t res;
168 
169 	buffer_string_prepare_copy(b, 4095);
170 
171 	do {
172 		res = read(fd, b->ptr, b->size-1);
173 	} while (-1 == res && errno == EINTR);
174 
175 	if (res >= 0) buffer_commit(b, res);
176 	return res;
177 }
178 
179 static int mod_rrdtool_create_rrd(server *srv, plugin_data *p, plugin_config *s) {
180 	struct stat st;
181 
182 	/* check if DB already exists */
183 	if (0 == stat(s->path_rrd->ptr, &st)) {
184 		/* check if it is plain file */
185 		if (!S_ISREG(st.st_mode)) {
186 			log_error_write(srv, __FILE__, __LINE__, "sb",
187 					"not a regular file:", s->path_rrd);
188 			return HANDLER_ERROR;
189 		}
190 
191 		/* still create DB if it's empty file */
192 		if (st.st_size > 0) {
193 			return HANDLER_GO_ON;
194 		}
195 	}
196 
197 	/* create a new one */
198 	buffer_copy_string_len(p->cmd, CONST_STR_LEN("create "));
199 	buffer_append_string_buffer(p->cmd, s->path_rrd);
200 	buffer_append_string_len(p->cmd, CONST_STR_LEN(
201 		" --step 60 "
202 		"DS:InOctets:ABSOLUTE:600:U:U "
203 		"DS:OutOctets:ABSOLUTE:600:U:U "
204 		"DS:Requests:ABSOLUTE:600:U:U "
205 		"RRA:AVERAGE:0.5:1:600 "
206 		"RRA:AVERAGE:0.5:6:700 "
207 		"RRA:AVERAGE:0.5:24:775 "
208 		"RRA:AVERAGE:0.5:288:797 "
209 		"RRA:MAX:0.5:1:600 "
210 		"RRA:MAX:0.5:6:700 "
211 		"RRA:MAX:0.5:24:775 "
212 		"RRA:MAX:0.5:288:797 "
213 		"RRA:MIN:0.5:1:600 "
214 		"RRA:MIN:0.5:6:700 "
215 		"RRA:MIN:0.5:24:775 "
216 		"RRA:MIN:0.5:288:797\n"));
217 
218 	if (-1 == (safe_write(p->write_fd, CONST_BUF_LEN(p->cmd)))) {
219 		log_error_write(srv, __FILE__, __LINE__, "ss",
220 			"rrdtool-write: failed", strerror(errno));
221 
222 		return HANDLER_ERROR;
223 	}
224 
225 	if (-1 == safe_read(p->read_fd, p->resp)) {
226 		log_error_write(srv, __FILE__, __LINE__, "ss",
227 			"rrdtool-read: failed", strerror(errno));
228 
229 		return HANDLER_ERROR;
230 	}
231 
232 	if (p->resp->ptr[0] != 'O' ||
233 		p->resp->ptr[1] != 'K') {
234 		log_error_write(srv, __FILE__, __LINE__, "sbb",
235 			"rrdtool-response:", p->cmd, p->resp);
236 
237 		return HANDLER_ERROR;
238 	}
239 
240 	return HANDLER_GO_ON;
241 }
242 
243 #define PATCH(x) \
244 	p->conf.x = s->x;
245 static int mod_rrd_patch_connection(server *srv, connection *con, plugin_data *p) {
246 	size_t i, j;
247 	plugin_config *s = p->config_storage[0];
248 
249 	PATCH(path_rrdtool_bin);
250 	PATCH(path_rrd);
251 
252 	p->conf.bytes_written_ptr = &(s->bytes_written);
253 	p->conf.bytes_read_ptr = &(s->bytes_read);
254 	p->conf.requests_ptr = &(s->requests);
255 
256 	/* skip the first, the global context */
257 	for (i = 1; i < srv->config_context->used; i++) {
258 		data_config *dc = (data_config *)srv->config_context->data[i];
259 		s = p->config_storage[i];
260 
261 		/* condition didn't match */
262 		if (!config_check_cond(srv, con, dc)) continue;
263 
264 		/* merge config */
265 		for (j = 0; j < dc->value->used; j++) {
266 			data_unset *du = dc->value->data[j];
267 
268 			if (buffer_is_equal_string(du->key, CONST_STR_LEN("rrdtool.db-name"))) {
269 				PATCH(path_rrd);
270 				/* get pointers to double values */
271 
272 				p->conf.bytes_written_ptr = &(s->bytes_written);
273 				p->conf.bytes_read_ptr = &(s->bytes_read);
274 				p->conf.requests_ptr = &(s->requests);
275 			}
276 		}
277 	}
278 
279 	return 0;
280 }
281 #undef PATCH
282 
283 static int mod_rrd_exec(server *srv, plugin_data *p) {
284     if (mod_rrd_create_pipe(srv, p)) {
285         return -1;
286     }
287 
288     p->rrdtool_running = 1;
289     p->rrdtool_startup_ts = srv->cur_ts;
290     return 0;
291 }
292 
293 SETDEFAULTS_FUNC(mod_rrd_set_defaults) {
294 	plugin_data *p = p_d;
295 	size_t i;
296 	int activate = 0;
297 
298 	config_values_t cv[] = {
299 		{ "rrdtool.binary",  NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
300 		{ "rrdtool.db-name", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
301 		{ NULL,              NULL, T_CONFIG_UNSET,  T_CONFIG_SCOPE_UNSET      }
302 	};
303 
304 	if (!p) return HANDLER_ERROR;
305 
306 	force_assert(srv->config_context->used > 0);
307 	p->config_storage = calloc(srv->config_context->used, sizeof(plugin_config *));
308 
309 	for (i = 0; i < srv->config_context->used; i++) {
310 		data_config const* config = (data_config const*)srv->config_context->data[i];
311 		plugin_config *s;
312 
313 		s = calloc(1, sizeof(plugin_config));
314 		s->path_rrdtool_bin = buffer_init();
315 		s->path_rrd = buffer_init();
316 		s->requests = 0;
317 		s->bytes_written = 0;
318 		s->bytes_read = 0;
319 
320 		cv[0].destination = s->path_rrdtool_bin;
321 		cv[1].destination = s->path_rrd;
322 
323 		p->config_storage[i] = s;
324 
325 		if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
326 			return HANDLER_ERROR;
327 		}
328 
329 		if (i > 0 && !buffer_string_is_empty(s->path_rrdtool_bin)) {
330 			/* path_rrdtool_bin is a global option */
331 
332 			log_error_write(srv, __FILE__, __LINE__, "s",
333 					"rrdtool.binary can only be set as a global option.");
334 
335 			return HANDLER_ERROR;
336 		}
337 
338 		if (!buffer_string_is_empty(s->path_rrd)) activate = 1;
339 	}
340 
341 	p->conf.path_rrdtool_bin = p->config_storage[0]->path_rrdtool_bin;
342 	p->rrdtool_running = 0;
343 	p->read_fd  = -1;
344 	p->write_fd = -1;
345 
346 	if (!activate) return HANDLER_GO_ON;
347 
348 	/* check for dir */
349 
350 	if (buffer_string_is_empty(p->conf.path_rrdtool_bin)) {
351 		log_error_write(srv, __FILE__, __LINE__, "s",
352 				"rrdtool.binary has to be set");
353 		return HANDLER_ERROR;
354 	}
355 
356 	return 0 == mod_rrd_exec(srv, p) ? HANDLER_GO_ON : HANDLER_ERROR;
357 }
358 
359 static void mod_rrd_fatal_error(server *srv, plugin_data *p) {
360     /* future: might send kill() signal to p->rrdtool_pid to trigger restart */
361     p->rrdtool_running = 0;
362     UNUSED(srv);
363 }
364 
365 TRIGGER_FUNC(mod_rrd_trigger) {
366 	plugin_data *p = p_d;
367 	size_t i;
368 
369 	if (!p->rrdtool_running) {
370 		/* limit restart to once every 5 sec */
371 		/*(0 == p->rrdtool_pid if never activated; not used)*/
372 		if (-1 == p->rrdtool_pid
373 		    && p->srv_pid == srv->pid
374 		    && p->rrdtool_startup_ts + 5 < srv->cur_ts) {
375 			mod_rrd_exec(srv, p);
376 		}
377 		return HANDLER_GO_ON;
378 	}
379 
380 	if ((srv->cur_ts % 60) != 0) return HANDLER_GO_ON;
381 
382 	for (i = 0; i < srv->config_context->used; i++) {
383 		plugin_config *s = p->config_storage[i];
384 
385 		if (buffer_string_is_empty(s->path_rrd)) continue;
386 
387 		/* write the data down every minute */
388 
389 		if (HANDLER_GO_ON != mod_rrdtool_create_rrd(srv, p, s)) return HANDLER_GO_ON;
390 
391 		buffer_copy_string_len(p->cmd, CONST_STR_LEN("update "));
392 		buffer_append_string_buffer(p->cmd, s->path_rrd);
393 		buffer_append_string_len(p->cmd, CONST_STR_LEN(" N:"));
394 		buffer_append_int(p->cmd, s->bytes_read);
395 		buffer_append_string_len(p->cmd, CONST_STR_LEN(":"));
396 		buffer_append_int(p->cmd, s->bytes_written);
397 		buffer_append_string_len(p->cmd, CONST_STR_LEN(":"));
398 		buffer_append_int(p->cmd, s->requests);
399 		buffer_append_string_len(p->cmd, CONST_STR_LEN("\n"));
400 
401 		if (-1 == safe_write(p->write_fd, CONST_BUF_LEN(p->cmd))) {
402 			log_error_write(srv, __FILE__, __LINE__, "ss",
403 					"rrdtool-write: failed", strerror(errno));
404 
405 			mod_rrd_fatal_error(srv, p);
406 			return HANDLER_GO_ON;
407 		}
408 
409 		if (-1 == safe_read(p->read_fd, p->resp)) {
410 			log_error_write(srv, __FILE__, __LINE__, "ss",
411 					"rrdtool-read: failed", strerror(errno));
412 
413 			mod_rrd_fatal_error(srv, p);
414 			return HANDLER_GO_ON;
415 		}
416 
417 		if (p->resp->ptr[0] != 'O' ||
418 		    p->resp->ptr[1] != 'K') {
419 			/* don't fail on this error if we just started (graceful restart, the old one might have just updated too) */
420 			if (!(strstr(p->resp->ptr, "(minimum one second step)") && (srv->cur_ts - srv->startup_ts < 3))) {
421 				log_error_write(srv, __FILE__, __LINE__, "sbb",
422 					"rrdtool-response:", p->cmd, p->resp);
423 
424 				mod_rrd_fatal_error(srv, p);
425 				return HANDLER_GO_ON;
426 			}
427 		}
428 		s->requests = 0;
429 		s->bytes_written = 0;
430 		s->bytes_read = 0;
431 	}
432 
433 	return HANDLER_GO_ON;
434 }
435 
436 static handler_t mod_rrd_waitpid_cb(server *srv, void *p_d, pid_t pid, int status) {
437 	plugin_data *p = p_d;
438 	if (pid != p->rrdtool_pid) return HANDLER_GO_ON;
439 	if (srv->pid != p->srv_pid) return HANDLER_GO_ON;
440 
441 	p->rrdtool_running = 0;
442 	p->rrdtool_pid = -1;
443 
444 	/* limit restart to once every 5 sec */
445 	if (p->rrdtool_startup_ts + 5 < srv->cur_ts)
446 		mod_rrd_exec(srv, p);
447 
448 	UNUSED(status);
449 	return HANDLER_FINISHED;
450 }
451 
452 REQUESTDONE_FUNC(mod_rrd_account) {
453 	plugin_data *p = p_d;
454 
455 	/*(0 == p->rrdtool_pid if never activated; not used)*/
456 	if (0 == p->rrdtool_pid) return HANDLER_GO_ON;
457 	mod_rrd_patch_connection(srv, con, p);
458 
459 	*(p->conf.requests_ptr)      += 1;
460 	*(p->conf.bytes_written_ptr) += con->bytes_written;
461 	*(p->conf.bytes_read_ptr)    += con->bytes_read;
462 
463 	return HANDLER_GO_ON;
464 }
465 
466 int mod_rrdtool_plugin_init(plugin *p);
467 int mod_rrdtool_plugin_init(plugin *p) {
468 	p->version     = LIGHTTPD_VERSION_ID;
469 	p->name        = buffer_init_string("rrd");
470 
471 	p->init        = mod_rrd_init;
472 	p->cleanup     = mod_rrd_free;
473 	p->set_defaults= mod_rrd_set_defaults;
474 
475 	p->handle_trigger      = mod_rrd_trigger;
476 	p->handle_waitpid      = mod_rrd_waitpid_cb;
477 	p->handle_request_done = mod_rrd_account;
478 
479 	p->data        = NULL;
480 
481 	return 0;
482 }
483