[PATCH 5 of 7] * Add initioal OO style data fetching support
Peter Stamfest <[email protected]>
| Newsgroups | gmane.comp.db.rrdtool.devel |
|---|---|
| Message-ID | <[email protected]> |
# HG changeset patch # User Peter Stamfest <[email protected]> # Date 1299524671 -3600 # Node ID 66ab2898ff2c8fdcdb03f1da63514ac55e616496 # Parent 5c2915ea1c62b764a97bdfa035371ce90ef2bc6e * Add initioal OO style data fetching support * This uses URLs to specify RRD files, possibly using rrdcached connections - currently only file:// urls supported, but they allow for rrdcached connections to allow for better backwards compatibility - the framework should allow for other schemes as well diff --git a/Makefile.am b/Makefile.am --- a/Makefile.am +++ b/Makefile.am @@ -5,7 +5,7 @@ # build the following subdirectories -SUBDIRS = po src examples doc bindings uriparser/lib +SUBDIRS = po uriparser/lib src examples doc bindings # the following files are not mentioned in any other Makefile EXTRA_DIST = COPYRIGHT CHANGES WIN32-BUILD-TIPS.txt TODO CONTRIBUTORS THREADS \ diff --git a/src/Makefile.am b/src/Makefile.am --- a/src/Makefile.am +++ b/src/Makefile.am @@ -12,7 +12,7 @@ INCLUDES = -DLOCALEDIR="\"$(datadir)/locale\"" RRD_DEFAULT_FONT=@RRD_DEFAULT_FONT@ AM_CPPFLAGS = -DRRD_DEFAULT_FONT=\"$(RRD_DEFAULT_FONT)\" -DNUMVERS=@NUMVERS@ -AM_CFLAGS = @CFLAGS@ +AM_CFLAGS = @CFLAGS@ -I$(top_srcdir)/uriparser/include ## no including this by default @WERROR@ UPD_C_FILES = \ @@ -43,6 +43,7 @@ rrd_flushcached.c \ rrd_fetch.c \ rrd_resize.c \ + rrd_source.c \ rrd_tune.c if BUILD_RRDGRAPH @@ -61,6 +62,7 @@ rrd_format.h rrd_tool.h rrd_xport.h rrd.h rrd_rpncalc.h \ rrd_hw.h rrd_hw_math.h rrd_hw_update.h \ fnv.h rrd_graph.h \ + rrd_source.h \ rrd_is_thread_safe.h if BUILD_LIBDBI @@ -84,7 +86,7 @@ librrd_la_SOURCES = $(RRD_C_FILES) librrd_la_DEPENDENCIES = librrdupd.la librrd.sym -librrd_la_LIBADD = librrdupd.la $(ALL_LIBS) +librrd_la_LIBADD = librrdupd.la $(ALL_LIBS) -L$(top_srcdir)/uriparser/lib -luriparser librrd_la_LDFLAGS = -version-info @LIBVERS@ librrd_la_LDFLAGS += -export-symbols librrd.sym @@ -93,7 +95,7 @@ librrd_th_la_CFLAGS = $(AM_CFLAGS) $(MULTITHREAD_CFLAGS) librrd_th_la_LDFLAGS = $(MULTITHREAD_LDFLAGS) -version-info @LIBVERS@ librrd_th_la_LDFLAGS += -export-symbols librrd.sym -librrd_th_la_LIBADD = $(ALL_LIBS) +librrd_th_la_LIBADD = $(ALL_LIBS) -L$(top_srcdir)/uriparser/lib -luriparser include_HEADERS = rrd.h rrd_format.h rrd_client.h diff --git a/src/rrd_source.c b/src/rrd_source.c new file mode 100644 --- /dev/null +++ b/src/rrd_source.c @@ -0,0 +1,316 @@ +/**************************************************************************** + * rrd_source.c - OO style rrd data fetch support + * (c) 2011 Peter Stamfest + * + * Part of rrdtool - redistributable and usable as set out in the COPYRIGHT + * file + ****************************************************************************/ +#include <stdlib.h> + +#include "rrd.h" +#include "rrd_tool.h" +#include "rrd_client.h" +#include "rrd_source.h" + + +int rrd_fetch_obj(rrd_base_source_t *obj, + const char *cf, /* which consolidation function ? */ + time_t *start, + time_t *end, /* which time frame do you want ? + * will be changed to represent reality */ + unsigned long *step, /* which stepsize do you want? + * will be changed to represent reality */ + unsigned long *ds_cnt, /* number of data sources in file */ + char ***ds_namv, /* names of data_sources */ + rrd_value_t **data) +{ + if (obj == NULL) return -1; + if (obj->vtab->fetch_m == NULL) return -1; + + return obj->vtab->fetch_m(obj, cf, start, end, step, ds_cnt, ds_namv, data); +} + +/* convenience method - it is important that nothing is done besides calling + * the destructor. */ +int delete_source_obj(rrd_base_source_t *obj) { + if (obj == NULL) return 0; + if (obj->vtab->delete_m != NULL) { + return obj->vtab->delete_m(obj); + } + return 0; +} + +/* base destructor. Will also free() the obj. */ +static int delete_base_source(rrd_base_source_t *obj) { + if (obj == NULL) return 0; + if (obj->uri != NULL) { + uriFreeUriMembersA(obj->uri); + free(obj->uri); + obj->uri = NULL; + } + free(obj); + return 0; +} + +static int delete_file_source(rrd_file_source_t *obj) { + if (obj == NULL) return 0; + if (obj->filename != NULL) free(obj->filename); + obj->filename = NULL; + + /* super destructor */ + delete_base_source((rrd_base_source_t *)obj); + return 0; +} + +static int fetch_file_source(rrd_file_source_t *obj, + const char *cf, /* which consolidation function ? */ + time_t *start, + time_t *end, /* which time frame do you want ? + * will be changed to represent reality */ + unsigned long *step, /* which stepsize do you want? + * will be changed to represent reality */ + unsigned long *ds_cnt, /* number of data sources in file */ + char ***ds_namv, /* names of data_sources */ + rrd_value_t **data) +{ + rrdc_connection_t *conn = NULL; + int status = 0; + + if (obj->cache == NULL) { + return rrd_fetch_r(obj->filename, cf, start, end, step, ds_cnt, ds_namv, data); + } + + conn = new_rrdc_connection(obj->cache); + if (conn == NULL) { + rrd_set_error("Problem connecting to the cache daemon"); + return -1; + } + status = rrdc_fetch (conn, obj->filename, cf, start, end, step, + ds_cnt, ds_namv, data); + delete_rrdc_connection(conn); + return status; +} + +static int compare_file_source(rrd_file_source_t *obj, + rrd_base_source_t *other) { + rrd_file_source_t *other_file; + int rc = 0; + + if (obj == NULL || other == NULL) return -1; + if (obj->super.vtab != other->vtab) return -1; + + /* OK, both are file sources */ + other_file = (rrd_file_source_t *) other; + + if (obj->filename == NULL || other_file->filename == NULL) + return -1; + rc = strcmp(obj->filename, other_file->filename); + if (rc != 0) return rc; + + if (obj->cache == NULL && other_file->cache == NULL) return 0; + if (obj->cache == NULL || other_file->cache == NULL) return -1; + + rc = strcmp(obj->cache, other_file->cache); + if (rc != 0) return rc; + + return 0; +} + +static char * to_string_file_source(rrd_file_source_t *obj) { + char *buf = NULL; + int len = 8; /* file:/// */ + + if (obj == NULL) return NULL; + + len += strlen(obj->filename); + if (obj->cache != NULL) { + len += 7; /* ?cache= */ + len += strlen(obj->cache); + } + len++; /* NUL */ + + buf = malloc(len); + if (buf == NULL) return buf; + + buf[0] = 0; + strcat(buf, "file:///"); + strcat(buf, obj->filename); + + if (obj->cache != NULL) { + strcat(buf, "?cache="); + strcat(buf, obj->cache); + } + return buf; +} + +static const struct rrd_source_base_vtab rrd_source_file_vtab = { + .delete_m = (delete_op_ptr) delete_file_source, + .fetch_m = (fetch_op_ptr) fetch_file_source, + .compare_m = (compare_op_ptr) compare_file_source, + .to_string_m = (to_string_op_ptr) to_string_file_source, +}; + +rrd_file_source_t *new_file_source(const char *filename, const char *cache) { + if (filename == NULL) { + rrd_set_error("non NULL filename required"); + return NULL; + } + + rrd_file_source_t *obj = calloc(1, sizeof(rrd_file_source_t)); + if (obj == NULL) { + rrd_set_error("Out of memory"); + return NULL; + } + + /* methods */ + obj->super.vtab = &rrd_source_file_vtab; + /* data */ + obj->filename = strdup(filename); + obj->cache = cache != NULL && strlen(cache) > 0 ? strdup(cache) : NULL; + + return obj; +} + +static char *partDup(UriTextRangeA *s) { + if (s == NULL) return NULL; + if (s->first == NULL) return NULL; + + return strndup(s->first, s->afterLast - s->first); +} + +/* Take parsed URI and reconstruct file from the list of path segments. + * + * Returned path must be free'd */ +static char *get_uri_path(UriUriA *uri) { + UriPathSegmentA *seg; + char *p = NULL, *pp; + int count = 0; + + if (uri == NULL) return NULL; + + int n = 0; + if (uri->absolutePath) { + n += 1; + } + /* two passes: first to calculate length, second to build path. */ + for(seg = uri->pathHead ; seg != uri->pathTail->next ; seg = seg->next) { + n += (seg->text.afterLast - seg->text.first) + 1; + } + n += 1; /* NUL byte */ + + p = pp = malloc(n); + if (p == NULL) { + /* OOM */ + /* should we call rrd_set_error ? */ + return NULL; + } + + if (uri->absolutePath) { + *(pp++) = '/'; + } + + for(seg = uri->pathHead ; seg != uri->pathTail->next ; seg = seg->next, count++) { + int l = seg->text.afterLast - seg->text.first; + if (count > 0) *(pp++) = '/'; + strncpy(pp, seg->text.first, l); + pp += l; + } + *pp = 0; + return p; +} + +rrd_base_source_t *parse_source_string(const char *spec) { + rrd_base_source_t *result = NULL; + UriParserStateA state; + UriUriA *uri = (UriUriA *) malloc(sizeof(UriUriA)); + + if (uri == NULL) { + /* OOM */ + rrd_set_error("Out of memory"); + goto out; + } + + state.uri = uri; + if (uriParseUriA(&state, spec) != URI_SUCCESS) { + rrd_set_error("Cannot parse '%s' as URL", spec); + goto out; + } + + result = parse_source_uri(uri); + +out: + if (result == NULL && uri != NULL) { + uriFreeUriMembersA(uri); + free(uri); + } + return result; +} + + +/** turn a parsed URI into a rrd source. + * + * The passed uri becomes part of the returned object, + * and will be free'd by the destructor. */ +rrd_base_source_t *parse_source_uri(UriUriA *uri) { + rrd_base_source_t *result = NULL; + char *scheme = NULL; + char *host = NULL, *port = NULL; + + if (uri == NULL) return NULL; + + scheme = partDup(&(uri->scheme)); + if (scheme == NULL) { + /* default is ordinary file with optional rrdcached connection... */ + return NULL; + } else if (strcmp(scheme, "file") == 0) { + /* file, if host/port given -> use that as the corresponding + * rrdcached. This only works for TCP connected rrdcached */ + + /* an optional query parameter "cache" also denotes the associated + * cache... this works for local sockets as well */ + + char *cache = NULL; + char *f = get_uri_path(uri); + UriQueryListA *queryList, *p; + int itemCount; + + if (f == NULL) { + goto out; + } + + host = partDup(&(uri->hostText)); + port = partDup(&(uri->portText)); + if (host != NULL && strlen(host) > 0) { + cache = malloc(strlen(host) + 1 + strlen(port ? port : "") + 1); + if (cache == NULL) goto out; + sprintf(cache, (port ? "%s:%s" : "%s"), host, port); + } + + if (uriDissectQueryMallocA(&queryList, &itemCount, uri->query.first, + uri->query.afterLast) == URI_SUCCESS) { + for (p = queryList ; p ; p = p->next) { + if (strcmp(p->key, "cache") == 0) { + if (cache != NULL) free(cache); /* in case we have set it before */ + cache = strdup(p->value); + if (cache == NULL) goto out; + } + } + + uriFreeQueryListA(queryList); + } + + result = (rrd_base_source_t*) new_file_source(f, cache); + result->uri = uri; + free(f); + if (cache != NULL) free(cache); + } else if (strcmp(scheme, "rrdcache") == 0) { + + } + +out: + if (scheme == NULL) free(scheme); + if (host == NULL) free(host); + if (port == NULL) free(port); + + return result; +} diff --git a/src/rrd_source.h b/src/rrd_source.h new file mode 100644 --- /dev/null +++ b/src/rrd_source.h @@ -0,0 +1,93 @@ +/**************************************************************************** + * rrd_source.h - OO style rrd data fetch support + * (c) 2011 Peter Stamfest + * + * Part of rrdtool - redistributable and usable as set out in the COPYRIGHT + * file + ****************************************************************************/ +#ifndef _RRD_SOURCE_H +#define _RRD_SOURCE_H + +#include "uriparser/Uri.h" + +struct rrd_source_base_s; +struct rrd_file_source_s; + +typedef int (*delete_op_ptr)(struct rrd_source_base_s *obj); + +typedef int (*fetch_op_ptr)( + struct rrd_source_base_s *obj, + const char *cf, /* which consolidation function ? */ + time_t *start, + time_t *end, /* which time frame do you want ? + * will be changed to represent reality */ + unsigned long *step, /* which stepsize do you want? + * will be changed to represent reality */ + unsigned long *ds_cnt, /* number of data sources in file */ + char ***ds_namv, /* names of data_sources */ + rrd_value_t **data); + +typedef int (*compare_op_ptr)(struct rrd_source_base_s *obj, + struct rrd_source_base_s *other); + +/* turn object into a human readable string representation. + * Returned string must be free'd. */ +typedef char * (*to_string_op_ptr)(struct rrd_source_base_s *obj); + + +/* + * A very simple OO system. It uses distinct virtual function + * tables (at least not now). This allows for runtime type identification + * (which should NOT be used lightly, but simplyfies comparing of objects) + */ +struct rrd_source_base_vtab { + delete_op_ptr delete_m; /* destructor. NOTE: Destructors MUST be + * chained, with the call to a base class + * destructor being the last thing done + * in the destructor of a derived class */ + fetch_op_ptr fetch_m; + compare_op_ptr compare_m; + to_string_op_ptr to_string_m; +}; + +struct rrd_source_base_s { + const struct rrd_source_base_vtab *vtab; + + UriUriA *uri; +}; + +/* effectively derived from rrd_source_base_s */ +struct rrd_file_source_s { + struct rrd_source_base_s super; + + char *filename; + char *cache; +}; + + +typedef struct rrd_file_source_s rrd_file_source_t; +typedef struct rrd_source_base_s rrd_base_source_t; + + +#define call0(obj, method) ((obj)->vtab->method((obj))) +#define call(obj, method, ...) ((obj)->vtab->method((obj), __VA_ARGS__)) + +int rrd_fetch_obj(rrd_base_source_t *obj, + const char *cf, /* which consolidation function ? */ + time_t *start, + time_t *end, /* which time frame do you want ? + * will be changed to represent reality */ + unsigned long *step, /* which stepsize do you want? + * will be changed to represent reality */ + unsigned long *ds_cnt, /* number of data sources in file */ + char ***ds_namv, /* names of data_sources */ + rrd_value_t **data); + +int delete_source_obj(rrd_base_source_t *obj); +rrd_file_source_t *new_file_source(const char *filename, const char *cache); + +rrd_base_source_t *parse_source_string(const char *uri); +rrd_base_source_t *parse_source_uri(UriUriA *uri); + + +#endif