19 buffer
*path_rrdtool_bin
;
22 double requests
, *requests_ptr
;
23 double bytes_written
, *bytes_written_ptr
;
24 double bytes_read
, *bytes_read_ptr
;
33 int read_fd
, write_fd
;
38 time_t rrdtool_startup_ts
;
40 plugin_config
**config_storage
;
44 INIT_FUNC(mod_rrd_init
) {
47 p
= calloc(1, sizeof(*p
));
49 p
->resp
= buffer_init();
50 p
->cmd
= buffer_init();
55 FREE_FUNC(mod_rrd_free
) {
59 if (!p
) return HANDLER_GO_ON
;
61 if (p
->config_storage
) {
62 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
63 plugin_config
*s
= p
->config_storage
[i
];
65 if (NULL
== s
) continue;
67 buffer_free(s
->path_rrdtool_bin
);
68 buffer_free(s
->path_rrd
);
76 free(p
->config_storage
);
78 if (p
->read_fd
>= 0) close(p
->read_fd
);
79 if (p
->write_fd
>= 0) close(p
->write_fd
);
81 if (p
->rrdtool_pid
> 0 && p
->srv_pid
== srv
->pid
) {
83 while (-1 == waitpid(p
->rrdtool_pid
, NULL
, 0) && errno
== EINTR
) ;
91 static int mod_rrd_create_pipe(server
*srv
, plugin_data
*p
) {
93 int to_rrdtool_fds
[2];
94 int from_rrdtool_fds
[2];
95 /* mod_rrdtool does not work with server.max-workers > 0
96 * since the data between workers is not aggregated,
97 * and it is not valid to send data to rrdtool more than once a sec
98 * (which would happen with multiple workers writing to same pipe)
99 * If pipes were to be shared, then existing pipes would need to be
100 * reused here, if they already exist (not -1), and after flushing
101 * existing contents (read and discard from read-end of pipes). */
102 if (pipe(to_rrdtool_fds
)) {
103 log_error_write(srv
, __FILE__
, __LINE__
, "ss",
104 "pipe failed: ", strerror(errno
));
107 if (pipe(from_rrdtool_fds
)) {
108 log_error_write(srv
, __FILE__
, __LINE__
, "ss",
109 "pipe failed: ", strerror(errno
));
112 fdevent_setfd_cloexec(to_rrdtool_fds
[1]);
113 fdevent_setfd_cloexec(from_rrdtool_fds
[0]);
114 *(const char **)&args
[0] = p
->conf
.path_rrdtool_bin
->ptr
;
115 *(const char **)&args
[1] = "-";
118 p
->rrdtool_pid
= fdevent_fork_execve(args
[0], args
, NULL
, to_rrdtool_fds
[0], from_rrdtool_fds
[1], -1, -1);
120 if (-1 != p
->rrdtool_pid
) {
121 close(from_rrdtool_fds
[1]);
122 close(to_rrdtool_fds
[0]);
123 if (p
->read_fd
>= 0) close(p
->read_fd
);
124 if (p
->write_fd
>= 0) close(p
->write_fd
);
125 p
->write_fd
= to_rrdtool_fds
[1];
126 p
->read_fd
= from_rrdtool_fds
[0];
127 p
->srv_pid
= srv
->pid
;
130 log_error_write(srv
, __FILE__
, __LINE__
, "SBss", "fork/exec(", p
->conf
.path_rrdtool_bin
, "):", strerror(errno
));
131 close(to_rrdtool_fds
[0]);
132 close(to_rrdtool_fds
[1]);
133 close(from_rrdtool_fds
[0]);
134 close(from_rrdtool_fds
[1]);
139 /* read/write wrappers to catch EINTR */
141 /* write to blocking socket; blocks until all data is sent, write returns 0 or an error (apart from EINTR) occurs. */
142 static ssize_t
safe_write(int fd
, const void *buf
, size_t count
) {
143 ssize_t res
, sum
= 0;
146 res
= write(fd
, buf
, count
);
149 /* do not try again if res == 0 */
150 if (res
== 0 || (size_t) res
== count
) return sum
;
152 buf
= (const char*) buf
+ res
;
164 /* this assumes we get enough data on a successful read */
165 static ssize_t
safe_read(int fd
, buffer
*b
) {
168 buffer_string_prepare_copy(b
, 4095);
171 res
= read(fd
, b
->ptr
, b
->size
-1);
172 } while (-1 == res
&& errno
== EINTR
);
174 if (res
>= 0) buffer_commit(b
, res
);
178 static int mod_rrdtool_create_rrd(server
*srv
, plugin_data
*p
, plugin_config
*s
) {
181 /* check if DB already exists */
182 if (0 == stat(s
->path_rrd
->ptr
, &st
)) {
183 /* check if it is plain file */
184 if (!S_ISREG(st
.st_mode
)) {
185 log_error_write(srv
, __FILE__
, __LINE__
, "sb",
186 "not a regular file:", s
->path_rrd
);
187 return HANDLER_ERROR
;
190 /* still create DB if it's empty file */
191 if (st
.st_size
> 0) {
192 return HANDLER_GO_ON
;
196 /* create a new one */
197 buffer_copy_string_len(p
->cmd
, CONST_STR_LEN("create "));
198 buffer_append_string_buffer(p
->cmd
, s
->path_rrd
);
199 buffer_append_string_len(p
->cmd
, CONST_STR_LEN(
201 "DS:InOctets:ABSOLUTE:600:U:U "
202 "DS:OutOctets:ABSOLUTE:600:U:U "
203 "DS:Requests:ABSOLUTE:600:U:U "
204 "RRA:AVERAGE:0.5:1:600 "
205 "RRA:AVERAGE:0.5:6:700 "
206 "RRA:AVERAGE:0.5:24:775 "
207 "RRA:AVERAGE:0.5:288:797 "
210 "RRA:MAX:0.5:24:775 "
211 "RRA:MAX:0.5:288:797 "
214 "RRA:MIN:0.5:24:775 "
215 "RRA:MIN:0.5:288:797\n"));
217 if (-1 == (safe_write(p
->write_fd
, CONST_BUF_LEN(p
->cmd
)))) {
218 log_error_write(srv
, __FILE__
, __LINE__
, "ss",
219 "rrdtool-write: failed", strerror(errno
));
221 return HANDLER_ERROR
;
224 if (-1 == safe_read(p
->read_fd
, p
->resp
)) {
225 log_error_write(srv
, __FILE__
, __LINE__
, "ss",
226 "rrdtool-read: failed", strerror(errno
));
228 return HANDLER_ERROR
;
231 if (p
->resp
->ptr
[0] != 'O' ||
232 p
->resp
->ptr
[1] != 'K') {
233 log_error_write(srv
, __FILE__
, __LINE__
, "sbb",
234 "rrdtool-response:", p
->cmd
, p
->resp
);
236 return HANDLER_ERROR
;
239 return HANDLER_GO_ON
;
244 static int mod_rrd_patch_connection(server
*srv
, connection
*con
, plugin_data
*p
) {
246 plugin_config
*s
= p
->config_storage
[0];
248 PATCH(path_rrdtool_bin
);
251 p
->conf
.bytes_written_ptr
= &(s
->bytes_written
);
252 p
->conf
.bytes_read_ptr
= &(s
->bytes_read
);
253 p
->conf
.requests_ptr
= &(s
->requests
);
255 /* skip the first, the global context */
256 for (i
= 1; i
< srv
->config_context
->used
; i
++) {
257 data_config
*dc
= (data_config
*)srv
->config_context
->data
[i
];
258 s
= p
->config_storage
[i
];
260 /* condition didn't match */
261 if (!config_check_cond(srv
, con
, dc
)) continue;
264 for (j
= 0; j
< dc
->value
->used
; j
++) {
265 data_unset
*du
= dc
->value
->data
[j
];
267 if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("rrdtool.db-name"))) {
269 /* get pointers to double values */
271 p
->conf
.bytes_written_ptr
= &(s
->bytes_written
);
272 p
->conf
.bytes_read_ptr
= &(s
->bytes_read
);
273 p
->conf
.requests_ptr
= &(s
->requests
);
282 static int mod_rrd_exec(server
*srv
, plugin_data
*p
) {
283 if (mod_rrd_create_pipe(srv
, p
)) {
287 p
->rrdtool_running
= 1;
288 p
->rrdtool_startup_ts
= srv
->cur_ts
;
292 SETDEFAULTS_FUNC(mod_rrd_set_defaults
) {
293 plugin_data
*p
= p_d
;
297 config_values_t cv
[] = {
298 { "rrdtool.binary", NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 0 */
299 { "rrdtool.db-name", NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 1 */
300 { NULL
, NULL
, T_CONFIG_UNSET
, T_CONFIG_SCOPE_UNSET
}
303 if (!p
) return HANDLER_ERROR
;
305 force_assert(srv
->config_context
->used
> 0);
306 p
->config_storage
= calloc(1, srv
->config_context
->used
* sizeof(plugin_config
*));
308 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
309 data_config
const* config
= (data_config
const*)srv
->config_context
->data
[i
];
312 s
= calloc(1, sizeof(plugin_config
));
313 s
->path_rrdtool_bin
= buffer_init();
314 s
->path_rrd
= buffer_init();
316 s
->bytes_written
= 0;
319 cv
[0].destination
= s
->path_rrdtool_bin
;
320 cv
[1].destination
= s
->path_rrd
;
322 p
->config_storage
[i
] = s
;
324 if (0 != config_insert_values_global(srv
, config
->value
, cv
, i
== 0 ? T_CONFIG_SCOPE_SERVER
: T_CONFIG_SCOPE_CONNECTION
)) {
325 return HANDLER_ERROR
;
328 if (i
> 0 && !buffer_string_is_empty(s
->path_rrdtool_bin
)) {
329 /* path_rrdtool_bin is a global option */
331 log_error_write(srv
, __FILE__
, __LINE__
, "s",
332 "rrdtool.binary can only be set as a global option.");
334 return HANDLER_ERROR
;
337 if (!buffer_string_is_empty(s
->path_rrd
)) activate
= 1;
340 p
->conf
.path_rrdtool_bin
= p
->config_storage
[0]->path_rrdtool_bin
;
341 p
->rrdtool_running
= 0;
345 if (!activate
) return HANDLER_GO_ON
;
349 if (buffer_string_is_empty(p
->conf
.path_rrdtool_bin
)) {
350 log_error_write(srv
, __FILE__
, __LINE__
, "s",
351 "rrdtool.binary has to be set");
352 return HANDLER_ERROR
;
355 return 0 == mod_rrd_exec(srv
, p
) ? HANDLER_GO_ON
: HANDLER_ERROR
;
358 static void mod_rrd_fatal_error(server
*srv
, plugin_data
*p
) {
359 /* future: might send kill() signal to p->rrdtool_pid to trigger restart */
360 p
->rrdtool_running
= 0;
364 TRIGGER_FUNC(mod_rrd_trigger
) {
365 plugin_data
*p
= p_d
;
368 if (!p
->rrdtool_running
) {
369 /* limit restart to once every 5 sec */
370 /*(0 == p->rrdtool_pid if never activated; not used)*/
371 if (-1 == p
->rrdtool_pid
372 && p
->srv_pid
== srv
->pid
373 && p
->rrdtool_startup_ts
+ 5 < srv
->cur_ts
) {
374 mod_rrd_exec(srv
, p
);
376 return HANDLER_GO_ON
;
379 if ((srv
->cur_ts
% 60) != 0) return HANDLER_GO_ON
;
381 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
382 plugin_config
*s
= p
->config_storage
[i
];
384 if (buffer_string_is_empty(s
->path_rrd
)) continue;
386 /* write the data down every minute */
388 if (HANDLER_GO_ON
!= mod_rrdtool_create_rrd(srv
, p
, s
)) return HANDLER_GO_ON
;
390 buffer_copy_string_len(p
->cmd
, CONST_STR_LEN("update "));
391 buffer_append_string_buffer(p
->cmd
, s
->path_rrd
);
392 buffer_append_string_len(p
->cmd
, CONST_STR_LEN(" N:"));
393 buffer_append_int(p
->cmd
, s
->bytes_read
);
394 buffer_append_string_len(p
->cmd
, CONST_STR_LEN(":"));
395 buffer_append_int(p
->cmd
, s
->bytes_written
);
396 buffer_append_string_len(p
->cmd
, CONST_STR_LEN(":"));
397 buffer_append_int(p
->cmd
, s
->requests
);
398 buffer_append_string_len(p
->cmd
, CONST_STR_LEN("\n"));
400 if (-1 == safe_write(p
->write_fd
, CONST_BUF_LEN(p
->cmd
))) {
401 log_error_write(srv
, __FILE__
, __LINE__
, "ss",
402 "rrdtool-write: failed", strerror(errno
));
404 mod_rrd_fatal_error(srv
, p
);
405 return HANDLER_GO_ON
;
408 if (-1 == safe_read(p
->read_fd
, p
->resp
)) {
409 log_error_write(srv
, __FILE__
, __LINE__
, "ss",
410 "rrdtool-read: failed", strerror(errno
));
412 mod_rrd_fatal_error(srv
, p
);
413 return HANDLER_GO_ON
;
416 if (p
->resp
->ptr
[0] != 'O' ||
417 p
->resp
->ptr
[1] != 'K') {
418 /* don't fail on this error if we just started (graceful restart, the old one might have just updated too) */
419 if (!(strstr(p
->resp
->ptr
, "(minimum one second step)") && (srv
->cur_ts
- srv
->startup_ts
< 3))) {
420 log_error_write(srv
, __FILE__
, __LINE__
, "sbb",
421 "rrdtool-response:", p
->cmd
, p
->resp
);
423 mod_rrd_fatal_error(srv
, p
);
424 return HANDLER_GO_ON
;
428 s
->bytes_written
= 0;
432 return HANDLER_GO_ON
;
435 static handler_t
mod_rrd_waitpid_cb(server
*srv
, void *p_d
, pid_t pid
, int status
) {
436 plugin_data
*p
= p_d
;
437 if (pid
!= p
->rrdtool_pid
) return HANDLER_GO_ON
;
438 if (srv
->pid
!= p
->srv_pid
) return HANDLER_GO_ON
;
440 p
->rrdtool_running
= 0;
443 /* limit restart to once every 5 sec */
444 if (p
->rrdtool_startup_ts
+ 5 < srv
->cur_ts
)
445 mod_rrd_exec(srv
, p
);
448 return HANDLER_FINISHED
;
451 REQUESTDONE_FUNC(mod_rrd_account
) {
452 plugin_data
*p
= p_d
;
454 /*(0 == p->rrdtool_pid if never activated; not used)*/
455 if (0 == p
->rrdtool_pid
) return HANDLER_GO_ON
;
456 mod_rrd_patch_connection(srv
, con
, p
);
458 *(p
->conf
.requests_ptr
) += 1;
459 *(p
->conf
.bytes_written_ptr
) += con
->bytes_written
;
460 *(p
->conf
.bytes_read_ptr
) += con
->bytes_read
;
462 return HANDLER_GO_ON
;
465 int mod_rrdtool_plugin_init(plugin
*p
);
466 int mod_rrdtool_plugin_init(plugin
*p
) {
467 p
->version
= LIGHTTPD_VERSION_ID
;
468 p
->name
= buffer_init_string("rrd");
470 p
->init
= mod_rrd_init
;
471 p
->cleanup
= mod_rrd_free
;
472 p
->set_defaults
= mod_rrd_set_defaults
;
474 p
->handle_trigger
= mod_rrd_trigger
;
475 p
->handle_waitpid
= mod_rrd_waitpid_cb
;
476 p
->handle_request_done
= mod_rrd_account
;