xref: /haiku/src/bin/debug/profile/profile.cpp (revision 909af08f4328301fbdef1ffb41f566c3b5bec0c7)
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             - Also profile kernel frames.\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 	profilerParameters.profile_kernel = gOptions.profile_kernel;
716 
717 	error = _kern_system_profiler_start(&profilerParameters);
718 	if (error != B_OK) {
719 		fprintf(stderr, "%s: Failed to start profiling: %s\n", kCommandName,
720 			strerror(error));
721 		exit(1);
722 	}
723 
724 	// resume the loaded team, if we have one
725 	if (threadID >= 0)
726 		resume_thread(threadID);
727 
728 	// main event loop
729 	while (true) {
730 		// get the current buffer
731 		size_t bufferStart = bufferHeader->start;
732 		size_t bufferSize = bufferHeader->size;
733 		uint8* buffer = bufferBase + bufferStart;
734 //printf("processing buffer of size %lu bytes\n", bufferSize);
735 
736 		bool quit;
737 		if (bufferStart + bufferSize <= totalBufferSize) {
738 			quit = process_event_buffer(threadManager, buffer, bufferSize,
739 				threadID);
740 		} else {
741 			size_t remainingSize = bufferStart + bufferSize - totalBufferSize;
742 			quit = process_event_buffer(threadManager, buffer,
743 					bufferSize - remainingSize, threadID)
744 				|| process_event_buffer(threadManager, bufferBase,
745 					remainingSize, threadID);
746 		}
747 
748 		if (quit)
749 			break;
750 
751 		// get next buffer
752 		uint64 droppedEvents = 0;
753 		error = _kern_system_profiler_next_buffer(bufferSize, &droppedEvents);
754 		if (droppedEvents > 0)
755 			fprintf(stderr, "system profiler: dropped %" B_PRIu64 "events\n", droppedEvents);
756 
757 		if (error != B_OK) {
758 			if (error == B_INTERRUPTED) {
759 				if (sCaughtDeadlySignal)
760 					break;
761 				continue;
762 			}
763 
764 			fprintf(stderr, "%s: Failed to get next sample buffer: %s\n",
765 				kCommandName, strerror(error));
766 			break;
767 		}
768 	}
769 
770 	// stop profiling
771 	_kern_system_profiler_stop();
772 
773 	// fetch CPU time for all remaining threads
774 	const int32 threadCount = threadManager.CountThreads();
775 	for (int32 i = 0; i < threadCount; i++) {
776 		Thread* thread = threadManager.ThreadAt(i);
777 		thread_info threadInfo;
778 		status_t error = get_thread_info(thread->ID(), &threadInfo);
779 		if (error != B_OK)
780 			continue;
781 
782 		thread->UpdateCPUTime(threadInfo.kernel_time + threadInfo.user_time);
783 	}
784 
785 	// print results
786 	for (int32 i = 0; i < threadCount; i++) {
787 		Thread* thread = threadManager.ThreadAt(i);
788 		thread->PrintResults();
789 	}
790 
791 	threadManager.PrintSummaryResults();
792 }
793 
794 
795 static void
796 dump_recorded()
797 {
798 	// retrieve recorded samples and parameters
799 	system_profiler_parameters profilerParameters;
800 	status_t error = _kern_system_profiler_recorded(&profilerParameters);
801 	if (error != B_OK) {
802 		fprintf(stderr, "%s: Failed to get recorded profiling buffer: %s\n",
803 			kCommandName, strerror(error));
804 		exit(1);
805 	}
806 
807 	// set global options to those of the profiler parameters
808 	gOptions.interval = profilerParameters.interval;
809 	gOptions.stack_depth = profilerParameters.stack_depth;
810 
811 	// create an area for the sample buffer
812 	area_info info;
813 	error = get_area_info(profilerParameters.buffer_area, &info);
814 	if (error != B_OK) {
815 		fprintf(stderr, "%s: Recorded profiling buffer invalid: %s\n",
816 			kCommandName, strerror(error));
817 		exit(1);
818 	}
819 
820 	system_profiler_buffer_header* bufferHeader
821 		= (system_profiler_buffer_header*)info.address;
822 
823 	uint8* bufferBase = (uint8*)(bufferHeader + 1);
824 	size_t totalBufferSize = info.size - (bufferBase - (uint8*)bufferHeader);
825 
826 	// create a thread manager
827 	ThreadManager threadManager(-1);	// TODO: We don't need a debugger port!
828 	error = threadManager.Init();
829 	if (error != B_OK) {
830 		fprintf(stderr, "%s: Failed to init thread manager: %s\n", kCommandName,
831 			strerror(error));
832 		exit(1);
833 	}
834 
835 	// get the current buffer
836 	size_t bufferStart = bufferHeader->start;
837 	size_t bufferSize = bufferHeader->size;
838 	uint8* buffer = bufferBase + bufferStart;
839 
840 	if (bufferStart + bufferSize <= totalBufferSize) {
841 		process_event_buffer(threadManager, buffer, bufferSize, -1);
842 	} else {
843 		size_t remainingSize = bufferStart + bufferSize - totalBufferSize;
844 		if (!process_event_buffer(threadManager, buffer,
845 				bufferSize - remainingSize, -1)) {
846 			process_event_buffer(threadManager, bufferBase, remainingSize, -1);
847 		}
848 	}
849 
850 	// print results
851 	int32 threadCount = threadManager.CountThreads();
852 	for (int32 i = 0; i < threadCount; i++) {
853 		Thread* thread = threadManager.ThreadAt(i);
854 		thread->PrintResults();
855 	}
856 
857 	threadManager.PrintSummaryResults();
858 }
859 
860 
861 static void
862 profile_single(const char* const* programArgs, int programArgCount)
863 {
864 	// get thread/team to be debugged
865 	thread_id threadID = load_program(programArgs, programArgCount,
866 		gOptions.profile_loading);
867 	if (threadID < 0) {
868 		fprintf(stderr, "%s: Failed to start `%s': %s\n", kCommandName,
869 			programArgs[0], strerror(threadID));
870 		exit(1);
871 	}
872 
873 	// get the team ID
874 	thread_info threadInfo;
875 	status_t error = get_thread_info(threadID, &threadInfo);
876 	if (error != B_OK) {
877 		fprintf(stderr,
878 			"%s: Failed to get info for thread %" B_PRId32 ": %s\n",
879 			kCommandName, threadID, strerror(error));
880 		exit(1);
881 	}
882 	team_id teamID = threadInfo.team;
883 
884 	// create a debugger port
885 	port_id debuggerPort = create_port(10, "debugger port");
886 	if (debuggerPort < 0) {
887 		fprintf(stderr, "%s: Failed to create debugger port: %s\n",
888 			kCommandName, strerror(debuggerPort));
889 		exit(1);
890 	}
891 
892 	// add team and thread to the thread manager
893 	ThreadManager threadManager(debuggerPort);
894 	error = threadManager.Init();
895 	if (error != B_OK) {
896 		fprintf(stderr, "%s: Failed to init thread manager: %s\n", kCommandName,
897 			strerror(error));
898 		exit(1);
899 	}
900 
901 	if (threadManager.AddTeam(teamID) != B_OK
902 		|| threadManager.AddThread(threadID) != B_OK) {
903 		exit(1);
904 	}
905 
906 	// debug loop
907 	while (true) {
908 		debug_debugger_message_data message;
909 		bool quitLoop = false;
910 		int32 code;
911 		ssize_t messageSize = read_port(debuggerPort, &code, &message,
912 			sizeof(message));
913 
914 		if (messageSize < 0) {
915 			if (messageSize == B_INTERRUPTED)
916 				continue;
917 
918 			fprintf(stderr, "%s: Reading from debugger port failed: %s\n",
919 				kCommandName, strerror(messageSize));
920 			exit(1);
921 		}
922 
923 		switch (code) {
924 			case B_DEBUGGER_MESSAGE_PROFILER_UPDATE:
925 			{
926 				Thread* thread = threadManager.FindThread(
927 					message.profiler_update.origin.thread);
928 				if (thread == NULL)
929 					break;
930 
931 				thread->AddSamples(message.profiler_update.sample_count,
932 					message.profiler_update.dropped_ticks,
933 					message.profiler_update.stack_depth,
934 					message.profiler_update.variable_stack_depth,
935 					message.profiler_update.image_event);
936 
937 				if (message.profiler_update.stopped) {
938 					thread->UpdateCPUTime(message.profiler_update.last_cpu_time);
939 					thread->PrintResults();
940 					threadManager.RemoveThread(thread->ID());
941 				}
942 				break;
943 			}
944 
945 			case B_DEBUGGER_MESSAGE_TEAM_CREATED:
946 				if (!gOptions.profile_teams)
947 					break;
948 
949 				if (threadManager.AddTeam(message.team_created.new_team)
950 						== B_OK) {
951 					threadManager.AddThread(message.team_created.new_team);
952 				}
953 				break;
954 			case B_DEBUGGER_MESSAGE_TEAM_DELETED:
955 				// a debugged team is gone -- quit, if it is our team
956 				threadManager.RemoveTeam(message.origin.team);
957 				quitLoop = message.origin.team == teamID;
958 				break;
959 			case B_DEBUGGER_MESSAGE_TEAM_EXEC:
960 				if (Team* team = threadManager.FindTeam(message.origin.team)) {
961 					team_info teamInfo;
962 					thread_info threadInfo;
963 					if (get_team_info(message.origin.team, &teamInfo) == B_OK
964 						&& get_thread_info(message.origin.team, &threadInfo)
965 							== B_OK) {
966 						team->Exec(message.team_exec.image_event, teamInfo.args,
967 							threadInfo.name);
968 					}
969 				}
970 				break;
971 
972 			case B_DEBUGGER_MESSAGE_THREAD_CREATED:
973 				if (!gOptions.profile_threads)
974 					break;
975 
976 				threadManager.AddThread(message.thread_created.new_thread);
977 				break;
978 			case B_DEBUGGER_MESSAGE_THREAD_DELETED:
979 				threadManager.RemoveThread(message.origin.thread);
980 				break;
981 
982 			case B_DEBUGGER_MESSAGE_IMAGE_CREATED:
983 				threadManager.AddImage(message.origin.team,
984 					message.image_created.info,
985 					message.image_created.image_event);
986 				break;
987 			case B_DEBUGGER_MESSAGE_IMAGE_DELETED:
988 				threadManager.RemoveImage(message.origin.team,
989 					message.image_deleted.info.id,
990 					message.image_deleted.image_event);
991 				break;
992 
993 			case B_DEBUGGER_MESSAGE_POST_SYSCALL:
994 			case B_DEBUGGER_MESSAGE_SIGNAL_RECEIVED:
995 			case B_DEBUGGER_MESSAGE_THREAD_DEBUGGED:
996 			case B_DEBUGGER_MESSAGE_DEBUGGER_CALL:
997 			case B_DEBUGGER_MESSAGE_BREAKPOINT_HIT:
998 			case B_DEBUGGER_MESSAGE_WATCHPOINT_HIT:
999 			case B_DEBUGGER_MESSAGE_SINGLE_STEP:
1000 			case B_DEBUGGER_MESSAGE_PRE_SYSCALL:
1001 			case B_DEBUGGER_MESSAGE_EXCEPTION_OCCURRED:
1002 				break;
1003 		}
1004 
1005 		if (quitLoop)
1006 			break;
1007 
1008 		// tell the thread to continue (only when there is a thread and the
1009 		// message was synchronous)
1010 		if (message.origin.thread >= 0 && message.origin.nub_port >= 0)
1011 			continue_thread(message.origin.nub_port, message.origin.thread);
1012 	}
1013 
1014 	// prints summary results
1015 	threadManager.PrintSummaryResults();
1016 }
1017 
1018 
1019 int
1020 main(int argc, const char* const* argv)
1021 {
1022 	int32 stackDepth = 0;
1023 	bool dumpRecorded = false;
1024 	const char* outputFile = NULL;
1025 
1026 	while (true) {
1027 		static struct option sLongOptions[] = {
1028 			{ "all", no_argument, 0, 'a' },
1029 			{ "help", no_argument, 0, 'h' },
1030 			{ "recorded", no_argument, 0, 'r' },
1031 			{ 0, 0, 0, 0 }
1032 		};
1033 
1034 		opterr = 0; // don't print errors
1035 		int c = getopt_long(argc, (char**)argv, "+acCfhi:klo:rs:Sv:",
1036 			sLongOptions, NULL);
1037 		if (c == -1)
1038 			break;
1039 
1040 		switch (c) {
1041 			case 'a':
1042 				gOptions.profile_all = true;
1043 				break;
1044 			case 'c':
1045 				gOptions.profile_threads = false;
1046 				break;
1047 			case 'C':
1048 				gOptions.profile_teams = false;
1049 				break;
1050 			case 'f':
1051 				gOptions.stack_depth = 64;
1052 				gOptions.analyze_full_stack = true;
1053 				break;
1054 			case 'h':
1055 				print_usage_and_exit(false);
1056 				break;
1057 			case 'i':
1058 				gOptions.interval = atol(optarg);
1059 				break;
1060 			case 'k':
1061 				gOptions.profile_kernel = true;
1062 				break;
1063 			case 'l':
1064 				gOptions.profile_loading = true;
1065 				break;
1066 			case 'o':
1067 				outputFile = optarg;
1068 				break;
1069 			case 'r':
1070 				dumpRecorded = true;
1071 				break;
1072 			case 's':
1073 				stackDepth = atol(optarg);
1074 				break;
1075 			case 'S':
1076 				gOptions.summary_result = true;
1077 				break;
1078 			case 'v':
1079 				gOptions.callgrind_directory = optarg;
1080 				gOptions.analyze_full_stack = true;
1081 				gOptions.stack_depth = 64;
1082 				break;
1083 			default:
1084 				print_usage_and_exit(true);
1085 				break;
1086 		}
1087 	}
1088 
1089 	if ((!gOptions.profile_all && !dumpRecorded && optind >= argc)
1090 		|| (dumpRecorded && optind != argc))
1091 		print_usage_and_exit(true);
1092 
1093 	if (stackDepth != 0)
1094 		gOptions.stack_depth = stackDepth;
1095 
1096 	if (outputFile != NULL) {
1097 		gOptions.output = fopen(outputFile, "w+");
1098 		if (gOptions.output == NULL) {
1099 			fprintf(stderr, "%s: Failed to open output file \"%s\": %s\n",
1100 				kCommandName, outputFile, strerror(errno));
1101 			exit(1);
1102 		}
1103 	} else
1104 		gOptions.output = stdout;
1105 
1106 	if (dumpRecorded) {
1107 		dump_recorded();
1108 		return 0;
1109 	}
1110 
1111 	const char* const* programArgs = argv + optind;
1112 	int programArgCount = argc - optind;
1113 
1114 	if (gOptions.profile_all) {
1115 		profile_all(programArgs, programArgCount);
1116 		return 0;
1117 	}
1118 
1119 	profile_single(programArgs, programArgCount);
1120 	return 0;
1121 }
1122