1
2#include <stdio.h>
3#include "evsel.h"
4#include "stat.h"
5#include "color.h"
6#include "pmu.h"
7#include "rblist.h"
8#include "evlist.h"
9#include "expr.h"
10#include "metricgroup.h"
11#include "cgroup.h"
12#include <linux/zalloc.h>
13
14
15
16
17
18
19
20
21
22
23struct runtime_stat rt_stat;
24struct stats walltime_nsecs_stats;
25
26struct saved_value {
27 struct rb_node rb_node;
28 struct evsel *evsel;
29 enum stat_type type;
30 int ctx;
31 int cpu;
32 struct cgroup *cgrp;
33 struct runtime_stat *stat;
34 struct stats stats;
35 u64 metric_total;
36 int metric_other;
37};
38
39static int saved_value_cmp(struct rb_node *rb_node, const void *entry)
40{
41 struct saved_value *a = container_of(rb_node,
42 struct saved_value,
43 rb_node);
44 const struct saved_value *b = entry;
45
46 if (a->cpu != b->cpu)
47 return a->cpu - b->cpu;
48
49
50
51
52
53
54
55
56 if (a->type != b->type)
57 return a->type - b->type;
58
59 if (a->ctx != b->ctx)
60 return a->ctx - b->ctx;
61
62 if (a->cgrp != b->cgrp)
63 return (char *)a->cgrp < (char *)b->cgrp ? -1 : +1;
64
65 if (a->evsel == NULL && b->evsel == NULL) {
66 if (a->stat == b->stat)
67 return 0;
68
69 if ((char *)a->stat < (char *)b->stat)
70 return -1;
71
72 return 1;
73 }
74
75 if (a->evsel == b->evsel)
76 return 0;
77 if ((char *)a->evsel < (char *)b->evsel)
78 return -1;
79 return +1;
80}
81
82static struct rb_node *saved_value_new(struct rblist *rblist __maybe_unused,
83 const void *entry)
84{
85 struct saved_value *nd = malloc(sizeof(struct saved_value));
86
87 if (!nd)
88 return NULL;
89 memcpy(nd, entry, sizeof(struct saved_value));
90 return &nd->rb_node;
91}
92
93static void saved_value_delete(struct rblist *rblist __maybe_unused,
94 struct rb_node *rb_node)
95{
96 struct saved_value *v;
97
98 BUG_ON(!rb_node);
99 v = container_of(rb_node, struct saved_value, rb_node);
100 free(v);
101}
102
103static struct saved_value *saved_value_lookup(struct evsel *evsel,
104 int cpu,
105 bool create,
106 enum stat_type type,
107 int ctx,
108 struct runtime_stat *st,
109 struct cgroup *cgrp)
110{
111 struct rblist *rblist;
112 struct rb_node *nd;
113 struct saved_value dm = {
114 .cpu = cpu,
115 .evsel = evsel,
116 .type = type,
117 .ctx = ctx,
118 .stat = st,
119 .cgrp = cgrp,
120 };
121
122 rblist = &st->value_list;
123
124
125 if (type == STAT_NSECS)
126 dm.ctx = 0;
127
128 nd = rblist__find(rblist, &dm);
129 if (nd)
130 return container_of(nd, struct saved_value, rb_node);
131 if (create) {
132 rblist__add_node(rblist, &dm);
133 nd = rblist__find(rblist, &dm);
134 if (nd)
135 return container_of(nd, struct saved_value, rb_node);
136 }
137 return NULL;
138}
139
140void runtime_stat__init(struct runtime_stat *st)
141{
142 struct rblist *rblist = &st->value_list;
143
144 rblist__init(rblist);
145 rblist->node_cmp = saved_value_cmp;
146 rblist->node_new = saved_value_new;
147 rblist->node_delete = saved_value_delete;
148}
149
150void runtime_stat__exit(struct runtime_stat *st)
151{
152 rblist__exit(&st->value_list);
153}
154
155void perf_stat__init_shadow_stats(void)
156{
157 runtime_stat__init(&rt_stat);
158}
159
160static int evsel_context(struct evsel *evsel)
161{
162 int ctx = 0;
163
164 if (evsel->core.attr.exclude_kernel)
165 ctx |= CTX_BIT_KERNEL;
166 if (evsel->core.attr.exclude_user)
167 ctx |= CTX_BIT_USER;
168 if (evsel->core.attr.exclude_hv)
169 ctx |= CTX_BIT_HV;
170 if (evsel->core.attr.exclude_host)
171 ctx |= CTX_BIT_HOST;
172 if (evsel->core.attr.exclude_idle)
173 ctx |= CTX_BIT_IDLE;
174
175 return ctx;
176}
177
178static void reset_stat(struct runtime_stat *st)
179{
180 struct rblist *rblist;
181 struct rb_node *pos, *next;
182
183 rblist = &st->value_list;
184 next = rb_first_cached(&rblist->entries);
185 while (next) {
186 pos = next;
187 next = rb_next(pos);
188 memset(&container_of(pos, struct saved_value, rb_node)->stats,
189 0,
190 sizeof(struct stats));
191 }
192}
193
194void perf_stat__reset_shadow_stats(void)
195{
196 reset_stat(&rt_stat);
197 memset(&walltime_nsecs_stats, 0, sizeof(walltime_nsecs_stats));
198}
199
200void perf_stat__reset_shadow_per_stat(struct runtime_stat *st)
201{
202 reset_stat(st);
203}
204
205struct runtime_stat_data {
206 int ctx;
207 struct cgroup *cgrp;
208};
209
210static void update_runtime_stat(struct runtime_stat *st,
211 enum stat_type type,
212 int cpu, u64 count,
213 struct runtime_stat_data *rsd)
214{
215 struct saved_value *v = saved_value_lookup(NULL, cpu, true, type,
216 rsd->ctx, st, rsd->cgrp);
217
218 if (v)
219 update_stats(&v->stats, count);
220}
221
222
223
224
225
226
227void perf_stat__update_shadow_stats(struct evsel *counter, u64 count,
228 int cpu, struct runtime_stat *st)
229{
230 u64 count_ns = count;
231 struct saved_value *v;
232 struct runtime_stat_data rsd = {
233 .ctx = evsel_context(counter),
234 .cgrp = counter->cgrp,
235 };
236
237 count *= counter->scale;
238
239 if (evsel__is_clock(counter))
240 update_runtime_stat(st, STAT_NSECS, cpu, count_ns, &rsd);
241 else if (evsel__match(counter, HARDWARE, HW_CPU_CYCLES))
242 update_runtime_stat(st, STAT_CYCLES, cpu, count, &rsd);
243 else if (perf_stat_evsel__is(counter, CYCLES_IN_TX))
244 update_runtime_stat(st, STAT_CYCLES_IN_TX, cpu, count, &rsd);
245 else if (perf_stat_evsel__is(counter, TRANSACTION_START))
246 update_runtime_stat(st, STAT_TRANSACTION, cpu, count, &rsd);
247 else if (perf_stat_evsel__is(counter, ELISION_START))
248 update_runtime_stat(st, STAT_ELISION, cpu, count, &rsd);
249 else if (perf_stat_evsel__is(counter, TOPDOWN_TOTAL_SLOTS))
250 update_runtime_stat(st, STAT_TOPDOWN_TOTAL_SLOTS,
251 cpu, count, &rsd);
252 else if (perf_stat_evsel__is(counter, TOPDOWN_SLOTS_ISSUED))
253 update_runtime_stat(st, STAT_TOPDOWN_SLOTS_ISSUED,
254 cpu, count, &rsd);
255 else if (perf_stat_evsel__is(counter, TOPDOWN_SLOTS_RETIRED))
256 update_runtime_stat(st, STAT_TOPDOWN_SLOTS_RETIRED,
257 cpu, count, &rsd);
258 else if (perf_stat_evsel__is(counter, TOPDOWN_FETCH_BUBBLES))
259 update_runtime_stat(st, STAT_TOPDOWN_FETCH_BUBBLES,
260 cpu, count, &rsd);
261 else if (perf_stat_evsel__is(counter, TOPDOWN_RECOVERY_BUBBLES))
262 update_runtime_stat(st, STAT_TOPDOWN_RECOVERY_BUBBLES,
263 cpu, count, &rsd);
264 else if (perf_stat_evsel__is(counter, TOPDOWN_RETIRING))
265 update_runtime_stat(st, STAT_TOPDOWN_RETIRING,
266 cpu, count, &rsd);
267 else if (perf_stat_evsel__is(counter, TOPDOWN_BAD_SPEC))
268 update_runtime_stat(st, STAT_TOPDOWN_BAD_SPEC,
269 cpu, count, &rsd);
270 else if (perf_stat_evsel__is(counter, TOPDOWN_FE_BOUND))
271 update_runtime_stat(st, STAT_TOPDOWN_FE_BOUND,
272 cpu, count, &rsd);
273 else if (perf_stat_evsel__is(counter, TOPDOWN_BE_BOUND))
274 update_runtime_stat(st, STAT_TOPDOWN_BE_BOUND,
275 cpu, count, &rsd);
276 else if (perf_stat_evsel__is(counter, TOPDOWN_HEAVY_OPS))
277 update_runtime_stat(st, STAT_TOPDOWN_HEAVY_OPS,
278 cpu, count, &rsd);
279 else if (perf_stat_evsel__is(counter, TOPDOWN_BR_MISPREDICT))
280 update_runtime_stat(st, STAT_TOPDOWN_BR_MISPREDICT,
281 cpu, count, &rsd);
282 else if (perf_stat_evsel__is(counter, TOPDOWN_FETCH_LAT))
283 update_runtime_stat(st, STAT_TOPDOWN_FETCH_LAT,
284 cpu, count, &rsd);
285 else if (perf_stat_evsel__is(counter, TOPDOWN_MEM_BOUND))
286 update_runtime_stat(st, STAT_TOPDOWN_MEM_BOUND,
287 cpu, count, &rsd);
288 else if (evsel__match(counter, HARDWARE, HW_STALLED_CYCLES_FRONTEND))
289 update_runtime_stat(st, STAT_STALLED_CYCLES_FRONT,
290 cpu, count, &rsd);
291 else if (evsel__match(counter, HARDWARE, HW_STALLED_CYCLES_BACKEND))
292 update_runtime_stat(st, STAT_STALLED_CYCLES_BACK,
293 cpu, count, &rsd);
294 else if (evsel__match(counter, HARDWARE, HW_BRANCH_INSTRUCTIONS))
295 update_runtime_stat(st, STAT_BRANCHES, cpu, count, &rsd);
296 else if (evsel__match(counter, HARDWARE, HW_CACHE_REFERENCES))
297 update_runtime_stat(st, STAT_CACHEREFS, cpu, count, &rsd);
298 else if (evsel__match(counter, HW_CACHE, HW_CACHE_L1D))
299 update_runtime_stat(st, STAT_L1_DCACHE, cpu, count, &rsd);
300 else if (evsel__match(counter, HW_CACHE, HW_CACHE_L1I))
301 update_runtime_stat(st, STAT_L1_ICACHE, cpu, count, &rsd);
302 else if (evsel__match(counter, HW_CACHE, HW_CACHE_LL))
303 update_runtime_stat(st, STAT_LL_CACHE, cpu, count, &rsd);
304 else if (evsel__match(counter, HW_CACHE, HW_CACHE_DTLB))
305 update_runtime_stat(st, STAT_DTLB_CACHE, cpu, count, &rsd);
306 else if (evsel__match(counter, HW_CACHE, HW_CACHE_ITLB))
307 update_runtime_stat(st, STAT_ITLB_CACHE, cpu, count, &rsd);
308 else if (perf_stat_evsel__is(counter, SMI_NUM))
309 update_runtime_stat(st, STAT_SMI_NUM, cpu, count, &rsd);
310 else if (perf_stat_evsel__is(counter, APERF))
311 update_runtime_stat(st, STAT_APERF, cpu, count, &rsd);
312
313 if (counter->collect_stat) {
314 v = saved_value_lookup(counter, cpu, true, STAT_NONE, 0, st,
315 rsd.cgrp);
316 update_stats(&v->stats, count);
317 if (counter->metric_leader)
318 v->metric_total += count;
319 } else if (counter->metric_leader) {
320 v = saved_value_lookup(counter->metric_leader,
321 cpu, true, STAT_NONE, 0, st, rsd.cgrp);
322 v->metric_total += count;
323 v->metric_other++;
324 }
325}
326
327
328enum grc_type {
329 GRC_STALLED_CYCLES_FE,
330 GRC_STALLED_CYCLES_BE,
331 GRC_CACHE_MISSES,
332 GRC_MAX_NR
333};
334
335static const char *get_ratio_color(enum grc_type type, double ratio)
336{
337 static const double grc_table[GRC_MAX_NR][3] = {
338 [GRC_STALLED_CYCLES_FE] = { 50.0, 30.0, 10.0 },
339 [GRC_STALLED_CYCLES_BE] = { 75.0, 50.0, 20.0 },
340 [GRC_CACHE_MISSES] = { 20.0, 10.0, 5.0 },
341 };
342 const char *color = PERF_COLOR_NORMAL;
343
344 if (ratio > grc_table[type][0])
345 color = PERF_COLOR_RED;
346 else if (ratio > grc_table[type][1])
347 color = PERF_COLOR_MAGENTA;
348 else if (ratio > grc_table[type][2])
349 color = PERF_COLOR_YELLOW;
350
351 return color;
352}
353
354static struct evsel *perf_stat__find_event(struct evlist *evsel_list,
355 const char *name)
356{
357 struct evsel *c2;
358
359 evlist__for_each_entry (evsel_list, c2) {
360 if (!strcasecmp(c2->name, name) && !c2->collect_stat)
361 return c2;
362 }
363 return NULL;
364}
365
366
367void perf_stat__collect_metric_expr(struct evlist *evsel_list)
368{
369 struct evsel *counter, *leader, **metric_events, *oc;
370 bool found;
371 struct expr_parse_ctx ctx;
372 struct hashmap_entry *cur;
373 size_t bkt;
374 int i;
375
376 expr__ctx_init(&ctx);
377 evlist__for_each_entry(evsel_list, counter) {
378 bool invalid = false;
379
380 leader = counter->leader;
381 if (!counter->metric_expr)
382 continue;
383
384 expr__ctx_clear(&ctx);
385 metric_events = counter->metric_events;
386 if (!metric_events) {
387 if (expr__find_other(counter->metric_expr,
388 counter->name,
389 &ctx, 1) < 0)
390 continue;
391
392 metric_events = calloc(sizeof(struct evsel *),
393 hashmap__size(&ctx.ids) + 1);
394 if (!metric_events) {
395 expr__ctx_clear(&ctx);
396 return;
397 }
398 counter->metric_events = metric_events;
399 }
400
401 i = 0;
402 hashmap__for_each_entry((&ctx.ids), cur, bkt) {
403 const char *metric_name = (const char *)cur->key;
404
405 found = false;
406 if (leader) {
407
408 for_each_group_member (oc, leader) {
409 if (!strcasecmp(oc->name,
410 metric_name) &&
411 !oc->collect_stat) {
412 found = true;
413 break;
414 }
415 }
416 }
417 if (!found) {
418
419 oc = perf_stat__find_event(evsel_list,
420 metric_name);
421 }
422 if (!oc) {
423
424 static char *printed;
425
426
427
428
429
430
431
432
433 if (!printed ||
434 strcasecmp(printed, metric_name)) {
435 fprintf(stderr,
436 "Add %s event to groups to get metric expression for %s\n",
437 metric_name,
438 counter->name);
439 printed = strdup(metric_name);
440 }
441 invalid = true;
442 continue;
443 }
444 metric_events[i++] = oc;
445 oc->collect_stat = true;
446 }
447 metric_events[i] = NULL;
448 if (invalid) {
449 free(metric_events);
450 counter->metric_events = NULL;
451 counter->metric_expr = NULL;
452 }
453 }
454 expr__ctx_clear(&ctx);
455}
456
457static double runtime_stat_avg(struct runtime_stat *st,
458 enum stat_type type, int cpu,
459 struct runtime_stat_data *rsd)
460{
461 struct saved_value *v;
462
463 v = saved_value_lookup(NULL, cpu, false, type, rsd->ctx, st, rsd->cgrp);
464 if (!v)
465 return 0.0;
466
467 return avg_stats(&v->stats);
468}
469
470static double runtime_stat_n(struct runtime_stat *st,
471 enum stat_type type, int cpu,
472 struct runtime_stat_data *rsd)
473{
474 struct saved_value *v;
475
476 v = saved_value_lookup(NULL, cpu, false, type, rsd->ctx, st, rsd->cgrp);
477 if (!v)
478 return 0.0;
479
480 return v->stats.n;
481}
482
483static void print_stalled_cycles_frontend(struct perf_stat_config *config,
484 int cpu, double avg,
485 struct perf_stat_output_ctx *out,
486 struct runtime_stat *st,
487 struct runtime_stat_data *rsd)
488{
489 double total, ratio = 0.0;
490 const char *color;
491
492 total = runtime_stat_avg(st, STAT_CYCLES, cpu, rsd);
493
494 if (total)
495 ratio = avg / total * 100.0;
496
497 color = get_ratio_color(GRC_STALLED_CYCLES_FE, ratio);
498
499 if (ratio)
500 out->print_metric(config, out->ctx, color, "%7.2f%%", "frontend cycles idle",
501 ratio);
502 else
503 out->print_metric(config, out->ctx, NULL, NULL, "frontend cycles idle", 0);
504}
505
506static void print_stalled_cycles_backend(struct perf_stat_config *config,
507 int cpu, double avg,
508 struct perf_stat_output_ctx *out,
509 struct runtime_stat *st,
510 struct runtime_stat_data *rsd)
511{
512 double total, ratio = 0.0;
513 const char *color;
514
515 total = runtime_stat_avg(st, STAT_CYCLES, cpu, rsd);
516
517 if (total)
518 ratio = avg / total * 100.0;
519
520 color = get_ratio_color(GRC_STALLED_CYCLES_BE, ratio);
521
522 out->print_metric(config, out->ctx, color, "%7.2f%%", "backend cycles idle", ratio);
523}
524
525static void print_branch_misses(struct perf_stat_config *config,
526 int cpu, double avg,
527 struct perf_stat_output_ctx *out,
528 struct runtime_stat *st,
529 struct runtime_stat_data *rsd)
530{
531 double total, ratio = 0.0;
532 const char *color;
533
534 total = runtime_stat_avg(st, STAT_BRANCHES, cpu, rsd);
535
536 if (total)
537 ratio = avg / total * 100.0;
538
539 color = get_ratio_color(GRC_CACHE_MISSES, ratio);
540
541 out->print_metric(config, out->ctx, color, "%7.2f%%", "of all branches", ratio);
542}
543
544static void print_l1_dcache_misses(struct perf_stat_config *config,
545 int cpu, double avg,
546 struct perf_stat_output_ctx *out,
547 struct runtime_stat *st,
548 struct runtime_stat_data *rsd)
549{
550 double total, ratio = 0.0;
551 const char *color;
552
553 total = runtime_stat_avg(st, STAT_L1_DCACHE, cpu, rsd);
554
555 if (total)
556 ratio = avg / total * 100.0;
557
558 color = get_ratio_color(GRC_CACHE_MISSES, ratio);
559
560 out->print_metric(config, out->ctx, color, "%7.2f%%", "of all L1-dcache accesses", ratio);
561}
562
563static void print_l1_icache_misses(struct perf_stat_config *config,
564 int cpu, double avg,
565 struct perf_stat_output_ctx *out,
566 struct runtime_stat *st,
567 struct runtime_stat_data *rsd)
568{
569 double total, ratio = 0.0;
570 const char *color;
571
572 total = runtime_stat_avg(st, STAT_L1_ICACHE, cpu, rsd);
573
574 if (total)
575 ratio = avg / total * 100.0;
576
577 color = get_ratio_color(GRC_CACHE_MISSES, ratio);
578 out->print_metric(config, out->ctx, color, "%7.2f%%", "of all L1-icache accesses", ratio);
579}
580
581static void print_dtlb_cache_misses(struct perf_stat_config *config,
582 int cpu, double avg,
583 struct perf_stat_output_ctx *out,
584 struct runtime_stat *st,
585 struct runtime_stat_data *rsd)
586{
587 double total, ratio = 0.0;
588 const char *color;
589
590 total = runtime_stat_avg(st, STAT_DTLB_CACHE, cpu, rsd);
591
592 if (total)
593 ratio = avg / total * 100.0;
594
595 color = get_ratio_color(GRC_CACHE_MISSES, ratio);
596 out->print_metric(config, out->ctx, color, "%7.2f%%", "of all dTLB cache accesses", ratio);
597}
598
599static void print_itlb_cache_misses(struct perf_stat_config *config,
600 int cpu, double avg,
601 struct perf_stat_output_ctx *out,
602 struct runtime_stat *st,
603 struct runtime_stat_data *rsd)
604{
605 double total, ratio = 0.0;
606 const char *color;
607
608 total = runtime_stat_avg(st, STAT_ITLB_CACHE, cpu, rsd);
609
610 if (total)
611 ratio = avg / total * 100.0;
612
613 color = get_ratio_color(GRC_CACHE_MISSES, ratio);
614 out->print_metric(config, out->ctx, color, "%7.2f%%", "of all iTLB cache accesses", ratio);
615}
616
617static void print_ll_cache_misses(struct perf_stat_config *config,
618 int cpu, double avg,
619 struct perf_stat_output_ctx *out,
620 struct runtime_stat *st,
621 struct runtime_stat_data *rsd)
622{
623 double total, ratio = 0.0;
624 const char *color;
625
626 total = runtime_stat_avg(st, STAT_LL_CACHE, cpu, rsd);
627
628 if (total)
629 ratio = avg / total * 100.0;
630
631 color = get_ratio_color(GRC_CACHE_MISSES, ratio);
632 out->print_metric(config, out->ctx, color, "%7.2f%%", "of all LL-cache accesses", ratio);
633}
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677static double sanitize_val(double x)
678{
679 if (x < 0 && x >= -0.02)
680 return 0.0;
681 return x;
682}
683
684static double td_total_slots(int cpu, struct runtime_stat *st,
685 struct runtime_stat_data *rsd)
686{
687 return runtime_stat_avg(st, STAT_TOPDOWN_TOTAL_SLOTS, cpu, rsd);
688}
689
690static double td_bad_spec(int cpu, struct runtime_stat *st,
691 struct runtime_stat_data *rsd)
692{
693 double bad_spec = 0;
694 double total_slots;
695 double total;
696
697 total = runtime_stat_avg(st, STAT_TOPDOWN_SLOTS_ISSUED, cpu, rsd) -
698 runtime_stat_avg(st, STAT_TOPDOWN_SLOTS_RETIRED, cpu, rsd) +
699 runtime_stat_avg(st, STAT_TOPDOWN_RECOVERY_BUBBLES, cpu, rsd);
700
701 total_slots = td_total_slots(cpu, st, rsd);
702 if (total_slots)
703 bad_spec = total / total_slots;
704 return sanitize_val(bad_spec);
705}
706
707static double td_retiring(int cpu, struct runtime_stat *st,
708 struct runtime_stat_data *rsd)
709{
710 double retiring = 0;
711 double total_slots = td_total_slots(cpu, st, rsd);
712 double ret_slots = runtime_stat_avg(st, STAT_TOPDOWN_SLOTS_RETIRED,
713 cpu, rsd);
714
715 if (total_slots)
716 retiring = ret_slots / total_slots;
717 return retiring;
718}
719
720static double td_fe_bound(int cpu, struct runtime_stat *st,
721 struct runtime_stat_data *rsd)
722{
723 double fe_bound = 0;
724 double total_slots = td_total_slots(cpu, st, rsd);
725 double fetch_bub = runtime_stat_avg(st, STAT_TOPDOWN_FETCH_BUBBLES,
726 cpu, rsd);
727
728 if (total_slots)
729 fe_bound = fetch_bub / total_slots;
730 return fe_bound;
731}
732
733static double td_be_bound(int cpu, struct runtime_stat *st,
734 struct runtime_stat_data *rsd)
735{
736 double sum = (td_fe_bound(cpu, st, rsd) +
737 td_bad_spec(cpu, st, rsd) +
738 td_retiring(cpu, st, rsd));
739 if (sum == 0)
740 return 0;
741 return sanitize_val(1.0 - sum);
742}
743
744
745
746
747
748
749static double td_metric_ratio(int cpu, enum stat_type type,
750 struct runtime_stat *stat,
751 struct runtime_stat_data *rsd)
752{
753 double sum = runtime_stat_avg(stat, STAT_TOPDOWN_RETIRING, cpu, rsd) +
754 runtime_stat_avg(stat, STAT_TOPDOWN_FE_BOUND, cpu, rsd) +
755 runtime_stat_avg(stat, STAT_TOPDOWN_BE_BOUND, cpu, rsd) +
756 runtime_stat_avg(stat, STAT_TOPDOWN_BAD_SPEC, cpu, rsd);
757 double d = runtime_stat_avg(stat, type, cpu, rsd);
758
759 if (sum)
760 return d / sum;
761 return 0;
762}
763
764
765
766
767
768
769static bool full_td(int cpu, struct runtime_stat *stat,
770 struct runtime_stat_data *rsd)
771{
772 int c = 0;
773
774 if (runtime_stat_avg(stat, STAT_TOPDOWN_RETIRING, cpu, rsd) > 0)
775 c++;
776 if (runtime_stat_avg(stat, STAT_TOPDOWN_BE_BOUND, cpu, rsd) > 0)
777 c++;
778 if (runtime_stat_avg(stat, STAT_TOPDOWN_FE_BOUND, cpu, rsd) > 0)
779 c++;
780 if (runtime_stat_avg(stat, STAT_TOPDOWN_BAD_SPEC, cpu, rsd) > 0)
781 c++;
782 return c >= 2;
783}
784
785static void print_smi_cost(struct perf_stat_config *config, int cpu,
786 struct perf_stat_output_ctx *out,
787 struct runtime_stat *st,
788 struct runtime_stat_data *rsd)
789{
790 double smi_num, aperf, cycles, cost = 0.0;
791 const char *color = NULL;
792
793 smi_num = runtime_stat_avg(st, STAT_SMI_NUM, cpu, rsd);
794 aperf = runtime_stat_avg(st, STAT_APERF, cpu, rsd);
795 cycles = runtime_stat_avg(st, STAT_CYCLES, cpu, rsd);
796
797 if ((cycles == 0) || (aperf == 0))
798 return;
799
800 if (smi_num)
801 cost = (aperf - cycles) / aperf * 100.00;
802
803 if (cost > 10)
804 color = PERF_COLOR_RED;
805 out->print_metric(config, out->ctx, color, "%8.1f%%", "SMI cycles%", cost);
806 out->print_metric(config, out->ctx, NULL, "%4.0f", "SMI#", smi_num);
807}
808
809static int prepare_metric(struct evsel **metric_events,
810 struct metric_ref *metric_refs,
811 struct expr_parse_ctx *pctx,
812 int cpu,
813 struct runtime_stat *st)
814{
815 double scale;
816 char *n, *pn;
817 int i, j, ret;
818
819 expr__ctx_init(pctx);
820 for (i = 0; metric_events[i]; i++) {
821 struct saved_value *v;
822 struct stats *stats;
823 u64 metric_total = 0;
824
825 if (!strcmp(metric_events[i]->name, "duration_time")) {
826 stats = &walltime_nsecs_stats;
827 scale = 1e-9;
828 } else {
829 v = saved_value_lookup(metric_events[i], cpu, false,
830 STAT_NONE, 0, st,
831 metric_events[i]->cgrp);
832 if (!v)
833 break;
834 stats = &v->stats;
835 scale = 1.0;
836
837 if (v->metric_other)
838 metric_total = v->metric_total;
839 }
840
841 n = strdup(metric_events[i]->name);
842 if (!n)
843 return -ENOMEM;
844
845
846
847
848
849 pn = strchr(n, ' ');
850 if (pn)
851 *pn = 0;
852
853 if (metric_total)
854 expr__add_id_val(pctx, n, metric_total);
855 else
856 expr__add_id_val(pctx, n, avg_stats(stats)*scale);
857 }
858
859 for (j = 0; metric_refs && metric_refs[j].metric_name; j++) {
860 ret = expr__add_ref(pctx, &metric_refs[j]);
861 if (ret)
862 return ret;
863 }
864
865 return i;
866}
867
868static void generic_metric(struct perf_stat_config *config,
869 const char *metric_expr,
870 struct evsel **metric_events,
871 struct metric_ref *metric_refs,
872 char *name,
873 const char *metric_name,
874 const char *metric_unit,
875 int runtime,
876 int cpu,
877 struct perf_stat_output_ctx *out,
878 struct runtime_stat *st)
879{
880 print_metric_t print_metric = out->print_metric;
881 struct expr_parse_ctx pctx;
882 double ratio, scale;
883 int i;
884 void *ctxp = out->ctx;
885
886 i = prepare_metric(metric_events, metric_refs, &pctx, cpu, st);
887 if (i < 0)
888 return;
889
890 if (!metric_events[i]) {
891 if (expr__parse(&ratio, &pctx, metric_expr, runtime) == 0) {
892 char *unit;
893 char metric_bf[64];
894
895 if (metric_unit && metric_name) {
896 if (perf_pmu__convert_scale(metric_unit,
897 &unit, &scale) >= 0) {
898 ratio *= scale;
899 }
900 if (strstr(metric_expr, "?"))
901 scnprintf(metric_bf, sizeof(metric_bf),
902 "%s %s_%d", unit, metric_name, runtime);
903 else
904 scnprintf(metric_bf, sizeof(metric_bf),
905 "%s %s", unit, metric_name);
906
907 print_metric(config, ctxp, NULL, "%8.1f",
908 metric_bf, ratio);
909 } else {
910 print_metric(config, ctxp, NULL, "%8.2f",
911 metric_name ?
912 metric_name :
913 out->force_header ? name : "",
914 ratio);
915 }
916 } else {
917 print_metric(config, ctxp, NULL, NULL,
918 out->force_header ?
919 (metric_name ? metric_name : name) : "", 0);
920 }
921 } else {
922 print_metric(config, ctxp, NULL, NULL,
923 out->force_header ?
924 (metric_name ? metric_name : name) : "", 0);
925 }
926
927 expr__ctx_clear(&pctx);
928}
929
930double test_generic_metric(struct metric_expr *mexp, int cpu, struct runtime_stat *st)
931{
932 struct expr_parse_ctx pctx;
933 double ratio = 0.0;
934
935 if (prepare_metric(mexp->metric_events, mexp->metric_refs, &pctx, cpu, st) < 0)
936 goto out;
937
938 if (expr__parse(&ratio, &pctx, mexp->metric_expr, 1))
939 ratio = 0.0;
940
941out:
942 expr__ctx_clear(&pctx);
943 return ratio;
944}
945
946void perf_stat__print_shadow_stats(struct perf_stat_config *config,
947 struct evsel *evsel,
948 double avg, int cpu,
949 struct perf_stat_output_ctx *out,
950 struct rblist *metric_events,
951 struct runtime_stat *st)
952{
953 void *ctxp = out->ctx;
954 print_metric_t print_metric = out->print_metric;
955 double total, ratio = 0.0, total2;
956 const char *color = NULL;
957 struct runtime_stat_data rsd = {
958 .ctx = evsel_context(evsel),
959 .cgrp = evsel->cgrp,
960 };
961 struct metric_event *me;
962 int num = 1;
963
964 if (evsel__match(evsel, HARDWARE, HW_INSTRUCTIONS)) {
965 total = runtime_stat_avg(st, STAT_CYCLES, cpu, &rsd);
966
967 if (total) {
968 ratio = avg / total;
969 print_metric(config, ctxp, NULL, "%7.2f ",
970 "insn per cycle", ratio);
971 } else {
972 print_metric(config, ctxp, NULL, NULL, "insn per cycle", 0);
973 }
974
975 total = runtime_stat_avg(st, STAT_STALLED_CYCLES_FRONT, cpu, &rsd);
976
977 total = max(total, runtime_stat_avg(st,
978 STAT_STALLED_CYCLES_BACK,
979 cpu, &rsd));
980
981 if (total && avg) {
982 out->new_line(config, ctxp);
983 ratio = total / avg;
984 print_metric(config, ctxp, NULL, "%7.2f ",
985 "stalled cycles per insn",
986 ratio);
987 }
988 } else if (evsel__match(evsel, HARDWARE, HW_BRANCH_MISSES)) {
989 if (runtime_stat_n(st, STAT_BRANCHES, cpu, &rsd) != 0)
990 print_branch_misses(config, cpu, avg, out, st, &rsd);
991 else
992 print_metric(config, ctxp, NULL, NULL, "of all branches", 0);
993 } else if (
994 evsel->core.attr.type == PERF_TYPE_HW_CACHE &&
995 evsel->core.attr.config == ( PERF_COUNT_HW_CACHE_L1D |
996 ((PERF_COUNT_HW_CACHE_OP_READ) << 8) |
997 ((PERF_COUNT_HW_CACHE_RESULT_MISS) << 16))) {
998
999 if (runtime_stat_n(st, STAT_L1_DCACHE, cpu, &rsd) != 0)
1000 print_l1_dcache_misses(config, cpu, avg, out, st, &rsd);
1001 else
1002 print_metric(config, ctxp, NULL, NULL, "of all L1-dcache accesses", 0);
1003 } else if (
1004 evsel->core.attr.type == PERF_TYPE_HW_CACHE &&
1005 evsel->core.attr.config == ( PERF_COUNT_HW_CACHE_L1I |
1006 ((PERF_COUNT_HW_CACHE_OP_READ) << 8) |
1007 ((PERF_COUNT_HW_CACHE_RESULT_MISS) << 16))) {
1008
1009 if (runtime_stat_n(st, STAT_L1_ICACHE, cpu, &rsd) != 0)
1010 print_l1_icache_misses(config, cpu, avg, out, st, &rsd);
1011 else
1012 print_metric(config, ctxp, NULL, NULL, "of all L1-icache accesses", 0);
1013 } else if (
1014 evsel->core.attr.type == PERF_TYPE_HW_CACHE &&
1015 evsel->core.attr.config == ( PERF_COUNT_HW_CACHE_DTLB |
1016 ((PERF_COUNT_HW_CACHE_OP_READ) << 8) |
1017 ((PERF_COUNT_HW_CACHE_RESULT_MISS) << 16))) {
1018
1019 if (runtime_stat_n(st, STAT_DTLB_CACHE, cpu, &rsd) != 0)
1020 print_dtlb_cache_misses(config, cpu, avg, out, st, &rsd);
1021 else
1022 print_metric(config, ctxp, NULL, NULL, "of all dTLB cache accesses", 0);
1023 } else if (
1024 evsel->core.attr.type == PERF_TYPE_HW_CACHE &&
1025 evsel->core.attr.config == ( PERF_COUNT_HW_CACHE_ITLB |
1026 ((PERF_COUNT_HW_CACHE_OP_READ) << 8) |
1027 ((PERF_COUNT_HW_CACHE_RESULT_MISS) << 16))) {
1028
1029 if (runtime_stat_n(st, STAT_ITLB_CACHE, cpu, &rsd) != 0)
1030 print_itlb_cache_misses(config, cpu, avg, out, st, &rsd);
1031 else
1032 print_metric(config, ctxp, NULL, NULL, "of all iTLB cache accesses", 0);
1033 } else if (
1034 evsel->core.attr.type == PERF_TYPE_HW_CACHE &&
1035 evsel->core.attr.config == ( PERF_COUNT_HW_CACHE_LL |
1036 ((PERF_COUNT_HW_CACHE_OP_READ) << 8) |
1037 ((PERF_COUNT_HW_CACHE_RESULT_MISS) << 16))) {
1038
1039 if (runtime_stat_n(st, STAT_LL_CACHE, cpu, &rsd) != 0)
1040 print_ll_cache_misses(config, cpu, avg, out, st, &rsd);
1041 else
1042 print_metric(config, ctxp, NULL, NULL, "of all LL-cache accesses", 0);
1043 } else if (evsel__match(evsel, HARDWARE, HW_CACHE_MISSES)) {
1044 total = runtime_stat_avg(st, STAT_CACHEREFS, cpu, &rsd);
1045
1046 if (total)
1047 ratio = avg * 100 / total;
1048
1049 if (runtime_stat_n(st, STAT_CACHEREFS, cpu, &rsd) != 0)
1050 print_metric(config, ctxp, NULL, "%8.3f %%",
1051 "of all cache refs", ratio);
1052 else
1053 print_metric(config, ctxp, NULL, NULL, "of all cache refs", 0);
1054 } else if (evsel__match(evsel, HARDWARE, HW_STALLED_CYCLES_FRONTEND)) {
1055 print_stalled_cycles_frontend(config, cpu, avg, out, st, &rsd);
1056 } else if (evsel__match(evsel, HARDWARE, HW_STALLED_CYCLES_BACKEND)) {
1057 print_stalled_cycles_backend(config, cpu, avg, out, st, &rsd);
1058 } else if (evsel__match(evsel, HARDWARE, HW_CPU_CYCLES)) {
1059 total = runtime_stat_avg(st, STAT_NSECS, cpu, &rsd);
1060
1061 if (total) {
1062 ratio = avg / total;
1063 print_metric(config, ctxp, NULL, "%8.3f", "GHz", ratio);
1064 } else {
1065 print_metric(config, ctxp, NULL, NULL, "Ghz", 0);
1066 }
1067 } else if (perf_stat_evsel__is(evsel, CYCLES_IN_TX)) {
1068 total = runtime_stat_avg(st, STAT_CYCLES, cpu, &rsd);
1069
1070 if (total)
1071 print_metric(config, ctxp, NULL,
1072 "%7.2f%%", "transactional cycles",
1073 100.0 * (avg / total));
1074 else
1075 print_metric(config, ctxp, NULL, NULL, "transactional cycles",
1076 0);
1077 } else if (perf_stat_evsel__is(evsel, CYCLES_IN_TX_CP)) {
1078 total = runtime_stat_avg(st, STAT_CYCLES, cpu, &rsd);
1079 total2 = runtime_stat_avg(st, STAT_CYCLES_IN_TX, cpu, &rsd);
1080
1081 if (total2 < avg)
1082 total2 = avg;
1083 if (total)
1084 print_metric(config, ctxp, NULL, "%7.2f%%", "aborted cycles",
1085 100.0 * ((total2-avg) / total));
1086 else
1087 print_metric(config, ctxp, NULL, NULL, "aborted cycles", 0);
1088 } else if (perf_stat_evsel__is(evsel, TRANSACTION_START)) {
1089 total = runtime_stat_avg(st, STAT_CYCLES_IN_TX, cpu, &rsd);
1090
1091 if (avg)
1092 ratio = total / avg;
1093
1094 if (runtime_stat_n(st, STAT_CYCLES_IN_TX, cpu, &rsd) != 0)
1095 print_metric(config, ctxp, NULL, "%8.0f",
1096 "cycles / transaction", ratio);
1097 else
1098 print_metric(config, ctxp, NULL, NULL, "cycles / transaction",
1099 0);
1100 } else if (perf_stat_evsel__is(evsel, ELISION_START)) {
1101 total = runtime_stat_avg(st, STAT_CYCLES_IN_TX, cpu, &rsd);
1102
1103 if (avg)
1104 ratio = total / avg;
1105
1106 print_metric(config, ctxp, NULL, "%8.0f", "cycles / elision", ratio);
1107 } else if (evsel__is_clock(evsel)) {
1108 if ((ratio = avg_stats(&walltime_nsecs_stats)) != 0)
1109 print_metric(config, ctxp, NULL, "%8.3f", "CPUs utilized",
1110 avg / (ratio * evsel->scale));
1111 else
1112 print_metric(config, ctxp, NULL, NULL, "CPUs utilized", 0);
1113 } else if (perf_stat_evsel__is(evsel, TOPDOWN_FETCH_BUBBLES)) {
1114 double fe_bound = td_fe_bound(cpu, st, &rsd);
1115
1116 if (fe_bound > 0.2)
1117 color = PERF_COLOR_RED;
1118 print_metric(config, ctxp, color, "%8.1f%%", "frontend bound",
1119 fe_bound * 100.);
1120 } else if (perf_stat_evsel__is(evsel, TOPDOWN_SLOTS_RETIRED)) {
1121 double retiring = td_retiring(cpu, st, &rsd);
1122
1123 if (retiring > 0.7)
1124 color = PERF_COLOR_GREEN;
1125 print_metric(config, ctxp, color, "%8.1f%%", "retiring",
1126 retiring * 100.);
1127 } else if (perf_stat_evsel__is(evsel, TOPDOWN_RECOVERY_BUBBLES)) {
1128 double bad_spec = td_bad_spec(cpu, st, &rsd);
1129
1130 if (bad_spec > 0.1)
1131 color = PERF_COLOR_RED;
1132 print_metric(config, ctxp, color, "%8.1f%%", "bad speculation",
1133 bad_spec * 100.);
1134 } else if (perf_stat_evsel__is(evsel, TOPDOWN_SLOTS_ISSUED)) {
1135 double be_bound = td_be_bound(cpu, st, &rsd);
1136 const char *name = "backend bound";
1137 static int have_recovery_bubbles = -1;
1138
1139
1140 if (have_recovery_bubbles < 0)
1141 have_recovery_bubbles = pmu_have_event("cpu",
1142 "topdown-recovery-bubbles");
1143 if (!have_recovery_bubbles)
1144 name = "backend bound/bad spec";
1145
1146 if (be_bound > 0.2)
1147 color = PERF_COLOR_RED;
1148 if (td_total_slots(cpu, st, &rsd) > 0)
1149 print_metric(config, ctxp, color, "%8.1f%%", name,
1150 be_bound * 100.);
1151 else
1152 print_metric(config, ctxp, NULL, NULL, name, 0);
1153 } else if (perf_stat_evsel__is(evsel, TOPDOWN_RETIRING) &&
1154 full_td(cpu, st, &rsd)) {
1155 double retiring = td_metric_ratio(cpu,
1156 STAT_TOPDOWN_RETIRING, st,
1157 &rsd);
1158 if (retiring > 0.7)
1159 color = PERF_COLOR_GREEN;
1160 print_metric(config, ctxp, color, "%8.1f%%", "retiring",
1161 retiring * 100.);
1162 } else if (perf_stat_evsel__is(evsel, TOPDOWN_FE_BOUND) &&
1163 full_td(cpu, st, &rsd)) {
1164 double fe_bound = td_metric_ratio(cpu,
1165 STAT_TOPDOWN_FE_BOUND, st,
1166 &rsd);
1167 if (fe_bound > 0.2)
1168 color = PERF_COLOR_RED;
1169 print_metric(config, ctxp, color, "%8.1f%%", "frontend bound",
1170 fe_bound * 100.);
1171 } else if (perf_stat_evsel__is(evsel, TOPDOWN_BE_BOUND) &&
1172 full_td(cpu, st, &rsd)) {
1173 double be_bound = td_metric_ratio(cpu,
1174 STAT_TOPDOWN_BE_BOUND, st,
1175 &rsd);
1176 if (be_bound > 0.2)
1177 color = PERF_COLOR_RED;
1178 print_metric(config, ctxp, color, "%8.1f%%", "backend bound",
1179 be_bound * 100.);
1180 } else if (perf_stat_evsel__is(evsel, TOPDOWN_BAD_SPEC) &&
1181 full_td(cpu, st, &rsd)) {
1182 double bad_spec = td_metric_ratio(cpu,
1183 STAT_TOPDOWN_BAD_SPEC, st,
1184 &rsd);
1185 if (bad_spec > 0.1)
1186 color = PERF_COLOR_RED;
1187 print_metric(config, ctxp, color, "%8.1f%%", "bad speculation",
1188 bad_spec * 100.);
1189 } else if (perf_stat_evsel__is(evsel, TOPDOWN_HEAVY_OPS) &&
1190 full_td(cpu, st, &rsd) && (config->topdown_level > 1)) {
1191 double retiring = td_metric_ratio(cpu,
1192 STAT_TOPDOWN_RETIRING, st,
1193 &rsd);
1194 double heavy_ops = td_metric_ratio(cpu,
1195 STAT_TOPDOWN_HEAVY_OPS, st,
1196 &rsd);
1197 double light_ops = retiring - heavy_ops;
1198
1199 if (retiring > 0.7 && heavy_ops > 0.1)
1200 color = PERF_COLOR_GREEN;
1201 print_metric(config, ctxp, color, "%8.1f%%", "heavy operations",
1202 heavy_ops * 100.);
1203 if (retiring > 0.7 && light_ops > 0.6)
1204 color = PERF_COLOR_GREEN;
1205 else
1206 color = NULL;
1207 print_metric(config, ctxp, color, "%8.1f%%", "light operations",
1208 light_ops * 100.);
1209 } else if (perf_stat_evsel__is(evsel, TOPDOWN_BR_MISPREDICT) &&
1210 full_td(cpu, st, &rsd) && (config->topdown_level > 1)) {
1211 double bad_spec = td_metric_ratio(cpu,
1212 STAT_TOPDOWN_BAD_SPEC, st,
1213 &rsd);
1214 double br_mis = td_metric_ratio(cpu,
1215 STAT_TOPDOWN_BR_MISPREDICT, st,
1216 &rsd);
1217 double m_clears = bad_spec - br_mis;
1218
1219 if (bad_spec > 0.1 && br_mis > 0.05)
1220 color = PERF_COLOR_RED;
1221 print_metric(config, ctxp, color, "%8.1f%%", "branch mispredict",
1222 br_mis * 100.);
1223 if (bad_spec > 0.1 && m_clears > 0.05)
1224 color = PERF_COLOR_RED;
1225 else
1226 color = NULL;
1227 print_metric(config, ctxp, color, "%8.1f%%", "machine clears",
1228 m_clears * 100.);
1229 } else if (perf_stat_evsel__is(evsel, TOPDOWN_FETCH_LAT) &&
1230 full_td(cpu, st, &rsd) && (config->topdown_level > 1)) {
1231 double fe_bound = td_metric_ratio(cpu,
1232 STAT_TOPDOWN_FE_BOUND, st,
1233 &rsd);
1234 double fetch_lat = td_metric_ratio(cpu,
1235 STAT_TOPDOWN_FETCH_LAT, st,
1236 &rsd);
1237 double fetch_bw = fe_bound - fetch_lat;
1238
1239 if (fe_bound > 0.2 && fetch_lat > 0.15)
1240 color = PERF_COLOR_RED;
1241 print_metric(config, ctxp, color, "%8.1f%%", "fetch latency",
1242 fetch_lat * 100.);
1243 if (fe_bound > 0.2 && fetch_bw > 0.1)
1244 color = PERF_COLOR_RED;
1245 else
1246 color = NULL;
1247 print_metric(config, ctxp, color, "%8.1f%%", "fetch bandwidth",
1248 fetch_bw * 100.);
1249 } else if (perf_stat_evsel__is(evsel, TOPDOWN_MEM_BOUND) &&
1250 full_td(cpu, st, &rsd) && (config->topdown_level > 1)) {
1251 double be_bound = td_metric_ratio(cpu,
1252 STAT_TOPDOWN_BE_BOUND, st,
1253 &rsd);
1254 double mem_bound = td_metric_ratio(cpu,
1255 STAT_TOPDOWN_MEM_BOUND, st,
1256 &rsd);
1257 double core_bound = be_bound - mem_bound;
1258
1259 if (be_bound > 0.2 && mem_bound > 0.2)
1260 color = PERF_COLOR_RED;
1261 print_metric(config, ctxp, color, "%8.1f%%", "memory bound",
1262 mem_bound * 100.);
1263 if (be_bound > 0.2 && core_bound > 0.1)
1264 color = PERF_COLOR_RED;
1265 else
1266 color = NULL;
1267 print_metric(config, ctxp, color, "%8.1f%%", "Core bound",
1268 core_bound * 100.);
1269 } else if (evsel->metric_expr) {
1270 generic_metric(config, evsel->metric_expr, evsel->metric_events, NULL,
1271 evsel->name, evsel->metric_name, NULL, 1, cpu, out, st);
1272 } else if (runtime_stat_n(st, STAT_NSECS, cpu, &rsd) != 0) {
1273 char unit = 'M';
1274 char unit_buf[10];
1275
1276 total = runtime_stat_avg(st, STAT_NSECS, cpu, &rsd);
1277
1278 if (total)
1279 ratio = 1000.0 * avg / total;
1280 if (ratio < 0.001) {
1281 ratio *= 1000;
1282 unit = 'K';
1283 }
1284 snprintf(unit_buf, sizeof(unit_buf), "%c/sec", unit);
1285 print_metric(config, ctxp, NULL, "%8.3f", unit_buf, ratio);
1286 } else if (perf_stat_evsel__is(evsel, SMI_NUM)) {
1287 print_smi_cost(config, cpu, out, st, &rsd);
1288 } else {
1289 num = 0;
1290 }
1291
1292 if ((me = metricgroup__lookup(metric_events, evsel, false)) != NULL) {
1293 struct metric_expr *mexp;
1294
1295 list_for_each_entry (mexp, &me->head, nd) {
1296 if (num++ > 0)
1297 out->new_line(config, ctxp);
1298 generic_metric(config, mexp->metric_expr, mexp->metric_events,
1299 mexp->metric_refs, evsel->name, mexp->metric_name,
1300 mexp->metric_unit, mexp->runtime, cpu, out, st);
1301 }
1302 }
1303 if (num == 0)
1304 print_metric(config, ctxp, NULL, NULL, NULL, 0);
1305}
1306