]> git.ipfire.org Git - thirdparty/rrdtool-1.x.git/commitdiff
rrdcached dump support (#1235)
authorTobias Hintze <thz@users.noreply.github.com>
Fri, 20 Oct 2023 21:03:19 +0000 (23:03 +0200)
committerGitHub <noreply@github.com>
Fri, 20 Oct 2023 21:03:19 +0000 (23:03 +0200)
* add missing newlines (\n) for TUNE

Help strings for TUNE command were lacking the trailing newline
character. This causes the FLUSH syntax printed in the same line as
TUNE's.

* fix sendall edge cases

The `sendall` func is implemented as a loop over multiple send() calls until all bytes are sent. Previously on subsequent calls the incorrect buffer was used for sending.
This change also forbids `allow_retry` when at least one byte was successfully sent already. This would otherwise mess up the protocol, when retrying from the start.

* rrcached: implement DUMP

* update tests for remote dump support

Previously, tests replaced the daemon-based dump use with a local use.
This is changed in this commit as dump is now supported via daemon.

* update doc/rrddump

* fix format argument type

* update to latest windows vcpkg tag

.github/workflows/ci-workflow.yml
.github/workflows/release-windows.yml
CHANGES
doc/rrddump.pod
src/rrd_client.c
src/rrd_client.h
src/rrd_daemon.c
src/rrd_dump.c
tests/functions

index c1d35ea386f9ca1cd9c2c14dc39f9d0f96c19f68..d69b6ba4b9cc02d5c505e7079779ef806ea04cd1 100644 (file)
@@ -51,14 +51,14 @@ jobs:
         include:
           - os: windows-2022
             triplet: x64-windows
-            # https://github.com/microsoft/vcpkg/commit/501db0f17ef6df184fcdbfbe0f87cde2313b6ab1
-            vcpkgCommitId: '501db0f17ef6df184fcdbfbe0f87cde2313b6ab1'
+            # https://github.com/microsoft/vcpkg/commit/8eb57355a4ffb410a2e94c07b4dca2dffbee8e50
+            vcpkgCommitId: '8eb57355a4ffb410a2e94c07b4dca2dffbee8e50'
             vcpkgPackages: 'cairo expat fontconfig freetype gettext glib libpng libxml2 pango pcre zlib'
             configuration: 'x64'
             nmake_configuration: 'USE_64BIT=1'
           - os: windows-2022
             triplet: x86-windows
-            vcpkgCommitId: '501db0f17ef6df184fcdbfbe0f87cde2313b6ab1'
+            vcpkgCommitId: '8eb57355a4ffb410a2e94c07b4dca2dffbee8e50'
             vcpkgPackages: 'cairo expat fontconfig freetype gettext glib libpng libxml2 pango pcre zlib'
             configuration: 'x86'
             nmake_configuration: ''
index bee8604822fba4440469db86cb58f356b910ef43..b8d99650f4fa935875381f7e4bb2f0af84cefbeb 100644 (file)
@@ -25,14 +25,14 @@ jobs:
         include:
           - os: windows-2022
             triplet: x64-windows
-            # https://github.com/microsoft/vcpkg/commit/501db0f17ef6df184fcdbfbe0f87cde2313b6ab1
-            vcpkgCommitId: '501db0f17ef6df184fcdbfbe0f87cde2313b6ab1'
+            # https://github.com/microsoft/vcpkg/commit/8eb57355a4ffb410a2e94c07b4dca2dffbee8e50
+            vcpkgCommitId: '8eb57355a4ffb410a2e94c07b4dca2dffbee8e50'
             vcpkgPackages: 'cairo expat fontconfig freetype gettext glib libpng libxml2 pango pcre zlib'
             configuration: 'x64'
             nmake_configuration: 'USE_64BIT=1'
           - os: windows-2022
             triplet: x86-windows
-            vcpkgCommitId: '501db0f17ef6df184fcdbfbe0f87cde2313b6ab1'
+            vcpkgCommitId: '8eb57355a4ffb410a2e94c07b4dca2dffbee8e50'
             vcpkgPackages: 'cairo expat fontconfig freetype gettext glib libpng libxml2 pango pcre zlib'
             configuration: 'x86'
             nmake_configuration: ''
diff --git a/CHANGES b/CHANGES
index e8d3239df69393ad17dc048a7cdc08e29f204545..1b6f4114fa5ec37c5d0f6f9977ccc74956a73222 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -39,6 +39,7 @@ Features
 
   rrd_updatex_r(filename, tmplt, RRD_FLAGS_LOCKING_MODE_BLOCK, ...);
 
+* Add (remote) dump support to rrdcached <Tobias Hintze>
 
 RRDtool 1.8.0 - 2022-03-13
 ==========================
index 3aaa49077ea1b245ecc056e5fb65a371067c8448..49a924ecb8ed1825a6ad59e95d3ff08b54dc55d8 100644 (file)
@@ -48,6 +48,8 @@ S<--no-header> option since 1.2 cannot deal with xml headers.
 Address of the L<rrdcached> daemon. If specified, a C<flush> command is sent
 to the server before reading the RRD files. This allows B<rrdtool> to return
 fresh data even if the daemon is configured to cache values for a long time.
+When specified the RRD filename signifies a server side file, but the output
+(XML) filename refers to the local side.
 For a list of accepted formats, see the B<-l> option in the L<rrdcached> manual.
 
  rrdtool dump --daemon unix:/var/run/rrdcached.sock /var/lib/rrd/foo.rrd
index 51ba91d66183bbdb33e5f5a769fc06ccb681d471..f4cec0d7c233d6f952c2cf5ea3919a03821ba1b1 100644 (file)
@@ -653,10 +653,11 @@ static int sendall(
         return -1;
 
     while (ret != -1 && len > 0) {
-        ret = send(client->sd, msg, len, 0);
+        ret = send(client->sd, bufp, len, 0);
         if (ret > 0) {
             bufp += ret;
             len -= ret;
+            allow_retry = 0; // partial read forbids retry
         }
     }
 
@@ -2059,6 +2060,125 @@ int rrdc_fetch(
     return status;
 }                       /* }}} int rrdc_fetch */
 
+int rrd_client_dump(
+    rrd_client_t *client,
+    const char *filename,   /* {{{ */
+    const char *opt_header,
+    rrd_output_callback_t output_cb,
+    void *cb_userdata)
+{
+    char    buffer[RRD_CMD_MAX];
+    char   *buffer_ptr;
+    size_t  buffer_free;
+    size_t  buffer_size;
+    int     status;
+    char   *file_path;
+    char    resp_buffer[256];
+
+    if (client == NULL) return -1;
+    if (filename == NULL) {
+        rrd_set_error("rrdc_dump: no input filename specified");
+        return -1;
+    }
+
+    memset(buffer, 0, sizeof(buffer));
+    buffer_ptr = &buffer[0];
+    buffer_free = sizeof(buffer);
+
+    status = buffer_add_string("dump", &buffer_ptr, &buffer_free);
+    if (status != 0) {
+        rrd_set_error("rrdc_dump: out of memory");
+        return -1;
+    }
+
+    file_path = get_path(client, filename);
+    if (file_path == NULL) {
+        return -1;
+    }
+
+    status = buffer_add_string(file_path, &buffer_ptr, &buffer_free);
+    free(file_path);
+    if (status != 0) {
+        rrd_set_error("rrdc_dump: out of memory");
+        return -1;
+    }
+
+    if (opt_header) {
+        status = buffer_add_string(opt_header, &buffer_ptr, &buffer_free);
+        if (status != 0) {
+            rrd_set_error("rrdc_dump: out of memory");
+            return -1;
+        }
+    }
+
+    /* buffer ready to send? */
+    assert(buffer_free < sizeof(buffer));
+    buffer_size = sizeof(buffer) - buffer_free;
+    assert(buffer[buffer_size - 1] == ' ');
+    buffer[buffer_size - 1] = '\n';
+
+    /* send request to rrdcached */
+    status = sendall(client, buffer, buffer_size, 1);
+    if (status == -1) {
+        rrd_set_error("rrdc_dump: socket error (%s) while talking to rrdcached", 
+                rrd_strerror(errno));
+        close_connection(client);
+        return -1;
+    }
+
+    /* receive response from rrdcached, relay to output_cb */
+    ssize_t received, written;
+    ssize_t response_len = 0;
+    while (1) {
+        received = recv(client->sd, buffer, sizeof(buffer), 0);
+        if (received == -1L) {
+            rrd_set_error("rrdc_dump: failed to recv from rrdcached: %s",
+                    rrd_strerror(errno));
+            close_connection(client);
+            return -1;
+        }
+        if (received == 0) {
+            close_connection(client);
+            break; // EOF
+        }
+        written = output_cb(buffer, received, cb_userdata);
+        if (written != received) {
+            rrd_set_error("rrdc_dump: unexpected number of bytes (%ld) "
+                    "written (output_cb)", written);
+            close_connection(client);
+            return -1;
+        }
+
+        // gather the first response bytes to detect XML response or status
+        size_t remaining_response_len =
+            ((signed) sizeof(resp_buffer) - response_len) > written ? written
+            : (signed)sizeof(resp_buffer) - response_len;
+        if (remaining_response_len > 0) { // continuously append to response buffer
+            memcpy(resp_buffer+response_len, buffer, remaining_response_len);
+            response_len += remaining_response_len;
+        }
+
+        if (response_len < 1) continue; // unlikely empty write
+
+        // handle non-xml response (error)
+        if (resp_buffer[0] != '<') {
+            char *nl = (char *) memchr((void *) resp_buffer, '\n', response_len);
+            if (nl == NULL) {
+                continue; // we did not get a line (yet)
+            }
+            *nl = '\0'; // \0 terminate at newline
+            chomp(resp_buffer); // chomp away possible \r too
+            rrd_set_error("rrdc_dump: failed to dump: %s", resp_buffer);
+            close_connection(client);
+            return -1;
+        }
+        // if the response starts with `<` an XML payload is assumed and the connection
+        // will be shutdown from the daemon to indicate EOF.
+    }
+
+    return 0;
+}                       /* }}} int rrd_client_dump */
+
 int rrd_client_tune(
     rrd_client_t *client,
     const char *filename,   /* {{{ */
@@ -2151,6 +2271,20 @@ int rrdc_tune(
     return status;
 }                       /* }}} int rrdc_tune */
 
+int rrdc_dump(
+    const char *filename,   /* {{{ */
+    const char *opt_header,
+    rrd_output_callback_t output_cb,
+    void *cb_userdata)
+{
+    mutex_lock(&lock);
+    int status =
+        rrd_client_dump(&default_client, filename, opt_header,
+                output_cb, cb_userdata);
+    mutex_unlock(&lock);
+    return status;
+}                       /* }}} int rrdc_tune */
+
 /* convenience function; if there is a daemon specified, or if we can
  * detect one from the environment, then flush the file.  Otherwise, no-op
  */
@@ -2357,5 +2491,5 @@ void rrdc_stats_free(
 }                       /* }}} void rrdc_stats_free */
 
 /*
- * vim: set sw=2 sts=2 ts=8 et fdm=marker :
+ * vim: set sw=4 sts=4 ts=4 et fdm=marker :
  */
index 1ccb7487b164b268adbc21d8c0353badfbf5f64c..207c80313387874e0845806f9f82140f388a3a08 100644 (file)
@@ -107,6 +107,9 @@ int rrd_client_tune(rrd_client_t *client, const char *filename,
     int argc,
     const char **argv);
 
+int rrd_client_dump(rrd_client_t *client, const char *filename, const char *opt_header,
+    rrd_output_callback_t output_cb, void *cb_userdata);
+
 int rrd_client_stats_get(rrd_client_t *client, rrdc_stats_t **ret_stats);
 
 /*
@@ -152,6 +155,9 @@ int rrdc_tune (const char *filename,
     int argc,
     const char **argv);
 
+int rrdc_dump (const char *filename, const char *opt_header,
+    rrd_output_callback_t output_cb, void *cb_userdata);
+
 int rrdc_fetch (const char *filename,
     const char *cf,
     time_t *ret_start, time_t *ret_end,
index 0bc296bbc9da079f1acf5e7ea13846e17d6c5b7d..751798aa1e901f56183dde63eecfcd2ce3db3198 100644 (file)
@@ -884,6 +884,35 @@ static int send_response(
     return 0;
 }                       /* }}} */
 
+/* send a chunk of bytes directly to the socket without buffering.
+ * the socket is passed as `void *user` parameter.
+ * this can be used as a callback for writes.
+ * rrd_dump_cb_r is an example use-case.
+ * returns number of bytes written on success, -1 on error
+ */
+static size_t send_unbuffered(
+    const void *data,
+    size_t len,
+    void *user)
+{                       /* {{{ */
+    size_t bytes_written=0;
+    if (!user) {
+        RRDD_LOG(LOG_INFO, "send_unbuffered: missing user pointer");
+        return -1;
+    }
+    listen_socket_t *sock = (listen_socket_t*)user;
+
+    while (bytes_written < len) {
+        ssize_t rc = write(sock->fd, (char*)data + bytes_written, len - bytes_written);
+        if (rc <= 0) {
+            RRDD_LOG(LOG_INFO, "send_unbuffered: could not write data (%d)", errno);
+            return -1;
+        }
+        bytes_written += rc;
+    }
+    return bytes_written;
+}                       /* }}} */
+
 static void wipe_ci_values(
     cache_item_t *ci,
     time_t when)
@@ -1793,6 +1822,56 @@ static int handle_request_update(
     return rc;
 }                       /* }}} int handle_request_update */
 
+static int handle_request_dump(
+    HANDLER_PROTO)
+{                       /* {{{ */
+    char *filename;
+    char *filepath;
+    int rc;
+
+    rc = buffer_get_field(&buffer, &buffer_size, &filename);
+    if (rc != 0) return syntax_error(sock, cmd);
+    filepath = get_abs_path(filename); /* absolute filename */
+    if (filepath == NULL) {
+        return send_response(sock, RESP_ERR, "%s\n", rrd_strerror(ENOMEM));
+    }
+
+    struct stat statbuf;
+    memset(&statbuf, 0, sizeof(statbuf));
+    if (stat(filepath, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) {
+        free(filepath);
+        return send_response(sock, RESP_ERR, "%s: failed to stat: %s\n",
+                filename, rrd_strerror(errno));
+    }
+
+    rc = flush_file(filepath);
+    switch (rc) {
+        case 0:
+            break; // success
+        case ENOENT:
+            break; // success - nothing to flush
+        default:
+            free(filepath);
+            return send_response(sock, RESP_ERR, "%s: failed to flush\n", filename);
+    }
+
+    rc = rrd_dump_cb_r(filepath, 1, send_unbuffered, (void*)sock);
+    if (rc != 0) {
+        RRDD_LOG(LOG_WARNING, "rrddump request for %s: failed to relay dump: %s", filepath, rrd_get_error());
+        free(filepath);
+        return send_response(sock, RESP_ERR, "%s: failed to relay dump: %s\n", filename, rrd_get_error());
+    }
+
+    RRDD_LOG(LOG_INFO, "rrddump request for %s succeeded", filepath);
+    free(filepath);
+
+    /*
+     * We return -1 here to indicate a bogus "failure".
+     * This will cause the connection to be closed and this conveys the
+     * end of the dumped XML file.
+     */
+    return -1;
+}                       /* }}} int handle_request_dump */
 
 static int handle_request_tune(
     HANDLER_PROTO)
@@ -2883,8 +2962,14 @@ static command_t list_of_commands[] = { /* {{{ */
      "TUNE",
      handle_request_tune,
      CMD_CONTEXT_CLIENT,
-     "TUNE <filename> [options]",
-     "Tunes the given file, takes the parameters as defined in rrdtool"},
+     "TUNE <filename> [options]\n",
+     "Tunes the given file, takes the parameters as defined in rrdtool.\n"},
+    {
+     "DUMP",
+     handle_request_dump,
+     CMD_CONTEXT_CLIENT,
+     "DUMP <filename> [-h none|xsd|dtd]\n",
+     "Dumps the specified RRD to XML.\n"},
     {
      "FLUSH",
      handle_request_flush,
@@ -5019,5 +5104,5 @@ int main(
 }                       /* int main */
 
 /*
- * vim: set sw=2 sts=2 ts=8 et fdm=marker :
+ * vim: set sw=4 sts=2 ts=4 et fdm=marker :
  */
index 264442947325e1125059e8561402c36470e6ed50..a4490d594eee4777b6f12ec062c1b6b7c8991d0e 100644 (file)
@@ -464,6 +464,29 @@ err_out:
 
 }
 
+static char *str_opt_xmlheader(int header) {
+    switch (header) {
+        case 1:
+            return "dtd";
+        case 2:
+            return "xsd";
+        default:
+            return "none";
+    }
+}
+
+static int parse_opt_xmlheader(const char *header) {
+    if (strcmp(header, "dtd") == 0) {
+        return 1;
+    } else if (strcmp(header, "xsd") == 0) {
+        return 2;
+    } else if (strcmp(header, "none") == 0) {
+        return 0;
+    }
+    return -1;
+}
+
+
 static size_t rrd_dump_opt_cb_fileout(
     const void *data,
     size_t len,
@@ -489,7 +512,13 @@ int rrd_dump_opt_r(
         out_file = stdout;
     }
 
-    res = rrd_dump_cb_r(filename, opt_noheader, rrd_dump_opt_cb_fileout, (void *)out_file);
+    if (rrdc_is_any_connected()) {
+        res = rrdc_dump(filename, str_opt_xmlheader(opt_noheader),
+                rrd_dump_opt_cb_fileout, (void *)out_file);
+    } else {
+        res = rrd_dump_cb_r(filename, opt_noheader,
+                rrd_dump_opt_cb_fileout, (void *)out_file);
+    }
 
     if (fflush(out_file) != 0) {
         rrd_set_error("error flushing output: %s", rrd_strerror(errno));
@@ -543,8 +572,7 @@ int rrd_dump(
                     free (opt_daemon);
             }
             opt_daemon = strdup(options.optarg);
-            if (opt_daemon == NULL)
-            {
+            if (opt_daemon == NULL) {
                 rrd_set_error ("strdup failed.");
                 return (-1);
             }
@@ -555,14 +583,8 @@ int rrd_dump(
            break;
 
         case 'h':
-          if (strcmp(options.optarg, "dtd") == 0) {
-               opt_header = 1;
-          } else if (strcmp(options.optarg, "xsd") == 0) {
-               opt_header = 2;
-          } else if (strcmp(options.optarg, "none") == 0) {
-               opt_header = 0;
-          }
-          break;
+           opt_header = parse_opt_xmlheader(options.optarg);
+           break;
 
         default:
             rrd_set_error("usage rrdtool %s [--header|-h {none,xsd,dtd}]\n"
@@ -602,3 +624,6 @@ int rrd_dump(
 
     return rc;
 }
+/*
+ * vim: set sw=4 sts=4 ts=4 et fdm=marker :
+ */
index 392239247bd639b70313012dd352bc5938531124..c88d2b6ad466da64e496a136154faf11349fa432 100644 (file)
@@ -159,13 +159,6 @@ function run_cached {
                     ARGS=( $(sed "s#${BUILDDIR}#${BASEDIR}#" <<< "${ARGS[@]}") )
                 fi
 
-                # rrdcached does not support remote dump
-                if [ $1 == "dump" ]; then
-                    RRDCACHED_STRIPPATH=${BUILDDIR} $RRDTOOL_ORIG flushcached $2 || fail flushcached
-                    ARGS=( $(sed "s#${BUILDDIR}#${BASEDIR}#" <<< "${ARGS[@]}") )
-                    RRDCACHED_STRIPPATH=${BASEDIR}
-                fi
-
                 # rrdcached does not support remote restore
                 if [ $1 == "restore" ]; then
                     ARGS=( "restore" "$2" $(sed "s#${BUILDDIR}#${BASEDIR}#" <<< $3) )