Makefile000755 000766 000024 00000001755 13163231534 013514 0ustar00numubeenstaff000000 000000 VERSION = 0.1 BINDIR=usr/bin WARNFLAGS=-Wall -Wformat CC=gcc CFLAGS+= -D VERSION=\"$(VERSION)\" CFLAGS+= -D_LINUX_ #CFLAGS+= -DDEBUG -g3 -ggdb -O0 -gdwarf-2 LDFLAGS+= -DPASS2 TARGET=psst INSTALL_PROGRAM=install -m 755 -p DEL_FILE=rm -f INSTALL_CONFIGFILE=install -m 644 -p CONFIG_FILE= CONFIG_PATH= OBJS = parse_config.o logger.o rapl.o perf_msr.o psst.o OBJS += psst: $(OBJS) Makefile psst.h $(CC) ${CFLAGS} $(LDFLAGS) $(OBJS) -o $(TARGET) -lpthread -lrt -lm install: - mkdir -p $(INSTALL_ROOT)/$(BINDIR) - $(INSTALL_PROGRAM) "$(TARGET)" "$(INSTALL_ROOT)/$(BINDIR)/$(TARGET)" - mkdir -p $(INSTALL_ROOT)/$(CONFIG_PATH) - $(INSTALL_CONFIGFILE) "$(CONFIG_FILE)" "$(INSTALL_ROOT)/$(CONFIG_PATH)" uninstall: $(DEL_FILE) "$(INSTALL_ROOT)/$(BINDIR)/$(TARGET)" $(CONFIG_FILE) "$(CONFIG_PATH)" clean: find . -name "*.o" | xargs $(DEL_FILE) rm -f $(TARGET) dist: git tag v$(VERSION) git archive --format=tar --prefix="$(TARGET)-$(VERSION)/" v$(VERSION) | \ gzip > $(TARGET)-$(VERSION).tar.gz README000755 000766 000024 00000010251 13163231534 012723 0ustar00numubeenstaff000000 000000 Power stress and shaping tool (PSST) - tool for controlled stress & monitor of x86 SoC for power/thermal analysis. what can psst do: ===================== psst is a controlled power virus for soc-sub components such as cpu, gpu & memory. The intent is to expend power at different run-time-varying levels for analysis. example of such analysis could be, a linear cpu hog function for governor evaluation, a single step function for PID coefficient evaluaton etc,. The fundamental difference with other power virus or monitor tools is that, the work done during hog is not a dummy function, but own useful work functions such as accounting, logging (in-memory), or power shape contour change etc. psst just executes real work function duty-cycling in controlled loops. more work functions could be added to the this tool overtime & they will be accounted for good -- against the ON-time of duty cycling. The tool's most importat usecase is to do logging at a fixed "own" overhead -- not more than the present requested load (active C0 percent). this ensures that monitoring does not influence the overall system load (C0 %). This serves to monitor soc power/thermal parameters at much fine grained time, comparable to governers's poll period (tens of ms). For instance, a 100ms poll could causes upto 50% cpu overhead in traditional tool. Fruthermore, psst's logging is aligned with the C0 activity that is being analyzed. USAGE ===== usage: psst [options ] Supported options are: -C|--cpumask hex bit mask of cpu selected for stress. (default: every online cpu. Max 1024) -p|--poll-period (ms) for logging (default: 500 ms) -d|--duration (ms) to run the tool (default: 3600000 i.e., 1hr) -l|--log-file (default: /var/log/pbot.csv) -v|--verbose enables verbose mode (default: disabled) -V|--Version prints version when specified -T|--track-max-cpu Track the cpu# with maxed freq during poll period -h|--help prints usage when specified -s|--shape-func Where supported power shape functions & args are: where v is load step height [C0%].(default shape: single-step,0.1) where w is wavelength [seconds] and a is the amplitude (max load %) where v is load step height [C0%], u is step length (sec) where v is load step height [C0%], u is step length (sec) where m is the slope (load/sec) slope m (load/sec) reversed after max a% or min(0.1)% example 1: use psst just for logging various power/thermal parameters $./psst #same as $./psst -s single-step,0.1 -p 500 -v example 2: linear ramp CPU power with slope 3% usage-per-secapplied for cpu1 & 3. poll and reportevery 700mS. output on termial. run for 33 sec $./psst -s linear-ramp,3 -C a -p 700 -d 33000 -v more details on some options ============================ * -T|--track-max-cpu Track the cpu# with maxed freq during poll period with this option, the cpu that requested max frequency in the poll window is determined & printed in output. one application of this, can be in determination of symmetric load distribution on SMP systems wherein each cpu gets equal % of hits. Note: different cpu can be stressed with different funtions simultaneously. to do this just invoking separate commands for each cpu. Here is a fun example to demonstrate the controllability of linear ramp on cpu0, sine wave on cpu1, single-step on cpu2, single-pulse on cpu3 -- at the same time. launch "system monitor" like utility & execute: sleep 2; sudo ./psst -C 0 -d 30000 -s linear-ramp,2 & sleep 2; sudo ./psst -C 1 -d 30000 -s sinosoid,15,50 & sleep 2; sudo ./psst -C 2 -d 30000 -s single-step,20 & sleep 5; sudo ./psst -C 3 -d 30000 -s single-pulse,60,2 & Code structure ============== . |-- logger.c # in-memory logging functions |-- logger.h |-- Makefile |-- parse_config.c # parse cmdline related routines |-- parse_config.h |-- perf_msr.c # x86 msr counters for aperf/mperf etc |-- perf_msr.h |-- psst.c # main routine & core work function |-- psst.h |-- rapl.c # x86 energy register interface |-- rapl.h `-- README # this file Build ===== $sudo make $sudo ./psst logger.c000755 000766 000024 00000044563 13163231534 013503 0ustar00numubeenstaff000000 000000 /* * logger.c functions around logging polled values of open file descriptors * * Copyright (c) 2017, Intel Corporation. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope 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. * * Author: Noor ul Mubeen */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include "logger.h" #include "rapl.h" #include "perf_msr.h" #include "parse_config.h" #ifdef _LINUX_ #include #elif defined _ANDROID_ #include #endif #define INIT_COL(e, h, u, f, m, p, v) { \ .report_enabled = e, \ .header_name = #h, \ .unit = #u, \ .fmt = #f, \ .unit_multiplier = m, \ .fd_type = p, \ .value = v, \ } /* fixed columns of the log output */ struct log_col_desc col_desc[] = { /* 1. TIME_STAMP_MS: time stamp in millisec */ INIT_COL(1, Time, [ms], 9.0, 1, NO_FD, 0), /* 2. FREQ_REALIZED: average frequency (cpu0) since last poll */ INIT_COL(1, FreqReal, [MHz], 8.0, 0.1, MSR_FD, 0), /* 3. MAX_FREQ_CPU: smp cpu that delivered max freq in last sample */ INIT_COL(1, MxdCpu, [#], 5.0, 1, NO_FD, 0), /* 4. LOAD_REQUEST: cpu overhead requested by this program */ INIT_COL(1, LoadIn, [C0_%], 6.2, 1, NO_FD, 0), /* 5. LOAD_REALIZED: actual overall cpu overhead in the system */ INIT_COL(1, LoadOut, [C0_%], 7.2, 1, MSR_FD, 0), /* 6. PKG_POWER_RAPL: sysfs rapl power package scope. */ INIT_COL(1, pwrPkg0, [mWatt], 8.2, 1, NORMAL_FD, 0), INIT_COL(1, pwrPkg1, [mWatt], 8.2, 1, NORMAL_FD, 0), INIT_COL(1, pwrPkg2, [mWatt], 8.2, 1, NORMAL_FD, 0), INIT_COL(1, pwrPkg3, [mWatt], 8.2, 1, NORMAL_FD, 0), /* 7. PKG_POWER_LIMIT: sysfs rapl power limit (pkg). */ INIT_COL(0, PkgLmt, [mWatt], 7.2, 0.001, NORMAL_FD, 0), /* 8. PP0_POWER_RAPL: sysfs rapl power PP0 or core scope. */ INIT_COL(1, PwrCore, [mWatt], 8.2, 1, NORMAL_FD, 0), /* 9. PP1_POWER_RAPL: sysfs rapl power PP1 or uncore scope. */ INIT_COL(1, PwrGpu, [mWatt], 8.2, 1, NORMAL_FD, 0), /* 10. DRAM_POWER_RAPL: sysfs rapl power PP1 or uncore scope. */ INIT_COL(1, PwrDram, [mWatt], 7.2, 1, NORMAL_FD, 0), /* 11. CPU_DTS: cpu die temp */ INIT_COL(1, CpuDts, [DegC], 6.2, 0.001, NORMAL_FD, 0), /* 12 SOC_DTS: cpu die temp */ INIT_COL(1, SocDts, [DegC], 6.2, 0.001, NORMAL_FD, 0), }; int complete_path(char *path, char *compl) { FILE *fp; int sz; fp = popen(path, "r"); if (!fp) { perror("complete_path()"); dbg_print("popen failed. path %s\n", path); return -1; } sz = fread(compl, 1, 256, fp); if (!ferror(fp) && (sz != 0)) { compl[sz-1] = '\0'; } else { dbg_print("fread failed. path %s\n", path); pclose(fp); return -1; } pclose(fp); return 0; } int count_tzone_paths(char *base, char *match) { int sz; FILE *fp; /* generally sufficient */ char cmd[512]; char result[8]; /* find ${base}* -name 2>/dev/null*/ sprintf(cmd, "find %s* -name %s 2>/dev/null | wc -l", base, match); fp = popen(cmd, "r"); if (!fp) { perror("find_tzone_path()"); dbg_print("popen failed. base %s match %s\n", base, match); return -1; } sz = fread(cmd, 1, sizeof(cmd), fp); if (!sz) { pclose(fp); dbg_print("fread failed. cmd %s\n", cmd); return -1; } strncpy(result, cmd, sz); result[sz - 1] = '\0'; pclose(fp); return atoi(result); } int get_node_name(char *base, char *node, char *result) { int sz; FILE *fp; char path[512]; sprintf(path, "cat %s/%s 2>/dev/null", base, node); fp = popen(path, "r"); if (!fp) { perror("find_path()"); dbg_print("popen failed. path %s\n", path); return -1; } sz = fread(path, 1, sizeof(path), fp); if (!sz) { pclose(fp); dbg_print("fread failed. path %s\n", path); return -1; } if (result) { strncpy(result, path, sz); result[sz - 1] = '\0'; } pclose(fp); return 0; } int find_path(char *base, char *node, char *match, char *replace, char *buf) { int sz, fd, found; FILE *fp; char value[64], path[512]; char list[2048] = {0}; char *token, *loc; sprintf(path, "find %s* -name %s 2>/dev/null", base, node); fp = popen(path, "r"); if (!fp) { perror("find_path()"); dbg_print("popen failed. base %s\n", base); return -1; } sz = fread(list, 1, sizeof(list), fp); if (!sz) { pclose(fp); dbg_print("fread failed. path %s\n", path); return -1; } else list[sz] = '\0'; token = strtok(list, "\n"); do { fd = open(token, O_RDONLY); if (fd > 0) sz = read(fd, value, sizeof(value)); if (sz > 0) value[sz - 1] = '\0'; if (strcmp(value, match) == 0) { found = 1; break; } token = strtok(NULL, "\n"); } while (token); if (!found | !token) return -1; loc = strstr(token, node); if (!loc) return -1; sz = loc - token; strncpy(buf, token, sz); strncpy(buf+sz, replace, strlen(replace)); buf[sz + strlen(replace)] = '\0'; return 0; } int exit_cpu_thread, exit_io_thread; #define PAGE_SIZE_BYTES 4096 static char *page[2]; static char *active_pg; static char *dirty_pg; static int active_pg_filled, dirty_pg_filled; static int io_inprogress; static pthread_mutex_t pmutex = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t pcond = PTHREAD_COND_INITIALIZER; void initialize_log_page(void) { page[0] = (char *)malloc(PAGE_SIZE_BYTES * 8); page[1] = (char *)malloc(PAGE_SIZE_BYTES * 8); active_pg = page[0]; dirty_pg = page[1]; } void trigger_disk_io(void) { /* trigger write IO */ pthread_mutex_lock(&pmutex); pthread_cond_signal(&pcond); pthread_mutex_unlock(&pmutex); } void accumulate_flush_record(char *record, int sz) { if (PAGE_SIZE_BYTES - active_pg_filled < sz) return; /* * each old record has nul terminator. write new one starting * on last nul terminator. we just need one nul at the end */ if (active_pg_filled != 0) active_pg_filled -= 1; memcpy(active_pg + active_pg_filled, record, sz); active_pg_filled += sz; if (PAGE_SIZE_BYTES - active_pg_filled <= sz) { /* * swap active with dirty. Note this is not mutex locked. * the idea for separate buffer of some size is that they * never conflict. if we have conflict, the purpose of * delegating io operation to seperate thread is defeated. */ if (!io_inprogress) { /* swap buffers */ dirty_pg = active_pg; dirty_pg_filled = active_pg_filled; active_pg = dirty_pg; active_pg_filled = 0; /* IO the other page */ dbg_print("actvpg:%x, drtypg:%x\n", (void *)active_pg, (void *)dirty_pg); trigger_disk_io(); } else { /* purpose defeated. too-much/too-slow IO ? */ printf("**IO err: fix buffer size or too slow IO **\n"); return; } } } void page_write_disk(void *confg) { int ret; struct config *cfg = (struct config *)confg; sigset_t sigmask; sigfillset(&sigmask); ret = pthread_sigmask(SIG_BLOCK, &sigmask, NULL); if (ret) printf("page_write_disk: couldnt mask signals. err:%d\n", ret); do { int wr_sz; UNUSED(wr_sz); pthread_mutex_lock(&pmutex); pthread_cond_wait(&pcond, &pmutex); pthread_mutex_unlock(&pmutex); io_inprogress = 1; /* if we are exiting, just dump the active page */ if (!exit_cpu_thread) { wr_sz = write(cfg->log_file_fd, dirty_pg, dirty_pg_filled - 1); dbg_print("wrote %d io page bytes to log.\n", wr_sz); } else { /* reset to top of page */ wr_sz = write(cfg->log_file_fd, active_pg, active_pg_filled - 1); /* NULL terminate whatever data we have written */ dbg_print("wrote %d active pg bytes to log.\n", wr_sz); } io_inprogress = 0; } while (!exit_io_thread); pthread_cond_destroy(&pcond); pthread_mutex_destroy(&pmutex); pthread_exit(NULL); } struct config configpv; int need_maxed_cpu; void initialize_logger(void) { int i; char path[256]; for (i = 0; i < MAX_COL_NUM; i++) { if (!col_desc[i].report_enabled) { dbg_print(" %d.report_disabled for %s\n", i, col_desc[i].header_name); continue; } switch (i) { case FREQ_REALIZED: case LOAD_REALIZED: /* XXX: for gfx C0, create separate columns */ if (get_node_name("/dev/cpu/0", "msr", NULL) < 0) { col_desc[i].report_enabled = 0; } continue; /* No file descritor required */ case MAX_FREQ_CPU: if ((get_node_name("/dev/cpu/0", "msr", NULL) < 0) || !need_maxed_cpu) col_desc[i].report_enabled = 0; continue; /* No file descritor required */ case TIME_STAMP_MS: case LOAD_REQUEST: continue; /* No file descritor required */ case PKG0_POWER_RAPL: if (find_path(BASE_PATH_RAPL, "name", "package-0", "energy_uj", path)) { col_desc[i].report_enabled = 0; rapl_pp0_supported = 0; } else { rapl_pp0_supported = 1; } break; case PKG1_POWER_RAPL: if (find_path(BASE_PATH_RAPL, "name", "package-1", "energy_uj", path)) { col_desc[i].report_enabled = 0; } break; case PKG2_POWER_RAPL: if (find_path(BASE_PATH_RAPL, "name", "package-2", "energy_uj", path)) { col_desc[i].report_enabled = 0; } break; case PKG3_POWER_RAPL: if (find_path(BASE_PATH_RAPL, "name", "package-3", "energy_uj", path)) { col_desc[i].report_enabled = 0; } break; case PP0_POWER_RAPL: if (find_path(BASE_PATH_RAPL, "name", "core", "energy_uj", path)) col_desc[i].report_enabled = 0; break; case PP1_POWER_RAPL: if (find_path(BASE_PATH_RAPL, "name", "uncore", "energy_uj", path)) col_desc[i].report_enabled = 0; break; case DRAM_POWER_RAPL: if (find_path(BASE_PATH_RAPL, "name", "dram", "energy_uj", path)) col_desc[i].report_enabled = 0; break; case CPU_DTS: if (find_path(BASE_PATH_CPUDTS, "name", "coretemp", "temp2_input", path)) col_desc[i].report_enabled = 0; break; case SOC_DTS: if (find_path(BASE_PATH_TZONE, "type", "x86_pkg_temp", "temp", path)) col_desc[i].report_enabled = 0; break; } /* close only on exit */ col_desc[i].poll_fd = open(path, 0, "r"); if (col_desc[i].poll_fd < 0) { dbg_print("disabling column %s\n", col_desc[i].header_name); col_desc[i].report_enabled = 0; } } initialize_log_page(); return; } static char *log_header; struct timespec plog_last_tm, first_tm; int plog_poll_sec, plog_poll_nsec; int duration_sec, duration_nsec; void initialize_log_clock(void) { struct timespec tm; if (clock_gettime(CLOCK_MONOTONIC, &tm)) perror("clock_gettime"); first_tm.tv_sec = plog_last_tm.tv_sec = tm.tv_sec; first_tm.tv_nsec = plog_last_tm.tv_nsec = tm.tv_nsec; } uint64_t diff_ns(struct timespec *ts_then, struct timespec *ts_now) { uint64_t diff = 0; if (ts_now->tv_sec > ts_then->tv_sec) { diff = (ts_now->tv_sec - ts_then->tv_sec) * NSEC_PER_SEC; diff = diff - ts_then->tv_nsec + ts_now->tv_nsec; } else { diff += ts_now->tv_nsec - ts_then->tv_nsec; } return diff; } int update_amperf_diffs(unsigned int *aperf_diff, unsigned int *mperf_diff, unsigned int *tsc_diff, int need_maxed_cpu) { int fd, maxed_cpu, c, i, max_load, next_max_load; uint64_t aperf_raw, mperf_raw, tsc_raw; unsigned int a_diff[MAX_CPU_REPORTS], m_diff[MAX_CPU_REPORTS]; tsc_raw = read_msr(dev_msr_fd[0], (uint32_t)MSR_IA32_TSC); *tsc_diff = get_diff_tsc(tsc_raw); for (c = 0, i = 0; c < CPU_SETSIZE && i < MAX_CPU_REPORTS; c++) { if (!CPU_ISSET(c, &configpv.cpumask)) continue; /* simple to log for cpu0 */ fd = dev_msr_fd[i]; aperf_raw = read_msr(fd, (uint32_t)MSR_IA32_APERF); a_diff[i] = cpu_get_diff_aperf(aperf_raw, i); mperf_raw = read_msr(fd, (uint32_t)MSR_IA32_MPERF); m_diff[i] = cpu_get_diff_mperf(mperf_raw, i); m_diff[i] = m_diff[i] == 0 ? 1 : m_diff[i]; i++; } if (!need_maxed_cpu) { *aperf_diff = a_diff[0]; *mperf_diff = m_diff[0]; return 0; } /* * find the cpu to which max load belonged. * Ideally for discrete voltage rails systems, we could * track max frequency. Until them, for most other systems * it is simple to track max load. * This however assumes that governance has freq as monotonically * proportional to load. */ max_load = 100*m_diff[0]/(*tsc_diff); maxed_cpu = 0; while (--i) { next_max_load = 100*m_diff[i]/(*tsc_diff); if (max_load >= next_max_load) continue; else { max_load = next_max_load; maxed_cpu = i; } } *aperf_diff = a_diff[maxed_cpu]; *mperf_diff = m_diff[maxed_cpu]; return maxed_cpu; } #define LOG_HEADER_SZ 1024 int first_log = 1; uint64_t pp0_initial_energy, soc_initial_energy[4]; unsigned int pp0_diff_uj, soc_diff_uj[4]; int rapl_pp0_supported; void do_logging(float *duty_cycle) { char buf[64]; char final_buf[512]; char val_fmt[16]; char delim[] = ", "; log_col_t i; int sz, sz1; struct timespec tm; if (clock_gettime(CLOCK_MONOTONIC, &tm)) perror("clock_gettime"); /* duration_* is the total time this tool runs */ if (!is_time_remaining(CLOCK_MONOTONIC, &first_tm, duration_sec, duration_nsec)) exit_cpu_thread = 1; /* we log once in plog_poll_* interval */ if (!first_log && is_time_remaining(CLOCK_MONOTONIC, &plog_last_tm, plog_poll_sec, plog_poll_nsec)) return; plog_last_tm.tv_sec = tm.tv_sec; plog_last_tm.tv_nsec = tm.tv_nsec; int c, pkg_num; unsigned int aperf_diff; unsigned int mperf_diff; unsigned int tsc_diff; unsigned int maxed_cpu; if (dev_msr_supported) maxed_cpu = update_amperf_diffs(&aperf_diff, &mperf_diff, &tsc_diff, need_maxed_cpu); for (i = 0; i < MAX_COL_NUM; i++) { if (!col_desc[i].report_enabled) continue; if (col_desc[i].fd_type == NORMAL_FD) { lseek(col_desc[i].poll_fd, 0L, SEEK_SET); sz = read(col_desc[i].poll_fd, buf, 64); if (sz == -1) { perror("read poll_fd 1"); printf(" col desc read fd err %d\n", i); } } switch (i) { case TIME_STAMP_MS: col_desc[i].value = diff_ns(&first_tm, &plog_last_tm)/1000000; break; case LOAD_REQUEST: col_desc[i].value = *duty_cycle; break; case LOAD_REALIZED: /* real C0 = delta-mperf/delta-tsc */ col_desc[i].value = (float) mperf_diff*100/tsc_diff; if (first_log) col_desc[i].value = 1; break; case MAX_FREQ_CPU: col_desc[i].value = maxed_cpu; break; case FREQ_REALIZED: /* real freq = TSC* delta-aperf/delta-mperf */ col_desc[i].value = (float)cpu_khz * aperf_diff/mperf_diff; break; case PKG0_POWER_RAPL: pkg_num = i - PKG0_POWER_RAPL; if (first_log) soc_initial_energy[pkg_num] = atoll(buf); soc_diff_uj[pkg_num] = atoll(buf) - soc_initial_energy[pkg_num]; col_desc[i].value = (float) rapl_ediff_pkg0(atoll(buf))/ configpv.poll_period; break; case PKG1_POWER_RAPL: pkg_num = i - PKG1_POWER_RAPL; if (first_log) soc_initial_energy[pkg_num] = atoll(buf); soc_diff_uj[pkg_num] = atoll(buf) - soc_initial_energy[pkg_num]; col_desc[i].value = (float) rapl_ediff_pkg1(atoll(buf))/ configpv.poll_period; break; case PKG2_POWER_RAPL: pkg_num = i - PKG2_POWER_RAPL; if (first_log) soc_initial_energy[pkg_num] = atoll(buf); soc_diff_uj[pkg_num] = atoll(buf) - soc_initial_energy[pkg_num]; col_desc[i].value = (float) rapl_ediff_pkg2(atoll(buf))/ configpv.poll_period; break; case PKG3_POWER_RAPL: pkg_num = i - PKG3_POWER_RAPL; if (first_log) soc_initial_energy[pkg_num] = atoll(buf); soc_diff_uj[pkg_num] = atoll(buf) - soc_initial_energy[pkg_num]; col_desc[i].value = (float) rapl_ediff_pkg3(atoll(buf))/ configpv.poll_period; break; /* pkg_num = i - PKG0_POWER_RAPL; if (first_log) soc_initial_energy[pkg_num] = atoll(buf); soc_diff_uj[pkg_num] = atoll(buf) - soc_initial_energy[pkg_num]; int (*fn) (long long); #define xstr(s) str(s) #define str(s) #s #define fncat(b) (xstr(b)) char outfn[36]; sprintf(outfn,"%s%d","rapl_ediff_pkg",pkg_num); fn = str(outfn); printf("fn is %s\n", outfn); col_desc[i].value = (float) (fn)(atoll(buf))/ configpv.poll_period; break; */ case PP0_POWER_RAPL: if (first_log) pp0_initial_energy = atoll(buf); pp0_diff_uj = atoll(buf) - pp0_initial_energy; col_desc[i].value = (float) rapl_ediff_cpu(atoll(buf))/ configpv.poll_period; break; case PP1_POWER_RAPL: col_desc[i].value = (float) rapl_ediff_gpu(atoll(buf))/ configpv.poll_period; break; case DRAM_POWER_RAPL: col_desc[i].value = (float) rapl_ediff_dram(atoll(buf))/ configpv.poll_period; break; case PKG_POWER_LIMIT: case CPU_DTS: case SOC_DTS: col_desc[i].value = atoi(buf); break; /* dead code. happy compiler */ case MAX_COL_NUM: break; } col_desc[i].value *= col_desc[i].unit_multiplier; } if (!log_header) { log_header = malloc(LOG_HEADER_SZ * sizeof(char)); if (!log_header) { perror("Failed to malloc log_header"); exit(EXIT_FAILURE); } char hdr_fmt[16]; int dash_len; sprintf(log_header, "%c", '#'); sz = 1; for (i = 0; i < MAX_COL_NUM; i++) { if (!col_desc[i].report_enabled) continue; sprintf(hdr_fmt, "%%%ds%s", atoi(col_desc[i].fmt), delim); sz1 = sprintf(log_header + sz, hdr_fmt, col_desc[i].header_name); sz += sz1; } dash_len = sz; sz = sz - sizeof(delim) + 2; log_header[sz - 1] = '\n'; sprintf(log_header+sz, "%c", '#'); sz += 1; for (i = 0; i < MAX_COL_NUM; i++) { if (!col_desc[i].report_enabled) continue; sprintf(hdr_fmt, "%%%ds%s", atoi(col_desc[i].fmt), delim); sz1 = sprintf(log_header + sz, hdr_fmt, col_desc[i].unit); sz += sz1; } sz = sz - sizeof(delim) + 2; log_header[sz - 1] = '\n'; sprintf(log_header+sz, "%c", '#'); sz += 1; for (i = 0; i < dash_len; i++) { sz1 = sprintf(log_header + sz, "%c", '-'); sz += sz1; } sz = sz - sizeof(delim) + 2; log_header[sz - 1] = '\n'; log_header[sz] = '\0'; sz = write(configpv.log_file_fd, log_header, sz); if (!configpv.verbose) printf("report being logged to %s\n", configpv.log_file_name); else printf("%s\n", log_header); } sz = 0; for (i = 0; i < MAX_COL_NUM; i++) { if (!col_desc[i].report_enabled) continue; sprintf(val_fmt, "%%%sf%s", col_desc[i].fmt, delim); sz1 = sprintf(final_buf + sz, val_fmt, col_desc[i].value); sz += sz1; } /* erase the last delimiter */ sz = sz - sizeof(delim) + 2; final_buf[sz-1] = '\n'; final_buf[sz] = '\0'; if (configpv.verbose) printf("%s", final_buf); else accumulate_flush_record(final_buf, sz+1); first_log = 0; } logger.h000755 000766 000024 00000004051 13163231534 013474 0ustar00numubeenstaff000000 000000 /* * Copyright (c) 2017, Intel Corporation. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope 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. * * Author: Noor ul Mubeen */ #ifndef _LOGGER_H_ #define _LOGGER_H_ #include #include #ifdef DEBUG #define dbg_print(fmt...) printf(fmt) #else #define dbg_print(fmt...) ((void)0) #endif #define UNUSED(expr) do { (void)(expr); } while (0) #define MSEC_TO_SEC(x) (x/1000) #define REMAINING_MS_TO_NS(x) ((x % 1000) * 1000000) #define SMP_MAX_FREQ_ENABLED 1 #define MAX_CPU_REPORTS (4 * SMP_MAX_FREQ_ENABLED) typedef enum log_col {TIME_STAMP_MS, FREQ_REALIZED, MAX_FREQ_CPU, LOAD_REQUEST, LOAD_REALIZED, PKG0_POWER_RAPL, PKG1_POWER_RAPL, PKG2_POWER_RAPL, PKG3_POWER_RAPL, PKG_POWER_LIMIT, PP0_POWER_RAPL, PP1_POWER_RAPL, DRAM_POWER_RAPL, CPU_DTS, SOC_DTS, MAX_COL_NUM,} log_col_t; enum col_processing { NO_FD, NORMAL_FD, MSR_FD }; struct log_col_desc { int report_enabled; char header_name[32]; char unit[32]; char fmt[32]; float unit_multiplier; enum col_processing fd_type; int poll_fd; float value; }; extern int rapl_pp0_supported; extern struct config configpv; extern need_maxed_cpu; extern int plog_poll_sec, plog_poll_nsec, duration_sec, duration_nsec; extern void do_logging(float *); extern void initialize_logger(void); extern void initialize_log_clock(void); extern void page_write_disk(void *); extern void trigger_disk_io(void); extern uint64_t diff_ns(struct timespec *, struct timespec *); extern int update_amperf_diffs(unsigned int *a, unsigned int *m, unsigned int *t, int max); #endif parse_config.c000755 000766 000024 00000027666 13163231534 014670 0ustar00numubeenstaff000000 000000 /* * parse_config.c: deals with cmd line arg parsing & default value population * * Copyright (c) 2017, Intel Corporation. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope 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. * * Author: Noor ul Mubeen */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include "parse_config.h" #include "logger.h" static struct option long_options[] = { {"cpumask", 1, 0, 'C'}, {"duration", 1, 0, 'd'}, {"gpumask", 1, 0, 'G'}, {"log-file", 1, 0, 'l'}, {"poll-period", 1, 0, 'p'}, {"shape-func", 1, 0, 's'}, {"verbose", 1, 0, 'v'}, {"version", 1, 0, 'V'}, {"help", 0, 0, 'h'}, {0, 0, 0, 0} }; void print_usage(char *prog) { printf("usage:\n"); printf("%s [options ]\n", prog); printf("\tSupported options are:\n"); printf("\t-C|--cpumask\t\t hex bit mask of cpu selected. (e.g., a1 selects cpu 0,5,7. default: every online cpu. Max 1024)\n"); printf("\t-p|--poll-period\t (ms) for logging (default: 500 ms)\n"); printf("\t-d|--duration\t\t (ms) to run the tool (default: 3600000 i.e., 1hr)\n"); printf("\t-l|--log-file\t\t (default: %s)\n", default_log_file); printf("\t-v|--verbose\t\tenables verbose mode (default: disabled)\n"); printf("\t-V|--Version\t\tprints version when specified\n"); printf("\t-T|--track-max-cpu\tTrack the cpu# with maxed freq during poll period\n"); printf("\t-h|--help\t\tprints usage when specified\n"); printf("\t-s|--shape-func\t\t\n"); printf("\tWhere supported power shape functions & args are:\n"); printf("\t\t\t\t"); printf("where v is load step height [C0%%].(default shape: single-step,0.1)\n"); printf("\t\t\t\t"); printf("where w is wavelength [seconds] and a is the amplitude (max load %%)\n"); printf("\t\t\t"); printf("where v is load step height [C0%%], u is step length (sec)\n"); printf("\t\t\t"); printf("where v is load step height [C0%%], u is step length (sec)\n"); printf("\t\t\t\twhere m is the slope (load/sec)\n"); printf("\t\t\t\tslope m (load/sec) reversed after max a%% or min(0.1)%%\n"); printf("\nexample 1: use psst just for logging various power/thermal parameters\n"); printf("\t $./psst #same as $./psst -s single-step,0.1 -p 500 -v\n"); printf("\nexample 2: linear ramp CPU power with slope 3%% usage-per-sec" " applied for cpu1 & 3.\n\t poll and report" "every 700mS. output on termial. run for 33 sec\n"); printf("\t $./psst -s linear-ramp,3 -C a -p 700 -d 33000 -v\n"); } static int populate_online_cpumask(cpu_set_t *cpumask); static void verbose_prints(struct config *configp); int populate_default_config(struct config *configp) { if (!configp->shape_func[0]) strncpy(configp->shape_func, "single-step,0.1", 15); if (!configp->v_unit) configp->v_unit = 'C'; if (cpu_stress_opt == UNDEFINED) populate_online_cpumask(&configp->cpumask); if (!configp->gpumask) configp->gpumask = 0x0; if (!configp->memmask) configp->memmask = 0x0; if (configp->memmask || configp->gpumask) { /* we want to use cpu0 for non-cpu submitter * hence we cant have any regular stress function * request on cpu 0 at the same time */ if (CPU_ISSET(0, &configp->cpumask) && (cpu_stress_opt == WELL_DEFINED)) { printf("can't stress cpu0 (-C xx) along with -G or -M\n"); return 0; } else { cpu_stress_opt = DONT_STRESS_CPU0; CPU_SET(0, &configp->cpumask); } } if (!configp->log_file_name[0]) { strncpy(configp->log_file_name, default_log_file, sizeof(default_log_file)+1); } /* verbose & version option are not turned on by default */ if (!configp->cpu_freq) configp->cpu_freq = -1; if (!configp->log_file_fd) { configp->log_file_fd = open(configp->log_file_name, O_RDWR|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP); if (configp->log_file_fd == -1) perror("log file"); } if (!configp->poll_period) configp->poll_period = 500; /* (ms) */ if (!configp->duration) configp->duration = 3600000; /* default 60min */ initialize_logger(); if (configp->verbose) verbose_prints(configp); return 1; } static int xchar_to_int(char x) { if (isalpha(x)) return toupper(x) - 55; if (isdigit(x)) return x - 48; return -1; } /* cpuset procfs reports online cpu in this format: * 0-4,7 : to mean 0,1,2,3,4 & 7 are online */ static int cpuset_to_bitmap(char *buf, cpu_set_t *cpumask) { int k; char *token, *subtoken, *pos; char *save1, *save2, *token_copy; pos = strchr(buf, '\n'); pos[0] = '\0'; /* e.g: 3,5-11 */ token = strtok_r(buf, ",", &save1); do { if (!token) break; token_copy = strdup(token); subtoken = strtok_r(token_copy, "-", &save2); /* update 3 (and 5 in next pass) ... */ if (!subtoken) { k = atoi(token); CPU_SET(k, cpumask); continue; } else { k = atoi(subtoken); CPU_SET(k, cpumask); pos = token + strlen(subtoken) + 1; while (++k <= atoi(pos)) CPU_SET(k, cpumask); } token = strtok_r(NULL, ",", &save1); } while (token); return 0; } static int populate_online_cpumask(cpu_set_t *cpumask) { char buf[64]; FILE *fp; /* Open the command for reading in pipe. */ fp = popen("cat /sys/devices/system/cpu/online", "r"); if (fp == NULL) { printf("Failed to get online cpu list\n"); return -1; } if (!fread(buf, 1, 64, fp)) { printf("populate_online_cpumask: fread failed\n"); return -1; } pclose(fp); cpuset_to_bitmap(buf, cpumask); return 0; } cpu_stress_opt_t cpu_stress_opt = UNDEFINED; static int set_cpu_mask(char *buf, struct config *configp) { int arg_bytes = strlen(buf); if ((arg_bytes == 1) && (buf[0] == '0')) { /* user wants to: not stress any cpu. * lets translate that to cpu0 submitter work */ cpu_stress_opt = DONT_STRESS_CPU0; CPU_SET(0, &(configp->cpumask)); return 0; } if ((arg_bytes * 4) > CPU_SETSIZE) { printf("max cpu supported is %d\n", CPU_SETSIZE); return -1; } else { /* arg "a1" or 0000.1010 0000.0001 selects cpu 0,5,7 */ int i, j, k = 0; for (i = arg_bytes - 1; i > -1; i--) { for (j = 0; j < 4; j++, k++) { if (!isxdigit(buf[i])) { printf("Invalid arg to -C\n"); return -1; } if (xchar_to_int(buf[i]) & (1<cpumask)); } } cpu_stress_opt = WELL_DEFINED; } return 0; } float Rmax, Rmin; int parse_cmd_config(int ac, char **av, struct config *configp) { int c, option_index; char buf[128]; memset(configp, 0, sizeof(struct config)); CPU_ZERO(&configp->cpumask); if (ac == 1) configp->verbose = 1; while ((c = getopt_long(ac, av, "s:G:C:E:l:p:d:hvVT", long_options, &option_index)) != -1) { /* XXX check optarg valid */ switch (c) { case 'l': strncpy(configp->log_file_name, optarg, sizeof(configp->log_file_name) - 1); break; case 'p': sscanf(optarg, "%d", &configp->poll_period); break; case 'd': sscanf(optarg, "%d", &configp->duration); break; case 'C': sscanf(optarg, "%s", buf); if (set_cpu_mask(buf, configp) < 0) return 0; break; case 'G': sscanf(optarg, "%x", &configp->gpumask); break; case 'v': configp->verbose = 1; break; case 'T': need_maxed_cpu = 1; break; case 'V': configp->version = 1; break; case 's': strncpy(configp->shape_func, optarg, sizeof(configp->shape_func)); break; case 'h': case '?': default: print_usage("psst"); return 0; } /* switch */ } /* while */ if (optind < ac) { print_usage("psst"); return 0; } return 1; } static void verbose_prints(struct config *configp) { int i, masklength; printf("Verbose mode ON\n"); dbg_print("v-unit is: %c\n", configp->v_unit); if (configp->v_unit != 'C') dbg_print("option not supported\n"); printf("CPU domain. Following %d cores selected:\n", CPU_COUNT(&configp->cpumask)); for (i = 0; i < CPU_SETSIZE; i++) { if (CPU_ISSET(i, &configp->cpumask)) { printf("\tcpu %d", i); if (i == 0) printf("\t[%s]\n", (cpu_stress_opt == DONT_STRESS_CPU0) ? "as work submitter" : "was online or chosen"); else printf("\t[%s]\n", "was online or chosen"); } } printf("\n"); printf("poll period %dms\n", configp->poll_period); printf("run duration %dms\n", configp->duration); printf("Log file path: %s\n", configp->log_file_name); printf("power curve shape: %s\n", configp->shape_func); printf("\n"); } int parse_ei_parms(char *max_min, float *max, float *min) { char *token; char *delimitor = ","; char *running; running = strdup(max_min); token = strtok(running, delimitor); if (token) *max = atof(token); else return 0; token = strtok(NULL, delimitor); if (token) *min = atof(token); else return 0; return 1; } int parse_power_shape(char *shape, data_t *pst) { char *token; char *delimitor = ","; char *running; if (!strcmp(shape, "")) return 0; /* use strdupa to auto free on stack exit */ running = strdup(shape); token = strtok(running, delimitor); if (!strcmp(token, "single-step")) { token = strtok(NULL, delimitor); if (token) { pst->psn = SINGLE_STEP; pst->psa.single_step.v_units = atof(token); dbg_print(" single step yunit %f\n", pst->psa.single_step.v_units); } else { return 0; } } else if (!strcmp(token, "stair-case")) { token = strtok(NULL, delimitor); if (token) { pst->psn = STAIR_CASE; pst->psa.staircase.y_height = atof(token); token = strtok(NULL, delimitor); if (token) { pst->psa.staircase.x_length = atof(token); dbg_print("staircase x, y %f ,%d\n", pst->psa.staircase.y_height, pst->psa.staircase.x_length); } } else { return 0; } } else if (!strcmp(token, "sinosoid")) { token = strtok(NULL, delimitor); if (token) { pst->psn = SINOSOID; pst->psa.sinosoid.x_wavelength = atof(token); token = strtok(NULL, delimitor); if (token) { pst->psa.sinosoid.y_amplitude = atof(token); dbg_print("sinosoid wavelength %d amplitude %f\n", pst->psa.sinosoid.x_wavelength, pst->psa.sinosoid.y_amplitude); } } else { return 0; } } else if (!strcmp(token, "single-pulse")) { token = strtok(NULL, delimitor); if (token) { pst->psn = SINGLE_PULSE; pst->psa.single_pulse.y_height = atof(token); token = strtok(NULL, delimitor); if (token) { pst->psa.single_pulse.x_length = atof(token); dbg_print("singlepulse x, y %f ,%d\n", pst->psa.single_pulse.y_height, pst->psa.single_pulse.x_length); } } else { return 0; } } else if (!strcmp(token, "linear-ramp")) { token = strtok(NULL, delimitor); if (token) { pst->psn = LINEAR_RAMP; pst->psa.linear_ramp.slope_y_per_sec = atof(token); dbg_print(" liner ramp %f\n", pst->psa.linear_ramp.slope_y_per_sec); } else { return 0; } } else if (!strcmp(token, "saw-tooth")) { token = strtok(NULL, delimitor); if (token) { pst->psn = SAW_TOOTH; pst->psa.saw_tooth.slope_y_per_sec = atof(token); token = strtok(NULL, delimitor); if (token) pst->psa.saw_tooth.max_y = atof(token); dbg_print(" saw tooth slope %.3f, max %.3f\n", pst->psa.saw_tooth.slope_y_per_sec, pst->psa.saw_tooth.max_y); } else { return 0; } } else if (!strcmp(token, "growth-curve")) { /* pst->psn = GROWTH_CURVE; */ printf("Growth Curve: Not implemented yet!\n"); return 0; } else if (!strcmp(token, "decay-curve")) { /* pst->psn = DECAY_CURVE; */ printf("Decay Curve: Not implemented yet!\n"); return 0; } else { return 0; } return 1; } parse_config.h000755 000766 000024 00000003645 13163231534 014664 0ustar00numubeenstaff000000 000000 /* * Copyright (c) 2017, Intel Corporation. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope 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. * * Author: Noor ul Mubeen */ #ifndef _PARSECONFIG_H_ #define _PARSECONFIG_H_ #define _GNU_SOURCE #include #include #include #include #include #include #include #include "psst.h" #include "logger.h" #define BASE_PATH_RAPL \ "/sys/devices/virtual/powercap/intel-rapl/intel-rapl" #define BASE_PATH_TZONE "/sys/devices/virtual/thermal/thermal_zone" #define BASE_PATH_CPUDTS \ "/sys/devices/platform/coretemp.0" /* paths & cmd specific to Android */ #if defined(_ANDROID_) #define default_log_file "/data/pbot.csv" /* paths & cmd specific to non-android Linux */ #elif defined(_LINUX_) #define default_log_file "/var/log/pbot.csv" #endif struct config { char v_unit; cpu_set_t cpumask; unsigned int gpumask; unsigned int memmask; unsigned int cpu_freq; unsigned int verbose; unsigned int version; char log_file_name[80]; unsigned int log_file_fd; char shape_func[20]; unsigned int poll_period; unsigned int duration; }; typedef enum cpu_stress_option { DONT_STRESS_CPU0, UNDEFINED, WELL_DEFINED } cpu_stress_opt_t; extern cpu_stress_opt_t cpu_stress_opt; extern int parse_cmd_config(int ac, char **av, struct config *configp); extern int populate_default_config(struct config *configp); extern int parse_power_shape(char *shape, data_t *pst); extern int avail_freq_item(int item); extern float Rmax, Rmin, Qmax, Qmin; #endif perf_msr.c000755 000766 000024 00000005320 13163231534 014025 0ustar00numubeenstaff000000 000000 /* * perf_msr.c: Intel cpu aperf/mperf msr counter interface * * Copyright (c) 2017, Intel Corporation. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope 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. * * Author: Noor ul Mubeen */ #include #include #include #include #include #include #include "perf_msr.h" uint64_t read_msr(int fd, uint32_t reg) { uint64_t data; if (pread(fd, &data, sizeof(data), reg) != sizeof(data)) { dbg_print("rdmsr: pread reg: %0x\n", reg); return -1; } return data; } int dev_msr_supported = -1; int cpu_khz; int initialize_dev_msr(int c) { int fd; char msr_file[128]; sprintf(msr_file, "/dev/cpu/%d/msr", c); fd = open(msr_file, O_RDONLY); if (fd < 0) { perror("rdmsr: open"); return -1; } return fd; } int initialize_cpu_khz(int fd) { uint64_t msr_val; msr_val = read_msr(fd, (uint32_t)MSR_PLATFORM_INFO); if (msr_val != -1) { /* most x86 have cpu_clk = ratio * freq_multiplier */ cpu_khz = ((msr_val >> 8) & 0xffUll) * 1000; } else { printf("***cant read MSR_PLATFORM_INFO***\n"); return -1; } return 0; } /* routine to evaluate & store a global msr value's diff */ static uint64_t last_tsc; #define VAR(a, b) (a##b) #define generate_msr_diff(scope) \ unsigned int get_diff_##scope(uint64_t cur_value) \ { \ uint64_t diff; \ diff = (VAR(last_, scope) == 0) ? 0 : (cur_value - VAR(last_, scope)); \ VAR(last_, scope) = cur_value; \ if (diff < 0) { \ return 1; \ } \ return (unsigned int)diff; \ } generate_msr_diff(tsc); static uint64_t last_aperf[MAX_CPU_REPORTS]; static uint64_t last_mperf[MAX_CPU_REPORTS]; /* routine to evaluate & store a per-cpu msr value's diff */ #define VARI(a, b, i) a##b[i] #define cpu_generate_msr_diff(scope) \ unsigned int cpu_get_diff_##scope(uint64_t cur_value, int instance) \ { \ uint64_t diff; \ diff = (VARI(last_, scope, instance) == 0) ? \ 0 : (cur_value - VARI(last_, scope, instance));\ VARI(last_, scope, instance) = cur_value; \ if (diff < 0) { \ return 1; \ } \ return (unsigned int)diff; \ } cpu_generate_msr_diff(aperf); cpu_generate_msr_diff(mperf); perf_msr.h000755 000766 000024 00000002232 13163231534 014031 0ustar00numubeenstaff000000 000000 /* * Copyright (c) 2017, Intel Corporation. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope 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. * * Author: Noor ul Mubeen */ #ifndef _PERF_MSR_ #define _PERF_MSR_ #include #include #include "logger.h" #define MSR_IA32_MPERF 0xe7 #define MSR_IA32_APERF 0xe8 #define MSR_IA32_TSC 0x10 #define MSR_PLATFORM_INFO 0xce #define MSR_PERF_STATUS 0x198 extern int cpu_khz; extern int dev_msr_fd[MAX_CPU_REPORTS]; extern int dev_msr_supported; extern uint64_t read_msr(int, uint32_t); extern int initialize_dev_msr(int c); extern int initialize_cpu_khz(int fd); extern unsigned int cpu_get_diff_aperf(uint64_t a, int i); extern unsigned int cpu_get_diff_mperf(uint64_t m, int i); extern unsigned int get_diff_tsc(uint64_t t); #endif psst.c000755 000766 000024 00000033044 13163231534 013205 0ustar00numubeenstaff000000 000000 /* * psst.c: main, creates threads & delegate work to SoC. * * Copyright (c) 2017, Intel Corporation. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope 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. * * Author: Noor ul Mubeen */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "parse_config.h" #include "psst.h" #include "logger.h" #include "rapl.h" #include "perf_msr.h" void print_version(void) { printf("psst version %s\n", VERSION); } int exit_cpu_thread, exit_io_thread; static int nr_threads; /* * Any additional stress function goes here. * However, the motive of this tool is reasonable peak power & its * controllabilty. both motives are met using meaningful work. */ static void cpu_work(int on_time_us) { (void)on_time_us; /* please the complier */ return; } int ts_compare(struct timespec *time1, struct timespec *time2) { if (time1->tv_sec < time2->tv_sec) return -1; /* Less than. */ else if (time1->tv_sec > time2->tv_sec) return 1; /* Greater than. */ else if (time1->tv_nsec < time2->tv_nsec) return -1; /* Less than. */ else if (time1->tv_nsec > time2->tv_nsec) return 1; /* Greater than. */ else return 0; /* Equal. */ } int is_time_remaining(clockid_t clk, struct timespec *ts_last, int sec, int nsec) { struct timespec ts_now, ts_later; ts_later.tv_sec = ts_last->tv_sec + sec; ts_later.tv_nsec = ts_last->tv_nsec + nsec; if (ts_later.tv_nsec > NSEC_PER_SEC) { ts_later.tv_sec++; ts_later.tv_nsec -= NSEC_PER_SEC; } clock_gettime(clk, &ts_now); if (ts_compare(&ts_now, &ts_later) < 0) return 1; else return 0; } int timespec_to_msec(struct timespec *t) { return t->tv_sec*1000 + t->tv_nsec/1000000; } unsigned long clockdiff_now_ns(clockid_t clk, struct timespec *ts_then) { struct timespec ts_now; clock_gettime(clk, &ts_now); return diff_ns(ts_then, &ts_now); } int set_affinity(int pr) { cpu_set_t mask; CPU_ZERO(&mask); CPU_SET(pr, &mask); if (sched_setaffinity(0, sizeof(cpu_set_t), &mask) != 0) perror("sched_setaffiny:"); return 0; } int set_sched_priority(int min_max) { int policy; struct sched_param param; pthread_getschedparam(pthread_self(), &policy, ¶m); if (min_max) param.sched_priority = sched_get_priority_max(policy); else param.sched_priority = sched_get_priority_min(policy); pthread_setschedparam(pthread_self(), policy, ¶m); return 0; } int cap_v_unit(float *v_unitp, float max, float min) { if (*v_unitp >= (float)max) { *v_unitp = max; return 1; } else if (*v_unitp <= (float)min) { *v_unitp = min; return 1; } return 0; } #define PS_MIN_POLL_MS (50) int power_shaping(ps_t *ps, float *v_unit) { float y_delta, rad; int x_delta = 0; long long time_ms; switch (ps->psn) { case LINEAR_RAMP: /* recalculate every PS_MIN_POLL_MS */ x_delta = PS_MIN_POLL_MS; if (is_time_remaining(CLOCK_MONOTONIC, &ps->last, 0, x_delta * 1000000)) return 0; y_delta = ps->psa.linear_ramp.slope_y_per_sec / (MSEC_PER_SEC/x_delta); *v_unit = *v_unit + y_delta; if (cap_v_unit(v_unit, MAX_LOAD, MIN_LOAD)) return 0; break; case SAW_TOOTH: x_delta = PS_MIN_POLL_MS; if (is_time_remaining(CLOCK_MONOTONIC, &ps->last, 0, x_delta * 1000000)) return 0; if ((*v_unit >= (float)ps->psa.saw_tooth.max_y) || (*v_unit <= (float)MIN_LOAD)) { ps->psa.linear_ramp.slope_y_per_sec *= -1; } y_delta = ps->psa.linear_ramp.slope_y_per_sec / (MSEC_PER_SEC/x_delta); *v_unit = *v_unit + y_delta; cap_v_unit(v_unit, ps->psa.saw_tooth.max_y, MIN_LOAD); break; case STAIR_CASE: /* recalculate every step-stride seconds */ x_delta = ps->psa.staircase.x_length; if (is_time_remaining(CLOCK_MONOTONIC, &ps->last, x_delta, 0)) return 0; y_delta = ps->psa.staircase.y_height; *v_unit = *v_unit + y_delta; cap_v_unit(v_unit, MAX_LOAD, MIN_LOAD); break; case SINOSOID: x_delta = PS_MIN_POLL_MS; if (is_time_remaining(CLOCK_MONOTONIC, &ps->last, 0, x_delta * 1000000)) return 0; /* 2*pi radians == 360 degree == 1 wavelength*/ time_ms = ps->last.tv_sec * 1000 + ps->last.tv_nsec/1000000; x_delta = time_ms % (ps->psa.sinosoid.x_wavelength * 1000); rad = (float)(2*3.14159 * x_delta)/(ps->psa.sinosoid.x_wavelength * 1000); /* scale sin(x) to +/-amplituted/2 excursions */ *v_unit = ps->psa.sinosoid.y_amplitude * (1 + sinf(rad))/2; /* duty cycle of 0.00 does not make sense. offset by +1% */ *v_unit += 1; break; case SINGLE_PULSE: if (*v_unit == 0.1) /* pulse ended */ return 0; /* rising edge of pulse */ if (*v_unit != ps->psa.single_pulse.y_height) { *v_unit = ps->psa.single_pulse.y_height; } else { x_delta = (int)ps->psa.single_pulse.x_length; x_delta = (x_delta == 0) ? 1 : x_delta; if (is_time_remaining(CLOCK_MONOTONIC, &ps->last, x_delta, 0)) { return 0; } else { /* pulse ended now */ dbg_print(" pulse ended %f after time %d\n", *v_unit, x_delta); *v_unit = 0; } } break; default: /* single step */ *v_unit = ps->psa.single_step.v_units; break; } /* if we din't return from above cases, we chaged shape. Update ps */ if (clock_gettime(CLOCK_MONOTONIC, &ps->last)) perror("clock_gettime"); return 1; } static int chore_thread = -1; pthread_mutex_t plock; #define START_DELAY 0 unsigned int pp0_diff_uj, soc_diff_uj; static void work_fn(void *data) { int i = 0; int start_pending = 0; int ret, tick_usec, on_time_us, off_time_us, pr; int cpu_work_exist = 0; float duty_cycle, last_duty_cycle; struct timespec ts; static int start_ms; ps_t ps; sigset_t maskset; sigfillset(&maskset); ret = pthread_sigmask(SIG_BLOCK, &maskset, NULL); if (ret) printf("Couldn't mask signals in work_fn. err:%d\n", ret); duty_cycle = ((data_t *)data)->duty; pr = ((data_t *)data)->affinity_pr; ps.psn = ((data_t *)data)->psn; ps.psa = ((data_t *)data)->psa; /* * if this thread is launched for non-cpu work (e,g gpu work requestor) * let it run like any normal thread in system */ if (CPU_ISSET(pr, &configpv.cpumask)) { cpu_work_exist = 1; set_affinity(pr); set_sched_priority(1); /* thread could override gpu or other XX_TICK_USEC */ tick_usec = IA_TICK_USEC; } /* fix duty cycle to to non-zero min value */ duty_cycle = (duty_cycle == 0) ? MIN_LOAD : duty_cycle; /* initial on/off time calcuation based on duty cycle */ on_time_us = (tick_usec * duty_cycle / 100); off_time_us = tick_usec - on_time_us; dbg_print("Thread:%x DutyCycle:%f ontime:%duS, idletime:%duS\n", (unsigned int)pthread_self(), duty_cycle, on_time_us, off_time_us); /* * If cpu0 was not selected, the first thread that comes * here will do the cpu0's logging work */ pthread_mutex_lock(&plock); if ((!CPU_ISSET(0, &configpv.cpumask) && (chore_thread < 0)) || (pr == 0)) chore_thread = pr; pthread_mutex_unlock(&plock); if (chore_thread == pr) { plog_poll_sec = MSEC_TO_SEC(configpv.poll_period); plog_poll_nsec = (plog_poll_sec > 0) ? REMAINING_MS_TO_NS(configpv.poll_period) : configpv.poll_period * 1000000; duration_sec = MSEC_TO_SEC(configpv.duration); duration_nsec = (duration_sec > 0) ? REMAINING_MS_TO_NS(configpv.duration) : configpv.duration * 1000000; dbg_print("thread %d sec: %d nsec %d\n", pr, duration_sec, duration_nsec); initialize_log_clock(); unsigned int dummy; dummy = update_amperf_diffs(&dummy, &dummy, &dummy, 0); } /* monotonic clock initial reference. updated during power_shaping */ if (clock_gettime(CLOCK_MONOTONIC, &ps.last)) perror("clock_gettime 1"); start_ms = timespec_to_msec(&ps.last); for (i = 0; (!exit_cpu_thread && cpu_work_exist); i++) { if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts)) perror("clock_gettime 2"); while (is_time_remaining(CLOCK_THREAD_CPUTIME_ID, &ts, 0, on_time_us*1000)) { if (timespec_to_msec(&ps.last) - start_ms < START_DELAY) { clock_gettime(CLOCK_MONOTONIC, &ps.last); start_pending = 1; } else { start_pending = 0; } if (!start_pending) { /* * add as much work as required in this loop. * it will be accounted for good. */ last_duty_cycle = duty_cycle; if (power_shaping(&ps, &duty_cycle)) { on_time_us = tick_usec * duty_cycle/100; off_time_us = tick_usec - on_time_us; } } if (!start_pending) { /* No work for cpu0 if it was just submitter */ if (((cpu_stress_opt != DONT_STRESS_CPU0) || (pr != 0)) && (cpu_work_exist)) { cpu_work(on_time_us); } } if (chore_thread == pr) { do_logging(&last_duty_cycle); /* XXX: gfx, mem work */ } } if (exit_cpu_thread) continue; /* now for OFF cycle */ ts.tv_sec = 0; ts.tv_nsec = off_time_us * 1000; nanosleep(&ts, NULL); } /* report out energy index details before exit */ int N; int time_ms; float soc_r_avg, pp0_r_avg, e_r; if (clock_gettime(CLOCK_MONOTONIC, &ts)) perror("clock_gettime 1"); time_ms = timespec_to_msec(&ts) - start_ms; N = (int)time_ms/configpv.poll_period; if (pr == 0) { printf("\nDuration: %d ms. poll: %d ms. samples: %d\n", time_ms, configpv.poll_period, N); if (rapl_pp0_supported) { soc_r_avg = (float)(soc_diff_uj)/(time_ms*1000); pp0_r_avg = (float)(pp0_diff_uj)/(time_ms*1000); printf("Applicable to SOC\n"); printf("\tAvg soc power: %.3f W\n", soc_r_avg); printf("\tEnergy consumed (soc): %.3f mJ\n", (float)soc_diff_uj/1000); printf("Applicable to CPU\n"); printf("\tAvg cpu power: %.3f W\n", pp0_r_avg); printf("\tEnergy consumed (cpu): %.3f mJ\n", (float)pp0_diff_uj/1000); } } pthread_exit(NULL); return; } /* signal handler: terminate all threads on cpu */ static void pbot_signal_handler(int sig) { dbg_print("caught signal %d\n", sig); switch (sig) { case SIGTERM: case SIGKILL: case SIGINT: exit_cpu_thread = 1; break; default: break; } } int dev_msr_fd[MAX_CPU_REPORTS]; int dev_msr_supported; int main(int argc, char *argv[]) { int c, i = 0, t = 0, duty, ret; static void *res; data_t *pst; struct config *cfg; exit_cpu_thread = 0; cfg = &configpv; if (!parse_cmd_config(argc, argv, cfg)) { printf("failed to parse_cmd_config\n"); exit(EXIT_FAILURE); } if (cfg->version) { print_version(); exit(EXIT_SUCCESS); } if (geteuid() != 0) { printf("run as root\n"); exit(EXIT_FAILURE); } if (!populate_default_config(cfg)) { printf("failed to populate_default_config\n"); exit(EXIT_FAILURE); } pthread_t io_thread; pthread_attr_t attr_io; if (pthread_attr_init(&attr_io)) { perror("io thread attr"); exit(EXIT_FAILURE); } pthread_attr_setdetachstate(&attr_io, PTHREAD_CREATE_JOINABLE); pthread_attr_t attr_t; data_t base_data; /* shape func is common to all threads */ pst = &base_data; /* Android lib does not support suboption(). parse it manually */ if (!parse_power_shape(cfg->shape_func, pst)) { printf("failed parse_power_shape \"%s\"\n", cfg->shape_func); printf("see --help for usage\n"); exit(EXIT_FAILURE); } /* default/starting duty cycle */ duty = 0; nr_threads = CPU_COUNT(&cfg->cpumask); data_t data[nr_threads]; pthread_t thread[nr_threads]; if (pthread_mutex_init(&plock, NULL) != 0) { printf("mutex init failed\n"); goto bail; } for (c = 0, i = 0; c < CPU_SETSIZE || i < MAX_CPU_REPORTS; c++, i++) { if (!CPU_ISSET(c, &cfg->cpumask)) continue; ret = initialize_dev_msr(c); if (ret < 0) { dev_msr_supported = 0; printf("*** No /dev/cpu%d/msr. check CONFIG_X86_MSR support ***\n\n", c); break; } else { dev_msr_fd[i] = ret; dev_msr_supported = 1; } } /* tsc msr is common to all cpu */ initialize_cpu_khz(dev_msr_fd[0]); /* thread for deferred disk IO of logs */ if (pthread_create(&io_thread, &attr_io, (void *)&page_write_disk, (void *)cfg)) { perror("io thread create"); goto bail; } if (!nr_threads) { nr_threads = 1; CPU_SET(0, &cfg->cpumask); } ret = pthread_attr_init(&attr_t); if (ret) { perror("pthread_attr_init:"); goto bail; } pthread_attr_setdetachstate(&attr_t, PTHREAD_CREATE_JOINABLE); /* fork pthreads for each logical cpu selected & set affinity to cpu. */ for (c = 0; c < CPU_SETSIZE; c++) { if (!CPU_ISSET(c, &cfg->cpumask)) continue; data[t].duty = duty; /* setaffinity to specific processor */ data[t].affinity_pr = c; data[t].psn = pst->psn; data[t].psa = pst->psa; ret = pthread_create(&thread[t], &attr_t, (void *)&work_fn, (void *)&data[t]); if (ret) { perror("Failed pthread create"); goto bail; } t++; } if (signal(SIGINT, pbot_signal_handler) == SIG_ERR) printf("Cannot handle SIGINT\n"); /* attr not needed after create */ pthread_attr_destroy(&attr_t); dbg_print("Created %d Thread + 1 io thread\n", t); while (0 < t--) { pthread_join(thread[t], res); dbg_print("Thread %d cleaned\n", t); } for (i = 0; i < MAX_CPU_REPORTS; i++) close(dev_msr_fd[i]); /* we exit the logger thread above. time to flush any remaining data */ exit_io_thread = 1; trigger_disk_io(); pthread_attr_destroy(&attr_io); pthread_join(io_thread, res); pthread_mutex_destroy(&plock); dbg_print("IO Thread cleaned\n"); bail: return 1; } ./._psst.h000755 000766 000024 00000000321 13163231534 013554 0ustar00numubeenstaff000000 000000 Mac OS X  2ŸÑATTRÑÌÌcom.macromates.selectionRangeÍcom.macromates.visibleIndex11338psst.h000755 000766 000024 00000003746 13163231534 013220 0ustar00numubeenstaff000000 000000 /* * Copyright (c) 2017, Intel Corporation. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope 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. * * Author: Noor ul Mubeen */ #ifndef _PSST_H_ #define _PSST_H_ #define MSEC_PER_SEC (1000) #define USEC_PER_SEC (1000000) #define NSEC_PER_SEC (1000000000) /* * kernel's USER_HZ is not exported to user space * typically platforms have kernel (HZ == USER_HZ == 1000 per sec) */ #define IA_DUTY_CYCLE_PER_SEC (50) #define IA_TICK_USEC (USEC_PER_SEC / IA_DUTY_CYCLE_PER_SEC) #define MIN_LOAD (0.10) #define MAX_LOAD (100) enum power_shape_name { SINGLE_STEP, SINOSOID, STAIR_CASE, SINGLE_PULSE, LINEAR_RAMP, SAW_TOOTH, GROWTH_CURVE, DECAY_CURVE }; typedef union { struct single_step_t { float v_units; } single_step; struct staircase_t { float y_height; int x_length; } staircase; struct sinosoid_t { float y_amplitude; int x_wavelength; } sinosoid; struct singlepulse_t { float y_height; int x_length; } single_pulse; struct linear_ramp_t { float slope_y_per_sec; } linear_ramp; struct saw_tooth_t { float slope_y_per_sec; float max_y; } saw_tooth; struct growth_curve_t { } growth_curve; struct decay_curve_t { } decay_curve; } power_shape_attr_t; typedef struct { enum power_shape_name psn; power_shape_attr_t psa; struct timespec last; } ps_t; typedef struct { float duty; int affinity_pr; enum power_shape_name psn; power_shape_attr_t psa; } data_t; typedef struct { int last_time_taken; struct timespec ts; } perf_t; extern int is_time_remaining(clockid_t, struct timespec *, int, int); extern unsigned int *perf_time; #endif rapl.c000755 000766 000024 00000002633 13163231534 013152 0ustar00numubeenstaff000000 000000 /* * Interface functions to intel rapl. * * Copyright (c) 2017, Intel Corporation. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope 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. * * Author: Noor ul Mubeen */ #include "logger.h" static long long prev_pkg0, prev_pkg1, prev_pkg2, prev_pkg3; static long long prev_cpu; static long long prev_gpu; static long long prev_dram; #define VAR(a, b) (a##b) #define generate_rapl_ediff(scope) \ int rapl_ediff_##scope(long long cur_ewma) \ { \ long long ediff; \ ediff = (VAR(prev_, scope) == 0) ? 0 : (cur_ewma - VAR(prev_, scope)); \ VAR(prev_, scope) = cur_ewma; \ if (ediff <= 0) { \ return 0; \ } \ return (int)ediff; \ } /* These functions return energy diff in micro-joules since last sample */ generate_rapl_ediff(pkg0); generate_rapl_ediff(pkg1); generate_rapl_ediff(pkg2); generate_rapl_ediff(pkg3); generate_rapl_ediff(cpu); generate_rapl_ediff(gpu); generate_rapl_ediff(dram); rapl.h000755 000766 000024 00000001614 13163231534 013155 0ustar00numubeenstaff000000 000000 /* * Copyright (c) 2017, Intel Corporation. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope 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. * * Author: Noor ul Mubeen */ #ifndef _RAPL_H_ #define _RAPL_H_ extern int rapl_ediff_pkg0(long long); extern int rapl_ediff_pkg1(long long); extern int rapl_ediff_pkg2(long long); extern int rapl_ediff_pkg3(long long); extern int rapl_ediff_soc(long long); extern int rapl_ediff_cpu(long long); extern int rapl_ediff_gpu(long long); extern int rapl_ediff_dram(long long); #endif