mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00

FILENAME_MAX is the same as PATH_MAX (4kb) in glibc rather than NAME_MAX's 255. Switch to using NAME_MAX and ensure the '\0' is accounted for in the path's buffer size. Signed-off-by: Ian Rogers <irogers@google.com> Link: https://lore.kernel.org/r/20250717150855.1032526-3-irogers@google.com Signed-off-by: Namhyung Kim <namhyung@kernel.org>
367 lines
9 KiB
C
367 lines
9 KiB
C
// 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 <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
|
|
/*
|
|
* 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[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;
|
|
}
|
|
|
|
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;
|
|
}
|