mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 17:12:43 +00:00 
			
		
		
		
	Kernel: Generate page fault events from the kernel profiler
Hook the kernel page fault handler and capture page fault events when the fault has a current thread attached in TLS. We capture the eip and ebp so we can unwind the stack and locate which pieces of code are generating the most page faults. Co-authored-by: Gunnar Beutner <gbeutner@serenityos.org>
This commit is contained in:
		
							parent
							
								
									6ac1ca5a9a
								
							
						
					
					
						commit
						83fc591cea
					
				
					 7 changed files with 47 additions and 2 deletions
				
			
		|  | @ -23,6 +23,7 @@ | ||||||
| #include <Kernel/Interrupts/UnhandledInterruptHandler.h> | #include <Kernel/Interrupts/UnhandledInterruptHandler.h> | ||||||
| #include <Kernel/KSyms.h> | #include <Kernel/KSyms.h> | ||||||
| #include <Kernel/Panic.h> | #include <Kernel/Panic.h> | ||||||
|  | #include <Kernel/PerformanceManager.h> | ||||||
| #include <Kernel/Process.h> | #include <Kernel/Process.h> | ||||||
| #include <Kernel/Random.h> | #include <Kernel/Random.h> | ||||||
| #include <Kernel/Thread.h> | #include <Kernel/Thread.h> | ||||||
|  | @ -243,8 +244,11 @@ void page_fault_handler(TrapFrame* trap) | ||||||
| 
 | 
 | ||||||
|     auto current_thread = Thread::current(); |     auto current_thread = Thread::current(); | ||||||
| 
 | 
 | ||||||
