| 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 | } |