1 #include "perf.h" 2 #include "util/debug.h" 3 #include "util/event.h" 4 #include "util/symbol.h" 5 #include "util/sort.h" 6 #include "util/evsel.h" 7 #include "util/evlist.h" 8 #include "util/machine.h" 9 #include "util/thread.h" 10 #include "util/parse-events.h" 11 #include "tests/tests.h" 12 #include "tests/hists_common.h" 13 #include <linux/kernel.h> 14 15 struct sample { 16 u32 cpu; 17 u32 pid; 18 u64 ip; 19 struct thread *thread; 20 struct map *map; 21 struct symbol *sym; 22 }; 23 24 /* For the numbers, see hists_common.c */ 25 static struct sample fake_samples[] = { 26 /* perf [kernel] schedule() */ 27 { .cpu = 0, .pid = FAKE_PID_PERF1, .ip = FAKE_IP_KERNEL_SCHEDULE, }, 28 /* perf [perf] main() */ 29 { .cpu = 1, .pid = FAKE_PID_PERF1, .ip = FAKE_IP_PERF_MAIN, }, 30 /* perf [perf] cmd_record() */ 31 { .cpu = 1, .pid = FAKE_PID_PERF1, .ip = FAKE_IP_PERF_CMD_RECORD, }, 32 /* perf [libc] malloc() */ 33 { .cpu = 1, .pid = FAKE_PID_PERF1, .ip = FAKE_IP_LIBC_MALLOC, }, 34 /* perf [libc] free() */ 35 { .cpu = 2, .pid = FAKE_PID_PERF1, .ip = FAKE_IP_LIBC_FREE, }, 36 /* perf [perf] main() */ 37 { .cpu = 2, .pid = FAKE_PID_PERF2, .ip = FAKE_IP_PERF_MAIN, }, 38 /* perf [kernel] page_fault() */ 39 { .cpu = 2, .pid = FAKE_PID_PERF2, .ip = FAKE_IP_KERNEL_PAGE_FAULT, }, 40 /* bash [bash] main() */ 41 { .cpu = 3, .pid = FAKE_PID_BASH, .ip = FAKE_IP_BASH_MAIN, }, 42 /* bash [bash] xmalloc() */ 43 { .cpu = 0, .pid = FAKE_PID_BASH, .ip = FAKE_IP_BASH_XMALLOC, }, 44 /* bash [kernel] page_fault() */ 45 { .cpu = 1, .pid = FAKE_PID_BASH, .ip = FAKE_IP_KERNEL_PAGE_FAULT, }, 46 }; 47 48 static int add_hist_entries(struct hists *hists, struct machine *machine) 49 { 50 struct addr_location al; 51 struct perf_evsel *evsel = hists_to_evsel(hists); 52 struct perf_sample sample = { .period = 100, }; 53 size_t i; 54 55 for (i = 0; i < ARRAY_SIZE(fake_samples); i++) { 56 struct hist_entry_iter iter = { 57 .evsel = evsel, 58 .sample = &sample, 59 .ops = &hist_iter_normal, 60 .hide_unresolved = false, 61 }; 62 63 sample.cpumode = PERF_RECORD_MISC_USER; 64 sample.cpu = fake_samples[i].cpu; 65 sample.pid = fake_samples[i].pid; 66 sample.tid = fake_samples[i].pid; 67 sample.ip = fake_samples[i].ip; 68 69 if (machine__resolve(machine, &al, &sample) < 0) 70 goto out; 71 72 if (hist_entry_iter__add(&iter, &al, sysctl_perf_event_max_stack, 73 NULL) < 0) { 74 addr_location__put(&al); 75 goto out; 76 } 77 78 fake_samples[i].thread = al.thread; 79 fake_samples[i].map = al.map; 80 fake_samples[i].sym = al.sym; 81 } 82 83 return TEST_OK; 84 85 out: 86 pr_debug("Not enough memory for adding a hist entry\n"); 87 return TEST_FAIL; 88 } 89 90 static void del_hist_entries(struct hists *hists) 91 { 92 struct hist_entry *he; 93 struct rb_root *root_in; 94 struct rb_root *root_out; 95 struct rb_node *node; 96 97 if (hists__has(hists, need_collapse)) 98 root_in = &hists->entries_collapsed; 99 else 100 root_in = hists->entries_in; 101 102 root_out = &hists->entries; 103 104 while (!RB_EMPTY_ROOT(root_out)) { 105 node = rb_first(root_out); 106 107 he = rb_entry(node, struct hist_entry, rb_node); 108 rb_erase(node, root_out); 109 rb_erase(&he->rb_node_in, root_in); 110 hist_entry__delete(he); 111 } 112 } 113 114 typedef int (*test_fn_t)(struct perf_evsel *, struct machine *); 115 116 #define COMM(he) (thread__comm_str(he->thread)) 117 #define DSO(he) (he->ms.map->dso->short_name) 118 #define SYM(he) (he->ms.sym->name) 119 #define CPU(he) (he->cpu) 120 #define PID(he) (he->thread->tid) 121 122 /* default sort keys (no field) */ 123 static int test1(struct perf_evsel *evsel, struct machine *machine) 124 { 125 int err; 126 struct hists *hists = evsel__hists(evsel); 127 struct hist_entry *he; 128 struct rb_root *root; 129 struct rb_node *node; 130 131 field_order = NULL; 132 sort_order = NULL; /* equivalent to sort_order = "comm,dso,sym" */ 133 134 setup_sorting(NULL); 135 136 /* 137 * expected output: 138 * 139 * Overhead Command Shared Object Symbol 140 * ======== ======= ============= ============== 141 * 20.00% perf perf [.] main 142 * 10.00% bash [kernel] [k] page_fault 143 * 10.00% bash bash [.] main 144 * 10.00% bash bash [.] xmalloc 145 * 10.00% perf [kernel] [k] page_fault 146 * 10.00% perf [kernel] [k] schedule 147 * 10.00% perf libc [.] free 148 * 10.00% perf libc [.] malloc 149 * 10.00% perf perf [.] cmd_record 150 */ 151 err = add_hist_entries(hists, machine); 152 if (err < 0) 153 goto out; 154 155 hists__collapse_resort(hists, NULL); 156 perf_evsel__output_resort(evsel, NULL); 157 158 if (verbose > 2) { 159 pr_info("[fields = %s, sort = %s]\n", field_order, sort_order); 160 print_hists_out(hists); 161 } 162 163 root = &hists->entries; 164 node = rb_first(root); 165 he = rb_entry(node, struct hist_entry, rb_node); 166 TEST_ASSERT_VAL("Invalid hist entry", 167 !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "perf") && 168 !strcmp(SYM(he), "main") && he->stat.period == 200); 169 170 node = rb_next(node); 171 he = rb_entry(node, struct hist_entry, rb_node); 172 TEST_ASSERT_VAL("Invalid hist entry", 173 !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "[kernel]") && 174 !strcmp(SYM(he), "page_fault") && he->stat.period == 100); 175 176 node = rb_next(node); 177 he = rb_entry(node, struct hist_entry, rb_node); 178 TEST_ASSERT_VAL("Invalid hist entry", 179 !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "bash") && 180 !strcmp(SYM(he), "main") && he->stat.period == 100); 181 182 node = rb_next(node); 183 he = rb_entry(node, struct hist_entry, rb_node); 184 TEST_ASSERT_VAL("Invalid hist entry", 185 !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "bash") && 186 !strcmp(SYM(he), "xmalloc") && he->stat.period == 100); 187 188 node = rb_next(node); 189 he = rb_entry(node, struct hist_entry, rb_node); 190 TEST_ASSERT_VAL("Invalid hist entry", 191 !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "[kernel]") && 192 !strcmp(SYM(he), "page_fault") && he->stat.period == 100); 193 194 node = rb_next(node); 195 he = rb_entry(node, struct hist_entry, rb_node); 196 TEST_ASSERT_VAL("Invalid hist entry", 197 !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "[kernel]") && 198 !strcmp(SYM(he), "schedule") && he->stat.period == 100); 199 200 node = rb_next(node); 201 he = rb_entry(node, struct hist_entry, rb_node); 202 TEST_ASSERT_VAL("Invalid hist entry", 203 !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "libc") && 204 !strcmp(SYM(he), "free") && he->stat.period == 100); 205 206 node = rb_next(node); 207 he = rb_entry(node, struct hist_entry, rb_node); 208 TEST_ASSERT_VAL("Invalid hist entry", 209 !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "libc") && 210 !strcmp(SYM(he), "malloc") && he->stat.period == 100); 211 212 node = rb_next(node); 213 he = rb_entry(node, struct hist_entry, rb_node); 214 TEST_ASSERT_VAL("Invalid hist entry", 215 !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "perf") && 216 !strcmp(SYM(he), "cmd_record") && he->stat.period == 100); 217 218 out: 219 del_hist_entries(hists); 220 reset_output_field(); 221 return err; 222 } 223 224 /* mixed fields and sort keys */ 225 static int test2(struct perf_evsel *evsel, struct machine *machine) 226 { 227 int err; 228 struct hists *hists = evsel__hists(evsel); 229 struct hist_entry *he; 230 struct rb_root *root; 231 struct rb_node *node; 232 233 field_order = "overhead,cpu"; 234 sort_order = "pid"; 235 236 setup_sorting(NULL); 237 238 /* 239 * expected output: 240 * 241 * Overhead CPU Command: Pid 242 * ======== === ============= 243 * 30.00% 1 perf : 100 244 * 10.00% 0 perf : 100 245 * 10.00% 2 perf : 100 246 * 20.00% 2 perf : 200 247 * 10.00% 0 bash : 300 248 * 10.00% 1 bash : 300 249 * 10.00% 3 bash : 300 250 */ 251 err = add_hist_entries(hists, machine); 252 if (err < 0) 253 goto out; 254 255 hists__collapse_resort(hists, NULL); 256 perf_evsel__output_resort(evsel, NULL); 257 258 if (verbose > 2) { 259 pr_info("[fields = %s, sort = %s]\n", field_order, sort_order); 260 print_hists_out(hists); 261 } 262 263 root = &hists->entries; 264 node = rb_first(root); 265 he = rb_entry(node, struct hist_entry, rb_node); 266 TEST_ASSERT_VAL("Invalid hist entry", 267 CPU(he) == 1 && PID(he) == 100 && he->stat.period == 300); 268 269 node = rb_next(node); 270 he = rb_entry(node, struct hist_entry, rb_node); 271 TEST_ASSERT_VAL("Invalid hist entry", 272 CPU(he) == 0 && PID(he) == 100 && he->stat.period == 100); 273 274 out: 275 del_hist_entries(hists); 276 reset_output_field(); 277 return err; 278 } 279 280 /* fields only (no sort key) */ 281 static int test3(struct perf_evsel *evsel, struct machine *machine) 282 { 283 int err; 284 struct hists *hists = evsel__hists(evsel); 285 struct hist_entry *he; 286 struct rb_root *root; 287 struct rb_node *node; 288 289 field_order = "comm,overhead,dso"; 290 sort_order = NULL; 291 292 setup_sorting(NULL); 293 294 /* 295 * expected output: 296 * 297 * Command Overhead Shared Object 298 * ======= ======== ============= 299 * bash 20.00% bash 300 * bash 10.00% [kernel] 301 * perf 30.00% perf 302 * perf 20.00% [kernel] 303 * perf 20.00% libc 304 */ 305 err = add_hist_entries(hists, machine); 306 if (err < 0) 307 goto out; 308 309 hists__collapse_resort(hists, NULL); 310 perf_evsel__output_resort(evsel, NULL); 311 312 if (verbose > 2) { 313 pr_info("[fields = %s, sort = %s]\n", field_order, sort_order); 314 print_hists_out(hists); 315 } 316 317 root = &hists->entries; 318 node = rb_first(root); 319 he = rb_entry(node, struct hist_entry, rb_node); 320 TEST_ASSERT_VAL("Invalid hist entry", 321 !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "bash") && 322 he->stat.period == 200); 323 324 node = rb_next(node); 325 he = rb_entry(node, struct hist_entry, rb_node); 326 TEST_ASSERT_VAL("Invalid hist entry", 327 !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "[kernel]") && 328 he->stat.period == 100); 329 330 node = rb_next(node); 331 he = rb_entry(node, struct hist_entry, rb_node); 332 TEST_ASSERT_VAL("Invalid hist entry", 333 !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "perf") && 334 he->stat.period == 300); 335 336 node = rb_next(node); 337 he = rb_entry(node, struct hist_entry, rb_node); 338 TEST_ASSERT_VAL("Invalid hist entry", 339 !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "[kernel]") && 340 he->stat.period == 200); 341 342 node = rb_next(node); 343 he = rb_entry(node, struct hist_entry, rb_node); 344 TEST_ASSERT_VAL("Invalid hist entry", 345 !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "libc") && 346 he->stat.period == 200); 347 348 out: 349 del_hist_entries(hists); 350 reset_output_field(); 351 return err; 352 } 353 354 /* handle duplicate 'dso' field */ 355 static int test4(struct perf_evsel *evsel, struct machine *machine) 356 { 357 int err; 358 struct hists *hists = evsel__hists(evsel); 359 struct hist_entry *he; 360 struct rb_root *root; 361 struct rb_node *node; 362 363 field_order = "dso,sym,comm,overhead,dso"; 364 sort_order = "sym"; 365 366 setup_sorting(NULL); 367 368 /* 369 * expected output: 370 * 371 * Shared Object Symbol Command Overhead 372 * ============= ============== ======= ======== 373 * perf [.] cmd_record perf 10.00% 374 * libc [.] free perf 10.00% 375 * bash [.] main bash 10.00% 376 * perf [.] main perf 20.00% 377 * libc [.] malloc perf 10.00% 378 * [kernel] [k] page_fault bash 10.00% 379 * [kernel] [k] page_fault perf 10.00% 380 * [kernel] [k] schedule perf 10.00% 381 * bash [.] xmalloc bash 10.00% 382 */ 383 err = add_hist_entries(hists, machine); 384 if (err < 0) 385 goto out; 386 387 hists__collapse_resort(hists, NULL); 388 perf_evsel__output_resort(evsel, NULL); 389 390 if (verbose > 2) { 391 pr_info("[fields = %s, sort = %s]\n", field_order, sort_order); 392 print_hists_out(hists); 393 } 394 395 root = &hists->entries; 396 node = rb_first(root); 397 he = rb_entry(node, struct hist_entry, rb_node); 398 TEST_ASSERT_VAL("Invalid hist entry", 399 !strcmp(DSO(he), "perf") && !strcmp(SYM(he), "cmd_record") && 400 !strcmp(COMM(he), "perf") && he->stat.period == 100); 401 402 node = rb_next(node); 403 he = rb_entry(node, struct hist_entry, rb_node); 404 TEST_ASSERT_VAL("Invalid hist entry", 405 !strcmp(DSO(he), "libc") && !strcmp(SYM(he), "free") && 406 !strcmp(COMM(he), "perf") && he->stat.period == 100); 407 408 node = rb_next(node); 409 he = rb_entry(node, struct hist_entry, rb_node); 410 TEST_ASSERT_VAL("Invalid hist entry", 411 !strcmp(DSO(he), "bash") && !strcmp(SYM(he), "main") && 412 !strcmp(COMM(he), "bash") && he->stat.period == 100); 413 414 node = rb_next(node); 415 he = rb_entry(node, struct hist_entry, rb_node); 416 TEST_ASSERT_VAL("Invalid hist entry", 417 !strcmp(DSO(he), "perf") && !strcmp(SYM(he), "main") && 418 !strcmp(COMM(he), "perf") && he->stat.period == 200); 419 420 node = rb_next(node); 421 he = rb_entry(node, struct hist_entry, rb_node); 422 TEST_ASSERT_VAL("Invalid hist entry", 423 !strcmp(DSO(he), "libc") && !strcmp(SYM(he), "malloc") && 424 !strcmp(COMM(he), "perf") && he->stat.period == 100); 425 426 node = rb_next(node); 427 he = rb_entry(node, struct hist_entry, rb_node); 428 TEST_ASSERT_VAL("Invalid hist entry", 429 !strcmp(DSO(he), "[kernel]") && !strcmp(SYM(he), "page_fault") && 430 !strcmp(COMM(he), "bash") && he->stat.period == 100); 431 432 node = rb_next(node); 433 he = rb_entry(node, struct hist_entry, rb_node); 434 TEST_ASSERT_VAL("Invalid hist entry", 435 !strcmp(DSO(he), "[kernel]") && !strcmp(SYM(he), "page_fault") && 436 !strcmp(COMM(he), "perf") && he->stat.period == 100); 437 438 node = rb_next(node); 439 he = rb_entry(node, struct hist_entry, rb_node); 440 TEST_ASSERT_VAL("Invalid hist entry", 441 !strcmp(DSO(he), "[kernel]") && !strcmp(SYM(he), "schedule") && 442 !strcmp(COMM(he), "perf") && he->stat.period == 100); 443 444 node = rb_next(node); 445 he = rb_entry(node, struct hist_entry, rb_node); 446 TEST_ASSERT_VAL("Invalid hist entry", 447 !strcmp(DSO(he), "bash") && !strcmp(SYM(he), "xmalloc") && 448 !strcmp(COMM(he), "bash") && he->stat.period == 100); 449 450 out: 451 del_hist_entries(hists); 452 reset_output_field(); 453 return err; 454 } 455 456 /* full sort keys w/o overhead field */ 457 static int test5(struct perf_evsel *evsel, struct machine *machine) 458 { 459 int err; 460 struct hists *hists = evsel__hists(evsel); 461 struct hist_entry *he; 462 struct rb_root *root; 463 struct rb_node *node; 464 465 field_order = "cpu,pid,comm,dso,sym"; 466 sort_order = "dso,pid"; 467 468 setup_sorting(NULL); 469 470 /* 471 * expected output: 472 * 473 * CPU Command: Pid Command Shared Object Symbol 474 * === ============= ======= ============= ============== 475 * 0 perf: 100 perf [kernel] [k] schedule 476 * 2 perf: 200 perf [kernel] [k] page_fault 477 * 1 bash: 300 bash [kernel] [k] page_fault 478 * 0 bash: 300 bash bash [.] xmalloc 479 * 3 bash: 300 bash bash [.] main 480 * 1 perf: 100 perf libc [.] malloc 481 * 2 perf: 100 perf libc [.] free 482 * 1 perf: 100 perf perf [.] cmd_record 483 * 1 perf: 100 perf perf [.] main 484 * 2 perf: 200 perf perf [.] main 485 */ 486 err = add_hist_entries(hists, machine); 487 if (err < 0) 488 goto out; 489 490 hists__collapse_resort(hists, NULL); 491 perf_evsel__output_resort(evsel, NULL); 492 493 if (verbose > 2) { 494 pr_info("[fields = %s, sort = %s]\n", field_order, sort_order); 495 print_hists_out(hists); 496 } 497 498 root = &hists->entries; 499 node = rb_first(root); 500 he = rb_entry(node, struct hist_entry, rb_node); 501 502 TEST_ASSERT_VAL("Invalid hist entry", 503 CPU(he) == 0 && PID(he) == 100 && 504 !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "[kernel]") && 505 !strcmp(SYM(he), "schedule") && he->stat.period == 100); 506 507 node = rb_next(node); 508 he = rb_entry(node, struct hist_entry, rb_node); 509 TEST_ASSERT_VAL("Invalid hist entry", 510 CPU(he) == 2 && PID(he) == 200 && 511 !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "[kernel]") && 512 !strcmp(SYM(he), "page_fault") && he->stat.period == 100); 513 514 node = rb_next(node); 515 he = rb_entry(node, struct hist_entry, rb_node); 516 TEST_ASSERT_VAL("Invalid hist entry", 517 CPU(he) == 1 && PID(he) == 300 && 518 !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "[kernel]") && 519 !strcmp(SYM(he), "page_fault") && he->stat.period == 100); 520 521 node = rb_next(node); 522 he = rb_entry(node, struct hist_entry, rb_node); 523 TEST_ASSERT_VAL("Invalid hist entry", 524 CPU(he) == 0 && PID(he) == 300 && 525 !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "bash") && 526 !strcmp(SYM(he), "xmalloc") && he->stat.period == 100); 527 528 node = rb_next(node); 529 he = rb_entry(node, struct hist_entry, rb_node); 530 TEST_ASSERT_VAL("Invalid hist entry", 531 CPU(he) == 3 && PID(he) == 300 && 532 !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "bash") && 533 !strcmp(SYM(he), "main") && he->stat.period == 100); 534 535 node = rb_next(node); 536 he = rb_entry(node, struct hist_entry, rb_node); 537 TEST_ASSERT_VAL("Invalid hist entry", 538 CPU(he) == 1 && PID(he) == 100 && 539 !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "libc") && 540 !strcmp(SYM(he), "malloc") && he->stat.period == 100); 541 542 node = rb_next(node); 543 he = rb_entry(node, struct hist_entry, rb_node); 544 TEST_ASSERT_VAL("Invalid hist entry", 545 CPU(he) == 2 && PID(he) == 100 && 546 !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "libc") && 547 !strcmp(SYM(he), "free") && he->stat.period == 100); 548 549 node = rb_next(node); 550 he = rb_entry(node, struct hist_entry, rb_node); 551 TEST_ASSERT_VAL("Invalid hist entry", 552 CPU(he) == 1 && PID(he) == 100 && 553 !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "perf") && 554 !strcmp(SYM(he), "cmd_record") && he->stat.period == 100); 555 556 node = rb_next(node); 557 he = rb_entry(node, struct hist_entry, rb_node); 558 TEST_ASSERT_VAL("Invalid hist entry", 559 CPU(he) == 1 && PID(he) == 100 && 560 !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "perf") && 561 !strcmp(SYM(he), "main") && he->stat.period == 100); 562 563 node = rb_next(node); 564 he = rb_entry(node, struct hist_entry, rb_node); 565 TEST_ASSERT_VAL("Invalid hist entry", 566 CPU(he) == 2 && PID(he) == 200 && 567 !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "perf") && 568 !strcmp(SYM(he), "main") && he->stat.period == 100); 569 570 out: 571 del_hist_entries(hists); 572 reset_output_field(); 573 return err; 574 } 575 576 int test__hists_output(int subtest __maybe_unused) 577 { 578 int err = TEST_FAIL; 579 struct machines machines; 580 struct machine *machine; 581 struct perf_evsel *evsel; 582 struct perf_evlist *evlist = perf_evlist__new(); 583 size_t i; 584 test_fn_t testcases[] = { 585 test1, 586 test2, 587 test3, 588 test4, 589 test5, 590 }; 591 592 TEST_ASSERT_VAL("No memory", evlist); 593 594 err = parse_events(evlist, "cpu-clock", NULL); 595 if (err) 596 goto out; 597 err = TEST_FAIL; 598 599 machines__init(&machines); 600 601 /* setup threads/dso/map/symbols also */ 602 machine = setup_fake_machine(&machines); 603 if (!machine) 604 goto out; 605 606 if (verbose > 1) 607 machine__fprintf(machine, stderr); 608 609 evsel = perf_evlist__first(evlist); 610 611 for (i = 0; i < ARRAY_SIZE(testcases); i++) { 612 err = testcases[i](evsel, machine); 613 if (err < 0) 614 break; 615 } 616 617 out: 618 /* tear down everything */ 619 perf_evlist__delete(evlist); 620 machines__exit(&machines); 621 622 return err; 623 } 624