master
c 792 lines 29 KB
Raw
1 #include "worker_utilization.h"
2
3 #define WORKER_IDLE 'I'
4 #define WORKER_BUSY 'B'
5
6 struct worker_job_type {
7 STRING *name;
8 STRING *units;
9
10 // statistics controlled variables
11 size_t statistics_last_jobs_started;
12 usec_t statistics_last_busy_time;
13 NETDATA_DOUBLE statistics_last_custom_value;
14
15 // worker controlled variables
16 volatile size_t worker_jobs_started;
17 volatile usec_t worker_busy_time;
18
19 WORKER_METRIC_TYPE type;
20 NETDATA_DOUBLE custom_value;
21 };
22
23 struct worker_spinlock {
24 const char *function;
25 size_t locks;
26 size_t spins;
27
28 size_t statistics_last_locks;
29 size_t statistics_last_spins;
30 };
31
32 struct worker {
33 pid_t pid;
34 const char *tag;
35 const char *workname;
36
37 // statistics controlled variables
38 volatile usec_t statistics_last_checkpoint;
39 size_t statistics_last_jobs_started;
40 usec_t statistics_last_busy_time;
41
42 // the worker controlled variables
43 size_t worker_max_job_id;
44 volatile size_t job_id;
45 volatile size_t jobs_started;
46 volatile usec_t busy_time;
47 volatile usec_t last_action_timestamp;
48 volatile char last_action;
49
50 struct worker_job_type per_job_type[WORKER_UTILIZATION_MAX_JOB_TYPES];
51
52 size_t spinlocks_used;
53 struct worker_spinlock spinlocks[WORKER_SPINLOCK_CONTENTION_FUNCTIONS];
54
55 uint64_t memory_calls[WORKERS_MEMORY_CALL_MAX];
56
57 struct worker *next;
58 struct worker *prev;
59 };
60
61 struct workers_workname { // this is what we add to JudyHS
62 SPINLOCK spinlock;
63 struct worker *base;
64 };
65
66 ENUM_STR_MAP_DEFINE(WORKERS_MEMORY_CALL) = {
67 {WORKERS_MEMORY_CALL_LIBC_MALLOC, "malloc"},
68 {WORKERS_MEMORY_CALL_LIBC_CALLOC, "calloc"},
69 {WORKERS_MEMORY_CALL_LIBC_REALLOC, "realloc"},
70 {WORKERS_MEMORY_CALL_LIBC_FREE, "free"},
71 {WORKERS_MEMORY_CALL_LIBC_STRDUP, "strdup"},
72 {WORKERS_MEMORY_CALL_LIBC_STRNDUP, "strndup"},
73 {WORKERS_MEMORY_CALL_LIBC_POSIX_MEMALIGN, "posix_memalign"},
74 {WORKERS_MEMORY_CALL_LIBC_POSIX_MEMALIGN_FREE, "posix_memalign_free"},
75 {WORKERS_MEMORY_CALL_MMAP, "mmap"},
76 {WORKERS_MEMORY_CALL_MUNMAP, "munmap"},
77
78 // terminator
79 {0, NULL},
80 };
81
82 ENUM_STR_DEFINE_FUNCTIONS(WORKERS_MEMORY_CALL, WORKERS_MEMORY_CALL_LIBC_MALLOC, "other");
83
84 static struct workers_globals {
85 bool enabled;
86
87 SPINLOCK spinlock;
88 Pvoid_t worknames_JudyHS;
89 size_t memory;
90
91 #ifdef FSANITIZE_ADDRESS
92 // For tracking all registered worker items during ASAN builds
93 Pvoid_t workers_JudyL; // JudyL array of all worker structs
94 Pvoid_t worknames_JudyL; // JudyL array of all workname structs
95 Pvoid_t worker_strings_JudyL; // JudyL array of all STRING objects in workers
96 #endif
97
98 } workers_globals = { // workers globals, the base of all worknames
99 .enabled = false,
100 .spinlock = SPINLOCK_INITIALIZER, // a lock for the worknames index
101 .worknames_JudyHS = NULL, // the worknames index
102 #ifdef FSANITIZE_ADDRESS
103 .workers_JudyL = NULL,
104 .worknames_JudyL = NULL,
105 .worker_strings_JudyL = NULL,
106 #endif
107 };
108
109 static __thread struct worker *worker = NULL; // the current thread worker
110 static __thread size_t last_job_id = 0;
111
112 size_t workers_get_last_job_id() {
113 return last_job_id;
114 }
115
116 static ALWAYS_INLINE usec_t worker_now_monotonic_usec(void) {
117 #ifdef NETDATA_WITHOUT_WORKERS_LATENCY
118 return 0;
119 #else
120 return now_monotonic_usec();
121 #endif
122 }
123
124 void workers_utilization_enable(void) {
125 workers_globals.enabled = true;
126 }
127
128 size_t workers_allocated_memory(void) {
129 if(!workers_globals.enabled)
130 return 0;
131
132 spinlock_lock(&workers_globals.spinlock);
133 size_t memory = workers_globals.memory;
134 spinlock_unlock(&workers_globals.spinlock);
135
136 return memory;
137 }
138
139 void worker_register(const char *name) {
140 if(likely(worker || !workers_globals.enabled))
141 return;
142
143 worker = callocz(1, sizeof(struct worker));
144 worker->pid = gettid_cached();
145 worker->tag = strdupz(nd_thread_tag());
146 worker->workname = strdupz(name);
147
148 usec_t now = worker_now_monotonic_usec();
149 worker->statistics_last_checkpoint = now;
150 worker->last_action_timestamp = now;
151 worker->last_action = WORKER_IDLE;
152
153 size_t name_size = strlen(name) + 1;
154 spinlock_lock(&workers_globals.spinlock);
155
156 workers_globals.memory += sizeof(struct worker) + strlen(worker->tag) + 1 + strlen(worker->workname) + 1;
157
158 #ifdef FSANITIZE_ADDRESS
159 // Track the worker struct in our JudyL array for ASAN builds
160 Pvoid_t *WValue = JudyLIns(&workers_globals.workers_JudyL, (Word_t)worker, PJE0);
161 if (WValue != PJERR)
162 *WValue = (void *)1;
163 #endif
164
165 JudyAllocThreadPulseReset();
166 Pvoid_t *PValue = JudyHSIns(&workers_globals.worknames_JudyHS, (void *)name, name_size, PJE0);
167 int64_t judy_mem = JudyAllocThreadPulseGetAndReset();
168
169 struct workers_workname *workname = *PValue;
170 if(!workname) {
171 workname = mallocz(sizeof(struct workers_workname));
172 spinlock_init(&workname->spinlock);
173 workname->base = NULL;
174 *PValue = workname;
175
176 workers_globals.memory = (int64_t)workers_globals.memory + (int64_t)sizeof(struct workers_workname) + judy_mem;
177
178 #ifdef FSANITIZE_ADDRESS
179 // Track the workname struct in our JudyL array for ASAN builds
180 Pvoid_t *WValue = JudyLIns(&workers_globals.worknames_JudyL, (Word_t)workname, PJE0);
181 if (WValue != PJERR)
182 *WValue = (void *)1;
183 #endif
184 }
185
186 spinlock_lock(&workname->spinlock);
187 DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(workname->base, worker, prev, next);
188 spinlock_unlock(&workname->spinlock);
189
190 spinlock_unlock(&workers_globals.spinlock);
191 }
192
193 void worker_register_job_custom_metric(size_t job_id, const char *name, const char *units, WORKER_METRIC_TYPE type) {
194 if(likely(!worker)) return;
195
196 if(unlikely(job_id >= WORKER_UTILIZATION_MAX_JOB_TYPES)) {
197 netdata_log_error("WORKER_UTILIZATION: job_id %zu is too big. Max is %zu", job_id, (size_t)(WORKER_UTILIZATION_MAX_JOB_TYPES - 1));
198 return;
199 }
200
201 if(job_id > worker->worker_max_job_id)
202 worker->worker_max_job_id = job_id;
203
204 if(worker->per_job_type[job_id].name) {
205 if(strcmp(string2str(worker->per_job_type[job_id].name), name) != 0 || worker->per_job_type[job_id].type != type || strcmp(string2str(worker->per_job_type[job_id].units), units) != 0)
206 netdata_log_error("WORKER_UTILIZATION: duplicate job registration: worker '%s' job id %zu is '%s', ignoring the later '%s'", worker->workname, job_id, string2str(worker->per_job_type[job_id].name), name);
207 return;
208 }
209
210 STRING *name_str = string_strdupz(name);
211 STRING *units_str = string_strdupz(units);
212
213 worker->per_job_type[job_id].name = name_str;
214 worker->per_job_type[job_id].units = units_str;
215 worker->per_job_type[job_id].type = type;
216
217 #ifdef FSANITIZE_ADDRESS
218 // Track the strings in our JudyL array for ASAN builds
219 spinlock_lock(&workers_globals.spinlock);
220
221 // Track the name STRING with reference counting
222 if (name_str) {
223 Pvoid_t *PValue = JudyLIns(&workers_globals.worker_strings_JudyL, (Word_t)name_str, PJE0);
224 if (PValue != PJERR) {
225 // Increment the reference count (or initialize to 1 if new)
226 size_t count = (size_t)(uintptr_t)*PValue;
227 count++;
228 *PValue = (void *)(uintptr_t)count;
229 }
230 }
231
232 // Track the units STRING with reference counting
233 if (units_str) {
234 Pvoid_t *PValue = JudyLIns(&workers_globals.worker_strings_JudyL, (Word_t)units_str, PJE0);
235 if (PValue != PJERR) {
236 // Increment the reference count (or initialize to 1 if new)
237 size_t count = (size_t)(uintptr_t)*PValue;
238 count++;
239 *PValue = (void *)(uintptr_t)count;
240 }
241 }
242
243 spinlock_unlock(&workers_globals.spinlock);
244 #endif
245 }
246
247 void worker_register_job_name(size_t job_id, const char *name) {
248 worker_register_job_custom_metric(job_id, name, "", WORKER_METRIC_IDLE_BUSY);
249 }
250
251 void worker_unregister(void) {
252 if(likely(!worker)) return;
253
254 size_t workname_size = strlen(worker->workname) + 1;
255 spinlock_lock(&workers_globals.spinlock);
256 Pvoid_t *PValue = JudyHSGet(workers_globals.worknames_JudyHS, (void *)worker->workname, workname_size);
257 if(PValue) {
258 struct workers_workname *workname = *PValue;
259 spinlock_lock(&workname->spinlock);
260 DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(workname->base, worker, prev, next);
261 spinlock_unlock(&workname->spinlock);
262
263 if(!workname->base) {
264 JError_t J_Error = { 0 };
265
266 JudyAllocThreadPulseReset();
267 int ret = JudyHSDel(&workers_globals.worknames_JudyHS, (void *)worker->workname, workname_size, &J_Error);
268 int64_t judy_mem = JudyAllocThreadPulseGetAndReset();
269 workers_globals.memory = (int64_t)workers_globals.memory + judy_mem;
270
271 if(likely(ret == 1)) {
272 freez(workname);
273 workers_globals.memory = (int64_t)workers_globals.memory - (int64_t)sizeof(struct workers_workname);
274 }
275 else if(unlikely(ret == JERR))
276 netdata_log_error("WORKER_UTILIZATION: cannot delete worker workname '%s' from JudyHS, JU_ERRNO_* == %u, ID == %d", worker->workname, JU_ERRNO(&J_Error), JU_ERRID(&J_Error));
277 else
278 netdata_log_error("WORKER_UTILIZATION: worker workname '%s' disappeared from JudyHS during unregister", worker->workname);
279 }
280 }
281 workers_globals.memory -= sizeof(struct worker) + strlen(worker->tag) + 1 + strlen(worker->workname) + 1;
282
283 #ifdef FSANITIZE_ADDRESS
284 // Remove this worker from the tracking array
285 JudyLDel(&workers_globals.workers_JudyL, (Word_t)worker, PJE0);
286 #endif
287
288 spinlock_unlock(&workers_globals.spinlock);
289
290 // Free all thread-local resources associated with this worker
291 for(int i = 0; i < WORKER_UTILIZATION_MAX_JOB_TYPES; i++) {
292 #ifdef FSANITIZE_ADDRESS
293 // Decrement reference count in tracking array before freeing
294 if (worker->per_job_type[i].name) {
295 spinlock_lock(&workers_globals.spinlock);
296 Pvoid_t *PValue = JudyLGet(workers_globals.worker_strings_JudyL, (Word_t)worker->per_job_type[i].name, PJE0);
297 if (PValue) {
298 size_t count = (size_t)(uintptr_t)*PValue;
299 if (count > 1) {
300 // Decrement reference count
301 count--;
302 *PValue = (void *)(uintptr_t)count;
303 } else {
304 // Last reference, remove from tracking
305 JudyLDel(&workers_globals.worker_strings_JudyL, (Word_t)worker->per_job_type[i].name, PJE0);
306 }
307 }
308 spinlock_unlock(&workers_globals.spinlock);
309 }
310 if (worker->per_job_type[i].units) {
311 spinlock_lock(&workers_globals.spinlock);
312 Pvoid_t *PValue = JudyLGet(workers_globals.worker_strings_JudyL, (Word_t)worker->per_job_type[i].units, PJE0);
313 if (PValue) {
314 size_t count = (size_t)(uintptr_t)*PValue;
315 if (count > 1) {
316 // Decrement reference count
317 count--;
318 *PValue = (void *)(uintptr_t)count;
319 } else {
320 // Last reference, remove from tracking
321 JudyLDel(&workers_globals.worker_strings_JudyL, (Word_t)worker->per_job_type[i].units, PJE0);
322 }
323 }
324 spinlock_unlock(&workers_globals.spinlock);
325 }
326 #endif
327 // Then free the strings
328 string_freez(worker->per_job_type[i].name);
329 string_freez(worker->per_job_type[i].units);
330 }
331
332 freez((void *)worker->tag);
333 freez((void *)worker->workname);
334 freez(worker);
335
336 worker = NULL;
337 }
338
339 // Cleanup all worker utilization resources
340 void worker_utilization_cleanup(void) {
341 if(!workers_globals.enabled)
342 return;
343
344 // Clean up the current thread's worker if it exists
345 worker_unregister();
346
347 spinlock_lock(&workers_globals.spinlock);
348
349 #ifdef FSANITIZE_ADDRESS
350 // Free any remaining strings in the tracking array according to their reference counts
351 if (workers_globals.worker_strings_JudyL) {
352 Word_t string_ptr = 0;
353 Pvoid_t *PValue = JudyLFirst(workers_globals.worker_strings_JudyL, &string_ptr, PJE0);
354 size_t total_strings = 0;
355 size_t total_refs = 0;
356
357 // First pass: count how many strings and references
358 while (PValue) {
359 total_strings++;
360 size_t refs = (size_t)(uintptr_t)*PValue;
361 total_refs += refs;
362 PValue = JudyLNext(workers_globals.worker_strings_JudyL, &string_ptr, PJE0);
363 }
364
365 // If any strings remain, emit info about them
366 if (total_strings > 0) {
367 fprintf(stderr, "WORKERS UTILIZATION: Freeing %zu STRING objects with %zu total references\n",
368 total_strings, total_refs);
369
370 // Second pass: free each string the correct number of times
371 string_ptr = 0;
372 PValue = JudyLFirst(workers_globals.worker_strings_JudyL, &string_ptr, PJE0);
373
374 while (PValue) {
375 STRING *str = (STRING *)string_ptr;
376 size_t refs = (size_t)(uintptr_t)*PValue;
377
378 // Get the next one before we potentially delete this entry
379 PValue = JudyLNext(workers_globals.worker_strings_JudyL, &string_ptr, PJE0);
380
381 // Free the string exactly the number of times it was referenced
382 for (size_t i = 0; i < refs; i++) {
383 string_freez(str);
384 }
385 }
386 }
387
388 // Free the array itself
389 JudyLFreeArray(&workers_globals.worker_strings_JudyL, PJE0);
390 workers_globals.worker_strings_JudyL = NULL;
391 }
392
393 // We don't need to free worker or workname structs here as they should
394 // be freed by worker_unregister, but we should free the tracking arrays
395 if (workers_globals.workers_JudyL) {
396 JudyLFreeArray(&workers_globals.workers_JudyL, PJE0);
397 workers_globals.workers_JudyL = NULL;
398 }
399
400 if (workers_globals.worknames_JudyL) {
401 JudyLFreeArray(&workers_globals.worknames_JudyL, PJE0);
402 workers_globals.worknames_JudyL = NULL;
403 }
404 #endif
405
406 // Free the JudyHS array that contains workname structs
407 if (workers_globals.worknames_JudyHS) {
408 #ifdef FSANITIZE_ADDRESS
409 // For ASAN builds, we need to free any remaining workname structures
410 // First, collect all workname pointers
411 Pvoid_t worknames_to_free = NULL;
412
413 // Cannot iterate JudyHS directly, but we can use worknames_JudyL which tracks all worknames
414 if (workers_globals.worknames_JudyL) {
415 Word_t workname_ptr = 0;
416 Pvoid_t *PValue = JudyLFirst(workers_globals.worknames_JudyL, &workname_ptr, PJE0);
417 size_t count = 0;
418
419 while (PValue) {
420 // Store this workname to free it later
421 Pvoid_t *StoreValue = JudyLIns(&worknames_to_free, workname_ptr, PJE0);
422 if (StoreValue != PJERR)
423 *StoreValue = (void *)1;
424
425 count++;
426 PValue = JudyLNext(workers_globals.worknames_JudyL, &workname_ptr, PJE0);
427 }
428
429 if (count > 0) {
430 fprintf(stderr, "WORKERS UTILIZATION: Freeing %zu workers_workname structures from tracking array\n", count);
431 }
432 }
433
434 // Also try to get the LIBUV workname directly since it's the one leaking
435 // This is a direct approach to ensure we don't miss any worknames
436 const char *libuv_name = "LIBUV";
437 size_t libuv_name_size = strlen(libuv_name) + 1;
438 Pvoid_t *LiuvValue = JudyHSGet(workers_globals.worknames_JudyHS, (void *)libuv_name, libuv_name_size);
439 if (LiuvValue && *LiuvValue) {
440 struct workers_workname *libuv_workname = *LiuvValue;
441 // Check if we've already got this workname in our tracking array
442 bool already_tracked = false;
443 if (worknames_to_free) {
444 Pvoid_t *ExistingValue = JudyLGet(worknames_to_free, (Word_t)libuv_workname, PJE0);
445 already_tracked = (ExistingValue != NULL);
446 }
447
448 if (!already_tracked) {
449 // Add this workname to our list
450 Pvoid_t *StoreValue = JudyLIns(&worknames_to_free, (Word_t)libuv_workname, PJE0);
451 if (StoreValue != PJERR) {
452 *StoreValue = (void *)1;
453 fprintf(stderr, "WORKERS UTILIZATION: Found LIBUV workname not in tracking array\n");
454 }
455 }
456 }
457 #endif
458
459 // Free the JudyHS array
460 JudyHSFreeArray(&workers_globals.worknames_JudyHS, PJE0);
461 workers_globals.worknames_JudyHS = NULL;
462
463 #ifdef FSANITIZE_ADDRESS
464 // Now free all the workname structures we collected
465 if (worknames_to_free) {
466 Word_t workname_ptr = 0;
467 Pvoid_t *PValue = JudyLFirst(worknames_to_free, &workname_ptr, PJE0);
468
469 while (PValue) {
470 struct workers_workname *workname = (struct workers_workname *)workname_ptr;
471 freez(workname);
472
473 PValue = JudyLNext(worknames_to_free, &workname_ptr, PJE0);
474 }
475
476 JudyLFreeArray(&worknames_to_free, PJE0);
477 }
478 #endif
479 }
480
481 // Reset memory count
482 workers_globals.memory = 0;
483
484 spinlock_unlock(&workers_globals.spinlock);
485 }
486
487 static void worker_is_idle_with_time(usec_t now) {
488 usec_t delta = now - worker->last_action_timestamp;
489 worker->busy_time += delta;
490 worker->per_job_type[worker->job_id].worker_busy_time += delta;
491
492 // the worker was busy
493 // set it to idle before we set the timestamp
494
495 worker->last_action = WORKER_IDLE;
496 if(likely(worker->last_action_timestamp < now))
497 worker->last_action_timestamp = now;
498 }
499
500 ALWAYS_INLINE void worker_is_idle(void) {
501 if(likely(!worker || worker->last_action != WORKER_BUSY)) return;
502
503 last_job_id = WORKER_UTILIZATION_MAX_JOB_TYPES;
504 worker_is_idle_with_time(worker_now_monotonic_usec());
505 }
506
507 static void worker_is_busy_do(size_t job_id) {
508 usec_t now = worker_now_monotonic_usec();
509
510 if(worker->last_action == WORKER_BUSY)
511 worker_is_idle_with_time(now);
512
513 // the worker was idle
514 // set the timestamp and then set it to busy
515
516 worker->job_id = job_id;
517 worker->per_job_type[job_id].worker_jobs_started++;
518 worker->jobs_started++;
519 worker->last_action_timestamp = now;
520 worker->last_action = WORKER_BUSY;
521 }
522
523 ALWAYS_INLINE void worker_is_busy(size_t job_id) {
524 last_job_id = job_id;
525
526 if(likely(!worker || job_id >= WORKER_UTILIZATION_MAX_JOB_TYPES))
527 return;
528
529 worker_is_busy_do(job_id);
530 }
531
532 static void worker_set_metric_do(size_t job_id, NETDATA_DOUBLE value) {
533 switch(worker->per_job_type[job_id].type) {
534 case WORKER_METRIC_INCREMENT:
535 worker->per_job_type[job_id].custom_value += value;
536 break;
537
538 case WORKER_METRIC_INCREMENTAL_TOTAL:
539 case WORKER_METRIC_ABSOLUTE:
540 default:
541 worker->per_job_type[job_id].custom_value = value;
542 break;
543 }
544 }
545
546 ALWAYS_INLINE void worker_set_metric(size_t job_id, NETDATA_DOUBLE value) {
547 if(likely(!worker || job_id >= WORKER_UTILIZATION_MAX_JOB_TYPES))
548 return;
549
550 worker_set_metric_do(job_id, value);
551 }
552
553 // --------------------------------------------------------------------------------------------------------------------
554
555 static ALWAYS_INLINE size_t pointer_hash_function(const char *func) {
556 uintptr_t addr = (uintptr_t)func;
557 return (size_t)(((addr >> 4) | (addr >> 16)) + func[0]) % WORKER_SPINLOCK_CONTENTION_FUNCTIONS;
558 }
559
560 static void worker_spinlock_contention_do(const char *func, size_t spins) {
561 size_t hash = pointer_hash_function(func);
562 for (size_t i = 0; i < WORKER_SPINLOCK_CONTENTION_FUNCTIONS; i++) {
563 size_t slot = (hash + i) % WORKER_SPINLOCK_CONTENTION_FUNCTIONS;
564 if (worker->spinlocks[slot].function == func || worker->spinlocks[slot].function == NULL) {
565 // Either an empty slot or a matching slot
566
567 worker->spinlocks[slot].function = func;
568 worker->spinlocks[slot].locks++;
569 worker->spinlocks[slot].spins += spins;
570
571 return;
572 }
573 }
574
575 // Array is full - do nothing
576 }
577
578 ALWAYS_INLINE void worker_spinlock_contention(const char *func, size_t spins) {
579 if(likely(!worker))
580 return;
581
582 worker_spinlock_contention_do(func, spins);
583 }
584
585 ALWAYS_INLINE void workers_memory_call(WORKERS_MEMORY_CALL call) {
586 if(likely(!worker || call >= WORKERS_MEMORY_CALL_MAX))
587 return;
588
589 worker->memory_calls[call]++;
590 }
591
592 // statistics interface
593
594 void workers_foreach(const char *name, void (*callback)(
595 void *data
596 , pid_t pid
597 , const char *thread_tag
598 , size_t max_job_id
599 , size_t utilization_usec
600 , size_t duration_usec
601 , size_t jobs_started, size_t is_running
602 , STRING **job_types_names
603 , STRING **job_types_units
604 , WORKER_METRIC_TYPE *job_metric_types
605 , size_t *job_types_jobs_started
606 , usec_t *job_types_busy_time
607 , NETDATA_DOUBLE *job_custom_values
608 , const char *spinlock_functions[]
609 , size_t *spinlock_locks
610 , size_t *spinlock_spins
611 , uint64_t *memory_calls
612 )
613 , void *data) {
614 if(!workers_globals.enabled)
615 return;
616
617 spinlock_lock(&workers_globals.spinlock);
618 usec_t busy_time, delta;
619 size_t jobs_started, jobs_running;
620
621 size_t workname_size = strlen(name) + 1;
622 struct workers_workname *workname;
623 Pvoid_t *PValue = JudyHSGet(workers_globals.worknames_JudyHS, (void *)name, workname_size);
624 if(PValue) {
625 workname = *PValue;
626 spinlock_lock(&workname->spinlock);
627 }
628 else
629 workname = NULL;
630
631 spinlock_unlock(&workers_globals.spinlock);
632
633 if(!workname)
634 return;
635
636 struct worker *p;
637 DOUBLE_LINKED_LIST_FOREACH_FORWARD(workname->base, p, prev, next) {
638 usec_t now = worker_now_monotonic_usec();
639
640 // find per job type statistics
641 STRING *per_job_type_name[WORKER_UTILIZATION_MAX_JOB_TYPES];
642 STRING *per_job_type_units[WORKER_UTILIZATION_MAX_JOB_TYPES];
643 WORKER_METRIC_TYPE per_job_metric_type[WORKER_UTILIZATION_MAX_JOB_TYPES];
644 size_t per_job_type_jobs_started[WORKER_UTILIZATION_MAX_JOB_TYPES];
645 usec_t per_job_type_busy_time[WORKER_UTILIZATION_MAX_JOB_TYPES];
646 NETDATA_DOUBLE per_job_custom_values[WORKER_UTILIZATION_MAX_JOB_TYPES];
647
648 const char *spinlock_functions[WORKER_SPINLOCK_CONTENTION_FUNCTIONS];
649 size_t spinlock_locks[WORKER_SPINLOCK_CONTENTION_FUNCTIONS];
650 size_t spinlock_spins[WORKER_SPINLOCK_CONTENTION_FUNCTIONS];
651
652 uint64_t memory_calls[WORKERS_MEMORY_CALL_MAX];
653
654 size_t max_job_id = p->worker_max_job_id;
655 for(size_t i = 0; i <= max_job_id ;i++) {
656 per_job_type_name[i] = p->per_job_type[i].name;
657 per_job_type_units[i] = p->per_job_type[i].units;
658 per_job_metric_type[i] = p->per_job_type[i].type;
659
660 switch(p->per_job_type[i].type) {
661 default:
662 case WORKER_METRIC_EMPTY: {
663 per_job_type_jobs_started[i] = 0;
664 per_job_type_busy_time[i] = 0;
665 per_job_custom_values[i] = NAN;
666 break;
667 }
668
669 case WORKER_METRIC_IDLE_BUSY: {
670 size_t tmp_jobs_started = p->per_job_type[i].worker_jobs_started;
671 per_job_type_jobs_started[i] = tmp_jobs_started - p->per_job_type[i].statistics_last_jobs_started;
672 p->per_job_type[i].statistics_last_jobs_started = tmp_jobs_started;
673
674 usec_t tmp_busy_time = p->per_job_type[i].worker_busy_time;
675 per_job_type_busy_time[i] = tmp_busy_time - p->per_job_type[i].statistics_last_busy_time;
676 p->per_job_type[i].statistics_last_busy_time = tmp_busy_time;
677
678 per_job_custom_values[i] = NAN;
679 break;
680 }
681
682 case WORKER_METRIC_ABSOLUTE: {
683 per_job_type_jobs_started[i] = 0;
684 per_job_type_busy_time[i] = 0;
685
686 per_job_custom_values[i] = p->per_job_type[i].custom_value;
687 break;
688 }
689
690 case WORKER_METRIC_INCREMENTAL_TOTAL:
691 case WORKER_METRIC_INCREMENT: {
692 per_job_type_jobs_started[i] = 0;
693 per_job_type_busy_time[i] = 0;
694
695 NETDATA_DOUBLE tmp_custom_value = p->per_job_type[i].custom_value;
696 per_job_custom_values[i] = tmp_custom_value - p->per_job_type[i].statistics_last_custom_value;
697 p->per_job_type[i].statistics_last_custom_value = tmp_custom_value;
698
699 break;
700 }
701 }
702 }
703
704 // get a copy of the worker variables
705 size_t worker_job_id = p->job_id;
706 usec_t worker_busy_time = p->busy_time;
707 size_t worker_jobs_started = p->jobs_started;
708 char worker_last_action = p->last_action;
709 usec_t worker_last_action_timestamp = p->last_action_timestamp;
710
711 delta = now - p->statistics_last_checkpoint;
712 p->statistics_last_checkpoint = now;
713
714 // this is the only variable both the worker thread and the statistics thread are writing
715 // we set this only when the worker is busy, so that the worker will not
716 // accumulate all the busy time, but only the time after the point we collected statistics
717 if(worker_last_action == WORKER_BUSY && p->last_action_timestamp == worker_last_action_timestamp && p->last_action == WORKER_BUSY)
718 p->last_action_timestamp = now;
719
720 // calculate delta busy time
721 busy_time = worker_busy_time - p->statistics_last_busy_time;
722 p->statistics_last_busy_time = worker_busy_time;
723
724 // calculate delta jobs done
725 jobs_started = worker_jobs_started - p->statistics_last_jobs_started;
726 p->statistics_last_jobs_started = worker_jobs_started;
727
728 jobs_running = 0;
729 if(worker_last_action == WORKER_BUSY) {
730 // the worker is still busy with something
731 // let's add that busy time to the reported one
732 usec_t dt = now - worker_last_action_timestamp;
733 busy_time += dt;
734 per_job_type_busy_time[worker_job_id] += dt;
735 jobs_running = 1;
736 }
737
738 // ------------------------------------------------------------------------------------------------------------
739 // spinlock contention
740
741 size_t t = 0;
742 for(size_t i = 0; i < WORKER_SPINLOCK_CONTENTION_FUNCTIONS ;i++) {
743 if(!p->spinlocks[i].function) continue;
744
745 spinlock_functions[t] = p->spinlocks[i].function;
746
747 size_t tmp = p->spinlocks[i].locks;
748 spinlock_locks[t] = tmp - p->spinlocks[i].statistics_last_locks;
749 p->spinlocks[i].statistics_last_locks = tmp;
750
751 tmp = p->spinlocks[i].spins;
752 spinlock_spins[t] = tmp - p->spinlocks[i].statistics_last_spins;
753 p->spinlocks[i].statistics_last_spins = tmp;
754
755 t++;
756 }
757
758 for(; t < WORKER_SPINLOCK_CONTENTION_FUNCTIONS ;t++) {
759 spinlock_functions[t] = NULL;
760 spinlock_locks[t] = 0;
761 spinlock_spins[t] = 0;
762 }
763
764 // ------------------------------------------------------------------------------------------------------------
765
766 memcpy(memory_calls, p->memory_calls, sizeof(memory_calls));
767
768 // ------------------------------------------------------------------------------------------------------------
769
770 callback(data
771 , p->pid
772 , p->tag
773 , max_job_id
774 , busy_time
775 , delta
776 , jobs_started
777 , jobs_running
778 , per_job_type_name
779 , per_job_type_units
780 , per_job_metric_type
781 , per_job_type_jobs_started
782 , per_job_type_busy_time
783 , per_job_custom_values
784 , spinlock_functions
785 , spinlock_locks
786 , spinlock_spins
787 , memory_calls
788 );
789 }
790
791 spinlock_unlock(&workname->spinlock);
792 }