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