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