//
// SingBench.c - A benchmark program
//
// Copyright Microsoft Corporation.
// All rights reserved.
//

#include <winlean.h>
#include <stdlib.h>
#include <stdio.h>

LARGE_INTEGER frequency;

int perfLoopCount = 10000;

HANDLE waitPerfEvent1, waitPerfEvent2;
ULONG64 startCount1, endCount1;
ULONG64 startCount2, endCount2;


void ErrorExit(char *function)
{
    VOID *message;
    DWORD error = GetLastError();

    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_FROM_SYSTEM,
        NULL,
        error,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR) &message,
        0, NULL );

    printf("%s failed with error %d: %s", function, error, message);

    LocalFree(message);
    ExitProcess(error);
}


//
// Read the processor's time stamp counter register
//
#define GetCycleCount() __rdtsc()
ULONG64
__rdtsc(VOID);
#pragma intrinsic(__rdtsc)


//
// Measure GetCycleCount call.
//
void DoGetCCPerf()
{
    ULONG64 before, after, elapsed;
    ULONG64 now;
    int loop;

    printf("\nMeasuring GetCycleCount call\n");

    before = GetCycleCount();

    for (loop = 0; loop < perfLoopCount; loop++) {
        now = GetCycleCount();
    }

    after = GetCycleCount();

    elapsed = after - before;
    printf("  Elapsed cycles = %d\n", elapsed);
    printf("  cycles/iteration = %d\n", elapsed / perfLoopCount);
}

//
// Measure ABI call.
//
void DoABIPerf()
{
    ULONG64 before, after, elapsed;
    int loop;

    DWORD dummy;

    printf("\nMeasuring ABI call using GetProcessId\n");

    before = GetCycleCount();

    for (loop = 0; loop < perfLoopCount; loop++) {
        dummy = GetProcessId(GetCurrentProcess());
    }

    after = GetCycleCount();

    elapsed = after - before;
    printf("  Elapsed cycles = %d\n", elapsed);
    printf("  cycles/iteration = %d\n", elapsed / perfLoopCount);
}


//
// Measure ABI call.
//
void DoABIPerf2()
{
    ULONG64 before, after, elapsed;
    int loop;

    LARGE_INTEGER dummy;

    printf("\nMeasuring ABI call using QueryPerformanceFrequency\n");

    before = GetCycleCount();

    for (loop = 0; loop < perfLoopCount; loop++) {
        (void)QueryPerformanceFrequency(&dummy);
    }

    after = GetCycleCount();

    elapsed = after - before;
    printf("  Elapsed cycles = %d\n", elapsed);
    printf("  cycles/iteration = %d\n", elapsed / perfLoopCount);
}

void DoABIPerf3()
{
    HANDLE hFile;
    LPCSTR lpFileName;

    char tempBuffer[TMP_MAX];

    ULONG64 before, after, elapsed;
    int loop;

    printf("\nMeasuring ABI call using SetFilePointer\n");

    lpFileName = tmpnam(tempBuffer);
    hFile = CreateFile(lpFileName, 0, FILE_SHARE_DELETE, NULL, CREATE_NEW,
                       FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE,
                       NULL);

    if (hFile == INVALID_HANDLE_VALUE)
    {
        ErrorExit("CreateFile");
        return;
    }

    before = GetCycleCount();

    for (loop = 0; loop < perfLoopCount; loop++) {
        SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
    }

    after = GetCycleCount();

    elapsed = after - before;
    printf("  Elapsed cycles = %d\n", elapsed);
    printf("  cycles/iteration = %d\n", elapsed / perfLoopCount);
}

void DoABIPerf4()
{
    HANDLE hFile;
    LPCSTR lpFileName;

    char tempBuffer[TMP_MAX];

    ULONG64 before, after, elapsed;
    int loop;

    printf("\nMeasuring ABI call using GetFilePointer\n");

    lpFileName = tmpnam(tempBuffer);
    hFile = CreateFile(lpFileName, 0, FILE_SHARE_DELETE, NULL, CREATE_NEW,
                       FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE,
                       NULL);

    if (hFile == INVALID_HANDLE_VALUE)
    {
        ErrorExit("CreateFile");
        return;
    }

    before = GetCycleCount();

    for (loop = 0; loop < perfLoopCount; loop++) {
        // Returns file pointer position
        SetFilePointer(hFile, 0, NULL, FILE_CURRENT);
    }

    after = GetCycleCount();

    elapsed = after - before;
    printf("  Elapsed cycles = %d\n", elapsed);
    printf("  cycles/iteration = %d\n", elapsed / perfLoopCount);
}

static volatile int g_threads = 0;

typedef struct s_YieldStats
{
    ULONG64 start;
    ULONG64 end;
    UINT32  calls;
} YieldStats;

static DWORD WINAPI ThreadYieldTestMain(LPVOID lpParameter)
{
    YieldStats* stats = (YieldStats*)lpParameter;

    g_threads++;

    while (g_threads != 2)
    {
        Sleep(0);
    }

    stats->start = GetCycleCount();

    while (stats->calls++ < 10000)
    {
        Sleep(0);
    }

    stats->end = GetCycleCount();

    g_threads--;
    while (g_threads != 0)
    {
        Sleep(0);
    }
    return 0;
}

