diff --git a/include/ply/perf_event.h b/include/ply/perf_event.h index 00c5750..8b51afb 100644 --- a/include/ply/perf_event.h +++ b/include/ply/perf_event.h @@ -15,6 +15,8 @@ int perf_event_attach(struct ply_probe *pb, const char *name, int task_mode); int perf_event_attach_raw(struct ply_probe *pb, int type, unsigned long config, unsigned long long period, int task_mode); +int perf_event_attach_profile(struct ply_probe *pb, int cpu, + unsigned long long freq); int perf_event_enable (int group_fd); int perf_event_disable(int group_fd); diff --git a/man/ply.1.ronn b/man/ply.1.ronn index 6237064..aa3bc0c 100644 --- a/man/ply.1.ronn +++ b/man/ply.1.ronn @@ -360,6 +360,15 @@ Examples: * _interval:1_: Called for every second * _interval:500ms_: Called for every 500 milli-second +### profile + +The profile provider supports profiling by allowing the user to specify +how many times it will fire per-second. Values of 1-1000 are supported, +and the profile provider supports two probe formats: + + * profile:[N]hz: Profile on all CPUs N times per second + * profile:[C]:[N]hz: Profile on CPU C N times per second + ## EXAMPLE diff --git a/src/libply/Makefile.am b/src/libply/Makefile.am index b7c206b..835dfb6 100644 --- a/src/libply/Makefile.am +++ b/src/libply/Makefile.am @@ -41,6 +41,7 @@ libply_la_SOURCES = \ provider/kprobe.c \ provider/kprobe.h \ provider/kretprobe.c \ + provider/profile.c \ provider/special.c \ provider/tracepoint.c \ provider/xprobe.c \ diff --git a/src/libply/aux/perf_event.c b/src/libply/aux/perf_event.c index d3c7e94..80161bf 100644 --- a/src/libply/aux/perf_event.c +++ b/src/libply/aux/perf_event.c @@ -99,6 +99,29 @@ int perf_event_attach_raw(struct ply_probe *pb, int type, unsigned long config, return fd; } +int perf_event_attach_profile(struct ply_probe *pb, int cpu, + unsigned long long freq) +{ + struct perf_event_attr attr = {}; + int fd; + + attr.type = PERF_TYPE_SOFTWARE; + attr.config = PERF_COUNT_SW_CPU_CLOCK; + attr.sample_freq = freq; + attr.freq = 1; + + fd = perf_event_open(&attr, -1, cpu, -1, 0); + if (fd < 0) + return -errno; + + if (ioctl(fd, PERF_EVENT_IOC_SET_BPF, pb->bpf_fd)) { + close(fd); + return -errno; + } + + return fd; +} + int perf_event_enable(int group_fd) { if (ioctl(group_fd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP)) diff --git a/src/libply/provider.c b/src/libply/provider.c index 4cc82ff..43f23a2 100644 --- a/src/libply/provider.c +++ b/src/libply/provider.c @@ -22,6 +22,7 @@ extern struct provider built_in; extern struct provider begin_provider; extern struct provider end_provider; extern struct provider interval; +extern struct provider profile; struct provider *provider_get(const char *name) { @@ -45,6 +46,7 @@ void provider_init(void) SLIST_INSERT_HEAD(&heads, &begin_provider, entry); SLIST_INSERT_HEAD(&heads, &built_in, entry); SLIST_INSERT_HEAD(&heads, &interval, entry); + SLIST_INSERT_HEAD(&heads, &profile, entry); SLIST_INSERT_HEAD(&heads, &tracepoint, entry); SLIST_INSERT_HEAD(&heads, &kretprobe, entry); /* place kprobe at head so that 'k' can match first. */ diff --git a/src/libply/provider/profile.c b/src/libply/provider/profile.c new file mode 100644 index 0000000..e5bb09a --- /dev/null +++ b/src/libply/provider/profile.c @@ -0,0 +1,123 @@ +/* + * Copyright Ism Hong + * + * SPDX-License-Identifier: GPL-2.0 + */ + +#include +#include +#include +#include + +#include +#include + + +struct profile_data { + int cpu; + int ncpus; + unsigned long long freq; + int *evfds; +}; + +static int profile_sym_alloc(struct ply_probe *pb, struct node *n) +{ + return -ENOENT; +} + +static int profile_probe(struct ply_probe *pb) +{ + int cpu = -1, ncpus = 0; + struct profile_data *data; + int freq = -1; + + /* + * Expected format is either profile:[n]hz where n is a number between + * 1 and 1000, or profile:[c]:[n]hz where c is the CPU to profile. + */ + if (sscanf(pb->probe, "profile:%d:%dhz", &cpu, &freq) != 2) { + cpu = -1; + if (sscanf(pb->probe, "profile:%dhz", &freq) != 1) + return -EINVAL; + } + + if (freq < 0 || freq > 1000) + return -EINVAL; + + ncpus = sysconf(_SC_NPROCESSORS_ONLN); + + if (cpu < -1 || cpu > ncpus) + return -EINVAL; + + if (cpu >= 0) + ncpus = 1; + + data = calloc(1, sizeof(*data)); + if (!data) + return -ENOMEM; + + data->evfds = calloc(ncpus, sizeof (int)); + if (!data->evfds) { + free(data); + return -ENOMEM; + } + + data->freq = (unsigned long long)freq; + data->cpu = cpu; + data->ncpus = ncpus ; + + pb->provider_data = data; + return 0; +} + +static int profile_attach(struct ply_probe *pb) +{ + struct profile_data *data = pb->provider_data; + int cpu; + + if (data->cpu != -1) { + data->evfds[0] = perf_event_attach_profile(pb, data->cpu, + data->freq); + if (data->evfds[0] < 0) { + _e("%s: Unable to attach profile probe: %s\n", + pb->probe, strerror(errno)); + return data->evfds[0]; + } + } else { + for (cpu = 0; cpu < data->ncpus; cpu++) { + data->evfds[cpu] = perf_event_attach_profile(pb, cpu, data->freq); + if (data->evfds[cpu] < 0) { + _e("%s: Unable to attach profile probe: %s\n", + pb->probe, strerror(errno)); + return data->evfds[cpu]; + } + } + } + + return 0; +} + +static int profile_detach(struct ply_probe *pb) +{ + struct profile_data *data = pb->provider_data; + + for (int i = 0; i < data->ncpus; i++) { + if (data->evfds[i] > 0) + close(data->evfds[i]); + } + free(data->evfds); + free(data); + + return 0; +} + +struct provider profile = { + .name = "profile", + .prog_type = BPF_PROG_TYPE_PERF_EVENT, + + .sym_alloc = profile_sym_alloc, + .probe = profile_probe, + + .attach = profile_attach, + .detach = profile_detach, +}; diff --git a/test/rootfs/lib/ply/test.sh b/test/rootfs/lib/ply/test.sh index 6d3b7b9..8135a6f 100755 --- a/test/rootfs/lib/ply/test.sh +++ b/test/rootfs/lib/ply/test.sh @@ -84,4 +84,19 @@ cat /tmp/tracepoint-dyn | awk ' END { exit(!(unames >= 10)); }' \ || fail "at least 10 unames" "$(cat /tmp/tracepoint-dyn)" +case=profile +ply 'BEGIN { printf("profile provider unit test\n"); c["profile_test"] = 0; } + profile:0:100hz + { + if (c["profile_test"] == 100) + exit(0); + else + c["profile_test"] = c["profile_test"] + 1; + }' >/tmp/profile \ +&& \ +cat /tmp/profile | awk -F': ' ' + /profile_test/ { count = $2; } + END { exit(count != 100); }' \ +|| fail "count should be 100 for profile provider test" "$(cat /tmp/profile)" + exit $total_fails