perf tools: Add perf data cache feature
Storing CPU cache details under perf data. It's stored as new HEADER_CACHE feature and it's displayed under header info with -I option: $ perf report --header-only -I ... # CPU cache info: # L1 Data 32K [0-1] # L1 Instruction 32K [0-1] # L1 Data 32K [2-3] # L1 Instruction 32K [2-3] # L2 Unified 256K [0-1] # L2 Unified 256K [2-3] # L3 Unified 4096K [0-3] ... All distinct caches are stored/displayed. Signed-off-by: Jiri Olsa <jolsa@kernel.org> Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com> Cc: David Ahern <dsahern@gmail.com> Cc: Jiri Olsa <jolsa@kernel.org> Cc: Namhyung Kim <namhyung@kernel.org> Cc: Peter Zijlstra <a.p.zijlstra@chello.nl> Link: http://lkml.kernel.org/r/20160216150143.GA7119@krava.brq.redhat.com [ Fixed leak on process_caches(), s/cache_level/cpu_cache_level/g ] Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
This commit is contained in:
Родитель
dd629cc097
Коммит
720e98b5fa
|
@ -6,6 +6,8 @@ struct perf_env perf_env;
|
|||
|
||||
void perf_env__exit(struct perf_env *env)
|
||||
{
|
||||
int i;
|
||||
|
||||
zfree(&env->hostname);
|
||||
zfree(&env->os_release);
|
||||
zfree(&env->version);
|
||||
|
@ -19,6 +21,10 @@ void perf_env__exit(struct perf_env *env)
|
|||
zfree(&env->numa_nodes);
|
||||
zfree(&env->pmu_mappings);
|
||||
zfree(&env->cpu);
|
||||
|
||||
for (i = 0; i < env->caches_cnt; i++)
|
||||
cpu_cache_level__free(&env->caches[i]);
|
||||
zfree(&env->caches);
|
||||
}
|
||||
|
||||
int perf_env__set_cmdline(struct perf_env *env, int argc, const char *argv[])
|
||||
|
@ -75,3 +81,10 @@ int perf_env__read_cpu_topology_map(struct perf_env *env)
|
|||
env->nr_cpus_avail = nr_cpus;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void cpu_cache_level__free(struct cpu_cache_level *cache)
|
||||
{
|
||||
free(cache->type);
|
||||
free(cache->map);
|
||||
free(cache->size);
|
||||
}
|
||||
|
|
|
@ -1,11 +1,23 @@
|
|||
#ifndef __PERF_ENV_H
|
||||
#define __PERF_ENV_H
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
struct cpu_topology_map {
|
||||
int socket_id;
|
||||
int core_id;
|
||||
};
|
||||
|
||||
struct cpu_cache_level {
|
||||
u32 level;
|
||||
u32 line_size;
|
||||
u32 sets;
|
||||
u32 ways;
|
||||
char *type;
|
||||
char *size;
|
||||
char *map;
|
||||
};
|
||||
|
||||
struct perf_env {
|
||||
char *hostname;
|
||||
char *os_release;
|
||||
|
@ -31,6 +43,8 @@ struct perf_env {
|
|||
char *numa_nodes;
|
||||
char *pmu_mappings;
|
||||
struct cpu_topology_map *cpu;
|
||||
struct cpu_cache_level *caches;
|
||||
int caches_cnt;
|
||||
};
|
||||
|
||||
extern struct perf_env perf_env;
|
||||
|
@ -41,4 +55,5 @@ int perf_env__set_cmdline(struct perf_env *env, int argc, const char *argv[]);
|
|||
|
||||
int perf_env__read_cpu_topology_map(struct perf_env *env);
|
||||
|
||||
void cpu_cache_level__free(struct cpu_cache_level *cache);
|
||||
#endif /* __PERF_ENV_H */
|
||||
|
|
|
@ -23,6 +23,8 @@
|
|||
#include "strbuf.h"
|
||||
#include "build-id.h"
|
||||
#include "data.h"
|
||||
#include <api/fs/fs.h>
|
||||
#include "asm/bug.h"
|
||||
|
||||
/*
|
||||
* magic2 = "PERFILE2"
|
||||
|
@ -868,6 +870,199 @@ static int write_auxtrace(int fd, struct perf_header *h,
|
|||
return err;
|
||||
}
|
||||
|
||||
static int cpu_cache_level__sort(const void *a, const void *b)
|
||||
{
|
||||
struct cpu_cache_level *cache_a = (struct cpu_cache_level *)a;
|
||||
struct cpu_cache_level *cache_b = (struct cpu_cache_level *)b;
|
||||
|
||||
return cache_a->level - cache_b->level;
|
||||
}
|
||||
|
||||
static bool cpu_cache_level__cmp(struct cpu_cache_level *a, struct cpu_cache_level *b)
|
||||
{
|
||||
if (a->level != b->level)
|
||||
return false;
|
||||
|
||||
if (a->line_size != b->line_size)
|
||||
return false;
|
||||
|
||||
if (a->sets != b->sets)
|
||||
return false;
|
||||
|
||||
if (a->ways != b->ways)
|
||||
return false;
|
||||
|
||||
if (strcmp(a->type, b->type))
|
||||
return false;
|
||||
|
||||
if (strcmp(a->size, b->size))
|
||||
return false;
|
||||
|
||||
if (strcmp(a->map, b->map))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int cpu_cache_level__read(struct cpu_cache_level *cache, u32 cpu, u16 level)
|
||||
{
|
||||
char path[PATH_MAX], file[PATH_MAX];
|
||||
struct stat st;
|
||||
size_t len;
|
||||
|
||||
scnprintf(path, PATH_MAX, "devices/system/cpu/cpu%d/cache/index%d/", cpu, level);
|
||||
scnprintf(file, PATH_MAX, "%s/%s", sysfs__mountpoint(), path);
|
||||
|
||||
if (stat(file, &st))
|
||||
return 1;
|
||||
|
||||
scnprintf(file, PATH_MAX, "%s/level", path);
|
||||
if (sysfs__read_int(file, (int *) &cache->level))
|
||||
return -1;
|
||||
|
||||
scnprintf(file, PATH_MAX, "%s/coherency_line_size", path);
|
||||
if (sysfs__read_int(file, (int *) &cache->line_size))
|
||||
return -1;
|
||||
|
||||
scnprintf(file, PATH_MAX, "%s/number_of_sets", path);
|
||||
if (sysfs__read_int(file, (int *) &cache->sets))
|
||||
return -1;
|
||||
|
||||
scnprintf(file, PATH_MAX, "%s/ways_of_associativity", path);
|
||||
if (sysfs__read_int(file, (int *) &cache->ways))
|
||||
return -1;
|
||||
|
||||
scnprintf(file, PATH_MAX, "%s/type", path);
|
||||
if (sysfs__read_str(file, &cache->type, &len))
|
||||
return -1;
|
||||
|
||||
cache->type[len] = 0;
|
||||
cache->type = rtrim(cache->type);
|
||||
|
||||
scnprintf(file, PATH_MAX, "%s/size", path);
|
||||
if (sysfs__read_str(file, &cache->size, &len)) {
|
||||
free(cache->type);
|
||||
return -1;
|
||||
}
|
||||
|
||||
cache->size[len] = 0;
|
||||
cache->size = rtrim(cache->size);
|
||||
|
||||
scnprintf(file, PATH_MAX, "%s/shared_cpu_list", path);
|
||||
if (sysfs__read_str(file, &cache->map, &len)) {
|
||||
free(cache->map);
|
||||
free(cache->type);
|
||||
return -1;
|
||||
}
|
||||
|
||||
cache->map[len] = 0;
|
||||
cache->map = rtrim(cache->map);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void cpu_cache_level__fprintf(FILE *out, struct cpu_cache_level *c)
|
||||
{
|
||||
fprintf(out, "L%d %-15s %8s [%s]\n", c->level, c->type, c->size, c->map);
|
||||
}
|
||||
|
||||
static int build_caches(struct cpu_cache_level caches[], u32 size, u32 *cntp)
|
||||
{
|
||||
u32 i, cnt = 0;
|
||||
long ncpus;
|
||||
u32 nr, cpu;
|
||||
u16 level;
|
||||
|
||||
ncpus = sysconf(_SC_NPROCESSORS_CONF);
|
||||
if (ncpus < 0)
|
||||
return -1;
|
||||
|
||||
nr = (u32)(ncpus & UINT_MAX);
|
||||
|
||||
for (cpu = 0; cpu < nr; cpu++) {
|
||||
for (level = 0; level < 10; level++) {
|
||||
struct cpu_cache_level c;
|
||||
int err;
|
||||
|
||||
err = cpu_cache_level__read(&c, cpu, level);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if (err == 1)
|
||||
break;
|
||||
|
||||
for (i = 0; i < cnt; i++) {
|
||||
if (cpu_cache_level__cmp(&c, &caches[i]))
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == cnt)
|
||||
caches[cnt++] = c;
|
||||
else
|
||||
cpu_cache_level__free(&c);
|
||||
|
||||
if (WARN_ONCE(cnt == size, "way too many cpu caches.."))
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
out:
|
||||
*cntp = cnt;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define MAX_CACHES 2000
|
||||
|
||||
static int write_cache(int fd, struct perf_header *h __maybe_unused,
|
||||
struct perf_evlist *evlist __maybe_unused)
|
||||
{
|
||||
struct cpu_cache_level caches[MAX_CACHES];
|
||||
u32 cnt = 0, i, version = 1;
|
||||
int ret;
|
||||
|
||||
ret = build_caches(caches, MAX_CACHES, &cnt);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
qsort(&caches, cnt, sizeof(struct cpu_cache_level), cpu_cache_level__sort);
|
||||
|
||||
ret = do_write(fd, &version, sizeof(u32));
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = do_write(fd, &cnt, sizeof(u32));
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
for (i = 0; i < cnt; i++) {
|
||||
struct cpu_cache_level *c = &caches[i];
|
||||
|
||||
#define _W(v) \
|
||||
ret = do_write(fd, &c->v, sizeof(u32)); \
|
||||
if (ret < 0) \
|
||||
goto out;
|
||||
|
||||
_W(level)
|
||||
_W(line_size)
|
||||
_W(sets)
|
||||
_W(ways)
|
||||
#undef _W
|
||||
|
||||
#define _W(v) \
|
||||
ret = do_write_string(fd, (const char *) c->v); \
|
||||
if (ret < 0) \
|
||||
goto out;
|
||||
|
||||
_W(type)
|
||||
_W(size)
|
||||
_W(map)
|
||||
#undef _W
|
||||
}
|
||||
|
||||
out:
|
||||
for (i = 0; i < cnt; i++)
|
||||
cpu_cache_level__free(&caches[i]);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int write_stat(int fd __maybe_unused,
|
||||
struct perf_header *h __maybe_unused,
|
||||
struct perf_evlist *evlist __maybe_unused)
|
||||
|
@ -1172,6 +1367,18 @@ static void print_stat(struct perf_header *ph __maybe_unused,
|
|||
fprintf(fp, "# contains stat data\n");
|
||||
}
|
||||
|
||||
static void print_cache(struct perf_header *ph __maybe_unused,
|
||||
int fd __maybe_unused, FILE *fp __maybe_unused)
|
||||
{
|
||||
int i;
|
||||
|
||||
fprintf(fp, "# CPU cache info:\n");
|
||||
for (i = 0; i < ph->env.caches_cnt; i++) {
|
||||
fprintf(fp, "# ");
|
||||
cpu_cache_level__fprintf(fp, &ph->env.caches[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void print_pmu_mappings(struct perf_header *ph, int fd __maybe_unused,
|
||||
FILE *fp)
|
||||
{
|
||||
|
@ -1920,6 +2127,68 @@ static int process_auxtrace(struct perf_file_section *section,
|
|||
return err;
|
||||
}
|
||||
|
||||
static int process_cache(struct perf_file_section *section __maybe_unused,
|
||||
struct perf_header *ph __maybe_unused, int fd __maybe_unused,
|
||||
void *data __maybe_unused)
|
||||
{
|
||||
struct cpu_cache_level *caches;
|
||||
u32 cnt, i, version;
|
||||
|
||||
if (readn(fd, &version, sizeof(version)) != sizeof(version))
|
||||
return -1;
|
||||
|
||||
if (ph->needs_swap)
|
||||
version = bswap_32(version);
|
||||
|
||||
if (version != 1)
|
||||
return -1;
|
||||
|
||||
if (readn(fd, &cnt, sizeof(cnt)) != sizeof(cnt))
|
||||
return -1;
|
||||
|
||||
if (ph->needs_swap)
|
||||
cnt = bswap_32(cnt);
|
||||
|
||||
caches = zalloc(sizeof(*caches) * cnt);
|
||||
if (!caches)
|
||||
return -1;
|
||||
|
||||
for (i = 0; i < cnt; i++) {
|
||||
struct cpu_cache_level c;
|
||||
|
||||
#define _R(v) \
|
||||
if (readn(fd, &c.v, sizeof(u32)) != sizeof(u32))\
|
||||
goto out_free_caches; \
|
||||
if (ph->needs_swap) \
|
||||
c.v = bswap_32(c.v); \
|
||||
|
||||
_R(level)
|
||||
_R(line_size)
|
||||
_R(sets)
|
||||
_R(ways)
|
||||
#undef _R
|
||||
|
||||
#define _R(v) \
|
||||
c.v = do_read_string(fd, ph); \
|
||||
if (!c.v) \
|
||||
goto out_free_caches;
|
||||
|
||||
_R(type)
|
||||
_R(size)
|
||||
_R(map)
|
||||
#undef _R
|
||||
|
||||
caches[i] = c;
|
||||
}
|
||||
|
||||
ph->env.caches = caches;
|
||||
ph->env.caches_cnt = cnt;
|
||||
return 0;
|
||||
out_free_caches:
|
||||
free(caches);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct feature_ops {
|
||||
int (*write)(int fd, struct perf_header *h, struct perf_evlist *evlist);
|
||||
void (*print)(struct perf_header *h, int fd, FILE *fp);
|
||||
|
@ -1962,6 +2231,7 @@ static const struct feature_ops feat_ops[HEADER_LAST_FEATURE] = {
|
|||
FEAT_OPP(HEADER_GROUP_DESC, group_desc),
|
||||
FEAT_OPP(HEADER_AUXTRACE, auxtrace),
|
||||
FEAT_OPA(HEADER_STAT, stat),
|
||||
FEAT_OPF(HEADER_CACHE, cache),
|
||||
};
|
||||
|
||||
struct header_print_data {
|
||||
|
|
|
@ -32,6 +32,7 @@ enum {
|
|||
HEADER_GROUP_DESC,
|
||||
HEADER_AUXTRACE,
|
||||
HEADER_STAT,
|
||||
HEADER_CACHE,
|
||||
HEADER_LAST_FEATURE,
|
||||
HEADER_FEAT_BITS = 256,
|
||||
};
|
||||
|
|
Загрузка…
Ссылка в новой задаче