diff options
author | Chris Wilson <chris@chris-wilson.co.uk> | 2016-03-09 22:39:16 +0000 |
---|---|---|
committer | Chris Wilson <chris@chris-wilson.co.uk> | 2016-03-09 23:40:21 +0000 |
commit | 6cd15fb930793f441eaa829bd087ac34e644e492 (patch) | |
tree | 5c5142c30ac87cc19a724487a9267074ba82b04c | |
parent | 0aacdac56fc3179b1fe6bc28bb1a3beb64c64619 (diff) |
benchmarks: Add gem_syslatency
Instead of measuring the wakeup latency of a GEM client, we turn the
tables here and ask what is the wakeup latency of a normal process
competing with GEM. In particular, a realtime process that expects
deterministic latency.
Signed-off-by: Chris Wilson <chris@chris-wilson.co.uk>
-rw-r--r-- | benchmarks/Makefile.am | 2 | ||||
-rw-r--r-- | benchmarks/Makefile.sources | 1 | ||||
-rw-r--r-- | benchmarks/gem_syslatency.c | 301 | ||||
-rw-r--r-- | lib/igt_stats.h | 11 |
4 files changed, 313 insertions, 2 deletions
diff --git a/benchmarks/Makefile.am b/benchmarks/Makefile.am index c31bec58..a555ab6a 100644 --- a/benchmarks/Makefile.am +++ b/benchmarks/Makefile.am @@ -11,5 +11,7 @@ gem_exec_tracer_la_LIBADD = -ldl gem_latency_CFLAGS = $(AM_CFLAGS) $(THREAD_CFLAGS) gem_latency_LDADD = $(LDADD) -lpthread +gem_syslatency_CFLAGS = $(AM_CFLAGS) $(THREAD_CFLAGS) +gem_syslatency_LDADD = $(LDADD) -lpthread -lrt EXTRA_DIST=README diff --git a/benchmarks/Makefile.sources b/benchmarks/Makefile.sources index 922b697e..81607a56 100644 --- a/benchmarks/Makefile.sources +++ b/benchmarks/Makefile.sources @@ -15,6 +15,7 @@ benchmarks_PROGRAMS = \ gem_mmap \ gem_prw \ gem_set_domain \ + gem_syslatency \ gem_userptr_benchmark \ kms_vblank \ $(NULL) diff --git a/benchmarks/gem_syslatency.c b/benchmarks/gem_syslatency.c new file mode 100644 index 00000000..1a879c6f --- /dev/null +++ b/benchmarks/gem_syslatency.c @@ -0,0 +1,301 @@ +/* + * Copyright © 2016 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + */ + +#define _GNU_SOURCE + +#include "igt.h" +#include <unistd.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <inttypes.h> +#include <pthread.h> +#include <sched.h> +#include <signal.h> +#include <errno.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <sys/time.h> +#include <time.h> +#include <limits.h> +#include "drm.h" + +#include <linux/unistd.h> + +#define gettid() syscall(__NR_gettid) +#define sigev_notify_thread_id _sigev_un._tid + +static volatile int done; + +struct gem_busyspin { + pthread_t thread; + int cpu; + unsigned long count; +}; + +struct sys_wait { + pthread_t thread; + int cpu; + struct igt_mean mean; +}; + +static void bind_cpu(pthread_t thread, int cpu) +{ + cpu_set_t mask; + + if (cpu == -1) + return; + + CPU_ZERO(&mask); + CPU_SET(cpu, &mask); + + pthread_setaffinity_np(thread, sizeof(mask), &mask); +} + +#define LOCAL_I915_EXEC_NO_RELOC (1<<11) +#define LOCAL_I915_EXEC_HANDLE_LUT (1<<12) + +#define LOCAL_I915_EXEC_BSD_SHIFT (13) +#define LOCAL_I915_EXEC_BSD_MASK (3 << LOCAL_I915_EXEC_BSD_SHIFT) + +#define ENGINE_FLAGS (I915_EXEC_RING_MASK | LOCAL_I915_EXEC_BSD_MASK) + +static bool ignore_engine(int fd, unsigned engine) +{ + if (engine == 0) + return true; + + if (gem_has_bsd2(fd) && engine == I915_EXEC_BSD) + return true; + + return false; +} + +static void *gem_busyspin(void *arg) +{ + const uint32_t bbe = MI_BATCH_BUFFER_END; + struct gem_busyspin *bs = arg; + struct drm_i915_gem_execbuffer2 execbuf; + struct drm_i915_gem_exec_object2 obj; + unsigned engines[16]; + unsigned nengine; + unsigned engine; + int fd; + + bind_cpu(bs->thread, bs->cpu); + + fd = drm_open_driver(DRIVER_INTEL); + + nengine = 0; + for_each_engine(fd, engine) + if (!ignore_engine(fd, engine)) engines[nengine++] = engine; + igt_require(nengine); + + memset(&obj, 0, sizeof(obj)); + obj.handle = gem_create(fd, 4096); + gem_write(fd, obj.handle, 0, &bbe, sizeof(bbe)); + + memset(&execbuf, 0, sizeof(execbuf)); + execbuf.buffers_ptr = (uintptr_t)&obj; + execbuf.buffer_count = 1; + execbuf.flags |= LOCAL_I915_EXEC_HANDLE_LUT; + execbuf.flags |= LOCAL_I915_EXEC_NO_RELOC; + if (__gem_execbuf(fd, &execbuf) == 0) { + execbuf.flags = 0; + gem_execbuf(fd, &execbuf); + } + + while (!done) { + for (int n = 0; n < nengine; n++) { + execbuf.flags &= ~ENGINE_FLAGS; + execbuf.flags |= engines[n]; + gem_execbuf(fd, &execbuf); + } + bs->count += nengine; + } + + close(fd); + return NULL; +} + +#define MSEC_PER_SEC (1000) +#define USEC_PER_SEC (1000 * MSEC_PER_SEC) +#define NSEC_PER_SEC (1000 * USEC_PER_SEC) + +static double elapsed(const struct timespec *a, const struct timespec *b) +{ + return NSEC_PER_SEC*(b->tv_sec - a->tv_sec) + (b->tv_nsec - a ->tv_nsec); +} + +static void *sys_wait(void *arg) +{ + struct sys_wait *w = arg; + struct sigevent sev; + timer_t timer; + sigset_t mask; + struct timespec now; +#define SIG SIGRTMIN + + bind_cpu(w->thread, w->cpu); + + sigemptyset(&mask); + sigaddset(&mask, SIG); + sigprocmask(SIG_SETMASK, &mask, NULL); + + sev.sigev_notify = SIGEV_SIGNAL | SIGEV_THREAD_ID; + sev.sigev_notify_thread_id = gettid(); + sev.sigev_signo = SIG; + timer_create(CLOCK_MONOTONIC, &sev, &timer); + + clock_gettime(CLOCK_MONOTONIC, &now); + while (!done) { + struct itimerspec its; + int sigs; + + its.it_value = now; + its.it_value.tv_nsec += 100 * 1000; + its.it_value.tv_nsec += rand() % (NSEC_PER_SEC / 1000); + if (its.it_value.tv_nsec >= NSEC_PER_SEC) { + its.it_value.tv_nsec -= NSEC_PER_SEC; + its.it_value.tv_sec += 1; + } + its.it_interval.tv_sec = its.it_interval.tv_nsec = 0; + timer_settime(timer, TIMER_ABSTIME, &its, NULL); + + sigwait(&mask, &sigs); + clock_gettime(CLOCK_MONOTONIC, &now); + igt_mean_add(&w->mean, elapsed(&its.it_value, &now)); + } + + sigprocmask(SIG_UNBLOCK, &mask, NULL); + timer_delete(timer); + + return NULL; +} + +static void rtprio(pthread_attr_t *attr, int prio) +{ +#ifdef PTHREAD_EXPLICIT_SCHED + struct sched_param param = { .sched_priority = 99 }; + pthread_attr_setinheritsched(attr, PTHREAD_EXPLICIT_SCHED); + pthread_attr_setschedpolicy(attr, SCHED_FIFO); + pthread_attr_setschedparam(attr, ¶m); +#endif +} + +static double l_estimate(igt_stats_t *stats) +{ + if (stats->n_values > 9) + return igt_stats_get_trimean(stats); + else if (stats->n_values > 5) + return igt_stats_get_median(stats); + else + return igt_stats_get_mean(stats); +} + +int main(int argc, char **argv) +{ + struct gem_busyspin *busy; + struct sys_wait *wait; + pthread_attr_t attr; + int ncpus = sysconf(_SC_NPROCESSORS_ONLN); + igt_stats_t cycles, mean, max; + int time = 10; + int field = -1; + int n, c; + + while ((c = getopt(argc, argv, "t:f:")) != -1) { + switch (c) { + case 't': + /* How long to run the benchmark for (seconds) */ + time = atoi(optarg); + if (time < 0) + time = INT_MAX; + break; + case 'f': + /* Select an output field */ + field = atoi(optarg); + break; + default: + break; + } + + } + + busy = calloc(ncpus, sizeof(*busy)); + wait = calloc(ncpus, sizeof(*wait)); + + for (n = 0; n < ncpus; n++) { + busy[n].cpu = n; + pthread_create(&busy[n].thread, NULL, gem_busyspin, &busy[n]); + } + + pthread_attr_init(&attr); + rtprio(&attr, 99); + for (n = 0; n < ncpus; n++) { + wait[n].cpu = n; + igt_mean_init(&wait[n].mean); + pthread_create(&wait[n].thread, &attr, sys_wait, &wait[n]); + } + + sleep(time); + done = 1; + + igt_stats_init_with_size(&cycles, ncpus); + for (n = 0; n < ncpus; n++) { + pthread_join(busy[n].thread, NULL); + igt_stats_push(&cycles, busy[n].count); + } + + igt_stats_init_with_size(&mean, ncpus); + igt_stats_init_with_size(&max, ncpus); + for (n = 0; n < ncpus; n++) { + pthread_join(wait[n].thread, NULL); + igt_stats_push_float(&mean, wait[n].mean.mean); + igt_stats_push_float(&max, wait[n].mean.max); + } + + switch (field) { + default: + printf("gem_syslatency: cycles=%.0f, latency mean=%.3fns max=%.0fns\n", + igt_stats_get_mean(&cycles), + igt_stats_get_mean(&mean), + l_estimate(&max)); + break; + case 0: + printf("%.0f\n", igt_stats_get_mean(&cycles)); + break; + case 1: + printf("%.3f\n", igt_stats_get_mean(&mean)); + break; + case 2: + printf("%.0f\n", l_estimate(&max)); + break; + } + + return 0; + +} diff --git a/lib/igt_stats.h b/lib/igt_stats.h index 105f3fb2..32f376cc 100644 --- a/lib/igt_stats.h +++ b/lib/igt_stats.h @@ -27,6 +27,7 @@ #include <stdint.h> #include <stdbool.h> +#include <math.h> /** * igt_stats_t: @@ -81,20 +82,26 @@ double igt_stats_get_variance(igt_stats_t *stats); double igt_stats_get_std_deviation(igt_stats_t *stats); struct igt_mean { - double mean, sq; + double mean, sq, min, max; unsigned long count; }; static inline void igt_mean_init(struct igt_mean *m) { memset(m, 0, sizeof(*m)); + m->max = -HUGE_VAL; + m->min = HUGE_VAL; } static inline void igt_mean_add(struct igt_mean *m, double v) { double delta = v - m->mean; - m->mean += delta / m->count++; + m->mean += delta / ++m->count; m->sq += delta * (v - m->mean); + if (v < m->min) + m->min = v; + if (v > m->max) + m->max = v; } static inline double igt_mean_get(struct igt_mean *m) |