From patchwork Mon Feb 11 11:45:22 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Tvrtko Ursulin X-Patchwork-Id: 10805541 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id C7438746 for ; Mon, 11 Feb 2019 11:45:20 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id B04E429BFC for ; Mon, 11 Feb 2019 11:45:20 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 9E03129C02; Mon, 11 Feb 2019 11:45:20 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-5.2 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_MED autolearn=ham version=3.3.1 Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id 24D4D29BFC for ; Mon, 11 Feb 2019 11:45:19 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 8C6D46E483; Mon, 11 Feb 2019 11:45:18 +0000 (UTC) X-Original-To: Intel-gfx@lists.freedesktop.org Delivered-To: Intel-gfx@lists.freedesktop.org Received: from mga01.intel.com (mga01.intel.com [192.55.52.88]) by gabe.freedesktop.org (Postfix) with ESMTPS id F37976E47B; Mon, 11 Feb 2019 11:45:16 +0000 (UTC) X-Amp-Result: SKIPPED(no attachment in message) X-Amp-File-Uploaded: False Received: from fmsmga007.fm.intel.com ([10.253.24.52]) by fmsmga101.fm.intel.com with ESMTP/TLS/DHE-RSA-AES256-GCM-SHA384; 11 Feb 2019 03:45:16 -0800 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.58,358,1544515200"; d="scan'208";a="121511164" Received: from jjcunnix-mobl.ger.corp.intel.com (HELO localhost.localdomain) ([10.252.13.125]) by fmsmga007.fm.intel.com with ESMTP; 11 Feb 2019 03:45:14 -0800 From: Tvrtko Ursulin To: igt-dev@lists.freedesktop.org Date: Mon, 11 Feb 2019 11:45:22 +0000 Message-Id: <20190211114523.22844-1-tvrtko.ursulin@linux.intel.com> X-Mailer: git-send-email 2.19.1 MIME-Version: 1.0 Subject: [Intel-gfx] [IGT 1/2] tools/intel_gpu_top: Add support for stdout logging X-BeenThere: intel-gfx@lists.freedesktop.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: Intel graphics driver community testing & development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Intel-gfx@lists.freedesktop.org, Eero Tamminen , 3.14pi@ukr.net Errors-To: intel-gfx-bounces@lists.freedesktop.org Sender: "Intel-gfx" X-Virus-Scanned: ClamAV using ClamSMTP From: Tvrtko Ursulin Two new output modes are added: listing of text data to standard out (-l on the command line), and dumping of JSON formatted records (-J), also to standard out. The first mode is selected automatically when non-interactive standard out is detected. Example of text output: Freq MHz IRQ RC6 Power IMC MiB/s RCS/0 BCS/0 VCS/0 VCS/1 VECS/0 req act /s % W rd wr % se wa % se wa % se wa % se wa % se wa 0 0 0 0 0.00 360 0 0.00 0 0 0.00 0 0 0.00 0 0 0.00 0 0 0.00 0 0 350 350 0 100 0.00 35 2 0.00 0 0 0.00 0 0 0.00 0 0 0.00 0 0 0.00 0 0 350 350 0 100 0.00 34 2 0.00 0 0 0.00 0 0 0.00 0 0 0.00 0 0 0.00 0 0 350 350 0 100 0.00 143 6 0.00 0 0 0.00 0 0 0.00 0 0 0.00 0 0 0.00 0 0 350 350 0 100 0.00 169 7 0.00 0 0 0.00 0 0 0.00 0 0 0.00 0 0 0.00 0 0 350 350 0 100 0.00 169 7 0.00 0 0 0.00 0 0 0.00 0 0 0.00 0 0 0.00 0 0 Example of JSON output: { "period": { "duration": 1002.525224, "unit": "ms" }, "frequency": { "requested": 349.118398, "actual": 349.118398, "unit": "MHz" }, "interrupts": { "count": 0.000000, "unit": "irq/s" }, "rc6": { "value": 99.897752, "unit": "%" }, "power": { "value": 0.000000, "unit": "W" }, "imc-bandwidth": { "reads": 149.683843, "writes": 6.104093, "unit": "MiB/s" }, "engines": { "Render/3D/0": { "busy": 0.000000, "sema": 0.000000, "wait": 0.000000, "unit": "%" }, "Blitter/0": { "busy": 0.000000, "sema": 0.000000, "wait": 0.000000, "unit": "%" }, "Video/0": { "busy": 0.000000, "sema": 0.000000, "wait": 0.000000, "unit": "%" }, "Video/1": { "busy": 0.000000, "sema": 0.000000, "wait": 0.000000, "unit": "%" }, "VideoEnhance/0": { "busy": 0.000000, "sema": 0.000000, "wait": 0.000000, "unit": "%" } } } v2: * Show example output in commit message. * Install signal handler to complete output on SIGINT. (Chris Wilson) v3: * Use asprintf where possible. (Chris Wilson) Signed-off-by: Tvrtko Ursulin References: https://bugs.freedesktop.org/show_bug.cgi?id=108689 Cc: Eero Tamminen Cc: 3.14pi@ukr.net Cc: Chris Wilson Reviewed-by: Chris Wilson --- man/intel_gpu_top.rst | 9 +- tools/intel_gpu_top.c | 761 +++++++++++++++++++++++++++++++++++------- 2 files changed, 644 insertions(+), 126 deletions(-) diff --git a/man/intel_gpu_top.rst b/man/intel_gpu_top.rst index 19c712307d28..d5bda093c8e8 100644 --- a/man/intel_gpu_top.rst +++ b/man/intel_gpu_top.rst @@ -7,9 +7,9 @@ Display a top-like summary of Intel GPU usage --------------------------------------------- .. include:: defs.rst :Author: IGT Developers -:Date: 2018-04-04 +:Date: 2019-02-08 :Version: |PACKAGE_STRING| -:Copyright: 2009,2011,2012,2016,2018 Intel Corporation +:Copyright: 2009,2011,2012,2016,2018,2019 Intel Corporation :Manual section: |MANUAL_SECTION| :Manual group: |MANUAL_GROUP| @@ -31,6 +31,11 @@ OPTIONS -s Refresh period in milliseconds. +-l + List text data to standard out. + +-J + Output JSON formatted data to standard output. -h Show help text. diff --git a/tools/intel_gpu_top.c b/tools/intel_gpu_top.c index b923c3cfbe97..900979eea7a1 100644 --- a/tools/intel_gpu_top.c +++ b/tools/intel_gpu_top.c @@ -1,5 +1,5 @@ /* - * Copyright © 2007-2018 Intel Corporation + * Copyright © 2007-2019 Intel Corporation * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), @@ -19,10 +19,6 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. - * - * Authors: - * Eric Anholt - * Eugeni Dodonov */ #include @@ -41,6 +37,8 @@ #include #include #include +#include +#include #include "igt_perf.h" @@ -58,7 +56,8 @@ struct pmu_counter { struct engine { const char *name; - const char *display_name; + char *display_name; + char *short_name; unsigned int class; unsigned int instance; @@ -142,6 +141,22 @@ static const char *class_display_name(unsigned int class) } } +static const char *class_short_name(unsigned int class) +{ + switch (class) { + case I915_ENGINE_CLASS_RENDER: + return "RCS"; + case I915_ENGINE_CLASS_COPY: + return "BCS"; + case I915_ENGINE_CLASS_VIDEO: + return "VCS"; + case I915_ENGINE_CLASS_VIDEO_ENHANCE: + return "VECS"; + default: + return "UNKN"; + } +} + static int engine_cmp(const void *__a, const void *__b) { const struct engine *a = (struct engine *)__a; @@ -220,17 +235,18 @@ static struct engines *discover_engines(void) I915_PMU_SAMPLE_BITS) & ((1 << I915_PMU_SAMPLE_INSTANCE_BITS) - 1); - ret = snprintf(buf, sizeof(buf), "%s/%u", + ret = asprintf(&engine->display_name, "%s/%u", class_display_name(engine->class), engine->instance); - if (ret < 0 || ret == sizeof(buf)) { - ret = ENOBUFS; + if (!engine->display_name) { + ret = errno; break; } - ret = 0; - engine->display_name = strdup(buf); - if (!engine->display_name) { + ret = asprintf(&engine->short_name, "%s/%u", + class_short_name(engine->class), + engine->instance); + if (!engine->short_name) { ret = errno; break; } @@ -242,6 +258,8 @@ static struct engines *discover_engines(void) ret = errno; break; } + + ret = 0; } if (ret) { @@ -551,7 +569,7 @@ static uint64_t pmu_read_multi(int fd, unsigned int num, uint64_t *val) return buf[1]; } -static double __pmu_calc(struct pmu_pair *p, double d, double t, double s) +static double pmu_calc(struct pmu_pair *p, double d, double t, double s) { double v; @@ -576,30 +594,6 @@ static void fill_str(char *buf, unsigned int bufsz, char c, unsigned int num) *buf = 0; } -static void pmu_calc(struct pmu_counter *cnt, - char *buf, unsigned int bufsz, - unsigned int width, unsigned width_dec, - double d, double t, double s) -{ - double val; - int len; - - assert(bufsz >= (width + width_dec + 1)); - - if (!cnt->present) { - fill_str(buf, bufsz, '-', width + width_dec); - return; - } - - val = __pmu_calc(&cnt->val, d, t, s); - - len = snprintf(buf, bufsz, "%*.*f", width + width_dec, width_dec, val); - if (len < 0 || len == bufsz) { - fill_str(buf, bufsz, 'X', width + width_dec); - return; - } -} - static uint64_t __pmu_read_single(int fd, uint64_t *ts) { uint64_t data[2] = { }; @@ -697,11 +691,559 @@ usage(const char *appname) "\n" "\tThe following parameters are optional:\n\n" "\t[-s ] Refresh period in milliseconds (default %ums).\n" + "\t[-l] List data to standard out.\n" + "\t[-J] JSON data to standard out.\n" "\t[-h] Show this help text.\n" "\n", appname, DEFAULT_PERIOD_MS); } +static enum { + INTERACTIVE, + STDOUT, + JSON +} output_mode; + +struct cnt_item { + struct pmu_counter *pmu; + unsigned int fmt_d; + unsigned int fmt_dd; + double d; + double t; + double s; + const char *name; + const char *unit; + + /* Internal fields. */ + char buf[16]; +}; + +struct cnt_group { + const char *name; + const char *display_name; + struct cnt_item *items; +}; + +static unsigned int json_indent_level; + +static const char *json_indent[] = { + "", + "\t", + "\t\t", + "\t\t\t", + "\t\t\t\t", + "\t\t\t\t\t", +}; + +#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0])) + +static unsigned int json_prev_struct_members; +static unsigned int json_struct_members; + +static void +json_open_struct(const char *name) +{ + assert(json_indent_level < ARRAY_SIZE(json_indent)); + + json_prev_struct_members = json_struct_members; + json_struct_members = 0; + + if (name) + printf("%s%s\"%s\": {\n", + json_prev_struct_members ? ",\n" : "", + json_indent[json_indent_level], + name); + else + printf("%s\n%s{\n", + json_prev_struct_members ? "," : "", + json_indent[json_indent_level]); + + json_indent_level++; +} + +static void +json_close_struct(void) +{ + assert(json_indent_level > 0); + + printf("\n%s}", json_indent[--json_indent_level]); + + if (json_indent_level == 0) + fflush(stdout); +} + +static unsigned int +json_add_member(const struct cnt_group *parent, struct cnt_item *item, + unsigned int headers) +{ + assert(json_indent_level < ARRAY_SIZE(json_indent)); + + printf("%s%s\"%s\": ", + json_struct_members ? ",\n" : "", + json_indent[json_indent_level], item->name); + + json_struct_members++; + + if (!strcmp(item->name, "unit")) + printf("\"%s\"", item->unit); + else + printf("%f", + pmu_calc(&item->pmu->val, item->d, item->t, item->s)); + + return 1; +} + +static unsigned int stdout_level; + +#define STDOUT_HEADER_REPEAT 20 +static unsigned int stdout_lines = STDOUT_HEADER_REPEAT; + +static void +stdout_open_struct(const char *name) +{ + stdout_level++; + assert(stdout_level > 0); +} + +static void +stdout_close_struct(void) +{ + assert(stdout_level > 0); + if (--stdout_level == 0) { + stdout_lines++; + printf("\n"); + fflush(stdout); + } +} + +static unsigned int +stdout_add_member(const struct cnt_group *parent, struct cnt_item *item, + unsigned int headers) +{ + unsigned int fmt_tot = item->fmt_d + (item->fmt_dd ? 1 : 0); + char buf[fmt_tot + 1]; + double val; + int len; + + if (!item->pmu) + return 0; + else if (!item->pmu->present) + return 0; + + if (headers == 1) { + unsigned int grp_tot = 0; + struct cnt_item *it; + + if (item != parent->items) + return 0; + + for (it = parent->items; it->pmu; it++) { + if (!it->pmu->present) + continue; + + grp_tot += 1 + it->fmt_d + (it->fmt_dd ? 1 : 0); + } + + printf("%*s ", grp_tot - 1, parent->display_name); + return 0; + } else if (headers == 2) { + printf("%*s ", fmt_tot, item->unit ?: item->name); + return 0; + } + + val = pmu_calc(&item->pmu->val, item->d, item->t, item->s); + + len = snprintf(buf, sizeof(buf), "%*.*f", fmt_tot, item->fmt_dd, val); + if (len < 0 || len == sizeof(buf)) + fill_str(buf, sizeof(buf), 'X', fmt_tot); + + len = printf("%s ", buf); + + return len > 0 ? len : 0; +} + +static void +term_open_struct(const char *name) +{ +} + +static void +term_close_struct(void) +{ +} + +static unsigned int +term_add_member(const struct cnt_group *parent, struct cnt_item *item, + unsigned int headers) +{ + unsigned int fmt_tot = item->fmt_d + (item->fmt_dd ? 1 : 0); + double val; + int len; + + if (!item->pmu) + return 0; + + assert(fmt_tot <= sizeof(item->buf)); + + if (!item->pmu->present) { + fill_str(item->buf, sizeof(item->buf), '-', fmt_tot); + return 1; + } + + val = pmu_calc(&item->pmu->val, item->d, item->t, item->s); + len = snprintf(item->buf, sizeof(item->buf), + "%*.*f", + fmt_tot, item->fmt_dd, val); + + if (len < 0 || len == sizeof(item->buf)) + fill_str(item->buf, sizeof(item->buf), 'X', fmt_tot); + + return 1; +} + +struct print_operations { + void (*open_struct)(const char *name); + void (*close_struct)(void); + unsigned int (*add_member)(const struct cnt_group *parent, + struct cnt_item *item, + unsigned int headers); + bool (*print_group)(struct cnt_group *group, unsigned int headers); +}; + +static const struct print_operations *pops; + +static unsigned int +present_in_group(const struct cnt_group *grp) +{ + unsigned int present = 0; + struct cnt_item *item; + + for (item = grp->items; item->name; item++) { + if (item->pmu && item->pmu->present) + present++; + } + + return present; +} + +static bool +print_group(struct cnt_group *grp, unsigned int headers) +{ + unsigned int consumed = 0; + struct cnt_item *item; + + if (!present_in_group(grp)) + return false; + + pops->open_struct(grp->name); + + for (item = grp->items; item->name; item++) + consumed += pops->add_member(grp, item, headers); + + pops->close_struct(); + + return consumed; +} + +static bool +term_print_group(struct cnt_group *grp, unsigned int headers) +{ + unsigned int consumed = 0; + struct cnt_item *item; + + pops->open_struct(grp->name); + + for (item = grp->items; item->name; item++) + consumed += pops->add_member(grp, item, headers); + + pops->close_struct(); + + return consumed; +} + +static const struct print_operations json_pops = { + .open_struct = json_open_struct, + .close_struct = json_close_struct, + .add_member = json_add_member, + .print_group = print_group, +}; + +static const struct print_operations stdout_pops = { + .open_struct = stdout_open_struct, + .close_struct = stdout_close_struct, + .add_member = stdout_add_member, + .print_group = print_group, +}; + +static const struct print_operations term_pops = { + .open_struct = term_open_struct, + .close_struct = term_close_struct, + .add_member = term_add_member, + .print_group = term_print_group, +}; + +static bool print_groups(struct cnt_group **groups) +{ + unsigned int headers = stdout_lines % STDOUT_HEADER_REPEAT + 1; + bool print_data = true; + + if (output_mode == STDOUT && (headers == 1 || headers == 2)) { + for (struct cnt_group **grp = groups; *grp; grp++) + print_data = pops->print_group(*grp, headers); + } + + for (struct cnt_group **grp = groups; print_data && *grp; grp++) + pops->print_group(*grp, false); + + return print_data; +} + +static int +print_header(struct engines *engines, double t, + int lines, int con_w, int con_h, bool *consumed) +{ + struct pmu_counter fake_pmu = { + .present = true, + .val.cur = 1, + }; + struct cnt_item period_items[] = { + { &fake_pmu, 0, 0, 1.0, 1.0, t * 1e3, "duration" }, + { NULL, 0, 0, 0.0, 0.0, 0.0, "unit", "ms" }, + { }, + }; + struct cnt_group period_group = { + .name = "period", + .items = period_items, + }; + struct cnt_item freq_items[] = { + { &engines->freq_req, 4, 0, 1.0, t, 1, "requested", "req" }, + { &engines->freq_act, 4, 0, 1.0, t, 1, "actual", "act" }, + { NULL, 0, 0, 0.0, 0.0, 0.0, "unit", "MHz" }, + { }, + }; + struct cnt_group freq_group = { + .name = "frequency", + .display_name = "Freq MHz", + .items = freq_items, + }; + struct cnt_item irq_items[] = { + { &engines->irq, 8, 0, 1.0, t, 1, "count", "/s" }, + { NULL, 0, 0, 0.0, 0.0, 0.0, "unit", "irq/s" }, + { }, + }; + struct cnt_group irq_group = { + .name = "interrupts", + .display_name = "IRQ", + .items = irq_items, + }; + struct cnt_item rc6_items[] = { + { &engines->rc6, 3, 0, 1e9, t, 100, "value", "%" }, + { NULL, 0, 0, 0.0, 0.0, 0.0, "unit", "%" }, + { }, + }; + struct cnt_group rc6_group = { + .name = "rc6", + .display_name = "RC6", + .items = rc6_items, + }; + struct cnt_item power_items[] = { + { &engines->rapl, 4, 2, 1.0, t, engines->rapl_scale, "value", + "W" }, + { NULL, 0, 0, 0.0, 0.0, 0.0, "unit", "W" }, + { }, + }; + struct cnt_group power_group = { + .name = "power", + .display_name = "Power", + .items = power_items, + }; + struct cnt_group *groups[] = { + &period_group, + &freq_group, + &irq_group, + &rc6_group, + &power_group, + NULL + }; + + if (output_mode != JSON) + memmove(&groups[0], &groups[1], + sizeof(groups) - sizeof(groups[0])); + + pops->open_struct(NULL); + + *consumed = print_groups(groups); + + if (output_mode == INTERACTIVE) { + printf("\033[H\033[J"); + + if (lines++ < con_h) + printf("intel-gpu-top - %s/%s MHz; %s%% RC6; %s %s; %s irqs/s\n", + freq_items[1].buf, freq_items[0].buf, + rc6_items[0].buf, power_items[0].buf, + engines->rapl_unit, + irq_items[0].buf); + + if (lines++ < con_h) + printf("\n"); + } + + return lines; +} + +static int +print_imc(struct engines *engines, double t, int lines, int con_w, int con_h) +{ + struct cnt_item imc_items[] = { + { &engines->imc_reads, 6, 0, 1.0, t, engines->imc_reads_scale, + "reads", "rd" }, + { &engines->imc_writes, 6, 0, 1.0, t, engines->imc_writes_scale, + "writes", "wr" }, + { NULL, 0, 0, 0.0, 0.0, 0.0, "unit" }, + { }, + }; + struct cnt_group imc_group = { + .name = "imc-bandwidth", + .items = imc_items, + }; + struct cnt_group *groups[] = { + &imc_group, + NULL + }; + int ret; + + ret = asprintf((char **)&imc_group.display_name, "IMC %s/s", + engines->imc_reads_unit); + assert(ret >= 0); + + ret = asprintf((char **)&imc_items[2].unit, "%s/s", + engines->imc_reads_unit); + assert(ret >= 0); + + print_groups(groups); + + free((void *)imc_group.display_name); + free((void *)imc_items[2].unit); + + if (output_mode == INTERACTIVE) { + if (lines++ < con_h) + printf(" IMC reads: %s %s/s\n", + imc_items[0].buf, engines->imc_reads_unit); + + if (lines++ < con_h) + printf(" IMC writes: %s %s/s\n", + imc_items[1].buf, engines->imc_writes_unit); + + if (lines++ < con_h) + printf("\n"); + } + + return lines; +} + +static int +print_engines_header(struct engines *engines, double t, + int lines, int con_w, int con_h) +{ + for (unsigned int i = 0; + i < engines->num_engines && lines < con_h; + i++) { + struct engine *engine = engine_ptr(engines, i); + + if (!engine->num_counters) + continue; + + pops->open_struct("engines"); + + if (output_mode == INTERACTIVE) { + const char *a = " ENGINE BUSY "; + const char *b = " MI_SEMA MI_WAIT"; + + printf("\033[7m%s%*s%s\033[0m\n", + a, (int)(con_w - 1 - strlen(a) - strlen(b)), + " ", b); + + lines++; + } + + break; + } + + return lines; +} + +static int +print_engine(struct engines *engines, unsigned int i, double t, + int lines, int con_w, int con_h) +{ + struct engine *engine = engine_ptr(engines, i); + struct cnt_item engine_items[] = { + { &engine->busy, 6, 2, 1e9, t, 100, "busy", "%" }, + { &engine->sema, 3, 0, 1e9, t, 100, "sema", "se" }, + { &engine->wait, 3, 0, 1e9, t, 100, "wait", "wa" }, + { NULL, 0, 0, 0.0, 0.0, 0.0, "unit", "%" }, + { }, + }; + struct cnt_group engine_group = { + .name = engine->display_name, + .display_name = engine->short_name, + .items = engine_items, + }; + struct cnt_group *groups[] = { + &engine_group, + NULL + }; + + if (!engine->num_counters) + return lines; + + print_groups(groups); + + if (output_mode == INTERACTIVE) { + unsigned int max_w = con_w - 1; + unsigned int len; + char buf[128]; + double val; + + len = snprintf(buf, sizeof(buf), " %s%% %s%%", + engine_items[1].buf, engine_items[2].buf); + + len += printf("%16s %s%% ", + engine->display_name, engine_items[0].buf); + + val = pmu_calc(&engine->busy.val, 1e9, t, 100); + print_percentage_bar(val, max_w - len); + + printf("%s\n", buf); + + lines++; + } + + return lines; +} + +static int +print_engines_footer(struct engines *engines, double t, + int lines, int con_w, int con_h) +{ + pops->close_struct(); + pops->close_struct(); + + if (output_mode == INTERACTIVE) { + if (lines++ < con_h) + printf("\n"); + } + + return lines; +} + +static bool stop_top; + +static void sigint_handler(int sig) +{ + stop_top = true; +} + int main(int argc, char **argv) { unsigned int period_us = DEFAULT_PERIOD_MS * 1000; @@ -711,11 +1253,17 @@ int main(int argc, char **argv) int ret, ch; /* Parse options */ - while ((ch = getopt(argc, argv, "s:h")) != -1) { + while ((ch = getopt(argc, argv, "s:Jlh")) != -1) { switch (ch) { case 's': period_us = atoi(optarg) * 1000; break; + case 'J': + output_mode = JSON; + break; + case 'l': + output_mode = STDOUT; + break; case 'h': usage(argv[0]); exit(0); @@ -726,6 +1274,31 @@ int main(int argc, char **argv) } } + if (output_mode == INTERACTIVE && isatty(1) != 1) + output_mode = STDOUT; + + if (output_mode != INTERACTIVE) { + sighandler_t sig = signal(SIGINT, sigint_handler); + + if (sig == SIG_ERR) + fprintf(stderr, "Failed to install signal handler!\n"); + } + + switch (output_mode) { + case INTERACTIVE: + pops = &term_pops; + break; + case STDOUT: + pops = &stdout_pops; + break; + case JSON: + pops = &json_pops; + break; + default: + assert(0); + break; + }; + engines = discover_engines(); if (!engines) { fprintf(stderr, @@ -743,21 +1316,16 @@ int main(int argc, char **argv) pmu_sample(engines); - for (;;) { - double t; -#define BUFSZ 16 - char freq[BUFSZ]; - char fact[BUFSZ]; - char irq[BUFSZ]; - char rc6[BUFSZ]; - char power[BUFSZ]; - char reads[BUFSZ]; - char writes[BUFSZ]; - struct winsize ws; + while (!stop_top) { + bool consumed = false; int lines = 0; + struct winsize ws; + double t; /* Update terminal size. */ - if (ioctl(0, TIOCGWINSZ, &ws) != -1) { + if (output_mode != INTERACTIVE) { + con_w = con_h = INT_MAX; + } else if (ioctl(0, TIOCGWINSZ, &ws) != -1) { con_w = ws.ws_col; con_h = ws.ws_row; } @@ -765,87 +1333,32 @@ int main(int argc, char **argv) pmu_sample(engines); t = (double)(engines->ts.cur - engines->ts.prev) / 1e9; - printf("\033[H\033[J"); - - pmu_calc(&engines->freq_req, freq, BUFSZ, 4, 0, 1.0, t, 1); - pmu_calc(&engines->freq_act, fact, BUFSZ, 4, 0, 1.0, t, 1); - pmu_calc(&engines->irq, irq, BUFSZ, 8, 0, 1.0, t, 1); - pmu_calc(&engines->rc6, rc6, BUFSZ, 3, 0, 1e9, t, 100); - pmu_calc(&engines->rapl, power, BUFSZ, 4, 2, 1.0, t, - engines->rapl_scale); - pmu_calc(&engines->imc_reads, reads, BUFSZ, 6, 0, 1.0, t, - engines->imc_reads_scale); - pmu_calc(&engines->imc_writes, writes, BUFSZ, 6, 0, 1.0, t, - engines->imc_writes_scale); - - if (lines++ < con_h) - printf("intel-gpu-top - %s/%s MHz; %s%% RC6; %s %s; %s irqs/s\n", - fact, freq, rc6, power, engines->rapl_unit, irq); - - if (lines++ < con_h) - printf("\n"); - - if (engines->imc_fd) { - if (lines++ < con_h) - printf(" IMC reads: %s %s/s\n", - reads, engines->imc_reads_unit); - - if (lines++ < con_h) - printf(" IMC writes: %s %s/s\n", - writes, engines->imc_writes_unit); - - if (++lines < con_h) - printf("\n"); - } - - for (i = 0; i < engines->num_engines; i++) { - struct engine *engine = engine_ptr(engines, i); - - if (engine->num_counters && lines < con_h) { - const char *a = " ENGINE BUSY "; - const char *b = " MI_SEMA MI_WAIT"; - - printf("\033[7m%s%*s%s\033[0m\n", - a, - (int)(con_w - 1 - strlen(a) - strlen(b)), - " ", b); - lines++; - break; - } - } - - for (i = 0; i < engines->num_engines && lines < con_h; i++) { - struct engine *engine = engine_ptr(engines, i); - unsigned int max_w = con_w - 1; - unsigned int len; - char sema[BUFSZ]; - char wait[BUFSZ]; - char busy[BUFSZ]; - char buf[128]; - double val; - - if (!engine->num_counters) - continue; + if (stop_top) + break; - pmu_calc(&engine->sema, sema, BUFSZ, 3, 0, 1e9, t, 100); - pmu_calc(&engine->wait, wait, BUFSZ, 3, 0, 1e9, t, 100); - len = snprintf(buf, sizeof(buf), " %s%% %s%%", - sema, wait); + while (!consumed) { + lines = print_header(engines, t, lines, con_w, con_h, + &consumed); - pmu_calc(&engine->busy, busy, BUFSZ, 6, 2, 1e9, t, - 100); - len += printf("%16s %s%% ", engine->display_name, busy); + if (engines->imc_fd) + lines = print_imc(engines, t, lines, con_w, + con_h); - val = __pmu_calc(&engine->busy.val, 1e9, t, 100); - print_percentage_bar(val, max_w - len); + lines = print_engines_header(engines, t, lines, con_w, + con_h); - printf("%s\n", buf); + for (i = 0; + i < engines->num_engines && lines < con_h; + i++) + lines = print_engine(engines, i, t, lines, + con_w, con_h); - lines++; + lines = print_engines_footer(engines, t, lines, con_w, + con_h); } - if (lines++ < con_h) - printf("\n"); + if (stop_top) + break; usleep(period_us); } From patchwork Mon Feb 11 11:45:23 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tvrtko Ursulin X-Patchwork-Id: 10805543 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 2D7B9746 for ; Mon, 11 Feb 2019 11:45:22 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 1C91629BFC for ; Mon, 11 Feb 2019 11:45:22 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 105F429C02; Mon, 11 Feb 2019 11:45:22 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-5.2 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_MED autolearn=ham version=3.3.1 Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id 8DF3529BFC for ; Mon, 11 Feb 2019 11:45:21 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id F1BC56E48F; Mon, 11 Feb 2019 11:45:20 +0000 (UTC) X-Original-To: Intel-gfx@lists.freedesktop.org Delivered-To: Intel-gfx@lists.freedesktop.org Received: from mga01.intel.com (mga01.intel.com [192.55.52.88]) by gabe.freedesktop.org (Postfix) with ESMTPS id 484B96E47B; Mon, 11 Feb 2019 11:45:18 +0000 (UTC) X-Amp-Result: SKIPPED(no attachment in message) X-Amp-File-Uploaded: False Received: from fmsmga007.fm.intel.com ([10.253.24.52]) by fmsmga101.fm.intel.com with ESMTP/TLS/DHE-RSA-AES256-GCM-SHA384; 11 Feb 2019 03:45:18 -0800 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.58,358,1544515200"; d="scan'208";a="121511169" Received: from jjcunnix-mobl.ger.corp.intel.com (HELO localhost.localdomain) ([10.252.13.125]) by fmsmga007.fm.intel.com with ESMTP; 11 Feb 2019 03:45:16 -0800 From: Tvrtko Ursulin To: igt-dev@lists.freedesktop.org Date: Mon, 11 Feb 2019 11:45:23 +0000 Message-Id: <20190211114523.22844-2-tvrtko.ursulin@linux.intel.com> X-Mailer: git-send-email 2.19.1 In-Reply-To: <20190211114523.22844-1-tvrtko.ursulin@linux.intel.com> References: <20190211114523.22844-1-tvrtko.ursulin@linux.intel.com> MIME-Version: 1.0 Subject: [Intel-gfx] [IGT 2/2] tools/intel_gpu_top: Add file output capability X-BeenThere: intel-gfx@lists.freedesktop.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: Intel graphics driver community testing & development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Intel-gfx@lists.freedesktop.org, Eero Tamminen , 3.14pi@ukr.net Errors-To: intel-gfx-bounces@lists.freedesktop.org Sender: "Intel-gfx" X-Virus-Scanned: ClamAV using ClamSMTP From: Tvrtko Ursulin A new -o command switch enables logging to a file. v2: * Support "-o -" for explicit stdout selection. (Chris Wilson) Signed-off-by: Tvrtko Ursulin References: https://bugs.freedesktop.org/show_bug.cgi?id=108689 Cc: Eero Tamminen Cc: 3.14pi@ukr.net Cc: Chris Wilson Reviewed-by: Chris Wilson --- man/intel_gpu_top.rst | 19 ++++++++----- tools/intel_gpu_top.c | 63 ++++++++++++++++++++++++++++--------------- 2 files changed, 53 insertions(+), 29 deletions(-) diff --git a/man/intel_gpu_top.rst b/man/intel_gpu_top.rst index d5bda093c8e8..d487baca0c63 100644 --- a/man/intel_gpu_top.rst +++ b/man/intel_gpu_top.rst @@ -28,16 +28,21 @@ The tool gathers data using perf performance counters (PMU) exposed by i915 and OPTIONS ======= --s - Refresh period in milliseconds. +-h + Show help text. + +-J + Output JSON formatted data. -l - List text data to standard out. + List plain text data. --J - Output JSON formatted data to standard output. --h - Show help text. +-o + Output to the specified file instead of standard output. + '-' can also be specified to explicitly select standard output. + +-s + Refresh period in milliseconds. LIMITATIONS =========== diff --git a/tools/intel_gpu_top.c b/tools/intel_gpu_top.c index 900979eea7a1..60505a606bfc 100644 --- a/tools/intel_gpu_top.c +++ b/tools/intel_gpu_top.c @@ -690,10 +690,11 @@ usage(const char *appname) "Usage: %s [parameters]\n" "\n" "\tThe following parameters are optional:\n\n" - "\t[-s ] Refresh period in milliseconds (default %ums).\n" - "\t[-l] List data to standard out.\n" - "\t[-J] JSON data to standard out.\n" "\t[-h] Show this help text.\n" + "\t[-J] Output JSON formatted data.\n" + "\t[-l] List plain text data.\n" + "\t[-o ] Output to specified file or '-' for standard out.\n" + "\t[-s ] Refresh period in milliseconds (default %ums).\n" "\n", appname, DEFAULT_PERIOD_MS); } @@ -740,6 +741,8 @@ static const char *json_indent[] = { static unsigned int json_prev_struct_members; static unsigned int json_struct_members; +FILE *out; + static void json_open_struct(const char *name) { @@ -749,14 +752,14 @@ json_open_struct(const char *name) json_struct_members = 0; if (name) - printf("%s%s\"%s\": {\n", - json_prev_struct_members ? ",\n" : "", - json_indent[json_indent_level], - name); + fprintf(out, "%s%s\"%s\": {\n", + json_prev_struct_members ? ",\n" : "", + json_indent[json_indent_level], + name); else - printf("%s\n%s{\n", - json_prev_struct_members ? "," : "", - json_indent[json_indent_level]); + fprintf(out, "%s\n%s{\n", + json_prev_struct_members ? "," : "", + json_indent[json_indent_level]); json_indent_level++; } @@ -766,7 +769,7 @@ json_close_struct(void) { assert(json_indent_level > 0); - printf("\n%s}", json_indent[--json_indent_level]); + fprintf(out, "\n%s}", json_indent[--json_indent_level]); if (json_indent_level == 0) fflush(stdout); @@ -778,17 +781,17 @@ json_add_member(const struct cnt_group *parent, struct cnt_item *item, { assert(json_indent_level < ARRAY_SIZE(json_indent)); - printf("%s%s\"%s\": ", + fprintf(out, "%s%s\"%s\": ", json_struct_members ? ",\n" : "", json_indent[json_indent_level], item->name); json_struct_members++; if (!strcmp(item->name, "unit")) - printf("\"%s\"", item->unit); + fprintf(out, "\"%s\"", item->unit); else - printf("%f", - pmu_calc(&item->pmu->val, item->d, item->t, item->s)); + fprintf(out, "%f", + pmu_calc(&item->pmu->val, item->d, item->t, item->s)); return 1; } @@ -811,8 +814,8 @@ stdout_close_struct(void) assert(stdout_level > 0); if (--stdout_level == 0) { stdout_lines++; - printf("\n"); - fflush(stdout); + fputs("\n", out); + fflush(out); } } @@ -844,10 +847,10 @@ stdout_add_member(const struct cnt_group *parent, struct cnt_item *item, grp_tot += 1 + it->fmt_d + (it->fmt_dd ? 1 : 0); } - printf("%*s ", grp_tot - 1, parent->display_name); + fprintf(out, "%*s ", grp_tot - 1, parent->display_name); return 0; } else if (headers == 2) { - printf("%*s ", fmt_tot, item->unit ?: item->name); + fprintf(out, "%*s ", fmt_tot, item->unit ?: item->name); return 0; } @@ -857,7 +860,7 @@ stdout_add_member(const struct cnt_group *parent, struct cnt_item *item, if (len < 0 || len == sizeof(buf)) fill_str(buf, sizeof(buf), 'X', fmt_tot); - len = printf("%s ", buf); + len = fprintf(out, "%s ", buf); return len > 0 ? len : 0; } @@ -1248,13 +1251,17 @@ int main(int argc, char **argv) { unsigned int period_us = DEFAULT_PERIOD_MS * 1000; int con_w = -1, con_h = -1; + char *output_path = NULL; struct engines *engines; unsigned int i; int ret, ch; /* Parse options */ - while ((ch = getopt(argc, argv, "s:Jlh")) != -1) { + while ((ch = getopt(argc, argv, "o:s:Jlh")) != -1) { switch (ch) { + case 'o': + output_path = optarg; + break; case 's': period_us = atoi(optarg) * 1000; break; @@ -1274,9 +1281,21 @@ int main(int argc, char **argv) } } - if (output_mode == INTERACTIVE && isatty(1) != 1) + if (output_mode == INTERACTIVE && (output_path || isatty(1) != 1)) output_mode = STDOUT; + if (output_path && strcmp(output_path, "-")) { + out = fopen(output_path, "w"); + + if (!out) { + fprintf(stderr, "Failed to open output file - '%s'!\n", + strerror(errno)); + exit(1); + } + } else { + out = stdout; + } + if (output_mode != INTERACTIVE) { sighandler_t sig = signal(SIGINT, sigint_handler);