root/src/tests/system/kernel/spinlock_contention.cpp
/*
 * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Distributed under the terms of the MIT License.
 */

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>

#include <SupportDefs.h>

#include <syscalls.h>
#include <spinlock_contention.h>


#define panic printf


struct dummy_spinlock {
        vint32  lock;
        vint32  count_low;
        vint32  count_high;
};


struct dummy_smp_msg {
        dummy_smp_msg*  next;
};


static int                              sNumCPUs = 2;
static bool                             sICIEnabled = true;
static dummy_spinlock   cpu_msg_spinlock[SMP_MAX_CPUS];
static dummy_smp_msg*   smp_msgs[SMP_MAX_CPUS];
static dummy_spinlock   broadcast_msg_spinlock;
static dummy_smp_msg*   smp_broadcast_msgs;


bool
dummy_are_interrupts_enabled()
{
        return false;
}


void
dummy_acquire_spinlock_nocheck(dummy_spinlock* lock)
{
        if (sNumCPUs > 1) {
                if (dummy_are_interrupts_enabled())
                        panic("acquire_spinlock_nocheck: attempt to acquire lock %p with interrupts enabled\n", lock);

                while (atomic_add(&lock->lock, 1) != 0) {
                }
        } else {
                if (dummy_are_interrupts_enabled())
                        panic("acquire_spinlock_nocheck: attempt to acquire lock %p with interrupts enabled\n", lock);
                if (atomic_set((int32 *)lock, 1) != 0)
                        panic("acquire_spinlock_nocheck: attempt to acquire lock %p twice on non-SMP system\n", lock);
        }
}


void
dummy_release_spinlock(dummy_spinlock* lock)
{
        if (sNumCPUs > 1) {
                if (dummy_are_interrupts_enabled())
                        panic("release_spinlock: attempt to release lock %p with interrupts enabled\n", lock);

                {
                        int32 count = atomic_set(&lock->lock, 0) - 1;
                        if (count < 0) {
                                panic("release_spinlock: lock %p was already released\n", lock);
                        } else {
                                // add to the total count -- deal with carry manually
                                if ((uint32)atomic_add(&lock->count_low, count) + count
                                                < (uint32)count) {
                                        atomic_add(&lock->count_high, 1);
                                }
                        }
                }
        } else {
                if (dummy_are_interrupts_enabled())
                        panic("release_spinlock: attempt to release lock %p with interrupts enabled\n", lock);
                if (atomic_set((int32 *)lock, 0) != 1)
                        panic("release_spinlock: lock %p was already released\n", lock);
        }
}


struct dummy_smp_msg *
dummy_check_for_message(int currentCPU, int *source_mailbox)
{
        struct dummy_smp_msg *msg;

        if (!sICIEnabled)
                return NULL;

        dummy_acquire_spinlock_nocheck(&cpu_msg_spinlock[currentCPU]);
        msg = smp_msgs[currentCPU];
        if (msg != NULL) {
                printf("yeah\n");
                dummy_release_spinlock(&cpu_msg_spinlock[currentCPU]);
                *source_mailbox = 1;
        } else {
                // try getting one from the broadcast mailbox

                dummy_release_spinlock(&cpu_msg_spinlock[currentCPU]);
                dummy_acquire_spinlock_nocheck(&broadcast_msg_spinlock);

                msg = smp_broadcast_msgs;
                while (msg != NULL) {
                        printf("yeah\n");
                        msg = msg->next;
                }
                dummy_release_spinlock(&broadcast_msg_spinlock);
        }
        return msg;
}


int32
dummy_process_pending_ici(int32 currentCPU)
{
        int retval = 42;
        int sourceMailbox = 0;

        dummy_smp_msg* msg = dummy_check_for_message(currentCPU, &sourceMailbox);
        if (msg == NULL)
                return retval;

        switch ((addr_t)msg) {
                case 0:
                        printf("foo\n");
                        break;
                case 1:
                        printf("foo\n");
                        break;
                case 2:
                        printf("foo\n");
                        break;
        }

        return 9;
}


static void
test_spinlock(dummy_spinlock* lock)
{
        while (atomic_add(&lock->lock, -1) != 0)
                dummy_process_pending_ici(0);
}


static double
estimate_spinlock_tick_time()
{
        // time the spinlock
        int32 count = (INT_MAX >> 16) + 1;
        while (true) {
                bigtime_t startTime = system_time();

                dummy_spinlock lock;
                lock.lock = count;
                test_spinlock(&lock);

                bigtime_t totalTime = system_time() - startTime;
                double tickTime = (double)totalTime / count;

                if (totalTime > 1000000 || INT_MAX >> 2 < count)
                        return tickTime;

                count <<= 1;
        }
}


static const char*
time_string(double timeInUsecs, char* buffer)
{
        static const char* const kUnits[] = { "us", "ms", "s ", NULL };

        double time = timeInUsecs;

        int32 i = 0;
        while (time > 1000 && kUnits[i + 1] != NULL) {
                time /= 1000;
                i++;
        }

        sprintf(buffer, "%.3f %s", time, kUnits[i]);

        return buffer;
}


int
main(int argc, char** argv)
{
        // get the initial contention info
        spinlock_contention_info startInfo;
        status_t error = _kern_generic_syscall(SPINLOCK_CONTENTION,
                GET_SPINLOCK_CONTENTION_INFO, &startInfo, sizeof(startInfo));
        if (error != B_OK) {
                fprintf(stderr, "Error: Failed to get spinlock contention info: %s\n",
                        strerror(error));
                exit(1);
        }
        bigtime_t startTime = system_time();

        pid_t child = fork();
        if (child < 0) {
                fprintf(stderr, "Error: fork() failed: %s\n", strerror(errno));
                exit(1);
        }

        if (child == 0) {
                execvp(argv[1], argv + 1);
                fprintf(stderr, "Error: exec() failed: %s\n", strerror(errno));
                exit(1);
        } else {
                int status;
                wait(&status);
        }

        // get the final contention info
        spinlock_contention_info endInfo;
        error = _kern_generic_syscall(SPINLOCK_CONTENTION,
                GET_SPINLOCK_CONTENTION_INFO, &endInfo, sizeof(endInfo));
        if (error != B_OK) {
                fprintf(stderr, "Error: Failed to get spinlock contention info: %s\n",
                        strerror(error));
                exit(1);
        }
        bigtime_t totalTime = system_time() - startTime;

        char buffer[128];
        printf("\ntotal run time: %s\n", time_string(totalTime, buffer));

        // estimate spinlock tick time
        printf("estimating time per spinlock tick...\n");
        double tickTime = estimate_spinlock_tick_time();
        printf("time per spinlock tick: %s\n",
                time_string(tickTime, buffer));

        // print results
        static const char* const kLockNames[] = { "thread creation", NULL };
        bigtime_t lockCounts[] = {
                endInfo.thread_creation_spinlock - startInfo.thread_creation_spinlock,
        };

        printf("\nlock             counter            time   wasted %% CPU\n");
        printf("-------------------------------------------------------\n");
        for (int32 i = 0; kLockNames[i] != NULL; i++) {
                double wastedUsecs = lockCounts[i] * tickTime;
                printf("%-10s  %12llu  %14s   %12.4f\n", kLockNames[i], lockCounts[i],
                        time_string(wastedUsecs, buffer), wastedUsecs / totalTime * 100);
        }

        return 0;
}