From patchwork Tue Feb 26 14:35:30 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sanjay Singh Rawat X-Patchwork-Id: 15087 Return-Path: X-Original-To: patchwork@peony.canonical.com Delivered-To: patchwork@peony.canonical.com Received: from fiordland.canonical.com (fiordland.canonical.com [91.189.94.145]) by peony.canonical.com (Postfix) with ESMTP id A18CC23E1A for ; Tue, 26 Feb 2013 14:36:18 +0000 (UTC) Received: from mail-ve0-f173.google.com (mail-ve0-f173.google.com [209.85.128.173]) by fiordland.canonical.com (Postfix) with ESMTP id 22ECAA1979A for ; Tue, 26 Feb 2013 14:36:18 +0000 (UTC) Received: by mail-ve0-f173.google.com with SMTP id oz10so3583543veb.32 for ; Tue, 26 Feb 2013 06:36:17 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20120113; h=x-received:x-forwarded-to:x-forwarded-for:delivered-to:x-received :received-spf:x-received:from:to:cc:subject:date:message-id:x-mailer :x-gm-message-state; bh=orPpuNFF0akRyKI4DhNYRPfm9ICA9m8LdHqs4/uy8E0=; b=AyK11r0lTU4ppO5MDFj2IoxaFdUfsa9h4OI/2i8LcKE89bv1fVxGzMnlG8dURWIxZF iMuZEhyjYDJCNGwdAgie+fI3CkuuwIGyu2EiB8uHxfLGhSg5JZUwOpaFklDNaoEmFFkJ +wy38meYYjR9xxn08B7FUJaEiXP/bsZPfRfN0TqxrWJTcdc18No0i2M7gsg22jxaziyR n4JBx6bNYmVnCLTf30XkmWDLvmcbcJeTRIhI89/k6rUKOQx4etLtagAUbLoyAy/8zlu/ fvwgY+kIfZDWmZRBEhxkfSpHRWdiTTiNExz1OhQaI3Kv4yINL7WE8ggx4TUxZiiuhM16 X2PQ== X-Received: by 10.52.18.148 with SMTP id w20mr10224239vdd.8.1361889377385; Tue, 26 Feb 2013 06:36:17 -0800 (PST) X-Forwarded-To: linaro-patchwork@canonical.com X-Forwarded-For: patch@linaro.org linaro-patchwork@canonical.com Delivered-To: patches@linaro.org Received: by 10.58.145.101 with SMTP id st5csp117241veb; Tue, 26 Feb 2013 06:36:16 -0800 (PST) X-Received: by 10.66.89.226 with SMTP id br2mr2063669pab.57.1361889375509; Tue, 26 Feb 2013 06:36:15 -0800 (PST) Received: from mail-da0-f51.google.com (mail-da0-f51.google.com [209.85.210.51]) by mx.google.com with ESMTPS id k8si1437098pax.59.2013.02.26.06.36.14 (version=TLSv1 cipher=ECDHE-RSA-RC4-SHA bits=128/128); Tue, 26 Feb 2013 06:36:15 -0800 (PST) Received-SPF: neutral (google.com: 209.85.210.51 is neither permitted nor denied by best guess record for domain of sanjay.rawat@linaro.org) client-ip=209.85.210.51; Authentication-Results: mx.google.com; spf=neutral (google.com: 209.85.210.51 is neither permitted nor denied by best guess record for domain of sanjay.rawat@linaro.org) smtp.mail=sanjay.rawat@linaro.org Received: by mail-da0-f51.google.com with SMTP id n15so2035483dad.24 for ; Tue, 26 Feb 2013 06:36:14 -0800 (PST) X-Received: by 10.68.196.168 with SMTP id in8mr23432507pbc.61.1361889374129; Tue, 26 Feb 2013 06:36:14 -0800 (PST) Received: from srawat-Latitude-E6420.LGE.NET ([203.247.149.152]) by mx.google.com with ESMTPS id y9sm1680608paw.1.2013.02.26.06.36.10 (version=TLSv1.1 cipher=ECDHE-RSA-RC4-SHA bits=128/128); Tue, 26 Feb 2013 06:36:13 -0800 (PST) From: Sanjay Singh Rawat To: linaro-dev@lists.linaro.org Cc: Daniel Lezcano , Sanjay Singh Rawat Subject: [PATCH] pmqa: cpuidle: add test to check cpuidle statistics Date: Tue, 26 Feb 2013 20:05:30 +0530 Message-Id: <1361889330-8739-1-git-send-email-sanjay.rawat@linaro.com> X-Mailer: git-send-email 1.7.9.5 X-Gm-Message-State: ALoCoQnShpzNV8eqKyd9xOf0iwKSRQfID2K6YpezT8MHwOPHAe7V9oGA4EdWGJEjPIqIMSa/lvX1 From: Daniel Lezcano Add functionality to record the current runtime cpuidle statistics and show the same. Signed-off-by: Sanjay Singh Rawat --- 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 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 (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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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] \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; +}