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
@@ -159,11 +160,32 @@ bool stack_trace_init(stack_trace_t* trace, uint32_t max_depth, uint64_t interva
159160 trace->thread_name = nullptr ;
160161 trace->timestamp = 0 ;
161162 trace->sampling_interval_nanos = interval_nanos;
163+ trace->cpu_time_nanos = 0 ;
162164 trace->frame_count = 0 ;
163165 trace->frames = (stack_frame_t *)malloc (max_depth * sizeof (stack_frame_t ));
164166 return trace->frames != nullptr ;
165167}
166168
169+ static bool thread_cpu_time_nanos (thread_t thread, uint64_t * cpu_time_nanos) {
170+ if (!cpu_time_nanos) return false ;
171+
172+ thread_basic_info_data_t info{};
173+ mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT ;
174+ if (thread_info (thread, THREAD_BASIC_INFO , reinterpret_cast <thread_info_t >(&info), &count) != KERN_SUCCESS ) {
175+ return false ;
176+ }
177+
178+ const uint64_t user_time_nanos =
179+ (static_cast <uint64_t >(info.user_time .seconds ) * 1000000000ULL )
180+ + (static_cast <uint64_t >(info.user_time .microseconds ) * 1000ULL );
181+ const uint64_t system_time_nanos =
182+ (static_cast <uint64_t >(info.system_time .seconds ) * 1000000000ULL )
183+ + (static_cast <uint64_t >(info.system_time .microseconds ) * 1000ULL );
184+
185+ *cpu_time_nanos = user_time_nanos + system_time_nanos;
186+ return true ;
187+ }
188+
167189/* *
168190 * Destroys a stack trace, freeing the thread name and frames array.
169191 *
@@ -395,6 +417,7 @@ bool mach_sampling_profiler::start_sampling() {
395417
396418 // Clear any leftover data from previous runs
397419 sample_buffer.clear ();
420+ previous_thread_cpu_time_nanos.clear ();
398421 if (sample_buffer.capacity () < config.max_buffer_size ) {
399422 sample_buffer.reserve (config.max_buffer_size );
400423 }
@@ -488,9 +511,10 @@ bool mach_sampling_profiler::is_profiler_internal_thread(thread_t thread) const
488511 * @param thread The thread to sample
489512 * @param interval_nanos The actual sampling interval in nanoseconds for this sample
490513 */
491- void mach_sampling_profiler::sample_thread (thread_t thread, uint64_t interval_nanos) {
514+ void mach_sampling_profiler::sample_thread (thread_t thread, uint64_t interval_nanos, uint64_t cpu_time_nanos ) {
492515 stack_trace_t trace;
493516 if (!stack_trace_init (&trace, config.max_stack_depth , interval_nanos)) return ;
517+ trace.cpu_time_nanos = cpu_time_nanos;
494518
495519 // Get thread info
496520 stack_trace_get_thread_info (&trace, thread);
@@ -516,6 +540,53 @@ void mach_sampling_profiler::sample_thread(thread_t thread, uint64_t interval_na
516540 }
517541}
518542
543+ uint64_t mach_sampling_profiler::thread_cpu_time_delta_nanos (thread_t thread) {
544+ if (!config.record_cpu_time ) {
545+ return 0 ;
546+ }
547+
548+ uint64_t current_cpu_time_nanos = 0 ;
549+ if (!thread_cpu_time_nanos (thread, ¤t_cpu_time_nanos)) {
550+ return 0 ;
551+ }
552+
553+ auto result = previous_thread_cpu_time_nanos.emplace (thread, current_cpu_time_nanos);
554+ if (result.second ) {
555+ return 0 ;
556+ }
557+
558+ const uint64_t previous_cpu_time_nanos = result.first ->second ;
559+ result.first ->second = current_cpu_time_nanos;
560+
561+ if (current_cpu_time_nanos < previous_cpu_time_nanos) {
562+ return 0 ;
563+ }
564+
565+ return current_cpu_time_nanos - previous_cpu_time_nanos;
566+ }
567+
568+ void mach_sampling_profiler::prune_thread_cpu_time_state (const thread_t * threads, mach_msg_type_number_t count) {
569+ if (!config.record_cpu_time || previous_thread_cpu_time_nanos.empty ()) {
570+ return ;
571+ }
572+
573+ for (auto it = previous_thread_cpu_time_nanos.begin (); it != previous_thread_cpu_time_nanos.end ();) {
574+ bool is_live_thread = false ;
575+ for (mach_msg_type_number_t i = 0 ; i < count; i++) {
576+ if (threads[i] == it->first ) {
577+ is_live_thread = true ;
578+ break ;
579+ }
580+ }
581+
582+ if (is_live_thread) {
583+ ++it;
584+ } else {
585+ it = previous_thread_cpu_time_nanos.erase (it);
586+ }
587+ }
588+ }
589+
519590/* *
520591 * Main sampling loop that collects stack traces from threads.
521592 */
@@ -532,7 +603,8 @@ void mach_sampling_profiler::main() {
532603 }
533604
534605 if (config.profile_current_thread_only ) {
535- sample_thread (pthread_mach_thread_np (target_thread), interval_nanos);
606+ const thread_t thread = pthread_mach_thread_np (target_thread);
607+ sample_thread (thread, interval_nanos, thread_cpu_time_delta_nanos (thread));
536608 if (sample_buffer.size () >= config.max_buffer_size ) {
537609 worker->enqueue_active_buffer (sample_buffer);
538610 }
@@ -554,13 +626,15 @@ void mach_sampling_profiler::main() {
554626 // Skip profiler-owned threads to avoid self-noise in customer profiles.
555627 if (is_profiler_internal_thread (threads[i])) continue ;
556628
557- sample_thread (threads[i], interval_nanos);
629+ sample_thread (threads[i], interval_nanos, thread_cpu_time_delta_nanos (threads[i]) );
558630
559631 if (sample_buffer.size () >= config.max_buffer_size ) {
560632 worker->enqueue_active_buffer (sample_buffer);
561633 }
562634 }
563635
636+ prune_thread_cpu_time_state (threads, count);
637+
564638 // Clean up thread references
565639 for (mach_msg_type_number_t i = 0 ; i < count; i++) {
566640 mach_port_deallocate (mach_task_self (), threads[i]);
0 commit comments