1/*
2 * Copyright 2008-2010, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Copyright 2013, Rene Gollent, rene@gollent.com.
4 * Distributed under the terms of the MIT License.
5 */
6
7
8#include <ctype.h>
9#include <errno.h>
10#include <getopt.h>
11#include <stdio.h>
12#include <stdlib.h>
13#include <string.h>
14
15#include <algorithm>
16#include <map>
17#include <new>
18#include <string>
19
20#include <debugger.h>
21#include <FindDirectory.h>
22#include <OS.h>
23#include <Path.h>
24#include <String.h>
25
26#include <syscalls.h>
27#include <system_profiler_defs.h>
28
29#include <AutoDeleter.h>
30#include <debug_support.h>
31#include <ObjectList.h>
32#include <Referenceable.h>
33
34#include <util/DoublyLinkedList.h>
35
36#include "BasicProfileResult.h"
37#include "CallgrindProfileResult.h"
38#include "debug_utils.h"
39#include "Image.h"
40#include "Options.h"
41#include "SummaryProfileResult.h"
42#include "Team.h"
43
44
45// size of the sample buffer area for system profiling
46#define PROFILE_ALL_SAMPLE_AREA_SIZE	(4 * 1024 * 1024)
47
48
49extern const char* __progname;
50const char* kCommandName = __progname;
51
52
53class Image;
54class Team;
55class Thread;
56
57
58static const char* kUsage =
59	"Usage: %s [ <options> ] [ <command line> ]\n"
60	"Profiles threads by periodically sampling the program counter. There are\n"
61	"two different modes: One profiles the complete system. The other starts\n"
62	"a program and profiles that and (optionally) its children. When a thread\n"
63	"terminates, a list of the functions where the thread was encountered is\n"
64	"printed.\n"
65	"\n"
66	"Options:\n"
67	"  -a, --all      - Profile all teams.\n"
68	"  -c             - Don't profile child threads. Default is to\n"
69	"                   recursively profile all threads created by a profiled\n"
70	"                   thread.\n"
71	"  -C             - Don't profile child teams. Default is to recursively\n"
72	"                   profile all teams created by a profiled team.\n"
73	"  -f             - Always analyze the full caller stack. The hit count\n"
74	"                   for every encountered function will be incremented.\n"
75	"                   This increases the default for the caller stack depth\n"
76	"                   (\"-s\") to 64.\n"
77	"  -h, --help     - Print this usage info.\n"
78	"  -i <interval>  - Use a tick interval of <interval> microseconds.\n"
79	"                   Default is 1000 (1 ms). On a fast machine, a shorter\n"
80	"                   interval might lead to better results, while it might\n"
81	"                   make them worse on slow machines.\n"
82	"  -k             - Don't check kernel images for hits.\n"
83	"  -l             - Also profile loading the executable.\n"
84	"  -o <output>    - Print the results to file <output>.\n"
85	"  -r, --recorded - Don't profile, but evaluate a recorded kernel profile\n"
86	"                   data.\n"
87	"  -s <depth>     - Number of return address samples to take from the\n"
88	"                   caller stack per tick. If the topmost address doesn't\n"
89	"                   hit a known image, the next address will be matched\n"
90	"                   (and so on).\n"
91	"  -S             - Don't output results for individual threads, but\n"
92	"                   produce a combined output at the end.\n"
93	"  -v <directory> - Create valgrind/callgrind output. <directory> is the\n"
94	"                   directory where to put the output files.\n"
95;
96
97
98Options gOptions;
99
100static bool sCaughtDeadlySignal = false;
101
102
103class ThreadManager : private ProfiledEntity {
104public:
105	ThreadManager(port_id debuggerPort)
106		:
107		fTeams(20),
108		fThreads(20, true),
109		fKernelTeam(NULL),
110		fDebuggerPort(debuggerPort),
111		fSummaryProfileResult(NULL)
112	{
113	}
114
115	virtual ~ThreadManager()
116	{
117		// release image references
118		for (ImageMap::iterator it = fImages.begin(); it != fImages.end(); ++it)
119			it->second->ReleaseReference();
120
121		if (fSummaryProfileResult != NULL)
122			fSummaryProfileResult->ReleaseReference();
123
124		for (int32 i = 0; Team* team = fTeams.ItemAt(i); i++)
125			team->ReleaseReference();
126	}
127
128	status_t Init()
129	{
130		if (!gOptions.summary_result)
131			return B_OK;
132
133		ProfileResult* profileResult;
134		status_t error = _CreateProfileResult(this, profileResult);
135		if (error != B_OK)
136			return error;
137
138		BReference<ProfileResult> profileResultReference(profileResult, true);
139
140		fSummaryProfileResult = new(std::nothrow) SummaryProfileResult(
141			profileResult);
142		if (fSummaryProfileResult == NULL)
143			return B_NO_MEMORY;
144
145		return fSummaryProfileResult->Init(profileResult->Entity());
146	}
147
148	status_t AddTeam(team_id teamID, Team** _team = NULL)
149	{
150		return _AddTeam(teamID, NULL, _team);
151	}
152
153	status_t AddTeam(system_profiler_team_added* addedInfo, Team** _team = NULL)
154	{
155		return _AddTeam(addedInfo->team, addedInfo, _team);
156	}
157
158	status_t AddThread(thread_id threadID)
159	{
160		thread_info threadInfo;
161		status_t error = get_thread_info(threadID, &threadInfo);
162		if (error != B_OK)
163			return error;
164
165		return AddThread(threadInfo.team, threadID, threadInfo.name);
166	}
167
168	status_t AddThread(team_id teamID, thread_id threadID, const char* name)
169	{
170		if (FindThread(threadID) != NULL)
171			return B_BAD_VALUE;
172
173		Team* team = FindTeam(teamID);
174		if (team == NULL)
175			return B_BAD_TEAM_ID;
176
177		Thread* thread = new(std::nothrow) Thread(threadID, name, team);
178		if (thread == NULL)
179			return B_NO_MEMORY;
180
181		status_t error = _CreateThreadProfileResult(thread);
182		if (error != B_OK) {
183			delete thread;
184			return error;
185		}
186
187		error = team->InitThread(thread);
188		if (error != B_OK) {
189			delete thread;
190			return error;
191		}
192
193		fThreads.AddItem(thread);
194		return B_OK;
195	}
196
197	void RemoveTeam(team_id teamID)
198	{
199		if (Team* team = FindTeam(teamID)) {
200			if (team == fKernelTeam)
201				fKernelTeam = NULL;
202			fTeams.RemoveItem(team);
203			team->ReleaseReference();
204		}
205	}
206
207	void RemoveThread(thread_id threadID)
208	{
209		if (Thread* thread = FindThread(threadID)) {
210			thread->GetTeam()->RemoveThread(thread);
211			fThreads.RemoveItem(thread, true);
212		}
213	}
214
215	Team* FindTeam(team_id teamID) const
216	{
217		for (int32 i = 0; Team* team = fTeams.ItemAt(i); i++) {
218			if (team->ID() == teamID)
219				return team;
220		}
221		return NULL;
222	}
223
224	Thread* FindThread(thread_id threadID) const
225	{
226		for (int32 i = 0; Thread* thread = fThreads.ItemAt(i); i++) {
227			if (thread->ID() == threadID)
228				return thread;
229		}
230		return NULL;
231	}
232
233	int32 CountThreads() const
234	{
235		return fThreads.CountItems();
236	}
237
238	Thread* ThreadAt(int32 index) const
239	{
240		return fThreads.ItemAt(index);
241	}
242
243	status_t AddImage(team_id teamID, const image_info& imageInfo, int32 event)
244	{
245		// get a shared image
246		SharedImage* sharedImage = NULL;
247		status_t error = _GetSharedImage(teamID, imageInfo, &sharedImage);
248		if (error != B_OK)
249			return error;
250
251		if (teamID == B_SYSTEM_TEAM) {
252			// a kernel image -- add it to all teams
253			int32 count = fTeams.CountItems();
254			for (int32 i = 0; i < count; i++) {
255				fTeams.ItemAt(i)->AddImage(sharedImage, imageInfo, teamID,
256					event);
257			}
258		}
259
260		// a userland team image -- add it to that image
261		if (Team* team = FindTeam(teamID))
262			return team->AddImage(sharedImage, imageInfo, teamID, event);
263
264		return B_BAD_TEAM_ID;
265	}
266
267	void RemoveImage(team_id teamID, image_id imageID, int32 event)
268	{
269		if (teamID == B_SYSTEM_TEAM) {
270			// a kernel image -- remove it from all teams
271			int32 count = fTeams.CountItems();
272			for (int32 i = 0; i < count; i++)
273				fTeams.ItemAt(i)->RemoveImage(imageID, event);
274		} else {
275			// a userland team image -- add it to that image
276			if (Team* team = FindTeam(teamID))
277				team->RemoveImage(imageID, event);
278		}
279	}
280
281	void PrintSummaryResults()
282	{
283		if (fSummaryProfileResult != NULL)
284			fSummaryProfileResult->PrintSummaryResults();
285	}
286
287private:
288	virtual int32 EntityID() const
289	{
290		return 1;
291	}
292
293	virtual const char* EntityName() const
294	{
295		return "all";
296	}
297
298	virtual const char* EntityType() const
299	{
300		return "summary";
301	}
302
303private:
304	status_t _AddTeam(team_id teamID, system_profiler_team_added* addedInfo,
305		Team** _team = NULL)
306	{
307		if (FindTeam(teamID) != NULL)
308			return B_BAD_VALUE;
309
310		Team* team = new(std::nothrow) Team;
311		if (team == NULL)
312			return B_NO_MEMORY;
313
314		status_t error = addedInfo != NULL
315			? _InitUndebuggedTeam(team, addedInfo)
316			: _InitDebuggedTeam(team, teamID);
317		if (error != B_OK) {
318			team->ReleaseReference();
319			return error;
320		}
321
322		fTeams.AddItem(team);
323
324		if (teamID == B_SYSTEM_TEAM)
325			fKernelTeam = team;
326
327		if (_team != NULL)
328			*_team = team;
329
330		return B_OK;
331	}
332
333	status_t _InitDebuggedTeam(Team* team, team_id teamID)
334	{
335		// init the team
336		status_t error = team->Init(teamID, fDebuggerPort);
337		if (error != B_OK)
338			return error;
339
340		// add the team's images
341		error = _LoadTeamImages(team, teamID);
342		if (error != B_OK)
343			return error;
344
345		// add the kernel images
346		return _LoadTeamImages(team, B_SYSTEM_TEAM);
347	}
348
349	status_t _InitUndebuggedTeam(Team* team,
350		system_profiler_team_added* addedInfo)
351	{
352		// init the team
353		status_t error = team->Init(addedInfo);
354		if (error != B_OK)
355			return error;
356
357		// in case of a user team, add the kernel images
358		if (team->ID() == B_SYSTEM_TEAM || fKernelTeam == NULL)
359			return B_OK;
360
361		const BObjectList<Image>& kernelImages = fKernelTeam->Images();
362		int32 count = kernelImages.CountItems();
363		for (int32 i = 0; i < count; i++) {
364			SharedImage* sharedImage = kernelImages.ItemAt(i)->GetSharedImage();
365			team->AddImage(sharedImage, sharedImage->Info(), B_SYSTEM_TEAM, 0);
366		}
367
368		return B_OK;
369	}
370
371	status_t _LoadTeamImages(Team* team, team_id teamID)
372	{
373		// iterate through the team's images and collect the symbols
374		image_info imageInfo;
375		int32 cookie = 0;
376		while (get_next_image_info(teamID, &cookie, &imageInfo) == B_OK) {
377			// get a shared image
378			SharedImage* sharedImage;
379			status_t error = _GetSharedImage(teamID, imageInfo, &sharedImage);
380			if (error != B_OK)
381				return error;
382
383			// add the image to the team
384			error = team->AddImage(sharedImage, imageInfo, teamID, 0);
385			if (error != B_OK)
386				return error;
387		}
388
389		return B_OK;
390	}
391
392	status_t _CreateThreadProfileResult(Thread* thread)
393	{
394		if (fSummaryProfileResult != NULL) {
395			thread->SetProfileResult(fSummaryProfileResult);
396			return B_OK;
397		}
398
399		ProfileResult* profileResult;
400		status_t error = _CreateProfileResult(thread, profileResult);
401		if (error != B_OK)
402			return error;
403
404		thread->SetProfileResult(profileResult);
405
406		return B_OK;
407	}
408
409	status_t _CreateProfileResult(ProfiledEntity* profiledEntity,
410		ProfileResult*& _profileResult)
411	{
412		ProfileResult* profileResult;
413
414		if (gOptions.callgrind_directory != NULL)
415			profileResult = new(std::nothrow) CallgrindProfileResult;
416		else if (gOptions.analyze_full_stack)
417			profileResult = new(std::nothrow) InclusiveProfileResult;
418		else
419			profileResult = new(std::nothrow) ExclusiveProfileResult;
420
421		if (profileResult == NULL)
422			return B_NO_MEMORY;
423
424		BReference<ProfileResult> profileResultReference(profileResult, true);
425
426		status_t error = profileResult->Init(profiledEntity);
427		if (error != B_OK)
428			return error;
429
430		_profileResult = profileResultReference.Detach();
431		return B_OK;
432	}
433
434	status_t _GetSharedImage(team_id teamID, const image_info& imageInfo,
435		SharedImage** _sharedImage)
436	{
437		// check whether the image has already been loaded
438		ImageMap::iterator it = fImages.find(imageInfo.name);
439		if (it != fImages.end()) {
440			*_sharedImage = it->second;
441			return B_OK;
442		}
443
444		// create the shared image
445		SharedImage* sharedImage = new(std::nothrow) SharedImage;
446		if (sharedImage == NULL)
447			return B_NO_MEMORY;
448		ObjectDeleter<SharedImage> imageDeleter(sharedImage);
449
450		// load the symbols
451		status_t error;
452		if (teamID == B_SYSTEM_TEAM) {
453			error = sharedImage->Init(teamID, imageInfo.id);
454			if (error != B_OK) {
455				// The image has obviously been unloaded already, try to get
456				// it by path.
457				BString name = imageInfo.name;
458				if (name.FindFirst('/') == -1) {
459					// modules without a path are likely to be boot modules
460					BPath bootAddonPath;
461					if (find_directory(B_SYSTEM_ADDONS_DIRECTORY,
462							&bootAddonPath) == B_OK
463						&& bootAddonPath.Append("kernel") == B_OK
464						&& bootAddonPath.Append("boot") == B_OK) {
465						name = BString(bootAddonPath.Path()) << "/" << name;
466				}
467				}
468
469				error = sharedImage->Init(name.String());
470			}
471		} else if (strcmp(imageInfo.name, "commpage") == 0)
472			error = sharedImage->Init(teamID, imageInfo.id);
473		else
474			error = sharedImage->Init(imageInfo.name);
475		if (error != B_OK)
476			return error;
477
478		try {
479			fImages[sharedImage->Name()] = sharedImage;
480		} catch (std::bad_alloc&) {
481			return B_NO_MEMORY;
482		}
483
484		imageDeleter.Detach();
485		*_sharedImage = sharedImage;
486		return B_OK;
487	}
488
489private:
490	typedef std::map<std::string, SharedImage*> ImageMap;
491
492private:
493	BObjectList<Team>				fTeams;
494	BObjectList<Thread>				fThreads;
495	ImageMap						fImages;
496	Team*							fKernelTeam;
497	port_id							fDebuggerPort;
498	SummaryProfileResult*			fSummaryProfileResult;
499};
500
501
502static void
503print_usage_and_exit(bool error)
504{
505    fprintf(error ? stderr : stdout, kUsage, __progname);
506    exit(error ? 1 : 0);
507}
508
509
510/*
511// get_id
512static bool
513get_id(const char *str, int32 &id)
514{
515	int32 len = strlen(str);
516	for (int32 i = 0; i < len; i++) {
517		if (!isdigit(str[i]))
518			return false;
519	}
520
521	id = atol(str);
522	return true;
523}
524*/
525
526
527static bool
528process_event_buffer(ThreadManager& threadManager, uint8* buffer,
529	size_t bufferSize, team_id mainTeam)
530{
531//printf("process_event_buffer(%p, %lu)\n", buffer, bufferSize);
532	const uint8* bufferEnd = buffer + bufferSize;
533
534	while (buffer < bufferEnd) {
535		system_profiler_event_header* header
536			= (system_profiler_event_header*)buffer;
537
538		buffer += sizeof(system_profiler_event_header);
539
540		switch (header->event) {
541			case B_SYSTEM_PROFILER_TEAM_ADDED:
542			{
543				system_profiler_team_added* event
544					= (system_profiler_team_added*)buffer;
545
546				if (threadManager.AddTeam(event) != B_OK)
547					exit(1);
548				break;
549			}
550
551			case B_SYSTEM_PROFILER_TEAM_REMOVED:
552			{
553				system_profiler_team_removed* event
554					= (system_profiler_team_removed*)buffer;
555
556				threadManager.RemoveTeam(event->team);
557
558				// quit, if the main team we're interested in is gone
559				if (mainTeam >= 0 && event->team == mainTeam)
560					return true;
561
562				break;
563			}
564
565			case B_SYSTEM_PROFILER_TEAM_EXEC:
566			{
567				system_profiler_team_exec* event
568					= (system_profiler_team_exec*)buffer;
569
570				if (Team* team = threadManager.FindTeam(event->team))
571					team->Exec(0, event->args, event->thread_name);
572				break;
573			}
574
575			case B_SYSTEM_PROFILER_THREAD_ADDED:
576			{
577				system_profiler_thread_added* event
578					= (system_profiler_thread_added*)buffer;
579
580				if (threadManager.AddThread(event->team, event->thread,
581						event->name) != B_OK) {
582					exit(1);
583				}
584				break;
585			}
586
587			case B_SYSTEM_PROFILER_THREAD_REMOVED:
588			{
589				system_profiler_thread_removed* event
590					= (system_profiler_thread_removed*)buffer;
591
592				if (Thread* thread = threadManager.FindThread(event->thread)) {
593					thread->PrintResults();
594					threadManager.RemoveThread(event->thread);
595				}
596				break;
597			}
598
599			case B_SYSTEM_PROFILER_IMAGE_ADDED:
600			{
601				system_profiler_image_added* event
602					= (system_profiler_image_added*)buffer;
603
604				threadManager.AddImage(event->team, event->info, 0);
605				break;
606			}
607
608			case B_SYSTEM_PROFILER_IMAGE_REMOVED:
609			{
610				system_profiler_image_removed* event
611					= (system_profiler_image_removed*)buffer;
612
613 				threadManager.RemoveImage(event->team, event->image, 0);
614				break;
615			}
616
617			case B_SYSTEM_PROFILER_SAMPLES:
618			{
619				system_profiler_samples* event
620					= (system_profiler_samples*)buffer;
621
622				Thread* thread = threadManager.FindThread(event->thread);
623				if (thread != NULL) {
624					thread->AddSamples(event->samples,
625						(addr_t*)(buffer + header->size) - event->samples);
626				}
627
628				break;
629			}
630
631			case B_SYSTEM_PROFILER_BUFFER_END:
632			{
633				// Marks the end of the ring buffer -- we need to ignore the
634				// remaining bytes.
635				return false;
636			}
637		}
638
639		buffer += header->size;
640	}
641
642	return false;
643}
644
645
646static void
647signal_handler(int signal, void* data)
648{
649	sCaughtDeadlySignal = true;
650}
651
652
653static void
654profile_all(const char* const* programArgs, int programArgCount)
655{
656	// Load the executable, if we have to.
657	thread_id threadID = -1;
658	if (programArgCount >= 1) {
659		threadID = load_program(programArgs, programArgCount,
660			gOptions.profile_loading);
661		if (threadID < 0) {
662			fprintf(stderr, "%s: Failed to start `%s': %s\n", kCommandName,
663				programArgs[0], strerror(threadID));
664			exit(1);
665		}
666	}
667
668	// install signal handlers so we can exit gracefully
669    struct sigaction action;
670    action.sa_handler = (__sighandler_t)signal_handler;
671    sigemptyset(&action.sa_mask);
672    action.sa_userdata = NULL;
673    if (sigaction(SIGHUP, &action, NULL) < 0
674		|| sigaction(SIGINT, &action, NULL) < 0
675		|| sigaction(SIGQUIT, &action, NULL) < 0) {
676		fprintf(stderr, "%s: Failed to install signal handlers: %s\n",
677			kCommandName, strerror(errno));
678		exit(1);
679    }
680
681	// create an area for the sample buffer
682	system_profiler_buffer_header* bufferHeader;
683	area_id area = create_area("profiling buffer", (void**)&bufferHeader,
684		B_ANY_ADDRESS, PROFILE_ALL_SAMPLE_AREA_SIZE, B_NO_LOCK,
685		B_READ_AREA | B_WRITE_AREA);
686	if (area < 0) {
687		fprintf(stderr, "%s: Failed to create sample area: %s\n", kCommandName,
688			strerror(area));
689		exit(1);
690	}
691
692	uint8* bufferBase = (uint8*)(bufferHeader + 1);
693	size_t totalBufferSize = PROFILE_ALL_SAMPLE_AREA_SIZE
694		- (bufferBase - (uint8*)bufferHeader);
695
696	// create a thread manager
697	ThreadManager threadManager(-1);	// TODO: We don't need a debugger port!
698	status_t error = threadManager.Init();
699	if (error != B_OK) {
700		fprintf(stderr, "%s: Failed to init thread manager: %s\n", kCommandName,
701			strerror(error));
702		exit(1);
703	}
704
705	// start profiling
706	system_profiler_parameters profilerParameters;
707	profilerParameters.buffer_area = area;
708	profilerParameters.flags = B_SYSTEM_PROFILER_TEAM_EVENTS
709		| B_SYSTEM_PROFILER_THREAD_EVENTS | B_SYSTEM_PROFILER_IMAGE_EVENTS
710		| B_SYSTEM_PROFILER_SAMPLING_EVENTS;
711	profilerParameters.interval = gOptions.interval;
712	profilerParameters.stack_depth = gOptions.stack_depth;
713
714	error = _kern_system_profiler_start(&profilerParameters);
715	if (error != B_OK) {
716		fprintf(stderr, "%s: Failed to start profiling: %s\n", kCommandName,
717			strerror(error));
718		exit(1);
719	}
720
721	// resume the loaded team, if we have one
722	if (threadID >= 0)
723		resume_thread(threadID);
724
725	// main event loop
726	while (true) {
727		// get the current buffer
728		size_t bufferStart = bufferHeader->start;
729		size_t bufferSize = bufferHeader->size;
730		uint8* buffer = bufferBase + bufferStart;
731//printf("processing buffer of size %lu bytes\n", bufferSize);
732
733		bool quit;
734		if (bufferStart + bufferSize <= totalBufferSize) {
735			quit = process_event_buffer(threadManager, buffer, bufferSize,
736				threadID);
737		} else {
738			size_t remainingSize = bufferStart + bufferSize - totalBufferSize;
739			quit = process_event_buffer(threadManager, buffer,
740					bufferSize - remainingSize, threadID)
741				|| process_event_buffer(threadManager, bufferBase,
742					remainingSize, threadID);
743		}
744
745		if (quit)
746			break;
747
748		// get next buffer
749		uint64 droppedEvents = 0;
750		error = _kern_system_profiler_next_buffer(bufferSize, &droppedEvents);
751
752		if (error != B_OK) {
753			if (error == B_INTERRUPTED) {
754				if (sCaughtDeadlySignal)
755					break;
756				continue;
757			}
758
759			fprintf(stderr, "%s: Failed to get next sample buffer: %s\n",
760				kCommandName, strerror(error));
761			break;
762		}
763	}
764
765	// stop profiling
766	_kern_system_profiler_stop();
767
768	// print results
769	int32 threadCount = threadManager.CountThreads();
770	for (int32 i = 0; i < threadCount; i++) {
771		Thread* thread = threadManager.ThreadAt(i);
772		thread->PrintResults();
773	}
774
775	threadManager.PrintSummaryResults();
776}
777
778
779static void
780dump_recorded()
781{
782	// retrieve recorded samples and parameters
783	system_profiler_parameters profilerParameters;
784	status_t error = _kern_system_profiler_recorded(&profilerParameters);
785	if (error != B_OK) {
786		fprintf(stderr, "%s: Failed to get recorded profiling buffer: %s\n",
787			kCommandName, strerror(error));
788		exit(1);
789	}
790
791	// set global options to those of the profiler parameters
792	gOptions.interval = profilerParameters.interval;
793	gOptions.stack_depth = profilerParameters.stack_depth;
794
795	// create an area for the sample buffer
796	area_info info;
797	error = get_area_info(profilerParameters.buffer_area, &info);
798	if (error != B_OK) {
799		fprintf(stderr, "%s: Recorded profiling buffer invalid: %s\n",
800			kCommandName, strerror(error));
801		exit(1);
802	}
803
804	system_profiler_buffer_header* bufferHeader
805		= (system_profiler_buffer_header*)info.address;
806
807	uint8* bufferBase = (uint8*)(bufferHeader + 1);
808	size_t totalBufferSize = info.size - (bufferBase - (uint8*)bufferHeader);
809
810	// create a thread manager
811	ThreadManager threadManager(-1);	// TODO: We don't need a debugger port!
812	error = threadManager.Init();
813	if (error != B_OK) {
814		fprintf(stderr, "%s: Failed to init thread manager: %s\n", kCommandName,
815			strerror(error));
816		exit(1);
817	}
818
819	// get the current buffer
820	size_t bufferStart = bufferHeader->start;
821	size_t bufferSize = bufferHeader->size;
822	uint8* buffer = bufferBase + bufferStart;
823
824	if (bufferStart + bufferSize <= totalBufferSize) {
825		process_event_buffer(threadManager, buffer, bufferSize, -1);
826	} else {
827		size_t remainingSize = bufferStart + bufferSize - totalBufferSize;
828		if (!process_event_buffer(threadManager, buffer,
829				bufferSize - remainingSize, -1)) {
830			process_event_buffer(threadManager, bufferBase, remainingSize, -1);
831		}
832	}
833
834	// print results
835	int32 threadCount = threadManager.CountThreads();
836	for (int32 i = 0; i < threadCount; i++) {
837		Thread* thread = threadManager.ThreadAt(i);
838		thread->PrintResults();
839	}
840
841	threadManager.PrintSummaryResults();
842}
843
844
845static void
846profile_single(const char* const* programArgs, int programArgCount)
847{
848	// get thread/team to be debugged
849	thread_id threadID = load_program(programArgs, programArgCount,
850		gOptions.profile_loading);
851	if (threadID < 0) {
852		fprintf(stderr, "%s: Failed to start `%s': %s\n", kCommandName,
853			programArgs[0], strerror(threadID));
854		exit(1);
855	}
856
857	// get the team ID
858	thread_info threadInfo;
859	status_t error = get_thread_info(threadID, &threadInfo);
860	if (error != B_OK) {
861		fprintf(stderr,
862			"%s: Failed to get info for thread %" B_PRId32 ": %s\n",
863			kCommandName, threadID, strerror(error));
864		exit(1);
865	}
866	team_id teamID = threadInfo.team;
867
868	// create a debugger port
869	port_id debuggerPort = create_port(10, "debugger port");
870	if (debuggerPort < 0) {
871		fprintf(stderr, "%s: Failed to create debugger port: %s\n",
872			kCommandName, strerror(debuggerPort));
873		exit(1);
874	}
875
876	// add team and thread to the thread manager
877	ThreadManager threadManager(debuggerPort);
878	error = threadManager.Init();
879	if (error != B_OK) {
880		fprintf(stderr, "%s: Failed to init thread manager: %s\n", kCommandName,
881			strerror(error));
882		exit(1);
883	}
884
885	if (threadManager.AddTeam(teamID) != B_OK
886		|| threadManager.AddThread(threadID) != B_OK) {
887		exit(1);
888	}
889
890	// debug loop
891	while (true) {
892		debug_debugger_message_data message;
893		bool quitLoop = false;
894		int32 code;
895		ssize_t messageSize = read_port(debuggerPort, &code, &message,
896			sizeof(message));
897
898		if (messageSize < 0) {
899			if (messageSize == B_INTERRUPTED)
900				continue;
901
902			fprintf(stderr, "%s: Reading from debugger port failed: %s\n",
903				kCommandName, strerror(messageSize));
904			exit(1);
905		}
906
907		switch (code) {
908			case B_DEBUGGER_MESSAGE_PROFILER_UPDATE:
909			{
910				Thread* thread = threadManager.FindThread(
911					message.profiler_update.origin.thread);
912				if (thread == NULL)
913					break;
914
915				thread->AddSamples(message.profiler_update.sample_count,
916					message.profiler_update.dropped_ticks,
917					message.profiler_update.stack_depth,
918					message.profiler_update.variable_stack_depth,
919					message.profiler_update.image_event);
920
921				if (message.profiler_update.stopped) {
922					thread->PrintResults();
923					threadManager.RemoveThread(thread->ID());
924				}
925				break;
926			}
927
928			case B_DEBUGGER_MESSAGE_TEAM_CREATED:
929				if (!gOptions.profile_teams)
930					break;
931
932				if (threadManager.AddTeam(message.team_created.new_team)
933						== B_OK) {
934					threadManager.AddThread(message.team_created.new_team);
935				}
936				break;
937			case B_DEBUGGER_MESSAGE_TEAM_DELETED:
938				// a debugged team is gone -- quit, if it is our team
939				threadManager.RemoveTeam(message.origin.team);
940				quitLoop = message.origin.team == teamID;
941				break;
942			case B_DEBUGGER_MESSAGE_TEAM_EXEC:
943				if (Team* team = threadManager.FindTeam(message.origin.team)) {
944					team_info teamInfo;
945					thread_info threadInfo;
946					if (get_team_info(message.origin.team, &teamInfo) == B_OK
947						&& get_thread_info(message.origin.team, &threadInfo)
948							== B_OK) {
949						team->Exec(message.team_exec.image_event, teamInfo.args,
950							threadInfo.name);
951					}
952				}
953				break;
954
955			case B_DEBUGGER_MESSAGE_THREAD_CREATED:
956				if (!gOptions.profile_threads)
957					break;
958
959				threadManager.AddThread(message.thread_created.new_thread);
960				break;
961			case B_DEBUGGER_MESSAGE_THREAD_DELETED:
962				threadManager.RemoveThread(message.origin.thread);
963				break;
964
965			case B_DEBUGGER_MESSAGE_IMAGE_CREATED:
966				threadManager.AddImage(message.origin.team,
967					message.image_created.info,
968					message.image_created.image_event);
969				break;
970			case B_DEBUGGER_MESSAGE_IMAGE_DELETED:
971				threadManager.RemoveImage(message.origin.team,
972					message.image_deleted.info.id,
973					message.image_deleted.image_event);
974				break;
975
976			case B_DEBUGGER_MESSAGE_POST_SYSCALL:
977			case B_DEBUGGER_MESSAGE_SIGNAL_RECEIVED:
978			case B_DEBUGGER_MESSAGE_THREAD_DEBUGGED:
979			case B_DEBUGGER_MESSAGE_DEBUGGER_CALL:
980			case B_DEBUGGER_MESSAGE_BREAKPOINT_HIT:
981			case B_DEBUGGER_MESSAGE_WATCHPOINT_HIT:
982			case B_DEBUGGER_MESSAGE_SINGLE_STEP:
983			case B_DEBUGGER_MESSAGE_PRE_SYSCALL:
984			case B_DEBUGGER_MESSAGE_EXCEPTION_OCCURRED:
985				break;
986		}
987
988		if (quitLoop)
989			break;
990
991		// tell the thread to continue (only when there is a thread and the
992		// message was synchronous)
993		if (message.origin.thread >= 0 && message.origin.nub_port >= 0)
994			continue_thread(message.origin.nub_port, message.origin.thread);
995	}
996
997	// prints summary results
998	threadManager.PrintSummaryResults();
999}
1000
1001
1002int
1003main(int argc, const char* const* argv)
1004{
1005	int32 stackDepth = 0;
1006	bool dumpRecorded = false;
1007	const char* outputFile = NULL;
1008
1009	while (true) {
1010		static struct option sLongOptions[] = {
1011			{ "all", no_argument, 0, 'a' },
1012			{ "help", no_argument, 0, 'h' },
1013			{ "recorded", no_argument, 0, 'r' },
1014			{ 0, 0, 0, 0 }
1015		};
1016
1017		opterr = 0; // don't print errors
1018		int c = getopt_long(argc, (char**)argv, "+acCfhi:klo:rs:Sv:",
1019			sLongOptions, NULL);
1020		if (c == -1)
1021			break;
1022
1023		switch (c) {
1024			case 'a':
1025				gOptions.profile_all = true;
1026				break;
1027			case 'c':
1028				gOptions.profile_threads = false;
1029				break;
1030			case 'C':
1031				gOptions.profile_teams = false;
1032				break;
1033			case 'f':
1034				gOptions.stack_depth = 64;
1035				gOptions.analyze_full_stack = true;
1036				break;
1037			case 'h':
1038				print_usage_and_exit(false);
1039				break;
1040			case 'i':
1041				gOptions.interval = atol(optarg);
1042				break;
1043			case 'k':
1044				gOptions.profile_kernel = false;
1045				break;
1046			case 'l':
1047				gOptions.profile_loading = true;
1048				break;
1049			case 'o':
1050				outputFile = optarg;
1051				break;
1052			case 'r':
1053				dumpRecorded = true;
1054				break;
1055			case 's':
1056				stackDepth = atol(optarg);
1057				break;
1058			case 'S':
1059				gOptions.summary_result = true;
1060				break;
1061			case 'v':
1062				gOptions.callgrind_directory = optarg;
1063				gOptions.analyze_full_stack = true;
1064				gOptions.stack_depth = 64;
1065				break;
1066			default:
1067				print_usage_and_exit(true);
1068				break;
1069		}
1070	}
1071
1072	if ((!gOptions.profile_all && !dumpRecorded && optind >= argc)
1073		|| (dumpRecorded && optind != argc))
1074		print_usage_and_exit(true);
1075
1076	if (stackDepth != 0)
1077		gOptions.stack_depth = stackDepth;
1078
1079	if (outputFile != NULL) {
1080		gOptions.output = fopen(outputFile, "w+");
1081		if (gOptions.output == NULL) {
1082			fprintf(stderr, "%s: Failed to open output file \"%s\": %s\n",
1083				kCommandName, outputFile, strerror(errno));
1084			exit(1);
1085		}
1086	} else
1087		gOptions.output = stdout;
1088
1089	if (dumpRecorded) {
1090		dump_recorded();
1091		return 0;
1092	}
1093
1094	const char* const* programArgs = argv + optind;
1095	int programArgCount = argc - optind;
1096
1097	if (gOptions.profile_all) {
1098		profile_all(programArgs, programArgCount);
1099		return 0;
1100	}
1101
1102	profile_single(programArgs, programArgCount);
1103	return 0;
1104}
1105