From 90ed52d7431f511d89cac0939f3f368c77e08a19 Mon Sep 17 00:00:00 2001 From: Maurice Parrish Date: Tue, 14 May 2019 13:13:28 -0700 Subject: [PATCH] [firebase_performance] Refactor and API update of firebase_performance (#1557) --- .../FirebasePerformancePlugin.java | 157 ++------ .../FlutterFirebasePerformance.java | 107 +++++ .../FlutterHttpMetric.java | 116 ++++++ .../firebaseperformance/FlutterTrace.java | 111 +++++ .../firebase_performance/example/pubspec.yaml | 2 - .../test_driver/firebase_performance.dart | 43 +- .../ios/Classes/FLTFirebasePerformance.m | 94 +++++ .../ios/Classes/FLTHttpMetric.m | 111 +++++ .../ios/Classes/FLTTrace.m | 98 +++++ .../FirebasePerformancePlugin+Internal.h | 28 ++ .../ios/Classes/FirebasePerformancePlugin.h | 1 + .../ios/Classes/FirebasePerformancePlugin.m | 149 ++----- .../lib/firebase_performance.dart | 1 - .../lib/src/firebase_performance.dart | 65 ++- .../lib/src/http_metric.dart | 171 +++++--- .../lib/src/performance_attributes.dart | 67 +++- .../firebase_performance/lib/src/trace.dart | 157 ++++---- .../test/firebase_performance_test.dart | 379 ++++++++---------- 18 files changed, 1177 insertions(+), 680 deletions(-) create mode 100644 packages/firebase_performance/android/src/main/java/io/flutter/plugins/firebaseperformance/FlutterFirebasePerformance.java create mode 100644 packages/firebase_performance/android/src/main/java/io/flutter/plugins/firebaseperformance/FlutterHttpMetric.java create mode 100644 packages/firebase_performance/android/src/main/java/io/flutter/plugins/firebaseperformance/FlutterTrace.java create mode 100644 packages/firebase_performance/ios/Classes/FLTFirebasePerformance.m create mode 100644 packages/firebase_performance/ios/Classes/FLTHttpMetric.m create mode 100644 packages/firebase_performance/ios/Classes/FLTTrace.m create mode 100644 packages/firebase_performance/ios/Classes/FirebasePerformancePlugin+Internal.h diff --git a/packages/firebase_performance/android/src/main/java/io/flutter/plugins/firebaseperformance/FirebasePerformancePlugin.java b/packages/firebase_performance/android/src/main/java/io/flutter/plugins/firebaseperformance/FirebasePerformancePlugin.java index 50dc0ada04aa..99a1d82d06e1 100644 --- a/packages/firebase_performance/android/src/main/java/io/flutter/plugins/firebaseperformance/FirebasePerformancePlugin.java +++ b/packages/firebase_performance/android/src/main/java/io/flutter/plugins/firebaseperformance/FirebasePerformancePlugin.java @@ -5,160 +5,53 @@ package io.flutter.plugins.firebaseperformance; import android.util.SparseArray; -import com.google.firebase.perf.FirebasePerformance; -import com.google.firebase.perf.metrics.HttpMetric; -import com.google.firebase.perf.metrics.Trace; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; -import io.flutter.plugin.common.MethodChannel.MethodCallHandler; -import io.flutter.plugin.common.MethodChannel.Result; import io.flutter.plugin.common.PluginRegistry.Registrar; -import java.util.Map; /** FirebasePerformancePlugin */ -public class FirebasePerformancePlugin implements MethodCallHandler { - private FirebasePerformance firebasePerformance; +public class FirebasePerformancePlugin implements MethodChannel.MethodCallHandler { + private static final String CHANNEL_NAME = "plugins.flutter.io/firebase_performance"; - private final SparseArray traces = new SparseArray<>(); - private final SparseArray httpMetrics = new SparseArray<>(); + private static final SparseArray handlers = new SparseArray<>(); public static void registerWith(Registrar registrar) { - final MethodChannel channel = - new MethodChannel(registrar.messenger(), "plugins.flutter.io/firebase_performance"); + final MethodChannel channel = new MethodChannel(registrar.messenger(), CHANNEL_NAME); channel.setMethodCallHandler(new FirebasePerformancePlugin()); } - private FirebasePerformancePlugin() { - firebasePerformance = FirebasePerformance.getInstance(); - } - @Override - public void onMethodCall(MethodCall call, Result result) { - switch (call.method) { - case "FirebasePerformance#isPerformanceCollectionEnabled": - result.success(firebasePerformance.isPerformanceCollectionEnabled()); - break; - case "FirebasePerformance#setPerformanceCollectionEnabled": - final boolean enabled = (boolean) call.arguments; - firebasePerformance.setPerformanceCollectionEnabled(enabled); - result.success(null); - break; - case "Trace#start": - handleTraceStart(call, result); - break; - case "Trace#stop": - handleTraceStop(call, result); - break; - case "HttpMetric#start": - handleHttpMetricStart(call, result); - break; - case "HttpMetric#stop": - handleHttpMetricStop(call, result); - break; - default: + public void onMethodCall(MethodCall call, MethodChannel.Result result) { + if (call.method.equals("FirebasePerformance#instance")) { + FlutterFirebasePerformance.getInstance(call, result); + } else { + final MethodChannel.MethodCallHandler handler = getHandler(call); + + if (handler != null) { + handler.onMethodCall(call, result); + } else { result.notImplemented(); + } } } - private void handleTraceStart(MethodCall call, Result result) { - Integer handle = call.argument("handle"); - String name = call.argument("name"); - - Trace trace = firebasePerformance.newTrace(name); - - traces.put(handle, trace); - - trace.start(); - result.success(null); - } - - private void handleTraceStop(MethodCall call, Result result) { - Integer handle = call.argument("handle"); - Trace trace = traces.get(handle); - - Map metrics = call.argument("metrics"); - for (Map.Entry entry : metrics.entrySet()) { - trace.incrementMetric(entry.getKey(), entry.getValue()); - } - - Map attributes = call.argument("attributes"); - for (Map.Entry entry : attributes.entrySet()) { - trace.putAttribute(entry.getKey(), entry.getValue()); + static void addHandler(final int handle, final MethodChannel.MethodCallHandler handler) { + if (handlers.get(handle) != null) { + final String message = String.format("Object for handle already exists: %s", handle); + throw new IllegalArgumentException(message); } - trace.stop(); - traces.remove(handle); - result.success(null); + handlers.put(handle, handler); } - private void handleHttpMetricStart(MethodCall call, Result result) { - Integer handle = call.argument("handle"); - String url = call.argument("url"); - - int httpMethod = call.argument("httpMethod"); - String httpMethodStr; - switch (httpMethod) { - case 0: - httpMethodStr = FirebasePerformance.HttpMethod.CONNECT; - break; - case 1: - httpMethodStr = FirebasePerformance.HttpMethod.DELETE; - break; - case 2: - httpMethodStr = FirebasePerformance.HttpMethod.GET; - break; - case 3: - httpMethodStr = FirebasePerformance.HttpMethod.HEAD; - break; - case 4: - httpMethodStr = FirebasePerformance.HttpMethod.OPTIONS; - break; - case 5: - httpMethodStr = FirebasePerformance.HttpMethod.PATCH; - break; - case 6: - httpMethodStr = FirebasePerformance.HttpMethod.POST; - break; - case 7: - httpMethodStr = FirebasePerformance.HttpMethod.PUT; - break; - case 8: - httpMethodStr = FirebasePerformance.HttpMethod.TRACE; - break; - default: - httpMethodStr = null; - break; - } - - HttpMetric metric = firebasePerformance.newHttpMetric(url, httpMethodStr); - - httpMetrics.put(handle, metric); - - metric.start(); - result.success(null); + static void removeHandler(final int handle) { + handlers.remove(handle); } - private void handleHttpMetricStop(MethodCall call, Result result) { - Integer handle = call.argument("handle"); - HttpMetric metric = httpMetrics.get(handle); - - Integer httpResponseCode = call.argument("httpResponseCode"); - Number requestPayloadSize = call.argument("requestPayloadSize"); - String responseContentType = call.argument("responseContentType"); - Number responsePayloadSize = call.argument("responsePayloadSize"); - - if (requestPayloadSize != null) metric.setRequestPayloadSize(requestPayloadSize.longValue()); - if (httpResponseCode != null) metric.setHttpResponseCode(httpResponseCode); - if (responseContentType != null) metric.setResponseContentType(responseContentType); - if (responsePayloadSize != null) metric.setResponsePayloadSize(responsePayloadSize.longValue()); - - Map attributes = call.argument("attributes"); - for (Map.Entry entry : attributes.entrySet()) { - metric.putAttribute(entry.getKey(), entry.getValue()); - } + private static MethodChannel.MethodCallHandler getHandler(final MethodCall call) { + final Integer handle = call.argument("handle"); - metric.stop(); - httpMetrics.remove(handle); - result.success(null); + if (handle == null) return null; + return handlers.get(handle); } } diff --git a/packages/firebase_performance/android/src/main/java/io/flutter/plugins/firebaseperformance/FlutterFirebasePerformance.java b/packages/firebase_performance/android/src/main/java/io/flutter/plugins/firebaseperformance/FlutterFirebasePerformance.java new file mode 100644 index 000000000000..ce04044a1ea8 --- /dev/null +++ b/packages/firebase_performance/android/src/main/java/io/flutter/plugins/firebaseperformance/FlutterFirebasePerformance.java @@ -0,0 +1,107 @@ +// Copyright 2019 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. + +package io.flutter.plugins.firebaseperformance; + +import com.google.firebase.perf.FirebasePerformance; +import com.google.firebase.perf.metrics.HttpMetric; +import com.google.firebase.perf.metrics.Trace; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; + +public class FlutterFirebasePerformance implements MethodChannel.MethodCallHandler { + private static String parseHttpMethod(String httpMethod) { + switch (httpMethod) { + case "HttpMethod.Connect": + return FirebasePerformance.HttpMethod.CONNECT; + case "HttpMethod.Delete": + return FirebasePerformance.HttpMethod.DELETE; + case "HttpMethod.Get": + return FirebasePerformance.HttpMethod.GET; + case "HttpMethod.Head": + return FirebasePerformance.HttpMethod.HEAD; + case "HttpMethod.Options": + return FirebasePerformance.HttpMethod.OPTIONS; + case "HttpMethod.Patch": + return FirebasePerformance.HttpMethod.PATCH; + case "HttpMethod.Post": + return FirebasePerformance.HttpMethod.POST; + case "HttpMethod.Put": + return FirebasePerformance.HttpMethod.PUT; + case "HttpMethod.Trace": + return FirebasePerformance.HttpMethod.TRACE; + default: + throw new IllegalArgumentException(String.format("No HttpMethod for: %s", httpMethod)); + } + } + + private final FirebasePerformance performance; + + @SuppressWarnings("ConstantConditions") + static void getInstance(MethodCall call, MethodChannel.Result result) { + final Integer handle = call.argument("handle"); + FirebasePerformancePlugin.addHandler(handle, new FlutterFirebasePerformance()); + result.success(null); + } + + private FlutterFirebasePerformance() { + this.performance = FirebasePerformance.getInstance(); + } + + @Override + public void onMethodCall(MethodCall call, MethodChannel.Result result) { + switch (call.method) { + case "FirebasePerformance#isPerformanceCollectionEnabled": + isPerformanceCollectionEnabled(result); + break; + case "FirebasePerformance#setPerformanceCollectionEnabled": + setPerformanceCollectionEnabled(call, result); + break; + case "FirebasePerformance#newTrace": + newTrace(call, result); + break; + case "FirebasePerformance#newHttpMetric": + newHttpMetric(call, result); + break; + default: + result.notImplemented(); + } + } + + private void isPerformanceCollectionEnabled(MethodChannel.Result result) { + result.success(performance.isPerformanceCollectionEnabled()); + } + + @SuppressWarnings("ConstantConditions") + private void setPerformanceCollectionEnabled(MethodCall call, MethodChannel.Result result) { + final Boolean enable = call.argument("enable"); + performance.setPerformanceCollectionEnabled(enable); + + result.success(null); + } + + @SuppressWarnings("ConstantConditions") + private void newTrace(MethodCall call, MethodChannel.Result result) { + final String name = call.argument("name"); + final Trace trace = performance.newTrace(name); + + final Integer handle = call.argument("traceHandle"); + FirebasePerformancePlugin.addHandler(handle, new FlutterTrace(trace)); + + result.success(null); + } + + @SuppressWarnings("ConstantConditions") + private void newHttpMetric(MethodCall call, MethodChannel.Result result) { + final String url = call.argument("url"); + final String httpMethod = call.argument("httpMethod"); + + final HttpMetric metric = performance.newHttpMetric(url, parseHttpMethod(httpMethod)); + + final Integer handle = call.argument("httpMetricHandle"); + FirebasePerformancePlugin.addHandler(handle, new FlutterHttpMetric(metric)); + + result.success(null); + } +} diff --git a/packages/firebase_performance/android/src/main/java/io/flutter/plugins/firebaseperformance/FlutterHttpMetric.java b/packages/firebase_performance/android/src/main/java/io/flutter/plugins/firebaseperformance/FlutterHttpMetric.java new file mode 100644 index 000000000000..2e2f2e6e4ed3 --- /dev/null +++ b/packages/firebase_performance/android/src/main/java/io/flutter/plugins/firebaseperformance/FlutterHttpMetric.java @@ -0,0 +1,116 @@ +// Copyright 2019 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. + +package io.flutter.plugins.firebaseperformance; + +import com.google.firebase.perf.metrics.HttpMetric; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; + +public class FlutterHttpMetric implements MethodChannel.MethodCallHandler { + private final HttpMetric httpMetric; + + FlutterHttpMetric(final HttpMetric metric) { + this.httpMetric = metric; + } + + @Override + public void onMethodCall(MethodCall call, MethodChannel.Result result) { + switch (call.method) { + case "HttpMetric#start": + start(result); + break; + case "HttpMetric#stop": + stop(call, result); + break; + case "HttpMetric#httpResponseCode": + setHttpResponseCode(call, result); + break; + case "HttpMetric#requestPayloadSize": + setRequestPayloadSize(call, result); + break; + case "HttpMetric#responseContentType": + setResponseContentType(call, result); + break; + case "HttpMetric#responsePayloadSize": + setResponsePayloadSize(call, result); + break; + case "PerformanceAttributes#putAttribute": + putAttribute(call, result); + break; + case "PerformanceAttributes#removeAttribute": + removeAttribute(call, result); + break; + case "PerformanceAttributes#getAttributes": + getAttributes(result); + break; + default: + result.notImplemented(); + } + } + + private void start(MethodChannel.Result result) { + httpMetric.start(); + result.success(null); + } + + @SuppressWarnings("ConstantConditions") + private void stop(MethodCall call, MethodChannel.Result result) { + httpMetric.stop(); + + final Integer handle = call.argument("handle"); + FirebasePerformancePlugin.removeHandler(handle); + + result.success(null); + } + + @SuppressWarnings("ConstantConditions") + private void setHttpResponseCode(MethodCall call, MethodChannel.Result result) { + final Integer httpResponseCode = call.argument("httpResponseCode"); + httpMetric.setHttpResponseCode(httpResponseCode); + result.success(null); + } + + @SuppressWarnings("ConstantConditions") + private void setRequestPayloadSize(MethodCall call, MethodChannel.Result result) { + final Number payloadSize = call.argument("requestPayloadSize"); + httpMetric.setRequestPayloadSize(payloadSize.longValue()); + result.success(null); + } + + private void setResponseContentType(MethodCall call, MethodChannel.Result result) { + final String contentType = call.argument("responseContentType"); + httpMetric.setResponseContentType(contentType); + result.success(null); + } + + @SuppressWarnings("ConstantConditions") + private void setResponsePayloadSize(MethodCall call, MethodChannel.Result result) { + final Number payloadSize = call.argument("responsePayloadSize"); + httpMetric.setResponsePayloadSize(payloadSize.longValue()); + result.success(null); + } + + @SuppressWarnings("ConstantConditions") + private void putAttribute(MethodCall call, MethodChannel.Result result) { + final String name = call.argument("name"); + final String value = call.argument("value"); + + httpMetric.putAttribute(name, value); + + result.success(null); + } + + @SuppressWarnings("ConstantConditions") + private void removeAttribute(MethodCall call, MethodChannel.Result result) { + final String name = call.argument("name"); + httpMetric.removeAttribute(name); + + result.success(null); + } + + private void getAttributes(MethodChannel.Result result) { + result.success(httpMetric.getAttributes()); + } +} diff --git a/packages/firebase_performance/android/src/main/java/io/flutter/plugins/firebaseperformance/FlutterTrace.java b/packages/firebase_performance/android/src/main/java/io/flutter/plugins/firebaseperformance/FlutterTrace.java new file mode 100644 index 000000000000..0bcb54f34834 --- /dev/null +++ b/packages/firebase_performance/android/src/main/java/io/flutter/plugins/firebaseperformance/FlutterTrace.java @@ -0,0 +1,111 @@ +// Copyright 2019 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. + +package io.flutter.plugins.firebaseperformance; + +import com.google.firebase.perf.metrics.Trace; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; + +public class FlutterTrace implements MethodChannel.MethodCallHandler { + private final Trace trace; + + FlutterTrace(final Trace trace) { + this.trace = trace; + } + + @Override + public void onMethodCall(MethodCall call, MethodChannel.Result result) { + switch (call.method) { + case "Trace#start": + start(result); + break; + case "Trace#stop": + stop(call, result); + break; + case "Trace#setMetric": + setMetric(call, result); + break; + case "Trace#incrementMetric": + incrementMetric(call, result); + break; + case "Trace#getMetric": + getMetric(call, result); + break; + case "PerformanceAttributes#putAttribute": + putAttribute(call, result); + break; + case "PerformanceAttributes#removeAttribute": + removeAttribute(call, result); + break; + case "PerformanceAttributes#getAttributes": + getAttributes(result); + break; + default: + result.notImplemented(); + } + } + + private void start(MethodChannel.Result result) { + trace.start(); + result.success(null); + } + + @SuppressWarnings("ConstantConditions") + private void stop(MethodCall call, MethodChannel.Result result) { + trace.stop(); + + final Integer handle = call.argument("handle"); + FirebasePerformancePlugin.removeHandler(handle); + + result.success(null); + } + + @SuppressWarnings("ConstantConditions") + private void setMetric(MethodCall call, MethodChannel.Result result) { + final String name = call.argument("name"); + final Number value = call.argument("value"); + trace.putMetric(name, value.longValue()); + + result.success(null); + } + + @SuppressWarnings("ConstantConditions") + private void incrementMetric(MethodCall call, MethodChannel.Result result) { + final String name = call.argument("name"); + final Number value = call.argument("value"); + trace.incrementMetric(name, value.longValue()); + + result.success(null); + } + + @SuppressWarnings("ConstantConditions") + private void getMetric(MethodCall call, MethodChannel.Result result) { + final String name = call.argument("name"); + + result.success(trace.getLongMetric(name)); + } + + @SuppressWarnings("ConstantConditions") + private void putAttribute(MethodCall call, MethodChannel.Result result) { + final String name = call.argument("name"); + final String value = call.argument("value"); + + trace.putAttribute(name, value); + + result.success(null); + } + + @SuppressWarnings("ConstantConditions") + private void removeAttribute(MethodCall call, MethodChannel.Result result) { + final String name = call.argument("name"); + trace.removeAttribute(name); + + result.success(null); + } + + private void getAttributes(MethodChannel.Result result) { + result.success(trace.getAttributes()); + } +} diff --git a/packages/firebase_performance/example/pubspec.yaml b/packages/firebase_performance/example/pubspec.yaml index 2bcf0b94b414..4c6291316faa 100644 --- a/packages/firebase_performance/example/pubspec.yaml +++ b/packages/firebase_performance/example/pubspec.yaml @@ -17,7 +17,5 @@ dev_dependencies: sdk: flutter test: any - - flutter: uses-material-design: true diff --git a/packages/firebase_performance/example/test_driver/firebase_performance.dart b/packages/firebase_performance/example/test_driver/firebase_performance.dart index fcabfe11f239..ae6d62069690 100644 --- a/packages/firebase_performance/example/test_driver/firebase_performance.dart +++ b/packages/firebase_performance/example/test_driver/firebase_performance.dart @@ -25,17 +25,44 @@ void main() { expect(disabled, isFalse); }); - test('metric', () async { + test('trace', () async { final Trace trace = performance.newTrace('test'); - trace.putAttribute('testAttribute', 'foo'); - trace.attributes['testAttribute2'] = 'bar'; + await trace.start(); - trace.incrementMetric('testMetric', 1); + + trace.putAttribute('testAttribute', 'foo'); + final Map attributes = await trace.getAttributes(); + expect(attributes, {'testAttribute': 'foo'}); + + trace.incrementMetric('testMetric', 22); + final int metric = await trace.getMetric('testMetric'); + expect(metric, 22); + + trace.setMetric('testMetric2', 33); + final int metric2 = await trace.getMetric('testMetric2'); + expect(metric2, 33); + await trace.stop(); - expect(trace.getAttribute('testAttribute'), 'foo'); - expect(trace.attributes['testAttribute'], 'foo'); - expect(trace.getAttribute('testAttribute2'), null); - expect(trace.getAttribute('testMetric'), null); + }); + + test('httpmetric', () async { + final HttpMetric httpMetric = performance.newHttpMetric( + 'https://www.google.com', + HttpMethod.Connect, + ); + + await httpMetric.start(); + + httpMetric.putAttribute('testAttribute', 'foo'); + final Map attributes = await httpMetric.getAttributes(); + expect(attributes, {'testAttribute': 'foo'}); + + httpMetric.httpResponseCode = 45; + httpMetric.requestPayloadSize = 45; + httpMetric.responseContentType = 'testString'; + httpMetric.responsePayloadSize = 45; + + await httpMetric.stop(); }); }); } diff --git a/packages/firebase_performance/ios/Classes/FLTFirebasePerformance.m b/packages/firebase_performance/ios/Classes/FLTFirebasePerformance.m new file mode 100644 index 000000000000..81cf29c06559 --- /dev/null +++ b/packages/firebase_performance/ios/Classes/FLTFirebasePerformance.m @@ -0,0 +1,94 @@ +// Copyright 2019 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 "FirebasePerformancePlugin+Internal.h" + +@interface FLTFirebasePerformance () +@property FIRPerformance *performance; +@end + +@implementation FLTFirebasePerformance ++ (instancetype _Nonnull)sharedInstance { + static FLTFirebasePerformance *sharedInstance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [[FLTFirebasePerformance alloc] init]; + sharedInstance.performance = [FIRPerformance sharedInstance]; + }); + return sharedInstance; +} + +- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { + if ([@"FirebasePerformance#isPerformanceCollectionEnabled" isEqualToString:call.method]) { + [self isPerformanceCollectionEnabled:result]; + } else if ([@"FirebasePerformance#setPerformanceCollectionEnabled" isEqualToString:call.method]) { + [self setPerformanceCollectionEnabled:call result:result]; + } else if ([@"FirebasePerformance#newTrace" isEqualToString:call.method]) { + [self newTrace:call result:result]; + } else if ([@"FirebasePerformance#newHttpMetric" isEqualToString:call.method]) { + [self newHttpMetric:call result:result]; + } else { + result(FlutterMethodNotImplemented); + } +} + +- (void)isPerformanceCollectionEnabled:(FlutterResult)result { + result(@([_performance isDataCollectionEnabled])); +} + +- (void)setPerformanceCollectionEnabled:(FlutterMethodCall *)call result:(FlutterResult)result { + NSNumber *enable = call.arguments[@"enable"]; + [_performance setDataCollectionEnabled:[enable boolValue]]; + result(nil); +} + +- (void)newTrace:(FlutterMethodCall *)call result:(FlutterResult)result { + NSString *name = call.arguments[@"name"]; + FIRTrace *trace = [_performance traceWithName:name]; + FLTTrace *handler = [[FLTTrace alloc] initWithTrace:trace]; + + NSNumber *handle = call.arguments[@"traceHandle"]; + [FLTFirebasePerformancePlugin addMethodHandler:handle methodHandler:handler]; + + result(nil); +} + +- (void)newHttpMetric:(FlutterMethodCall *)call result:(FlutterResult)result { + FIRHTTPMethod method = [FLTFirebasePerformance parseHttpMethod:call.arguments[@"httpMethod"]]; + NSURL *url = [NSURL URLWithString:call.arguments[@"url"]]; + + FIRHTTPMetric *metric = [[FIRHTTPMetric alloc] initWithURL:url HTTPMethod:method]; + FLTHttpMetric *handler = [[FLTHttpMetric alloc] initWithHTTPMetric:metric]; + + NSNumber *handle = call.arguments[@"httpMetricHandle"]; + [FLTFirebasePerformancePlugin addMethodHandler:handle methodHandler:handler]; + + result(nil); +} + ++ (FIRHTTPMethod)parseHttpMethod:(NSString *)method { + if ([@"HttpMethod.Connect" isEqualToString:method]) { + return FIRHTTPMethodCONNECT; + } else if ([@"HttpMethod.Delete" isEqualToString:method]) { + return FIRHTTPMethodDELETE; + } else if ([@"HttpMethod.Get" isEqualToString:method]) { + return FIRHTTPMethodGET; + } else if ([@"HttpMethod.Head" isEqualToString:method]) { + return FIRHTTPMethodHEAD; + } else if ([@"HttpMethod.Options" isEqualToString:method]) { + return FIRHTTPMethodOPTIONS; + } else if ([@"HttpMethod.Patch" isEqualToString:method]) { + return FIRHTTPMethodPATCH; + } else if ([@"HttpMethod.Post" isEqualToString:method]) { + return FIRHTTPMethodPOST; + } else if ([@"HttpMethod.Put" isEqualToString:method]) { + return FIRHTTPMethodPUT; + } else if ([@"HttpMethod.Trace" isEqualToString:method]) { + return FIRHTTPMethodTRACE; + } + + NSString *reason = [NSString stringWithFormat:@"Invalid HttpMethod: %@", method]; + @throw [[NSException alloc] initWithName:NSInvalidArgumentException reason:reason userInfo:nil]; +} +@end diff --git a/packages/firebase_performance/ios/Classes/FLTHttpMetric.m b/packages/firebase_performance/ios/Classes/FLTHttpMetric.m new file mode 100644 index 000000000000..3a12f3400613 --- /dev/null +++ b/packages/firebase_performance/ios/Classes/FLTHttpMetric.m @@ -0,0 +1,111 @@ +// Copyright 2019 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 "FirebasePerformancePlugin+Internal.h" + +@interface FLTHttpMetric () +@property FIRHTTPMetric *metric; +@end + +@implementation FLTHttpMetric +- (instancetype _Nonnull)initWithHTTPMetric:(FIRHTTPMetric *)metric { + self = [self init]; + if (self) { + _metric = metric; + } + + return self; +} + +- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { + if ([@"HttpMetric#start" isEqualToString:call.method]) { + [self start:result]; + } else if ([@"HttpMetric#stop" isEqualToString:call.method]) { + [self stop:call result:result]; + } else if ([@"HttpMetric#httpResponseCode" isEqualToString:call.method]) { + [self setHttpResponseCode:call result:result]; + } else if ([@"HttpMetric#requestPayloadSize" isEqualToString:call.method]) { + [self requestPayloadSize:call result:result]; + } else if ([@"HttpMetric#responseContentType" isEqualToString:call.method]) { + [self responseContentType:call result:result]; + } else if ([@"HttpMetric#responsePayloadSize" isEqualToString:call.method]) { + [self responsePayloadSize:call result:result]; + } else if ([@"PerformanceAttributes#putAttribute" isEqualToString:call.method]) { + [self putAttribute:call result:result]; + } else if ([@"PerformanceAttributes#removeAttribute" isEqualToString:call.method]) { + [self removeAttribute:call result:result]; + } else if ([@"PerformanceAttributes#getAttributes" isEqualToString:call.method]) { + [self getAttributes:result]; + } else { + result(FlutterMethodNotImplemented); + } +} + +- (void)start:(FlutterResult)result { + [_metric start]; + result(nil); +} + +- (void)stop:(FlutterMethodCall *)call result:(FlutterResult)result { + [_metric stop]; + + NSNumber *handle = call.arguments[@"handle"]; + [FLTFirebasePerformancePlugin removeMethodHandler:handle]; + + result(nil); +} + +- (void)setHttpResponseCode:(FlutterMethodCall *)call result:(FlutterResult)result { + NSNumber *responseCode = call.arguments[@"httpResponseCode"]; + + if (![responseCode isEqual:[NSNull null]]) _metric.responseCode = [responseCode integerValue]; + result(nil); +} + +- (void)requestPayloadSize:(FlutterMethodCall *)call result:(FlutterResult)result { + NSNumber *requestPayloadSize = call.arguments[@"requestPayloadSize"]; + + if (![requestPayloadSize isEqual:[NSNull null]]) { + _metric.requestPayloadSize = [requestPayloadSize longValue]; + } + result(nil); +} + +- (void)responseContentType:(FlutterMethodCall *)call result:(FlutterResult)result { + NSString *responseContentType = call.arguments[@"responseContentType"]; + + if (![responseContentType isEqual:[NSNull null]]) { + _metric.responseContentType = responseContentType; + } + result(nil); +} + +- (void)responsePayloadSize:(FlutterMethodCall *)call result:(FlutterResult)result { + NSNumber *responsePayloadSize = call.arguments[@"responsePayloadSize"]; + + if (![responsePayloadSize isEqual:[NSNull null]]) { + _metric.responsePayloadSize = [responsePayloadSize longValue]; + } + result(nil); +} + +- (void)putAttribute:(FlutterMethodCall *)call result:(FlutterResult)result { + NSString *name = call.arguments[@"name"]; + NSString *value = call.arguments[@"value"]; + + [_metric setValue:value forAttribute:name]; + result(nil); +} + +- (void)removeAttribute:(FlutterMethodCall *)call result:(FlutterResult)result { + NSString *name = call.arguments[@"name"]; + + [_metric removeAttribute:name]; + result(nil); +} + +- (void)getAttributes:(FlutterResult)result { + result([_metric attributes]); +} +@end diff --git a/packages/firebase_performance/ios/Classes/FLTTrace.m b/packages/firebase_performance/ios/Classes/FLTTrace.m new file mode 100644 index 000000000000..3ce6af5fc3ef --- /dev/null +++ b/packages/firebase_performance/ios/Classes/FLTTrace.m @@ -0,0 +1,98 @@ +// Copyright 2019 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 "FirebasePerformancePlugin+Internal.h" + +@interface FLTTrace () +@property FIRTrace *trace; +@end + +@implementation FLTTrace +- (instancetype _Nonnull)initWithTrace:(FIRTrace *)trace { + self = [self init]; + if (self) { + _trace = trace; + } + + return self; +} + +- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { + if ([@"Trace#start" isEqualToString:call.method]) { + [self start:result]; + } else if ([@"Trace#stop" isEqualToString:call.method]) { + [self stop:call result:result]; + } else if ([@"Trace#setMetric" isEqualToString:call.method]) { + [self setMetric:call result:result]; + } else if ([@"Trace#incrementMetric" isEqualToString:call.method]) { + [self incrementMetric:call result:result]; + } else if ([@"Trace#getMetric" isEqualToString:call.method]) { + [self getMetric:call result:result]; + } else if ([@"PerformanceAttributes#putAttribute" isEqualToString:call.method]) { + [self putAttribute:call result:result]; + } else if ([@"PerformanceAttributes#removeAttribute" isEqualToString:call.method]) { + [self removeAttribute:call result:result]; + } else if ([@"PerformanceAttributes#getAttributes" isEqualToString:call.method]) { + [self getAttributes:result]; + } else { + result(FlutterMethodNotImplemented); + } +} + +- (void)start:(FlutterResult)result { + [_trace start]; + result(nil); +} + +- (void)stop:(FlutterMethodCall *)call result:(FlutterResult)result { + [_trace stop]; + + NSNumber *handle = call.arguments[@"handle"]; + [FLTFirebasePerformancePlugin removeMethodHandler:handle]; + + result(nil); +} + +- (void)setMetric:(FlutterMethodCall *)call result:(FlutterResult)result { + NSString *name = call.arguments[@"name"]; + NSNumber *value = call.arguments[@"value"]; + + [_trace setIntValue:value.longValue forMetric:name]; + result(nil); +} + +- (void)incrementMetric:(FlutterMethodCall *)call result:(FlutterResult)result { + NSString *name = call.arguments[@"name"]; + NSNumber *value = call.arguments[@"value"]; + + [_trace incrementMetric:name byInt:value.longValue]; + result(nil); +} + +- (void)getMetric:(FlutterMethodCall *)call result:(FlutterResult)result { + NSString *name = call.arguments[@"name"]; + + int64_t metric = [_trace valueForIntMetric:name]; + result(@(metric)); +} + +- (void)putAttribute:(FlutterMethodCall *)call result:(FlutterResult)result { + NSString *name = call.arguments[@"name"]; + NSString *value = call.arguments[@"value"]; + + [_trace setValue:value forAttribute:name]; + result(nil); +} + +- (void)removeAttribute:(FlutterMethodCall *)call result:(FlutterResult)result { + NSString *name = call.arguments[@"name"]; + + [_trace removeAttribute:name]; + result(nil); +} + +- (void)getAttributes:(FlutterResult)result { + result([_trace attributes]); +} +@end diff --git a/packages/firebase_performance/ios/Classes/FirebasePerformancePlugin+Internal.h b/packages/firebase_performance/ios/Classes/FirebasePerformancePlugin+Internal.h new file mode 100644 index 000000000000..f0adca5b905b --- /dev/null +++ b/packages/firebase_performance/ios/Classes/FirebasePerformancePlugin+Internal.h @@ -0,0 +1,28 @@ +// Copyright 2019 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 "FirebasePerformancePlugin.h" + +@protocol MethodCallHandler +@required +- (void)handleMethodCall:(FlutterMethodCall *_Nonnull)call result:(FlutterResult _Nonnull)result; +@end + +@interface FLTFirebasePerformancePlugin (Internal) ++ (void)addMethodHandler:(NSNumber *_Nonnull)handle + methodHandler:(id _Nonnull)handler; ++ (void)removeMethodHandler:(NSNumber *_Nonnull)handle; +@end + +@interface FLTFirebasePerformance : NSObject ++ (instancetype _Nonnull)sharedInstance; +@end + +@interface FLTTrace : NSObject +- (instancetype _Nonnull)initWithTrace:(FIRTrace *_Nonnull)trace; +@end + +@interface FLTHttpMetric : NSObject +- (instancetype _Nonnull)initWithHTTPMetric:(FIRHTTPMetric *_Nonnull)metric; +@end diff --git a/packages/firebase_performance/ios/Classes/FirebasePerformancePlugin.h b/packages/firebase_performance/ios/Classes/FirebasePerformancePlugin.h index d71c5351ac8d..44bf53592eb9 100644 --- a/packages/firebase_performance/ios/Classes/FirebasePerformancePlugin.h +++ b/packages/firebase_performance/ios/Classes/FirebasePerformancePlugin.h @@ -1,3 +1,4 @@ +#import #import @interface FLTFirebasePerformancePlugin : NSObject diff --git a/packages/firebase_performance/ios/Classes/FirebasePerformancePlugin.m b/packages/firebase_performance/ios/Classes/FirebasePerformancePlugin.m index 25073643815e..1c387265c988 100644 --- a/packages/firebase_performance/ios/Classes/FirebasePerformancePlugin.m +++ b/packages/firebase_performance/ios/Classes/FirebasePerformancePlugin.m @@ -2,21 +2,19 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#import "FirebasePerformancePlugin.h" - -#import - -@interface FLTFirebasePerformancePlugin () -@property(nonatomic, retain) NSMutableDictionary *traces; -@property(nonatomic, retain) NSMutableDictionary *httpMetrics; -@end +#import "FirebasePerformancePlugin+Internal.h" @implementation FLTFirebasePerformancePlugin +static NSMutableDictionary> *methodHandlers; + + (void)registerWithRegistrar:(NSObject *)registrar { + methodHandlers = [NSMutableDictionary new]; + FlutterMethodChannel *channel = [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/firebase_performance" binaryMessenger:[registrar messenger]]; - FLTFirebasePerformancePlugin *instance = [[FLTFirebasePerformancePlugin alloc] init]; + + FLTFirebasePerformancePlugin *instance = [FLTFirebasePerformancePlugin new]; [registrar addMethodCallDelegate:instance channel:channel]; } @@ -28,133 +26,40 @@ - (instancetype)init { [FIRApp configure]; NSLog(@"Configured the default Firebase app %@.", [FIRApp defaultApp].name); } - - _traces = [[NSMutableDictionary alloc] init]; - _httpMetrics = [[NSMutableDictionary alloc] init]; } return self; } - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { - if ([@"FirebasePerformance#isPerformanceCollectionEnabled" isEqualToString:call.method]) { - result(@([[FIRPerformance sharedInstance] isDataCollectionEnabled])); - } else if ([@"FirebasePerformance#setPerformanceCollectionEnabled" isEqualToString:call.method]) { - NSNumber *enable = call.arguments; - [[FIRPerformance sharedInstance] setDataCollectionEnabled:[enable boolValue]]; + if ([@"FirebasePerformance#instance" isEqualToString:call.method]) { + NSNumber *handle = call.arguments[@"handle"]; + FLTFirebasePerformance *performance = [FLTFirebasePerformance sharedInstance]; + + [FLTFirebasePerformancePlugin addMethodHandler:handle methodHandler:performance]; result(nil); - } else if ([@"Trace#start" isEqualToString:call.method]) { - [self handleTraceStart:call result:result]; - } else if ([@"Trace#stop" isEqualToString:call.method]) { - [self handleTraceStop:call result:result]; - } else if ([@"HttpMetric#start" isEqualToString:call.method]) { - [self handleHttpMetricStart:call result:result]; - } else if ([@"HttpMetric#stop" isEqualToString:call.method]) { - [self handleHttpMetricStop:call result:result]; } else { - result(FlutterMethodNotImplemented); - } -} - -- (void)handleTraceStart:(FlutterMethodCall *)call result:(FlutterResult)result { - NSNumber *handle = call.arguments[@"handle"]; - NSString *name = call.arguments[@"name"]; - - FIRTrace *trace = [[FIRPerformance sharedInstance] traceWithName:name]; - [_traces setObject:trace forKey:handle]; - [trace start]; - result(nil); -} - -- (void)handleTraceStop:(FlutterMethodCall *)call result:(FlutterResult)result { - NSNumber *handle = call.arguments[@"handle"]; - FIRTrace *trace = [_traces objectForKey:handle]; - - NSDictionary *metrics = call.arguments[@"metrics"]; - [metrics enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSNumber *value, BOOL *stop) { - [trace setIntValue:[value longLongValue] forMetric:key]; - }]; + NSNumber *handle = call.arguments[@"handle"]; - NSDictionary *attributes = call.arguments[@"attributes"]; - [attributes enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, BOOL *stop) { - [trace setValue:value forAttribute:key]; - }]; - - [trace stop]; - [_traces removeObjectForKey:handle]; - result(nil); + if (![handle isEqual:[NSNull null]]) { + [methodHandlers[handle] handleMethodCall:call result:result]; + } else { + result(FlutterMethodNotImplemented); + } + } } -- (void)handleHttpMetricStart:(FlutterMethodCall *)call result:(FlutterResult)result { - NSNumber *handle = call.arguments[@"handle"]; - NSURL *url = [NSURL URLWithString:call.arguments[@"url"]]; - - NSNumber *httpMethod = call.arguments[@"httpMethod"]; - FIRHTTPMethod method; - switch ([httpMethod intValue]) { - case 0: - method = FIRHTTPMethodCONNECT; - break; - case 1: - method = FIRHTTPMethodDELETE; - break; - case 2: - method = FIRHTTPMethodGET; - break; - case 3: - method = FIRHTTPMethodHEAD; - break; - case 4: - method = FIRHTTPMethodOPTIONS; - break; - case 5: - method = FIRHTTPMethodPATCH; - break; - case 6: - method = FIRHTTPMethodPOST; - break; - case 7: - method = FIRHTTPMethodPUT; - break; - case 8: - method = FIRHTTPMethodTRACE; - break; - default: - method = [httpMethod intValue]; - break; ++ (void)addMethodHandler:(NSNumber *)handle methodHandler:(id)handler { + if (methodHandlers[handle]) { + NSString *reason = + [[NSString alloc] initWithFormat:@"Object for handle already exists: %d", handle.intValue]; + @throw [[NSException alloc] initWithName:NSInvalidArgumentException reason:reason userInfo:nil]; } - FIRHTTPMetric *metric = [[FIRHTTPMetric alloc] initWithURL:url HTTPMethod:method]; - [_httpMetrics setObject:metric forKey:handle]; - [metric start]; - result(nil); + methodHandlers[handle] = handler; } -- (void)handleHttpMetricStop:(FlutterMethodCall *)call result:(FlutterResult)result { - NSNumber *handle = call.arguments[@"handle"]; - FIRHTTPMetric *metric = [_httpMetrics objectForKey:handle]; - - NSNumber *responseCode = call.arguments[@"httpResponseCode"]; - NSNumber *requestPayloadSize = call.arguments[@"requestPayloadSize"]; - NSString *responseContentType = call.arguments[@"responseContentType"]; - NSNumber *responsePayloadSize = call.arguments[@"responsePayloadSize"]; - - if (![responseCode isEqual:[NSNull null]]) metric.responseCode = [responseCode integerValue]; - if (![requestPayloadSize isEqual:[NSNull null]]) - metric.requestPayloadSize = [requestPayloadSize longValue]; - if (![responseContentType isEqual:[NSNull null]]) - metric.responseContentType = responseContentType; - if (![responsePayloadSize isEqual:[NSNull null]]) - metric.responsePayloadSize = [responsePayloadSize longValue]; - - NSDictionary *attributes = call.arguments[@"attributes"]; - [attributes enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, BOOL *stop) { - [metric setValue:value forAttribute:key]; - }]; - - [metric stop]; - [_httpMetrics removeObjectForKey:handle]; - result(nil); ++ (void)removeMethodHandler:(NSNumber *)handle { + [methodHandlers removeObjectForKey:handle]; } - @end diff --git a/packages/firebase_performance/lib/firebase_performance.dart b/packages/firebase_performance/lib/firebase_performance.dart index 17dbcfc07863..8f8a2e7a4348 100644 --- a/packages/firebase_performance/lib/firebase_performance.dart +++ b/packages/firebase_performance/lib/firebase_performance.dart @@ -5,7 +5,6 @@ library firebase_performance; import 'dart:async'; -import 'dart:collection'; import 'package:flutter/services.dart'; import 'package:flutter/foundation.dart'; diff --git a/packages/firebase_performance/lib/src/firebase_performance.dart b/packages/firebase_performance/lib/src/firebase_performance.dart index 381236d44dc7..e2498ec172cb 100644 --- a/packages/firebase_performance/lib/src/firebase_performance.dart +++ b/packages/firebase_performance/lib/src/firebase_performance.dart @@ -11,59 +11,82 @@ enum HttpMethod { Connect, Delete, Get, Head, Options, Patch, Post, Put, Trace } /// /// You can get an instance by calling [FirebasePerformance.instance]. class FirebasePerformance { - FirebasePerformance._(); + FirebasePerformance._(this._handle) { + channel.invokeMethod( + 'FirebasePerformance#instance', + {'handle': _handle}, + ); + } + + static int _nextHandle = 0; - static int _traceCount = 0; - static int _httpMetricCount = 0; + final int _handle; @visibleForTesting static const MethodChannel channel = MethodChannel('plugins.flutter.io/firebase_performance'); /// Singleton of [FirebasePerformance]. - static final FirebasePerformance instance = FirebasePerformance._(); + static final FirebasePerformance instance = + FirebasePerformance._(_nextHandle++); /// Determines whether performance monitoring is enabled or disabled. /// /// True if performance monitoring is enabled and false if performance /// monitoring is disabled. This is for dynamic enable/disable state. This /// does not reflect whether instrumentation is enabled/disabled. - Future isPerformanceCollectionEnabled() async { - final bool isEnabled = await channel - // TODO(amirh): remove this on when the invokeMethod update makes it to stable Flutter. - // https://github.com/flutter/flutter/issues/26431 - // ignore: strong_mode_implicit_dynamic_method - .invokeMethod('FirebasePerformance#isPerformanceCollectionEnabled'); - return isEnabled; + Future isPerformanceCollectionEnabled() { + return channel.invokeMethod( + '$FirebasePerformance#isPerformanceCollectionEnabled', + {'handle': _handle}, + ); } /// Enables or disables performance monitoring. /// /// This setting is persisted and applied on future invocations of your /// application. By default, performance monitoring is enabled. - Future setPerformanceCollectionEnabled(bool enable) async { - // TODO(amirh): remove this on when the invokeMethod update makes it to stable Flutter. - // https://github.com/flutter/flutter/issues/26431 - // ignore: strong_mode_implicit_dynamic_method - await channel.invokeMethod( - 'FirebasePerformance#setPerformanceCollectionEnabled', enable); + Future setPerformanceCollectionEnabled(bool enable) { + return channel.invokeMethod( + '$FirebasePerformance#setPerformanceCollectionEnabled', + {'handle': _handle, 'enable': enable}, + ); } /// Creates a [Trace] object with given [name]. /// /// The [name] requires no leading or trailing whitespace, no leading - /// underscore _ character, max length of [Trace.maxTraceNameLength] + /// underscore _ character, and max length of [Trace.maxTraceNameLength] /// characters. Trace newTrace(String name) { - return Trace._(_traceCount++, name); + final int handle = _nextHandle++; + + FirebasePerformance.channel.invokeMethod( + '$FirebasePerformance#newTrace', + {'handle': _handle, 'traceHandle': handle, 'name': name}, + ); + + return Trace._(handle, name); } /// Creates [HttpMetric] for collecting performance for one request/response. HttpMetric newHttpMetric(String url, HttpMethod httpMethod) { - return HttpMetric._(_httpMetricCount++, url, httpMethod); + final int handle = _nextHandle++; + + FirebasePerformance.channel.invokeMethod( + '$FirebasePerformance#newHttpMetric', + { + 'handle': _handle, + 'httpMetricHandle': handle, + 'url': url, + 'httpMethod': httpMethod.toString(), + }, + ); + + return HttpMetric._(handle, url, httpMethod); } - /// Creates a [Trace] object with given [name] and start the trace. + /// Creates a [Trace] object with given [name] and starts the trace. /// /// The [name] requires no leading or trailing whitespace, no leading /// underscore _ character, max length of [Trace.maxTraceNameLength] diff --git a/packages/firebase_performance/lib/src/http_metric.dart b/packages/firebase_performance/lib/src/http_metric.dart index a2e221b368e1..c0b27bb41ce3 100644 --- a/packages/firebase_performance/lib/src/http_metric.dart +++ b/packages/firebase_performance/lib/src/http_metric.dart @@ -6,106 +6,149 @@ part of firebase_performance; /// Metric used to collect data for network requests/responses. /// -/// It is possible to have more than one httpmetric running at a time. +/// It is possible to have more than one [HttpMetric] running at a time. /// Attributes can also be added to help measure performance related events. A -/// httpmetric also measures the time between calling start() and stop(). +/// [HttpMetric] also measures the time between calling `start()` and `stop()`. /// /// Data collected is automatically sent to the associated Firebase console /// after stop() is called. /// /// You can confirm that Performance Monitoring results appear in the Firebase /// console. Results should appear within 12 hours. +/// +/// It is highly recommended that one always calls `start()` and `stop()` on +/// each created [HttpMetric] to avoid leaking on the platform side. class HttpMetric extends PerformanceAttributes { - HttpMetric._(this._handle, this._url, this._httpMethod); + HttpMetric._(this._handle, this.url, this.httpMethod); - final int _handle; - final String _url; - final HttpMethod _httpMethod; + final String url; + final HttpMethod httpMethod; + @override bool _hasStarted = false; + + @override bool _hasStopped = false; + int _httpResponseCode; + int _requestPayloadSize; + String _responseContentType; + int _responsePayloadSize; + + @override + final int _handle; + /// HttpResponse code of the request. - int httpResponseCode; + int get httpResponseCode => _httpResponseCode; /// Size of the request payload. - int requestPayloadSize; + int get requestPayloadSize => _requestPayloadSize; /// Content type of the response such as text/html, application/json, etc... - String responseContentType; + String get responseContentType => _responseContentType; /// Size of the response payload. - int responsePayloadSize; + int get responsePayloadSize => _responsePayloadSize; - /// Starts this httpmetric. + /// HttpResponse code of the request. /// - /// Can only be called once, otherwise assertion error is thrown. + /// If the [HttpMetric] has already been stopped, returns immediately without + /// taking action. + set httpResponseCode(int httpResponseCode) { + if (_hasStopped) return; + + _httpResponseCode = httpResponseCode; + FirebasePerformance.channel.invokeMethod( + '$HttpMetric#httpResponseCode', + { + 'handle': _handle, + 'httpResponseCode': httpResponseCode, + }, + ); + } + + /// Size of the request payload. /// - /// Using ```await``` with this method is only necessary when accurate timing - /// is relevant. - Future start() { - assert(!_hasStarted); + /// If the [HttpMetric] has already been stopped, returns immediately without + /// taking action. + set requestPayloadSize(int requestPayloadSize) { + if (_hasStopped) return; - _hasStarted = true; - return FirebasePerformance.channel - // TODO(amirh): remove this on when the invokeMethod update makes it to stable Flutter. - // https://github.com/flutter/flutter/issues/26431 - // ignore: strong_mode_implicit_dynamic_method - .invokeMethod('HttpMetric#start', { - 'handle': _handle, - 'url': _url, - 'httpMethod': _httpMethod.index, - }); + _requestPayloadSize = requestPayloadSize; + FirebasePerformance.channel.invokeMethod( + '$HttpMetric#requestPayloadSize', + { + 'handle': _handle, + 'requestPayloadSize': requestPayloadSize, + }, + ); } - /// Stops this httpMetric. + /// Content type of the response such as text/html, application/json, etc... /// - /// Can only be called once and only after start(), otherwise assertion error - /// is thrown. Data collected is automatically sent to the associated - /// Firebase console after stop() is called. + /// If the [HttpMetric] has already been stopped, returns immediately without + /// taking action. + set responseContentType(String responseContentType) { + if (_hasStopped) return; + + _responseContentType = responseContentType; + FirebasePerformance.channel.invokeMethod( + '$HttpMetric#responseContentType', + { + 'handle': _handle, + 'responseContentType': responseContentType, + }, + ); + } + + /// Size of the response payload. /// - /// Not necessary to use ```await``` with this method. - Future stop() { - assert(!_hasStopped); - assert(_hasStarted); - - final Map data = { - 'handle': _handle, - 'httpResponseCode': httpResponseCode, - 'requestPayloadSize': requestPayloadSize, - 'responseContentType': responseContentType, - 'responsePayloadSize': responsePayloadSize, - 'attributes': _attributes, - }; + /// If the [HttpMetric] has already been stopped, returns immediately without + /// taking action. + set responsePayloadSize(int responsePayloadSize) { + if (_hasStopped) return; - _hasStopped = true; - // TODO(amirh): remove this on when the invokeMethod update makes it to stable Flutter. - // https://github.com/flutter/flutter/issues/26431 - // ignore: strong_mode_implicit_dynamic_method - return FirebasePerformance.channel.invokeMethod('HttpMetric#stop', data); + _responsePayloadSize = responsePayloadSize; + FirebasePerformance.channel.invokeMethod( + '$HttpMetric#responsePayloadSize', + { + 'handle': _handle, + 'responsePayloadSize': responsePayloadSize, + }, + ); } - /// Sets a String [value] for the specified [attribute]. + /// Starts this [HttpMetric]. /// - /// If the httpmetric has been stopped, this method throws an assertion - /// error. + /// Can only be called once. /// - /// See [PerformanceAttributes.putAttribute]. - @override - void putAttribute(String attribute, String value) { - assert(!_hasStopped); - super.putAttribute(attribute, value); + /// Using `await` with this method is only necessary when accurate timing + /// is relevant. + Future start() { + if (_hasStopped) return Future.value(null); + + _hasStarted = true; + return FirebasePerformance.channel.invokeMethod( + '$HttpMetric#start', + {'handle': _handle}, + ); } - /// Removes an already added [attribute]. + /// Stops this [HttpMetric]. /// - /// If the httpmetric has been stopped, this method throws an assertion - /// error. + /// Can only be called once and only after start(), Data collected is + /// automatically sent to the associate Firebase console after stop() is + /// called. You can confirm that Performance Monitoring results appear in the + /// Firebase console. Results should appear within 12 hours. /// - /// See [PerformanceAttributes.removeAttribute]. - @override - void removeAttribute(String attribute) { - assert(!_hasStopped); - super.removeAttribute(attribute); + /// Not necessary to use `await` with this method. + Future stop() { + if (_hasStopped) return Future.value(null); + + _hasStopped = true; + return FirebasePerformance.channel.invokeMethod( + '$HttpMetric#stop', + {'handle': _handle}, + ); } } diff --git a/packages/firebase_performance/lib/src/performance_attributes.dart b/packages/firebase_performance/lib/src/performance_attributes.dart index 0543242fdeb2..119567a1eaf0 100644 --- a/packages/firebase_performance/lib/src/performance_attributes.dart +++ b/packages/firebase_performance/lib/src/performance_attributes.dart @@ -4,10 +4,7 @@ part of firebase_performance; -/// Abstract class that allows adding/removing attributes to any object. -/// -/// Enforces constraints for adding attributes and values required by -/// FirebasePerformance API. See [putAttribute]. +/// Abstract class that allows adding/removing attributes to an object. abstract class PerformanceAttributes { /// Maximum allowed length of a key passed to [putAttribute]. static const int maxAttributeKeyLength = 40; @@ -20,10 +17,12 @@ abstract class PerformanceAttributes { final Map _attributes = {}; - /// Copy of all the attributes added. - Map get attributes => Map.from(_attributes); + bool get _hasStarted; + bool get _hasStopped; - /// Sets a String [value] for the specified [attribute]. + int get _handle; + + /// Sets a String [value] for the specified attribute with [name]. /// /// Updates the value of the attribute if the attribute already exists. /// The maximum number of attributes that can be added are @@ -32,22 +31,48 @@ abstract class PerformanceAttributes { /// Name of the attribute has max length of [maxAttributeKeyLength] /// characters. Value of the attribute has max length of /// [maxAttributeValueLength] characters. - void putAttribute(String attribute, String value) { - assert(attribute != null); - assert(!attribute.startsWith(RegExp(r'[_\s]'))); - assert(!attribute.contains(RegExp(r'[_\s]$'))); - assert(attribute.length <= maxAttributeKeyLength); - assert(value.length <= maxAttributeValueLength); - assert(_attributes.length < maxCustomAttributes); - - _attributes[attribute] = value; + Future putAttribute(String name, String value) { + if (!_hasStarted || + _hasStopped || + name.length > maxAttributeKeyLength || + value.length > maxAttributeValueLength || + _attributes.length == 5) { + return Future.value(null); + } + + _attributes[name] = value; + return FirebasePerformance.channel.invokeMethod( + '$PerformanceAttributes#putAttribute', + { + 'handle': _handle, + 'name': name, + 'value': value, + }, + ); } - /// Removes an already added [attribute]. - void removeAttribute(String attribute) { - _attributes.remove(attribute); + /// Removes an already added attribute with [name]. + Future removeAttribute(String name) { + if (!_hasStarted || _hasStopped) return Future.value(null); + + _attributes.remove(name); + return FirebasePerformance.channel.invokeMethod( + '$PerformanceAttributes#removeAttribute', + {'handle': _handle, 'name': name}, + ); } - /// Returns the value of an [attribute]. - String getAttribute(String attribute) => _attributes[attribute]; + /// All attributes added. + Future> getAttributes() { + if (_hasStopped) { + return Future>.value(Map.unmodifiable( + _attributes, + )); + } + + return FirebasePerformance.channel.invokeMapMethod( + '$PerformanceAttributes#getAttributes', + {'handle': _handle}, + ); + } } diff --git a/packages/firebase_performance/lib/src/trace.dart b/packages/firebase_performance/lib/src/trace.dart index 96d173bef4aa..cf8e1fb01d3a 100644 --- a/packages/firebase_performance/lib/src/trace.dart +++ b/packages/firebase_performance/lib/src/trace.dart @@ -4,139 +4,120 @@ part of firebase_performance; -/// Trace allows you to set the beginning and end of a custom trace in your app. +/// [Trace] allows you to set the beginning and end of a custom trace in your app. /// /// A trace is a report of performance data associated with some of the /// code in your app. You can have multiple custom traces, and it is /// possible to have more than one custom trace running at a time. Each custom /// trace can have multiple metrics and attributes added to help measure /// performance related events. A trace also measures the time between calling -/// start() and stop(). +/// `start()` and `stop()`. /// /// Data collected is automatically sent to the associated Firebase console /// after stop() is called. /// /// You can confirm that Performance Monitoring results appear in the Firebase /// console. Results should appear within 12 hours. +/// +/// It is highly recommended that one always calls `start()` and `stop()` on +/// each created [Trace] to not avoid leaking on the platform side. class Trace extends PerformanceAttributes { - Trace._(this._handle, this._name) { - assert(_name != null); - assert(!_name.startsWith(RegExp(r'[_\s]'))); - assert(!_name.contains(RegExp(r'[_\s]$'))); - assert(_name.length <= maxTraceNameLength); - } + Trace._(this._handle, this.name); /// Maximum allowed length of the name of a [Trace]. static const int maxTraceNameLength = 100; - final int _handle; - final String _name; + final Map _metrics = {}; + @override bool _hasStarted = false; + + @override bool _hasStopped = false; - final HashMap _metrics = HashMap(); + @override + final int _handle; - /// Starts this trace. + /// Name representing this [Trace] on the Firebase Console. + final String name; + + /// Starts this [Trace]. /// - /// Can only be called once, otherwise assertion error is thrown. + /// Can only be called once. /// - /// Using ```await``` with this method is only necessary when accurate timing + /// Using `await` with this method is only necessary when accurate timing /// is relevant. Future start() { - assert(!_hasStarted); + if (_hasStopped) return Future.value(null); _hasStarted = true; - return FirebasePerformance.channel - // TODO(amirh): remove this on when the invokeMethod update makes it to stable Flutter. - // https://github.com/flutter/flutter/issues/26431 - // ignore: strong_mode_implicit_dynamic_method - .invokeMethod('Trace#start', { - 'handle': _handle, - 'name': _name, - }); + return FirebasePerformance.channel.invokeMethod( + '$Trace#start', + {'handle': _handle}, + ); } - /// Stops this trace. + /// Stops this [Trace]. /// - /// Can only be called once and only after start(), otherwise assertion error - /// is thrown. Data collected is automatically sent to the associated Firebase - /// console after stop() is called. + /// Can only be called once and only after start() Data collected is + /// automatically sent to the associated Firebase console after stop() is + /// called. You can confirm that Performance Monitoring results appear in the + /// Firebase console. Results should appear within 12 hours. /// - /// Not necessary to use ```await``` with this method. + /// Not necessary to use `await` with this method. Future stop() { - assert(!_hasStopped); - assert(_hasStarted); - - final Map data = { - 'handle': _handle, - 'name': _name, - 'metrics': _metrics, - 'attributes': _attributes, - }; + if (_hasStopped) return Future.value(null); _hasStopped = true; - // TODO(amirh): remove this on when the invokeMethod update makes it to stable Flutter. - // https://github.com/flutter/flutter/issues/26431 - // ignore: strong_mode_implicit_dynamic_method - return FirebasePerformance.channel.invokeMethod('Trace#stop', data); + return FirebasePerformance.channel.invokeMethod( + '$Trace#stop', + {'handle': _handle}, + ); } - /// Increments the counter with the given [name] by [incrementBy]. + /// Increments the metric with the given [name]. /// - /// The counter is incremented by 1 if [incrementBy] was not passed. If a - /// counter does not already exist, a new one will be created. If the trace - /// has not been started or has already been stopped, an assertion error is - /// thrown. - /// - /// The name of the counter requires no leading or - /// trailing whitespace, no leading underscore _ character, and max length of - /// 32 characters. - @Deprecated('Use `incrementMetric` instead.') - void incrementCounter(String name, [int incrementBy = 1]) { - incrementMetric(name, incrementBy); - } - - /// Increments the metric with the given [name] by [incrementBy]. - /// - /// If a metric does not already exist, a new one will be created with the - /// initial value [incrementBy]. If the trace has not been started or has - /// already been stopped, an assertion error is thrown. - /// - /// The name of the metric requires no leading or trailing whitespace, no - /// leading underscore _ character, and max length of 32 characters. - void incrementMetric(String name, int incrementBy) { - assert(!_hasStopped); - assert(name != null); - assert(!name.startsWith(RegExp(r'[_\s]'))); - assert(!name.contains(RegExp(r'[_\s]$'))); - assert(name.length <= 32); + /// If the metric does not exist, a new one will be created. If the [Trace] has + /// not been started or has already been stopped, returns immediately without + /// taking action. + Future incrementMetric(String name, int value) { + if (!_hasStarted || _hasStopped) { + return Future.value(null); + } _metrics.putIfAbsent(name, () => 0); - _metrics[name] += incrementBy; + _metrics[name] += value; + return FirebasePerformance.channel.invokeMethod( + '$Trace#incrementMetric', + {'handle': _handle, 'name': name, 'value': value}, + ); } - /// Sets a String [value] for the specified [attribute]. + /// Sets the [value] of the metric with the given [name]. /// - /// If the trace has been stopped, this method throws an assertion - /// error. - /// - /// See [PerformanceAttributes.putAttribute]. - @override - void putAttribute(String attribute, String value) { - assert(!_hasStopped); - super.putAttribute(attribute, value); + /// If a metric with the given name doesn't exist, a new one will be created. + /// If the [Trace] has not been started or has already been stopped, returns + /// immediately without taking action. + Future setMetric(String name, int value) { + if (!_hasStarted || _hasStopped) return Future.value(null); + + _metrics[name] = value; + return FirebasePerformance.channel.invokeMethod( + '$Trace#setMetric', + {'handle': _handle, 'name': name, 'value': value}, + ); } - /// Removes an already added [attribute]. + /// Gets the value of the metric with the given [name]. /// - /// If the trace has been stopped, this method throws an assertion - /// error. - /// - /// See [PerformanceAttributes.removeAttribute]. - @override - void removeAttribute(String attribute) { - assert(!_hasStopped); - super.removeAttribute(attribute); + /// If a metric with the given name doesn't exist, it is NOT created and a 0 + /// is returned. + Future getMetric(String name) { + if (_hasStopped) return Future.value(_metrics[name] ?? 0); + + return FirebasePerformance.channel.invokeMethod( + '$Trace#getMetric', + {'handle': _handle, 'name': name}, + ); } } diff --git a/packages/firebase_performance/test/firebase_performance_test.dart b/packages/firebase_performance/test/firebase_performance_test.dart index bf2b42e50499..c2dbd1997c0a 100644 --- a/packages/firebase_performance/test/firebase_performance_test.dart +++ b/packages/firebase_performance/test/firebase_performance_test.dart @@ -7,15 +7,14 @@ import 'package:flutter/services.dart'; import 'package:firebase_performance/firebase_performance.dart'; import 'package:flutter_test/flutter_test.dart'; -class MockPerformanceAttributes extends PerformanceAttributes {} - void main() { group('$FirebasePerformance', () { - final FirebasePerformance performance = FirebasePerformance.instance; + FirebasePerformance performance; final List log = []; - bool performanceCollectionEnable = true; - int currentTraceHandle; - int currentHttpMetricHandle; + bool isPerformanceCollectionEnabledResult; + + int firebasePerformanceHandle; + int nextHandle = 0; setUp(() { FirebasePerformance.channel @@ -23,20 +22,7 @@ void main() { log.add(methodCall); switch (methodCall.method) { case 'FirebasePerformance#isPerformanceCollectionEnabled': - return performanceCollectionEnable; - case 'FirebasePerformance#setPerformanceCollectionEnabled': - performanceCollectionEnable = methodCall.arguments; - return null; - case 'Trace#start': - currentTraceHandle = methodCall.arguments['handle']; - return null; - case 'Trace#stop': - return null; - case 'HttpMetric#start': - currentHttpMetricHandle = methodCall.arguments['handle']; - return null; - case 'HttpMetric#stop': - return null; + return isPerformanceCollectionEnabledResult; default: return null; } @@ -44,221 +30,163 @@ void main() { log.clear(); }); + test('instance', () { + firebasePerformanceHandle = nextHandle++; + + performance = FirebasePerformance.instance; + + expect(log, [ + isMethodCall( + 'FirebasePerformance#instance', + arguments: {'handle': firebasePerformanceHandle}, + ), + ]); + }); + test('isPerformanceCollectionEnabled', () async { + isPerformanceCollectionEnabledResult = true; final bool enabled = await performance.isPerformanceCollectionEnabled(); + expect(enabled, isTrue); + + isPerformanceCollectionEnabledResult = false; + final bool disabled = await performance.isPerformanceCollectionEnabled(); + expect(disabled, isFalse); - expect(performanceCollectionEnable, enabled); expect(log, [ isMethodCall( 'FirebasePerformance#isPerformanceCollectionEnabled', - arguments: null, + arguments: {'handle': firebasePerformanceHandle}, + ), + isMethodCall( + 'FirebasePerformance#isPerformanceCollectionEnabled', + arguments: {'handle': firebasePerformanceHandle}, ), ]); }); - test('setPerformanceCollectionEnabled', () async { - await performance.setPerformanceCollectionEnabled(true); - performanceCollectionEnable = true; - - await performance.setPerformanceCollectionEnabled(false); - performanceCollectionEnable = false; + test('setPerformanceCollectionEnabled', () { + performance.setPerformanceCollectionEnabled(true); + performance.setPerformanceCollectionEnabled(false); expect(log, [ isMethodCall( 'FirebasePerformance#setPerformanceCollectionEnabled', - arguments: true, + arguments: { + 'handle': firebasePerformanceHandle, + 'enable': true, + }, ), isMethodCall( 'FirebasePerformance#setPerformanceCollectionEnabled', - arguments: false, + arguments: { + 'handle': firebasePerformanceHandle, + 'enable': false, + }, ), ]); }); - test('newTrace', () async { - final Trace trace = performance.newTrace('test-trace'); - await trace.start(); + test('newTrace', () { + performance.newTrace('test-trace'); expect(log, [ isMethodCall( - 'Trace#start', - arguments: { - 'handle': currentTraceHandle, + 'FirebasePerformance#newTrace', + arguments: { + 'handle': firebasePerformanceHandle, + 'traceHandle': nextHandle++, 'name': 'test-trace', }, ), ]); }); - test('newHttpMetric', () async { - final HttpMetric metric = performance.newHttpMetric( - 'https://google.com', - HttpMethod.Connect, - ); - await metric.start(); + test('newHttpMetric', () { + final String url = 'https://google.com'; + performance.newHttpMetric(url, HttpMethod.Connect); expect(log, [ isMethodCall( - 'HttpMetric#start', - arguments: { - 'handle': currentTraceHandle, - 'url': 'https://google.com', - 'httpMethod': HttpMethod.Connect.index, + 'FirebasePerformance#newHttpMetric', + arguments: { + 'handle': firebasePerformanceHandle, + 'httpMetricHandle': nextHandle++, + 'url': url, + 'httpMethod': HttpMethod.Connect.toString(), }, ), ]); }); - test('startTrace', () async { - await FirebasePerformance.startTrace('startTrace-test'); + test('$HttpMethod', () { + final String url = 'https://google.com'; + final HttpMethod method = HttpMethod.Connect; + + performance.newHttpMetric('https://google.com', method); expect(log, [ isMethodCall( - 'Trace#start', - arguments: { - 'handle': currentTraceHandle, - 'name': 'startTrace-test', + 'FirebasePerformance#newHttpMetric', + arguments: { + 'handle': firebasePerformanceHandle, + 'httpMetricHandle': nextHandle++, + 'url': url, + 'httpMethod': method.toString(), }, ), ]); }); - test('$HttpMethod', () async { - expect(HttpMethod.Connect.index, 0); - expect(HttpMethod.Delete.index, 1); - expect(HttpMethod.Get.index, 2); - expect(HttpMethod.Head.index, 3); - expect(HttpMethod.Options.index, 4); - expect(HttpMethod.Patch.index, 5); - expect(HttpMethod.Post.index, 6); - expect(HttpMethod.Put.index, 7); - expect(HttpMethod.Trace.index, 8); - }); - group('$Trace', () { Trace testTrace; + int currentTestTraceHandle; setUp(() { testTrace = performance.newTrace('test'); + currentTestTraceHandle = nextHandle++; + log.clear(); }); - test('start', () async { - await testTrace.start(); + test('start', () { + testTrace.start(); expect(log, [ isMethodCall( 'Trace#start', - arguments: { - 'handle': currentTraceHandle, - 'name': 'test', - }, + arguments: {'handle': currentTestTraceHandle}, ), ]); }); - test('stop', () async { - await testTrace.start(); - await testTrace.stop(); + test('stop', () { + testTrace.start(); + log.clear(); - expect(log, [ - isMethodCall('Trace#start', arguments: { - 'handle': currentTraceHandle, - 'name': 'test', - }), - isMethodCall( - 'Trace#stop', - arguments: { - 'handle': currentTraceHandle, - 'name': 'test', - 'metrics': {}, - 'attributes': {}, - }, - ), - ]); - }); - - test('incrementCounter', () async { - final Trace trace = performance.newTrace('test'); - - // ignore: deprecated_member_use_from_same_package - trace.incrementCounter('counter1'); - - // ignore: deprecated_member_use_from_same_package - trace.incrementCounter('counter2'); - // ignore: deprecated_member_use_from_same_package - trace.incrementCounter('counter2'); - - // ignore: deprecated_member_use_from_same_package - trace.incrementCounter('counter3', 5); - // ignore: deprecated_member_use_from_same_package - trace.incrementCounter('counter3', 5); - - // ignore: deprecated_member_use_from_same_package - trace.incrementCounter('counter4', -5); - - await trace.start(); - await trace.stop(); + testTrace.stop(); expect(log, [ - isMethodCall( - 'Trace#start', - arguments: { - 'handle': currentTraceHandle, - 'name': 'test', - }, - ), isMethodCall( 'Trace#stop', - arguments: { - 'handle': currentTraceHandle, - 'name': 'test', - 'metrics': { - 'counter1': 1, - 'counter2': 2, - 'counter3': 10, - 'counter4': -5, - }, - 'attributes': {}, - }, + arguments: {'handle': currentTestTraceHandle}, ), ]); }); - test('incrementMetric', () async { - final Trace trace = performance.newTrace('test'); - trace.incrementMetric('metric1', 1); - - trace.incrementMetric('metric2', 1); - trace.incrementMetric('metric2', 1); - - trace.incrementMetric('metric3', 5); - trace.incrementMetric('metric3', 5); - - trace.incrementMetric('metric4', -5); + test('incrementMetric', () { + testTrace.start(); + log.clear(); - await trace.start(); - await trace.stop(); + final String name = 'metric1'; + final int increment = 3; + testTrace.incrementMetric(name, increment); expect(log, [ isMethodCall( - 'Trace#start', - arguments: { - 'handle': currentTraceHandle, - 'name': 'test', - }, - ), - isMethodCall( - 'Trace#stop', + 'Trace#incrementMetric', arguments: { - 'handle': currentTraceHandle, - 'name': 'test', - 'metrics': { - 'metric1': 1, - 'metric2': 2, - 'metric3': 10, - 'metric4': -5, - }, - 'attributes': {}, + 'handle': currentTestTraceHandle, + 'name': name, + 'value': increment, }, ), ]); @@ -267,105 +195,114 @@ void main() { group('$HttpMetric', () { HttpMetric testMetric; + int currentTestMetricHandle; setUp(() { testMetric = performance.newHttpMetric( 'https://google.com', HttpMethod.Get, ); + currentTestMetricHandle = nextHandle++; + log.clear(); }); - test('start', () async { - await testMetric.start(); + test('start', () { + testMetric.start(); expect(log, [ isMethodCall( 'HttpMetric#start', - arguments: { - 'handle': currentHttpMetricHandle, - 'url': 'https://google.com', - 'httpMethod': HttpMethod.Get.index, - }, + arguments: {'handle': currentTestMetricHandle}, ), ]); }); - test('stop', () async { - testMetric.httpResponseCode = 1; - testMetric.requestPayloadSize = 5000000; - testMetric.responseContentType = 'text/html'; - testMetric.responsePayloadSize = 1992304820934820; + test('stop', () { + testMetric.start(); + log.clear(); - await testMetric.start(); - await testMetric.stop(); + testMetric.stop(); expect(log, [ - isMethodCall( - 'HttpMetric#start', - arguments: { - 'handle': currentHttpMetricHandle, - 'url': 'https://google.com', - 'httpMethod': HttpMethod.Get.index, - }, - ), isMethodCall( 'HttpMetric#stop', - arguments: { - 'handle': currentHttpMetricHandle, - 'httpResponseCode': 1, - 'requestPayloadSize': 5000000, - 'responseContentType': 'text/html', - 'responsePayloadSize': 1992304820934820, - 'attributes': {}, - }, + arguments: {'handle': currentTestMetricHandle}, ), ]); }); }); group('$PerformanceAttributes', () { - PerformanceAttributes attributes; + Trace attributeTrace; + int currentTraceHandle; + + final Map getAttributesResult = { + 'a1': 'hello', + 'a2': 'friend', + }; setUp(() { - attributes = MockPerformanceAttributes(); + FirebasePerformance.channel + .setMockMethodCallHandler((MethodCall methodCall) async { + log.add(methodCall); + switch (methodCall.method) { + case 'PerformanceAttributes#getAttributes': + return getAttributesResult; + default: + return null; + } + }); + + attributeTrace = performance.newTrace('trace'); + currentTraceHandle = nextHandle++; + attributeTrace.start(); + log.clear(); }); - test('putAttribute', () async { - attributes.putAttribute('attr1', 'apple'); - attributes.putAttribute('attr2', 'are'); - expect(attributes.attributes, { - 'attr1': 'apple', - 'attr2': 'are', - }); + test('putAttribute', () { + final String name = 'attr1'; + final String value = 'apple'; - attributes.putAttribute('attr1', 'delicious'); - expect(attributes.attributes, { - 'attr1': 'delicious', - 'attr2': 'are', - }); + attributeTrace.putAttribute(name, value); + + expect(log, [ + isMethodCall( + 'PerformanceAttributes#putAttribute', + arguments: { + 'handle': currentTraceHandle, + 'name': name, + 'value': value, + }, + ), + ]); }); - test('removeAttribute', () async { - attributes.putAttribute('attr1', 'apple'); - attributes.putAttribute('attr2', 'are'); - attributes.removeAttribute('no-attr'); - expect(attributes.attributes, { - 'attr1': 'apple', - 'attr2': 'are', - }); + test('removeAttribute', () { + final String name = 'attr1'; + attributeTrace.removeAttribute(name); - attributes.removeAttribute('attr1'); - expect(attributes.attributes, { - 'attr2': 'are', - }); + expect(log, [ + isMethodCall( + 'PerformanceAttributes#removeAttribute', + arguments: { + 'handle': currentTraceHandle, + 'name': name, + }, + ), + ]); }); - test('getAttribute', () { - attributes.putAttribute('attr1', 'apple'); - attributes.putAttribute('attr2', 'are'); - expect(attributes.getAttribute('attr1'), 'apple'); + test('getAttributes', () async { + final Map result = await attributeTrace.getAttributes(); + + expect(log, [ + isMethodCall( + 'PerformanceAttributes#getAttributes', + arguments: {'handle': currentTraceHandle}, + ), + ]); - expect(attributes.getAttribute('attr3'), isNull); + expect(result, getAttributesResult); }); }); });