// 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 "../libslang.h" #include "config.h" #include #include #include #include #include #define SCRIPT_NAMELEN 128 #define SCRIPT_MAX_NO 64 /* * Usually the full path for a script is: * /home/username/libexec/perf-core/scripts/python/xxx.py * /home/username/libexec/perf-core/scripts/perl/xxx.pl * So 256 should be long enough to contain the full path. */ #define SCRIPT_FULLPATH_LEN 256 struct script_config { const char **names; char **paths; int index; const char *perf; char extra_format[256]; }; 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"); } 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; } static int scripts_config(const char *var, const char *value, void *data) { struct script_config *c = data; 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; } /* * 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) { char line[BUFSIZ]; FILE *fp; { char filename[FILENAME_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; } 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; } } } fclose(fp); return 0; } /* * 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) { 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 ((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); } closedir(scripts_dir); perf_session__delete(session); return i; } /* * 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 *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 }; script_name[0] = 0; /* Preset the script name to SCRIPT_NAMELEN */ buf = malloc(SCRIPT_MAX_NO * (SCRIPT_NAMELEN + SCRIPT_FULLPATH_LEN)); if (!buf) return -1; 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; } 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; } 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; } 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(); } int script_browse(const char *script_opt, struct evsel *evsel) { char *cmd, script_name[SCRIPT_FULLPATH_LEN]; bool custom = false; memset(script_name, 0, SCRIPT_FULLPATH_LEN); if (list_scripts(script_name, &custom, evsel)) return -1; 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; run_script(cmd); free(cmd); return 0; }