//
// Measure thread yield.
//
void DoYieldPerf()
{
    YieldStats stats1, stats2;
    HANDLE thread1;

    printf("\nYield performance\n");

    ZeroMemory(&stats1, sizeof(YieldStats));
    ZeroMemory(&stats2, sizeof(YieldStats));

    thread1 = CreateThread(NULL, 0, &ThreadYieldTestMain,
                           (LPVOID) &stats1, 0, NULL);
    if (thread1 == INVALID_HANDLE_VALUE)
    {
        ErrorExit("CreateThread(thread1)");
    }

    ThreadYieldTestMain((LPVOID) &stats2);

    CloseHandle(thread1);

    printf("Thread1 yield perf = %I64u / (2 * %u) = %I64u\n",
           stats1.end - stats1.start, stats1.calls,
           (stats1.end - stats1.start) / (2 * stats1.calls));

    printf("Thread2 yield perf = %I64u / (2 * %u) = %I64u\n",
           stats2.end - stats2.start, stats2.calls,
           (stats2.end - stats2.start) / (2 * stats2.calls));
}

//
// Part of DoWaitPerf.
//
DWORD WINAPI WaitPerfThread1(void *foo)
{
    int loop;

    startCount1 = GetCycleCount();
    for (loop = 0; loop < perfLoopCount; loop++) {
        SetEvent(waitPerfEvent2);
        WaitForSingleObject(waitPerfEvent1, INFINITE);
    }
    endCount1 = GetCycleCount();

    return 0;
}


//
// Another part of DoWaitPerf.
//
DWORD WINAPI WaitPerfThread2(void *foo)
{
    int loop;

    startCount2 = GetCycleCount();

    for (loop = 0; loop < perfLoopCount; loop++) {
        WaitForSingleObject(waitPerfEvent2, INFINITE);
        SetEvent(waitPerfEvent1);
    }
    SetEvent(waitPerfEvent1);

    endCount2 = GetCycleCount();

    return 0;
}

//
// Measure waiting/signaling of auto-reset events.
//
void DoWaitPerf()
{
    HANDLE thread1, thread2;

    //
    // Create the auto-reset events.
    //
    waitPerfEvent1 = CreateEvent(NULL, FALSE, FALSE, NULL);
    if (waitPerfEvent1 == NULL) {
        ErrorExit("CreateEvent");
    }
    waitPerfEvent2 = CreateEvent(NULL, FALSE, FALSE, NULL);
    if (waitPerfEvent2 == NULL) {
        ErrorExit("CreateEvent");
    }

    printf("\nStarting WaitTest threads\n");

    //
    // Create the threads.
    //
    thread1 = CreateThread(NULL, 0, WaitPerfThread1, NULL,
                           CREATE_SUSPENDED, NULL);
    if (thread1 == NULL) {
        ErrorExit("CreateThread");
    }
    thread2 = CreateThread(NULL, 0, WaitPerfThread2, NULL,
                           CREATE_SUSPENDED, NULL);
    if (thread2 == NULL) {
        ErrorExit("CreateThread");
    }

    //
    // Start the threads running.
    //
    if (ResumeThread(thread2) == -1) {
        ErrorExit("ResumeThread");
    }
    if (ResumeThread(thread1) == -1) {
        ErrorExit("ResumeThread");
    }

    //
    // Output results after both threads have finished.
    //
    printf("Waiting for threads...\n");
    WaitForSingleObject(thread2, INFINITE);
    printf("Thread 2 finished\n");
    printf("  Elapsed cycles = %d\n", endCount2 - startCount2);
    printf("  cycles/iteration = %d\n",
           (endCount2 - startCount2) / perfLoopCount);
    WaitForSingleObject(thread1, INFINITE);
    printf("Thread 1 finished\n");
    printf("  Elapsed cycles = %d\n", endCount1 - startCount1);
    printf("  cycles/iteration = %d\n",
           (endCount1 - startCount1) / perfLoopCount);


    (void)CloseHandle(waitPerfEvent1);
    (void)CloseHandle(waitPerfEvent2);
}


//
// Client process for DoPong.
//
void DoPing(bytes)
{
    HANDLE pipe;
    DWORD mode;
    char *buffer;
    int loop;
    int got, wrote;

    //
    // Allocate some buffer space.
    //
    buffer = malloc(bytes);

    //
    // Establish connection to server.
    //
    pipe = CreateFile("\\\\.\\pipe\\SingBench",
                      GENERIC_READ | GENERIC_WRITE,
                      0,
                      NULL,
                      OPEN_EXISTING,
                      0,
                      NULL);
    if (pipe == INVALID_HANDLE_VALUE) {
        ErrorExit("CreateFile");
    }

    //
    // We want message, not byte-stream mode.
    //
    mode = PIPE_READMODE_MESSAGE;
    if (!SetNamedPipeHandleState(pipe, &mode, NULL, NULL)) {
        ErrorExit("SetNamedPipeHandleState");
    }

    //
    // Time the exchange of messages.
    //
    for (loop = 0; loop < perfLoopCount; loop++) {
        //
        // Write the message to the server.
        //
        if (!WriteFile(pipe, buffer, bytes, &wrote, NULL) || wrote != bytes) {
            ErrorExit("WriteFile");
        }
        //
        // Read the message back.
        //
        if (!ReadFile(pipe, buffer, bytes, &got, NULL) || got != bytes) {
            ErrorExit("ReadFile");
        }
    }

    CloseHandle(pipe);
}


