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