blob: 3af38df68055328d518307c08585fb2296c75857 [file] [log] [blame]
// 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