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