power-calibrate-0.01.21/0000775000175000017500000000000012645507523013410 5ustar kingkingpower-calibrate-0.01.21/perf.c0000664000175000017500000001121412645507517014512 0ustar kingking/* * Copyright (C) 2014-2016 Canonical, Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * This code is a complete clean re-write of the stress tool by * Colin Ian King and attempts to be * backwardly compatible with the stress tool by Amos Waterland * but has more stress tests and more * functionality. * */ #define _GNU_SOURCE #include "perf.h" #if defined(PERF_ENABLED) #include #include #include #include #include #include #include #include #include #include #include #include #include #define THOUSAND (1000.0) #define MILLION (THOUSAND * THOUSAND) #define BILLION (THOUSAND * MILLION) #define TRILLION (THOUSAND * BILLION) #define QUADRILLION (THOUSAND * TRILLION) #define QUINTILLION (THOUSAND * QUADRILLION) #define PERF_INVALID (~0ULL) #define PERF_INFO(type, config) \ { PERF_ ## config, PERF_TYPE_ ## type, PERF_COUNT_ ## config } /* perf counters to be read */ static const perf_info_t perf_info[PERF_MAX] = { PERF_INFO(HARDWARE, HW_CPU_CYCLES), PERF_INFO(HARDWARE, HW_INSTRUCTIONS), }; int perf_start(perf_t *p, const pid_t pid) { int i; memset(p, 0, sizeof(perf_t)); p->perf_opened = 0; for (i = 0; i < PERF_MAX; i++) { p->perf_stat[i].fd = -1; p->perf_stat[i].counter = 0; } if (pid <= 0) return 0; for (i = 0; i < PERF_MAX; i++) { struct perf_event_attr attr; memset(&attr, 0, sizeof(attr)); attr.type = perf_info[i].type; attr.config = perf_info[i].config; attr.disabled = 1; attr.inherit = 1; attr.read_format = PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING; attr.size = sizeof(attr); p->perf_stat[i].fd = syscall(__NR_perf_event_open, &attr, pid, -1, -1, 0); if (p->perf_stat[i].fd > -1) p->perf_opened++; else { fprintf(stderr, "perf fail: %d %s\n", errno, strerror(errno)); } } if (!p->perf_opened) { munmap(p, sizeof(perf_t)); return -1; } for (i = 0; i < PERF_MAX; i++) { int fd = p->perf_stat[i].fd; if (fd > -1) { if (ioctl(fd, PERF_EVENT_IOC_RESET, PERF_IOC_FLAG_GROUP) < 0) { (void)close(fd); p->perf_stat[i].fd = -1; continue; } if (ioctl(fd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP) < 0) { (void)close(fd); p->perf_stat[i].fd = -1; } } } return 0; } /* * perf_stop() * stop and read counters */ int perf_stop(perf_t *p) { /* perf data */ typedef struct { uint64_t counter; /* perf counter */ uint64_t time_enabled; /* perf time enabled */ uint64_t time_running; /* perf time running */ } perf_data_t; size_t i = 0; perf_data_t data; ssize_t ret; int rc = -1; double scale; if (!p) return -1; if (!p->perf_opened) goto out_ok; for (i = 0; i < PERF_MAX; i++) { int fd = p->perf_stat[i].fd; if (fd < 0) { p->perf_stat[i].counter = PERF_INVALID; continue; } if (ioctl(fd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP) < 0) { p->perf_stat[i].counter = 0; } else { memset(&data, 0, sizeof(data)); ret = read(fd, &data, sizeof(data)); if (ret != sizeof(data)) p->perf_stat[i].counter = PERF_INVALID; else { /* Ensure we don't get division by zero */ if (data.time_running == 0) { scale = (data.time_enabled == 0) ? 1.0 : 0.0; } else { scale = (double)data.time_enabled / (double)data.time_running; } p->perf_stat[i].counter = (uint64_t)((double)data.counter * scale); } } (void)close(fd); p->perf_stat[i].fd = -1; } out_ok: rc = 0; for (; i < PERF_MAX; i++) p->perf_stat[i].counter = PERF_INVALID; return rc; } /* * perf_counter * fetch counter and index via perf ID */ void perf_counter( const perf_t *p, const int id, double *counter) { int i; for (i = 0; i < PERF_MAX; i++) { if (perf_info[i].id == id) { if (p->perf_stat[i].counter == PERF_INVALID) { *counter = 0; } else { *counter = (double)p->perf_stat[i].counter; } } } } #endif power-calibrate-0.01.21/Makefile0000664000175000017500000000347312645507517015062 0ustar kingking# # Copyright (C) 2014-2016 Canonical, Ltd. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # VERSION=0.01.21 CFLAGS += -Wall -Wextra -DVERSION='"$(VERSION)"' -O2 BINDIR=/usr/sbin MANDIR=/usr/share/man/man8 SRC = power-calibrate.c perf.c OBJS = $(SRC:.c=.o) power-calibrate: $(OBJS) Makefile perf.h $(CC) $(CPPFLAGS) $(CFLAGS) $(OBJS) -lm -o $@ $(LDFLAGS) power-calibrate.o: power-calibrate.c Makefile perf.h $(CC) $(CPPFLAGS) $(CFLAGS) -c power-calibrate.c -o $@ perf.o: perf.c Makefile perf.h $(CC) $(CPPFLAGS) $(CFLAGS) -c perf.c -o $@ power-calibrate.8.gz: power-calibrate.8 gzip -c $< > $@ dist: rm -rf power-calibrate-$(VERSION) mkdir power-calibrate-$(VERSION) cp -rp Makefile scripts power-calibrate.c power-calibrate.8 perf.c perf.h COPYING power-calibrate-$(VERSION) tar -zcf power-calibrate-$(VERSION).tar.gz power-calibrate-$(VERSION) rm -rf power-calibrate-$(VERSION) clean: rm -f power-calibrate $(OBJS) power-calibrate.8.gz rm -f power-calibrate-$(VERSION).tar.gz install: power-calibrate power-calibrate.8.gz mkdir -p ${DESTDIR}${BINDIR} cp power-calibrate ${DESTDIR}${BINDIR} mkdir -p ${DESTDIR}${MANDIR} cp power-calibrate.8.gz ${DESTDIR}${MANDIR} power-calibrate-0.01.21/scripts/0000775000175000017500000000000012645507517015102 5ustar kingkingpower-calibrate-0.01.21/scripts/power-calibrate-json-parse.py0000775000175000017500000001040612645507517022617 0ustar kingking#!/usr/bin/python # # Copyright (C) 2014 Canonical # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # This script takes json output from power-calibrate and health-check # to calculate an esimate of power consumption # # import sys, os, json def r2tostr(r2): if r2 < 0.4: return "very poor" if r2 < 0.75: return "poor" if r2 < 0.80: return "fair" if r2 < 0.90: return "good" if r2 < 0.95: return "very good" if r2 < 1.0: return "excellent" return "perfect" if len(sys.argv) != 3: sys.stderr.write("Usage: " + sys.argv[0] + " power-calibrate.json health-check.json\n") os._exit(1) try: file = sys.argv[1] f = open(file, 'r') data = json.load(f) f.close() except: sys.stderr.write("Failed to open and parse JSON file " + file + "\n"); os._exit(1) if not "power-calibrate" in data: sys.stderr.write("Failed to find power calibration data in file " + file + "\n"); os._exit(1) pc = data["power-calibrate"] if "cpu-load" in pc: cpu_1pc = pc["cpu-load"]["one-percent-cpu-load"] cpu_r2 = pc["cpu-load"]["r-squared"] else: cpu_1pc = 0 cpu_r2 = 0 if "context-switches" in pc: ctxt_sw = pc["context-switches"]["one-context-switch"] ctxt_r2 = pc["context-switches"]["r-squared"] else: ctxt_sw = 0 ctxt_r2 = 0 if not "test-run" in pc: sys.stderr.write("Failed to find power calibration machine information in file " + file + "\n"); os._exit(1) test_date = pc["test-run"]["date"] test_time = pc["test-run"]["time"] test_sys = pc["test-run"]["sysname"] test_node = pc["test-run"]["nodename"] test_rel = pc["test-run"]["release"] test_arch = pc["test-run"]["machine"] try: file = sys.argv[2] f = open(file, 'r') data = json.load(f) f.close() except: sys.stderr.write("Failed to open and parse JSON file " + file + "\n"); os._exit(1) if not "health-check" in data: sys.stderr.write("Failed to find health-check data in file " + file + "\n"); os._exit(1) hc = data["health-check"] if not "cpu-load" in hc: sys.stderr.write("Failed to find cpu-load stats in health-check file " + file + "\n"); os._exit(1) total_cpu = hc["cpu-load"]["cpu-load-total"]["total-cpu-percent"] if not "context-switches" in hc: sys.stderr.write("Failed to find context-switch stats in health-check file " + file + "\n"); os._exit(1) ctxt_switches = hc["context-switches"]["context-switches-total"]["context-switch-total-rate"] cpu_total_watts = cpu_1pc * total_cpu ctxt_total_watts = ctxt_sw * ctxt_switches total_watts = cpu_total_watts + ctxt_total_watts if total_watts == 0.0: print "CPU : %8.3f Watts (%.2f%% CPU)" % \ (cpu_total_watts, total_cpu) print "Context Switches : %8.3f Watts (%.2f switches)" % \ (ctxt_sw * ctxt_switches, ctxt_switches) print "Total : %8.3f Watts" % total_watts else: rel = ((cpu_total_watts * cpu_r2) + (ctxt_total_watts * ctxt_r2)) / total_watts cpu_pc = cpu_total_watts / total_watts * 100 ctxt_pc = ctxt_total_watts / total_watts * 100 print "CPU : %8.3f Watts (%.2f%% CPU, %.2f%% of total power)" % \ (cpu_total_watts, total_cpu, cpu_pc) print "Context Switches : %8.3f Watts (%.2f switches, %.2f%% of total power)" % \ (ctxt_sw * ctxt_switches, ctxt_switches, ctxt_pc) print "Total : %8.3f Watts (%s estimate)" % (total_watts, r2tostr(rel)) print " " print "Note, estimate is based on calibration data:" print " 1%% CPU : %10.5f Watts, R^2 %f (%s)" % (cpu_1pc, cpu_r2, r2tostr(cpu_r2)) print " 1 Context Switch : %10.5f Watts, R^2 %f (%s)" % (ctxt_sw, ctxt_r2, r2tostr(ctxt_r2)) print " " print "Tested on " + test_date + " at " + test_time + " on " + test_node + " (" + \ test_sys + " " + test_rel + " " + test_arch + ")" power-calibrate-0.01.21/perf.h0000664000175000017500000000402612645507517014522 0ustar kingking/* * Copyright (C) 2013-2015 Canonical, Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * This code is a complete clean re-write of the stress tool by * Colin Ian King and attempts to be * backwardly compatible with the stress tool by Amos Waterland * but has more stress tests and more * functionality. * */ #ifndef __PERF_POWER_CALIBRATE_H__ #define __PERF_POWER_CALIBRATE_H__ #define _GNU_SOURCE #include #include #include #include #if defined(__linux__) && defined(__NR_perf_event_open) #define PERF_ENABLED #endif enum { PERF_HW_CPU_CYCLES = 0, PERF_HW_INSTRUCTIONS, PERF_MAX }; /* per perf counter info */ typedef struct { uint64_t counter; /* perf counter */ int fd; /* perf per counter fd */ } perf_stat_t; typedef struct { perf_stat_t perf_stat[PERF_MAX]; /* perf counters */ int perf_opened; /* count of opened counters */ } perf_t; /* used for table of perf events to gather */ typedef struct { int id; /* stress-ng perf ID */ unsigned long type; /* perf types */ unsigned long config; /* perf type specific config */ } perf_info_t; extern int perf_start(perf_t *p, const pid_t pid); extern int perf_stop(perf_t *p); extern void perf_counter(const perf_t *p, const int id, double *counter); #endif power-calibrate-0.01.21/power-calibrate.80000664000175000017500000002021312645507517016562 0ustar kingking.\" Hey, EMACS: -*- nroff -*- .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) .TH POWER-CALIBRATE 8 "18 May, 2015" .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: .\" .nh disable hyphenation .\" .hy enable hyphenation .\" .ad l left justify .\" .ad b justify to both left and right margins .\" .nf disable filling .\" .fi enable filling .\" .br insert line break .\" .sp insert n+1 empty lines .\" for manpage-specific macros, see man(7) .nr SZ ((\n[.l] - \n[.i]) / 1n - 17) .SH NAME power-calibrate \- a tool to calibrate power consumption. .br .SH SYNOPSIS .B power-calibrate .RI [ options ] .br .SH DESCRIPTION Power-calibrate calibrates the power consumption of a mobile device that has a battery power source or a recent Intel processor using the RAPL (Running Average Power Limit) interface. It will attempt to calculate the power usage of 1% of CPU utiltisation. If perf is available, power-calibrate will also attempt to estimate the power consumption of 1 CPU cycle and 1 CPU instruction. .SH OPTIONS power-calibrate options are as follow: .TP .B \-d specify the delay in seconds from starting a new test configuration and before starting the sampling. The default is 20 seconds, which is normally enough time to allow the battery statistics to settle down during the current test. .TP .B \-e Calibrate for each CPU run, rather than for all the CPUs. By default, power-calibrate will run tests on all the CPUs and produce one set of results for all the runs. While this provides a good average result, it may be misleading for processors where power utilisation or performance is not uniform across the processors, for example, with hyperthreading. .TP .B \-h show help. .TP .B \-n specify a list CPU numbers to run on. By default, the number of CPUs is determined automatically, but this option allows one to override this by listing the CPUs (range 0..number of CPUs-1) using a comma separated list. .TP .B \-o file output results into a YAML formatted file. .TP .B \-p show progress. This will display the progress in terms of % completion of a test run and also % completion of the total run. The progress is based on work done rather than on an accurate time estimate. .TP .B \-r secs Set run duration. Normally, the default of 120 seconds is suitable for most laptop devices where discharge rates from the battery can be fairly accurately determined over this duration. Setting this to a shorter duration will complete the calibration tests in less time but may be less accurate. .TP .B \-R read power statistics from the RAPL (Running Average Power Limit) domains. This is supported by recent Linux kernels and Sandybridge and later Intel processors. This option just measures the per processor package power utilization so the readings do not cover the entire machine. .TP .B \-s samples specify the number of samples for the CPU (\-c) test. The CPU test will measure 0% to 100% CPU loading across 1..number of CPUs. The number of test rounds to be done per CPU is controlled by the samples value. If samples is low then less data points are gathered for the calculation of the power utilisation and this leads to less accurate results. .SH OUTPUT During the testing/data sampling phase, power-calibrate will show the following information: .TS expand; lB2 lBw(\n[SZ]n) l l. Column Description CPU load T{ The loading of each CPU in terms of % utilisation for each CPU and the number of CPUs being loaded. For example, 80% x 2 means 2 CPUs each at 80% utilisation and for an 8 CPU machine this is effectivly 20% total utilisation since 6 other CPUs are idle. T} User T{ % load of the CPU consumed by user space programs. T} Sys T{ % load of the CPU consumed by the kernel. T} Idle T{ % idle time of the CPU. T} Ctxt/s T{ Context switches per second. T} IRQ/s T{ Interrupts per second. T} Ops/s T{ Bogo operations per second. This is the number of compute operations per second (computation of a random number using a 32 bit multiple-with-carry). T} Watts T{ Power used in Watts. T} .TE .br The following is an example of the output for the \-c option: .br .nf Power (Watts) = (% CPU load * 0.424631) + 10.131925 Each 1% CPU load is about 424.63 mW (about 37.87 mA @ 11.21 V) Coefficient of determination R^2 = 0.999015 (very strong) Power (Watts) = (bogo op * 4.267444e-08) + 10.666399 1 bogo ops is about 42.67 nW (about 3.81 nA @ 11.21 V) Coefficient of determination R^2 = 0.999460 (very strong) .fi .br Power-calibrate will determine a suitable way of calculating the power consumed based on the CPU load and an estimation of the amount of power consumed for each 1% of CPU loading. .br The Coefficient of determination shows how close the results are to the measured data; a perfect match results in R^2 = 1.0 and this will drop towards zero as the accuracy drops. Power-calibrate will annotate R^2 to provide some hint on how strong the relationship between the raw data and the linear estimation of power consumption. .br An estimate of the power used per bogo-op is estimated, that is, the power consumed to perform 1 computation of a random number using a 64 bit multiple-with-carry. This can be used a very na\[:i]ve compute benchmarking metric when comparing different processors or power configurations. .SH EXAMPLES .LP power-calibrate -d 60 -s 5 -n 0,1 -r 200 -p .RS Measure Watts per 1% CPU (and bogo compute ops per Watt) with a 60 second warm-up delay per test round, 5 tests in the CPU load level (0%, 25%, 50%, 75%, 100%) on CPUs 0 and 1 with a 200 second run time per test round while showing progress. .RE .LP power-calibrate -n 0,2,1,3 .RS Measure Watts per 1% CPU (and bogo compute oper per Watt) on 4 CPUs. 4 rounds of tests will be run for each CPU load interval, measuring: .RE .TS center; l l. 1 CPU: CPU 0 2 CPUs: CPUs 0,2 3 CPUs: CPUs 0,2,1 4 CPUs: CPUs 0,2,1,3 .TE .LP power-calibrate -R -r 10 -d 5 -s 21 -n 0 -p .RS Measure per-CPU package Watts consumed using the Intel RAPL interface. This example has a 5 second warm-up delay per test round, 21 tests in the CPU load level (0%, 5%, 10%, .., 95%, 100%) on CPUs 0 with a 10 second run time per test round while showing progress. .SH BUGS Power-calibrate attempts to find a linear relationship between power consumed and the CPU loading, bogo operations per second and the context switching. This is not necessarily the case for all processors. If the R^2 coefficient of determination is not close to 1.0 then this indicates there may not be a linear relationship. .SH NOTES Power-calibrate relies on the battery interface to provide timely stats on battery power consumption and this will vary from device to device. Software or firmware may adjust the battery readings from gas-gauges and even re-calibrate the values during battery drain hence skewing the final results from power-calibrate. Batteries also show a non-linear discharge characteristic, so running power-calibrate on low battery charge is not advised. .br Processors with hyperthreading or non-uniform processing performance will skew the bogo-ops calculations since different logical CPUs will vary in processing throughput. .br In general, the more samples gathered, the more accurate the final results will be, however, the calibration will take longer to run. With more samples, the tendency to get a few random outliers in the samples may increase, and that may affect the final R^2 coefficient of determination. .br Power-calibrate is a power estimation tool, so results will vary between different runs. It is not meant to be an accurate substitute for power measurements using a high precision multimeter. .SH SEE ALSO .BR powerstat (8), .BR eventstat (8) .SH AUTHOR power-calibrate was written by Colin King .PP This manual page was written by Colin King , for the Ubuntu project (but may be used by others). .SH COPYRIGHT Copyright \(co 2014-2016 Canonical Ltd. .br This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. power-calibrate-0.01.21/COPYING0000664000175000017500000004325412645507517014456 0ustar kingking GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. power-calibrate-0.01.21/power-calibrate.c0000664000175000017500000014276612645507517016657 0ustar kingking/* * Copyright (C) 2014-2016 Canonical * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Author: Colin Ian King * * Much of this code was derived from other GPL-2+ projects by the author * such as powerstat, fnotifystat and stress-ng. */ #define _GNU_SOURCE #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 "perf.h" #define MIN_RUN_DURATION (10) /* Minimum run duration */ #define DEFAULT_RUN_DURATION (120) /* Default duration */ #define SAMPLE_DELAY (1) /* Delay between samples in seconds */ #define START_DELAY_BATTERY (20) /* Delay to wait before sampling */ #define START_DELAY_RAPL (0) /* Delay to wait before sampling */ #define RATE_ZERO_LIMIT (0.001) /* Less than this is a 0 power rate */ #define MAX_CPU_LOAD (100) /* Maximum CPU load */ #define DEFAULT_TIMEOUT (10) /* Zero load sleep duration */ #define CPU_ANY (-1) #define DETECT_DISCHARGING (1) #define OPT_DELAY (0x00000001) #define OPT_PROGRESS (0x00000002) #define OPT_CALIBRATE_EACH_CPU (0x00000004) #define OPT_RAPL (0x00000008) #define MAX_POWER_DOMAINS (16) #define MAX_POWER_VALUES (MAX_POWER_DOMAINS + 1) #define CPU_USER (0) #define CPU_NICE (1) #define CPU_SYS (2) #define CPU_IDLE (3) #define CPU_IOWAIT (4) #define CPU_IRQ (5) #define CPU_SOFTIRQ (6) #define CPU_INTR (7) #define CPU_CTXT (8) #define CPU_PROCS_RUN (9) #define CPU_PROCS_BLK (10) #define VOLTAGE_NOW (11) #define CURRENT_NOW (12) #define PROC_FORK (13) #define PROC_EXEC (14) #define PROC_EXIT (15) #define BOGO_OPS (16) #define CPU_CYCLES (17) #define CPU_INSTRUCTIONS (18) #define POWER_NOW (19) #define POWER_DOMAIN_0 (20) #define MAX_VALUES (POWER_DOMAIN_0 + MAX_POWER_VALUES) #define MWC_SEED_Z (362436069UL) #define MWC_SEED_W (521288629UL) #define SYS_CLASS_POWER_SUPPLY "/sys/class/power_supply" #define PROC_ACPI_BATTERY "/proc/acpi/battery" #define SYS_FIELD_VOLTAGE_NOW "POWER_SUPPLY_VOLTAGE_NOW=" #define SYS_FIELD_POWER_NOW "POWER_SUPPLY_POWER_NOW=" #define SYS_FIELD_ENERGY_NOW "POWER_SUPPLY_ENERGY_NOW=" #define SYS_FIELD_CURRENT_NOW "POWER_SUPPLY_CURRENT_NOW=" #define SYS_FIELD_CHARGE_NOW "POWER_SUPPLY_CHARGE_NOW=" #define SYS_FIELD_STATUS_DISCHARGING "POWER_SUPPLY_STATUS=Discharging" #if defined(__x86_64__) || defined(__x86_64) || \ defined(__i386__) || defined(__i386) #define RAPL_X86 #endif /* Statistics entry */ typedef struct { double value[MAX_VALUES]; bool inaccurate[MAX_VALUES]; } stats_t; /* x,y data pair, for trend analysis */ typedef struct { double x; double y; double voltage; int cpu_id; int cpus_used; } value_t; /* Bogo operation stats */ typedef struct { double ops; uint8_t padding[64]; /* Make ops not align on cache boundary */ } bogo_ops_t; /* CPUs to use from -n option */ typedef struct cpu_info { struct cpu_info *next; int cpu_id; /* CPU number, 0 = first CPU */ pid_t pid; #if defined(PERF_ENABLED) perf_t perf; #endif } cpu_info_t; /* CPU list */ typedef struct cpu_list { cpu_info_t *head; cpu_info_t *tail; uint32_t count; } cpu_list_t; /* RAPL domain info */ typedef struct rapl_info { struct rapl_info *next; char *name; char *domain_name; double max_energy_uj; double last_energy_uj; double t_last; bool is_package; } rapl_info_t; typedef void (*func)( uint64_t param, const int instance, bogo_ops_t *bogo_ops); static volatile bool stop_flag; /* sighandler stop flag */ static int32_t opt_flags; /* command options */ static char *app_name = "power-calibrate"; /* application name */ /* * Attempt to catch a range of signals so * we can clean */ static const int signals[] = { /* POSIX.1-1990 */ #ifdef SIGHUP SIGHUP, #endif #ifdef SIGINT SIGINT, #endif #ifdef SIGQUIT SIGQUIT, #endif #ifdef SIGFPE SIGFPE, #endif #ifdef SIGTERM SIGTERM, #endif #ifdef SIGUSR1 SIGUSR1, #endif #ifdef SIGUSR2 SIGUSR2, /* POSIX.1-2001 */ #endif #ifdef SIGXCPU SIGXCPU, #endif #ifdef SIGXFSZ SIGXFSZ, #endif /* Linux various */ #ifdef SIGIOT SIGIOT, #endif #ifdef SIGSTKFLT SIGSTKFLT, #endif #ifdef SIGPWR SIGPWR, #endif #ifdef SIGINFO SIGINFO, #endif #ifdef SIGVTALRM SIGVTALRM, #endif -1, }; /* * units_to_str() * units to strings */ static char *units_to_str( const double val, char *units, char *const buf, const size_t buflen) { double v = (double)val; size_t i; static const char *scales[] = { "", "m", "µ", "n", "p", "f", "a", NULL }; for (i = 0; i < 6; i++, v *= 1000) { if (v > 0.5) break; } snprintf(buf, buflen, "%.2f %s%s", v, scales[i], units); return buf; } /* * value_to_str() * values to strings */ static char *value_to_str( const double val, const bool inaccurate, char *const buf, const size_t buflen) { if (inaccurate) { snprintf(buf, buflen, "-N/A-"); } else { double v = (double)val; size_t i; static const char scales[] = " KMBTPE"; for (i = 0; i < sizeof(scales) - 1; i++, v /= 1000) { if (v <= 500) break; } snprintf(buf, buflen, "%5.1f%c", v, scales[i]); } return buf; } /* * timeval_to_double * timeval to a double (in seconds) */ static inline double timeval_to_double(const struct timeval *const tv) { return (double)tv->tv_sec + ((double)tv->tv_usec / 1000000.0); } /* * double_to_timeval * seconds in double to timeval */ static inline struct timeval double_to_timeval(const double val) { struct timeval tv; tv.tv_sec = val; tv.tv_usec = (val - (time_t)val) * 1000000.0; return tv; } /* * gettime_to_double() * get time as a double */ static double gettime_to_double(void) { struct timeval tv; if (gettimeofday(&tv, NULL) < 0) { fprintf(stderr, "gettimeofday failed: errno=%d (%s).\n", errno, strerror(errno)); return -1.0; } return timeval_to_double(&tv); } /* * mwc() * fast pseudo random number generator, see * http://www.cse.yorku.ca/~oz/marsaglia-rng.html */ static inline uint32_t mwc(void) { static uint32_t w = MWC_SEED_W, z = MWC_SEED_Z; z = 36969 * (z & 65535) + (z >> 16); w = 18000 * (w & 65535) + (w >> 16); return (z << 16) + w; } /* * handle_sig() * catch signal and flag a stop */ static void handle_sig(int dummy) { (void)dummy; stop_flag = true; } /* * set_affinity() * set cpu affinity */ static int set_affinity(const int cpu) { cpu_set_t mask; int ret; CPU_ZERO(&mask); CPU_SET(cpu, &mask); ret = sched_setaffinity(0, sizeof(mask), &mask); if (ret < 0) { fprintf(stderr, "sched_setffinity failed: errno=%d (%s).\n", errno, strerror(errno)); return -1; } return 0; } /* * stress_cpu() * stress CPU */ static void stress_cpu( const uint64_t cpu_load, const int instance, bogo_ops_t *bogo_ops) { /* * Normal use case, 100% load, simple spinning on CPU */ if (cpu_load == 100) { uint64_t i; for (;;) { for (i = 0; i < 1000000; i++) { #if __GNUC__ /* Stop optimising out */ __asm__ __volatile__(""); #endif mwc(); if (stop_flag) { bogo_ops[instance].ops += i; exit(EXIT_SUCCESS); } } bogo_ops[instance].ops += i; } exit(EXIT_SUCCESS); } if (cpu_load == 0) { for (;;) { sleep(DEFAULT_TIMEOUT); if (stop_flag) exit(EXIT_SUCCESS); } exit(EXIT_SUCCESS); } /* * More complex percentage CPU utilisation. This is * not intended to be 100% accurate timing, it is good * enough for most purposes. */ for (;;) { uint64_t i; double time_start, delay; struct timeval tv; time_start = gettime_to_double(); for (i = 0; i < 1000000; i++) { #if __GNUC__ /* Stop optimising out */ __asm__ __volatile__(""); #endif mwc(); if (stop_flag) { bogo_ops[instance].ops += i; exit(EXIT_SUCCESS); } } bogo_ops[instance].ops += i; delay = gettime_to_double() - time_start; /* Must not calculate this with zero % load */ delay *= (((100.0 / (double) cpu_load)) - 1.0); tv = double_to_timeval(delay); select(0, NULL, NULL, NULL, &tv); } exit(EXIT_SUCCESS); } /* * stop_load() * kill load child processes */ static void stop_load(cpu_list_t *cpu_list, const int total_procs) { int i; cpu_info_t *c; /* Kill.. */ for (c = cpu_list->head, i = 0; c && i < total_procs; c = c->next, i++) { if (c->pid > -1) kill(c->pid, SIGKILL); } /* And ensure we don't get zombies */ for (c = cpu_list->head, i = 0; c && i < total_procs; c = c->next, i++) { if (c->pid > -1) { int status; waitpid(c->pid, &status, 0); } c->pid = -1; } } /* * start_load() * load system with some stress processes */ static void start_load( cpu_list_t *cpu_list, const int total_procs, const func load_func, const uint64_t param, bogo_ops_t *bogo_ops) { int i; struct sigaction new_action; cpu_info_t *c; memset(&new_action, 0, sizeof(new_action)); for (i = 0; signals[i] != -1; i++) { new_action.sa_handler = handle_sig; sigemptyset(&new_action.sa_mask); new_action.sa_flags = 0; (void)sigaction(signals[i], &new_action, NULL); (void)siginterrupt(signals[i], 1); } for (c = cpu_list->head, i = 0; c && i < total_procs; c = c->next, i++) { c->pid = fork(); switch (c->pid) { case -1: fprintf(stderr, "Cannot fork, errno=%d (%s)\n", errno, strerror(errno)); stop_load(cpu_list, i); exit(EXIT_FAILURE); case 0: /* Child */ if (set_affinity(c->cpu_id) < 0) exit(0); load_func(param, i, bogo_ops); exit(0); default: break; } } } /* * file_get() * read a line from a /sys file */ static char *file_get(const char *const file) { FILE *fp; char buffer[4096]; if ((fp = fopen(file, "r")) == NULL) return NULL; if (fgets(buffer, sizeof(buffer), fp) == NULL) { (void)fclose(fp); return NULL; } (void)fclose(fp); return strdup(buffer); } /* * get_time() * Gather current time in buffer */ static void get_time(char *const buffer, const size_t buflen) { struct tm tm; time_t now; now = time(NULL); if (now == ((time_t) -1)) { snprintf(buffer, buflen, "--:--:-- "); } else { (void)localtime_r(&now, &tm); snprintf(buffer, buflen, "%2.2d:%2.2d:%2.2d ", tm.tm_hour, tm.tm_min, tm.tm_sec); } } /* * stats_clear() * clear stats */ static void stats_clear(stats_t *const stats) { int i; for (i = 0; i < MAX_VALUES; i++) { stats->value[i] = 0.0; stats->inaccurate[i] = false; } } /* * stats_clear_all() * zero stats data */ static void stats_clear_all(stats_t *const stats, const int n) { int i; for (i = 0; i < n; i++) stats_clear(&stats[i]); } /* * stats_read() * gather pertinent /proc/stat data */ static int stats_read( const int32_t num_cpus, stats_t *const stats, bogo_ops_t *bogo_ops) { FILE *fp; char buf[4096]; int i, j; static int indices[] = { CPU_USER, CPU_NICE, CPU_SYS, CPU_IDLE, CPU_IOWAIT, CPU_IRQ, CPU_SOFTIRQ, CPU_CTXT, CPU_INTR, CPU_PROCS_RUN, CPU_PROCS_BLK, -1 }; for (i = 0; (j = indices[i]) != -1; i++) { stats->value[j] = 0.0; stats->inaccurate[j] = true; } if ((fp = fopen("/proc/stat", "r")) == NULL) { fprintf(stderr, "Cannot read /proc/stat, errno=%d (%s).\n", errno, strerror(errno)); return -1; } while (fgets(buf, sizeof(buf), fp) != NULL) { if (strncmp(buf, "cpu ", 4) == 0) if (sscanf(buf, "%*s %15lf %15lf %15lf %15lf %15lf %15lf %15lf", &(stats->value[CPU_USER]), &(stats->value[CPU_NICE]), &(stats->value[CPU_SYS]), &(stats->value[CPU_IDLE]), &(stats->value[CPU_IOWAIT]), &(stats->value[CPU_IRQ]), &(stats->value[CPU_SOFTIRQ])) == 7) { stats->inaccurate[CPU_USER] = false; stats->inaccurate[CPU_NICE] = false; stats->inaccurate[CPU_SYS] = false; stats->inaccurate[CPU_IDLE] = false; stats->inaccurate[CPU_IOWAIT] = false; stats->inaccurate[CPU_IRQ] = false; stats->inaccurate[CPU_SOFTIRQ] = false; } if (strncmp(buf, "ctxt ", 5) == 0) if (sscanf(buf, "%*s %15lf", &(stats->value[CPU_CTXT])) == 1) stats->inaccurate[CPU_CTXT] = false; if (strncmp(buf, "intr ", 5) == 0) if (sscanf(buf, "%*s %15lf", &(stats->value[CPU_INTR])) == 1) stats->inaccurate[CPU_INTR] = false; if (strncmp(buf, "procs_running ", 14) == 0) if (sscanf(buf, "%*s %15lf", &(stats->value[CPU_PROCS_RUN])) == 1) stats->inaccurate[CPU_PROCS_RUN] = false; if (strncmp(buf, "procs_blocked ", 14) == 0) if (sscanf(buf, "%*s %15lf", &(stats->value[CPU_PROCS_BLK])) == 1) stats->inaccurate[CPU_PROCS_BLK] = false; } (void)fclose(fp); stats->value[BOGO_OPS] = 0; stats->inaccurate[BOGO_OPS] = false; for (i = 0; i < num_cpus; i++) { stats->value[BOGO_OPS] += bogo_ops[i].ops; } return 0; } /* * stats_sane() * check if stats are accurate and calculate a * sane -ve delta */ static double stats_sane( const stats_t *const s1, const stats_t *const s2, const int index) { double ret; /* Discard inaccurate or empty stats */ if (s1->inaccurate[index] || s2->inaccurate[index]) return 0.0; /* * On Nexus 4 we occasionally get idle time going backwards so * work around this by ensuring we don't get -ve deltas. */ ret = s2->value[index] - s1->value[index]; return ret < 0.0 ? 0.0 : ret; } #define INACCURATE(s1, s2, index) \ (s1->inaccurate[index] | s2->inaccurate[index]) /* * stats_gather() * gather up delta between last stats and current to get * some form of per sample accounting calculated. */ static bool stats_gather( cpu_list_t *cpu_list, const uint32_t sample_delay, const stats_t *const s1, const stats_t *const s2, stats_t *const res) { double total; int i, j; bool inaccurate = false; static int indices[] = { CPU_USER, CPU_NICE, CPU_SYS, CPU_IDLE, CPU_IOWAIT, -1 }; res->value[CPU_USER] = stats_sane(s1, s2, CPU_USER); res->value[CPU_NICE] = stats_sane(s1, s2, CPU_NICE); res->value[CPU_SYS] = stats_sane(s1, s2, CPU_SYS); res->value[CPU_IDLE] = stats_sane(s1, s2, CPU_IDLE); res->value[CPU_IOWAIT] = stats_sane(s1, s2, CPU_IOWAIT); res->value[CPU_IRQ] = stats_sane(s1, s2, CPU_IRQ); res->value[CPU_SOFTIRQ] = stats_sane(s1, s2, CPU_SOFTIRQ); res->value[CPU_CTXT] = stats_sane(s1, s2, CPU_CTXT); res->value[CPU_INTR] = stats_sane(s1, s2, CPU_INTR); res->value[BOGO_OPS] = stats_sane(s1, s2, BOGO_OPS); res->value[CPU_CYCLES] = 0.0; res->inaccurate[CPU_CYCLES] = false; res->value[CPU_INSTRUCTIONS] = 0.0; res->inaccurate[CPU_INSTRUCTIONS] = false; #if defined(PERF_ENABLED) { cpu_info_t *c; for (c = cpu_list->head; c; c = c->next) { double value; perf_counter(&c->perf, PERF_HW_CPU_CYCLES, &value); if (value > 0.0) res->value[CPU_CYCLES] += value; perf_counter(&c->perf, PERF_HW_INSTRUCTIONS, &value); if (value > 0.0) res->value[CPU_INSTRUCTIONS] += value; } } #else (void)cpu_list; #endif for (i = 0; (j = indices[i]) != -1; i++) inaccurate |= (s1->inaccurate[j] | s2->inaccurate[j]); total = res->value[CPU_USER] + res->value[CPU_NICE] + res->value[CPU_SYS] + res->value[CPU_IDLE] + res->value[CPU_IOWAIT]; /* * This should not happen, but we need to avoid division * by zero or weird results if the data is deemed valid */ if (!inaccurate && total <= 0.0) return false; for (i = 0; (j = indices[i]) != -1; i++) { res->value[j] = (INACCURATE(s1, s2, j) || (total <= 0.0)) ? NAN : (100.0 * res->value[j]) / total; } res->value[CPU_CTXT] = (INACCURATE(s1, s2, CPU_CTXT) || (sample_delay <= 0.0)) ? NAN : res->value[CPU_CTXT] / sample_delay; res->value[CPU_INTR] = (INACCURATE(s1, s2, CPU_INTR) || (sample_delay <= 0.0)) ? NAN : res->value[CPU_INTR] / sample_delay; res->value[BOGO_OPS] = (INACCURATE(s1, s2, BOGO_OPS) || (sample_delay <= 0.0)) ? NAN : res->value[BOGO_OPS] / sample_delay; res->value[CPU_PROCS_RUN] = s2->inaccurate[CPU_PROCS_RUN] ? NAN : s2->value[CPU_PROCS_RUN]; res->value[CPU_PROCS_BLK] = s2->inaccurate[CPU_PROCS_BLK] ? NAN : s2->value[CPU_PROCS_BLK]; return true; } /* * stats_headings() * dump heading columns */ static void stats_headings(const char *test) { printf("%10.10s User Sys Idle Run Ctxt/s IRQ/s Ops/s " #if defined(PERF_ENABLED) "Cycl/s Inst/s " #endif " Watts\n", test); } /* * stats_print() * print out statistics with accuracy depending if it's a summary or not */ static void stats_print( const char *const prefix, const bool summary, const stats_t *const s) { char buf[10], bogo_ops[10]; #if defined(PERF_ENABLED) char cpu_cycles[10], cpu_instr[10]; #endif char *fmt; if (summary) { if (s->inaccurate[POWER_NOW]) snprintf(buf, sizeof(buf), "-N/A-"); else snprintf(buf, sizeof(buf), "%6.3f", s->value[POWER_NOW]); } else { snprintf(buf, sizeof(buf), "%6.3f%s", s->value[POWER_NOW], s->inaccurate[POWER_NOW] ? "E" : ""); } value_to_str(s->value[BOGO_OPS], s->inaccurate[BOGO_OPS], bogo_ops, sizeof(bogo_ops)); #if defined(PERF_ENABLED) value_to_str(s->value[CPU_CYCLES], s->inaccurate[CPU_CYCLES], cpu_cycles, sizeof(cpu_cycles)); value_to_str(s->value[CPU_INSTRUCTIONS], s->inaccurate[CPU_INSTRUCTIONS], cpu_instr, sizeof(cpu_instr)); #endif fmt = summary ? #if defined(PERF_ENABLED) "%10.10s %5.1f %5.1f %5.1f %4.1f %7.1f %6.1f %6s %6s %6s %s\n" : "%10.10s %5.1f %5.1f %5.1f %4.0f %7.0f %6.0f %6s %6s %6s %s\n"; #else "%10.10s %5.1f %5.1f %5.1f %4.1f %7.1f %6.1f %6s %s\n" : "%10.10s %5.1f %5.1f %5.1f %4.0f %7.0f %6.0f %6s %s\n"; #endif printf(fmt, prefix, s->value[CPU_USER], s->value[CPU_SYS], s->value[CPU_IDLE], s->value[CPU_PROCS_RUN], s->value[CPU_CTXT], s->value[CPU_INTR], bogo_ops, #if defined(PERF_ENABLED) cpu_cycles, cpu_instr, #endif buf); } /* * stats_average_stddev_min_max() * calculate average, std deviation, min and max */ static void stats_average_stddev_min_max( const stats_t *const stats, const int num, stats_t *const average, stats_t *const stddev) { int i, j, valid; for (j = 0; j < MAX_VALUES; j++) { double total = 0.0; for (valid = 0, i = 0; i < num; i++) { if (!stats[i].inaccurate[j]) { total += stats[i].value[j]; valid++; } } if (valid) { average->value[j] = total / (double)valid; total = 0.0; for (i = 0; i < num; i++) { if (!stats[i].inaccurate[j]) { double diff = (double)stats[i].value[j] - average->value[j]; diff = diff * diff; total += diff; } } stddev->value[j] = total / (double)num; stddev->value[j] = sqrt(stddev->value[j]); } else { average->inaccurate[j] = true; stddev->inaccurate[j] = true; average->value[j] = 0.0; stddev->value[j] = 0.0; } } } /* * power_get_sys_fs() * get power discharge rate from battery via /sys interface */ static int power_get_sys_fs( stats_t *stats, bool *const discharging, bool *const inaccurate) { DIR *dir; struct dirent *dirent; double total_watts = 0.0; double average_voltage = 0.0; int n = 0; stats->value[POWER_NOW] = 0.0; stats->value[VOLTAGE_NOW] = 0.0; stats->value[CURRENT_NOW] = 0.0; *discharging = false; *inaccurate = true; if ((dir = opendir(SYS_CLASS_POWER_SUPPLY)) == NULL) { fprintf(stderr, "Machine does not have %s, cannot run the test.\n", SYS_CLASS_POWER_SUPPLY); return -1; } do { dirent = readdir(dir); if (dirent && strlen(dirent->d_name) > 2) { char path[PATH_MAX]; char *data; int val; FILE *fp; /* Check that type field matches the expected type */ snprintf(path, sizeof(path), "%s/%s/type", SYS_CLASS_POWER_SUPPLY, dirent->d_name); if ((data = file_get(path)) != NULL) { bool mismatch = (strstr(data, "Battery") == NULL); free(data); if (mismatch) continue; /* type don't match, skip this entry */ } else continue; /* can't check type, skip this entry */ snprintf(path, sizeof(path), "%s/%s/uevent", SYS_CLASS_POWER_SUPPLY, dirent->d_name); if ((fp = fopen(path, "r")) == NULL) { fprintf(stderr, "Battery %s present but under supported - " "no state present.", dirent->d_name); (void)closedir(dir); return -1; } else { char buffer[4096]; double voltage = 0.0; double amps_rate = 0.0; double watts_rate = 0.0; while (fgets(buffer, sizeof(buffer)-1, fp) != NULL) { if (strstr(buffer, SYS_FIELD_STATUS_DISCHARGING)) *discharging = true; if (strstr(buffer, SYS_FIELD_CURRENT_NOW) && strlen(buffer) > sizeof(SYS_FIELD_CURRENT_NOW) - 1) { sscanf(buffer + sizeof(SYS_FIELD_CURRENT_NOW) - 1, "%12d", &val); amps_rate = (double)val / 1000000.0; } if (strstr(buffer, SYS_FIELD_POWER_NOW) && strlen(buffer) > sizeof(SYS_FIELD_POWER_NOW) - 1) { sscanf(buffer + sizeof(SYS_FIELD_POWER_NOW) - 1, "%12d", &val); watts_rate = (double)val / 1000000.0; } if (strstr(buffer, SYS_FIELD_VOLTAGE_NOW) && strlen(buffer) > sizeof(SYS_FIELD_VOLTAGE_NOW) - 1) { sscanf(buffer + sizeof(SYS_FIELD_VOLTAGE_NOW) - 1, "%12d", &val); voltage = (double)val / 1000000.0; } } average_voltage += voltage; total_watts += watts_rate + voltage * amps_rate; n++; (void)fclose(fp); } } } while (dirent); (void)closedir(dir); #if DETECT_DISCHARGING if (! *discharging) { printf("Machine is not discharging, cannot measure power usage.\n"); #if defined(RAPL_X86) printf("Alternatively, use the RAPL power measuring option '-R'.\n"); #endif return -1; } #else *discharging = true; #endif /* * If the battery is helpful it supplies the rate already, in which case * we know the results from the battery are as good as we can and we don't * have to figure out anything from capacity change over time. */ if (total_watts > RATE_ZERO_LIMIT) { stats->value[POWER_NOW] = total_watts; stats->value[VOLTAGE_NOW] = average_voltage / (double)n; stats->value[CURRENT_NOW] = total_watts / average_voltage; *inaccurate = (total_watts < 0.0); return 0; } /* * Rate not known, battery is less than useful. We could * calculate it from delta in charge, but that is not accurate * for this kind of use case, so error out instead. */ fprintf(stderr, "The battery just provided charge data which is not accurate enough.\n"); return -1; } /* * power_get_proc_acpi() * get power discharge rate from battery via /proc/acpi interface */ static int power_get_proc_acpi( stats_t *stats, bool *const discharging, bool *const inaccurate) { DIR *dir; FILE *file; struct dirent *dirent; char filename[PATH_MAX]; double total_watts = 0.0; double average_voltage = 0.0; int n = 0; stats->value[POWER_NOW] = 0.0; stats->value[VOLTAGE_NOW] = 0.0; stats->value[CURRENT_NOW] = 0.0; *discharging = false; *inaccurate = true; if ((dir = opendir(PROC_ACPI_BATTERY)) == NULL) { fprintf(stderr, "Machine does not have %s, cannot run the test.\n", PROC_ACPI_BATTERY); return -1; } while ((dirent = readdir(dir))) { double voltage = 0.0; double amps_rate = 0.0; double watts_rate = 0.0; char buffer[4096]; char *ptr; if (strlen(dirent->d_name) < 3) continue; sprintf(filename, "/proc/acpi/battery/%s/state", dirent->d_name); if ((file = fopen(filename, "r")) == NULL) continue; memset(buffer, 0, sizeof(buffer)); while (fgets(buffer, sizeof(buffer), file) != NULL) { if (strstr(buffer, "present:") && strstr(buffer, "no")) break; if (strstr(buffer, "charging state:") && (strstr(buffer, "discharging") || strstr(buffer, "critical"))) *discharging = true; ptr = strchr(buffer, ':'); if (ptr) { ptr++; if (strstr(buffer, "present voltage")) voltage = strtoull(ptr, NULL, 10) / 1000.0; if (strstr(buffer, "present rate")) { if (strstr(ptr, "mW")) watts_rate = strtoull(ptr, NULL, 10) / 1000.0 ; if (strstr(ptr, "mA")) amps_rate = strtoull(ptr, NULL, 10) / 1000.0; } } } (void)fclose(file); /* * Some HP firmware is broken and has an undefined * 'present voltage' field and instead returns this in * the design_voltage field, so work around this. */ if (voltage == 0.0) { sprintf(filename, "/proc/acpi/battery/%s/info", dirent->d_name); if ((file = fopen(filename, "r")) != NULL) { while (fgets(buffer, sizeof(buffer), file) != NULL) { ptr = strchr(buffer, ':'); if (ptr) { ptr++; if (strstr(buffer, "design voltage:")) { voltage = strtoull(ptr, NULL, 10) / 1000.0; break; } } } (void)fclose(file); } } average_voltage += voltage; total_watts += watts_rate + voltage * amps_rate; n++; } (void)closedir(dir); #if DETECT_DISCHARGING if (! *discharging) { printf("Machine is indicating it is not discharging and hence " "we cannot measure power usage.\n"); return -1; } #else *discharging = true; #endif /* * If the battery is helpful it supplies the rate already, in which * case we know the results from the battery are as good as we can * and we don't have to figure out anything from capacity change over * time. */ if (total_watts > RATE_ZERO_LIMIT) { stats->value[POWER_NOW] = total_watts; stats->value[VOLTAGE_NOW] = average_voltage / (double)n; stats->value[CURRENT_NOW] = total_watts / average_voltage; *inaccurate = (total_watts < 0.0); return 0; } /* * Rate not known, battery is less than useful. We could * calculate it from delta in charge, but that is not accurate * for this kind of use case, so error out instead. */ fprintf(stderr, "The battery just provided charge data which is not accurate enough.\n"); return -1; } #if defined(RAPL_X86) /* * rapl_free_list() * free RAPL list */ static void rapl_free_list(rapl_info_t *rapl_list) { rapl_info_t *rapl = rapl_list; while (rapl) { rapl_info_t *next = rapl->next; free(rapl->name); free(rapl->domain_name); free(rapl); rapl = next; } } /* * rapl_get_domains() */ static int rapl_get_domains(rapl_info_t **rapl_list) { DIR *dir; struct dirent *entry; int n = 0; dir = opendir("/sys/class/powercap"); if (dir == NULL) { printf("Device does not have RAPL, cannot measure power usage.\n"); return -1; } while ((entry = readdir(dir)) != NULL) { char path[PATH_MAX]; FILE *fp; rapl_info_t *rapl; /* Ignore non Intel RAPL interfaces */ if (strncmp(entry->d_name, "intel-rapl", 10)) continue; if ((rapl = calloc(1, sizeof(*rapl))) == NULL) { fprintf(stderr, "Cannot allocate RAPL information.\n"); closedir(dir); return -1; } if ((rapl->name = strdup(entry->d_name)) == NULL) { fprintf(stderr, "Cannot allocate RAPL name information.\n"); closedir(dir); free(rapl); return -1; } snprintf(path, sizeof(path), "/sys/class/powercap/%s/max_energy_range_uj", entry->d_name); rapl->max_energy_uj = 0.0; if ((fp = fopen(path, "r")) != NULL) { if (fscanf(fp, "%lf\n", &rapl->max_energy_uj) != 1) rapl->max_energy_uj = 0.0; (void)fclose(fp); } snprintf(path, sizeof(path), "/sys/class/powercap/%s/name", entry->d_name); rapl->domain_name = NULL; if ((fp = fopen(path, "r")) != NULL) { char domain_name[128]; if (fgets(domain_name, sizeof(domain_name), fp) != NULL) { domain_name[strcspn(domain_name, "\n")] = '\0'; rapl->domain_name = strdup(domain_name); } (void)fclose(fp); } if (rapl->domain_name == NULL) { free(rapl->name); free(rapl); continue; } rapl->is_package = (strncmp(rapl->domain_name, "package-", 8) == 0); rapl->next = *rapl_list; *rapl_list = rapl; n++; } (void)closedir(dir); if (!n) printf("Device does not have any RAPL domains, cannot power measure power usage.\n"); return n; } /* * power_get_rapl() * get power discharge rate from battery via the RAPL interface */ static int power_get_rapl( rapl_info_t *rapl_list, stats_t *stats, bool *const discharging) { double t_now; static bool first = true; rapl_info_t *rapl; int n = 0; /* Assume OK until found otherwise */ stats->inaccurate[POWER_NOW] = false; stats->value[POWER_NOW] = 0.0; *discharging = false; t_now = gettime_to_double(); for (rapl = rapl_list; rapl; rapl = rapl->next) { char path[PATH_MAX]; FILE *fp; double ujoules; snprintf(path, sizeof(path), "/sys/class/powercap/%s/energy_uj", rapl->name); if ((fp = fopen(path, "r")) == NULL) continue; if (fscanf(fp, "%lf\n", &ujoules) == 1) { double t_delta = t_now - rapl->t_last; double last_energy_uj = rapl->last_energy_uj; rapl->t_last = t_now; /* Wrapped around since last time? */ if (ujoules - rapl->last_energy_uj < 0.0) { rapl->last_energy_uj = ujoules; ujoules += rapl->max_energy_uj; } else { rapl->last_energy_uj = ujoules; } if (first || (t_delta <= 0.0)) { stats->value[POWER_DOMAIN_0 + n] = 0.0; stats->inaccurate[POWER_NOW] = true; } else { stats->value[POWER_DOMAIN_0 + n] = (ujoules - last_energy_uj) / (t_delta * 1000000.0); } if (rapl->is_package) stats->value[POWER_NOW] += stats->value[POWER_DOMAIN_0 + n]; n++; *discharging = true; } fclose(fp); } if (first) { stats->inaccurate[POWER_NOW] = true; first = false; } if (!n) { printf("Device does not have any RAPL domains, cannot power measure power usage.\n"); return -1; } return 0; } #endif /* * power_get() * get consumption rate */ static int power_get( rapl_info_t *rapl_list, stats_t *stats, bool *const discharging, bool *const inaccurate) { struct stat buf; int i; for (i = POWER_NOW; i < MAX_VALUES; i++) { stats->value[i] = i; stats->inaccurate[i] = 0.0; } #if defined(RAPL_X86) if (opt_flags & OPT_RAPL) return power_get_rapl(rapl_list, stats, discharging); #else (void)rapl_list; #endif if ((stat(SYS_CLASS_POWER_SUPPLY, &buf) != -1) && S_ISDIR(buf.st_mode)) return power_get_sys_fs(stats, discharging, inaccurate); if ((stat(PROC_ACPI_BATTERY, &buf) != -1) && S_ISDIR(buf.st_mode)) return power_get_proc_acpi(stats, discharging, inaccurate); fprintf(stderr, "Machine does not seem to have a battery, cannot measure power.\n"); return -1; } /* * not_discharging() * returns true if battery is not discharging */ static inline bool not_discharging(rapl_info_t *rapl_list) { stats_t dummy; bool discharging, inaccurate; return power_get(rapl_list, &dummy, &discharging, &inaccurate) < 0; } /* * monitor() * monitor system activity and power consumption */ static inline int monitor( const int num_cpus, cpu_list_t *cpu_list, rapl_info_t *rapl_list, const int start_delay, const int sample_delay, const int max_readings, const char *test, const double percent_each, const double percent, bogo_ops_t *bogo_ops, double *busy, double *power, double *voltage, double *ops, double *cpu_cycles, double *cpu_instr) { int readings = 0, i; int64_t t = 1; stats_t *stats, s1, s2, average, stddev; bool discharging, dummy_inaccurate; double time_start; if (start_delay > 0) { stats_t dummy; /* Gather up initial data */ for (i = 0; i < start_delay; i++) { if (opt_flags & OPT_PROGRESS) { fprintf(stdout, "%10.10s: test warming up %5.1f%%..\r", test, 100.0 * i / start_delay); fflush(stdout); } if (power_get(rapl_list, &dummy, &discharging, &dummy_inaccurate) < 0) return -1; if (sleep(1) || stop_flag) return -1; if (!discharging) return -1; } } if (not_discharging(rapl_list)) return -1; if ((stats = calloc(max_readings, sizeof(stats_t))) == NULL) { fprintf(stderr, "Cannot allocate statistics table.\n"); return -1; } stats_clear_all(stats, max_readings); stats_clear(&average); stats_clear(&stddev); if ((time_start = gettime_to_double()) < 0.0) { free(stats); return -1; } if (stats_read(num_cpus, &s1, bogo_ops) < 0) { free(stats); return -1; } while (!stop_flag && (readings < max_readings)) { int ret = 0; double secs, time_now; struct timeval tv; #if defined(PERF_ENABLED) cpu_info_t *c; #endif if ((time_now = gettime_to_double()) < 0.0) { free(stats); return -1; } #if defined(PERF_ENABLED) for (c = cpu_list->head; c; c = c->next) perf_start(&c->perf, c->pid); #endif if (opt_flags & OPT_PROGRESS) { double progress = readings * 100.0 / max_readings; fprintf(stdout, "%10.10s: test progress %5.1f%% (total progress %6.2f%%)\r", test, progress, (progress * percent_each / 100.0) + percent); fflush(stdout); } secs = time_start + ((double)t * sample_delay) - time_now; if (secs < 0.0) goto sample_now; tv = double_to_timeval(secs); ret = select(0, NULL, NULL, NULL, &tv); if (ret < 0) { #if defined(PERF_ENABLED) for (c = cpu_list->head; c; c = c->next) perf_stop(&c->perf); #endif if (errno == EINTR) break; fprintf(stderr,"select failed: errno=%d (%s).\n", errno, strerror(errno)); free(stats); return -1; } sample_now: if (stop_flag) break; /* Time out, so measure some more samples */ if (ret == 0) { char tmbuffer[10]; bool discharging; #if defined(PERF_ENABLED) for (c = cpu_list->head; c; c = c->next) perf_stop(&c->perf); #endif get_time(tmbuffer, sizeof(tmbuffer)); if (stats_read(num_cpus, &s2, bogo_ops) < 0) goto tidy_exit; /* * Total ticks was zero, something is broken, * so re-sample */ if (!stats_gather(cpu_list, sample_delay, &s1, &s2, &stats[readings])) { stats_clear(&stats[readings]); if (stats_read(num_cpus, &s1, bogo_ops) < 0) goto tidy_exit; continue; } if (power_get(rapl_list, &stats[readings], &discharging, &stats[readings].inaccurate[POWER_NOW]) < 0) goto tidy_exit; if (!discharging) goto tidy_exit; /* No longer discharging! */ readings++; s1 = s2; t++; } } /* * Stats now gathered, calculate averages, stddev, min * and max and display */ stats_average_stddev_min_max(stats, readings, &average, &stddev); if (readings > 0) { stats_print(test, true, &average); } *busy = 100.0 - average.value[CPU_IDLE]; *power = average.value[POWER_NOW]; *voltage = average.value[VOLTAGE_NOW]; *ops = average.value[BOGO_OPS]; *cpu_cycles = average.value[CPU_CYCLES]; *cpu_instr = average.value[CPU_INSTRUCTIONS]; free(stats); return 0; tidy_exit: free(stats); return -1; } /* * calc_trend() * calculate linear trendline - compute gradient, y intercept and * coefficient of determination. */ static int calc_trend( const char *heading, const int cpus_used, const value_t *values, const int num_values, double *gradient, double *intercept, double *r2) { int i, n = 0; double a = 0.0, b, c = 0.0, d, e, f; double sum_x = 0.0, sum_y = 0.0; double sum_x2 = 0.0, sum_y2 = 0.0; double sum_xy = 0.0, r; double n1, n2; for (i = 0; i < num_values; i++) { if (cpus_used == CPU_ANY || cpus_used >= values[i].cpus_used) { a += values[i].x * values[i].y; sum_x += values[i].x; sum_y += values[i].y; sum_xy += (values[i].x * values[i].y); sum_x2 += (values[i].x * values[i].x); sum_y2 += (values[i].y * values[i].y); n++; } } if (!n) { printf("%s: Cannot perform trend analysis, zero samples.\n", heading); return -1; } /* * Coefficient of determination R^2, * http://mathbits.com/MathBits/TISection/Statistics2/correlation.htm */ n1 = sqrt(((double)n * sum_x2) - (sum_x * sum_x)); n2 = sqrt(((double)n * sum_y2) - (sum_y * sum_y)); d = n1 * n2; if (d <= 0.0) { printf("%s: Cannot perform trend analysis\n" "(the coefficient of determination is not invalid).\n", heading); return -1; } r = (((double)n * sum_xy) - (sum_x * sum_y)) / d; *r2 = r * r; /* * Regression Equation(y) = a + bx * Slope = (NΣXY - (ΣX)(ΣY)) / (NΣX2 - (ΣX)2) * Intercept = (ΣY - b(ΣX)) / N */ a *= (double)n; b = sum_x * sum_y; c = sum_x2 * (double)n; d = sum_x * sum_x; e = sum_y; *gradient = (a - b) / (c - d); f = (*gradient) * sum_x; *intercept = (e - f) / (double)n; return 0; } /* * show_help() * simple help */ static void show_help(char *const argv[]) { printf("%s, version %s\n\n", app_name, VERSION); printf("usage: %s [options]\n", argv[0]); printf(" -d secs specify delay before starting\n"); printf(" -h show help\n"); printf(" -n cpus specify number of CPUs to exercise\n"); printf(" -o file output results into YAML formatted file\n"); printf(" -p show progress\n"); printf(" -r secs specify run duration in seconds of each test cycle\n"); #if defined(RAPL_X86) printf(" -R use Intel RAPL per CPU package data to measure Watts\n"); #endif printf(" -s num number of samples (tests) per CPU for CPU calibration\n"); printf("\nExample: power-calibrate -R -r 20 -d 5 -s 21 -n 0 -p\n"); } /* * coefficient_r2() * give some idea of r2 */ static const char *coefficient_r2(const double r2) { if (r2 < 0.4) return "very weak"; if (r2 < 0.75) return "weak"; if (r2 < 0.80) return "fair"; if (r2 < 0.90) return "good"; if (r2 < 0.95) return "strong"; if (r2 < 1.0) return "very strong"; return "perfect"; } /* * dump_yaml_values() * output yaml formatted data */ static void dump_yaml_values( FILE *yaml, const char *heading, const char *field, const double value, const double r2) { if (!yaml) return; fprintf(yaml, " %s:\n", heading); fprintf(yaml, " %s: %e\n", field, value); fprintf(yaml, " r-squared: %f\n", r2); } /* * dump_yaml_misc() * output json misc test data */ static void dump_yaml_misc(FILE *yaml) { time_t now; struct tm tm; struct utsname buf; now = time(NULL); if (now == ((time_t) -1)) { memset(&tm, 0, sizeof(tm)); } else { localtime_r(&now, &tm); } memset(&buf, 0, sizeof(struct utsname)); uname(&buf); fprintf(yaml, " test-run:\n"); fprintf(yaml, " date: %2.2d/%2.2d/%-2.2d\n", tm.tm_mday, tm.tm_mon + 1, (tm.tm_year+1900) % 100); fprintf(yaml, " time: %-2.2d:%-2.2d:%-2.2d\n", tm.tm_hour, tm.tm_min, tm.tm_sec); fprintf(yaml, " sysname: %s\n", buf.sysname); fprintf(yaml, " nodename: %s\n", buf.nodename); fprintf(yaml, " release: %s\n", buf.release); fprintf(yaml, " machine: %s\n", buf.machine); } /* * calc_average_voltage() * calculatate average voltage on CPU ids */ static double calc_average_voltage( const int cpus_used, value_t *values, const int num_values) { int i, n = 0; double average = 0.0; for (i = 0; i < num_values; i++) { if (cpus_used == CPU_ANY || cpus_used >= values[i].cpus_used) { average += values[i].voltage; n++; } } average = (n == 0) ? 0.0 : average / n; if (average == 0.0) return -1; return average; } /* * show_trend() * Show power trend */ static void show_trend( FILE *yaml, const int cpus_used, value_t *values, const int num_values, const char *unit, const char *each, const char *heading, const char *field, bool power) { char watts[16]; double gradient, intercept, r2; double average_voltage = calc_average_voltage(cpus_used, values, num_values); if (calc_trend(heading, cpus_used, values, num_values, &gradient, &intercept, &r2) < 0) return; units_to_str(gradient, "W", watts, sizeof(watts)); printf(" %s (%s) = (%s * %e) + %f\n", power ? "Power" : "Energy", power ? "Watts" : "Watt-seconds", unit, gradient, intercept); if (average_voltage > 0) { char amps[16], volts[16]; units_to_str(average_voltage, "V", volts, sizeof(volts)); units_to_str(gradient / average_voltage, "A", amps, sizeof(amps)); printf(" %s is about %s (about %s @ %s)\n", each, watts, amps, volts); } else { printf(" %s is about %s%s\n", each, watts, power ? "" : "s"); } printf(" Coefficient of determination R^2 = %f (%s)\n", r2, coefficient_r2(r2)); dump_yaml_values(yaml, heading, field, gradient, r2); } /* * monitor_cpu_load() * load CPU(s) and gather stats */ static int monitor_cpu_load( FILE *fp, const int32_t num_cpus, const int32_t max_cpus, const int32_t samples_cpu, const int32_t sample_delay, cpu_list_t *cpu_list, rapl_info_t *rapl_list, const int start_delay, const int max_readings, bogo_ops_t *bogo_ops) { uint32_t i, n = 0; value_t values_load[num_cpus * samples_cpu], *value_load = values_load; value_t values_ops[num_cpus * samples_cpu], *value_ops = values_ops; value_t values_cpu_cycles[num_cpus * samples_cpu], *value_cpu_cycles = values_cpu_cycles; value_t values_cpu_instr[num_cpus * samples_cpu], *value_cpu_instr = values_cpu_instr; double scale = (double)MAX_CPU_LOAD / (samples_cpu - 1); stats_headings("CPU load"); for (i = 0; i < (uint32_t)samples_cpu; i++) { cpu_info_t *c; uint32_t n_cpus; for (n_cpus = 1, c = cpu_list->head; c; n_cpus++, c = c->next) { char buffer[1024]; int cpu_load = scale * i; int ret; double percent_each = 100.0 / (samples_cpu * num_cpus); double percent = n * percent_each; snprintf(buffer, sizeof(buffer), "%d%% x %d", cpu_load, n_cpus); start_load(cpu_list, n_cpus, stress_cpu, (uint64_t)cpu_load, bogo_ops); ret = monitor(num_cpus, cpu_list, rapl_list, start_delay, sample_delay, max_readings, buffer, percent_each, percent, bogo_ops, &value_load->x, &value_load->y, &value_load->voltage, &value_ops->x, &value_cpu_cycles->x, &value_cpu_instr->x); value_ops->y = value_load->y; value_ops->voltage = value_load->voltage; value_ops->cpu_id = value_load->cpu_id = c->cpu_id; value_ops->cpus_used = value_load->cpus_used = n_cpus; value_cpu_cycles->y = value_load->y; value_cpu_cycles->voltage = value_load->voltage; value_cpu_cycles->cpu_id = value_load->cpu_id; value_cpu_cycles->cpus_used = value_load->cpus_used; value_cpu_instr->y = value_load->y; value_cpu_instr->voltage = value_load->voltage; value_cpu_instr->cpu_id = value_load->cpu_id; value_cpu_instr->cpus_used = value_load->cpus_used; stop_load(cpu_list, n_cpus); if (stop_flag || (ret < 0)) return -1; value_load++; value_ops++; value_cpu_cycles++; value_cpu_instr++; n++; } } /* Keep static analysis happy */ if (n <= 0) { printf("\nZero samples, cannot compute statistics.\n"); return -1; } if (opt_flags & OPT_CALIBRATE_EACH_CPU) { cpu_info_t *c; int cpus_used = 0; for (c = cpu_list->head, cpus_used = 1; c; c = c->next, cpus_used++) { printf("\nFor %d CPU%s (of a %d CPU system):\n", cpus_used, cpus_used > 1 ? "s" : "", max_cpus); show_trend(NULL, cpus_used, values_load, n, "% CPU load", "1% CPU load", "cpu-load", "one-percent-cpu-load-watts", true); printf("\n"); show_trend(NULL, cpus_used, values_ops, n, "bogo op", "1 bogo op", "bogo-op", "one-bogo-op-power-watt-seconds", false); #if defined(PERF_ENABLED) printf("\n"); show_trend(NULL, cpus_used, values_cpu_cycles, n, "CPU cycle", "1 CPU cycle", "cpu-cycle", "one-cpu-cycle-watt-seconds", false); printf("\n"); show_trend(NULL, cpus_used, values_cpu_instr, n, "CPU instruction", "1 CPU instruction", "cpu-instruction", "on-cpu-instruction-watt-seconds", false); #endif } } else { printf("\nFor %d CPU%s (of a %d CPU system):\n", cpu_list->count, cpu_list->count > 1 ? "s" : "", max_cpus); show_trend(fp, CPU_ANY, values_load, n, "% CPU load", "1% CPU load", "cpu-load", "one-percent-cpu-load-watts", true); printf("\n"); show_trend(fp, CPU_ANY, values_ops, n, "bogo op", "1 bogo op", "bogo-op", "one-bogo-op-watts-seconds", false); #if defined(PERF_ENABLED) printf("\n"); show_trend(fp, CPU_ANY, values_cpu_cycles, n, "CPU cycle", "1 CPU cycle", "cpu-cycle", "one-cpu-cycle-watt-seconds", false); printf("\n"); show_trend(fp, CPU_ANY, values_cpu_instr, n, "CPU instruction", "1 CPU instruction", "cpu-instruction", "on-cpu-instruction-watt-seconds", false); #endif } return 0; } /* * add_cpu_info() * add cpu # to cpu_info list */ static int add_cpu_info(cpu_list_t *cpu_list, const int cpu) { cpu_info_t *c; c = calloc(1, sizeof(cpu_info_t)); if (!c) { fprintf(stderr, "Out of memory allocating CPU info.\n"); return -1; } if (cpu_list->head) cpu_list->tail->next = c; else cpu_list->head = c; c->cpu_id = cpu; cpu_list->tail = c; cpu_list->count++; return 0; } /* * parse_cpu_info() * parse cpu info */ static int parse_cpu_info( int32_t *num_cpus, const int32_t max_cpus, cpu_list_t *cpu_list, char *arg) { char *str, *token; int n = 0; for (str = arg; (token = strtok(str, ",")) != NULL; str = NULL) { int cpu; char *endptr; errno = 0; cpu = strtol(token, &endptr, 10); if (errno || endptr == token) { fprintf(stderr, "Invalid CPU specified.\n"); return -1; } if (cpu < 0 || cpu > max_cpus - 1) { fprintf(stderr, "CPU number out of range.\n"); return -1; } if (add_cpu_info(cpu_list, cpu) < 0) return -1; n++; } if (!cpu_list->head) { fprintf(stderr, "No valid CPU numbers given.\n"); return -1; } *num_cpus = n; return 0; } /* * populate_cpu_info() * if user has not supplied cpu info then * we need to populate the list */ static inline int populate_cpu_info(const int32_t num_cpus, cpu_list_t *cpu_list) { int cpu; if (cpu_list->head) return 0; for (cpu = 0; cpu < num_cpus; cpu++) if (add_cpu_info(cpu_list, cpu) < 0) return -1; return 0; } /* * free_cpu_info() * free CPU info list */ static void free_cpu_info(cpu_list_t *cpu_list) { cpu_info_t *c = cpu_list->head; while (c) { cpu_info_t *next = c->next; free(c); c = next; } cpu_list->head = NULL; cpu_list->tail = NULL; cpu_list->count = 0; } int main(int argc, char * const argv[]) { int max_readings, run_duration, start_delay = START_DELAY_BATTERY; int opt_run_duration = DEFAULT_RUN_DURATION; char *filename = NULL; FILE *yaml = NULL; int ret = EXIT_FAILURE, i; struct sigaction new_action; bogo_ops_t *bogo_ops = NULL; rapl_info_t *rapl_list = NULL; /* RAPL domain info list */ cpu_list_t cpu_list; int32_t samples_cpu = 11.0; /* samples per run */ int32_t sample_delay = SAMPLE_DELAY; /* time between each sampl */ int32_t num_cpus; /* number of CPUs */ int32_t max_cpus; /* number of CPUs in system */ memset(&cpu_list, 0, sizeof(cpu_list)); max_cpus = num_cpus = sysconf(_SC_NPROCESSORS_CONF); if (num_cpus < 1) { /* Zero CPUs makes no sense, -ve is an error */ fprintf(stderr, "Cannot determine number of CPUs, errno=%d (%s).\n", errno, strerror(errno)); goto out; } for (;;) { int c = getopt(argc, argv, "d:ehn:o:ps:r:R"); if (c == -1) break; switch (c) { case 'd': opt_flags |= OPT_DELAY; start_delay = atoi(optarg); if (start_delay < 0) { fprintf(stderr, "Start delay must be 0 or more seconds.\n"); goto out; } break; case 'e': opt_flags |= OPT_CALIBRATE_EACH_CPU; break; case 'h': show_help(argv); goto out; case 'n': if (parse_cpu_info(&num_cpus, max_cpus, &cpu_list, optarg) < 0) goto out; break; case 'o': filename = optarg; break; case 'p': opt_flags |= OPT_PROGRESS; break; case 'r': opt_run_duration = atoi(optarg); if (opt_run_duration < MIN_RUN_DURATION) { fprintf(stderr, "Minimum run duration must be %d seconds or more\n", MIN_RUN_DURATION); goto out; } break; #if defined(RAPL_X86) case 'R': opt_flags |= OPT_RAPL; break; #endif case 's': samples_cpu = atoi(optarg); if ((samples_cpu < 3.0) || (samples_cpu > MAX_CPU_LOAD)) { fprintf(stderr, "Samples for CPU measurements out of range.\n"); goto out; } break; default: show_help(argv); goto out; } } if ((opt_flags & (OPT_RAPL | OPT_DELAY)) == OPT_RAPL) start_delay = START_DELAY_RAPL; populate_cpu_info(num_cpus, &cpu_list); #if defined(RAPL_X86) if ((opt_flags & OPT_RAPL) && (rapl_get_domains(&rapl_list) < 1)) exit(EXIT_FAILURE); #endif if (optind < argc) { sample_delay = atoi(argv[optind++]); if (sample_delay < 1) { fprintf(stderr, "Sample delay must be >= 1.\n"); goto out; } } if (filename) { if ((yaml = fopen(filename, "w")) == NULL) { fprintf(stderr, "Cannot open json output file '%s', " "errno=%d (%s).\n", filename, errno, strerror(errno)); goto out; } fprintf(yaml, "---\n%s:\n", app_name); } memset(&new_action, 0, sizeof(new_action)); for (i = 0; signals[i] != -1; i++) { new_action.sa_handler = handle_sig; sigemptyset(&new_action.sa_mask); new_action.sa_flags = 0; if (sigaction(signals[i], &new_action, NULL) < 0) { fprintf(stderr, "sigaction failed: errno=%d (%s).\n", errno, strerror(errno)); goto out; } (void)siginterrupt(signals[i], 1); } run_duration = opt_run_duration; max_readings = run_duration / sample_delay; bogo_ops = mmap(NULL, sizeof(bogo_ops_t) * num_cpus, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0); if (bogo_ops == MAP_FAILED) { fprintf(stderr, "mmap failed: errno=%d (%s).\n", errno, strerror(errno)); goto out; } if (not_discharging(rapl_list)) goto out; if (monitor_cpu_load(yaml, num_cpus, max_cpus, samples_cpu, sample_delay, &cpu_list, rapl_list, start_delay, max_readings, bogo_ops) < 0) goto out; ret = EXIT_SUCCESS; out: if (bogo_ops) (void)munmap(bogo_ops, sizeof(bogo_ops_t) * num_cpus); if (yaml) { dump_yaml_misc(yaml); fprintf(yaml, "...\n"); (void)fclose(yaml); if (ret != EXIT_SUCCESS) unlink(filename); } if (cpu_list.head) free_cpu_info(&cpu_list); #if defined(RAPL_X86) rapl_free_list(rapl_list); #endif exit(ret); }