linux/tools/testing/selftests/seccomp/seccomp_benchmark.c
<<
>>
Prefs
   1/*
   2 * Strictly speaking, this is not a test. But it can report during test
   3 * runs so relative performace can be measured.
   4 */
   5#define _GNU_SOURCE
   6#include <assert.h>
   7#include <limits.h>
   8#include <stdbool.h>
   9#include <stddef.h>
  10#include <stdio.h>
  11#include <stdlib.h>
  12#include <time.h>
  13#include <unistd.h>
  14#include <linux/filter.h>
  15#include <linux/seccomp.h>
  16#include <sys/param.h>
  17#include <sys/prctl.h>
  18#include <sys/syscall.h>
  19#include <sys/types.h>
  20
  21#define ARRAY_SIZE(a)    (sizeof(a) / sizeof(a[0]))
  22
  23unsigned long long timing(clockid_t clk_id, unsigned long long samples)
  24{
  25        struct timespec start, finish;
  26        unsigned long long i;
  27        pid_t pid, ret;
  28
  29        pid = getpid();
  30        assert(clock_gettime(clk_id, &start) == 0);
  31        for (i = 0; i < samples; i++) {
  32                ret = syscall(__NR_getpid);
  33                assert(pid == ret);
  34        }
  35        assert(clock_gettime(clk_id, &finish) == 0);
  36
  37        i = finish.tv_sec - start.tv_sec;
  38        i *= 1000000000ULL;
  39        i += finish.tv_nsec - start.tv_nsec;
  40
  41        printf("%lu.%09lu - %lu.%09lu = %llu (%.1fs)\n",
  42                finish.tv_sec, finish.tv_nsec,
  43                start.tv_sec, start.tv_nsec,
  44                i, (double)i / 1000000000.0);
  45
  46        return i;
  47}
  48
  49unsigned long long calibrate(void)
  50{
  51        struct timespec start, finish;
  52        unsigned long long i, samples, step = 9973;
  53        pid_t pid, ret;
  54        int seconds = 15;
  55
  56        printf("Calibrating sample size for %d seconds worth of syscalls ...\n", seconds);
  57
  58        samples = 0;
  59        pid = getpid();
  60        assert(clock_gettime(CLOCK_MONOTONIC, &start) == 0);
  61        do {
  62                for (i = 0; i < step; i++) {
  63                        ret = syscall(__NR_getpid);
  64                        assert(pid == ret);
  65                }
  66                assert(clock_gettime(CLOCK_MONOTONIC, &finish) == 0);
  67
  68                samples += step;
  69                i = finish.tv_sec - start.tv_sec;
  70                i *= 1000000000ULL;
  71                i += finish.tv_nsec - start.tv_nsec;
  72        } while (i < 1000000000ULL);
  73
  74        return samples * seconds;
  75}
  76
  77bool approx(int i_one, int i_two)
  78{
  79        double one = i_one, one_bump = one * 0.01;
  80        double two = i_two, two_bump = two * 0.01;
  81
  82        one_bump = one + MAX(one_bump, 2.0);
  83        two_bump = two + MAX(two_bump, 2.0);
  84
  85        /* Equal to, or within 1% or 2 digits */
  86        if (one == two ||
  87            (one > two && one <= two_bump) ||
  88            (two > one && two <= one_bump))
  89                return true;
  90        return false;
  91}
  92
  93bool le(int i_one, int i_two)
  94{
  95        if (i_one <= i_two)
  96                return true;
  97        return false;
  98}
  99
 100long compare(const char *name_one, const char *name_eval, const char *name_two,
 101             unsigned long long one, bool (*eval)(int, int), unsigned long long two)
 102{
 103        bool good;
 104
 105        printf("\t%s %s %s (%lld %s %lld): ", name_one, name_eval, name_two,
 106               (long long)one, name_eval, (long long)two);
 107        if (one > INT_MAX) {
 108                printf("Miscalculation! Measurement went negative: %lld\n", (long long)one);
 109                return 1;
 110        }
 111        if (two > INT_MAX) {
 112                printf("Miscalculation! Measurement went negative: %lld\n", (long long)two);
 113                return 1;
 114        }
 115
 116        good = eval(one, two);
 117        printf("%s\n", good ? "✔️" : "❌");
 118
 119        return good ? 0 : 1;
 120}
 121
 122int main(int argc, char *argv[])
 123{
 124        struct sock_filter bitmap_filter[] = {
 125                BPF_STMT(BPF_LD|BPF_W|BPF_ABS, offsetof(struct seccomp_data, nr)),
 126                BPF_STMT(BPF_RET|BPF_K, SECCOMP_RET_ALLOW),
 127        };
 128        struct sock_fprog bitmap_prog = {
 129                .len = (unsigned short)ARRAY_SIZE(bitmap_filter),
 130                .filter = bitmap_filter,
 131        };
 132        struct sock_filter filter[] = {
 133                BPF_STMT(BPF_LD|BPF_W|BPF_ABS, offsetof(struct seccomp_data, args[0])),
 134                BPF_STMT(BPF_RET|BPF_K, SECCOMP_RET_ALLOW),
 135        };
 136        struct sock_fprog prog = {
 137                .len = (unsigned short)ARRAY_SIZE(filter),
 138                .filter = filter,
 139        };
 140
 141        long ret, bits;
 142        unsigned long long samples, calc;
 143        unsigned long long native, filter1, filter2, bitmap1, bitmap2;
 144        unsigned long long entry, per_filter1, per_filter2;
 145
 146        setbuf(stdout, NULL);
 147
 148        printf("Running on:\n");
 149        system("uname -a");
 150
 151        printf("Current BPF sysctl settings:\n");
 152        /* Avoid using "sysctl" which may not be installed. */
 153        system("grep -H . /proc/sys/net/core/bpf_jit_enable");
 154        system("grep -H . /proc/sys/net/core/bpf_jit_harden");
 155
 156        if (argc > 1)
 157                samples = strtoull(argv[1], NULL, 0);
 158        else
 159                samples = calibrate();
 160
 161        printf("Benchmarking %llu syscalls...\n", samples);
 162
 163        /* Native call */
 164        native = timing(CLOCK_PROCESS_CPUTIME_ID, samples) / samples;
 165        printf("getpid native: %llu ns\n", native);
 166
 167        ret = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
 168        assert(ret == 0);
 169
 170        /* One filter resulting in a bitmap */
 171        ret = prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &bitmap_prog);
 172        assert(ret == 0);
 173
 174        bitmap1 = timing(CLOCK_PROCESS_CPUTIME_ID, samples) / samples;
 175        printf("getpid RET_ALLOW 1 filter (bitmap): %llu ns\n", bitmap1);
 176
 177        /* Second filter resulting in a bitmap */
 178        ret = prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &bitmap_prog);
 179        assert(ret == 0);
 180
 181        bitmap2 = timing(CLOCK_PROCESS_CPUTIME_ID, samples) / samples;
 182        printf("getpid RET_ALLOW 2 filters (bitmap): %llu ns\n", bitmap2);
 183
 184        /* Third filter, can no longer be converted to bitmap */
 185        ret = prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog);
 186        assert(ret == 0);
 187
 188        filter1 = timing(CLOCK_PROCESS_CPUTIME_ID, samples) / samples;
 189        printf("getpid RET_ALLOW 3 filters (full): %llu ns\n", filter1);
 190
 191        /* Fourth filter, can not be converted to bitmap because of filter 3 */
 192        ret = prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &bitmap_prog);
 193        assert(ret == 0);
 194
 195        filter2 = timing(CLOCK_PROCESS_CPUTIME_ID, samples) / samples;
 196        printf("getpid RET_ALLOW 4 filters (full): %llu ns\n", filter2);
 197
 198        /* Estimations */
 199#define ESTIMATE(fmt, var, what)        do {                    \
 200                var = (what);                                   \
 201                printf("Estimated " fmt ": %llu ns\n", var);    \
 202                if (var > INT_MAX)                              \
 203                        goto more_samples;                      \
 204        } while (0)
 205
 206        ESTIMATE("total seccomp overhead for 1 bitmapped filter", calc,
 207                 bitmap1 - native);
 208        ESTIMATE("total seccomp overhead for 2 bitmapped filters", calc,
 209                 bitmap2 - native);
 210        ESTIMATE("total seccomp overhead for 3 full filters", calc,
 211                 filter1 - native);
 212        ESTIMATE("total seccomp overhead for 4 full filters", calc,
 213                 filter2 - native);
 214        ESTIMATE("seccomp entry overhead", entry,
 215                 bitmap1 - native - (bitmap2 - bitmap1));
 216        ESTIMATE("seccomp per-filter overhead (last 2 diff)", per_filter1,
 217                 filter2 - filter1);
 218        ESTIMATE("seccomp per-filter overhead (filters / 4)", per_filter2,
 219                 (filter2 - native - entry) / 4);
 220
 221        printf("Expectations:\n");
 222        ret |= compare("native", "≤", "1 bitmap", native, le, bitmap1);
 223        bits = compare("native", "≤", "1 filter", native, le, filter1);
 224        if (bits)
 225                goto more_samples;
 226
 227        ret |= compare("per-filter (last 2 diff)", "≈", "per-filter (filters / 4)",
 228                        per_filter1, approx, per_filter2);
 229
 230        bits = compare("1 bitmapped", "≈", "2 bitmapped",
 231                        bitmap1 - native, approx, bitmap2 - native);
 232        if (bits) {
 233                printf("Skipping constant action bitmap expectations: they appear unsupported.\n");
 234                goto out;
 235        }
 236
 237        ret |= compare("entry", "≈", "1 bitmapped", entry, approx, bitmap1 - native);
 238        ret |= compare("entry", "≈", "2 bitmapped", entry, approx, bitmap2 - native);
 239        ret |= compare("native + entry + (per filter * 4)", "≈", "4 filters total",
 240                        entry + (per_filter1 * 4) + native, approx, filter2);
 241        if (ret == 0)
 242                goto out;
 243
 244more_samples:
 245        printf("Saw unexpected benchmark result. Try running again with more samples?\n");
 246out:
 247        return 0;
 248}
 249