12a48ded7SIngo Weinhold/*
22a48ded7SIngo Weinhold * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
32a48ded7SIngo Weinhold * Distributed under the terms of the MIT License.
42a48ded7SIngo Weinhold */
52a48ded7SIngo Weinhold
683f19b67SIngo Weinhold
72a48ded7SIngo Weinhold#include <errno.h>
82a48ded7SIngo Weinhold#include <fcntl.h>
92a48ded7SIngo Weinhold#include <getopt.h>
102a48ded7SIngo Weinhold#include <stdio.h>
112a48ded7SIngo Weinhold#include <stdlib.h>
122a48ded7SIngo Weinhold#include <string.h>
132a48ded7SIngo Weinhold#include <sys/stat.h>
142a48ded7SIngo Weinhold#include <unistd.h>
152a48ded7SIngo Weinhold
162a48ded7SIngo Weinhold#include <File.h>
172a48ded7SIngo Weinhold
182a48ded7SIngo Weinhold#include <syscalls.h>
192a48ded7SIngo Weinhold#include <system_profiler_defs.h>
202a48ded7SIngo Weinhold
212a48ded7SIngo Weinhold#include <DebugEventStream.h>
222a48ded7SIngo Weinhold
232a48ded7SIngo Weinhold#include "debug_utils.h"
242a48ded7SIngo Weinhold
252a48ded7SIngo Weinhold
262a48ded7SIngo Weinhold#define SCHEDULING_RECORDING_AREA_SIZE	(4 * 1024 * 1024)
272a48ded7SIngo Weinhold
2883f19b67SIngo Weinhold#define DEBUG_EVENT_MASK \
3083f19b67SIngo Weinhold		| B_SYSTEM_PROFILER_SCHEDULING_EVENTS							\
322a48ded7SIngo Weinhold
332a48ded7SIngo Weinhold
342a48ded7SIngo Weinholdextern const char* __progname;
352a48ded7SIngo Weinholdconst char* kCommandName = __progname;
362a48ded7SIngo Weinhold
372a48ded7SIngo Weinhold
382a48ded7SIngo Weinholdstatic const char* kUsage =
392a48ded7SIngo Weinhold	"Usage: %s [ <options> ] <output file> [ <command line> ]\n"
402a48ded7SIngo Weinhold	"Records thread scheduling information to a file for later analysis.\n"
411bb436e1SIngo Weinhold	"If a command line <command line> is given, recording starts right before\n"
426ed2992fSAxel Dörfler	"executing the command and stops when the respective team quits.\n"
432a48ded7SIngo Weinhold	"\n"
442a48ded7SIngo Weinhold	"Options:\n"
452a48ded7SIngo Weinhold	"  -l           - When a command line is given: Start recording before\n"
462a48ded7SIngo Weinhold	"                 executable has been loaded.\n"
472a48ded7SIngo Weinhold	"  -h, --help   - Print this usage info.\n"
482a48ded7SIngo Weinhold;
492a48ded7SIngo Weinhold
502a48ded7SIngo Weinhold
512a48ded7SIngo Weinholdstatic void
522a48ded7SIngo Weinholdprint_usage_and_exit(bool error)
532a48ded7SIngo Weinhold{
542a48ded7SIngo Weinhold    fprintf(error ? stderr : stdout, kUsage, kCommandName);
552a48ded7SIngo Weinhold    exit(error ? 1 : 0);
562a48ded7SIngo Weinhold}
572a48ded7SIngo Weinhold
582a48ded7SIngo Weinhold
592a48ded7SIngo Weinholdclass Recorder {
602a48ded7SIngo Weinholdpublic:
612a48ded7SIngo Weinhold	Recorder()
622a48ded7SIngo Weinhold		:
632a48ded7SIngo Weinhold		fMainTeam(-1),
642a48ded7SIngo Weinhold		fSkipLoading(true),
652a48ded7SIngo Weinhold		fCaughtDeadlySignal(false)
662a48ded7SIngo Weinhold	{
672a48ded7SIngo Weinhold	}
682a48ded7SIngo Weinhold
692a48ded7SIngo Weinhold	~Recorder()
702a48ded7SIngo Weinhold	{
712a48ded7SIngo Weinhold		fOutput.Flush();
722a48ded7SIngo Weinhold	}
732a48ded7SIngo Weinhold
742a48ded7SIngo Weinhold
752a48ded7SIngo Weinhold	status_t Init(const char* outputFile)
762a48ded7SIngo Weinhold	{
772a48ded7SIngo Weinhold		// open file
782a48ded7SIngo Weinhold		status_t error = fOutputFile.SetTo(outputFile,
792a48ded7SIngo Weinhold			B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE);
802a48ded7SIngo Weinhold		if (error != B_OK) {
812a48ded7SIngo Weinhold			fprintf(stderr, "Error: Failed to open \"%s\": %s\n", outputFile,
822a48ded7SIngo Weinhold				strerror(error));
832a48ded7SIngo Weinhold			return error;
842a48ded7SIngo Weinhold		}
852a48ded7SIngo Weinhold
862a48ded7SIngo Weinhold		// create output stream
872a48ded7SIngo Weinhold		error = fOutput.SetTo(&fOutputFile, 0, DEBUG_EVENT_MASK);
882a48ded7SIngo Weinhold		if (error != B_OK) {
892a48ded7SIngo Weinhold			fprintf(stderr, "Error: Failed to initialize the output "
902a48ded7SIngo Weinhold				"stream: %s\n", strerror(error));
912a48ded7SIngo Weinhold			return error;
922a48ded7SIngo Weinhold		}
932a48ded7SIngo Weinhold
942a48ded7SIngo Weinhold		return B_OK;
952a48ded7SIngo Weinhold	}
962a48ded7SIngo Weinhold
972a48ded7SIngo Weinhold	void SetSkipLoading(bool skipLoading)
982a48ded7SIngo Weinhold	{
992a48ded7SIngo Weinhold		fSkipLoading = skipLoading;
1002a48ded7SIngo Weinhold	}
1012a48ded7SIngo Weinhold
1022a48ded7SIngo Weinhold	void Run(const char* const* programArgs, int programArgCount)
1032a48ded7SIngo Weinhold	{
1042a48ded7SIngo Weinhold		// Load the executable, if we have to.
1052a48ded7SIngo Weinhold		thread_id threadID = -1;
1062a48ded7SIngo Weinhold		if (programArgCount >= 1) {
1072a48ded7SIngo Weinhold			threadID = load_program(programArgs, programArgCount,
1082a48ded7SIngo Weinhold				!fSkipLoading);
1092a48ded7SIngo Weinhold			if (threadID < 0) {
1102a48ded7SIngo Weinhold				fprintf(stderr, "%s: Failed to start `%s': %s\n", kCommandName,
1112a48ded7SIngo Weinhold					programArgs[0], strerror(threadID));
1122a48ded7SIngo Weinhold				exit(1);
1132a48ded7SIngo Weinhold			}
1142a48ded7SIngo Weinhold			fMainTeam = threadID;
1152a48ded7SIngo Weinhold		}
1162a48ded7SIngo Weinhold
1172a48ded7SIngo Weinhold		// install signal handlers so we can exit gracefully
1182a48ded7SIngo Weinhold		struct sigaction action;
11924df6592SIngo Weinhold		action.sa_handler = (__sighandler_t)_SignalHandler;
12021726702SIngo Weinhold		action.sa_flags = 0;
1212a48ded7SIngo Weinhold		sigemptyset(&action.sa_mask);
1222a48ded7SIngo Weinhold		action.sa_userdata = this;
1232a48ded7SIngo Weinhold		if (sigaction(SIGHUP, &action, NULL) < 0
1242a48ded7SIngo Weinhold			|| sigaction(SIGINT, &action, NULL) < 0
1252a48ded7SIngo Weinhold			|| sigaction(SIGQUIT, &action, NULL) < 0) {
1262a48ded7SIngo Weinhold			fprintf(stderr, "%s: Failed to install signal handlers: %s\n",
1272a48ded7SIngo Weinhold				kCommandName, strerror(errno));
1282a48ded7SIngo Weinhold			exit(1);
1292a48ded7SIngo Weinhold		}
1302a48ded7SIngo Weinhold
1312a48ded7SIngo Weinhold		// create an area for the sample buffer
1322a48ded7SIngo Weinhold		system_profiler_buffer_header* bufferHeader;
1332a48ded7SIngo Weinhold		area_id area = create_area("profiling buffer", (void**)&bufferHeader,
1352a48ded7SIngo Weinhold			B_READ_AREA | B_WRITE_AREA);
1362a48ded7SIngo Weinhold		if (area < 0) {
1372a48ded7SIngo Weinhold			fprintf(stderr, "%s: Failed to create sample area: %s\n",
1382a48ded7SIngo Weinhold				kCommandName, strerror(area));
1392a48ded7SIngo Weinhold			exit(1);
1402a48ded7SIngo Weinhold		}
1412a48ded7SIngo Weinhold
1422a48ded7SIngo Weinhold		uint8* bufferBase = (uint8*)(bufferHeader + 1);
1432a48ded7SIngo Weinhold		size_t totalBufferSize = SCHEDULING_RECORDING_AREA_SIZE
1442a48ded7SIngo Weinhold			- (bufferBase - (uint8*)bufferHeader);
1452a48ded7SIngo Weinhold
1462a48ded7SIngo Weinhold		// start profiling
1472a48ded7SIngo Weinhold		system_profiler_parameters profilerParameters;
1482a48ded7SIngo Weinhold		profilerParameters.buffer_area = area;
1492a48ded7SIngo Weinhold		profilerParameters.flags = DEBUG_EVENT_MASK;
1502a48ded7SIngo Weinhold		profilerParameters.locking_lookup_size = 64 * 1024;
1512a48ded7SIngo Weinhold
1522a48ded7SIngo Weinhold		status_t error = _kern_system_profiler_start(&profilerParameters);
1532a48ded7SIngo Weinhold		if (error != B_OK) {
1542a48ded7SIngo Weinhold			fprintf(stderr, "%s: Failed to start profiling: %s\n", kCommandName,
1552a48ded7SIngo Weinhold				strerror(error));
1562a48ded7SIngo Weinhold			exit(1);
1572a48ded7SIngo Weinhold		}
1582a48ded7SIngo Weinhold
1592a48ded7SIngo Weinhold		// resume the loaded team, if we have one
1602a48ded7SIngo Weinhold		if (threadID >= 0)
1612a48ded7SIngo Weinhold			resume_thread(threadID);
1622a48ded7SIngo Weinhold
1632a48ded7SIngo Weinhold		// main event loop
1642a48ded7SIngo Weinhold		while (true) {
1652a48ded7SIngo Weinhold			// get the current buffer
1662a48ded7SIngo Weinhold			size_t bufferStart = bufferHeader->start;
1672a48ded7SIngo Weinhold			size_t bufferSize = bufferHeader->size;
1682a48ded7SIngo Weinhold			uint8* buffer = bufferBase + bufferStart;
1692a48ded7SIngo Weinhold//printf("processing buffer of size %lu bytes\n", bufferSize);
1702a48ded7SIngo Weinhold
1712a48ded7SIngo Weinhold			bool quit;
1722a48ded7SIngo Weinhold			if (bufferStart + bufferSize <= totalBufferSize) {
1732a48ded7SIngo Weinhold				quit = _ProcessEventBuffer(buffer, bufferSize);
1742a48ded7SIngo Weinhold			} else {
1752a48ded7SIngo Weinhold				size_t remainingSize = bufferStart + bufferSize
1762a48ded7SIngo Weinhold					- totalBufferSize;
1772a48ded7SIngo Weinhold				quit = _ProcessEventBuffer(buffer, bufferSize - remainingSize)
1782a48ded7SIngo Weinhold					|| _ProcessEventBuffer(bufferBase, remainingSize);
1792a48ded7SIngo Weinhold			}
1802a48ded7SIngo Weinhold
18121726702SIngo Weinhold			if (quit || fCaughtDeadlySignal)
1822a48ded7SIngo Weinhold				break;
1832a48ded7SIngo Weinhold
1842a48ded7SIngo Weinhold			// get next buffer
185227fe7d3SIngo Weinhold			uint64 droppedEvents = 0;
186227fe7d3SIngo Weinhold			error = _kern_system_profiler_next_buffer(bufferSize,
187227fe7d3SIngo Weinhold				&droppedEvents);
1882a48ded7SIngo Weinhold
1892a48ded7SIngo Weinhold			if (error != B_OK) {
19021726702SIngo Weinhold				if (error == B_INTERRUPTED)
1912a48ded7SIngo Weinhold					continue;
1922a48ded7SIngo Weinhold
1932a48ded7SIngo Weinhold				fprintf(stderr, "%s: Failed to get next sample buffer: %s\n",
1942a48ded7SIngo Weinhold					kCommandName, strerror(error));
1952a48ded7SIngo Weinhold				break;
1962a48ded7SIngo Weinhold			}
197227fe7d3SIngo Weinhold
198227fe7d3SIngo Weinhold			if (droppedEvents > 0)
199227fe7d3SIngo Weinhold				fprintf(stderr, "%llu events dropped\n", droppedEvents);
2002a48ded7SIngo Weinhold		}
2012a48ded7SIngo Weinhold
2022a48ded7SIngo Weinhold		// stop profiling
2032a48ded7SIngo Weinhold		_kern_system_profiler_stop();
2042a48ded7SIngo Weinhold	}
2052a48ded7SIngo Weinhold
2062a48ded7SIngo Weinholdprivate:
2072a48ded7SIngo Weinhold	bool _ProcessEventBuffer(uint8* buffer, size_t bufferSize)
2082a48ded7SIngo Weinhold	{
2092a48ded7SIngo Weinhold//printf("_ProcessEventBuffer(%p, %lu)\n", buffer, bufferSize);
2102a48ded7SIngo Weinhold		const uint8* bufferStart = buffer;
2112a48ded7SIngo Weinhold		const uint8* bufferEnd = buffer + bufferSize;
2122a48ded7SIngo Weinhold		size_t usableBufferSize = bufferSize;
2132a48ded7SIngo Weinhold		bool quit = false;
2142a48ded7SIngo Weinhold
2152a48ded7SIngo Weinhold		while (buffer < bufferEnd) {
2162a48ded7SIngo Weinhold			system_profiler_event_header* header
2172a48ded7SIngo Weinhold				= (system_profiler_event_header*)buffer;
2182a48ded7SIngo Weinhold
2192a48ded7SIngo Weinhold			buffer += sizeof(system_profiler_event_header);
2202a48ded7SIngo Weinhold
2212a48ded7SIngo Weinhold			if (header->event == B_SYSTEM_PROFILER_BUFFER_END) {
2222a48ded7SIngo Weinhold				// Marks the end of the ring buffer -- we need to ignore the
2232a48ded7SIngo Weinhold				// remaining bytes.
2242a48ded7SIngo Weinhold				usableBufferSize = (uint8*)header - bufferStart;
2252a48ded7SIngo Weinhold				break;
2262a48ded7SIngo Weinhold			}
2272a48ded7SIngo Weinhold
2282a48ded7SIngo Weinhold			if (header->event == B_SYSTEM_PROFILER_TEAM_REMOVED) {
2292a48ded7SIngo Weinhold				system_profiler_team_removed* event
2302a48ded7SIngo Weinhold					= (system_profiler_team_removed*)buffer;
2312a48ded7SIngo Weinhold
2322a48ded7SIngo Weinhold				// quit, if the main team we're interested in is gone
2332a48ded7SIngo Weinhold				if (fMainTeam >= 0 && event->team == fMainTeam) {
2342a48ded7SIngo Weinhold					usableBufferSize = buffer + header->size - bufferStart;
2352a48ded7SIngo Weinhold					quit = true;
2362a48ded7SIngo Weinhold					break;
2372a48ded7SIngo Weinhold				}
2382a48ded7SIngo Weinhold			}
2392a48ded7SIngo Weinhold
2402a48ded7SIngo Weinhold			buffer += header->size;
2412a48ded7SIngo Weinhold		}
2422a48ded7SIngo Weinhold
2432a48ded7SIngo Weinhold		// write buffer to file
2442a48ded7SIngo Weinhold		if (usableBufferSize > 0) {
2452a48ded7SIngo Weinhold			status_t error = fOutput.Write(bufferStart, usableBufferSize);
2462a48ded7SIngo Weinhold			if (error != B_OK) {
2472a48ded7SIngo Weinhold				fprintf(stderr, "%s: Failed to write buffer: %s\n",
2482a48ded7SIngo Weinhold					kCommandName, strerror(error));
2492a48ded7SIngo Weinhold				quit = true;
2502a48ded7SIngo Weinhold			}
2512a48ded7SIngo Weinhold		}
2522a48ded7SIngo Weinhold
2532a48ded7SIngo Weinhold		return quit;
2542a48ded7SIngo Weinhold	}
2552a48ded7SIngo Weinhold
2562a48ded7SIngo Weinhold
2572a48ded7SIngo Weinhold	static void _SignalHandler(int signal, void* data)
2582a48ded7SIngo Weinhold	{
2592a48ded7SIngo Weinhold		Recorder* self = (Recorder*)data;
2602a48ded7SIngo Weinhold		self->fCaughtDeadlySignal = true;
2612a48ded7SIngo Weinhold	}
2622a48ded7SIngo Weinhold
2632a48ded7SIngo Weinholdprivate:
2642a48ded7SIngo Weinhold	BFile					fOutputFile;
2652a48ded7SIngo Weinhold	BDebugEventOutputStream	fOutput;
2662a48ded7SIngo Weinhold	team_id					fMainTeam;
2672a48ded7SIngo Weinhold	bool					fSkipLoading;
2682a48ded7SIngo Weinhold	bool					fCaughtDeadlySignal;
2692a48ded7SIngo Weinhold};
2702a48ded7SIngo Weinhold
2712a48ded7SIngo Weinhold
2722a48ded7SIngo Weinholdint
2732a48ded7SIngo Weinholdmain(int argc, const char* const* argv)
2742a48ded7SIngo Weinhold{
2752a48ded7SIngo Weinhold	Recorder recorder;
2762a48ded7SIngo Weinhold
2772a48ded7SIngo Weinhold	while (true) {
2782a48ded7SIngo Weinhold		static struct option sLongOptions[] = {
2792a48ded7SIngo Weinhold			{ "help", no_argument, 0, 'h' },
2802a48ded7SIngo Weinhold			{ 0, 0, 0, 0 }
2812a48ded7SIngo Weinhold		};
2822a48ded7SIngo Weinhold
2832a48ded7SIngo Weinhold		opterr = 0; // don't print errors
2842a48ded7SIngo Weinhold		int c = getopt_long(argc, (char**)argv, "+hl", sLongOptions, NULL);
2852a48ded7SIngo Weinhold		if (c == -1)
2862a48ded7SIngo Weinhold			break;
2872a48ded7SIngo Weinhold
2882a48ded7SIngo Weinhold		switch (c) {
2892a48ded7SIngo Weinhold			case 'h':
2902a48ded7SIngo Weinhold				print_usage_and_exit(false);
2912a48ded7SIngo Weinhold				break;
2922a48ded7SIngo Weinhold			case 'l':
2932a48ded7SIngo Weinhold				recorder.SetSkipLoading(false);
2942a48ded7SIngo Weinhold				break;
2952a48ded7SIngo Weinhold
2962a48ded7SIngo Weinhold			default:
2972a48ded7SIngo Weinhold				print_usage_and_exit(true);
2982a48ded7SIngo Weinhold				break;
2992a48ded7SIngo Weinhold		}
3002a48ded7SIngo Weinhold	}
3012a48ded7SIngo Weinhold
3022a48ded7SIngo Weinhold	// Remaining arguments should be the output file and the optional command
3032a48ded7SIngo Weinhold	// line.
3042a48ded7SIngo Weinhold	if (optind >= argc)
3052a48ded7SIngo Weinhold		print_usage_and_exit(true);
3062a48ded7SIngo Weinhold
3072a48ded7SIngo Weinhold	const char* outputFile = argv[optind++];
3082a48ded7SIngo Weinhold	const char* const* programArgs = argv + optind;
3092a48ded7SIngo Weinhold	int programArgCount = argc - optind;
3102a48ded7SIngo Weinhold
3112a48ded7SIngo Weinhold	// prepare for battle
3122a48ded7SIngo Weinhold	if (recorder.Init(outputFile) != B_OK)
3132a48ded7SIngo Weinhold		exit(1);
3142a48ded7SIngo Weinhold
3152a48ded7SIngo Weinhold	// start the action
3162a48ded7SIngo Weinhold	recorder.Run(programArgs, programArgCount);
3172a48ded7SIngo Weinhold
3182a48ded7SIngo Weinhold	return 0;
3192a48ded7SIngo Weinhold}