diff --git a/.gitignore b/.gitignore index 8879584..211abf7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ build/ .cache/ tests_general +benchmark .copycat.conf # Created by https://www.toptal.com/developers/gitignore/api/c,cmake diff --git a/tests/benchmark.c b/tests/benchmark.c new file mode 100644 index 0000000..ea409f3 --- /dev/null +++ b/tests/benchmark.c @@ -0,0 +1,79 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void check_correct_fd(int fd) { + assert(fd >= 0); + static char c; + + // we should be able to read from the file descriptor + ssize_t r = read(fd, &c, 1); + assert(r); + + // the read char should be either 'a' or 'b' or 'c', depending on whether system calls are intercepted and which file is read + assert((c == 'a') || (c == 'b') || (c == 'c')); + + // close the descriptor again + int a = close(fd); + assert(!a); +} + +int do_openat(const char *filename) { + return openat(0, filename, O_RDONLY); +} + +// we need to do a benchmark without system calls involved, to check for general performance overhead +int is_prime(int n) { + for (int i = 2; i < sqrt(n); ++i) { + if ((n % i) == 0) { + return 0; + } + } + return 1; +} + +int main(int argc, char *argv[]) +{ + const char hot_filename[] = "/tmp/a"; + const char cold_filename[] = "/tmp/c"; + int f = -1; + size_t n = 1000; + struct timespec start, end; + + // hot path, every call is intercepted and changed + clock_gettime(CLOCK_MONOTONIC_RAW, &start); + for (size_t i = 0; i < n; ++i) { + f = do_openat(hot_filename); + check_correct_fd(f); + } + clock_gettime(CLOCK_MONOTONIC_RAW, &end); + printf("openat (intercepted and modified): %lu us\n", (end.tv_sec - start.tv_sec) * 1000000 + (end.tv_nsec - start.tv_nsec) / 1000); + + // cold path, every call is intercepted but no arguments are changed (the BPF filter does not trigger) + clock_gettime(CLOCK_MONOTONIC_RAW, &start); + for (size_t i = 0; i < n; ++i) { + f = do_openat(cold_filename); + check_correct_fd(f); + } + clock_gettime(CLOCK_MONOTONIC_RAW, &end); + printf("openat (intercepted and not modified): %lu us\n", (end.tv_sec - start.tv_sec) * 1000000 + (end.tv_nsec - start.tv_nsec) / 1000); + + // passive path, no call is intercepted (no system calls) + int primes = 0; + clock_gettime(CLOCK_MONOTONIC_RAW, &start); + for (int i = 0; i < n; ++i) { + primes += is_prime(i); + } + clock_gettime(CLOCK_MONOTONIC_RAW, &end); + printf("path with no system calls: Found %d primes in %lu us\n", primes, (end.tv_sec - start.tv_sec) * 1000000 + (end.tv_nsec - start.tv_nsec) / 1000); + + return EXIT_SUCCESS; +} diff --git a/tests/run-tests.sh b/tests/run-tests.sh index 69d7695..35e6ae0 100755 --- a/tests/run-tests.sh +++ b/tests/run-tests.sh @@ -2,5 +2,12 @@ echo "a" > /tmp/a echo "b" > /tmp/b +echo "c" > /tmp/c gcc tests/tests_general.c -o tests_general COPYCAT="/tmp/a /tmp/b" build/copycat -- ./tests_general + +gcc -lm tests/benchmark.c -o benchmark +echo -e "\nRunning benchmark without interception:" +./benchmark +echo -e "\nRunning benchmark with interception:" +COPYCAT="/tmp/a /tmp/b" build/copycat -- ./benchmark diff --git a/tests/tests_general.c b/tests/tests_general.c index 0de1fd1..b032e4a 100644 --- a/tests/tests_general.c +++ b/tests/tests_general.c @@ -10,14 +10,18 @@ void check_correct_fd(int fd) { static char c; + // we should be able to read from the file descriptor - assert(read(fd, &c, 1)); + ssize_t r = read(fd, &c, 1); + assert(r); + // the read char should be 'b', and not 'a' assert(c != 'a'); assert(c == 'b'); // close the descriptor again - assert(!close(fd)); + int a = close(fd); + assert(!a); } int do_open(const char *filename) {