Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F170446009
D57777.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
18 KB
Referenced Files
None
Subscribers
None
D57777.diff
View Options
diff --git a/usr.sbin/pmc/cmd_pmc_record.cc b/usr.sbin/pmc/cmd_pmc_record.cc
new file mode 100644
--- /dev/null
+++ b/usr.sbin/pmc/cmd_pmc_record.cc
@@ -0,0 +1,750 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026, Netflix, Inc.
+ *
+ * This software was developed by Ali Mashtizadeh under the sponsorship from
+ * Netflix, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include <sys/param.h>
+#include <sys/cdefs.h>
+#include <sys/cpuset.h>
+#include <sys/event.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <sys/time.h>
+#include <sys/ttycom.h>
+#include <sys/user.h>
+#include <sys/wait.h>
+
+#include <assert.h>
+#include <curses.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <kvm.h>
+#include <libgen.h>
+#include <limits.h>
+#include <locale.h>
+#include <math.h>
+#include <pmc.h>
+#include <pmclog.h>
+#include <regex.h>
+#include <signal.h>
+#include <stdalign.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include <machine/cpufunc.h>
+
+#include <libpmcstat.h>
+#include "cmd_pmc.h"
+
+#include <iostream>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "display.hh"
+#include "headers.hh"
+
+#define DEFAULT_RATE 65536
+
+class pmc_config
+{
+public:
+ std::string event;
+ uint64_t count;
+ cpuset_t cpumask;
+ uint32_t caps;
+ pmc_config() : event(), count(0), ids()
+ {
+ CPU_ZERO(&cpumask);
+ }
+ pmc_config(const std::string &event, uint64_t count, cpuset_t cpumask)
+ : event(event), count(count), ids()
+ {
+ CPU_COPY(&cpumask, &this->cpumask);
+ }
+ /*
+ * Read the capabilities and fills in the cpumask for a given event
+ * counter. The way that the hwpmc module currently works we do not
+ * know which pmc class will back our counter until we look it up. The
+ * easiest way is to program the counter into the first CPU present in
+ * the mask and then retrieve the capabilities.
+ */
+ void getcaps() {
+ int status;
+ int testcpu;
+ pmc_id_t id;
+
+ testcpu = CPU_FFS(&cpumask) - 1;
+
+ status = pmc_allocate(event.c_str(), PMC_MODE_SS, PMC_F_CALLCHAIN,
+ testcpu, &id, count);
+ if (status < 0)
+ err(EX_OSERR, "ERROR: Cannot allocate event '%s'", event.c_str());
+
+ status = pmc_capabilities(id, &caps);
+ if (status < 0)
+ err(EX_OSERR, "ERROR: Cannot get pmc capabilities");
+
+ pmc_release(id);
+
+ if (caps & PMC_CAP_SYSWIDE) {
+ CPU_ZERO(&cpumask);
+ CPU_SET(0, &cpumask);
+ }
+ if (caps & PMC_CAP_DOMWIDE) {
+ int domains;
+ size_t len;
+
+ CPU_ZERO(&cpumask);
+
+ len = sizeof(domains);
+ if (sysctlbyname("vm.ndomains", &domains, &len, NULL, 0) == -1)
+ err(EX_OSERR, "ERROR: Cannot get number of domains");
+
+ for (int i = 1; i < domains; i++) {
+ cpuset_t dmask;
+
+ CPU_ZERO(&dmask);
+ status = cpuset_getaffinity(CPU_LEVEL_WHICH, CPU_WHICH_DOMAIN, i,
+ sizeof(dmask), &dmask);
+ if (status < 0)
+ err(EX_OSERR, "ERROR: Cannot get domain mask");
+ CPU_SET(CPU_FFS(&dmask) - 1, &cpumask);
+ }
+ }
+ }
+ /*
+ * Allocate the PMC across all CPUs.
+ */
+ void allocate() {
+ int cpu;
+ int status;
+ pmc_id_t id;
+
+ for (cpu = 0; cpu < CPU_SETSIZE; cpu++) {
+ if (!CPU_ISSET(cpu, &cpumask))
+ continue;
+
+ status = pmc_allocate(event.c_str(), PMC_MODE_SS, PMC_F_CALLCHAIN,
+ cpu, &id, count);
+ if (status < 0)
+ err(EX_OSERR, "ERROR: Cannot allocate event '%s'",
+ event.c_str());
+
+ status = pmc_set(id, count);
+ if (status < 0)
+ err(EX_OSERR, "ERROR: Cannot set sampling count for event '%s'",
+ event.c_str());
+
+ ids.push_back(id);
+ }
+ }
+ /*
+ * Release all PMC instances (one per CPU).
+ */
+ void release() {
+ for (auto i : ids) {
+ if (pmc_release(i) < 0) {
+ perror("pmc_start");
+ }
+ }
+ }
+ /*
+ * Start all PMC instances (one per CPU).
+ */
+ void start() {
+ for (auto i : ids) {
+ if (pmc_start(i) < 0) {
+ perror("pmc_start");
+ }
+ }
+ }
+ /*
+ * Stop all PMC instances (one per CPU).
+ */
+ void stop() {
+ for (auto i : ids) {
+ if (pmc_stop(i) < 0) {
+ perror("pmc_start");
+ }
+ }
+ }
+private:
+ std::vector<pmc_id_t> ids;
+};
+
+static std::vector<pmc_config> pmcs = std::vector<pmc_config>();
+static struct option longopts[] = {
+ { "rate", required_argument, NULL, 'n' },
+ { "counter", required_argument, NULL, 'c' },
+ { "time", required_argument, NULL, 't' },
+ { "study", required_argument, NULL, 's' },
+ { NULL, 0, NULL, 0 }
+};
+static struct timespec start;
+static int timelimit = 0;
+
+static void
+usage(void)
+{
+ printf("Usage: pmc record [options] [output.pmc]\n\n");
+ printf("Record a study\n\n");
+ printf("Options:\n");
+ printf("\t-c Sampling counter\n");
+ printf("\t-r Sample rate (default: %d)\n", DEFAULT_RATE);
+ printf("\t-s Specify a study\n");
+ printf("\t-t Run the study for specified amount of time\n");
+ printf("\nStudies:\n");
+#if defined(__i386__) || defined(__amd64__)
+ printf("\tbranches Study branch misprediction (Requires AMD IBS)\n");
+ printf("\tc2c Study cache to cache communications (Requires AMD IBS)\n");
+#endif
+ printf("\tdefault Instruction sampling for general analysis\n");
+ printf("\tflamegraph Instruction sampling for general analysis\n");
+#if defined(__i386__) || defined(__amd64__)
+ printf("\tfrontend Study frontend stalls (Requires AMD IBS)\n");
+ printf("\tmemory Study memory operations (Requires AMD IBS)\n");
+#endif
+}
+
+int
+writelog(int logfd, const void *buf, size_t len)
+{
+ int status;
+ const char *cur = (const char *)buf;
+
+ while (len != 0) {
+ status = write(logfd, cur, len);
+ if (status < 0) {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ else
+ return status;
+ }
+ if (status == 0)
+ return status;
+
+ cur += status;
+ len -= status;
+ }
+
+ return 0;
+}
+
+/*
+ * Write the PMC header. Each of the header payloads have their own header
+ * containing the payloads size. Thus the pmc tools can just skip over regions
+ * that they do not know how to handle.
+ */
+int
+write_header(int logfd)
+{
+ int status;
+ pmchdr_header hdr;
+
+ hdr.magic = PMC_HEADER_MAGIC;
+ hdr.version = PMC_HEADER_VERSION;
+#if defined(__amd64__)
+ hdr.arch = PMC_ARCH_AMD64;
+#elif defined(__aarch64__)
+ hdr.arch = PMC_ARCH_ARM64;
+#elif defined(__powerpc64__)
+ hdr.arch = PMC_ARCH_PPC64;
+#elif defined(__riscv)
+ hdr.arch = PMC_ARCH_RISCV64;
+#elif defined(__arm__)
+ hdr.arch = PMC_ARCH_ARM;
+#else
+ hdr.arch = 0;
+#endif
+
+ status = writelog(logfd, &hdr, sizeof(hdr));
+ if (status < 0) {
+ perror("writelog");
+ }
+
+ return status;
+}
+
+/*
+ * Write out the sysinfo header.
+ */
+int
+write_sysinfo(int logfd)
+{
+ int status;
+ pmchdr_infohdr hdr;
+ pmchdr_sysinfo sys;
+ char val[64];
+ size_t valsz;
+
+ valsz = sizeof(val);
+ status = sysctlbyname("hw.model", &val, &valsz, NULL, 0);
+ if (status < 0) {
+ perror("sysctlbyname");
+ return status;
+ }
+ strlcpy(sys.cpumodel, val, sizeof(sys.cpumodel));
+
+ valsz = sizeof(val);
+ status = sysctlbyname("kern.osrelease", &val, &valsz, NULL, 0);
+ if (status < 0) {
+ perror("sysctlbyname");
+ return status;
+ }
+ strlcpy(sys.osrelease, val, sizeof(sys.osrelease));
+
+ valsz = sizeof(val);
+ status = sysctlbyname("kern.build_id", &val, &valsz, NULL, 0);
+ if (status < 0) {
+ perror("sysctlbyname");
+ return status;
+ }
+ strlcpy(sys.buildid, val, sizeof(sys.buildid));
+
+ hdr.type = INFOHDR_TYPE_SYSINFO;
+ hdr.length = sizeof(sys);
+ status = writelog(logfd, &hdr, sizeof(hdr));
+ if (status < 0) {
+ err(EX_IOERR, "writelog");
+ }
+
+ status = writelog(logfd, &sys, sizeof(sys));
+ if (status < 0) {
+ err(EX_IOERR, "writelog");
+ }
+
+ return status;
+};
+
+#if defined(__i386__) || defined(__amd64__)
+#define CPUID_ROOT_BASE 0x00000000
+#define CPUID_ROOT_VM 0x40000000
+#define CPUID_ROOT_EXT 0x80000000
+
+/*
+ * Write out the CPU Info block. After the standard header that declares the
+ * size of the payload, we write out all the CPUID root and leafs for each of
+ * the three major roots: base, VM, and extended artribute space. On all
+ * modern processors the root contains the maximum leaf as the first value
+ * allowing us to decode how many leafs there are.
+ */
+int
+write_cpuinfo(int logfd)
+{
+ int status;
+ u_int tmp[4];
+ uint32_t base_max, vm_max, ext_max;
+ uint32_t len;
+ pmchdr_infohdr *hdr;
+ uint32_t *buf, *off;
+
+ /*
+ * Find the length of the main, VM, and extended CPUID leafs
+ */
+ do_cpuid(CPUID_ROOT_BASE, tmp);
+ base_max = tmp[0];
+ len = base_max + 1;
+
+ do_cpuid(CPUID_ROOT_VM, tmp);
+ vm_max = tmp[0];
+ if (vm_max != 0)
+ len += vm_max - CPUID_ROOT_VM + 1;
+
+ do_cpuid(CPUID_ROOT_EXT, tmp);
+ ext_max = tmp[0];
+ if (ext_max != 0)
+ len += ext_max - CPUID_ROOT_EXT + 1;
+
+ // 4 Registers per Leaf x 4 Bytes per Register
+ len *= 4;
+
+ buf = (uint32_t *)new uint32_t[len + sizeof(pmchdr_infohdr) / 4];
+ hdr = (pmchdr_infohdr *)buf;
+ hdr->type = INFOHDR_TYPE_CPUID;
+ hdr->length = 4 * len;
+ off = (uint32_t *)(hdr + 1);
+
+ for (uint32_t i = 0; i <= base_max; i++) {
+ do_cpuid(i, off);
+ off += 4;
+ }
+
+ if (vm_max) {
+ for (uint32_t i = CPUID_ROOT_VM; i <= vm_max; i++) {
+ do_cpuid(i, off);
+ off += 4;
+ }
+ }
+ if (ext_max) {
+ for (uint32_t i = CPUID_ROOT_EXT; i <= ext_max; i++) {
+ do_cpuid(i, off);
+ off += 4;
+ }
+ }
+
+ status = writelog(logfd, buf, 4 * len + sizeof(*hdr));
+ if (status < 0) {
+ delete[] buf;
+ err(EX_IOERR, "writelog");
+ }
+
+ delete[] buf;
+
+ return 0;
+}
+#elif defined(__aarch64__)
+int
+write_cpuinfo(__unused int logfd)
+{
+ return 0;
+}
+#elif defined(__powerpc64__)
+int
+write_cpuinfo(__unused int logfd)
+{
+ return 0;
+}
+#else
+int
+write_cpuinfo(__unused int logfd)
+{
+ return 0;
+}
+#endif
+
+int
+write_footer(int logfd)
+{
+ int status;
+ pmchdr_infohdr hdr;
+
+ hdr.type = INFOHDR_TYPE_DONE;
+ hdr.length = 0;
+
+ status = writelog(logfd, &hdr, sizeof(hdr));
+ if (status < 0) {
+ perror("writelog");
+ }
+
+ return status;
+}
+
+#if defined(__i386__) || defined(__amd64__)
+int
+setup_study(const std::string &study, uint64_t rate, cpuset_t mask)
+{
+ u_int tmp[4];
+ alignas(4) char vendor[16];
+ std::string event;
+ uint32_t ext_max;
+ uint32_t ibs_features;
+ bool is_amd, is_intel;
+
+ do_cpuid(CPUID_ROOT_BASE, tmp);
+
+ /* Find the brand */
+ is_intel = false;
+ is_amd = false;
+
+ /* i386 complains without explicit alignment */
+ ((u_int *)&vendor)[0] = tmp[1];
+ ((u_int *)&vendor)[1] = tmp[3];
+ ((u_int *)&vendor)[2] = tmp[2];
+ vendor[12] = 0;
+ if (strncmp(vendor, INTEL_VENDOR_ID, 12) == 0)
+ is_intel = true;
+ if (strncmp(vendor, AMD_VENDOR_ID, 12) == 0)
+ is_amd = true;
+ if (strncmp(vendor, HYGON_VENDOR_ID, 12) == 0)
+ is_amd = true;
+
+ ibs_features = 0;
+ do_cpuid(CPUID_ROOT_EXT, tmp);
+ ext_max = tmp[0];
+ if (is_amd && ext_max >= CPUID_IBSID) {
+ do_cpuid(CPUID_IBSID, tmp);
+ ibs_features = tmp[0];
+ }
+
+ if (study == "default" || study == "flamegraph") {
+ pmcs.push_back(pmc_config("unhalted-cycles", rate, mask));
+ return 0;
+ }
+
+ if (is_intel) {
+ err(EX_SOFTWARE, "ERROR: Study is unsupported on Intel");
+ }
+
+ if (study == "frontend") {
+ event = "ibs-fetch,randomize";
+ } else if (study == "branches" || study == "memory") {
+ event = "ibs-op";
+ if (ibs_features & CPUID_IBSID_OPCNT)
+ event += ",opcount";
+ } else if (study == "c2c") {
+ event = "ibs-op";
+ if (ibs_features & CPUID_IBSID_IBSLOADLATENCYFILT)
+ event += ",l3miss";
+ if (ibs_features & CPUID_IBSID_OPCNT)
+ event += ",opcount";
+ } else {
+ err(EX_USAGE, "ERROR: Study '%s' unknown", study.c_str());
+ }
+
+ pmcs.push_back(pmc_config(event, rate, mask));
+
+ return 0;
+}
+#else
+int
+setup_study(const std::string &study, uint64_t rate, cpuset_t mask)
+{
+ if (study == "default" || study == "flamegraph") {
+ pmcs.push_back(pmc_config("unhalted-cycles", rate, mask));
+ return 0;
+ }
+
+ err(EX_SOFTWARE, "ERROR: Study is unsupported on this architecture");
+}
+#endif
+
+/*
+ * Compute the current runtime and print it to the screen, if it exceeds
+ * timelimit return 1 otherwise 0.
+ */
+int
+update_status(int logfd)
+{
+ int status;
+ long sec, nsec;
+ struct timespec end;
+ struct stat sb;
+ std::string filesz;
+
+ status = clock_gettime(CLOCK_MONOTONIC, &end);
+ if (status < 0) {
+ err(EX_OSERR, "Could not read current time");
+ }
+
+ sec = end.tv_sec - start.tv_sec;
+ nsec = end.tv_nsec - start.tv_nsec;
+ if (nsec < 0) {
+ sec--;
+ nsec += 1000000000L;
+ }
+
+ // Do this another way for sockets
+ status = fstat(logfd, &sb);
+ if (status < 0) {
+ err(EX_OSERR, "Failed to fstat log file");
+ }
+
+ filesz = format_binprefix(sb.st_size);
+
+ /*
+ * Use %-10s to ensure we print spaces after in case the size string
+ * shrinks.
+ */
+ printf("Elapsed: %ld.%03ld seconds, Log file size: %-10s\r",
+ sec, nsec / 1000000,
+ filesz.c_str());
+ fflush(stdout);
+
+ if (timelimit != 0 && sec >= timelimit) {
+ printf("\nElapsed time completed\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+int
+cmd_pmc_record(int argc, char **argv)
+{
+ struct kevent kev;
+ cpuset_t mask;
+ uint64_t rate = DEFAULT_RATE;
+ const char *logfile = "default.log";
+ int status;
+ int option, logfd, kq;
+
+ CPU_ZERO(&mask);
+ if (cpuset_getaffinity(CPU_LEVEL_ROOT, CPU_WHICH_PID, -1,
+ sizeof(mask), &mask) == -1)
+ err(EX_OSERR, "ERROR: Cannot determine the available CPUs");
+
+ if (pmc_init() < 0)
+ err(EX_SOFTWARE, "ERROR: Failed to initialize libpmc");
+
+ while ((option = getopt_long(argc, argv, "c:r:s:t:", longopts, NULL)) != -1) {
+ switch (option) {
+ case 'c':
+ pmcs.push_back(pmc_config(optarg, rate, mask));
+ break;
+ case 'r':
+ rate = strtoull(optarg, NULL, 0);
+ break;
+ case 's':
+ setup_study(optarg, rate, mask);
+ break;
+ case 't':
+ timelimit = atoi(optarg);
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc != 0 && argc != 1) {
+ usage();
+ exit(EX_USAGE);
+ }
+ if (argc == 1)
+ logfile = argv[0];
+
+ if (pmcs.size() == 0) {
+ errx(EX_NOINPUT, "ERROR: Please select one or more counters or performance studies.");
+ }
+
+ if ((logfd = open(logfile, O_CREAT|O_EXCL|O_WRONLY,
+ S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)) < 0) {
+ errx(EX_OSERR, "ERROR: Cannot open \"%s\" for writing: %s.", logfile,
+ strerror(errno));
+ }
+
+ setup_screen();
+
+ for (auto &p : pmcs) {
+ p.getcaps();
+ }
+
+ write_header(logfd);
+ write_sysinfo(logfd);
+ write_cpuinfo(logfd);
+ write_footer(logfd);
+
+ // Record pmclog
+ kq = kqueue();
+ if (kq < 0)
+ err(EX_OSERR, "ERROR: kqueue creation failed");
+
+ EV_SET(&kev, fileno(stdin), EVFILT_READ, EV_ADD, 0, 0, NULL);
+ if (kevent(kq, &kev, 1, NULL, 0, NULL) < 0)
+ err(EX_OSERR, "ERROR: kevent failed to register stdin");
+
+ EV_SET(&kev, SIGINT, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);
+ if (kevent(kq, &kev, 1, NULL, 0, NULL) < 0)
+ err(EX_OSERR, "ERROR: kevent failed to register SIGINT");
+ EV_SET(&kev, SIGIO, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);
+ if (kevent(kq, &kev, 1, NULL, 0, NULL) < 0)
+ err(EX_OSERR, "ERROR: kevent failed to register SIGIO");
+
+ EV_SET(&kev, 0, EVFILT_TIMER, EV_ADD, 0, 100, NULL);
+ if (kevent(kq, &kev, 1, NULL, 0, NULL) < 0)
+ err(EX_OSERR, "ERROR: Cannot register kevent for timer");
+
+ pmc_configure_logfile(logfd);
+
+ for (auto &p : pmcs) {
+ p.allocate();
+ }
+
+ for (auto &p : pmcs) {
+ p.start();
+ }
+
+ if (clock_gettime(CLOCK_MONOTONIC, &start) < 0)
+ err(EX_OSERR, "ERROR: Could not get the current time");
+
+ printf("Recording performance trace press Ctrl-C to stop\n");
+
+ /*
+ * loop till either the target process (if any) exits, or we
+ * are killed by a SIGINT or we reached the time duration.
+ */
+ while (1) {
+ status = kevent(kq, NULL, 0, &kev, 1, NULL);
+ if (status <= 0) {
+ if (errno != EINTR)
+ err(EX_OSERR, "ERROR: kevent failed");
+ else
+ continue;
+ }
+
+ if (kev.flags & EV_ERROR)
+ errc(EX_OSERR, kev.data, "ERROR: kevent failed");
+
+ switch (kev.filter) {
+ case EVFILT_READ: /* log file data is present */
+ if (kev.ident == (unsigned)fileno(stdin)) {
+ // Check for exit key
+ }
+ break;
+ case EVFILT_SIGNAL:
+ if (kev.ident == SIGIO) {
+ fprintf(stderr, "ERROR: IO error");
+ status = EX_OSERR;
+ goto done;
+ } else if (kev.ident == SIGINT) {
+ status = EX_OK;
+ goto done;
+ } else {
+ err(EX_OSERR, "Unknown signal recieved");
+ }
+ break;
+ case EVFILT_TIMER:
+ if (update_status(logfd) == 1)
+ goto done;
+ break;
+ }
+ }
+
+done:
+ for (auto &p : pmcs) {
+ p.stop();
+ p.release();
+ }
+ close(logfd);
+
+ return (status);
+}
+
diff --git a/usr.sbin/pmc/headers.hh b/usr.sbin/pmc/headers.hh
--- a/usr.sbin/pmc/headers.hh
+++ b/usr.sbin/pmc/headers.hh
@@ -39,6 +39,7 @@
#define PMC_ARCH_ARM64 0x02
#define PMC_ARCH_PPC64 0x03
#define PMC_ARCH_RISCV64 0x04
+#define PMC_ARCH_ARM 0x05
struct pmchdr_header
{
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Sep 5, 9:05 PM (6 h, 48 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
38015765
Default Alt Text
D57777.diff (18 KB)
Attached To
Mode
D57777: pmc: pmc record command
Attached
Detach File
Event Timeline
Log In to Comment