idlestat-0.5/0000775000175000017500000000000012454014313011630 5ustar kingkingidlestat-0.5/trace.c0000664000175000017500000001174712454011467013113 0ustar kingking/* * trace.c * * Copyright (C) 2014, Linaro Limited. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * 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; version 2 of the License. * * 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., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Contributors: * Daniel Lezcano * Zoran Markovic * Koan-Sin Tan * Tuukka Tikkanen */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include "trace.h" #include "utils.h" #include "list.h" struct trace_options { int buffer_size; struct list_head list; }; struct enabled_eventtype { char *name; struct list_head list; }; int idlestat_restore_trace_options(struct trace_options *options) { struct enabled_eventtype *pos, *n; int write_problem = 0; if (write_int(TRACE_BUFFER_SIZE_PATH, options->buffer_size)) write_problem = -1; list_for_each_entry_safe(pos, n, &options->list, list) { if (write_int(pos->name, 1)) write_problem = -1; free(pos->name); list_del(&pos->list); free(pos); } free(options); return write_problem; } int idlestat_trace_enable(bool enable) { return write_int(TRACE_ON_PATH, enable); } int idlestat_flush_trace(void) { return write_int(TRACE_FILE, 0); } static int events_scan(char *name, struct list_head *head) { FTSENT *file = NULL; char value; struct enabled_eventtype *found_type; char *paths[2]; paths[0] = name; paths[1] = NULL; FTS *fts = fts_open(paths, FTS_PHYSICAL, NULL); if (!fts) return error("fts_open"); while (NULL != (file = fts_read(fts))) { if (file->fts_info == FTS_ERR) { fprintf(stderr, "%s: %s\n", file->fts_path, strerror(file->fts_errno)); fts_close(fts); return -1; } if (strcmp(file->fts_name, "enable")) continue; if (read_char(file->fts_path, &value)) { fts_close(fts); return -1; } if (value != '1') continue; found_type = calloc(1, sizeof(struct enabled_eventtype)); if (!found_type) { fts_close(fts); return error(__func__); } found_type->name = strdup(file->fts_path); if (!found_type->name) { free(found_type); return error(__func__); } list_add(&found_type->list, head); } fts_close(fts); return 0; } #define TRACE_EVENTS_DIR TRACE_PATH "/events/" struct trace_options *idlestat_store_trace_options() { int status; struct trace_options *options; struct enabled_eventtype *pos, *n; options = calloc(1, sizeof(struct trace_options)); if (!options) return ptrerror(__func__); INIT_LIST_HEAD(&options->list); if (read_int(TRACE_BUFFER_SIZE_PATH, &options->buffer_size)) goto cannot_get_event_options; status = events_scan(TRACE_EVENTS_DIR, &options->list); if (status == 0) return options; cannot_get_event_options: /* Failure, clean up */ list_for_each_entry_safe(pos, n, &options->list, list) { free(pos->name); list_del(&pos->list); free(pos); } free(options); return ptrerror(NULL); } int idlestat_init_trace(unsigned int duration) { int bufsize; /* Assuming the worst case where we can have for cpuidle, * TRACE_IDLE_NRHITS_PER_SEC. Each state enter/exit line are * 196 chars wide, so we have 2 x 196 x TRACE_IDLE_NRHITS_PER_SEC lines. * For cpufreq, assume a 196-character line for each frequency change, * and expect a rate of TRACE_CPUFREQ_NRHITS_PER_SEC. * Divide by 2^10 to have Kb. We add 1Kb to be sure to round up. */ bufsize = 2 * TRACE_IDLE_LENGTH * TRACE_IDLE_NRHITS_PER_SEC; bufsize += TRACE_CPUFREQ_LENGTH * TRACE_CPUFREQ_NRHITS_PER_SEC; bufsize = (bufsize * duration / (1 << 10)) + 1; if (write_int(TRACE_BUFFER_SIZE_PATH, bufsize)) return -1; if (read_int(TRACE_BUFFER_TOTAL_PATH, &bufsize)) return -1; printf("Total trace buffer: %d kB\n", bufsize); /* Disable all the traces */ if (write_int(TRACE_EVENT_PATH, 0)) return -1; /* Enable cpu_idle traces */ if (write_int(TRACE_CPUIDLE_EVENT_PATH, 1)) return -1; /* Enable cpu_frequency traces */ if (write_int(TRACE_CPUFREQ_EVENT_PATH, 1)) return -1; /* Enable irq traces */ if (write_int(TRACE_IRQ_EVENT_PATH, 1)) return -1; /* Enable ipi traces.. * Ignore if not present, for backward compatibility */ write_int(TRACE_IPI_EVENT_PATH, 1); return 0; } idlestat-0.5/trace_ops.h0000664000175000017500000000326712454011467013777 0ustar kingking/* * trace_ops.h * * Copyright (C) 2014, Linaro Limited. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * 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; version 2 of the License. * * 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., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Contributors: * Tuukka Tikkanen * */ #ifndef __TRACE_OPS_H #define __TRACE_OPS_H #include struct cpuidle_datas; struct trace_ops { const char *name; int (*check_magic)(const char *filename); struct cpuidle_datas *(*load)(const char *filename); }; extern int load_text_data_line(char *buffer, struct cpuidle_datas *datas, char *format, double *begin, double *end, size_t *count, size_t *start); extern void load_text_data_lines(FILE *f, char *buffer, struct cpuidle_datas *datas); #define EXPORT_TRACE_OPS(tracetype_name) \ static const struct trace_ops \ __attribute__ ((__used__)) \ __attribute__ ((__section__ ("__trace_ops"))) \ * tracetype_name ## _trace_ptr = &tracetype_name##_trace_ops extern const struct trace_ops *trace_ops_head; #endif idlestat-0.5/topology.h0000664000175000017500000000745612454011467013700 0ustar kingking/* * topology.h * * Copyright (C) 2014, Linaro Limited. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * 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; version 2 of the License. * * 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., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Contributors: * Daniel Lezcano * Zoran Markovic * Tuukka Tikkanen * */ #ifndef __TOPOLOGY_H #define __TOPOLOGY_H #include "list.h" #include struct cpuidle_datas; struct report_ops; struct cpu_cpu { struct list_head list_cpu; int cpu_id; struct list_head list_phy_enum; struct cpuidle_cstates *cstates; struct cpufreq_pstates *pstates; struct cpuidle_cstates *base_cstates; struct cpufreq_pstates *base_pstates; }; struct cpu_core { struct list_head list_core; int core_id; struct list_head cpu_head; int cpu_num; bool is_ht; struct cpuidle_cstates *cstates; struct cpuidle_cstates *base_cstates; }; struct cpu_physical { struct list_head list_physical; int physical_id; struct list_head core_head; int core_num; struct list_head cpu_enum_head; struct cpuidle_cstates *cstates; struct cpuidle_cstates *base_cstates; }; struct cpu_topology { struct list_head physical_head; int physical_num; }; extern struct cpu_topology *alloc_cpu_topo_info(void); extern struct cpu_topology *read_cpu_topo_info(FILE *f, char *buf); extern struct cpu_topology *read_sysfs_cpu_topo(void); extern int release_cpu_topo_info(struct cpu_topology *topo); extern int output_cpu_topo_info(struct cpu_topology *topo, FILE *f); extern void assign_baseline_in_topo(struct cpuidle_datas *datas); extern int release_cpu_topo_cstates(struct cpu_topology *topo); extern int dump_cpu_topo_info(struct report_ops *ops, void *report_data, int (*dump)(struct report_ops *, void *, void *, char *, void *), struct cpu_topology *topo, int cstate); extern struct cpu_physical *cpu_to_cluster(int cpuid, struct cpu_topology *topo); extern struct cpu_core *cpu_to_core(int cpuid, struct cpu_topology *topo); #define core_for_each_cpu(cpu, core) \ list_for_each_entry(cpu, &core->cpu_head, list_cpu) #define cluster_for_each_core(core, clust) \ list_for_each_entry(core, &clust->core_head, list_core) #define cluster_for_each_cpu(cpu, clust) \ list_for_each_entry(cpu, &clust->cpu_enum_head, list_phy_enum) #define topo_for_each_cluster(clust, topo) \ list_for_each_entry(clust, &topo->physical_head, list_physical) extern int cluster_get_least_cstate(struct cpu_physical *clust); extern int cluster_get_highest_freq(struct cpu_physical *clust); #define get_affected_cluster_least_cstate(cpuid, topo) \ cluster_get_least_cstate(cpu_to_cluster(cpuid, topo)) #define get_affected_cluster_highest_freq(cpuid, topo) \ cluster_get_highest_freq(cpu_to_cluster(cpuid, topo)) extern int core_get_least_cstate(struct cpu_core *core); extern int core_get_highest_freq(struct cpu_core *core); #define get_affected_core_least_cstate(cpuid, topo) \ core_get_least_cstate(cpu_to_core(cpuid, topo)) #define get_affected_core_highest_freq(cpuid, topo) \ core_get_highest_freq(cpu_to_core(cpuid, topo)) extern int setup_topo_states(struct cpuidle_datas *datas); #endif idlestat-0.5/utils.c0000664000175000017500000001244412454011467013150 0ustar kingking/* * utils.c * * Copyright (C) 2014, Linaro Limited. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * 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; version 2 of the License. * * 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., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Contributors: * Daniel Lezcano * Zoran Markovic * Koan-Sin Tan * Tuukka Tikkanen */ #define _GNU_SOURCE #include #undef _GNU_SOURCE #include #include #include #include #include #include #include #include #include "utils.h" static void * const ERROR_VALUE_PTR = (void * const)&ERROR_VALUE_PTR; int error(const char *str) { perror(str); return -1; } void *ptrerror(const char *str) { if (str != NULL) perror(str); return ERROR_VALUE_PTR; } int is_err(const void *ptr) { return ptr == ERROR_VALUE_PTR; } static int verbose_level; void set_verbose_level(int level) { verbose_level = level; } int verbose_printf(int min_level, const char *fmt, ...) { va_list ap; if (min_level > verbose_level) return 0; return vprintf(fmt, ap); } int verbose_fprintf(FILE *f, int min_level, const char *fmt, ...) { va_list ap; if (min_level > verbose_level) return 0; return vfprintf(f, fmt, ap); } int write_int(const char *path, int val) { FILE *f; f = fopen(path, "w"); if (!f) { fprintf(stderr, "failed to open '%s': %m\n", path); return -1; } fprintf(f, "%d", val); fclose(f); return 0; } int read_int(const char *path, int *val) { FILE *f; int ret; f = fopen(path, "r"); if (!f) { fprintf(stderr, "failed to open '%s': %m\n", path); return -1; } ret = fscanf(f, "%d", val); fclose(f); if (ret != 1) { fprintf(stderr, "Warning: failed to parse integer from %s\n", path); return -1; } return 0; } int read_char(const char *path, char *val) { FILE *f; int ret; f = fopen(path, "r"); if (!f) { fprintf(stderr, "failed to open '%s': %m\n", path); return -1; } ret = fscanf(f, "%c", val); fclose(f); if (ret != 1) { fprintf(stderr, "%s: failed to parse a char\n", path); return -1; } return 0; } int store_line(const char *line, void *data) { FILE *f = data; /* ignore comment line */ if (line[0] == '#') return 0; fprintf(f, "%s", line); return 0; } /* * This functions is a helper to read a specific file content and store * the content inside a variable pointer passed as parameter, the format * parameter gives the variable type to be read from the file. * * @path : directory path containing the file * @name : name of the file to be read * @format : the format of the format * @value : a pointer to a variable to store the content of the file * Returns 0 on success, -1 otherwise */ int file_read_value(const char *path, const char *name, const char *format, void *value) { FILE *file; char *rpath; int ret; ret = asprintf(&rpath, "%s/%s", path, name); if (ret < 0) return ret; file = fopen(rpath, "r"); if (!file) { ret = -1; goto out_free; } ret = fscanf(file, format, value) != 1 ? -1 : 0; fclose(file); out_free: free(rpath); return ret; } int redirect_stdout_to_file(const char *path) { int ret = 0; int fd; if (path) { fd = open(path, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP |S_IROTH); if (fd < 0) { fprintf(stderr, "%s: failed to open '%s'\n", __func__, path); return -1; } fflush(stdout); ret = dup2(fd, STDOUT_FILENO); close(fd); if (ret < 0) { fprintf(stderr, "%s: failed to duplicate '%s'\n", __func__, path); unlink(path); return ret; } } return 0; } void display_factored_time(double time, int align) { char buffer[128]; if (time < 1000) { sprintf(buffer, "%.0lfus", time); printf("%*s", align, buffer); } else if (time < 1000000) { sprintf(buffer, "%.2lfms", time / 1000.0); printf("%*s", align, buffer); } else { sprintf(buffer, "%.2lfs", time / 1000000.0); printf("%*s", align, buffer); } } void display_factored_freq(int freq, int align) { char buffer[128]; if (freq < 1000) { sprintf(buffer, "%dHz", freq); printf("%*s", align, buffer); } else if (freq < 1000000) { sprintf(buffer, "%.2fMHz", (float)freq / 1000.0); printf("%*s", align, buffer); } else { sprintf(buffer, "%.2fGHz", (float)freq / 1000000.0); printf("%*s", align, buffer); } } int check_window_size(void) { struct winsize winsize; /* Output is redirected */ if (!isatty(STDOUT_FILENO)) return 0; /* Get terminal window size */ if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize) == -1) return -1; if (winsize.ws_col >= 80) return 0; return -1; } idlestat-0.5/csv_report.c0000664000175000017500000001050212454011467014167 0ustar kingking/* * csv_report.c * * Copyright (C) 2014, Linaro Limited. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * 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; version 2 of the License. * * 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., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Contributors: * Koan-Sin Tan * Tuukka Tikkanen * */ #include #include #include #include #include #include "report_ops.h" #include "idlestat.h" #include "utils.h" static int csv_check_output(struct program_options *options, void *report_data) { return 0; } static int csv_open_report_file(char *path, void *report_data) { return redirect_stdout_to_file(path); } static int csv_close_report_file(void *report_data) { return (fclose(stdout) == EOF) ? -1 : 0; } static void csv_cstate_table_header(void *report_data) { printf("C-State Table\n"); printf("cluster,core,cpu,C-state,min (us),max (us),avg (us),total (us),hits,over,under\n"); } static void csv_cstate_table_footer(void *report_data) { printf("\n\n"); } static void csv_cstate_cpu_header(const char *cpu, void *report_data) { if (strstr(cpu, "cluster")) printf("%s\n", cpu); else if (strstr(cpu, "core")) printf(",%s\n", cpu); else printf(",,%s\n", cpu); } static void csv_cstate_single_state(struct cpuidle_cstate *c, void *report_data) { printf(",,,%s,", c->name); printf("%f,", c->min_time == DBL_MAX ? 0. : c->min_time); printf("%f,%f,%f,", c->max_time, c->avg_time, c->duration); printf("%d,%d,%d", c->nrdata, c->early_wakings, c->late_wakings); printf("\n"); } static void csv_cstate_end_cpu(void *report_data) { } static void csv_pstate_table_header(void *report_data) { printf("P-State Table\n"); printf(",,,P-state (kHz),min (us),max (us),avg (us),total (us),hits\n"); } static void csv_pstate_table_footer(void *report_data) { printf("\n\n"); } static void csv_pstate_single_freq(struct cpufreq_pstate *p, void *report_data) { printf(",,,%d,", p->freq); printf("%f,", p->min_time == DBL_MAX ? 0. : p->min_time); printf("%f,%f,%f,", p->max_time, p->avg_time, p->duration); printf("%d", p->count); printf("\n"); } static void csv_wakeup_table_header(void *report_data) { printf("\n"); printf("Wakeup Table\n"); printf("cluster,core,cpu,IRQ,Name,Count,early,late\n"); } static void csv_wakeup_table_footer(void *report_data) { printf("\n\n"); } static void csv_wakeup_single_irq(struct wakeup_irq *irqinfo, void *report_data) { if (irqinfo->id != -1) { printf(",,,%d,%s,%d,%d,%d\n", irqinfo->id, irqinfo->name, irqinfo->count, irqinfo->early_triggers, irqinfo->late_triggers); } else { printf(",,,IPI,%s,%d,%d,%d\n", irqinfo->name, irqinfo->count, irqinfo->early_triggers, irqinfo->late_triggers); } } static struct report_ops csv_report_ops = { .name = "csv", .check_output = csv_check_output, .open_report_file = csv_open_report_file, .close_report_file = csv_close_report_file, .cstate_table_header = csv_cstate_table_header, .cstate_table_footer = csv_cstate_table_footer, .cstate_cpu_header = csv_cstate_cpu_header, .cstate_single_state = csv_cstate_single_state, .cstate_end_cpu = csv_cstate_end_cpu, .pstate_table_header = csv_pstate_table_header, .pstate_table_footer = csv_pstate_table_footer, .pstate_cpu_header = csv_cstate_cpu_header, .pstate_single_freq = csv_pstate_single_freq, .pstate_end_cpu = csv_cstate_end_cpu, .wakeup_table_header = csv_wakeup_table_header, .wakeup_table_footer = csv_wakeup_table_footer, .wakeup_cpu_header = csv_cstate_cpu_header, .wakeup_single_irq = csv_wakeup_single_irq, .wakeup_end_cpu = csv_cstate_end_cpu, }; EXPORT_REPORT_OPS(csv); idlestat-0.5/energy_model.h0000664000175000017500000000233012454011467014457 0ustar kingking/* * energy_model.h * * Copyright (C) 2014, Linaro Limited. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * 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; version 2 of the License. * * 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., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Contributors: * Larry Bassel * */ #ifndef ENERGY_MODEL_H #define ENERGY_MODEL_H struct program_options; /* Defined elsewhere */ struct cpu_topology; int parse_energy_model(struct program_options *); void calculate_energy_consumption(struct cpu_topology *cpu_topo, struct program_options *); #endif idlestat-0.5/energy_model.c0000664000175000017500000004017512454011467014463 0ustar kingking/* * energy_model.c * * Copyright (C) 2014, Linaro Limited. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * 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; version 2 of the License. * * 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., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Contributors: * Larry Bassel * Tuukka Tikkanen * */ #include #include #include #include #include #include #include #include "energy_model.h" #include "idlestat.h" #include "topology.h" #include "list.h" #include "utils.h" static struct cluster_energy_info *cluster_energy_table; static unsigned int clusters_in_energy_file = 0; static int make_energy_model_template(struct program_options *options) { FILE *f; struct cpuidle_datas *datas; struct cpu_physical *s_phy; struct cpu_core *s_core; struct cpu_cpu *s_cpu; unsigned int cluster_count = 0; unsigned int cluster_number = 0; struct cpu_topology *cpu_topo = NULL; f = fopen(options->energy_model_filename, "w+"); if (!f) return -1; fprintf(f, "# This is a energy model template generated by idlestat\n"); fprintf(f, "# Lines starting with # or which are blank are ignored\n"); fprintf(f, "# Replace ? with correct values\n"); datas = idlestat_load(options->filename); if (!datas) { fclose(f); unlink(options->energy_model_filename); return -1; } cpu_topo = datas->topo; list_for_each_entry(s_phy, &cpu_topo->physical_head, list_physical) cluster_count++; fprintf(f, "clusters %d\n", cluster_count); list_for_each_entry(s_phy, &cpu_topo->physical_head, list_physical) { unsigned int num_cap_states = 0; unsigned int num_c_states = 0; unsigned int i; s_core = list_entry((&s_phy->core_head)->prev, struct cpu_core, list_core); s_cpu = list_entry((&s_core->cpu_head)->prev, struct cpu_cpu, list_cpu); num_cap_states = s_cpu->pstates->max; num_c_states = s_cpu->cstates->cstate_max; fprintf(f, "cluster%c: %d cap states %d C states\n\n", cluster_number + 'A', num_cap_states, num_c_states); fprintf(f, "P-states:\n"); fprintf(f, "# speed, cluster power, core power\n"); for (i = 0; i < s_cpu->pstates->max; i++) { struct cpufreq_pstate *p = &s_cpu->pstates->pstate[i]; fprintf(f, "%d\t\t?\t?\n", p->freq/1000); } fprintf(f, "\nC-states:\n"); fprintf(f, "# name, cluster power, core power\n"); for (i = 0; i < s_cpu->cstates->cstate_max + 1; i++) { struct cpuidle_cstate *c = &s_cpu->cstates->cstate[i]; fprintf(f, "%s\t\t?\t?\n", c->name); } fprintf(f, "\nwakeup\t\t?\t?\n"); cluster_number++; } fclose(f); release_cpu_topo_cstates(cpu_topo); release_cpu_topo_info(cpu_topo); return 0; } int parse_energy_model(struct program_options *options) { FILE *f; char tmp; struct cluster_energy_info *clustp; unsigned int number_cap_states, number_c_states; int current_cluster = -1; unsigned int current_pstate; unsigned int current_cstate; unsigned int clust_p, core_p; char buffer[BUFSIZE]; char *path = options->energy_model_filename; int ret; assert(path != NULL); f = fopen(path, "r"); if (!f) { if (errno == ENOENT) { if (options->mode == TRACE) { fprintf(stderr, "Energy model file %s does not " "exist.\nIdlestat was started in " "trace mode. If you wish to " "generate energy model\ntemplate, " "run idlestat in import mode.\n", path); return -1; } ret = make_energy_model_template(options); exit(ret); } fprintf(stderr, "%s: failed to open '%s': %m\n", __func__, path); return -1; } while (fgets(buffer, BUFSIZE, f)) { if (buffer[0] == '#') continue; if (strlen(buffer) == 1) continue; if (strstr(buffer, "clusters")) { if (clusters_in_energy_file) { fprintf(stderr, "%s: number of clusters already specified in %s\n", __func__, path); fclose(f); return -1; } sscanf(buffer, "%*s %d", &clusters_in_energy_file); cluster_energy_table = calloc(sizeof(struct cluster_energy_info), clusters_in_energy_file); continue; } if (strstr(buffer, "cluster")) { sscanf(buffer, "cluster%c: %d cap states %d C states", &tmp, &number_cap_states, &number_c_states); current_cluster = tmp - 'A'; if (current_cluster >= clusters_in_energy_file) { fprintf(stderr, "%s: cluster%c out of range in %s\n", __func__, tmp, path); fclose(f); return -1; } clustp = cluster_energy_table + current_cluster; if (clustp->number_cap_states) { fprintf(stderr, "%s: number of cap states for cluster%c already specified in %s\n", __func__, tmp, path); fclose(f); return -1; } clustp->number_cap_states = number_cap_states; clustp->number_c_states = number_c_states; clustp->p_energy = calloc(number_cap_states, sizeof(struct pstate_energy_info)); clustp->c_energy = calloc(number_c_states, sizeof(struct cstate_energy_info)); clustp->state = parsed_cluster_info; continue; } if (strstr(buffer, "P-states")) { if (current_cluster == -1) { fprintf(stderr, "%s: unknown cluster (cap states) in %s\n", __func__, path); fclose(f); return -1; } if (clustp->state < parsed_cluster_info) { fprintf(stderr, "%s: number of cap states for cluster%c not specified in %s\n", __func__, current_cluster, path); fclose(f); return -1; } current_pstate = 0; clustp->state = parsing_cap_states; continue; } if (strstr(buffer, "C-states")) { if (current_cluster == -1) { fprintf(stderr, "%s: unknown cluster (c states) in %s\n", __func__, path); fclose(f); return -1; } if (clustp->state < parsed_cluster_info) { fprintf(stderr, "%s: number of c states for cluster%c not specified in %s\n", __func__, current_cluster, path); fclose(f); return -1; } current_cstate = 0; clustp->state = parsing_c_states; continue; } if (strstr(buffer, "wakeup")) { unsigned int clust_w, core_w; if (current_cluster == -1) { fprintf(stderr, "%s: unknown cluster (wakeup) in %s\n", __func__, path); fclose(f); return -1; } sscanf(buffer, "%*s %d %d", &clust_w, &core_w); clustp->wakeup_energy.cluster_wakeup_energy = clust_w; clustp->wakeup_energy.core_wakeup_energy = core_w; continue; } if (!clustp) { fprintf(stderr, "%s: unknown cluster in %s\n", __func__, path); fclose(f); return -1; } if (clustp->state == parsing_cap_states) { struct pstate_energy_info *pp; unsigned int speed; if (sscanf(buffer, "%d %d %d", &speed, &clust_p, &core_p) != 3) { fprintf(stderr, "%s: expected P state (speed cluster core) for cluster%c in %s\n", __func__, current_cluster, path); fclose(f); return -1; } if (current_pstate >= clustp->number_cap_states) { fprintf(stderr, "%s: too many cap states specified for cluster%c in %s\n", __func__, current_cluster, path); fclose(f); return -1; } pp = &clustp->p_energy[current_pstate++]; pp->speed = speed; pp->cluster_power = clust_p; pp->core_power = core_p; continue; } if (clustp->state == parsing_c_states) { char name[NAMELEN]; struct cstate_energy_info *cp; if (sscanf(buffer, "%s %d %d", name, &clust_p, &core_p) != 3) { fprintf(stderr, "%s: expected C state (name cluster core) for cluster%c in %s\n", __func__, current_cluster, path); fclose(f); return -1; } if (current_cstate >= clustp->number_c_states) { fprintf(stderr, "%s: too many C states specified for cluster%c in %s\n", __func__, current_cluster, path); fclose(f); return -1; } cp = &clustp->c_energy[current_cstate++]; strncpy(cp->cstate_name, name, NAMELEN); cp->cluster_idle_power = clust_p; cp->core_idle_power = core_p; continue; } } fclose(f); printf("Parsed energy model file successfully\n"); return 0; } static struct cstate_energy_info *find_cstate_energy_info(const unsigned int cluster, const char *name) { struct cluster_energy_info *clustp; struct cstate_energy_info *cp; int i; clustp = cluster_energy_table + cluster; cp = &clustp->c_energy[0]; for (i = 0; i < clustp->number_c_states; i++, cp++) { if (!strcmp(cp->cstate_name, name)) return cp; } return NULL; } static struct pstate_energy_info *find_pstate_energy_info(const unsigned int cluster, const unsigned int speed) { struct cluster_energy_info *clustp; struct pstate_energy_info *pp; int i; clustp = cluster_energy_table + cluster; pp = &clustp->p_energy[0]; for (i = 0; i < clustp->number_cap_states; i++, pp++) { if (speed == pp->speed) return pp; } return NULL; } #define US_TO_SEC(US) (US / 1e6) void calculate_energy_consumption(struct cpu_topology *cpu_topo, struct program_options *options) { struct cpu_physical *s_phy; struct cpu_core *s_core; struct cpu_cpu *s_cpu; /* Overall energy breakdown */ double total_energy = 0.0; double total_cap = 0.0; double total_idl = 0.0; double total_wkp = 0.0; /* Per cluster energy breakdown */ double cluster_energy; double cluster_cap; double cluster_idl; double cluster_wkp; int i, j; unsigned int current_cluster; struct cstate_energy_info *cp; struct pstate_energy_info *pp; struct cluster_energy_info *clustp; /* Contributions are computed per cluster */ list_for_each_entry(s_phy, &cpu_topo->physical_head, list_physical) { current_cluster = s_phy->physical_id; clustp = cluster_energy_table + current_cluster; cluster_energy = 0.0; cluster_cap = 0.0; cluster_idl = 0.0; cluster_wkp = 0.0; verbose_fprintf(stderr, 1, "\n\nCluster%c%29s | %13s | %7s | %7s | %12s | %12s | %12s |\n", 'A' + current_cluster, "", "[us] Duration", "Power", "Energy", "E_cap", "E_idle", "E_wkup"); /* All C-States on current cluster */ for (j = 0; j < s_phy->cstates->cstate_max + 1; j++) { struct cpuidle_cstate *c = &s_phy->cstates->cstate[j]; if (c->nrdata == 0) { verbose_fprintf(stderr, 2, " C%-2d +%7d hits for [%s]\n", j, c->nrdata, c->name); continue; } cp = find_cstate_energy_info(current_cluster, c->name); if (!cp) { verbose_fprintf(stderr, 2, " C%-2d no energy model for [%s] (%d hits, %f duration)\n", j, c->name, c->nrdata, c->duration); continue; } /* Cluster wakeup energy: defined just for wakeups from C1 */ if (strcmp(c->name, "C1") == 0) { cluster_wkp += c->nrdata * clustp->wakeup_energy.cluster_wakeup_energy; verbose_fprintf(stderr, 1, " C%-2d +%7d wkps frm [%4s] | %13s | %7s | %7d | %12s | %12s | %12.0f |\n", j, c->nrdata, c->name, "", "", clustp->wakeup_energy.cluster_wakeup_energy, "", "", cluster_wkp); } cluster_idl += c->duration * cp->cluster_idle_power; verbose_fprintf(stderr, 1, " C%-2d +%7d hits for [%7s] | %13.0f | %7d | %7s | %12s | %12.0f | %12s |\n", j, c->nrdata, c->name, c->duration, cp->cluster_idle_power, "", "", cluster_idl, ""); } /* Add current cluster wakeup energy contribution */ /* This assumes that cluster wakeup hits equal the number of cluster C-States enter */ /* All C-States and P-States for the CPUs on current cluster */ list_for_each_entry(s_core, &s_phy->core_head, list_core) { list_for_each_entry(s_cpu, &s_core->cpu_head, list_cpu) { /* All C-States of current CPU */ for (i = 0; i < s_cpu->cstates->cstate_max + 1; i++) { struct cpuidle_cstate *c = &s_cpu->cstates->cstate[i]; if (c->nrdata == 0) { verbose_fprintf(stderr, 2, "Cpu%d C%-2d +%7d hits for [%4s]\n", s_cpu->cpu_id, i, c->nrdata, c->name); continue; } cp = find_cstate_energy_info(current_cluster, c->name); if (!cp) { verbose_fprintf(stderr, 2, "Cpu%d C%-2d no energy model for [%s] (%d hits, %f duration)\n", s_cpu->cpu_id, i, c->name, c->nrdata, c->duration); continue; } cluster_idl += c->duration * cp->core_idle_power; verbose_fprintf(stderr, 1, "Cpu%d C%-2d +%7d hits for [%7s] | %13.0f | %7d | %7s | %12s | %12.0f | %12s |\n", s_cpu->cpu_id, i, c->nrdata, c->name, c->duration, cp->core_idle_power, "", "", cluster_idl, ""); /* CPU wakeup energy: defined just for wakeups from WFI (on filtered trace) */ if (strcmp(c->name, "WFI") == 0) { cluster_wkp += c->nrdata * clustp->wakeup_energy.core_wakeup_energy; verbose_fprintf(stderr, 1, "Cpu%d C%-2d +%6d wkps frm [%4s] | %13s | %7s | %7d | %12s | %12s | %12.0f |\n", s_cpu->cpu_id, i, c->nrdata, c->name, "", "", clustp->wakeup_energy.core_wakeup_energy, "", "", cluster_idl); } } /* All P-States of current CPU */ for (i = 0; i < s_cpu->pstates->max; i++) { struct cpufreq_pstate *p = &s_cpu->pstates->pstate[i]; if (p->count == 0) { verbose_fprintf(stderr, 2, "Cpu%d P%-2d +%7d hits for [%d]\n", s_cpu->cpu_id, i, p->count, p->freq/1000); continue; } pp = find_pstate_energy_info(current_cluster, p->freq/1000); if (!pp) { verbose_fprintf(stderr, 2, "Cpu%d P%-2d no energy model for [%d] (%d hits, %f duration)\n", s_cpu->cpu_id, i, p->freq/1000, p->count, p->duration); continue; } pp->max_core_duration = MAX(p->duration, pp->max_core_duration); cluster_cap += p->duration * pp->core_power; verbose_fprintf(stderr, 1, "Cpu%d P%-2d +%7d hits for [%7d] | %13.0f | %7d | %7s | %12.0f | %12s | %12s |\n", s_cpu->cpu_id, i, p->count, p->freq/1000, p->duration, pp->core_power, "", cluster_cap, "", ""); } } } /* * XXX * No cluster P-state duration info available yet, so estimate this * as the maximum of the durations of its cores at that frequency. */ for (i = 0; i < clustp->number_cap_states; i++) { pp = &clustp->p_energy[i]; cluster_cap += pp->max_core_duration * pp->cluster_power; verbose_fprintf(stderr, 1, " P%02d cap estimate for [%7d] | %13.0f | %7d | %7s | %12.0f | %12s | %12s |\n", clustp->number_cap_states - i - 1, pp->speed, pp->max_core_duration, pp->cluster_power, "", cluster_cap, "", ""); } /* The scheduler model is normalized to a wake-up rate of 1000 wake-ups per * second. Thus this conversion is required: * * idlestat_wakeup_energy = sched_wakeup_energy * 47742 / (1000 * 1024) * * where: * - 47742: Removes wake-up tracking pre-scaling. * - 1024: Removes << 10 * - 1000: Single wake-up instead of 1000/s. */ cluster_wkp *= 47742; cluster_wkp /= (1024 * 1000); printf("\n"); verbose_fprintf(stderr, 1, "\n\nCluster%c%29s | %13s | %7s | %7s | %12s | %12s | %12s |\n", 'A' + current_cluster, "", "[us] Duration", "Power", "Energy", "E_cap", "E_idle", "E_wkup"); /* Convert all [us] components to [s] just here to avoid summing * truncation errors due to small components */ cluster_cap = US_TO_SEC(cluster_cap); cluster_idl = US_TO_SEC(cluster_idl); printf("Cluster%c Energy Caps %14.0f (%e)\n", 'A' + current_cluster, cluster_cap, cluster_cap); total_cap += cluster_cap; printf("Cluster%c Energy Idle %14.0f (%e)\n", 'A' + current_cluster, cluster_idl, cluster_idl); total_idl += cluster_idl; printf("Cluster%c Energy Wkps %14.0f (%e)\n", 'A' + current_cluster, cluster_wkp, cluster_wkp); total_wkp += cluster_wkp; cluster_energy = cluster_cap + cluster_idl + cluster_wkp; total_energy += cluster_energy; printf("Cluster%c Energy Index %14.0f (%e)\n", 'A' + current_cluster, cluster_energy, cluster_energy); } printf("\n Total Energy Index %14.0f (%e)\n\n\n", total_energy, total_energy); } idlestat-0.5/default_report.c0000664000175000017500000002244512454011467015031 0ustar kingking/* * default_report.c * * Copyright (C) 2014, Linaro Limited. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * 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; version 2 of the License. * * 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., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Contributors: * Tuukka Tikkanen * */ #include #include #include #include #include #include #include "report_ops.h" #include "idlestat.h" #include "utils.h" static void charrep(char c, int count) { int i; for (i = 0; i < count; i++) printf("%c", c); } static int default_check_output(struct program_options *options, void *report_data) { if (check_window_size() && !options->outfilename) { fprintf(stderr, "The terminal must be at least " "80 columns wide\n"); return 1; } return 0; } static int default_open_report_file(char *path, void *report_data) { return redirect_stdout_to_file(path); } static int default_close_report_file(void *report_data) { return (fclose(stdout) == EOF) ? -1 : 0; } /* Topology headers for all tables (C-state/P-state/Wakeups) */ static void boxless_cpu_header(const char *cpu, void *report_data) { /* No pipe characters and less aggressive indentions */ if (strstr(cpu, "cluster")) printf(" %s\n", cpu); else if (strstr(cpu, "core")) printf(" %s\n", cpu); else printf(" %s\n", cpu); } static void default_cpu_header(const char *cpu, int len) { charrep('-', len); printf("\n"); if (strstr(cpu, "cluster")) printf("| %-*s |\n", len - 4, cpu); else if (strstr(cpu, "core")) printf("| %-*s |\n", len - 9, cpu); else printf("| %-*s |\n", len - 16, cpu); charrep('-', len); printf("\n"); } static void default_end_cpu(void *report_data) { } static void boxless_end_cpu(void *report_data) { printf("\n"); } /* C-states */ static void boxless_cstate_table_header(void *report_data) { /* Note: Data is right-aligned, so boxless headers are too */ printf(" C-state min max avg total hits over under\n"); } static void default_cstate_table_header(void *report_data) { /* Note: Boxed header columns appear centered */ charrep('-', 80); printf("\n| C-state | min | max | avg | total | hits | over | under |\n"); } static void default_cstate_cpu_header(const char *cpu, void *report_data) { default_cpu_header(cpu, 80); } static void boxless_cstate_single_state(struct cpuidle_cstate *c, void *report_data) { printf(" %8s ", c->name); display_factored_time(c->min_time == DBL_MAX ? 0. : c->min_time, 8); printf(" "); display_factored_time(c->max_time, 8); printf(" "); display_factored_time(c->avg_time, 8); printf(" "); display_factored_time(c->duration, 8); printf(" "); printf("%5d %5d %5d\n", c->nrdata, c->early_wakings, c->late_wakings); } static void default_cstate_single_state(struct cpuidle_cstate *c, void *report_data) { printf("| %8s | ", c->name); display_factored_time(c->min_time == DBL_MAX ? 0. : c->min_time, 8); printf(" | "); display_factored_time(c->max_time, 8); printf(" | "); display_factored_time(c->avg_time, 8); printf(" | "); display_factored_time(c->duration, 8); printf(" | "); printf("%5d | %5d | %5d |\n", c->nrdata, c->early_wakings, c->late_wakings); } static void boxless_cstate_table_footer(void *report_data) { printf("\n"); } static void default_cstate_table_footer(void *report_data) { charrep('-', 80); printf("\n\n"); } /* P-states */ static void boxless_pstate_table_header(void *report_data) { /* Note: Data is right-aligned, so boxless headers are too */ printf(" P-state min max avg total hits\n"); } static void default_pstate_table_header(void *report_data) { charrep('-', 64); printf("\n"); /* Note: Boxed header columns appear centered */ printf("| P-state | min | max | avg | total | hits |\n"); } static void default_pstate_cpu_header(const char *cpu, void *report_data) { default_cpu_header(cpu, 64); } static void boxless_pstate_single_freq(struct cpufreq_pstate *p, void *report_data) { printf(" "); display_factored_freq(p->freq, 8); printf(" "); display_factored_time(p->min_time == DBL_MAX ? 0. : p->min_time, 8); printf(" "); display_factored_time(p->max_time, 8); printf(" "); display_factored_time(p->avg_time, 8); printf(" "); display_factored_time(p->duration, 8); printf(" %5d\n", p->count); } static void default_pstate_single_freq(struct cpufreq_pstate *p, void *report_data) { printf("| "); display_factored_freq(p->freq, 8); printf(" | "); display_factored_time(p->min_time == DBL_MAX ? 0. : p->min_time, 8); printf(" | "); display_factored_time(p->max_time, 8); printf(" | "); display_factored_time(p->avg_time, 8); printf(" | "); display_factored_time(p->duration, 8); printf(" | %5d |\n", p->count); } static void boxless_pstate_table_footer(void *report_data) { printf("\n"); } static void default_pstate_table_footer(void *report_data) { charrep('-', 64); printf("\n\n"); } /* Wakeups */ static void boxless_wakeup_table_header(void *report_data) { /* * Note: Columns 1 and 2 are left-aligned, others are right-aligned. * Boxless headers follow the data convention */ printf(" IRQ Name Count early late\n"); } static void default_wakeup_table_header(void *report_data) { charrep('-', 55); printf("\n"); /* Note: Boxed header columns appear centered */ printf("| IRQ | Name | Count | early | late |\n"); } static void default_wakeup_cpu_header(const char *cpu, void *report_data) { default_cpu_header(cpu, 55); } static void boxless_wakeup_single_irq(struct wakeup_irq *irqinfo, void *report_data) { if (irqinfo->id != -1) { printf(" %-3d %-15.15s %7d %7d %7d\n", irqinfo->id, irqinfo->name, irqinfo->count, irqinfo->early_triggers, irqinfo->late_triggers); } else { printf(" IPI %-15.15s %7d %7d %7d\n", irqinfo->name, irqinfo->count, irqinfo->early_triggers, irqinfo->late_triggers); } } static void default_wakeup_single_irq(struct wakeup_irq *irqinfo, void *report_data) { if (irqinfo->id != -1) { printf("| %-3d | %-15.15s | %7d | %7d | %7d |\n", irqinfo->id, irqinfo->name, irqinfo->count, irqinfo->early_triggers, irqinfo->late_triggers); } else { printf("| IPI | %-15.15s | %7d | %7d | %7d |\n", irqinfo->name, irqinfo->count, irqinfo->early_triggers, irqinfo->late_triggers); } } static void boxless_wakeup_table_footer(void *report_data) { printf("\n"); } static void default_wakeup_table_footer(void *report_data) { charrep('-', 55); printf("\n\n"); } static struct report_ops default_report_ops = { .name = "default", .check_output = default_check_output, /* Shared */ .open_report_file = default_open_report_file, /* Shared */ .close_report_file = default_close_report_file, /* Shared */ .cstate_table_header = default_cstate_table_header, .cstate_table_footer = default_cstate_table_footer, .cstate_cpu_header = default_cstate_cpu_header, .cstate_single_state = default_cstate_single_state, .cstate_end_cpu = default_end_cpu, .pstate_table_header = default_pstate_table_header, .pstate_table_footer = default_pstate_table_footer, .pstate_cpu_header = default_pstate_cpu_header, .pstate_single_freq = default_pstate_single_freq, .pstate_end_cpu = default_end_cpu, .wakeup_table_header = default_wakeup_table_header, .wakeup_table_footer = default_wakeup_table_footer, .wakeup_cpu_header = default_wakeup_cpu_header, .wakeup_single_irq = default_wakeup_single_irq, .wakeup_end_cpu = default_end_cpu, }; EXPORT_REPORT_OPS(default); static struct report_ops boxless_report_ops = { .name = "boxless", .check_output = default_check_output, .open_report_file = default_open_report_file, .close_report_file = default_close_report_file, .cstate_table_header = boxless_cstate_table_header, .cstate_table_footer = boxless_cstate_table_footer, .cstate_cpu_header = boxless_cpu_header, .cstate_single_state = boxless_cstate_single_state, .cstate_end_cpu = boxless_end_cpu, .pstate_table_header = boxless_pstate_table_header, .pstate_table_footer = boxless_pstate_table_footer, .pstate_cpu_header = boxless_cpu_header, .pstate_single_freq = boxless_pstate_single_freq, .pstate_end_cpu = boxless_end_cpu, .wakeup_table_header = boxless_wakeup_table_header, .wakeup_table_footer = boxless_wakeup_table_footer, .wakeup_cpu_header = boxless_cpu_header, .wakeup_single_irq = boxless_wakeup_single_irq, .wakeup_end_cpu = boxless_end_cpu, }; EXPORT_REPORT_OPS(boxless); idlestat-0.5/tracefile_ftrace.c0000664000175000017500000000655512454011467015300 0ustar kingking/* * tracefile_ftrace.c * * Copyright (C) 2014, Linaro Limited. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * 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; version 2 of the License. * * 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., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Contributors: * Tuukka Tikkanen */ #include "topology.h" #include "trace_ops.h" #include "utils.h" #include "idlestat.h" #include #include #include #include #include #include #define TRACE_FORMAT "%*[^]]] %*s %lf:%*[^=]=%u%*[^=]=%d" static int ftrace_magic(const char *filename) { FILE *f; char *line; char buffer[BUFSIZE]; f = fopen(filename, "r"); if (!f) { fprintf(stderr, "%s: failed to open '%s': %m\n", __func__, filename); return -1; } line = fgets(buffer, BUFSIZE, f); fclose(f); return (line != NULL) && !strncmp(buffer, "# tracer", 8); } static struct cpuidle_datas * ftrace_load(const char *filename) { FILE *f; unsigned int nrcpus; struct cpuidle_datas *datas; int ret; char *line; char buffer[BUFSIZE]; f = fopen(filename, "r"); if (!f) { fprintf(stderr, "%s: failed to open '%s': %m\n", __func__, filename); return ptrerror(NULL); } /* Version line */ line = fgets(buffer, BUFSIZE, f); if (!line) goto error_close; /* Number of CPUs */ nrcpus = 0; while(!feof(f)) { if (buffer[0] != '#') break; if (strncmp(buffer, "#P:", 3)) { ret = sscanf(buffer, "#%*[^#]#P:%u", &nrcpus); if (ret != 1) nrcpus = 0; } line = fgets(buffer, BUFSIZE, f); } if (!line) goto error_close; if (!nrcpus) { fclose(f); return ptrerror("Cannot load trace file (nrcpus == 0)"); } datas = calloc(sizeof(*datas), 1); if (!datas) { fclose(f); return ptrerror(__func__); } datas->nrcpus = nrcpus; datas->pstates = build_pstate_info(nrcpus); if (!datas->pstates) goto propagate_error_free_datas; datas->topo = read_sysfs_cpu_topo(); if (is_err(datas->topo)) goto propagate_error_free_datas; /* Build C-state information from current host sysfs */ datas->cstates = build_cstate_info(nrcpus); if (is_err(datas->cstates)) goto propagate_error_free_datas; load_text_data_lines(f, buffer, datas); fclose(f); return datas; propagate_error_free_datas: fclose(f); if (!is_err(datas->topo)) release_cpu_topo_info(datas->topo); if (!is_err(datas->cstates)) release_cstate_info(datas->cstates, nrcpus); free(datas); return ptrerror(NULL); error_close: fclose(f); fprintf(stderr, "%s: error or EOF while reading '%s': %m", __func__, filename); return ptrerror(NULL); } static const struct trace_ops ftrace_trace_ops = { .name = "ftrace", .check_magic = ftrace_magic, .load = ftrace_load }; EXPORT_TRACE_OPS(ftrace); idlestat-0.5/utils.h0000664000175000017500000000367712454011467013165 0ustar kingking/* * utils.h * * Copyright (C) 2014, Linaro Limited. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * 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; version 2 of the License. * * 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., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Contributors: * Daniel Lezcano * Zoran Markovic * Koan-Sin Tan * Tuukka Tikkanen * */ #ifndef __UTILS_H #define __UTILS_H #include extern void set_verbose_level(int level); extern int verbose_printf(int min_level, const char *fmt, ...); extern int verbose_fprintf(FILE *f, int min_level, const char *fmt, ...); extern int write_int(const char *path, int val); extern int read_int(const char *path, int *val); extern int read_char(const char *path, char *val); extern int store_line(const char *line, void *data); extern int file_read_value(const char *path, const char *name, const char *format, void *value); extern int redirect_stdout_to_file(const char *path); extern void display_factored_time(double time, int align); extern void display_factored_freq(int freq, int align); extern int check_window_size(void); extern int error(const char *str); extern void *ptrerror(const char *str); extern int is_err(const void *ptr); #endif idlestat-0.5/ops_head.c0000664000175000017500000000241112454011467013563 0ustar kingking/* * ops_head.c * * Copyright (C) 2014, Linaro Limited. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * 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; version 2 of the License. * * 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., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Contributors: * Tuukka Tikkanen * */ #include "report_ops.h" #include "trace_ops.h" #include const struct trace_ops __attribute__((__used__)) __attribute__ ((__section__ ("__trace_ops"))) *trace_ops_head = NULL; const struct report_ops __attribute__((__used__)) __attribute__ ((__section__ ("__report_ops"))) *report_ops_head = NULL; idlestat-0.5/LICENSE0000664000175000017500000004313312454011467012650 0ustar kingking GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 51 Franklin St, 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 Library 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 St, 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 Library General Public License instead of this License. idlestat-0.5/Makefile0000664000175000017500000000251012454013112013262 0ustar kingking# # Makefile # # Copyright (C) 2014, Linaro Limited # # 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. # # Contributors: # Daniel Lezcano # Zoran Markovic # CFLAGS?=-g -Wall CC=gcc TRACE_OBJS = tracefile_idlestat.o tracefile_ftrace.o \ tracefile_tracecmd.o REPORT_OBJS = default_report.o csv_report.o comparison_report.o OBJS = idlestat.o topology.o trace.o utils.o energy_model.o reports.o \ ops_head.o \ $(REPORT_OBJS) \ $(TRACE_OBJS) \ ops_tail.o default: idlestat %.o: %.c $(CROSS_COMPILE)$(CC) -c -o $@ $< $(CFLAGS) idlestat: $(OBJS) $(CROSS_COMPILE)$(CC) $(CFLAGS) $(OBJS) -o $@ clean: rm -f $(OBJS) idlestat idlestat-0.5/ops_tail.c0000664000175000017500000000242712454011467013622 0ustar kingking/* * ops_tail.c * * Copyright (C) 2014, Linaro Limited. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * 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; version 2 of the License. * * 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., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Contributors: * Tuukka Tikkanen * */ #include "report_ops.h" #include "trace_ops.h" #include static const struct trace_ops __attribute__((__used__)) __attribute__ ((__section__ ("__trace_ops"))) *trace_ops_tail = NULL; static const struct report_ops __attribute__((__used__)) __attribute__ ((__section__ ("__report_ops"))) *report_ops_tail = NULL; idlestat-0.5/idlestat.h0000664000175000017500000001102512454011467013620 0ustar kingking/* * idlestat.h * * Copyright (C) 2014, Linaro Limited. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * 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; version 2 of the License. * * 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., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Contributors: * Daniel Lezcano * Zoran Markovic * Tuukka Tikkanen * */ #ifndef __IDLESTAT_H #define __IDLESTAT_H #define BUFSIZE 256 #define NAMELEN 16 #define MAXCSTATE 16 #define MAXPSTATE 16 #define MAX(A, B) (A > B ? A : B) #define MIN(A, B) (A < B ? A : B) #define AVG(A, B, I) ((A) + ((B - A) / (I))) #define IRQ_WAKEUP_UNIT_NAME "cpu" #define CPUIDLE_STATE_TARGETRESIDENCY_PATH_FORMAT \ "/sys/devices/system/cpu/cpu%d/cpuidle/state%d/residency" #define CPUFREQ_AVFREQ_PATH_FORMAT \ "/sys/devices/system/cpu/cpu%d/cpufreq/scaling_available_frequencies" #define CPUIDLE_STATENAME_PATH_FORMAT \ "/sys/devices/system/cpu/cpu%d/cpuidle/state%d/name" #define CPUFREQ_CURFREQ_PATH_FORMAT \ "/sys/devices/system/cpu/cpu%d/cpufreq/cpuinfo_cur_freq" struct cpuidle_data { double begin; double end; double duration; }; struct cpuidle_cstate { char *name; struct cpuidle_data *data; int nrdata; int early_wakings; int late_wakings; double avg_time; double max_time; double min_time; double duration; int target_residency; /* -1 if not available */ }; struct wakeup_irq { int id; char name[NAMELEN+1]; int count; int early_triggers; int late_triggers; }; struct wakeup_info { struct wakeup_irq *irqinfo; int nrdata; }; struct cpuidle_cstates { struct cpuidle_cstate cstate[MAXCSTATE]; struct wakeup_info wakeinfo; int current_cstate; int cstate_max; struct wakeup_irq *wakeirq; enum {as_expected, too_long, too_short} actual_residency; }; extern void release_cstate_info(struct cpuidle_cstates *cstates, int nrcpus); struct cpufreq_pstate { int id; unsigned int freq; int count; double min_time; double max_time; double avg_time; double duration; }; struct cpufreq_pstates { struct cpufreq_pstate *pstate; int current; int idle; double time_enter; double time_exit; int max; }; struct cpu_topology; struct cpuidle_datas { struct cpuidle_cstates *cstates; struct cpufreq_pstates *pstates; struct cpu_topology *topo; struct cpuidle_datas *baseline; int nrcpus; }; enum modes { TRACE = 0, IMPORT }; struct program_options { int mode; int display; unsigned int duration; char *filename; char *baseline_filename; char *outfilename; int verbose; char *energy_model_filename; char *report_type_name; }; #define IDLE_DISPLAY 0x1 #define FREQUENCY_DISPLAY 0x2 #define WAKEUP_DISPLAY 0x4 struct cpuidle_datas *idlestat_load(const char *); struct pstate_energy_info { unsigned int speed; unsigned int cluster_power; unsigned int core_power; double max_core_duration; }; struct cstate_energy_info { char cstate_name[NAMELEN]; unsigned int cluster_idle_power; unsigned int core_idle_power; double cluster_duration; }; struct wakeup_energy_info { unsigned int cluster_wakeup_energy; unsigned int core_wakeup_energy; }; enum energy_file_parse_state { uninitialized = 0, parsed_cluster_info, parsing_cap_states, parsing_c_states }; struct cluster_energy_info { unsigned int number_cap_states; unsigned int number_c_states; struct pstate_energy_info *p_energy; struct cstate_energy_info *c_energy; struct wakeup_energy_info wakeup_energy; enum energy_file_parse_state state; }; struct init_pstates { int nrcpus; unsigned int *freqs; }; extern int store_data(double time, int state, int cpu, struct cpuidle_datas *datas); extern struct cpuidle_cstates *build_cstate_info(int nrcpus); extern struct cpufreq_pstates *build_pstate_info(int nrcpus); extern void cpu_change_pstate(struct cpuidle_datas *datas, int cpu, unsigned int freq, double time); extern int get_wakeup_irq(struct cpuidle_datas *datas, char *buffer, int count); #endif idlestat-0.5/report_ops.h0000664000175000017500000000530012454011467014202 0ustar kingking/* * report_ops.h * * Copyright (C) 2014, Linaro Limited. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * 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; version 2 of the License. * * 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., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Contributors: * Tuukka Tikkanen * */ #ifndef __REPORT_OPS_H #define __REPORT_OPS_H struct program_options; struct cpuidle_cstate; struct cpufreq_pstate; struct wakeup_irq; struct report_ops { const char *name; /* No other method may be called or address taken before this */ int (*prepare)(struct report_ops *); int (*check_options)(struct program_options *); int (*check_output)(struct program_options *, void *); void * (*allocate_report_data)(struct program_options *); void (*release_report_data)(void *); int (*open_report_file)(char *path, void *); int (*close_report_file)(void *); void (*cstate_table_header)(void *); void (*cstate_table_footer)(void *); void (*cstate_cpu_header)(const char *cpu, void *); void (*cstate_baseline_state)(struct cpuidle_cstate*, void *); void (*cstate_single_state)(struct cpuidle_cstate*, void *); void (*cstate_end_cpu)(void *); void (*pstate_table_header)(void *); void (*pstate_table_footer)(void *); void (*pstate_cpu_header)(const char *cpu, void *); void (*pstate_baseline_freq)(struct cpufreq_pstate*, void *); void (*pstate_single_freq)(struct cpufreq_pstate*, void *); void (*pstate_end_cpu)(void*); void (*wakeup_table_header)(void *); void (*wakeup_table_footer)(void *); void (*wakeup_cpu_header)(const char *cpu, void *); void (*wakeup_single_irq)(struct wakeup_irq *irqinfo, void *); void (*wakeup_end_cpu)(void *); }; extern void list_report_formats_to_stderr(void); extern struct report_ops *get_report_ops(const char *name); #define EXPORT_REPORT_OPS(reporttype_name) \ static const struct report_ops \ __attribute__ ((__used__)) \ __attribute__ ((__section__ ("__report_ops"))) \ * reporttype_name ## _report_ptr = &reporttype_name##_report_ops extern const struct report_ops *report_ops_head; #endif idlestat-0.5/tracefile_tracecmd.c0000664000175000017500000000747012454011467015613 0ustar kingking/* * tracefile_tracecmd.c * * Copyright (C) 2014, Linaro Limited. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * 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; version 2 of the License. * * 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., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Contributors: * Koan-Sin Tan * Tuukka Tikkanen * */ #include "topology.h" #include "trace_ops.h" #include "utils.h" #include "idlestat.h" #include #include #include #include #include #include #define TRACE_CMD_REPORT_FORMAT "%*[^]]] %lf:%*[^=]=%u%*[^=]=%d" void tracecmd_load_text_data_lines(FILE *f, char *buffer, struct cpuidle_datas *datas) { double begin = 0, end = 0; size_t count = 0, start = 1; setup_topo_states(datas); do { load_text_data_line(buffer, datas, TRACE_CMD_REPORT_FORMAT, &begin, &end, &count, &start); } while (fgets(buffer, BUFSIZE, f)); fprintf(stderr, "Log is %lf secs long with %zd events\n", end - begin, count); } static int tracecmd_report_magic(const char *filename) { FILE *f; char *line; char buffer[BUFSIZE]; f = fopen(filename, "r"); if (!f) { fprintf(stderr, "%s: failed to open '%s': %m\n", __func__, filename); return -1; } line = fgets(buffer, BUFSIZE, f); fclose(f); return (line != NULL) && !strncmp(buffer, "version = ", 10); } static struct cpuidle_datas * tracecmd_report_load(const char *filename) { FILE *f; unsigned int nrcpus; struct cpuidle_datas *datas; int ret; char *line; char buffer[BUFSIZE]; f = fopen(filename, "r"); if (!f) { fprintf(stderr, "%s: failed to open '%s': %m\n", __func__, filename); return ptrerror(NULL); } /* Version line */ line = fgets(buffer, BUFSIZE, f); if (!line) goto error_close; /* Number of CPUs */ nrcpus = 0; line = fgets(buffer, BUFSIZE, f); ret = sscanf(buffer, "cpus=%u", &nrcpus); if (ret != 1) nrcpus = 0; line = fgets(buffer, BUFSIZE, f); if (!line) goto error_close; if (!nrcpus) { fclose(f); return ptrerror("Cannot load trace file (nrcpus == 0)"); } datas = calloc(sizeof(*datas), 1); if (!datas) { fclose(f); return ptrerror(__func__); } datas->nrcpus = nrcpus; datas->pstates = build_pstate_info(nrcpus); if (!datas->pstates) goto propagate_error_free_datas; datas->topo = read_sysfs_cpu_topo(); if (is_err(datas->topo)) goto propagate_error_free_datas; /* Build C-state information from current host sysfs */ datas->cstates = build_cstate_info(nrcpus); if (is_err(datas->cstates)) goto propagate_error_free_datas; tracecmd_load_text_data_lines(f, buffer, datas); fclose(f); return datas; propagate_error_free_datas: fclose(f); if (!is_err(datas->topo)) release_cpu_topo_info(datas->topo); if (!is_err(datas->cstates)) release_cstate_info(datas->cstates, nrcpus); free(datas); return ptrerror(NULL); error_close: fclose(f); fprintf(stderr, "%s: error or EOF while reading '%s': %m", __func__, filename); return ptrerror(NULL); } static const struct trace_ops tracecmd_report_trace_ops = { .name = "trace-cmd report", .check_magic = tracecmd_report_magic, .load = tracecmd_report_load }; EXPORT_TRACE_OPS(tracecmd_report); idlestat-0.5/reports.c0000664000175000017500000000473212454011467013507 0ustar kingking/* * reports.c * * Copyright (C) 2014, Linaro Limited. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * 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; version 2 of the License. * * 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., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Contributors: * Tuukka Tikkanen * */ #include "report_ops.h" #include "utils.h" #include #include #include void list_report_formats_to_stderr(void) { const struct report_ops **ops_it; for (ops_it = (&report_ops_head)+1 ; *ops_it ; ++ops_it) fprintf(stderr, " %s", (*ops_it)->name); fprintf(stderr, "\n"); } struct report_ops *get_report_ops(const char *name) { struct report_ops **ops_it; for (ops_it = (struct report_ops **)(&report_ops_head)+1 ; *ops_it ; ++ops_it) { /* Compare name */ assert((*ops_it)->name); if (strcmp((*ops_it)->name, name)) continue; /* Prepare for use */ if ((*ops_it)->prepare && -1 == (*ops_it)->prepare(*ops_it)) return NULL; /* Check mandatory operations */ assert((*ops_it)->check_output); assert((*ops_it)->open_report_file); assert((*ops_it)->close_report_file); assert((*ops_it)->cstate_table_header); assert((*ops_it)->cstate_table_footer); assert((*ops_it)->cstate_cpu_header); assert((*ops_it)->cstate_single_state); assert((*ops_it)->cstate_end_cpu); assert((*ops_it)->pstate_table_header); assert((*ops_it)->pstate_table_footer); assert((*ops_it)->pstate_cpu_header); assert((*ops_it)->pstate_single_freq); assert((*ops_it)->pstate_end_cpu); assert((*ops_it)->wakeup_table_header); assert((*ops_it)->wakeup_table_footer); assert((*ops_it)->wakeup_cpu_header); assert((*ops_it)->wakeup_single_irq); assert((*ops_it)->wakeup_end_cpu); return *ops_it; } fprintf(stderr, "Report style %s does not exist\n", name); return ptrerror(NULL); } idlestat-0.5/Android.mk0000664000175000017500000000221112454011467013544 0ustar kingking# # Android.mk # # Copyright (C) 2014, Linaro Limited # # 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. # # Contributors: # Daniel Lezcano # Zoran Markovic # LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE_TAGS := optional LOCAL_SHARED_LIBRARIES := libstlport LOCAL_MODULE := idlestat LOCAL_C_INCLUDES += bionic \ LOCAL_SRC_FILES += \ idlestat.c \ topology.c \ trace.c \ utils.c \ include $(BUILD_EXECUTABLE) idlestat-0.5/list.h0000664000175000017500000004103712454011467012770 0ustar kingking/* * list.h copied from the Linux kernel * * */ #ifndef _LINUX_LIST_H #define _LINUX_LIST_H #include #include #include #define LIST_POISON1 ((void *)0x00100100) #define LIST_POISON2 ((void *)0x00200200) struct list_head { struct list_head *next, *prev; }; /* * Simple doubly linked list implementation. * * Some of the internal functions ("__xxx") are useful when * manipulating whole lists rather than single entries, as * sometimes we already know the next/prev entries and we can * generate better code by using them directly rather than * using the generic single-entry routines. */ #define LIST_HEAD_INIT(name) { &(name), &(name) } #define LIST_HEAD(name) \ struct list_head name = LIST_HEAD_INIT(name) static inline void INIT_LIST_HEAD(struct list_head *list) { list->next = list; list->prev = list; } /* * Insert a new entry between two known consecutive entries. * * This is only for internal list manipulation where we know * the prev/next entries already! */ static inline void __list_add(struct list_head *new, struct list_head *prev, struct list_head *next) { next->prev = new; new->next = next; new->prev = prev; prev->next = new; } /** * list_add - add a new entry * @new: new entry to be added * @head: list head to add it after * * Insert a new entry after the specified head. * This is good for implementing stacks. */ static inline void list_add(struct list_head *new, struct list_head *head) { __list_add(new, head, head->next); } /** * list_add_tail - add a new entry * @new: new entry to be added * @head: list head to add it before * * Insert a new entry before the specified head. * This is useful for implementing queues. */ static inline void list_add_tail(struct list_head *new, struct list_head *head) { __list_add(new, head->prev, head); } /* * Delete a list entry by making the prev/next entries * point to each other. * * This is only for internal list manipulation where we know * the prev/next entries already! */ static inline void __list_del(struct list_head *prev, struct list_head *next) { next->prev = prev; prev->next = next; } /** * list_del - deletes entry from list. * @entry: the element to delete from the list. * Note: list_empty() on entry does not return true after this, the entry is * in an undefined state. */ static inline void __list_del_entry(struct list_head *entry) { __list_del(entry->prev, entry->next); } static inline void list_del(struct list_head *entry) { __list_del(entry->prev, entry->next); entry->next = LIST_POISON1; entry->prev = LIST_POISON2; } /** * list_replace - replace old entry by new one * @old : the element to be replaced * @new : the new element to insert * * If @old was empty, it will be overwritten. */ static inline void list_replace(struct list_head *old, struct list_head *new) { new->next = old->next; new->next->prev = new; new->prev = old->prev; new->prev->next = new; } static inline void list_replace_init(struct list_head *old, struct list_head *new) { list_replace(old, new); INIT_LIST_HEAD(old); } /** * list_del_init - deletes entry from list and reinitialize it. * @entry: the element to delete from the list. */ static inline void list_del_init(struct list_head *entry) { __list_del_entry(entry); INIT_LIST_HEAD(entry); } /** * list_move - delete from one list and add as another's head * @list: the entry to move * @head: the head that will precede our entry */ static inline void list_move(struct list_head *list, struct list_head *head) { __list_del_entry(list); list_add(list, head); } /** * list_move_tail - delete from one list and add as another's tail * @list: the entry to move * @head: the head that will follow our entry */ static inline void list_move_tail(struct list_head *list, struct list_head *head) { __list_del_entry(list); list_add_tail(list, head); } /** * list_is_last - tests whether @list is the last entry in list @head * @list: the entry to test * @head: the head of the list */ static inline int list_is_last(const struct list_head *list, const struct list_head *head) { return list->next == head; } /** * list_empty - tests whether a list is empty * @head: the list to test. */ static inline int list_empty(const struct list_head *head) { return head->next == head; } /** * list_empty_careful - tests whether a list is empty and not being modified * @head: the list to test * * Description: * tests whether a list is empty _and_ checks that no other CPU might be * in the process of modifying either member (next or prev) * * NOTE: using list_empty_careful() without synchronization * can only be safe if the only activity that can happen * to the list entry is list_del_init(). Eg. it cannot be used * if another CPU could re-list_add() it. */ static inline int list_empty_careful(const struct list_head *head) { struct list_head *next = head->next; return (next == head) && (next == head->prev); } /** * list_rotate_left - rotate the list to the left * @head: the head of the list */ static inline void list_rotate_left(struct list_head *head) { struct list_head *first; if (!list_empty(head)) { first = head->next; list_move_tail(first, head); } } /** * list_is_singular - tests whether a list has just one entry. * @head: the list to test. */ static inline int list_is_singular(const struct list_head *head) { return !list_empty(head) && (head->next == head->prev); } static inline void __list_cut_position(struct list_head *list, struct list_head *head, struct list_head *entry) { struct list_head *new_first = entry->next; list->next = head->next; list->next->prev = list; list->prev = entry; entry->next = list; head->next = new_first; new_first->prev = head; } /** * list_cut_position - cut a list into two * @list: a new list to add all removed entries * @head: a list with entries * @entry: an entry within head, could be the head itself * and if so we won't cut the list * * This helper moves the initial part of @head, up to and * including @entry, from @head to @list. You should * pass on @entry an element you know is on @head. @list * should be an empty list or a list you do not care about * losing its data. * */ static inline void list_cut_position(struct list_head *list, struct list_head *head, struct list_head *entry) { if (list_empty(head)) return; if (list_is_singular(head) && (head->next != entry && head != entry)) return; if (entry == head) INIT_LIST_HEAD(list); else __list_cut_position(list, head, entry); } static inline void __list_splice(const struct list_head *list, struct list_head *prev, struct list_head *next) { struct list_head *first = list->next; struct list_head *last = list->prev; first->prev = prev; prev->next = first; last->next = next; next->prev = last; } /** * list_splice - join two lists, this is designed for stacks * @list: the new list to add. * @head: the place to add it in the first list. */ static inline void list_splice(const struct list_head *list, struct list_head *head) { if (!list_empty(list)) __list_splice(list, head, head->next); } /** * list_splice_tail - join two lists, each list being a queue * @list: the new list to add. * @head: the place to add it in the first list. */ static inline void list_splice_tail(struct list_head *list, struct list_head *head) { if (!list_empty(list)) __list_splice(list, head->prev, head); } /** * list_splice_init - join two lists and reinitialise the emptied list. * @list: the new list to add. * @head: the place to add it in the first list. * * The list at @list is reinitialised */ static inline void list_splice_init(struct list_head *list, struct list_head *head) { if (!list_empty(list)) { __list_splice(list, head, head->next); INIT_LIST_HEAD(list); } } /** * list_splice_tail_init - join two lists and reinitialise the emptied list * @list: the new list to add. * @head: the place to add it in the first list. * * Each of the lists is a queue. * The list at @list is reinitialised */ static inline void list_splice_tail_init(struct list_head *list, struct list_head *head) { if (!list_empty(list)) { __list_splice(list, head->prev, head); INIT_LIST_HEAD(list); } } #undef offsetof #define offsetof(s, m) ((size_t)&(((s *)0)->m)) #undef container_of #define container_of(ptr, type, member) ({ \ const typeof(((type *)0)->member) * __mptr = (ptr); \ (type *)((char *)__mptr - offsetof(type, member)); }) /** * list_entry - get the struct for this entry * @ptr: the &struct list_head pointer. * @type: the type of the struct this is embedded in. * @member: the name of the list_struct within the struct. */ #define list_entry(ptr, type, member) \ container_of(ptr, type, member) /** * list_first_entry - get the first element from a list * @ptr: the list head to take the element from. * @type: the type of the struct this is embedded in. * @member: the name of the list_struct within the struct. * * Note, that list is expected to be not empty. */ #define list_first_entry(ptr, type, member) \ list_entry((ptr)->next, type, member) /** * list_for_each - iterate over a list * @pos: the &struct list_head to use as a loop cursor. * @head: the head for your list. */ #define list_for_each(pos, head) \ for (pos = (head)->next; pos != (head); pos = pos->next) /** * __list_for_each - iterate over a list * @pos: the &struct list_head to use as a loop cursor. * @head: the head for your list. * * This variant doesn't differ from list_for_each() any more. * We don't do prefetching in either case. */ #define __list_for_each(pos, head) \ for (pos = (head)->next; pos != (head); pos = pos->next) /** * list_for_each_prev - iterate over a list backwards * @pos: the &struct list_head to use as a loop cursor. * @head: the head for your list. */ #define list_for_each_prev(pos, head) \ for (pos = (head)->prev; pos != (head); pos = pos->prev) /** * list_for_each_safe - iterate over a list safe against removal of list entry * @pos: the &struct list_head to use as a loop cursor. * @n: another &struct list_head to use as temporary storage * @head: the head for your list. */ #define list_for_each_safe(pos, n, head) \ for (pos = (head)->next, n = pos->next; pos != (head); \ pos = n, n = pos->next) /** * list_for_each_prev_safe - iterate over a list backwards safe against removal of list entry * @pos: the &struct list_head to use as a loop cursor. * @n: another &struct list_head to use as temporary storage * @head: the head for your list. */ #define list_for_each_prev_safe(pos, n, head) \ for (pos = (head)->prev, n = pos->prev; \ pos != (head); \ pos = n, n = pos->prev) /** * list_for_each_entry - iterate over list of given type * @pos: the type * to use as a loop cursor. * @head: the head for your list. * @member: the name of the list_struct within the struct. */ #define list_for_each_entry(pos, head, member) \ for (pos = list_entry((head)->next, typeof(*pos), member); \ &pos->member != (head); \ pos = list_entry(pos->member.next, typeof(*pos), member)) /** * list_for_each_entry_reverse - iterate backwards over list of given type. * @pos: the type * to use as a loop cursor. * @head: the head for your list. * @member: the name of the list_struct within the struct. */ #define list_for_each_entry_reverse(pos, head, member) \ for (pos = list_entry((head)->prev, typeof(*pos), member); \ &pos->member != (head); \ pos = list_entry(pos->member.prev, typeof(*pos), member)) /** * list_prepare_entry - prepare a entry for use in list_for_each_entry_continue() * @pos: the type * to use as a start point * @head: the head of the list * @member: the name of the list_struct within the struct. * * Prepares a entry for use as a start point in list_for_each_entry_continue(). */ #define list_prepare_entry(pos, head, member) \ ((pos) ? : list_entry(head, typeof(*pos), member)) /** * list_for_each_entry_continue - continue iteration over list of given type * @pos: the type * to use as a loop cursor. * @head: the head for your list. * @member: the name of the list_struct within the struct. * * Continue to iterate over list of given type, continuing after * the current position. */ #define list_for_each_entry_continue(pos, head, member) \ for (pos = list_entry(pos->member.next, typeof(*pos), member); \ &pos->member != (head); \ pos = list_entry(pos->member.next, typeof(*pos), member)) /** * list_for_each_entry_continue_reverse - iterate backwards from the given point * @pos: the type * to use as a loop cursor. * @head: the head for your list. * @member: the name of the list_struct within the struct. * * Start to iterate over list of given type backwards, continuing after * the current position. */ #define list_for_each_entry_continue_reverse(pos, head, member) \ for (pos = list_entry(pos->member.prev, typeof(*pos), member); \ &pos->member != (head); \ pos = list_entry(pos->member.prev, typeof(*pos), member)) /** * list_for_each_entry_from - iterate over list of given type from the current point * @pos: the type * to use as a loop cursor. * @head: the head for your list. * @member: the name of the list_struct within the struct. * * Iterate over list of given type, continuing from current position. */ #define list_for_each_entry_from(pos, head, member) \ for (; &pos->member != (head); \ pos = list_entry(pos->member.next, typeof(*pos), member)) /** * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry * @pos: the type * to use as a loop cursor. * @n: another type * to use as temporary storage * @head: the head for your list. * @member: the name of the list_struct within the struct. */ #define list_for_each_entry_safe(pos, n, head, member) \ for (pos = list_entry((head)->next, typeof(*pos), member), \ n = list_entry(pos->member.next, typeof(*pos), member); \ &pos->member != (head); \ pos = n, n = list_entry(n->member.next, typeof(*n), member)) /** * list_for_each_entry_safe_continue - continue list iteration safe against removal * @pos: the type * to use as a loop cursor. * @n: another type * to use as temporary storage * @head: the head for your list. * @member: the name of the list_struct within the struct. * * Iterate over list of given type, continuing after current point, * safe against removal of list entry. */ #define list_for_each_entry_safe_continue(pos, n, head, member) \ for (pos = list_entry(pos->member.next, typeof(*pos), member), \ n = list_entry(pos->member.next, typeof(*pos), member); \ &pos->member != (head); \ pos = n, n = list_entry(n->member.next, typeof(*n), member)) /** * list_for_each_entry_safe_from - iterate over list from current point safe against removal * @pos: the type * to use as a loop cursor. * @n: another type * to use as temporary storage * @head: the head for your list. * @member: the name of the list_struct within the struct. * * Iterate over list of given type from current point, safe against * removal of list entry. */ #define list_for_each_entry_safe_from(pos, n, head, member) \ for (n = list_entry(pos->member.next, typeof(*pos), member); \ &pos->member != (head); \ pos = n, n = list_entry(n->member.next, typeof(*n), member)) /** * list_for_each_entry_safe_reverse - iterate backwards over list safe against removal * @pos: the type * to use as a loop cursor. * @n: another type * to use as temporary storage * @head: the head for your list. * @member: the name of the list_struct within the struct. * * Iterate backwards over list of given type, safe against removal * of list entry. */ #define list_for_each_entry_safe_reverse(pos, n, head, member) \ for (pos = list_entry((head)->prev, typeof(*pos), member), \ n = list_entry(pos->member.prev, typeof(*pos), member); \ &pos->member != (head); \ pos = n, n = list_entry(n->member.prev, typeof(*n), member)) /** * list_safe_reset_next - reset a stale list_for_each_entry_safe loop * @pos: the loop cursor used in the list_for_each_entry_safe loop * @n: temporary storage used in list_for_each_entry_safe * @member: the name of the list_struct within the struct. * * list_safe_reset_next is not safe to use in general if the list may be * modified concurrently (eg. the lock is dropped in the loop body). An * exception to this is if the cursor element (pos) is pinned in the list, * and list_safe_reset_next is called after re-taking the lock and before * completing the current iteration of the loop body. */ #define list_safe_reset_next(pos, n, member) \ (n = list_entry(pos->member.next, typeof(*pos), member)) #endif idlestat-0.5/idlestat.c0000664000175000017500000010762412454011467013626 0ustar kingking/* * idlestat.c * * Copyright (C) 2014, Linaro Limited. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * 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; version 2 of the License. * * 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., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Contributors: * Daniel Lezcano * Zoran Markovic * Tuukka Tikkanen * */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include "idlestat.h" #include "utils.h" #include "trace.h" #include "list.h" #include "topology.h" #include "energy_model.h" #include "report_ops.h" #include "trace_ops.h" #define IDLESTAT_VERSION "0.5" #define USEC_PER_SEC 1000000 static char buffer[BUFSIZE]; /* I happen to agree with David Wheeler's assertion that Unix filenames * are too flexible. Eliminate some of the madness. * http://www.dwheeler.com/essays/fixing-unix-linux-filenames.html */ static inline int bad_filename(const char *filename) { const char *c; c = filename; /* Check for first char being '-' */ if (*c == '-') { fprintf(stderr, "Bad character '%c' found in filename\n", *c); return EINVAL; } for (; *c; c++) { /* Check for control chars and other bad characters */ if (*c < 32 || *c == '<' || *c == '>' || *c == '|') { fprintf(stderr, isprint(*c) ? "Bad character '%c' found in filename\n" : "Bad character 0x%02x found in filename\n", *c); return EINVAL; } } return 0; } #define TRACE_TS_FORMAT "%*[^:]:%lf" static int get_trace_ts(double *ts) { FILE *f; f = fopen(TRACE_STAT_FILE, "r"); if (!f) return error("fopen " TRACE_STAT_FILE); while (fgets(buffer, BUFSIZE, f)) { if (!strstr(buffer, "now ts")) continue; fclose(f); if (sscanf(buffer, TRACE_TS_FORMAT, ts) == 1) return 0; fprintf(stderr, "get_trace_ts: Failed to parse timestamp\n"); return -1; } fclose(f); fprintf(stderr, "get_trace_ts: Failed to find timestamp in %s\n", TRACE_STAT_FILE); return -1; } static int display_cstates(struct report_ops *ops, void *arg, void *baseline, char *cpu, void *report_data) { int i; bool cpu_header = false; struct cpuidle_cstates *cstates = arg; struct cpuidle_cstates *base_cstates = baseline; for (i = 0; i < cstates->cstate_max + 1; i++) { struct cpuidle_cstate *c; struct cpuidle_cstate *b; c = cstates->cstate + i; b = base_cstates ? base_cstates->cstate + i : NULL; if (c->nrdata == 0 && (!b || b->nrdata == 0)) /* nothing to report for this state */ continue; if (!cpu_header) { ops->cstate_cpu_header(cpu, report_data); cpu_header = true; } if (b && ops->cstate_baseline_state) ops->cstate_baseline_state(b, report_data); ops->cstate_single_state(c, report_data); } if (cpu_header) ops->cstate_end_cpu(report_data); return 0; } static int display_pstates(struct report_ops *ops, void *arg, void *baseline, char *cpu, void *report_data) { int i; bool cpu_header = false; struct cpufreq_pstates *pstates = arg; struct cpufreq_pstates *base_pstates = baseline; for (i = 0; i < pstates->max; i++) { struct cpufreq_pstate *p; struct cpufreq_pstate *b; p = pstates->pstate + i; b = base_pstates ? base_pstates->pstate + i : NULL; if (p->count == 0 && (!b || b->count == 0)) /* nothing to report for this state */ continue; if (!cpu_header) { ops->pstate_cpu_header(cpu, report_data); cpu_header = true; } if (b && ops->pstate_baseline_freq) ops->pstate_baseline_freq(b, report_data); ops->pstate_single_freq(p, report_data); } if (cpu_header) ops->pstate_end_cpu(report_data); return 0; } static int display_wakeup(struct report_ops *ops, void *arg, void *baseline, char *cpu, void *report_data) { int i; bool cpu_header = false; struct cpuidle_cstates *cstates = arg; struct wakeup_info *wakeinfo = &cstates->wakeinfo; struct wakeup_irq *irqinfo = wakeinfo->irqinfo; for (i = 0; i < wakeinfo->nrdata; i++, irqinfo++) { if (!cpu_header) { ops->wakeup_cpu_header(cpu, report_data); cpu_header = true; } ops->wakeup_single_irq(irqinfo, report_data); } if (cpu_header) ops->wakeup_end_cpu(report_data); return 0; } static char *cpuidle_cstate_name(int cpu, int state) { char *fpath, *name; FILE *snf; char line[256]; if (asprintf(&fpath, CPUIDLE_STATENAME_PATH_FORMAT, cpu, state) < 0) return NULL; /* read cpuidle state name for the CPU */ snf = fopen(fpath, "r"); free(fpath); if (!snf) /* file not found, or other error */ return NULL; name = fgets(line, sizeof(line)/sizeof(line[0]), snf); fclose(snf); if (name) { /* get rid of trailing characters and duplicate string */ name = strtok(name, "\n "); name = strdup(name); } return name; } /** * release_cstate_info - free all C-state related structs * @cstates: per-cpu array of C-state statistics structs * @nrcpus: number of CPUs */ void release_cstate_info(struct cpuidle_cstates *cstates, int nrcpus) { int cpu, i; if (!cstates) /* already cleaned up */ return; /* free C-state names */ for (cpu = 0; cpu < nrcpus; cpu++) { for (i = 0; i < MAXCSTATE; i++) { struct cpuidle_cstate *c = &(cstates[cpu].cstate[i]); free(c->name); free(c->data); } } free(cstates->wakeinfo.irqinfo); /* free the cstates array */ free(cstates); } /** * cpuidle_get_target_residency - return the target residency of a c-state * @cpu: cpuid * @state: c-state number */ int cpuidle_get_target_residency(int cpu, int state) { char *fpath; unsigned int tr; FILE *snf; if (asprintf(&fpath, CPUIDLE_STATE_TARGETRESIDENCY_PATH_FORMAT, cpu, state) < 0) return -1; /* read cpuidle state name for the CPU */ snf = fopen(fpath, "r"); if (!snf) { /* file not found, or other error */ free(fpath); return -1; } fscanf(snf, "%u", &tr); fclose(snf); return tr; } /** * build_cstate_info - parse cpuidle sysfs entries and build per-CPU * structs to maintain statistics of C-state transitions * * @nrcpus: number of CPUs * * @return: per-CPU array of structs (success) or ptrerror() (error) */ struct cpuidle_cstates *build_cstate_info(int nrcpus) { int cpu; struct cpuidle_cstates *cstates; assert(nrcpus > 0); cstates = calloc(nrcpus, sizeof(*cstates)); if (!cstates) return ptrerror(__func__); /* initialize cstate_max for each cpu */ for (cpu = 0; cpu < nrcpus; cpu++) { int i; struct cpuidle_cstate *c; cstates[cpu].cstate_max = -1; cstates[cpu].current_cstate = -1; for (i = 0; i < MAXCSTATE; i++) { c = &(cstates[cpu].cstate[i]); c->name = cpuidle_cstate_name(cpu, i); c->data = NULL; c->nrdata = 0; c->early_wakings = 0; c->late_wakings = 0; c->avg_time = 0.; c->max_time = 0.; c->min_time = DBL_MAX; c->duration = 0.; c->target_residency = cpuidle_get_target_residency(cpu, i); } } return cstates; } static void release_init_pstates(struct init_pstates *initp) { if (!initp) return; free(initp->freqs); free(initp); } static struct init_pstates *build_init_pstates(void) { struct init_pstates *initp; int nrcpus, cpu; unsigned int *freqs; nrcpus = sysconf(_SC_NPROCESSORS_CONF); if (nrcpus < 0) return NULL; initp = calloc(sizeof(*initp), 1); if (!initp) return NULL; freqs = calloc(nrcpus, sizeof(*freqs)); if (!freqs) { free(initp); return NULL; } initp->nrcpus = nrcpus; initp->freqs = freqs; for (cpu = 0; cpu < nrcpus; cpu++) { char *fpath; unsigned int *freq = &(freqs[cpu]); if (asprintf(&fpath, CPUFREQ_CURFREQ_PATH_FORMAT, cpu) < 0) { release_init_pstates(initp); return NULL; } if (read_int(fpath, (int *)freq)) *freq = 0; free(fpath); } return initp; } static void output_pstates(FILE *f, struct init_pstates *initp, int nrcpus, double ts) { int cpu; unsigned int freq; unsigned long ts_sec, ts_usec; ts_sec = (unsigned long)ts; ts_usec = (ts - ts_sec) * USEC_PER_SEC; for (cpu = 0; cpu < nrcpus; cpu++) { freq = initp? initp->freqs[cpu] : -1; fprintf(f, "%16s-%-5d [%03d] .... %5lu.%06lu: cpu_frequency: " "state=%u cpu_id=%d\n", "idlestat", getpid(), cpu, ts_sec, ts_usec, freq, cpu); } } /** * alloc_pstate - allocate and initialize a cpufreq_pstate struct if needed * @pstates: per-CPU P-state statistics struct * @freq: frequency for which the new pstate should be allocated * * This function checks the array of struct cpufreq_pstate in @pstates * for an entry for @freq. If one if found, the index for this entry * is returned. If not, a new entry is inserted into the array so that * the frequencies are in decreasing order and the index for the new * entry is returned. * @return: the index of the existing or newly allocated pstate struct */ static int alloc_pstate(struct cpufreq_pstates *pstates, unsigned int freq) { struct cpufreq_pstate *pstate, *tmp; int nrfreq, i, next = 0; pstate = pstates->pstate; nrfreq = pstates->max; for (i = 0; i < nrfreq && freq <= pstate[i].freq; i++) { if (pstate[i].freq == freq) return i; } next = i; tmp = realloc(pstate, sizeof(*pstate) * (nrfreq + 1)); if (!tmp) { perror(__func__); exit(1); } pstate = tmp; pstates->pstate = tmp; pstates->max = nrfreq + 1; memmove(pstate + next + 1, pstate + next, sizeof(*pstate) * (nrfreq - next)); memset(pstate + next, 0, sizeof(*pstate)); for (i = nrfreq; i > next; i--) pstate[i].id = i; if (pstates->current >= next) pstates->current++; pstate[next].id = next; pstate[next].freq = freq; pstate[next].count = 0; pstate[next].min_time = DBL_MAX; pstate[next].max_time = 0; pstate[next].avg_time = 0; pstate[next].duration = 0; return next; } /** * release_pstate_info - free all P-state related structs * @pstates: per-cpu array of P-state statistics structs * @nrcpus: number of CPUs */ static void release_pstate_info(struct cpufreq_pstates *pstates, int nrcpus) { int cpu; if (!pstates) /* already cleaned up */ return; /* first check and clean per-cpu structs */ for (cpu = 0; cpu < nrcpus; cpu++) if (pstates[cpu].pstate) free(pstates[cpu].pstate); /* now free the master cpufreq structs */ free(pstates); return; } /** * build_pstate_info - allocate and initialize per-CPU structs to maintain * statistics of P-state transitions * * @nrcpus: number of CPUs * * @return: per-CPU array of structs (success) or NULL (error) */ struct cpufreq_pstates *build_pstate_info(int nrcpus) { int cpu; struct cpufreq_pstates *pstates; pstates = calloc(nrcpus, sizeof(*pstates)); if (!pstates) return NULL; for (cpu = 0; cpu < nrcpus; cpu++) { pstates[cpu].pstate = NULL; pstates[cpu].max = 0; pstates[cpu].current = -1; /* unknown */ pstates[cpu].idle = -1; /* unknown */ pstates[cpu].time_enter = 0.; pstates[cpu].time_exit = 0.; } return pstates; } static int get_current_pstate(struct cpuidle_datas *datas, int cpu, struct cpufreq_pstates **pstates, struct cpufreq_pstate **pstate) { struct cpufreq_pstates *ps; if (cpu < 0 || cpu > datas->nrcpus) return -2; ps = &(datas->pstates[cpu]); *pstate = (ps->current == -1 ? NULL : &(ps->pstate[ps->current])); *pstates = ps; /* return 1 if CPU is idle, otherwise return 0 */ return ps->idle; } static void open_current_pstate(struct cpufreq_pstates *ps, double time) { ps->time_enter = time; } static void open_next_pstate(struct cpufreq_pstates *ps, int s, double time) { ps->current = s; if (ps->idle > 0) { fprintf(stderr, "Warning: opening P-state on an idle CPU\n"); return; } open_current_pstate(ps, time); } static void close_current_pstate(struct cpufreq_pstates *ps, double time) { int c = ps->current; struct cpufreq_pstate *p = &(ps->pstate[c]); double elapsed; if (ps->idle > 0) { fprintf(stderr, "Warning: closing P-state on an idle CPU\n"); return; } elapsed = (time - ps->time_enter) * USEC_PER_SEC; p->min_time = MIN(p->min_time, elapsed); p->max_time = MAX(p->max_time, elapsed); p->avg_time = AVG(p->avg_time, elapsed, p->count + 1); p->duration += elapsed; p->count++; } void cpu_change_pstate(struct cpuidle_datas *datas, int cpu, unsigned int freq, double time) { struct cpufreq_pstates *ps; struct cpufreq_pstate *p; int cur, next; cur = get_current_pstate(datas, cpu, &ps, &p); next = alloc_pstate(ps, freq); assert (next >= 0); switch (cur) { case 1: /* if CPU is idle, update current state and leave * stats unchanged */ ps->current = next; return; case -1: /* current pstate is -1, i.e. this is the first update */ open_next_pstate(ps, next, time); return; case 0: /* running CPU, update all stats, but skip closing current * state if it's the initial update for CPU */ if (p) close_current_pstate(ps, time); open_next_pstate(ps, next, time); return; default: fprintf(stderr, "illegal pstate %d for cpu %d, exiting.\n", cur, cpu); exit(-1); } } /** * merge_pstates - make sure both main trace and baseline have same pstates * @datas: pointer to struct cpuidle_datas for main trace * @baseline: pointer to struct cpuidle_datas for baseline trace * * This function adds "empty" pstate records for frequencies that exist * in main trace but not in baseline trace or vice versa. This makes sure * that the data (with zero hits into state for thusly created entries) * exists in both trace results for all frequencies used by either trace. */ static void merge_pstates(struct cpuidle_datas *datas, struct cpuidle_datas *baseline) { int cpu; int idx; struct cpufreq_pstates *percpu_a, *percpu_b; assert(datas && !is_err(datas)); assert(baseline && !is_err(baseline)); for (cpu = 0; cpu < datas->nrcpus; ++cpu) { percpu_a = &(datas->pstates[cpu]); percpu_b = &(baseline->pstates[cpu]); for (idx = 0; idx < percpu_a->max && idx < percpu_b->max; ) { if (percpu_a->pstate[idx].freq > percpu_b->pstate[idx].freq) { assert(alloc_pstate(percpu_b, percpu_a->pstate[idx].freq) == idx); continue; } if (percpu_a->pstate[idx].freq < percpu_b->pstate[idx].freq) { assert(alloc_pstate(percpu_a, percpu_b->pstate[idx].freq) == idx); continue; } ++idx; } } } static void cpu_pstate_idle(struct cpuidle_datas *datas, int cpu, double time) { struct cpufreq_pstates *ps = &(datas->pstates[cpu]); if (ps->current != -1) close_current_pstate(ps, time); ps->idle = 1; } static void cpu_pstate_running(struct cpuidle_datas *datas, int cpu, double time) { struct cpufreq_pstates *ps = &(datas->pstates[cpu]); ps->idle = 0; if (ps->current != -1) open_current_pstate(ps, time); } static int cstate_begin(double time, int state, struct cpuidle_cstates *cstates) { struct cpuidle_cstate *cstate = &cstates->cstate[state]; struct cpuidle_data *data = cstate->data; int nrdata = cstate->nrdata; data = realloc(data, sizeof(*data) * (nrdata + 1)); if (!data) return error(__func__); memset(data + nrdata, 0, sizeof(*data)); data[nrdata].begin = time; cstate->data = data; cstates->cstate_max = MAX(cstates->cstate_max, state); cstates->current_cstate = state; cstates->wakeirq = NULL; return 0; } static void cstate_end(double time, struct cpuidle_cstates *cstates) { int last_cstate = cstates->current_cstate; struct cpuidle_cstate *cstate = &cstates->cstate[last_cstate]; struct cpuidle_data *data = &cstate->data[cstate->nrdata]; data->end = time; data->duration = data->end - data->begin; /* That happens when precision digit in the file exceed * 7 (eg. xxx.1000000). Ignoring the result because I don't * find a way to fix with the sscanf used in the caller */ if (data->duration < 0) data->duration = 0; /* convert to us */ data->duration *= USEC_PER_SEC; cstates->actual_residency = as_expected; if (data->duration < cstate->target_residency) { /* over estimated */ cstate->early_wakings++; cstates->actual_residency = too_short; } else { /* under estimated */ int next_cstate = last_cstate + 1; if (next_cstate <= cstates->cstate_max) { int tr = cstates->cstate[next_cstate].target_residency; if (tr > 0 && data->duration >= tr) { cstate->late_wakings++; cstates->actual_residency = too_long; } } } cstate->min_time = MIN(cstate->min_time, data->duration); cstate->max_time = MAX(cstate->max_time, data->duration); cstate->avg_time = AVG(cstate->avg_time, data->duration, cstate->nrdata + 1); cstate->duration += data->duration; cstate->nrdata++; /* CPU is no longer idle */ cstates->current_cstate = -1; } int record_cstate_event(struct cpuidle_cstates *cstates, double time, int state) { int ret = 0; /* Ignore when we enter the current state (cores and clusters) */ if (state == cstates->current_cstate) return 0; if (cstates->current_cstate != -1) cstate_end(time, cstates); if (state != -1) ret = cstate_begin(time, state, cstates); return ret; } int store_data(double time, int state, int cpu, struct cpuidle_datas *datas) { struct cpuidle_cstates *cstates = &datas->cstates[cpu]; struct cpufreq_pstate *pstate = datas->pstates[cpu].pstate; struct cpu_core *aff_core; struct cpu_physical *aff_cluster; /* ignore when we got a "closing" state first */ if (state == -1 && cstates->cstate_max == -1) return 0; if (record_cstate_event(cstates, time, state) == -1) return -1; /* Update P-state stats if supported */ if (pstate) { if (state == -1) cpu_pstate_running(datas, cpu, time); else cpu_pstate_idle(datas, cpu, time); } /* Update core and cluster */ aff_core = cpu_to_core(cpu, datas->topo); if (record_cstate_event(aff_core->cstates, time, state) == -1) return -1; aff_cluster = cpu_to_cluster(cpu, datas->topo); return record_cstate_event(aff_cluster->cstates, time,state); } static void release_datas(struct cpuidle_datas *datas) { if (datas == NULL) return; release_datas(datas->baseline); release_cpu_topo_cstates(datas->topo); release_cpu_topo_info(datas->topo); release_pstate_info(datas->pstates, datas->nrcpus); release_cstate_info(datas->cstates, datas->nrcpus); free(datas); } static struct wakeup_irq *find_irqinfo(struct wakeup_info *wakeinfo, int irqid, const char *irqname) { struct wakeup_irq *irqinfo; int i; for (i = 0; i < wakeinfo->nrdata; i++) { irqinfo = &wakeinfo->irqinfo[i]; if (irqinfo->id == irqid && !strcmp(irqinfo->name, irqname)) return irqinfo; } return NULL; } static int store_irq(int cpu, int irqid, char *irqname, struct cpuidle_datas *datas) { struct cpuidle_cstates *cstates = &datas->cstates[cpu]; struct wakeup_irq *irqinfo; struct wakeup_info *wakeinfo = &cstates->wakeinfo; if (cstates->wakeirq != NULL) return 0; irqinfo = find_irqinfo(wakeinfo, irqid, irqname); if (NULL == irqinfo) { irqinfo = realloc(wakeinfo->irqinfo, sizeof(*irqinfo) * (wakeinfo->nrdata + 1)); if (!irqinfo) return error("realloc irqinfo"); memset(irqinfo + wakeinfo->nrdata, 0, sizeof(*irqinfo)); wakeinfo->irqinfo = irqinfo; irqinfo += wakeinfo->nrdata++; irqinfo->id = irqid; strncpy(irqinfo->name, irqname, sizeof(irqinfo->name)); irqinfo->name[sizeof(irqinfo->name) - 1] = '\0'; irqinfo->count = 0; irqinfo->early_triggers = 0; irqinfo->late_triggers = 0; } irqinfo->count++; if (cstates->actual_residency == too_short) irqinfo->early_triggers++; else if (cstates->actual_residency == too_long) irqinfo->late_triggers++; cstates->wakeirq = irqinfo; return 0; } static void write_cstate_info(FILE *f, int cpu, char *name, int target) { fprintf(f, "\t%s\n", name); fprintf(f, "\t%d\n", target); } void output_cstate_info(FILE *f, int nrcpus) { struct cpuidle_cstates *cstates; int i, j; cstates = build_cstate_info(nrcpus); assert(!is_err(cstates)); for (i=0; i < nrcpus; i++) { fprintf(f, "cpuid %d:\n", i); for (j=0; j < MAXCSTATE ; j++) { write_cstate_info(f, i, cstates[i].cstate[j].name, cstates[i].cstate[j].target_residency); } } } #define TRACE_IRQ_FORMAT "%*[^[][%d] %*[^=]=%d%*[^=]=%16s" #define TRACE_IPIIRQ_FORMAT "%*[^[][%d] %*[^(](%32s" #define TRACECMD_REPORT_FORMAT "%*[^]]] %lf:%*[^=]=%u%*[^=]=%d" #define TRACE_FORMAT "%*[^]]] %*s %lf:%*[^=]=%u%*[^=]=%d" int get_wakeup_irq(struct cpuidle_datas *datas, char *buffer, int count) { int cpu, irqid; char irqname[NAMELEN+1]; if (strstr(buffer, "irq_handler_entry")) { if (sscanf(buffer, TRACE_IRQ_FORMAT, &cpu, &irqid, irqname) != 3) { fprintf(stderr, "warning: Unrecognized " "irq_handler_entry record skipped.\n"); return -1; } store_irq(cpu, irqid, irqname, datas); return 0; } if (strstr(buffer, "ipi_entry")) { if (sscanf(buffer, TRACE_IPIIRQ_FORMAT, &cpu, irqname) != 2) { fprintf(stderr, "warning: Unrecognized ipi_entry " "record skipped\n"); return -1; } irqname[strlen(irqname) - 1] = '\0'; store_irq(cpu, -1, irqname, datas); return 0; } return -1; } struct cpuidle_datas *idlestat_load(const char *filename) { const struct trace_ops **ops_it; int ret; for (ops_it = (&trace_ops_head)+1 ; *ops_it ; ++ops_it) { assert((*ops_it)->name); assert((*ops_it)->check_magic); assert((*ops_it)->load); ret = (*ops_it)->check_magic(filename); if (ret == -1) return ptrerror(NULL); /* File format supported by these ops? */ if (ret > 0) { return (*ops_it)->load(filename); } } fprintf(stderr, "Trace file format not recognized\n"); return ptrerror(NULL); } static void help(const char *cmd) { fprintf(stderr, "\nUsage:\nTrace mode:\n\t%s --trace -f|--trace-file " " -b|--baseline-trace " " -o|--output-file -t|--duration " " -r|--report-format " " -C|--csv-report -B|--boxless-report" " -c|--idle -p|--frequency -w|--wakeup", basename(cmd)); fprintf(stderr, "\nReporting mode:\n\t%s --import -f|--trace-file " " -b|--baseline-trace " " -r|--report-format " " -C|--csv-report -B|--boxless-report" " -o|--output-file ", basename(cmd)); fprintf(stderr, "\n\nExamples:\n1. Run a trace, post-process the results" " (default is to show only C-state statistics):\n\tsudo " "./%s --trace -f /tmp/mytrace -t 10\n", basename(cmd)); fprintf(stderr, "\n2. Run a trace, post-process the results and print all" " statistics:\n\tsudo ./%s --trace -f /tmp/mytrace -t 10 -p -c -w\n", basename(cmd)); fprintf(stderr, "\n3. Run a trace with an external workload, post-process the" " results:\n\tsudo ./%s --trace -f /tmp/mytrace -t 10 -p -c -w -- rt-app /tmp/mp3.json\n", basename(cmd)); fprintf(stderr, "\n4. Post-process a trace captured earlier:\n\t./%s" " --import -f /tmp/mytrace\n", basename(cmd)); fprintf(stderr, "\n5. Run a trace, post-process the results and print all" " statistics into a file:\n\tsudo ./%s --trace -f /tmp/mytrace -t 10 -p -c -w" " -o /tmp/myreport\n", basename(cmd)); fprintf(stderr, "\n6. Run a comparison trace, say, before and after making changes to system behaviour\n" "\tsudo ./%s --trace -f /tmp/baseline -t 10\n" "\tsudo ./%s --trace -f /tmp/changedstate -t 10\n" "\t./%s --import -f /tmp/changedstate -b /tmp/baseline -r comparison\n", basename(cmd), basename(cmd), basename(cmd)); fprintf(stderr, "\nReport formats supported:"); list_report_formats_to_stderr(); } static void version(const char *cmd) { printf("%s version %s\n", basename(cmd), IDLESTAT_VERSION); } int getoptions(int argc, char *argv[], struct program_options *options) { /* Keep options sorted alphabetically and make sure the short options * are also added to the getopt_long call below */ struct option long_options[] = { { "trace", no_argument, &options->mode, TRACE }, { "import", no_argument, &options->mode, IMPORT }, { "baseline-trace", required_argument, NULL, 'b' }, { "idle", no_argument, NULL, 'c' }, { "energy-model-file", required_argument, NULL, 'e' }, { "trace-file", required_argument, NULL, 'f' }, { "help", no_argument, NULL, 'h' }, { "output-file", required_argument, NULL, 'o' }, { "frequency", no_argument, NULL, 'p' }, { "report-format", required_argument, NULL, 'r' }, { "duration", required_argument, NULL, 't' }, { "verbose", no_argument, NULL, 'v' }, { "wakeup", no_argument, NULL, 'w' }, { "boxless-report", no_argument, NULL, 'B' }, { "csv-report", no_argument, NULL, 'C' }, { "version", no_argument, NULL, 'V' }, { 0, 0, 0, 0 } }; int c; memset(options, 0, sizeof(*options)); options->filename = NULL; options->outfilename = NULL; options->mode = -1; while (1) { int optindex = 0; c = getopt_long(argc, argv, ":b:ce:f:ho:pr:t:vwBCV", long_options, &optindex); if (c == -1) break; switch (c) { case 'f': options->filename = optarg; break; case 'b': options->baseline_filename = optarg; break; case 'o': options->outfilename = optarg; break; case 'h': help(argv[0]); exit(0); break; case 't': options->duration = atoi(optarg); break; case 'c': options->display |= IDLE_DISPLAY; break; case 'p': options->display |= FREQUENCY_DISPLAY; break; case 'w': options->display |= WAKEUP_DISPLAY; break; case 'V': version(argv[0]); exit(0); break; case 'v': set_verbose_level(++options->verbose); break; case 'e': options->energy_model_filename = optarg; break; case 'r': if (options->report_type_name == NULL) { options->report_type_name = optarg; break; } fprintf(stderr, "-r: report type already set to %s\n", options->report_type_name); return -1; case 'C': if (options->report_type_name == NULL) { options->report_type_name = "csv"; break; } fprintf(stderr, "-C: report type already set to %s\n", options->report_type_name); return -1; case 'B': if (options->report_type_name == NULL) { options->report_type_name = "boxless"; break; } fprintf(stderr, "-B: report type already set to %s\n", options->report_type_name); return -1; case 0: /* getopt_long() set a variable, just keep going */ break; case ':': /* missing option argument */ fprintf(stderr, "%s: option `-%c' requires an argument\n", basename(argv[0]), optopt); return -1; case '?': /* invalid option */ default: fprintf(stderr, "%s: Unknown option `-%c'.\n", basename(argv[0]), optopt); help(argv[0]); return -1; } } if (options->report_type_name == NULL) options->report_type_name = "default"; if (options->mode < 0) { fprintf(stderr, "select a mode: --trace or --import\n"); return -1; } if (NULL == options->filename) { fprintf(stderr, "expected -f \n"); return -1; } if (bad_filename(options->filename)) return -1; if (options->baseline_filename != NULL && bad_filename(options->baseline_filename)) return -1; if (options->outfilename && bad_filename(options->outfilename)) return -1; if (options->mode == TRACE) { if (options->duration <= 0) { fprintf(stderr, "expected -t \n"); return -1; } } if (options->display == 0) options->display = IDLE_DISPLAY; return optind; } static int idlestat_file_for_each_line(const char *path, void *data, int (*handler)(const char *, void *)) { FILE *f; int ret; if (!handler) return -1; f = fopen(path, "r"); if (!f) { fprintf(stderr, "%s: failed to open '%s': %m\n", __func__, path); return -1; } while (fgets(buffer, BUFSIZE, f)) { ret = handler(buffer, data); if (ret) break; } fclose(f); return ret; } static int idlestat_store(const char *path, double start_ts, double end_ts, struct init_pstates *initp, struct cpu_topology *cpu_topo) { FILE *f; int ret; ret = sysconf(_SC_NPROCESSORS_CONF); if (ret < 0) return -1; if (initp) assert(ret == initp->nrcpus); f = fopen(path, "w+"); if (!f) { fprintf(stderr, "%s: failed to open '%s': %m\n", __func__, path); return -1; } fprintf(f, "idlestat version = %s\n", IDLESTAT_VERSION); fprintf(f, "cpus=%d\n", ret); /* output topology information */ output_cpu_topo_info(cpu_topo, f); /* output c-states information */ output_cstate_info(f, ret); /* emit initial pstate changes */ if (initp) output_pstates(f, initp, initp->nrcpus, start_ts); ret = idlestat_file_for_each_line(TRACE_FILE, f, store_line); /* emit final pstate changes */ if (initp) output_pstates(f, NULL, initp->nrcpus, end_ts); fclose(f); return ret; } static int idlestat_wake_all(void) { int rcpu, i, ret; cpu_set_t cpumask; cpu_set_t original_cpumask; ret = sysconf(_SC_NPROCESSORS_CONF); if (ret < 0) return -1; rcpu = sched_getcpu(); if (rcpu < 0) return -1; /* Keep track of the CPUs we will run on */ sched_getaffinity(0, sizeof(original_cpumask), &original_cpumask); for (i = 0; i < ret; i++) { /* Pointless to wake up ourself */ if (i == rcpu) continue; /* Pointless to wake CPUs we will not run on */ if (!CPU_ISSET(i, &original_cpumask)) continue; CPU_ZERO(&cpumask); CPU_SET(i, &cpumask); sched_setaffinity(0, sizeof(cpumask), &cpumask); } /* Enable all the CPUs of the original mask */ sched_setaffinity(0, sizeof(original_cpumask), &original_cpumask); return 0; } static volatile sig_atomic_t sigalrm = 0; static void sighandler(int sig) { if (sig == SIGALRM) sigalrm = 1; } static int execute(int argc, char *argv[], char *const envp[], struct program_options *options) { pid_t pid; int status; /* Nothing to execute, just wait an amount of time */ if (!argc) return sleep(options->duration); pid = fork(); if (pid < 0) { perror("fork"); return -1; } if (pid == 0 && execvpe(argv[0], argv, envp)) { /* Forked child */ perror("execvpe"); exit(1); } if (pid) { struct sigaction s = { .sa_handler = sighandler, .sa_flags = SA_RESETHAND, }; sigaddset(&s.sa_mask, SIGALRM); sigaction(SIGALRM, &s, NULL); alarm(options->duration); again: if (waitpid(pid, &status, 0) < 0) { if (errno == EINTR && sigalrm) kill(pid, SIGTERM); goto again; } if (WIFEXITED(status)) { /* * Cancel the timer in case the program * finished before the timeout */ alarm(0); return 0; } if (WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM) return 0; } return -1; } int main(int argc, char *argv[], char *const envp[]) { struct cpuidle_datas *datas; struct cpuidle_datas *baseline; struct program_options options; int args; double start_ts = 0, end_ts = 0; struct init_pstates *initp = NULL; struct report_ops *output_handler = NULL; struct cpu_topology *cpu_topo = NULL; struct trace_options *saved_trace_options = NULL; void *report_data = NULL; args = getoptions(argc, argv, &options); if (args <= 0) return 1; /* Tracing requires manipulation of some files only accessible * to root */ if ((options.mode == TRACE) && getuid()) { fprintf(stderr, "must be root to run traces\n"); return 1; } output_handler = get_report_ops(options.report_type_name); if (is_err(output_handler)) return 1; if (output_handler->check_options && output_handler->check_options(&options) < 0) return 1; if (output_handler->allocate_report_data) { report_data = output_handler->allocate_report_data(&options); if (is_err(report_data)) return 1; } if (output_handler->check_output(&options, report_data)) return 1; if (options.energy_model_filename && parse_energy_model(&options) < 0) { fprintf(stderr, "can't parse energy model file\n"); return 1; } /* Acquisition time specified means we will get the traces */ if ((options.mode == TRACE) || args < argc) { /* Read cpu topology info from sysfs */ cpu_topo = read_sysfs_cpu_topo(); if (is_err(cpu_topo)) { fprintf(stderr, "Failed to read CPU topology info from" " sysfs.\n"); return 1; } /* Stop tracing (just in case) */ if (idlestat_trace_enable(false)) { fprintf(stderr, "idlestat requires kernel Ftrace and " "debugfs mounted on /sys/kernel/debug\n"); return 1; } saved_trace_options = idlestat_store_trace_options(); if (is_err(saved_trace_options)) return 1; /* Initialize the traces for cpu_idle and increase the * buffer size to let 'idlestat' to sleep instead of * acquiring data, hence preventing it to pertubate the * measurements. */ if (idlestat_init_trace(options.duration)) goto err_restore_trace_options; /* Remove all the previous traces */ if (idlestat_flush_trace()) goto err_restore_trace_options; /* Get starting timestamp */ if (get_trace_ts(&start_ts) == -1) goto err_restore_trace_options; initp = build_init_pstates(); /* Start the recording */ if (idlestat_trace_enable(true)) goto err_restore_trace_options; /* We want to prevent to begin the acquisition with a cpu in * idle state because we won't be able later to close the * state and to determine which state it was. */ if (idlestat_wake_all()) goto err_restore_trace_options; /* Execute the command or wait a specified delay */ if (execute(argc - args, &argv[args], envp, &options)) goto err_restore_trace_options; /* Wake up all cpus again to account for last idle state */ if (idlestat_wake_all()) goto err_restore_trace_options; /* Stop tracing */ if (idlestat_trace_enable(false)) goto err_restore_trace_options; /* Get ending timestamp */ if (get_trace_ts(&end_ts) == -1) goto err_restore_trace_options; /* At this point we should have some spurious wake up * at the beginning of the traces and at the end (wake * up all cpus and timer expiration for the timer * acquisition). We assume these will be lost in the number * of other traces and could be negligible. */ if (idlestat_store(options.filename, start_ts, end_ts, initp, cpu_topo)) goto err_restore_trace_options; /* Restore original kernel ftrace options */ if (idlestat_restore_trace_options(saved_trace_options)) return 1; /* Discard topology, will be reloaded during trace load */ release_cpu_topo_cstates(cpu_topo); release_cpu_topo_info(cpu_topo); cpu_topo = NULL; } /* Load the idle states information */ datas = idlestat_load(options.filename); if (is_err(datas)) return 1; cpu_topo = datas->topo; if (options.baseline_filename) { baseline = idlestat_load(options.baseline_filename); merge_pstates(datas, baseline); } else { baseline = NULL; } if (is_err(baseline)) return 1; datas->baseline = baseline; assign_baseline_in_topo(datas); if (output_handler->open_report_file(options.outfilename, report_data)) return 1; if (options.display & IDLE_DISPLAY) { output_handler->cstate_table_header(report_data); dump_cpu_topo_info(output_handler, report_data, display_cstates, cpu_topo, 1); output_handler->cstate_table_footer(report_data); } if (options.display & FREQUENCY_DISPLAY) { output_handler->pstate_table_header(report_data); dump_cpu_topo_info(output_handler, report_data, display_pstates, cpu_topo, 0); output_handler->pstate_table_footer(report_data); } if (options.display & WAKEUP_DISPLAY) { output_handler->wakeup_table_header(report_data); dump_cpu_topo_info(output_handler, report_data, display_wakeup, cpu_topo, 1); output_handler->wakeup_table_footer(report_data); } if (options.energy_model_filename) calculate_energy_consumption(cpu_topo, &options); output_handler->close_report_file(report_data); release_init_pstates(initp); release_datas(datas); if (output_handler->release_report_data) output_handler->release_report_data(report_data); return 0; err_restore_trace_options: /* Restore original kernel ftrace options */ idlestat_restore_trace_options(saved_trace_options); return 1; } idlestat-0.5/energy_model0000664000175000017500000000101512454011467014230 0ustar kingking# Lines starting with # or which are blank are ignored clusters 1 # clusterA: 16 cap states 5 C states P-states: # speed, cluster power, core power # in some cases we also might need cpu power (?) 2901 6200 3200 2900 6190 3190 2800 6180 3180 2700 6170 3170 2500 6160 3160 2400 6150 3150 2300 6140 3140 2200 6130 3130 2000 6120 3120 1900 6110 3110 1800 6100 3100 1700 6090 3090 1600 6080 3080 1400 6070 3070 1300 6060 3060 1200 6050 3050 C-states: C1-IVB 25 0 C1E-IVB 30 0 C3-IVB 35 0 C6-IVB 40 0 C7-IVB 35 0 wakeup 210 6 idlestat-0.5/tracefile_idlestat.c0000664000175000017500000001456612454011467015646 0ustar kingking/* * tracefile_idlestat.c * * Copyright (C) 2014, Linaro Limited. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * 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; version 2 of the License. * * 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., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Based on code migrated from idlestat.c * * Contributors: * Tuukka Tikkanen */ #include "topology.h" #include "trace_ops.h" #include "utils.h" #include "idlestat.h" #include #include #include #include #include #include #define TRACE_FORMAT "%*[^]]] %*s %lf:%*[^=]=%u%*[^=]=%d" /** * load_and_build_cstate_info - load c-state info written to idlestat * trace file. * * @f: the file handle of the idlestat trace file * @nrcpus: number of CPUs * * @return: per-CPU array of structs (success) or ptrerror() (error) */ static struct cpuidle_cstates *load_and_build_cstate_info(FILE* f, char *buffer, int nrcpus) { int cpu; struct cpuidle_cstates *cstates; assert(f != NULL); assert(buffer != NULL); assert(nrcpus > 0); cstates = calloc(nrcpus, sizeof(*cstates)); if (!cstates) return ptrerror(__func__); for (cpu = 0; cpu < nrcpus; cpu++) { int i, read_cpu; struct cpuidle_cstate *c; cstates[cpu].cstate_max = -1; cstates[cpu].current_cstate = -1; if (sscanf(buffer, "cpuid %d:\n", &read_cpu) != 1 || read_cpu != cpu) { release_cstate_info(cstates, cpu); fprintf(stderr, "%s: Error reading trace file\n" "Expected: cpuid %d:\n" "Read: %s", __func__, cpu, buffer); return ptrerror(NULL); } for (i = 0; i < MAXCSTATE; i++) { int residency; char *name = malloc(128); if (!name) { release_cstate_info(cstates, cpu); return ptrerror(__func__); } fgets(buffer, BUFSIZE, f); sscanf(buffer, "\t%s\n", name); fgets(buffer, BUFSIZE, f); sscanf(buffer, "\t%d\n", &residency); c = &(cstates[cpu].cstate[i]); if (!strcmp(name, "(null)")) { free(name); c->name = NULL; } else { c->name = name; } c->data = NULL; c->nrdata = 0; c->early_wakings = 0; c->late_wakings = 0; c->avg_time = 0.; c->max_time = 0.; c->min_time = DBL_MAX; c->duration = 0.; c->target_residency = residency; } fgets(buffer, BUFSIZE, f); } return cstates; } int load_text_data_line(char *buffer, struct cpuidle_datas *datas, char *format, double *begin, double *end, size_t *count, size_t *start) { unsigned int state, freq, cpu; double time; int ret; if (strstr(buffer, "cpu_idle")) { if (sscanf(buffer, format, &time, &state, &cpu) != 3) { fprintf(stderr, "warning: Unrecognized cpuidle " "record. The result of analysis might " "be wrong.\n"); return -1; } if (*start) { *begin = time; *start = 0; } *end = time; store_data(time, state, cpu, datas); (*count)++; return 0; } else if (strstr(buffer, "cpu_frequency")) { if (sscanf(buffer, format, &time, &freq, &cpu) != 3) { fprintf(stderr, "warning: Unrecognized cpufreq " "record. The result of analysis might " "be wrong.\n"); return -1; } cpu_change_pstate(datas, cpu, freq, time); (*count)++; return 0; } ret = get_wakeup_irq(datas, buffer, *count); *count += (0 == ret) ? 1 : 0; return 0; } void load_text_data_lines(FILE *f, char *buffer, struct cpuidle_datas *datas) { double begin = 0, end = 0; size_t count = 0, start = 1; setup_topo_states(datas); do { load_text_data_line(buffer, datas, TRACE_FORMAT, &begin, &end, &count, &start); } while (fgets(buffer, BUFSIZE, f)); fprintf(stderr, "Log is %lf secs long with %zd events\n", end - begin, count); } static int idlestat_magic(const char *filename) { FILE *f; char *line; char buffer[BUFSIZE]; f = fopen(filename, "r"); if (!f) { fprintf(stderr, "%s: failed to open '%s': %m\n", __func__, filename); return -1; } line = fgets(buffer, BUFSIZE, f); fclose(f); return (line != NULL) && !strncmp(buffer, "idlestat version", 16); } static struct cpuidle_datas * idlestat_native_load(const char *filename) { FILE *f; unsigned int nrcpus; struct cpuidle_datas *datas; char *line; char buffer[BUFSIZE]; f = fopen(filename, "r"); if (!f) { fprintf(stderr, "%s: failed to open '%s': %m\n", __func__, filename); return ptrerror(NULL); } /* Version line */ line = fgets(buffer, BUFSIZE, f); if (!line) goto error_close; /* Number of CPUs */ line = fgets(buffer, BUFSIZE, f); if (!line) goto error_close; if (sscanf(buffer, "cpus=%u", &nrcpus) != 1 || nrcpus == 0) { fclose(f); return ptrerror("Cannot load trace file (nrcpus == 0)"); } line = fgets(buffer, BUFSIZE, f); if (!line) goto error_close; datas = calloc(sizeof(*datas), 1); if (!datas) { fclose(f); return ptrerror(__func__); } datas->nrcpus = nrcpus; datas->pstates = build_pstate_info(nrcpus); if (!datas->pstates) goto propagate_error_free_datas; /* Read topology information */ datas->topo = read_cpu_topo_info(f, buffer); if (is_err(datas->topo)) goto propagate_error_free_datas; /* Read C-state information */ datas->cstates = load_and_build_cstate_info(f, buffer, nrcpus); if (is_err(datas->cstates)) goto propagate_error_free_datas; load_text_data_lines(f, buffer, datas); fclose(f); return datas; propagate_error_free_datas: fclose(f); if (!is_err(datas->topo)) release_cpu_topo_info(datas->topo); if (!is_err(datas->cstates)) release_cstate_info(datas->cstates, nrcpus); free(datas); return ptrerror(NULL); error_close: fclose(f); fprintf(stderr, "%s: error or EOF while reading '%s': %m", __func__, filename); return ptrerror(NULL); } static const struct trace_ops idlestat_trace_ops = { .name = "Idlestat native", .check_magic = idlestat_magic, .load = idlestat_native_load }; EXPORT_TRACE_OPS(idlestat); idlestat-0.5/README0000664000175000017500000000352212454011467012521 0ustar kingkingIntroduction ------------ The purpose of idlestat is to measure how long we have been in the different idle and operating states. Idlestat uses kernel's FTRACE function to monitor and capture C-state and P-state transitions of CPUs over a time interval. It extracts the following information from trace file: - Times when CPUs entered and exited a certain C-state - Times when CPUs entered and exited a certain P-state - Raised IRQs Following a successful run, idlestat calculates and reports the following information: - Total, average, minimum and maximum time spent in each C-state, per-CPU. - Total, average, minimum and maximum time spent in each P-state, per-CPU. - Total, average, minimum and maximum time during which all CPUs in a cluster were in the same C-state, per-cluster. - Number of times a certain IRQ caused a CPU to exit idle state, per-CPU and per-IRQ Requirements ------------ 1. It must be run as root in order to access /sys/kernel/debug. 2. It requires tracing to be enabled in the kernel. Also, IPI reporting requires appropriate tracepoints in the kernel. This is available for ARM and ARM64 since v3.17-rc1. A patch for X86 was submitted but still not merged upstream. It can be retrieved here: https://lkml.org/lkml/2014/9/17/745 Help ---- ./idlestat -h will show all the options Example Usage ------------- Trace mode: sudo ./idlestat --trace -f /tmp/mytrace -t 10 Reporting mode (/tmp/mytrace already contains traces): sudo ./idlestat --import -f /tmp/mytrace Trace mode with workload (e.g. sleep, cyclictest): sudo ./idlestat --trace -f /tmp/mytrace -t 10 -- /bin/sleep 10 sudo ./idlestat --trace -f /tmp/myoutput -t 10 -- cyclictest -t 4 -i 2000 -q -D 5 Selective trace output sudo ./idlestate --import -f /tmp/mytrace -w sudo ./idlestate --import -f /tmp/mytrace -c -p sudo ./idlestate --import -f /tmp/mytrace -p -widlestat-0.5/trace.h0000664000175000017500000000413312454011467013107 0ustar kingking/* * trace.h * * Copyright (C) 2014 Zoran Markovic * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * 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; version 2 of the License. * * 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., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ #ifndef __TRACE_H #define __TRACE_H #define TRACE_PATH "/sys/kernel/debug/tracing" #define TRACE_ON_PATH TRACE_PATH "/tracing_on" #define TRACE_BUFFER_SIZE_PATH TRACE_PATH "/buffer_size_kb" #define TRACE_BUFFER_TOTAL_PATH TRACE_PATH "/buffer_total_size_kb" #define TRACE_CPUIDLE_EVENT_PATH TRACE_PATH "/events/power/cpu_idle/enable" #define TRACE_CPUFREQ_EVENT_PATH TRACE_PATH "/events/power/cpu_frequency/enable" #define TRACE_IRQ_EVENT_PATH TRACE_PATH "/events/irq/irq_handler_entry/enable" #define TRACE_IPI_EVENT_PATH TRACE_PATH "/events/ipi/ipi_entry/enable" #define TRACE_EVENT_PATH TRACE_PATH "/events/enable" #define TRACE_FREE TRACE_PATH "/free_buffer" #define TRACE_FILE TRACE_PATH "/trace" #define TRACE_STAT_FILE TRACE_PATH "/per_cpu/cpu0/stats" #define TRACE_IDLE_NRHITS_PER_SEC 10000 #define TRACE_IDLE_LENGTH 196 #define TRACE_CPUFREQ_NRHITS_PER_SEC 100 #define TRACE_CPUFREQ_LENGTH 196 struct trace_options; extern int idlestat_trace_enable(bool enable); extern int idlestat_flush_trace(void); extern int idlestat_init_trace(unsigned int duration); extern struct trace_options *idlestat_store_trace_options(void); extern int idlestat_restore_trace_options(struct trace_options *options); #endif idlestat-0.5/csv-format.txt0000664000175000017500000000452012454011467014462 0ustar kingkingThe CSV output is intended as an alternative to default output format to facilitate easy importing into spreadsheet applications or further processing with scripts. The file contains one or more of the tables (C-state/P-state/wakeups). In case of multiple tables, they are separated by empty lines. For each table, the first line is the name of the table, i.e. one of C-State Table P-State Table Wakeup Table The second line is a header line that describes the names of fields in the table., E.g. cluster,core,cpu,C-state,min (us),max (us),avg (us),total (us),hits,over,under The following lines are either topological names or data entries. The data may relate to a cluster (package), a core (collection of hyperthreaded cpus) or a cpu. A cpu belongs to a core (hyperthreading) or a cluster (no ht). A core always belongs to a cluster. Topology names: Cluster names are in the first column and all other columns are missing. Core names (if present) are in the second column and all subsequent columns are missing. The first column is empty. Cpu names (regardless of existence of core layer) are in the third column and all subsequent columns are missing. The first and second columns are empty. Data lines: Each data line begins with 3 empty columns (reserved for topological names) and are followed by report-specific values. For meaning of the columns, refer to the header line for the report. PARTIAL EXAMPLE (idle state data): C-State Table cluster,core,cpu,C-state,min (us),max (us),avg (us),total (us),hits,over,under clusterA ,,,C6-IVB,0.000000,16739.001498,11564.733154,14953199.967742,1293,0,0 ,core0 ,,,C6-IVB,0.000000,16739.001498,12625.515565,14986486.975104,1187,0,0 ,,cpu0 ,,,C1-IVB,17.000362,19.999221,18.333395,55.000186,3,0,0 ,,,C3-IVB,307.999551,5589.000881,2948.500216,5897.000432,2,0,2 ,,,C6-IVB,70.000067,16741.000116,14009.491556,14990155.965090,1070,17,0 ,,cpu4 ,,,C6-IVB,16.000122,297569.001094,126147.353020,15011535.009369,119,8,0 (more cores and cpus follow) The cluster A has been in C6-IVB state for a total of 14.95 seconds during 1293 separate idle periods. The core 0 (belonging to cluster A) has been in C6-IVB for a total of 14.99 seconds during 1187 separate idle periods. The cpu 0 (belonging to core 0) has been in 3 different idle states as detailed. The cpu 4 (belonging to core 0) has been in C6-IVB for a total of 15.01 seconds. idlestat-0.5/topology.c0000664000175000017500000004643312454012263013664 0ustar kingking/* * topology.c * * Copyright (C) 2014, Linaro Limited. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * 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; version 2 of the License. * * 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., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Contributors: * Daniel Lezcano * Zoran Markovic * Tuukka Tikkanen * */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include "list.h" #include "utils.h" #include "topology.h" #include "idlestat.h" struct topology_info { int physical_id; int core_id; int cpu_id; }; struct list_info { struct list_head hlist; int id; }; struct list_head *check_exist_from_head(struct list_head *head, int id) { struct list_head *tmp; list_for_each(tmp, head) { if (id == ((struct list_info *)tmp)->id) return tmp; } return NULL; } struct list_head *check_pos_from_head(struct list_head *head, int id) { struct list_head *tmp; list_for_each(tmp, head) { if (id < ((struct list_info *)tmp)->id) break; } return tmp; } int add_topo_info(struct cpu_topology *topo_list, struct topology_info *info) { struct cpu_physical *s_phy; struct cpu_core *s_core; struct cpu_cpu *s_cpu = NULL; struct list_head *ptr; /* add cpu physical info */ ptr = check_exist_from_head(&topo_list->physical_head, info->physical_id); if (!ptr) { s_phy = calloc(sizeof(struct cpu_physical), 1); if (!s_phy) return -1; s_phy->core_num = 0; s_phy->physical_id = info->physical_id; INIT_LIST_HEAD(&s_phy->core_head); INIT_LIST_HEAD(&s_phy->cpu_enum_head); ptr = check_pos_from_head(&topo_list->physical_head, s_phy->physical_id); list_add_tail(&s_phy->list_physical, ptr); topo_list->physical_num++; } else { s_phy = list_entry(ptr, struct cpu_physical, list_physical); } /* add cpu core info */ ptr = check_exist_from_head(&s_phy->core_head, info->core_id); if (!ptr) { s_core = calloc(sizeof(struct cpu_core), 1); if (!s_core) return -1; s_core->cpu_num = 0; s_core->is_ht = false; s_core->core_id = info->core_id; INIT_LIST_HEAD(&s_core->cpu_head); ptr = check_pos_from_head(&s_phy->core_head, s_core->core_id); list_add_tail(&s_core->list_core, ptr); s_phy->core_num++; } else { s_core = list_entry(ptr, struct cpu_core, list_core); } /* add cpu info */ if (check_exist_from_head(&s_core->cpu_head, info->cpu_id) != NULL) return 0; s_cpu = calloc(sizeof(struct cpu_cpu), 1); if (!s_cpu) return -1; s_cpu->cpu_id = info->cpu_id; ptr = check_pos_from_head(&s_core->cpu_head, s_cpu->cpu_id); list_add_tail(&s_cpu->list_cpu, ptr); s_core->cpu_num++; if (s_core->cpu_num > 1) s_core->is_ht = true; /* Assumption: Same cpuid cannot exist in 2 different cores */ assert(!check_exist_from_head(&s_phy->cpu_enum_head, info->cpu_id)); /* Add to the list (really a set) of all contained cpus in s_phy */ list_add_tail(&s_cpu->list_phy_enum, &s_phy->cpu_enum_head); return 0; } struct cpu_physical *cpu_to_cluster(int cpuid, struct cpu_topology *topo) { struct cpu_physical *phy; struct cpu_cpu *cpu; topo_for_each_cluster(phy, topo) cluster_for_each_cpu(cpu, phy) if (cpu->cpu_id == cpuid) return phy; return NULL; } struct cpu_core *cpu_to_core(int cpuid, struct cpu_topology *topo) { struct cpu_physical *phy; struct cpu_core *core; struct cpu_cpu *cpu; topo_for_each_cluster(phy, topo) cluster_for_each_core(core, phy) core_for_each_cpu(cpu, core) if (cpu->cpu_id == cpuid) return core; return NULL; } void free_cpu_cpu_list(struct list_head *head) { struct cpu_cpu *lcpu, *n; list_for_each_entry_safe(lcpu, n, head, list_cpu) { list_del(&lcpu->list_cpu); list_del(&lcpu->list_phy_enum); free(lcpu); } } void free_cpu_core_list(struct list_head *head) { struct cpu_core *lcore, *n; list_for_each_entry_safe(lcore, n, head, list_core) { free_cpu_cpu_list(&lcore->cpu_head); list_del(&lcore->list_core); free(lcore); } } void free_cpu_topology(struct list_head *head) { struct cpu_physical *lphysical, *n; list_for_each_entry_safe(lphysical, n, head, list_physical) { free_cpu_core_list(&lphysical->core_head); list_del(&lphysical->list_physical); free(lphysical); } } int output_topo_info(struct cpu_topology *topo_list) { struct cpu_physical *s_phy; struct cpu_core *s_core; struct cpu_cpu *s_cpu; list_for_each_entry(s_phy, &topo_list->physical_head, list_physical) { printf("cluster%c:\n", s_phy->physical_id + 'A'); list_for_each_entry(s_core, &s_phy->core_head, list_core) { printf("\tcore%d\n", s_core->core_id); list_for_each_entry(s_cpu, &s_core->cpu_head, list_cpu) printf("\t\tcpu%d\n", s_cpu->cpu_id); } } return 0; } int outfile_topo_info(FILE *f, struct cpu_topology *topo_list) { struct cpu_physical *s_phy; struct cpu_core *s_core; struct cpu_cpu *s_cpu; list_for_each_entry(s_phy, &topo_list->physical_head, list_physical) { fprintf(f, "cluster%c:\n", s_phy->physical_id + 'A'); list_for_each_entry(s_core, &s_phy->core_head, list_core) { fprintf(f, "\tcore%d\n", s_core->core_id); list_for_each_entry(s_cpu, &s_core->cpu_head, list_cpu) fprintf(f, "\t\tcpu%d\n", s_cpu->cpu_id); } } return 0; } struct cpu_cpu *find_cpu_point(struct cpu_topology *topo_list, int cpuid) { struct cpu_physical *s_phy; struct cpu_core *s_core; struct cpu_cpu *s_cpu; list_for_each_entry(s_phy, &topo_list->physical_head, list_physical) list_for_each_entry(s_core, &s_phy->core_head, list_core) list_for_each_entry(s_cpu, &s_core->cpu_head, list_cpu) if (s_cpu->cpu_id == cpuid) return s_cpu; return NULL; } static inline int read_topology_cb(char *path, struct topology_info *info) { file_read_value(path, "core_id", "%d", &info->core_id); file_read_value(path, "physical_package_id", "%d", &info->physical_id); return 0; } typedef int (*folder_filter_t)(const char *name); static int cpu_filter_cb(const char *name) { /* let's ignore some directories in order to avoid to be * pulled inside the sysfs circular symlinks mess/hell * (choose the word which fit better)*/ if (!strcmp(name, "cpuidle")) return 1; if (!strcmp(name, "cpufreq")) return 1; return 0; } /* * This function will browse the directory structure and build a * reflecting the content of the directory tree. * * @path : the root node of the folder * @filter : a callback to filter out the directories * Returns 0 on success, -1 otherwise */ static struct cpu_topology *topo_folder_scan(char *path, folder_filter_t filter) { DIR *dir, *dir_topology; char *basedir, *newpath; struct dirent dirent, *direntp; struct stat s; int ret; struct cpu_topology *result = NULL; dir = opendir(path); if (!dir) return ptrerror(path); ret = asprintf(&basedir, "%s", path); if (ret < 0) { closedir(dir); return ptrerror(__func__); } result = alloc_cpu_topo_info(); if (is_err(result)) { free(basedir); closedir(dir); return result; } while (!readdir_r(dir, &dirent, &direntp)) { if (!direntp) break; if (direntp->d_name[0] == '.') continue; if (filter && filter(direntp->d_name)) continue; if (!strstr(direntp->d_name, "cpu")) continue; ret = asprintf(&newpath, "%s/%s/%s", basedir, direntp->d_name, "topology"); if (ret < 0) goto out_free_basedir; ret = stat(newpath, &s); if (ret) goto out_free_newpath; if (S_ISDIR(s.st_mode) || (S_ISLNK(s.st_mode))) { struct topology_info cpu_info; dir_topology = opendir(path); if (!dir_topology) continue; closedir(dir_topology); read_topology_cb(newpath, &cpu_info); if (sscanf(direntp->d_name, "cpu%d", &cpu_info.cpu_id) != 1) { ret = -1; fprintf(stderr, "Cannot extract cpu number " "from %s\n", newpath); goto out_free_newpath; } add_topo_info(result, &cpu_info); } free(newpath); } free(basedir); closedir(dir); return result; out_free_newpath: free(newpath); out_free_basedir: free(basedir); closedir(dir); release_cpu_topo_info(result); return ptrerror(__func__); } struct cpu_topology *alloc_cpu_topo_info(void) { struct cpu_topology *ret; ret = calloc(sizeof(*ret), 1); if (ret == NULL) return ptrerror(__func__); INIT_LIST_HEAD(&ret->physical_head); ret->physical_num = 0; return ret; } struct cpu_topology *read_sysfs_cpu_topo(void) { return topo_folder_scan("/sys/devices/system/cpu", cpu_filter_cb); } struct cpu_topology *read_cpu_topo_info(FILE *f, char *buf) { int ret = 0; struct topology_info cpu_info; bool is_ht = false; char pid; struct cpu_topology *result = NULL; result = alloc_cpu_topo_info(); if (is_err(result)) return result; do { ret = sscanf(buf, "cluster%c", &pid); if (!ret) break; cpu_info.physical_id = pid - 'A'; fgets(buf, BUFSIZE, f); do { ret = sscanf(buf, "\tcore%d", &cpu_info.core_id); if (ret) { is_ht = true; fgets(buf, BUFSIZE, f); } else { ret = sscanf(buf, "\tcpu%d", &cpu_info.cpu_id); if (ret) is_ht = false; else break; } do { if (!is_ht) { ret = sscanf(buf, "\tcpu%d", &cpu_info.cpu_id); cpu_info.core_id = cpu_info.cpu_id; } else { ret = sscanf(buf, "\t\tcpu%d", &cpu_info.cpu_id); } if (!ret) break; add_topo_info(result, &cpu_info); fgets(buf, BUFSIZE, f); } while (1); } while (1); } while (1); /* output_topo_info(result); */ return result; } int release_cpu_topo_info(struct cpu_topology *topo) { if (topo == NULL) return 0; /* Free alloced memory */ free_cpu_topology(&topo->physical_head); free(topo); return 0; } int output_cpu_topo_info(struct cpu_topology *topo, FILE *f) { outfile_topo_info(f, topo); return 0; } void assign_baseline_in_topo(struct cpuidle_datas *datas) { struct cpu_physical *main_phy; struct cpu_core *main_core; struct cpu_cpu *main_cpu; struct cpu_physical *base_phy; struct cpu_core *base_core; struct cpu_cpu *base_cpu; struct cpu_topology *topo; struct cpu_topology *base_topo; struct cpuidle_datas *baseline; assert(datas != NULL); baseline = datas->baseline; if (!baseline) return; topo = datas->topo; base_topo = baseline->topo; assert(topo != NULL); assert(base_topo != NULL); /* Cluster loop */ base_phy = list_first_entry(&base_topo->physical_head, struct cpu_physical, list_physical); topo_for_each_cluster(main_phy, topo) { main_phy->base_cstates = base_phy->cstates; /* Core loop */ base_core = list_first_entry(&base_phy->core_head, struct cpu_core, list_core); cluster_for_each_core(main_core, main_phy) { main_core->base_cstates = base_core->cstates; /* Cpu loop */ base_cpu = list_first_entry(&base_core->cpu_head, struct cpu_cpu, list_cpu); core_for_each_cpu(main_cpu, main_core) { main_cpu->base_cstates = base_cpu->cstates; main_cpu->base_pstates = base_cpu->pstates; /* Step to next baseline cpu */ base_cpu = list_first_entry(&base_cpu->list_cpu, struct cpu_cpu, list_cpu); } /* Step to next baseline core */ base_core = list_first_entry(&base_core->list_core, struct cpu_core, list_core); } /* Step to next baseline cluster */ base_phy = list_first_entry(&base_phy->list_physical, struct cpu_physical, list_physical); } } int dump_cpu_topo_info(struct report_ops *ops, void *report_data, int (*dump)(struct report_ops *, void *, void *, char *, void *), struct cpu_topology *topo, int cstate) { struct cpu_physical *s_phy; struct cpu_core *s_core; struct cpu_cpu *s_cpu; char tmp[30]; list_for_each_entry(s_phy, &topo->physical_head, list_physical) { sprintf(tmp, "cluster%c", s_phy->physical_id + 'A'); if (cstate) dump(ops, s_phy->cstates, s_phy->base_cstates, tmp, report_data); list_for_each_entry(s_core, &s_phy->core_head, list_core) { if (s_core->is_ht && cstate) { sprintf(tmp, "core%d", s_core->core_id); dump(ops, s_core->cstates, s_core->base_cstates, tmp, report_data); } list_for_each_entry(s_cpu, &s_core->cpu_head, list_cpu) { sprintf(tmp, "cpu%d", s_cpu->cpu_id); if (cstate) dump(ops, s_cpu->cstates, s_cpu->base_cstates, tmp, report_data); else dump(ops, s_cpu->pstates, s_cpu->base_pstates, tmp, report_data); } } } return 0; } int release_cpu_topo_cstates(struct cpu_topology *topo) { struct cpu_physical *s_phy; struct cpu_core *s_core; list_for_each_entry(s_phy, &topo->physical_head, list_physical) { release_cstate_info(s_phy->cstates, 1); s_phy->cstates = NULL; list_for_each_entry(s_core, &s_phy->core_head, list_core) if (s_core->is_ht) { release_cstate_info(s_core->cstates, 1); s_core->cstates = NULL; } } return 0; } int cluster_get_least_cstate(struct cpu_physical *clust) { struct cpu_cpu *cpu; int cpu_cstate; int ret = MAXCSTATE; cluster_for_each_cpu(cpu, clust) { cpu_cstate = cpu->cstates->current_cstate; if (cpu_cstate < ret) ret = cpu_cstate; } return ret; } int cluster_get_highest_freq(struct cpu_physical *clust) { struct cpu_cpu *cpu; int cpu_pstate_index; unsigned int cpu_freq; unsigned int ret = ~0; cluster_for_each_cpu(cpu, clust) { cpu_pstate_index = cpu->pstates->current; if (cpu_pstate_index < 0) continue; cpu_freq = cpu->pstates->pstate[cpu_pstate_index].freq; if (cpu_freq < ret) ret = cpu_freq; } /* It is possible we don't know anything near the start of trace */ if (ret == ~0) ret = 0; return ret; } int core_get_least_cstate(struct cpu_core *core) { struct cpu_cpu *cpu; int cpu_cstate; int ret = MAXCSTATE; core_for_each_cpu(cpu, core) { cpu_cstate = cpu->cstates->current_cstate; if (cpu_cstate < ret) ret = cpu_cstate; } return ret; } int core_get_highest_freq(struct cpu_core *core) { struct cpu_cpu *cpu; int cpu_pstate_index; unsigned int cpu_freq; unsigned int ret = ~0; core_for_each_cpu(cpu, core) { cpu_pstate_index = cpu->pstates->current; if (cpu_pstate_index < 0) continue; cpu_freq = cpu->pstates->pstate[cpu_pstate_index].freq; if (cpu_freq < ret) ret = cpu_freq; } /* It is possible we don't know anything near the start of trace */ if (ret == ~0) ret = 0; return ret; } /** * create_states - Helper for allocating cpuidle_cstates and copying names * * This function allocates a struct cpuidle_cstates for recording state * statistics for a core or a cluster. The c-state information (e.g. names) * is copied from an existing array of struct cpuidle_cstate[MAXCSTATE] * pointed to by @s_state. * * In case of any error, this function will print an error message to * stderr before returning value from ptrerror(). * * @s_state: Pointer to first element in an array of struct cpuidle_cstate * @return: Pointer to the created structure or ptrerror() */ static struct cpuidle_cstates *create_states(struct cpuidle_cstate *s_state) { struct cpuidle_cstates *result; struct cpuidle_cstate *d_state; int i; result = calloc(1, sizeof(*result)); if (!result) return ptrerror(__func__); result->cstate_max = -1; result->current_cstate = -1; /* Copy state information */ d_state = result->cstate; for (i = 0; i <= MAXCSTATE; ++d_state, ++s_state, ++i) { if (s_state->name == NULL) continue; d_state->min_time = DBL_MAX; d_state->target_residency = s_state->target_residency; d_state->name = strdup(s_state->name); if (!d_state->name) { release_cstate_info(result, 1); return ptrerror(__func__); } } return result; } /** * create_core_states - Create c-state data structure for a core * * This function allocates a struct cpuidle_cstates for recording state * statistics for @s_core. The c-state information (e.g. names) is copied * from the first cpu within the core. The cpu topology mapping must have * been established before calling this function. * * The case where there are no cpus in the core is considered an internal * error. * * In case of any error, this function will print an error message to * stderr before returning value from ptrerror(). * * @s_core: The core that the structure is allocated for * @return: Pointer to the created structure or ptrerror() */ static struct cpuidle_cstates *create_core_states(struct cpu_core *s_core) { struct cpu_cpu *origin_cpu = NULL; struct cpuidle_cstate *first_s_state; assert(s_core != NULL); assert(!list_empty(&s_core->cpu_head)); /* Copy state names from the first cpu */ origin_cpu = list_first_entry(&s_core->cpu_head, struct cpu_cpu, list_cpu); first_s_state = origin_cpu->cstates->cstate; return create_states(first_s_state); } /** * create_cluster_states - Create c-state data structure for a cluster * * This function allocates a struct cpuidle_cstates for recording state * statistics for @s_phy. The c-state information (e.g. names) is copied * from the first core within the cluster. The core states must have * been established before calling this function. * * The case where there are no cores in the cluster is considered an internal * error. * * In case of any error, this function will print an error message to * stderr before returning value from ptrerror(). * * @s_phy: The cluster that the structure is allocated for * @return: Pointer to the created structure or ptrerror() */ static struct cpuidle_cstates *create_cluster_states(struct cpu_physical *s_phy) { struct cpu_core *origin_core = NULL; struct cpuidle_cstate *first_s_state; assert(s_phy != NULL); assert(!list_empty(&s_phy->core_head)); /* Copy state names from the first cpu */ origin_core = list_first_entry(&s_phy->core_head, struct cpu_core, list_core); first_s_state = origin_core->cstates->cstate; return create_states(first_s_state); } int setup_topo_states(struct cpuidle_datas *datas) { struct cpu_physical *s_phy; struct cpu_core *s_core; struct cpu_cpu *s_cpu; int i; struct cpu_topology *topo; assert(datas != NULL); topo = datas->topo; assert(topo != NULL); /* Map cpu state arrays into topology structures */ for (i = 0; i < datas->nrcpus; i++) { s_cpu = find_cpu_point(topo, i); if (s_cpu) { s_cpu->cstates = &datas->cstates[i]; s_cpu->pstates = &datas->pstates[i]; } else { fprintf(stderr, "Warning: Cannot map cpu %d into topology\n", i); } } /* Create cluster-level and core-level state structures */ topo_for_each_cluster(s_phy, topo) { cluster_for_each_core(s_core, s_phy) { s_core->cstates = create_core_states(s_core); if (is_err(s_core->cstates)) { s_core->cstates = NULL; return -1; } } s_phy->cstates = create_cluster_states(s_phy); if (is_err(s_phy->cstates)) { s_phy->cstates = NULL; return -1; } } return 0; } idlestat-0.5/comparison_report.c0000664000175000017500000002210112454011467015544 0ustar kingking/* * comparison_report.c * * Copyright (C) 2014, Linaro Limited. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * 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; version 2 of the License. * * 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., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Contributors: * Tuukka Tikkanen * */ #include #include #include #include #include #include #include #include #include #include "report_ops.h" #include "idlestat.h" #include "utils.h" struct compare_report_data { struct cpuidle_cstate *curr_cstate_baseline; struct cpufreq_pstate *curr_pstate_baseline; }; static void display_factored_time_delta(double time, int align) { char buffer[128]; if (fabs(time) < 1000.0) { snprintf(buffer, sizeof(buffer), "%+.0lfus", time); printf("%*s", align, buffer); } else if (fabs(time) < 1000000.0) { snprintf(buffer, sizeof(buffer), "%+.1lfms", time / 1000.0); printf("%*s", align, buffer); } else if (fabs(time) < 100000000000.0) { snprintf(buffer, sizeof(buffer), "%+.1lfs", time / 1000000.0); printf("%*s", align, buffer); } else printf("%.*s", align, " "); } static void display_int_delta(int value, int align) { printf(value ? " %+*d |" : " %*d |", align, value); } static int compare_check_options(struct program_options *options) { if (options->baseline_filename == NULL) { fprintf(stderr, "Error: Comparison report requires baseline trace\n"); return -1; } return 0; } static void * compare_alloc_data(struct program_options *options) { struct compare_report_data *ret = calloc(sizeof(*ret), 1); if (ret == NULL) return ptrerror(__func__); return ret; } static void compare_release_data(void *data) { free(data); } static void compare_cstate_single_state(struct cpuidle_cstate *c, void *report_data) { struct cpuidle_cstate empty; struct cpuidle_cstate diff; struct cpuidle_cstate *b; struct compare_report_data *rdata; assert(report_data != NULL); rdata = report_data; /* * On entry, either current state or baseline state might be NULL: * c = NULL implies this state exists only in baseline * curr_baseline = NULL implies this state exists only in trace * It should never occur that both c and current baseline are NULL. * * If c is NULL, set c to point to empty state and alias the name * in baseline. * * If no current baseline exists, use empty state as baseline. */ b = rdata->curr_cstate_baseline; rdata->curr_cstate_baseline = NULL; assert(c != NULL || b != NULL); if (c == NULL) { memset(&empty, 0, sizeof(empty)); empty.name = b->name; c = ∅ } if (b == NULL) { memset(&empty, 0, sizeof(empty)); b = ∅ } diff.min_time = c->min_time - b->min_time; diff.max_time = c->max_time - b->max_time; diff.avg_time = c->avg_time - b->avg_time; diff.duration = c->duration - b->duration; diff.nrdata = c->nrdata - b->nrdata; diff.early_wakings = c->early_wakings - b->early_wakings; diff.late_wakings = b->late_wakings - b->late_wakings; printf("| %8s | ", c->name); display_factored_time(c->min_time == DBL_MAX ? 0. : c->min_time, 8); printf(" | "); display_factored_time(c->max_time, 8); printf(" | "); display_factored_time(c->avg_time, 8); printf(" | "); display_factored_time(c->duration, 8); printf(" | "); printf("%5d | %5d | %5d |\n", c->nrdata, c->early_wakings, c->late_wakings); /* Delta */ printf("| | "); display_factored_time_delta(diff.min_time, 8); printf(" | "); display_factored_time_delta(diff.max_time, 8); printf(" | "); display_factored_time_delta(diff.avg_time, 8); printf(" | "); display_factored_time_delta(diff.duration, 8); printf(" |"); display_int_delta(diff.nrdata, 5); display_int_delta(diff.early_wakings, 5); display_int_delta(diff.late_wakings, 5); printf("\n"); } static void compare_set_baseline_cstate(struct cpuidle_cstate *b, void *report_data) { struct compare_report_data *rdata; assert(report_data != NULL); rdata = report_data; /* Process previous state if trace did not have data */ if (rdata->curr_cstate_baseline) compare_cstate_single_state(NULL, report_data); if (b == NULL) return; rdata->curr_cstate_baseline = b; } static void compare_cstate_end_cpu(void *report_data) { compare_set_baseline_cstate(NULL, report_data); } static void compare_pstate_single_freq(struct cpufreq_pstate *p, void *report_data) { struct cpufreq_pstate empty; struct cpufreq_pstate diff; struct cpufreq_pstate *b; struct compare_report_data *rdata; assert(report_data != NULL); rdata = report_data; /* * On entry, either current state or baseline state might be NULL: * p = NULL implies this state exists only in baseline * curr_baseline = NULL implies this state exists only in trace * It should never occur that both p and current baseline are NULL. * * If p is NULL, set p to point to empty state and copy the frequency * from baseline. * * If no current baseline exists, use empty state as baseline. */ b = rdata->curr_pstate_baseline; rdata->curr_pstate_baseline = NULL; assert(p != NULL || b != NULL); if (p == NULL) { memset(&empty, 0, sizeof(empty)); empty.freq = b->freq; p = ∅ } if (b == NULL) { memset(&empty, 0, sizeof(empty)); b = ∅ } diff.min_time = p->min_time - b->min_time; diff.max_time = p->max_time - b->max_time; diff.avg_time = p->avg_time - b->avg_time; diff.duration = p->duration - b->duration; diff.count = p->count - b->count; printf("| "); display_factored_freq(p->freq, 8); printf(" | "); display_factored_time(p->min_time == DBL_MAX ? 0. : p->min_time, 8); printf(" | "); display_factored_time(p->max_time, 8); printf(" | "); display_factored_time(p->avg_time, 8); printf(" | "); display_factored_time(p->duration, 8); printf(" | %5d |\n", p->count); printf("| | "); display_factored_time_delta(diff.min_time, 8); printf(" | "); display_factored_time_delta(diff.max_time, 8); printf(" | "); display_factored_time_delta(diff.avg_time, 8); printf(" | "); display_factored_time_delta(diff.duration, 8); printf(" |"); display_int_delta(diff.count, 5); printf("\n"); } static void compare_set_baseline_pstate(struct cpufreq_pstate *b, void *report_data) { struct compare_report_data *rdata; assert(report_data != NULL); rdata = report_data; /* Process previous state if trace did not have data */ if (rdata->curr_pstate_baseline) compare_pstate_single_freq(NULL, report_data); if (b == NULL) return; rdata->curr_pstate_baseline = b; } static void compare_pstate_end_cpu(void *report_data) { compare_set_baseline_pstate(NULL, report_data); } static int copy_ops_from_default(struct report_ops *); static struct report_ops comparison_report_ops = { .name = "comparison", .prepare = copy_ops_from_default, .check_options = compare_check_options, .allocate_report_data = compare_alloc_data, .release_report_data = compare_release_data, .cstate_baseline_state = compare_set_baseline_cstate, .cstate_single_state = compare_cstate_single_state, .cstate_end_cpu = compare_cstate_end_cpu, .pstate_baseline_freq = compare_set_baseline_pstate, .pstate_single_freq = compare_pstate_single_freq, .pstate_end_cpu = compare_pstate_end_cpu, }; static int copy_ops_from_default(struct report_ops *self) { struct report_ops *def = get_report_ops("default"); assert(self == &comparison_report_ops); if (is_err(def)) { fprintf(stderr, "Comparison report: cannot copy ops from default\n"); return -1; } comparison_report_ops.check_output = def->check_output; comparison_report_ops.open_report_file = def->open_report_file; comparison_report_ops.close_report_file = def->close_report_file; comparison_report_ops.cstate_table_header = def->cstate_table_header; comparison_report_ops.cstate_table_footer = def->cstate_table_footer; comparison_report_ops.cstate_cpu_header = def->cstate_cpu_header; comparison_report_ops.pstate_table_header = def->pstate_table_header; comparison_report_ops.pstate_table_footer = def->pstate_table_footer; comparison_report_ops.pstate_cpu_header = def->pstate_cpu_header; comparison_report_ops.wakeup_table_header = def->wakeup_table_header; comparison_report_ops.wakeup_table_footer = def->wakeup_table_footer; comparison_report_ops.wakeup_cpu_header = def->wakeup_cpu_header; comparison_report_ops.wakeup_single_irq = def->wakeup_single_irq; comparison_report_ops.wakeup_end_cpu = def->wakeup_end_cpu; return 0; } EXPORT_REPORT_OPS(comparison); idlestat-0.5/energy-model-format.txt0000664000175000017500000000225312454011467016257 0ustar kingkingThis file describes the format of idlestat energy model files (using the included energy_model file as an example): Lines starting with # or which are blank are ignored. First, specify how many clusters there are. clusters 1 For each cluster (named clusterA, clusterB, etc.) specify how many cap states and C states there are. Idlestat will check that the correct number of clusters are provided. # clusterA: 16 cap states 5 C states Then specify the name, cluster power and core power for each P-state (idlestat will check if there are the correct number of P-states provided) in the cluster. P-states: # speed, cluster power, core power 2901 6200 3200 2900 6190 3190 2800 6180 3180 2700 6170 3170 2500 6160 3160 2400 6150 3150 2300 6140 3140 2200 6130 3130 2000 6120 3120 1900 6110 3110 1800 6100 3100 1700 6090 3090 1600 6080 3080 1400 6070 3070 1300 6060 3060 1200 6050 3050 Then specify the name, cluster power and core power for each C-state (idlestat will check if there are the correct number of C-states provided) in the cluster. C-states: C1-IVB 25 0 C1E-IVB 30 0 C3-IVB 35 0 C6-IVB 40 0 C7-IVB 35 0 Finally, specify the wakeup power. wakeup 210 6 Repeat for each cluster.