From 2c65282db8cdda0e3f43511465cc62d2ccd94238 Mon Sep 17 00:00:00 2001 From: Sven Panne Date: Tue, 30 May 2017 14:12:20 +0200 Subject: [PATCH] Added SUSPEND/RESUME/SUSPENDALL/RESUMEALL commands. Suspending/resuming is essential to make e.g. backups of RRDs while rrdcached is running. --- doc/rrdcached.pod | 20 +++++++ src/rrd_daemon.c | 146 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 164 insertions(+), 2 deletions(-) diff --git a/doc/rrdcached.pod b/doc/rrdcached.pod index bf022a39..f5629db2 100644 --- a/doc/rrdcached.pod +++ b/doc/rrdcached.pod @@ -749,6 +749,26 @@ message itself. The first user command after B is command number one. This command allows to list directories and rrd databases as seen by the daemon. The root "directory" is the base_dir (see '-b dir'). When invoked with 'LIST RECURSIVE /' it will behave similarly to 'ls -R' but limited to rrd files (listing all the rrd bases in the subtree of , skipping empty directories). +=item B I + +Suspend writing to an RRD file. While a file is suspended, all metrics for it +are cached in memory until B is called for that file or B is +called. + +=item B I + +Resume writing to an RRD file previously suspended by B or B. + +=item B + +Suspend writing to all RRD files. While a file is suspended, all metrics for it +are cached in memory until B is called for that file or B is +called. + +=item B + +Resume writing to all RRD files previously suspended by B or B. + =item B Disconnect from rrdcached. diff --git a/src/rrd_daemon.c b/src/rrd_daemon.c index 83925a12..1b30c187 100644 --- a/src/rrd_daemon.c +++ b/src/rrd_daemon.c @@ -197,6 +197,7 @@ struct cache_item_s double last_update_stamp; #define CI_FLAGS_IN_TREE (1<<0) #define CI_FLAGS_IN_QUEUE (1<<1) +#define CI_FLAGS_SUSPENDED (1<<2) int flags; pthread_cond_t flushed; cache_item_t *prev; @@ -952,7 +953,8 @@ static gboolean tree_callback_flush (gpointer key, gpointer value, /* {{{ */ return FALSE; if (ci->values_num > 0 - && (ci->last_flush_time <= cfd->abs_timeout || state != RUNNING)) + && (ci->last_flush_time <= cfd->abs_timeout || state != RUNNING) + && ((ci->flags & CI_FLAGS_SUSPENDED) == 0)) { enqueue_cache_item (ci, TAIL); } @@ -1274,7 +1276,8 @@ static int flush_file (const char *filename) /* {{{ */ return (ENOENT); } - if (ci->values_num > 0) + if ((ci->values_num > 0) + && ((ci->flags & CI_FLAGS_SUSPENDED) == 0)) { /* Enqueue at head */ enqueue_cache_item (ci, HEAD); @@ -1700,6 +1703,7 @@ static int handle_request_update (HANDLER_PROTO) /* {{{ */ if (((now - ci->last_flush_time) >= config_write_interval) && ((ci->flags & CI_FLAGS_IN_QUEUE) == 0) + && ((ci->flags & CI_FLAGS_SUSPENDED) == 0) && (ci->values_num > 0)) { enqueue_cache_item (ci, TAIL); @@ -2578,6 +2582,111 @@ out_send_response: return (0); } /* }}} int handle_request_list */ +static cache_item_t *buffer_get_cache_item(listen_socket_t *sock, + command_t *cmd, char **buffer, + size_t *buffer_size, int *rc, + char **file_name) +{ + char *pbuffile; + cache_item_t *ci; + int status; + + /* obtain filename */ + status = buffer_get_field(buffer, buffer_size, &pbuffile); + if (status != 0) { + *rc = syntax_error(sock, cmd); + return NULL; + } + /* get full pathname */ + *file_name = get_abs_path(pbuffile); + if (file_name == NULL) { + *rc = send_response(sock, RESP_ERR, "%s + %s\n", *file_name, rrd_strerror(ENOMEM)); + return NULL; + } + + ci = g_tree_lookup(cache_tree, *file_name); + if (ci == NULL) { + *rc = send_response(sock, RESP_ERR, "%s - %s\n", *file_name, rrd_strerror(ENOENT)); + return NULL; + } + + *rc = 0; + return ci; +} + +static int handle_request_suspend(HANDLER_PROTO) /* {{{ */ +{ + char *file_name = NULL; + int rc; + cache_item_t *ci = buffer_get_cache_item(sock, cmd, &buffer, &buffer_size, &rc, &file_name); + if (ci == NULL) + rc = -1; + else if ((ci->flags & CI_FLAGS_SUSPENDED) == CI_FLAGS_SUSPENDED) + rc = send_response(sock, RESP_OK, "%s already suspended\n", file_name); + else + { + ci->flags |= CI_FLAGS_SUSPENDED; + rc = send_response(sock, RESP_OK, "%s suspended\n", file_name); + } + free(file_name); + return rc; +} /* }}} static int handle_request_suspend */ + +static int handle_request_resume (HANDLER_PROTO) /* {{{ */ +{ + char *file_name = NULL; + int rc; + cache_item_t *ci = buffer_get_cache_item(sock, cmd, &buffer, &buffer_size, &rc, &file_name); + if (ci == NULL) + rc = -1; + else if ((ci->flags & CI_FLAGS_SUSPENDED) == 0) + rc = send_response(sock, RESP_OK, "%s not suspended\n", file_name); + else + { + ci->flags &= ~CI_FLAGS_SUSPENDED; + rc = send_response(sock, RESP_OK, "%s resumed\n", file_name); + } + free(file_name); + return rc; +} /* }}} static int handle_request_resume */ + +static gboolean tree_callback_suspend (gpointer UNUSED(key), /* {{{ */ + gpointer value, gpointer pointer) +{ + cache_item_t *ci = (cache_item_t *) value; + int *count = (int*) pointer; + if ((ci->flags & CI_FLAGS_SUSPENDED) == 0) { + ci->flags |= CI_FLAGS_SUSPENDED; + *count += 1; + } + return (FALSE); +} /* }}} gboolean tree_callback_suspend */ + +static int handle_request_suspendall(HANDLER_PROTO) /* {{{ */ +{ + int count = 0; + g_tree_foreach (cache_tree, tree_callback_suspend, (gpointer) &count); + return send_response(sock, RESP_OK, "%d rrds suspend\n", count); +} /* }}} static int handle_request_suspendall */ + +static gboolean tree_callback_resume (gpointer UNUSED(key), /* {{{ */ + gpointer value, gpointer pointer) +{ + cache_item_t *ci = (cache_item_t *) value; + int *count = (int*) pointer; + if ((ci->flags & CI_FLAGS_SUSPENDED) != 0) { + ci->flags &= ~CI_FLAGS_SUSPENDED; + *count += 1; + } + return (FALSE); +} /* }}} gboolean tree_callback_resume */ + +static int handle_request_resumeall(HANDLER_PROTO) /* {{{ */ +{ + int count = 0; + g_tree_foreach (cache_tree, tree_callback_resume, (gpointer) &count); + return send_response(sock, RESP_OK, "%d rrds resumed\n", count); +} /* }}} static int handle_request_resumeall */ /* start "BATCH" processing */ static int batch_start (HANDLER_PROTO) /* {{{ */ @@ -2801,6 +2910,39 @@ static command_t list_of_commands[] = { /* {{{ */ "'ls -R' but limited to rrd files (listing all the rrd bases in the subtree\n" " of , skipping empty directories).\n" }, + { + "SUSPEND", + handle_request_suspend, + CMD_CONTEXT_CLIENT | CMD_CONTEXT_BATCH, + "SUSPEND \n", + "The SUSPEND command will suspend writing to an RRD file. While a file is\n" + "suspended, all metrics for it are cached in memory until RESUME is called\n" + "for that file or RESUMEALL is called.\n" + }, + { + "RESUME", + handle_request_resume, + CMD_CONTEXT_CLIENT | CMD_CONTEXT_BATCH, + "RESUME \n", + "The RESUME command will resume writing to an RRD file previously suspended\n" + "by SUSPEND or SUSPENDALL.\n" + }, + { + "SUSPENDALL", + handle_request_suspendall, + CMD_CONTEXT_CLIENT | CMD_CONTEXT_BATCH, + "SUSPENDALL\n", + "The SUSPENDALL command will suspend writing to all RRD files. While a file\n" + "is suspended, all metrics for it are cached in memory until RESUME is called\n" + "for that file or RESUMEALL is called.\n" + }, + { + "RESUMEALL", + handle_request_resumeall, + CMD_CONTEXT_CLIENT | CMD_CONTEXT_BATCH, + "RESUMEALL\n", + "The RESUMEALL command will resume writing to all RRD files previously suspended.\n" + }, { "QUIT", handle_request_quit, -- 2.47.2