1/* 2 * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 7#include <errno.h> 8#include <fcntl.h> 9#include <getopt.h> 10#include <stdio.h> 11#include <stdlib.h> 12#include <string.h> 13#include <sys/stat.h> 14#include <unistd.h> 15 16#include <File.h> 17 18#include <syscalls.h> 19#include <system_profiler_defs.h> 20 21#include <DebugEventStream.h> 22 23#include "debug_utils.h" 24 25 26#define SCHEDULING_RECORDING_AREA_SIZE (4 * 1024 * 1024) 27 28#define DEBUG_EVENT_MASK \ 29 (B_SYSTEM_PROFILER_TEAM_EVENTS | B_SYSTEM_PROFILER_THREAD_EVENTS \ 30 | B_SYSTEM_PROFILER_SCHEDULING_EVENTS \ 31 | B_SYSTEM_PROFILER_IO_SCHEDULING_EVENTS) 32 33 34extern const char* __progname; 35const char* kCommandName = __progname; 36 37 38static const char* kUsage = 39 "Usage: %s [ <options> ] <output file> [ <command line> ]\n" 40 "Records thread scheduling information to a file for later analysis.\n" 41 "If a command line <command line> is given, recording starts right before\n" 42 "executing the command and stops when the respective team quits.\n" 43 "\n" 44 "Options:\n" 45 " -l - When a command line is given: Start recording before\n" 46 " executable has been loaded.\n" 47 " -h, --help - Print this usage info.\n" 48; 49 50 51static void 52print_usage_and_exit(bool error) 53{ 54 fprintf(error ? stderr : stdout, kUsage, kCommandName); 55 exit(error ? 1 : 0); 56} 57 58 59class Recorder { 60public: 61 Recorder() 62 : 63 fMainTeam(-1), 64 fSkipLoading(true), 65 fCaughtDeadlySignal(false) 66 { 67 } 68 69 ~Recorder() 70 { 71 fOutput.Flush(); 72 } 73 74 75 status_t Init(const char* outputFile) 76 { 77 // open file 78 status_t error = fOutputFile.SetTo(outputFile, 79 B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE); 80 if (error != B_OK) { 81 fprintf(stderr, "Error: Failed to open \"%s\": %s\n", outputFile, 82 strerror(error)); 83 return error; 84 } 85 86 // create output stream 87 error = fOutput.SetTo(&fOutputFile, 0, DEBUG_EVENT_MASK); 88 if (error != B_OK) { 89 fprintf(stderr, "Error: Failed to initialize the output " 90 "stream: %s\n", strerror(error)); 91 return error; 92 } 93 94 return B_OK; 95 } 96 97 void SetSkipLoading(bool skipLoading) 98 { 99 fSkipLoading = skipLoading; 100 } 101 102 void Run(const char* const* programArgs, int programArgCount) 103 { 104 // Load the executable, if we have to. 105 thread_id threadID = -1; 106 if (programArgCount >= 1) { 107 threadID = load_program(programArgs, programArgCount, 108 !fSkipLoading); 109 if (threadID < 0) { 110 fprintf(stderr, "%s: Failed to start `%s': %s\n", kCommandName, 111 programArgs[0], strerror(threadID)); 112 exit(1); 113 } 114 fMainTeam = threadID; 115 } 116 117 // install signal handlers so we can exit gracefully 118 struct sigaction action; 119 action.sa_handler = (__sighandler_t)_SignalHandler; 120 action.sa_flags = 0; 121 sigemptyset(&action.sa_mask); 122 action.sa_userdata = this; 123 if (sigaction(SIGHUP, &action, NULL) < 0 124 || sigaction(SIGINT, &action, NULL) < 0 125 || sigaction(SIGQUIT, &action, NULL) < 0) { 126 fprintf(stderr, "%s: Failed to install signal handlers: %s\n", 127 kCommandName, strerror(errno)); 128 exit(1); 129 } 130 131 // create an area for the sample buffer 132 system_profiler_buffer_header* bufferHeader; 133 area_id area = create_area("profiling buffer", (void**)&bufferHeader, 134 B_ANY_ADDRESS, SCHEDULING_RECORDING_AREA_SIZE, B_NO_LOCK, 135 B_READ_AREA | B_WRITE_AREA); 136 if (area < 0) { 137 fprintf(stderr, "%s: Failed to create sample area: %s\n", 138 kCommandName, strerror(area)); 139 exit(1); 140 } 141 142 uint8* bufferBase = (uint8*)(bufferHeader + 1); 143 size_t totalBufferSize = SCHEDULING_RECORDING_AREA_SIZE 144 - (bufferBase - (uint8*)bufferHeader); 145 146 // start profiling 147 system_profiler_parameters profilerParameters; 148 profilerParameters.buffer_area = area; 149 profilerParameters.flags = DEBUG_EVENT_MASK; 150 profilerParameters.locking_lookup_size = 64 * 1024; 151 152 status_t error = _kern_system_profiler_start(&profilerParameters); 153 if (error != B_OK) { 154 fprintf(stderr, "%s: Failed to start profiling: %s\n", kCommandName, 155 strerror(error)); 156 exit(1); 157 } 158 159 // resume the loaded team, if we have one 160 if (threadID >= 0) 161 resume_thread(threadID); 162 163 // main event loop 164 while (true) { 165 // get the current buffer 166 size_t bufferStart = bufferHeader->start; 167 size_t bufferSize = bufferHeader->size; 168 uint8* buffer = bufferBase + bufferStart; 169//printf("processing buffer of size %lu bytes\n", bufferSize); 170 171 bool quit; 172 if (bufferStart + bufferSize <= totalBufferSize) { 173 quit = _ProcessEventBuffer(buffer, bufferSize); 174 } else { 175 size_t remainingSize = bufferStart + bufferSize 176 - totalBufferSize; 177 quit = _ProcessEventBuffer(buffer, bufferSize - remainingSize) 178 || _ProcessEventBuffer(bufferBase, remainingSize); 179 } 180 181 if (quit || fCaughtDeadlySignal) 182 break; 183 184 // get next buffer 185 uint64 droppedEvents = 0; 186 error = _kern_system_profiler_next_buffer(bufferSize, 187 &droppedEvents); 188 189 if (error != B_OK) { 190 if (error == B_INTERRUPTED) 191 continue; 192 193 fprintf(stderr, "%s: Failed to get next sample buffer: %s\n", 194 kCommandName, strerror(error)); 195 break; 196 } 197 198 if (droppedEvents > 0) 199 fprintf(stderr, "%llu events dropped\n", droppedEvents); 200 } 201 202 // stop profiling 203 _kern_system_profiler_stop(); 204 } 205 206private: 207 bool _ProcessEventBuffer(uint8* buffer, size_t bufferSize) 208 { 209//printf("_ProcessEventBuffer(%p, %lu)\n", buffer, bufferSize); 210 const uint8* bufferStart = buffer; 211 const uint8* bufferEnd = buffer + bufferSize; 212 size_t usableBufferSize = bufferSize; 213 bool quit = false; 214 215 while (buffer < bufferEnd) { 216 system_profiler_event_header* header 217 = (system_profiler_event_header*)buffer; 218 219 buffer += sizeof(system_profiler_event_header); 220 221 if (header->event == B_SYSTEM_PROFILER_BUFFER_END) { 222 // Marks the end of the ring buffer -- we need to ignore the 223 // remaining bytes. 224 usableBufferSize = (uint8*)header - bufferStart; 225 break; 226 } 227 228 if (header->event == B_SYSTEM_PROFILER_TEAM_REMOVED) { 229 system_profiler_team_removed* event 230 = (system_profiler_team_removed*)buffer; 231 232 // quit, if the main team we're interested in is gone 233 if (fMainTeam >= 0 && event->team == fMainTeam) { 234 usableBufferSize = buffer + header->size - bufferStart; 235 quit = true; 236 break; 237 } 238 } 239 240 buffer += header->size; 241 } 242 243 // write buffer to file 244 if (usableBufferSize > 0) { 245 status_t error = fOutput.Write(bufferStart, usableBufferSize); 246 if (error != B_OK) { 247 fprintf(stderr, "%s: Failed to write buffer: %s\n", 248 kCommandName, strerror(error)); 249 quit = true; 250 } 251 } 252 253 return quit; 254 } 255 256 257 static void _SignalHandler(int signal, void* data) 258 { 259 Recorder* self = (Recorder*)data; 260 self->fCaughtDeadlySignal = true; 261 } 262 263private: 264 BFile fOutputFile; 265 BDebugEventOutputStream fOutput; 266 team_id fMainTeam; 267 bool fSkipLoading; 268 bool fCaughtDeadlySignal; 269}; 270 271 272int 273main(int argc, const char* const* argv) 274{ 275 Recorder recorder; 276 277 while (true) { 278 static struct option sLongOptions[] = { 279 { "help", no_argument, 0, 'h' }, 280 { 0, 0, 0, 0 } 281 }; 282 283 opterr = 0; // don't print errors 284 int c = getopt_long(argc, (char**)argv, "+hl", sLongOptions, NULL); 285 if (c == -1) 286 break; 287 288 switch (c) { 289 case 'h': 290 print_usage_and_exit(false); 291 break; 292 case 'l': 293 recorder.SetSkipLoading(false); 294 break; 295 296 default: 297 print_usage_and_exit(true); 298 break; 299 } 300 } 301 302 // Remaining arguments should be the output file and the optional command 303 // line. 304 if (optind >= argc) 305 print_usage_and_exit(true); 306 307 const char* outputFile = argv[optind++]; 308 const char* const* programArgs = argv + optind; 309 int programArgCount = argc - optind; 310 311 // prepare for battle 312 if (recorder.Init(outputFile) != B_OK) 313 exit(1); 314 315 // start the action 316 recorder.Run(programArgs, programArgCount); 317 318 return 0; 319} 320