From 227fe7d34aeed45d0727a0abde2ea2309352983b Mon Sep 17 00:00:00 2001 From: Ingo Weinhold Date: Thu, 23 Apr 2009 13:47:52 +0000 Subject: [PATCH] * Scheduler/wait object listener: - Moved scheduler listening interface to and added more convenient to use templatized notification functions. - Added a listener mechanism for the wait objects (semaphores, condition variables, mutex, rw_lock). * system profiler: - Hopefully fixed locking issues related to notifying the profiler thread for good. We still had an inconsistent locking order, since the scheduler notification callbacks are invoked with the thread lock held and have to acquire the object lock then, while the other callbacks acquired the object lock first and as a side effect of ConditionVariable::NotifyOne() acquired the thread lock. Now we make sure the object lock is the innermost lock. - Track the number of dropped events due to a full buffer. _user_system_profiler_next_buffer() returns this count now. - When scheduling profiling events are requested also listen to wait objects and generate the respective profiling events. We send those events lazily and cache the infos to avoid resending an event for the same wait object. - When starting profiling we do now generate "thread scheduled" events for the already running threads. - _user_system_profiler_start(): Check whether the parameters pointer is a userland address at all. - The system_profiler_team_added event does now also contain the team's name. * Added a sem_get_name_unsafe() returning a semaphore's name. It is "unsafe", since the caller has to ensure that the semaphore exists and continues to exist as long as the returned name is used. * Adjusted the "profile" and "scheduling_recorder" according to the system profiling changes. The latter prints the number of dropped events, now. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@30345 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- headers/private/kernel/kscheduler.h | 21 - headers/private/kernel/listeners.h | 119 +++++ headers/private/kernel/sem.h | 2 + headers/private/kernel/system_profiler.h | 3 +- headers/private/system/syscalls.h | 3 +- headers/private/system/system_profiler_defs.h | 9 +- src/bin/debug/profile/Team.cpp | 2 +- src/bin/debug/profile/profile.cpp | 3 +- .../scheduling_recorder.cpp | 7 +- src/system/kernel/Jamfile | 1 + src/system/kernel/condition_variable.cpp | 5 + src/system/kernel/debug/system_profiler.cpp | 485 ++++++++++++++---- src/system/kernel/listeners.cpp | 30 ++ src/system/kernel/lock.cpp | 5 + src/system/kernel/scheduler/scheduler.cpp | 1 + .../kernel/scheduler/scheduler_simple.cpp | 19 +- src/system/kernel/sem.cpp | 20 +- 17 files changed, 584 insertions(+), 151 deletions(-) create mode 100644 headers/private/kernel/listeners.h create mode 100644 src/system/kernel/listeners.cpp diff --git a/headers/private/kernel/kscheduler.h b/headers/private/kernel/kscheduler.h index 79aa32cfc2..e740015dc8 100644 --- a/headers/private/kernel/kscheduler.h +++ b/headers/private/kernel/kscheduler.h @@ -15,27 +15,6 @@ struct thread; struct SchedulerListener; -#ifdef __cplusplus - -#include - -struct SchedulerListener : DoublyLinkedListLinkImpl { - virtual ~SchedulerListener(); - - virtual void ThreadEnqueuedInRunQueue( - struct thread* thread) = 0; - virtual void ThreadRemovedFromRunQueue( - struct thread* thread) = 0; - virtual void ThreadScheduled(struct thread* oldThread, - struct thread* newThread) = 0; -}; - -typedef DoublyLinkedList SchedulerListenerList; -extern SchedulerListenerList gSchedulerListeners; - -#endif // __cplusplus - - struct scheduler_ops { void (*enqueue_in_run_queue)(struct thread* thread); void (*reschedule)(void); diff --git a/headers/private/kernel/listeners.h b/headers/private/kernel/listeners.h new file mode 100644 index 0000000000..a12924506d --- /dev/null +++ b/headers/private/kernel/listeners.h @@ -0,0 +1,119 @@ +/* + * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ +#ifndef KERNEL_LISTENERS_H +#define KERNEL_LISTENERS_H + +#include + +#include +#include + + +class ConditionVariable; +struct mutex; +struct rw_lock; +struct thread; + + +// scheduler listeners + + +struct SchedulerListener : DoublyLinkedListLinkImpl { + virtual ~SchedulerListener(); + + virtual void ThreadEnqueuedInRunQueue( + struct thread* thread) = 0; + virtual void ThreadRemovedFromRunQueue( + struct thread* thread) = 0; + virtual void ThreadScheduled(struct thread* oldThread, + struct thread* newThread) = 0; +}; + + +typedef DoublyLinkedList SchedulerListenerList; +extern SchedulerListenerList gSchedulerListeners; + // guarded by the thread spinlock + + +template +inline void +NotifySchedulerListeners(void (SchedulerListener::*hook)(Parameter1), + Parameter1 parameter1) +{ + if (!gSchedulerListeners.IsEmpty()) { + SchedulerListenerList::Iterator it = gSchedulerListeners.GetIterator(); + while (SchedulerListener* listener = it.Next()) + (listener->*hook)(parameter1); + } +} + + +template +inline void +NotifySchedulerListeners( + void (SchedulerListener::*hook)(Parameter1, Parameter2), + Parameter1 parameter1, Parameter2 parameter2) +{ + if (!gSchedulerListeners.IsEmpty()) { + SchedulerListenerList::Iterator it = gSchedulerListeners.GetIterator(); + while (SchedulerListener* listener = it.Next()) + (listener->*hook)(parameter1, parameter2); + } +} + + +// wait object listeners + + +struct WaitObjectListener : DoublyLinkedListLinkImpl { + virtual void SemaphoreCreated(sem_id id, + const char* name) = 0; + virtual void ConditionVariableInitialized( + ConditionVariable* variable) = 0; + virtual void MutexInitialized(mutex* lock) = 0; + virtual void RWLockInitialized(rw_lock* lock) = 0; +}; + +typedef DoublyLinkedList WaitObjectListenerList; +extern WaitObjectListenerList gWaitObjectListeners; +extern spinlock gWaitObjectListenerLock; + + +template +inline void +NotifyWaitObjectListeners(void (WaitObjectListener::*hook)(Parameter1), + Parameter1 parameter1) +{ + if (!gWaitObjectListeners.IsEmpty()) { + InterruptsSpinLocker locker(gWaitObjectListenerLock); + WaitObjectListenerList::Iterator it + = gWaitObjectListeners.GetIterator(); + while (WaitObjectListener* listener = it.Next()) + (listener->*hook)(parameter1); + } +} + + +template +inline void +NotifyWaitObjectListeners( + void (WaitObjectListener::*hook)(Parameter1, Parameter2), + Parameter1 parameter1, Parameter2 parameter2) +{ + if (!gWaitObjectListeners.IsEmpty()) { + InterruptsSpinLocker locker(gWaitObjectListenerLock); + WaitObjectListenerList::Iterator it + = gWaitObjectListeners.GetIterator(); + while (WaitObjectListener* listener = it.Next()) + (listener->*hook)(parameter1, parameter2); + } +} + + +void add_wait_object_listener(struct WaitObjectListener* listener); +void remove_wait_object_listener(struct WaitObjectListener* listener); + + +#endif // KERNEL_LISTENERS_H diff --git a/headers/private/kernel/sem.h b/headers/private/kernel/sem.h index e6e51cd75d..20b668d7e0 100644 --- a/headers/private/kernel/sem.h +++ b/headers/private/kernel/sem.h @@ -31,6 +31,8 @@ extern status_t deselect_sem(int32 object, struct select_info *info, extern sem_id create_sem_etc(int32 count, const char *name, team_id owner); +extern const char* sem_get_name_unsafe(sem_id id); + /* user calls */ sem_id _user_create_sem(int32 count, const char *name); status_t _user_delete_sem(sem_id id); diff --git a/headers/private/kernel/system_profiler.h b/headers/private/kernel/system_profiler.h index 029ebaafaa..3e460f43eb 100644 --- a/headers/private/kernel/system_profiler.h +++ b/headers/private/kernel/system_profiler.h @@ -17,7 +17,8 @@ __BEGIN_DECLS status_t _user_system_profiler_start( struct system_profiler_parameters* parameters); -status_t _user_system_profiler_next_buffer(size_t bytesRead); +status_t _user_system_profiler_next_buffer(size_t bytesRead, + uint64* _droppedEvents); status_t _user_system_profiler_stop(); __END_DECLS diff --git a/headers/private/system/syscalls.h b/headers/private/system/syscalls.h index dc1d911379..77d16b7c45 100644 --- a/headers/private/system/syscalls.h +++ b/headers/private/system/syscalls.h @@ -393,7 +393,8 @@ extern status_t _kern_clear_debugger_breakpoint(void *address, extern status_t _kern_system_profiler_start( struct system_profiler_parameters* parameters); -extern status_t _kern_system_profiler_next_buffer(size_t bytesRead); +extern status_t _kern_system_profiler_next_buffer(size_t bytesRead, + uint64* _droppedEvents); extern status_t _kern_system_profiler_stop(); /* atomic_* ops (needed for CPUs that don't support them directly) */ diff --git a/headers/private/system/system_profiler_defs.h b/headers/private/system/system_profiler_defs.h index 85c2e60ffe..1b7f7e42f3 100644 --- a/headers/private/system/system_profiler_defs.h +++ b/headers/private/system/system_profiler_defs.h @@ -81,7 +81,8 @@ struct system_profiler_event_header { // B_SYSTEM_PROFILER_TEAM_ADDED struct system_profiler_team_added { team_id team; - char args[1]; + uint16 args_offset; + char name[1]; }; // B_SYSTEM_PROFILER_TEAM_REMOVED @@ -127,7 +128,7 @@ struct system_profiler_samples { addr_t samples[0]; }; -// B_SYSTEM_PROFILER_THREAD_SCHEDULED, +// B_SYSTEM_PROFILER_THREAD_SCHEDULED struct system_profiler_thread_scheduled { bigtime_t time; thread_id thread; @@ -137,14 +138,14 @@ struct system_profiler_thread_scheduled { addr_t previous_thread_wait_object; }; -// B_SYSTEM_PROFILER_THREAD_ENQUEUED_IN_RUN_QUEUE, +// B_SYSTEM_PROFILER_THREAD_ENQUEUED_IN_RUN_QUEUE struct system_profiler_thread_enqueued_in_run_queue { bigtime_t time; thread_id thread; uint8 priority; }; -// B_SYSTEM_PROFILER_THREAD_REMOVED_FROM_RUN_QUEUE, +// B_SYSTEM_PROFILER_THREAD_REMOVED_FROM_RUN_QUEUE struct system_profiler_thread_removed_from_run_queue { bigtime_t time; thread_id thread; diff --git a/src/bin/debug/profile/Team.cpp b/src/bin/debug/profile/Team.cpp index 1b6dc5924e..4f9d6c3732 100644 --- a/src/bin/debug/profile/Team.cpp +++ b/src/bin/debug/profile/Team.cpp @@ -95,7 +95,7 @@ status_t Team::Init(system_profiler_team_added* addedInfo) { fID = addedInfo->team; - fArgs = addedInfo->args; + fArgs = addedInfo->name + addedInfo->args_offset; return B_OK; } diff --git a/src/bin/debug/profile/profile.cpp b/src/bin/debug/profile/profile.cpp index 5f8c0ff42a..d008ee8b56 100644 --- a/src/bin/debug/profile/profile.cpp +++ b/src/bin/debug/profile/profile.cpp @@ -637,7 +637,8 @@ profile_all(const char* const* programArgs, int programArgCount) break; // get next buffer - error = _kern_system_profiler_next_buffer(bufferSize); + uint64 droppedEvents = 0; + error = _kern_system_profiler_next_buffer(bufferSize, &droppedEvents); if (error != B_OK) { if (error == B_INTERRUPTED) { diff --git a/src/bin/debug/scheduling_recorder/scheduling_recorder.cpp b/src/bin/debug/scheduling_recorder/scheduling_recorder.cpp index e41f2be791..f911e8f510 100644 --- a/src/bin/debug/scheduling_recorder/scheduling_recorder.cpp +++ b/src/bin/debug/scheduling_recorder/scheduling_recorder.cpp @@ -179,7 +179,9 @@ public: break; // get next buffer - error = _kern_system_profiler_next_buffer(bufferSize); + uint64 droppedEvents = 0; + error = _kern_system_profiler_next_buffer(bufferSize, + &droppedEvents); if (error != B_OK) { if (error == B_INTERRUPTED) { @@ -192,6 +194,9 @@ public: kCommandName, strerror(error)); break; } + + if (droppedEvents > 0) + fprintf(stderr, "%llu events dropped\n", droppedEvents); } // stop profiling diff --git a/src/system/kernel/Jamfile b/src/system/kernel/Jamfile index 4c764fae3a..5acdcadeb4 100644 --- a/src/system/kernel/Jamfile +++ b/src/system/kernel/Jamfile @@ -30,6 +30,7 @@ KernelMergeObject kernel_core.o : int.c kernel_daemon.cpp linkhack.c + listeners.cpp lock.cpp low_resource_manager.cpp main.cpp diff --git a/src/system/kernel/condition_variable.cpp b/src/system/kernel/condition_variable.cpp index 9d4a70fea6..1975ea6150 100644 --- a/src/system/kernel/condition_variable.cpp +++ b/src/system/kernel/condition_variable.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -190,6 +191,8 @@ ConditionVariable::Init(const void* object, const char* objectType) new(&fEntries) EntryList; T_SCHEDULING_ANALYSIS(InitConditionVariable(this, object, objectType)); + NotifyWaitObjectListeners(&WaitObjectListener::ConditionVariableInitialized, + this); } @@ -203,6 +206,8 @@ ConditionVariable::Publish(const void* object, const char* objectType) new(&fEntries) EntryList; T_SCHEDULING_ANALYSIS(InitConditionVariable(this, object, objectType)); + NotifyWaitObjectListeners(&WaitObjectListener::ConditionVariableInitialized, + this); InterruptsLocker _; SpinLocker locker(sConditionVariablesLock); diff --git a/src/system/kernel/debug/system_profiler.cpp b/src/system/kernel/debug/system_profiler.cpp index 8c27babda3..060dceb1bc 100644 --- a/src/system/kernel/debug/system_profiler.cpp +++ b/src/system/kernel/debug/system_profiler.cpp @@ -13,9 +13,12 @@ #include #include +#include #include #include +#include #include +#include #include #include #include @@ -32,12 +35,17 @@ class SystemProfiler; +// minimum/maximum size of the table used for wait object caching +#define MIN_WAIT_OBJECT_COUNT 128 +#define MAX_WAIT_OBJECT_COUNT 1024 + + static spinlock sProfilerLock = B_SPINLOCK_INITIALIZER; static SystemProfiler* sProfiler = NULL; class SystemProfiler : public Referenceable, private NotificationListener, - private SchedulerListener { + private SchedulerListener, private WaitObjectListener { public: SystemProfiler(team_id team, const area_info& userAreaInfo, @@ -48,7 +56,8 @@ public: team_id Team() const { return fTeam; } status_t Init(); - status_t NextBuffer(size_t bytesRead); + status_t NextBuffer(size_t bytesRead, + uint64* _droppedEvents); private: virtual void EventOccured(NotificationService& service, @@ -60,6 +69,13 @@ private: virtual void ThreadScheduled(struct thread* oldThread, struct thread* newThread); + virtual void SemaphoreCreated(sem_id id, + const char* name); + virtual void ConditionVariableInitialized( + ConditionVariable* variable); + virtual void MutexInitialized(mutex* lock); + virtual void RWLockInitialized(rw_lock* lock); + bool _TeamAdded(struct team* team); bool _TeamRemoved(struct team* team); bool _TeamExec(struct team* team); @@ -70,6 +86,12 @@ private: bool _ImageAdded(struct image* image); bool _ImageRemoved(struct image* image); + void _WaitObjectCreated(addr_t object, uint32 type); + void _WaitObjectUsed(addr_t object, uint32 type); + + inline void _MaybeNotifyProfilerThreadLocked(); + inline void _MaybeNotifyProfilerThread(); + static bool _InitialTeamIterator(struct team* team, void* cookie); static bool _InitialThreadIterator(struct thread* thread, @@ -78,8 +100,7 @@ private: void* cookie); void* _AllocateBuffer(size_t size, int event, int cpu, - int count, bool threadsLocked = false, - bool* _unlockProfiler = NULL); + int count); static void _InitTimers(void* cookie, int cpu); static void _UninitTimers(void* cookie, int cpu); @@ -97,6 +118,45 @@ private: addr_t buffer[B_DEBUG_STACK_TRACE_DEPTH]; }; + struct WaitObjectKey { + addr_t object; + uint32 type; + }; + + struct WaitObject : DoublyLinkedListLinkImpl, + HashTableLink, WaitObjectKey { + }; + + struct WaitObjectTableDefinition { + typedef WaitObjectKey KeyType; + typedef WaitObject ValueType; + + size_t HashKey(const WaitObjectKey& key) const + { + return (size_t)key.object ^ (size_t)key.type; + } + + size_t Hash(const WaitObject* value) const + { + return HashKey(*value); + } + + bool Compare(const WaitObjectKey& key, + const WaitObject* value) const + { + return value->type == key.type + && value->object == key.object; + } + + HashTableLink* GetLink(WaitObject* value) const + { + return value; + } + }; + + typedef DoublyLinkedList WaitObjectList; + typedef OpenHashTable WaitObjectTable; + private: spinlock fLock; team_id fTeam; @@ -111,6 +171,7 @@ private: size_t fBufferCapacity; size_t fBufferStart; size_t fBufferSize; + uint64 fDroppedEvents; bool fTeamNotificationsRequested; bool fTeamNotificationsEnabled; bool fThreadNotificationsRequested; @@ -118,21 +179,50 @@ private: bool fImageNotificationsRequested; bool fImageNotificationsEnabled; bool fSchedulerNotificationsRequested; + bool fWaitObjectNotificationsRequested; ConditionVariable fProfilerWaitCondition; bool fProfilerWaiting; bool fProfilingActive; bool fReentered[B_MAX_CPU_COUNT]; CPUProfileData fCPUData[B_MAX_CPU_COUNT]; + struct thread** fRunningThreads; + WaitObject* fWaitObjectBuffer; + int32 fWaitObjectCount; + WaitObjectList fUsedWaitObjects; + WaitObjectList fFreeWaitObjects; + WaitObjectTable fWaitObjectTable; }; +inline void +SystemProfiler::_MaybeNotifyProfilerThreadLocked() +{ + // If the buffer is full enough, notify the profiler. + if (fProfilerWaiting && fBufferSize > fBufferCapacity / 2) { + fProfilerWaiting = false; + int cpu = smp_get_current_cpu(); + fReentered[cpu] = true; + fProfilerWaitCondition.NotifyOne(true); + fReentered[cpu] = false; + } +} + + +inline void +SystemProfiler::_MaybeNotifyProfilerThread() +{ + if (!fProfilerWaiting) + return; + + InterruptsSpinLocker threadsLocker(gThreadSpinlock); + SpinLocker locker(fLock); + + _MaybeNotifyProfilerThreadLocked(); +} + + SystemProfiler::SystemProfiler(team_id team, const area_info& userAreaInfo, const system_profiler_parameters& parameters) -#if 0 - // scheduling - size_t locking_lookup_size; // size of the lookup table used for - // caching the locking primitive infos -#endif : fTeam(team), fUserArea(userAreaInfo.area), @@ -146,6 +236,7 @@ SystemProfiler::SystemProfiler(team_id team, const area_info& userAreaInfo, fBufferCapacity(0), fBufferStart(0), fBufferSize(0), + fDroppedEvents(0), fTeamNotificationsRequested(false), fTeamNotificationsEnabled(false), fThreadNotificationsRequested(false), @@ -153,12 +244,28 @@ SystemProfiler::SystemProfiler(team_id team, const area_info& userAreaInfo, fImageNotificationsRequested(false), fImageNotificationsEnabled(false), fSchedulerNotificationsRequested(false), - fProfilerWaiting(false) + fWaitObjectNotificationsRequested(false), + fProfilerWaiting(false), + fWaitObjectBuffer(NULL), + fWaitObjectCount(0), + fUsedWaitObjects(), + fFreeWaitObjects(), + fWaitObjectTable() { B_INITIALIZE_SPINLOCK(&fLock); fProfilerWaitCondition.Init(this, "system profiler"); memset(fReentered, 0, sizeof(fReentered)); + + // compute the number wait objects we want to cache + if ((fFlags & B_SYSTEM_PROFILER_SCHEDULING_EVENTS) != 0) { + fWaitObjectCount = parameters.locking_lookup_size + / (sizeof(WaitObject) + sizeof(void*)); + if (fWaitObjectCount < MIN_WAIT_OBJECT_COUNT) + fWaitObjectCount = MIN_WAIT_OBJECT_COUNT; + if (fWaitObjectCount > MAX_WAIT_OBJECT_COUNT) + fWaitObjectCount = MAX_WAIT_OBJECT_COUNT; + } } @@ -180,6 +287,12 @@ SystemProfiler::~SystemProfiler() scheduler_remove_listener(this); } + // stop wait object listening + if (fWaitObjectNotificationsRequested) { + InterruptsSpinLocker locker(gWaitObjectListenerLock); + remove_wait_object_listener(this); + } + // deactivate the profiling timers on all CPUs if ((fFlags & B_SYSTEM_PROFILER_SAMPLING_EVENTS) != 0) call_all_cpus(_UninitTimers, this); @@ -206,6 +319,10 @@ SystemProfiler::~SystemProfiler() notificationManager.RemoveListener("teams", NULL, *this); } + // delete wait object related allocations + fWaitObjectTable.Clear(); + delete[] fWaitObjectBuffer; + // unlock the memory and delete the area if (fKernelArea >= 0) { unlock_memory(fHeader, fAreaSize, B_READ_DEVICE); @@ -241,6 +358,20 @@ SystemProfiler::Init() fHeader->start = 0; fHeader->size = 0; + // allocate the wait object buffer and init the hash table + if (fWaitObjectCount > 0) { + fWaitObjectBuffer = new(std::nothrow) WaitObject[fWaitObjectCount]; + if (fWaitObjectBuffer == NULL) + return B_NO_MEMORY; + + for (int32 i = 0; i < fWaitObjectCount; i++) + fFreeWaitObjects.Add(fWaitObjectBuffer + i); + + error = fWaitObjectTable.Init(fWaitObjectCount); + if (error != B_OK) + return error; + } + // start listening for notifications // teams @@ -284,32 +415,50 @@ SystemProfiler::Init() teamsLocker.Unlock(); } - // threads - if ((fFlags & B_SYSTEM_PROFILER_THREAD_EVENTS) != 0) { - InterruptsSpinLocker threadsLocker(gThreadSpinlock); - if (thread_iterate_through_threads(&_InitialThreadIterator, this) - != NULL) { - return B_BUFFER_OVERFLOW; - } - fThreadNotificationsEnabled = true; - threadsLocker.Unlock(); - } - // images if ((fFlags & B_SYSTEM_PROFILER_IMAGE_EVENTS) != 0) { if (image_iterate_through_images(&_InitialImageIterator, this) != NULL) return B_BUFFER_OVERFLOW; } + // threads + struct thread* runningThreads[B_MAX_CPU_COUNT]; + memset(runningThreads, 0, sizeof(runningThreads)); + fRunningThreads = runningThreads; + + InterruptsSpinLocker threadsLocker(gThreadSpinlock); + if ((fFlags & B_SYSTEM_PROFILER_THREAD_EVENTS) != 0 + || (fFlags & B_SYSTEM_PROFILER_SCHEDULING_EVENTS) != 0) { + if (thread_iterate_through_threads(&_InitialThreadIterator, this) + != NULL) { + return B_BUFFER_OVERFLOW; + } + fThreadNotificationsEnabled + = (fFlags & B_SYSTEM_PROFILER_THREAD_EVENTS) != 0; + } + fProfilingActive = true; - // start scheduler listening + // start scheduler and wait object listening if ((fFlags & B_SYSTEM_PROFILER_SCHEDULING_EVENTS) != 0) { - InterruptsSpinLocker threadsLocker(gThreadSpinlock); scheduler_add_listener(this); fSchedulerNotificationsRequested = true; + + SpinLocker waitObjectLocker(gWaitObjectListenerLock); + add_wait_object_listener(this); + fWaitObjectNotificationsRequested = true; + waitObjectLocker.Unlock(); + + // fake schedule events for the initially running threads + int32 cpuCount = smp_get_num_cpus(); + for (int32 i = 0; i < cpuCount; i++) { + if (runningThreads[i] != NULL) + ThreadScheduled(runningThreads[i], runningThreads[i]); + } } + threadsLocker.Unlock(); + // activate the profiling timers on all CPUs if ((fFlags & B_SYSTEM_PROFILER_SAMPLING_EVENTS) != 0) call_all_cpus(_InitTimers, this); @@ -319,7 +468,7 @@ SystemProfiler::Init() status_t -SystemProfiler::NextBuffer(size_t bytesRead) +SystemProfiler::NextBuffer(size_t bytesRead, uint64* _droppedEvents) { InterruptsSpinLocker locker(fLock); @@ -348,12 +497,14 @@ SystemProfiler::NextBuffer(size_t bytesRead) status_t error = waitEntry.Wait( B_CAN_INTERRUPT | B_RELATIVE_TIMEOUT, 1000000); - if (error == B_OK) { - // the caller has unset fProfilerWaiting for us - return B_OK; - } locker.Lock(); + + if (error == B_OK) { + // the caller has unset fProfilerWaiting for us + break; + } + fProfilerWaiting = false; if (error != B_TIMED_OUT) @@ -361,8 +512,15 @@ SystemProfiler::NextBuffer(size_t bytesRead) // just the timeout -- return, if the buffer is not empty if (fBufferSize > 0) - return B_OK; + break; } + + if (_droppedEvents != NULL) { + *_droppedEvents = fDroppedEvents; + fDroppedEvents = 0; + } + + return B_OK; } @@ -445,35 +603,24 @@ SystemProfiler::EventOccured(NotificationService& service, break; } } + + _MaybeNotifyProfilerThread(); } -#if 0 -// B_SYSTEM_PROFILER_WAIT_OBJECT_INFO -struct system_profiler_wait_object_info { - uint32 type; - void* object; - void* referenced_object; - char name[1]; -}; -#endif - void SystemProfiler::ThreadEnqueuedInRunQueue(struct thread* thread) { int cpu = smp_get_current_cpu(); - InterruptsSpinLocker locker(fLock, false, !fReentered[cpu]); + SpinLocker locker(fLock, false, !fReentered[cpu]); // When re-entering, we already hold the lock. - bool unlockProfiler = false; - system_profiler_thread_enqueued_in_run_queue* event = (system_profiler_thread_enqueued_in_run_queue*) _AllocateBuffer( sizeof(system_profiler_thread_enqueued_in_run_queue), - B_SYSTEM_PROFILER_THREAD_ENQUEUED_IN_RUN_QUEUE, - cpu, 0, true, &unlockProfiler); + B_SYSTEM_PROFILER_THREAD_ENQUEUED_IN_RUN_QUEUE, cpu, 0); if (event == NULL) return; @@ -487,16 +634,8 @@ SystemProfiler::ThreadEnqueuedInRunQueue(struct thread* thread) // if it had been waiting on a condition variable, since then we'd likely // deadlock in ConditionVariable::NotifyOne(), as it acquires a static // spinlock. - if (unlockProfiler - && thread->wait.type != THREAD_BLOCK_TYPE_CONDITION_VARIABLE) { - // NotifyOne() will probably re-enqueue the profiler thread to the - // run queue, thus causing our ThreadEnqueuedInRunQueue() to be invoked. - // Hence we need the re-entering detection. - fProfilerWaiting = false; - fReentered[cpu] = true; - fProfilerWaitCondition.NotifyOne(true); - fReentered[cpu] = false; - } + if (thread->wait.type != THREAD_BLOCK_TYPE_CONDITION_VARIABLE) + _MaybeNotifyProfilerThreadLocked(); } @@ -505,17 +644,14 @@ SystemProfiler::ThreadRemovedFromRunQueue(struct thread* thread) { int cpu = smp_get_current_cpu(); - InterruptsSpinLocker locker(fLock, false, !fReentered[cpu]); + SpinLocker locker(fLock, false, !fReentered[cpu]); // When re-entering, we already hold the lock. - bool unlockProfiler = false; - system_profiler_thread_removed_from_run_queue* event = (system_profiler_thread_removed_from_run_queue*) _AllocateBuffer( sizeof(system_profiler_thread_removed_from_run_queue), - B_SYSTEM_PROFILER_THREAD_REMOVED_FROM_RUN_QUEUE, - smp_get_current_cpu(), 0, true, &unlockProfiler); + B_SYSTEM_PROFILER_THREAD_REMOVED_FROM_RUN_QUEUE, cpu, 0); if (event == NULL) return; @@ -525,15 +661,7 @@ SystemProfiler::ThreadRemovedFromRunQueue(struct thread* thread) fHeader->size = fBufferSize; // unblock the profiler thread, if necessary - if (unlockProfiler) { - // NotifyOne() will probably re-enqueue the profiler thread to the - // run queue, thus causing our ThreadEnqueuedInRunQueue() to be invoked. - // Hence we need the re-entering detection. - fProfilerWaiting = false; - fReentered[cpu] = true; - fProfilerWaitCondition.NotifyOne(true); - fReentered[cpu] = false; - } + _MaybeNotifyProfilerThreadLocked(); } @@ -543,18 +671,17 @@ SystemProfiler::ThreadScheduled(struct thread* oldThread, { int cpu = smp_get_current_cpu(); - InterruptsSpinLocker locker(fLock, false, !fReentered[cpu]); + SpinLocker locker(fLock, false, !fReentered[cpu]); // When re-entering, we already hold the lock. - // TODO: Deal with the wait object! - - bool unlockProfiler = false; + // If the old thread starts waiting, handle the wait object. + if (oldThread->state == B_THREAD_WAITING) + _WaitObjectUsed((addr_t)oldThread->wait.object, oldThread->wait.type); system_profiler_thread_scheduled* event = (system_profiler_thread_scheduled*) _AllocateBuffer(sizeof(system_profiler_thread_scheduled), - B_SYSTEM_PROFILER_THREAD_SCHEDULED, smp_get_current_cpu(), 0, - true, &unlockProfiler); + B_SYSTEM_PROFILER_THREAD_SCHEDULED, cpu, 0); if (event == NULL) return; @@ -568,15 +695,35 @@ SystemProfiler::ThreadScheduled(struct thread* oldThread, fHeader->size = fBufferSize; // unblock the profiler thread, if necessary - if (unlockProfiler) { - // NotifyOne() will probably re-enqueue the profiler thread to the - // run queue, thus causing our ThreadEnqueuedInRunQueue() to be invoked. - // Hence we need the re-entering detection. - fProfilerWaiting = false; - fReentered[cpu] = true; - fProfilerWaitCondition.NotifyOne(true); - fReentered[cpu] = false; - } + _MaybeNotifyProfilerThreadLocked(); +} + + +void +SystemProfiler::SemaphoreCreated(sem_id id, const char* name) +{ + _WaitObjectCreated((addr_t)id, THREAD_BLOCK_TYPE_SEMAPHORE); +} + + +void +SystemProfiler::ConditionVariableInitialized(ConditionVariable* variable) +{ + _WaitObjectCreated((addr_t)variable, THREAD_BLOCK_TYPE_CONDITION_VARIABLE); +} + + +void +SystemProfiler::MutexInitialized(mutex* lock) +{ + _WaitObjectCreated((addr_t)lock, THREAD_BLOCK_TYPE_MUTEX); +} + + +void +SystemProfiler::RWLockInitialized(rw_lock* lock) +{ + _WaitObjectCreated((addr_t)lock, THREAD_BLOCK_TYPE_RW_LOCK); } @@ -585,16 +732,20 @@ SystemProfiler::_TeamAdded(struct team* team) { InterruptsSpinLocker locker(fLock); + size_t nameLen = strlen(team->name); size_t argsLen = strlen(team->args); system_profiler_team_added* event = (system_profiler_team_added*) - _AllocateBuffer(sizeof(system_profiler_team_added) + argsLen, + _AllocateBuffer( + sizeof(system_profiler_team_added) + nameLen + 1 + argsLen, B_SYSTEM_PROFILER_TEAM_ADDED, 0, 0); if (event == NULL) return false; event->team = team->id; - strcpy(event->args, team->args); + strcpy(event->name, team->name); + event->args_offset = nameLen + 1; + strcpy(event->name + nameLen + 1, team->args); fHeader->size = fBufferSize; @@ -727,6 +878,124 @@ SystemProfiler::_ImageRemoved(struct image* image) } +void +SystemProfiler::_WaitObjectCreated(addr_t object, uint32 type) +{ + SpinLocker locker(fLock); + + // look up the object + WaitObjectKey key; + key.object = object; + key.type = type; + WaitObject* waitObject = fWaitObjectTable.Lookup(key); + + // If found, remove it and add it to the free list. This might sound weird, + // but it makes sense, since we lazily track *used* wait objects only. + // I.e. the object in the table is now guaranteedly obsolete. + if (waitObject) { + fWaitObjectTable.Remove(waitObject); + fUsedWaitObjects.Remove(waitObject); + fFreeWaitObjects.Add(waitObject, false); + } +} + +void +SystemProfiler::_WaitObjectUsed(addr_t object, uint32 type) +{ + // look up the object + WaitObjectKey key; + key.object = object; + key.type = type; + WaitObject* waitObject = fWaitObjectTable.Lookup(key); + + // If already known, re-queue it as most recently used and be done. + if (waitObject != NULL) { + fUsedWaitObjects.Remove(waitObject); + fUsedWaitObjects.Add(waitObject); + return; + } + + // not known yet -- get the info + const char* name = NULL; + const void* referencedObject = NULL; + + switch (type) { + case THREAD_BLOCK_TYPE_SEMAPHORE: + { + name = sem_get_name_unsafe((sem_id)object); + break; + } + + case THREAD_BLOCK_TYPE_CONDITION_VARIABLE: + { + ConditionVariable* variable = (ConditionVariable*)object; + name = variable->ObjectType(); + referencedObject = variable->Object(); + break; + } + + case THREAD_BLOCK_TYPE_MUTEX: + { + mutex* lock = (mutex*)object; + name = lock->name; + break; + } + + case THREAD_BLOCK_TYPE_RW_LOCK: + { + rw_lock* lock = (rw_lock*)object; + name = lock->name; + break; + } + + case THREAD_BLOCK_TYPE_OTHER: + { + name = (const char*)(void*)object; + break; + } + + case THREAD_BLOCK_TYPE_SNOOZE: + case THREAD_BLOCK_TYPE_SIGNAL: + default: + return; + } + + // add the event + size_t nameLen = name != NULL ? strlen(name) : 0; + + system_profiler_wait_object_info* event + = (system_profiler_wait_object_info*) + _AllocateBuffer(sizeof(system_profiler_wait_object_info) + nameLen, + B_SYSTEM_PROFILER_WAIT_OBJECT_INFO, 0, 0); + if (event != NULL) + return; + + event->type = type; + event->object = object; + event->referenced_object = (addr_t)referencedObject; + if (name != NULL) + strcpy(event->name, name); + else + event->name[0] = '\0'; + + fHeader->size = fBufferSize; + + // add the wait object + + // get a free one or steal the least recently used one + waitObject = fFreeWaitObjects.RemoveHead(); + if (waitObject == NULL) { + waitObject = fUsedWaitObjects.RemoveHead(); + fWaitObjectTable.Remove(waitObject); + } + + waitObject->object = object; + waitObject->type = type; + fWaitObjectTable.Insert(waitObject); + fUsedWaitObjects.Add(waitObject); +} + + /*static*/ bool SystemProfiler::_InitialTeamIterator(struct team* team, void* cookie) { @@ -739,6 +1008,12 @@ SystemProfiler::_InitialTeamIterator(struct team* team, void* cookie) SystemProfiler::_InitialThreadIterator(struct thread* thread, void* cookie) { SystemProfiler* self = (SystemProfiler*)cookie; + + if ((self->fFlags & B_SYSTEM_PROFILER_SCHEDULING_EVENTS) != 0 + && thread->state == B_THREAD_RUNNING && thread->cpu != NULL) { + self->fRunningThreads[thread->cpu->cpu_num] = thread; + } + return !self->_ThreadAdded(thread); } @@ -754,8 +1029,7 @@ SystemProfiler::_InitialImageIterator(struct image* image, void* cookie) void* -SystemProfiler::_AllocateBuffer(size_t size, int event, int cpu, int count, - bool threadsLocked, bool* _unlockProfiler) +SystemProfiler::_AllocateBuffer(size_t size, int event, int cpu, int count) { size = (size + 3) / 4 * 4; size += sizeof(system_profiler_event_header); @@ -773,8 +1047,10 @@ SystemProfiler::_AllocateBuffer(size_t size, int event, int cpu, int count, } else end -= fBufferCapacity; - if (end + size > fBufferStart) + if (end + size > fBufferStart) { + fDroppedEvents++; return NULL; + } } system_profiler_event_header* header @@ -785,20 +1061,6 @@ SystemProfiler::_AllocateBuffer(size_t size, int event, int cpu, int count, fBufferSize += size; - // If the buffer is full enough notify the profiler. - if (fProfilerWaiting && fBufferSize > fBufferCapacity / 2) { - if (threadsLocked) { - // We're obviously recording scheduler events. NotifyOne() will - // likely requeue the profiler thread in the run queue, thus causing - // recursion. We can't really handle the problem here, so just - // notify our caller. - *_unlockProfiler = true; - } else { - fProfilerWaiting = false; - fProfilerWaitCondition.NotifyOne(); - } - } - return header + 1; } @@ -851,10 +1113,8 @@ SystemProfiler::_DoSample() _AllocateBuffer(sizeof(system_profiler_samples) + count * sizeof(addr_t), B_SYSTEM_PROFILER_SAMPLES, cpu, count); - if (event == NULL) { - // TODO: Count drops! + if (event == NULL) return; - } event->thread = thread->id; memcpy(event->samples, cpuData.buffer, count * sizeof(addr_t)); @@ -883,7 +1143,7 @@ _user_system_profiler_start(struct system_profiler_parameters* userParameters) { // copy params to the kernel struct system_profiler_parameters parameters; - if (userParameters == NULL + if (userParameters == NULL || !IS_USER_ADDRESS(userParameters) || user_memcpy(¶meters, userParameters, sizeof(parameters)) != B_OK) { return B_BAD_ADDRESS; @@ -942,8 +1202,11 @@ _user_system_profiler_start(struct system_profiler_parameters* userParameters) status_t -_user_system_profiler_next_buffer(size_t bytesRead) +_user_system_profiler_next_buffer(size_t bytesRead, uint64* _droppedEvents) { + if (_droppedEvents != NULL && !IS_USER_ADDRESS(_droppedEvents)) + return B_BAD_ADDRESS; + team_id team = thread_get_current_thread()->team->id; InterruptsSpinLocker locker(sProfilerLock); @@ -955,7 +1218,13 @@ _user_system_profiler_next_buffer(size_t bytesRead) Reference reference(profiler); locker.Unlock(); - return profiler->NextBuffer(bytesRead); + uint64 droppedEvents; + status_t error = profiler->NextBuffer(bytesRead, + _droppedEvents != NULL ? &droppedEvents : NULL); + if (error == B_OK && _droppedEvents != NULL) + user_memcpy(_droppedEvents, &droppedEvents, sizeof(droppedEvents)); + + return error; } diff --git a/src/system/kernel/listeners.cpp b/src/system/kernel/listeners.cpp new file mode 100644 index 0000000000..c05a0833ef --- /dev/null +++ b/src/system/kernel/listeners.cpp @@ -0,0 +1,30 @@ +/* + * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ + +#include + + +WaitObjectListenerList gWaitObjectListeners; +spinlock gWaitObjectListenerLock = B_SPINLOCK_INITIALIZER; + + +/*! Add the given wait object listener. gWaitObjectListenerLock lock must be + held. +*/ +void +add_wait_object_listener(struct WaitObjectListener* listener) +{ + gWaitObjectListeners.Add(listener); +} + + +/*! Remove the given wait object listener. gWaitObjectListenerLock lock must be + held. +*/ +void +remove_wait_object_listener(struct WaitObjectListener* listener) +{ + gWaitObjectListeners.Remove(listener); +} diff --git a/src/system/kernel/lock.cpp b/src/system/kernel/lock.cpp index f2b0cc4c0b..058c2bafaf 100644 --- a/src/system/kernel/lock.cpp +++ b/src/system/kernel/lock.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -219,6 +220,7 @@ rw_lock_init(rw_lock* lock, const char* name) lock->flags = 0; T_SCHEDULING_ANALYSIS(InitRWLock(lock, name)); + NotifyWaitObjectListeners(&WaitObjectListener::RWLockInitialized, lock); } @@ -234,6 +236,7 @@ rw_lock_init_etc(rw_lock* lock, const char* name, uint32 flags) lock->flags = flags & RW_LOCK_FLAG_CLONE_NAME; T_SCHEDULING_ANALYSIS(InitRWLock(lock, name)); + NotifyWaitObjectListeners(&WaitObjectListener::RWLockInitialized, lock); } @@ -430,6 +433,7 @@ mutex_init(mutex* lock, const char *name) lock->flags = 0; T_SCHEDULING_ANALYSIS(InitMutex(lock, name)); + NotifyWaitObjectListeners(&WaitObjectListener::MutexInitialized, lock); } @@ -446,6 +450,7 @@ mutex_init_etc(mutex* lock, const char *name, uint32 flags) lock->flags = flags & MUTEX_FLAG_CLONE_NAME; T_SCHEDULING_ANALYSIS(InitMutex(lock, name)); + NotifyWaitObjectListeners(&WaitObjectListener::MutexInitialized, lock); } diff --git a/src/system/kernel/scheduler/scheduler.cpp b/src/system/kernel/scheduler/scheduler.cpp index 1b8b1b9488..0279838024 100644 --- a/src/system/kernel/scheduler/scheduler.cpp +++ b/src/system/kernel/scheduler/scheduler.cpp @@ -4,6 +4,7 @@ */ #include +#include #include #include "scheduler_affine.h" diff --git a/src/system/kernel/scheduler/scheduler_simple.cpp b/src/system/kernel/scheduler/scheduler_simple.cpp index abecc574ea..f7d4e1acc5 100644 --- a/src/system/kernel/scheduler/scheduler_simple.cpp +++ b/src/system/kernel/scheduler/scheduler_simple.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -147,10 +148,8 @@ simple_enqueue_in_run_queue(struct thread *thread) } // notify listeners - for (SchedulerListenerList::Iterator it = gSchedulerListeners.GetIterator(); - SchedulerListener* listener = it.Next();) { - listener->ThreadEnqueuedInRunQueue(thread); - } + NotifySchedulerListeners(&SchedulerListener::ThreadEnqueuedInRunQueue, + thread); } @@ -174,10 +173,8 @@ simple_set_thread_priority(struct thread *thread, int32 priority) T(RemoveThread(thread)); // notify listeners - for (SchedulerListenerList::Iterator it = gSchedulerListeners.GetIterator(); - SchedulerListener* listener = it.Next();) { - listener->ThreadRemovedFromRunQueue(thread); - } + NotifySchedulerListeners(&SchedulerListener::ThreadRemovedFromRunQueue, + thread); // find thread in run queue struct thread *item, *prev; @@ -348,10 +345,8 @@ simple_reschedule(void) T(ScheduleThread(nextThread, oldThread)); // notify listeners - for (SchedulerListenerList::Iterator it = gSchedulerListeners.GetIterator(); - SchedulerListener* listener = it.Next();) { - listener->ThreadScheduled(oldThread, nextThread); - } + NotifySchedulerListeners(&SchedulerListener::ThreadScheduled, + oldThread, nextThread); nextThread->state = B_THREAD_RUNNING; nextThread->next_state = B_THREAD_READY; diff --git a/src/system/kernel/sem.cpp b/src/system/kernel/sem.cpp index 30fa818806..30630cbbd5 100644 --- a/src/system/kernel/sem.cpp +++ b/src/system/kernel/sem.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -359,7 +360,7 @@ delete_sem_internal(sem_id id, bool checkPermission) return B_OK; } - + // #pragma mark - Private Kernel API @@ -495,6 +496,8 @@ create_sem_etc(int32 count, const char *name, team_id owner) count, name, owner, id); T_SCHEDULING_ANALYSIS(CreateSemaphore(id, name)); + NotifyWaitObjectListeners(&WaitObjectListener::SemaphoreCreated, id, + name); } RELEASE_SEM_LIST_LOCK(); @@ -1154,6 +1157,21 @@ set_sem_owner(sem_id id, team_id team) } +/*! Returns the name of the semaphore. The name is not copied, so the caller + must make sure that the semaphore remains alive as long as the name is used. +*/ +const char* +sem_get_name_unsafe(sem_id id) +{ + int slot = id % sMaxSems; + + if (sSemsActive == false || id < 0 || sSems[slot].id != id) + return NULL; + + return sSems[slot].u.used.name; +} + + // #pragma mark - Syscalls