pmqa: cpuidle: add test to check cpuidle statistics

Message ID 1361889330-8739-1-git-send-email-sanjay.rawat@linaro.com
State New
Headers show

Commit Message

Sanjay Singh Rawat Feb. 26, 2013, 2:35 p.m.
From: Daniel Lezcano <daniel.lezcano@linaro.org>

Add functionality to record the current runtime cpuidle statistics
and show the same.

Signed-off-by: Sanjay Singh Rawat <sanjay.rawat@linaro.com>
---
 README                  |    6 +
 Test.mk                 |    6 +
 cpuidle/cpuidle_04.sh   |   41 ++++
 cpuidle/cpuidle_04.txt  |    1 +
 cpuidle/cpuidle_stats.c |  472 +++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 526 insertions(+)
 create mode 100755 cpuidle/cpuidle_04.sh
 create mode 100644 cpuidle/cpuidle_04.txt
 create mode 100644 cpuidle/cpuidle_stats.c

Patch

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 100755
index 0000000..42d6de5
--- /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..971fcc9
--- /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;
+}