diff --git a/README b/README new file mode 100644 index 0000000..a123ee7 --- /dev/null +++ b/README @@ -0,0 +1,45 @@ +mod_rrd_graph +============= + +RRDtool[1] stores and graphs time-series data. ngx_rrd_graph provides an HTTP +interface to RRDtool's graphing facilities. By linking RRDtool code directly +into Nginx, it is faster than scripts and CGIs with similar purposes. + +To install, compile nginx with this option: + + --add-module=/path/to/this/directory + +ngx_rrd_graph requires RRDtool 1.3 or later. + +After compiling, installing, and restarting Nginx, ngx_rrd_graph can be enabled +at a particular location with the "rrd_graph" directive, like so: + + location /rrdtool { + rrd_graph; + } + +RRDtool graphing commands can then be appended to that location in request +URLs. The syntax is just the same as the arguments to the "rrdtool graph" +command, omitting the filename. (Refer to rrdgraph(1).) These commands should +be URL-encoded, so that this command-line invocation: + + rrdtool graph --start now-300s \ + --end now \ + DEF:ds0=test.rrd:reading:AVERAGE \ + LINE1:ds0#00FF00 + +becomes: + + http://mysite.com/rrdtool--start%20now-300s%20--end%20now%20DEF%3Ads0%3Dtest.rrd%3Areading%3AAVERAGE%20LINE1%3Ads0%2300FF00 + +The module supports all the features of your copy of RRDtool. It can output +PNG, PDF, SVG, and EPS graphics (see the --imgformat option of rrdgraph(1)). + +If you'd prefer not to provide absolute paths to files referenced in DEF commands, +you may supply a root directory with the "rrd_graph_root" directive. Files mentioned +in DEF commands will be automatically prefixed with the value of rrd_graph_root. +As of this writing there are no guards against relative paths (e.g. ".."). + +Questions/comments to emmiller@gmail.com. + +[1] http://oss.oetiker.ch/rrdtool/ diff --git a/config b/config new file mode 100644 index 0000000..73b8204 --- /dev/null +++ b/config @@ -0,0 +1,20 @@ +ngx_feature="RRDtool" +ngx_feature_name= +ngx_feature_run=no +ngx_feature_incs="#include " +ngx_feature_path= +ngx_feature_libs=-lrrd +ngx_feature_test="rrd_graph_v;" +. auto/feature + +if [ $ngx_found = yes ]; then + ngx_addon_name=ngx_http_rrd_graph_module + HTTP_MODULES="$HTTP_MODULES ngx_http_rrd_graph_module" + NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_rrd_graph_module.c" + CORE_LIBS="$CORE_LIBS -lrrd" +else + cat << END +$0: error: ngx_rrd_graph requires RRDtool 1.3 or later. +END + exit 1 +fi diff --git a/ngx_http_rrd_graph_module.c b/ngx_http_rrd_graph_module.c new file mode 100644 index 0000000..ad305e3 --- /dev/null +++ b/ngx_http_rrd_graph_module.c @@ -0,0 +1,295 @@ +/* + * mod_rrd_graph - Graph data in a Round Robin Database (RRD). + * + * Copyright (C) Evan Miller + */ + +#include +#include +#include +#include + +typedef struct { + ngx_str_t root; +} ngx_http_rrd_graph_conf_t; + +static void * ngx_http_rrd_graph_create_conf(ngx_conf_t *cf); +static char * ngx_http_rrd_graph_merge_conf(ngx_conf_t *cf, void *parent, void *child); +static char* ngx_http_rrd_graph(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static ngx_int_t ngx_http_rrd_graph_handler(ngx_http_request_t *r); +static ngx_int_t ngx_http_rrd_graph_send_image(ngx_http_request_t *r, u_char *image, size_t image_size); +static ngx_int_t ngx_http_rrd_graph_parse_uri(ngx_http_request_t *r, int *argc_ptr, + char ***argv_ptr, size_t **argv_len_ptr); + +static u_char ngx_http_png_header[] = { '\x89', 'P', 'N', 'G' }; +static u_char ngx_http_pdf_header[] = { '%', 'P', 'D', 'F' }; +static u_char ngx_http_svg_header[] = { '<', '?', 'x', 'm', 'l' }; +static u_char ngx_http_eps_header[] = { '%', '!', 'P', 'S' }; + +static ngx_command_t ngx_http_rrd_graph_commands[] = { + { ngx_string("rrd_graph"), + NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS, + ngx_http_rrd_graph, + 0, + 0, + NULL }, + +/* the root directory that will be prefixed to file names */ + { ngx_string("rrd_graph_root"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_rrd_graph_conf_t, root), + NULL }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_http_rrd_graph_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + ngx_http_rrd_graph_create_conf,/* create location configuration */ + ngx_http_rrd_graph_merge_conf /* merge location configuration */ +}; + + +ngx_module_t ngx_http_rrd_graph_module = { + NGX_MODULE_V1, + &ngx_http_rrd_graph_module_ctx, /* module context */ + ngx_http_rrd_graph_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_int_t +ngx_http_rrd_graph_handler(ngx_http_request_t *r) +{ + ngx_int_t rc; + /* image metadata */ + rrd_info_t *image_info = NULL, *walker; + u_char *image = NULL, *tmp; + size_t image_size = 0; + + int argc = 3; /* two dummies + at least one real */ + char **argv; + size_t *argv_len; + + if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) + return NGX_HTTP_NOT_ALLOWED; + + rc = ngx_http_discard_request_body(r); + if (rc != NGX_OK && rc != NGX_AGAIN) + return rc; + + if ((rc = ngx_http_rrd_graph_parse_uri(r, &argc, &argv, &argv_len)) != NGX_OK) + return rc; + + image_info = rrd_graph_v(argc, argv); + + if (rrd_test_error()) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "mod_rrd_graph: RRD graph failed: %s", rrd_get_error()); + rrd_clear_error(); + rrd_info_free(image_info); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + for (walker = image_info; walker; walker = walker->next) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "mod_rrd_graph: found key '%s'", walker->key); + if (ngx_strcmp(walker->key, "image") == 0) { + image = walker->value.u_blo.ptr; + image_size = walker->value.u_blo.size; + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "mod_rrd_graph: image size is %d bytes", image_size); + } + } + + if (image_size) { + if ((tmp = ngx_palloc(r->pool, image_size)) == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "mod_rrd_graph: Failed to allocate response buffer."); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + ngx_memcpy(tmp, image, image_size); + rrd_info_free(image_info); + return ngx_http_rrd_graph_send_image(r, tmp, image_size); + } + + rrd_info_free(image_info); + + return NGX_HTTP_NOT_FOUND; +} + +static char * +ngx_http_rrd_graph(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_core_loc_conf_t *clcf; + + clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); + clcf->handler = ngx_http_rrd_graph_handler; + + return NGX_CONF_OK; +} + +static ngx_int_t +ngx_http_rrd_graph_parse_uri(ngx_http_request_t *r, int *argc_ptr, + char ***argv_ptr, size_t **argv_len_ptr) +{ + int i, argc = 3; + char **argv; + size_t *argv_len; + char *tmp, *p; + u_char *uri_copy; + ngx_http_core_loc_conf_t *clcf; + ngx_http_rrd_graph_conf_t *conf; + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (r->uri.len == clcf->name.len) + return NGX_HTTP_NOT_FOUND; + + /* tokenize */ + if ((uri_copy = ngx_palloc(r->pool, (r->uri.len + 1)*sizeof(char))) == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "mod_rrd_graph: Failed to copy URI."); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + ngx_memcpy(uri_copy, r->uri.data, r->uri.len); + uri_copy[r->uri.len] = '\0'; /* RRDtool needs null-terminated strings */ + p = (char *)uri_copy + clcf->name.len; + + while(*p++) + if (*p == ' ') + argc++; + + argv = ngx_palloc(r->pool, argc*sizeof(char *)); + argv_len = ngx_pcalloc(r->pool, argc*sizeof(size_t)); + argv[0] = "mod_rrd_graph"; + argv[1] = "-"; + argv[2] = p = (char *)uri_copy + clcf->name.len; + argc = 3; + while (*p) { + if (*p == ' ') { + *p = '\0'; + argv[argc++] = p+1; + } else { + argv_len[argc-1]++; + } + p++; + } + + conf = ngx_http_get_module_loc_conf(r, ngx_http_rrd_graph_module); + /* splice in the RRD directory root */ + /* TODO guard against relative paths */ + for (i=2; i