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