root/src/bin/debug/time_stats/scheduling_analysis.cpp
/*
 * Copyright 2008-2009, 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 <algorithm>

#include <OS.h>

#include <AutoDeleter.h>

#include <scheduler_defs.h>
#include <syscalls.h>
#include <thread_defs.h>

#include "time_stats.h"


struct wait_object_group {
        scheduling_analysis_thread_wait_object**        objects;
        int32                                                                           count;
        bigtime_t                                                                       wait_time;
        int64                                                                           waits;
};


struct ThreadRunTimeComparator {
        inline bool operator()(const scheduling_analysis_thread* a,
                const scheduling_analysis_thread* b)
        {
                return a->total_run_time > b->total_run_time;
        }
};


struct WaitObjectGroupingComparator {
        inline bool operator()(const scheduling_analysis_thread_wait_object* a,
                const scheduling_analysis_thread_wait_object* b)
        {
                return a->wait_object->type < b->wait_object->type
                        || (a->wait_object->type == b->wait_object->type
                                && strcmp(a->wait_object->name, b->wait_object->name) < 0);
        }
};


struct WaitObjectTimeComparator {
        inline bool operator()(const scheduling_analysis_thread_wait_object* a,
                const scheduling_analysis_thread_wait_object* b)
        {
                return a->wait_time > b->wait_time;
        }
};


struct WaitObjectGroupTimeComparator {
        inline bool operator()(const wait_object_group& a,
                const wait_object_group& b)
        {
                return a.wait_time > b.wait_time;
        }
};


static const char*
wait_object_to_string(scheduling_analysis_wait_object* waitObject, char* buffer,
        bool nameOnly = false)
{
        uint32 type = waitObject->type;
        void* object = waitObject->object;

        switch (type) {
                case THREAD_BLOCK_TYPE_SEMAPHORE:
                        if (nameOnly) {
                                sprintf(buffer, "sem \"%s\"", waitObject->name);
                        } else {
                                sprintf(buffer, "sem %ld (%s)", (sem_id)(addr_t)object,
                                        waitObject->name);
                        }
                        break;
                case THREAD_BLOCK_TYPE_CONDITION_VARIABLE:
                        if (nameOnly) {
                                sprintf(buffer, "cvar \"%s\"", waitObject->name);
                        } else {
                                sprintf(buffer, "cvar %p (%s %p)", object, waitObject->name,
                                        waitObject->referenced_object);
                        }
                        break;
                case THREAD_BLOCK_TYPE_SNOOZE:
                        strcpy(buffer, "snooze");
                        break;
                case THREAD_BLOCK_TYPE_SIGNAL:
                        strcpy(buffer, "signal");
                        break;
                case THREAD_BLOCK_TYPE_MUTEX:
                        if (nameOnly)
                                sprintf(buffer, "mutex \"%s\"", waitObject->name);
                        else
                                sprintf(buffer, "mutex %p (%s)", object, waitObject->name);
                        break;
                case THREAD_BLOCK_TYPE_RW_LOCK:
                        if (nameOnly)
                                sprintf(buffer, "rwlock \"%s\"", waitObject->name);
                        else
                                sprintf(buffer, "rwlock %p (%s)", object, waitObject->name);
                        break;
                case THREAD_BLOCK_TYPE_USER:
                        strcpy(buffer, "user");
                        break;
                case THREAD_BLOCK_TYPE_OTHER:
                        sprintf(buffer, "other %p (%s)", object, waitObject->name);
                        break;
                case THREAD_BLOCK_TYPE_OTHER_OBJECT:
                        sprintf(buffer, "other object %p", object);
                        break;
                default:
                        sprintf(buffer, "unknown %p", object);
                        break;
        }

        return buffer;
}


void
do_scheduling_analysis(bigtime_t startTime, bigtime_t endTime,
        size_t bufferSize)
{
        printf("\n");

        // allocate a chunk of memory for the scheduling analysis
        void* buffer = malloc(bufferSize);
        if (buffer == NULL) {
                fprintf(stderr, "Error: Failed to allocate memory for the scheduling "
                        "analysis.\n");
                exit(1);
        }
        MemoryDeleter _(buffer);

        // do the scheduling analysis
        scheduling_analysis analysis;
        status_t error = _kern_analyze_scheduling(startTime, endTime, buffer,
                bufferSize, &analysis);
        if (error != B_OK) {
                fprintf(stderr, "Error: Scheduling analysis failed: %s\n",
                        strerror(error));
                exit(1);
        }

        // allocate arrays for grouping and sorting the wait objects
        scheduling_analysis_thread_wait_object** waitObjects
                = new(std::nothrow) scheduling_analysis_thread_wait_object*[
                        analysis.thread_wait_object_count];
        ArrayDeleter<scheduling_analysis_thread_wait_object*> _2(waitObjects);

        wait_object_group* waitObjectGroups = new(std::nothrow) wait_object_group[
                analysis.thread_wait_object_count];
        ArrayDeleter<wait_object_group> _3(waitObjectGroups);

        if (waitObjects == NULL || waitObjectGroups == NULL) {
                fprintf(stderr, "Error: Out of memory\n");
                exit(1);
        }

        printf("scheduling analysis: %lu threads, %llu wait objects, "
                "%llu thread wait objects\n", analysis.thread_count,
                analysis.wait_object_count, analysis.thread_wait_object_count);

        // sort the thread by run time
        std::sort(analysis.threads, analysis.threads + analysis.thread_count,
                ThreadRunTimeComparator());

        for (uint32 i = 0; i < analysis.thread_count; i++) {
                scheduling_analysis_thread* thread = analysis.threads[i];

                // compute total wait time and prepare the objects for sorting
                int32 waitObjectCount = 0;
                bigtime_t waitTime = 0;
                scheduling_analysis_thread_wait_object* threadWaitObject
                        = thread->wait_objects;
                while (threadWaitObject != NULL) {
                        waitObjects[waitObjectCount++] = threadWaitObject;
                        waitTime += threadWaitObject->wait_time;
                        threadWaitObject = threadWaitObject->next_in_list;
                }

                // sort the wait objects by type + name
                std::sort(waitObjects, waitObjects + waitObjectCount,
                        WaitObjectGroupingComparator());

                // create the groups
                wait_object_group* group = NULL;
                int32 groupCount = 0;
                for (int32 i = 0; i < waitObjectCount; i++) {
                        scheduling_analysis_thread_wait_object* threadWaitObject
                                = waitObjects[i];
                        scheduling_analysis_wait_object* waitObject
                                = threadWaitObject->wait_object;

                        if (groupCount == 0 || strcmp(waitObject->name, "?") == 0
                                || waitObject->type != group->objects[0]->wait_object->type
                                || strcmp(waitObject->name,
                                                group->objects[0]->wait_object->name) != 0) {
                                // create a new group
                                group = &waitObjectGroups[groupCount++];
                                group->objects = waitObjects + i;
                                group->count = 0;
                                group->wait_time = 0;
                                group->waits = 0;
                        }

                        group->count++;
                        group->wait_time += threadWaitObject->wait_time;
                        group->waits += threadWaitObject->waits;
                }

                // sort the groups by wait time
                std::sort(waitObjectGroups, waitObjectGroups + groupCount,
                        WaitObjectGroupTimeComparator());

                printf("\nthread %ld \"%s\":\n", thread->id, thread->name);
                printf("  run time:    %lld us (%lld runs)\n", thread->total_run_time,
                        thread->runs);
                printf("  wait time:   %lld us\n", waitTime);
                printf("  latencies:   %lld us (%lld)\n", thread->total_latency,
                        thread->latencies);
                printf("  preemptions: %lld us (%lld)\n", thread->total_rerun_time,
                        thread->reruns);
                printf("  unspecified: %lld us\n", thread->unspecified_wait_time);

                printf("  waited on:\n");
                for (int32 i = 0; i < groupCount; i++) {
                        wait_object_group& group = waitObjectGroups[i];
                        char buffer[1024];

                        if (group.count == 1) {
                                // only one element -- just print it
                                scheduling_analysis_thread_wait_object* threadWaitObject
                                        = group.objects[0];
                                scheduling_analysis_wait_object* waitObject
                                        = threadWaitObject->wait_object;
                                wait_object_to_string(waitObject, buffer);
                                printf("    %s: %lld us (%lld)\n", buffer,
                                        threadWaitObject->wait_time, threadWaitObject->waits);
                        } else {
                                // sort the wait objects by wait time
                                std::sort(group.objects, group.objects + group.count,
                                        WaitObjectTimeComparator());

                                // print the group line
                                wait_object_to_string(group.objects[0]->wait_object, buffer,
                                        true);
                                printf("    group %s: %lld us (%lld)\n", buffer,
                                        group.wait_time, group.waits);

                                // print the wait objects
                                for (int32 k = 0; k < group.count; k++) {
                                        scheduling_analysis_thread_wait_object* threadWaitObject
                                                = group.objects[k];
                                        scheduling_analysis_wait_object* waitObject
                                                = threadWaitObject->wait_object;
                                        wait_object_to_string(waitObject, buffer);
                                        printf("      %s: %lld us (%lld)\n", buffer,
                                                threadWaitObject->wait_time, threadWaitObject->waits);
                                }
                        }
                }
        }
}