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