Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

First commit.

  • Loading branch information...
commit f1a155e200dea4b149f88bb56a8d23116ef5b541 0 parents
Evan Miller authored
Showing with 360 additions and 0 deletions.
  1. +45 −0 README
  2. +20 −0 config
  3. +295 −0 ngx_http_rrd_graph_module.c
45 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/
20 config
@@ -0,0 +1,20 @@
+ngx_feature="RRDtool"
+ngx_feature_name=
+ngx_feature_run=no
+ngx_feature_incs="#include <rrd.h>"
+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
295 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 <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <rrd.h>
+
+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<argc; i++) {
+ if (ngx_strncmp(argv[i], "DEF:", sizeof("DEF:") - 1) == 0) {
+ p = argv[i] + sizeof("DEF:") - 1;
+ if ((p = ngx_strchr(p, '=')) != NULL) {
+ p++;
+ tmp = ngx_pcalloc(r->pool, argv_len[i] + conf->root.len + 1);
+ ngx_memcpy(tmp, argv[i], p - argv[i]); /* prefix */
+ ngx_memcpy(tmp + (p - argv[i]), conf->root.data, conf->root.len); /* root dir */
+ ngx_memcpy(tmp + (p - argv[i]) + conf->root.len, /* suffix */
+ p, argv_len[i] - (p - argv[i]));
+ argv[i] = tmp;
+ }
+ }
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "mod_rrd_graph: parsed arg %s", argv[i]);
+ }
+ *argc_ptr = argc;
+ *argv_ptr = argv;
+ *argv_len_ptr = argv_len;
+ return NGX_OK;
+}
+
+static ngx_int_t
+ngx_http_rrd_graph_send_image(ngx_http_request_t *r, u_char *image, size_t image_size)
+{
+ int rc;
+ ngx_chain_t out;
+
+ out.next = NULL;
+ if ((out.buf = ngx_pcalloc(r->pool, sizeof(ngx_buf_t))) == NULL) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "mod_rrd_graph: Failed to allocate response buffer.");
+ free(image);
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (image_size > 4) {
+ if (ngx_strncmp(image, ngx_http_png_header,
+ sizeof(ngx_http_png_header)) == 0) {
+ r->headers_out.content_type.len = sizeof("image/png") - 1;
+ r->headers_out.content_type.data = (u_char *)"image/png";
+ } else if (ngx_strncmp(image, ngx_http_pdf_header,
+ sizeof(ngx_http_pdf_header)) == 0) {
+ r->headers_out.content_type.len = sizeof("application/pdf") - 1;
+ r->headers_out.content_type.data = (u_char *)"application/pdf";
+ } else if (ngx_strncmp(image, ngx_http_eps_header,
+ sizeof(ngx_http_eps_header)) == 0) {
+ r->headers_out.content_type.len = sizeof("application/postscript") - 1;
+ r->headers_out.content_type.data = (u_char *)"application/postscript";
+ } else if (ngx_strncmp(image, ngx_http_svg_header,
+ sizeof(ngx_http_svg_header)) == 0) {
+ r->headers_out.content_type.len = sizeof("image/svg+xml") - 1;
+ r->headers_out.content_type.data = (u_char *)"image/svg+xml";
+ }
+ }
+
+ r->headers_out.status = NGX_HTTP_OK;
+ r->headers_out.content_length_n = image_size;
+
+ rc = ngx_http_send_header(r);
+
+ if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
+ free(image);
+ return rc;
+ }
+
+ out.buf->pos = image;
+ out.buf->last = image + image_size;
+
+ out.buf->memory = 1;
+ out.buf->last_buf = 1;
+
+ return ngx_http_output_filter(r, &out);
+}
+
+static void *
+ngx_http_rrd_graph_create_conf(ngx_conf_t *cf)
+{
+ ngx_http_rrd_graph_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_rrd_graph_conf_t));
+ if (conf == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ return conf;
+}
+
+static char *
+ngx_http_rrd_graph_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_rrd_graph_conf_t *prev = parent;
+ ngx_http_rrd_graph_conf_t *conf = child;
+
+ ngx_conf_merge_str_value(conf->root, prev->root, "");
+
+ return NGX_CONF_OK;
+}
Please sign in to comment.
Something went wrong with that request. Please try again.