@cryptotaxi247 / netdata-1 / commits / 16f835489

Implement netdata command server and cli tool (#7325)

* Checkpoint commit (POC) * Implemented command server in the daemon * Add netdatacli implementation * Added prints in command server setup functions * Make libuv version 1 a hard dependency for the agent * Additional documentation * Improved accuracy of names and documentation * Fixed documentation * Fixed buffer overflow * Added support for exit status in cli. Added prefixes for exit code, stdout and stderr. Fixed parsers. * Fix compilation errors * Fix compile errors * Fix compile errors * Fix compile error * Fix linker error for muslc

Markos Fountoulakis committed Dec 5, 2019 at 00:21 UTC 16f835489c1a93b5dbd84747c52c883f43b716f0
21 files changed +940 -23
.gitignore
+1
@@ -37,6 +37,7 @@ sha256sums.txt
37
38 # netdata binaries
39 netdata
40 +netdatacli
41 !netdata/
42 upload/
43 artifacts/
CMakeLists.txt
+17
@@ -628,6 +628,8 @@ set(DAEMON_FILES
628 daemon/main.h
629 daemon/signals.c
630 daemon/signals.h
631 + daemon/commands.c
632 + daemon/commands.h
633 daemon/unit_test.c
634 daemon/unit_test.h
635 )
@@ -648,6 +650,12 @@ set(NETDATA_FILES
650 ${WEB_PLUGIN_FILES}
651 )
652
653 +set(NETDATACLI_FILES
654 + daemon/commands.h
655 + cli/cli.c
656 + cli/cli.h
657 + )
658 +
659 include_directories(AFTER .)
660
661 add_definitions(
@@ -784,6 +792,15 @@ ELSE()
792 ENDIF()
793
794
795 +# -----------------------------------------------------------------------------
796 +# netdatacli
797 +
798 +add_executable(netdatacli config.h ${NETDATACLI_FILES})
799 +target_link_libraries (netdatacli libnetdata ${NETDATA_COMMON_LIBRARIES})
800 +target_include_directories(netdatacli PUBLIC ${NETDATA_COMMON_INCLUDE_DIRS})
801 +target_compile_options(netdatacli PUBLIC ${NETDATA_COMMON_CFLAGS})
802 +
803 +
804 # -----------------------------------------------------------------------------
805 # apps.plugin
806
Makefile.am
+20
@@ -482,6 +482,8 @@ DAEMON_FILES = \
482 daemon/main.h \
483 daemon/signals.c \
484 daemon/signals.h \
485 + daemon/commands.c \
486 + daemon/commands.h \
487 daemon/unit_test.c \
488 daemon/unit_test.h \
489 $(NULL)
@@ -537,6 +539,13 @@ NETDATA_COMMON_LIBS = \
539 $(OPTIONAL_JSONC_LIBS) \
540 $(NULL)
541
542 +NETDATACLI_FILES = \
543 + daemon/commands.h \
544 + $(LIBNETDATA_FILES) \
545 + cli/cli.c \
546 + cli/cli.h \
547 + $(NULL)
548 +
549 sbin_PROGRAMS += netdata
550 netdata_SOURCES = $(NETDATA_FILES)
551 netdata_LDADD = \
@@ -548,6 +557,17 @@ else
557 netdata_LINK = $(CCLD) $(CFLAGS) $(LDFLAGS) -o $@
558 endif
559
560 +sbin_PROGRAMS += netdatacli
561 +netdatacli_SOURCES = $(NETDATACLI_FILES)
562 +netdatacli_LDADD = \
563 + $(NETDATA_COMMON_LIBS) \
564 + $(NULL)
565 +if ENABLE_CXX_LINKER
566 + netdatacli_LINK = $(CXXLD) $(CXXFLAGS) $(LDFLAGS) -o $@
567 +else
568 + netdatacli_LINK = $(CCLD) $(CFLAGS) $(LDFLAGS) -o $@
569 +endif
570 +
571 if ENABLE_PLUGIN_APPS
572 plugins_PROGRAMS += apps.plugin
573 apps_plugin_SOURCES = $(APPS_PLUGIN_FILES)
cli/Makefile.am new
+8
@@ -0,0 +1,8 @@
1 +# SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +AUTOMAKE_OPTIONS = subdir-objects
4 +MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
5 +
6 +dist_noinst_DATA = \
7 + README.md \
8 + $(NULL)
cli/README.md new
+26
@@ -0,0 +1,26 @@
1 +# Netdata cli
2 +
3 +You can see the commands netdatacli supports by executing it with `netdatacli` and entering `help` in
4 +standard input. All commands are given as standard input to `netdatacli`.
5 +
6 +The commands that a running netdata agent can execute are the following:
7 +
8 +```sh
9 +The commands are (arguments are in brackets):
10 +help
11 + Show this help menu.
12 +reload-health
13 + Reload health configuration.
14 +save-database
15 + Save internal DB to disk for for memory mode save.
16 +reopen-logs
17 + Close and reopen log files.
18 +shutdown-agent
19 + Cleanup and exit the netdata agent.
20 +fatal-agent
21 + Log the state and halt the netdata agent.
22 +```
23 +
24 +Those commands are the same that can be sent to netdata via [signals](../daemon#command-line-options).
25 +
26 +[![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Fcli%2FREADME&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)](<>)
cli/cli.c new
+201
@@ -0,0 +1,201 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "cli.h"
4 +#include "../libnetdata/required_dummies.h"
5 +
6 +static uv_pipe_t client_pipe;
7 +static uv_write_t write_req;
8 +static uv_shutdown_t shutdown_req;
9 +
10 +static char command_string[MAX_COMMAND_LENGTH];
11 +static unsigned command_string_size;
12 +
13 +static char response_string[MAX_COMMAND_LENGTH];
14 +static unsigned response_string_size;
15 +
16 +static int exit_status;
17 +
18 +struct command_context {
19 + uv_work_t work;
20 + uv_stream_t *client;
21 + cmd_t idx;
22 + char *args;
23 + char *message;
24 + cmd_status_t status;
25 +};
26 +
27 +static void parse_command_reply(void)
28 +{
29 + FILE *stream = NULL;
30 + char *pos;
31 + int syntax_error = 0;
32 +
33 + for (pos = response_string ;
34 + pos < response_string + response_string_size && !syntax_error ;
35 + ++pos) {
36 + /* Skip white-space characters */
37 + for ( ; isspace(*pos) && ('\0' != *pos); ++pos) {;}
38 +
39 + if ('\0' == *pos)
40 + continue;
41 +
42 + switch (*pos) {
43 + case CMD_PREFIX_EXIT_CODE:
44 + exit_status = atoi(++pos);
45 + break;
46 + case CMD_PREFIX_INFO:
47 + stream = stdout;
48 + break;
49 + case CMD_PREFIX_ERROR:
50 + stream = stderr;
51 + break;
52 + default:
53 + syntax_error = 1;
54 + fprintf(stderr, "Syntax error, failed to parse command response.\n");
55 + break;
56 + }
57 + if (stream) {
58 + fprintf(stream, "%s\n", ++pos);
59 + pos += strlen(pos);
60 + stream = NULL;
61 + }
62 + }
63 +}
64 +
65 +static void pipe_read_cb(uv_stream_t *client, ssize_t nread, const uv_buf_t *buf)
66 +{
67 + if (0 == nread) {
68 + fprintf(stderr, "%s: Zero bytes read by command pipe.\n", __func__);
69 + } else if (UV_EOF == nread) {
70 +// fprintf(stderr, "EOF found in command pipe.\n");
71 + parse_command_reply();
72 + } else if (nread < 0) {
73 + fprintf(stderr, "%s: %s\n", __func__, uv_strerror(nread));
74 + }
75 +
76 + if (nread < 0) { /* stop stream due to EOF or error */
77 + (void)uv_read_stop((uv_stream_t *)client);
78 + } else if (nread) {
79 + size_t to_copy;
80 +
81 + to_copy = MIN(nread, MAX_COMMAND_LENGTH - 1 - response_string_size);
82 + memcpy(response_string + response_string_size, buf->base, to_copy);
83 + response_string_size += to_copy;
84 + response_string[response_string_size] = '\0';
85 + }
86 + if (buf && buf->len) {
87 + free(buf->base);
88 + }
89 +}
90 +
91 +static void alloc_cb(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf)
92 +{
93 + (void)handle;
94 +
95 + buf->base = malloc(suggested_size);
96 + buf->len = suggested_size;
97 +}
98 +
99 +static void shutdown_cb(uv_shutdown_t* req, int status)
100 +{
101 + int ret;
102 +
103 + (void)req;
104 + (void)status;
105 +
106 + /* receive reply */
107 + response_string_size = 0;
108 + response_string[0] = '\0';
109 +
110 + ret = uv_read_start((uv_stream_t *)&client_pipe, alloc_cb, pipe_read_cb);
111 + if (ret) {
112 + fprintf(stderr, "uv_read_start(): %s\n", uv_strerror(ret));
113 + uv_close((uv_handle_t *)&client_pipe, NULL);
114 + return;
115 + }
116 +
117 +}
118 +
119 +static void pipe_write_cb(uv_write_t* req, int status)
120 +{
121 + int ret;
122 +
123 + (void)req;
124 + (void)status;
125 +
126 + ret = uv_shutdown(&shutdown_req, (uv_stream_t *)&client_pipe, shutdown_cb);
127 + if (ret) {
128 + fprintf(stderr, "uv_shutdown(): %s\n", uv_strerror(ret));
129 + uv_close((uv_handle_t *)&client_pipe, NULL);
130 + return;
131 + }
132 +}
133 +
134 +static void connect_cb(uv_connect_t* req, int status)
135 +{
136 + int ret;
137 + uv_buf_t write_buf;
138 + char *s;
139 +
140 + (void)req;
141 + if (status) {
142 + fprintf(stderr, "uv_pipe_connect(): %s\n", uv_strerror(status));
143 + fprintf(stderr, "Make sure the netdata service is running.\n");
144 + exit(-1);
145 + }
146 + if (0 == command_string_size) {
147 + s = fgets(command_string, MAX_COMMAND_LENGTH, stdin);
148 + }
149 + (void)s; /* We don't need input to communicate with the server */
150 + command_string_size = strlen(command_string);
151 +
152 + write_req.data = &client_pipe;
153 + write_buf.base = command_string;
154 + write_buf.len = command_string_size;
155 + ret = uv_write(&write_req, (uv_stream_t *)&client_pipe, &write_buf, 1, pipe_write_cb);
156 + if (ret) {
157 + fprintf(stderr, "uv_write(): %s\n", uv_strerror(ret));
158 + }
159 +// fprintf(stderr, "COMMAND: Sending command: \"%s\"\n", command_string);
160 +}
161 +
162 +int main(int argc, char **argv)
163 +{
164 + int ret, i;
165 + static uv_loop_t* loop;
166 + uv_connect_t req;
167 +
168 + exit_status = -1; /* default status for when there is no command response from server */
169 +
170 + loop = uv_default_loop();
171 +
172 + ret = uv_pipe_init(loop, &client_pipe, 1);
173 + if (ret) {
174 + fprintf(stderr, "uv_pipe_init(): %s\n", uv_strerror(ret));
175 + return exit_status;
176 + }
177 +
178 + command_string_size = 0;
179 + command_string[0] = '\0';
180 + for (i = 1 ; i < argc ; ++i) {
181 + size_t to_copy;
182 +
183 + to_copy = MIN(strlen(argv[i]), MAX_COMMAND_LENGTH - 1 - command_string_size);
184 + strncpyz(command_string + command_string_size, argv[i], to_copy);
185 + command_string_size += to_copy;
186 +
187 + if (command_string_size < MAX_COMMAND_LENGTH - 1) {
188 + command_string[command_string_size++] = ' ';
189 + } else {
190 + break;
191 + }
192 + }
193 +
194 + uv_pipe_connect(&req, &client_pipe, PIPENAME, connect_cb);
195 +
196 + uv_run(loop, UV_RUN_DEFAULT);
197 +
198 + uv_close((uv_handle_t *)&client_pipe, NULL);
199 +
200 + return exit_status;
201 +}
\ No newline at end of file
cli/cli.h new
+8
@@ -0,0 +1,8 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_CLI_H
4 +#define NETDATA_CLI_H 1
5 +
6 +#include "../daemon/common.h"
7 +
8 +#endif //NETDATA_CLI_H
configure.ac
+4 -5
@@ -279,6 +279,10 @@ AC_CHECK_LIB(
279 [uv_fs_scandir_next],
280 [UV_LIBS="-luv"]
281 )
282 +test -z "${UV_LIBS}" && \
283 + AC_MSG_ERROR([libuv required but not found. Try installing 'libuv1-dev' or 'libuv-devel'.])
284 +OPTIONAL_UV_CFLAGS="${UV_CFLAGS}"
285 +OPTIONAL_UV_LIBS="${UV_LIBS}"
286
287
288 # -----------------------------------------------------------------------------
@@ -363,9 +367,6 @@ OPTIONAL_JSONC_LIBS="${JSONC_LIBS}"
367
368 # -----------------------------------------------------------------------------
369 # DB engine and HTTPS
366 -test "${enable_dbengine}" = "yes" -a -z "${UV_LIBS}" && \
367 - AC_MSG_ERROR([libuv required but not found. Try installing 'libuv1-dev' or 'libuv-devel'.])
368 -
370 test "${enable_dbengine}" = "yes" -a -z "${LZ4_LIBS}" && \
371 AC_MSG_ERROR([liblz4 required but not found. Try installing 'liblz4-dev' or 'lz4-devel'.])
372
@@ -382,8 +383,6 @@ AC_MSG_CHECKING([if netdata dbengine should be used])
383 if test "${enable_dbengine}" != "no" -a "${UV_LIBS}" -a "${LZ4_LIBS}" -a "${JUDY_LIBS}" -a "${SSL_LIBS}"; then
384 enable_dbengine="yes"
385 AC_DEFINE([ENABLE_DBENGINE], [1], [netdata dbengine usability])
385 - OPTIONAL_UV_CFLAGS="${UV_CFLAGS}"
386 - OPTIONAL_UV_LIBS="${UV_LIBS}"
386 OPTIONAL_LZ4_CFLAGS="${LZ4_CFLAGS}"
387 OPTIONAL_LZ4_LIBS="${LZ4_LIBS}"
388 OPTIONAL_JUDY_CFLAGS="${JUDY_CFLAGS}"
daemon/README.md
+2
@@ -185,6 +185,8 @@ The command line options of the Netdata 1.10.0 version are the following:
185 - USR2 Reload health configuration.
186 ```
187
188 +You can send commands during runtime via [netdatacli](../cli).
189 +
190 ## Log files
191
192 Netdata uses 3 log files:
daemon/commands.c new
+554
@@ -0,0 +1,554 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "common.h"
4 +#include "../database/engine/rrdenginelib.h"
5 +
6 +static uv_thread_t thread;
7 +static uv_loop_t* loop;
8 +static uv_async_t async;
9 +static struct completion completion;
10 +static uv_pipe_t server_pipe;
11 +
12 +char cmd_prefix_by_status[] = {
13 + CMD_PREFIX_INFO,
14 + CMD_PREFIX_ERROR,
15 + CMD_PREFIX_ERROR
16 +};
17 +
18 +static int command_thread_error;
19 +static int command_thread_shutdown;
20 +static unsigned clients = 0;
21 +static char command_string[MAX_COMMAND_LENGTH];
22 +static unsigned command_string_size;
23 +
24 +struct command_context {
25 + uv_work_t work;
26 + uv_stream_t *client;
27 + cmd_t idx;
28 + char *args;
29 + char *message;
30 + cmd_status_t status;
31 +};
32 +
33 +/* Forward declarations */
34 +static cmd_status_t cmd_help_execute(char *args, char **message);
35 +static cmd_status_t cmd_reload_health_execute(char *args, char **message);
36 +static cmd_status_t cmd_save_database_execute(char *args, char **message);
37 +static cmd_status_t cmd_reopen_logs_execute(char *args, char **message);
38 +static cmd_status_t cmd_exit_execute(char *args, char **message);
39 +static cmd_status_t cmd_fatal_execute(char *args, char **message);
40 +
41 +static command_info_t command_info_array[] = {
42 + {"help", cmd_help_execute, CMD_TYPE_HIGH_PRIORITY}, // show help menu
43 + {"reload-health", cmd_reload_health_execute, CMD_TYPE_ORTHOGONAL}, // reload health configuration
44 + {"save-database", cmd_save_database_execute, CMD_TYPE_ORTHOGONAL}, // save database for memory mode save
45 + {"reopen-logs", cmd_reopen_logs_execute, CMD_TYPE_ORTHOGONAL}, // Close and reopen log files
46 + {"shutdown-agent", cmd_exit_execute, CMD_TYPE_EXCLUSIVE}, // exit cleanly
47 + {"fatal-agent", cmd_fatal_execute, CMD_TYPE_HIGH_PRIORITY}, // exit with fatal error
48 +};
49 +
50 +/* Mutexes for commands of type CMD_TYPE_ORTHOGONAL */
51 +static uv_mutex_t command_lock_array[CMD_TOTAL_COMMANDS];
52 +/* Commands of type CMD_TYPE_EXCLUSIVE are writers */
53 +static uv_rwlock_t exclusive_rwlock;
54 +/*
55 + * Locking order:
56 + * 1. exclusive_rwlock
57 + * 2. command_lock_array[]
58 + */
59 +
60 +/* Forward declarations */
61 +static void cmd_lock_exclusive(unsigned index);
62 +static void cmd_lock_orthogonal(unsigned index);
63 +static void cmd_lock_idempotent(unsigned index);
64 +static void cmd_lock_high_priority(unsigned index);
65 +
66 +static command_lock_t *cmd_lock_by_type[] = {
67 + cmd_lock_exclusive,
68 + cmd_lock_orthogonal,
69 + cmd_lock_idempotent,
70 + cmd_lock_high_priority
71 +};
72 +
73 +/* Forward declarations */
74 +static void cmd_unlock_exclusive(unsigned index);
75 +static void cmd_unlock_orthogonal(unsigned index);
76 +static void cmd_unlock_idempotent(unsigned index);
77 +static void cmd_unlock_high_priority(unsigned index);
78 +
79 +static command_lock_t *cmd_unlock_by_type[] = {
80 + cmd_unlock_exclusive,
81 + cmd_unlock_orthogonal,
82 + cmd_unlock_idempotent,
83 + cmd_unlock_high_priority
84 +};
85 +
86 +static cmd_status_t cmd_help_execute(char *args, char **message)
87 +{
88 + (void)args;
89 +
90 + *message = mallocz(MAX_COMMAND_LENGTH);
91 + strncpyz(*message,
92 + "\nThe commands are (arguments are in brackets):\n"
93 + "help\n"
94 + " Show this help menu.\n"
95 + "reload-health\n"
96 + " Reload health configuration.\n"
97 + "save-database\n"
98 + " Save internal DB to disk for memory mode save.\n"
99 + "reopen-logs\n"
100 + " Close and reopen log files.\n"
101 + "shutdown-agent\n"
102 + " Cleanup and exit the netdata agent.\n"
103 + "fatal-agent\n"
104 + " Log the state and halt the netdata agent.\n",
105 + MAX_COMMAND_LENGTH - 1);
106 + return CMD_STATUS_SUCCESS;
107 +}
108 +
109 +static cmd_status_t cmd_reload_health_execute(char *args, char **message)
110 +{
111 + (void)args;
112 + (void)message;
113 +
114 + error_log_limit_unlimited();
115 + info("COMMAND: Reloading HEALTH configuration.");
116 + health_reload();
117 + error_log_limit_reset();
118 +
119 + return CMD_STATUS_SUCCESS;
120 +}
121 +
122 +static cmd_status_t cmd_save_database_execute(char *args, char **message)
123 +{
124 + (void)args;
125 + (void)message;
126 +
127 + error_log_limit_unlimited();
128 + info("COMMAND: Saving databases.");
129 + rrdhost_save_all();
130 + info("COMMAND: Databases saved.");
131 + error_log_limit_reset();
132 +
133 + return CMD_STATUS_SUCCESS;
134 +}
135 +
136 +static cmd_status_t cmd_reopen_logs_execute(char *args, char **message)
137 +{
138 + (void)args;
139 + (void)message;
140 +
141 + error_log_limit_unlimited();
142 + info("COMMAND: Reopening all log files.");
143 + reopen_all_log_files();
144 + error_log_limit_reset();
145 +
146 + return CMD_STATUS_SUCCESS;
147 +}
148 +
149 +static cmd_status_t cmd_exit_execute(char *args, char **message)
150 +{
151 + (void)args;
152 + (void)message;
153 +
154 + error_log_limit_unlimited();
155 + info("COMMAND: Cleaning up to exit.");
156 + netdata_cleanup_and_exit(0);
157 + exit(0);
158 +
159 + return CMD_STATUS_SUCCESS;
160 +}
161 +
162 +static cmd_status_t cmd_fatal_execute(char *args, char **message)
163 +{
164 + (void)args;
165 + (void)message;
166 +
167 + fatal("COMMAND: netdata now exits.");
168 +
169 + return CMD_STATUS_SUCCESS;
170 +}
171 +
172 +static void cmd_lock_exclusive(unsigned index)
173 +{
174 + (void)index;
175 +
176 + uv_rwlock_wrlock(&exclusive_rwlock);
177 +}
178 +
179 +static void cmd_lock_orthogonal(unsigned index)
180 +{
181 + uv_rwlock_rdlock(&exclusive_rwlock);
182 + uv_mutex_lock(&command_lock_array[index]);
183 +}
184 +
185 +static void cmd_lock_idempotent(unsigned index)
186 +{
187 + (void)index;
188 +
189 + uv_rwlock_rdlock(&exclusive_rwlock);
190 +}
191 +
192 +static void cmd_lock_high_priority(unsigned index)
193 +{
194 + (void)index;
195 +}
196 +
197 +static void cmd_unlock_exclusive(unsigned index)
198 +{
199 + (void)index;
200 +
201 + uv_rwlock_wrunlock(&exclusive_rwlock);
202 +}
203 +
204 +static void cmd_unlock_orthogonal(unsigned index)
205 +{
206 + uv_rwlock_rdunlock(&exclusive_rwlock);
207 + uv_mutex_unlock(&command_lock_array[index]);
208 +}
209 +
210 +static void cmd_unlock_idempotent(unsigned index)
211 +{
212 + (void)index;
213 +
214 + uv_rwlock_rdunlock(&exclusive_rwlock);
215 +}
216 +
217 +static void cmd_unlock_high_priority(unsigned index)
218 +{
219 + (void)index;
220 +}
221 +
222 +static void pipe_write_cb(uv_write_t* req, int status)
223 +{
224 + (void)status;
225 + uv_pipe_t *client = req->data;
226 +
227 + uv_close((uv_handle_t *)client, NULL);
228 + freez(client);
229 + --clients;
230 + info("Command Clients = %u\n", clients);
231 +}
232 +
233 +static inline void add_char_to_command_reply(char *reply_string, unsigned *reply_string_size, char character)
234 +{
235 + reply_string[(*reply_string_size)++] = character;
236 +}
237 +
238 +static inline void add_string_to_command_reply(char *reply_string, unsigned *reply_string_size, char *str)
239 +{
240 + unsigned len;
241 +
242 + len = strlen(str);
243 + strncpyz(reply_string + *reply_string_size, str, len);
244 + *reply_string_size += len;
245 +}
246 +
247 +static void send_command_reply(uv_stream_t *client, cmd_status_t status, char *message)
248 +{
249 + int ret;
250 + char reply_string[MAX_COMMAND_LENGTH] = {'\0', };
251 + char exit_status_string[MAX_EXIT_STATUS_LENGTH + 1] = {'\0', };
252 + unsigned reply_string_size = 0;
253 + uv_buf_t write_buf;
254 + uv_write_t write_req;
255 +
256 + snprintfz(exit_status_string, MAX_EXIT_STATUS_LENGTH, "%u", status);
257 + add_char_to_command_reply(reply_string, &reply_string_size, CMD_PREFIX_EXIT_CODE);
258 + add_string_to_command_reply(reply_string, &reply_string_size, exit_status_string);
259 + add_char_to_command_reply(reply_string, &reply_string_size, '\0');
260 +
261 + if (message) {
262 + add_char_to_command_reply(reply_string, &reply_string_size, cmd_prefix_by_status[status]);
263 + add_string_to_command_reply(reply_string, &reply_string_size, message);
264 + }
265 +
266 + write_req.data = client;
267 + write_buf.base = reply_string;
268 + write_buf.len = reply_string_size;
269 + ret = uv_write(&write_req, (uv_stream_t *)client, &write_buf, 1, pipe_write_cb);
270 + if (ret) {
271 + error("uv_write(): %s", uv_strerror(ret));
272 + }
273 + info("COMMAND: Sending reply: \"%s\"", reply_string);
274 +}
275 +
276 +cmd_status_t execute_command(cmd_t idx, char *args, char **message)
277 +{
278 + cmd_status_t status;
279 + cmd_type_t type = command_info_array[idx].type;
280 +
281 + cmd_lock_by_type[type](idx);
282 + status = command_info_array[idx].func(args, message);
283 + cmd_unlock_by_type[type](idx);
284 +
285 + return status;
286 +}
287 +
288 +static void after_schedule_command(uv_work_t *req, int status)
289 +{
290 + struct command_context *cmd_ctx = req->data;
291 +
292 + (void)status;
293 +
294 + send_command_reply(cmd_ctx->client, cmd_ctx->status, cmd_ctx->message);
295 + if (cmd_ctx->message)
296 + freez(cmd_ctx->message);
297 +}
298 +
299 +static void schedule_command(uv_work_t *req)
300 +{
301 + struct command_context *cmd_ctx = req->data;
302 +
303 + cmd_ctx->status = execute_command(cmd_ctx->idx, cmd_ctx->args, &cmd_ctx->message);
304 +}
305 +
306 +static void parse_commands(uv_stream_t *client)
307 +{
308 + char *message = NULL, *pos;
309 + cmd_t i;
310 + cmd_status_t status;
311 + struct command_context *cmd_ctx;
312 +
313 + status = CMD_STATUS_FAILURE;
314 +
315 + /* Skip white-space characters */
316 + for (pos = command_string ; isspace(*pos) && ('\0' != *pos) ; ++pos) {;}
317 + for (i = 0 ; i < CMD_TOTAL_COMMANDS ; ++i) {
318 + if (!strncmp(pos, command_info_array[i].cmd_str, strlen(command_info_array[i].cmd_str))) {
319 + cmd_ctx = mallocz(sizeof(*cmd_ctx));
320 + cmd_ctx->work.data = cmd_ctx;
321 + cmd_ctx->client = client;
322 + cmd_ctx->idx = i;
323 + cmd_ctx->args = pos + strlen(command_info_array[i].cmd_str);
324 + cmd_ctx->message = NULL;
325 +
326 + assert(0 == uv_queue_work(loop, &cmd_ctx->work, schedule_command, after_schedule_command));
327 + break;
328 + }
329 + }
330 + if (CMD_TOTAL_COMMANDS == i) {
331 + /* no command found */
332 + message = strdupz("Illegal command. Please type \"help\" for instructions.");
333 + send_command_reply(client, status, message);
334 + freez(message);
335 + }
336 +}
337 +
338 +static void pipe_read_cb(uv_stream_t *client, ssize_t nread, const uv_buf_t *buf)
339 +{
340 + if (0 == nread) {
341 + info("%s: Zero bytes read by command pipe.", __func__);
342 + } else if (UV_EOF == nread) {
343 + info("EOF found in command pipe.");
344 + parse_commands(client);
345 + } else if (nread < 0) {
346 + error("%s: %s", __func__, uv_strerror(nread));
347 + }
348 +
349 + if (nread < 0) { /* stop stream due to EOF or error */
350 + (void)uv_read_stop((uv_stream_t *)client);
351 + } else if (nread) {
352 + size_t to_copy;
353 +
354 + to_copy = MIN(nread, MAX_COMMAND_LENGTH - 1 - command_string_size);
355 + strncpyz(command_string + command_string_size, buf->base, to_copy);
356 + command_string_size += to_copy;
357 + }
358 + if (buf && buf->len) {
359 + freez(buf->base);
360 + }
361 +
362 + if (nread < 0 && UV_EOF != nread) {
363 + uv_close((uv_handle_t *)client, NULL);
364 + freez(client);
365 + --clients;
366 + info("Command Clients = %u\n", clients);
367 + }
368 +}
369 +
370 +static void alloc_cb(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf)
371 +{
372 + (void)handle;
373 +
374 + buf->base = mallocz(suggested_size);
375 + buf->len = suggested_size;
376 +}
377 +
378 +static void connection_cb(uv_stream_t *server, int status)
379 +{
380 + int ret;
381 + uv_pipe_t *client;
382 + assert(status == 0);
383 +
384 + client = mallocz(sizeof(*client));
385 + ret = uv_pipe_init(server->loop, client, 1);
386 + if (ret) {
387 + error("uv_pipe_init(): %s", uv_strerror(ret));
388 + freez(client);
389 + return;
390 + }
391 + ret = uv_accept(server, (uv_stream_t *)client);
392 + if (ret) {
393 + error("uv_accept(): %s", uv_strerror(ret));
394 + uv_close((uv_handle_t *)client, NULL);
395 + freez(client);
396 + return;
397 + }
398 +
399 + ++clients;
400 + info("Command Clients = %u\n", clients);
401 + /* Start parsing a new command */
402 + command_string_size = 0;
403 + command_string[0] = '\0';
404 +
405 + ret = uv_read_start((uv_stream_t*)client, alloc_cb, pipe_read_cb);
406 + if (ret) {
407 + error("uv_read_start(): %s", uv_strerror(ret));
408 + uv_close((uv_handle_t *)client, NULL);
409 + freez(client);
410 + --clients;
411 + info("Command Clients = %u\n", clients);
412 + return;
413 + }
414 +}
415 +
416 +static void async_cb(uv_async_t *handle)
417 +{
418 + uv_stop(handle->loop);
419 +}
420 +
421 +static void command_thread(void *arg)
422 +{
423 + int ret;
424 + uv_fs_t req;
425 +
426 + (void) arg;
427 + loop = mallocz(sizeof(uv_loop_t));
428 + ret = uv_loop_init(loop);
429 + if (ret) {
430 + error("uv_loop_init(): %s", uv_strerror(ret));
431 + command_thread_error = ret;
432 + goto error_after_loop_init;
433 + }
434 + loop->data = NULL;
435 +
436 + ret = uv_async_init(loop, &async, async_cb);
437 + if (ret) {
438 + error("uv_async_init(): %s", uv_strerror(ret));
439 + command_thread_error = ret;
440 + goto error_after_async_init;
441 + }
442 + async.data = NULL;
443 +
444 + ret = uv_pipe_init(loop, &server_pipe, 1);
445 + if (ret) {
446 + error("uv_pipe_init(): %s", uv_strerror(ret));
447 + command_thread_error = ret;
448 + goto error_after_pipe_init;
449 + }
450 + (void)uv_fs_unlink(loop, &req, PIPENAME, NULL);
451 + uv_fs_req_cleanup(&req);
452 + ret = uv_pipe_bind(&server_pipe, PIPENAME);
453 + if (ret) {
454 + error("uv_pipe_bind(): %s", uv_strerror(ret));
455 + command_thread_error = ret;
456 + goto error_after_pipe_bind;
457 + }
458 + if ((ret = uv_listen((uv_stream_t *)&server_pipe, SOMAXCONN, connection_cb))) {
459 + error("uv_listen(): %s", uv_strerror(ret));
460 + command_thread_error = ret;
461 + goto error_after_uv_listen;
462 + }
463 +
464 + command_thread_error = 0;
465 + command_thread_shutdown = 0;
466 + /* wake up initialization thread */
467 + complete(&completion);
468 +
469 + while (command_thread_shutdown == 0) {
470 + uv_run(loop, UV_RUN_DEFAULT);
471 + }
472 + /* cleanup operations of the event loop */
473 + info("Shutting down command event loop.");
474 + uv_close((uv_handle_t *)&async, NULL);
475 + uv_close((uv_handle_t*)&server_pipe, NULL);
476 + uv_run(loop, UV_RUN_DEFAULT);
477 +
478 + info("Shutting down command loop complete.");
479 + assert(0 == uv_loop_close(loop));
480 + freez(loop);
481 +
482 + return;
483 +
484 +error_after_uv_listen:
485 +error_after_pipe_bind:
486 + uv_close((uv_handle_t*)&server_pipe, NULL);
487 +error_after_pipe_init:
488 + uv_close((uv_handle_t *)&async, NULL);
489 +error_after_async_init:
490 + assert(0 == uv_loop_close(loop));
491 +error_after_loop_init:
492 + freez(loop);
493 +
494 + /* wake up initialization thread */
495 + complete(&completion);
496 +}
497 +
498 +static void sanity_check(void)
499 +{
500 + /* The size of command_info_array must be CMD_TOTAL_COMMANDS elements */
501 + BUILD_BUG_ON(CMD_TOTAL_COMMANDS != sizeof(command_info_array) / sizeof(command_info_array[0]));
502 +}
503 +
504 +void commands_init(void)
505 +{
506 + cmd_t i;
507 + int error;
508 +
509 + sanity_check();
510 + info("Initializing command server.");
511 + for (i = 0 ; i < CMD_TOTAL_COMMANDS ; ++i) {
512 + uv_mutex_init(&command_lock_array[i]);
513 + }
514 + assert(0 == uv_rwlock_init(&exclusive_rwlock));
515 +
516 + init_completion(&completion);
517 + error = uv_thread_create(&thread, command_thread, NULL);
518 + if (error) {
519 + error("uv_thread_create(): %s", uv_strerror(error));
520 + goto after_error;
521 + }
522 + /* wait for worker thread to initialize */
523 + wait_for_completion(&completion);
524 + destroy_completion(&completion);
525 +
526 + if (command_thread_error) {
527 + error = uv_thread_join(&thread);
528 + if (error) {
529 + error("uv_thread_create(): %s", uv_strerror(error));
530 + }
531 + goto after_error;
532 + }
533 + return;
534 +
535 +after_error:
536 + error("Failed to initialize command server.");
537 +}
538 +
539 +void commands_exit(void)
540 +{
541 + cmd_t i;
542 +
543 + command_thread_shutdown = 1;
544 + info("Shutting down command server.");
545 + /* wake up event loop */
546 + assert(0 == uv_async_send(&async));
547 + assert(0 == uv_thread_join(&thread));
548 +
549 + for (i = 0 ; i < CMD_TOTAL_COMMANDS ; ++i) {
550 + uv_mutex_destroy(&command_lock_array[i]);
551 + }
552 + uv_rwlock_destroy(&exclusive_rwlock);
553 + info("Command server has stopped.");
554 +}
daemon/commands.h new
+76
@@ -0,0 +1,76 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_COMMANDS_H
4 +#define NETDATA_COMMANDS_H 1
5 +
6 +#ifdef _WIN32
7 +# define PIPENAME "\\\\?\\pipe\\netdata-cli"
8 +#else
9 +# define PIPENAME "/tmp/netdata-ipc"
10 +#endif
11 +
12 +#define MAX_COMMAND_LENGTH 4096
13 +#define MAX_EXIT_STATUS_LENGTH 23 /* Can't ever be bigger than "X-18446744073709551616" */
14 +
15 +typedef enum cmd {
16 + CMD_HELP = 0,
17 + CMD_RELOAD_HEALTH,
18 + CMD_SAVE_DATABASE,
19 + CMD_REOPEN_LOGS,
20 + CMD_EXIT,
21 + CMD_FATAL,
22 + CMD_TOTAL_COMMANDS
23 +} cmd_t;
24 +
25 +typedef enum cmd_status {
26 + CMD_STATUS_SUCCESS = 0,
27 + CMD_STATUS_FAILURE,
28 + CMD_STATUS_BUSY
29 +} cmd_status_t;
30 +
31 +#define CMD_PREFIX_INFO 'O' /* Following string should go to cli stdout */
32 +#define CMD_PREFIX_ERROR 'E' /* Following string should go to cli stderr */
33 +#define CMD_PREFIX_EXIT_CODE 'X' /* Following string is cli integer exit code */
34 +
35 +typedef enum cmd_type {
36 + /*
37 + * No other command is allowed to run at the same time (except for CMD_TYPE_HIGH_PRIORITY).
38 + */
39 + CMD_TYPE_EXCLUSIVE = 0,
40 + /*
41 + * Other commands are allowed to run concurrently (except for CMD_TYPE_EXCLUSIVE) but calls to this command are
42 + * serialized.
43 + */
44 + CMD_TYPE_ORTHOGONAL,
45 + /*
46 + * Other commands are allowed to run concurrently (except for CMD_TYPE_EXCLUSIVE) as are calls to this command.
47 + */
48 + CMD_TYPE_CONCURRENT,
49 + /*
50 + * Those commands are always allowed to run.
51 + */
52 + CMD_TYPE_HIGH_PRIORITY
53 +} cmd_type_t;
54 +
55 +/**
56 + * Executes a command and returns the status.
57 + *
58 + * @param args a string that may contain additional parameters to be parsed
59 + * @param message allocate and return a message if need be (up to MAX_COMMAND_LENGTH bytes)
60 + * @return CMD_FAILURE or CMD_SUCCESS
61 + */
62 +typedef cmd_status_t (command_action_t) (char *args, char **message);
63 +
64 +typedef struct command_info {
65 + char *cmd_str; // the command string
66 + command_action_t *func; // the function that executes the command
67 + cmd_type_t type; // Concurrency control information for the command
68 +} command_info_t;
69 +
70 +typedef void (command_lock_t) (unsigned index);
71 +
72 +cmd_status_t execute_command(cmd_t idx, char *args, char **message);
73 +extern void commands_init(void);
74 +extern void commands_exit(void);
75 +
76 +#endif //NETDATA_COMMANDS_H
daemon/common.h
+1
@@ -65,6 +65,7 @@
65 #include "daemon.h"
66 #include "main.h"
67 #include "signals.h"
68 +#include "commands.h"
69
70 // global netdata daemon variables
71 extern char *netdata_configured_hostname;
daemon/main.c
+5
@@ -1285,6 +1285,11 @@ int main(int argc, char **argv) {
1285 else debug(D_SYSTEM, "Not starting thread %s.", st->name);
1286 }
1287
1288 + // ------------------------------------------------------------------------
1289 + // Initialize netdata agent command serving from cli and signals
1290 +
1291 + commands_init();
1292 +
1293 info("netdata initialization completed. Enjoy real-time performance monitoring!");
1294 netdata_ready = 1;
1295
daemon/signals.c
+9 -7
@@ -9,7 +9,7 @@ typedef enum signal_action {
9 NETDATA_SIGNAL_IGNORE,
10 NETDATA_SIGNAL_EXIT_CLEANLY,
11 NETDATA_SIGNAL_SAVE_DATABASE,
12 - NETDATA_SIGNAL_LOG_ROTATE,
12 + NETDATA_SIGNAL_REOPEN_LOGS,
13 NETDATA_SIGNAL_RELOAD_HEALTH,
14 NETDATA_SIGNAL_FATAL,
15 NETDATA_SIGNAL_CHILD,
@@ -25,7 +25,7 @@ static struct {
25 { SIGINT , "SIGINT", 0, NETDATA_SIGNAL_EXIT_CLEANLY },
26 { SIGQUIT, "SIGQUIT", 0, NETDATA_SIGNAL_EXIT_CLEANLY },
27 { SIGTERM, "SIGTERM", 0, NETDATA_SIGNAL_EXIT_CLEANLY },
28 - { SIGHUP, "SIGHUP", 0, NETDATA_SIGNAL_LOG_ROTATE },
28 + { SIGHUP, "SIGHUP", 0, NETDATA_SIGNAL_REOPEN_LOGS },
29 { SIGUSR1, "SIGUSR1", 0, NETDATA_SIGNAL_SAVE_DATABASE },
30 { SIGUSR2, "SIGUSR2", 0, NETDATA_SIGNAL_RELOAD_HEALTH },
31 { SIGBUS, "SIGBUS", 0, NETDATA_SIGNAL_FATAL },
@@ -221,33 +221,35 @@ void signals_handle(void) {
221 case NETDATA_SIGNAL_RELOAD_HEALTH:
222 error_log_limit_unlimited();
223 info("SIGNAL: Received %s. Reloading HEALTH configuration...", name);
224 - health_reload();
224 error_log_limit_reset();
225 + execute_command(CMD_RELOAD_HEALTH, NULL, NULL);
226 break;
227
228 case NETDATA_SIGNAL_SAVE_DATABASE:
229 error_log_limit_unlimited();
230 info("SIGNAL: Received %s. Saving databases...", name);
231 - rrdhost_save_all();
232 - info("Databases saved.");
231 error_log_limit_reset();
232 + execute_command(CMD_SAVE_DATABASE, NULL, NULL);
233 break;
234
236 - case NETDATA_SIGNAL_LOG_ROTATE:
235 + case NETDATA_SIGNAL_REOPEN_LOGS:
236 error_log_limit_unlimited();
237 info("SIGNAL: Received %s. Reopening all log files...", name);
239 - reopen_all_log_files();
238 error_log_limit_reset();
239 + execute_command(CMD_REOPEN_LOGS, NULL, NULL);
240 break;
241
242 case NETDATA_SIGNAL_EXIT_CLEANLY:
243 error_log_limit_unlimited();
244 info("SIGNAL: Received %s. Cleaning up to exit...", name);
245 + commands_exit();
246 netdata_cleanup_and_exit(0);
247 exit(0);
248 + break;
249
250 case NETDATA_SIGNAL_FATAL:
251 fatal("SIGNAL: Received %s. netdata now exits.", name);
252 + break;
253
254 case NETDATA_SIGNAL_CHILD:
255 debug(D_CHILDS, "SIGNAL: Received %s. Reaping...", name);
database/engine/rrdengine.c
+2 -1
@@ -8,7 +8,7 @@ rrdeng_stats_t global_fs_errors = 0;
8 rrdeng_stats_t rrdeng_reserved_file_descriptors = 0;
9 rrdeng_stats_t global_flushing_errors = 0;
10
11 -void sanity_check(void)
11 +static void sanity_check(void)
12 {
13 /* Magic numbers must fit in the super-blocks */
14 BUILD_BUG_ON(strlen(RRDENG_DF_MAGIC) > RRDENG_MAGIC_SZ);
@@ -845,6 +845,7 @@ void rrdengine_main(void)
845 int ret;
846 struct rrdengine_instance *ctx;
847
848 + sanity_check();
849 ret = rrdeng_init(&ctx, "/tmp", RRDENG_MIN_PAGE_CACHE_SIZE_MB, RRDENG_MIN_DISK_SPACE_MB);
850 if (ret) {
851 exit(ret);
database/engine/rrdengine.h
+1 -5
@@ -7,14 +7,11 @@
7 #define _GNU_SOURCE
8 #endif
9 #include <fcntl.h>
10 -#include <aio.h>
11 -#include <uv.h>
12 -#include <assert.h>
10 #include <lz4.h>
11 #include <Judy.h>
12 #include <openssl/sha.h>
13 #include <openssl/evp.h>
17 -#include <stdint.h>
14 +#include "../../daemon/common.h"
15 #include "../rrd.h"
16 #include "rrddiskprotocol.h"
17 #include "rrdenginelib.h"
@@ -177,7 +174,6 @@ struct rrdengine_instance {
174 struct rrdengine_statistics stats;
175 };
176
180 -extern void sanity_check(void);
177 extern int init_rrd_files(struct rrdengine_instance *ctx);
178 extern void finalize_rrd_files(struct rrdengine_instance *ctx);
179 extern void rrdeng_test_quota(struct rrdengine_worker_config* wc);
database/engine/rrdengineapi.c
-2
@@ -745,8 +745,6 @@ int rrdeng_init(struct rrdengine_instance **ctxp, char *dbfiles_path, unsigned p
745 int error;
746 uint32_t max_open_files;
747
748 - sanity_check();
749 -
748 max_open_files = rlimit_nofile.rlim_cur / 4;
749
750 /* reserve RRDENG_FD_BUDGET_PER_INSTANCE file descriptors for this instance */
database/engine/rrdenginelib.h
+1 -2
@@ -3,10 +3,9 @@
3 #ifndef NETDATA_RRDENGINELIB_H
4 #define NETDATA_RRDENGINELIB_H
5
6 -#include "rrdengine.h"
7 -
6 /* Forward declarations */
7 struct rrdeng_page_descr;
8 +struct rrdengine_instance;
9
10 #define STR_HELPER(x) #x
11 #define STR(x) STR_HELPER(x)
libnetdata/libnetdata.h
+2
@@ -83,6 +83,8 @@
83 #include <unistd.h>
84 #include <uuid/uuid.h>
85 #include <spawn.h>
86 +#include <uv.h>
87 +#include <assert.h>
88
89 #ifdef HAVE_NETINET_IN_H
90 #include <netinet/in.h>
packaging/installer/README.md
+1 -1
@@ -231,6 +231,7 @@ Once Netdata is compiled, to run it the following packages are required (already
231 |:-----:|-----------|
232 | `libuuid` | part of `util-linux` for GUIDs management|
233 | `zlib` | gzip compression for the internal Netdata web server|
234 +| `libuv` | Multi-platform support library with a focus on asynchronous I/O, version 1 or greater|
235
236 *Netdata will fail to start without the above.*
237
@@ -260,7 +261,6 @@ Netdata DB engine can be enabled when these are installed (they are optional):
261
262 | package | description|
263 |:-----:|-----------|
263 -| `libuv` | Multi-platform support library with a focus on asynchronous I/O, version 1 or greater|
264 | `liblz4` | Extremely fast compression algorithm, version r129 or greater|
265 | `Judy` | General purpose dynamic array|
266 | `openssl`| Cryptography and SSL/TLS toolkit|
packaging/installer/netdata-uninstaller.sh
+1
@@ -312,6 +312,7 @@ if [ -n "${NETDATA_PREFIX}" ] && [ -d "${NETDATA_PREFIX}" ]; then
312 rm_dir "${NETDATA_PREFIX}"
313 else
314 rm_file "/usr/sbin/netdata"
315 + rm_file "/usr/sbin/netdatacli"
316 rm_dir "/usr/share/netdata"
317 rm_dir "/usr/libexec/netdata"
318 rm_dir "/var/lib/netdata"