2 * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
6 #include <scheduling_analysis.h>
10 #include <scheduler_defs.h>
12 #include <util/AutoLock.h>
14 #include "scheduler_tracing.h"
19 namespace SchedulingAnalysis
{
21 using namespace SchedulerTracing
;
23 #if SCHEDULING_ANALYSIS_TRACING
24 using namespace SchedulingAnalysisTracing
;
27 struct ThreadWaitObject
;
29 struct HashObjectKey
{
30 virtual ~HashObjectKey()
34 virtual uint32
HashKey() const = 0;
45 virtual uint32
HashKey() const = 0;
46 virtual bool Equals(const HashObjectKey
* key
) const = 0;
50 struct ThreadKey
: HashObjectKey
{
53 ThreadKey(thread_id id
)
59 virtual uint32
HashKey() const
66 struct Thread
: HashObject
, scheduling_analysis_thread
{
70 ThreadWaitObject
* waitObject
;
97 unspecified_wait_time
= 0;
104 virtual uint32
HashKey() const
109 virtual bool Equals(const HashObjectKey
* _key
) const
111 const ThreadKey
* key
= dynamic_cast<const ThreadKey
*>(_key
);
114 return key
->id
== id
;
119 struct WaitObjectKey
: HashObjectKey
{
123 WaitObjectKey(uint32 type
, void* object
)
130 virtual uint32
HashKey() const
132 return type
^ (uint32
)(addr_t
)object
;
137 struct WaitObject
: HashObject
, scheduling_analysis_wait_object
{
138 WaitObject(uint32 type
, void* object
)
141 this->object
= object
;
143 referenced_object
= NULL
;
146 virtual uint32
HashKey() const
148 return type
^ (uint32
)(addr_t
)object
;
151 virtual bool Equals(const HashObjectKey
* _key
) const
153 const WaitObjectKey
* key
= dynamic_cast<const WaitObjectKey
*>(_key
);
156 return key
->type
== type
&& key
->object
== object
;
161 struct ThreadWaitObjectKey
: HashObjectKey
{
166 ThreadWaitObjectKey(thread_id thread
, uint32 type
, void* object
)
174 virtual uint32
HashKey() const
176 return thread
^ type
^ (uint32
)(addr_t
)object
;
181 struct ThreadWaitObject
: HashObject
, scheduling_analysis_thread_wait_object
{
182 ThreadWaitObject(thread_id thread
, WaitObject
* waitObject
)
184 this->thread
= thread
;
185 wait_object
= waitObject
;
191 virtual uint32
HashKey() const
193 return thread
^ wait_object
->type
^ (uint32
)(addr_t
)wait_object
->object
;
196 virtual bool Equals(const HashObjectKey
* _key
) const
198 const ThreadWaitObjectKey
* key
199 = dynamic_cast<const ThreadWaitObjectKey
*>(_key
);
202 return key
->thread
== thread
&& key
->type
== wait_object
->type
203 && key
->object
== wait_object
->object
;
208 class SchedulingAnalysisManager
{
210 SchedulingAnalysisManager(void* buffer
, size_t size
)
217 fAnalysis
.thread_count
= 0;
218 fAnalysis
.threads
= 0;
219 fAnalysis
.wait_object_count
= 0;
220 fAnalysis
.thread_wait_object_count
= 0;
222 size_t maxObjectSize
= max_c(max_c(sizeof(Thread
), sizeof(WaitObject
)),
223 sizeof(ThreadWaitObject
));
224 fHashTableSize
= size
/ (maxObjectSize
+ sizeof(HashObject
*));
225 fHashTable
= (HashObject
**)((uint8
*)fBuffer
+ fSize
) - fHashTableSize
;
226 fNextAllocation
= (uint8
*)fBuffer
;
227 fRemainingBytes
= (addr_t
)fHashTable
- (addr_t
)fBuffer
;
230 if (elf_get_image_info_for_address((addr_t
)&scheduler_init
, &info
)
232 fKernelStart
= (addr_t
)info
.text
;
233 fKernelEnd
= (addr_t
)info
.data
+ info
.data_size
;
240 const scheduling_analysis
* Analysis() const
245 void* Allocate(size_t size
)
247 size
= (size
+ 7) & ~(size_t)7;
249 if (size
> fRemainingBytes
)
252 void* address
= fNextAllocation
;
253 fNextAllocation
+= size
;
254 fRemainingBytes
-= size
;
258 void Insert(HashObject
* object
)
260 uint32 index
= object
->HashKey() % fHashTableSize
;
261 object
->next
= fHashTable
[index
];
262 fHashTable
[index
] = object
;
265 void Remove(HashObject
* object
)
267 uint32 index
= object
->HashKey() % fHashTableSize
;
268 HashObject
** slot
= &fHashTable
[index
];
269 while (*slot
!= object
)
270 slot
= &(*slot
)->next
;
272 *slot
= object
->next
;
275 HashObject
* Lookup(const HashObjectKey
& key
) const
277 uint32 index
= key
.HashKey() % fHashTableSize
;
278 HashObject
* object
= fHashTable
[index
];
279 while (object
!= NULL
&& !object
->Equals(&key
))
280 object
= object
->next
;
284 Thread
* ThreadFor(thread_id id
) const
286 return dynamic_cast<Thread
*>(Lookup(ThreadKey(id
)));
289 WaitObject
* WaitObjectFor(uint32 type
, void* object
) const
291 return dynamic_cast<WaitObject
*>(Lookup(WaitObjectKey(type
, object
)));
294 ThreadWaitObject
* ThreadWaitObjectFor(thread_id thread
, uint32 type
,
297 return dynamic_cast<ThreadWaitObject
*>(
298 Lookup(ThreadWaitObjectKey(thread
, type
, object
)));
301 status_t
AddThread(thread_id id
, const char* name
)
303 Thread
* thread
= ThreadFor(id
);
304 if (thread
== NULL
) {
305 void* memory
= Allocate(sizeof(Thread
));
309 thread
= new(memory
) Thread(id
);
311 fAnalysis
.thread_count
++;
314 if (name
!= NULL
&& thread
->name
[0] == '\0')
315 strlcpy(thread
->name
, name
, sizeof(thread
->name
));
320 status_t
AddWaitObject(uint32 type
, void* object
,
321 WaitObject
** _waitObject
= NULL
)
323 if (WaitObjectFor(type
, object
) != NULL
)
326 void* memory
= Allocate(sizeof(WaitObject
));
330 WaitObject
* waitObject
= new(memory
) WaitObject(type
, object
);
332 fAnalysis
.wait_object_count
++;
334 // Set a dummy name for snooze() and waiting for signals, so we don't
335 // try to update them later on.
336 if (type
== THREAD_BLOCK_TYPE_SNOOZE
337 || type
== THREAD_BLOCK_TYPE_SIGNAL
) {
338 strcpy(waitObject
->name
, "?");
341 if (_waitObject
!= NULL
)
342 *_waitObject
= waitObject
;
347 status_t
UpdateWaitObject(uint32 type
, void* object
, const char* name
,
348 void* referencedObject
)
350 WaitObject
* waitObject
= WaitObjectFor(type
, object
);
351 if (waitObject
== NULL
)
354 if (waitObject
->name
[0] != '\0') {
355 // This is a new object at the same address. Replace the old one.
357 status_t error
= AddWaitObject(type
, object
, &waitObject
);
365 strlcpy(waitObject
->name
, name
, sizeof(waitObject
->name
));
366 waitObject
->referenced_object
= referencedObject
;
371 bool UpdateWaitObjectDontAdd(uint32 type
, void* object
, const char* name
,
372 void* referencedObject
)
374 WaitObject
* waitObject
= WaitObjectFor(type
, object
);
375 if (waitObject
== NULL
|| waitObject
->name
[0] != '\0')
381 strlcpy(waitObject
->name
, name
, sizeof(waitObject
->name
));
382 waitObject
->referenced_object
= referencedObject
;
387 status_t
AddThreadWaitObject(Thread
* thread
, uint32 type
, void* object
)
389 WaitObject
* waitObject
= WaitObjectFor(type
, object
);
390 if (waitObject
== NULL
) {
391 // The algorithm should prevent this case.
395 ThreadWaitObject
* threadWaitObject
= ThreadWaitObjectFor(thread
->id
,
397 if (threadWaitObject
== NULL
398 || threadWaitObject
->wait_object
!= waitObject
) {
399 if (threadWaitObject
!= NULL
)
400 Remove(threadWaitObject
);
402 void* memory
= Allocate(sizeof(ThreadWaitObject
));
406 threadWaitObject
= new(memory
) ThreadWaitObject(thread
->id
,
408 Insert(threadWaitObject
);
409 fAnalysis
.thread_wait_object_count
++;
411 threadWaitObject
->next_in_list
= thread
->wait_objects
;
412 thread
->wait_objects
= threadWaitObject
;
415 thread
->waitObject
= threadWaitObject
;
420 int32
MissingWaitObjects() const
422 // Iterate through the hash table and count the wait objects that don't
425 for (uint32 i
= 0; i
< fHashTableSize
; i
++) {
426 HashObject
* object
= fHashTable
[i
];
427 while (object
!= NULL
) {
428 WaitObject
* waitObject
= dynamic_cast<WaitObject
*>(object
);
429 if (waitObject
!= NULL
&& waitObject
->name
[0] == '\0')
432 object
= object
->next
;
439 status_t
FinishAnalysis()
441 // allocate the thread array
442 scheduling_analysis_thread
** threads
443 = (scheduling_analysis_thread
**)Allocate(
444 sizeof(Thread
*) * fAnalysis
.thread_count
);
448 // Iterate through the hash table and collect all threads. Also polish
449 // all wait objects that haven't been update yet.
451 for (uint32 i
= 0; i
< fHashTableSize
; i
++) {
452 HashObject
* object
= fHashTable
[i
];
453 while (object
!= NULL
) {
454 Thread
* thread
= dynamic_cast<Thread
*>(object
);
455 if (thread
!= NULL
) {
456 threads
[index
++] = thread
;
457 } else if (WaitObject
* waitObject
458 = dynamic_cast<WaitObject
*>(object
)) {
459 _PolishWaitObject(waitObject
);
462 object
= object
->next
;
466 fAnalysis
.threads
= threads
;
467 dprintf("scheduling analysis: free bytes: %lu/%lu\n", fRemainingBytes
, fSize
);
472 void _PolishWaitObject(WaitObject
* waitObject
)
474 if (waitObject
->name
[0] != '\0')
477 switch (waitObject
->type
) {
478 case THREAD_BLOCK_TYPE_SEMAPHORE
:
481 if (get_sem_info((sem_id
)(addr_t
)waitObject
->object
, &info
)
483 strlcpy(waitObject
->name
, info
.name
,
484 sizeof(waitObject
->name
));
488 case THREAD_BLOCK_TYPE_CONDITION_VARIABLE
:
490 // If the condition variable object is in the kernel image,
491 // assume, it is still initialized.
492 ConditionVariable
* variable
493 = (ConditionVariable
*)waitObject
->object
;
494 if (!_IsInKernelImage(variable
))
497 waitObject
->referenced_object
= (void*)variable
->Object();
498 strlcpy(waitObject
->name
, variable
->ObjectType(),
499 sizeof(waitObject
->name
));
503 case THREAD_BLOCK_TYPE_MUTEX
:
505 // If the mutex object is in the kernel image, assume, it is
506 // still initialized.
507 mutex
* lock
= (mutex
*)waitObject
->object
;
508 if (!_IsInKernelImage(lock
))
511 strlcpy(waitObject
->name
, lock
->name
, sizeof(waitObject
->name
));
515 case THREAD_BLOCK_TYPE_RW_LOCK
:
517 // If the mutex object is in the kernel image, assume, it is
518 // still initialized.
519 rw_lock
* lock
= (rw_lock
*)waitObject
->object
;
520 if (!_IsInKernelImage(lock
))
523 strlcpy(waitObject
->name
, lock
->name
, sizeof(waitObject
->name
));
527 case THREAD_BLOCK_TYPE_OTHER
:
529 const char* name
= (const char*)waitObject
->object
;
530 if (name
== NULL
|| _IsInKernelImage(name
))
533 strlcpy(waitObject
->name
, name
, sizeof(waitObject
->name
));
536 case THREAD_BLOCK_TYPE_SNOOZE
:
537 case THREAD_BLOCK_TYPE_SIGNAL
:
542 if (waitObject
->name
[0] != '\0')
545 strcpy(waitObject
->name
, "?");
548 bool _IsInKernelImage(const void* _address
)
550 addr_t address
= (addr_t
)_address
;
551 return address
>= fKernelStart
&& address
< fKernelEnd
;
555 scheduling_analysis fAnalysis
;
558 HashObject
** fHashTable
;
559 uint32 fHashTableSize
;
560 uint8
* fNextAllocation
;
561 size_t fRemainingBytes
;
568 analyze_scheduling(bigtime_t from
, bigtime_t until
,
569 SchedulingAnalysisManager
& manager
)
571 // analyze how much threads and locking primitives we're talking about
572 TraceEntryIterator iterator
;
573 iterator
.MoveTo(INT_MAX
);
574 while (TraceEntry
* _entry
= iterator
.Previous()) {
575 SchedulerTraceEntry
* baseEntry
576 = dynamic_cast<SchedulerTraceEntry
*>(_entry
);
577 if (baseEntry
== NULL
|| baseEntry
->Time() >= until
)
579 if (baseEntry
->Time() < from
)
582 status_t error
= manager
.AddThread(baseEntry
->ThreadID(),
587 if (ScheduleThread
* entry
= dynamic_cast<ScheduleThread
*>(_entry
)) {
588 error
= manager
.AddThread(entry
->PreviousThreadID(), NULL
);
592 if (entry
->PreviousState() == B_THREAD_WAITING
) {
593 void* waitObject
= (void*)entry
->PreviousWaitObject();
594 switch (entry
->PreviousWaitObjectType()) {
595 case THREAD_BLOCK_TYPE_SNOOZE
:
596 case THREAD_BLOCK_TYPE_SIGNAL
:
599 case THREAD_BLOCK_TYPE_SEMAPHORE
:
600 case THREAD_BLOCK_TYPE_CONDITION_VARIABLE
:
601 case THREAD_BLOCK_TYPE_MUTEX
:
602 case THREAD_BLOCK_TYPE_RW_LOCK
:
603 case THREAD_BLOCK_TYPE_OTHER
:
608 error
= manager
.AddWaitObject(entry
->PreviousWaitObjectType(),
616 #if SCHEDULING_ANALYSIS_TRACING
617 int32 startEntryIndex
= iterator
.Index();
620 while (TraceEntry
* _entry
= iterator
.Next()) {
621 #if SCHEDULING_ANALYSIS_TRACING
622 // might be info on a wait object
623 if (WaitObjectTraceEntry
* waitObjectEntry
624 = dynamic_cast<WaitObjectTraceEntry
*>(_entry
)) {
625 status_t error
= manager
.UpdateWaitObject(waitObjectEntry
->Type(),
626 waitObjectEntry
->Object(), waitObjectEntry
->Name(),
627 waitObjectEntry
->ReferencedObject());
634 SchedulerTraceEntry
* baseEntry
635 = dynamic_cast<SchedulerTraceEntry
*>(_entry
);
636 if (baseEntry
== NULL
)
638 if (baseEntry
->Time() >= until
)
641 if (ScheduleThread
* entry
= dynamic_cast<ScheduleThread
*>(_entry
)) {
643 Thread
* thread
= manager
.ThreadFor(entry
->ThreadID());
645 bigtime_t diffTime
= entry
->Time() - thread
->lastTime
;
647 if (thread
->state
== READY
) {
648 // thread scheduled after having been woken up
650 thread
->total_latency
+= diffTime
;
651 if (thread
->min_latency
< 0 || diffTime
< thread
->min_latency
)
652 thread
->min_latency
= diffTime
;
653 if (diffTime
> thread
->max_latency
)
654 thread
->max_latency
= diffTime
;
655 } else if (thread
->state
== PREEMPTED
) {
656 // thread scheduled after having been preempted before
658 thread
->total_rerun_time
+= diffTime
;
659 if (thread
->min_rerun_time
< 0
660 || diffTime
< thread
->min_rerun_time
) {
661 thread
->min_rerun_time
= diffTime
;
663 if (diffTime
> thread
->max_rerun_time
)
664 thread
->max_rerun_time
= diffTime
;
667 if (thread
->state
== STILL_RUNNING
) {
668 // Thread was running and continues to run.
669 thread
->state
= RUNNING
;
672 if (thread
->state
!= RUNNING
) {
673 thread
->lastTime
= entry
->Time();
674 thread
->state
= RUNNING
;
677 // unscheduled thread
679 if (entry
->ThreadID() == entry
->PreviousThreadID())
682 thread
= manager
.ThreadFor(entry
->PreviousThreadID());
684 diffTime
= entry
->Time() - thread
->lastTime
;
686 if (thread
->state
== STILL_RUNNING
) {
689 thread
->preemptions
++;
690 thread
->total_run_time
+= diffTime
;
691 if (thread
->min_run_time
< 0 || diffTime
< thread
->min_run_time
)
692 thread
->min_run_time
= diffTime
;
693 if (diffTime
> thread
->max_run_time
)
694 thread
->max_run_time
= diffTime
;
696 thread
->lastTime
= entry
->Time();
697 thread
->state
= PREEMPTED
;
698 } else if (thread
->state
== RUNNING
) {
699 // thread starts waiting (it hadn't been added to the run
700 // queue before being unscheduled)
702 thread
->total_run_time
+= diffTime
;
703 if (thread
->min_run_time
< 0 || diffTime
< thread
->min_run_time
)
704 thread
->min_run_time
= diffTime
;
705 if (diffTime
> thread
->max_run_time
)
706 thread
->max_run_time
= diffTime
;
708 if (entry
->PreviousState() == B_THREAD_WAITING
) {
709 void* waitObject
= (void*)entry
->PreviousWaitObject();
710 switch (entry
->PreviousWaitObjectType()) {
711 case THREAD_BLOCK_TYPE_SNOOZE
:
712 case THREAD_BLOCK_TYPE_SIGNAL
:
715 case THREAD_BLOCK_TYPE_SEMAPHORE
:
716 case THREAD_BLOCK_TYPE_CONDITION_VARIABLE
:
717 case THREAD_BLOCK_TYPE_MUTEX
:
718 case THREAD_BLOCK_TYPE_RW_LOCK
:
719 case THREAD_BLOCK_TYPE_OTHER
:
724 status_t error
= manager
.AddThreadWaitObject(thread
,
725 entry
->PreviousWaitObjectType(), waitObject
);
730 thread
->lastTime
= entry
->Time();
731 thread
->state
= WAITING
;
732 } else if (thread
->state
== UNKNOWN
) {
733 uint32 threadState
= entry
->PreviousState();
734 if (threadState
== B_THREAD_WAITING
735 || threadState
== B_THREAD_SUSPENDED
) {
736 thread
->lastTime
= entry
->Time();
737 thread
->state
= WAITING
;
738 } else if (threadState
== B_THREAD_READY
) {
739 thread
->lastTime
= entry
->Time();
740 thread
->state
= PREEMPTED
;
743 } else if (EnqueueThread
* entry
744 = dynamic_cast<EnqueueThread
*>(_entry
)) {
745 // thread enqueued in run queue
747 Thread
* thread
= manager
.ThreadFor(entry
->ThreadID());
749 if (thread
->state
== RUNNING
|| thread
->state
== STILL_RUNNING
) {
750 // Thread was running and is reentered into the run queue. This
751 // is done by the scheduler, if the thread remains ready.
752 thread
->state
= STILL_RUNNING
;
754 // Thread was waiting and is ready now.
755 bigtime_t diffTime
= entry
->Time() - thread
->lastTime
;
756 if (thread
->waitObject
!= NULL
) {
757 thread
->waitObject
->wait_time
+= diffTime
;
758 thread
->waitObject
->waits
++;
759 thread
->waitObject
= NULL
;
760 } else if (thread
->state
!= UNKNOWN
)
761 thread
->unspecified_wait_time
+= diffTime
;
763 thread
->lastTime
= entry
->Time();
764 thread
->state
= READY
;
766 } else if (RemoveThread
* entry
= dynamic_cast<RemoveThread
*>(_entry
)) {
767 // thread removed from run queue
769 Thread
* thread
= manager
.ThreadFor(entry
->ThreadID());
771 // This really only happens when the thread priority is changed
772 // while the thread is ready.
774 bigtime_t diffTime
= entry
->Time() - thread
->lastTime
;
775 if (thread
->state
== RUNNING
) {
776 // This should never happen.
778 thread
->total_run_time
+= diffTime
;
779 if (thread
->min_run_time
< 0 || diffTime
< thread
->min_run_time
)
780 thread
->min_run_time
= diffTime
;
781 if (diffTime
> thread
->max_run_time
)
782 thread
->max_run_time
= diffTime
;
783 } else if (thread
->state
== READY
|| thread
->state
== PREEMPTED
) {
784 // Not really correct, but the case is rare and we keep it
786 thread
->unspecified_wait_time
+= diffTime
;
789 thread
->lastTime
= entry
->Time();
790 thread
->state
= WAITING
;
795 #if SCHEDULING_ANALYSIS_TRACING
796 int32 missingWaitObjects
= manager
.MissingWaitObjects();
797 if (missingWaitObjects
> 0) {
798 iterator
.MoveTo(startEntryIndex
+ 1);
799 while (TraceEntry
* _entry
= iterator
.Previous()) {
800 if (WaitObjectTraceEntry
* waitObjectEntry
801 = dynamic_cast<WaitObjectTraceEntry
*>(_entry
)) {
802 if (manager
.UpdateWaitObjectDontAdd(
803 waitObjectEntry
->Type(), waitObjectEntry
->Object(),
804 waitObjectEntry
->Name(),
805 waitObjectEntry
->ReferencedObject())) {
806 if (--missingWaitObjects
== 0)
817 } // namespace SchedulingAnalysis
819 #endif // SCHEDULER_TRACING
823 _user_analyze_scheduling(bigtime_t from
, bigtime_t until
, void* buffer
,
824 size_t size
, scheduling_analysis
* analysis
)
826 #if SCHEDULER_TRACING
827 using namespace SchedulingAnalysis
;
829 if ((addr_t
)buffer
& 0x7) {
830 addr_t diff
= (addr_t
)buffer
& 0x7;
831 buffer
= (void*)((addr_t
)buffer
+ 8 - diff
);
834 size
&= ~(size_t)0x7;
836 if (buffer
== NULL
|| !IS_USER_ADDRESS(buffer
) || size
== 0)
839 status_t error
= lock_memory(buffer
, size
, B_READ_DEVICE
);
843 SchedulingAnalysisManager
manager(buffer
, size
);
845 InterruptsLocker locker
;
846 lock_tracing_buffer();
848 error
= analyze_scheduling(from
, until
, manager
);
850 unlock_tracing_buffer();
854 error
= manager
.FinishAnalysis();
856 unlock_memory(buffer
, size
, B_READ_DEVICE
);
859 error
= user_memcpy(analysis
, manager
.Analysis(),
860 sizeof(scheduling_analysis
));