From 5f48615842bd6c16984ae1113951c68bc987ade6 Mon Sep 17 00:00:00 2001 From: Sebastian Harl Date: Wed, 24 Dec 2014 16:47:18 +0100 Subject: [PATCH] proto: Define a wire format for host/service/metric and add marshal functions. --- src/include/frontend/proto.h | 31 +++++++ src/include/utils/proto.h | 48 ++++++++++ src/utils/proto.c | 99 ++++++++++++++++++++ t/unit/utils/proto_test.c | 173 +++++++++++++++++++++++++++++++++++ 4 files changed, 351 insertions(+) diff --git a/src/include/frontend/proto.h b/src/include/frontend/proto.h index 7aae68d..57c721f 100644 --- a/src/include/frontend/proto.h +++ b/src/include/frontend/proto.h @@ -245,6 +245,37 @@ typedef enum { */ SDB_CONNECTION_TIMESERIES, + /* + * SDB_CONNECTION_STORE: + * Execute the 'STORE' command in the server. The message body shall + * include the type of the object to be stored, the timestamp of the last + * update, and a list of fields describing the object depending on the + * object type. Object types are encoded as 32bit integers in network + * byte-order. Timestamps are encoded as 64bit integers in network + * byte-order. Fields are null-terminated strings. + * + * 0 32 64 + * +---------------+---------------+ + * | STORE | length | + * +---------------+---------------+ + * | object type | last_update.. | + * +---------------+---------------+ + * | ... | fields | + * +---------------+ | + * | ... | + * + * Fields: + * + * HOST: name + * SERVICE: hostname, name + * METRIC: hostname, name, [store type, store id] + * ATTRIBUTE: parent object type, hostname, [object name], key, + * + * Values are encoded as their type (32bit integer in network byte-order), + * and their content as implemented by sdb_proto_marshal_data. + */ + SDB_CONNECTION_STORE = 50, + /* * Command subcomponents. */ diff --git a/src/include/utils/proto.h b/src/include/utils/proto.h index ab01cf5..eb0d528 100644 --- a/src/include/utils/proto.h +++ b/src/include/utils/proto.h @@ -37,6 +37,30 @@ extern "C" { #endif +/* + * sdb_proto_host, sdb_proto_service, sdb_proto_metric: + * Protocol-specific representations of the basic information of stored + * objects. + */ +typedef struct { + sdb_time_t last_update; + const char *name; +} sdb_proto_host_t; + +typedef struct { + sdb_time_t last_update; + const char *hostname; + const char *name; +} sdb_proto_service_t; + +typedef struct { + sdb_time_t last_update; + const char *hostname; + const char *name; + const char *store_type; /* optional */ + const char *store_id; /* optional */ +} sdb_proto_metric_t; + /* * sdb_proto_marshal: * Encode the message into the wire format by adding an appropriate header. @@ -68,6 +92,30 @@ sdb_proto_marshal(char *buf, size_t buf_len, uint32_t code, ssize_t sdb_proto_marshal_data(char *buf, size_t buf_len, sdb_data_t *datum); +/* + * sdb_proto_marshal_host, sdb_proto_marshal_service, + * sdb_proto_marshal_metric: + * Encode the basic information of a stored object into the wire format and + * write it to buf. These functions are similar to the sdb_store_ + * functions. See their documentation for details about the arguments. + * + * Returns: + * - The number of bytes of the full encoded datum on success. The function + * does not write more than 'buf_len' bytes. If the output was truncated + * then the return value is the number of bytes which would have been + * written if enough space had been available. + * - a negative value else + */ +ssize_t +sdb_proto_marshal_host(char *buf, size_t buf_len, + const sdb_proto_host_t *host); +ssize_t +sdb_proto_marshal_service(char *buf, size_t buf_len, + const sdb_proto_service_t *svc); +ssize_t +sdb_proto_marshal_metric(char *buf, size_t buf_len, + const sdb_proto_metric_t *metric); + /* * sdb_proto_unmarshal_header: * Read and decode a message header from the specified string. diff --git a/src/utils/proto.c b/src/utils/proto.c index f767fa2..f543dd6 100644 --- a/src/utils/proto.c +++ b/src/utils/proto.c @@ -30,6 +30,7 @@ #endif #include "core/data.h" +#include "core/store.h" #include "core/time.h" #include "utils/error.h" #include "utils/proto.h" @@ -53,6 +54,16 @@ * return the number of bytes that would have been written if enough space had * been available. */ +static ssize_t +marshal_int32(char *buf, size_t buf_len, uint32_t v) +{ + if (buf_len >= sizeof(v)) { + v = htonl(v); + memcpy(buf, &v, sizeof(v)); + } + return sizeof(v); +} /* marshal_int32 */ + static ssize_t marshal_int64(char *buf, size_t buf_len, int64_t v) { @@ -108,6 +119,22 @@ marshal_string(char *buf, size_t buf_len, const char *v) return len; } /* marshal_string */ +#define OBJ_HEADER_LEN (sizeof(uint32_t) + sizeof(sdb_time_t)) +static ssize_t +marshal_obj_header(char *buf, size_t buf_len, + int type, sdb_time_t last_update) +{ + ssize_t n; + + if (buf_len < OBJ_HEADER_LEN) + return OBJ_HEADER_LEN; + + n = marshal_int32(buf, buf_len, (uint32_t)type); + buf += n; buf_len -= n; + marshal_datetime(buf, buf_len, last_update); + return OBJ_HEADER_LEN; +} /* marshal_obj_header */ + /* * public API */ @@ -240,6 +267,78 @@ sdb_proto_marshal_data(char *buf, size_t buf_len, sdb_data_t *datum) return len; } /* sdb_proto_marshal_data */ +ssize_t +sdb_proto_marshal_host(char *buf, size_t buf_len, + const sdb_proto_host_t *host) +{ + size_t len; + ssize_t n; + + if ((! host) || (! host->name)) + return -1; + + len = OBJ_HEADER_LEN + strlen(host->name) + 1; + if (buf_len < len) + return len; + + n = marshal_obj_header(buf, buf_len, SDB_HOST, host->last_update); + buf += n; buf_len -= n; + marshal_string(buf, buf_len, host->name); + return len; +} /* sdb_proto_marshal_host */ + +ssize_t +sdb_proto_marshal_service(char *buf, size_t buf_len, + const sdb_proto_service_t *svc) +{ + size_t len; + ssize_t n; + + if ((! svc) || (! svc->hostname) || (! svc->name)) + return -1; + + len = OBJ_HEADER_LEN + strlen(svc->hostname) + strlen(svc->name) + 2; + if (buf_len < len) + return len; + + n = marshal_obj_header(buf, buf_len, SDB_SERVICE, svc->last_update); + buf += n; buf_len -= n; + n = marshal_string(buf, buf_len, svc->hostname); + buf += n; buf_len -= n; + marshal_string(buf, buf_len, svc->name); + return len; +} /* sdb_proto_marshal_service */ + +ssize_t +sdb_proto_marshal_metric(char *buf, size_t buf_len, + const sdb_proto_metric_t *metric) +{ + size_t len; + ssize_t n; + + if ((! metric) || (! metric->hostname) || (! metric->name)) + return -1; + + len = OBJ_HEADER_LEN + strlen(metric->hostname) + strlen(metric->name) + 2; + if (metric->store_type && metric->store_id) + len += strlen(metric->store_type) + strlen(metric->store_id) + 2; + if (buf_len < len) + return len; + + n = marshal_obj_header(buf, buf_len, SDB_METRIC, metric->last_update); + buf += n; buf_len -= n; + n = marshal_string(buf, buf_len, metric->hostname); + buf += n; buf_len -= n; + n = marshal_string(buf, buf_len, metric->name); + buf += n; buf_len -= n; + if (metric->store_type && metric->store_id) { + n = marshal_string(buf, buf_len, metric->store_type); + buf += n; buf_len -= n; + marshal_string(buf, buf_len, metric->store_id); + } + return len; +} /* sdb_proto_marshal_metric */ + int sdb_proto_unmarshal_header(const char *buf, size_t buf_len, uint32_t *code, uint32_t *msg_len) diff --git a/t/unit/utils/proto_test.c b/t/unit/utils/proto_test.c index c718529..6bf8a7b 100644 --- a/t/unit/utils/proto_test.c +++ b/t/unit/utils/proto_test.c @@ -173,6 +173,176 @@ START_TEST(test_marshal_data) } END_TEST +#define HOST_TYPE "\0\0\0\1" +#define SVC_TYPE "\0\0\0\2" +#define METRIC_TYPE "\0\0\0\3" + +START_TEST(test_marshal_host) +{ + struct { + sdb_proto_host_t host; + ssize_t expected_len; + char *expected; + } golden_data[] = { + { + { 4711, "hostA" }, + 18, HOST_TYPE "\0\0\0\0\0\0\x12\x67" "hostA\0" + }, + { + { 0, "hostA" }, + 18, HOST_TYPE "\0\0\0\0\0\0\0\0" "hostA\0" + }, + { { 4711, NULL }, -1, NULL }, + }; + + size_t i; + + for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data); ++i) { + ssize_t len = sdb_proto_marshal_host(NULL, 0, &golden_data[i].host); + char buf[len > 0 ? len : 1]; + + fail_unless(len == golden_data[i].expected_len, + "<%zu> sdb_proto_marshal_host(NULL, 0, %s) = %zi; expected: %zi", + i, golden_data[i].host.name, len, golden_data[i].expected_len); + + if (len < 0) + continue; + + len = sdb_proto_marshal_host(buf, sizeof(buf), &golden_data[i].host); + fail_unless(len == golden_data[i].expected_len, + "<%zu> sdb_proto_marshal_host(, %zu, %s) = %zi; expected: %zi", + i, sizeof(buf), golden_data[i].host.name, + len, golden_data[i].expected_len); + if (memcmp(buf, golden_data[i].expected, len) != 0) { + size_t pos; + for (pos = 0; pos < (size_t)len; ++pos) + if (buf[pos] != golden_data[i].expected[pos]) + break; + fail("<%zu> sdb_proto_marshal_host(%s) -> \"%s\"; expected: \"%s\" " + "(bytes %zu differ: '%x' != '%x')", + i, golden_data[i].host.name, buf, golden_data[i].expected, + pos, (int)buf[pos], (int)golden_data[i].expected[pos]); + } + } +} +END_TEST + +START_TEST(test_marshal_service) +{ + struct { + sdb_proto_service_t svc; + ssize_t expected_len; + char *expected; + } golden_data[] = { + { + { 4711, "hostA", "serviceX" }, + 27, SVC_TYPE "\0\0\0\0\0\0\x12\x67" "hostA\0serviceX\0" + }, + { + { 0, "hostA", "serviceX" }, + 27, SVC_TYPE "\0\0\0\0\0\0\0\0" "hostA\0serviceX\0" + }, + { { 4711, "hostA", NULL }, -1, NULL }, + { { 4711, NULL, "serviceX" }, -1, NULL }, + { { 4711, NULL, NULL }, -1, NULL }, + }; + + size_t i; + + for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data); ++i) { + ssize_t len = sdb_proto_marshal_service(NULL, 0, &golden_data[i].svc); + char buf[len > 0 ? len : 1]; + + fail_unless(len == golden_data[i].expected_len, + "<%zu> sdb_proto_marshal_service(NULL, 0, %s) = %zi; expected: %zi", + i, golden_data[i].svc.name, len, golden_data[i].expected_len); + + if (len < 0) + continue; + + len = sdb_proto_marshal_service(buf, sizeof(buf), &golden_data[i].svc); + fail_unless(len == golden_data[i].expected_len, + "<%zu> sdb_proto_marshal_service(, %zu, %s) = %zi; expected: %zi", + i, sizeof(buf), golden_data[i].svc.name, + len, golden_data[i].expected_len); + if (memcmp(buf, golden_data[i].expected, len) != 0) { + size_t pos; + for (pos = 0; pos < (size_t)len; ++pos) + if (buf[pos] != golden_data[i].expected[pos]) + break; + fail("<%zu> sdb_proto_marshal_service(%s) -> \"%s\"; expected: \"%s\" " + "(bytes %zu differ: '%x' != '%x')", + i, golden_data[i].svc.name, buf, golden_data[i].expected, + pos, (int)buf[pos], (int)golden_data[i].expected[pos]); + } + } +} +END_TEST + +START_TEST(test_marshal_metric) +{ + struct { + sdb_proto_metric_t metric; + ssize_t expected_len; + char *expected; + } golden_data[] = { + { + { 4711, "hostA", "metricX", NULL, NULL }, + 26, METRIC_TYPE "\0\0\0\0\0\0\x12\x67" "hostA\0metricX\0" + }, + { + { 0, "hostA", "metricX", NULL, NULL }, + 26, METRIC_TYPE "\0\0\0\0\0\0\0\0" "hostA\0metricX\0" + }, + { + { 0, "hostA", "metricX", "type", NULL }, + 26, METRIC_TYPE "\0\0\0\0\0\0\0\0" "hostA\0metricX\0" + }, + { + { 0, "hostA", "metricX", NULL, "id" }, + 26, METRIC_TYPE "\0\0\0\0\0\0\0\0" "hostA\0metricX\0" + }, + { + { 4711, "hostA", "metricX", "type", "id" }, + 34, METRIC_TYPE "\0\0\0\0\0\0\x12\x67" "hostA\0metricX\0type\0id\0" + }, + { { 4711, "hostA", NULL, NULL, NULL }, -1, NULL }, + { { 4711, NULL, "metricX", NULL, NULL }, -1, NULL }, + { { 4711, NULL, NULL, NULL, NULL }, -1, NULL }, + }; + + size_t i; + + for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data); ++i) { + ssize_t len = sdb_proto_marshal_metric(NULL, 0, &golden_data[i].metric); + char buf[len > 0 ? len : 1]; + + fail_unless(len == golden_data[i].expected_len, + "<%zu> sdb_proto_marshal_metric(NULL, 0, %s) = %zi; expected: %zi", + i, golden_data[i].metric.name, len, golden_data[i].expected_len); + + if (len < 0) + continue; + + len = sdb_proto_marshal_metric(buf, sizeof(buf), &golden_data[i].metric); + fail_unless(len == golden_data[i].expected_len, + "<%zu> sdb_proto_marshal_metric(, %zu, %s) = %zi; expected: %zi", + i, sizeof(buf), golden_data[i].metric.name, + len, golden_data[i].expected_len); + if (memcmp(buf, golden_data[i].expected, len) != 0) { + size_t pos; + for (pos = 0; pos < (size_t)len; ++pos) + if (buf[pos] != golden_data[i].expected[pos]) + break; + fail("<%zu> sdb_proto_marshal_metric(%s) -> \"%s\"; expected: \"%s\" " + "(bytes %zu differ: '%x' != '%x')", + i, golden_data[i].metric.name, buf, golden_data[i].expected, + pos, (int)buf[pos], (int)golden_data[i].expected[pos]); + } + } +} +END_TEST + Suite * util_proto_suite(void) { @@ -181,6 +351,9 @@ util_proto_suite(void) tc = tcase_create("core"); tcase_add_test(tc, test_marshal_data); + tcase_add_test(tc, test_marshal_host); + tcase_add_test(tc, test_marshal_service); + tcase_add_test(tc, test_marshal_metric); suite_add_tcase(s, tc); return s; -- 2.30.2