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 extra_format[256];
34 };
35
attr_to_script(char * extra_format,struct perf_event_attr * attr)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
add_script_option(const char * name,const char * opt,struct script_config * c)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
scripts_config(const char * var,const char * value,void * data)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 */
check_ev_match(int dir_fd,const char * scriptname,struct perf_session * session)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[FILENAME_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 */
find_scripts(char ** scripts_array,char ** scripts_path_array,int num,int pathlen)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 /* Skip those real time scripts: xxxtop.p[yl] */
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 */
list_scripts(char * script_name,bool * custom,struct evsel * evsel)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 /* 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(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
run_script(char * cmd)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
script_browse(const char * script_opt,struct evsel * evsel)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 }
368