src/utils/cmds/getval.h \
src/utils/cmds/listval.c \
src/utils/cmds/listval.h \
+ src/utils/cmds/putmetric.c \
+ src/utils/cmds/putmetric.h \
src/utils/cmds/putnotif.c \
src/utils/cmds/putnotif.h \
src/utils/cmds/putval.c \
src/testing.h
test_utils_cmds_LDADD = \
libcmds.la \
+ libmetric.la \
libplugin_mock.la
liblookup_la_SOURCES = \
int plugin_dispatch_values(value_list_t const *vl) { return ENOTSUP; }
+int plugin_dispatch_metric_family(metric_family_t const *fam) { return ENOTSUP; }
+
int plugin_dispatch_notification(__attribute__((unused))
const notification_t *notif) {
return ENOTSUP;
return ENOTSUP;
}
+int uc_get_rate_by_name(const char *name, gauge_t *ret_value) {
+ return ENOTSUP;
+}
+
int uc_get_rate_by_name_vl(const char *name, gauge_t **ret_values,
size_t *ret_values_num) {
return ENOTSUP;
#include "utils/cmds/listval.h"
#include "utils/cmds/parse_option.h"
#include "utils/cmds/putval.h"
+#include "utils/cmds/putmetric.h"
#include "utils/common/common.h"
#include <stdbool.h>
ret_cmd->type = CMD_PUTVAL;
status =
cmd_parse_putval(argc - 1, argv + 1, &ret_cmd->cmd.putval, opts, err);
+ } else if (strcasecmp("PUTMETRIC", command) == 0) {
+ ret_cmd->type = CMD_PUTMETRIC;
+ status =
+ cmd_parse_putmetric(argc - 1, argv + 1, &ret_cmd->cmd.putmetric, opts, err);
} else {
ret_cmd->type = CMD_UNKNOWN;
cmd_error(CMD_UNKNOWN_COMMAND, err, "Unknown command `%s'.", command);
case CMD_PUTVAL:
cmd_destroy_putval(&cmd->cmd.putval);
break;
+ case CMD_PUTMETRIC:
+ cmd_destroy_putmetric(&cmd->cmd.putmetric);
+ break;
}
} /* void cmd_destroy */
CMD_GETVAL = 2,
CMD_LISTVAL = 3,
CMD_PUTVAL = 4,
+ CMD_PUTMETRIC = 5,
} cmd_type_t;
#define CMD_TO_STRING(type) \
((type) == CMD_FLUSH) \
? "GETVAL" \
: ((type) == CMD_LISTVAL) \
? "LISTVAL" \
- : ((type) == CMD_PUTVAL) ? "PUTVAL" : "UNKNOWN"
+ : ((type) == CMD_PUTVAL) ? "PUTVAL" \
+ : ((type) == CMD_PUTMETRIC) ? "PUTMETRIC" : "UNKNOWN"
typedef struct {
double timeout;
metric_family_t *family;
} cmd_putval_t;
+typedef struct {
+ /* Depending on the function, this is an input or output field:
+ *
+ * cmd_parse_putmetric:
+ * OUTPUT Receives parsed metric information. The metric family will
+ * contain a single metric.
+ * cmd_create_putmetric:
+ * INPUT Holds the metrics for which to format the PUTVAL command. The
+ * metric family may contain multiple metrics.
+ */
+ metric_family_t *family;
+} cmd_putmetric_t;
+
/*
* NAME
* cmd_t
cmd_flush_t flush;
cmd_getval_t getval;
cmd_putval_t putval;
+ cmd_putmetric_t putmetric;
} cmd;
} cmd_t;
* Explicit order is required or _FILE_OFFSET_BITS will have definition mismatches on Solaris
* See Github Issue #3193 for details
*/
+#include "utils/cmds/cmds.h"
+
#include "utils/common/common.h"
+#include "utils/strbuf/strbuf.h"
#include "testing.h"
-#include "utils/cmds/cmds.h"
+#include "utils/cmds/putmetric.h"
// clang-format on
static void error_cb(void *ud, cmd_status_t status, const char *format,
if (status == CMD_OK)
return;
- printf("ERROR[%d]: ", status);
- vprintf(format, ap);
- printf("\n");
- fflush(stdout);
+ strbuf_t *buf = ud;
+
+ strbuf_printf(buf, "ERROR[%d]: ", status);
+
+ va_list ap_copy;
+ va_copy(ap_copy, ap);
+
+ int size = vsnprintf(NULL, 0, format, ap_copy);
+ assert(size > 0);
+
+ char buffer[size];
+ vsnprintf(buffer, sizeof(buffer), format, ap);
+
+ strbuf_print(buf, buffer);
} /* void error_cb */
static cmd_options_t default_host_opts = {
},
/* Valid PUTVAL commands. */
+ {
+ "PUTVAL unit_test N:42",
+ &default_host_opts,
+ CMD_OK,
+ CMD_PUTVAL,
+ },
{
"PUTVAL magic/MAGIC N:42",
&default_host_opts,
},
*/
+ /* Valid PUTMETRIC commands. */
+ {
+ "PUTMETRIC unit_test 42",
+ NULL,
+ CMD_OK,
+ CMD_PUTMETRIC,
+ },
+ {
+ "PUTMETRIC gauge type=GAUGE 42",
+ NULL,
+ CMD_OK,
+ CMD_PUTMETRIC,
+ },
+ {
+ "PUTMETRIC counter type=Counter 42",
+ NULL,
+ CMD_OK,
+ CMD_PUTMETRIC,
+ },
+ {
+ "PUTMETRIC untyped type=untyped 42",
+ NULL,
+ CMD_OK,
+ CMD_PUTMETRIC,
+ },
+ {
+ "PUTMETRIC quoted_gauge type=\"GAUGE\" 42",
+ NULL,
+ CMD_OK,
+ CMD_PUTMETRIC,
+ },
+ {
+ "PUTMETRIC with_interval interval=10.0 42",
+ NULL,
+ CMD_OK,
+ CMD_PUTMETRIC,
+ },
+ {
+ "PUTMETRIC with_time time=1594806526 42",
+ NULL,
+ CMD_OK,
+ CMD_PUTMETRIC,
+ },
+ {
+ "PUTMETRIC with_label label:unquoted=bare 42",
+ NULL,
+ CMD_OK,
+ CMD_PUTMETRIC,
+ },
+ {
+ "PUTMETRIC with_label label:quoted=\"with space\" 42",
+ NULL,
+ CMD_OK,
+ CMD_PUTMETRIC,
+ },
+ {
+ "PUTMETRIC multiple_label label:foo=1 label:bar=2 42",
+ NULL,
+ CMD_OK,
+ CMD_PUTMETRIC,
+ },
+
/* Invalid commands. */
{
"INVALID",
};
DEF_TEST(parse) {
- cmd_error_handler_t err = {error_cb, NULL};
int test_result = 0;
for (size_t i = 0; i < STATIC_ARRAY_SIZE(parse_data); i++) {
char *input = strdup(parse_data[i].input);
- char description[1024];
- cmd_status_t status;
- cmd_t cmd;
-
- bool result;
+ strbuf_t errbuf = STRBUF_CREATE;
+ cmd_error_handler_t err = {error_cb, &errbuf};
- memset(&cmd, 0, sizeof(cmd));
+ cmd_t cmd = {0};
+ cmd_status_t status = cmd_parse(input, &cmd, parse_data[i].opts, &err);
- status = cmd_parse(input, &cmd, parse_data[i].opts, &err);
+ char description[1024];
ssnprintf(description, sizeof(description),
"cmd_parse (\"%s\", opts=%p) = "
"%d (type=%d [%s]); want %d "
CMD_TO_STRING(cmd.type), parse_data[i].expected_status,
parse_data[i].expected_type,
CMD_TO_STRING(parse_data[i].expected_type));
- result = (status == parse_data[i].expected_status) &&
- (cmd.type == parse_data[i].expected_type);
+
+ bool result = (status == parse_data[i].expected_status) &&
+ (cmd.type == parse_data[i].expected_type);
+
+ if (errbuf.ptr != NULL) {
+ printf("error buffer = \"%s\"\n", errbuf.ptr);
+ }
+
LOG(result, description);
/* Run all tests before failing. */
return test_result;
}
+DEF_TEST(format_putmetric) {
+ struct {
+ metric_t m;
+ char *want;
+ int want_err;
+ } cases[] = {
+ {
+ .m =
+ {
+ .family =
+ &(metric_family_t){
+ .name = "test",
+ .type = METRIC_TYPE_UNTYPED,
+ },
+ .value.gauge = 42,
+ },
+ .want = "PUTMETRIC test 42",
+ },
+ {
+ .m =
+ {
+ .family =
+ &(metric_family_t){
+ .name = "test",
+ .type = METRIC_TYPE_GAUGE,
+ },
+ .value.gauge = 42,
+ },
+ .want = "PUTMETRIC test type=GAUGE 42",
+ },
+ {
+ .m =
+ {
+ .family =
+ &(metric_family_t){
+ .name = "test",
+ .type = METRIC_TYPE_COUNTER,
+ },
+ .value.counter = 42,
+ },
+ .want = "PUTMETRIC test type=COUNTER 42",
+ },
+ {
+ .m =
+ {
+ .family =
+ &(metric_family_t){
+ .name = "test",
+ .type = METRIC_TYPE_UNTYPED,
+ },
+ .value.gauge = 42,
+ .time = TIME_T_TO_CDTIME_T(1594809888),
+ },
+ .want = "PUTMETRIC test time=1594809888.000 42",
+ },
+ {
+ .m =
+ {
+ .family =
+ &(metric_family_t){
+ .name = "test",
+ .type = METRIC_TYPE_UNTYPED,
+ },
+ .value.gauge = 42,
+ .interval = TIME_T_TO_CDTIME_T(10),
+ },
+ .want = "PUTMETRIC test interval=10.000 42",
+ },
+ {
+ .m =
+ {
+ .family =
+ &(metric_family_t){
+ .name = "test",
+ .type = METRIC_TYPE_UNTYPED,
+ },
+ .value.gauge = 42,
+ .label.ptr =
+ &(label_pair_t){
+ .name = "foo",
+ .value = "with \"quotes\"",
+ },
+ .label.num = 1,
+ },
+ .want = "PUTMETRIC test label:foo=\"with \\\"quotes\\\"\" 42",
+ },
+ };
+
+ for (size_t i = 0; i < STATIC_ARRAY_SIZE(cases); i++) {
+ strbuf_t buf = STRBUF_CREATE;
+
+ EXPECT_EQ_INT(cases[i].want_err, cmd_format_putmetric(&buf, &cases[i].m));
+ if (cases[i].want_err) {
+ STRBUF_DESTROY(buf);
+ continue;
+ }
+
+ EXPECT_EQ_STR(cases[i].want, buf.ptr);
+
+ STRBUF_DESTROY(buf);
+ }
+
+ return 0;
+}
+
int main(int argc, char **argv) {
RUN_TEST(parse);
+ RUN_TEST(format_putmetric);
END_TEST;
}
--- /dev/null
+/**
+ * collectd - src/utils_cmd_putmetric.c
+ * Copyright (C) 2007-2009 Florian octo Forster
+ * Copyright (C) 2016 Sebastian tokkee Harl
+ *
+ * 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.
+ *
+ * Authors:
+ * Florian octo Forster <octo at collectd.org>
+ * Sebastian tokkee Harl <sh at tokkee.org>
+ **/
+
+#include "collectd.h"
+
+#include "utils/cmds/putmetric.h"
+#include "utils/common/common.h"
+
+/*
+ * private helper functions
+ */
+
+/* TODO(octo): add an option to set metric->value_type */
+static int set_option(metric_t *m, char const *key, char const *value,
+ cmd_error_handler_t *err) {
+ if ((m == NULL) || (key == NULL) || (value == NULL))
+ return -1;
+
+ printf("set_option(\"%s\", \"%s\")\n", key, value);
+
+ if (strcasecmp("type", key) == 0) {
+ if (strcasecmp("GAUGE", value) == 0) {
+ m->family->type = METRIC_TYPE_GAUGE;
+ } else if (strcasecmp("COUNTER", value) == 0) {
+ m->family->type = METRIC_TYPE_COUNTER;
+ } else if (strcasecmp("UNTYPED", value) == 0) {
+ m->family->type = METRIC_TYPE_UNTYPED;
+ } else {
+ return CMD_ERROR;
+ }
+ } else if (strcasecmp("interval", key) == 0) {
+ errno = 0;
+ char *endptr = NULL;
+ double d = strtod(value, &endptr);
+
+ if ((errno != 0) || (endptr == NULL) || (*endptr != 0) || (d < 0)) {
+ return CMD_ERROR;
+ }
+ m->interval = DOUBLE_TO_CDTIME_T(d);
+ } else if (strcasecmp("time", key) == 0) {
+ errno = 0;
+ char *endptr = NULL;
+ double d = strtod(value, &endptr);
+
+ if ((errno != 0) || (endptr == NULL) || (*endptr != 0) || (d < 0)) {
+ return CMD_ERROR;
+ }
+ m->time = DOUBLE_TO_CDTIME_T(d);
+ } else if (strncasecmp("label:", key, 5) == 0) {
+ char const *name = key + strlen("label:");
+ return metric_label_set(m, name, value) ? CMD_ERROR : CMD_OK;
+ } else {
+ return CMD_ERROR;
+ }
+ return CMD_OK;
+} /* int set_option */
+
+/*
+ * public API
+ */
+
+cmd_status_t cmd_parse_putmetric(size_t argc, char **argv,
+ cmd_putmetric_t *ret_putmetric,
+ __attribute__((unused))
+ cmd_options_t const *opts,
+ cmd_error_handler_t *errhndl) {
+ if ((argc < 2) || (argv == NULL) || (ret_putmetric == NULL)) {
+ errno = EINVAL;
+ cmd_error(CMD_ERROR, errhndl, "Invalid arguments to cmd_parse_putmetric.");
+ return CMD_ERROR;
+ }
+
+ if (argc < 2) {
+ cmd_error(CMD_PARSE_ERROR, errhndl,
+ "Missing identifier and/or value-list.");
+ return CMD_PARSE_ERROR;
+ }
+
+ metric_family_t *fam = calloc(1, sizeof(*fam));
+ if (fam == NULL) {
+ cmd_error(CMD_ERROR, errhndl, "calloc failed");
+ return CMD_ERROR;
+ }
+ fam->type = METRIC_TYPE_UNTYPED;
+
+ int status = metric_family_metric_append(fam, (metric_t){0});
+ if (status != 0) {
+ return CMD_ERROR;
+ }
+ metric_t *m = fam->metric.ptr;
+
+ int next_pos = 0;
+ cmd_status_t result = CMD_OK;
+ for (size_t i = 0; i < argc; ++i) {
+ char *key = NULL;
+ char *value = NULL;
+
+ int status = cmd_parse_option(argv[i], &key, &value, errhndl);
+ if (status == CMD_OK) {
+ assert(key != NULL);
+ assert(value != NULL);
+
+ result = set_option(m, key, value, errhndl);
+ if (result != CMD_OK) {
+ break;
+ }
+ continue;
+ } else if (status == CMD_NO_OPTION) {
+ /* Positional argument */
+ if (next_pos == 0) {
+ fam->name = strdup(argv[i]);
+ if (fam->name == NULL) {
+ cmd_error(CMD_ERROR, errhndl, "calloc failed");
+ result = CMD_ERROR;
+ break;
+ }
+ next_pos++;
+ continue;
+ } else if (next_pos == 1) {
+ int status = parse_value(argv[i], &m->value, fam->type);
+ if (status != 0) {
+ cmd_error(CMD_ERROR, errhndl, "parse_value failed");
+ result = CMD_ERROR;
+ break;
+ }
+ next_pos++;
+ continue;
+ } else {
+ /* error is handled after the loop */
+ next_pos++;
+ continue;
+ }
+ } else {
+ /* parse_option failed, buffer has been modified.
+ * => we need to abort */
+ result = status;
+ break;
+ }
+ }
+
+ if ((result == CMD_OK) && (next_pos != 2)) {
+ char errmsg[256];
+ snprintf(errmsg, sizeof(errmsg),
+ "Found %d positional argument(s), expected 2.", next_pos);
+ cmd_error(CMD_PARSE_ERROR, errhndl, errmsg);
+ result = CMD_ERROR;
+ }
+
+ if (result != CMD_OK) {
+ metric_family_free(fam);
+ return result;
+ }
+
+ *ret_putmetric = (cmd_putmetric_t){
+ .family = fam,
+ };
+ return CMD_OK;
+} /* cmd_status_t cmd_parse_putmetric */
+
+void cmd_destroy_putmetric(cmd_putmetric_t *putmetric) {
+ if (putmetric == NULL)
+ return;
+
+ metric_family_free(putmetric->family);
+
+ (*putmetric) = (cmd_putmetric_t){0};
+} /* void cmd_destroy_putmetric */
+
+cmd_status_t cmd_handle_putmetric(FILE *fh, char *buffer) {
+ cmd_error_handler_t err = {cmd_error_fh, fh};
+
+ DEBUG("utils_cmd_putmetric: cmd_handle_putmetric (fh = %p, buffer = %s);",
+ (void *)fh, buffer);
+
+ cmd_t cmd = {0};
+ int status;
+ if ((status = cmd_parse(buffer, &cmd, NULL, &err)) != CMD_OK)
+ return status;
+ if (cmd.type != CMD_PUTVAL) {
+ cmd_error(CMD_UNKNOWN_COMMAND, &err, "Unexpected command: `%s'.",
+ CMD_TO_STRING(cmd.type));
+ cmd_destroy(&cmd);
+ return CMD_UNKNOWN_COMMAND;
+ }
+
+ status = plugin_dispatch_metric_family(cmd.cmd.putmetric.family);
+ if (status != 0) {
+ cmd_error(CMD_ERROR, &err,
+ "plugin_dispatch_metric_list failed with status %d.", status);
+ cmd_destroy(&cmd);
+ return CMD_ERROR;
+ }
+
+ if (fh != stdout) {
+ cmd_putmetric_t *putmetric = &cmd.cmd.putmetric;
+ size_t n = putmetric->family->metric.num;
+ cmd_error(CMD_OK, &err, "Success: %zu %s been dispatched.", n,
+ (n == 1) ? "metric has" : "metrics have");
+ }
+
+ cmd_destroy(&cmd);
+ return CMD_OK;
+} /* int cmd_handle_putmetric */
+
+/* TODO(octo): Improve the readability of the command.
+ *
+ * Currently, this assumes lines similar to:
+ *
+ * PUTVAL "metric_name{key=\"value\"}" interval=10.000 42
+ *
+ * Encoding the labels in this way generates a lot of escaped quotes, which is
+ * not ideal. An alternative representation would be:
+ *
+ * PUTVAL metric_name label:key="value" interval=10.000 42
+ */
+int cmd_format_putmetric(strbuf_t *buf, metric_t const *m) { /* {{{ */
+ if ((buf == NULL) || (m == NULL)) {
+ return EINVAL;
+ }
+
+ strbuf_print(buf, "PUTMETRIC ");
+ strbuf_print(buf, m->family->name);
+ switch (m->family->type) {
+ case METRIC_TYPE_UNTYPED:
+ /* no op */
+ break;
+ case METRIC_TYPE_COUNTER:
+ strbuf_print(buf, " type=COUNTER");
+ break;
+ case METRIC_TYPE_GAUGE:
+ strbuf_print(buf, " type=GAUGE");
+ break;
+ default:
+ return EINVAL;
+ }
+
+ if (m->time != 0) {
+ strbuf_printf(buf, " time=%.3f", CDTIME_T_TO_DOUBLE(m->time));
+ }
+ if (m->interval != 0) {
+ strbuf_printf(buf, " interval=%.3f", CDTIME_T_TO_DOUBLE(m->interval));
+ }
+
+ for (size_t i = 0; i < m->label.num; i++) {
+ label_pair_t *l = m->label.ptr + i;
+ strbuf_printf(buf, " label:%s=\"", l->name);
+ strbuf_print_escaped(buf, l->value, "\\\"\n\r\t", '\\');
+ strbuf_print(buf, "\"");
+ }
+
+ strbuf_print(buf, " ");
+ return value_marshal_text(buf, m->value, m->family->type);
+} /* }}} int cmd_format_putmetric */
--- /dev/null
+/**
+ * collectd - src/utils_cmd_putval.h
+ * Copyright (C) 2007–2020 Florian octo Forster
+ *
+ * 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.
+ *
+ * Authors:
+ * Florian octo Forster <octo at collectd.org>
+ **/
+
+#ifndef UTILS_CMD_PUTMETRIC_H
+#define UTILS_CMD_PUTMETRIC_H 1
+
+#include "plugin.h"
+#include "utils/cmds/cmds.h"
+
+#include <stdio.h>
+
+cmd_status_t cmd_parse_putmetric(size_t argc, char **argv,
+ cmd_putmetric_t *ret_putmetric,
+ const cmd_options_t *opts,
+ cmd_error_handler_t *err);
+
+cmd_status_t cmd_handle_putmetric(FILE *fh, char *buffer);
+
+void cmd_destroy_putmetric(cmd_putmetric_t *putmetric);
+
+int cmd_format_putmetric(strbuf_t *buf, metric_t const *m);
+
+#endif /* UTILS_CMD_PUTMETRIC_H */
}
char *identifier = strdup(argv[0]);
- if (ret_putval->raw_identifier == NULL) {
+ if (identifier == NULL) {
cmd_error(CMD_ERROR, errhndl, "malloc failed.");
return CMD_ERROR;
}