aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Lezcano <daniel.lezcano@linaro.org>2013-04-10 11:34:20 +0530
committerSanjay Singh Rawat <sanjay.rawat@linaro.org>2013-04-10 11:36:08 +0530
commitf698a6736fc105beca5feca8e5f5e4b6c97c41c8 (patch)
tree643cd8ec6bb8f6b8cac6a8bb434f8d64e7ba4603
parent8b40f39eadc6b4f10cc4eebf79944d08d9b395ec (diff)
pmqa: cpuidle: add test to check cpuidle statistics
Add functionality to record the current runtime cpuidle statistics and show the same. Signed-off-by: Sanjay Singh Rawat <sanjay.rawat@linaro.org>
-rw-r--r--README6
-rw-r--r--Test.mk6
-rw-r--r--cpuidle/cpuidle_04.sh41
-rw-r--r--cpuidle/cpuidle_04.txt1
-rw-r--r--cpuidle/cpuidle_stats.c472
5 files changed, 526 insertions, 0 deletions
diff --git a/README b/README
index a22a3c8..41c5f41 100644
--- a/README
+++ b/README
@@ -10,3 +10,9 @@ If you want to run a subset of the tests, do:
make -C sched_mc check
make -C cpufreq check
+
+For running cpuidle-stats test, which is 4th subtest in cpuidle; run:
+
+ make -C cpuidle check TEST=4
+
+Note: Any other value for TEST will not run any other subtest
diff --git a/Test.mk b/Test.mk
index 671bbf5..19d7346 100644
--- a/Test.mk
+++ b/Test.mk
@@ -36,7 +36,13 @@ SANITY_STATUS:= $(shell if test $(SNT) && test -f $(SNT); then \
echo 1; fi; else echo 1; fi)
ifeq "$(SANITY_STATUS)" "1"
+
+ifeq "$(TEST)" "4"
+TST=cpuidle_04.sh
+run_tests: uncheck $(EXEC) $(LOG)
+else
run_tests: uncheck $(EXEC) $(LOG)
+endif
%.log: %.sh
@echo "###"
diff --git a/cpuidle/cpuidle_04.sh b/cpuidle/cpuidle_04.sh
new file mode 100644
index 0000000..7ec12d3
--- /dev/null
+++ b/cpuidle/cpuidle_04.sh
@@ -0,0 +1,41 @@
+#!/bin/bash
+#
+# PM-QA validation test suite for the power management on Linux
+#
+# Copyright (C) 2011, 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 <daniel.lezcano@linaro.org> (IBM Corporation)
+# - initial API and implementation
+#
+
+source ../include/functions.sh
+
+CPUIDLE_STATS=./cpuidle_stats
+
+if [ $(id -u) != 0 ]; then
+ log_skip "run as non-root"
+ exit 0
+fi
+
+check_cpuidle_stats() {
+ trace-cmd record -e cpu_idle
+ trace-cmd report trace.dat > trace-cpuidle.dat
+ check "Running cpuidle_stats on collected data" "./$CPUIDLE_STATS" trace-cpuidle.dat
+}
+
+check_cpuidle_stats
diff --git a/cpuidle/cpuidle_04.txt b/cpuidle/cpuidle_04.txt
new file mode 100644
index 0000000..8cf6bb1
--- /dev/null
+++ b/cpuidle/cpuidle_04.txt
@@ -0,0 +1 @@
+Run cpuidle_stats program to check runtime cpuidle statistics.
diff --git a/cpuidle/cpuidle_stats.c b/cpuidle/cpuidle_stats.c
new file mode 100644
index 0000000..f12623b
--- /dev/null
+++ b/cpuidle/cpuidle_stats.c
@@ -0,0 +1,472 @@
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <string.h>
+#include <values.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#define BUFSIZE 256
+#define MAXCSTATE 8
+#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)))
+
+static char buffer[BUFSIZE];
+
+struct cpuidle_data {
+ double begin;
+ double end;
+ double duration;
+};
+
+struct cpuidle_cstate {
+ struct cpuidle_data *data;
+ int nrdata;
+ double avg_time;
+ double max_time;
+ double min_time;
+ double duration;
+};
+
+struct cpuidle_cstates {
+ struct cpuidle_cstate cstate[MAXCSTATE];
+ int last_cstate;
+ int cstate_max;
+};
+
+struct cpuidle_datas {
+ struct cpuidle_cstates *cstates;
+ int nrcpus;
+};
+
+static inline int error(const char *str)
+{
+ perror(str);
+ return -1;
+}
+
+static inline void *ptrerror(const char *str)
+{
+ perror(str);
+ return NULL;
+}
+
+static int dump_data(struct cpuidle_datas *datas, int state, int count)
+{
+ int i = 0, j, k, nrcpus = datas->nrcpus;
+ struct cpuidle_cstates *cstates;
+ struct cpuidle_cstate *cstate;
+
+ do {
+ cstates = &datas->cstates[i];
+
+ for (j = 0; j < cstates->cstate_max + 1; j++) {
+
+ if (state != -1 && state != j)
+ continue;
+
+ cstate = &cstates->cstate[j];
+
+ for (k = 0; k < MIN(count, cstate->nrdata); k++) {
+ printf("%lf %d\n", cstate->data[k].begin, j);
+ printf("%lf 0\n", cstate->data[k].end);
+ }
+
+ /* add a break */
+ printf("\n");
+ }
+
+ i++;
+
+ } while (i < nrcpus && nrcpus != -1);
+
+ return 0;
+}
+
+static int display_data(struct cpuidle_datas *datas, int state)
+{
+ int i = 0, j, nrcpus = datas->nrcpus;
+ struct cpuidle_cstates *cstates;
+ struct cpuidle_cstate *cstate;
+
+ do {
+ cstates = &datas->cstates[i];
+
+ for (j = 0; j < cstates->cstate_max + 1; j++) {
+
+ if (state != -1 && state != j)
+ continue;
+
+ cstate = &cstates->cstate[j];
+
+ if (nrcpus == -1)
+ printf("\ncluster");
+ else
+ printf("\ncpu%d", i);
+
+ printf("/state%d, %d hits, total %.2lfus, "\
+ "avg %.2lfus, min %.2lfus, max %.2lfus",
+ j, cstate->nrdata, cstate->duration,
+ cstate->avg_time, cstate->min_time,
+ cstate->max_time);
+ }
+
+ i++;
+
+ } while (i < nrcpus && nrcpus != -1);
+ printf("\n");
+ return 0;
+}
+
+static struct cpuidle_data *intersection(struct cpuidle_data *data1,
+ struct cpuidle_data *data2)
+{
+ double begin, end;
+ struct cpuidle_data *data;
+
+ begin = MAX(data1->begin, data2->begin);
+ end = MIN(data1->end, data2->end);
+
+ if (begin >= end)
+ return NULL;
+
+ data = malloc(sizeof(*data));
+ if (!data)
+ return NULL;
+
+ data->begin = begin;
+ data->end = end;
+ data->duration = end - begin;
+ data->duration *= 1000000;
+
+ return data;
+}
+
+static struct cpuidle_cstate *inter(struct cpuidle_cstate *c1,
+ struct cpuidle_cstate *c2)
+{
+ int i, j;
+ struct cpuidle_data *interval;
+ struct cpuidle_cstate *result;
+ struct cpuidle_data *data = NULL;
+ size_t index;
+
+ if (!c1)
+ return c2;
+ if (!c2)
+ return c1;
+
+ result = calloc(sizeof(*result), 1);
+ if (!result)
+ return NULL;
+
+ for (i = 0, index = 0; i < c1->nrdata; i++) {
+
+ for (j = index; j < c2->nrdata; j++) {
+
+ /* intervals are ordered, no need to go further */
+ if (c1->data[i].end < c2->data[j].begin)
+ break;
+
+ /* primary loop begins where we ended */
+ if (c1->data[i].begin > c2->data[j].end)
+ index = j;
+
+ interval = intersection(&c1->data[i], &c2->data[j]);
+ if (!interval)
+ continue;
+
+ result->min_time = MIN(!result->nrdata ? 999999.0 :
+ result->min_time,
+ interval->duration);
+
+ result->max_time = MAX(result->max_time,
+ interval->duration);
+
+ result->avg_time = AVG(result->avg_time,
+ interval->duration,
+ result->nrdata + 1);
+
+ result->duration += interval->duration;
+
+ result->nrdata++;
+
+ data = realloc(data, sizeof(*data) *
+ (result->nrdata + 1));
+ if (!data)
+ return NULL;
+
+ result->data = data;
+ result->data[result->nrdata - 1] = *interval;
+
+ free(interval);
+ }
+ }
+
+ return result;
+}
+
+static int store_data(double time, int state, int cpu,
+ struct cpuidle_datas *datas, int count)
+{
+ struct cpuidle_cstates *cstates = &datas->cstates[cpu];
+ struct cpuidle_cstate *cstate;
+ struct cpuidle_data *data;
+ int nrdata, last_cstate = cstates->last_cstate;
+
+ /* ignore when we got a "closing" state first */
+ if (state == -1 && !cstates->cstate_max)
+ return 0;
+
+ cstate = &cstates->cstate[state == -1 ? last_cstate : state ];
+ data = cstate->data;
+ nrdata = cstate->nrdata;
+
+ if (state == -1) {
+
+ data = &data[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)
+ return 0;
+
+ /* convert to us */
+ data->duration *= 1000000;
+ cstate->min_time = MIN(!nrdata ? 999999.0 : 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++;
+
+ return 0;
+ }
+
+ data = realloc(data, sizeof(*data) * (nrdata + 1));
+ if (!data)
+ return error("realloc data");;
+
+ data[nrdata].begin = time;
+
+ cstates->cstate[state].data = data;
+ cstates->cstate_max = MAX(cstates->cstate_max, state);
+ cstates->last_cstate = state;
+
+ return 0;
+}
+
+static struct cpuidle_datas *load_data(const char *path)
+{
+ FILE *f;
+ unsigned int state = 0, cpu = 0, nrcpus= 0;
+ double time, begin, end;
+ size_t count;
+
+ struct cpuidle_datas *datas;
+
+ f = fopen(path, "r");
+ if (!f)
+ return ptrerror("fopen");
+
+ for (count = 0; count < 2; count++) {
+ fgets(buffer, BUFSIZE, f);
+ sscanf(buffer, "cpus=%u", &nrcpus);
+ }
+
+ if (!nrcpus)
+ return ptrerror("read error for 'cpus=' in trace file");
+
+ datas = malloc(sizeof(*datas));
+ if (!datas)
+ return ptrerror("malloc datas");
+
+ datas->cstates = calloc(sizeof(*datas->cstates), nrcpus);
+ if (!datas->cstates)
+ return ptrerror("calloc cstate");
+
+ datas->nrcpus = nrcpus;
+
+ for (; fgets(buffer, BUFSIZE, f); count++) {
+
+ sscanf(buffer, "%*[^]]] %lf:%*[^=]=%u%*[^=]=%d",
+ &time, &state, &cpu);
+
+ if (count == 2)
+ begin = time;
+ end = time;
+
+ store_data(time, state, cpu, datas, count);
+ }
+
+ fclose(f);
+
+ fprintf(stderr, "Log is %lf secs long with %d events\n",
+ end - begin, count);
+
+ return datas;
+}
+
+struct cpuidle_datas *cluster_data(struct cpuidle_datas *datas)
+{
+ struct cpuidle_cstate *c1, *cstates;
+ struct cpuidle_datas *result;
+ int i, j;
+ int cstate_max = -1;
+
+ result = malloc(sizeof(*result));
+ if (!result)
+ return NULL;
+
+ result->nrcpus = -1; /* the cluster */
+
+ result->cstates = calloc(sizeof(*result->cstates), 1);
+ if (!result->cstates)
+ return NULL;
+
+ /* hack but negligeable overhead */
+ for (i = 0; i < datas->nrcpus; i++)
+ cstate_max = MAX(cstate_max, datas->cstates[i].cstate_max);
+ result->cstates[0].cstate_max = cstate_max;
+
+ for (i = 0; i < cstate_max + 1; i++) {
+
+ for (j = 0, cstates = NULL; j < datas->nrcpus; j++) {
+
+ c1 = &datas->cstates[j].cstate[i];
+
+ cstates = inter(cstates, c1);
+ if (!cstates)
+ continue;
+ }
+
+ result->cstates[0].cstate[i] = *cstates;
+ }
+
+ return result;
+}
+
+static int help(const char *cmd)
+{
+ fprintf(stderr, "%s [-d/--dump] [-c/--cstate=x] <file>\n", cmd);
+ exit(0);
+}
+
+static struct option long_options[] = {
+ { "dump", 0, 0, 'd' },
+ { "iterations", 0, 0, 'i' },
+ { "cstate", 0, 0, 'c' },
+ { "debug", 0, 0, 'g' },
+ { "verbose", 0, 0, 'v' },
+ { "help", 0, 0, 'h' },
+ { 0, 0, 0, 0 }
+};
+
+struct idledebug_options {
+ bool debug;
+ bool dump;
+ int cstate;
+ int iterations;
+};
+
+int getoptions(int argc, char *argv[], struct idledebug_options *options)
+{
+ int c;
+
+ memset(options, 0, sizeof(*options));
+ options->cstate = -1;
+
+ while (1) {
+
+ int optindex = 0;
+
+ c = getopt_long(argc, argv, "gdvhi:c:",
+ long_options, &optindex);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'g':
+ options->debug = true;
+ break;
+ case 'd':
+ options->dump = true;
+ break;
+ case 'i':
+ options->iterations = atoi(optarg);
+ break;
+ case 'c':
+ options->cstate = atoi(optarg);
+ break;
+ case 'h':
+ help(argv[0]);
+ break;
+ case '?':
+ fprintf(stderr, "%s: Unknown option %c'.\n",
+ argv[0], optopt);
+ default:
+ return -1;
+ }
+ }
+
+ if (options->cstate >= MAXCSTATE) {
+ fprintf(stderr, "C-state must be less than %d\n",
+ MAXCSTATE);
+ return -1;
+ }
+
+ if (options->iterations < 0) {
+ fprintf(stderr, "dump values must be a positive value\n");
+ }
+
+ if (optind == argc) {
+ fprintf(stderr, "expected filename\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ struct cpuidle_datas *datas;
+ struct cpuidle_datas *cluster;
+ struct idledebug_options options;
+ struct rusage rusage;
+
+
+ if (getoptions(argc, argv, &options))
+ return 1;
+
+ datas = load_data(argv[optind]);
+ if (!datas)
+ return 1;
+
+ cluster = cluster_data(datas);
+ if (!cluster)
+ return 1;
+
+ if (options.dump > 0) {
+ dump_data(datas, options.cstate, options.iterations);
+ dump_data(cluster, options.cstate, options.iterations);
+ } else {
+ display_data(datas, options.cstate);
+ display_data(cluster, options.cstate);
+ }
+
+ if (options.debug) {
+ getrusage(RUSAGE_SELF, &rusage);
+ printf("max rss : %ld kB\n", rusage.ru_maxrss);
+ }
+
+ return 0;
+}