| 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | #include "../../util/util.h" // perf_exe() |
| 3 | #include "../util.h" |
| 4 | #include "../../util/evlist.h" |
| 5 | #include "../../util/hist.h" |
| 6 | #include "../../util/debug.h" |
| 7 | #include "../../util/session.h" |
| 8 | #include "../../util/symbol.h" |
| 9 | #include "../browser.h" |
| 10 | #include "../libslang.h" |
| 11 | #include "config.h" |
| 12 | #include <linux/err.h> |
| 13 | #include <linux/string.h> |
| 14 | #include <linux/zalloc.h> |
| 15 | #include <subcmd/exec-cmd.h> |
| 16 | #include <stdlib.h> |
| 17 | |
| 18 | #define SCRIPT_NAMELEN 128 |
| 19 | #define SCRIPT_MAX_NO 64 |
| 20 | /* |
| 21 | * Usually the full path for a script is: |
| 22 | * /home/username/libexec/perf-core/scripts/python/xxx.py |
| 23 | * /home/username/libexec/perf-core/scripts/perl/xxx.pl |
| 24 | * So 256 should be long enough to contain the full path. |
| 25 | */ |
| 26 | #define SCRIPT_FULLPATH_LEN 256 |
| 27 | |
| 28 | struct script_config { |
| 29 | const char **names; |
| 30 | char **paths; |
| 31 | int index; |
| 32 | const char *perf; |
| 33 | char [256]; |
| 34 | }; |
| 35 | |
| 36 | void attr_to_script(char *, struct perf_event_attr *attr) |
| 37 | { |
| 38 | extra_format[0] = 0; |
| 39 | if (attr->read_format & PERF_FORMAT_GROUP) |
| 40 | strcat(dest: extra_format, src: " -F +metric" ); |
| 41 | if (attr->sample_type & PERF_SAMPLE_BRANCH_STACK) |
| 42 | strcat(dest: extra_format, src: " -F +brstackinsn --xed" ); |
| 43 | if (attr->sample_type & PERF_SAMPLE_REGS_INTR) |
| 44 | strcat(dest: extra_format, src: " -F +iregs" ); |
| 45 | if (attr->sample_type & PERF_SAMPLE_REGS_USER) |
| 46 | strcat(dest: extra_format, src: " -F +uregs" ); |
| 47 | if (attr->sample_type & PERF_SAMPLE_PHYS_ADDR) |
| 48 | strcat(dest: extra_format, src: " -F +phys_addr" ); |
| 49 | } |
| 50 | |
| 51 | static int add_script_option(const char *name, const char *opt, |
| 52 | struct script_config *c) |
| 53 | { |
| 54 | c->names[c->index] = name; |
| 55 | if (asprintf(ptr: &c->paths[c->index], |
| 56 | fmt: "%s script %s -F +metric %s %s" , |
| 57 | c->perf, opt, symbol_conf.inline_name ? " --inline" : "" , |
| 58 | c->extra_format) < 0) |
| 59 | return -1; |
| 60 | c->index++; |
| 61 | return 0; |
| 62 | } |
| 63 | |
| 64 | static int scripts_config(const char *var, const char *value, void *data) |
| 65 | { |
| 66 | struct script_config *c = data; |
| 67 | |
| 68 | if (!strstarts(var, "scripts." )) |
| 69 | return -1; |
| 70 | if (c->index >= SCRIPT_MAX_NO) |
| 71 | return -1; |
| 72 | c->names[c->index] = strdup(s: var + 7); |
| 73 | if (!c->names[c->index]) |
| 74 | return -1; |
| 75 | if (asprintf(ptr: &c->paths[c->index], fmt: "%s %s" , value, |
| 76 | c->extra_format) < 0) |
| 77 | return -1; |
| 78 | c->index++; |
| 79 | return 0; |
| 80 | } |
| 81 | |
| 82 | /* |
| 83 | * Some scripts specify the required events in their "xxx-record" file, |
| 84 | * this function will check if the events in perf.data match those |
| 85 | * mentioned in the "xxx-record". |
| 86 | * |
| 87 | * Fixme: All existing "xxx-record" are all in good formats "-e event ", |
| 88 | * which is covered well now. And new parsing code should be added to |
| 89 | * cover the future complex formats like event groups etc. |
| 90 | */ |
| 91 | static int check_ev_match(int dir_fd, const char *scriptname, struct perf_session *session) |
| 92 | { |
| 93 | char line[BUFSIZ]; |
| 94 | FILE *fp; |
| 95 | |
| 96 | { |
| 97 | char filename[NAME_MAX + 5]; |
| 98 | int fd; |
| 99 | |
| 100 | scnprintf(filename, sizeof(filename), "bin/%s-record" , scriptname); |
| 101 | fd = openat(fd: dir_fd, file: filename, O_RDONLY); |
| 102 | if (fd == -1) |
| 103 | return -1; |
| 104 | fp = fdopen(fd: fd, modes: "r" ); |
| 105 | if (!fp) |
| 106 | return -1; |
| 107 | } |
| 108 | |
| 109 | while (fgets(s: line, n: sizeof(line), stream: fp)) { |
| 110 | char *p = skip_spaces(line); |
| 111 | |
| 112 | if (*p == '#') |
| 113 | continue; |
| 114 | |
| 115 | while (strlen(s: p)) { |
| 116 | int match, len; |
| 117 | struct evsel *pos; |
| 118 | char evname[128]; |
| 119 | |
| 120 | p = strstr(haystack: p, needle: "-e" ); |
| 121 | if (!p) |
| 122 | break; |
| 123 | |
| 124 | p += 2; |
| 125 | p = skip_spaces(p); |
| 126 | len = strcspn(s: p, reject: " \t" ); |
| 127 | if (!len) |
| 128 | break; |
| 129 | |
| 130 | snprintf(s: evname, maxlen: len + 1, format: "%s" , p); |
| 131 | |
| 132 | match = 0; |
| 133 | evlist__for_each_entry(session->evlist, pos) { |
| 134 | if (evsel__name_is(pos, evname)) { |
| 135 | match = 1; |
| 136 | break; |
| 137 | } |
| 138 | } |
| 139 | |
| 140 | if (!match) { |
| 141 | fclose(fp); |
| 142 | return -1; |
| 143 | } |
| 144 | } |
| 145 | } |
| 146 | |
| 147 | fclose(stream: fp); |
| 148 | return 0; |
| 149 | } |
| 150 | |
| 151 | /* |
| 152 | * Return -1 if none is found, otherwise the actual scripts number. |
| 153 | * |
| 154 | * Currently the only user of this function is the script browser, which |
| 155 | * will list all statically runnable scripts, select one, execute it and |
| 156 | * show the output in a perf browser. |
| 157 | */ |
| 158 | static int find_scripts(char **scripts_array, char **scripts_path_array, int num, |
| 159 | int pathlen) |
| 160 | { |
| 161 | struct dirent *script_dirent, *lang_dirent; |
| 162 | int scripts_dir_fd, lang_dir_fd; |
| 163 | DIR *scripts_dir, *lang_dir; |
| 164 | struct perf_session *session; |
| 165 | struct perf_data data = { |
| 166 | .path = input_name, |
| 167 | .mode = PERF_DATA_MODE_READ, |
| 168 | }; |
| 169 | char *temp; |
| 170 | int i = 0; |
| 171 | const char *exec_path = get_argv_exec_path(); |
| 172 | |
| 173 | session = perf_session__new(data: &data, NULL); |
| 174 | if (IS_ERR(session)) |
| 175 | return PTR_ERR(session); |
| 176 | |
| 177 | { |
| 178 | char scripts_path[PATH_MAX]; |
| 179 | |
| 180 | snprintf(s: scripts_path, maxlen: sizeof(scripts_path), format: "%s/scripts" , exec_path); |
| 181 | scripts_dir_fd = open(file: scripts_path, O_DIRECTORY); |
| 182 | pr_err("Failed to open directory '%s'" , scripts_path); |
| 183 | if (scripts_dir_fd == -1) { |
| 184 | perf_session__delete(session); |
| 185 | return -1; |
| 186 | } |
| 187 | } |
| 188 | scripts_dir = fdopendir(fd: scripts_dir_fd); |
| 189 | if (!scripts_dir) { |
| 190 | close(fd: scripts_dir_fd); |
| 191 | perf_session__delete(session); |
| 192 | return -1; |
| 193 | } |
| 194 | |
| 195 | while ((lang_dirent = readdir(dirp: scripts_dir)) != NULL) { |
| 196 | if (lang_dirent->d_type != DT_DIR && |
| 197 | (lang_dirent->d_type == DT_UNKNOWN && |
| 198 | !is_directory_at(dir_fd: scripts_dir_fd, path: lang_dirent->d_name))) |
| 199 | continue; |
| 200 | if (!strcmp(s1: lang_dirent->d_name, s2: "." ) || !strcmp(s1: lang_dirent->d_name, s2: ".." )) |
| 201 | continue; |
| 202 | |
| 203 | #ifndef HAVE_LIBPERL_SUPPORT |
| 204 | if (strstr(haystack: lang_dirent->d_name, needle: "perl" )) |
| 205 | continue; |
| 206 | #endif |
| 207 | #ifndef HAVE_LIBPYTHON_SUPPORT |
| 208 | if (strstr(haystack: lang_dirent->d_name, needle: "python" )) |
| 209 | continue; |
| 210 | #endif |
| 211 | |
| 212 | lang_dir_fd = openat(fd: scripts_dir_fd, file: lang_dirent->d_name, O_DIRECTORY); |
| 213 | if (lang_dir_fd == -1) |
| 214 | continue; |
| 215 | lang_dir = fdopendir(fd: lang_dir_fd); |
| 216 | if (!lang_dir) { |
| 217 | close(fd: lang_dir_fd); |
| 218 | continue; |
| 219 | } |
| 220 | while ((script_dirent = readdir(dirp: lang_dir)) != NULL) { |
| 221 | if (script_dirent->d_type == DT_DIR) |
| 222 | continue; |
| 223 | if (script_dirent->d_type == DT_UNKNOWN && |
| 224 | is_directory_at(dir_fd: lang_dir_fd, path: script_dirent->d_name)) |
| 225 | continue; |
| 226 | /* Skip those real time scripts: xxxtop.p[yl] */ |
| 227 | if (strstr(haystack: script_dirent->d_name, needle: "top." )) |
| 228 | continue; |
| 229 | if (i >= num) |
| 230 | break; |
| 231 | scnprintf(scripts_path_array[i], pathlen, "%s/scripts/%s/%s" , |
| 232 | exec_path, |
| 233 | lang_dirent->d_name, |
| 234 | script_dirent->d_name); |
| 235 | temp = strchr(s: script_dirent->d_name, c: '.'); |
| 236 | snprintf(s: scripts_array[i], |
| 237 | maxlen: (temp - script_dirent->d_name) + 1, |
| 238 | format: "%s" , script_dirent->d_name); |
| 239 | |
| 240 | if (check_ev_match(dir_fd: lang_dir_fd, scriptname: scripts_array[i], session)) |
| 241 | continue; |
| 242 | |
| 243 | i++; |
| 244 | } |
| 245 | closedir(dirp: lang_dir); |
| 246 | } |
| 247 | |
| 248 | closedir(dirp: scripts_dir); |
| 249 | perf_session__delete(session); |
| 250 | return i; |
| 251 | } |
| 252 | |
| 253 | /* |
| 254 | * When success, will copy the full path of the selected script |
| 255 | * into the buffer pointed by script_name, and return 0. |
| 256 | * Return -1 on failure. |
| 257 | */ |
| 258 | static int list_scripts(char *script_name, bool *custom, |
| 259 | struct evsel *evsel) |
| 260 | { |
| 261 | char *buf, *paths[SCRIPT_MAX_NO], *names[SCRIPT_MAX_NO]; |
| 262 | int i, num, choice; |
| 263 | int ret = 0; |
| 264 | int max_std, custom_perf; |
| 265 | char pbuf[256]; |
| 266 | const char *perf = perf_exe(buf: pbuf, len: sizeof pbuf); |
| 267 | struct script_config scriptc = { |
| 268 | .names = (const char **)names, |
| 269 | .paths = paths, |
| 270 | .perf = perf |
| 271 | }; |
| 272 | |
| 273 | script_name[0] = 0; |
| 274 | |
| 275 | /* Preset the script name to SCRIPT_NAMELEN */ |
| 276 | buf = malloc(SCRIPT_MAX_NO * (SCRIPT_NAMELEN + SCRIPT_FULLPATH_LEN)); |
| 277 | if (!buf) |
| 278 | return -1; |
| 279 | |
| 280 | if (evsel) |
| 281 | attr_to_script(extra_format: scriptc.extra_format, attr: &evsel->core.attr); |
| 282 | add_script_option(name: "Show individual samples" , opt: "" , c: &scriptc); |
| 283 | add_script_option(name: "Show individual samples with assembler" , opt: "-F +disasm" , |
| 284 | c: &scriptc); |
| 285 | add_script_option(name: "Show individual samples with source" , opt: "-F +srcline,+srccode" , |
| 286 | c: &scriptc); |
| 287 | perf_config(scripts_config, &scriptc); |
| 288 | custom_perf = scriptc.index; |
| 289 | add_script_option(name: "Show samples with custom perf script arguments" , opt: "" , c: &scriptc); |
| 290 | i = scriptc.index; |
| 291 | max_std = i; |
| 292 | |
| 293 | for (; i < SCRIPT_MAX_NO; i++) { |
| 294 | names[i] = buf + (i - max_std) * (SCRIPT_NAMELEN + SCRIPT_FULLPATH_LEN); |
| 295 | paths[i] = names[i] + SCRIPT_NAMELEN; |
| 296 | } |
| 297 | |
| 298 | num = find_scripts(scripts_array: names + max_std, scripts_path_array: paths + max_std, SCRIPT_MAX_NO - max_std, |
| 299 | SCRIPT_FULLPATH_LEN); |
| 300 | if (num < 0) |
| 301 | num = 0; |
| 302 | choice = ui__popup_menu(argc: num + max_std, argv: (char * const *)names, NULL); |
| 303 | if (choice < 0) { |
| 304 | ret = -1; |
| 305 | goto out; |
| 306 | } |
| 307 | if (choice == custom_perf) { |
| 308 | char script_args[50]; |
| 309 | int key = ui_browser__input_window(title: "perf script command" , |
| 310 | text: "Enter perf script command line (without perf script prefix)" , |
| 311 | input: script_args, exit_msg: "" , delay_sec: 0); |
| 312 | if (key != K_ENTER) { |
| 313 | ret = -1; |
| 314 | goto out; |
| 315 | } |
| 316 | sprintf(s: script_name, format: "%s script %s" , perf, script_args); |
| 317 | } else if (choice < num + max_std) { |
| 318 | strcpy(dest: script_name, src: paths[choice]); |
| 319 | } |
| 320 | *custom = choice >= max_std; |
| 321 | |
| 322 | out: |
| 323 | free(ptr: buf); |
| 324 | for (i = 0; i < max_std; i++) |
| 325 | zfree(&paths[i]); |
| 326 | return ret; |
| 327 | } |
| 328 | |
| 329 | void run_script(char *cmd) |
| 330 | { |
| 331 | pr_debug("Running %s\n" , cmd); |
| 332 | SLang_reset_tty(); |
| 333 | if (system(command: cmd) < 0) |
| 334 | pr_warning("Cannot run %s\n" , cmd); |
| 335 | /* |
| 336 | * SLang doesn't seem to reset the whole terminal, so be more |
| 337 | * forceful to get back to the original state. |
| 338 | */ |
| 339 | printf(format: "\033[c\033[H\033[J" ); |
| 340 | fflush(stdout); |
| 341 | SLang_init_tty(0, 0, 0); |
| 342 | SLtty_set_suspend_state(true); |
| 343 | SLsmg_refresh(); |
| 344 | } |
| 345 | |
| 346 | int script_browse(const char *script_opt, struct evsel *evsel) |
| 347 | { |
| 348 | char *cmd, script_name[SCRIPT_FULLPATH_LEN]; |
| 349 | bool custom = false; |
| 350 | |
| 351 | memset(s: script_name, c: 0, SCRIPT_FULLPATH_LEN); |
| 352 | if (list_scripts(script_name, custom: &custom, evsel)) |
| 353 | return -1; |
| 354 | |
| 355 | if (asprintf(ptr: &cmd, fmt: "%s%s %s %s%s 2>&1 | less" , |
| 356 | custom ? "perf script -s " : "" , |
| 357 | script_name, |
| 358 | script_opt ? script_opt : "" , |
| 359 | input_name ? "-i " : "" , |
| 360 | input_name ? input_name : "" ) < 0) |
| 361 | return -1; |
| 362 | |
| 363 | run_script(cmd); |
| 364 | free(ptr: cmd); |
| 365 | |
| 366 | return 0; |
| 367 | } |
| 368 | |