From 2a48ded78bd0fecf50012bc56c288f637f615c4f Mon Sep 17 00:00:00 2001 From: Ingo Weinhold Date: Sat, 18 Apr 2009 17:41:46 +0000 Subject: [PATCH] Added a simple tool "scheduling_recorder" that records scheduling information (as generated by the system profiling interface) to a file for later analysis. Only the analysis tool is missing, yet. :-) git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@30245 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- src/bin/debug/Jamfile | 1 + src/bin/debug/scheduling_recorder/Jamfile | 16 + .../scheduling_recorder.cpp | 314 ++++++++++++++++++ 3 files changed, 331 insertions(+) create mode 100644 src/bin/debug/scheduling_recorder/Jamfile create mode 100644 src/bin/debug/scheduling_recorder/scheduling_recorder.cpp diff --git a/src/bin/debug/Jamfile b/src/bin/debug/Jamfile index 0a493bd701..4a6e20f0a5 100644 --- a/src/bin/debug/Jamfile +++ b/src/bin/debug/Jamfile @@ -9,5 +9,6 @@ StaticLibrary debug_utils.a : debug_utils.cpp ; HaikuSubInclude ltrace ; HaikuSubInclude profile ; +HaikuSubInclude scheduling_recorder ; HaikuSubInclude strace ; HaikuSubInclude time_stats ; diff --git a/src/bin/debug/scheduling_recorder/Jamfile b/src/bin/debug/scheduling_recorder/Jamfile new file mode 100644 index 0000000000..6544576915 --- /dev/null +++ b/src/bin/debug/scheduling_recorder/Jamfile @@ -0,0 +1,16 @@ +SubDir HAIKU_TOP src bin debug scheduling_recorder ; + +UsePrivateHeaders debug kernel libroot shared ; +UsePrivateSystemHeaders ; + +SubDirHdrs [ FDirName $(SUBDIR) $(DOTDOT) ] ; + +BinCommand scheduling_recorder + : + scheduling_recorder.cpp + : + debug_utils.a + libdebug.so + be + $(TARGET_LIBSTDC++) +; diff --git a/src/bin/debug/scheduling_recorder/scheduling_recorder.cpp b/src/bin/debug/scheduling_recorder/scheduling_recorder.cpp new file mode 100644 index 0000000000..31b29daf2a --- /dev/null +++ b/src/bin/debug/scheduling_recorder/scheduling_recorder.cpp @@ -0,0 +1,314 @@ +/* + * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include + +#include "debug_utils.h" + + +#define SCHEDULING_RECORDING_AREA_SIZE (4 * 1024 * 1024) + +#define DEBUG_EVENT_MASK (B_SYSTEM_PROFILER_TEAM_EVENTS \ + | B_SYSTEM_PROFILER_THREAD_EVENTS \ + | B_SYSTEM_PROFILER_SCHEDULING_EVENTS) + + +extern const char* __progname; +const char* kCommandName = __progname; + + +static const char* kUsage = + "Usage: %s [ ] [ ]\n" + "Records thread scheduling information to a file for later analysis.\n" + "If a command line is given, recording starts right before\" + "executing the command and steps when the respective team quits.\n" + "\n" + "Options:\n" + " -l - When a command line is given: Start recording before\n" + " executable has been loaded.\n" + " -h, --help - Print this usage info.\n" +; + + +static void +print_usage_and_exit(bool error) +{ + fprintf(error ? stderr : stdout, kUsage, kCommandName); + exit(error ? 1 : 0); +} + + +class Recorder { +public: + Recorder() + : + fMainTeam(-1), + fSkipLoading(true), + fCaughtDeadlySignal(false) + { + } + + ~Recorder() + { + fOutput.Flush(); + } + + + status_t Init(const char* outputFile) + { + // open file + status_t error = fOutputFile.SetTo(outputFile, + B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE); + if (error != B_OK) { + fprintf(stderr, "Error: Failed to open \"%s\": %s\n", outputFile, + strerror(error)); + return error; + } + + // create output stream + error = fOutput.SetTo(&fOutputFile, 0, DEBUG_EVENT_MASK); + if (error != B_OK) { + fprintf(stderr, "Error: Failed to initialize the output " + "stream: %s\n", strerror(error)); + return error; + } + + return B_OK; + } + + void SetSkipLoading(bool skipLoading) + { + fSkipLoading = skipLoading; + } + + void Run(const char* const* programArgs, int programArgCount) + { + // Load the executable, if we have to. + thread_id threadID = -1; + if (programArgCount >= 1) { + threadID = load_program(programArgs, programArgCount, + !fSkipLoading); + if (threadID < 0) { + fprintf(stderr, "%s: Failed to start `%s': %s\n", kCommandName, + programArgs[0], strerror(threadID)); + exit(1); + } + fMainTeam = threadID; + } + + // install signal handlers so we can exit gracefully + struct sigaction action; + action.sa_handler = (sighandler_t)_SignalHandler; + sigemptyset(&action.sa_mask); + action.sa_userdata = this; + if (sigaction(SIGHUP, &action, NULL) < 0 + || sigaction(SIGINT, &action, NULL) < 0 + || sigaction(SIGQUIT, &action, NULL) < 0) { + fprintf(stderr, "%s: Failed to install signal handlers: %s\n", + kCommandName, strerror(errno)); + exit(1); + } + + // create an area for the sample buffer + system_profiler_buffer_header* bufferHeader; + area_id area = create_area("profiling buffer", (void**)&bufferHeader, + B_ANY_ADDRESS, SCHEDULING_RECORDING_AREA_SIZE, B_NO_LOCK, + B_READ_AREA | B_WRITE_AREA); + if (area < 0) { + fprintf(stderr, "%s: Failed to create sample area: %s\n", + kCommandName, strerror(area)); + exit(1); + } + + uint8* bufferBase = (uint8*)(bufferHeader + 1); + size_t totalBufferSize = SCHEDULING_RECORDING_AREA_SIZE + - (bufferBase - (uint8*)bufferHeader); + + // start profiling + system_profiler_parameters profilerParameters; + profilerParameters.buffer_area = area; + profilerParameters.flags = DEBUG_EVENT_MASK; + profilerParameters.locking_lookup_size = 64 * 1024; + + status_t error = _kern_system_profiler_start(&profilerParameters); + if (error != B_OK) { + fprintf(stderr, "%s: Failed to start profiling: %s\n", kCommandName, + strerror(error)); + exit(1); + } + + // resume the loaded team, if we have one + if (threadID >= 0) + resume_thread(threadID); + + // main event loop + while (true) { + // get the current buffer + size_t bufferStart = bufferHeader->start; + size_t bufferSize = bufferHeader->size; + uint8* buffer = bufferBase + bufferStart; +//printf("processing buffer of size %lu bytes\n", bufferSize); + + bool quit; + if (bufferStart + bufferSize <= totalBufferSize) { + quit = _ProcessEventBuffer(buffer, bufferSize); + } else { + size_t remainingSize = bufferStart + bufferSize + - totalBufferSize; + quit = _ProcessEventBuffer(buffer, bufferSize - remainingSize) + || _ProcessEventBuffer(bufferBase, remainingSize); + } + + if (quit) + break; + + // get next buffer + error = _kern_system_profiler_next_buffer(bufferSize); + + if (error != B_OK) { + if (error == B_INTERRUPTED) { + if (fCaughtDeadlySignal) + break; + continue; + } + + fprintf(stderr, "%s: Failed to get next sample buffer: %s\n", + kCommandName, strerror(error)); + break; + } + } + + // stop profiling + _kern_system_profiler_stop(); + } + +private: + bool _ProcessEventBuffer(uint8* buffer, size_t bufferSize) + { +//printf("_ProcessEventBuffer(%p, %lu)\n", buffer, bufferSize); + const uint8* bufferStart = buffer; + const uint8* bufferEnd = buffer + bufferSize; + size_t usableBufferSize = bufferSize; + bool quit = false; + + while (buffer < bufferEnd) { + system_profiler_event_header* header + = (system_profiler_event_header*)buffer; + + buffer += sizeof(system_profiler_event_header); + + if (header->event == B_SYSTEM_PROFILER_BUFFER_END) { + // Marks the end of the ring buffer -- we need to ignore the + // remaining bytes. + usableBufferSize = (uint8*)header - bufferStart; + break; + } + + if (header->event == B_SYSTEM_PROFILER_TEAM_REMOVED) { + system_profiler_team_removed* event + = (system_profiler_team_removed*)buffer; + + // quit, if the main team we're interested in is gone + if (fMainTeam >= 0 && event->team == fMainTeam) { + usableBufferSize = buffer + header->size - bufferStart; + quit = true; + break; + } + } + + buffer += header->size; + } + + // write buffer to file + if (usableBufferSize > 0) { + status_t error = fOutput.Write(bufferStart, usableBufferSize); + if (error != B_OK) { + fprintf(stderr, "%s: Failed to write buffer: %s\n", + kCommandName, strerror(error)); + quit = true; + } + } + + return quit; + } + + + static void _SignalHandler(int signal, void* data) + { + Recorder* self = (Recorder*)data; + self->fCaughtDeadlySignal = true; + } + +private: + BFile fOutputFile; + BDebugEventOutputStream fOutput; + team_id fMainTeam; + bool fSkipLoading; + bool fCaughtDeadlySignal; +}; + + +int +main(int argc, const char* const* argv) +{ + Recorder recorder; + + while (true) { + static struct option sLongOptions[] = { + { "help", no_argument, 0, 'h' }, + { 0, 0, 0, 0 } + }; + + opterr = 0; // don't print errors + int c = getopt_long(argc, (char**)argv, "+hl", sLongOptions, NULL); + if (c == -1) + break; + + switch (c) { + case 'h': + print_usage_and_exit(false); + break; + case 'l': + recorder.SetSkipLoading(false); + break; + + default: + print_usage_and_exit(true); + break; + } + } + + // Remaining arguments should be the output file and the optional command + // line. + if (optind >= argc) + print_usage_and_exit(true); + + const char* outputFile = argv[optind++]; + const char* const* programArgs = argv + optind; + int programArgCount = argc - optind; + + // prepare for battle + if (recorder.Init(outputFile) != B_OK) + exit(1); + + // start the action + recorder.Run(programArgs, programArgCount); + + return 0; +}