1227652Sgrehan/* 2252707Sbryanv * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de. 3227652Sgrehan * Distributed under the terms of the MIT License. 4227652Sgrehan */ 5227652Sgrehan 6227652Sgrehan 7227652Sgrehan#include <errno.h> 8227652Sgrehan#include <fcntl.h> 9227652Sgrehan#include <getopt.h> 10227652Sgrehan#include <stdio.h> 11227652Sgrehan#include <stdlib.h> 12227652Sgrehan#include <string.h> 13227652Sgrehan#include <sys/stat.h> 14227652Sgrehan#include <unistd.h> 15227652Sgrehan 16227652Sgrehan#include <File.h> 17227652Sgrehan 18227652Sgrehan#include <syscalls.h> 19227652Sgrehan#include <system_profiler_defs.h> 20227652Sgrehan 21227652Sgrehan#include <DebugEventStream.h> 22227652Sgrehan 23227652Sgrehan#include "debug_utils.h" 24227652Sgrehan 25227652Sgrehan 26227652Sgrehan#define SCHEDULING_RECORDING_AREA_SIZE (4 * 1024 * 1024) 27227652Sgrehan 28227652Sgrehan#define DEBUG_EVENT_MASK \ 29227652Sgrehan (B_SYSTEM_PROFILER_TEAM_EVENTS | B_SYSTEM_PROFILER_THREAD_EVENTS \ 30227652Sgrehan | B_SYSTEM_PROFILER_SCHEDULING_EVENTS \ 31227652Sgrehan | B_SYSTEM_PROFILER_IO_SCHEDULING_EVENTS) 32227652Sgrehan 33227652Sgrehan 34227652Sgrehanextern const char* __progname; 35227652Sgrehanconst char* kCommandName = __progname; 36227652Sgrehan 37227652Sgrehan 38227652Sgrehanstatic const char* kUsage = 39227652Sgrehan "Usage: %s [ <options> ] <output file> [ <command line> ]\n" 40227652Sgrehan "Records thread scheduling information to a file for later analysis.\n" 41227652Sgrehan "If a command line <command line> is given, recording starts right before\n" 42227652Sgrehan "executing the command and stops when the respective team quits.\n" 43227652Sgrehan "\n" 44227652Sgrehan "Options:\n" 45227652Sgrehan " -l - When a command line is given: Start recording before\n" 46227652Sgrehan " executable has been loaded.\n" 47227652Sgrehan " -h, --help - Print this usage info.\n" 48227652Sgrehan; 49227652Sgrehan 50227652Sgrehan 51227652Sgrehanstatic void 52227652Sgrehanprint_usage_and_exit(bool error) 53227652Sgrehan{ 54252708Sbryanv fprintf(error ? stderr : stdout, kUsage, kCommandName); 55252708Sbryanv exit(error ? 1 : 0); 56252708Sbryanv} 57252708Sbryanv 58252708Sbryanv 59252708Sbryanvclass Recorder { 60252708Sbryanvpublic: 61252708Sbryanv Recorder() 62252708Sbryanv : 63252708Sbryanv fMainTeam(-1), 64252708Sbryanv fSkipLoading(true), 65227652Sgrehan fCaughtDeadlySignal(false) 66227652Sgrehan { 67227652Sgrehan } 68227652Sgrehan 69227652Sgrehan ~Recorder() 70227652Sgrehan { 71238360Sgrehan fOutput.Flush(); 72238360Sgrehan } 73238360Sgrehan 74238360Sgrehan 75238360Sgrehan status_t Init(const char* outputFile) 76238360Sgrehan { 77238360Sgrehan // open file 78227652Sgrehan status_t error = fOutputFile.SetTo(outputFile, 79238360Sgrehan B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE); 80227652Sgrehan if (error != B_OK) { 81227652Sgrehan fprintf(stderr, "Error: Failed to open \"%s\": %s\n", outputFile, 82227652Sgrehan strerror(error)); 83227652Sgrehan return error; 84252708Sbryanv } 85227652Sgrehan 86227652Sgrehan // create output stream 87252708Sbryanv error = fOutput.SetTo(&fOutputFile, 0, DEBUG_EVENT_MASK); 88252708Sbryanv if (error != B_OK) { 89252708Sbryanv fprintf(stderr, "Error: Failed to initialize the output " 90252708Sbryanv "stream: %s\n", strerror(error)); 91252708Sbryanv return error; 92227652Sgrehan } 93252708Sbryanv 94252708Sbryanv return B_OK; 95227652Sgrehan } 96252708Sbryanv 97252708Sbryanv void SetSkipLoading(bool skipLoading) 98252708Sbryanv { 99227652Sgrehan fSkipLoading = skipLoading; 100227652Sgrehan } 101227652Sgrehan 102227652Sgrehan void Run(const char* const* programArgs, int programArgCount) 103227652Sgrehan { 104227652Sgrehan // Load the executable, if we have to. 105227652Sgrehan thread_id threadID = -1; 106227652Sgrehan if (programArgCount >= 1) { 107227652Sgrehan threadID = load_program(programArgs, programArgCount, 108227652Sgrehan !fSkipLoading); 109227652Sgrehan if (threadID < 0) { 110227652Sgrehan fprintf(stderr, "%s: Failed to start `%s': %s\n", kCommandName, 111227652Sgrehan programArgs[0], strerror(threadID)); 112227652Sgrehan exit(1); 113227652Sgrehan } 114227652Sgrehan fMainTeam = threadID; 115227652Sgrehan } 116227652Sgrehan 117227652Sgrehan // install signal handlers so we can exit gracefully 118227652Sgrehan struct sigaction action; 119227652Sgrehan action.sa_handler = (__sighandler_t)_SignalHandler; 120227652Sgrehan action.sa_flags = 0; 121227652Sgrehan sigemptyset(&action.sa_mask); 122227652Sgrehan action.sa_userdata = this; 123227652Sgrehan if (sigaction(SIGHUP, &action, NULL) < 0 124227652Sgrehan || sigaction(SIGINT, &action, NULL) < 0 125227652Sgrehan || sigaction(SIGQUIT, &action, NULL) < 0) { 126227652Sgrehan fprintf(stderr, "%s: Failed to install signal handlers: %s\n", 127227652Sgrehan kCommandName, strerror(errno)); 128227652Sgrehan exit(1); 129227652Sgrehan } 130252708Sbryanv 131252708Sbryanv // create an area for the sample buffer 132252708Sbryanv system_profiler_buffer_header* bufferHeader; 133252708Sbryanv area_id area = create_area("profiling buffer", (void**)&bufferHeader, 134252708Sbryanv B_ANY_ADDRESS, SCHEDULING_RECORDING_AREA_SIZE, B_NO_LOCK, 135252708Sbryanv B_READ_AREA | B_WRITE_AREA); 136252708Sbryanv if (area < 0) { 137252708Sbryanv fprintf(stderr, "%s: Failed to create sample area: %s\n", 138238360Sgrehan kCommandName, strerror(area)); 139238360Sgrehan exit(1); 140252708Sbryanv } 141238360Sgrehan 142252708Sbryanv uint8* bufferBase = (uint8*)(bufferHeader + 1); 143238360Sgrehan size_t totalBufferSize = SCHEDULING_RECORDING_AREA_SIZE 144252708Sbryanv - (bufferBase - (uint8*)bufferHeader); 145252708Sbryanv 146252708Sbryanv // start profiling 147238360Sgrehan system_profiler_parameters profilerParameters; 148252708Sbryanv profilerParameters.buffer_area = area; 149252708Sbryanv profilerParameters.flags = DEBUG_EVENT_MASK; 150252708Sbryanv profilerParameters.locking_lookup_size = 64 * 1024; 151252708Sbryanv 152227652Sgrehan status_t error = _kern_system_profiler_start(&profilerParameters); 153252708Sbryanv if (error != B_OK) { 154252708Sbryanv fprintf(stderr, "%s: Failed to start profiling: %s\n", kCommandName, 155227652Sgrehan strerror(error)); 156227652Sgrehan exit(1); 157227652Sgrehan } 158252708Sbryanv 159227652Sgrehan // resume the loaded team, if we have one 160227652Sgrehan if (threadID >= 0) 161238360Sgrehan resume_thread(threadID); 162238360Sgrehan 163252702Sbryanv // main event loop 164252702Sbryanv while (true) { 165252702Sbryanv // get the current buffer 166252702Sbryanv size_t bufferStart = bufferHeader->start; 167252702Sbryanv size_t bufferSize = bufferHeader->size; 168252702Sbryanv uint8* buffer = bufferBase + bufferStart; 169227652Sgrehan//printf("processing buffer of size %lu bytes\n", bufferSize); 170238360Sgrehan 171238360Sgrehan bool quit; 172268010Sbryanv if (bufferStart + bufferSize <= totalBufferSize) { 173268010Sbryanv quit = _ProcessEventBuffer(buffer, bufferSize); 174268010Sbryanv } else { 175227652Sgrehan size_t remainingSize = bufferStart + bufferSize 176227652Sgrehan - totalBufferSize; 177227652Sgrehan quit = _ProcessEventBuffer(buffer, bufferSize - remainingSize) 178227652Sgrehan || _ProcessEventBuffer(bufferBase, remainingSize); 179227652Sgrehan } 180227652Sgrehan 181227652Sgrehan if (quit || fCaughtDeadlySignal) 182227652Sgrehan break; 183227652Sgrehan 184227652Sgrehan // get next buffer 185227652Sgrehan uint64 droppedEvents = 0; 186227652Sgrehan error = _kern_system_profiler_next_buffer(bufferSize, 187227652Sgrehan &droppedEvents); 188227652Sgrehan 189227652Sgrehan if (error != B_OK) { 190227652Sgrehan if (error == B_INTERRUPTED) 191227652Sgrehan continue; 192227652Sgrehan 193227652Sgrehan fprintf(stderr, "%s: Failed to get next sample buffer: %s\n", 194227652Sgrehan kCommandName, strerror(error)); 195227652Sgrehan break; 196227652Sgrehan } 197227652Sgrehan 198227652Sgrehan if (droppedEvents > 0) 199227652Sgrehan fprintf(stderr, "%llu events dropped\n", droppedEvents); 200227652Sgrehan } 201227652Sgrehan 202227652Sgrehan // stop profiling 203227652Sgrehan _kern_system_profiler_stop(); 204227652Sgrehan } 205227652Sgrehan 206227652Sgrehanprivate: 207227652Sgrehan bool _ProcessEventBuffer(uint8* buffer, size_t bufferSize) 208227652Sgrehan { 209227652Sgrehan//printf("_ProcessEventBuffer(%p, %lu)\n", buffer, bufferSize); 210227652Sgrehan const uint8* bufferStart = buffer; 211227652Sgrehan const uint8* bufferEnd = buffer + bufferSize; 212227652Sgrehan size_t usableBufferSize = bufferSize; 213227652Sgrehan bool quit = false; 214227652Sgrehan 215227652Sgrehan while (buffer < bufferEnd) { 216234270Sgrehan system_profiler_event_header* header 217227652Sgrehan = (system_profiler_event_header*)buffer; 218227652Sgrehan 219227652Sgrehan buffer += sizeof(system_profiler_event_header); 220227652Sgrehan 221227652Sgrehan if (header->event == B_SYSTEM_PROFILER_BUFFER_END) { 222227652Sgrehan // Marks the end of the ring buffer -- we need to ignore the 223227652Sgrehan // remaining bytes. 224227652Sgrehan usableBufferSize = (uint8*)header - bufferStart; 225227652Sgrehan break; 226227652Sgrehan } 227227652Sgrehan 228227652Sgrehan if (header->event == B_SYSTEM_PROFILER_TEAM_REMOVED) { 229227652Sgrehan system_profiler_team_removed* event 230227652Sgrehan = (system_profiler_team_removed*)buffer; 231227652Sgrehan 232227652Sgrehan // quit, if the main team we're interested in is gone 233227652Sgrehan if (fMainTeam >= 0 && event->team == fMainTeam) { 234227652Sgrehan usableBufferSize = buffer + header->size - bufferStart; 235227652Sgrehan quit = true; 236227652Sgrehan break; 237227652Sgrehan } 238227652Sgrehan } 239227652Sgrehan 240227652Sgrehan buffer += header->size; 241227652Sgrehan } 242227652Sgrehan 243227652Sgrehan // write buffer to file 244227652Sgrehan if (usableBufferSize > 0) { 245227652Sgrehan status_t error = fOutput.Write(bufferStart, usableBufferSize); 246227652Sgrehan if (error != B_OK) { 247227652Sgrehan fprintf(stderr, "%s: Failed to write buffer: %s\n", 248227652Sgrehan kCommandName, strerror(error)); 249227652Sgrehan quit = true; 250227652Sgrehan } 251227652Sgrehan } 252227652Sgrehan 253227652Sgrehan return quit; 254227652Sgrehan } 255227652Sgrehan 256227652Sgrehan 257227652Sgrehan static void _SignalHandler(int signal, void* data) 258227652Sgrehan { 259227652Sgrehan Recorder* self = (Recorder*)data; 260227652Sgrehan self->fCaughtDeadlySignal = true; 261227652Sgrehan } 262227652Sgrehan 263227652Sgrehanprivate: 264227652Sgrehan BFile fOutputFile; 265227652Sgrehan BDebugEventOutputStream fOutput; 266227652Sgrehan team_id fMainTeam; 267227652Sgrehan bool fSkipLoading; 268227652Sgrehan bool fCaughtDeadlySignal; 269227652Sgrehan}; 270227652Sgrehan 271227652Sgrehan 272227652Sgrehanint 273227652Sgrehanmain(int argc, const char* const* argv) 274227652Sgrehan{ 275227652Sgrehan Recorder recorder; 276227652Sgrehan 277227652Sgrehan while (true) { 278232470Sjhb static struct option sLongOptions[] = { 279238360Sgrehan { "help", no_argument, 0, 'h' }, 280227652Sgrehan { 0, 0, 0, 0 } 281232470Sjhb }; 282227652Sgrehan 283227652Sgrehan opterr = 0; // don't print errors 284227652Sgrehan int c = getopt_long(argc, (char**)argv, "+hl", sLongOptions, NULL); 285227652Sgrehan if (c == -1) 286227652Sgrehan break; 287227652Sgrehan 288238360Sgrehan switch (c) { 289227652Sgrehan case 'h': 290227652Sgrehan print_usage_and_exit(false); 291227652Sgrehan break; 292227652Sgrehan case 'l': 293227652Sgrehan recorder.SetSkipLoading(false); 294227652Sgrehan break; 295227652Sgrehan 296227652Sgrehan default: 297227652Sgrehan print_usage_and_exit(true); 298227652Sgrehan break; 299227652Sgrehan } 300227652Sgrehan } 301227652Sgrehan 302227652Sgrehan // Remaining arguments should be the output file and the optional command 303227652Sgrehan // line. 304227652Sgrehan if (optind >= argc) 305227652Sgrehan print_usage_and_exit(true); 306227652Sgrehan 307227652Sgrehan const char* outputFile = argv[optind++]; 308227652Sgrehan const char* const* programArgs = argv + optind; 309227652Sgrehan int programArgCount = argc - optind; 310227652Sgrehan 311227652Sgrehan // prepare for battle 312227652Sgrehan if (recorder.Init(outputFile) != B_OK) 313227652Sgrehan exit(1); 314227652Sgrehan 315227652Sgrehan // start the action 316227652Sgrehan recorder.Run(programArgs, programArgCount); 317227652Sgrehan 318227652Sgrehan return 0; 319227652Sgrehan} 320227652Sgrehan