xref: /freebsd/usr.sbin/pmc/cmd_pmc_record.cc (revision 78bbe9fc3e3529731df07e29464f103f96f6ebed)
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