| 1 | |
| 2 | #include "../../util/util.h"
|
| 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 extra_format[256];
|
| 34 | };
|
| 35 |
|
| 36 | void attr_to_script(char *extra_format, struct perf_event_attr *attr)
|
| 37 | {
|
| 38 | extra_format[0] = 0;
|
| 39 | if (attr->read_format & PERF_FORMAT_GROUP)
|
| 40 | strcat(extra_format, " -F +metric");
|
| 41 | if (attr->sample_type & PERF_SAMPLE_BRANCH_STACK)
|
| 42 | strcat(extra_format, " -F +brstackinsn --xed");
|
| 43 | if (attr->sample_type & PERF_SAMPLE_REGS_INTR)
|
| 44 | strcat(extra_format, " -F +iregs");
|
| 45 | if (attr->sample_type & PERF_SAMPLE_REGS_USER)
|
| 46 | strcat(extra_format, " -F +uregs");
|
| 47 | if (attr->sample_type & PERF_SAMPLE_PHYS_ADDR)
|
| 48 | strcat(extra_format, " -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(&c->paths[c->index],
|
| 56 | "%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(var + 7);
|
| 73 | if (!c->names[c->index])
|
| 74 | return -1;
|
| 75 | if (asprintf(&c->paths[c->index], "%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(dir_fd, filename, O_RDONLY);
|
| 102 | if (fd == -1)
|
| 103 | return -1;
|
| 104 | fp = fdopen(fd, "r");
|
| 105 | if (!fp)
|
| 106 | return -1;
|
| 107 | }
|
| 108 |
|
| 109 | while (fgets(line, sizeof(line), fp)) {
|
| 110 | char *p = skip_spaces(line);
|
| 111 |
|
| 112 | if (*p == '#')
|
| 113 | continue;
|
| 114 |
|
| 115 | while (strlen(p)) {
|
| 116 | int match, len;
|
| 117 | struct evsel *pos;
|
| 118 | char evname[128];
|
| 119 |
|
| 120 | p = strstr(p, "-e");
|
| 121 | if (!p)
|
| 122 | break;
|
| 123 |
|
| 124 | p += 2;
|
| 125 | p = skip_spaces(p);
|
| 126 | len = strcspn(p, " \t");
|
| 127 | if (!len)
|
| 128 | break;
|
| 129 |
|
| 130 | snprintf(evname, len + 1, "%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(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, NULL);
|
| 174 | if (IS_ERR(session))
|
| 175 | return PTR_ERR(session);
|
| 176 |
|
| 177 | {
|
| 178 | char scripts_path[PATH_MAX];
|
| 179 |
|
| 180 | snprintf(scripts_path, sizeof(scripts_path), "%s/scripts", exec_path);
|
| 181 | scripts_dir_fd = open(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(scripts_dir_fd);
|
| 189 | if (!scripts_dir) {
|
| 190 | close(scripts_dir_fd);
|
| 191 | perf_session__delete(session);
|
| 192 | return -1;
|
| 193 | }
|
| 194 |
|
| 195 | while ((lang_dirent = readdir(scripts_dir)) != NULL) {
|
| 196 | if (lang_dirent->d_type != DT_DIR &&
|
| 197 | (lang_dirent->d_type == DT_UNKNOWN &&
|
| 198 | !is_directory_at(scripts_dir_fd, lang_dirent->d_name)))
|
| 199 | continue;
|
| 200 | if (!strcmp(lang_dirent->d_name, ".") || !strcmp(lang_dirent->d_name, ".."))
|
| 201 | continue;
|
| 202 |
|
| 203 | #ifndef HAVE_LIBPERL_SUPPORT
|
| 204 | if (strstr(lang_dirent->d_name, "perl"))
|
| 205 | continue;
|
| 206 | #endif
|
| 207 | #ifndef HAVE_LIBPYTHON_SUPPORT
|
| 208 | if (strstr(lang_dirent->d_name, "python"))
|
| 209 | continue;
|
| 210 | #endif
|
| 211 |
|
| 212 | lang_dir_fd = openat(scripts_dir_fd, lang_dirent->d_name, O_DIRECTORY);
|
| 213 | if (lang_dir_fd == -1)
|
| 214 | continue;
|
| 215 | lang_dir = fdopendir(lang_dir_fd);
|
| 216 | if (!lang_dir) {
|
| 217 | close(lang_dir_fd);
|
| 218 | continue;
|
| 219 | }
|
| 220 | while ((script_dirent = readdir(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(lang_dir_fd, script_dirent->d_name))
|
| 225 | continue;
|
| 226 |
|
| 227 | if (strstr(script_dirent->d_name, "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(script_dirent->d_name, '.');
|
| 236 | snprintf(scripts_array[i],
|
| 237 | (temp - script_dirent->d_name) + 1,
|
| 238 | "%s", script_dirent->d_name);
|
| 239 |
|
| 240 | if (check_ev_match(lang_dir_fd, scripts_array[i], session))
|
| 241 | continue;
|
| 242 |
|
| 243 | i++;
|
| 244 | }
|
| 245 | closedir(lang_dir);
|
| 246 | }
|
| 247 |
|
| 248 | closedir(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(pbuf, 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 |
|
| 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(scriptc.extra_format, &evsel->core.attr);
|
| 282 | add_script_option("Show individual samples", "", &scriptc);
|
| 283 | add_script_option("Show individual samples with assembler", "-F +disasm",
|
| 284 | &scriptc);
|
| 285 | add_script_option("Show individual samples with source", "-F +srcline,+srccode",
|
| 286 | &scriptc);
|
| 287 | perf_config(scripts_config, &scriptc);
|
| 288 | custom_perf = scriptc.index;
|
| 289 | add_script_option("Show samples with custom perf script arguments", "", &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(names + max_std, paths + max_std, SCRIPT_MAX_NO - max_std,
|
| 299 | SCRIPT_FULLPATH_LEN);
|
| 300 | if (num < 0)
|
| 301 | num = 0;
|
| 302 | choice = ui__popup_menu(num + max_std, (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("perf script command",
|
| 310 | "Enter perf script command line (without perf script prefix)",
|
| 311 | script_args, "", 0);
|
| 312 | if (key != K_ENTER) {
|
| 313 | ret = -1;
|
| 314 | goto out;
|
| 315 | }
|
| 316 | sprintf(script_name, "%s script %s", perf, script_args);
|
| 317 | } else if (choice < num + max_std) {
|
| 318 | strcpy(script_name, paths[choice]);
|
| 319 | }
|
| 320 | *custom = choice >= max_std;
|
| 321 |
|
| 322 | out:
|
| 323 | free(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(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("\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(script_name, 0, SCRIPT_FULLPATH_LEN);
|
| 352 | if (list_scripts(script_name, &custom, evsel))
|
| 353 | return -1;
|
| 354 |
|
| 355 | if (asprintf(&cmd, "%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(cmd);
|
| 365 |
|
| 366 | return 0;
|
| 367 | }
|