// Copyright 2020 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #import "FirebasePerformance/Sources/Gauges/CPU/FPRCPUGaugeCollector.h" #import "FirebasePerformance/Sources/Gauges/CPU/FPRCPUGaugeCollector+Private.h" #import "FirebasePerformance/Sources/AppActivity/FPRSessionManager.h" #import "FirebasePerformance/Sources/Configurations/FPRConfigurations.h" #import "FirebasePerformance/Sources/FPRConsoleLogger.h" #import #import @interface FPRCPUGaugeCollector () /** @brief Timer property used for the frequency of CPU data collection. */ @property(nonatomic) dispatch_source_t timerSource; /** @brief Gauge collector queue on which the gauge data collected. */ @property(nonatomic) dispatch_queue_t gaugeCollectorQueue; /** @brief Boolean to see if the timer is active or paused. */ @property(nonatomic) BOOL timerPaused; @end /** * Fetches the CPU metric and returns an instance of FPRCPUGaugeData. * * This implementation is inspired by the following references: * http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/thread_basic_info.html * https://stackoverflow.com/a/8382889 * * @return Instance of FPRCPUGaugeData. */ FPRCPUGaugeData *fprCollectCPUMetric() { kern_return_t kernelReturnValue; mach_msg_type_number_t task_info_count; task_info_data_t taskInfo; thread_array_t threadList; mach_msg_type_number_t threadCount; task_basic_info_t taskBasicInfo; thread_basic_info_t threadBasicInfo; NSDate *collectionTime = [NSDate date]; // Get the task info to find out the CPU time used by terminated threads. task_info_count = TASK_BASIC_INFO_COUNT; kernelReturnValue = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)taskInfo, &task_info_count); if (kernelReturnValue != KERN_SUCCESS) { return nil; } taskBasicInfo = (task_basic_info_t)taskInfo; // Get the current set of threads and find their CPU time. kernelReturnValue = task_threads(mach_task_self(), &threadList, &threadCount); if (kernelReturnValue != KERN_SUCCESS) { return nil; } uint64_t totalUserTimeUsec = taskBasicInfo->user_time.seconds * USEC_PER_SEC + taskBasicInfo->user_time.microseconds; uint64_t totalSystemTimeUsec = taskBasicInfo->system_time.seconds * USEC_PER_SEC + taskBasicInfo->system_time.microseconds; thread_info_data_t threadInfo; mach_msg_type_number_t threadInfoCount; for (int i = 0; i < (int)threadCount; i++) { threadInfoCount = THREAD_INFO_MAX; kernelReturnValue = thread_info(threadList[i], THREAD_BASIC_INFO, (thread_info_t)threadInfo, &threadInfoCount); if (kernelReturnValue != KERN_SUCCESS) { return nil; } threadBasicInfo = (thread_basic_info_t)threadInfo; if (!(threadBasicInfo->flags & TH_FLAGS_IDLE)) { totalUserTimeUsec += threadBasicInfo->user_time.seconds * USEC_PER_SEC + threadBasicInfo->user_time.microseconds; totalSystemTimeUsec += threadBasicInfo->system_time.seconds * USEC_PER_SEC + threadBasicInfo->system_time.microseconds; } } kernelReturnValue = vm_deallocate(mach_task_self(), (vm_offset_t)threadList, threadCount * sizeof(thread_t)); assert(kernelReturnValue == KERN_SUCCESS); FPRCPUGaugeData *gaugeData = [[FPRCPUGaugeData alloc] initWithCollectionTime:collectionTime systemTime:totalSystemTimeUsec userTime:totalUserTimeUsec]; return gaugeData; } @implementation FPRCPUGaugeCollector - (instancetype)initWithDelegate:(id)delegate { self = [super init]; if (self) { _delegate = delegate; _gaugeCollectorQueue = dispatch_queue_create("com.google.firebase.FPRCPUGaugeCollector", DISPATCH_QUEUE_SERIAL); _configurations = [FPRConfigurations sharedInstance]; _timerPaused = YES; [self updateSamplingFrequencyForApplicationState:[FPRAppActivityTracker sharedInstance] .applicationState]; } return self; } - (void)stopCollecting { if (self.timerPaused == NO) { dispatch_source_cancel(self.timerSource); self.timerPaused = YES; } } - (void)resumeCollecting { [self updateSamplingFrequencyForApplicationState:[FPRAppActivityTracker sharedInstance] .applicationState]; } - (void)updateSamplingFrequencyForApplicationState:(FPRApplicationState)applicationState { uint32_t frequencyInMs = (applicationState == FPRApplicationStateBackground) ? [self.configurations cpuSamplingFrequencyInBackgroundInMS] : [self.configurations cpuSamplingFrequencyInForegroundInMS]; [self captureCPUGaugeAtFrequency:frequencyInMs]; } /** * Captures the CPU gauge at a defined frequency. * * @param frequencyInMs Frequency at which the CPU gauges are collected. */ - (void)captureCPUGaugeAtFrequency:(uint32_t)frequencyInMs { [self stopCollecting]; if (frequencyInMs > 0) { self.timerSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.gaugeCollectorQueue); dispatch_source_set_timer(self.timerSource, dispatch_time(DISPATCH_TIME_NOW, frequencyInMs * NSEC_PER_MSEC), frequencyInMs * NSEC_PER_MSEC, (1ull * NSEC_PER_SEC) / 10); FPRCPUGaugeCollector __weak *weakSelf = self; dispatch_source_set_event_handler(weakSelf.timerSource, ^{ FPRCPUGaugeCollector *strongSelf = weakSelf; if (strongSelf) { [strongSelf collectMetric]; } }); dispatch_resume(self.timerSource); self.timerPaused = NO; } else { FPRLogDebug(kFPRCPUCollection, @"CPU metric collection is disabled."); } } - (void)collectMetric { FPRCPUGaugeData *gaugeMetric = fprCollectCPUMetric(); [self.delegate cpuGaugeCollector:self gaugeData:gaugeMetric]; } @end