master
c 1,201 lines 46 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #define PULSE_INTERNALS 1
4 #include "pulse-workers.h"
5
6 #define WORKERS_MIN_PERCENT_DEFAULT 10000.0
7
8 struct worker_spinlocks {
9 size_t locks;
10 size_t spins;
11
12 RRDDIM *rd_locks;
13 RRDDIM *rd_spins;
14 };
15
16 DEFINE_JUDYL_TYPED(SPINLOCKS, struct worker_spinlocks *);
17 SPINLOCKS_JudyLSet ALL_SPINLOCKS = { 0 };
18
19 struct worker_job_type_gs {
20 STRING *name;
21 STRING *units;
22
23 struct {
24 size_t jobs_started;
25 usec_t busy_time;
26 } data[2];
27
28 RRDDIM *rd_jobs_started;
29 RRDDIM *rd_busy_time;
30 RRDDIM *rd_avg_time;
31
32 WORKER_METRIC_TYPE type;
33 NETDATA_DOUBLE min_value;
34 NETDATA_DOUBLE max_value;
35 NETDATA_DOUBLE sum_value;
36 size_t count_value;
37
38 RRDSET *st;
39 RRDDIM *rd_min;
40 RRDDIM *rd_max;
41 RRDDIM *rd_avg;
42 };
43
44 struct worker_thread {
45 pid_t pid;
46 bool enabled;
47
48 bool cpu_enabled;
49 double cpu;
50
51 kernel_uint_t utime;
52 kernel_uint_t stime;
53
54 kernel_uint_t utime_old;
55 kernel_uint_t stime_old;
56
57 usec_t collected_time;
58 usec_t collected_time_old;
59
60 size_t jobs_started;
61 usec_t busy_time;
62
63 struct worker_thread *next;
64 struct worker_thread *prev;
65 };
66
67 struct worker_utilization {
68 const char *name;
69 const char *family;
70 size_t priority;
71 uint32_t flags;
72
73 char *name_lowercase;
74
75 struct worker_job_type_gs per_job_type[WORKER_UTILIZATION_MAX_JOB_TYPES];
76
77 size_t workers_max_job_id;
78 size_t workers_registered;
79 size_t workers_busy;
80 usec_t workers_total_busy_time;
81 usec_t workers_total_duration;
82 size_t workers_total_jobs_started;
83 double workers_min_busy_time;
84 double workers_max_busy_time;
85
86 size_t workers_cpu_registered;
87 double workers_cpu_min;
88 double workers_cpu_max;
89 double workers_cpu_total;
90
91 uint64_t memory_calls[WORKERS_MEMORY_CALL_MAX];
92
93 struct worker_thread *threads;
94
95 RRDSET *st_workers_time;
96 RRDDIM *rd_workers_time_avg;
97 RRDDIM *rd_workers_time_min;
98 RRDDIM *rd_workers_time_max;
99
100 RRDSET *st_workers_cpu;
101 RRDDIM *rd_workers_cpu_avg;
102 RRDDIM *rd_workers_cpu_min;
103 RRDDIM *rd_workers_cpu_max;
104
105 RRDSET *st_workers_threads;
106 RRDDIM *rd_workers_threads_free;
107 RRDDIM *rd_workers_threads_busy;
108
109 RRDSET *st_workers_jobs_per_job_type;
110 RRDSET *st_workers_time_per_job_type;
111 RRDSET *st_workers_avg_time_per_job_type;
112
113 RRDDIM *rd_total_cpu_utilizaton;
114
115 RRDSET *st_spinlocks_locks;
116 RRDSET *st_spinlocks_spins;
117 SPINLOCKS_JudyLSet spinlocks;
118
119 RRDSET *st_memory_calls;
120 RRDDIM *rd_memory_calls[WORKERS_MEMORY_CALL_MAX];
121 };
122
123 static inline size_t workers_chart_context_job_name_max_length(const char *worker_name_lowercase, const char *metric_type) {
124 size_t fixed_length = strlen("netdata.workers.") + strlen(worker_name_lowercase) + strlen(metric_type);
125
126 if(fixed_length >= RRD_ID_LENGTH_MAX)
127 return 0;
128
129 return RRD_ID_LENGTH_MAX - fixed_length - 1;
130 }
131
132 static struct worker_utilization all_workers_utilization[] = {
133 { .name = "PULSE", .family = "workers pulse", .priority = 1000000 },
134 { .name = "HEALTH", .family = "workers health alerts", .priority = 1000000 },
135 { .name = "MLTRAIN", .family = "workers ML training", .priority = 1000000 },
136 { .name = "MLDETECT", .family = "workers ML detection", .priority = 1000000 },
137 { .name = "STREAM", .family = "workers streaming", .priority = 1000000 },
138 { .name = "STREAMCNT", .family = "workers streaming connect", .priority = 1000000 },
139 { .name = "DBENGINE", .family = "workers dbengine instances", .priority = 1000000 },
140 { .name = "LIBUV", .family = "workers libuv threadpool", .priority = 1000000 },
141 { .name = "WEB", .family = "workers web server", .priority = 1000000 },
142 { .name = "ACLK", .family = "workers aclk", .priority = 1000000 },
143 { .name = "ACLKSYNC", .family = "workers aclk sync", .priority = 1000000 },
144 { .name = "METASYNC", .family = "workers metadata sync", .priority = 1000000 },
145 { .name = "PLUGINSD", .family = "workers plugins.d", .priority = 1000000 },
146 { .name = "STATSD", .family = "workers plugin statsd", .priority = 1000000 },
147 { .name = "STATSDFLUSH", .family = "workers plugin statsd flush", .priority = 1000000 },
148 { .name = "PROC", .family = "workers plugin proc", .priority = 1000000 },
149 { .name = "WIN", .family = "workers plugin windows", .priority = 1000000 },
150 { .name = "NETDEV", .family = "workers plugin proc netdev", .priority = 1000000 },
151 { .name = "FREEBSD", .family = "workers plugin freebsd", .priority = 1000000 },
152 { .name = "MACOS", .family = "workers plugin macos", .priority = 1000000 },
153 { .name = "CGROUPS", .family = "workers plugin cgroups", .priority = 1000000 },
154 { .name = "CGROUPSDISC", .family = "workers plugin cgroups find", .priority = 1000000 },
155 { .name = "DISKSPACE", .family = "workers plugin diskspace", .priority = 1000000 },
156 { .name = "TC", .family = "workers plugin tc", .priority = 1000000 },
157 { .name = "TIMEX", .family = "workers plugin timex", .priority = 1000000 },
158 { .name = "IDLEJITTER", .family = "workers plugin idlejitter", .priority = 1000000 },
159 { .name = "RRDCONTEXT", .family = "workers contexts", .priority = 1000000 },
160 { .name = "REPLICATION", .family = "workers replication sender", .priority = 1000000 },
161 { .name = "SERVICE", .family = "workers service", .priority = 1000000 },
162 { .name = "PROFILER", .family = "workers profile", .priority = 1000000 },
163 { .name = "PGCEVICT", .family = "workers dbengine eviction", .priority = 1000000 },
164 { .name = "BACKFILL", .family = "workers backfill", .priority = 1000000 },
165 { .name = "WEBSOCKET", .family = "workers websocket", .priority = 1000000 },
166
167 // has to be terminated with a NULL
168 { .name = NULL, .family = NULL }
169 };
170
171 static void workers_total_spinlock_contention_chart(void) {
172 {
173 static RRDSET *st = NULL;
174
175 if(unlikely(!st)) {
176 st = rrdset_create_localhost(
177 "netdata"
178 , "spinlock_total_locks"
179 , NULL
180 , "spinlocks"
181 , "netdata.spinlock_total_locks"
182 , "Netdata Total Spinlock Locks"
183 , "locks"
184 , "netdata"
185 , "pulse"
186 , 920000
187 , localhost->rrd_update_every
188 , RRDSET_TYPE_LINE
189 );
190 }
191
192 Word_t idx = 0;
193 for(struct worker_spinlocks *wusp = SPINLOCKS_FIRST(&ALL_SPINLOCKS, &idx);
194 wusp;
195 wusp = SPINLOCKS_NEXT(&ALL_SPINLOCKS, &idx)) {
196 const char *func = (const char *)idx;
197 RRDDIM *rd = rrddim_find(st, func, false);
198 if(!rd) rd = rrddim_add(st, func, NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
199 rrddim_set_by_pointer(st, rd, (collected_number)wusp->locks);
200 }
201
202 rrdset_done(st);
203 }
204
205 {
206 static RRDSET *st = NULL;
207 if(unlikely(!st)) {
208 st = rrdset_create_localhost(
209 "netdata"
210 , "spinlock_total_spins"
211 , NULL
212 , "spinlocks"
213 , "netdata.spinlock_total_spins"
214 , "Netdata Total Spinlock Spins"
215 , "spins"
216 , "netdata"
217 , "pulse"
218 , 920001
219 , localhost->rrd_update_every
220 , RRDSET_TYPE_LINE
221 );
222 }
223
224 Word_t idx = 0;
225 for(struct worker_spinlocks *wusp = SPINLOCKS_FIRST(&ALL_SPINLOCKS, &idx);
226 wusp;
227 wusp = SPINLOCKS_NEXT(&ALL_SPINLOCKS, &idx)) {
228 const char *func = (const char *)idx;
229 RRDDIM *rd = rrddim_find(st, func, false);
230 if(!rd) rd = rrddim_add(st, func, NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
231 rrddim_set_by_pointer(st, rd, (collected_number)wusp->spins);
232 }
233
234 rrdset_done(st);
235 }
236
237 {
238 static RRDSET *st = NULL;
239 if(unlikely(!st)) {
240 st = rrdset_create_localhost(
241 "netdata"
242 , "spinlock_total_spins_per_lock"
243 , NULL
244 , "spinlocks"
245 , "netdata.spinlock_total_spins_per_lock"
246 , "Netdata Average Spinlock Spins Per Lock"
247 , "spins"
248 , "netdata"
249 , "pulse"
250 , 920002
251 , localhost->rrd_update_every
252 , RRDSET_TYPE_LINE
253 );
254 }
255
256 Word_t idx = 0;
257 for(struct worker_spinlocks *wusp = SPINLOCKS_FIRST(&ALL_SPINLOCKS, &idx);
258 wusp;
259 wusp = SPINLOCKS_NEXT(&ALL_SPINLOCKS, &idx)) {
260 const char *func = (const char *)idx;
261 RRDDIM *rd = rrddim_find(st, func, false);
262 if(!rd) rd = rrddim_add(st, func, NULL, 1, 10000, RRD_ALGORITHM_ABSOLUTE);
263 if(!wusp->locks)
264 rrddim_set_by_pointer(st, rd, 0);
265 else
266 rrddim_set_by_pointer(st, rd, (collected_number)((uint64_t)wusp->spins * 10000ULL / (uint64_t)wusp->locks));
267 }
268
269 rrdset_done(st);
270 }
271 }
272
273 static void workers_total_memory_calls_chart(void) {
274 {
275 static RRDSET *st = NULL;
276 static RRDDIM *rd[WORKERS_MEMORY_CALL_MAX] = { NULL };
277 uint64_t memory_calls[WORKERS_MEMORY_CALL_MAX] = { 0 };
278
279 if(unlikely(!st)) {
280 st = rrdset_create_localhost(
281 "netdata"
282 , "memory_calls_total"
283 , NULL
284 , "memory calls"
285 , "netdata.memory_calls_total"
286 , "Netdata Total Memory Calls"
287 , "calls"
288 , "netdata"
289 , "pulse"
290 , 920005
291 , localhost->rrd_update_every
292 , RRDSET_TYPE_LINE
293 );
294
295 for (int j = 0; j < WORKERS_MEMORY_CALL_MAX; ++j)
296 rd[j] = rrddim_add(st, WORKERS_MEMORY_CALL_2str(j), NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
297 }
298
299 for(size_t i = 0; all_workers_utilization[i].name ;i++) {
300 struct worker_utilization *wu = &all_workers_utilization[i];
301
302 for (int j = 0; j < WORKERS_MEMORY_CALL_MAX; ++j)
303 memory_calls[j] += wu->memory_calls[j];
304 }
305
306 for (int j = 0; j < WORKERS_MEMORY_CALL_MAX; ++j)
307 rrddim_set_by_pointer(st, rd[j], (collected_number)memory_calls[j]);
308
309 rrdset_done(st);
310 }
311 }
312
313 static void workers_total_cpu_utilization_chart(void) {
314 size_t i, cpu_enabled = 0;
315 for(i = 0; all_workers_utilization[i].name ;i++)
316 if(all_workers_utilization[i].workers_cpu_registered) cpu_enabled++;
317
318 if(!cpu_enabled) return;
319
320 static RRDSET *st = NULL;
321
322 if(!st) {
323 st = rrdset_create_localhost(
324 "netdata",
325 "workers_cpu",
326 NULL,
327 "workers",
328 "netdata.workers.cpu_total",
329 "Netdata Workers CPU Utilization (100% = 1 core)",
330 "%",
331 "netdata",
332 "pulse",
333 999000,
334 localhost->rrd_update_every,
335 RRDSET_TYPE_STACKED);
336 }
337
338 for(i = 0; all_workers_utilization[i].name ;i++) {
339 struct worker_utilization *wu = &all_workers_utilization[i];
340 if(!wu->workers_cpu_registered) continue;
341
342 if(!wu->rd_total_cpu_utilizaton)
343 wu->rd_total_cpu_utilizaton = rrddim_add(st, wu->name_lowercase, NULL, 1, 100, RRD_ALGORITHM_ABSOLUTE);
344
345 rrddim_set_by_pointer(st, wu->rd_total_cpu_utilizaton, (collected_number)((double)wu->workers_cpu_total * 100.0));
346 }
347
348 rrdset_done(st);
349 }
350
351 #define WORKER_CHART_DECIMAL_PRECISION 100
352
353 static void workers_utilization_update_chart(struct worker_utilization *wu) {
354 if(!wu->workers_registered) return;
355
356 //fprintf(stderr, "%-12s WORKER UTILIZATION: %-3.2f%%, %zu jobs done, %zu running, on %zu workers, min %-3.02f%%, max %-3.02f%%.\n",
357 // wu->name,
358 // (double)wu->workers_total_busy_time * 100.0 / (double)wu->workers_total_duration,
359 // wu->workers_total_jobs_started, wu->workers_busy, wu->workers_registered,
360 // wu->workers_min_busy_time, wu->workers_max_busy_time);
361
362 // ----------------------------------------------------------------------
363
364 if(unlikely(!wu->st_workers_time)) {
365 char name[RRD_ID_LENGTH_MAX + 1];
366 snprintfz(name, RRD_ID_LENGTH_MAX, "workers_time_%s", wu->name_lowercase);
367
368 char context[RRD_ID_LENGTH_MAX + 1];
369 snprintf(context, RRD_ID_LENGTH_MAX, "netdata.workers.%s.time", wu->name_lowercase);
370
371 wu->st_workers_time = rrdset_create_localhost(
372 "netdata"
373 , name
374 , NULL
375 , wu->family
376 , context
377 , "Netdata Workers Busy Time (100% = all workers busy)"
378 , "%"
379 , "netdata"
380 , "pulse"
381 , wu->priority
382 , localhost->rrd_update_every
383 , RRDSET_TYPE_AREA
384 );
385 }
386
387 // we add the min and max dimensions only when we have multiple workers
388
389 if(unlikely(!wu->rd_workers_time_min && wu->workers_registered > 1))
390 wu->rd_workers_time_min = rrddim_add(wu->st_workers_time, "min", NULL, 1, WORKER_CHART_DECIMAL_PRECISION, RRD_ALGORITHM_ABSOLUTE);
391
392 if(unlikely(!wu->rd_workers_time_max && wu->workers_registered > 1))
393 wu->rd_workers_time_max = rrddim_add(wu->st_workers_time, "max", NULL, 1, WORKER_CHART_DECIMAL_PRECISION, RRD_ALGORITHM_ABSOLUTE);
394
395 if(unlikely(!wu->rd_workers_time_avg))
396 wu->rd_workers_time_avg = rrddim_add(wu->st_workers_time, "average", NULL, 1, WORKER_CHART_DECIMAL_PRECISION, RRD_ALGORITHM_ABSOLUTE);
397
398 if(unlikely(wu->workers_min_busy_time == WORKERS_MIN_PERCENT_DEFAULT)) wu->workers_min_busy_time = 0.0;
399
400 if(wu->rd_workers_time_min)
401 rrddim_set_by_pointer(wu->st_workers_time, wu->rd_workers_time_min, (collected_number)((double)wu->workers_min_busy_time * WORKER_CHART_DECIMAL_PRECISION));
402
403 if(wu->rd_workers_time_max)
404 rrddim_set_by_pointer(wu->st_workers_time, wu->rd_workers_time_max, (collected_number)((double)wu->workers_max_busy_time * WORKER_CHART_DECIMAL_PRECISION));
405
406 if(wu->workers_total_duration == 0)
407 rrddim_set_by_pointer(wu->st_workers_time, wu->rd_workers_time_avg, 0);
408 else
409 rrddim_set_by_pointer(wu->st_workers_time, wu->rd_workers_time_avg, (collected_number)((double)wu->workers_total_busy_time * 100.0 * WORKER_CHART_DECIMAL_PRECISION / (double)wu->workers_total_duration));
410
411 rrdset_done(wu->st_workers_time);
412
413 // ----------------------------------------------------------------------
414
415 #ifdef __linux__
416 if(wu->workers_cpu_registered || wu->st_workers_cpu) {
417 if(unlikely(!wu->st_workers_cpu)) {
418 char name[RRD_ID_LENGTH_MAX + 1];
419 snprintfz(name, RRD_ID_LENGTH_MAX, "workers_cpu_%s", wu->name_lowercase);
420
421 char context[RRD_ID_LENGTH_MAX + 1];
422 snprintf(context, RRD_ID_LENGTH_MAX, "netdata.workers.%s.cpu", wu->name_lowercase);
423
424 wu->st_workers_cpu = rrdset_create_localhost(
425 "netdata"
426 , name
427 , NULL
428 , wu->family
429 , context
430 , "Netdata Workers CPU Utilization (100% = all workers busy)"
431 , "%"
432 , "netdata"
433 , "pulse"
434 , wu->priority + 1
435 , localhost->rrd_update_every
436 , RRDSET_TYPE_AREA
437 );
438 }
439
440 if (unlikely(!wu->rd_workers_cpu_min && wu->workers_registered > 1))
441 wu->rd_workers_cpu_min = rrddim_add(wu->st_workers_cpu, "min", NULL, 1, WORKER_CHART_DECIMAL_PRECISION, RRD_ALGORITHM_ABSOLUTE);
442
443 if (unlikely(!wu->rd_workers_cpu_max && wu->workers_registered > 1))
444 wu->rd_workers_cpu_max = rrddim_add(wu->st_workers_cpu, "max", NULL, 1, WORKER_CHART_DECIMAL_PRECISION, RRD_ALGORITHM_ABSOLUTE);
445
446 if(unlikely(!wu->rd_workers_cpu_avg))
447 wu->rd_workers_cpu_avg = rrddim_add(wu->st_workers_cpu, "average", NULL, 1, WORKER_CHART_DECIMAL_PRECISION, RRD_ALGORITHM_ABSOLUTE);
448
449 if(unlikely(wu->workers_cpu_min == WORKERS_MIN_PERCENT_DEFAULT)) wu->workers_cpu_min = 0.0;
450
451 if(wu->rd_workers_cpu_min)
452 rrddim_set_by_pointer(wu->st_workers_cpu, wu->rd_workers_cpu_min, (collected_number)(wu->workers_cpu_min * WORKER_CHART_DECIMAL_PRECISION));
453
454 if(wu->rd_workers_cpu_max)
455 rrddim_set_by_pointer(wu->st_workers_cpu, wu->rd_workers_cpu_max, (collected_number)(wu->workers_cpu_max * WORKER_CHART_DECIMAL_PRECISION));
456
457 if(wu->workers_cpu_registered == 0)
458 rrddim_set_by_pointer(wu->st_workers_cpu, wu->rd_workers_cpu_avg, 0);
459 else
460 rrddim_set_by_pointer(wu->st_workers_cpu, wu->rd_workers_cpu_avg, (collected_number)( wu->workers_cpu_total * WORKER_CHART_DECIMAL_PRECISION / (NETDATA_DOUBLE)wu->workers_cpu_registered ));
461
462 rrdset_done(wu->st_workers_cpu);
463 }
464 #endif
465
466 // ----------------------------------------------------------------------------------------------------------------
467
468 if(unlikely(!wu->st_workers_jobs_per_job_type)) {
469 char name[RRD_ID_LENGTH_MAX + 1];
470 snprintfz(name, RRD_ID_LENGTH_MAX, "workers_jobs_by_type_%s", wu->name_lowercase);
471
472 char context[RRD_ID_LENGTH_MAX + 1];
473 snprintf(context, RRD_ID_LENGTH_MAX, "netdata.workers.%s.jobs_started_by_type", wu->name_lowercase);
474
475 wu->st_workers_jobs_per_job_type = rrdset_create_localhost(
476 "netdata"
477 , name
478 , NULL
479 , wu->family
480 , context
481 , "Netdata Workers Jobs Started by Type"
482 , "jobs"
483 , "netdata"
484 , "pulse"
485 , wu->priority + 2
486 , localhost->rrd_update_every
487 , RRDSET_TYPE_STACKED
488 );
489 }
490
491 {
492 size_t i;
493 for(i = 0; i <= wu->workers_max_job_id ;i++) {
494 if(unlikely(wu->per_job_type[i].type != WORKER_METRIC_IDLE_BUSY))
495 continue;
496
497 if (wu->per_job_type[i].name) {
498
499 if(unlikely(!wu->per_job_type[i].rd_jobs_started))
500 wu->per_job_type[i].rd_jobs_started = rrddim_add(wu->st_workers_jobs_per_job_type, string2str(wu->per_job_type[i].name), NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
501
502 rrddim_set_by_pointer(wu->st_workers_jobs_per_job_type, wu->per_job_type[i].rd_jobs_started, (collected_number)(wu->per_job_type[i].data[0].jobs_started));
503 }
504 }
505 }
506
507 rrdset_done(wu->st_workers_jobs_per_job_type);
508
509 // ----------------------------------------------------------------------------------------------------------------
510
511 if(unlikely(!wu->st_workers_time_per_job_type)) {
512 char name[RRD_ID_LENGTH_MAX + 1];
513 snprintfz(name, RRD_ID_LENGTH_MAX, "workers_busy_time_by_type_%s", wu->name_lowercase);
514
515 char context[RRD_ID_LENGTH_MAX + 1];
516 snprintf(context, RRD_ID_LENGTH_MAX, "netdata.workers.%s.time_by_type", wu->name_lowercase);
517
518 wu->st_workers_time_per_job_type = rrdset_create_localhost(
519 "netdata"
520 , name
521 , NULL
522 , wu->family
523 , context
524 , "Netdata Workers Busy Time by Type"
525 , "ms"
526 , "netdata"
527 , "pulse"
528 , wu->priority + 3
529 , localhost->rrd_update_every
530 , RRDSET_TYPE_STACKED
531 );
532 }
533
534 {
535 size_t i;
536 for(i = 0; i <= wu->workers_max_job_id ;i++) {
537 if(unlikely(wu->per_job_type[i].type != WORKER_METRIC_IDLE_BUSY))
538 continue;
539
540 if (wu->per_job_type[i].name) {
541
542 if(unlikely(!wu->per_job_type[i].rd_busy_time))
543 wu->per_job_type[i].rd_busy_time = rrddim_add(wu->st_workers_time_per_job_type, string2str(wu->per_job_type[i].name), NULL, 1, USEC_PER_MS, RRD_ALGORITHM_ABSOLUTE);
544
545 rrddim_set_by_pointer(wu->st_workers_time_per_job_type, wu->per_job_type[i].rd_busy_time, (collected_number)(wu->per_job_type[i].data[0].busy_time));
546 }
547 }
548 }
549
550 rrdset_done(wu->st_workers_time_per_job_type);
551
552 // ----------------------------------------------------------------------------------------------------------------
553
554 if(unlikely(!wu->st_workers_avg_time_per_job_type)) {
555 char name[RRD_ID_LENGTH_MAX + 1];
556 snprintfz(name, RRD_ID_LENGTH_MAX, "workers_avg_time_by_type_%s", wu->name_lowercase);
557
558 char context[RRD_ID_LENGTH_MAX + 1];
559 snprintf(context, RRD_ID_LENGTH_MAX, "netdata.workers.%s.avg_time_by_type", wu->name_lowercase);
560
561 wu->st_workers_avg_time_per_job_type = rrdset_create_localhost(
562 "netdata"
563 , name
564 , NULL
565 , wu->family
566 , context
567 , "Netdata Workers Average Time by Type"
568 , "ms"
569 , "netdata"
570 , "pulse"
571 , wu->priority + 4
572 , localhost->rrd_update_every
573 , RRDSET_TYPE_STACKED
574 );
575 }
576
577 {
578 size_t i;
579 for(i = 0; i <= wu->workers_max_job_id ;i++) {
580 if(unlikely(wu->per_job_type[i].type != WORKER_METRIC_IDLE_BUSY))
581 continue;
582
583 if (wu->per_job_type[i].name) {
584
585 if(unlikely(!wu->per_job_type[i].rd_avg_time))
586 wu->per_job_type[i].rd_avg_time = rrddim_add(wu->st_workers_avg_time_per_job_type, string2str(wu->per_job_type[i].name), NULL, 1, USEC_PER_MS, RRD_ALGORITHM_ABSOLUTE);
587
588 ssize_t jobs_delta = (ssize_t)wu->per_job_type[i].data[0].jobs_started;
589 susec_t time_delta = (susec_t)wu->per_job_type[i].data[0].busy_time;
590 susec_t average = (jobs_delta != 0) ? time_delta / jobs_delta : 0;
591 rrddim_set_by_pointer(wu->st_workers_avg_time_per_job_type, wu->per_job_type[i].rd_avg_time, (collected_number)average);
592 }
593 }
594 }
595
596 rrdset_done(wu->st_workers_avg_time_per_job_type);
597
598 // ----------------------------------------------------------------------------------------------------------------
599
600 if(wu->st_workers_threads || wu->workers_registered > 1) {
601 if(unlikely(!wu->st_workers_threads)) {
602 char name[RRD_ID_LENGTH_MAX + 1];
603 snprintfz(name, RRD_ID_LENGTH_MAX, "workers_threads_%s", wu->name_lowercase);
604
605 char context[RRD_ID_LENGTH_MAX + 1];
606 snprintf(context, RRD_ID_LENGTH_MAX, "netdata.workers.%s.threads", wu->name_lowercase);
607
608 wu->st_workers_threads = rrdset_create_localhost(
609 "netdata"
610 , name
611 , NULL
612 , wu->family
613 , context
614 , "Netdata Workers Threads"
615 , "threads"
616 , "netdata"
617 , "pulse"
618 , wu->priority + 5
619 , localhost->rrd_update_every
620 , RRDSET_TYPE_STACKED
621 );
622
623 wu->rd_workers_threads_free = rrddim_add(wu->st_workers_threads, "free", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
624 wu->rd_workers_threads_busy = rrddim_add(wu->st_workers_threads, "busy", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
625 }
626
627 rrddim_set_by_pointer(wu->st_workers_threads, wu->rd_workers_threads_free, (collected_number)(wu->workers_registered - wu->workers_busy));
628 rrddim_set_by_pointer(wu->st_workers_threads, wu->rd_workers_threads_busy, (collected_number)(wu->workers_busy));
629 rrdset_done(wu->st_workers_threads);
630 }
631
632 // ----------------------------------------------------------------------
633 // spinlocks
634
635 {
636 if(unlikely(!wu->st_spinlocks_locks)) {
637 char name[RRD_ID_LENGTH_MAX + 1];
638 snprintfz(name, RRD_ID_LENGTH_MAX, "workers_spinlock_locks_%s", wu->name_lowercase);
639
640 char context[RRD_ID_LENGTH_MAX + 1];
641 snprintf(context, RRD_ID_LENGTH_MAX, "netdata.workers.%s.spinlock_locks", wu->name_lowercase);
642
643 wu->st_spinlocks_locks = rrdset_create_localhost(
644 "netdata"
645 , name
646 , NULL
647 , wu->family
648 , context
649 , "Netdata Spinlock Locks"
650 , "locks"
651 , "netdata"
652 , "pulse"
653 , wu->priority + 6
654 , localhost->rrd_update_every
655 , RRDSET_TYPE_LINE
656 );
657 }
658
659 Word_t idx = 0;
660 for(struct worker_spinlocks *wusp = SPINLOCKS_FIRST(&wu->spinlocks, &idx);
661 wusp;
662 wusp = SPINLOCKS_NEXT(&wu->spinlocks, &idx)) {
663 const char *func = (const char *)idx;
664 if(!wusp->rd_locks)
665 wusp->rd_locks = rrddim_add(wu->st_spinlocks_locks, func, NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
666
667 rrddim_set_by_pointer(wu->st_spinlocks_locks, wusp->rd_locks, (collected_number)wusp->locks);
668 }
669
670 rrdset_done(wu->st_spinlocks_locks);
671 }
672
673 {
674 if(unlikely(!wu->st_spinlocks_spins)) {
675 char name[RRD_ID_LENGTH_MAX + 1];
676 snprintfz(name, RRD_ID_LENGTH_MAX, "workers_spinlock_spins_%s", wu->name_lowercase);
677
678 char context[RRD_ID_LENGTH_MAX + 1];
679 snprintf(context, RRD_ID_LENGTH_MAX, "netdata.workers.%s.spinlock_spins", wu->name_lowercase);
680
681 wu->st_spinlocks_spins = rrdset_create_localhost(
682 "netdata"
683 , name
684 , NULL
685 , wu->family
686 , context
687 , "Netdata Spinlock Spins"
688 , "spins"
689 , "netdata"
690 , "pulse"
691 , wu->priority + 7
692 , localhost->rrd_update_every
693 , RRDSET_TYPE_LINE
694 );
695 }
696
697 Word_t idx = 0;
698 for(struct worker_spinlocks *wusp = SPINLOCKS_FIRST(&wu->spinlocks, &idx);
699 wusp;
700 wusp = SPINLOCKS_NEXT(&wu->spinlocks, &idx)) {
701 const char *func = (const char *)idx;
702 if(!wusp->rd_spins)
703 wusp->rd_spins = rrddim_add(wu->st_spinlocks_spins, func, NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
704
705 rrddim_set_by_pointer(wu->st_spinlocks_spins, wusp->rd_spins, (collected_number)wusp->spins);
706 }
707
708 rrdset_done(wu->st_spinlocks_spins);
709 }
710
711 // ----------------------------------------------------------------------
712 // memory calls
713
714 {
715 if(unlikely(!wu->st_memory_calls)) {
716 char name[RRD_ID_LENGTH_MAX + 1];
717 snprintfz(name, RRD_ID_LENGTH_MAX, "workers_memory_calls_%s", wu->name_lowercase);
718
719 char context[RRD_ID_LENGTH_MAX + 1];
720 snprintf(context, RRD_ID_LENGTH_MAX, "netdata.workers.%s.memory_calls", wu->name_lowercase);
721
722 wu->st_memory_calls = rrdset_create_localhost(
723 "netdata"
724 , name
725 , NULL
726 , wu->family
727 , context
728 , "Netdata Memory Calls"
729 , "calls"
730 , "netdata"
731 , "pulse"
732 , wu->priority + 8
733 , localhost->rrd_update_every
734 , RRDSET_TYPE_LINE
735 );
736 }
737
738 for(size_t i = 0; i < WORKERS_MEMORY_CALL_MAX; i++) {
739 if(!wu->rd_memory_calls[i])
740 wu->rd_memory_calls[i] = rrddim_add(wu->st_memory_calls, WORKERS_MEMORY_CALL_2str(i), NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
741
742 rrddim_set_by_pointer(wu->st_memory_calls, wu->rd_memory_calls[i], (collected_number)wu->memory_calls[i]);
743 }
744
745 rrdset_done(wu->st_memory_calls);
746 }
747
748 // ----------------------------------------------------------------------
749 // custom metric types WORKER_METRIC_ABSOLUTE
750
751 {
752 size_t i;
753 for (i = 0; i <= wu->workers_max_job_id; i++) {
754 if(wu->per_job_type[i].type != WORKER_METRIC_ABSOLUTE)
755 continue;
756
757 if(!wu->per_job_type[i].count_value)
758 continue;
759
760 if(!wu->per_job_type[i].st) {
761 size_t job_name_len = string_strlen(wu->per_job_type[i].name);
762 size_t job_name_max_len = workers_chart_context_job_name_max_length(wu->name_lowercase, ".value.");
763 if(job_name_len > job_name_max_len) job_name_len = job_name_max_len;
764
765 char job_name_sanitized[RRD_ID_LENGTH_MAX + 1];
766 rrdset_strncpyz_name(job_name_sanitized, string2str(wu->per_job_type[i].name), job_name_len);
767
768 char name[RRD_ID_LENGTH_MAX + 1];
769 snprintfz(name, RRD_ID_LENGTH_MAX, "workers_%s_value_%s", wu->name_lowercase, job_name_sanitized);
770
771 char context[RRD_ID_LENGTH_MAX + 1];
772 size_t context_len = (size_t)snprintfz(context, RRD_ID_LENGTH_MAX + 1, "netdata.workers.%s.value.", wu->name_lowercase);
773 strcatz(context, context_len, job_name_sanitized, sizeof(context));
774
775 char title[1000 + 1];
776 snprintf(title, 1000, "Netdata Workers %s value of %s", wu->name_lowercase, string2str(wu->per_job_type[i].name));
777
778 wu->per_job_type[i].st = rrdset_create_localhost(
779 "netdata"
780 , name
781 , NULL
782 , wu->family
783 , context
784 , title
785 , (wu->per_job_type[i].units)?string2str(wu->per_job_type[i].units):"value"
786 , "netdata"
787 , "pulse"
788 , wu->priority + 10 + i
789 , localhost->rrd_update_every
790 , RRDSET_TYPE_LINE
791 );
792
793 wu->per_job_type[i].rd_min = rrddim_add(wu->per_job_type[i].st, "min", NULL, 1, WORKER_CHART_DECIMAL_PRECISION, RRD_ALGORITHM_ABSOLUTE);
794 wu->per_job_type[i].rd_max = rrddim_add(wu->per_job_type[i].st, "max", NULL, 1, WORKER_CHART_DECIMAL_PRECISION, RRD_ALGORITHM_ABSOLUTE);
795 wu->per_job_type[i].rd_avg = rrddim_add(wu->per_job_type[i].st, "average", NULL, 1, WORKER_CHART_DECIMAL_PRECISION, RRD_ALGORITHM_ABSOLUTE);
796 }
797
798 rrddim_set_by_pointer(wu->per_job_type[i].st, wu->per_job_type[i].rd_min, (collected_number)(wu->per_job_type[i].min_value * WORKER_CHART_DECIMAL_PRECISION));
799 rrddim_set_by_pointer(wu->per_job_type[i].st, wu->per_job_type[i].rd_max, (collected_number)(wu->per_job_type[i].max_value * WORKER_CHART_DECIMAL_PRECISION));
800 rrddim_set_by_pointer(wu->per_job_type[i].st, wu->per_job_type[i].rd_avg, (collected_number)(wu->per_job_type[i].sum_value / wu->per_job_type[i].count_value * WORKER_CHART_DECIMAL_PRECISION));
801
802 rrdset_done(wu->per_job_type[i].st);
803 }
804 }
805
806 // ----------------------------------------------------------------------
807 // custom metric types WORKER_METRIC_INCREMENTAL
808
809 {
810 size_t i;
811 for (i = 0; i <= wu->workers_max_job_id ; i++) {
812 if(wu->per_job_type[i].type != WORKER_METRIC_INCREMENT && wu->per_job_type[i].type != WORKER_METRIC_INCREMENTAL_TOTAL)
813 continue;
814
815 if(!wu->per_job_type[i].count_value)
816 continue;
817
818 if(!wu->per_job_type[i].st) {
819 size_t job_name_len = string_strlen(wu->per_job_type[i].name);
820 size_t job_name_max_len = workers_chart_context_job_name_max_length(wu->name_lowercase, ".rate.");
821 if(job_name_len > job_name_max_len) job_name_len = job_name_max_len;
822
823 char job_name_sanitized[RRD_ID_LENGTH_MAX + 1];
824 rrdset_strncpyz_name(job_name_sanitized, string2str(wu->per_job_type[i].name), job_name_len);
825
826 char name[RRD_ID_LENGTH_MAX + 1];
827 snprintfz(name, RRD_ID_LENGTH_MAX, "workers_%s_rate_%s", wu->name_lowercase, job_name_sanitized);
828
829 char context[RRD_ID_LENGTH_MAX + 1];
830 size_t context_len = (size_t)snprintfz(context, RRD_ID_LENGTH_MAX + 1, "netdata.workers.%s.rate.", wu->name_lowercase);
831 strcatz(context, context_len, job_name_sanitized, sizeof(context));
832
833 char title[1000 + 1];
834 snprintf(title, 1000, "Netdata Workers %s rate of %s", wu->name_lowercase, string2str(wu->per_job_type[i].name));
835
836 wu->per_job_type[i].st = rrdset_create_localhost(
837 "netdata"
838 , name
839 , NULL
840 , wu->family
841 , context
842 , title
843 , (wu->per_job_type[i].units)?string2str(wu->per_job_type[i].units):"rate"
844 , "netdata"
845 , "pulse"
846 , wu->priority + 10 + i
847 , localhost->rrd_update_every
848 , RRDSET_TYPE_LINE
849 );
850
851 wu->per_job_type[i].rd_min = rrddim_add(wu->per_job_type[i].st, "min", NULL, 1, WORKER_CHART_DECIMAL_PRECISION, RRD_ALGORITHM_ABSOLUTE);
852 wu->per_job_type[i].rd_max = rrddim_add(wu->per_job_type[i].st, "max", NULL, 1, WORKER_CHART_DECIMAL_PRECISION, RRD_ALGORITHM_ABSOLUTE);
853 wu->per_job_type[i].rd_avg = rrddim_add(wu->per_job_type[i].st, "average", NULL, 1, WORKER_CHART_DECIMAL_PRECISION, RRD_ALGORITHM_ABSOLUTE);
854 }
855
856 rrddim_set_by_pointer(wu->per_job_type[i].st, wu->per_job_type[i].rd_min, (collected_number)(wu->per_job_type[i].min_value * WORKER_CHART_DECIMAL_PRECISION));
857 rrddim_set_by_pointer(wu->per_job_type[i].st, wu->per_job_type[i].rd_max, (collected_number)(wu->per_job_type[i].max_value * WORKER_CHART_DECIMAL_PRECISION));
858 rrddim_set_by_pointer(wu->per_job_type[i].st, wu->per_job_type[i].rd_avg, (collected_number)(wu->per_job_type[i].sum_value / wu->per_job_type[i].count_value * WORKER_CHART_DECIMAL_PRECISION));
859
860 rrdset_done(wu->per_job_type[i].st);
861 }
862 }
863 }
864
865 static void workers_utilization_reset_statistics(struct worker_utilization *wu) {
866 Word_t idx = 0;
867 for(struct worker_spinlocks *wusp = SPINLOCKS_FIRST(&wu->spinlocks, &idx);
868 wusp;
869 wusp = SPINLOCKS_NEXT(&wu->spinlocks, &idx)) {
870 wusp->locks = 0;
871 wusp->spins = 0;
872 }
873
874 wu->workers_registered = 0;
875 wu->workers_busy = 0;
876 wu->workers_total_busy_time = 0;
877 wu->workers_total_duration = 0;
878 wu->workers_total_jobs_started = 0;
879 wu->workers_min_busy_time = WORKERS_MIN_PERCENT_DEFAULT;
880 wu->workers_max_busy_time = 0;
881
882 wu->workers_cpu_registered = 0;
883 wu->workers_cpu_min = WORKERS_MIN_PERCENT_DEFAULT;
884 wu->workers_cpu_max = 0;
885 wu->workers_cpu_total = 0;
886
887 size_t i;
888 for(i = 0; i < WORKER_UTILIZATION_MAX_JOB_TYPES ;i++) {
889 if(unlikely(!wu->name_lowercase)) {
890 wu->name_lowercase = strdupz(wu->name);
891 char *s = wu->name_lowercase;
892 for( ; *s ; s++) *s = tolower(*s);
893 }
894
895 // copy the usage data
896 wu->per_job_type[i].data[1].jobs_started = wu->per_job_type[i].data[0].jobs_started;
897 wu->per_job_type[i].data[1].busy_time = wu->per_job_type[i].data[0].busy_time;
898
899 // reset them for the next collection
900 wu->per_job_type[i].data[0].jobs_started = 0;
901 wu->per_job_type[i].data[0].busy_time = 0;
902
903 wu->per_job_type[i].min_value = NAN;
904 wu->per_job_type[i].max_value = NAN;
905 wu->per_job_type[i].sum_value = NAN;
906 wu->per_job_type[i].count_value = 0;
907 }
908
909 struct worker_thread *wt;
910 for(wt = wu->threads; wt ; wt = wt->next) {
911 wt->enabled = false;
912 wt->cpu_enabled = false;
913 }
914
915 memset(wu->memory_calls, 0, sizeof(wu->memory_calls));
916 }
917
918 #define TASK_STAT_PREFIX "/proc/self/task/"
919 #define TASK_STAT_SUFFIX "/stat"
920
921 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) {
922 #ifdef __linux__
923 static char filename[sizeof(TASK_STAT_PREFIX) + sizeof(TASK_STAT_SUFFIX) + 20] = TASK_STAT_PREFIX;
924 static size_t start_pos = sizeof(TASK_STAT_PREFIX) - 1;
925 static procfile *ff = NULL;
926
927 // construct the filename
928 size_t end_pos = snprintfz(&filename[start_pos], 20, "%d", pid);
929 strcpy(&filename[start_pos + end_pos], TASK_STAT_SUFFIX);
930
931 // (re)open the procfile to the new filename
932 bool set_quotes = (ff == NULL) ? true : false;
933 ff = procfile_reopen(ff, filename, NULL, PROCFILE_FLAG_ERROR_ON_ERROR_LOG);
934 if(unlikely(!ff)) return -1;
935
936 if(set_quotes)
937 procfile_set_open_close(ff, "(", ")");
938
939 // read the entire file and split it to lines and words
940 ff = procfile_readall(ff);
941 if(unlikely(!ff)) return -1;
942
943 // parse the numbers we are interested
944 *utime = str2kernel_uint_t(procfile_lineword(ff, 0, 13));
945 *stime = str2kernel_uint_t(procfile_lineword(ff, 0, 14));
946
947 // leave the file open for the next iteration
948
949 return 0;
950 #else
951 // TODO: add here cpu time detection per thread, for FreeBSD and MacOS
952 *utime = 0;
953 *stime = 0;
954 return 1;
955 #endif
956 }
957
958 static Pvoid_t workers_by_pid_JudyL_array = NULL;
959
960 static void workers_threads_cleanup(struct worker_utilization *wu) {
961 struct worker_thread *t = wu->threads;
962 while(t) {
963 struct worker_thread *next = t->next;
964
965 if(!t->enabled) {
966 JudyLDel(&workers_by_pid_JudyL_array, t->pid, PJE0);
967 DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(wu->threads, t, prev, next);
968 freez(t);
969 }
970 t = next;
971 }
972 }
973
974 static struct worker_thread *worker_thread_find(struct worker_utilization *wu __maybe_unused, pid_t pid) {
975 struct worker_thread *wt = NULL;
976
977 Pvoid_t *PValue = JudyLGet(workers_by_pid_JudyL_array, pid, PJE0);
978 if(PValue)
979 wt = *PValue;
980
981 return wt;
982 }
983
984 static struct worker_thread *worker_thread_create(struct worker_utilization *wu, pid_t pid) {
985 struct worker_thread *wt;
986
987 wt = (struct worker_thread *)callocz(1, sizeof(struct worker_thread));
988 wt->pid = pid;
989
990 Pvoid_t *PValue = JudyLIns(&workers_by_pid_JudyL_array, pid, PJE0);
991 *PValue = wt;
992
993 // link it
994 DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(wu->threads, wt, prev, next);
995
996 return wt;
997 }
998
999 static struct worker_thread *worker_thread_find_or_create(struct worker_utilization *wu, pid_t pid) {
1000 struct worker_thread *wt;
1001 wt = worker_thread_find(wu, pid);
1002 if(!wt) wt = worker_thread_create(wu, pid);
1003
1004 return wt;
1005 }
1006
1007 static void worker_utilization_charts_callback(void *ptr
1008 , pid_t pid
1009 , const char *thread_tag __maybe_unused
1010 , size_t max_job_id
1011 , size_t utilization_usec
1012 , size_t duration_usec
1013 , size_t jobs_started
1014 , size_t is_running
1015 , STRING **job_types_names
1016 , STRING **job_types_units
1017 , WORKER_METRIC_TYPE *job_types_metric_types
1018 , size_t *job_types_jobs_started
1019 , usec_t *job_types_busy_time
1020 , NETDATA_DOUBLE *job_types_custom_metrics
1021 , const char *spinlock_functions[]
1022 , size_t *spinlock_locks
1023 , size_t *spinlock_spins
1024 , uint64_t *memory_calls
1025 ) {
1026 struct worker_utilization *wu = (struct worker_utilization *)ptr;
1027
1028 // find the worker_thread in the list
1029 struct worker_thread *wt = worker_thread_find_or_create(wu, pid);
1030
1031 if(utilization_usec > duration_usec)
1032 utilization_usec = duration_usec;
1033
1034 wt->enabled = true;
1035 wt->busy_time = utilization_usec;
1036 wt->jobs_started = jobs_started;
1037
1038 wt->utime_old = wt->utime;
1039 wt->stime_old = wt->stime;
1040 wt->collected_time_old = wt->collected_time;
1041
1042 if(max_job_id > wu->workers_max_job_id)
1043 wu->workers_max_job_id = max_job_id;
1044
1045 wu->workers_total_busy_time += utilization_usec;
1046 wu->workers_total_duration += duration_usec;
1047 wu->workers_total_jobs_started += jobs_started;
1048 wu->workers_busy += is_running;
1049 wu->workers_registered++;
1050
1051 double util = (double)utilization_usec * 100.0 / (double)duration_usec;
1052 if(util > wu->workers_max_busy_time)
1053 wu->workers_max_busy_time = util;
1054
1055 if(util < wu->workers_min_busy_time)
1056 wu->workers_min_busy_time = util;
1057
1058 // accumulate per job type statistics
1059 for(size_t i = 0; i <= max_job_id ;i++) {
1060 if(!wu->per_job_type[i].name && job_types_names[i])
1061 wu->per_job_type[i].name = string_dup(job_types_names[i]);
1062
1063 if(!wu->per_job_type[i].units && job_types_units[i])
1064 wu->per_job_type[i].units = string_dup(job_types_units[i]);
1065
1066 wu->per_job_type[i].type = job_types_metric_types[i];
1067
1068 wu->per_job_type[i].data[0].jobs_started += job_types_jobs_started[i];
1069 wu->per_job_type[i].data[0].busy_time += job_types_busy_time[i];
1070
1071 NETDATA_DOUBLE value = job_types_custom_metrics[i];
1072 if(netdata_double_isnumber(value)) {
1073 if(!wu->per_job_type[i].count_value) {
1074 wu->per_job_type[i].count_value = 1;
1075 wu->per_job_type[i].min_value = value;
1076 wu->per_job_type[i].max_value = value;
1077 wu->per_job_type[i].sum_value = value;
1078 }
1079 else {
1080 wu->per_job_type[i].count_value++;
1081 wu->per_job_type[i].sum_value += value;
1082 if(value < wu->per_job_type[i].min_value) wu->per_job_type[i].min_value = value;
1083 if(value > wu->per_job_type[i].max_value) wu->per_job_type[i].max_value = value;
1084 }
1085 }
1086 }
1087
1088 // find its CPU utilization
1089 if((!read_thread_cpu_time_from_proc_stat(pid, &wt->utime, &wt->stime))) {
1090 wt->collected_time = now_realtime_usec();
1091 usec_t delta = wt->collected_time - wt->collected_time_old;
1092
1093 double utime = (double)(wt->utime - wt->utime_old) / (double)system_hz * 100.0 * (double)USEC_PER_SEC / (double)delta;
1094 double stime = (double)(wt->stime - wt->stime_old) / (double)system_hz * 100.0 * (double)USEC_PER_SEC / (double)delta;
1095 double cpu = utime + stime;
1096 wt->cpu = cpu;
1097 wt->cpu_enabled = true;
1098
1099 wu->workers_cpu_total += cpu;
1100 if(cpu < wu->workers_cpu_min) wu->workers_cpu_min = cpu;
1101 if(cpu > wu->workers_cpu_max) wu->workers_cpu_max = cpu;
1102 }
1103 wu->workers_cpu_registered += (wt->cpu_enabled) ? 1 : 0;
1104
1105 // ----------------------------------------------------------------------------------------------------------------
1106 // spinlock contention
1107
1108 // spinlocks
1109 for(size_t i = 0; i < WORKER_SPINLOCK_CONTENTION_FUNCTIONS && spinlock_functions[i] ;i++) {
1110 struct worker_spinlocks *wusp = SPINLOCKS_GET(&wu->spinlocks, (Word_t)spinlock_functions[i]);
1111 if(!wusp) {
1112 wusp = callocz(1, sizeof(*wusp));
1113 SPINLOCKS_SET(&wu->spinlocks, (Word_t)spinlock_functions[i], wusp);
1114 }
1115 wusp->locks += spinlock_locks[i];
1116 wusp->spins += spinlock_spins[i];
1117
1118 wusp = SPINLOCKS_GET(&ALL_SPINLOCKS, (Word_t)spinlock_functions[i]);
1119 if(!wusp) {
1120 wusp = callocz(1, sizeof(*wusp));
1121 SPINLOCKS_SET(&ALL_SPINLOCKS, (Word_t)spinlock_functions[i], wusp);
1122 }
1123 wusp->locks += spinlock_locks[i];
1124 wusp->spins += spinlock_spins[i];
1125 }
1126
1127 // ----------------------------------------------------------------------------------------------------------------
1128 // memory calls
1129
1130 for(size_t i = 0; i < WORKERS_MEMORY_CALL_MAX ;i++)
1131 wu->memory_calls[i] += memory_calls[i];
1132 }
1133
1134 static void spinlocks_free_callback(Word_t key __maybe_unused, struct worker_spinlocks *wusp, void *data __maybe_unused) {
1135 freez(wusp);
1136 }
1137
1138 void pulse_workers_cleanup(void) {
1139 int i, j;
1140 for(i = 0; all_workers_utilization[i].name ;i++) {
1141 struct worker_utilization *wu = &all_workers_utilization[i];
1142
1143 if(wu->name_lowercase) {
1144 freez(wu->name_lowercase);
1145 wu->name_lowercase = NULL;
1146 }
1147
1148 for(j = 0; j < WORKER_UTILIZATION_MAX_JOB_TYPES ;j++) {
1149 string_freez(wu->per_job_type[j].name);
1150 wu->per_job_type[j].name = NULL;
1151
1152 string_freez(wu->per_job_type[j].units);
1153 wu->per_job_type[j].units = NULL;
1154 }
1155
1156 // Free the spinlocks Judy array for this worker
1157 SPINLOCKS_FREE(&wu->spinlocks, spinlocks_free_callback, NULL);
1158
1159 // mark all threads as not enabled
1160 struct worker_thread *t;
1161 for(t = wu->threads; t ; t = t->next)
1162 t->enabled = false;
1163
1164 // let the cleanup job free them
1165 workers_threads_cleanup(wu);
1166 }
1167
1168 // Clean up the global spinlocks array
1169 SPINLOCKS_FREE(&ALL_SPINLOCKS, spinlocks_free_callback, NULL);
1170 }
1171
1172 void pulse_workers_do(bool extended) {
1173 if(!extended) return;
1174
1175 static size_t iterations = 0;
1176 iterations++;
1177
1178 Word_t idx = 0;
1179 for(struct worker_spinlocks *wusp = SPINLOCKS_FIRST(&ALL_SPINLOCKS, &idx);
1180 wusp;
1181 wusp = SPINLOCKS_NEXT(&ALL_SPINLOCKS, &idx)) {
1182 wusp->locks = 0;
1183 wusp->spins = 0;
1184 }
1185
1186 for(int i = 0; all_workers_utilization[i].name ;i++) {
1187 workers_utilization_reset_statistics(&all_workers_utilization[i]);
1188
1189 workers_foreach(all_workers_utilization[i].name, worker_utilization_charts_callback, &all_workers_utilization[i]);
1190
1191 // skip the first iteration, so that we don't accumulate startup utilization to our charts
1192 if(likely(iterations > 1))
1193 workers_utilization_update_chart(&all_workers_utilization[i]);
1194
1195 workers_threads_cleanup(&all_workers_utilization[i]);
1196 }
1197
1198 workers_total_cpu_utilization_chart();
1199 workers_total_spinlock_contention_chart();
1200 workers_total_memory_calls_chart();
1201 }