From nobody Tue Jul 28 19:29:24 2026 X-Original-To: dev-commits-src-all@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4h8ls0606nz6n9h5 for ; Tue, 28 Jul 2026 19:29:24 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "YR1" (not verified)) by mx1.freebsd.org (Postfix) with ESMTPS id 4h8ls03n9Fz43xV for ; Tue, 28 Jul 2026 19:29:24 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1785266964; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=DvCDB1OG5H9Ig7DP2VR9htPjFAiD7EzVDjfD5OXGTrw=; b=ixIMP5EheKMGbA+sItss0jOWeqZF+TWQhzW7JzyfigKhUxwW1WAFPlJU7/vG9JseBK8q1M PqxH4arP+fzygaj5otQVQdMomgwd0hTixbX7F5xEuGB0J0L074DMrtHskVFcPg9JqDhssc i1I/4XPq1eDFTvOeK3DvoxCuYBM+AplBClg9qgeHTwGyKQ3y0K086o7Ci8EmqfJwMe7VXz eI8nzld3IYdrLld//1+Z39HCv81FA4PKm1G/5/A60tkf0psrYqqzpPg1e9kpPeWjp+tLEq XhY0m2vU1qwFSFrSMDnH+fcP6f1WFrzXyTfHQuhMmqvuONlrZNtwxXc1bkCO6Q== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1785266964; a=rsa-sha256; cv=none; b=RHR125Pxg1zwYi3FN7OcnRwMqCczF4EeWJRc4JJy+VAGzMN9vPaipRZSaM66e+puV7rw2C mFnc87FjCgbSV4Tph/mP0V606UfC/NE9z8Lo1GMu6QhlSK+T+ats1MlRyaq5/UhlWrTQxm c4lRFGkERLKYDf65Axr2YOa8cAlBSJaPRVCPnnl0IPvNvwN3xvyaO2+tSxFSseyYj75LxV tCHx8rvtrzFeMmxPOHgFXdnajPnOAEt43WKyuknscVSCuWSI29vaX3wI8C8Pw3xrNlE2cY tPVQBXdAxgLUG/xyv2gf9Vkomg0Tf/5AHyL1RI3gHQc5y2HQ+yy7MyGTNbivGA== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1785266964; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=DvCDB1OG5H9Ig7DP2VR9htPjFAiD7EzVDjfD5OXGTrw=; b=cTz5+SPw6ogvOa5O+cQM7KaxEcBsFIeGIBUFNWu6TkmHfgaMzrMf5L2ty0ZbFBMG2Mi5LE Yl8EDh6rm1I9g2veK3d2muAS5r3i73ywllDfLPD7eJ/lIpukm30LeyhsnISTeP7SOP74u3 nluoZgepBIK3wmnE/Qm/j83E+3R9DT7eB6dFyCUdcbQxmRz6sWqmDcgn4HKvY3XBZZzG8d QQWpEWX1p+sOmkshLZ7zc2izJaZBVLEMGnpRPSuBtWc493mfoaACoZ1n/N+D6ZxZsqRlhc sKqZKVtNGtQ2/O8wg5Wbj32UYljD++R9+4KzmQ9WiG8eqiiitEySx0OsOdj/Ng== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4h8ls02JPMzsxx for ; Tue, 28 Jul 2026 19:29:24 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 4359c by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Tue, 28 Jul 2026 19:29:24 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org Cc: Ali Mashtizadeh From: Warner Losh Subject: git: a79a051e7d16 - main - pmc: pmc record command List-Id: Commit messages for all branches of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-all List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-all@freebsd.org Sender: owner-dev-commits-src-all@FreeBSD.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: imp X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: a79a051e7d16684b5bce7792dbfb4ad81f350b09 Auto-Submitted: auto-generated Date: Tue, 28 Jul 2026 19:29:24 +0000 Message-Id: <6a690314.4359c.66bf7d70@gitrepo.freebsd.org> The branch main has been updated by imp: URL: https://cgit.FreeBSD.org/src/commit/?id=a79a051e7d16684b5bce7792dbfb4ad81f350b09 commit a79a051e7d16684b5bce7792dbfb4ad81f350b09 Author: Ali Mashtizadeh AuthorDate: 2026-07-28 17:46:37 +0000 Commit: Warner Losh CommitDate: 2026-07-28 19:27:45 +0000 pmc: pmc record command The record command is designed around the idea of predefined studies. While you can still select individual counters, the predefined studies are meant to enable the best hardware options for a given generation. It implements all of the base studies that I have built so far. Sponsored by: Netflix Reviewed by: adrian Differential Revision: https://reviews.freebsd.org/D57777 --- usr.sbin/pmc/cmd_pmc_record.cc | 750 +++++++++++++++++++++++++++++++++++++++++ usr.sbin/pmc/headers.hh | 1 + 2 files changed, 751 insertions(+) diff --git a/usr.sbin/pmc/cmd_pmc_record.cc b/usr.sbin/pmc/cmd_pmc_record.cc new file mode 100644 index 000000000000..a8373b330705 --- /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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include "cmd_pmc.h" + +#include +#include +#include +#include + +#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 ids; +}; + +static std::vector pmcs = std::vector(); +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 index 29f76cd7cc2c..6bdabc4fbe6c 100644 --- 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 {