optimize workers statistics performance (#14077)
* find the issue with workers performance and optimize it * have 2 independent locks in workers * break down workers statistics work to measure the time of each module * optimize reading cpu for workers; cleanup procfile from unecessary allocations and operations * cleanup workers * fix the workers statistics dimension name
Costa Tsaousis committed
Dec 1, 2022 at 17:17 UTC
c97645d6f94c178f8c5a6810d60401c99dce4c2f
6 files changed
+140
-97
collectors/proc.plugin/ipc.c
+4
-4
@@ -195,7 +195,7 @@ int ipc_msq_get_info(char *msg_filename, struct message_queue **message_queue_ro
195
size_t words = 0;
196
197
if(unlikely(lines < 2)) {
198
- error("Cannot read %s. Expected 2 or more lines, read %zu.", ff->filename, lines);
198
+ error("Cannot read %s. Expected 2 or more lines, read %zu.", procfile_filename(ff), lines);
199
return 1;
200
}
201
@@ -205,7 +205,7 @@ int ipc_msq_get_info(char *msg_filename, struct message_queue **message_queue_ro
205
words = procfile_linewords(ff, l);
206
if(unlikely(words < 2)) continue;
207
if(unlikely(words < 14)) {
208
- error("Cannot read %s line. Expected 14 params, read %zu.", ff->filename, words);
208
+ error("Cannot read %s line. Expected 14 params, read %zu.", procfile_filename(ff), words);
209
continue;
210
}
211
@@ -250,7 +250,7 @@ int ipc_shm_get_info(char *shm_filename, struct shm_stats *shm) {
250
size_t words = 0;
251
252
if(unlikely(lines < 2)) {
253
- error("Cannot read %s. Expected 2 or more lines, read %zu.", ff->filename, lines);
253
+ error("Cannot read %s. Expected 2 or more lines, read %zu.", procfile_filename(ff), lines);
254
return 1;
255
}
256
@@ -263,7 +263,7 @@ int ipc_shm_get_info(char *shm_filename, struct shm_stats *shm) {
263
words = procfile_linewords(ff, l);
264
if(unlikely(words < 2)) continue;
265
if(unlikely(words < 16)) {
266
- error("Cannot read %s line. Expected 16 params, read %zu.", ff->filename, words);
266
+ error("Cannot read %s line. Expected 16 params, read %zu.", procfile_filename(ff), words);
267
continue;
268
}
269
daemon/global_statistics.c
+32
-17
@@ -4,14 +4,14 @@
4
5
#define GLOBAL_STATS_RESET_WEB_USEC_MAX 0x01
6
7
-#define WORKER_JOB_GLOBAL 0
8
-#define WORKER_JOB_REGISTRY 1
9
-#define WORKER_JOB_WORKERS 2
10
-#define WORKER_JOB_DBENGINE 3
11
-#define WORKER_JOB_HEARTBEAT 4
12
-#define WORKER_JOB_STRINGS 5
13
-#define WORKER_JOB_DICTIONARIES 6
14
-#define WORKER_JOB_MALLOC_TRACE 7
7
+#define WORKER_JOB_GLOBAL 0
8
+#define WORKER_JOB_REGISTRY 1
9
+#define WORKER_JOB_DBENGINE 2
10
+#define WORKER_JOB_HEARTBEAT 3
11
+#define WORKER_JOB_STRINGS 4
12
+#define WORKER_JOB_DICTIONARIES 5
13
+#define WORKER_JOB_MALLOC_TRACE 6
14
+#define WORKER_JOB_WORKERS 7
15
16
#if WORKER_UTILIZATION_MAX_JOB_TYPES < 8
17
#error WORKER_UTILIZATION_MAX_JOB_TYPES has to be at least 8
@@ -2447,21 +2447,37 @@ static void workers_utilization_reset_statistics(struct worker_utilization *wu)
2447
}
2448
}
2449
2450
+#define TASK_STAT_PREFIX "/proc/self/task/"
2451
+#define TASK_STAT_SUFFIX "/stat"
2452
+
2453
static int read_thread_cpu_time_from_proc_stat(pid_t pid __maybe_unused, kernel_uint_t *utime __maybe_unused, kernel_uint_t *stime __maybe_unused) {
2454
#ifdef __linux__
2452
- char filename[200 + 1];
2453
- snprintfz(filename, 200, "/proc/self/task/%d/stat", pid);
2455
+ static char filename[sizeof(TASK_STAT_PREFIX) + sizeof(TASK_STAT_SUFFIX) + 20] = TASK_STAT_PREFIX;
2456
+ static size_t start_pos = sizeof(TASK_STAT_PREFIX) - 1;
2457
+ static procfile *ff = NULL;
2458
+
2459
+ // construct the filename
2460
+ size_t end_pos = snprintfz(&filename[start_pos], 20, "%d", pid);
2461
+ strcpy(&filename[start_pos + end_pos], TASK_STAT_SUFFIX);
2462
2455
- procfile *ff = procfile_open(filename, " ", PROCFILE_FLAG_NO_ERROR_ON_FILE_IO);
2456
- if(!ff) return -1;
2463
+ // (re)open the procfile to the new filename
2464
+ bool set_quotes = (ff == NULL) ? true : false;
2465
+ ff = procfile_reopen(ff, filename, NULL, PROCFILE_FLAG_DEFAULT);
2466
+ if(unlikely(!ff)) return -1;
2467
2468
+ if(set_quotes)
2469
+ procfile_set_open_close(ff, "(", ")");
2470
+
2471
+ // read the entire file and split it to lines and words
2472
ff = procfile_readall(ff);
2459
- if(!ff) return -1;
2473
+ if(unlikely(!ff)) return -1;
2474
2475
+ // parse the numbers we are interested
2476
*utime = str2kernel_uint_t(procfile_lineword(ff, 0, 13));
2477
*stime = str2kernel_uint_t(procfile_lineword(ff, 0, 14));
2478
2464
- procfile_close(ff);
2479
+ // leave the file open for the next iteration
2480
+
2481
return 0;
2482
#else
2483
// TODO: add here cpu time detection per thread, for FreeBSD and MacOS
@@ -2621,8 +2637,7 @@ static void worker_utilization_charts(void) {
2637
static size_t iterations = 0;
2638
iterations++;
2639
2624
- int i;
2625
- for(i = 0; all_workers_utilization[i].name ;i++) {
2640
+ for(int i = 0; all_workers_utilization[i].name ;i++) {
2641
workers_utilization_reset_statistics(&all_workers_utilization[i]);
2642
workers_foreach(all_workers_utilization[i].name, worker_utilization_charts_callback, &all_workers_utilization[i]);
2643
@@ -2672,11 +2687,11 @@ static void global_statistics_register_workers(void) {
2687
worker_register("STATS");
2688
worker_register_job_name(WORKER_JOB_GLOBAL, "global");
2689
worker_register_job_name(WORKER_JOB_REGISTRY, "registry");
2675
- worker_register_job_name(WORKER_JOB_WORKERS, "workers");
2690
worker_register_job_name(WORKER_JOB_DBENGINE, "dbengine");
2691
worker_register_job_name(WORKER_JOB_STRINGS, "strings");
2692
worker_register_job_name(WORKER_JOB_DICTIONARIES, "dictionaries");
2693
worker_register_job_name(WORKER_JOB_MALLOC_TRACE, "malloc_trace");
2694
+ worker_register_job_name(WORKER_JOB_WORKERS, "workers");
2695
}
2696
2697
static void global_statistics_cleanup(void *ptr)
libnetdata/procfile/procfile.c
+32
-35
@@ -22,16 +22,21 @@ size_t procfile_max_allocation = PROCFILE_INCREMENT_BUFFER;
22
// ----------------------------------------------------------------------------
23
24
char *procfile_filename(procfile *ff) {
25
- if(ff->filename[0]) return ff->filename;
25
+ if(ff->filename)
26
+ return ff->filename;
27
28
+ char filename[FILENAME_MAX + 1];
29
char buffer[FILENAME_MAX + 1];
30
snprintfz(buffer, FILENAME_MAX, "/proc/self/fd/%d", ff->fd);
31
30
- ssize_t l = readlink(buffer, ff->filename, FILENAME_MAX);
32
+ ssize_t l = readlink(buffer, filename, FILENAME_MAX);
33
if(unlikely(l == -1))
32
- snprintfz(ff->filename, FILENAME_MAX, "unknown filename for fd %d", ff->fd);
34
+ snprintfz(filename, FILENAME_MAX, "unknown filename for fd %d", ff->fd);
35
else
34
- ff->filename[l] = '\0';
36
+ filename[l] = '\0';
37
+
38
+
39
+ ff->filename = strdupz(filename);
40
41
// on non-linux systems, something like this will be needed
42
// fcntl(ff->fd, F_GETPATH, ff->filename)
@@ -141,8 +146,9 @@ void procfile_close(procfile *ff) {
146
147
debug(D_PROCFILE, PF_PREFIX ": Closing file '%s'", procfile_filename(ff));
148
144
- if(likely(ff->lines)) procfile_lines_free(ff->lines);
145
- if(likely(ff->words)) procfile_words_free(ff->words);
149
+ freez(ff->filename);
150
+ procfile_lines_free(ff->lines);
151
+ procfile_words_free(ff->words);
152
153
if(likely(ff->fd != -1)) close(ff->fd);
154
freez(ff);
@@ -319,40 +325,31 @@ procfile *procfile_readall(procfile *ff) {
325
return ff;
326
}
327
322
-NOINLINE
323
-static void procfile_set_separators(procfile *ff, const char *separators) {
324
- static PF_CHAR_TYPE def[256];
325
- static char initialized = 0;
326
-
327
- if(unlikely(!initialized)) {
328
- // this is thread safe
329
- // if initialized is zero, multiple threads may be executing
330
- // this code at the same time, setting in def[] the exact same values
331
- int i = 256;
332
- while(i--) {
333
- if(unlikely(i == '\n' || i == '\r'))
334
- def[i] = PF_CHAR_IS_NEWLINE;
328
+static PF_CHAR_TYPE procfile_default_separators[256];
329
+__attribute__((constructor)) void procfile_initialize_default_separators(void) {
330
+ int i = 256;
331
+ while(i--) {
332
+ if(unlikely(i == '\n' || i == '\r'))
333
+ procfile_default_separators[i] = PF_CHAR_IS_NEWLINE;
334
336
- else if(unlikely(isspace(i) || !isprint(i)))
337
- def[i] = PF_CHAR_IS_SEPARATOR;
335
+ else if(unlikely(isspace(i) || !isprint(i)))
336
+ procfile_default_separators[i] = PF_CHAR_IS_SEPARATOR;
337
339
- else
340
- def[i] = PF_CHAR_IS_WORD;
341
- }
342
-
343
- initialized = 1;
338
+ else
339
+ procfile_default_separators[i] = PF_CHAR_IS_WORD;
340
}
341
+}
342
346
- // copy the default
347
- PF_CHAR_TYPE *ffs = ff->separators, *ffd = def, *ffe = &def[256];
348
- while(ffd != ffe)
349
- *ffs++ = *ffd++;
350
-
343
+NOINLINE
344
+static void procfile_set_separators(procfile *ff, const char *separators) {
345
// set the separators
346
if(unlikely(!separators))
347
separators = " \t=|";
348
355
- ffs = ff->separators;
349
+ // copy the default
350
+ memcpy(ff->separators, procfile_default_separators, 256 * sizeof(PF_CHAR_TYPE));
351
+
352
+ PF_CHAR_TYPE *ffs = ff->separators;
353
const char *s = separators;
354
while(*s)
355
ffs[(int)*s++] = PF_CHAR_IS_SEPARATOR;
@@ -416,8 +413,7 @@ procfile *procfile_open(const char *filename, const char *separators, uint32_t f
413
procfile *ff = mallocz(sizeof(procfile) + size);
414
415
//strncpyz(ff->filename, filename, FILENAME_MAX);
419
- ff->filename[0] = '\0';
420
-
416
+ ff->filename = NULL;
417
ff->fd = fd;
418
ff->size = size;
419
ff->len = 0;
@@ -449,7 +445,8 @@ procfile *procfile_reopen(procfile *ff, const char *filename, const char *separa
445
// info("PROCFILE: opened '%s' on fd %d", filename, ff->fd);
446
447
//strncpyz(ff->filename, filename, FILENAME_MAX);
452
- ff->filename[0] = '\0';
448
+ freez(ff->filename);
449
+ ff->filename = NULL;
450
ff->flags = flags;
451
452
// do not do the separators again if NULL is given
libnetdata/procfile/procfile.h
+7
-8
@@ -37,7 +37,7 @@ typedef struct {
37
#define PROCFILE_FLAG_DEFAULT 0x00000000
38
#define PROCFILE_FLAG_NO_ERROR_ON_FILE_IO 0x00000001
39
40
-typedef enum procfile_separator {
40
+typedef enum __attribute__ ((__packed__)) procfile_separator {
41
PF_CHAR_IS_SEPARATOR,
42
PF_CHAR_IS_NEWLINE,
43
PF_CHAR_IS_WORD,
@@ -46,17 +46,16 @@ typedef enum procfile_separator {
46
PF_CHAR_IS_CLOSE
47
} PF_CHAR_TYPE;
48
49
-typedef struct {
50
- char filename[FILENAME_MAX + 1]; // not populated until profile_filename() is called
51
-
49
+typedef struct procfile {
50
+ char *filename; // not populated until procfile_filename() is called
51
uint32_t flags;
53
- int fd; // the file descriptor
54
- size_t len; // the bytes we have placed into data
55
- size_t size; // the bytes we have allocated for data
52
+ int fd; // the file descriptor
53
+ size_t len; // the bytes we have placed into data
54
+ size_t size; // the bytes we have allocated for data
55
pflines *lines;
56
pfwords *words;
57
PF_CHAR_TYPE separators[256];
59
- char data[]; // allocated buffer to keep file contents
58
+ char data[]; // allocated buffer to keep file contents
59
} procfile;
60
61
// close the proc file and free all related memory
libnetdata/worker_utilization/worker_utilization.c
+63
-31
@@ -44,35 +44,55 @@ struct worker {
44
struct worker *prev;
45
};
46
47
-static netdata_mutex_t workers_base_lock = NETDATA_MUTEX_INITIALIZER;
48
-static __thread struct worker *worker = NULL;
49
-static Pvoid_t workers_per_workname_JudyHS_array = NULL;
47
+struct workers_workname { // this is what we add to JudyHS
48
+ SPINLOCK spinlock;
49
+ struct worker *base;
50
+};
51
+
52
+static struct workers_globals {
53
+ SPINLOCK spinlock;
54
+ Pvoid_t worknames_JudyHS;
55
+
56
+} workers_globals = { // workers globals, the base of all worknames
57
+ .spinlock = NETDATA_SPINLOCK_INITIALIZER, // a lock for the worknames index
58
+ .worknames_JudyHS = NULL, // the worknames index
59
+};
60
51
-void worker_register(const char *workname) {
61
+static __thread struct worker *worker = NULL; // the current thread worker
62
+
63
+void worker_register(const char *name) {
64
if(unlikely(worker)) return;
65
66
worker = callocz(1, sizeof(struct worker));
67
worker->pid = gettid();
68
worker->tag = strdupz(netdata_thread_tag());
57
- worker->workname = strdupz(workname);
69
+ worker->workname = strdupz(name);
70
71
usec_t now = now_monotonic_usec();
72
worker->statistics_last_checkpoint = now;
73
worker->last_action_timestamp = now;
74
worker->last_action = WORKER_IDLE;
75
64
- size_t workname_size = strlen(workname) + 1;
65
- netdata_mutex_lock(&workers_base_lock);
76
+ size_t name_size = strlen(name) + 1;
77
+ netdata_spinlock_lock(&workers_globals.spinlock);
78
67
- Pvoid_t *PValue = JudyHSGet(workers_per_workname_JudyHS_array, (void *)workname, workname_size);
79
+ Pvoid_t *PValue = JudyHSGet(workers_globals.worknames_JudyHS, (void *)name, name_size);
80
if(!PValue)
69
- PValue = JudyHSIns(&workers_per_workname_JudyHS_array, (void *)workname, workname_size, PJE0);
81
+ PValue = JudyHSIns(&workers_globals.worknames_JudyHS, (void *)name, name_size, PJE0);
82
+
83
+ struct workers_workname *workname = *PValue;
84
+ if(!workname) {
85
+ workname = mallocz(sizeof(struct workers_workname));
86
+ workname->spinlock = NETDATA_SPINLOCK_INITIALIZER;
87
+ workname->base = NULL;
88
+ *PValue = workname;
89
+ }
90
71
- struct worker *base = *PValue;
72
- DOUBLE_LINKED_LIST_APPEND_UNSAFE(base, worker, prev, next);
73
- *PValue = base;
91
+ netdata_spinlock_lock(&workname->spinlock);
92
+ DOUBLE_LINKED_LIST_APPEND_UNSAFE(workname->base, worker, prev, next);
93
+ netdata_spinlock_unlock(&workname->spinlock);
94
75
- netdata_mutex_unlock(&workers_base_lock);
95
+ netdata_spinlock_unlock(&workers_globals.spinlock);
96
}
97
98
void worker_register_job_custom_metric(size_t job_id, const char *name, const char *units, WORKER_METRIC_TYPE type) {
@@ -105,17 +125,20 @@ void worker_unregister(void) {
125
if(unlikely(!worker)) return;
126
127
size_t workname_size = strlen(worker->workname) + 1;
108
- netdata_mutex_lock(&workers_base_lock);
109
- Pvoid_t *PValue = JudyHSGet(workers_per_workname_JudyHS_array, (void *)worker->workname, workname_size);
128
+ netdata_spinlock_lock(&workers_globals.spinlock);
129
+ Pvoid_t *PValue = JudyHSGet(workers_globals.worknames_JudyHS, (void *)worker->workname, workname_size);
130
if(PValue) {
111
- struct worker *base = *PValue;
112
- DOUBLE_LINKED_LIST_REMOVE_UNSAFE(base, worker, prev, next);
113
- *PValue = base;
114
-
115
- if(!base)
116
- JudyHSDel(&workers_per_workname_JudyHS_array, (void *)worker->workname, workname_size, PJE0);
131
+ struct workers_workname *workname = *PValue;
132
+ netdata_spinlock_lock(&workname->spinlock);
133
+ DOUBLE_LINKED_LIST_REMOVE_UNSAFE(workname->base, worker, prev, next);
134
+ netdata_spinlock_unlock(&workname->spinlock);
135
+
136
+ if(!workname->base) {
137
+ JudyHSDel(&workers_globals.worknames_JudyHS, (void *) worker->workname, workname_size, PJE0);
138
+ freez(workname);
139
+ }
140
}
118
- netdata_mutex_unlock(&workers_base_lock);
141
+ netdata_spinlock_unlock(&workers_globals.spinlock);
142
143
for(int i = 0; i < WORKER_UTILIZATION_MAX_JOB_TYPES ;i++) {
144
string_freez(worker->per_job_type[i].name);
@@ -187,7 +210,7 @@ void worker_set_metric(size_t job_id, NETDATA_DOUBLE value) {
210
211
// statistics interface
212
190
-void workers_foreach(const char *workname, void (*callback)(
213
+void workers_foreach(const char *name, void (*callback)(
214
void *data
215
, pid_t pid
216
, const char *thread_tag
@@ -203,18 +226,27 @@ void workers_foreach(const char *workname, void (*callback)(
226
, NETDATA_DOUBLE *job_custom_values
227
)
228
, void *data) {
206
- netdata_mutex_lock(&workers_base_lock);
229
+ netdata_spinlock_lock(&workers_globals.spinlock);
230
usec_t busy_time, delta;
231
size_t i, jobs_started, jobs_running;
232
210
- size_t workname_size = strlen(workname) + 1;
211
- struct worker *base = NULL;
212
- Pvoid_t *PValue = JudyHSGet(workers_per_workname_JudyHS_array, (void *)workname, workname_size);
213
- if(PValue)
214
- base = *PValue;
233
+ size_t workname_size = strlen(name) + 1;
234
+ struct workers_workname *workname;
235
+ Pvoid_t *PValue = JudyHSGet(workers_globals.worknames_JudyHS, (void *)name, workname_size);
236
+ if(PValue) {
237
+ workname = *PValue;
238
+ netdata_spinlock_lock(&workname->spinlock);
239
+ }
240
+ else
241
+ workname = NULL;
242
+
243
+ netdata_spinlock_unlock(&workers_globals.spinlock);
244
+
245
+ if(!workname)
246
+ return;
247
248
struct worker *p;
217
- DOUBLE_LINKED_LIST_FOREACH_FORWARD(base, p, prev, next) {
249
+ DOUBLE_LINKED_LIST_FOREACH_FORWARD(workname->base, p, prev, next) {
250
usec_t now = now_monotonic_usec();
251
252
// find per job type statistics
@@ -326,5 +358,5 @@ void workers_foreach(const char *workname, void (*callback)(
358
);
359
}
360
329
- netdata_mutex_unlock(&workers_base_lock);
361
+ netdata_spinlock_unlock(&workname->spinlock);
362
}
libnetdata/worker_utilization/worker_utilization.h
+2
-2
@@ -15,7 +15,7 @@ typedef enum {
15
WORKER_METRIC_INCREMENTAL_TOTAL = 4,
16
} WORKER_METRIC_TYPE;
17
18
-void worker_register(const char *workname);
18
+void worker_register(const char *name);
19
void worker_register_job_name(size_t job_id, const char *name);
20
void worker_register_job_custom_metric(size_t job_id, const char *name, const char *units, WORKER_METRIC_TYPE type);
21
void worker_unregister(void);
@@ -26,7 +26,7 @@ void worker_set_metric(size_t job_id, NETDATA_DOUBLE value);
26
27
// statistics interface
28
29
-void workers_foreach(const char *workname, void (*callback)(
29
+void workers_foreach(const char *name, void (*callback)(
30
void *data
31
, pid_t pid
32
, const char *thread_tag