This repository has been archived by the owner on Dec 17, 2022. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Evan Miller
committed
Oct 19, 2009
0 parents
commit f1a155e
Showing
3 changed files
with
360 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -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/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -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; | |||
} |