1919#include < setjmp.h>
2020#include < mach/thread_act.h>
2121#include < mach/thread_status.h>
22+ #include < mach/thread_info.h>
2223#include < mach/machine/thread_state.h>
2324#include < new>
2425#include < utility>
@@ -160,11 +161,32 @@ bool stack_trace_init(stack_trace_t* trace, uint32_t max_depth, uint64_t interva
160161 trace->thread_name = nullptr ;
161162 trace->timestamp = 0 ;
162163 trace->sampling_interval_nanos = interval_nanos;
164+ trace->cpu_time_nanos = 0 ;
163165 trace->frame_count = 0 ;
164166 trace->frames = (stack_frame_t *)malloc (max_depth * sizeof (stack_frame_t ));
165167 return trace->frames != nullptr ;
166168}
167169
170+ static bool thread_cpu_time_nanos (thread_t thread, uint64_t * cpu_time_nanos) {
171+ if (!cpu_time_nanos) return false ;
172+
173+ thread_basic_info_data_t info{};
174+ mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT ;
175+ if (thread_info (thread, THREAD_BASIC_INFO , reinterpret_cast <thread_info_t >(&info), &count) != KERN_SUCCESS ) {
176+ return false ;
177+ }
178+
179+ const uint64_t user_time_nanos =
180+ (static_cast <uint64_t >(info.user_time .seconds ) * 1000000000ULL )
181+ + (static_cast <uint64_t >(info.user_time .microseconds ) * 1000ULL );
182+ const uint64_t system_time_nanos =
183+ (static_cast <uint64_t >(info.system_time .seconds ) * 1000000000ULL )
184+ + (static_cast <uint64_t >(info.system_time .microseconds ) * 1000ULL );
185+
186+ *cpu_time_nanos = user_time_nanos + system_time_nanos;
187+ return true ;
188+ }
189+
168190/* *
169191 * Destroys a stack trace, freeing the thread name and frames array.
170192 *
@@ -396,6 +418,7 @@ bool mach_sampling_profiler::start_sampling() {
396418
397419 // Clear any leftover data from previous runs
398420 sample_buffer.clear ();
421+ previous_thread_cpu_time_nanos.clear ();
399422 if (sample_buffer.capacity () < config.max_buffer_size ) {
400423 sample_buffer.reserve (config.max_buffer_size );
401424 }
@@ -491,9 +514,10 @@ bool mach_sampling_profiler::is_profiler_internal_thread(thread_t thread) const
491514 * @param thread The thread to sample
492515 * @param interval_nanos The actual sampling interval in nanoseconds for this sample
493516 */
494- void mach_sampling_profiler::sample_thread (thread_t thread, uint64_t interval_nanos) {
517+ void mach_sampling_profiler::sample_thread (thread_t thread, uint64_t interval_nanos, uint64_t cpu_time_nanos ) {
495518 stack_trace_t trace;
496519 if (!stack_trace_init (&trace, config.max_stack_depth , interval_nanos)) return ;
520+ trace.cpu_time_nanos = cpu_time_nanos;
497521
498522 // Get thread info
499523 stack_trace_get_thread_info (&trace, thread);
@@ -519,6 +543,53 @@ void mach_sampling_profiler::sample_thread(thread_t thread, uint64_t interval_na
519543 }
520544}
521545
546+ uint64_t mach_sampling_profiler::thread_cpu_time_delta_nanos (thread_t thread) {
547+ if (!config.record_cpu_time ) {
548+ return 0 ;
549+ }
550+
551+ uint64_t current_cpu_time_nanos = 0 ;
552+ if (!thread_cpu_time_nanos (thread, ¤t_cpu_time_nanos)) {
553+ return 0 ;
554+ }
555+
556+ auto result = previous_thread_cpu_time_nanos.emplace (thread, current_cpu_time_nanos);
557+ if (result.second ) {
558+ return 0 ;
559+ }
560+
561+ const uint64_t previous_cpu_time_nanos = result.first ->second ;
562+ result.first ->second = current_cpu_time_nanos;
563+
564+ if (current_cpu_time_nanos < previous_cpu_time_nanos) {
565+ return 0 ;
566+ }
567+
568+ return current_cpu_time_nanos - previous_cpu_time_nanos;
569+ }
570+
571+ void mach_sampling_profiler::prune_thread_cpu_time_state (const thread_t * threads, mach_msg_type_number_t count) {
572+ if (!config.record_cpu_time || previous_thread_cpu_time_nanos.empty ()) {
573+ return ;
574+ }
575+
576+ for (auto it = previous_thread_cpu_time_nanos.begin (); it != previous_thread_cpu_time_nanos.end ();) {
577+ bool is_live_thread = false ;
578+ for (mach_msg_type_number_t i = 0 ; i < count; i++) {
579+ if (threads[i] == it->first ) {
580+ is_live_thread = true ;
581+ break ;
582+ }
583+ }
584+
585+ if (is_live_thread) {
586+ ++it;
587+ } else {
588+ it = previous_thread_cpu_time_nanos.erase (it);
589+ }
590+ }
591+ }
592+
522593/* *
523594 * Main sampling loop that collects stack traces from threads.
524595 */
@@ -535,7 +606,8 @@ void mach_sampling_profiler::main() {
535606 }
536607
537608 if (config.profile_current_thread_only ) {
538- sample_thread (pthread_mach_thread_np (target_thread), interval_nanos);
609+ const thread_t thread = pthread_mach_thread_np (target_thread);
610+ sample_thread (thread, interval_nanos, thread_cpu_time_delta_nanos (thread));
539611 if (sample_buffer.size () >= config.max_buffer_size ) {
540612 worker->enqueue_active_buffer (sample_buffer);
541613 }
@@ -557,13 +629,15 @@ void mach_sampling_profiler::main() {
557629 // Skip profiler-owned threads to avoid self-noise in customer profiles.
558630 if (is_profiler_internal_thread (threads[i])) continue ;
559631
560- sample_thread (threads[i], interval_nanos);
632+ sample_thread (threads[i], interval_nanos, thread_cpu_time_delta_nanos (threads[i]) );
561633
562634 if (sample_buffer.size () >= config.max_buffer_size ) {
563635 worker->enqueue_active_buffer (sample_buffer);
564636 }
565637 }
566638
639+ prune_thread_cpu_time_state (threads, count);
640+
567641 // Clean up thread references
568642 for (mach_msg_type_number_t i = 0 ; i < count; i++) {
569643 mach_port_deallocate (mach_task_self (), threads[i]);
0 commit comments