2016-09-22 18:36:38 +03:00
|
|
|
#include <linux/compiler.h>
|
|
|
|
#include <linux/kernel.h>
|
2016-09-22 18:36:48 +03:00
|
|
|
#include <linux/stringify.h>
|
2016-09-22 18:36:38 +03:00
|
|
|
#include "util.h"
|
|
|
|
#include "debug.h"
|
|
|
|
#include "builtin.h"
|
|
|
|
#include <subcmd/parse-options.h>
|
perf c2c: Add record subcommand
Adding c2c record subcommand. It setups options related to HITM
cacheline analysis and calls standard perf record command.
$ sudo perf c2c record -v -- -a
calling: record -W -d --sample-cpu -e cpu/mem-loads,ldlat=30/P -e cpu/mem-stores/P -a
...
It produces perf.data, which is to be reported by perf c2c report, that
comes in following patches.
Details are described in the man page, which is added in one of the
following patches.
Committer notes:
Testing it:
# perf c2c record -a sleep 1
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 5.050 MB perf.data (412 samples) ]
# ls -la perf.data
-rw-------. 1 root root 5301752 Oct 4 13:32 perf.data
# perf evlist
cpu/mem-loads,ldlat=30/P
cpu/mem-stores/P
# perf evlist -v
cpu/mem-loads,ldlat=30/P: type: 4, size: 112, config: 0x1cd, { sample_period, sample_freq }: 4000, sample_type: IP|TID|TIME|ADDR|ID|CPU|PERIOD|DATA_SRC|WEIGHT, read_format: ID, disabled: 1, inherit: 1, mmap: 1, comm: 1, freq: 1, task: 1, precise_ip: 3, mmap_data: 1, sample_id_all: 1, mmap2: 1, comm_exec: 1, { bp_addr, config1 }: 0x1f
cpu/mem-stores/P: type: 4, size: 112, config: 0x82d0, { sample_period, sample_freq }: 4000, sample_type: IP|TID|TIME|ADDR|ID|CPU|PERIOD|DATA_SRC|WEIGHT, read_format: ID, disabled: 1, inherit: 1, freq: 1, precise_ip: 3, sample_id_all: 1
#
# perf report --stdio
<SNIP>
# Total Lost Samples: 14
# Samples: 216 of event 'cpu/mem-loads,ldlat=30/P'
# Event count (approx.): 15207
# Overhead Symbol Shared Object
# ........ ..................................... ............................
10.32% [k] update_blocked_averages [kernel.vmlinux]
3.43% [.] 0x00000000001a2122 qemu-system-x86_64 (deleted)
2.52% [k] enqueue_entity [kernel.vmlinux]
1.88% [.] g_main_context_query libglib-2.0.so.0.4800.2
1.86% [k] __schedule [kernel.vmlinux]
<SNIP>
# Samples: 196 of event 'cpu/mem-stores/P'
# Event count (approx.): 14771346
# Overhead Symbol Shared Object
# ........ ................................... ............................
13.91% [k] intel_idle [kernel.vmlinux]
3.02% [.] 0x00000000022f06ea chrome
2.94% [.] 0x00000000001a1b4c qemu-system-x86_64 (deleted)
2.94% [.] 0x000000000019d8e4 qemu-system-x86_64 (deleted)
2.38% [.] 0x00000000001a1c52 qemu-system-x86_64 (deleted)
<SNIP>
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Andi Kleen <andi@firstfloor.org>
Cc: David Ahern <dsahern@gmail.com>
Cc: Don Zickus <dzickus@redhat.com>
Cc: Joe Mario <jmario@redhat.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Link: http://lkml.kernel.org/r/1474558645-19956-12-git-send-email-jolsa@kernel.org
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2016-09-22 18:36:39 +03:00
|
|
|
#include "mem-events.h"
|
2016-09-22 18:36:40 +03:00
|
|
|
#include "session.h"
|
|
|
|
#include "hist.h"
|
2016-09-22 18:36:48 +03:00
|
|
|
#include "sort.h"
|
2016-09-22 18:36:40 +03:00
|
|
|
#include "tool.h"
|
|
|
|
#include "data.h"
|
2016-09-22 18:36:42 +03:00
|
|
|
#include "sort.h"
|
2016-09-22 18:36:40 +03:00
|
|
|
|
2016-09-22 18:36:41 +03:00
|
|
|
struct c2c_hists {
|
|
|
|
struct hists hists;
|
|
|
|
struct perf_hpp_list list;
|
2016-09-22 18:36:46 +03:00
|
|
|
struct c2c_stats stats;
|
2016-09-22 18:36:41 +03:00
|
|
|
};
|
|
|
|
|
2016-09-22 18:36:44 +03:00
|
|
|
struct c2c_hist_entry {
|
|
|
|
struct c2c_hists *hists;
|
2016-09-22 18:36:46 +03:00
|
|
|
struct c2c_stats stats;
|
2016-09-22 18:36:44 +03:00
|
|
|
/*
|
|
|
|
* must be at the end,
|
|
|
|
* because of its callchain dynamic entry
|
|
|
|
*/
|
|
|
|
struct hist_entry he;
|
|
|
|
};
|
|
|
|
|
2016-09-22 18:36:40 +03:00
|
|
|
struct perf_c2c {
|
2016-09-22 18:36:41 +03:00
|
|
|
struct perf_tool tool;
|
|
|
|
struct c2c_hists hists;
|
2016-09-22 18:36:40 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
static struct perf_c2c c2c;
|
2016-09-22 18:36:38 +03:00
|
|
|
|
2016-09-22 18:36:44 +03:00
|
|
|
static void *c2c_he_zalloc(size_t size)
|
|
|
|
{
|
|
|
|
struct c2c_hist_entry *c2c_he;
|
|
|
|
|
|
|
|
c2c_he = zalloc(size + sizeof(*c2c_he));
|
|
|
|
if (!c2c_he)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
return &c2c_he->he;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void c2c_he_free(void *he)
|
|
|
|
{
|
|
|
|
struct c2c_hist_entry *c2c_he;
|
|
|
|
|
|
|
|
c2c_he = container_of(he, struct c2c_hist_entry, he);
|
|
|
|
if (c2c_he->hists) {
|
|
|
|
hists__delete_entries(&c2c_he->hists->hists);
|
|
|
|
free(c2c_he->hists);
|
|
|
|
}
|
|
|
|
|
|
|
|
free(c2c_he);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct hist_entry_ops c2c_entry_ops = {
|
|
|
|
.new = c2c_he_zalloc,
|
|
|
|
.free = c2c_he_free,
|
|
|
|
};
|
|
|
|
|
2016-09-22 18:36:45 +03:00
|
|
|
static int c2c_hists__init(struct c2c_hists *hists,
|
|
|
|
const char *sort);
|
|
|
|
|
2016-09-22 18:36:46 +03:00
|
|
|
static struct c2c_hists*
|
|
|
|
he__get_c2c_hists(struct hist_entry *he,
|
|
|
|
const char *sort)
|
2016-09-22 18:36:45 +03:00
|
|
|
{
|
|
|
|
struct c2c_hist_entry *c2c_he;
|
|
|
|
struct c2c_hists *hists;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
c2c_he = container_of(he, struct c2c_hist_entry, he);
|
|
|
|
if (c2c_he->hists)
|
2016-09-22 18:36:46 +03:00
|
|
|
return c2c_he->hists;
|
2016-09-22 18:36:45 +03:00
|
|
|
|
|
|
|
hists = c2c_he->hists = zalloc(sizeof(*hists));
|
|
|
|
if (!hists)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
ret = c2c_hists__init(hists, sort);
|
|
|
|
if (ret) {
|
|
|
|
free(hists);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2016-09-22 18:36:46 +03:00
|
|
|
return hists;
|
2016-09-22 18:36:45 +03:00
|
|
|
}
|
|
|
|
|
2016-09-22 18:36:44 +03:00
|
|
|
static int process_sample_event(struct perf_tool *tool __maybe_unused,
|
|
|
|
union perf_event *event,
|
|
|
|
struct perf_sample *sample,
|
|
|
|
struct perf_evsel *evsel __maybe_unused,
|
|
|
|
struct machine *machine)
|
|
|
|
{
|
2016-09-22 18:36:46 +03:00
|
|
|
struct c2c_hists *c2c_hists = &c2c.hists;
|
|
|
|
struct c2c_hist_entry *c2c_he;
|
|
|
|
struct c2c_stats stats = { .nr_entries = 0, };
|
2016-09-22 18:36:44 +03:00
|
|
|
struct hist_entry *he;
|
|
|
|
struct addr_location al;
|
2016-09-22 18:36:45 +03:00
|
|
|
struct mem_info *mi, *mi_dup;
|
2016-09-22 18:36:44 +03:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (machine__resolve(machine, &al, sample) < 0) {
|
|
|
|
pr_debug("problem processing %d event, skipping it.\n",
|
|
|
|
event->header.type);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
mi = sample__resolve_mem(sample, &al);
|
|
|
|
if (mi == NULL)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2016-09-22 18:36:45 +03:00
|
|
|
mi_dup = memdup(mi, sizeof(*mi));
|
|
|
|
if (!mi_dup)
|
|
|
|
goto free_mi;
|
|
|
|
|
2016-09-22 18:36:46 +03:00
|
|
|
c2c_decode_stats(&stats, mi);
|
|
|
|
|
|
|
|
he = hists__add_entry_ops(&c2c_hists->hists, &c2c_entry_ops,
|
2016-09-22 18:36:44 +03:00
|
|
|
&al, NULL, NULL, mi,
|
|
|
|
sample, true);
|
2016-09-22 18:36:45 +03:00
|
|
|
if (he == NULL)
|
|
|
|
goto free_mi_dup;
|
2016-09-22 18:36:44 +03:00
|
|
|
|
2016-09-22 18:36:46 +03:00
|
|
|
c2c_he = container_of(he, struct c2c_hist_entry, he);
|
|
|
|
c2c_add_stats(&c2c_he->stats, &stats);
|
|
|
|
c2c_add_stats(&c2c_hists->stats, &stats);
|
|
|
|
|
|
|
|
hists__inc_nr_samples(&c2c_hists->hists, he->filtered);
|
2016-09-22 18:36:44 +03:00
|
|
|
ret = hist_entry__append_callchain(he, sample);
|
|
|
|
|
2016-09-22 18:36:45 +03:00
|
|
|
if (!ret) {
|
|
|
|
mi = mi_dup;
|
|
|
|
|
|
|
|
mi_dup = memdup(mi, sizeof(*mi));
|
|
|
|
if (!mi_dup)
|
|
|
|
goto free_mi;
|
|
|
|
|
2016-09-22 18:36:46 +03:00
|
|
|
c2c_hists = he__get_c2c_hists(he, "offset");
|
|
|
|
if (!c2c_hists)
|
2016-09-22 18:36:45 +03:00
|
|
|
goto free_mi_dup;
|
|
|
|
|
2016-09-22 18:36:46 +03:00
|
|
|
he = hists__add_entry_ops(&c2c_hists->hists, &c2c_entry_ops,
|
2016-09-22 18:36:45 +03:00
|
|
|
&al, NULL, NULL, mi,
|
|
|
|
sample, true);
|
|
|
|
if (he == NULL)
|
|
|
|
goto free_mi_dup;
|
|
|
|
|
2016-09-22 18:36:46 +03:00
|
|
|
c2c_he = container_of(he, struct c2c_hist_entry, he);
|
|
|
|
c2c_add_stats(&c2c_he->stats, &stats);
|
|
|
|
c2c_add_stats(&c2c_hists->stats, &stats);
|
|
|
|
|
|
|
|
hists__inc_nr_samples(&c2c_hists->hists, he->filtered);
|
2016-09-22 18:36:45 +03:00
|
|
|
ret = hist_entry__append_callchain(he, sample);
|
|
|
|
}
|
|
|
|
|
|
|
|
out:
|
2016-09-22 18:36:44 +03:00
|
|
|
addr_location__put(&al);
|
|
|
|
return ret;
|
2016-09-22 18:36:45 +03:00
|
|
|
|
|
|
|
free_mi_dup:
|
|
|
|
free(mi_dup);
|
|
|
|
free_mi:
|
|
|
|
free(mi);
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto out;
|
2016-09-22 18:36:44 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct perf_c2c c2c = {
|
|
|
|
.tool = {
|
|
|
|
.sample = process_sample_event,
|
|
|
|
.mmap = perf_event__process_mmap,
|
|
|
|
.mmap2 = perf_event__process_mmap2,
|
|
|
|
.comm = perf_event__process_comm,
|
|
|
|
.exit = perf_event__process_exit,
|
|
|
|
.fork = perf_event__process_fork,
|
|
|
|
.lost = perf_event__process_lost,
|
|
|
|
.ordered_events = true,
|
|
|
|
.ordering_requires_timestamps = true,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2016-09-22 18:36:38 +03:00
|
|
|
static const char * const c2c_usage[] = {
|
2016-09-22 18:36:40 +03:00
|
|
|
"perf c2c {record|report}",
|
2016-09-22 18:36:38 +03:00
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
2016-09-22 18:36:40 +03:00
|
|
|
static const char * const __usage_report[] = {
|
|
|
|
"perf c2c report",
|
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
|
|
|
static const char * const *report_c2c_usage = __usage_report;
|
|
|
|
|
2016-09-22 18:36:41 +03:00
|
|
|
#define C2C_HEADER_MAX 2
|
|
|
|
|
|
|
|
struct c2c_header {
|
|
|
|
struct {
|
|
|
|
const char *text;
|
|
|
|
int span;
|
|
|
|
} line[C2C_HEADER_MAX];
|
|
|
|
};
|
|
|
|
|
|
|
|
struct c2c_dimension {
|
|
|
|
struct c2c_header header;
|
|
|
|
const char *name;
|
|
|
|
int width;
|
2016-09-22 18:36:42 +03:00
|
|
|
struct sort_entry *se;
|
2016-09-22 18:36:41 +03:00
|
|
|
|
|
|
|
int64_t (*cmp)(struct perf_hpp_fmt *fmt,
|
|
|
|
struct hist_entry *, struct hist_entry *);
|
|
|
|
int (*entry)(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
|
|
|
|
struct hist_entry *he);
|
|
|
|
int (*color)(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
|
|
|
|
struct hist_entry *he);
|
|
|
|
};
|
|
|
|
|
|
|
|
struct c2c_fmt {
|
|
|
|
struct perf_hpp_fmt fmt;
|
|
|
|
struct c2c_dimension *dim;
|
|
|
|
};
|
|
|
|
|
|
|
|
static int c2c_width(struct perf_hpp_fmt *fmt,
|
|
|
|
struct perf_hpp *hpp __maybe_unused,
|
|
|
|
struct hists *hists __maybe_unused)
|
|
|
|
{
|
|
|
|
struct c2c_fmt *c2c_fmt;
|
2016-09-22 18:36:42 +03:00
|
|
|
struct c2c_dimension *dim;
|
2016-09-22 18:36:41 +03:00
|
|
|
|
|
|
|
c2c_fmt = container_of(fmt, struct c2c_fmt, fmt);
|
2016-09-22 18:36:42 +03:00
|
|
|
dim = c2c_fmt->dim;
|
|
|
|
|
|
|
|
return dim->se ? hists__col_len(hists, dim->se->se_width_idx) :
|
|
|
|
c2c_fmt->dim->width;
|
2016-09-22 18:36:41 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static int c2c_header(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
|
2016-09-22 18:36:42 +03:00
|
|
|
struct hists *hists, int line, int *span)
|
2016-09-22 18:36:41 +03:00
|
|
|
{
|
2016-09-22 18:36:42 +03:00
|
|
|
struct perf_hpp_list *hpp_list = hists->hpp_list;
|
2016-09-22 18:36:41 +03:00
|
|
|
struct c2c_fmt *c2c_fmt;
|
|
|
|
struct c2c_dimension *dim;
|
2016-09-22 18:36:42 +03:00
|
|
|
const char *text = NULL;
|
|
|
|
int width = c2c_width(fmt, hpp, hists);
|
2016-09-22 18:36:41 +03:00
|
|
|
|
|
|
|
c2c_fmt = container_of(fmt, struct c2c_fmt, fmt);
|
|
|
|
dim = c2c_fmt->dim;
|
|
|
|
|
2016-09-22 18:36:42 +03:00
|
|
|
if (dim->se) {
|
|
|
|
text = dim->header.line[line].text;
|
|
|
|
/* Use the last line from sort_entry if not defined. */
|
|
|
|
if (!text && (line == hpp_list->nr_header_lines - 1))
|
|
|
|
text = dim->se->se_header;
|
2016-09-22 18:36:41 +03:00
|
|
|
} else {
|
2016-09-22 18:36:42 +03:00
|
|
|
text = dim->header.line[line].text;
|
|
|
|
|
|
|
|
if (*span) {
|
|
|
|
(*span)--;
|
|
|
|
return 0;
|
|
|
|
} else {
|
|
|
|
*span = dim->header.line[line].span;
|
|
|
|
}
|
2016-09-22 18:36:41 +03:00
|
|
|
}
|
|
|
|
|
2016-09-22 18:36:42 +03:00
|
|
|
if (text == NULL)
|
|
|
|
text = "";
|
|
|
|
|
|
|
|
return scnprintf(hpp->buf, hpp->size, "%*s", width, text);
|
2016-09-22 18:36:41 +03:00
|
|
|
}
|
|
|
|
|
2016-09-22 18:36:48 +03:00
|
|
|
#define HEX_STR(__s, __v) \
|
|
|
|
({ \
|
|
|
|
scnprintf(__s, sizeof(__s), "0x%" PRIx64, __v); \
|
|
|
|
__s; \
|
|
|
|
})
|
|
|
|
|
|
|
|
static int64_t
|
|
|
|
dcacheline_cmp(struct perf_hpp_fmt *fmt __maybe_unused,
|
|
|
|
struct hist_entry *left, struct hist_entry *right)
|
|
|
|
{
|
|
|
|
return sort__dcacheline_cmp(left, right);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dcacheline_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
|
|
|
|
struct hist_entry *he)
|
|
|
|
{
|
|
|
|
uint64_t addr = 0;
|
|
|
|
int width = c2c_width(fmt, hpp, he->hists);
|
|
|
|
char buf[20];
|
|
|
|
|
|
|
|
if (he->mem_info)
|
|
|
|
addr = cl_address(he->mem_info->daddr.addr);
|
|
|
|
|
|
|
|
return scnprintf(hpp->buf, hpp->size, "%*s", width, HEX_STR(buf, addr));
|
|
|
|
}
|
|
|
|
|
2016-04-29 15:37:06 +03:00
|
|
|
static int offset_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
|
|
|
|
struct hist_entry *he)
|
|
|
|
{
|
|
|
|
uint64_t addr = 0;
|
|
|
|
int width = c2c_width(fmt, hpp, he->hists);
|
|
|
|
char buf[20];
|
|
|
|
|
|
|
|
if (he->mem_info)
|
|
|
|
addr = cl_offset(he->mem_info->daddr.al_addr);
|
|
|
|
|
|
|
|
return scnprintf(hpp->buf, hpp->size, "%*s", width, HEX_STR(buf, addr));
|
|
|
|
}
|
|
|
|
|
|
|
|
static int64_t
|
|
|
|
offset_cmp(struct perf_hpp_fmt *fmt __maybe_unused,
|
|
|
|
struct hist_entry *left, struct hist_entry *right)
|
|
|
|
{
|
|
|
|
uint64_t l = 0, r = 0;
|
|
|
|
|
|
|
|
if (left->mem_info)
|
|
|
|
l = cl_offset(left->mem_info->daddr.addr);
|
|
|
|
if (right->mem_info)
|
|
|
|
r = cl_offset(right->mem_info->daddr.addr);
|
|
|
|
|
|
|
|
return (int64_t)(r - l);
|
|
|
|
}
|
|
|
|
|
2016-05-03 22:48:56 +03:00
|
|
|
static int
|
|
|
|
iaddr_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
|
|
|
|
struct hist_entry *he)
|
|
|
|
{
|
|
|
|
uint64_t addr = 0;
|
|
|
|
int width = c2c_width(fmt, hpp, he->hists);
|
|
|
|
char buf[20];
|
|
|
|
|
|
|
|
if (he->mem_info)
|
|
|
|
addr = he->mem_info->iaddr.addr;
|
|
|
|
|
|
|
|
return scnprintf(hpp->buf, hpp->size, "%*s", width, HEX_STR(buf, addr));
|
|
|
|
}
|
|
|
|
|
|
|
|
static int64_t
|
|
|
|
iaddr_cmp(struct perf_hpp_fmt *fmt __maybe_unused,
|
|
|
|
struct hist_entry *left, struct hist_entry *right)
|
|
|
|
{
|
|
|
|
return sort__iaddr_cmp(left, right);
|
|
|
|
}
|
|
|
|
|
2016-05-23 17:20:14 +03:00
|
|
|
static int
|
|
|
|
tot_hitm_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
|
|
|
|
struct hist_entry *he)
|
|
|
|
{
|
|
|
|
struct c2c_hist_entry *c2c_he;
|
|
|
|
int width = c2c_width(fmt, hpp, he->hists);
|
|
|
|
unsigned int tot_hitm;
|
|
|
|
|
|
|
|
c2c_he = container_of(he, struct c2c_hist_entry, he);
|
|
|
|
tot_hitm = c2c_he->stats.lcl_hitm + c2c_he->stats.rmt_hitm;
|
|
|
|
|
|
|
|
return scnprintf(hpp->buf, hpp->size, "%*u", width, tot_hitm);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int64_t
|
|
|
|
tot_hitm_cmp(struct perf_hpp_fmt *fmt __maybe_unused,
|
|
|
|
struct hist_entry *left, struct hist_entry *right)
|
|
|
|
{
|
|
|
|
struct c2c_hist_entry *c2c_left;
|
|
|
|
struct c2c_hist_entry *c2c_right;
|
|
|
|
unsigned int tot_hitm_left;
|
|
|
|
unsigned int tot_hitm_right;
|
|
|
|
|
|
|
|
c2c_left = container_of(left, struct c2c_hist_entry, he);
|
|
|
|
c2c_right = container_of(right, struct c2c_hist_entry, he);
|
|
|
|
|
|
|
|
tot_hitm_left = c2c_left->stats.lcl_hitm + c2c_left->stats.rmt_hitm;
|
|
|
|
tot_hitm_right = c2c_right->stats.lcl_hitm + c2c_right->stats.rmt_hitm;
|
|
|
|
|
|
|
|
return tot_hitm_left - tot_hitm_right;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define STAT_FN_ENTRY(__f) \
|
|
|
|
static int \
|
|
|
|
__f ## _entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, \
|
|
|
|
struct hist_entry *he) \
|
|
|
|
{ \
|
|
|
|
struct c2c_hist_entry *c2c_he; \
|
|
|
|
int width = c2c_width(fmt, hpp, he->hists); \
|
|
|
|
\
|
|
|
|
c2c_he = container_of(he, struct c2c_hist_entry, he); \
|
|
|
|
return scnprintf(hpp->buf, hpp->size, "%*u", width, \
|
|
|
|
c2c_he->stats.__f); \
|
|
|
|
}
|
|
|
|
|
|
|
|
#define STAT_FN_CMP(__f) \
|
|
|
|
static int64_t \
|
|
|
|
__f ## _cmp(struct perf_hpp_fmt *fmt __maybe_unused, \
|
|
|
|
struct hist_entry *left, struct hist_entry *right) \
|
|
|
|
{ \
|
|
|
|
struct c2c_hist_entry *c2c_left, *c2c_right; \
|
|
|
|
\
|
|
|
|
c2c_left = container_of(left, struct c2c_hist_entry, he); \
|
|
|
|
c2c_right = container_of(right, struct c2c_hist_entry, he); \
|
|
|
|
return c2c_left->stats.__f - c2c_right->stats.__f; \
|
|
|
|
}
|
|
|
|
|
|
|
|
#define STAT_FN(__f) \
|
|
|
|
STAT_FN_ENTRY(__f) \
|
|
|
|
STAT_FN_CMP(__f)
|
|
|
|
|
|
|
|
STAT_FN(rmt_hitm)
|
|
|
|
STAT_FN(lcl_hitm)
|
2016-05-04 11:10:11 +03:00
|
|
|
STAT_FN(store)
|
|
|
|
STAT_FN(st_l1hit)
|
|
|
|
STAT_FN(st_l1miss)
|
2016-05-04 11:18:24 +03:00
|
|
|
STAT_FN(ld_fbhit)
|
|
|
|
STAT_FN(ld_l1hit)
|
|
|
|
STAT_FN(ld_l2hit)
|
2016-05-04 11:27:51 +03:00
|
|
|
STAT_FN(ld_llchit)
|
|
|
|
STAT_FN(rmt_hit)
|
2016-05-23 17:20:14 +03:00
|
|
|
|
2016-05-19 11:10:51 +03:00
|
|
|
static uint64_t llc_miss(struct c2c_stats *stats)
|
|
|
|
{
|
|
|
|
uint64_t llcmiss;
|
|
|
|
|
|
|
|
llcmiss = stats->lcl_dram +
|
|
|
|
stats->rmt_dram +
|
|
|
|
stats->rmt_hitm +
|
|
|
|
stats->rmt_hit;
|
|
|
|
|
|
|
|
return llcmiss;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
ld_llcmiss_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
|
|
|
|
struct hist_entry *he)
|
|
|
|
{
|
|
|
|
struct c2c_hist_entry *c2c_he;
|
|
|
|
int width = c2c_width(fmt, hpp, he->hists);
|
|
|
|
|
|
|
|
c2c_he = container_of(he, struct c2c_hist_entry, he);
|
|
|
|
|
|
|
|
return scnprintf(hpp->buf, hpp->size, "%*lu", width,
|
|
|
|
llc_miss(&c2c_he->stats));
|
|
|
|
}
|
|
|
|
|
|
|
|
static int64_t
|
|
|
|
ld_llcmiss_cmp(struct perf_hpp_fmt *fmt __maybe_unused,
|
|
|
|
struct hist_entry *left, struct hist_entry *right)
|
|
|
|
{
|
|
|
|
struct c2c_hist_entry *c2c_left;
|
|
|
|
struct c2c_hist_entry *c2c_right;
|
|
|
|
|
|
|
|
c2c_left = container_of(left, struct c2c_hist_entry, he);
|
|
|
|
c2c_right = container_of(right, struct c2c_hist_entry, he);
|
|
|
|
|
|
|
|
return llc_miss(&c2c_left->stats) - llc_miss(&c2c_right->stats);
|
|
|
|
}
|
|
|
|
|
2016-05-04 11:35:29 +03:00
|
|
|
static uint64_t total_records(struct c2c_stats *stats)
|
|
|
|
{
|
|
|
|
uint64_t lclmiss, ldcnt, total;
|
|
|
|
|
|
|
|
lclmiss = stats->lcl_dram +
|
|
|
|
stats->rmt_dram +
|
|
|
|
stats->rmt_hitm +
|
|
|
|
stats->rmt_hit;
|
|
|
|
|
|
|
|
ldcnt = lclmiss +
|
|
|
|
stats->ld_fbhit +
|
|
|
|
stats->ld_l1hit +
|
|
|
|
stats->ld_l2hit +
|
|
|
|
stats->ld_llchit +
|
|
|
|
stats->lcl_hitm;
|
|
|
|
|
|
|
|
total = ldcnt +
|
|
|
|
stats->st_l1hit +
|
|
|
|
stats->st_l1miss;
|
|
|
|
|
|
|
|
return total;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
tot_recs_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
|
|
|
|
struct hist_entry *he)
|
|
|
|
{
|
|
|
|
struct c2c_hist_entry *c2c_he;
|
|
|
|
int width = c2c_width(fmt, hpp, he->hists);
|
|
|
|
uint64_t tot_recs;
|
|
|
|
|
|
|
|
c2c_he = container_of(he, struct c2c_hist_entry, he);
|
|
|
|
tot_recs = total_records(&c2c_he->stats);
|
|
|
|
|
|
|
|
return scnprintf(hpp->buf, hpp->size, "%*" PRIu64, width, tot_recs);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int64_t
|
|
|
|
tot_recs_cmp(struct perf_hpp_fmt *fmt __maybe_unused,
|
|
|
|
struct hist_entry *left, struct hist_entry *right)
|
|
|
|
{
|
|
|
|
struct c2c_hist_entry *c2c_left;
|
|
|
|
struct c2c_hist_entry *c2c_right;
|
|
|
|
uint64_t tot_recs_left;
|
|
|
|
uint64_t tot_recs_right;
|
|
|
|
|
|
|
|
c2c_left = container_of(left, struct c2c_hist_entry, he);
|
|
|
|
c2c_right = container_of(right, struct c2c_hist_entry, he);
|
|
|
|
|
|
|
|
tot_recs_left = total_records(&c2c_left->stats);
|
|
|
|
tot_recs_right = total_records(&c2c_right->stats);
|
|
|
|
|
|
|
|
return tot_recs_left - tot_recs_right;
|
|
|
|
}
|
|
|
|
|
2016-09-22 18:36:47 +03:00
|
|
|
#define HEADER_LOW(__h) \
|
|
|
|
{ \
|
|
|
|
.line[1] = { \
|
|
|
|
.text = __h, \
|
|
|
|
}, \
|
|
|
|
}
|
|
|
|
|
|
|
|
#define HEADER_BOTH(__h0, __h1) \
|
|
|
|
{ \
|
|
|
|
.line[0] = { \
|
|
|
|
.text = __h0, \
|
|
|
|
}, \
|
|
|
|
.line[1] = { \
|
|
|
|
.text = __h1, \
|
|
|
|
}, \
|
|
|
|
}
|
|
|
|
|
|
|
|
#define HEADER_SPAN(__h0, __h1, __s) \
|
|
|
|
{ \
|
|
|
|
.line[0] = { \
|
|
|
|
.text = __h0, \
|
|
|
|
.span = __s, \
|
|
|
|
}, \
|
|
|
|
.line[1] = { \
|
|
|
|
.text = __h1, \
|
|
|
|
}, \
|
|
|
|
}
|
|
|
|
|
|
|
|
#define HEADER_SPAN_LOW(__h) \
|
|
|
|
{ \
|
|
|
|
.line[1] = { \
|
|
|
|
.text = __h, \
|
|
|
|
}, \
|
|
|
|
}
|
|
|
|
|
2016-09-22 18:36:48 +03:00
|
|
|
static struct c2c_dimension dim_dcacheline = {
|
|
|
|
.header = HEADER_LOW("Cacheline"),
|
|
|
|
.name = "dcacheline",
|
|
|
|
.cmp = dcacheline_cmp,
|
|
|
|
.entry = dcacheline_entry,
|
|
|
|
.width = 18,
|
|
|
|
};
|
|
|
|
|
2016-04-29 15:37:06 +03:00
|
|
|
static struct c2c_dimension dim_offset = {
|
|
|
|
.header = HEADER_BOTH("Data address", "Offset"),
|
|
|
|
.name = "offset",
|
|
|
|
.cmp = offset_cmp,
|
|
|
|
.entry = offset_entry,
|
|
|
|
.width = 18,
|
|
|
|
};
|
|
|
|
|
2016-05-03 22:48:56 +03:00
|
|
|
static struct c2c_dimension dim_iaddr = {
|
|
|
|
.header = HEADER_LOW("Code address"),
|
|
|
|
.name = "iaddr",
|
|
|
|
.cmp = iaddr_cmp,
|
|
|
|
.entry = iaddr_entry,
|
|
|
|
.width = 18,
|
|
|
|
};
|
|
|
|
|
2016-05-23 17:20:14 +03:00
|
|
|
static struct c2c_dimension dim_tot_hitm = {
|
|
|
|
.header = HEADER_SPAN("----- LLC Load Hitm -----", "Total", 2),
|
|
|
|
.name = "tot_hitm",
|
|
|
|
.cmp = tot_hitm_cmp,
|
|
|
|
.entry = tot_hitm_entry,
|
|
|
|
.width = 7,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct c2c_dimension dim_lcl_hitm = {
|
|
|
|
.header = HEADER_SPAN_LOW("Lcl"),
|
|
|
|
.name = "lcl_hitm",
|
|
|
|
.cmp = lcl_hitm_cmp,
|
|
|
|
.entry = lcl_hitm_entry,
|
|
|
|
.width = 7,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct c2c_dimension dim_rmt_hitm = {
|
|
|
|
.header = HEADER_SPAN_LOW("Rmt"),
|
|
|
|
.name = "rmt_hitm",
|
|
|
|
.cmp = rmt_hitm_cmp,
|
|
|
|
.entry = rmt_hitm_entry,
|
|
|
|
.width = 7,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct c2c_dimension dim_cl_rmt_hitm = {
|
|
|
|
.header = HEADER_SPAN("----- HITM -----", "Rmt", 1),
|
|
|
|
.name = "cl_rmt_hitm",
|
|
|
|
.cmp = rmt_hitm_cmp,
|
|
|
|
.entry = rmt_hitm_entry,
|
|
|
|
.width = 7,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct c2c_dimension dim_cl_lcl_hitm = {
|
|
|
|
.header = HEADER_SPAN_LOW("Lcl"),
|
|
|
|
.name = "cl_lcl_hitm",
|
|
|
|
.cmp = lcl_hitm_cmp,
|
|
|
|
.entry = lcl_hitm_entry,
|
|
|
|
.width = 7,
|
|
|
|
};
|
|
|
|
|
2016-05-04 11:10:11 +03:00
|
|
|
static struct c2c_dimension dim_stores = {
|
|
|
|
.header = HEADER_SPAN("---- Store Reference ----", "Total", 2),
|
|
|
|
.name = "stores",
|
|
|
|
.cmp = store_cmp,
|
|
|
|
.entry = store_entry,
|
|
|
|
.width = 7,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct c2c_dimension dim_stores_l1hit = {
|
|
|
|
.header = HEADER_SPAN_LOW("L1Hit"),
|
|
|
|
.name = "stores_l1hit",
|
|
|
|
.cmp = st_l1hit_cmp,
|
|
|
|
.entry = st_l1hit_entry,
|
|
|
|
.width = 7,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct c2c_dimension dim_stores_l1miss = {
|
|
|
|
.header = HEADER_SPAN_LOW("L1Miss"),
|
|
|
|
.name = "stores_l1miss",
|
|
|
|
.cmp = st_l1miss_cmp,
|
|
|
|
.entry = st_l1miss_entry,
|
|
|
|
.width = 7,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct c2c_dimension dim_cl_stores_l1hit = {
|
|
|
|
.header = HEADER_SPAN("-- Store Refs --", "L1 Hit", 1),
|
|
|
|
.name = "cl_stores_l1hit",
|
|
|
|
.cmp = st_l1hit_cmp,
|
|
|
|
.entry = st_l1hit_entry,
|
|
|
|
.width = 7,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct c2c_dimension dim_cl_stores_l1miss = {
|
|
|
|
.header = HEADER_SPAN_LOW("L1 Miss"),
|
|
|
|
.name = "cl_stores_l1miss",
|
|
|
|
.cmp = st_l1miss_cmp,
|
|
|
|
.entry = st_l1miss_entry,
|
|
|
|
.width = 7,
|
|
|
|
};
|
|
|
|
|
2016-05-04 11:18:24 +03:00
|
|
|
static struct c2c_dimension dim_ld_fbhit = {
|
|
|
|
.header = HEADER_SPAN("----- Core Load Hit -----", "FB", 2),
|
|
|
|
.name = "ld_fbhit",
|
|
|
|
.cmp = ld_fbhit_cmp,
|
|
|
|
.entry = ld_fbhit_entry,
|
|
|
|
.width = 7,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct c2c_dimension dim_ld_l1hit = {
|
|
|
|
.header = HEADER_SPAN_LOW("L1"),
|
|
|
|
.name = "ld_l1hit",
|
|
|
|
.cmp = ld_l1hit_cmp,
|
|
|
|
.entry = ld_l1hit_entry,
|
|
|
|
.width = 7,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct c2c_dimension dim_ld_l2hit = {
|
|
|
|
.header = HEADER_SPAN_LOW("L2"),
|
|
|
|
.name = "ld_l2hit",
|
|
|
|
.cmp = ld_l2hit_cmp,
|
|
|
|
.entry = ld_l2hit_entry,
|
|
|
|
.width = 7,
|
|
|
|
};
|
|
|
|
|
2016-05-04 11:27:51 +03:00
|
|
|
static struct c2c_dimension dim_ld_llchit = {
|
|
|
|
.header = HEADER_SPAN("-- LLC Load Hit --", "Llc", 1),
|
|
|
|
.name = "ld_lclhit",
|
|
|
|
.cmp = ld_llchit_cmp,
|
|
|
|
.entry = ld_llchit_entry,
|
|
|
|
.width = 8,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct c2c_dimension dim_ld_rmthit = {
|
|
|
|
.header = HEADER_SPAN_LOW("Rmt"),
|
|
|
|
.name = "ld_rmthit",
|
|
|
|
.cmp = rmt_hit_cmp,
|
|
|
|
.entry = rmt_hit_entry,
|
|
|
|
.width = 8,
|
|
|
|
};
|
|
|
|
|
2016-05-19 11:10:51 +03:00
|
|
|
static struct c2c_dimension dim_ld_llcmiss = {
|
|
|
|
.header = HEADER_BOTH("LLC", "Ld Miss"),
|
|
|
|
.name = "ld_llcmiss",
|
|
|
|
.cmp = ld_llcmiss_cmp,
|
|
|
|
.entry = ld_llcmiss_entry,
|
|
|
|
.width = 7,
|
|
|
|
};
|
|
|
|
|
2016-05-04 11:35:29 +03:00
|
|
|
static struct c2c_dimension dim_tot_recs = {
|
|
|
|
.header = HEADER_BOTH("Total", "records"),
|
|
|
|
.name = "tot_recs",
|
|
|
|
.cmp = tot_recs_cmp,
|
|
|
|
.entry = tot_recs_entry,
|
|
|
|
.width = 7,
|
|
|
|
};
|
|
|
|
|
2016-09-22 18:36:41 +03:00
|
|
|
static struct c2c_dimension *dimensions[] = {
|
2016-09-22 18:36:48 +03:00
|
|
|
&dim_dcacheline,
|
2016-04-29 15:37:06 +03:00
|
|
|
&dim_offset,
|
2016-05-03 22:48:56 +03:00
|
|
|
&dim_iaddr,
|
2016-05-23 17:20:14 +03:00
|
|
|
&dim_tot_hitm,
|
|
|
|
&dim_lcl_hitm,
|
|
|
|
&dim_rmt_hitm,
|
|
|
|
&dim_cl_lcl_hitm,
|
|
|
|
&dim_cl_rmt_hitm,
|
2016-05-04 11:10:11 +03:00
|
|
|
&dim_stores,
|
|
|
|
&dim_stores_l1hit,
|
|
|
|
&dim_stores_l1miss,
|
|
|
|
&dim_cl_stores_l1hit,
|
|
|
|
&dim_cl_stores_l1miss,
|
2016-05-04 11:18:24 +03:00
|
|
|
&dim_ld_fbhit,
|
|
|
|
&dim_ld_l1hit,
|
|
|
|
&dim_ld_l2hit,
|
2016-05-04 11:27:51 +03:00
|
|
|
&dim_ld_llchit,
|
|
|
|
&dim_ld_rmthit,
|
2016-05-19 11:10:51 +03:00
|
|
|
&dim_ld_llcmiss,
|
2016-05-04 11:35:29 +03:00
|
|
|
&dim_tot_recs,
|
2016-09-22 18:36:41 +03:00
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
|
|
|
static void fmt_free(struct perf_hpp_fmt *fmt)
|
|
|
|
{
|
|
|
|
struct c2c_fmt *c2c_fmt;
|
|
|
|
|
|
|
|
c2c_fmt = container_of(fmt, struct c2c_fmt, fmt);
|
|
|
|
free(c2c_fmt);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool fmt_equal(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b)
|
|
|
|
{
|
|
|
|
struct c2c_fmt *c2c_a = container_of(a, struct c2c_fmt, fmt);
|
|
|
|
struct c2c_fmt *c2c_b = container_of(b, struct c2c_fmt, fmt);
|
|
|
|
|
|
|
|
return c2c_a->dim == c2c_b->dim;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct c2c_dimension *get_dimension(const char *name)
|
|
|
|
{
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
for (i = 0; dimensions[i]; i++) {
|
|
|
|
struct c2c_dimension *dim = dimensions[i];
|
|
|
|
|
|
|
|
if (!strcmp(dim->name, name))
|
|
|
|
return dim;
|
|
|
|
};
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2016-09-22 18:36:42 +03:00
|
|
|
static int c2c_se_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
|
|
|
|
struct hist_entry *he)
|
|
|
|
{
|
|
|
|
struct c2c_fmt *c2c_fmt = container_of(fmt, struct c2c_fmt, fmt);
|
|
|
|
struct c2c_dimension *dim = c2c_fmt->dim;
|
|
|
|
size_t len = fmt->user_len;
|
|
|
|
|
|
|
|
if (!len)
|
|
|
|
len = hists__col_len(he->hists, dim->se->se_width_idx);
|
|
|
|
|
|
|
|
return dim->se->se_snprintf(he, hpp->buf, hpp->size, len);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int64_t c2c_se_cmp(struct perf_hpp_fmt *fmt,
|
|
|
|
struct hist_entry *a, struct hist_entry *b)
|
|
|
|
{
|
|
|
|
struct c2c_fmt *c2c_fmt = container_of(fmt, struct c2c_fmt, fmt);
|
|
|
|
struct c2c_dimension *dim = c2c_fmt->dim;
|
|
|
|
|
|
|
|
return dim->se->se_cmp(a, b);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int64_t c2c_se_collapse(struct perf_hpp_fmt *fmt,
|
|
|
|
struct hist_entry *a, struct hist_entry *b)
|
|
|
|
{
|
|
|
|
struct c2c_fmt *c2c_fmt = container_of(fmt, struct c2c_fmt, fmt);
|
|
|
|
struct c2c_dimension *dim = c2c_fmt->dim;
|
|
|
|
int64_t (*collapse_fn)(struct hist_entry *, struct hist_entry *);
|
|
|
|
|
|
|
|
collapse_fn = dim->se->se_collapse ?: dim->se->se_cmp;
|
|
|
|
return collapse_fn(a, b);
|
|
|
|
}
|
|
|
|
|
2016-09-22 18:36:41 +03:00
|
|
|
static struct c2c_fmt *get_format(const char *name)
|
|
|
|
{
|
|
|
|
struct c2c_dimension *dim = get_dimension(name);
|
|
|
|
struct c2c_fmt *c2c_fmt;
|
|
|
|
struct perf_hpp_fmt *fmt;
|
|
|
|
|
|
|
|
if (!dim)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
c2c_fmt = zalloc(sizeof(*c2c_fmt));
|
|
|
|
if (!c2c_fmt)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
c2c_fmt->dim = dim;
|
|
|
|
|
|
|
|
fmt = &c2c_fmt->fmt;
|
|
|
|
INIT_LIST_HEAD(&fmt->list);
|
|
|
|
INIT_LIST_HEAD(&fmt->sort_list);
|
|
|
|
|
2016-09-22 18:36:42 +03:00
|
|
|
fmt->cmp = dim->se ? c2c_se_cmp : dim->cmp;
|
|
|
|
fmt->sort = dim->se ? c2c_se_cmp : dim->cmp;
|
|
|
|
fmt->entry = dim->se ? c2c_se_entry : dim->entry;
|
2016-09-22 18:36:41 +03:00
|
|
|
fmt->header = c2c_header;
|
|
|
|
fmt->width = c2c_width;
|
2016-09-22 18:36:42 +03:00
|
|
|
fmt->collapse = dim->se ? c2c_se_collapse : dim->cmp;
|
2016-09-22 18:36:41 +03:00
|
|
|
fmt->equal = fmt_equal;
|
|
|
|
fmt->free = fmt_free;
|
|
|
|
|
|
|
|
return c2c_fmt;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int c2c_hists__init_output(struct perf_hpp_list *hpp_list, char *name)
|
|
|
|
{
|
|
|
|
struct c2c_fmt *c2c_fmt = get_format(name);
|
|
|
|
|
2016-09-22 18:36:43 +03:00
|
|
|
if (!c2c_fmt) {
|
|
|
|
reset_dimensions();
|
|
|
|
return output_field_add(hpp_list, name);
|
|
|
|
}
|
2016-09-22 18:36:41 +03:00
|
|
|
|
|
|
|
perf_hpp_list__column_register(hpp_list, &c2c_fmt->fmt);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int c2c_hists__init_sort(struct perf_hpp_list *hpp_list, char *name)
|
|
|
|
{
|
|
|
|
struct c2c_fmt *c2c_fmt = get_format(name);
|
|
|
|
|
2016-09-22 18:36:43 +03:00
|
|
|
if (!c2c_fmt) {
|
|
|
|
reset_dimensions();
|
|
|
|
return sort_dimension__add(hpp_list, name, NULL, 0);
|
|
|
|
}
|
2016-09-22 18:36:41 +03:00
|
|
|
|
|
|
|
perf_hpp_list__register_sort_field(hpp_list, &c2c_fmt->fmt);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define PARSE_LIST(_list, _fn) \
|
|
|
|
do { \
|
|
|
|
char *tmp, *tok; \
|
|
|
|
ret = 0; \
|
|
|
|
\
|
|
|
|
if (!_list) \
|
|
|
|
break; \
|
|
|
|
\
|
|
|
|
for (tok = strtok_r((char *)_list, ", ", &tmp); \
|
|
|
|
tok; tok = strtok_r(NULL, ", ", &tmp)) { \
|
|
|
|
ret = _fn(hpp_list, tok); \
|
|
|
|
if (ret == -EINVAL) { \
|
|
|
|
error("Invalid --fields key: `%s'", tok); \
|
|
|
|
break; \
|
|
|
|
} else if (ret == -ESRCH) { \
|
|
|
|
error("Unknown --fields key: `%s'", tok); \
|
|
|
|
break; \
|
|
|
|
} \
|
|
|
|
} \
|
|
|
|
} while (0)
|
|
|
|
|
|
|
|
static int hpp_list__parse(struct perf_hpp_list *hpp_list,
|
|
|
|
const char *output_,
|
|
|
|
const char *sort_)
|
|
|
|
{
|
|
|
|
char *output = output_ ? strdup(output_) : NULL;
|
|
|
|
char *sort = sort_ ? strdup(sort_) : NULL;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
PARSE_LIST(output, c2c_hists__init_output);
|
|
|
|
PARSE_LIST(sort, c2c_hists__init_sort);
|
|
|
|
|
|
|
|
/* copy sort keys to output fields */
|
|
|
|
perf_hpp__setup_output_field(hpp_list);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We dont need other sorting keys other than those
|
|
|
|
* we already specified. It also really slows down
|
|
|
|
* the processing a lot with big number of output
|
|
|
|
* fields, so switching this off for c2c.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
/* and then copy output fields to sort keys */
|
|
|
|
perf_hpp__append_sort_keys(&hists->list);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
free(output);
|
|
|
|
free(sort);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int c2c_hists__init(struct c2c_hists *hists,
|
|
|
|
const char *sort)
|
|
|
|
{
|
|
|
|
__hists__init(&hists->hists, &hists->list);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Initialize only with sort fields, we need to resort
|
|
|
|
* later anyway, and that's where we add output fields
|
|
|
|
* as well.
|
|
|
|
*/
|
|
|
|
perf_hpp_list__init(&hists->list);
|
|
|
|
|
|
|
|
return hpp_list__parse(&hists->list, NULL, sort);
|
|
|
|
}
|
|
|
|
|
|
|
|
__maybe_unused
|
|
|
|
static int c2c_hists__reinit(struct c2c_hists *c2c_hists,
|
|
|
|
const char *output,
|
|
|
|
const char *sort)
|
|
|
|
{
|
|
|
|
perf_hpp__reset_output_field(&c2c_hists->list);
|
|
|
|
return hpp_list__parse(&c2c_hists->list, output, sort);
|
|
|
|
}
|
|
|
|
|
2016-09-22 18:36:45 +03:00
|
|
|
static int filter_cb(struct hist_entry *he __maybe_unused)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int resort_cl_cb(struct hist_entry *he)
|
|
|
|
{
|
|
|
|
struct c2c_hist_entry *c2c_he;
|
|
|
|
struct c2c_hists *c2c_hists;
|
|
|
|
|
|
|
|
c2c_he = container_of(he, struct c2c_hist_entry, he);
|
|
|
|
c2c_hists = c2c_he->hists;
|
|
|
|
|
|
|
|
if (c2c_hists) {
|
|
|
|
hists__collapse_resort(&c2c_hists->hists, NULL);
|
|
|
|
hists__output_resort_cb(&c2c_hists->hists, NULL, filter_cb);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-09-22 18:36:40 +03:00
|
|
|
static int perf_c2c__report(int argc, const char **argv)
|
|
|
|
{
|
|
|
|
struct perf_session *session;
|
2016-09-22 18:36:44 +03:00
|
|
|
struct ui_progress prog;
|
2016-09-22 18:36:40 +03:00
|
|
|
struct perf_data_file file = {
|
|
|
|
.mode = PERF_DATA_MODE_READ,
|
|
|
|
};
|
|
|
|
const struct option c2c_options[] = {
|
|
|
|
OPT_STRING('k', "vmlinux", &symbol_conf.vmlinux_name,
|
|
|
|
"file", "vmlinux pathname"),
|
|
|
|
OPT_INCR('v', "verbose", &verbose,
|
|
|
|
"be more verbose (show counter open errors, etc)"),
|
|
|
|
OPT_STRING('i', "input", &input_name, "file",
|
|
|
|
"the input file to process"),
|
|
|
|
OPT_END()
|
|
|
|
};
|
|
|
|
int err = 0;
|
|
|
|
|
|
|
|
argc = parse_options(argc, argv, c2c_options, report_c2c_usage,
|
|
|
|
PARSE_OPT_STOP_AT_NON_OPTION);
|
2016-09-22 18:36:44 +03:00
|
|
|
if (argc)
|
2016-09-22 18:36:40 +03:00
|
|
|
usage_with_options(report_c2c_usage, c2c_options);
|
|
|
|
|
2016-09-22 18:36:44 +03:00
|
|
|
if (!input_name || !strlen(input_name))
|
|
|
|
input_name = "perf.data";
|
|
|
|
|
2016-09-22 18:36:40 +03:00
|
|
|
file.path = input_name;
|
|
|
|
|
2016-09-22 18:36:41 +03:00
|
|
|
err = c2c_hists__init(&c2c.hists, "dcacheline");
|
|
|
|
if (err) {
|
|
|
|
pr_debug("Failed to initialize hists\n");
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2016-09-22 18:36:40 +03:00
|
|
|
session = perf_session__new(&file, 0, &c2c.tool);
|
|
|
|
if (session == NULL) {
|
|
|
|
pr_debug("No memory for session\n");
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (symbol__init(&session->header.env) < 0)
|
|
|
|
goto out_session;
|
|
|
|
|
|
|
|
/* No pipe support at the moment. */
|
|
|
|
if (perf_data_file__is_pipe(session->file)) {
|
|
|
|
pr_debug("No pipe support at the moment.\n");
|
|
|
|
goto out_session;
|
|
|
|
}
|
|
|
|
|
2016-09-22 18:36:44 +03:00
|
|
|
err = perf_session__process_events(session);
|
|
|
|
if (err) {
|
|
|
|
pr_err("failed to process sample\n");
|
|
|
|
goto out_session;
|
|
|
|
}
|
|
|
|
|
|
|
|
ui_progress__init(&prog, c2c.hists.hists.nr_entries, "Sorting...");
|
|
|
|
|
|
|
|
hists__collapse_resort(&c2c.hists.hists, NULL);
|
2016-09-22 18:36:45 +03:00
|
|
|
hists__output_resort_cb(&c2c.hists.hists, &prog, resort_cl_cb);
|
2016-09-22 18:36:44 +03:00
|
|
|
|
|
|
|
ui_progress__finish();
|
|
|
|
|
2016-09-22 18:36:40 +03:00
|
|
|
out_session:
|
|
|
|
perf_session__delete(session);
|
|
|
|
out:
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
perf c2c: Add record subcommand
Adding c2c record subcommand. It setups options related to HITM
cacheline analysis and calls standard perf record command.
$ sudo perf c2c record -v -- -a
calling: record -W -d --sample-cpu -e cpu/mem-loads,ldlat=30/P -e cpu/mem-stores/P -a
...
It produces perf.data, which is to be reported by perf c2c report, that
comes in following patches.
Details are described in the man page, which is added in one of the
following patches.
Committer notes:
Testing it:
# perf c2c record -a sleep 1
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 5.050 MB perf.data (412 samples) ]
# ls -la perf.data
-rw-------. 1 root root 5301752 Oct 4 13:32 perf.data
# perf evlist
cpu/mem-loads,ldlat=30/P
cpu/mem-stores/P
# perf evlist -v
cpu/mem-loads,ldlat=30/P: type: 4, size: 112, config: 0x1cd, { sample_period, sample_freq }: 4000, sample_type: IP|TID|TIME|ADDR|ID|CPU|PERIOD|DATA_SRC|WEIGHT, read_format: ID, disabled: 1, inherit: 1, mmap: 1, comm: 1, freq: 1, task: 1, precise_ip: 3, mmap_data: 1, sample_id_all: 1, mmap2: 1, comm_exec: 1, { bp_addr, config1 }: 0x1f
cpu/mem-stores/P: type: 4, size: 112, config: 0x82d0, { sample_period, sample_freq }: 4000, sample_type: IP|TID|TIME|ADDR|ID|CPU|PERIOD|DATA_SRC|WEIGHT, read_format: ID, disabled: 1, inherit: 1, freq: 1, precise_ip: 3, sample_id_all: 1
#
# perf report --stdio
<SNIP>
# Total Lost Samples: 14
# Samples: 216 of event 'cpu/mem-loads,ldlat=30/P'
# Event count (approx.): 15207
# Overhead Symbol Shared Object
# ........ ..................................... ............................
10.32% [k] update_blocked_averages [kernel.vmlinux]
3.43% [.] 0x00000000001a2122 qemu-system-x86_64 (deleted)
2.52% [k] enqueue_entity [kernel.vmlinux]
1.88% [.] g_main_context_query libglib-2.0.so.0.4800.2
1.86% [k] __schedule [kernel.vmlinux]
<SNIP>
# Samples: 196 of event 'cpu/mem-stores/P'
# Event count (approx.): 14771346
# Overhead Symbol Shared Object
# ........ ................................... ............................
13.91% [k] intel_idle [kernel.vmlinux]
3.02% [.] 0x00000000022f06ea chrome
2.94% [.] 0x00000000001a1b4c qemu-system-x86_64 (deleted)
2.94% [.] 0x000000000019d8e4 qemu-system-x86_64 (deleted)
2.38% [.] 0x00000000001a1c52 qemu-system-x86_64 (deleted)
<SNIP>
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Andi Kleen <andi@firstfloor.org>
Cc: David Ahern <dsahern@gmail.com>
Cc: Don Zickus <dzickus@redhat.com>
Cc: Joe Mario <jmario@redhat.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Link: http://lkml.kernel.org/r/1474558645-19956-12-git-send-email-jolsa@kernel.org
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2016-09-22 18:36:39 +03:00
|
|
|
static int parse_record_events(const struct option *opt __maybe_unused,
|
|
|
|
const char *str, int unset __maybe_unused)
|
|
|
|
{
|
|
|
|
bool *event_set = (bool *) opt->value;
|
|
|
|
|
|
|
|
*event_set = true;
|
|
|
|
return perf_mem_events__parse(str);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static const char * const __usage_record[] = {
|
|
|
|
"perf c2c record [<options>] [<command>]",
|
|
|
|
"perf c2c record [<options>] -- <command> [<options>]",
|
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
|
|
|
static const char * const *record_mem_usage = __usage_record;
|
|
|
|
|
|
|
|
static int perf_c2c__record(int argc, const char **argv)
|
|
|
|
{
|
|
|
|
int rec_argc, i = 0, j;
|
|
|
|
const char **rec_argv;
|
|
|
|
int ret;
|
|
|
|
bool all_user = false, all_kernel = false;
|
|
|
|
bool event_set = false;
|
|
|
|
struct option options[] = {
|
|
|
|
OPT_CALLBACK('e', "event", &event_set, "event",
|
|
|
|
"event selector. Use 'perf mem record -e list' to list available events",
|
|
|
|
parse_record_events),
|
|
|
|
OPT_INCR('v', "verbose", &verbose,
|
|
|
|
"be more verbose (show counter open errors, etc)"),
|
|
|
|
OPT_BOOLEAN('u', "all-user", &all_user, "collect only user level data"),
|
|
|
|
OPT_BOOLEAN('k', "all-kernel", &all_kernel, "collect only kernel level data"),
|
|
|
|
OPT_UINTEGER('l', "ldlat", &perf_mem_events__loads_ldlat, "setup mem-loads latency"),
|
|
|
|
OPT_END()
|
|
|
|
};
|
|
|
|
|
|
|
|
if (perf_mem_events__init()) {
|
|
|
|
pr_err("failed: memory events not supported\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
argc = parse_options(argc, argv, options, record_mem_usage,
|
|
|
|
PARSE_OPT_KEEP_UNKNOWN);
|
|
|
|
|
|
|
|
rec_argc = argc + 10; /* max number of arguments */
|
|
|
|
rec_argv = calloc(rec_argc + 1, sizeof(char *));
|
|
|
|
if (!rec_argv)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
rec_argv[i++] = "record";
|
|
|
|
|
|
|
|
if (!event_set) {
|
|
|
|
perf_mem_events[PERF_MEM_EVENTS__LOAD].record = true;
|
|
|
|
perf_mem_events[PERF_MEM_EVENTS__STORE].record = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (perf_mem_events[PERF_MEM_EVENTS__LOAD].record)
|
|
|
|
rec_argv[i++] = "-W";
|
|
|
|
|
|
|
|
rec_argv[i++] = "-d";
|
|
|
|
rec_argv[i++] = "--sample-cpu";
|
|
|
|
|
|
|
|
for (j = 0; j < PERF_MEM_EVENTS__MAX; j++) {
|
|
|
|
if (!perf_mem_events[j].record)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (!perf_mem_events[j].supported) {
|
|
|
|
pr_err("failed: event '%s' not supported\n",
|
|
|
|
perf_mem_events[j].name);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
rec_argv[i++] = "-e";
|
|
|
|
rec_argv[i++] = perf_mem_events__name(j);
|
|
|
|
};
|
|
|
|
|
|
|
|
if (all_user)
|
|
|
|
rec_argv[i++] = "--all-user";
|
|
|
|
|
|
|
|
if (all_kernel)
|
|
|
|
rec_argv[i++] = "--all-kernel";
|
|
|
|
|
|
|
|
for (j = 0; j < argc; j++, i++)
|
|
|
|
rec_argv[i] = argv[j];
|
|
|
|
|
|
|
|
if (verbose > 0) {
|
|
|
|
pr_debug("calling: ");
|
|
|
|
|
|
|
|
j = 0;
|
|
|
|
|
|
|
|
while (rec_argv[j]) {
|
|
|
|
pr_debug("%s ", rec_argv[j]);
|
|
|
|
j++;
|
|
|
|
}
|
|
|
|
pr_debug("\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = cmd_record(i, rec_argv, NULL);
|
|
|
|
free(rec_argv);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2016-09-22 18:36:38 +03:00
|
|
|
int cmd_c2c(int argc, const char **argv, const char *prefix __maybe_unused)
|
|
|
|
{
|
|
|
|
const struct option c2c_options[] = {
|
|
|
|
OPT_INCR('v', "verbose", &verbose, "be more verbose"),
|
|
|
|
OPT_END()
|
|
|
|
};
|
|
|
|
|
|
|
|
argc = parse_options(argc, argv, c2c_options, c2c_usage,
|
|
|
|
PARSE_OPT_STOP_AT_NON_OPTION);
|
perf c2c: Add record subcommand
Adding c2c record subcommand. It setups options related to HITM
cacheline analysis and calls standard perf record command.
$ sudo perf c2c record -v -- -a
calling: record -W -d --sample-cpu -e cpu/mem-loads,ldlat=30/P -e cpu/mem-stores/P -a
...
It produces perf.data, which is to be reported by perf c2c report, that
comes in following patches.
Details are described in the man page, which is added in one of the
following patches.
Committer notes:
Testing it:
# perf c2c record -a sleep 1
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 5.050 MB perf.data (412 samples) ]
# ls -la perf.data
-rw-------. 1 root root 5301752 Oct 4 13:32 perf.data
# perf evlist
cpu/mem-loads,ldlat=30/P
cpu/mem-stores/P
# perf evlist -v
cpu/mem-loads,ldlat=30/P: type: 4, size: 112, config: 0x1cd, { sample_period, sample_freq }: 4000, sample_type: IP|TID|TIME|ADDR|ID|CPU|PERIOD|DATA_SRC|WEIGHT, read_format: ID, disabled: 1, inherit: 1, mmap: 1, comm: 1, freq: 1, task: 1, precise_ip: 3, mmap_data: 1, sample_id_all: 1, mmap2: 1, comm_exec: 1, { bp_addr, config1 }: 0x1f
cpu/mem-stores/P: type: 4, size: 112, config: 0x82d0, { sample_period, sample_freq }: 4000, sample_type: IP|TID|TIME|ADDR|ID|CPU|PERIOD|DATA_SRC|WEIGHT, read_format: ID, disabled: 1, inherit: 1, freq: 1, precise_ip: 3, sample_id_all: 1
#
# perf report --stdio
<SNIP>
# Total Lost Samples: 14
# Samples: 216 of event 'cpu/mem-loads,ldlat=30/P'
# Event count (approx.): 15207
# Overhead Symbol Shared Object
# ........ ..................................... ............................
10.32% [k] update_blocked_averages [kernel.vmlinux]
3.43% [.] 0x00000000001a2122 qemu-system-x86_64 (deleted)
2.52% [k] enqueue_entity [kernel.vmlinux]
1.88% [.] g_main_context_query libglib-2.0.so.0.4800.2
1.86% [k] __schedule [kernel.vmlinux]
<SNIP>
# Samples: 196 of event 'cpu/mem-stores/P'
# Event count (approx.): 14771346
# Overhead Symbol Shared Object
# ........ ................................... ............................
13.91% [k] intel_idle [kernel.vmlinux]
3.02% [.] 0x00000000022f06ea chrome
2.94% [.] 0x00000000001a1b4c qemu-system-x86_64 (deleted)
2.94% [.] 0x000000000019d8e4 qemu-system-x86_64 (deleted)
2.38% [.] 0x00000000001a1c52 qemu-system-x86_64 (deleted)
<SNIP>
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Andi Kleen <andi@firstfloor.org>
Cc: David Ahern <dsahern@gmail.com>
Cc: Don Zickus <dzickus@redhat.com>
Cc: Joe Mario <jmario@redhat.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Link: http://lkml.kernel.org/r/1474558645-19956-12-git-send-email-jolsa@kernel.org
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2016-09-22 18:36:39 +03:00
|
|
|
|
|
|
|
if (!argc)
|
|
|
|
usage_with_options(c2c_usage, c2c_options);
|
|
|
|
|
|
|
|
if (!strncmp(argv[0], "rec", 3)) {
|
|
|
|
return perf_c2c__record(argc, argv);
|
2016-09-22 18:36:40 +03:00
|
|
|
} else if (!strncmp(argv[0], "rep", 3)) {
|
|
|
|
return perf_c2c__report(argc, argv);
|
perf c2c: Add record subcommand
Adding c2c record subcommand. It setups options related to HITM
cacheline analysis and calls standard perf record command.
$ sudo perf c2c record -v -- -a
calling: record -W -d --sample-cpu -e cpu/mem-loads,ldlat=30/P -e cpu/mem-stores/P -a
...
It produces perf.data, which is to be reported by perf c2c report, that
comes in following patches.
Details are described in the man page, which is added in one of the
following patches.
Committer notes:
Testing it:
# perf c2c record -a sleep 1
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 5.050 MB perf.data (412 samples) ]
# ls -la perf.data
-rw-------. 1 root root 5301752 Oct 4 13:32 perf.data
# perf evlist
cpu/mem-loads,ldlat=30/P
cpu/mem-stores/P
# perf evlist -v
cpu/mem-loads,ldlat=30/P: type: 4, size: 112, config: 0x1cd, { sample_period, sample_freq }: 4000, sample_type: IP|TID|TIME|ADDR|ID|CPU|PERIOD|DATA_SRC|WEIGHT, read_format: ID, disabled: 1, inherit: 1, mmap: 1, comm: 1, freq: 1, task: 1, precise_ip: 3, mmap_data: 1, sample_id_all: 1, mmap2: 1, comm_exec: 1, { bp_addr, config1 }: 0x1f
cpu/mem-stores/P: type: 4, size: 112, config: 0x82d0, { sample_period, sample_freq }: 4000, sample_type: IP|TID|TIME|ADDR|ID|CPU|PERIOD|DATA_SRC|WEIGHT, read_format: ID, disabled: 1, inherit: 1, freq: 1, precise_ip: 3, sample_id_all: 1
#
# perf report --stdio
<SNIP>
# Total Lost Samples: 14
# Samples: 216 of event 'cpu/mem-loads,ldlat=30/P'
# Event count (approx.): 15207
# Overhead Symbol Shared Object
# ........ ..................................... ............................
10.32% [k] update_blocked_averages [kernel.vmlinux]
3.43% [.] 0x00000000001a2122 qemu-system-x86_64 (deleted)
2.52% [k] enqueue_entity [kernel.vmlinux]
1.88% [.] g_main_context_query libglib-2.0.so.0.4800.2
1.86% [k] __schedule [kernel.vmlinux]
<SNIP>
# Samples: 196 of event 'cpu/mem-stores/P'
# Event count (approx.): 14771346
# Overhead Symbol Shared Object
# ........ ................................... ............................
13.91% [k] intel_idle [kernel.vmlinux]
3.02% [.] 0x00000000022f06ea chrome
2.94% [.] 0x00000000001a1b4c qemu-system-x86_64 (deleted)
2.94% [.] 0x000000000019d8e4 qemu-system-x86_64 (deleted)
2.38% [.] 0x00000000001a1c52 qemu-system-x86_64 (deleted)
<SNIP>
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Andi Kleen <andi@firstfloor.org>
Cc: David Ahern <dsahern@gmail.com>
Cc: Don Zickus <dzickus@redhat.com>
Cc: Joe Mario <jmario@redhat.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Link: http://lkml.kernel.org/r/1474558645-19956-12-git-send-email-jolsa@kernel.org
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2016-09-22 18:36:39 +03:00
|
|
|
} else {
|
|
|
|
usage_with_options(c2c_usage, c2c_options);
|
|
|
|
}
|
|
|
|
|
2016-09-22 18:36:38 +03:00
|
|
|
return 0;
|
|
|
|
}
|