1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * builtin-list.c 4 * 5 * Builtin list command: list all event types 6 * 7 * Copyright (C) 2009, Thomas Gleixner <tglx@linutronix.de> 8 * Copyright (C) 2008-2009, Red Hat Inc, Ingo Molnar <mingo@redhat.com> 9 * Copyright (C) 2011, Red Hat Inc, Arnaldo Carvalho de Melo <acme@redhat.com> 10 */ 11 #include "builtin.h" 12 13 #include "util/print-events.h" 14 #include "util/pmus.h" 15 #include "util/pmu.h" 16 #include "util/debug.h" 17 #include "util/metricgroup.h" 18 #include "util/pfm.h" 19 #include "util/string2.h" 20 #include "util/strlist.h" 21 #include "util/strbuf.h" 22 #include <subcmd/pager.h> 23 #include <subcmd/parse-options.h> 24 #include <linux/zalloc.h> 25 #include <ctype.h> 26 #include <stdarg.h> 27 #include <stdio.h> 28 29 /** 30 * struct print_state - State and configuration passed to the default_print 31 * functions. 32 */ 33 struct print_state { 34 /** @fp: File to write output to. */ 35 FILE *fp; 36 /** 37 * @pmu_glob: Optionally restrict PMU and metric matching to PMU or 38 * debugfs subsystem name. 39 */ 40 char *pmu_glob; 41 /** @event_glob: Optional pattern matching glob. */ 42 char *event_glob; 43 /** @name_only: Print event or metric names only. */ 44 bool name_only; 45 /** @desc: Print the event or metric description. */ 46 bool desc; 47 /** @long_desc: Print longer event or metric description. */ 48 bool long_desc; 49 /** @deprecated: Print deprecated events or metrics. */ 50 bool deprecated; 51 /** 52 * @detailed: Print extra information on the perf event such as names 53 * and expressions used internally by events. 54 */ 55 bool detailed; 56 /** @metrics: Controls printing of metric and metric groups. */ 57 bool metrics; 58 /** @metricgroups: Controls printing of metric and metric groups. */ 59 bool metricgroups; 60 /** @last_topic: The last printed event topic. */ 61 char *last_topic; 62 /** @last_metricgroups: The last printed metric group. */ 63 char *last_metricgroups; 64 /** @visited_metrics: Metrics that are printed to avoid duplicates. */ 65 struct strlist *visited_metrics; 66 }; 67 68 static void default_print_start(void *ps) 69 { 70 struct print_state *print_state = ps; 71 72 if (!print_state->name_only && pager_in_use()) { 73 fprintf(print_state->fp, 74 "\nList of pre-defined events (to be used in -e or -M):\n\n"); 75 } 76 } 77 78 static void default_print_end(void *print_state __maybe_unused) {} 79 80 static const char *skip_spaces_or_commas(const char *str) 81 { 82 while (isspace(*str) || *str == ',') 83 ++str; 84 return str; 85 } 86 87 static void wordwrap(FILE *fp, const char *s, int start, int max, int corr) 88 { 89 int column = start; 90 int n; 91 bool saw_newline = false; 92 bool comma = false; 93 94 while (*s) { 95 int wlen = strcspn(s, " ,\t\n"); 96 const char *sep = comma ? "," : " "; 97 98 if ((column + wlen >= max && column > start) || saw_newline) { 99 fprintf(fp, comma ? ",\n%*s" : "\n%*s", start, ""); 100 column = start + corr; 101 } 102 if (column <= start) 103 sep = ""; 104 n = fprintf(fp, "%s%.*s", sep, wlen, s); 105 if (n <= 0) 106 break; 107 saw_newline = s[wlen] == '\n'; 108 s += wlen; 109 comma = s[0] == ','; 110 column += n; 111 s = skip_spaces_or_commas(s); 112 } 113 } 114 115 static void default_print_event(void *ps, const char *pmu_name, const char *topic, 116 const char *event_name, const char *event_alias, 117 const char *scale_unit __maybe_unused, 118 bool deprecated, const char *event_type_desc, 119 const char *desc, const char *long_desc, 120 const char *encoding_desc) 121 { 122 struct print_state *print_state = ps; 123 int pos; 124 FILE *fp = print_state->fp; 125 126 if (deprecated && !print_state->deprecated) 127 return; 128 129 if (print_state->pmu_glob && pmu_name && !strglobmatch(pmu_name, print_state->pmu_glob)) 130 return; 131 132 if (print_state->event_glob && 133 (!event_name || !strglobmatch(event_name, print_state->event_glob)) && 134 (!event_alias || !strglobmatch(event_alias, print_state->event_glob)) && 135 (!topic || !strglobmatch_nocase(topic, print_state->event_glob))) 136 return; 137 138 if (print_state->name_only) { 139 if (event_alias && strlen(event_alias)) 140 fprintf(fp, "%s ", event_alias); 141 else 142 fprintf(fp, "%s ", event_name); 143 return; 144 } 145 146 if (strcmp(print_state->last_topic, topic ?: "")) { 147 if (topic) 148 fprintf(fp, "\n%s:\n", topic); 149 zfree(&print_state->last_topic); 150 print_state->last_topic = strdup(topic ?: ""); 151 } 152 153 if (event_alias && strlen(event_alias)) 154 pos = fprintf(fp, " %s OR %s", event_name, event_alias); 155 else 156 pos = fprintf(fp, " %s", event_name); 157 158 if (!topic && event_type_desc) { 159 for (; pos < 53; pos++) 160 fputc(' ', fp); 161 fprintf(fp, "[%s]\n", event_type_desc); 162 } else 163 fputc('\n', fp); 164 165 if (desc && print_state->desc) { 166 char *desc_with_unit = NULL; 167 int desc_len = -1; 168 169 if (pmu_name && strcmp(pmu_name, "default_core")) { 170 desc_len = strlen(desc); 171 desc_len = asprintf(&desc_with_unit, 172 desc[desc_len - 1] != '.' 173 ? "%s. Unit: %s" : "%s Unit: %s", 174 desc, pmu_name); 175 } 176 fprintf(fp, "%*s", 8, "["); 177 wordwrap(fp, desc_len > 0 ? desc_with_unit : desc, 8, pager_get_columns(), 0); 178 fprintf(fp, "]\n"); 179 free(desc_with_unit); 180 } 181 long_desc = long_desc ?: desc; 182 if (long_desc && print_state->long_desc) { 183 fprintf(fp, "%*s", 8, "["); 184 wordwrap(fp, long_desc, 8, pager_get_columns(), 0); 185 fprintf(fp, "]\n"); 186 } 187 188 if (print_state->detailed && encoding_desc) { 189 fprintf(fp, "%*s", 8, ""); 190 wordwrap(fp, encoding_desc, 8, pager_get_columns(), 0); 191 fputc('\n', fp); 192 } 193 } 194 195 static void default_print_metric(void *ps, 196 const char *group, 197 const char *name, 198 const char *desc, 199 const char *long_desc, 200 const char *expr, 201 const char *threshold, 202 const char *unit __maybe_unused) 203 { 204 struct print_state *print_state = ps; 205 FILE *fp = print_state->fp; 206 207 if (print_state->event_glob && 208 (!print_state->metrics || !name || !strglobmatch(name, print_state->event_glob)) && 209 (!print_state->metricgroups || !group || !strglobmatch(group, print_state->event_glob))) 210 return; 211 212 if (!print_state->name_only && !print_state->last_metricgroups) { 213 if (print_state->metricgroups) { 214 fprintf(fp, "\nMetric Groups:\n"); 215 if (!print_state->metrics) 216 fputc('\n', fp); 217 } else { 218 fprintf(fp, "\nMetrics:\n\n"); 219 } 220 } 221 if (!print_state->last_metricgroups || 222 strcmp(print_state->last_metricgroups, group ?: "")) { 223 if (group && print_state->metricgroups) { 224 if (print_state->name_only) { 225 fprintf(fp, "%s ", group); 226 } else { 227 const char *gdesc = print_state->desc 228 ? describe_metricgroup(group) 229 : NULL; 230 const char *print_colon = ""; 231 232 if (print_state->metrics) { 233 print_colon = ":"; 234 fputc('\n', fp); 235 } 236 237 if (gdesc) 238 fprintf(fp, "%s%s [%s]\n", group, print_colon, gdesc); 239 else 240 fprintf(fp, "%s%s\n", group, print_colon); 241 } 242 } 243 zfree(&print_state->last_metricgroups); 244 print_state->last_metricgroups = strdup(group ?: ""); 245 } 246 if (!print_state->metrics) 247 return; 248 249 if (print_state->name_only) { 250 if (print_state->metrics && 251 !strlist__has_entry(print_state->visited_metrics, name)) { 252 fprintf(fp, "%s ", name); 253 strlist__add(print_state->visited_metrics, name); 254 } 255 return; 256 } 257 fprintf(fp, " %s\n", name); 258 259 if (desc && print_state->desc) { 260 fprintf(fp, "%*s", 8, "["); 261 wordwrap(fp, desc, 8, pager_get_columns(), 0); 262 fprintf(fp, "]\n"); 263 } 264 if (long_desc && print_state->long_desc) { 265 fprintf(fp, "%*s", 8, "["); 266 wordwrap(fp, long_desc, 8, pager_get_columns(), 0); 267 fprintf(fp, "]\n"); 268 } 269 if (expr && print_state->detailed) { 270 fprintf(fp, "%*s", 8, "["); 271 wordwrap(fp, expr, 8, pager_get_columns(), 0); 272 fprintf(fp, "]\n"); 273 } 274 if (threshold && print_state->detailed) { 275 fprintf(fp, "%*s", 8, "["); 276 wordwrap(fp, threshold, 8, pager_get_columns(), 0); 277 fprintf(fp, "]\n"); 278 } 279 } 280 281 struct json_print_state { 282 /** @fp: File to write output to. */ 283 FILE *fp; 284 /** Should a separator be printed prior to the next item? */ 285 bool need_sep; 286 }; 287 288 static void json_print_start(void *ps) 289 { 290 struct json_print_state *print_state = ps; 291 FILE *fp = print_state->fp; 292 293 fprintf(fp, "[\n"); 294 } 295 296 static void json_print_end(void *ps) 297 { 298 struct json_print_state *print_state = ps; 299 FILE *fp = print_state->fp; 300 301 fprintf(fp, "%s]\n", print_state->need_sep ? "\n" : ""); 302 } 303 304 static void fix_escape_fprintf(FILE *fp, struct strbuf *buf, const char *fmt, ...) 305 { 306 va_list args; 307 308 va_start(args, fmt); 309 strbuf_setlen(buf, 0); 310 for (size_t fmt_pos = 0; fmt_pos < strlen(fmt); fmt_pos++) { 311 switch (fmt[fmt_pos]) { 312 case '%': 313 fmt_pos++; 314 switch (fmt[fmt_pos]) { 315 case 's': { 316 const char *s = va_arg(args, const char*); 317 318 strbuf_addstr(buf, s); 319 break; 320 } 321 case 'S': { 322 const char *s = va_arg(args, const char*); 323 324 for (size_t s_pos = 0; s_pos < strlen(s); s_pos++) { 325 switch (s[s_pos]) { 326 case '\n': 327 strbuf_addstr(buf, "\\n"); 328 break; 329 case '\r': 330 strbuf_addstr(buf, "\\r"); 331 break; 332 case '\\': 333 fallthrough; 334 case '\"': 335 strbuf_addch(buf, '\\'); 336 fallthrough; 337 default: 338 strbuf_addch(buf, s[s_pos]); 339 break; 340 } 341 } 342 break; 343 } 344 default: 345 pr_err("Unexpected format character '%c'\n", fmt[fmt_pos]); 346 strbuf_addch(buf, '%'); 347 strbuf_addch(buf, fmt[fmt_pos]); 348 } 349 break; 350 default: 351 strbuf_addch(buf, fmt[fmt_pos]); 352 break; 353 } 354 } 355 va_end(args); 356 fputs(buf->buf, fp); 357 } 358 359 static void json_print_event(void *ps, const char *pmu_name, const char *topic, 360 const char *event_name, const char *event_alias, 361 const char *scale_unit, 362 bool deprecated, const char *event_type_desc, 363 const char *desc, const char *long_desc, 364 const char *encoding_desc) 365 { 366 struct json_print_state *print_state = ps; 367 bool need_sep = false; 368 FILE *fp = print_state->fp; 369 struct strbuf buf; 370 371 strbuf_init(&buf, 0); 372 fprintf(fp, "%s{\n", print_state->need_sep ? ",\n" : ""); 373 print_state->need_sep = true; 374 if (pmu_name) { 375 fix_escape_fprintf(fp, &buf, "\t\"Unit\": \"%S\"", pmu_name); 376 need_sep = true; 377 } 378 if (topic) { 379 fix_escape_fprintf(fp, &buf, "%s\t\"Topic\": \"%S\"", 380 need_sep ? ",\n" : "", 381 topic); 382 need_sep = true; 383 } 384 if (event_name) { 385 fix_escape_fprintf(fp, &buf, "%s\t\"EventName\": \"%S\"", 386 need_sep ? ",\n" : "", 387 event_name); 388 need_sep = true; 389 } 390 if (event_alias && strlen(event_alias)) { 391 fix_escape_fprintf(fp, &buf, "%s\t\"EventAlias\": \"%S\"", 392 need_sep ? ",\n" : "", 393 event_alias); 394 need_sep = true; 395 } 396 if (scale_unit && strlen(scale_unit)) { 397 fix_escape_fprintf(fp, &buf, "%s\t\"ScaleUnit\": \"%S\"", 398 need_sep ? ",\n" : "", 399 scale_unit); 400 need_sep = true; 401 } 402 if (event_type_desc) { 403 fix_escape_fprintf(fp, &buf, "%s\t\"EventType\": \"%S\"", 404 need_sep ? ",\n" : "", 405 event_type_desc); 406 need_sep = true; 407 } 408 if (deprecated) { 409 fix_escape_fprintf(fp, &buf, "%s\t\"Deprecated\": \"%S\"", 410 need_sep ? ",\n" : "", 411 deprecated ? "1" : "0"); 412 need_sep = true; 413 } 414 if (desc) { 415 fix_escape_fprintf(fp, &buf, "%s\t\"BriefDescription\": \"%S\"", 416 need_sep ? ",\n" : "", 417 desc); 418 need_sep = true; 419 } 420 if (long_desc) { 421 fix_escape_fprintf(fp, &buf, "%s\t\"PublicDescription\": \"%S\"", 422 need_sep ? ",\n" : "", 423 long_desc); 424 need_sep = true; 425 } 426 if (encoding_desc) { 427 fix_escape_fprintf(fp, &buf, "%s\t\"Encoding\": \"%S\"", 428 need_sep ? ",\n" : "", 429 encoding_desc); 430 need_sep = true; 431 } 432 fprintf(fp, "%s}", need_sep ? "\n" : ""); 433 strbuf_release(&buf); 434 } 435 436 static void json_print_metric(void *ps __maybe_unused, const char *group, 437 const char *name, const char *desc, 438 const char *long_desc, const char *expr, 439 const char *threshold, const char *unit) 440 { 441 struct json_print_state *print_state = ps; 442 bool need_sep = false; 443 FILE *fp = print_state->fp; 444 struct strbuf buf; 445 446 strbuf_init(&buf, 0); 447 fprintf(fp, "%s{\n", print_state->need_sep ? ",\n" : ""); 448 print_state->need_sep = true; 449 if (group) { 450 fix_escape_fprintf(fp, &buf, "\t\"MetricGroup\": \"%S\"", group); 451 need_sep = true; 452 } 453 if (name) { 454 fix_escape_fprintf(fp, &buf, "%s\t\"MetricName\": \"%S\"", 455 need_sep ? ",\n" : "", 456 name); 457 need_sep = true; 458 } 459 if (expr) { 460 fix_escape_fprintf(fp, &buf, "%s\t\"MetricExpr\": \"%S\"", 461 need_sep ? ",\n" : "", 462 expr); 463 need_sep = true; 464 } 465 if (threshold) { 466 fix_escape_fprintf(fp, &buf, "%s\t\"MetricThreshold\": \"%S\"", 467 need_sep ? ",\n" : "", 468 threshold); 469 need_sep = true; 470 } 471 if (unit) { 472 fix_escape_fprintf(fp, &buf, "%s\t\"ScaleUnit\": \"%S\"", 473 need_sep ? ",\n" : "", 474 unit); 475 need_sep = true; 476 } 477 if (desc) { 478 fix_escape_fprintf(fp, &buf, "%s\t\"BriefDescription\": \"%S\"", 479 need_sep ? ",\n" : "", 480 desc); 481 need_sep = true; 482 } 483 if (long_desc) { 484 fix_escape_fprintf(fp, &buf, "%s\t\"PublicDescription\": \"%S\"", 485 need_sep ? ",\n" : "", 486 long_desc); 487 need_sep = true; 488 } 489 fprintf(fp, "%s}", need_sep ? "\n" : ""); 490 strbuf_release(&buf); 491 } 492 493 static bool json_skip_duplicate_pmus(void *ps __maybe_unused) 494 { 495 return false; 496 } 497 498 static bool default_skip_duplicate_pmus(void *ps) 499 { 500 struct print_state *print_state = ps; 501 502 return !print_state->long_desc; 503 } 504 505 int cmd_list(int argc, const char **argv) 506 { 507 int i, ret = 0; 508 struct print_state default_ps = { 509 .fp = stdout, 510 }; 511 struct print_state json_ps = { 512 .fp = stdout, 513 }; 514 void *ps = &default_ps; 515 struct print_callbacks print_cb = { 516 .print_start = default_print_start, 517 .print_end = default_print_end, 518 .print_event = default_print_event, 519 .print_metric = default_print_metric, 520 .skip_duplicate_pmus = default_skip_duplicate_pmus, 521 }; 522 const char *cputype = NULL; 523 const char *unit_name = NULL; 524 const char *output_path = NULL; 525 bool json = false; 526 struct option list_options[] = { 527 OPT_BOOLEAN(0, "raw-dump", &default_ps.name_only, "Dump raw events"), 528 OPT_BOOLEAN('j', "json", &json, "JSON encode events and metrics"), 529 OPT_BOOLEAN('d', "desc", &default_ps.desc, 530 "Print extra event descriptions. --no-desc to not print."), 531 OPT_BOOLEAN('v', "long-desc", &default_ps.long_desc, 532 "Print longer event descriptions."), 533 OPT_BOOLEAN(0, "details", &default_ps.detailed, 534 "Print information on the perf event names and expressions used internally by events."), 535 OPT_STRING('o', "output", &output_path, "file", "output file name"), 536 OPT_BOOLEAN(0, "deprecated", &default_ps.deprecated, 537 "Print deprecated events."), 538 OPT_STRING(0, "cputype", &cputype, "cpu type", 539 "Limit PMU or metric printing to the given PMU (e.g. cpu, core or atom)."), 540 OPT_STRING(0, "unit", &unit_name, "PMU name", 541 "Limit PMU or metric printing to the specified PMU."), 542 OPT_INCR(0, "debug", &verbose, 543 "Enable debugging output"), 544 OPT_END() 545 }; 546 const char * const list_usage[] = { 547 #ifdef HAVE_LIBPFM 548 "perf list [<options>] [hw|sw|cache|tracepoint|pmu|sdt|metric|metricgroup|event_glob|pfm]", 549 #else 550 "perf list [<options>] [hw|sw|cache|tracepoint|pmu|sdt|metric|metricgroup|event_glob]", 551 #endif 552 NULL 553 }; 554 555 set_option_flag(list_options, 0, "raw-dump", PARSE_OPT_HIDDEN); 556 /* Hide hybrid flag for the more generic 'unit' flag. */ 557 set_option_flag(list_options, 0, "cputype", PARSE_OPT_HIDDEN); 558 559 argc = parse_options(argc, argv, list_options, list_usage, 560 PARSE_OPT_STOP_AT_NON_OPTION); 561 562 if (output_path) { 563 default_ps.fp = fopen(output_path, "w"); 564 json_ps.fp = default_ps.fp; 565 } 566 567 setup_pager(); 568 569 if (!default_ps.name_only) 570 setup_pager(); 571 572 if (json) { 573 print_cb = (struct print_callbacks){ 574 .print_start = json_print_start, 575 .print_end = json_print_end, 576 .print_event = json_print_event, 577 .print_metric = json_print_metric, 578 .skip_duplicate_pmus = json_skip_duplicate_pmus, 579 }; 580 ps = &json_ps; 581 } else { 582 default_ps.desc = !default_ps.long_desc; 583 default_ps.last_topic = strdup(""); 584 assert(default_ps.last_topic); 585 default_ps.visited_metrics = strlist__new(NULL, NULL); 586 assert(default_ps.visited_metrics); 587 if (unit_name) 588 default_ps.pmu_glob = strdup(unit_name); 589 else if (cputype) { 590 const struct perf_pmu *pmu = perf_pmus__pmu_for_pmu_filter(cputype); 591 592 if (!pmu) { 593 pr_err("ERROR: cputype is not supported!\n"); 594 ret = -1; 595 goto out; 596 } 597 default_ps.pmu_glob = strdup(pmu->name); 598 } 599 } 600 print_cb.print_start(ps); 601 602 if (argc == 0) { 603 default_ps.metrics = true; 604 default_ps.metricgroups = true; 605 print_events(&print_cb, ps); 606 goto out; 607 } 608 609 for (i = 0; i < argc; ++i) { 610 char *sep, *s; 611 612 if (strcmp(argv[i], "tracepoint") == 0) 613 print_tracepoint_events(&print_cb, ps); 614 else if (strcmp(argv[i], "hw") == 0 || 615 strcmp(argv[i], "hardware") == 0) 616 print_symbol_events(&print_cb, ps, PERF_TYPE_HARDWARE, 617 event_symbols_hw, PERF_COUNT_HW_MAX); 618 else if (strcmp(argv[i], "sw") == 0 || 619 strcmp(argv[i], "software") == 0) { 620 print_symbol_events(&print_cb, ps, PERF_TYPE_SOFTWARE, 621 event_symbols_sw, PERF_COUNT_SW_MAX); 622 print_tool_events(&print_cb, ps); 623 } else if (strcmp(argv[i], "cache") == 0 || 624 strcmp(argv[i], "hwcache") == 0) 625 print_hwcache_events(&print_cb, ps); 626 else if (strcmp(argv[i], "pmu") == 0) 627 perf_pmus__print_pmu_events(&print_cb, ps); 628 else if (strcmp(argv[i], "sdt") == 0) 629 print_sdt_events(&print_cb, ps); 630 else if (strcmp(argv[i], "metric") == 0 || strcmp(argv[i], "metrics") == 0) { 631 default_ps.metricgroups = false; 632 default_ps.metrics = true; 633 metricgroup__print(&print_cb, ps); 634 } else if (strcmp(argv[i], "metricgroup") == 0 || 635 strcmp(argv[i], "metricgroups") == 0) { 636 default_ps.metricgroups = true; 637 default_ps.metrics = false; 638 metricgroup__print(&print_cb, ps); 639 } 640 #ifdef HAVE_LIBPFM 641 else if (strcmp(argv[i], "pfm") == 0) 642 print_libpfm_events(&print_cb, ps); 643 #endif 644 else if ((sep = strchr(argv[i], ':')) != NULL) { 645 char *old_pmu_glob = default_ps.pmu_glob; 646 647 default_ps.event_glob = strdup(argv[i]); 648 if (!default_ps.event_glob) { 649 ret = -1; 650 goto out; 651 } 652 653 print_tracepoint_events(&print_cb, ps); 654 print_sdt_events(&print_cb, ps); 655 default_ps.metrics = true; 656 default_ps.metricgroups = true; 657 metricgroup__print(&print_cb, ps); 658 zfree(&default_ps.event_glob); 659 default_ps.pmu_glob = old_pmu_glob; 660 } else { 661 if (asprintf(&s, "*%s*", argv[i]) < 0) { 662 printf("Critical: Not enough memory! Trying to continue...\n"); 663 continue; 664 } 665 default_ps.event_glob = s; 666 print_symbol_events(&print_cb, ps, PERF_TYPE_HARDWARE, 667 event_symbols_hw, PERF_COUNT_HW_MAX); 668 print_symbol_events(&print_cb, ps, PERF_TYPE_SOFTWARE, 669 event_symbols_sw, PERF_COUNT_SW_MAX); 670 print_tool_events(&print_cb, ps); 671 print_hwcache_events(&print_cb, ps); 672 perf_pmus__print_pmu_events(&print_cb, ps); 673 print_tracepoint_events(&print_cb, ps); 674 print_sdt_events(&print_cb, ps); 675 default_ps.metrics = true; 676 default_ps.metricgroups = true; 677 metricgroup__print(&print_cb, ps); 678 free(s); 679 } 680 } 681 682 out: 683 print_cb.print_end(ps); 684 free(default_ps.pmu_glob); 685 free(default_ps.last_topic); 686 free(default_ps.last_metricgroups); 687 strlist__delete(default_ps.visited_metrics); 688 if (output_path) 689 fclose(default_ps.fp); 690 691 return ret; 692 } 693