1 // SPDX-License-Identifier: CDDL-1.0 2 /* 3 * This file and its contents are supplied under the terms of the 4 * Common Development and Distribution License ("CDDL"), version 1.0. 5 * You may only use this file in accordance with the terms of version 6 * 1.0 of the CDDL. 7 * 8 * A full copy of the text of the CDDL should have accompanied this 9 * source. A copy of the CDDL is also available via the Internet at 10 * https://opensource.org/license/CDDL-1.0. 11 */ 12 13 /* 14 * Copyright (c) 2026 by Garth Snyder. All rights reserved. 15 */ 16 17 #include <assert.h> 18 #include <atomic.h> 19 #include <err.h> 20 #include <errno.h> 21 #include <math.h> 22 #include <pthread.h> 23 #include <sched.h> 24 #include <stddef.h> 25 #include <stdint.h> 26 #include <stdio.h> 27 #include <stdlib.h> 28 #include <string.h> 29 #include <sys/param.h> 30 #include <sys/random.h> 31 #include <sys/stdtypes.h> 32 #include <sys/sysmacros.h> 33 #include <sys/time.h> 34 #include <time.h> 35 #include <unistd.h> 36 37 #include "zstream_queue.h" 38 #include "zstream_util.h" 39 40 #define ENQUEUE_DELAY_NSEC (100 * 1000) /* 100us */ 41 #define DISPATCH_BACKUP_NSEC (1000 * 1000) /* 1ms */ 42 #define BATCH_TIME_NSEC (400 * 1000) /* 400us */ 43 44 #define Q_MOD(index) ((index) % ZQ_SLOTS_PER_QUEUE) 45 #define Q_SLOT(queue, index) ((queue)->zq_slots[Q_MOD(index)]) 46 47 #define Q_FULL(queue) ((queue)->zq_ix.enqueue - (queue)->zq_ix.dequeue >= \ 48 ZQ_SLOTS_PER_QUEUE) 49 50 // #define MONITOR_QUEUES /* Queue tenancy data per interval */ 51 // #define SHOW_BATCH_HISTOGRAMS /* Batch size histograms per queue */ 52 53 /* 54 * A zstream_queue is a ring buffer with four indexes: enqueue, claim, 55 * complete, and dequeue, in that order. No index can move beyond its 56 * preceding index. Every interval between indexes contains work items in a 57 * particular state: enqueued, claimed for work, or completed. Items never 58 * leave the ring buffer, so FIFO order is guaranteed on dequeueing. 59 * 60 * In concept, every index has a corresponding condition that threads can 61 * wait on if they are interested in knowing when that index moves: 62 * enqueued, claimed, completed, dequeued. However, the reality deviates 63 * from this model in two ways: 64 * 65 * - There is no "claimed" condition, because no thread would wait on it. 66 * Claiming and processing are one unified operation. Dequeuers await the 67 * "completed" condition. 68 * 69 * - All queues share one thread pool, so idle threads are not bound to any 70 * particular queue. Instead of having queue-specific "enqueued" conditions, 71 * queues share a centralized dispatch system. On being awakened, worker 72 * threads assign themselves to a queue through a scoring mechanism 73 * described in the comments at score_queue(). 74 * 75 * LOCKING 76 * 77 * There are three types of lock: 78 * 79 * - One global lock that gates changes to the thread pool and queue cohort. 80 * This lock also acts as the mutex for the tp_wake_worker condition. 81 * 82 * - A second, low-contention global lock that protects the dispatch system 83 * 84 * - One lock for each queue 85 * 86 * Any operation that adds or removes queues or threads should hold the pool 87 * lock. Any operation that moves a queue's indexes should hold the queue 88 * lock. Any thread waiting for work waits on tp_wake_worker. 89 * 90 * Worker threads hold no locks while they are actually processing items. 91 * 92 * The global locking order is dispatch -> pool -> queue. 93 * 94 * DISPATCH 95 * 96 * Four events trigger dispatch loops: 97 * 98 * 1) A worker thread completing its batch. Threads always check to see if 99 * there's more claimable work before going to sleep. 100 * 101 * 2) A worker thread discovering more work than it can handle on its own. 102 * Before starting work on its own batch, the worker attempts to signal 103 * another thread to wake up and assess the current state. 104 * 105 * 3) Enqueues. These go through the dispatch system and are batched. 106 * Roughly ENQUEUE_DELAY_NSEC after an enqueue (on any queue), the dispatch 107 * thread attempts to awaken a worker. 108 * 109 * 4) The expiration of a backup timer. The atomic value tp_unclaimed tracks 110 * the total number of enqueued-but-unclaimed items across all queues. When 111 * zero, it indicates that no worker dispatch is currently necessary. This 112 * value is rigorously maintained and the increments and decrements are 113 * sequentially consistent. However, the reads are relaxed, so a reader may 114 * see a stale value. In the event that a critical worker wakeup is dropped, 115 * the backup timer intervenes to keep dispatches running. 116 */ 117 118 typedef struct { 119 queue_item_t *qs_item; 120 size_t qs_cost; 121 boolean_t qs_completed; 122 boolean_t qs_end_of_stream; 123 } queue_slot_t; 124 125 typedef struct { 126 uint64_t enqueue; 127 uint64_t claim; 128 uint64_t complete; 129 uint64_t dequeue; 130 } zq_indexes_t; 131 132 typedef struct { 133 pthread_cond_t completed; 134 pthread_cond_t dequeued; 135 } zq_conditions_t; 136 137 #ifdef MONITOR_QUEUES 138 /* 139 * Running mean and standard deviation of queue depth, accumulated by 140 * Welford's online algorithm. Welford is numerically stable and doesn't 141 * need wider-than-platform integers. 142 */ 143 typedef struct { 144 int min; 145 int max; 146 uint64_t ds_samples; 147 double ds_mean; 148 double ds_m2; /* Sum of squared deviations */ 149 } depth_stats_t; 150 #endif 151 152 typedef struct { 153 #ifdef MONITOR_QUEUES 154 depth_stats_t depth; 155 #endif 156 uint64_t nsec_used; 157 size_t cost_processed; 158 } zq_stats_t; 159 160 struct zstream_queue { 161 int zq_id; 162 queue_slot_t *zq_slots; 163 pthread_mutex_t zq_mutex; 164 zq_indexes_t zq_ix; 165 zq_conditions_t zq_cond; 166 zq_params_t zq_params; 167 boolean_t zq_disallow_enqueue; 168 zq_stats_t zq_stats; 169 #ifdef SHOW_BATCH_HISTOGRAMS 170 uint64_t zq_histogram[ZQ_MAX_BATCH+1]; /* Batch sizes */ 171 #endif 172 }; 173 174 typedef struct { 175 pthread_mutex_t tp_pool_mutex; 176 pthread_cond_t tp_wake_worker; /* Awaited by workers */ 177 178 pthread_mutex_t tp_dispatch_mutex; 179 pthread_cond_t tp_request_dispatch; /* By dispatch thread */ 180 boolean_t tp_dispatch_requested; 181 182 zstream_queue_t *tp_queues[ZQ_MAX_QUEUES]; 183 int tp_num_queues; 184 185 boolean_t tp_threads_created; 186 int tp_num_threads; 187 188 uint64_t tp_unclaimed; /* Atomic, all queues */ 189 } thread_pool_t; 190 191 typedef union { 192 long long ll; 193 long double ld; 194 void *p; 195 void (*fp)(void); 196 } worst_case_alignment_t; 197 198 static void *queue_worker(void *); 199 static void *dispatch_worker(void *); 200 201 #ifdef MONITOR_QUEUES 202 static void *cpu_and_queue_monitor(void *); 203 static inline void initialize_monitor_data(zstream_queue_t *); 204 static inline void update_monitor_data(zstream_queue_t *); 205 #endif 206 207 #ifdef SHOW_BATCH_HISTOGRAMS 208 static void print_batch_size_histogram(zstream_queue_t *); 209 #endif 210 211 static thread_pool_t pool = {0}; 212 static pthread_once_t once_control = PTHREAD_ONCE_INIT; 213 214 /* 215 * The dispatch timer needs sub-millisecond accuracy. POSIX timers on 216 * FreeBSD don't implement that, but nanosleep() works fine. 217 */ 218 static void 219 sleep_nsec(uint64_t nsec) 220 { 221 struct timespec ts = { 222 .tv_sec = nsec / NANOSEC, 223 .tv_nsec = nsec % NANOSEC 224 }; 225 while (nanosleep(&ts, &ts) != 0) { 226 if (errno != EINTR) 227 err(1, "nanosleep failed"); 228 } 229 } 230 231 static void 232 thread_pool_init(void) 233 { 234 pthread_mutex_init(&pool.tp_pool_mutex, NULL); 235 pthread_cond_init(&pool.tp_wake_worker, NULL); 236 237 pthread_mutex_init(&pool.tp_dispatch_mutex, NULL); 238 pthread_cond_init(&pool.tp_request_dispatch, NULL); 239 240 safe_create_thread(dispatch_worker, NULL, "dispatch", B_TRUE); 241 } 242 243 /* 244 * If this function is to be called at all, it must be called before any 245 * queues have been created. 246 */ 247 void 248 zstream_queue_set_num_threads(int n) 249 { 250 pthread_once(&once_control, thread_pool_init); 251 pthread_mutex_lock(&pool.tp_pool_mutex); 252 if (pool.tp_threads_created) { 253 errx(1, "thread pool size must be set before creating queues"); 254 } else if (n < 1) { 255 errx(1, "number of threads must be at least 1"); 256 } else if (n < ZQ_MIN_THREADS) { 257 warnx("using only %d threads may limit performance, setting " 258 "anyway...", n); 259 } else if (n > 256) { 260 warnx("num_threads = %d seems suspiciously high, setting " 261 "anyway...", n); 262 } 263 pool.tp_num_threads = n; 264 pthread_mutex_unlock(&pool.tp_pool_mutex); 265 } 266 267 /* 268 * Locking: the caller must hold the pool mutex. 269 * 270 * If tp_num_threads is nonzero, it sets the number of threads to spawn. 271 * Otherwise, one thread is spawned per core, with a minimum of 6 threads. 272 * 273 * sched_getaffinity() is a better estimate of available threads than 274 * sysconf because sysconf doesn't account for limits that might be set on, 275 * e.g., a container. 276 */ 277 static void 278 thread_pool_spinup(void) 279 { 280 if (pool.tp_num_threads == 0) { 281 #ifdef CPU_COUNT 282 cpu_set_t cpu_set; 283 if (sched_getaffinity(0, sizeof (cpu_set_t), &cpu_set) != 0) { 284 warn("sched_getaffinity failed, using sysconf"); 285 pool.tp_num_threads = sysconf(_SC_NPROCESSORS_ONLN); 286 } else { 287 pool.tp_num_threads = CPU_COUNT(&cpu_set); 288 } 289 #else 290 pool.tp_num_threads = sysconf(_SC_NPROCESSORS_ONLN); 291 #endif 292 pool.tp_num_threads = MAX(pool.tp_num_threads, ZQ_MIN_THREADS); 293 } 294 for (int i = 0; i < pool.tp_num_threads; i++) { 295 char name[32]; 296 snprintf(name, sizeof (name), "queue-%d", i); 297 safe_create_thread(queue_worker, NULL, name, B_TRUE); 298 } 299 #ifdef MONITOR_QUEUES 300 safe_create_thread(cpu_and_queue_monitor, NULL, "monitor", B_TRUE); 301 #endif 302 } 303 304 zstream_queue_t * 305 zstream_queue_create(zq_params_t *params) 306 { 307 static int next_queue_id = 0; 308 309 VERIFY3P(params->qp_process, !=, NULL); 310 VERIFY3P(params->qp_cost, !=, NULL); 311 VERIFY3U(params->qp_item_size, >, 0); 312 313 pthread_once(&once_control, thread_pool_init); 314 pthread_mutex_lock(&pool.tp_pool_mutex); 315 VERIFY3S(pool.tp_num_queues, <, ZQ_MAX_QUEUES); 316 317 if (!pool.tp_threads_created) { 318 thread_pool_spinup(); 319 pool.tp_threads_created = B_TRUE; 320 } 321 322 zstream_queue_t *queue = safe_malloc(sizeof (zstream_queue_t)); 323 *queue = (zstream_queue_t) { 324 .zq_id = next_queue_id++, 325 .zq_params = *params, 326 .zq_slots = safe_malloc(ZQ_SLOTS_PER_QUEUE * 327 (sizeof (queue_slot_t))) 328 }; 329 pool.tp_queues[pool.tp_num_queues] = queue; 330 331 #ifdef MONITOR_QUEUES 332 initialize_monitor_data(queue); 333 #endif 334 335 size_t qpis_rounded = P2ROUNDUP(params->qp_item_size, 336 _Alignof(worst_case_alignment_t)); 337 uint8_t *items = safe_malloc(ZQ_SLOTS_PER_QUEUE * qpis_rounded); 338 for (int i = 0; i < ZQ_SLOTS_PER_QUEUE; i++) { 339 queue->zq_slots[i].qs_item = 340 (queue_item_t *)(items + i * qpis_rounded); 341 } 342 343 pthread_mutex_init(&queue->zq_mutex, NULL); 344 pthread_cond_init(&queue->zq_cond.completed, NULL); 345 pthread_cond_init(&queue->zq_cond.dequeued, NULL); 346 347 pool.tp_num_queues++; 348 pthread_mutex_unlock(&pool.tp_pool_mutex); 349 return (queue); 350 } 351 352 /* 353 * Try to advance the "claim" and "complete" indexes as far as possible by 354 * examining the qs_completed flag on each item. This can't be done directly 355 * by the threads that complete work, for a couple of reasons: 356 * 357 * - Items can be completed in any order. Just because you (a thread) have 358 * finished your batch doesn't mean that all prior batches have completed. 359 * If there are uncompleted items ahead of you in the ring buffer, you can't 360 * advance the completion index past them on your way out. 361 * 362 * - Items for which the cost function returns 0 are marked as qs_completed 363 * on enqueue and are never seen by a worker thread. So, there needs to be 364 * an independent mechanism to sweep the completion index past these items 365 * whenever that becomes possible. 366 * 367 * This function is called: 368 * 369 * - Whenever a thread completes a batch 370 * - Whenever a thread claims a batch 371 * - Whenever an item of cost 0 is enqueued 372 * 373 * Strictly speaking, advancing on claiming a batch is not logically 374 * necessary. However, the claimer already holds the queue mutex, and it's 375 * in our interest to make completed items available for dequeueing as 376 * expeditiously as possible. 377 * 378 * Sweeping of the "claim" index is also an optimization. It is not 379 * necessary for correctness. However, if we don't do it here, it can only 380 * be done by threads as they claim jobs to work on. In some cases, not 381 * advancing the "claim" index here can result in an empty batch and a 382 * wasted claim cycle. 383 * 384 * Locking: the caller must hold the queue mutex. 385 */ 386 static inline void 387 advance_indexes(zstream_queue_t *queue) 388 { 389 boolean_t any_completed = B_FALSE; 390 uint64_t claimed = 0; 391 392 while (queue->zq_ix.claim < queue->zq_ix.enqueue && 393 Q_SLOT(queue, queue->zq_ix.claim).qs_completed) { 394 queue->zq_ix.claim++; 395 claimed++; 396 } 397 if (claimed > 0) { 398 /* 399 * tp_unclaimed is decremented both here and in 400 * claim_batch(). The conditions are mutually exclusive, so 401 * double counting will not occur. 402 */ 403 atomic_sub_64(&pool.tp_unclaimed, claimed); 404 } 405 while (queue->zq_ix.complete < queue->zq_ix.claim && 406 Q_SLOT(queue, queue->zq_ix.complete).qs_completed) { 407 queue->zq_ix.complete++; 408 any_completed = B_TRUE; 409 } 410 if (any_completed) { 411 pthread_cond_signal(&queue->zq_cond.completed); 412 } 413 } 414 415 /* 416 * Score a queue according to its need for workers. Higher is better. 417 * 418 * Threads are distributed among queues in proportion to their scores (see 419 * select_stochastic()), so a queue with twice as much outstanding work 420 * attracts twice as many workers. Queues with no claimable work score 0 and 421 * are never selected. 422 * 423 * The score is just the number of unclaimed items. More elaborate scoring 424 * is possible. Earlier versions weighted queues by how close they were to 425 * being full and by how little completed work they had available to 426 * dequeue. But, benchmarking showed no benefit over the simple count below. 427 * Also not effective: backpressure-style differencing with the next 428 * downstream queue. 429 * 430 * Locking: the caller must hold the thread pool mutex and the queue mutex. 431 */ 432 static inline uint32_t 433 score_queue(zstream_queue_t *queue) 434 { 435 return (queue->zq_ix.enqueue - queue->zq_ix.claim); 436 } 437 438 /* 439 * Return a random index into weights[], with the likelihood of index i 440 * being selected equal to weights[i] / sum(weights). 441 * 442 * The caller must guarantee that at least one weight is nonzero; 443 * a uniformly zero array has no meaningful answer and would divide by 444 * zero. assign_queue_and_get_work() checks this before calling. 445 */ 446 static inline int 447 select_stochastic(uint32_t weights[], int num_values) 448 { 449 uint64_t randval; 450 uint32_t total = 0; 451 452 for (int i = 0; i < num_values; i++) { 453 total += weights[i]; 454 } 455 if (total == 0) 456 return (0); 457 random_get_pseudo_bytes((uint8_t *)&randval, sizeof (randval)); 458 uint64_t select_val = randval % total; 459 for (int i = 0; i < num_values; i++) { 460 if (select_val < weights[i]) 461 return (i); 462 select_val -= weights[i]; 463 } 464 return (num_values - 1); 465 } 466 467 /* 468 * Claim up to ZQ_MAX_BATCH work items from the given queue. All items in a 469 * batch will be drawn from the same queue. 470 * 471 * Queues track the historical relationship between cost values and 472 * processing times, which is assumed to be roughly linear. The goal is for 473 * each batch to have a processing time of BATCH_TIME_NSEC. However, most 474 * batches fall short of this goal because of the availability of work. 475 * claim_batch() does not block waiting to fill the budget; it returns 476 * whatever is available. 477 * 478 * The cost/time calculation is a cumulative average over the life of the 479 * queue, so it converges early and changes only slowly. Stationarity is 480 * assumed. If nonstationary work is being performed, callers should adjust 481 * their cost functions to reflect that. 482 * 483 * Locking: this function must be called with both the queue mutex and the 484 * thread pool mutex held. zstream_queue_destroy() can't hold a queue's 485 * mutex while destroying it (because destruction entails destroying the 486 * queue mutex, which must be unlocked), so holding the queue mutex while 487 * attempting to claim work is not a sufficient guarantee of correctness. 488 * 489 * In other contexts, we have more certainty about whether a queue still has 490 * work to do. If it does, it can't be destroyed while we hold the queue 491 * mutex alone. But here, we merely suspect that there's work available 492 * based on possibly outdated queue scoring information. By the time we get 493 * here, the queue might already have been finalized. Holding the thread 494 * pool mutex guarantees that the queue won't have been destroyed out from 495 * under us. 496 */ 497 static int 498 claim_batch(zstream_queue_t *queue, queue_slot_t **batch) 499 { 500 size_t cost_to_claim, cost_claimed = 0; 501 int count = 0; 502 uint64_t passed = 0; 503 boolean_t more_to_claim, more_slots, more_budget; 504 boolean_t have_cost_data = queue->zq_stats.nsec_used != 0 && 505 queue->zq_stats.cost_processed != 0; 506 507 if (have_cost_data) { 508 double ns_per_cost = (double)queue->zq_stats.nsec_used / 509 (double)queue->zq_stats.cost_processed; 510 double budget = BATCH_TIME_NSEC / ns_per_cost; 511 if (budget >= (double)SIZE_MAX) 512 cost_to_claim = SIZE_MAX; 513 else 514 cost_to_claim = MAX(1, (size_t)budget); 515 } else { 516 cost_to_claim = 1; 517 } 518 519 while (B_TRUE) { 520 more_to_claim = queue->zq_ix.claim < queue->zq_ix.enqueue; 521 more_slots = count < ZQ_MAX_BATCH; 522 more_budget = cost_claimed < cost_to_claim; 523 524 if (!more_to_claim || !more_slots || !more_budget) { 525 break; 526 } 527 queue_slot_t *slot = &Q_SLOT(queue, queue->zq_ix.claim); 528 if (!slot->qs_completed) { 529 cost_claimed += slot->qs_cost; 530 batch[count++] = slot; 531 } 532 queue->zq_ix.claim++; 533 passed++; 534 } 535 536 /* 537 * Every slot the claim index moved over leaves the unclaimed pool, 538 * whether we took it for the batch or skipped it as already complete. 539 */ 540 if (passed > 0) { 541 atomic_sub_64(&pool.tp_unclaimed, passed); 542 } 543 advance_indexes(queue); 544 #ifdef SHOW_BATCH_HISTOGRAMS 545 queue->zq_histogram[count]++; 546 #endif 547 return (count); 548 } 549 550 /* 551 * Threads are assigned to a queue on each loop so they can be shifted 552 * dynamically to follow available work. Idle threads will typically be 553 * waiting on the tp_wake_worker condition within this function. 554 * 555 * Locking: we hold the pool mutex throughout, both to keep a queue from 556 * being destroyed out from under us while we score it or claim from it, and 557 * because it is the mutex for tp_wake_worker. Individual queues are locked 558 * for only as long as it takes to score or claim from them. 559 */ 560 static int 561 assign_queue_and_get_work(zstream_queue_t **queue, queue_slot_t **batch) 562 { 563 pthread_mutex_lock(&pool.tp_pool_mutex); 564 565 while (B_TRUE) { 566 int num_queues = pool.tp_num_queues; 567 uint32_t weights[ZQ_MAX_QUEUES]; 568 int queues_with_work = 0; 569 570 for (int i = 0; i < num_queues; i++) { 571 zstream_queue_t *to_score = pool.tp_queues[i]; 572 pthread_mutex_lock(&to_score->zq_mutex); 573 weights[i] = score_queue(to_score); 574 pthread_mutex_unlock(&to_score->zq_mutex); 575 if (weights[i] > 0) 576 queues_with_work++; 577 } 578 if (!queues_with_work) { 579 pthread_cond_wait(&pool.tp_wake_worker, 580 &pool.tp_pool_mutex); 581 } else { 582 int q = select_stochastic(weights, num_queues); 583 *queue = pool.tp_queues[q]; 584 pthread_mutex_lock(&(*queue)->zq_mutex); 585 int count = claim_batch(*queue, batch); 586 pthread_mutex_unlock(&(*queue)->zq_mutex); 587 /* 588 * Try to wake up another worker thread if there 589 * still seems to be work available (on any queue). 590 */ 591 if (atomic_load_64(&pool.tp_unclaimed) > 0) { 592 pthread_cond_signal(&pool.tp_wake_worker); 593 } 594 pthread_mutex_unlock(&pool.tp_pool_mutex); 595 return (count); 596 } 597 } 598 } 599 600 static inline uint64_t 601 time_now_ns(void) 602 { 603 struct timespec ts; 604 clock_gettime(CLOCK_MONOTONIC, &ts); 605 return ((uint64_t)ts.tv_sec * 1000000000ULL + ts.tv_nsec); 606 } 607 608 /* 609 * Batches are processed without holding any locks. The existence of the 610 * items we're working on guarantees that the queue can't be destroyed out 611 * from under us. 612 * 613 * However, we can't mark items completed without holding the queue lock 614 * because that creates a potential race condition with advance_indexes() 615 * being called on another thread. 616 */ 617 static void * 618 queue_worker(void *dummy) 619 { 620 (void) dummy; 621 zstream_queue_t *queue; 622 queue_slot_t *batch[ZQ_MAX_BATCH]; 623 int count; 624 625 while (B_TRUE) { 626 count = assign_queue_and_get_work(&queue, batch); 627 if (count) { 628 size_t batch_cost = 0; 629 uint64_t start = time_now_ns(); 630 zq_process_item_f *process = 631 queue->zq_params.qp_process; 632 void *context = queue->zq_params.qp_context; 633 for (int i = 0; i < count; i++) { 634 process(batch[i]->qs_item, context); 635 batch_cost += batch[i]->qs_cost; 636 } 637 uint64_t nsec = time_now_ns() - start; 638 pthread_mutex_lock(&queue->zq_mutex); 639 for (int i = 0; i < count; i++) { 640 batch[i]->qs_completed = B_TRUE; 641 } 642 /* Collect processing rate data */ 643 queue->zq_stats.cost_processed += batch_cost; 644 queue->zq_stats.nsec_used += nsec; 645 advance_indexes(queue); 646 pthread_mutex_unlock(&queue->zq_mutex); 647 } 648 } 649 return (NULL); 650 } 651 652 /* 653 * Locking: must be called with the dispatch mutex held 654 * 655 * Skips the wakeup if tp_unclaimed == 0. 656 */ 657 static inline void 658 maybe_wake_worker(void) 659 { 660 pool.tp_dispatch_requested = B_FALSE; 661 if (atomic_load_64(&pool.tp_unclaimed) > 0) { 662 pthread_mutex_lock(&pool.tp_pool_mutex); 663 pthread_cond_signal(&pool.tp_wake_worker); 664 pthread_mutex_unlock(&pool.tp_pool_mutex); 665 } 666 } 667 668 static inline struct timespec 669 timeout_deadline(void) 670 { 671 struct timespec expire; 672 struct timeval tv; 673 674 if (gettimeofday(&tv, NULL) != 0) 675 err(1, "couldn't gettimeofday()"); 676 uint64_t nsec = tv.tv_usec * 1000 + DISPATCH_BACKUP_NSEC; 677 expire.tv_sec = tv.tv_sec + nsec / NANOSEC; 678 expire.tv_nsec = nsec % NANOSEC; 679 return (expire); 680 } 681 682 /* 683 * The enqueue notification pacing thread, which converts a notification 684 * from an enqueuer into a possible worker wakeup roughly ENQUEUE_DELAY_NSEC 685 * later. The delay facilitates larger batch sizes and keeps enqueuers on a 686 * less-contested mutex. 687 * 688 * The condwait timeout is necessary because the tp_unclaimed count is not 689 * the final word on whether there is actually any work to claim. It is 690 * calculated rigorously. However, it's a bare atomic and therefore 691 * potentially out of date at any given moment. A backup strategy is 692 * necessary to restart processing in the event of a race. 693 */ 694 static void * 695 dispatch_worker(void *nope) 696 { 697 (void) nope; 698 pthread_mutex_lock(&pool.tp_dispatch_mutex); 699 while (B_TRUE) { 700 while (!pool.tp_dispatch_requested) { 701 int rc; 702 struct timespec expire = timeout_deadline(); 703 rc = pthread_cond_timedwait(&pool.tp_request_dispatch, 704 &pool.tp_dispatch_mutex, &expire); 705 if (rc == ETIMEDOUT) { 706 maybe_wake_worker(); 707 } else if (rc != 0) { 708 errx(1, "pthread_cond_timedwait() failed: %s", 709 strerror(rc)); 710 } 711 } 712 pthread_mutex_unlock(&pool.tp_dispatch_mutex); 713 sleep_nsec(ENQUEUE_DELAY_NSEC); 714 pthread_mutex_lock(&pool.tp_dispatch_mutex); 715 maybe_wake_worker(); 716 } 717 return (NULL); 718 } 719 720 /* 721 * Implements both _enqueue and _fini. item == NULL for fini. 722 */ 723 void 724 zstream_enqueue(zstream_queue_t *queue, queue_item_t *item) 725 { 726 VERIFY3P(queue, !=, NULL); 727 pthread_mutex_lock(&queue->zq_mutex); 728 729 VERIFY3B(queue->zq_disallow_enqueue, ==, B_FALSE); 730 while (Q_FULL(queue)) { 731 pthread_cond_wait(&queue->zq_cond.dequeued, &queue->zq_mutex); 732 } 733 VERIFY3B(queue->zq_disallow_enqueue, ==, B_FALSE); 734 queue_slot_t *slot = &Q_SLOT(queue, queue->zq_ix.enqueue); 735 if (item) { 736 slot->qs_cost = 737 queue->zq_params.qp_cost(item, queue->zq_params.qp_context); 738 slot->qs_completed = slot->qs_cost == 0; 739 slot->qs_end_of_stream = B_FALSE; 740 memcpy(slot->qs_item, item, queue->zq_params.qp_item_size); 741 } else { 742 slot->qs_cost = 0; 743 slot->qs_completed = B_TRUE; 744 slot->qs_end_of_stream = B_TRUE; 745 queue->zq_disallow_enqueue = B_TRUE; 746 } 747 queue->zq_ix.enqueue++; 748 atomic_inc_64(&pool.tp_unclaimed); 749 if (slot->qs_cost == 0) 750 advance_indexes(queue); 751 752 #ifdef MONITOR_QUEUES 753 update_monitor_data(queue); 754 #endif 755 756 pthread_mutex_unlock(&queue->zq_mutex); 757 758 pthread_mutex_lock(&pool.tp_dispatch_mutex); 759 pool.tp_dispatch_requested = B_TRUE; 760 pthread_cond_signal(&pool.tp_request_dispatch); 761 pthread_mutex_unlock(&pool.tp_dispatch_mutex); 762 } 763 764 void 765 zstream_queue_fini(zstream_queue_t *queue) 766 { 767 zstream_enqueue(queue, NULL); 768 } 769 770 /* 771 * This function is not public. The only way to destroy a queue through the 772 * public API is to call zstream_queue_fini(), wait for all items to be 773 * processed, and then dequeue all items. As a consequence, threads are 774 * entitled to assume that any queue with unprocessed work will not be 775 * removed without locking the pool mutex. 776 * 777 * Locking: the caller must NOT hold the queue lock. The pool mutex is held 778 * while destroying the queue. 779 */ 780 static void 781 zstream_queue_destroy(zstream_queue_t *queue) 782 { 783 pthread_mutex_lock(&pool.tp_pool_mutex); 784 785 #ifdef SHOW_BATCH_HISTOGRAMS 786 print_batch_size_histogram(queue); 787 #endif 788 789 VERIFY0(pthread_mutex_destroy(&queue->zq_mutex)); 790 VERIFY0(pthread_cond_destroy(&queue->zq_cond.dequeued)); 791 if (pthread_cond_destroy(&queue->zq_cond.completed) != 0) { 792 errx(1, "cannot destroy zstream_queue completed condition - " 793 "are you attempting to dequeue from multiple threads " 794 "simultaneously?"); 795 } 796 pool.tp_num_queues--; 797 if (pool.tp_num_queues > 0) { 798 /* Gaps are not allowed in the tp_queues array */ 799 zstream_queue_t **qscan = &pool.tp_queues[0]; 800 int i = pool.tp_num_queues; 801 while (*qscan != queue) { qscan++; i--; } 802 if (i > 0) 803 memmove(qscan, qscan + 1, i * sizeof (*qscan)); 804 } 805 /* 806 * Items are allocated as a single block. The address of the first 807 * item field is in fact the start of the block. 808 */ 809 free(queue->zq_slots[0].qs_item); 810 free(queue->zq_slots); 811 queue->zq_slots = NULL; 812 free(queue); 813 814 pthread_mutex_unlock(&pool.tp_pool_mutex); 815 } 816 817 /* 818 * Locking: if more than one thread attempts to dequeue items 819 * simultaneously, disaster is likely. It will work fine until the end of 820 * the stream, at which point it becomes a tossup between a race condition 821 * with multiple attempts to destroy the whole queue vs. an attempt to 822 * delete a condition that another thread is waiting on. Hence the warning 823 * not to do multithreaded dequeues in zstream_queue.h. 824 * 825 * Returns B_TRUE if real data is returned, B_FALSE if the end of the queue 826 * has been reached. 827 */ 828 boolean_t 829 zstream_dequeue(zstream_queue_t *queue, queue_item_t *item) 830 { 831 pthread_mutex_lock(&queue->zq_mutex); 832 while (queue->zq_ix.dequeue >= queue->zq_ix.complete) { 833 pthread_cond_wait(&queue->zq_cond.completed, &queue->zq_mutex); 834 } 835 queue_slot_t *slot = &Q_SLOT(queue, queue->zq_ix.dequeue); 836 queue->zq_ix.dequeue++; 837 838 #ifdef MONITOR_QUEUES 839 update_monitor_data(queue); 840 #endif 841 842 if (slot->qs_end_of_stream) { 843 pthread_mutex_unlock(&queue->zq_mutex); 844 /* Potential multi-dequeuer race point */ 845 zstream_queue_destroy(queue); 846 return (B_FALSE); 847 } else { 848 memcpy(item, slot->qs_item, queue->zq_params.qp_item_size); 849 pthread_cond_signal(&queue->zq_cond.dequeued); 850 pthread_mutex_unlock(&queue->zq_mutex); 851 return (B_TRUE); 852 } 853 } 854 855 #ifdef SHOW_BATCH_HISTOGRAMS 856 857 /* 858 * Called only by zstream_queue_destroy(), under the pool mutex 859 * 860 * Prints one dense line per queue: the bucket counts for batch sizes 0 861 * through the largest batch observed. Now that ZQ_MAX_BATCH is 1024 and 862 * batches routinely reach the cap, that line can run to a thousand 863 * comma-separated values. Not very readable in a terminal, but still useful 864 * when plotted. 865 */ 866 static void 867 print_batch_size_histogram(zstream_queue_t *queue) 868 { 869 int last_nonzero = 0; 870 static int lines_printed = 0; 871 872 if (lines_printed++ == 0) 873 fprintf(stderr, "\nBatch size histograms:\n"); 874 for (last_nonzero = ZQ_MAX_BATCH; last_nonzero >= 0; last_nonzero--) { 875 if (queue->zq_histogram[last_nonzero] > 0) 876 break; 877 } 878 fprintf(stderr, "Queue %d: ", queue->zq_id); 879 const char *sep = ""; 880 for (int i = 0; i <= last_nonzero; i++) { 881 fprintf(stderr, "%s%llu", sep, 882 (u_longlong_t)queue->zq_histogram[i]); 883 sep = ", "; 884 } 885 fprintf(stderr, "\n"); 886 fflush(stderr); 887 } 888 889 #endif 890 891 #ifdef MONITOR_QUEUES 892 893 #define USEC_PER_JIFFY 10000 894 #define SAMPLE_DURATION_USEC 1000000 895 #define CPU_FIELD_WIDTH 14 896 897 static inline void 898 update_depth_stats(depth_stats_t *ds, uint64_t depth) 899 { 900 double delta = (double)depth - ds->ds_mean; 901 ds->ds_samples++; 902 ds->ds_mean += delta / (double)ds->ds_samples; 903 ds->ds_m2 += delta * ((double)depth - ds->ds_mean); 904 } 905 906 static inline double 907 depth_stdev(const depth_stats_t *ds) 908 { 909 if (ds->ds_samples < 2) 910 return (0.0); 911 return (sqrt(ds->ds_m2 / (double)(ds->ds_samples - 1))); 912 } 913 914 static inline void 915 initialize_monitor_data(zstream_queue_t *queue) 916 { 917 zq_stats_t *stats = &queue->zq_stats; 918 stats->depth = (depth_stats_t) {0}; 919 stats->depth.min = INT_MAX; 920 } 921 922 /* 923 * Sample the current depth. Called on every enqueue and every dequeue, so 924 * the statistics are per-event rather than per-unit-time; a queue that sits 925 * full while nothing moves contributes no samples. 926 */ 927 static inline void 928 update_monitor_data(zstream_queue_t *queue) 929 { 930 uint64_t depth = queue->zq_ix.enqueue - queue->zq_ix.dequeue; 931 932 queue->zq_stats.depth.max = MAX(queue->zq_stats.depth.max, depth); 933 queue->zq_stats.depth.min = MIN(queue->zq_stats.depth.min, depth); 934 update_depth_stats(&queue->zq_stats.depth, depth); 935 } 936 937 /* 938 * Monitor queue and CPU usage from a separate thread. This is all 939 * Linux-specific. It's largely a development remnant. Now that queue depths 940 * are standardized and batch sizes are sized to BATCH_TIME_NSEC, there are 941 * relatively few levers that need tuning. 942 * 943 * For each period, prints the minimum and maximum queue depth observed, 944 * followed by the mean depth and its standard deviation. 945 * 946 * Example output: 947 * 948 * CPU: 99.85% Queue 0: 745-4096 (2553 +/- 994) Queue 1: ... 949 */ 950 static void * 951 cpu_and_queue_monitor(void *dummy) 952 { 953 (void) dummy; 954 uint64_t period = SAMPLE_DURATION_USEC; 955 struct timespec clock = {0}; 956 uint64_t start_us, end_us; 957 uint64_t cpu_jif_prior = 0; 958 uint64_t delta_jif, delta_cpu_jif; 959 long unsigned int utime, stime; 960 char buff[1024]; 961 FILE *fp; 962 963 fprintf(stderr, "Queue depths:\n"); 964 965 while (B_TRUE) { 966 967 usleep(period); 968 969 fp = fopen("/proc/self/stat", "r"); 970 VERIFY3P(fp, !=, NULL); 971 VERIFY3P(fgets(buff, sizeof (buff), fp), !=, NULL); 972 fclose(fp); 973 char *p = strrchr(buff, ')'); 974 VERIFY3P(p, !=, NULL); 975 p += 2; /* skip ") " and fields 3-13 */ 976 for (int i = 0; i < 11; i++) { 977 p = strchr(p, ' '); 978 VERIFY3P(p, !=, NULL); 979 p++; 980 } 981 VERIFY3U(sscanf(p, "%lu %lu", &utime, &stime), ==, 2); 982 983 pthread_mutex_lock(&pool.tp_pool_mutex); 984 985 clock_gettime(CLOCK_MONOTONIC, &clock); 986 end_us = clock.tv_sec * 1000000 + clock.tv_nsec / 1000; 987 988 if (cpu_jif_prior > 0) { 989 delta_cpu_jif = utime + stime - cpu_jif_prior; 990 delta_jif = (end_us - start_us) / USEC_PER_JIFFY; 991 double cpu_pct = (double)delta_cpu_jif / 992 (pool.tp_num_threads * delta_jif); 993 cpu_pct = MIN(cpu_pct, 0.9999); /* Don't print 100% */ 994 fprintf(stderr, "CPU: %5.2f%% ", 100 * cpu_pct); 995 } else { 996 /* No CPU data available for the first interval */ 997 fprintf(stderr, "%*s", CPU_FIELD_WIDTH, ""); 998 } 999 1000 for (int i = 0; i < pool.tp_num_queues; i++) { 1001 zstream_queue_t *q = pool.tp_queues[i]; 1002 pthread_mutex_lock(&q->zq_mutex); 1003 zq_stats_t *stats = &q->zq_stats; 1004 double avg = stats->depth.ds_mean; 1005 double stdev = depth_stdev(&stats->depth); 1006 if (stats->depth.min > stats->depth.max) 1007 stats->depth.min = stats->depth.max = 0; 1008 const char *plusminus = "\xc2\xb1"; /* UTF-8 */ 1009 fprintf(stderr, "Queue %d: %4d-%-4d (%4d %s %-4d) ", 1010 q->zq_id, stats->depth.min, stats->depth.max, 1011 (int)avg, plusminus, (int)stdev); 1012 fflush(stderr); 1013 initialize_monitor_data(q); 1014 pthread_mutex_unlock(&q->zq_mutex); 1015 } 1016 1017 pthread_mutex_unlock(&pool.tp_pool_mutex); 1018 1019 fprintf(stderr, "\n"); 1020 fflush(stderr); 1021 1022 cpu_jif_prior = utime + stime; 1023 start_us = end_us; 1024 } 1025 return (NULL); 1026 } 1027 1028 #endif /* MONITOR_QUEUES */ 1029