diff options
Diffstat (limited to 'tools/perf/ui/browsers/scripts.c')
| -rw-r--r-- | tools/perf/ui/browsers/scripts.c | 453 |
1 files changed, 316 insertions, 137 deletions
diff --git a/tools/perf/ui/browsers/scripts.c b/tools/perf/ui/browsers/scripts.c index 12f009e61e94..1e8c2c2f952d 100644 --- a/tools/perf/ui/browsers/scripts.c +++ b/tools/perf/ui/browsers/scripts.c @@ -1,33 +1,19 @@ -#include <elf.h> -#include <inttypes.h> -#include <sys/ttydefaults.h> -#include <string.h> -#include "../../util/sort.h" -#include "../../util/util.h" +// SPDX-License-Identifier: GPL-2.0 +#include "../../util/util.h" // perf_exe() +#include "../util.h" +#include "../../util/evlist.h" #include "../../util/hist.h" #include "../../util/debug.h" +#include "../../util/session.h" #include "../../util/symbol.h" #include "../browser.h" -#include "../helpline.h" #include "../libslang.h" - -/* 2048 lines should be enough for a script output */ -#define MAX_LINES 2048 - -/* 160 bytes for one output line */ -#define AVERAGE_LINE_LEN 160 - -struct script_line { - struct list_head node; - char line[AVERAGE_LINE_LEN]; -}; - -struct perf_script_browser { - struct ui_browser b; - struct list_head entries; - const char *script_name; - int nr_lines; -}; +#include "config.h" +#include <linux/err.h> +#include <linux/string.h> +#include <linux/zalloc.h> +#include <subcmd/exec-cmd.h> +#include <stdlib.h> #define SCRIPT_NAMELEN 128 #define SCRIPT_MAX_NO 64 @@ -39,150 +25,343 @@ struct perf_script_browser { */ #define SCRIPT_FULLPATH_LEN 256 -/* - * When success, will copy the full path of the selected script - * into the buffer pointed by script_name, and return 0. - * Return -1 on failure. - */ -static int list_scripts(char *script_name) -{ - char *buf, *names[SCRIPT_MAX_NO], *paths[SCRIPT_MAX_NO]; - int i, num, choice, ret = -1; +struct script_config { + const char **names; + char **paths; + int index; + const char *perf; + char extra_format[256]; +}; - /* Preset the script name to SCRIPT_NAMELEN */ - buf = malloc(SCRIPT_MAX_NO * (SCRIPT_NAMELEN + SCRIPT_FULLPATH_LEN)); - if (!buf) - return ret; +void attr_to_script(char *extra_format, struct perf_event_attr *attr) +{ + extra_format[0] = 0; + if (attr->read_format & PERF_FORMAT_GROUP) + strcat(extra_format, " -F +metric"); + if (attr->sample_type & PERF_SAMPLE_BRANCH_STACK) + strcat(extra_format, " -F +brstackinsn --xed"); + if (attr->sample_type & PERF_SAMPLE_REGS_INTR) + strcat(extra_format, " -F +iregs"); + if (attr->sample_type & PERF_SAMPLE_REGS_USER) + strcat(extra_format, " -F +uregs"); + if (attr->sample_type & PERF_SAMPLE_PHYS_ADDR) + strcat(extra_format, " -F +phys_addr"); +} - for (i = 0; i < SCRIPT_MAX_NO; i++) { - names[i] = buf + i * (SCRIPT_NAMELEN + SCRIPT_FULLPATH_LEN); - paths[i] = names[i] + SCRIPT_NAMELEN; - } +static int add_script_option(const char *name, const char *opt, + struct script_config *c) +{ + c->names[c->index] = name; + if (asprintf(&c->paths[c->index], + "%s script %s -F +metric %s %s", + c->perf, opt, symbol_conf.inline_name ? " --inline" : "", + c->extra_format) < 0) + return -1; + c->index++; + return 0; +} - num = find_scripts(names, paths); - if (num > 0) { - choice = ui__popup_menu(num, names); - if (choice < num && choice >= 0) { - strcpy(script_name, paths[choice]); - ret = 0; - } - } +static int scripts_config(const char *var, const char *value, void *data) +{ + struct script_config *c = data; - free(buf); - return ret; + if (!strstarts(var, "scripts.")) + return -1; + if (c->index >= SCRIPT_MAX_NO) + return -1; + c->names[c->index] = strdup(var + 7); + if (!c->names[c->index]) + return -1; + if (asprintf(&c->paths[c->index], "%s %s", value, + c->extra_format) < 0) + return -1; + c->index++; + return 0; } -static void script_browser__write(struct ui_browser *browser, - void *entry, int row) +/* + * Some scripts specify the required events in their "xxx-record" file, + * this function will check if the events in perf.data match those + * mentioned in the "xxx-record". + * + * Fixme: All existing "xxx-record" are all in good formats "-e event ", + * which is covered well now. And new parsing code should be added to + * cover the future complex formats like event groups etc. + */ +static int check_ev_match(int dir_fd, const char *scriptname, struct perf_session *session) { - struct script_line *sline = list_entry(entry, struct script_line, node); - bool current_entry = ui_browser__is_current_entry(browser, row); + char line[BUFSIZ]; + FILE *fp; + + { + char filename[NAME_MAX + 5]; + int fd; + + scnprintf(filename, sizeof(filename), "bin/%s-record", scriptname); + fd = openat(dir_fd, filename, O_RDONLY); + if (fd == -1) + return -1; + fp = fdopen(fd, "r"); + if (!fp) + return -1; + } - ui_browser__set_color(browser, current_entry ? HE_COLORSET_SELECTED : - HE_COLORSET_NORMAL); + while (fgets(line, sizeof(line), fp)) { + char *p = skip_spaces(line); + + if (*p == '#') + continue; + + while (strlen(p)) { + int match, len; + struct evsel *pos; + char evname[128]; + + p = strstr(p, "-e"); + if (!p) + break; + + p += 2; + p = skip_spaces(p); + len = strcspn(p, " \t"); + if (!len) + break; + + snprintf(evname, len + 1, "%s", p); + + match = 0; + evlist__for_each_entry(session->evlist, pos) { + if (evsel__name_is(pos, evname)) { + match = 1; + break; + } + } + + if (!match) { + fclose(fp); + return -1; + } + } + } - slsmg_write_nstring(sline->line, browser->width); + fclose(fp); + return 0; } -static int script_browser__run(struct perf_script_browser *self) +/* + * Return -1 if none is found, otherwise the actual scripts number. + * + * Currently the only user of this function is the script browser, which + * will list all statically runnable scripts, select one, execute it and + * show the output in a perf browser. + */ +static int find_scripts(char **scripts_array, char **scripts_path_array, int num, + int pathlen) { - int key; - - if (ui_browser__show(&self->b, self->script_name, - "Press <- or ESC to exit") < 0) + struct dirent *script_dirent, *lang_dirent; + int scripts_dir_fd, lang_dir_fd; + DIR *scripts_dir, *lang_dir; + struct perf_session *session; + struct perf_data data = { + .path = input_name, + .mode = PERF_DATA_MODE_READ, + }; + char *temp; + int i = 0; + const char *exec_path = get_argv_exec_path(); + + session = perf_session__new(&data, NULL); + if (IS_ERR(session)) + return PTR_ERR(session); + + { + char scripts_path[PATH_MAX]; + + snprintf(scripts_path, sizeof(scripts_path), "%s/scripts", exec_path); + scripts_dir_fd = open(scripts_path, O_DIRECTORY); + pr_err("Failed to open directory '%s'", scripts_path); + if (scripts_dir_fd == -1) { + perf_session__delete(session); + return -1; + } + } + scripts_dir = fdopendir(scripts_dir_fd); + if (!scripts_dir) { + close(scripts_dir_fd); + perf_session__delete(session); return -1; + } - while (1) { - key = ui_browser__run(&self->b, 0); - - /* We can add some special key handling here if needed */ - break; + while ((lang_dirent = readdir(scripts_dir)) != NULL) { + if (lang_dirent->d_type != DT_DIR && + (lang_dirent->d_type == DT_UNKNOWN && + !is_directory_at(scripts_dir_fd, lang_dirent->d_name))) + continue; + if (!strcmp(lang_dirent->d_name, ".") || !strcmp(lang_dirent->d_name, "..")) + continue; + +#ifndef HAVE_LIBPERL_SUPPORT + if (strstr(lang_dirent->d_name, "perl")) + continue; +#endif +#ifndef HAVE_LIBPYTHON_SUPPORT + if (strstr(lang_dirent->d_name, "python")) + continue; +#endif + + lang_dir_fd = openat(scripts_dir_fd, lang_dirent->d_name, O_DIRECTORY); + if (lang_dir_fd == -1) + continue; + lang_dir = fdopendir(lang_dir_fd); + if (!lang_dir) { + close(lang_dir_fd); + continue; + } + while ((script_dirent = readdir(lang_dir)) != NULL) { + if (script_dirent->d_type == DT_DIR) + continue; + if (script_dirent->d_type == DT_UNKNOWN && + is_directory_at(lang_dir_fd, script_dirent->d_name)) + continue; + /* Skip those real time scripts: xxxtop.p[yl] */ + if (strstr(script_dirent->d_name, "top.")) + continue; + if (i >= num) + break; + scnprintf(scripts_path_array[i], pathlen, "%s/scripts/%s/%s", + exec_path, + lang_dirent->d_name, + script_dirent->d_name); + temp = strchr(script_dirent->d_name, '.'); + snprintf(scripts_array[i], + (temp - script_dirent->d_name) + 1, + "%s", script_dirent->d_name); + + if (check_ev_match(lang_dir_fd, scripts_array[i], session)) + continue; + + i++; + } + closedir(lang_dir); } - ui_browser__hide(&self->b); - return key; + closedir(scripts_dir); + perf_session__delete(session); + return i; } - -int script_browse(const char *script_opt) +/* + * When success, will copy the full path of the selected script + * into the buffer pointed by script_name, and return 0. + * Return -1 on failure. + */ +static int list_scripts(char *script_name, bool *custom, + struct evsel *evsel) { - char cmd[SCRIPT_FULLPATH_LEN*2], script_name[SCRIPT_FULLPATH_LEN]; - char *line = NULL; - size_t len = 0; - ssize_t retlen; - int ret = -1, nr_entries = 0; - FILE *fp; - void *buf; - struct script_line *sline; - - struct perf_script_browser script = { - .b = { - .refresh = ui_browser__list_head_refresh, - .seek = ui_browser__list_head_seek, - .write = script_browser__write, - }, - .script_name = script_name, + char *buf, *paths[SCRIPT_MAX_NO], *names[SCRIPT_MAX_NO]; + int i, num, choice; + int ret = 0; + int max_std, custom_perf; + char pbuf[256]; + const char *perf = perf_exe(pbuf, sizeof pbuf); + struct script_config scriptc = { + .names = (const char **)names, + .paths = paths, + .perf = perf }; - INIT_LIST_HEAD(&script.entries); + script_name[0] = 0; - /* Save each line of the output in one struct script_line object. */ - buf = zalloc((sizeof(*sline)) * MAX_LINES); + /* Preset the script name to SCRIPT_NAMELEN */ + buf = malloc(SCRIPT_MAX_NO * (SCRIPT_NAMELEN + SCRIPT_FULLPATH_LEN)); if (!buf) return -1; - sline = buf; - - memset(script_name, 0, SCRIPT_FULLPATH_LEN); - if (list_scripts(script_name)) - goto exit; - - sprintf(cmd, "perf script -s %s ", script_name); - if (script_opt) - strcat(cmd, script_opt); - - if (input_name) { - strcat(cmd, " -i "); - strcat(cmd, input_name); + if (evsel) + attr_to_script(scriptc.extra_format, &evsel->core.attr); + add_script_option("Show individual samples", "", &scriptc); + add_script_option("Show individual samples with assembler", "-F +disasm", + &scriptc); + add_script_option("Show individual samples with source", "-F +srcline,+srccode", + &scriptc); + perf_config(scripts_config, &scriptc); + custom_perf = scriptc.index; + add_script_option("Show samples with custom perf script arguments", "", &scriptc); + i = scriptc.index; + max_std = i; + + for (; i < SCRIPT_MAX_NO; i++) { + names[i] = buf + (i - max_std) * (SCRIPT_NAMELEN + SCRIPT_FULLPATH_LEN); + paths[i] = names[i] + SCRIPT_NAMELEN; } - strcat(cmd, " 2>&1"); - - fp = popen(cmd, "r"); - if (!fp) - goto exit; - - while ((retlen = getline(&line, &len, fp)) != -1) { - strncpy(sline->line, line, AVERAGE_LINE_LEN); - - /* If one output line is very large, just cut it short */ - if (retlen >= AVERAGE_LINE_LEN) { - sline->line[AVERAGE_LINE_LEN - 1] = '\0'; - sline->line[AVERAGE_LINE_LEN - 2] = '\n'; + num = find_scripts(names + max_std, paths + max_std, SCRIPT_MAX_NO - max_std, + SCRIPT_FULLPATH_LEN); + if (num < 0) + num = 0; + choice = ui__popup_menu(num + max_std, (char * const *)names, NULL); + if (choice < 0) { + ret = -1; + goto out; + } + if (choice == custom_perf) { + char script_args[50]; + int key = ui_browser__input_window("perf script command", + "Enter perf script command line (without perf script prefix)", + script_args, "", 0); + if (key != K_ENTER) { + ret = -1; + goto out; } - list_add_tail(&sline->node, &script.entries); + sprintf(script_name, "%s script %s", perf, script_args); + } else if (choice < num + max_std) { + strcpy(script_name, paths[choice]); + } + *custom = choice >= max_std; + +out: + free(buf); + for (i = 0; i < max_std; i++) + zfree(&paths[i]); + return ret; +} - if (script.b.width < retlen) - script.b.width = retlen; +void run_script(char *cmd) +{ + pr_debug("Running %s\n", cmd); + SLang_reset_tty(); + if (system(cmd) < 0) + pr_warning("Cannot run %s\n", cmd); + /* + * SLang doesn't seem to reset the whole terminal, so be more + * forceful to get back to the original state. + */ + printf("\033[c\033[H\033[J"); + fflush(stdout); + SLang_init_tty(0, 0, 0); + SLtty_set_suspend_state(true); + SLsmg_refresh(); +} - if (nr_entries++ >= MAX_LINES - 1) - break; - sline++; - } +int script_browse(const char *script_opt, struct evsel *evsel) +{ + char *cmd, script_name[SCRIPT_FULLPATH_LEN]; + bool custom = false; - if (script.b.width > AVERAGE_LINE_LEN) - script.b.width = AVERAGE_LINE_LEN; + memset(script_name, 0, SCRIPT_FULLPATH_LEN); + if (list_scripts(script_name, &custom, evsel)) + return -1; - if (line) - free(line); - pclose(fp); + if (asprintf(&cmd, "%s%s %s %s%s 2>&1 | less", + custom ? "perf script -s " : "", + script_name, + script_opt ? script_opt : "", + input_name ? "-i " : "", + input_name ? input_name : "") < 0) + return -1; - script.nr_lines = nr_entries; - script.b.nr_entries = nr_entries; - script.b.entries = &script.entries; + run_script(cmd); + free(cmd); - ret = script_browser__run(&script); -exit: - free(buf); - return ret; + return 0; } |
