a50d11a10c
Add new buildid cache if the update target file is not cached. This can happen when an old binary is replaced by new one after caching the old one. In this case, user sees his operation just failed. But it does not look straight, since user just pass the binary "path", not "build-id". ---- # ./perf buildid-cache --add ./perf (update ./perf to new binary) # ./perf buildid-cache --update ./perf ./perf wasn't in the cache # ---- This patch adds given new binary to cache if the new binary is not cached. So we'll not see the above error. ---- # ./perf buildid-cache --add ./perf (update ./perf to new binary) # ./perf buildid-cache --update ./perf # ---- Signed-off-by: Masami Hiramatsu <masami.hiramatsu.pt@hitachi.com> Cc: Adrian Hunter <adrian.hunter@intel.com> Cc: Borislav Petkov <bp@suse.de> Cc: Hemant Kumar <hemant@linux.vnet.ibm.com> Cc: Ingo Molnar <mingo@redhat.com> Cc: Jiri Olsa <jolsa@kernel.org> Cc: Namhyung Kim <namhyung@kernel.org> Cc: Paul Mackerras <paulus@samba.org> Cc: Peter Zijlstra <a.p.zijlstra@chello.nl> Link: http://lkml.kernel.org/r/20150226065440.23912.1494.stgit@localhost.localdomain Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
401 lines
9.3 KiB
C
401 lines
9.3 KiB
C
/*
|
|
* builtin-buildid-cache.c
|
|
*
|
|
* Builtin buildid-cache command: Manages build-id cache
|
|
*
|
|
* Copyright (C) 2010, Red Hat Inc.
|
|
* Copyright (C) 2010, Arnaldo Carvalho de Melo <acme@redhat.com>
|
|
*/
|
|
#include <sys/types.h>
|
|
#include <sys/time.h>
|
|
#include <time.h>
|
|
#include <dirent.h>
|
|
#include <unistd.h>
|
|
#include "builtin.h"
|
|
#include "perf.h"
|
|
#include "util/cache.h"
|
|
#include "util/debug.h"
|
|
#include "util/header.h"
|
|
#include "util/parse-options.h"
|
|
#include "util/strlist.h"
|
|
#include "util/build-id.h"
|
|
#include "util/session.h"
|
|
#include "util/symbol.h"
|
|
|
|
static int build_id_cache__kcore_buildid(const char *proc_dir, char *sbuildid)
|
|
{
|
|
char root_dir[PATH_MAX];
|
|
char notes[PATH_MAX];
|
|
u8 build_id[BUILD_ID_SIZE];
|
|
char *p;
|
|
|
|
strlcpy(root_dir, proc_dir, sizeof(root_dir));
|
|
|
|
p = strrchr(root_dir, '/');
|
|
if (!p)
|
|
return -1;
|
|
*p = '\0';
|
|
|
|
scnprintf(notes, sizeof(notes), "%s/sys/kernel/notes", root_dir);
|
|
|
|
if (sysfs__read_build_id(notes, build_id, sizeof(build_id)))
|
|
return -1;
|
|
|
|
build_id__sprintf(build_id, sizeof(build_id), sbuildid);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int build_id_cache__kcore_dir(char *dir, size_t sz)
|
|
{
|
|
struct timeval tv;
|
|
struct tm tm;
|
|
char dt[32];
|
|
|
|
if (gettimeofday(&tv, NULL) || !localtime_r(&tv.tv_sec, &tm))
|
|
return -1;
|
|
|
|
if (!strftime(dt, sizeof(dt), "%Y%m%d%H%M%S", &tm))
|
|
return -1;
|
|
|
|
scnprintf(dir, sz, "%s%02u", dt, (unsigned)tv.tv_usec / 10000);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool same_kallsyms_reloc(const char *from_dir, char *to_dir)
|
|
{
|
|
char from[PATH_MAX];
|
|
char to[PATH_MAX];
|
|
const char *name;
|
|
u64 addr1 = 0, addr2 = 0;
|
|
int i;
|
|
|
|
scnprintf(from, sizeof(from), "%s/kallsyms", from_dir);
|
|
scnprintf(to, sizeof(to), "%s/kallsyms", to_dir);
|
|
|
|
for (i = 0; (name = ref_reloc_sym_names[i]) != NULL; i++) {
|
|
addr1 = kallsyms__get_function_start(from, name);
|
|
if (addr1)
|
|
break;
|
|
}
|
|
|
|
if (name)
|
|
addr2 = kallsyms__get_function_start(to, name);
|
|
|
|
return addr1 == addr2;
|
|
}
|
|
|
|
static int build_id_cache__kcore_existing(const char *from_dir, char *to_dir,
|
|
size_t to_dir_sz)
|
|
{
|
|
char from[PATH_MAX];
|
|
char to[PATH_MAX];
|
|
char to_subdir[PATH_MAX];
|
|
struct dirent *dent;
|
|
int ret = -1;
|
|
DIR *d;
|
|
|
|
d = opendir(to_dir);
|
|
if (!d)
|
|
return -1;
|
|
|
|
scnprintf(from, sizeof(from), "%s/modules", from_dir);
|
|
|
|
while (1) {
|
|
dent = readdir(d);
|
|
if (!dent)
|
|
break;
|
|
if (dent->d_type != DT_DIR)
|
|
continue;
|
|
scnprintf(to, sizeof(to), "%s/%s/modules", to_dir,
|
|
dent->d_name);
|
|
scnprintf(to_subdir, sizeof(to_subdir), "%s/%s",
|
|
to_dir, dent->d_name);
|
|
if (!compare_proc_modules(from, to) &&
|
|
same_kallsyms_reloc(from_dir, to_subdir)) {
|
|
strlcpy(to_dir, to_subdir, to_dir_sz);
|
|
ret = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
closedir(d);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int build_id_cache__add_kcore(const char *filename, bool force)
|
|
{
|
|
char dir[32], sbuildid[BUILD_ID_SIZE * 2 + 1];
|
|
char from_dir[PATH_MAX], to_dir[PATH_MAX];
|
|
char *p;
|
|
|
|
strlcpy(from_dir, filename, sizeof(from_dir));
|
|
|
|
p = strrchr(from_dir, '/');
|
|
if (!p || strcmp(p + 1, "kcore"))
|
|
return -1;
|
|
*p = '\0';
|
|
|
|
if (build_id_cache__kcore_buildid(from_dir, sbuildid))
|
|
return -1;
|
|
|
|
scnprintf(to_dir, sizeof(to_dir), "%s/[kernel.kcore]/%s",
|
|
buildid_dir, sbuildid);
|
|
|
|
if (!force &&
|
|
!build_id_cache__kcore_existing(from_dir, to_dir, sizeof(to_dir))) {
|
|
pr_debug("same kcore found in %s\n", to_dir);
|
|
return 0;
|
|
}
|
|
|
|
if (build_id_cache__kcore_dir(dir, sizeof(dir)))
|
|
return -1;
|
|
|
|
scnprintf(to_dir, sizeof(to_dir), "%s/[kernel.kcore]/%s/%s",
|
|
buildid_dir, sbuildid, dir);
|
|
|
|
if (mkdir_p(to_dir, 0755))
|
|
return -1;
|
|
|
|
if (kcore_copy(from_dir, to_dir)) {
|
|
/* Remove YYYYmmddHHMMSShh directory */
|
|
if (!rmdir(to_dir)) {
|
|
p = strrchr(to_dir, '/');
|
|
if (p)
|
|
*p = '\0';
|
|
/* Try to remove buildid directory */
|
|
if (!rmdir(to_dir)) {
|
|
p = strrchr(to_dir, '/');
|
|
if (p)
|
|
*p = '\0';
|
|
/* Try to remove [kernel.kcore] directory */
|
|
rmdir(to_dir);
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
pr_debug("kcore added to build-id cache directory %s\n", to_dir);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int build_id_cache__add_file(const char *filename)
|
|
{
|
|
char sbuild_id[BUILD_ID_SIZE * 2 + 1];
|
|
u8 build_id[BUILD_ID_SIZE];
|
|
int err;
|
|
|
|
if (filename__read_build_id(filename, &build_id, sizeof(build_id)) < 0) {
|
|
pr_debug("Couldn't read a build-id in %s\n", filename);
|
|
return -1;
|
|
}
|
|
|
|
build_id__sprintf(build_id, sizeof(build_id), sbuild_id);
|
|
err = build_id_cache__add_s(sbuild_id, filename,
|
|
false, false);
|
|
if (verbose)
|
|
pr_info("Adding %s %s: %s\n", sbuild_id, filename,
|
|
err ? "FAIL" : "Ok");
|
|
return err;
|
|
}
|
|
|
|
static int build_id_cache__remove_file(const char *filename)
|
|
{
|
|
u8 build_id[BUILD_ID_SIZE];
|
|
char sbuild_id[BUILD_ID_SIZE * 2 + 1];
|
|
|
|
int err;
|
|
|
|
if (filename__read_build_id(filename, &build_id, sizeof(build_id)) < 0) {
|
|
pr_debug("Couldn't read a build-id in %s\n", filename);
|
|
return -1;
|
|
}
|
|
|
|
build_id__sprintf(build_id, sizeof(build_id), sbuild_id);
|
|
err = build_id_cache__remove_s(sbuild_id);
|
|
if (verbose)
|
|
pr_info("Removing %s %s: %s\n", sbuild_id, filename,
|
|
err ? "FAIL" : "Ok");
|
|
|
|
return err;
|
|
}
|
|
|
|
static bool dso__missing_buildid_cache(struct dso *dso, int parm __maybe_unused)
|
|
{
|
|
char filename[PATH_MAX];
|
|
u8 build_id[BUILD_ID_SIZE];
|
|
|
|
if (dso__build_id_filename(dso, filename, sizeof(filename)) &&
|
|
filename__read_build_id(filename, build_id,
|
|
sizeof(build_id)) != sizeof(build_id)) {
|
|
if (errno == ENOENT)
|
|
return false;
|
|
|
|
pr_warning("Problems with %s file, consider removing it from the cache\n",
|
|
filename);
|
|
} else if (memcmp(dso->build_id, build_id, sizeof(dso->build_id))) {
|
|
pr_warning("Problems with %s file, consider removing it from the cache\n",
|
|
filename);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static int build_id_cache__fprintf_missing(struct perf_session *session, FILE *fp)
|
|
{
|
|
perf_session__fprintf_dsos_buildid(session, fp, dso__missing_buildid_cache, 0);
|
|
return 0;
|
|
}
|
|
|
|
static int build_id_cache__update_file(const char *filename)
|
|
{
|
|
u8 build_id[BUILD_ID_SIZE];
|
|
char sbuild_id[BUILD_ID_SIZE * 2 + 1];
|
|
|
|
int err = 0;
|
|
|
|
if (filename__read_build_id(filename, &build_id, sizeof(build_id)) < 0) {
|
|
pr_debug("Couldn't read a build-id in %s\n", filename);
|
|
return -1;
|
|
}
|
|
|
|
build_id__sprintf(build_id, sizeof(build_id), sbuild_id);
|
|
if (build_id_cache__cached(sbuild_id))
|
|
err = build_id_cache__remove_s(sbuild_id);
|
|
|
|
if (!err)
|
|
err = build_id_cache__add_s(sbuild_id, filename, false, false);
|
|
|
|
if (verbose)
|
|
pr_info("Updating %s %s: %s\n", sbuild_id, filename,
|
|
err ? "FAIL" : "Ok");
|
|
|
|
return err;
|
|
}
|
|
|
|
int cmd_buildid_cache(int argc, const char **argv,
|
|
const char *prefix __maybe_unused)
|
|
{
|
|
struct strlist *list;
|
|
struct str_node *pos;
|
|
int ret = 0;
|
|
bool force = false;
|
|
char const *add_name_list_str = NULL,
|
|
*remove_name_list_str = NULL,
|
|
*missing_filename = NULL,
|
|
*update_name_list_str = NULL,
|
|
*kcore_filename = NULL;
|
|
char sbuf[STRERR_BUFSIZE];
|
|
|
|
struct perf_data_file file = {
|
|
.mode = PERF_DATA_MODE_READ,
|
|
};
|
|
struct perf_session *session = NULL;
|
|
|
|
const struct option buildid_cache_options[] = {
|
|
OPT_STRING('a', "add", &add_name_list_str,
|
|
"file list", "file(s) to add"),
|
|
OPT_STRING('k', "kcore", &kcore_filename,
|
|
"file", "kcore file to add"),
|
|
OPT_STRING('r', "remove", &remove_name_list_str, "file list",
|
|
"file(s) to remove"),
|
|
OPT_STRING('M', "missing", &missing_filename, "file",
|
|
"to find missing build ids in the cache"),
|
|
OPT_BOOLEAN('f', "force", &force, "don't complain, do it"),
|
|
OPT_STRING('u', "update", &update_name_list_str, "file list",
|
|
"file(s) to update"),
|
|
OPT_INCR('v', "verbose", &verbose, "be more verbose"),
|
|
OPT_END()
|
|
};
|
|
const char * const buildid_cache_usage[] = {
|
|
"perf buildid-cache [<options>]",
|
|
NULL
|
|
};
|
|
|
|
argc = parse_options(argc, argv, buildid_cache_options,
|
|
buildid_cache_usage, 0);
|
|
|
|
if (missing_filename) {
|
|
file.path = missing_filename;
|
|
file.force = force;
|
|
|
|
session = perf_session__new(&file, false, NULL);
|
|
if (session == NULL)
|
|
return -1;
|
|
}
|
|
|
|
if (symbol__init(session ? &session->header.env : NULL) < 0)
|
|
goto out;
|
|
|
|
setup_pager();
|
|
|
|
if (add_name_list_str) {
|
|
list = strlist__new(true, add_name_list_str);
|
|
if (list) {
|
|
strlist__for_each(pos, list)
|
|
if (build_id_cache__add_file(pos->s)) {
|
|
if (errno == EEXIST) {
|
|
pr_debug("%s already in the cache\n",
|
|
pos->s);
|
|
continue;
|
|
}
|
|
pr_warning("Couldn't add %s: %s\n",
|
|
pos->s, strerror_r(errno, sbuf, sizeof(sbuf)));
|
|
}
|
|
|
|
strlist__delete(list);
|
|
}
|
|
}
|
|
|
|
if (remove_name_list_str) {
|
|
list = strlist__new(true, remove_name_list_str);
|
|
if (list) {
|
|
strlist__for_each(pos, list)
|
|
if (build_id_cache__remove_file(pos->s)) {
|
|
if (errno == ENOENT) {
|
|
pr_debug("%s wasn't in the cache\n",
|
|
pos->s);
|
|
continue;
|
|
}
|
|
pr_warning("Couldn't remove %s: %s\n",
|
|
pos->s, strerror_r(errno, sbuf, sizeof(sbuf)));
|
|
}
|
|
|
|
strlist__delete(list);
|
|
}
|
|
}
|
|
|
|
if (missing_filename)
|
|
ret = build_id_cache__fprintf_missing(session, stdout);
|
|
|
|
if (update_name_list_str) {
|
|
list = strlist__new(true, update_name_list_str);
|
|
if (list) {
|
|
strlist__for_each(pos, list)
|
|
if (build_id_cache__update_file(pos->s)) {
|
|
if (errno == ENOENT) {
|
|
pr_debug("%s wasn't in the cache\n",
|
|
pos->s);
|
|
continue;
|
|
}
|
|
pr_warning("Couldn't update %s: %s\n",
|
|
pos->s, strerror_r(errno, sbuf, sizeof(sbuf)));
|
|
}
|
|
|
|
strlist__delete(list);
|
|
}
|
|
}
|
|
|
|
if (kcore_filename && build_id_cache__add_kcore(kcore_filename, force))
|
|
pr_warning("Couldn't add %s\n", kcore_filename);
|
|
|
|
out:
|
|
if (session)
|
|
perf_session__delete(session);
|
|
|
|
return ret;
|
|
}
|