|     if (current_thread) |     if (current_thread) { | ||||||
|         current_thread->set_handling_page_fault(true); |         current_thread->set_handling_page_fault(true); | ||||||
|  |         PerformanceManager::add_page_fault_event(*current_thread, regs); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     ScopeGuard guard = [current_thread] { |     ScopeGuard guard = [current_thread] { | ||||||
|         if (current_thread) |         if (current_thread) | ||||||
|             current_thread->set_handling_page_fault(false); |             current_thread->set_handling_page_fault(false); | ||||||
|  |  | ||||||
|  | @ -7,6 +7,7 @@ | ||||||
| #include <AK/JsonArraySerializer.h> | #include <AK/JsonArraySerializer.h> | ||||||
| #include <AK/JsonObject.h> | #include <AK/JsonObject.h> | ||||||
| #include <AK/JsonObjectSerializer.h> | #include <AK/JsonObjectSerializer.h> | ||||||
|  | #include <AK/ScopeGuard.h> | ||||||
| #include <Kernel/Arch/x86/SmapDisabler.h> | #include <Kernel/Arch/x86/SmapDisabler.h> | ||||||
| #include <Kernel/FileSystem/Custody.h> | #include <Kernel/FileSystem/Custody.h> | ||||||
| #include <Kernel/KBufferBuilder.h> | #include <Kernel/KBufferBuilder.h> | ||||||
|  | @ -63,6 +64,17 @@ KResult PerformanceEventBuffer::append_with_eip_and_ebp(ProcessID pid, ThreadID | ||||||
|     if ((g_profiling_event_mask & type) == 0) |     if ((g_profiling_event_mask & type) == 0) | ||||||
|         return EINVAL; |         return EINVAL; | ||||||
| 
 | 
 | ||||||
|  |     auto current_thread = Thread::current(); | ||||||
|  |     u32 enter_count = 0; | ||||||
|  |     if (current_thread) | ||||||
|  |         enter_count = current_thread->enter_profiler(); | ||||||
|  |     ScopeGuard leave_profiler([&] { | ||||||
|  |         if (current_thread) | ||||||
|  |             current_thread->leave_profiler(); | ||||||
|  |     }); | ||||||
|  |     if (enter_count > 0) | ||||||
|  |         return EINVAL; | ||||||
|  | 
 | ||||||
|     PerformanceEvent event; |     PerformanceEvent event; | ||||||
|     event.type = type; |     event.type = type; | ||||||
|     event.lost_samples = lost_samples; |     event.lost_samples = lost_samples; | ||||||
|  | @ -122,6 +134,8 @@ KResult PerformanceEventBuffer::append_with_eip_and_ebp(ProcessID pid, ThreadID | ||||||
|         event.data.kfree.size = arg1; |         event.data.kfree.size = arg1; | ||||||
|         event.data.kfree.ptr = arg2; |         event.data.kfree.ptr = arg2; | ||||||
|         break; |         break; | ||||||
|  |     case PERF_EVENT_PAGE_FAULT: | ||||||
|  |         break; | ||||||
|     default: |     default: | ||||||
|         return EINVAL; |         return EINVAL; | ||||||
|     } |     } | ||||||
|  | @ -210,6 +224,9 @@ bool PerformanceEventBuffer::to_json_impl(Serializer& object) const | ||||||
|             event_object.add("ptr", static_cast<u64>(event.data.kfree.ptr)); |             event_object.add("ptr", static_cast<u64>(event.data.kfree.ptr)); | ||||||
|             event_object.add("size", static_cast<u64>(event.data.kfree.size)); |             event_object.add("size", static_cast<u64>(event.data.kfree.size)); | ||||||
|             break; |             break; | ||||||
|  |         case PERF_EVENT_PAGE_FAULT: | ||||||
|  |             event_object.add("type", "page_fault"); | ||||||
|  |             break; | ||||||
|         } |         } | ||||||
|         event_object.add("pid", event.pid); |         event_object.add("pid", event.pid); | ||||||
|         event_object.add("tid", event.tid); |         event_object.add("tid", event.tid); | ||||||
|  |  | ||||||
|  | @ -106,6 +106,15 @@ public: | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     inline static void add_page_fault_event(Thread& thread, const RegisterState& regs) | ||||||
|  |     { | ||||||
|  |         if (auto* event_buffer = thread.process().current_perf_events_buffer()) { | ||||||
|  |             [[maybe_unused]] auto rc = event_buffer->append_with_eip_and_ebp( | ||||||
|  |                 thread.pid(), thread.tid(), | ||||||
|  |                 regs.eip, regs.ebp, PERF_EVENT_PAGE_FAULT, 0, 0, 0, nullptr); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     inline static void timer_tick(RegisterState const& regs) |     inline static void timer_tick(RegisterState const& regs) | ||||||
|     { |     { | ||||||
|         static Time last_wakeup; |         static Time last_wakeup; | ||||||
|  |  | ||||||
|  | @ -1120,6 +1120,16 @@ public: | ||||||
|     void set_idle_thread() { m_is_idle_thread = true; } |     void set_idle_thread() { m_is_idle_thread = true; } | ||||||
|     bool is_idle_thread() const { return m_is_idle_thread; } |     bool is_idle_thread() const { return m_is_idle_thread; } | ||||||
| 
 | 
 | ||||||
|  |     ALWAYS_INLINE u32 enter_profiler() | ||||||
|  |     { | ||||||
|  |         return m_nested_profiler_calls.fetch_add(1, AK::MemoryOrder::memory_order_acq_rel); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     ALWAYS_INLINE u32 leave_profiler() | ||||||
|  |     { | ||||||
|  |         return m_nested_profiler_calls.fetch_sub(1, AK::MemoryOrder::memory_order_acquire); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
| private: | private: | ||||||
|     Thread(NonnullRefPtr<Process>, NonnullOwnPtr<Region> kernel_stack_region); |     Thread(NonnullRefPtr<Process>, NonnullOwnPtr<Region> kernel_stack_region); | ||||||
| 
 | 
 | ||||||
|  | @ -1257,6 +1267,7 @@ private: | ||||||
|     bool m_in_block { false }; |     bool m_in_block { false }; | ||||||
|     bool m_is_idle_thread { false }; |     bool m_is_idle_thread { false }; | ||||||
|     Atomic<bool> m_have_any_unmasked_pending_signals { false }; |     Atomic<bool> m_have_any_unmasked_pending_signals { false }; | ||||||
|  |     Atomic<u32> m_nested_profiler_calls { 0 }; | ||||||
| 
 | 
 | ||||||
|     void yield_without_holding_big_lock(); |     void yield_without_holding_big_lock(); | ||||||
|     void donate_without_holding_big_lock(RefPtr<Thread>&, const char*); |     void donate_without_holding_big_lock(RefPtr<Thread>&, const char*); | ||||||
|  |  | ||||||
|  | @ -60,6 +60,7 @@ enum { | ||||||
|     PERF_EVENT_CONTEXT_SWITCH = 1024, |     PERF_EVENT_CONTEXT_SWITCH = 1024, | ||||||
|     PERF_EVENT_KMALLOC = 2048, |     PERF_EVENT_KMALLOC = 2048, | ||||||
|     PERF_EVENT_KFREE = 4096, |     PERF_EVENT_KFREE = 4096, | ||||||
|  |     PERF_EVENT_PAGE_FAULT = 8192, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| #define WNOHANG 1 | #define WNOHANG 1 | ||||||
|  |  | ||||||
|  | @ -89,6 +89,7 @@ enum { | ||||||
|     PERF_EVENT_CONTEXT_SWITCH = 1024, |     PERF_EVENT_CONTEXT_SWITCH = 1024, | ||||||
|     PERF_EVENT_KMALLOC = 2048, |     PERF_EVENT_KMALLOC = 2048, | ||||||
|     PERF_EVENT_KFREE = 4096, |     PERF_EVENT_KFREE = 4096, | ||||||
|  |     PERF_EVENT_PAGE_FAULT = 8192, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| #define PERF_EVENT_MASK_ALL (~0ull) | #define PERF_EVENT_MASK_ALL (~0ull) | ||||||
|  |  | ||||||
|  | @ -44,6 +44,8 @@ int main(int argc, char** argv) | ||||||
|                 event_mask |= PERF_EVENT_KMALLOC; |                 event_mask |= PERF_EVENT_KMALLOC; | ||||||
|             else if (event_type == "kfree") |             else if (event_type == "kfree") | ||||||
|                 event_mask |= PERF_EVENT_KFREE; |                 event_mask |= PERF_EVENT_KFREE; | ||||||
|  |             else if (event_type == "page_fault") | ||||||
|  |                 event_mask |= PERF_EVENT_PAGE_FAULT; | ||||||
|             else { |             else { | ||||||
|                 warnln("Unknown event type '{}' specified.", event_type); |                 warnln("Unknown event type '{}' specified.", event_type); | ||||||
|                 exit(1); |                 exit(1); | ||||||
|  | @ -53,7 +55,7 @@ int main(int argc, char** argv) | ||||||
| 
 | 
 | ||||||
|     auto print_types = [] { |     auto print_types = [] { | ||||||
|         outln(); |         outln(); | ||||||
|         outln("Event type can be one of: sample, context_switch, kmalloc and kfree."); |         outln("Event type can be one of: sample, context_switch, page_fault, kmalloc and kfree."); | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     if (!args_parser.parse(argc, argv, false)) { |     if (!args_parser.parse(argc, argv, false)) { | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Brian Gianforcaro
						Brian Gianforcaro