1 // SPDX-License-Identifier: GPL-2.0 2 3 /* 4 * Test module for lockless object pool 5 * 6 * Copyright: wuqiang.matt@bytedance.com 7 */ 8 9 #include <linux/errno.h> 10 #include <linux/module.h> 11 #include <linux/moduleparam.h> 12 #include <linux/completion.h> 13 #include <linux/kthread.h> 14 #include <linux/slab.h> 15 #include <linux/vmalloc.h> 16 #include <linux/delay.h> 17 #include <linux/hrtimer.h> 18 #include <linux/objpool.h> 19 20 #define OT_NR_MAX_BULK (16) 21 22 /* memory usage */ 23 struct ot_mem_stat { 24 atomic_long_t alloc; 25 atomic_long_t free; 26 }; 27 28 /* object allocation results */ 29 struct ot_obj_stat { 30 unsigned long nhits; 31 unsigned long nmiss; 32 }; 33 34 /* control & results per testcase */ 35 struct ot_data { 36 struct rw_semaphore start; 37 struct completion wait; 38 struct completion rcu; 39 atomic_t nthreads ____cacheline_aligned_in_smp; 40 atomic_t stop ____cacheline_aligned_in_smp; 41 struct ot_mem_stat kmalloc; 42 struct ot_mem_stat vmalloc; 43 struct ot_obj_stat objects; 44 u64 duration; 45 }; 46 47 /* testcase */ 48 struct ot_test { 49 int async; /* synchronous or asynchronous */ 50 int mode; /* only mode 0 supported */ 51 int objsz; /* object size */ 52 int duration; /* ms */ 53 int delay; /* ms */ 54 int bulk_normal; 55 int bulk_irq; 56 unsigned long hrtimer; /* ms */ 57 const char *name; 58 struct ot_data data; 59 }; 60 61 /* per-cpu worker */ 62 struct ot_item { 63 struct objpool_head *pool; /* pool head */ 64 struct ot_test *test; /* test parameters */ 65 66 void (*worker)(struct ot_item *item, int irq); 67 68 /* hrtimer control */ 69 ktime_t hrtcycle; 70 struct hrtimer hrtimer; 71 72 int bulk[2]; /* for thread and irq */ 73 int delay; 74 u32 niters; 75 76 /* summary per thread */ 77 struct ot_obj_stat stat[2]; /* thread and irq */ 78 u64 duration; 79 }; 80 81 /* 82 * memory leakage checking 83 */ 84 85 static void *ot_kzalloc(struct ot_test *test, long size) 86 { 87 void *ptr = kzalloc(size, GFP_KERNEL); 88 89 if (ptr) 90 atomic_long_add(size, &test->data.kmalloc.alloc); 91 return ptr; 92 } 93 94 static void ot_kfree(struct ot_test *test, void *ptr, long size) 95 { 96 if (!ptr) 97 return; 98 atomic_long_add(size, &test->data.kmalloc.free); 99 kfree(ptr); 100 } 101 102 static void ot_mem_report(struct ot_test *test) 103 { 104 long alloc, free; 105 106 pr_info("memory allocation summary for %s\n", test->name); 107 108 alloc = atomic_long_read(&test->data.kmalloc.alloc); 109 free = atomic_long_read(&test->data.kmalloc.free); 110 pr_info(" kmalloc: %lu - %lu = %lu\n", alloc, free, alloc - free); 111 112 alloc = atomic_long_read(&test->data.vmalloc.alloc); 113 free = atomic_long_read(&test->data.vmalloc.free); 114 pr_info(" vmalloc: %lu - %lu = %lu\n", alloc, free, alloc - free); 115 } 116 117 /* user object instance */ 118 struct ot_node { 119 void *owner; 120 unsigned long data; 121 unsigned long refs; 122 unsigned long payload[32]; 123 }; 124 125 /* user objpool manager */ 126 struct ot_context { 127 struct objpool_head pool; /* objpool head */ 128 struct ot_test *test; /* test parameters */ 129 void *ptr; /* user pool buffer */ 130 unsigned long size; /* buffer size */ 131 struct rcu_head rcu; 132 }; 133 134 static DEFINE_PER_CPU(struct ot_item, ot_pcup_items); 135 136 static int ot_init_data(struct ot_data *data) 137 { 138 memset(data, 0, sizeof(*data)); 139 init_rwsem(&data->start); 140 init_completion(&data->wait); 141 init_completion(&data->rcu); 142 atomic_set(&data->nthreads, 1); 143 144 return 0; 145 } 146 147 static int ot_init_node(void *nod, void *context) 148 { 149 struct ot_context *sop = context; 150 struct ot_node *on = nod; 151 152 on->owner = &sop->pool; 153 return 0; 154 } 155 156 static enum hrtimer_restart ot_hrtimer_handler(struct hrtimer *hrt) 157 { 158 struct ot_item *item = container_of(hrt, struct ot_item, hrtimer); 159 struct ot_test *test = item->test; 160 161 if (atomic_read_acquire(&test->data.stop)) 162 return HRTIMER_NORESTART; 163 164 /* do bulk-testings for objects pop/push */ 165 item->worker(item, 1); 166 167 hrtimer_forward(hrt, hrt->base->get_time(), item->hrtcycle); 168 return HRTIMER_RESTART; 169 } 170 171 static void ot_start_hrtimer(struct ot_item *item) 172 { 173 if (!item->test->hrtimer) 174 return; 175 hrtimer_start(&item->hrtimer, item->hrtcycle, HRTIMER_MODE_REL); 176 } 177 178 static void ot_stop_hrtimer(struct ot_item *item) 179 { 180 if (!item->test->hrtimer) 181 return; 182 hrtimer_cancel(&item->hrtimer); 183 } 184 185 static int ot_init_hrtimer(struct ot_item *item, unsigned long hrtimer) 186 { 187 struct hrtimer *hrt = &item->hrtimer; 188 189 if (!hrtimer) 190 return -ENOENT; 191 192 item->hrtcycle = ktime_set(0, hrtimer * 1000000UL); 193 hrtimer_init(hrt, CLOCK_MONOTONIC, HRTIMER_MODE_REL); 194 hrt->function = ot_hrtimer_handler; 195 return 0; 196 } 197 198 static int ot_init_cpu_item(struct ot_item *item, 199 struct ot_test *test, 200 struct objpool_head *pool, 201 void (*worker)(struct ot_item *, int)) 202 { 203 memset(item, 0, sizeof(*item)); 204 item->pool = pool; 205 item->test = test; 206 item->worker = worker; 207 208 item->bulk[0] = test->bulk_normal; 209 item->bulk[1] = test->bulk_irq; 210 item->delay = test->delay; 211 212 /* initialize hrtimer */ 213 ot_init_hrtimer(item, item->test->hrtimer); 214 return 0; 215 } 216 217 static int ot_thread_worker(void *arg) 218 { 219 struct ot_item *item = arg; 220 struct ot_test *test = item->test; 221 ktime_t start; 222 223 atomic_inc(&test->data.nthreads); 224 down_read(&test->data.start); 225 up_read(&test->data.start); 226 start = ktime_get(); 227 ot_start_hrtimer(item); 228 do { 229 if (atomic_read_acquire(&test->data.stop)) 230 break; 231 /* do bulk-testings for objects pop/push */ 232 item->worker(item, 0); 233 } while (!kthread_should_stop()); 234 ot_stop_hrtimer(item); 235 item->duration = (u64) ktime_us_delta(ktime_get(), start); 236 if (atomic_dec_and_test(&test->data.nthreads)) 237 complete(&test->data.wait); 238 239 return 0; 240 } 241 242 static void ot_perf_report(struct ot_test *test, u64 duration) 243 { 244 struct ot_obj_stat total, normal = {0}, irq = {0}; 245 int cpu, nthreads = 0; 246 247 pr_info("\n"); 248 pr_info("Testing summary for %s\n", test->name); 249 250 for_each_possible_cpu(cpu) { 251 struct ot_item *item = per_cpu_ptr(&ot_pcup_items, cpu); 252 if (!item->duration) 253 continue; 254 normal.nhits += item->stat[0].nhits; 255 normal.nmiss += item->stat[0].nmiss; 256 irq.nhits += item->stat[1].nhits; 257 irq.nmiss += item->stat[1].nmiss; 258 pr_info("CPU: %d duration: %lluus\n", cpu, item->duration); 259 pr_info("\tthread:\t%16lu hits \t%16lu miss\n", 260 item->stat[0].nhits, item->stat[0].nmiss); 261 pr_info("\tirq: \t%16lu hits \t%16lu miss\n", 262 item->stat[1].nhits, item->stat[1].nmiss); 263 pr_info("\ttotal: \t%16lu hits \t%16lu miss\n", 264 item->stat[0].nhits + item->stat[1].nhits, 265 item->stat[0].nmiss + item->stat[1].nmiss); 266 nthreads++; 267 } 268 269 total.nhits = normal.nhits + irq.nhits; 270 total.nmiss = normal.nmiss + irq.nmiss; 271 272 pr_info("ALL: \tnthreads: %d duration: %lluus\n", nthreads, duration); 273 pr_info("SUM: \t%16lu hits \t%16lu miss\n", 274 total.nhits, total.nmiss); 275 276 test->data.objects = total; 277 test->data.duration = duration; 278 } 279 280 /* 281 * synchronous test cases for objpool manipulation 282 */ 283 284 /* objpool manipulation for synchronous mode (percpu objpool) */ 285 static struct ot_context *ot_init_sync_m0(struct ot_test *test) 286 { 287 struct ot_context *sop = NULL; 288 int max = num_possible_cpus() << 3; 289 gfp_t gfp = GFP_KERNEL; 290 291 sop = (struct ot_context *)ot_kzalloc(test, sizeof(*sop)); 292 if (!sop) 293 return NULL; 294 sop->test = test; 295 if (test->objsz < 512) 296 gfp = GFP_ATOMIC; 297 298 if (objpool_init(&sop->pool, max, test->objsz, 299 gfp, sop, ot_init_node, NULL)) { 300 ot_kfree(test, sop, sizeof(*sop)); 301 return NULL; 302 } 303 WARN_ON(max != sop->pool.nr_objs); 304 305 return sop; 306 } 307 308 static void ot_fini_sync(struct ot_context *sop) 309 { 310 objpool_fini(&sop->pool); 311 ot_kfree(sop->test, sop, sizeof(*sop)); 312 } 313 314 static struct { 315 struct ot_context * (*init)(struct ot_test *oc); 316 void (*fini)(struct ot_context *sop); 317 } g_ot_sync_ops[] = { 318 {.init = ot_init_sync_m0, .fini = ot_fini_sync}, 319 }; 320 321 /* 322 * synchronous test cases: performance mode 323 */ 324 325 static void ot_bulk_sync(struct ot_item *item, int irq) 326 { 327 struct ot_node *nods[OT_NR_MAX_BULK]; 328 int i; 329 330 for (i = 0; i < item->bulk[irq]; i++) 331 nods[i] = objpool_pop(item->pool); 332 333 if (!irq && (item->delay || !(++(item->niters) & 0x7FFF))) 334 msleep(item->delay); 335 336 while (i-- > 0) { 337 struct ot_node *on = nods[i]; 338 if (on) { 339 on->refs++; 340 objpool_push(on, item->pool); 341 item->stat[irq].nhits++; 342 } else { 343 item->stat[irq].nmiss++; 344 } 345 } 346 } 347 348 static int ot_start_sync(struct ot_test *test) 349 { 350 struct ot_context *sop; 351 ktime_t start; 352 u64 duration; 353 unsigned long timeout; 354 int cpu; 355 356 /* initialize objpool for syncrhonous testcase */ 357 sop = g_ot_sync_ops[test->mode].init(test); 358 if (!sop) 359 return -ENOMEM; 360 361 /* grab rwsem to block testing threads */ 362 down_write(&test->data.start); 363 364 for_each_possible_cpu(cpu) { 365 struct ot_item *item = per_cpu_ptr(&ot_pcup_items, cpu); 366 struct task_struct *work; 367 368 ot_init_cpu_item(item, test, &sop->pool, ot_bulk_sync); 369 370 /* skip offline cpus */ 371 if (!cpu_online(cpu)) 372 continue; 373 374 work = kthread_create_on_node(ot_thread_worker, item, 375 cpu_to_node(cpu), "ot_worker_%d", cpu); 376 if (IS_ERR(work)) { 377 pr_err("failed to create thread for cpu %d\n", cpu); 378 } else { 379 kthread_bind(work, cpu); 380 wake_up_process(work); 381 } 382 } 383 384 /* wait a while to make sure all threads waiting at start line */ 385 msleep(20); 386 387 /* in case no threads were created: memory insufficient ? */ 388 if (atomic_dec_and_test(&test->data.nthreads)) 389 complete(&test->data.wait); 390 391 // sched_set_fifo_low(current); 392 393 /* start objpool testing threads */ 394 start = ktime_get(); 395 up_write(&test->data.start); 396 397 /* yeild cpu to worker threads for duration ms */ 398 timeout = msecs_to_jiffies(test->duration); 399 schedule_timeout_interruptible(timeout); 400 401 /* tell workers threads to quit */ 402 atomic_set_release(&test->data.stop, 1); 403 404 /* wait all workers threads finish and quit */ 405 wait_for_completion(&test->data.wait); 406 duration = (u64) ktime_us_delta(ktime_get(), start); 407 408 /* cleanup objpool */ 409 g_ot_sync_ops[test->mode].fini(sop); 410 411 /* report testing summary and performance results */ 412 ot_perf_report(test, duration); 413 414 /* report memory allocation summary */ 415 ot_mem_report(test); 416 417 return 0; 418 } 419 420 /* 421 * asynchronous test cases: pool lifecycle controlled by refcount 422 */ 423 424 static void ot_fini_async_rcu(struct rcu_head *rcu) 425 { 426 struct ot_context *sop = container_of(rcu, struct ot_context, rcu); 427 struct ot_test *test = sop->test; 428 429 /* here all cpus are aware of the stop event: test->data.stop = 1 */ 430 WARN_ON(!atomic_read_acquire(&test->data.stop)); 431 432 objpool_fini(&sop->pool); 433 complete(&test->data.rcu); 434 } 435 436 static void ot_fini_async(struct ot_context *sop) 437 { 438 /* make sure the stop event is acknowledged by all cores */ 439 call_rcu(&sop->rcu, ot_fini_async_rcu); 440 } 441 442 static int ot_objpool_release(struct objpool_head *head, void *context) 443 { 444 struct ot_context *sop = context; 445 446 WARN_ON(!head || !sop || head != &sop->pool); 447 448 /* do context cleaning if needed */ 449 if (sop) 450 ot_kfree(sop->test, sop, sizeof(*sop)); 451 452 return 0; 453 } 454 455 static struct ot_context *ot_init_async_m0(struct ot_test *test) 456 { 457 struct ot_context *sop = NULL; 458 int max = num_possible_cpus() << 3; 459 gfp_t gfp = GFP_KERNEL; 460 461 sop = (struct ot_context *)ot_kzalloc(test, sizeof(*sop)); 462 if (!sop) 463 return NULL; 464 sop->test = test; 465 if (test->objsz < 512) 466 gfp = GFP_ATOMIC; 467 468 if (objpool_init(&sop->pool, max, test->objsz, gfp, sop, 469 ot_init_node, ot_objpool_release)) { 470 ot_kfree(test, sop, sizeof(*sop)); 471 return NULL; 472 } 473 WARN_ON(max != sop->pool.nr_objs); 474 475 return sop; 476 } 477 478 static struct { 479 struct ot_context * (*init)(struct ot_test *oc); 480 void (*fini)(struct ot_context *sop); 481 } g_ot_async_ops[] = { 482 {.init = ot_init_async_m0, .fini = ot_fini_async}, 483 }; 484 485 static void ot_nod_recycle(struct ot_node *on, struct objpool_head *pool, 486 int release) 487 { 488 struct ot_context *sop; 489 490 on->refs++; 491 492 if (!release) { 493 /* push object back to opjpool for reuse */ 494 objpool_push(on, pool); 495 return; 496 } 497 498 sop = container_of(pool, struct ot_context, pool); 499 WARN_ON(sop != pool->context); 500 501 /* unref objpool with nod removed forever */ 502 objpool_drop(on, pool); 503 } 504 505 static void ot_bulk_async(struct ot_item *item, int irq) 506 { 507 struct ot_test *test = item->test; 508 struct ot_node *nods[OT_NR_MAX_BULK]; 509 int i, stop; 510 511 for (i = 0; i < item->bulk[irq]; i++) 512 nods[i] = objpool_pop(item->pool); 513 514 if (!irq) { 515 if (item->delay || !(++(item->niters) & 0x7FFF)) 516 msleep(item->delay); 517 get_cpu(); 518 } 519 520 stop = atomic_read_acquire(&test->data.stop); 521 522 /* drop all objects and deref objpool */ 523 while (i-- > 0) { 524 struct ot_node *on = nods[i]; 525 526 if (on) { 527 on->refs++; 528 ot_nod_recycle(on, item->pool, stop); 529 item->stat[irq].nhits++; 530 } else { 531 item->stat[irq].nmiss++; 532 } 533 } 534 535 if (!irq) 536 put_cpu(); 537 } 538 539 static int ot_start_async(struct ot_test *test) 540 { 541 struct ot_context *sop; 542 ktime_t start; 543 u64 duration; 544 unsigned long timeout; 545 int cpu; 546 547 /* initialize objpool for syncrhonous testcase */ 548 sop = g_ot_async_ops[test->mode].init(test); 549 if (!sop) 550 return -ENOMEM; 551 552 /* grab rwsem to block testing threads */ 553 down_write(&test->data.start); 554 555 for_each_possible_cpu(cpu) { 556 struct ot_item *item = per_cpu_ptr(&ot_pcup_items, cpu); 557 struct task_struct *work; 558 559 ot_init_cpu_item(item, test, &sop->pool, ot_bulk_async); 560 561 /* skip offline cpus */ 562 if (!cpu_online(cpu)) 563 continue; 564 565 work = kthread_create_on_node(ot_thread_worker, item, 566 cpu_to_node(cpu), "ot_worker_%d", cpu); 567 if (IS_ERR(work)) { 568 pr_err("failed to create thread for cpu %d\n", cpu); 569 } else { 570 kthread_bind(work, cpu); 571 wake_up_process(work); 572 } 573 } 574 575 /* wait a while to make sure all threads waiting at start line */ 576 msleep(20); 577 578 /* in case no threads were created: memory insufficient ? */ 579 if (atomic_dec_and_test(&test->data.nthreads)) 580 complete(&test->data.wait); 581 582 /* start objpool testing threads */ 583 start = ktime_get(); 584 up_write(&test->data.start); 585 586 /* yeild cpu to worker threads for duration ms */ 587 timeout = msecs_to_jiffies(test->duration); 588 schedule_timeout_interruptible(timeout); 589 590 /* tell workers threads to quit */ 591 atomic_set_release(&test->data.stop, 1); 592 593 /* do async-finalization */ 594 g_ot_async_ops[test->mode].fini(sop); 595 596 /* wait all workers threads finish and quit */ 597 wait_for_completion(&test->data.wait); 598 duration = (u64) ktime_us_delta(ktime_get(), start); 599 600 /* assure rcu callback is triggered */ 601 wait_for_completion(&test->data.rcu); 602 603 /* 604 * now we are sure that objpool is finalized either 605 * by rcu callback or by worker threads 606 */ 607 608 /* report testing summary and performance results */ 609 ot_perf_report(test, duration); 610 611 /* report memory allocation summary */ 612 ot_mem_report(test); 613 614 return 0; 615 } 616 617 /* 618 * predefined testing cases: 619 * synchronous case / overrun case / async case 620 * 621 * async: synchronous or asynchronous testing 622 * mode: only mode 0 supported 623 * objsz: object size 624 * duration: int, total test time in ms 625 * delay: int, delay (in ms) between each iteration 626 * bulk_normal: int, repeat times for thread worker 627 * bulk_irq: int, repeat times for irq consumer 628 * hrtimer: unsigned long, hrtimer intervnal in ms 629 * name: char *, tag for current test ot_item 630 */ 631 632 #define NODE_COMPACT sizeof(struct ot_node) 633 #define NODE_VMALLOC (512) 634 635 static struct ot_test g_testcases[] = { 636 637 /* sync & normal */ 638 {0, 0, NODE_COMPACT, 1000, 0, 1, 0, 0, "sync: percpu objpool"}, 639 {0, 0, NODE_VMALLOC, 1000, 0, 1, 0, 0, "sync: percpu objpool from vmalloc"}, 640 641 /* sync & hrtimer */ 642 {0, 0, NODE_COMPACT, 1000, 0, 1, 1, 4, "sync & hrtimer: percpu objpool"}, 643 {0, 0, NODE_VMALLOC, 1000, 0, 1, 1, 4, "sync & hrtimer: percpu objpool from vmalloc"}, 644 645 /* sync & overrun */ 646 {0, 0, NODE_COMPACT, 1000, 0, 16, 0, 0, "sync overrun: percpu objpool"}, 647 {0, 0, NODE_VMALLOC, 1000, 0, 16, 0, 0, "sync overrun: percpu objpool from vmalloc"}, 648 649 /* async mode */ 650 {1, 0, NODE_COMPACT, 1000, 100, 1, 0, 0, "async: percpu objpool"}, 651 {1, 0, NODE_VMALLOC, 1000, 100, 1, 0, 0, "async: percpu objpool from vmalloc"}, 652 653 /* async + hrtimer mode */ 654 {1, 0, NODE_COMPACT, 1000, 0, 4, 4, 4, "async & hrtimer: percpu objpool"}, 655 {1, 0, NODE_VMALLOC, 1000, 0, 4, 4, 4, "async & hrtimer: percpu objpool from vmalloc"}, 656 }; 657 658 static int __init ot_mod_init(void) 659 { 660 int i; 661 662 /* perform testings */ 663 for (i = 0; i < ARRAY_SIZE(g_testcases); i++) { 664 ot_init_data(&g_testcases[i].data); 665 if (g_testcases[i].async) 666 ot_start_async(&g_testcases[i]); 667 else 668 ot_start_sync(&g_testcases[i]); 669 } 670 671 /* show tests summary */ 672 pr_info("\n"); 673 pr_info("Summary of testcases:\n"); 674 for (i = 0; i < ARRAY_SIZE(g_testcases); i++) { 675 pr_info(" duration: %lluus \thits: %10lu \tmiss: %10lu \t%s\n", 676 g_testcases[i].data.duration, g_testcases[i].data.objects.nhits, 677 g_testcases[i].data.objects.nmiss, g_testcases[i].name); 678 } 679 680 return -EAGAIN; 681 } 682 683 static void __exit ot_mod_exit(void) 684 { 685 } 686 687 module_init(ot_mod_init); 688 module_exit(ot_mod_exit); 689 690 MODULE_LICENSE("GPL");
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.