//
// Test sending messages back and forth between processes.
//
void DoPong(int bytes)
{
    char szCmdLine[256];
    char *buffer;
    HANDLE pipe;
    BOOL created, connected;
    STARTUPINFO startInfo;
    PROCESS_INFORMATION procInfo;
    ULONG64 before, after, delta;
    int loop;
    int got, wrote;

    printf("\nStarting ping-pong test with %d byte messages\n", bytes);

    //
    // Allocate some buffer space.
    //
    buffer = malloc(bytes);

    //
    // We use named pipes instead of anonymous pipes because anonymous
    // pipes are only one-way, while named pipes can be duplex.
    //
    // Named pipes can either be byte-stream or message-based.  We're
    // using message-based (closer match to Singularity Channels).
    // We also set our pipe to blocking mode.
    //
    pipe = CreateNamedPipe("\\\\.\\pipe\\SingBench",  // Pipe name.
        PIPE_ACCESS_DUPLEX,  // Bi-directional.
        PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,  // Pipe mode.
        1,  // MaxInstances.
        bytes,  // OutBufferSize.
        bytes,  // InBufferSize.
        INFINITE,  // DefaultTimeOut.
        NULL);  // SecurityAttributes.
    if (pipe == INVALID_HANDLE_VALUE) {
        free(buffer);
        ErrorExit("CreateNamedPipe");
    }

    //
    // Spawn child (client) process.
    //
    sprintf(szCmdLine, "SingBench %d", bytes);
    printf("Launching child process: %s\n", szCmdLine);
    ZeroMemory(&startInfo, sizeof(startInfo));
    startInfo.cb = sizeof(startInfo);
    created = CreateProcess("SingBench.exe",  // Application name to start.
                            szCmdLine,  // Command line for new process.
                            NULL,  // ? child process can inherit this process.
                            NULL,  // ? child threads can inherit this process.
                            FALSE,  // New process inherits our handles?
                            0,  // Process creation flags.
                            NULL,  // Environment.
                            NULL,  // Current directory.
                            &startInfo,  // Startup info.
                            &procInfo);  // Out: Process info.
    if (!created) {
        //
        // Failed to create process.
        //
        free(buffer);
        ErrorExit("CreateProcess");
    }


    //
    // Wait for child to phone home (if it hasn't already).
    //
    connected = ConnectNamedPipe(pipe, NULL);
    if (!connected && GetLastError() != ERROR_PIPE_CONNECTED) {
        free(buffer);
        ErrorExit("ConnectNamedPipe");
    }

    //
    // Time the exchange of messages.
    //
    before = GetCycleCount();
    for (loop = 0; loop < perfLoopCount; loop++) {
        //
        // Read the message from the client.
        //
        if (!ReadFile(pipe, buffer, bytes, &got, NULL) || got != bytes) {
            ErrorExit("ReadFile");
        }
        //
        // Write the message back.
        //
        if (!WriteFile(pipe, buffer, bytes, &wrote, NULL) || wrote != bytes) {
            ErrorExit("WriteFile");
        }
    }
    after = GetCycleCount();
    delta = (after - before) / 2;

    FlushFileBuffers(pipe);
    DisconnectNamedPipe(pipe);
    CloseHandle(pipe);
    free(buffer);

    printf("Done playing ping-pong with %d byte messages\n", bytes);
    printf("  Elapsed cycles = %d\n", delta);
    printf("  cycles/iteration = %d\n", delta / perfLoopCount);
}


int __cdecl
main(int argc, char **argv)
{
    int i;

    if (argc > 1) {
        //
        // If called with an argument, we're the DoPong client.
        //
        DoPing(atoi(argv[1]));
        ExitProcess(0);
    }

    printf("\nSingBench\n\n");

    //
    // The frequency of the high-resolution performance counter cannot change
    // while the system is running.
    //
    if (QueryPerformanceFrequency(&frequency) == 0) {
        ErrorExit("QueryPerformanceFrequency");
    }
    printf("Performance Counter Frequency = %I64u counts/second\n", frequency);

    DoGetCCPerf();
    DoABIPerf();
    DoABIPerf2();
    DoABIPerf3();
    DoABIPerf4();
    DoYieldPerf();
    DoWaitPerf();

    for (i = 1; i <= 65536; i *= 2) {
        DoPong(i);
    }
}
