| // Copyright 2020 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "ios/chrome/app/application_delegate/metric_kit_subscriber.h" |
| |
| #import <Foundation/Foundation.h> |
| #import <MetricKit/MetricKit.h> |
| |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/ios/ios_util.h" |
| #include "base/run_loop.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "base/test/ios/wait_util.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/task_environment.h" |
| #include "ios/chrome/app/application_delegate/mock_metrickit_metric_payload.h" |
| #include "testing/platform_test.h" |
| #import "third_party/ocmock/OCMock/OCMock.h" |
| #include "third_party/ocmock/gtest_support.h" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| namespace { |
| base::FilePath MetricKitReportDirectory() { |
| NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, |
| NSUserDomainMask, YES); |
| NSString* documents_directory = [paths objectAtIndex:0]; |
| NSString* metric_kit_report_directory = [documents_directory |
| stringByAppendingPathComponent:kChromeMetricKitPayloadsDirectory]; |
| |
| return base::FilePath(base::SysNSStringToUTF8(metric_kit_report_directory)); |
| } |
| |
| NSString* const kEnableMetricKit = @"EnableMetricKit"; |
| } |
| |
| class MetricKitSubscriberTest : public PlatformTest { |
| public: |
| MetricKitSubscriberTest() { |
| base::DeletePathRecursively(MetricKitReportDirectory()); |
| NSUserDefaults* standard_defaults = [NSUserDefaults standardUserDefaults]; |
| [standard_defaults setBool:YES forKey:kEnableMetricKit]; |
| } |
| |
| ~MetricKitSubscriberTest() override { |
| base::DeletePathRecursively(MetricKitReportDirectory()); |
| NSUserDefaults* standard_defaults = [NSUserDefaults standardUserDefaults]; |
| [standard_defaults removeObjectForKey:kEnableMetricKit]; |
| } |
| |
| private: |
| base::test::TaskEnvironment task_environment_; |
| }; |
| |
| TEST_F(MetricKitSubscriberTest, Metrics) { |
| base::HistogramTester tester; |
| if (@available(iOS 13, *)) { |
| MXMetricPayload* mock_report = MockMetricPayload(@{ |
| @"applicationTimeMetrics" : |
| @{@"cumulativeForegroundTime" : @1, @"cumulativeBackgroundTime" : @2}, |
| @"memoryMetrics" : |
| @{@"peakMemoryUsage" : @3, @"averageSuspendedMemory" : @4}, |
| @"applicationLaunchMetrics" : @{ |
| @"histogrammedResumeTime" : @{@25 : @5, @35 : @7}, |
| @"histogrammedTimeToFirstDrawKey" : @{@5 : @2, @15 : @4} |
| }, |
| @"applicationExitMetrics" : @{ |
| @"backgroundExitData" : @{ |
| @"cumulativeAppWatchdogExitCount" : @1, |
| @"cumulativeMemoryResourceLimitExitCount" : @2, |
| // These two entries are present in the simulated payload but not in |
| // the SDK. |
| // @"cumulativeBackgroundURLSessionCompletionTimeoutExitCount" : @3, |
| // @"cumulativeBackgroundFetchCompletionTimeoutExitCount" : @4, |
| @"cumulativeAbnormalExitCount" : @5, |
| @"cumulativeSuspendedWithLockedFileExitCount" : @6, |
| @"cumulativeIllegalInstructionExitCount" : @7, |
| @"cumulativeMemoryPressureExitCount" : @8, |
| @"cumulativeBadAccessExitCount" : @9, |
| @"cumulativeCPUResourceLimitExitCount" : @10, |
| @"cumulativeBackgroundTaskAssertionTimeoutExitCount" : @11, |
| @"cumulativeNormalAppExitCount" : @12 |
| }, |
| @"foregroundExitData" : @{ |
| @"cumulativeBadAccessExitCount" : @13, |
| @"cumulativeAbnormalExitCount" : @14, |
| @"cumulativeMemoryResourceLimitExitCount" : @15, |
| @"cumulativeNormalAppExitCount" : @16, |
| // This entry is present in the simulated payload but not in the SDK. |
| // @"cumulativeCPUResourceLimitExitCount" : @17, |
| @"cumulativeIllegalInstructionExitCount" : @18, |
| @"cumulativeAppWatchdogExitCount" : @19 |
| } |
| }, |
| }); |
| NSArray* array = @[ mock_report ]; |
| [[MetricKitSubscriber sharedInstance] didReceiveMetricPayloads:array]; |
| tester.ExpectUniqueTimeSample("IOS.MetricKit.ForegroundTimePerDay", |
| base::TimeDelta::FromSeconds(1), 1); |
| tester.ExpectUniqueTimeSample("IOS.MetricKit.BackgroundTimePerDay", |
| base::TimeDelta::FromSeconds(2), 1); |
| tester.ExpectUniqueSample("IOS.MetricKit.PeakMemoryUsage", 3, 1); |
| tester.ExpectUniqueSample("IOS.MetricKit.AverageSuspendedMemory", 4, 1); |
| |
| tester.ExpectTotalCount("IOS.MetricKit.ApplicationResumeTime", 12); |
| tester.ExpectBucketCount("IOS.MetricKit.ApplicationResumeTime", 25, 5); |
| tester.ExpectBucketCount("IOS.MetricKit.ApplicationResumeTime", 35, 7); |
| |
| tester.ExpectTotalCount("IOS.MetricKit.TimeToFirstDraw", 6); |
| tester.ExpectBucketCount("IOS.MetricKit.TimeToFirstDraw", 5, 2); |
| tester.ExpectBucketCount("IOS.MetricKit.TimeToFirstDraw", 15, 4); |
| |
| #if defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0 |
| if (base::ios::IsRunningOnIOS14OrLater()) { |
| tester.ExpectTotalCount("IOS.MetricKit.BackgroundExitData", 71); |
| tester.ExpectBucketCount("IOS.MetricKit.BackgroundExitData", 2, 1); |
| tester.ExpectBucketCount("IOS.MetricKit.BackgroundExitData", 4, 2); |
| tester.ExpectBucketCount("IOS.MetricKit.BackgroundExitData", 1, 5); |
| tester.ExpectBucketCount("IOS.MetricKit.BackgroundExitData", 6, 6); |
| tester.ExpectBucketCount("IOS.MetricKit.BackgroundExitData", 8, 7); |
| tester.ExpectBucketCount("IOS.MetricKit.BackgroundExitData", 5, 8); |
| tester.ExpectBucketCount("IOS.MetricKit.BackgroundExitData", 7, 9); |
| tester.ExpectBucketCount("IOS.MetricKit.BackgroundExitData", 3, 10); |
| tester.ExpectBucketCount("IOS.MetricKit.BackgroundExitData", 9, 11); |
| tester.ExpectBucketCount("IOS.MetricKit.BackgroundExitData", 0, 12); |
| |
| tester.ExpectTotalCount("IOS.MetricKit.ForegroundExitData", 95); |
| tester.ExpectBucketCount("IOS.MetricKit.ForegroundExitData", 7, 13); |
| tester.ExpectBucketCount("IOS.MetricKit.ForegroundExitData", 1, 14); |
| tester.ExpectBucketCount("IOS.MetricKit.ForegroundExitData", 4, 15); |
| tester.ExpectBucketCount("IOS.MetricKit.ForegroundExitData", 0, 16); |
| tester.ExpectBucketCount("IOS.MetricKit.ForegroundExitData", 8, 18); |
| tester.ExpectBucketCount("IOS.MetricKit.ForegroundExitData", 2, 19); |
| } |
| #endif |
| } |
| } |
| |
| // Tests that Metrickit reports are correctly saved in the document directory. |
| TEST_F(MetricKitSubscriberTest, SaveMetricsReport) { |
| if (@available(iOS 13, *)) { |
| id mock_report = OCMClassMock([MXMetricPayload class]); |
| NSDate* date = [NSDate date]; |
| std::string file_data("report content"); |
| NSData* data = [NSData dataWithBytes:file_data.c_str() |
| length:file_data.size()]; |
| NSDateFormatter* formatter = [[NSDateFormatter alloc] init]; |
| [formatter setDateFormat:@"yyyyMMdd_HHmmss"]; |
| [formatter setTimeZone:[NSTimeZone timeZoneWithName:@"UTC"]]; |
| NSString* file_name = [NSString |
| stringWithFormat:@"Metrics-%@.json", [formatter stringFromDate:date]]; |
| |
| base::FilePath file_path = |
| MetricKitReportDirectory().Append(base::SysNSStringToUTF8(file_name)); |
| OCMStub([mock_report timeStampEnd]).andReturn(date); |
| OCMStub([mock_report JSONRepresentation]).andReturn(data); |
| NSArray* array = @[ mock_report ]; |
| [[MetricKitSubscriber sharedInstance] didReceiveMetricPayloads:array]; |
| |
| EXPECT_TRUE(base::test::ios::WaitUntilConditionOrTimeout( |
| base::test::ios::kWaitForFileOperationTimeout, ^bool() { |
| base::RunLoop().RunUntilIdle(); |
| return base::PathExists(file_path); |
| })); |
| std::string content; |
| base::ReadFileToString(file_path, &content); |
| EXPECT_EQ(content, file_data); |
| } |
| } |
| |
| #if defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0 |
| TEST_F(MetricKitSubscriberTest, SaveDiagnosticReport) { |
| if (@available(iOS 14, *)) { |
| id mock_report = OCMClassMock([MXDiagnosticPayload class]); |
| NSDate* date = [NSDate date]; |
| std::string file_data("report content"); |
| NSData* data = [NSData dataWithBytes:file_data.c_str() |
| length:file_data.size()]; |
| NSDateFormatter* formatter = [[NSDateFormatter alloc] init]; |
| [formatter setDateFormat:@"yyyyMMdd_HHmmss"]; |
| [formatter setTimeZone:[NSTimeZone timeZoneWithName:@"UTC"]]; |
| NSString* file_name = |
| [NSString stringWithFormat:@"Diagnostic-%@.json", |
| [formatter stringFromDate:date]]; |
| |
| base::FilePath file_path = |
| MetricKitReportDirectory().Append(base::SysNSStringToUTF8(file_name)); |
| OCMStub([mock_report timeStampEnd]).andReturn(date); |
| OCMStub([mock_report JSONRepresentation]).andReturn(data); |
| NSArray* array = @[ mock_report ]; |
| [[MetricKitSubscriber sharedInstance] didReceiveDiagnosticPayloads:array]; |
| |
| EXPECT_TRUE(base::test::ios::WaitUntilConditionOrTimeout( |
| base::test::ios::kWaitForFileOperationTimeout, ^bool() { |
| base::RunLoop().RunUntilIdle(); |
| return base::PathExists(file_path); |
| })); |
| std::string content; |
| base::ReadFileToString(file_path, &content); |
| EXPECT_EQ(content, file_data); |
| } |
| } |
| #endif |