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
This commit is contained in:
@@ -9,5 +9,6 @@ StaticLibrary <bin>debug_utils.a : debug_utils.cpp ;
|
|||||||
|
|
||||||
HaikuSubInclude ltrace ;
|
HaikuSubInclude ltrace ;
|
||||||
HaikuSubInclude profile ;
|
HaikuSubInclude profile ;
|
||||||
|
HaikuSubInclude scheduling_recorder ;
|
||||||
HaikuSubInclude strace ;
|
HaikuSubInclude strace ;
|
||||||
HaikuSubInclude time_stats ;
|
HaikuSubInclude time_stats ;
|
||||||
|
|||||||
@@ -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
|
||||||
|
:
|
||||||
|
<bin>debug_utils.a
|
||||||
|
libdebug.so
|
||||||
|
be
|
||||||
|
$(TARGET_LIBSTDC++)
|
||||||
|
;
|
||||||
@@ -0,0 +1,314 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2009, Ingo Weinhold, [email protected].
|
||||||
|
* Distributed under the terms of the MIT License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <File.h>
|
||||||
|
|
||||||
|
#include <syscalls.h>
|
||||||
|
#include <system_profiler_defs.h>
|
||||||
|
|
||||||
|
#include <DebugEventStream.h>
|
||||||
|
|
||||||
|
#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 [ <options> ] <output file> [ <command line> ]\n"
|
||||||
|
"Records thread scheduling information to a file for later analysis.\n"
|
||||||
|
"If a command line <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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user