diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3efc9f1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +*.[ch]~ +rrdclient +rrdtest +ocaml/_build +ocaml/rrdreader.native +*.o +*.a +*.rrd + diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..39d0d29 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "parson"] + path = parson + url = https://github.com/kgabis/parson.git diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..a59147b --- /dev/null +++ b/.travis.yml @@ -0,0 +1,14 @@ + +language: c +sudo: false + +matrix: + include: + - compiler: gcc + os: linux + +script: + - make parson + - make + - make test + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..46333ae --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Citrix + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..238d30e --- /dev/null +++ b/Makefile @@ -0,0 +1,99 @@ +# vim: set ts=8 sw=8 noet: +# +# + +CC = gcc +CFLAGS = -std=gnu99 -g -Wall +OBJ += librrd.o +OBJ += parson/parson.o +LIB += -lz + +OCB = ocamlbuild -use-ocamlfind + +.PHONY: all +all: librrd.a rrdtest rrdclient + +.PHONY: clean +clean: + rm -f $(OBJ) + rm -f librrd.a + rm -f librrd.o + rm -f parson/parson.o + rm -f rrdtest.o rrdtest + rm -f rrdclient.o rrdclient + rm -rf config.xml cov-int html coverity.out + cd ocaml; $(OCB) -clean + +.PHONY: test +test: rrdtest rrdclient + ./rrdtest + seq 1 10 | ./rrdclient rrdclient.rrd + seq 1 10 \ + | while read i; do echo $$i; sleep 1; done \ + | ./rrdclient rrdclient.rrd + +.PHONY: valgrind +valgrind: rrdtest + valgrind --leak-check=yes ./rrdtest + seq 1 10 | valgrind --leak-check=yes ./rrdclient rrdclient.rrd + +.PHONY: indent +indent: librrd.h librrd.c rrdtest.c + indent -orig -nut $^ + +.PHONY: depend +depend: librrd.c rrdtest.c + $(CC) -MM $^ + +.PHONY: parson +parson: + # git submodule add https://github.com/kgabis/parson.git + git submodule init + git submodule update + +%.o: %.c + $(CC) $(CFLAGS) -c -o $@ $< + +librrd.a: $(OBJ) + ar rc $@ $(OBJ) + ranlib $@ + +rrdtest: rrdtest.o librrd.a + $(CC) $(CFLAGS) -o $@ $^ $(LIB) + +rrdclient: rrdclient.o librrd.a + $(CC) $(CFLAGS) -o $@ $^ $(LIB) + +# coverity analysis + +COV_OPTS += --cpp +COV_OPTS += --aggressiveness-level high +COV_OPTS += --all +COV_OPTS += --rule +COV_OPTS += --disable-parse-warnings +COV_OPTS += --enable-fnptr + +COV_DIR = cov-int + +cov: parson + cov-configure --gcc --config config.xml + cov-build --dir $(COV_DIR) --config config.xml $(MAKE) + cov-analyze $(COV_OPTS) --dir $(COV_DIR) --config config.xml + cov-format-errors --dir $(COV_DIR) --emacs-style > coverity.out + cov-format-errors --dir $(COV_DIR) --html-output html + +# C dependencies + +parson/parson.h: parson +parson/parson.c: parson + +parson/parson.o: parson/parson.h +rrdtest.o: parson/parson.h librrd.h +librrd.o: parson/parson.h librrd.h + +# OCaml test utility +# You need: yum install -y ocaml-rrd-transport-devel + +rrdreader: + cd ocaml; $(OCB) -pkg rrd-transport -tag thread rrdreader.native + diff --git a/README.md b/README.md index e5be669..97dfeed 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ +[![Build Status](https://travis-ci.org/lindig/rrd-client-lib-1.svg?branch=master)](https://travis-ci.org/lindig/rrd-client-lib-1) + # rrd-client-lib - A Library to Provide RRD Data The RRD service is a system-level service that aggregates @@ -19,14 +21,15 @@ needs to be initialised: make parson make - + + make test ./rrdtest ## Parson -The [Parson](https://github.com/kgabis/parson.git) library is included -as a Git submodule. A submodule points to a specific commit in an -external repository and does not track its master branch as this +The JSON library [Parson](https://github.com/kgabis/parson.git) is +included as a Git submodule. A submodule points to a specific commit in +an external repository and does not track its master branch as this advances. Instead, it needs to be updated explicitly. ## Documentation - Overview @@ -34,11 +37,11 @@ advances. Instead, it needs to be updated explicitly. The header file `librrd.h` contains the essential information to use the library: - typedef ... rrd_value; + typedef ... rrd_value_t; typedef ... RRD_SOURCE; typedef ... RRD_PLUGIN; - RRD_PLUGIN *rrd_open(char *name, rrd_domain domain, char *path); + RRD_PLUGIN *rrd_open(char *name, rrd_domain_t domain, char *path); int rrd_close(RRD_PLUGIN * plugin); int rrd_add_src(RRD_PLUGIN * plugin, RRD_SOURCE * source); int rrd_del_src(RRD_PLUGIN * plugin, RRD_SOURCE * source); @@ -55,6 +58,20 @@ samples values from all data sources and writes the data to the `path` provided to `rrd_open`. It is the client's responsibility to call `rrd_sample` regularly at an interval of 5 seconds. +## Sample Code + +See `rrdclient.c` for a simple client that reads integers from standard +input as a data source and reports them to RRD. Here is how everything +is compiled and linked: + + gcc -std=gnu99 -g -Wall -c -o librrd.o librrd.c + gcc -std=gnu99 -g -Wall -c -o parson/parson.o parson/parson.c + ar rc librrd.a librrd.o parson/parson.o + ranlib librrd.a + gcc -std=gnu99 -g -Wall -c -o rrdclient.o rrdclient.c + gcc -std=gnu99 -g -Wall -o rrdclient rrdclient.o librrd.a -lz + + ## Interface <>= @@ -78,11 +95,15 @@ A plugin opens a file for communication and periodically calls considered private to the library. <>= - typedef enum { RRD_LOCAL_DOMAIN = 0, RRD_INTER_DOMAIN } rrd_domain; + /* rrd_domain_t */ + typedef int32_t rrd_domain_t; + #define RRD_LOCAL_DOMAIN 0 + #define RRD_INTER_DOMAIN 1 + <>= - RRD_PLUGIN *rrd_open(char *name, rrd_domain domain, char *path); + RRD_PLUGIN *rrd_open(char *name, rrd_domain_t domain, char *path); int rrd_close(RRD_PLUGIN * plugin); int rrd_sample(RRD_PLUGIN * plugin); @@ -98,32 +119,44 @@ integer or float values. Data sources can be added and removed dynamically. <>= - typedef enum { RRD_GAUGE = 0, RRD_ABSOLUTE, RRD_DERIVE } rrd_scale; - typedef enum { RRD_HOST = 0, RRD_VM, RRD_SR } rrd_owner; - typedef enum { RRD_FLOAT64 = 0, RRD_INT64 } rrd_type; + /* rrd_scale_t */ + typedef int32_t rrd_scale_t; + #define RRD_GAUGE 0 + #define RRD_ABSOLUTE 1 + #define RRD_DERIVE 2 + + /* rrd_owner_t */ + typedef int32_t rrd_owner_t; + #define RRD_HOST 0 + #define RRD_VM 1 + #define RRD_SR 2 + + /* rrd_type_t */ + typedef int32_t rrd_type_t; + #define RRD_FLOAT64 0 + #define RRD_INT64 1 typedef union { int64_t int64; - float float64; - } rrd_value; + double float64; + } rrd_value_t; typedef struct rrd_source { char *name; /* name of the data source */ char *description; /* for user interface */ - rrd_owner owner; - int rrd_default;/* true: rrd daemon will archive */ char *owner_uuid; /* UUID of the owner or NULL */ char *rrd_units; /* for user interface */ - rrd_scale scale; /* presentation of value */ - rrd_type type; /* type of value */ char *min; /* min <= sample() <= max */ char *max; /* min <= sample() <= max */ - rrd_value(*sample) (void); /* reads value that gets * - * reported */ + rrd_value_t(*sample) (void); /* reads value */ + rrd_owner_t owner; + int32_t rrd_default; /* true: rrd daemon will archive */ + rrd_scale_t scale; /* presentation of value */ + rrd_type_t type; /* type of value */ } RRD_SOURCE; + typedef struct rrd_plugin RRD_PLUGIN; - <> <>= int rrd_add_src(RRD_PLUGIN *plugin, RRD_SOURCE *source); @@ -135,7 +168,7 @@ reporting. It includes a function (pointer) `sample` that obtains the value being reported. A value can be either a 64-bit integer or a 64-bit floating point value -- discriminated by `type`. -## Error Handling +## Constants and Error Handling Some functions return an error code. @@ -149,26 +182,6 @@ Some functions return an error code. #define RRD_ERROR 4 - -## Private Types - - <>= - #define RRD_MAX_SOURCES 128 - - - <>= - typedef struct rrd_plugin { - char *name; /* name of the plugin */ - int file; /* where we report data */ - rrd_domain domain; /* domain of this plugin */ - RRD_SOURCE *sources[RRD_MAX_SOURCES]; - uint32_t n; /* number of used slots */ - JSON_Value *meta; /* meta data for the plugin */ - char *buf; /* buffer where we keep protocol data */ - size_t buf_size; /* size of the buffer */ - } RRD_PLUGIN; - - ## Design The library updates the file provided to `rrd_open` when `rrd_sample` is diff --git a/librrd.c b/librrd.c new file mode 100644 index 0000000..4457017 --- /dev/null +++ b/librrd.c @@ -0,0 +1,450 @@ +/* + * + * Copyright (c) 2016 Citrix + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "librrd.h" +#include "parson/parson.h" + +#define MAGIC "DATASOURCES" +#define MAGIC_SIZE (sizeof (MAGIC)-1) + +#ifndef __APPLE__ +#include +#define htonll(x) htobe64(x) +#endif + +/* + * The type RRD_PLUGIN below is private to the implementation and + * entirely managed by it. + */ + +struct rrd_plugin { + char *name; /* name of the plugin */ + RRD_SOURCE *sources[RRD_MAX_SOURCES]; + JSON_Value *meta; /* meta data for the plugin */ + char *buf; /* buffer where we keep protocol data */ + rrd_domain_t domain; /* domain of this plugin */ + uint32_t n; /* number of used slots */ + size_t buf_size; /* size of the buffer */ + int file; /* where we report data */ +}; + + +/* + * The struct represents the first 31 bytes in the protocol header. Fields + * are not 4-byte or 8-byte aligned which is why we need the packed + * attribute. + */ +struct rrd_header { + uint8_t rrd_magic[MAGIC_SIZE]; + uint32_t rrd_checksum_value; + uint32_t rrd_checksum_meta; + uint32_t rrd_header_datasources; + uint64_t rrd_timestamp; + uint32_t rrd_data[0]; /* more data follows */ +} __attribute__ ((packed)); +typedef struct rrd_header RRD_HEADER; + +/* + * invalidate the current buffer by removing it. It will be re-created by + * sample(). + */ +static void +invalidate(RRD_PLUGIN * plugin) +{ + assert(plugin); + + free(plugin->buf); + plugin->buf = NULL; + plugin->buf_size = 0; + json_value_free(plugin->meta); + plugin->meta = NULL; +} + +/* + * Generate JSON for a data source and return it as an JSON object (from + * where it can be rendered to a string. + */ +static JSON_Value * +json_for_source(RRD_SOURCE * source) +{ + assert(source); + + JSON_Value *json = json_value_init_object(); + JSON_Object *src = json_value_get_object(json); + + json_object_set_string(src, "description", source->description); + json_object_set_string(src, "units", source->rrd_units); + json_object_set_string(src, "min", source->min); + json_object_set_string(src, "max", source->max); + json_object_set_string(src, "default", + source->rrd_default ? "true" : "false"); + + char *owner = NULL; + switch (source->owner) { + case RRD_HOST: + owner = "host"; + break; + case RRD_VM: + owner = "vm"; + break; + case RRD_SR: + owner = "rrd"; + break; + default: + abort(); + } + json_object_set_string(src, "owner", owner); + + char *value_type = NULL; + switch (source->type) { + case RRD_INT64: + value_type = "int64"; + break; + case RRD_FLOAT64: + value_type = "float"; + break; + default: + abort(); + } + json_object_set_string(src, "value_type", value_type); + + + char *scale = NULL; + switch (source->scale) { + case RRD_GAUGE: + scale = "gauge"; + break; + case RRD_ABSOLUTE: + scale = "absolute"; + break; + case RRD_DERIVE: + scale = "derive"; + break; + default: + abort(); + } + json_object_set_string(src, "type", scale); + + return json; +} +/* + * Generate JSON for a plugin. This is just a JSON object containing a + * sub-object for every data source. + */ +static JSON_Value * +json_for_plugin(RRD_PLUGIN * plugin) +{ + assert(plugin); + + JSON_Value *root_json = json_value_init_object(); + JSON_Object *root = json_value_get_object(root_json); + + JSON_Value *ds_json = json_value_init_object(); + JSON_Object *ds = json_value_get_object(ds_json); + + json_object_set_value(root, "datasources", ds_json); + for (size_t i = 0; i < RRD_MAX_SOURCES; i++) { + JSON_Value *src; + if (plugin->sources[i] == NULL) + continue; + src = json_for_source(plugin->sources[i]); + json_object_set_value(ds, plugin->sources[i]->name, src); + } + return root_json; +} +/* + * initialise the buffer that we update and write out to a file. Once + * initialised, it is kept up to date by sample(). + */ +static int +initialise(RRD_PLUGIN * plugin) +{ + + RRD_HEADER *header; + uint32_t size_meta; + uint32_t size_total; + int64_t *p64; + int32_t *p32; + + assert(plugin); + assert(plugin->meta == NULL); + assert(plugin->buf == NULL); + assert(plugin->n <= RRD_MAX_SOURCES); + + plugin->meta = json_for_plugin(plugin); + size_meta = json_serialization_size_pretty(plugin->meta); + assert(size_meta < 2048 * plugin->n); /* just a safeguard */ + + size_total = 0; + size_total += sizeof(RRD_HEADER); + size_total += plugin->n * sizeof(int64_t); + size_total += sizeof(uint32_t); + size_total += size_meta; + + plugin->buf_size = size_total; + plugin->buf = malloc(size_total); + if (!plugin->buf) { + /* + * fatal + */ + plugin->buf_size = 0; + return -1; + } + /* + * all values need to be in network byte order + */ + header = (RRD_HEADER *) plugin->buf; + memcpy(&header->rrd_magic, MAGIC, MAGIC_SIZE); + header->rrd_checksum_value = htonl(0x01234567); + header->rrd_header_datasources = htonl(plugin->n); + header->rrd_timestamp = htonll((uint64_t) time(NULL)); + if (header->rrd_timestamp == -1) { + free(plugin->buf); + plugin->buf_size = 0; + plugin->buf = NULL; + return -1; + } + + p64 = (int64_t *) (plugin->buf + sizeof(RRD_HEADER)); + for (size_t i = 0; i < plugin->n; i++) { + *p64++ = htonll(0x0011223344556677); + } + p32 = (int32_t *) p64; + *p32++ = htonl(size_meta); + json_serialize_to_buffer_pretty(plugin->meta, (char *) p32, size_meta); + + uint32_t crc; + crc = crc32(0L, Z_NULL, 0); + crc = crc32(crc, (unsigned char *) p32, size_meta); + header->rrd_checksum_meta = htonl(crc); + return 0; +} + +/* + * rrd_open creates the data structure that represents a plugin with + * initially no data source. Data sources will be added later by + * rrd_add_src. + */ +RRD_PLUGIN * +rrd_open(char *name, rrd_domain_t domain, char *path) +{ + assert(name); + assert(path); + + RRD_PLUGIN *plugin = malloc(sizeof(RRD_PLUGIN)); + if (!plugin) { + return NULL; + } + + plugin->name = name; + plugin->domain = domain; + /* + * mark all slots for data sources as free + */ + for (int i = 0; i < RRD_MAX_SOURCES; i++) { + plugin->sources[i] = (RRD_SOURCE *) NULL; + } + plugin->n = 0; + plugin->buf_size = 0; + plugin->buf = NULL; /* will be initialised by sample() */ + plugin->meta = NULL; + + plugin->file = open(path, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); + if (plugin->file == -1) { + free(plugin); + return NULL; + } + return plugin; +} + +/* + * unregister a plugin. Free all resources that we have allocted. Note + * that calling free(NULL) is fine in case some resource was already + * de-allocated. + */ +int +rrd_close(RRD_PLUGIN * plugin) +{ + assert(plugin); + int rc; + + rc = close(plugin->file); + json_value_free(plugin->meta); + free(plugin->buf); + free(plugin); + return (rc == 0 ? RRD_OK : RRD_FILE_ERROR); +} + +/* + * Add a new data source to a plugin. It is inserted into the first free + * slot available. + */ +int +rrd_add_src(RRD_PLUGIN * plugin, RRD_SOURCE * source) +{ + assert(plugin); + assert(source); + + /* + * find free slot + */ + size_t i; + for (i = 0; i < RRD_MAX_SOURCES; i++) { + if (plugin->sources[i] == NULL) + break; + } + if (i >= RRD_MAX_SOURCES) { + return RRD_TOO_MANY_SOURCES; + } + plugin->sources[i] = source; + plugin->n++; + invalidate(plugin); + + return RRD_OK; +} + +/* + * Remove a previously registered data source from a plugin, + */ +int +rrd_del_src(RRD_PLUGIN * plugin, RRD_SOURCE * source) +{ + assert(source); + size_t i; + /* + * find slot with source + */ + for (i = 0; i < RRD_MAX_SOURCES; i++) { + if (plugin->sources[i] == source) + break; + } + if (i >= RRD_MAX_SOURCES) { + return RRD_NO_SUCH_SOURCE; + } + plugin->sources[i] = NULL; + plugin->n--; + invalidate(plugin); + + return RRD_OK; +} + +/* + * write data to fd + */ +static int +write_exact(int fd, const void *data, size_t size) +{ + size_t offset = 0; + ssize_t len; + while (offset < size) { + len = write(fd, (const char *) data + offset, size - offset); + if ((len == -1) && (errno == EINTR)) + continue; + if (len <= 0) + return -1; + offset += len; + } + return 0; +} +/* + * Sample obtains a values form all data sources by calling their sample + * functions. It updates the buffer with all data and writes it out. If + * there is no buffer it means it was invalidated previously because a + * data source was added or removed. In that case in creates a new buffer + * first. + */ +int +rrd_sample(RRD_PLUGIN * plugin) +{ + assert(plugin); + JSON_Value *json; + int n = 0; + int64_t *p; + RRD_HEADER *header; + + json = json_for_plugin(plugin); + json_value_free(json); + + if (plugin->buf == NULL) { + int rc; + rc = initialise(plugin); + if (rc != 0) { + return RRD_ERROR; + } + } + assert(plugin->buf); + header = (RRD_HEADER *) plugin->buf; + + /* + * sample n sources and write values to buffer + */ + p = (int64_t *) (plugin->buf + sizeof(RRD_HEADER)); + for (size_t i = 0; i < RRD_MAX_SOURCES; i++) { + if (plugin->sources[i] == NULL) + continue; + rrd_value_t v = plugin->sources[i]->sample(); + *p++ = htonll((uint64_t) v.int64); + n++; + } + /* + * must have sampled exactly n data sources + */ + assert(n == plugin->n); + + /* + * update timestamp, calculate crc + */ + header->rrd_timestamp = htonll((uint64_t) time(NULL)); + uint32_t crc = crc32(0L, Z_NULL, 0); + crc = crc32(crc, + (unsigned char *) &header->rrd_timestamp, + (n + 1) * sizeof(int64_t)); + header->rrd_checksum_value = htonl(crc); + + /* + * reset file pointer, write out buffer + */ + if (lseek(plugin->file, 0, SEEK_SET) < 0) { + return RRD_FILE_ERROR; + } + if (write_exact(plugin->file, plugin->buf, plugin->buf_size) != 0) { + return RRD_FILE_ERROR; + } + return RRD_OK; +} diff --git a/librrd.h b/librrd.h new file mode 100644 index 0000000..2f18aad --- /dev/null +++ b/librrd.h @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2016 Citrix + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + +#include +#include "parson/parson.h" + +#define RRD_MAX_SOURCES 128 + +#define RRD_OK 0 +#define RRD_TOO_MANY_SOURCES 1 +#define RRD_NO_SUCH_SOURCE 2 +#define RRD_FILE_ERROR 3 +#define RRD_ERROR 4 + +/* rrd_domain_t */ +typedef int32_t rrd_domain_t; +#define RRD_LOCAL_DOMAIN 0 +#define RRD_INTER_DOMAIN 1 + +/* rrd_scale_t */ +typedef int32_t rrd_scale_t; +#define RRD_GAUGE 0 +#define RRD_ABSOLUTE 1 +#define RRD_DERIVE 2 + +/* rrd_owner_t */ +typedef int32_t rrd_owner_t; +#define RRD_HOST 0 +#define RRD_VM 1 +#define RRD_SR 2 + +/* rrd_type_t */ +typedef int32_t rrd_type_t; +#define RRD_FLOAT64 0 +#define RRD_INT64 1 + + +/* + * An RRD_SOURCE can report float or integer values - represented as a + * rrd_value. The type component tells which one it is. + */ +typedef union { + int64_t int64; + double float64; +} rrd_value_t; + +/* + * An RRD_SOURCE describes the value being reported and contains a + * sample() function to obtain such values. Strings are expected + * to be in UTF8 encoding. + */ +typedef struct rrd_source { + char *name; /* name of the data source */ + char *description; /* for user interface */ + char *owner_uuid; /* UUID of the owner or NULL */ + char *rrd_units; /* for user interface */ + char *min; /* min <= sample() <= max */ + char *max; /* min <= sample() <= max */ + rrd_value_t(*sample) (void); /* reads value */ + rrd_owner_t owner; + int32_t rrd_default; /* true: rrd daemon will archive */ + rrd_scale_t scale; /* presentation of value */ + rrd_type_t type; /* type of value */ +} RRD_SOURCE; + +typedef struct rrd_plugin RRD_PLUGIN; + +/* + * Memory management policy: the library does not free the memory of any + * objects that are passed into it (like strings or RRD_SOURCE objects). + * It does free any memory that it allocates internally when calling + * rrd_close(). + */ + +/* + * rrd_open - regsiter a new plugin + * name: name of the plugin in UTF8 encoding. + * domain: INTER_DOMAIN if it reports data from multiple domains + * path: file path where the plugin writes it samples + * returns: + * NULL on error + */ +RRD_PLUGIN *rrd_open(char *name, rrd_domain_t domain, char *path); + +/* + * rrd_close - close a plugin. Data sources do not need to be removed + * from the plugin before calling rrd_close. + */ +int rrd_close(RRD_PLUGIN * plugin); + +/* + * rrd_add_src - add a new data source returns: error code At most + * RRD_MAX_SOURCES can be active. It is an unchecked error to register the + * same source multiple times. The name of the source must be unique for + * all sources added to a plugin. + */ +int rrd_add_src(RRD_PLUGIN * plugin, RRD_SOURCE * source); + +/* + * rrd_del_src - remove a data source. Returns an error code. + */ +int rrd_del_src(RRD_PLUGIN * plugin, RRD_SOURCE * source); + +/* + * calling rrd_sample(plugin) triggers that all data sources are sampled + * and the results are reported to the RRD daemon. This function needs to + * be called every 5 seconds by the client, + */ +int rrd_sample(RRD_PLUGIN * plugin); diff --git a/ocaml/rrdreader.ml b/ocaml/rrdreader.ml new file mode 100644 index 0000000..371889d --- /dev/null +++ b/ocaml/rrdreader.ml @@ -0,0 +1,73 @@ +(* + * Copyright (c) 2016 Citrix + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + *) + +(* This is a small utility to test the librrd library *) + +open Rrd_protocol + +let string_of_data_source owner ds = + let owner_string = match owner with + | Rrd.Host -> "Host" + | Rrd.SR sr -> "SR " ^ sr + | Rrd.VM vm -> "VM " ^ vm + in + let value_string = match ds.Ds.ds_value with + | Rrd.VT_Float f -> Printf.sprintf "float %f" f + | Rrd.VT_Int64 i -> Printf.sprintf "int64 %Ld" i + | Rrd.VT_Unknown -> Printf.sprintf "unknown" + in + let type_string = match ds.Ds.ds_type with + | Rrd.Absolute -> "absolute" + | Rrd.Gauge -> "gauge" + | Rrd.Derive -> "derive" + in + Printf.sprintf + "owner: %s\nname: %s\ntype: %s\nvalue: %s\nunits: %s" + owner_string ds.Ds.ds_name type_string value_string ds.Ds.ds_units + +let print payload = + print_endline "------------ Metadata ------------"; + Printf.printf "timestamp = %Ld\n%!" payload.timestamp; + print_endline "---------- Data sources ----------"; + List.iter + (fun (owner, ds) -> + print_endline (string_of_data_source owner ds); + print_endline "----------") + payload.datasources + +let read path = + let v2 = Rrd_protocol_v2.protocol in + let reader = Rrd_reader.FileReader.create path v2 in + reader.Rrd_reader.read_payload () + |> print + +let main () = + let args = Array.to_list Sys.argv in + let this = Sys.executable_name in + match args with + | [_;path] -> read path; exit 0 + | _ -> Printf.printf "usage: %s file.rrd\n" this; exit 1 + +let () = main () + + diff --git a/parson b/parson new file mode 160000 index 0000000..a1c356e --- /dev/null +++ b/parson @@ -0,0 +1 @@ +Subproject commit a1c356eaa9a0a2b31d05b7ff597b570afb421165 diff --git a/rrdclient.c b/rrdclient.c new file mode 100644 index 0000000..7431659 --- /dev/null +++ b/rrdclient.c @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2016 Citrix + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + +#include +#include +#include +#include +#include "librrd.h" +#include "assert.h" + +static RRD_SOURCE src; +static rrd_value_t v; + +static rrd_value_t +sample(void) +{ + printf("sample called: %"PRId64"\n", v.int64); + return v; +} + + +int +main(int argc, char **argv) +{ + RRD_PLUGIN *plugin; + char line[256]; + + if (argc != 2) { + fprintf(stderr, "usage: %s file.rrd\n", basename(argv[0])); + exit(1); + } + + plugin = rrd_open(argv[0], RRD_LOCAL_DOMAIN, argv[1]); + if (!plugin) { + fprintf(stderr, "can't open %s\n", argv[1]); + exit(1); + } + rrd_add_src(plugin, &src); + + src.name = "stdin"; + src.description = "integers read from stdin"; + src.owner = RRD_HOST; + src.owner_uuid = "931388d6-559e-11e6-ab0a-73658ca1c515"; + src.rrd_units = "numbers"; + src.type = RRD_INT64; + src.scale = RRD_GAUGE; + src.min = "-inf"; + src.max = "inf"; + src.rrd_default = 0; + src.sample = sample; + + int rc; + while (fgets(line, sizeof(line), stdin) != NULL) { + v.int64 = (int64_t) atol(line); + rc = rrd_sample(plugin); + assert(rc == RRD_OK); + } + + rrd_del_src(plugin, &src); + rrd_close(plugin); + return 0; +} diff --git a/rrdtest.c b/rrdtest.c new file mode 100644 index 0000000..5b58b0e --- /dev/null +++ b/rrdtest.c @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2016 Citrix + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + +#include +#include +#include +#include +#include + +#include "librrd.h" + +static int64_t numbers[] = + { 2, 16, 28, 29, 29, 34, 40, 48, 49, 52, 54, 55, 55, 57, 66, 67, 83, + 85, 90, 97 +}; + +/* + * Return the next value from the numbers array. sample keeps an internal + * index into the array that wraps around when it reaches the end. + */ + +static rrd_value_t +sample(void) +{ + rrd_value_t v; + static size_t i = 0; + + v.int64 = numbers[i++ % (sizeof(numbers) / sizeof(numbers[0]))]; + + printf("sample called: %"PRId64"\n", v.int64); + return v; +} + +static RRD_SOURCE src[2]; + +int +main(int argc, char **argv) +{ + RRD_PLUGIN *plugin; + int rc; + + if (argc != 1) { + fprintf(stderr, "usage: %s\n", basename(argv[0])); + exit(1); + } + + plugin = rrd_open("rrdtest", RRD_LOCAL_DOMAIN, "rrdtest.rrd"); + assert(plugin); + + src[0].name = "first"; + src[0].description = "description"; + src[0].owner = RRD_HOST; + src[0].owner_uuid = "4cc1f2e0-5405-11e6-8c2f-572fc76ac144"; + src[0].rrd_units = "points"; + src[0].scale = RRD_GAUGE; + src[0].type = RRD_INT64; + src[0].min = "-inf"; + src[0].max = "inf"; + src[0].rrd_default = 1; + src[0].sample = sample; + + printf("adding source: %s\n", src[0].name); + rrd_add_src(plugin, &src[0]); + rc = rrd_sample(plugin); + assert(rc == RRD_OK); + + src[1].name = "second"; + src[1].description = "description"; + src[1].owner = RRD_HOST; + src[1].owner_uuid = "e8969702-5414-11e6-8cf5-47824be728c3"; + src[1].rrd_units = "points"; + src[1].scale = RRD_GAUGE; + src[1].type = RRD_INT64; + src[1].min = "-inf"; + src[1].max = "inf"; + src[1].rrd_default = 1; + src[1].sample = sample; + + printf("adding source: %s\n", src[1].name); + rrd_add_src(plugin, &src[1]); + rc = rrd_sample(plugin); + assert(rc == RRD_OK); + + printf("removing source: %s\n", src[0].name); + rrd_del_src(plugin, &src[0]); + rc = rrd_sample(plugin); + assert(rc == RRD_OK); + + printf("removing source: %s\n", src[1].name); + rrd_del_src(plugin, &src[1]); + rrd_close(plugin); + return 0; +}