root/tools/testing/selftests/powerpc/benchmarks/fork.c
// SPDX-License-Identifier: GPL-2.0+

/*
 * Context switch microbenchmark.
 *
 * Copyright 2018, Anton Blanchard, IBM Corp.
 */

#define _GNU_SOURCE
#include <assert.h>
#include <errno.h>
#include <getopt.h>
#include <limits.h>
#include <linux/futex.h>
#include <pthread.h>
#include <sched.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

static unsigned int timeout = 30;

static void set_cpu(int cpu)
{
        cpu_set_t cpuset;

        if (cpu == -1)
                return;

        CPU_ZERO(&cpuset);
        CPU_SET(cpu, &cpuset);

        if (sched_setaffinity(0, sizeof(cpuset), &cpuset)) {
                perror("sched_setaffinity");
                exit(1);
        }
}

static void start_process_on(void *(*fn)(void *), void *arg, int cpu)
{
        int pid;

        pid = fork();
        if (pid == -1) {
                perror("fork");
                exit(1);
        }

        if (pid)
                return;

        set_cpu(cpu);

        fn(arg);

        exit(0);
}

static int cpu;
static int do_fork = 0;
static int do_vfork = 0;
static int do_exec = 0;
static char *exec_file;
static int exec_target = 0;
static unsigned long iterations;
static unsigned long iterations_prev;

static void run_exec(void)
{
        char *const argv[] = { "./exec_target", NULL };

        if (execve("./exec_target", argv, NULL) == -1) {
                perror("execve");
                exit(1);
        }
}

static void bench_fork(void)
{
        while (1) {
                pid_t pid = fork();
                if (pid == -1) {
                        perror("fork");
                        exit(1);
                }
                if (pid == 0) {
                        if (do_exec)
                                run_exec();
                        _exit(0);
                }
                pid = waitpid(pid, NULL, 0);
                if (pid == -1) {
                        perror("waitpid");
                        exit(1);
                }
                iterations++;
        }
}

static void bench_vfork(void)
{
        while (1) {
                pid_t pid = vfork();
                if (pid == -1) {
                        perror("fork");
                        exit(1);
                }
                if (pid == 0) {
                        if (do_exec)
                                run_exec();
                        _exit(0);
                }
                pid = waitpid(pid, NULL, 0);
                if (pid == -1) {
                        perror("waitpid");
                        exit(1);
                }
                iterations++;
        }
}

static void *null_fn(void *arg)
{
        pthread_exit(NULL);
}

static void bench_thread(void)
{
        pthread_t tid;
        cpu_set_t cpuset;
        pthread_attr_t attr;
        int rc;

        rc = pthread_attr_init(&attr);
        if (rc) {
                errno = rc;
                perror("pthread_attr_init");
                exit(1);
        }

        if (cpu != -1) {
                CPU_ZERO(&cpuset);
                CPU_SET(cpu, &cpuset);

                rc = pthread_attr_setaffinity_np(&attr, sizeof(cpu_set_t), &cpuset);
                if (rc) {
                        errno = rc;
                        perror("pthread_attr_setaffinity_np");
                        exit(1);
                }
        }

        while (1) {
                rc = pthread_create(&tid, &attr, null_fn, NULL);
                if (rc) {
                        errno = rc;
                        perror("pthread_create");
                        exit(1);
                }
                rc = pthread_join(tid, NULL);
                if (rc) {
                        errno = rc;
                        perror("pthread_join");
                        exit(1);
                }
                iterations++;
        }
}

static void sigalrm_handler(int junk)
{
        unsigned long i = iterations;

        printf("%ld\n", i - iterations_prev);
        iterations_prev = i;

        if (--timeout == 0)
                kill(0, SIGUSR1);

        alarm(1);
}

static void sigusr1_handler(int junk)
{
        exit(0);
}

static void *bench_proc(void *arg)
{
        signal(SIGALRM, sigalrm_handler);
        alarm(1);

        if (do_fork)
                bench_fork();
        else if (do_vfork)
                bench_vfork();
        else
                bench_thread();

        return NULL;
}

static struct option options[] = {
        { "fork", no_argument, &do_fork, 1 },
        { "vfork", no_argument, &do_vfork, 1 },
        { "exec", no_argument, &do_exec, 1 },
        { "timeout", required_argument, 0, 's' },
        { "exec-target", no_argument, &exec_target, 1 },
        { NULL },
};

static void usage(void)
{
        fprintf(stderr, "Usage: fork <options> CPU\n\n");
        fprintf(stderr, "\t\t--fork\tUse fork() (default threads)\n");
        fprintf(stderr, "\t\t--vfork\tUse vfork() (default threads)\n");
        fprintf(stderr, "\t\t--exec\tAlso exec() (default no exec)\n");
        fprintf(stderr, "\t\t--timeout=X\tDuration in seconds to run (default 30)\n");
        fprintf(stderr, "\t\t--exec-target\tInternal option for exec workload\n");
}

int main(int argc, char *argv[])
{
        signed char c;

        while (1) {
                int option_index = 0;

                c = getopt_long(argc, argv, "", options, &option_index);

                if (c == -1)
                        break;

                switch (c) {
                case 0:
                        if (options[option_index].flag != 0)
                                break;

                        usage();
                        exit(1);
                        break;

                case 's':
                        timeout = atoi(optarg);
                        break;

                default:
                        usage();
                        exit(1);
                }
        }

        if (do_fork && do_vfork) {
                usage();
                exit(1);
        }
        if (do_exec && !do_fork && !do_vfork) {
                usage();
                exit(1);
        }

        if (do_exec) {
                char *dirname = strdup(argv[0]);
                int i;
                i = strlen(dirname) - 1;
                while (i) {
                        if (dirname[i] == '/') {
                                dirname[i] = '\0';
                                if (chdir(dirname) == -1) {
                                        perror("chdir");
                                        exit(1);
                                }
                                break;
                        }
                        i--;
                }
        }

        if (exec_target) {
                exit(0);
        }

        if (((argc - optind) != 1)) {
                cpu = -1;
        } else {
                cpu = atoi(argv[optind++]);
        }

        if (do_exec)
                exec_file = argv[0];

        set_cpu(cpu);

        printf("Using ");
        if (do_fork)
                printf("fork");
        else if (do_vfork)
                printf("vfork");
        else
                printf("clone");

        if (do_exec)
                printf(" + exec");

        printf(" on cpu %d\n", cpu);

        /* Create a new process group so we can signal everyone for exit */
        setpgid(getpid(), getpid());

        signal(SIGUSR1, sigusr1_handler);

        start_process_on(bench_proc, NULL, cpu);

        while (1)
                sleep(3600);

        return 0;
}