1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2026, Netflix, Inc.
5 *
6 * This software was developed by Ali Mashtizadeh under the sponsorship from
7 * Netflix, Inc.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
29 *
30 */
31
32 #include <sys/param.h>
33 #include <sys/cdefs.h>
34 #include <sys/cpuset.h>
35 #include <sys/event.h>
36 #include <sys/queue.h>
37 #include <sys/socket.h>
38 #include <sys/stat.h>
39 #include <sys/sysctl.h>
40 #include <sys/time.h>
41 #include <sys/ttycom.h>
42 #include <sys/user.h>
43 #include <sys/wait.h>
44
45 #include <assert.h>
46 #include <curses.h>
47 #include <err.h>
48 #include <errno.h>
49 #include <fcntl.h>
50 #include <getopt.h>
51 #include <kvm.h>
52 #include <libgen.h>
53 #include <limits.h>
54 #include <locale.h>
55 #include <math.h>
56 #include <pmc.h>
57 #include <pmclog.h>
58 #include <regex.h>
59 #include <signal.h>
60 #include <stdalign.h>
61 #include <stdarg.h>
62 #include <stdint.h>
63 #include <stdio.h>
64 #include <stdlib.h>
65 #include <stddef.h>
66 #include <string.h>
67 #include <sysexits.h>
68 #include <unistd.h>
69
70 #include <machine/cpufunc.h>
71
72 #include <libpmcstat.h>
73 #include "cmd_pmc.h"
74
75 #include <iostream>
76 #include <map>
77 #include <string>
78 #include <vector>
79
80 #include "display.hh"
81 #include "headers.hh"
82
83 #define DEFAULT_RATE 65536
84
85 class pmc_config
86 {
87 public:
88 std::string event;
89 uint64_t count;
90 cpuset_t cpumask;
91 uint32_t caps;
pmc_config()92 pmc_config() : event(), count(0), ids()
93 {
94 CPU_ZERO(&cpumask);
95 }
pmc_config(const std::string & event,uint64_t count,cpuset_t cpumask)96 pmc_config(const std::string &event, uint64_t count, cpuset_t cpumask)
97 : event(event), count(count), ids()
98 {
99 CPU_COPY(&cpumask, &this->cpumask);
100 }
101 /*
102 * Read the capabilities and fills in the cpumask for a given event
103 * counter. The way that the hwpmc module currently works we do not
104 * know which pmc class will back our counter until we look it up. The
105 * easiest way is to program the counter into the first CPU present in
106 * the mask and then retrieve the capabilities.
107 */
getcaps()108 void getcaps() {
109 int status;
110 int testcpu;
111 pmc_id_t id;
112
113 testcpu = CPU_FFS(&cpumask) - 1;
114
115 status = pmc_allocate(event.c_str(), PMC_MODE_SS, PMC_F_CALLCHAIN,
116 testcpu, &id, count);
117 if (status < 0)
118 err(EX_OSERR, "ERROR: Cannot allocate event '%s'", event.c_str());
119
120 status = pmc_capabilities(id, &caps);
121 if (status < 0)
122 err(EX_OSERR, "ERROR: Cannot get pmc capabilities");
123
124 pmc_release(id);
125
126 if (caps & PMC_CAP_SYSWIDE) {
127 CPU_ZERO(&cpumask);
128 CPU_SET(0, &cpumask);
129 }
130 if (caps & PMC_CAP_DOMWIDE) {
131 int domains;
132 size_t len;
133
134 CPU_ZERO(&cpumask);
135
136 len = sizeof(domains);
137 if (sysctlbyname("vm.ndomains", &domains, &len, NULL, 0) == -1)
138 err(EX_OSERR, "ERROR: Cannot get number of domains");
139
140 for (int i = 1; i < domains; i++) {
141 cpuset_t dmask;
142
143 CPU_ZERO(&dmask);
144 status = cpuset_getaffinity(CPU_LEVEL_WHICH, CPU_WHICH_DOMAIN, i,
145 sizeof(dmask), &dmask);
146 if (status < 0)
147 err(EX_OSERR, "ERROR: Cannot get domain mask");
148 CPU_SET(CPU_FFS(&dmask) - 1, &cpumask);
149 }
150 }
151 }
152 /*
153 * Allocate the PMC across all CPUs.
154 */
allocate()155 void allocate() {
156 int cpu;
157 int status;
158 pmc_id_t id;
159
160 for (cpu = 0; cpu < CPU_SETSIZE; cpu++) {
161 if (!CPU_ISSET(cpu, &cpumask))
162 continue;
163
164 status = pmc_allocate(event.c_str(), PMC_MODE_SS, PMC_F_CALLCHAIN,
165 cpu, &id, count);
166 if (status < 0)
167 err(EX_OSERR, "ERROR: Cannot allocate event '%s'",
168 event.c_str());
169
170 status = pmc_set(id, count);
171 if (status < 0)
172 err(EX_OSERR, "ERROR: Cannot set sampling count for event '%s'",
173 event.c_str());
174
175 ids.push_back(id);
176 }
177 }
178 /*
179 * Release all PMC instances (one per CPU).
180 */
release()181 void release() {
182 for (auto i : ids) {
183 if (pmc_release(i) < 0) {
184 perror("pmc_start");
185 }
186 }
187 }
188 /*
189 * Start all PMC instances (one per CPU).
190 */
start()191 void start() {
192 for (auto i : ids) {
193 if (pmc_start(i) < 0) {
194 perror("pmc_start");
195 }
196 }
197 }
198 /*
199 * Stop all PMC instances (one per CPU).
200 */
stop()201 void stop() {
202 for (auto i : ids) {
203 if (pmc_stop(i) < 0) {
204 perror("pmc_start");
205 }
206 }
207 }
208 private:
209 std::vector<pmc_id_t> ids;
210 };
211
212 static std::vector<pmc_config> pmcs = std::vector<pmc_config>();
213 static struct option longopts[] = {
214 { "rate", required_argument, NULL, 'n' },
215 { "counter", required_argument, NULL, 'c' },
216 { "time", required_argument, NULL, 't' },
217 { "study", required_argument, NULL, 's' },
218 { NULL, 0, NULL, 0 }
219 };
220 static struct timespec start;
221 static int timelimit = 0;
222
223 static void
usage(void)224 usage(void)
225 {
226 printf("Usage: pmc record [options] [output.pmc]\n\n");
227 printf("Record a study\n\n");
228 printf("Options:\n");
229 printf("\t-c Sampling counter\n");
230 printf("\t-r Sample rate (default: %d)\n", DEFAULT_RATE);
231 printf("\t-s Specify a study\n");
232 printf("\t-t Run the study for specified amount of time\n");
233 printf("\nStudies:\n");
234 #if defined(__i386__) || defined(__amd64__)
235 printf("\tbranches Study branch misprediction (Requires AMD IBS)\n");
236 printf("\tc2c Study cache to cache communications (Requires AMD IBS)\n");
237 #endif
238 printf("\tdefault Instruction sampling for general analysis\n");
239 printf("\tflamegraph Instruction sampling for general analysis\n");
240 #if defined(__i386__) || defined(__amd64__)
241 printf("\tfrontend Study frontend stalls (Requires AMD IBS)\n");
242 printf("\tmemory Study memory operations (Requires AMD IBS)\n");
243 #endif
244 }
245
246 int
writelog(int logfd,const void * buf,size_t len)247 writelog(int logfd, const void *buf, size_t len)
248 {
249 int status;
250 const char *cur = (const char *)buf;
251
252 while (len != 0) {
253 status = write(logfd, cur, len);
254 if (status < 0) {
255 if (errno == EINTR || errno == EAGAIN)
256 continue;
257 else
258 return status;
259 }
260 if (status == 0)
261 return status;
262
263 cur += status;
264 len -= status;
265 }
266
267 return 0;
268 }
269
270 /*
271 * Write the PMC header. Each of the header payloads have their own header
272 * containing the payloads size. Thus the pmc tools can just skip over regions
273 * that they do not know how to handle.
274 */
275 int
write_header(int logfd)276 write_header(int logfd)
277 {
278 int status;
279 pmchdr_header hdr;
280
281 hdr.magic = PMC_HEADER_MAGIC;
282 hdr.version = PMC_HEADER_VERSION;
283 #if defined(__amd64__)
284 hdr.arch = PMC_ARCH_AMD64;
285 #elif defined(__aarch64__)
286 hdr.arch = PMC_ARCH_ARM64;
287 #elif defined(__powerpc64__)
288 hdr.arch = PMC_ARCH_PPC64;
289 #elif defined(__riscv)
290 hdr.arch = PMC_ARCH_RISCV64;
291 #elif defined(__arm__)
292 hdr.arch = PMC_ARCH_ARM;
293 #else
294 hdr.arch = 0;
295 #endif
296
297 status = writelog(logfd, &hdr, sizeof(hdr));
298 if (status < 0) {
299 perror("writelog");
300 }
301
302 return status;
303 }
304
305 /*
306 * Write out the sysinfo header.
307 */
308 int
write_sysinfo(int logfd)309 write_sysinfo(int logfd)
310 {
311 int status;
312 pmchdr_infohdr hdr;
313 pmchdr_sysinfo sys;
314 char val[64];
315 size_t valsz;
316
317 valsz = sizeof(val);
318 status = sysctlbyname("hw.model", &val, &valsz, NULL, 0);
319 if (status < 0) {
320 perror("sysctlbyname");
321 return status;
322 }
323 strlcpy(sys.cpumodel, val, sizeof(sys.cpumodel));
324
325 valsz = sizeof(val);
326 status = sysctlbyname("kern.osrelease", &val, &valsz, NULL, 0);
327 if (status < 0) {
328 perror("sysctlbyname");
329 return status;
330 }
331 strlcpy(sys.osrelease, val, sizeof(sys.osrelease));
332
333 valsz = sizeof(val);
334 status = sysctlbyname("kern.build_id", &val, &valsz, NULL, 0);
335 if (status < 0) {
336 perror("sysctlbyname");
337 return status;
338 }
339 strlcpy(sys.buildid, val, sizeof(sys.buildid));
340
341 hdr.type = INFOHDR_TYPE_SYSINFO;
342 hdr.length = sizeof(sys);
343 status = writelog(logfd, &hdr, sizeof(hdr));
344 if (status < 0) {
345 err(EX_IOERR, "writelog");
346 }
347
348 status = writelog(logfd, &sys, sizeof(sys));
349 if (status < 0) {
350 err(EX_IOERR, "writelog");
351 }
352
353 return status;
354 };
355
356 #if defined(__i386__) || defined(__amd64__)
357 #define CPUID_ROOT_BASE 0x00000000
358 #define CPUID_ROOT_VM 0x40000000
359 #define CPUID_ROOT_EXT 0x80000000
360
361 /*
362 * Write out the CPU Info block. After the standard header that declares the
363 * size of the payload, we write out all the CPUID root and leafs for each of
364 * the three major roots: base, VM, and extended artribute space. On all
365 * modern processors the root contains the maximum leaf as the first value
366 * allowing us to decode how many leafs there are.
367 */
368 int
write_cpuinfo(int logfd)369 write_cpuinfo(int logfd)
370 {
371 int status;
372 u_int tmp[4];
373 uint32_t base_max, vm_max, ext_max;
374 uint32_t len;
375 pmchdr_infohdr *hdr;
376 uint32_t *buf, *off;
377
378 /*
379 * Find the length of the main, VM, and extended CPUID leafs
380 */
381 do_cpuid(CPUID_ROOT_BASE, tmp);
382 base_max = tmp[0];
383 len = base_max + 1;
384
385 do_cpuid(CPUID_ROOT_VM, tmp);
386 vm_max = tmp[0];
387 if (vm_max != 0)
388 len += vm_max - CPUID_ROOT_VM + 1;
389
390 do_cpuid(CPUID_ROOT_EXT, tmp);
391 ext_max = tmp[0];
392 if (ext_max != 0)
393 len += ext_max - CPUID_ROOT_EXT + 1;
394
395 // 4 Registers per Leaf x 4 Bytes per Register
396 len *= 4;
397
398 buf = (uint32_t *)new uint32_t[len + sizeof(pmchdr_infohdr) / 4];
399 hdr = (pmchdr_infohdr *)buf;
400 hdr->type = INFOHDR_TYPE_CPUID;
401 hdr->length = 4 * len;
402 off = (uint32_t *)(hdr + 1);
403
404 for (uint32_t i = 0; i <= base_max; i++) {
405 do_cpuid(i, off);
406 off += 4;
407 }
408
409 if (vm_max) {
410 for (uint32_t i = CPUID_ROOT_VM; i <= vm_max; i++) {
411 do_cpuid(i, off);
412 off += 4;
413 }
414 }
415 if (ext_max) {
416 for (uint32_t i = CPUID_ROOT_EXT; i <= ext_max; i++) {
417 do_cpuid(i, off);
418 off += 4;
419 }
420 }
421
422 status = writelog(logfd, buf, 4 * len + sizeof(*hdr));
423 if (status < 0) {
424 delete[] buf;
425 err(EX_IOERR, "writelog");
426 }
427
428 delete[] buf;
429
430 return 0;
431 }
432 #elif defined(__aarch64__)
433 int
write_cpuinfo(__unused int logfd)434 write_cpuinfo(__unused int logfd)
435 {
436 return 0;
437 }
438 #elif defined(__powerpc64__)
439 int
write_cpuinfo(__unused int logfd)440 write_cpuinfo(__unused int logfd)
441 {
442 return 0;
443 }
444 #else
445 int
write_cpuinfo(__unused int logfd)446 write_cpuinfo(__unused int logfd)
447 {
448 return 0;
449 }
450 #endif
451
452 int
write_footer(int logfd)453 write_footer(int logfd)
454 {
455 int status;
456 pmchdr_infohdr hdr;
457
458 hdr.type = INFOHDR_TYPE_DONE;
459 hdr.length = 0;
460
461 status = writelog(logfd, &hdr, sizeof(hdr));
462 if (status < 0) {
463 perror("writelog");
464 }
465
466 return status;
467 }
468
469 #if defined(__i386__) || defined(__amd64__)
470 int
setup_study(const std::string & study,uint64_t rate,cpuset_t mask)471 setup_study(const std::string &study, uint64_t rate, cpuset_t mask)
472 {
473 u_int tmp[4];
474 alignas(4) char vendor[16];
475 std::string event;
476 uint32_t ext_max;
477 uint32_t ibs_features;
478 bool is_amd, is_intel;
479
480 do_cpuid(CPUID_ROOT_BASE, tmp);
481
482 /* Find the brand */
483 is_intel = false;
484 is_amd = false;
485
486 /* i386 complains without explicit alignment */
487 ((u_int *)&vendor)[0] = tmp[1];
488 ((u_int *)&vendor)[1] = tmp[3];
489 ((u_int *)&vendor)[2] = tmp[2];
490 vendor[12] = 0;
491 if (strncmp(vendor, INTEL_VENDOR_ID, 12) == 0)
492 is_intel = true;
493 if (strncmp(vendor, AMD_VENDOR_ID, 12) == 0)
494 is_amd = true;
495 if (strncmp(vendor, HYGON_VENDOR_ID, 12) == 0)
496 is_amd = true;
497
498 ibs_features = 0;
499 do_cpuid(CPUID_ROOT_EXT, tmp);
500 ext_max = tmp[0];
501 if (is_amd && ext_max >= CPUID_IBSID) {
502 do_cpuid(CPUID_IBSID, tmp);
503 ibs_features = tmp[0];
504 }
505
506 if (study == "default" || study == "flamegraph") {
507 pmcs.push_back(pmc_config("unhalted-cycles", rate, mask));
508 return 0;
509 }
510
511 if (is_intel) {
512 err(EX_SOFTWARE, "ERROR: Study is unsupported on Intel");
513 }
514
515 if (study == "frontend") {
516 event = "ibs-fetch,randomize";
517 } else if (study == "branches" || study == "memory") {
518 event = "ibs-op";
519 if (ibs_features & CPUID_IBSID_OPCNT)
520 event += ",opcount";
521 } else if (study == "c2c") {
522 event = "ibs-op";
523 if (ibs_features & CPUID_IBSID_IBSLOADLATENCYFILT)
524 event += ",l3miss";
525 if (ibs_features & CPUID_IBSID_OPCNT)
526 event += ",opcount";
527 } else {
528 err(EX_USAGE, "ERROR: Study '%s' unknown", study.c_str());
529 }
530
531 pmcs.push_back(pmc_config(event, rate, mask));
532
533 return 0;
534 }
535 #else
536 int
setup_study(const std::string & study,uint64_t rate,cpuset_t mask)537 setup_study(const std::string &study, uint64_t rate, cpuset_t mask)
538 {
539 if (study == "default" || study == "flamegraph") {
540 pmcs.push_back(pmc_config("unhalted-cycles", rate, mask));
541 return 0;
542 }
543
544 err(EX_SOFTWARE, "ERROR: Study is unsupported on this architecture");
545 }
546 #endif
547
548 /*
549 * Compute the current runtime and print it to the screen, if it exceeds
550 * timelimit return 1 otherwise 0.
551 */
552 int
update_status(int logfd)553 update_status(int logfd)
554 {
555 int status;
556 long sec, nsec;
557 struct timespec end;
558 struct stat sb;
559 std::string filesz;
560
561 status = clock_gettime(CLOCK_MONOTONIC, &end);
562 if (status < 0) {
563 err(EX_OSERR, "Could not read current time");
564 }
565
566 sec = end.tv_sec - start.tv_sec;
567 nsec = end.tv_nsec - start.tv_nsec;
568 if (nsec < 0) {
569 sec--;
570 nsec += 1000000000L;
571 }
572
573 // Do this another way for sockets
574 status = fstat(logfd, &sb);
575 if (status < 0) {
576 err(EX_OSERR, "Failed to fstat log file");
577 }
578
579 filesz = format_binprefix(sb.st_size);
580
581 /*
582 * Use %-10s to ensure we print spaces after in case the size string
583 * shrinks.
584 */
585 printf("Elapsed: %ld.%03ld seconds, Log file size: %-10s\r",
586 sec, nsec / 1000000,
587 filesz.c_str());
588 fflush(stdout);
589
590 if (timelimit != 0 && sec >= timelimit) {
591 printf("\nElapsed time completed\n");
592 return 1;
593 }
594
595 return 0;
596 }
597
598 int
cmd_pmc_record(int argc,char ** argv)599 cmd_pmc_record(int argc, char **argv)
600 {
601 struct kevent kev;
602 cpuset_t mask;
603 uint64_t rate = DEFAULT_RATE;
604 const char *logfile = "default.log";
605 int status;
606 int option, logfd, kq;
607
608 CPU_ZERO(&mask);
609 if (cpuset_getaffinity(CPU_LEVEL_ROOT, CPU_WHICH_PID, -1,
610 sizeof(mask), &mask) == -1)
611 err(EX_OSERR, "ERROR: Cannot determine the available CPUs");
612
613 if (pmc_init() < 0)
614 err(EX_SOFTWARE, "ERROR: Failed to initialize libpmc");
615
616 while ((option = getopt_long(argc, argv, "c:r:s:t:", longopts, NULL)) != -1) {
617 switch (option) {
618 case 'c':
619 pmcs.push_back(pmc_config(optarg, rate, mask));
620 break;
621 case 'r':
622 rate = strtoull(optarg, NULL, 0);
623 break;
624 case 's':
625 setup_study(optarg, rate, mask);
626 break;
627 case 't':
628 timelimit = atoi(optarg);
629 break;
630 case '?':
631 default:
632 usage();
633 }
634 }
635 argc -= optind;
636 argv += optind;
637 if (argc != 0 && argc != 1) {
638 usage();
639 exit(EX_USAGE);
640 }
641 if (argc == 1)
642 logfile = argv[0];
643
644 if (pmcs.size() == 0) {
645 errx(EX_NOINPUT, "ERROR: Please select one or more counters or performance studies.");
646 }
647
648 if ((logfd = open(logfile, O_CREAT|O_EXCL|O_WRONLY,
649 S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)) < 0) {
650 errx(EX_OSERR, "ERROR: Cannot open \"%s\" for writing: %s.", logfile,
651 strerror(errno));
652 }
653
654 setup_screen();
655
656 for (auto &p : pmcs) {
657 p.getcaps();
658 }
659
660 write_header(logfd);
661 write_sysinfo(logfd);
662 write_cpuinfo(logfd);
663 write_footer(logfd);
664
665 // Record pmclog
666 kq = kqueue();
667 if (kq < 0)
668 err(EX_OSERR, "ERROR: kqueue creation failed");
669
670 EV_SET(&kev, fileno(stdin), EVFILT_READ, EV_ADD, 0, 0, NULL);
671 if (kevent(kq, &kev, 1, NULL, 0, NULL) < 0)
672 err(EX_OSERR, "ERROR: kevent failed to register stdin");
673
674 EV_SET(&kev, SIGINT, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);
675 if (kevent(kq, &kev, 1, NULL, 0, NULL) < 0)
676 err(EX_OSERR, "ERROR: kevent failed to register SIGINT");
677 EV_SET(&kev, SIGIO, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);
678 if (kevent(kq, &kev, 1, NULL, 0, NULL) < 0)
679 err(EX_OSERR, "ERROR: kevent failed to register SIGIO");
680
681 EV_SET(&kev, 0, EVFILT_TIMER, EV_ADD, 0, 100, NULL);
682 if (kevent(kq, &kev, 1, NULL, 0, NULL) < 0)
683 err(EX_OSERR, "ERROR: Cannot register kevent for timer");
684
685 pmc_configure_logfile(logfd);
686
687 for (auto &p : pmcs) {
688 p.allocate();
689 }
690
691 for (auto &p : pmcs) {
692 p.start();
693 }
694
695 if (clock_gettime(CLOCK_MONOTONIC, &start) < 0)
696 err(EX_OSERR, "ERROR: Could not get the current time");
697
698 printf("Recording performance trace press Ctrl-C to stop\n");
699
700 /*
701 * loop till either the target process (if any) exits, or we
702 * are killed by a SIGINT or we reached the time duration.
703 */
704 while (1) {
705 status = kevent(kq, NULL, 0, &kev, 1, NULL);
706 if (status <= 0) {
707 if (errno != EINTR)
708 err(EX_OSERR, "ERROR: kevent failed");
709 else
710 continue;
711 }
712
713 if (kev.flags & EV_ERROR)
714 errc(EX_OSERR, kev.data, "ERROR: kevent failed");
715
716 switch (kev.filter) {
717 case EVFILT_READ: /* log file data is present */
718 if (kev.ident == (unsigned)fileno(stdin)) {
719 // Check for exit key
720 }
721 break;
722 case EVFILT_SIGNAL:
723 if (kev.ident == SIGIO) {
724 fprintf(stderr, "ERROR: IO error");
725 status = EX_OSERR;
726 goto done;
727 } else if (kev.ident == SIGINT) {
728 status = EX_OK;
729 goto done;
730 } else {
731 err(EX_OSERR, "Unknown signal recieved");
732 }
733 break;
734 case EVFILT_TIMER:
735 if (update_status(logfd) == 1)
736 goto done;
737 break;
738 }
739 }
740
741 done:
742 for (auto &p : pmcs) {
743 p.stop();
744 p.release();
745 }
746 close(logfd);
747
748 return (status);
749 }
750
751