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