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:
ThreadManager(port_id debuggerPort)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
~ThreadManager()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
Init()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
AddTeam(team_id teamID,Team ** _team=NULL)148 status_t AddTeam(team_id teamID, Team** _team = NULL)
149 {
150 return _AddTeam(teamID, NULL, _team);
151 }
152
AddTeam(system_profiler_team_added * addedInfo,Team ** _team=NULL)153 status_t AddTeam(system_profiler_team_added* addedInfo, Team** _team = NULL)
154 {
155 return _AddTeam(addedInfo->team, addedInfo, _team);
156 }
157
AddThread(thread_id threadID)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
AddThread(team_id teamID,thread_id threadID,const char * name,bigtime_t cpuTime)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
RemoveTeam(team_id teamID)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
RemoveThread(thread_id threadID)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
FindTeam(team_id teamID) const216 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
FindThread(thread_id threadID) const225 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
CountThreads() const234 int32 CountThreads() const
235 {
236 return fThreads.CountItems();
237 }
238
ThreadAt(int32 index) const239 Thread* ThreadAt(int32 index) const
240 {
241 return fThreads.ItemAt(index);
242 }
243
AddImage(team_id teamID,const image_info & imageInfo,int32 event)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
RemoveImage(team_id teamID,image_id imageID,int32 event)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
PrintSummaryResults()282 void PrintSummaryResults()
283 {
284 if (fSummaryProfileResult != NULL)
285 fSummaryProfileResult->PrintSummaryResults();
286 }
287
288 private:
EntityID() const289 virtual int32 EntityID() const
290 {
291 return 1;
292 }
293
EntityName() const294 virtual const char* EntityName() const
295 {
296 return "all";
297 }
298
EntityType() const299 virtual const char* EntityType() const
300 {
301 return "summary";
302 }
303
304 private:
_AddTeam(team_id teamID,system_profiler_team_added * addedInfo,Team ** _team=NULL)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
_InitDebuggedTeam(Team * team,team_id teamID)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
_InitUndebuggedTeam(Team * team,system_profiler_team_added * addedInfo)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
_LoadTeamImages(Team * team,team_id teamID)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
_CreateThreadProfileResult(Thread * thread)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
_CreateProfileResult(ProfiledEntity * profiledEntity,ProfileResult * & _profileResult)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
_GetSharedImage(team_id teamID,const image_info & imageInfo,SharedImage ** _sharedImage)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
print_usage_and_exit(bool error)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
process_event_buffer(ThreadManager & threadManager,uint8 * buffer,size_t bufferSize,team_id mainTeam)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
signal_handler(int signal,void * data)649 signal_handler(int signal, void* data)
650 {
651 sCaughtDeadlySignal = true;
652 }
653
654
655 static void
profile_all(const char * const * programArgs,int programArgCount)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
dump_recorded()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
profile_single(const char * const * programArgs,int programArgCount)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
main(int argc,const char * const * argv)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