From a966ef8827433f643bc9bb8fbba377adae14c9a3 Mon Sep 17 00:00:00 2001 From: jsimantov Date: Mon, 8 Feb 2021 14:02:33 -0800 Subject: [PATCH] Added experimental Firebase Performance library code. PiperOrigin-RevId: 356347936 --- performance/example_usage.cc | 193 ++++++++ .../src/android/firebase_performance.cc | 147 ++++++ performance/src/android/http_metric.cc | 348 ++++++++++++++ .../android/performance_android_internal.h | 78 ++++ performance/src/android/trace.cc | 322 +++++++++++++ performance/src/http_metric_test.cc | 430 ++++++++++++++++++ .../src/include/firebase/performance.h | 62 +++ .../firebase/performance/http_metric.h | 168 +++++++ .../src/include/firebase/performance/trace.h | 134 ++++++ performance/src/ios/firebase_performance.mm | 48 ++ performance/src/ios/http_metric.mm | 258 +++++++++++ performance/src/ios/trace.mm | 235 ++++++++++ performance/src/performance_common.cc | 60 +++ performance/src/performance_common.h | 30 ++ performance/src/performance_test.cc | 87 ++++ performance/src/trace_test.cc | 329 ++++++++++++++ performance/src_ios/fake/FIRHTTPMetric.h | 68 +++ performance/src_ios/fake/FIRHTTPMetric.mm | 160 +++++++ performance/src_ios/fake/FIRPerformance.h | 61 +++ performance/src_ios/fake/FIRPerformance.mm | 81 ++++ .../src_ios/fake/FIRPerformanceAttributable.h | 37 ++ performance/src_ios/fake/FIRTrace.h | 91 ++++ performance/src_ios/fake/FIRTrace.mm | 173 +++++++ .../firebase/perf/FirebasePerformance.java | 118 +++++ .../perf/FirebasePerformanceAttributable.java | 54 +++ .../firebase/perf/metrics/HttpMetric.java | 140 ++++++ .../google/firebase/perf/metrics/Trace.java | 253 +++++++++++ performance/stubs/http_metric_stub.cc | 100 ++++ performance/stubs/performance_stub.cc | 15 + performance/stubs/trace_stub.cc | 90 ++++ 30 files changed, 4370 insertions(+) create mode 100644 performance/example_usage.cc create mode 100644 performance/src/android/firebase_performance.cc create mode 100644 performance/src/android/http_metric.cc create mode 100644 performance/src/android/performance_android_internal.h create mode 100644 performance/src/android/trace.cc create mode 100644 performance/src/http_metric_test.cc create mode 100644 performance/src/include/firebase/performance.h create mode 100644 performance/src/include/firebase/performance/http_metric.h create mode 100644 performance/src/include/firebase/performance/trace.h create mode 100644 performance/src/ios/firebase_performance.mm create mode 100644 performance/src/ios/http_metric.mm create mode 100644 performance/src/ios/trace.mm create mode 100644 performance/src/performance_common.cc create mode 100644 performance/src/performance_common.h create mode 100644 performance/src/performance_test.cc create mode 100644 performance/src/trace_test.cc create mode 100644 performance/src_ios/fake/FIRHTTPMetric.h create mode 100644 performance/src_ios/fake/FIRHTTPMetric.mm create mode 100644 performance/src_ios/fake/FIRPerformance.h create mode 100644 performance/src_ios/fake/FIRPerformance.mm create mode 100644 performance/src_ios/fake/FIRPerformanceAttributable.h create mode 100644 performance/src_ios/fake/FIRTrace.h create mode 100644 performance/src_ios/fake/FIRTrace.mm create mode 100644 performance/src_java/fake/com/google/firebase/perf/FirebasePerformance.java create mode 100644 performance/src_java/fake/com/google/firebase/perf/FirebasePerformanceAttributable.java create mode 100644 performance/src_java/fake/com/google/firebase/perf/metrics/HttpMetric.java create mode 100644 performance/src_java/fake/com/google/firebase/perf/metrics/Trace.java create mode 100644 performance/stubs/http_metric_stub.cc create mode 100644 performance/stubs/performance_stub.cc create mode 100644 performance/stubs/trace_stub.cc diff --git a/performance/example_usage.cc b/performance/example_usage.cc new file mode 100644 index 0000000000..b0ab6806ab --- /dev/null +++ b/performance/example_usage.cc @@ -0,0 +1,193 @@ +#include + +#include "app/src/include/firebase/app.h" +#include "performance/src/include/firebase/performance.h" +#include "performance/src/include/firebase/performance/http_metric.h" +#include "performance/src/include/firebase/performance/trace.h" + +int main() { + // Initialize + firebase::InitResult fireperf_init_result = + firebase::performance::Initialize(*firebase::App::GetInstance()); + + if (fireperf_init_result == + firebase::InitResult::kInitResultFailedMissingDependency) { + std::cout << "Failed to initialize firebase performance." << std::endl; + } else { + std::cout << "Successfully initialized firebase performance." << std::endl; + } + + // Enable firebase performance monitoring. + firebase::performance::SetPerformanceCollectionEnabled(true); + + // Disable firebase performance monitoring. + firebase::performance::SetPerformanceCollectionEnabled(false); + + if (firebase::performance::GetPerformanceCollectionEnabled()) { + std::cout << "Firebase Performance monitoring is enabled." << std::endl; + } else { + std::cout << "Firebase Performance monitoring is disabled." << std::endl; + } + + // Create and start a Trace on the heap, add custom attributes, metrics. + auto trace = + new firebase::performance::Trace("myMethod"); // Also starts the trace. + std::cout << "Trace started status: " << trace->is_started() << std::endl; + trace->IncrementMetric("cacheHit", 2); + trace->SetMetric("cacheSize", 50); + // Currently returns a stub value, but it should return 50 when a fake is + // implemented. + std::cout << "Value of the \"cacheSize\" metric: " + << trace->GetLongMetric("cacheSize") << std::endl; + + trace->SetAttribute("level", "4"); + trace->GetAttribute("level"); // returns "4" + trace->SetAttribute("level", nullptr); + + // Stop trace, and re-use the object for another trace. + trace->Start("myOtherMethod"); + + delete trace; // Logs myOtherMethod and deletes the object. + + // Create a Trace on the heap, start it later and then stop it. + auto delayed_start_trace = new firebase::performance::Trace(); + // Do some set up work that we don't want included in the trace duration. + + // Once we're ready, start. + delayed_start_trace->Start("criticalSectionOfCode"); + + // Interesting code ends. + delete delayed_start_trace; // Stops and logs it to the backend. + + // Trace using automatic storage (in this case on the stack). + { + firebase::performance::Trace trace_stack("myMethod"); + trace_stack.IncrementMetric("cacheHit", 2); + trace_stack.SetMetric("cacheSize", 50); + // Currently returns a stub value, but it should return 50 when a fake is + // implemented. + std::cout << "Value of the \"cacheSize\" metric: " + << trace_stack.GetLongMetric("cacheSize") << std::endl; + + trace_stack.SetAttribute("level", "4"); + // Currently returns a stub value, but should return 4 when a fake is + // implemented. + std::cout << "Value of \"level\" attribute on the \"myMethod\" trace: " + << trace_stack.GetAttribute("level") << std::endl; + trace_stack.SetAttribute("level", nullptr); + std::cout << "Trace started status: " << trace_stack.is_started() + << std::endl; + } + // Stop is called when it's destructed, and the trace is logged to the + // backend. + + // Trace on the stack, and start it later. + { + firebase::performance::Trace trace_stack; + + trace_stack.Start("someTrace"); + trace_stack.IncrementMetric("cacheHit", 2); + + trace_stack.Start( + "someOtherTrace"); // Logs someTrace, and starts "someOtherTrace" + trace_stack.Cancel(); // Cancel someOtherTrace. + std::cout << "Trace started status: " << trace_stack.is_started() + << std::endl; + } + + // Create an HttpMetric, custom attributes, counters and add details. + // Note: Only needed if developer is using non-standard networking library + + // On the heap + auto http_metric = new firebase::performance::HttpMetric( + "https://google.com", firebase::performance::HttpMethod::kHttpMethodGet); + + // Add more detail to http metric + http_metric->set_http_response_code(200); + http_metric->set_request_payload_size(25); + http_metric->set_response_content_type("application/json"); + http_metric->set_response_payload_size(500); + + std::cout << "HttpMetric started status: " << http_metric->is_started() + << std::endl; + + http_metric->SetAttribute("level", "4"); + // Currently returns a stub value, but should return 4 when a fake is + // implemented. + std::cout + << "Value of \"level\" attribute on the \"google.com\" http metric: " + << http_metric->GetAttribute("level") << std::endl; + + // Logs the google.com http metric and starts a new one for a different + // network request. + http_metric->Start("https://firebase.com", + firebase::performance::HttpMethod::kHttpMethodPost); + http_metric->set_response_payload_size(500); + + delete http_metric; // Stops and logs it to the backend. + + // create an http metric object on the heap, but start it later. + auto http_metric_delayed_start = new firebase::performance::HttpMetric(); + + // Do some setup. + + // Start the trace. + http_metric_delayed_start->Start( + "https://firebase.com", + firebase::performance::HttpMethod::kHttpMethodGet); + + // Stop it. + http_metric_delayed_start->Stop(); + + // HttpMetric using Automatic storage (in this case on the stack), restarted + // so that the first one is logged, and then the new one is cancelled which is + // not logged. + { + // This also starts the HttpMetric. + firebase::performance::HttpMetric http_metric_stack( + "https://google.com", + firebase::performance::HttpMethod::kHttpMethodGet); + + // Add more detail to http metric + http_metric_stack.set_http_response_code(200); + http_metric_stack.set_request_payload_size(25); + http_metric_stack.set_response_content_type("application/json"); + http_metric_stack.set_response_payload_size(500); + + http_metric_stack.SetAttribute("level", "4"); + // Currently returns a stub value, but should return 4 when a fake is + // implemented. + std::cout + << "Value of \"level\" attribute on the \"google.com\" http metric: " + << http_metric_stack.GetAttribute("level") << std::endl; + + // Stops the google.com http metric and starts a new one that tracks the + // firebase.com network request. + http_metric_stack.Start("https://firebase.com", + firebase::performance::HttpMethod::kHttpMethodPost); + + std::cout << "HttpMetric started status: " << http_metric_stack.is_started() + << std::endl; + + // Cancels the new firebase.com network trace, because it doesn't have any + // valid data. + http_metric_stack.Cancel(); + + std::cout << "HttpMetric started status: " << http_metric_stack.is_started() + << std::endl; + } + + // HttpMetric on stack is stopped and logged when it's destroyed. + { + firebase::performance::HttpMetric http_metric_stack; + + http_metric_stack.Start("https://google.com", + firebase::performance::HttpMethod::kHttpMethodGet); + + // Add more detail to http metric + http_metric_stack.set_http_response_code(200); + } // HttpMetric is stopped and logged to the backend as part of being + // destroyed. + + return 0; +} diff --git a/performance/src/android/firebase_performance.cc b/performance/src/android/firebase_performance.cc new file mode 100644 index 0000000000..59bbc99e28 --- /dev/null +++ b/performance/src/android/firebase_performance.cc @@ -0,0 +1,147 @@ +// Copyright 2016 Google Inc. All Rights Reserved. + +#include +#include + +#include +#include + +#include "app/src/assert.h" +#include "app/src/include/firebase/app.h" +#include "app/src/include/firebase/version.h" +#include "app/src/log.h" +#include "performance/src/android/performance_android_internal.h" +#include "performance/src/include/firebase/performance.h" +#include "performance/src/performance_common.h" + +namespace firebase { +namespace performance { + +// Global reference to the Android Performance class instance. +// This is initialized in performance::Initialize() and never released +// during the lifetime of the application. +static jobject g_performance_class_instance = nullptr; + +// Used to retrieve the JNI environment in order to call methods on the +// Android Performance class. +static const ::firebase::App* g_app = nullptr; + +// Initialize the Performance API. +InitResult Initialize(const ::firebase::App& app) { + if (g_app) { + LogWarning("%s API already initialized", internal::kPerformanceModuleName); + return kInitResultSuccess; + } + LogInfo("Firebase Performance API Initializing"); + FIREBASE_ASSERT(!g_performance_class_instance); + JNIEnv* env = app.GetJNIEnv(); + + if (!util::Initialize(env, app.activity())) { + return kInitResultFailedMissingDependency; + } + + // Cache method pointers on FirebasePerformance. + if (!internal::performance_jni::CacheMethodIds(env, app.activity())) { + util::Terminate(env); + return kInitResultFailedMissingDependency; + } + + // Cache method pointers on HttpMetric. + if (!internal::http_metric_jni::CacheMethodIds(env, app.activity())) { + util::Terminate(env); + return kInitResultFailedMissingDependency; + } + + // Cache method pointers on Trace. + if (!internal::trace_jni::CacheMethodIds(env, app.activity())) { + util::Terminate(env); + return kInitResultFailedMissingDependency; + } + + g_app = &app; + // Get / create the Performance singleton. + jobject performance_class_instance_local = + env->CallStaticObjectMethod(internal::performance_jni::GetClass(), + internal::performance_jni::GetMethodId( + internal::performance_jni::kGetInstance)); + util::CheckAndClearJniExceptions(env); + // Keep a reference to the Performance singleton. + g_performance_class_instance = + env->NewGlobalRef(performance_class_instance_local); + FIREBASE_ASSERT(g_performance_class_instance); + + env->DeleteLocalRef(performance_class_instance_local); + internal::RegisterTerminateOnDefaultAppDestroy(); + LogInfo("%s API Initialized", internal::kPerformanceModuleName); + return kInitResultSuccess; +} + +namespace internal { + +// Determine whether the performance module is initialized. +bool IsInitialized() { return g_app != nullptr; } + +jobject GetFirebasePerformanceClassInstance() { + return g_performance_class_instance; +} + +const ::firebase::App* GetFirebaseApp() { return g_app; } + +DEFINE_FIREBASE_VERSION_STRING(FirebasePerformance); + +METHOD_LOOKUP_DEFINITION(performance_jni, + PROGUARD_KEEP_CLASS + "com/google/firebase/perf/FirebasePerformance", + PERFORMANCE_METHODS) + +METHOD_LOOKUP_DEFINITION(http_metric_jni, + "com/google/firebase/perf/metrics/HttpMetric", + HTTP_METRIC_METHODS) + +METHOD_LOOKUP_DEFINITION(trace_jni, "com/google/firebase/perf/metrics/Trace", + TRACE_METHODS) + +} // namespace internal + +// Clean up the API. +void Terminate() { + if (!g_app) { + LogWarning("%s API already shut down", internal::kPerformanceModuleName); + return; + } + JNIEnv* env = g_app->GetJNIEnv(); + util::CancelCallbacks(env, internal::kPerformanceModuleName); + internal::UnregisterTerminateOnDefaultAppDestroy(); + g_app = nullptr; + env->DeleteGlobalRef(g_performance_class_instance); + g_performance_class_instance = nullptr; + internal::performance_jni::ReleaseClass(env); + util::Terminate(env); +} + +// Determines if performance collection is enabled. +bool GetPerformanceCollectionEnabled() { + FIREBASE_ASSERT_RETURN(false, internal::IsInitialized()); + JNIEnv* env = g_app->GetJNIEnv(); + jboolean result = + env->CallBooleanMethod(g_performance_class_instance, + internal::performance_jni::GetMethodId( + internal::performance_jni::kGetEnabled)); + util::CheckAndClearJniExceptions(env); + return static_cast(result); +} + +// Sets performance collection enabled or disabled. +void SetPerformanceCollectionEnabled(bool enabled) { + FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized()); + JNIEnv* env = g_app->GetJNIEnv(); + jboolean collection_enabled = enabled; + env->CallVoidMethod(g_performance_class_instance, + internal::performance_jni::GetMethodId( + internal::performance_jni::kSetEnabled), + collection_enabled); + util::CheckAndClearJniExceptions(env); +} + +} // namespace performance +} // namespace firebase diff --git a/performance/src/android/http_metric.cc b/performance/src/android/http_metric.cc new file mode 100644 index 0000000000..1211037a03 --- /dev/null +++ b/performance/src/android/http_metric.cc @@ -0,0 +1,348 @@ +#import "performance/src/include/firebase/performance/http_metric.h" + +#include + +#include + +#include "app/src/assert.h" +#include "app/src/include/firebase/internal/common.h" +#include "app/src/log.h" +#include "app/src/util_android.h" +#include "performance/src/android/performance_android_internal.h" +#include "performance/src/include/firebase/performance.h" +#include "performance/src/performance_common.h" + +namespace firebase { +namespace performance { + +namespace internal { + +// The order in the array has to be exactly the same as the order in which +// they're declared in http_metric.h. +static const char* kHttpMethodToString[] = {"GET", "PUT", "POST", + "DELETE", "HEAD", "PATCH", + "OPTIONS", "TRACE", "CONNECT"}; + +// Maps the firebase::performance::HttpMethod enum to its string counterpart. +const char* GetFIRHttpMethodString(HttpMethod method) { + FIREBASE_ASSERT(method >= 0 && + method < FIREBASE_ARRAYSIZE(kHttpMethodToString)); + return kHttpMethodToString[method]; +} + +// The Internal implementation of HttpMetric as recommended by the pImpl design +// pattern. This class is thread safe as long as we can assume that raw ponter +// access is atomic on any of the platforms this will be used on. +class HttpMetricInternal { + public: + explicit HttpMetricInternal() {} + + ~HttpMetricInternal() { + if (active_http_metric_) { + if (stop_on_destroy_) { + StopHttpMetric(); + } else { + CancelHttpMetric(); + } + } + } + + // Creates an underlying Java HttpMetric. If a previous one exists, + // it is cancelled. + void CreateHttpMetric(const char* url, HttpMethod http_method, + bool stop_on_destroy) { + FIREBASE_ASSERT_RETURN_VOID(IsInitialized()); + stop_on_destroy_ = stop_on_destroy; + + if (WarnIf(url == nullptr, + "URL cannot be nullptr. Unable to create HttpMetric.")) + return; + + JNIEnv* env = GetFirebaseApp()->GetJNIEnv(); + + if (active_http_metric_ != nullptr) { + CancelHttpMetric(); + } + + jstring url_jstring = url ? env->NewStringUTF(url) : nullptr; + jstring http_method_jstring = + env->NewStringUTF(GetFIRHttpMethodString(http_method)); + + jobject local_active_http_metric = env->CallObjectMethod( + GetFirebasePerformanceClassInstance(), + performance_jni::GetMethodId(performance_jni::kNewHttpMetric), + url_jstring, http_method_jstring); + util::CheckAndClearJniExceptions(env); + + active_http_metric_ = env->NewGlobalRef(local_active_http_metric); + env->DeleteLocalRef(local_active_http_metric); + if (url_jstring) env->DeleteLocalRef(url_jstring); + env->DeleteLocalRef(http_method_jstring); + } + + // Starts an already created Java HttpMetric. + void StartCreatedHttpMetric() { + JNIEnv* env = GetFirebaseApp()->GetJNIEnv(); + if (active_http_metric_) { + env->CallVoidMethod( + active_http_metric_, + http_metric_jni::GetMethodId(http_metric_jni::kStartHttpMetric)); + util::CheckAndClearJniExceptions(env); + } + } + + // Creates and starts an underlying Java HttpMetric. If a previous one + // exists, it is cancelled. + void CreateAndStartHttpMetric(const char* url, HttpMethod http_method) { + CreateHttpMetric(url, http_method, true); + StartCreatedHttpMetric(); + } + + // Gets whether the underlying HttpMetric associated with this object is + // created. + bool IsHttpMetricCreated() { return active_http_metric_ != nullptr; } + + // Cancels the http metric, and makes sure it isn't logged to the backend. + void CancelHttpMetric() { + FIREBASE_ASSERT_RETURN_VOID(IsInitialized()); + if (WarnIfNotCreated("Cannot cancel HttpMetric.")) return; + + JNIEnv* env = GetFirebaseApp()->GetJNIEnv(); + env->DeleteGlobalRef(active_http_metric_); + active_http_metric_ = nullptr; + } + + // Stops the network trace if it hasn't already been stopped, and logs it to + // the backend. + void StopHttpMetric() { + FIREBASE_ASSERT_RETURN_VOID(IsInitialized()); + if (WarnIfNotCreated("Cannot stop HttpMetric.")) return; + + JNIEnv* env = GetFirebaseApp()->GetJNIEnv(); + env->CallVoidMethod( + active_http_metric_, + http_metric_jni::GetMethodId(http_metric_jni::kStopHttpMetric)); + util::CheckAndClearJniExceptions(env); + env->DeleteGlobalRef(active_http_metric_); + active_http_metric_ = nullptr; + } + + // Creates a custom attribute for the given network trace with the + // given name and value. + void SetAttribute(const char* attribute_name, const char* attribute_value) { + FIREBASE_ASSERT_RETURN_VOID(IsInitialized()); + if (WarnIf(attribute_name == nullptr, + "Cannot set value for null attribute.")) + return; + if (WarnIfNotCreated("Cannot SetAttribute.")) return; + + JNIEnv* env = GetFirebaseApp()->GetJNIEnv(); + + jstring attribute_name_jstring = env->NewStringUTF(attribute_name); + + if (attribute_value == nullptr) { + env->CallVoidMethod( + active_http_metric_, + http_metric_jni::GetMethodId(http_metric_jni::kRemoveAttribute), + attribute_name_jstring); + } else { + jstring attribute_value_jstring = env->NewStringUTF(attribute_value); + env->CallVoidMethod( + active_http_metric_, + http_metric_jni::GetMethodId(http_metric_jni::kSetAttribute), + attribute_name_jstring, attribute_value_jstring); + env->DeleteLocalRef(attribute_value_jstring); + } + + util::CheckAndClearJniExceptions(env); + env->DeleteLocalRef(attribute_name_jstring); + } + + // Gets the value of the custom attribute identified by the given + // name or an empty string if it hasn't been set. + std::string GetAttribute(const char* attribute_name) const { + FIREBASE_ASSERT_RETURN("", IsInitialized()); + if (WarnIf(attribute_name == nullptr, "attribute_name cannot be null.")) + return ""; + if (WarnIfNotCreated("Cannot GetAttribute.")) return ""; + + JNIEnv* env = GetFirebaseApp()->GetJNIEnv(); + jstring attribute_name_jstring = + attribute_name ? env->NewStringUTF(attribute_name) : nullptr; + jobject attribute_value_jstring = env->CallObjectMethod( + active_http_metric_, + http_metric_jni::GetMethodId(http_metric_jni::kGetAttribute), + attribute_name_jstring); + util::CheckAndClearJniExceptions(env); + env->DeleteLocalRef(attribute_name_jstring); + + if (attribute_value_jstring == nullptr) { + return ""; + } else { + return util::JniStringToString(env, attribute_value_jstring); + } + } + + // Sets the HTTP Response Code (for eg. 404 or 200) of the network + // trace. + void set_http_response_code(int http_response_code) { + FIREBASE_ASSERT_RETURN_VOID(IsInitialized()); + if (WarnIfNotCreated("Cannot set_http_response_code.")) return; + + JNIEnv* env = GetFirebaseApp()->GetJNIEnv(); + env->CallVoidMethod( + active_http_metric_, + http_metric_jni::GetMethodId(http_metric_jni::kSetHttpResponseCode), + http_response_code); + util::CheckAndClearJniExceptions(env); + } + + // Sets the Request Payload size in bytes for the network trace. + void set_request_payload_size(int64_t bytes) { + FIREBASE_ASSERT_RETURN_VOID(IsInitialized()); + if (WarnIfNotCreated("Cannot set_request_payload_size.")) return; + + JNIEnv* env = GetFirebaseApp()->GetJNIEnv(); + env->CallVoidMethod( + active_http_metric_, + http_metric_jni::GetMethodId(http_metric_jni::kSetRequestPayloadSize), + bytes); + util::CheckAndClearJniExceptions(env); + } + + // Sets the Response Content Type of the network trace. + void set_response_content_type(const char* content_type) { + FIREBASE_ASSERT_RETURN_VOID(IsInitialized()); + if (WarnIf(content_type == nullptr, "Cannot set null ResponseContentType.")) + return; + if (WarnIfNotCreated("Cannot set_response_content_type.")) return; + + JNIEnv* env = GetFirebaseApp()->GetJNIEnv(); + jstring content_type_jstring = env->NewStringUTF(content_type); + env->CallVoidMethod( + active_http_metric_, + http_metric_jni::GetMethodId(http_metric_jni::kSetResponseContentType), + content_type_jstring); + util::CheckAndClearJniExceptions(env); + env->DeleteLocalRef(content_type_jstring); + } + + // Sets the Response Payload Size in bytes for the network trace. + void set_response_payload_size(int64_t bytes) { + FIREBASE_ASSERT_RETURN_VOID(IsInitialized()); + if (WarnIfNotCreated("Cannot set_response_payload_size.")) return; + + JNIEnv* env = GetFirebaseApp()->GetJNIEnv(); + env->CallVoidMethod( + active_http_metric_, + http_metric_jni::GetMethodId(http_metric_jni::kSetResponsePayloadSize), + bytes); + util::CheckAndClearJniExceptions(env); + } + + private: + jobject active_http_metric_ = nullptr; + + // The unity implementation doesn't stop the underlying Java trace, whereas + // the C++ implementation does. This flag is set when a Java trace is created + // to track whether it should be stopped before deallocating the object. + bool stop_on_destroy_ = false; + + bool WarnIfNotCreated(const char* warning_message_details) const { + if (!active_http_metric_) { + LogWarning( + "%s HttpMetric is not active. Please create a " + "new HttpMetric.", + warning_message_details); + return true; + } + return false; + } + + bool WarnIf(bool condition, const char* warning_message) const { + if (condition) LogWarning(warning_message); + return condition; + } +}; + +} // namespace internal + +HttpMetric::HttpMetric() { + FIREBASE_ASSERT(internal::IsInitialized()); + internal_ = new internal::HttpMetricInternal(); +} + +HttpMetric::HttpMetric(const char* url, HttpMethod http_method) { + FIREBASE_ASSERT(internal::IsInitialized()); + internal_ = new internal::HttpMetricInternal(); + internal_->CreateAndStartHttpMetric(url, http_method); +} + +HttpMetric::~HttpMetric() { delete internal_; } + +HttpMetric::HttpMetric(HttpMetric&& other) { + internal_ = other.internal_; + other.internal_ = nullptr; +} + +HttpMetric& HttpMetric::operator=(HttpMetric&& other) { + if (this != &other) { + delete internal_; + + internal_ = other.internal_; + other.internal_ = nullptr; + } + return *this; +} + +bool HttpMetric::is_started() { + // In the C++ API we never allow a situation where an underlying HttpMetric + // is created, but not started, which is why this check is sufficient. + // This isn't used in the Unity implementation. + return internal_->IsHttpMetricCreated(); +} + +void HttpMetric::Cancel() { internal_->CancelHttpMetric(); } + +void HttpMetric::Stop() { internal_->StopHttpMetric(); } + +void HttpMetric::Start(const char* url, HttpMethod http_method) { + internal_->StopHttpMetric(); + internal_->CreateAndStartHttpMetric(url, http_method); +} + +void HttpMetric::SetAttribute(const char* attribute_name, + const char* attribute_value) { + internal_->SetAttribute(attribute_name, attribute_value); +} + +std::string HttpMetric::GetAttribute(const char* attribute_name) const { + return internal_->GetAttribute(attribute_name); +} + +void HttpMetric::set_http_response_code(int http_response_code) { + internal_->set_http_response_code(http_response_code); +} + +void HttpMetric::set_request_payload_size(int64_t bytes) { + internal_->set_request_payload_size(bytes); +} + +void HttpMetric::set_response_content_type(const char* content_type) { + internal_->set_response_content_type(content_type); +} + +void HttpMetric::set_response_payload_size(int64_t bytes) { + internal_->set_response_payload_size(bytes); +} + +void HttpMetric::Create(const char* url, HttpMethod http_method) { + internal_->CreateHttpMetric(url, http_method, false); +} + +void HttpMetric::StartCreatedHttpMetric() { + internal_->StartCreatedHttpMetric(); +} + +} // namespace performance +} // namespace firebase diff --git a/performance/src/android/performance_android_internal.h b/performance/src/android/performance_android_internal.h new file mode 100644 index 0000000000..693c42d3e9 --- /dev/null +++ b/performance/src/android/performance_android_internal.h @@ -0,0 +1,78 @@ +// This file contains the declarations of common internal functions that are +// needed for the android implementation of the Firebase Performance C++ +// bindings to work. Their definitions live in firebase_performance.cc. +#ifndef FIREBASE_PERFORMANCE_CLIENT_CPP_SRC_ANDROID_PERFORMANCE_ANDROID_INTERNAL_H_ +#define FIREBASE_PERFORMANCE_CLIENT_CPP_SRC_ANDROID_PERFORMANCE_ANDROID_INTERNAL_H_ + +#include + +#include "app/src/include/firebase/app.h" +#include "app/src/util_android.h" + +namespace firebase { +namespace performance { +namespace internal { + +// Used to setup the cache of Performance class method IDs to reduce time spent +// looking up methods by string. +// clang-format off +#define PERFORMANCE_METHODS(X) \ + X(SetEnabled, "setPerformanceCollectionEnabled", "(Z)V"), \ + X(GetEnabled, "isPerformanceCollectionEnabled", "()Z"), \ + X(GetInstance, "getInstance", \ + "()Lcom/google/firebase/perf/FirebasePerformance;", \ + firebase::util::kMethodTypeStatic), \ + X(NewTrace, "newTrace", \ + "(Ljava/lang/String;)Lcom/google/firebase/perf/metrics/Trace;"), \ + X(NewHttpMetric, "newHttpMetric", \ + "(Ljava/lang/String;Ljava/lang/String;)Lcom/google/firebase/perf/metrics/HttpMetric;") // NOLINT +// clang-format on + +METHOD_LOOKUP_DECLARATION(performance_jni, PERFORMANCE_METHODS) + +// Used to setup the cache of HttpMetric class method IDs to reduce time spent +// looking up methods by string. +// clang-format off +#define HTTP_METRIC_METHODS(X) \ + X(StartHttpMetric, "start", "()V"), \ + X(StopHttpMetric, "stop", "()V"), \ + X(SetAttribute, "putAttribute", "(Ljava/lang/String;Ljava/lang/String;)V"), \ + X(GetAttribute, "getAttribute", "(Ljava/lang/String;)Ljava/lang/String;"), \ + X(RemoveAttribute, "removeAttribute", "(Ljava/lang/String;)V"), \ + X(SetHttpResponseCode, "setHttpResponseCode", "(I)V"), \ + X(SetRequestPayloadSize, "setRequestPayloadSize", "(J)V"), \ + X(SetResponseContentType, "setResponseContentType", \ + "(Ljava/lang/String;)V"), \ + X(SetResponsePayloadSize, "setResponsePayloadSize", "(J)V") +// clang-format on + +METHOD_LOOKUP_DECLARATION(http_metric_jni, HTTP_METRIC_METHODS) + +// Used to setup the cache of HttpMetric class method IDs to reduce time spent +// looking up methods by string. +// clang-format off +#define TRACE_METHODS(X) \ + X(StartTrace, "start", "()V"), \ + X(StopTrace, "stop", "()V"), \ + X(SetAttribute, "putAttribute", "(Ljava/lang/String;Ljava/lang/String;)V"), \ + X(GetAttribute, "getAttribute", "(Ljava/lang/String;)Ljava/lang/String;"), \ + X(RemoveAttribute, "removeAttribute", "(Ljava/lang/String;)V"), \ + X(IncrementMetric, "incrementMetric", "(Ljava/lang/String;J)V"), \ + X(GetLongMetric, "getLongMetric", "(Ljava/lang/String;)J"), \ + X(PutMetric, "putMetric", "(Ljava/lang/String;J)V") +// clang-format on + +METHOD_LOOKUP_DECLARATION(trace_jni, TRACE_METHODS) + +// Returns the reference to the singleton FirebasePerformance object that is +// created when firebase::performance::internal::Initialize() is called. +jobject GetFirebasePerformanceClassInstance(); + +// Returns the Firebase App default instance if initialized, nullptr otherwise. +const ::firebase::App* GetFirebaseApp(); + +} // namespace internal +} // namespace performance +} // namespace firebase + +#endif // FIREBASE_PERFORMANCE_CLIENT_CPP_SRC_ANDROID_PERFORMANCE_ANDROID_INTERNAL_H_ diff --git a/performance/src/android/trace.cc b/performance/src/android/trace.cc new file mode 100644 index 0000000000..c2cb8c0817 --- /dev/null +++ b/performance/src/android/trace.cc @@ -0,0 +1,322 @@ +#import "performance/src/include/firebase/performance/trace.h" + +#include "app/src/assert.h" +#include "performance/src/android/performance_android_internal.h" +#include "performance/src/include/firebase/performance.h" +#include "performance/src/performance_common.h" + +namespace firebase { +namespace performance { + +namespace internal { + +// The Internal implementation of Trace as recommended by the pImpl design +// pattern. This class is thread safe as long as we can assume that raw ponter +// access is atomic on any of the platforms this will be used on. +class TraceInternal { + public: + explicit TraceInternal() {} + + ~TraceInternal() { + if (active_trace_) { + if (stop_on_destroy_) { + StopTrace(); + } else { + CancelTrace(); + } + } + } + + // Creates a Trace using the Android implementation. If this method + // is called before stopping the previous trace, the previous trace is + // cancelled. + void CreateTrace(const char* name, bool stop_on_destroy) { + FIREBASE_ASSERT_RETURN_VOID(IsInitialized()); + + stop_on_destroy_ = stop_on_destroy; + + if (WarnIf(name == nullptr, "Cannot start trace. Name cannot be null.")) + return; + + JNIEnv* env = GetFirebaseApp()->GetJNIEnv(); + + if (active_trace_ != nullptr) { + CancelTrace(); + } + + jstring name_jstring = env->NewStringUTF(name); + + jobject local_active_trace = env->CallObjectMethod( + GetFirebasePerformanceClassInstance(), + performance_jni::GetMethodId(performance_jni::kNewTrace), name_jstring); + util::CheckAndClearJniExceptions(env); + + active_trace_ = env->NewGlobalRef(local_active_trace); + env->DeleteLocalRef(local_active_trace); + env->DeleteLocalRef(name_jstring); + } + + // Creates and Starts a Trace using the Android implementation. If this + // method is called before stopping the previous trace, the previous trace + // is cancelled. + void StartCreatedTrace() { + if (active_trace_) { + JNIEnv* env = GetFirebaseApp()->GetJNIEnv(); + env->CallVoidMethod(active_trace_, + trace_jni::GetMethodId(trace_jni::kStartTrace)); + util::CheckAndClearJniExceptions(env); + } + } + + // Creates and Starts a Trace using the Android implementation. If this + // method is called before stopping the previous trace, the previous trace + // is cancelled. + void CreateAndStartTrace(const char* name) { + CreateTrace(name, true); + StartCreatedTrace(); + } + + // Stops the underlying Java trace if it has been started. Does nothing + // otherwise. + void StopTrace() { + FIREBASE_ASSERT_RETURN_VOID(IsInitialized()); + if (WarnIfNotCreated("Cannot stop Trace.")) return; + + JNIEnv* env = GetFirebaseApp()->GetJNIEnv(); + env->CallVoidMethod(active_trace_, + trace_jni::GetMethodId(trace_jni::kStopTrace)); + util::CheckAndClearJniExceptions(env); + env->DeleteGlobalRef(active_trace_); + active_trace_ = nullptr; + } + + // Cancels the currently running trace if one exists, which prevents it from + // being logged to the backend. + void CancelTrace() { + FIREBASE_ASSERT_RETURN_VOID(IsInitialized()); + if (WarnIfNotCreated("Cannot cancel Trace.")) return; + + JNIEnv* env = GetFirebaseApp()->GetJNIEnv(); + env->DeleteGlobalRef(active_trace_); + active_trace_ = nullptr; + } + + // Returns whether there is a trace that is currently created. + bool IsTraceCreated() { return active_trace_ != nullptr; } + + // Sets a value for the given attribute for the given trace. + void SetAttribute(const char* attribute_name, const char* attribute_value) { + FIREBASE_ASSERT_RETURN_VOID(IsInitialized()); + if (WarnIf(attribute_name == nullptr, + "Cannot SetAttribute for null attribute_name.")) + return; + if (WarnIfNotCreated("Cannot SetAttribute.")) return; + + JNIEnv* env = GetFirebaseApp()->GetJNIEnv(); + + jstring attribute_name_jstring = env->NewStringUTF(attribute_name); + + if (attribute_value == nullptr) { + env->CallVoidMethod(active_trace_, + trace_jni::GetMethodId(trace_jni::kRemoveAttribute), + attribute_name_jstring); + } else { + jstring attribute_value_jstring = env->NewStringUTF(attribute_value); + env->CallVoidMethod(active_trace_, + trace_jni::GetMethodId(trace_jni::kSetAttribute), + attribute_name_jstring, attribute_value_jstring); + env->DeleteLocalRef(attribute_value_jstring); + } + + util::CheckAndClearJniExceptions(env); + env->DeleteLocalRef(attribute_name_jstring); + } + + // Gets the value of the custom attribute identified by the given + // name or nullptr if it hasn't been set. + std::string GetAttribute(const char* attribute_name) const { + FIREBASE_ASSERT_RETURN("", IsInitialized()); + if (WarnIf(attribute_name == nullptr, + "Cannot GetAttribute for null attribute_name.")) + return ""; + if (WarnIfNotCreated("Cannot GetAttribute.")) return ""; + + JNIEnv* env = GetFirebaseApp()->GetJNIEnv(); + jstring attribute_name_jstring = + attribute_name ? env->NewStringUTF(attribute_name) : nullptr; + jobject attribute_value_jstring = env->CallObjectMethod( + active_trace_, trace_jni::GetMethodId(trace_jni::kGetAttribute), + attribute_name_jstring); + util::CheckAndClearJniExceptions(env); + env->DeleteLocalRef(attribute_name_jstring); + + if (attribute_value_jstring == nullptr) { + return ""; + } else { + return util::JniStringToString(env, attribute_value_jstring); + } + } + + // Gets the value of the metric identified by the metric_name or 0 if it + // hasn't yet been set. + int64_t GetLongMetric(const char* metric_name) const { + FIREBASE_ASSERT_RETURN(0, IsInitialized()); + if (WarnIf(metric_name == nullptr, + "Cannot GetLongMetric for null metric_name.")) + return 0; + if (WarnIfNotCreated("Cannot GetLongMetric.")) return 0; + + JNIEnv* env = GetFirebaseApp()->GetJNIEnv(); + jstring metric_name_jstring = + metric_name ? env->NewStringUTF(metric_name) : nullptr; + + jlong metric_value = env->CallLongMethod( + active_trace_, trace_jni::GetMethodId(trace_jni::kGetLongMetric), + metric_name_jstring); + env->DeleteLocalRef(metric_name_jstring); + util::CheckAndClearJniExceptions(env); + + // jlong == int64_t, and so this is ok. + return metric_value; + } + + // Increments the existing value of the given metric by increment_by or sets + // it to increment_by if the metric hasn't been set. + void IncrementMetric(const char* metric_name, const int64_t increment_by) { + FIREBASE_ASSERT_RETURN_VOID(IsInitialized()); + if (WarnIf(metric_name == nullptr, + "Cannot IncrementMetric for null metric_name.")) + return; + if (WarnIfNotCreated("Cannot IncrementMetric.")) return; + + JNIEnv* env = GetFirebaseApp()->GetJNIEnv(); + + jstring metric_name_jstring = env->NewStringUTF(metric_name); + + env->CallVoidMethod(active_trace_, + trace_jni::GetMethodId(trace_jni::kIncrementMetric), + metric_name_jstring, increment_by); + + util::CheckAndClearJniExceptions(env); + env->DeleteLocalRef(metric_name_jstring); + } + + // Sets the value of the given metric to metric_value. + void SetMetric(const char* metric_name, const int64_t metric_value) { + FIREBASE_ASSERT_RETURN_VOID(IsInitialized()); + if (WarnIf(metric_name == nullptr, + "Cannot SetMetric for null metric_name.")) + return; + if (WarnIfNotCreated("Cannot SetMetric.")) return; + + JNIEnv* env = GetFirebaseApp()->GetJNIEnv(); + + jstring metric_name_jstring = env->NewStringUTF(metric_name); + + env->CallVoidMethod(active_trace_, + trace_jni::GetMethodId(trace_jni::kPutMetric), + metric_name_jstring, metric_value); + + util::CheckAndClearJniExceptions(env); + env->DeleteLocalRef(metric_name_jstring); + } + + private: + jobject active_trace_ = nullptr; + + // The unity implementation doesn't stop the underlying Java trace, whereas + // the C++ implementation does. This flag is set when a Java trace is + // created to track whether it should be stopped before deallocating the + // object. + bool stop_on_destroy_ = false; + + bool WarnIfNotCreated(const char* warning_message_details) const { + if (!active_trace_) { + LogWarning( + "%s Trace is not active. Please create a new " + "Trace.", + warning_message_details); + return true; + } + return false; + } + + bool WarnIf(bool condition, const char* warning_message) const { + if (condition) LogWarning(warning_message); + return condition; + } +}; + +} // namespace internal + +Trace::Trace() { + FIREBASE_ASSERT(internal::IsInitialized()); + internal_ = new internal::TraceInternal(); +} + +Trace::Trace(const char* name) { + FIREBASE_ASSERT(internal::IsInitialized()); + internal_ = new internal::TraceInternal(); + internal_->CreateAndStartTrace(name); +} + +Trace::~Trace() { delete internal_; } + +Trace::Trace(Trace&& other) { + internal_ = other.internal_; + other.internal_ = nullptr; +} + +Trace& Trace::operator=(Trace&& other) { + if (this != &other) { + delete internal_; + + internal_ = other.internal_; + other.internal_ = nullptr; + } + return *this; +} + +bool Trace::is_started() { + // In the C++ API we never allow a situation where an underlying HttpMetric + // is created, but not started, which is why this check is sufficient. + return internal_->IsTraceCreated(); +} + +void Trace::Cancel() { internal_->CancelTrace(); } + +void Trace::Stop() { internal_->StopTrace(); } + +void Trace::Start(const char* name) { + internal_->StopTrace(); + internal_->CreateAndStartTrace(name); +} + +void Trace::SetAttribute(const char* attribute_name, + const char* attribute_value) { + internal_->SetAttribute(attribute_name, attribute_value); +} + +std::string Trace::GetAttribute(const char* attribute_name) const { + return internal_->GetAttribute(attribute_name); +} + +int64_t Trace::GetLongMetric(const char* metric_name) const { + return internal_->GetLongMetric(metric_name); +} + +void Trace::IncrementMetric(const char* metric_name, + const int64_t increment_by) { + internal_->IncrementMetric(metric_name, increment_by); +} + +void Trace::SetMetric(const char* metric_name, const int64_t metric_value) { + internal_->SetMetric(metric_name, metric_value); +} + +void Trace::Create(const char* name) { internal_->CreateTrace(name, false); } + +void Trace::StartCreatedTrace() { internal_->StartCreatedTrace(); } + +} // namespace performance +} // namespace firebase diff --git a/performance/src/http_metric_test.cc b/performance/src/http_metric_test.cc new file mode 100644 index 0000000000..55c9ca12dc --- /dev/null +++ b/performance/src/http_metric_test.cc @@ -0,0 +1,430 @@ +#if defined(FIREBASE_ANDROID_FOR_DESKTOP) +#define __ANDROID__ +#include + +#include "testing/run_all_tests.h" +#endif // defined(FIREBASE_ANDROID_FOR_DESKTOP) + +#include + +#include "app/src/include/firebase/app.h" +#include "app/tests/include/firebase/app_for_testing.h" +#include "performance/src/include/firebase/performance.h" +#include "performance/src/include/firebase/performance/http_metric.h" +#include "performance/src/include/firebase/performance/trace.h" +#include "performance/src/performance_common.h" + +#ifdef __ANDROID__ +#include "app/src/util_android.h" +#endif // __ANDROID__ + +#if defined(FIREBASE_ANDROID_FOR_DESKTOP) +#undef __ANDROID__ +#endif // defined(FIREBASE_ANDROID_FOR_DESKTOP) + +#include "gtest/gtest.h" +#include "gmock/gmock.h" +#include "testing/config.h" +#include "testing/reporter.h" +#include "absl/memory/memory.h" + +namespace firebase { +namespace performance { + +class HttpMetricTest : public ::testing::Test { + protected: + void SetUp() override { + firebase::testing::cppsdk::ConfigSet("{}"); + reporter_.reset(); + + firebase_app_.reset(testing::CreateApp()); + AddExpectationAndroid("FirebasePerformance.getInstance", {}); + performance::Initialize(*firebase_app_); + } + + void TearDown() override { + firebase::testing::cppsdk::ConfigReset(); + Terminate(); + firebase_app_.reset(); + EXPECT_THAT(reporter_.getFakeReports(), + ::testing::Eq(reporter_.getExpectations())); + } + + void AddExpectationApple(const char* fake, + std::initializer_list args) { + reporter_.addExpectation(fake, "", firebase::testing::cppsdk::kIos, args); + } + + void AddExpectationAndroid(const char* fake, + std::initializer_list args) { + reporter_.addExpectation(fake, "", firebase::testing::cppsdk::kAndroid, + args); + } + + std::unique_ptr firebase_app_; + firebase::testing::cppsdk::Reporter reporter_; +}; + +TEST_F(HttpMetricTest, TestCreateGetRequest) { + AddExpectationApple("-[FIRHTTPMetric initWithUrl:HTTPMethod:]", + {"https://google.com", "GET"}); + AddExpectationApple("-[FIRHTTPMetric start]", {}); + AddExpectationApple("-[FIRHTTPMetric stop]", {}); + + AddExpectationAndroid("new HttpMetric", {"https://google.com", "GET"}); + AddExpectationAndroid("HttpMetric.start", {}); + AddExpectationAndroid("HttpMetric.stop", {}); + + HttpMetric metric("https://google.com", kHttpMethodGet); +} + +TEST_F(HttpMetricTest, TestCreatePutRequest) { + AddExpectationApple("-[FIRHTTPMetric initWithUrl:HTTPMethod:]", + {"https://google.com", "PUT"}); + AddExpectationApple("-[FIRHTTPMetric start]", {}); + AddExpectationApple("-[FIRHTTPMetric stop]", {}); + + AddExpectationAndroid("new HttpMetric", {"https://google.com", "PUT"}); + AddExpectationAndroid("HttpMetric.start", {}); + AddExpectationAndroid("HttpMetric.stop", {}); + + HttpMetric metric("https://google.com", kHttpMethodPut); +} + +TEST_F(HttpMetricTest, TestCreatePostRequest) { + AddExpectationApple("-[FIRHTTPMetric initWithUrl:HTTPMethod:]", + {"https://google.com", "POST"}); + AddExpectationApple("-[FIRHTTPMetric start]", {}); + AddExpectationApple("-[FIRHTTPMetric stop]", {}); + + AddExpectationAndroid("new HttpMetric", {"https://google.com", "POST"}); + AddExpectationAndroid("HttpMetric.start", {}); + AddExpectationAndroid("HttpMetric.stop", {}); + + HttpMetric metric("https://google.com", kHttpMethodPost); +} + +TEST_F(HttpMetricTest, TestCreateDeleteRequest) { + AddExpectationApple("-[FIRHTTPMetric initWithUrl:HTTPMethod:]", + {"https://google.com", "DELETE"}); + AddExpectationApple("-[FIRHTTPMetric start]", {}); + AddExpectationApple("-[FIRHTTPMetric stop]", {}); + + AddExpectationAndroid("new HttpMetric", {"https://google.com", "DELETE"}); + AddExpectationAndroid("HttpMetric.start", {}); + AddExpectationAndroid("HttpMetric.stop", {}); + + HttpMetric metric("https://google.com", kHttpMethodDelete); +} + +TEST_F(HttpMetricTest, TestCreateHeadRequest) { + AddExpectationApple("-[FIRHTTPMetric initWithUrl:HTTPMethod:]", + {"https://google.com", "HEAD"}); + AddExpectationApple("-[FIRHTTPMetric start]", {}); + AddExpectationApple("-[FIRHTTPMetric stop]", {}); + + AddExpectationAndroid("new HttpMetric", {"https://google.com", "HEAD"}); + AddExpectationAndroid("HttpMetric.start", {}); + AddExpectationAndroid("HttpMetric.stop", {}); + + HttpMetric metric("https://google.com", kHttpMethodHead); +} + +TEST_F(HttpMetricTest, TestCreatePatchRequest) { + AddExpectationApple("-[FIRHTTPMetric initWithUrl:HTTPMethod:]", + {"https://google.com", "PATCH"}); + AddExpectationApple("-[FIRHTTPMetric start]", {}); + AddExpectationApple("-[FIRHTTPMetric stop]", {}); + + AddExpectationAndroid("new HttpMetric", {"https://google.com", "PATCH"}); + AddExpectationAndroid("HttpMetric.start", {}); + AddExpectationAndroid("HttpMetric.stop", {}); + + HttpMetric metric("https://google.com", kHttpMethodPatch); +} + +TEST_F(HttpMetricTest, TestCreateOptionsRequest) { + AddExpectationApple("-[FIRHTTPMetric initWithUrl:HTTPMethod:]", + {"https://google.com", "OPTIONS"}); + AddExpectationApple("-[FIRHTTPMetric start]", {}); + AddExpectationApple("-[FIRHTTPMetric stop]", {}); + + AddExpectationAndroid("new HttpMetric", {"https://google.com", "OPTIONS"}); + AddExpectationAndroid("HttpMetric.start", {}); + AddExpectationAndroid("HttpMetric.stop", {}); + + HttpMetric metric("https://google.com", kHttpMethodOptions); +} + +TEST_F(HttpMetricTest, TestCreateTraceRequest) { + AddExpectationApple("-[FIRHTTPMetric initWithUrl:HTTPMethod:]", + {"https://google.com", "TRACE"}); + AddExpectationApple("-[FIRHTTPMetric start]", {}); + AddExpectationApple("-[FIRHTTPMetric stop]", {}); + + AddExpectationAndroid("new HttpMetric", {"https://google.com", "TRACE"}); + AddExpectationAndroid("HttpMetric.start", {}); + AddExpectationAndroid("HttpMetric.stop", {}); + + HttpMetric metric("https://google.com", kHttpMethodTrace); +} + +TEST_F(HttpMetricTest, TestCreateConnectRequest) { + AddExpectationApple("-[FIRHTTPMetric initWithUrl:HTTPMethod:]", + {"https://google.com", "CONNECT"}); + AddExpectationApple("-[FIRHTTPMetric start]", {}); + AddExpectationApple("-[FIRHTTPMetric stop]", {}); + + AddExpectationAndroid("new HttpMetric", {"https://google.com", "CONNECT"}); + AddExpectationAndroid("HttpMetric.start", {}); + AddExpectationAndroid("HttpMetric.stop", {}); + + HttpMetric metric("https://google.com", kHttpMethodConnect); +} + +TEST_F(HttpMetricTest, TestCreateDelayedGetRequest) { + AddExpectationApple("-[FIRHTTPMetric initWithUrl:HTTPMethod:]", + {"https://google.com", "GET"}); + AddExpectationApple("-[FIRHTTPMetric start]", {}); + AddExpectationApple("-[FIRHTTPMetric stop]", {}); + + AddExpectationAndroid("new HttpMetric", {"https://google.com", "GET"}); + AddExpectationAndroid("HttpMetric.start", {}); + AddExpectationAndroid("HttpMetric.stop", {}); + + HttpMetric metric; + metric.Start("https://google.com", kHttpMethodGet); +} + +TEST_F(HttpMetricTest, TestCreateHttpMetricCppObject) { + HttpMetric metric; + + // No expectations, it shouldn't call into the native implementation. +} + +TEST_F(HttpMetricTest, TestCreateButNotStart) { + HttpMetric metric; + metric.Create("https://google.com", kHttpMethodGet); + + AddExpectationApple("-[FIRHTTPMetric initWithUrl:HTTPMethod:]", + {"https://google.com", "GET"}); + AddExpectationAndroid("new HttpMetric", {"https://google.com", "GET"}); +} + +TEST_F(HttpMetricTest, TestStartAfterCreate) { + HttpMetric metric; + metric.Create("https://google.com", kHttpMethodGet); + metric.StartCreatedHttpMetric(); + + AddExpectationApple("-[FIRHTTPMetric initWithUrl:HTTPMethod:]", + {"https://google.com", "GET"}); + AddExpectationApple("-[FIRHTTPMetric start]", {}); + // Stop isn't called as expected. + + AddExpectationAndroid("new HttpMetric", {"https://google.com", "GET"}); + AddExpectationAndroid("HttpMetric.start", {}); + // Stop isn't called as expected. +} + +TEST_F(HttpMetricTest, TestCreateGetRequestNullUrl) { + // No android or iOS expectation as it shouldn't even call through to the + // native layers. + + EXPECT_NO_THROW(HttpMetric metric(nullptr, kHttpMethodGet)); +} + +TEST_F(HttpMetricTest, TestIsStarted) { + AddExpectationApple("-[FIRHTTPMetric initWithUrl:HTTPMethod:]", + {"https://google.com", "GET"}); + AddExpectationApple("-[FIRHTTPMetric start]", {}); + + AddExpectationAndroid("new HttpMetric", {"https://google.com", "GET"}); + AddExpectationAndroid("HttpMetric.start", {}); + + HttpMetric metric("https://google.com", kHttpMethodGet); + EXPECT_TRUE(metric.is_started()); + + AddExpectationApple("-[FIRHTTPMetric stop]", {}); + AddExpectationAndroid("HttpMetric.stop", {}); + + metric.Stop(); + EXPECT_FALSE(metric.is_started()); +} + +TEST_F(HttpMetricTest, TestSetAttribute) { + HttpMetric metric("https://google.com", kHttpMethodGet); + reporter_.reset(); + AddExpectationApple("-[FIRHTTPMetric setValue:forAttribute:]", + {"my_value", "my_attribute"}); + AddExpectationApple("-[FIRHTTPMetric stop]", {}); + + AddExpectationAndroid("HttpMetric.putAttribute", + {"my_attribute", "my_value"}); + AddExpectationAndroid("HttpMetric.stop", {}); + + metric.SetAttribute("my_attribute", "my_value"); +} + +TEST_F(HttpMetricTest, TestSetAttributeStoppedHttpMetric) { + HttpMetric metric("https://google.com", kHttpMethodGet); + metric.Stop(); + reporter_.reset(); + + EXPECT_NO_THROW(metric.SetAttribute("my_attribute", "my_value")); +} + +TEST_F(HttpMetricTest, TestSetAttributeNullAttributeName) { + HttpMetric metric("https://google.com", kHttpMethodGet); + metric.Stop(); + reporter_.reset(); + + EXPECT_NO_THROW(metric.SetAttribute(nullptr, "my_value")); + EXPECT_EQ(metric.GetAttribute(nullptr), ""); +} + +TEST_F(HttpMetricTest, TestGetAttribute) { + HttpMetric metric("https://google.com", kHttpMethodGet); + metric.SetAttribute("my_attribute", "my_value"); + reporter_.reset(); + + AddExpectationApple("-[FIRHTTPMetric valueForAttribute:]", {"my_attribute"}); + AddExpectationApple("-[FIRHTTPMetric stop]", {}); + AddExpectationAndroid("HttpMetric.getAttribute", {"my_attribute"}); + AddExpectationAndroid("HttpMetric.stop", {}); + + metric.GetAttribute("my_attribute"); +} + +TEST_F(HttpMetricTest, TestGetAttributeNull) { + HttpMetric metric("https://google.com", kHttpMethodGet); + metric.Stop(); + reporter_.reset(); + + // Also ensures that this doesn't crash the process. + EXPECT_EQ("", metric.GetAttribute(nullptr)); +} + +TEST_F(HttpMetricTest, TestGetAttributeStoppedHttpMetric) { + HttpMetric metric("https://google.com", kHttpMethodGet); + metric.Stop(); + reporter_.reset(); + + // Also ensures that this doesn't crash the process. + EXPECT_EQ("", metric.GetAttribute("my_attribute")); +} + +TEST_F(HttpMetricTest, TestRemoveAttribute) { + HttpMetric metric("https://google.com", kHttpMethodGet); + reporter_.reset(); + AddExpectationApple("-[FIRHTTPMetric removeAttribute:]", {"my_attribute"}); + AddExpectationApple("-[FIRHTTPMetric stop]", {}); + + AddExpectationAndroid("HttpMetric.removeAttribute", {"my_attribute"}); + AddExpectationAndroid("HttpMetric.stop", {}); + + metric.SetAttribute("my_attribute", nullptr); +} + +TEST_F(HttpMetricTest, TestRemoveAttributeStoppedHttpMetric) { + HttpMetric metric("https://google.com", kHttpMethodGet); + metric.Stop(); + reporter_.reset(); + + EXPECT_NO_THROW(metric.SetAttribute("my_attribute", nullptr)); +} + +TEST_F(HttpMetricTest, TestSetHttpResponseCode) { + HttpMetric metric("https://google.com", kHttpMethodGet); + reporter_.reset(); + AddExpectationApple("-[FIRHTTPMetric setResponseCode:]", {"404"}); + AddExpectationApple("-[FIRHTTPMetric stop]", {}); + + AddExpectationAndroid("HttpMetric.setHttpResponseCode", {"404"}); + AddExpectationAndroid("HttpMetric.stop", {}); + + metric.set_http_response_code(404); +} + +TEST_F(HttpMetricTest, TestSetHttpResponseCodeStoppedHttpMetric) { + HttpMetric metric("https://google.com", kHttpMethodGet); + metric.Stop(); + reporter_.reset(); + + EXPECT_NO_THROW(metric.set_http_response_code(404)); +} + +TEST_F(HttpMetricTest, TestSetRequestPayloadSize) { + HttpMetric metric("https://google.com", kHttpMethodGet); + reporter_.reset(); + AddExpectationApple("-[FIRHTTPMetric setRequestPayloadSize:]", {"2000"}); + AddExpectationApple("-[FIRHTTPMetric stop]", {}); + + AddExpectationAndroid("HttpMetric.setRequestPayloadSize", {"2000"}); + AddExpectationAndroid("HttpMetric.stop", {}); + + metric.set_request_payload_size(2000); +} + +TEST_F(HttpMetricTest, TestSetRequestPayloadSizeStoppedHttpMetric) { + HttpMetric metric("https://google.com", kHttpMethodGet); + metric.Stop(); + reporter_.reset(); + + EXPECT_NO_THROW(metric.set_request_payload_size(2000)); +} + +TEST_F(HttpMetricTest, TestSetResponsePayloadSize) { + HttpMetric metric("https://google.com", kHttpMethodGet); + reporter_.reset(); + AddExpectationApple("-[FIRHTTPMetric setResponsePayloadSize:]", {"2000"}); + AddExpectationApple("-[FIRHTTPMetric stop]", {}); + + AddExpectationAndroid("HttpMetric.setResponsePayloadSize", {"2000"}); + AddExpectationAndroid("HttpMetric.stop", {}); + + metric.set_response_payload_size(2000); +} + +TEST_F(HttpMetricTest, TestSetResponsePayloadSizeStoppedHttpMetric) { + HttpMetric metric("https://google.com", kHttpMethodGet); + metric.Stop(); + reporter_.reset(); + + EXPECT_NO_THROW(metric.set_response_payload_size(2000)); +} + +TEST_F(HttpMetricTest, TestSetResponseContentType) { + HttpMetric metric("https://google.com", kHttpMethodGet); + reporter_.reset(); + AddExpectationApple("-[FIRHTTPMetric setResponseContentType:]", + {"application/json"}); + AddExpectationApple("-[FIRHTTPMetric stop]", {}); + + AddExpectationAndroid("HttpMetric.setResponseContentType", + {"application/json"}); + AddExpectationAndroid("HttpMetric.stop", {}); + + metric.set_response_content_type("application/json"); +} + +TEST_F(HttpMetricTest, TestSetResponseContentTypeNull) { + HttpMetric metric("https://google.com", kHttpMethodGet); + reporter_.reset(); + + AddExpectationApple("-[FIRHTTPMetric stop]", {}); + AddExpectationAndroid("HttpMetric.stop", {}); + + // This is a no-op. + EXPECT_NO_THROW(metric.set_response_content_type(nullptr)); +} + +TEST_F(HttpMetricTest, TestSetResponseContentTypeStoppedHttpMetric) { + HttpMetric metric("https://google.com", kHttpMethodGet); + metric.Stop(); + reporter_.reset(); + + EXPECT_NO_THROW(metric.set_response_content_type("application/json")); +} + +} // namespace performance +} // namespace firebase diff --git a/performance/src/include/firebase/performance.h b/performance/src/include/firebase/performance.h new file mode 100644 index 0000000000..13f05684c6 --- /dev/null +++ b/performance/src/include/firebase/performance.h @@ -0,0 +1,62 @@ +#ifndef FIREBASE_PERFORMANCE_CLIENT_CPP_SRC_INCLUDE_FIREBASE_PERFORMANCE_H_ +#define FIREBASE_PERFORMANCE_CLIENT_CPP_SRC_INCLUDE_FIREBASE_PERFORMANCE_H_ + +#include "firebase/app.h" +#include "firebase/performance/http_metric.h" +#include "firebase/performance/trace.h" + +/// @brief Namespace that encompasses all Firebase APIs. +namespace firebase { + +/// @brief Firebase Performance API. +/// +/// See the developer guides for general +/// information on using Firebase Performance in your apps. +/// +/// This library is experimental and is not currently officially supported. +namespace performance { + +/// @brief Initialize the Performance API. +/// +/// This must be called prior to calling any other methods in the +/// firebase::performance namespace. +/// +/// @param[in] app Default @ref firebase::App instance. +/// @return kInitResultSuccess if initialization succeeded, or +/// kInitResultFailedMissingDependency on Android if Google Play services is +/// not available on the current device. +/// +/// @see firebase::App::GetInstance(). +InitResult Initialize(const App& app); + +/// @brief Terminate the Performance API. +/// +/// Cleans up resources associated with the API. +/// +/// Do note this does not disable any of the automatic platform specific +/// instrumentation that firebase performance does. Please explicitly disable +/// performance monitoring through +/// firebase::performance::SetPerformanceCollectionEnabled(bool enabled) for +/// that to happen. +void Terminate(); + +/// @brief Determines if Performance collection is enabled. +/// @returns true if performance collection is enabled, false otherwise. +bool GetPerformanceCollectionEnabled(); + +/// @brief Sets whether performance collection is enabled for this app on this +/// device. +/// +/// This setting is persisted across app sessions. By default it is enabled. +/// +/// This can be called before firebase::performance::Initialize(const App& +/// app) on iOS, but that is not true on Android due to the way the SDK is +/// initialized. If you need to disable firebase performance before that, see +/// the documentation. +/// +/// @param[in] enabled true to enable performance collection, false to disable. +void SetPerformanceCollectionEnabled(bool enabled); +} // namespace performance +} // namespace firebase + +#endif // FIREBASE_PERFORMANCE_CLIENT_CPP_SRC_INCLUDE_FIREBASE_PERFORMANCE_H_ diff --git a/performance/src/include/firebase/performance/http_metric.h b/performance/src/include/firebase/performance/http_metric.h new file mode 100644 index 0000000000..ad09d48e58 --- /dev/null +++ b/performance/src/include/firebase/performance/http_metric.h @@ -0,0 +1,168 @@ +#ifndef FIREBASE_PERFORMANCE_CLIENT_CPP_SRC_INCLUDE_FIREBASE_PERFORMANCE_HTTP_METRIC_H_ +#define FIREBASE_PERFORMANCE_CLIENT_CPP_SRC_INCLUDE_FIREBASE_PERFORMANCE_HTTP_METRIC_H_ + +#include +#include + +namespace firebase { + +namespace performance { + +namespace internal { +class HttpMetricInternal; +} + +/// @brief Identifies different HTTP methods like GET, PUT and POST. +/// For more information about these, see +/// https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html +enum HttpMethod { + /// Use this when the request is using the GET HTTP Method. + kHttpMethodGet = 0, + /// Use this when the request is using the PUT HTTP Method. + kHttpMethodPut, + /// Use this when the request is using the POST HTTP Method. + kHttpMethodPost, + /// Use this when the request is using the DELETE HTTP Method. + kHttpMethodDelete, + /// Use this when the request is using the HEAD HTTP Method. + kHttpMethodHead, + /// Use this when the request is using the PATCH HTTP Method. + kHttpMethodPatch, + /// Use this when the request is using the OPTIONS HTTP Method. + kHttpMethodOptions, + /// Use this when the request is using the TRACE HTTP Method. + kHttpMethodTrace, + /// Use this when the request is using the CONNECT HTTP Method. + kHttpMethodConnect +}; + +/// @brief Create instances of this class to manually instrument http network +/// activity. You can also add custom attributes to the http metric +/// which help you segment your data based off of the attributes (e.g. level +/// or country). +/// +/// @note This API is not meant to be interacted with at high frequency because +/// almost all API calls involve interacting with Objective-C (on iOS) or with +/// JNI (on Android) as well as allocating a new ObjC or Java object with each +/// start/stop call on this API. +class HttpMetric { + public: + /// Construct an HttpMetric object. + /// + /// @note In order to start tracing a network request, call the Start(const + /// char* url, HttpMethod http_method) method on the object that's returned. + HttpMetric(); + + /// Construct an HttpMetric with the given url and http method and start + /// tracing the network request. + /// + /// @param url The string representation of the url for the network request + /// that's being instrumented with this http metric. + /// @param http_method The http method of the network request that's being + /// instrumented by this http metirc. + HttpMetric(const char* url, HttpMethod http_method); + + /// @brief Destroys any resources allocated for a HttpMetric object. + ~HttpMetric(); + + /// @brief Move constructor. + HttpMetric(HttpMetric&& other); + + /// @brief Move assignment operator. + HttpMetric& operator=(HttpMetric&& other); + + /// @brief Gets whether the HttpMetric associated with this object is started. + /// @return true if the HttpMetric is started, false if it's stopped or + /// canceled. + bool is_started(); + + /// @brief Cancels the http metric, and makes sure it isn't logged to the + /// backend. + void Cancel(); + + /// @brief Stops the network trace if it hasn't already been stopped, and logs + /// it to the backend. + void Stop(); + + /// @brief Starts a network trace with the given url and HTTP method. If a + /// network trace had previously been started using this object, the + /// previous one is stopped before starting this one. + /// + /// @note All attributes, metrics, and other metadata are cleared from the + /// metric. + void Start(const char* url, HttpMethod http_method); + +#if defined(INTERNAL_EXPERIMENTAL) || defined(SWIG) + // We need to decouple creating and starting an HttpMetric for the Unity + // implementation and so we expose these methods. + + /// @brief Creates a network trace with the given url and HTTP Method. If this + /// method is called when an HttpMetric is already active (which it shouldn't) + /// then the previous HttpMetric is cancelled. + void Create(const char* url, HttpMethod http_method); + + /// @brief Starts the HttpMetric that was created. Does nothing if there isn't + /// one created. + void StartCreatedHttpMetric(); +#endif // defined(INTERNAL_EXPERIMENTAL) || defined(SWIG) + + /// @brief Sets a custom attribute for the given network trace with the + /// given name and value. + /// + /// Setting the value to nullptr will delete a previously set attribute. + /// + /// @param[in] attribute_name The name of the attribute you want to set. + /// @param[in] attribute_value The value you want to set the attribute to. + /// + /// @note Call this method only after you've started a network trace, + /// otherwise the attributes will not be logged. + void SetAttribute(const char* attribute_name, const char* attribute_value); + + /// @brief Gets the value of the custom attribute identified by the given + /// name or nullptr if it hasn't been set. + /// + /// @param[in] attribute_name The name of the attribute you want to retrieve + /// the value for. + /// @return The value of the attribute if you've set it, if not an empty + /// string. + std::string GetAttribute(const char* attribute_name) const; + + /// @brief Sets the HTTP Response Code (for eg. 404 or 200) of the network + /// trace. + /// + /// @param[in] http_response_code The http response code of the network + /// response. + void set_http_response_code(int http_response_code); + + /// @brief Sets the Request Payload size in bytes for the network trace. + /// + /// @param[in] bytes The size of the request payload in bytes. + /// + /// @note Call this method only after you've started a network trace, + /// otherwise the request payload size will not be logged. + void set_request_payload_size(int64_t bytes); + + /// @brief Sets the Response Content Type of the network trace. + /// + /// @param[in] content_type The content type of the response of the http + /// request. + void set_response_content_type(const char* content_type); + + /// @brief Sets the Response Payload Size in bytes for the network trace. + /// + /// @param[in] bytes The size of the response payload in bytes. + void set_response_payload_size(int64_t bytes); + + private: + /// @cond FIREBASE_APP_INTERNAL + internal::HttpMetricInternal* internal_; + + HttpMetric(const HttpMetric& src); + HttpMetric& operator=(const HttpMetric& src); + /// @endcond +}; + +} // namespace performance +} // namespace firebase + +#endif // FIREBASE_PERFORMANCE_CLIENT_CPP_SRC_INCLUDE_FIREBASE_PERFORMANCE_HTTP_METRIC_H_ diff --git a/performance/src/include/firebase/performance/trace.h b/performance/src/include/firebase/performance/trace.h new file mode 100644 index 0000000000..3a19585f90 --- /dev/null +++ b/performance/src/include/firebase/performance/trace.h @@ -0,0 +1,134 @@ +#ifndef FIREBASE_PERFORMANCE_CLIENT_CPP_SRC_INCLUDE_FIREBASE_PERFORMANCE_TRACE_H_ +#define FIREBASE_PERFORMANCE_CLIENT_CPP_SRC_INCLUDE_FIREBASE_PERFORMANCE_TRACE_H_ + +#include +#include + +#include "firebase/performance.h" + +namespace firebase { + +namespace performance { + +namespace internal { +class TraceInternal; +} + +/// @brief Create instances of a trace to manually instrument any arbitrary +/// section of your code. You can also add custom attributes to the trace +/// which help you segment your data based off of the attributes (for eg. level +/// or country) and you also have the ability to add custom metrics (for eg. +/// cache hit count). +/// +/// @note This API is not meant to be interacted with at high frequency because +/// almost all API calls involve interacting with Objective-C (on iOS) or with +/// JNI (on Android). +class Trace { + public: + /// Constructs a trace object. In order to start a trace, please call the + /// Start("traceName") method on the object that this constructor returns. + explicit Trace(); + + /// Constructs a trace with the given name and starts it. + /// + /// @param name Name of the trace. The name cannot begin with _ and has to be + /// less than 100 characters in length. + explicit Trace(const char* name); + + /// @brief Destroys any resources allocated for a Trace object. + ~Trace(); + + /// @brief Move constructor. + Trace(Trace&& other); + + /// @brief Move assignment operator. + Trace& operator=(Trace&& other); + + /// @brief Gets whether the Trace associated with this object is started. + /// @return true if the Trace is started, false if it's stopped or + /// canceled. + bool is_started(); + + /// @brief Cancels the trace, so that it isn't logged to the backend. + void Cancel(); + + /// @brief Stops the trace and logs it to the backend. If you haven't + /// cancelled a trace, this happens automatically when the object is + /// destroyed. + void Stop(); + + /// @brief Starts a trace with the given name. If a trace had previously been + /// started using the same object, that previous trace is stopped before + /// starting this one. + /// + /// @note None of the associated attributes, metrics, and other metadata is + /// carried over to a new Trace if one is created. + void Start(const char* name); + +#if defined(INTERNAL_EXPERIMENTAL) || defined(SWIG) + // We need to decouple creating and starting an HttpMetric for the Unity + // implementation and so we expose these methods. + + /// @brief Creates a trace with the given name. If this method is called when + /// a Trace is already active (which it shouldn't) then the previous Trace is + /// cancelled. + void Create(const char* name); + + /// @brief Starts the Trace that was created. Does nothing if there isn't + /// one created. + void StartCreatedTrace(); +#endif // defined(INTERNAL_EXPERIMENTAL) || defined(SWIG) + + /// @brief Gets the value of the metric identified by the metric_name or 0 + /// if it hasn't yet been set. + /// + /// @param[in] matric_name The name of the metric to get the value of. + /// @return The previously set of the given metric or 0 if it hasn't been + /// set. + int64_t GetLongMetric(const char* metric_name) const; + + /// @brief Increments the existing value of the given metric by + /// increment_by or sets it to increment_by if the metric hasn't been set. + /// + /// @param[in] matric_name The name of the metric to increment the value + /// of. + /// @param[in] increment_by The value by which the metric should be + /// incremented. + void IncrementMetric(const char* metric_name, const int64_t increment_by); + + /// @brief Sets the value of the given metric to metric_value. + /// + /// @param[in] metric_name The name of the metric to set the value of. + /// @param[in] metric_value The value to set the metric to. + void SetMetric(const char* metric_name, const int64_t metric_value); + + /// @brief Creates a custom attribute for the given trace with the given name + /// and value. + /// + /// Setting the value to nullptr will delete a previously set attribute. + /// @param[in] attribute_name The name of the attribute you want to set. + /// @param[in] attribute_value The value you want to set the attribute to. + void SetAttribute(const char* attribute_name, const char* attribute_value); + + /// @brief Gets the value of the custom attribute identified by the given + /// name or nullptr if it hasn't been set. + /// + /// @param[in] attribute_name The name of the attribute you want to retrieve + /// the value for. + /// @return The value of the attribute if you've set it, if not an empty + /// string. + std::string GetAttribute(const char* attribute_name) const; + + private: + /// @cond FIREBASE_APP_INTERNAL + internal::TraceInternal* internal_; + + Trace(const Trace& src); + Trace& operator=(const Trace& src); + /// @endcond +}; + +} // namespace performance +} // namespace firebase + +#endif // FIREBASE_PERFORMANCE_CLIENT_CPP_SRC_INCLUDE_FIREBASE_PERFORMANCE_TRACE_H_ diff --git a/performance/src/ios/firebase_performance.mm b/performance/src/ios/firebase_performance.mm new file mode 100644 index 0000000000..617fb4cb7a --- /dev/null +++ b/performance/src/ios/firebase_performance.mm @@ -0,0 +1,48 @@ +#import + +#import "FIRPerformance.h" + +#include "app/src/assert.h" +#include "performance/src/include/firebase/performance.h" +#include "performance/src/performance_common.h" + +namespace firebase { +namespace performance { + +static bool g_initialized = false; + +// Initializes the API. +InitResult Initialize(const ::firebase::App& app) { + g_initialized = true; + internal::RegisterTerminateOnDefaultAppDestroy(); + // TODO(tdeshpande): Figure out if there are any situations where initalizing fails on iOS. + return InitResult::kInitResultSuccess; +} + +namespace internal { + +// Determines whether the analytics module is initialized. +bool IsInitialized() { return g_initialized; } + +} // namespace internal + +// Terminates the API. +void Terminate() { + internal::UnregisterTerminateOnDefaultAppDestroy(); + g_initialized = false; +} + +// Determines if performance collection is enabled. +bool GetPerformanceCollectionEnabled() { + FIREBASE_ASSERT_RETURN(false, internal::IsInitialized()); + return [[FIRPerformance sharedInstance] isDataCollectionEnabled]; +} + +// Sets performance collection enabled or disabled. +void SetPerformanceCollectionEnabled(bool enabled) { + FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized()); + [FIRPerformance sharedInstance].dataCollectionEnabled = enabled; +} + +} // namespace performance +} // namespace firebase diff --git a/performance/src/ios/http_metric.mm b/performance/src/ios/http_metric.mm new file mode 100644 index 0000000000..eb020f3d37 --- /dev/null +++ b/performance/src/ios/http_metric.mm @@ -0,0 +1,258 @@ +#import + +#import "FIRHTTPMetric.h" +#import "FIRPerformance.h" + +#include "app/src/assert.h" +#include "app/src/include/firebase/internal/common.h" +#include "performance/src/include/firebase/performance.h" +#import "performance/src/include/firebase/performance/http_metric.h" +#include "performance/src/performance_common.h" + +namespace firebase { +namespace performance { + +namespace internal { + +// The order in the array has to be exactly the same as the order in which they're declared in +// http_metric.h. +static const FIRHTTPMethod kHttpMethodToFIRHTTPmethod[] = { + FIRHTTPMethodGET, FIRHTTPMethodPUT, FIRHTTPMethodPOST, + FIRHTTPMethodDELETE, FIRHTTPMethodHEAD, FIRHTTPMethodPATCH, + FIRHTTPMethodOPTIONS, FIRHTTPMethodTRACE, FIRHTTPMethodCONNECT}; + +// Maps the firebase::performance::HttpMethod enum to its ObjC counterpart. +FIRHTTPMethod GetFIRHttpMethod(HttpMethod method) { + FIREBASE_ASSERT(method >= 0 && method < FIREBASE_ARRAYSIZE(kHttpMethodToFIRHTTPmethod)); + return kHttpMethodToFIRHTTPmethod[method]; +} + +// The Internal implementation of HttpMetric as recommended by the pImpl design pattern. +// This class is thread safe as long as we can assume that raw ponter access is atomic on any of the +// platforms this will be used on. +class HttpMetricInternal { + public: + explicit HttpMetricInternal() { current_http_metric_ = nil; } + + ~HttpMetricInternal() { + if (current_http_metric_) { + if (stop_on_destroy_) { + StopHttpMetric(); + } else { + CancelHttpMetric(); + } + } + } + + void CreateHttpMetric(const char* url, HttpMethod http_method, bool stop_on_destroy) { + FIREBASE_ASSERT_RETURN_VOID(IsInitialized()); + + stop_on_destroy_ = stop_on_destroy; + + if (WarnIf(url == nullptr, "URL cannot be nullptr. Unable to start HttpMetric.")) return; + + FIRHTTPMetric* http_metric = [[FIRHTTPMetric alloc] initWithURL:[NSURL URLWithString:@(url)] + HTTPMethod:GetFIRHttpMethod(http_method)]; + if (http_metric != nil) { + current_http_metric_ = http_metric; + } + } + + void StartCreatedHttpMetric() { [current_http_metric_ start]; } + + // Creates and starts an underlying ObjC HttpMetric. If a previous one exists, it is cancelled. + void CreateAndStartHttpMetric(const char* url, HttpMethod http_method) { + CreateHttpMetric(url, http_method, true); + StartCreatedHttpMetric(); + } + + // Gets whether the HttpMetric associated with this object is started. + bool IsHttpMetricCreated() { return current_http_metric_ != nil; } + + // Cancels the http metric, and makes sure it isn't logged to the backend. + void CancelHttpMetric() { + FIREBASE_ASSERT_RETURN_VOID(IsInitialized()); + if (WarnIfNotCreated("Cannot cancel HttpMetric.")) return; + + current_http_metric_ = nil; + } + + // Stops the network trace if it hasn't already been stopped, and logs it to the backend. + void StopHttpMetric() { + FIREBASE_ASSERT_RETURN_VOID(IsInitialized()); + if (WarnIfNotCreated("Cannot stop HttpMetric.")) return; + + [current_http_metric_ stop]; + current_http_metric_ = nil; + } + + // Creates a custom attribute for the given network trace with the + // given name and value. + void SetAttribute(const char* attribute_name, const char* attribute_value) { + FIREBASE_ASSERT_RETURN_VOID(IsInitialized()); + if (WarnIf(attribute_name == nullptr, "Cannot set value for null attribute.")) return; + if (WarnIfNotCreated("Cannot SetAttribute.")) return; + + if (attribute_value == nullptr) { + [current_http_metric_ removeAttribute:@(attribute_name)]; + } else { + [current_http_metric_ setValue:@(attribute_value) forAttribute:@(attribute_name)]; + } + } + + // Gets the value of the custom attribute identified by the given + // name or an empty string if it hasn't been set. + std::string GetAttribute(const char* attribute_name) const { + FIREBASE_ASSERT_RETURN("", IsInitialized()); + if (WarnIf(attribute_name == nullptr, "attribute_name cannot be null.")) return ""; + if (WarnIfNotCreated("Cannot GetAttribute.")) return ""; + + NSString* attribute_value = [current_http_metric_ valueForAttribute:@(attribute_name)]; + if (attribute_value != nil) { + return [attribute_value UTF8String]; + } else { + return ""; + } + } + + // Sets the HTTP Response Code (for eg. 404 or 200) of the network + // trace. + void set_http_response_code(int http_response_code) { + FIREBASE_ASSERT_RETURN_VOID(IsInitialized()); + if (WarnIfNotCreated("Cannot set_http_response_code.")) return; + + current_http_metric_.responseCode = http_response_code; + } + + // Sets the Request Payload size in bytes for the network trace. + void set_request_payload_size(int64_t bytes) { + FIREBASE_ASSERT_RETURN_VOID(IsInitialized()); + if (WarnIfNotCreated("Cannot set_request_payload_size.")) return; + + current_http_metric_.requestPayloadSize = bytes; + } + + // Sets the Response Content Type of the network trace. + void set_response_content_type(const char* content_type) { + FIREBASE_ASSERT_RETURN_VOID(IsInitialized()); + if (WarnIf(content_type == nullptr, "Cannot set null ResponseContentType.")) return; + if (WarnIfNotCreated("Cannot set_response_content_type.")) return; + + current_http_metric_.responseContentType = @(content_type); + } + + // Sets the Response Payload Size in bytes for the network trace. + void set_response_payload_size(int64_t bytes) { + FIREBASE_ASSERT_RETURN_VOID(IsInitialized()); + if (WarnIfNotCreated("Cannot set_response_payload_size.")) return; + current_http_metric_.responsePayloadSize = bytes; + } + + private: + FIRHTTPMetric* current_http_metric_ = nil; + + // The unity implementation doesn't stop the underlying ObjC trace, whereas + // the C++ implementation does. This flag is set when the ObjC trace is created + // to track whether it should be stopped before deallocating the object. + bool stop_on_destroy_ = false; + + bool WarnIfNotCreated(const char* warning_message_details) const { + if (!current_http_metric_) { + LogWarning("%s HttpMetric is not active. Please create a " + "new HttpMetric.", + warning_message_details); + return true; + } + return false; + } + + bool WarnIf(bool condition, const char* warning_message) const { + if (condition) LogWarning(warning_message); + return condition; + } +}; + +} // namespace internal + +HttpMetric::HttpMetric() { + FIREBASE_ASSERT(internal::IsInitialized()); + internal_ = new internal::HttpMetricInternal(); +} + +HttpMetric::HttpMetric(const char* url, HttpMethod http_method) { + FIREBASE_ASSERT(internal::IsInitialized()); + internal_ = new internal::HttpMetricInternal(); + internal_->CreateAndStartHttpMetric(url, http_method); +} + +HttpMetric::~HttpMetric() { delete internal_; } + +HttpMetric::HttpMetric(HttpMetric&& other) { + internal_ = other.internal_; + other.internal_ = nullptr; +} + +HttpMetric& HttpMetric::operator=(HttpMetric&& other) { + if (this != &other) { + delete internal_; + + internal_ = other.internal_; + other.internal_ = nullptr; + } + return *this; +} + +bool HttpMetric::is_started() { + // In the C++ API we never allow a situation where an underlying HttpMetric is created, but not + // started, which is why this check is sufficient. + return internal_->IsHttpMetricCreated(); +} + +void HttpMetric::Cancel() { internal_->CancelHttpMetric(); } + +void HttpMetric::Stop() { internal_->StopHttpMetric(); } + +void HttpMetric::Start(const char* url, HttpMethod http_method) { + internal_->StopHttpMetric(); + internal_->CreateAndStartHttpMetric(url, http_method); +} + +void HttpMetric::SetAttribute(const char* attribute_name, const char* attribute_value) { + internal_->SetAttribute(attribute_name, attribute_value); +} + +std::string HttpMetric::GetAttribute(const char* attribute_name) const { + return internal_->GetAttribute(attribute_name); +} + +void HttpMetric::set_http_response_code(int http_response_code) { + internal_->set_http_response_code(http_response_code); +} + +void HttpMetric::set_request_payload_size(int64_t bytes) { + internal_->set_request_payload_size(bytes); +} + +void HttpMetric::set_response_content_type(const char* content_type) { + internal_->set_response_content_type(content_type); +} + +void HttpMetric::set_response_payload_size(int64_t bytes) { + internal_->set_response_payload_size(bytes); +} + +// Used only in Unity. + +// We need to expose this functionality because the Unity implementation needs +// it. The Unity implementation (unlike the C++ implementation) can create an +// HttpMetric without starting it, similar to the ObjC and Java APIs, which is +// why this is needed. + +void HttpMetric::Create(const char* url, HttpMethod http_method) { + internal_->CreateHttpMetric(url, http_method, false); +} + +void HttpMetric::StartCreatedHttpMetric() { internal_->StartCreatedHttpMetric(); } + +} // namespace performance +} // namespace firebase diff --git a/performance/src/ios/trace.mm b/performance/src/ios/trace.mm new file mode 100644 index 0000000000..bc3122e91a --- /dev/null +++ b/performance/src/ios/trace.mm @@ -0,0 +1,235 @@ +#import + +#import "FIRPerformance.h" +#import "FIRTrace.h" + +#include "app/src/assert.h" +#include "performance/src/include/firebase/performance.h" +#import "performance/src/include/firebase/performance/trace.h" +#include "performance/src/performance_common.h" + +namespace firebase { +namespace performance { + +namespace internal { + +// The Internal implementation of Trace as recommended by the pImpl design pattern. +// This class is thread safe as long as we can assume that raw ponter access is atomic on any of the +// platforms this will be used on. +class TraceInternal { + public: + explicit TraceInternal() { current_trace_ = nil; } + + ~TraceInternal() { + if (current_trace_) { + if (stop_on_destroy_) { + StopTrace(); + } else { + CancelTrace(); + } + } + } + + // Creates a Trace using the ObjC implementation. If this method is called before + // stopping the previous trace, the previous trace is cancelled. + void CreateTrace(const char* name, bool stop_on_destroy) { + FIREBASE_ASSERT_RETURN_VOID(IsInitialized()); + + stop_on_destroy_ = stop_on_destroy; + + if (WarnIf(name == nullptr, "Cannot create trace. Name cannot be null.")) return; + + FIRTrace* created_trace = [[FIRPerformance sharedInstance] traceWithName:@(name)]; + if (created_trace != nil) { + current_trace_ = created_trace; + } + } + + void StartCreatedTrace() { [current_trace_ start]; } + + // Creates and Starts a Trace using the ObjC implementation. If this method is called before + // stopping the previous trace, the previous trace is cancelled. + void CreateAndStartTrace(const char* name) { + CreateTrace(name, true); + StartCreatedTrace(); + } + + // Stops the underlying ObjC trace if it has been started. Does nothing otherwise. + void StopTrace() { + FIREBASE_ASSERT_RETURN_VOID(IsInitialized()); + if (WarnIfNotCreated("Cannot stop Trace.")) return; + + [current_trace_ stop]; + current_trace_ = nil; + } + + // Cancels the currently running trace if one exists, which prevents it from being logged to the + // backend. + void CancelTrace() { + FIREBASE_ASSERT_RETURN_VOID(IsInitialized()); + if (WarnIfNotCreated("Cannot cancel Trace.")) return; + + current_trace_ = nil; + } + + // Returns whether there is a trace that is currently active. + bool IsTraceCreated() { return current_trace_ != nil; } + + // Sets a value for the given attribute for the given trace. + void SetAttribute(const char* attribute_name, const char* attribute_value) { + FIREBASE_ASSERT_RETURN_VOID(IsInitialized()); + if (WarnIf(attribute_name == nullptr, "Cannot SetAttribute for null attribute_name.")) return; + if (WarnIfNotCreated("Cannot SetAttribute.")) return; + + if (attribute_value == nullptr) { + [current_trace_ removeAttribute:@(attribute_name)]; + } else { + [current_trace_ setValue:@(attribute_value) forAttribute:@(attribute_name)]; + } + } + + // Gets the value of the custom attribute identified by the given + // name or nullptr if it hasn't been set. + std::string GetAttribute(const char* attribute_name) const { + FIREBASE_ASSERT_RETURN("", IsInitialized()); + if (WarnIf(attribute_name == nullptr, "Cannot GetAttribute for null attribute_name.")) + return ""; + if (WarnIfNotCreated("Cannot GetAttribute.")) return ""; + + NSString* attribute_value = [current_trace_ valueForAttribute:@(attribute_name)]; + if (attribute_value != nil) { + return [attribute_value UTF8String]; + } else { + return ""; + } + } + + // Gets the value of the metric identified by the metric_name or 0 if it hasn't yet been set. + int64_t GetLongMetric(const char* metric_name) const { + FIREBASE_ASSERT_RETURN(0, IsInitialized()); + if (WarnIf(metric_name == nullptr, "Cannot GetLongMetric for null metric_name.")) return 0; + if (WarnIfNotCreated("Cannot GetLongMetric.")) return 0; + + return [current_trace_ valueForIntMetric:@(metric_name)]; + } + + // Increments the existing value of the given metric by increment_by or sets + // it to increment_by if the metric hasn't been set. + void IncrementMetric(const char* metric_name, const int64_t increment_by) { + FIREBASE_ASSERT_RETURN_VOID(IsInitialized()); + if (WarnIf(metric_name == nullptr, "Cannot IncrementMetric for null metric_name.")) return; + if (WarnIfNotCreated("Cannot IncrementMetric.")) return; + + [current_trace_ incrementMetric:@(metric_name) byInt:increment_by]; + } + + // Sets the value of the given metric to metric_value. + void SetMetric(const char* metric_name, const int64_t metric_value) { + FIREBASE_ASSERT_RETURN_VOID(IsInitialized()); + if (WarnIf(metric_name == nullptr, "Cannot SetMetric for null metric_name.")) return; + if (WarnIfNotCreated("Cannot SetMetric.")) return; + + [current_trace_ setIntValue:metric_value forMetric:@(metric_name)]; + } + + private: + FIRTrace* current_trace_ = nil; + + // The unity implementation doesn't stop the underlying ObjC trace, whereas + // the C++ implementation does. This flag is set when the ObjC trace is created + // to track whether it should be stopped before deallocating the object. + bool stop_on_destroy_ = false; + + bool WarnIfNotCreated(const char* warning_message_details) const { + if (!current_trace_) { + LogWarning("%s Trace is not active. Please create a new " + "Trace.", + warning_message_details); + return true; + } + return false; + } + + bool WarnIf(bool condition, const char* warning_message) const { + if (condition) LogWarning(warning_message); + return condition; + } +}; + +} // namespace internal + +Trace::Trace() { + FIREBASE_ASSERT(internal::IsInitialized()); + internal_ = new internal::TraceInternal(); +} + +Trace::Trace(const char* name) { + FIREBASE_ASSERT(internal::IsInitialized()); + internal_ = new internal::TraceInternal(); + internal_->CreateAndStartTrace(name); +} + +Trace::~Trace() { delete internal_; } + +Trace::Trace(Trace&& other) { + internal_ = other.internal_; + other.internal_ = nullptr; +} + +Trace& Trace::operator=(Trace&& other) { + if (this != &other) { + delete internal_; + + internal_ = other.internal_; + other.internal_ = nullptr; + } + return *this; +} + +bool Trace::is_started() { + // In the C++ API we never allow a situation where an underlying Trace is created, but not + // started, which is why this check is sufficient. + return internal_->IsTraceCreated(); +} + +void Trace::Cancel() { internal_->CancelTrace(); } + +void Trace::Stop() { internal_->StopTrace(); } + +void Trace::Start(const char* name) { + internal_->StopTrace(); + internal_->CreateAndStartTrace(name); +} + +void Trace::SetAttribute(const char* attribute_name, const char* attribute_value) { + internal_->SetAttribute(attribute_name, attribute_value); +} + +std::string Trace::GetAttribute(const char* attribute_name) const { + return internal_->GetAttribute(attribute_name); +} + +int64_t Trace::GetLongMetric(const char* metric_name) const { + return internal_->GetLongMetric(metric_name); +} + +void Trace::IncrementMetric(const char* metric_name, const int64_t increment_by) { + internal_->IncrementMetric(metric_name, increment_by); +} + +void Trace::SetMetric(const char* metric_name, const int64_t metric_value) { + internal_->SetMetric(metric_name, metric_value); +} + +// Used only in Unity. + +// We need to expose this functionality because the Unity implementation needs +// it. The Unity implementation (unlike the C++ implementation) can create a +// Trace without starting it, similar to the ObjC and Java APIs, which is +// why this is needed. + +void Trace::Create(const char* name) { internal_->CreateTrace(name, false); } +void Trace::StartCreatedTrace() { internal_->StartCreatedTrace(); } + +} // namespace performance +} // namespace firebase diff --git a/performance/src/performance_common.cc b/performance/src/performance_common.cc new file mode 100644 index 0000000000..4dea3af725 --- /dev/null +++ b/performance/src/performance_common.cc @@ -0,0 +1,60 @@ +#include "performance/src/performance_common.h" + +#include + +#include "app/src/cleanup_notifier.h" +#include "app/src/util.h" +#include "performance/src/include/firebase/performance.h" + +// Register the module initializer. +FIREBASE_APP_REGISTER_CALLBACKS( + performance, + { + if (app == ::firebase::App::GetInstance()) { + return firebase::performance::Initialize(*app); + } + return kInitResultSuccess; + }, + { + if (app == ::firebase::App::GetInstance()) { + firebase::performance::Terminate(); + } + }); + +namespace firebase { +namespace performance { +namespace internal { + +const char* kPerformanceModuleName = "performance"; + +void RegisterTerminateOnDefaultAppDestroy() { + if (!AppCallback::GetEnabledByName(kPerformanceModuleName)) { + CleanupNotifier* cleanup_notifier = + CleanupNotifier::FindByOwner(App::GetInstance()); + assert(cleanup_notifier); + cleanup_notifier->RegisterObject( + const_cast(kPerformanceModuleName), [](void*) { + LogError( + "performance::Terminate() should be called before default app is " + "destroyed."); + if (firebase::performance::internal::IsInitialized()) { + firebase::performance::Terminate(); + } + }); + } +} + +void UnregisterTerminateOnDefaultAppDestroy() { + if (!AppCallback::GetEnabledByName(kPerformanceModuleName) && + firebase::performance::internal::IsInitialized()) { + CleanupNotifier* cleanup_notifier = + CleanupNotifier::FindByOwner(App::GetInstance()); + assert(cleanup_notifier); + cleanup_notifier->UnregisterObject( + const_cast(kPerformanceModuleName)); + } +} + +} // namespace internal +} // namespace performance +} // namespace firebase diff --git a/performance/src/performance_common.h b/performance/src/performance_common.h new file mode 100644 index 0000000000..269876bd85 --- /dev/null +++ b/performance/src/performance_common.h @@ -0,0 +1,30 @@ +// This file contains the declarations of common functions that deal with +// initialization of the Firebase Performance C++ API and how it deals with the +// lifecycle of the common FirebaseApp instance. Some of these functions are +// implemented in a platform independent manner, while some are implemented +// specific to each platform. +#ifndef FIREBASE_PERFORMANCE_CLIENT_CPP_SRC_PERFORMANCE_COMMON_H_ +#define FIREBASE_PERFORMANCE_CLIENT_CPP_SRC_PERFORMANCE_COMMON_H_ + +namespace firebase { +namespace performance { +namespace internal { + +extern const char* kPerformanceModuleName; + +// Returns whether the performance module is initialized. +// This is implemented per platform. +bool IsInitialized(); + +// Registers a cleanup task for this module if auto-initialization is disabled. +void RegisterTerminateOnDefaultAppDestroy(); + +// Unregisters the cleanup task for this module if auto-initialization is +// disabled. +void UnregisterTerminateOnDefaultAppDestroy(); + +} // namespace internal +} // namespace performance +} // namespace firebase + +#endif // FIREBASE_PERFORMANCE_CLIENT_CPP_SRC_PERFORMANCE_COMMON_H_ diff --git a/performance/src/performance_test.cc b/performance/src/performance_test.cc new file mode 100644 index 0000000000..931fafbddd --- /dev/null +++ b/performance/src/performance_test.cc @@ -0,0 +1,87 @@ +#if defined(FIREBASE_ANDROID_FOR_DESKTOP) +#define __ANDROID__ +#include + +#include "testing/run_all_tests.h" +#endif // defined(FIREBASE_ANDROID_FOR_DESKTOP) + +#include "app/src/include/firebase/app.h" +#include "app/tests/include/firebase/app_for_testing.h" +#include "performance/src/include/firebase/performance.h" +#include "performance/src/include/firebase/performance/http_metric.h" +#include "performance/src/include/firebase/performance/trace.h" +#include "performance/src/performance_common.h" + +#ifdef __ANDROID__ +#include "app/src/util_android.h" +#endif // __ANDROID__ + +#if defined(FIREBASE_ANDROID_FOR_DESKTOP) +#undef __ANDROID__ +#endif // defined(FIREBASE_ANDROID_FOR_DESKTOP) + +#include "gtest/gtest.h" +#include "gmock/gmock.h" +#include "testing/config.h" +#include "testing/reporter.h" +#include "absl/memory/memory.h" + +namespace firebase { +namespace performance { + +class PerformanceTest : public ::testing::Test { + protected: + void SetUp() override { + firebase::testing::cppsdk::ConfigSet("{}"); + reporter_.reset(); + + firebase_app_.reset(testing::CreateApp()); + AddExpectationAndroid("FirebasePerformance.getInstance", {}); + performance::Initialize(*firebase_app_); + } + + void TearDown() override { + firebase::testing::cppsdk::ConfigReset(); + Terminate(); + firebase_app_.reset(); + EXPECT_THAT(reporter_.getFakeReports(), + ::testing::Eq(reporter_.getExpectations())); + } + + void AddExpectationAndroid(const char* fake, + std::initializer_list args) { + reporter_.addExpectation(fake, "", firebase::testing::cppsdk::kAndroid, + args); + } + + void AddExpectationApple(const char* fake, + std::initializer_list args) { + reporter_.addExpectation(fake, "", firebase::testing::cppsdk::kIos, args); + } + + std::unique_ptr firebase_app_; + firebase::testing::cppsdk::Reporter reporter_; +}; + +TEST_F(PerformanceTest, TestDestroyDefaultApp) { + EXPECT_TRUE(internal::IsInitialized()); + firebase_app_.reset(); + EXPECT_FALSE(internal::IsInitialized()); +} + +TEST_F(PerformanceTest, TestSetPerformanceCollectionEnabled) { + AddExpectationApple("-[FIRPerformance setDataCollectionEnabled:]", {"YES"}); + AddExpectationAndroid("FirebasePerformance.setPerformanceCollectionEnabled", + {"true"}); + SetPerformanceCollectionEnabled(true); +} + +TEST_F(PerformanceTest, TestSetPerformanceCollectionDisabled) { + AddExpectationApple("-[FIRPerformance setDataCollectionEnabled:]", {"NO"}); + AddExpectationAndroid("FirebasePerformance.setPerformanceCollectionEnabled", + {"false"}); + SetPerformanceCollectionEnabled(false); +} + +} // namespace performance +} // namespace firebase diff --git a/performance/src/trace_test.cc b/performance/src/trace_test.cc new file mode 100644 index 0000000000..fc06297512 --- /dev/null +++ b/performance/src/trace_test.cc @@ -0,0 +1,329 @@ +#if defined(FIREBASE_ANDROID_FOR_DESKTOP) +#ifndef __ANDROID__ +#define __ANDROID__ +#endif // __ANDROID__ +#include + +#include "testing/run_all_tests.h" +#endif // defined(FIREBASE_ANDROID_FOR_DESKTOP) + +#include "app/src/include/firebase/app.h" +#include "app/tests/include/firebase/app_for_testing.h" +#include "performance/src/include/firebase/performance.h" +#include "performance/src/include/firebase/performance/http_metric.h" +#include "performance/src/include/firebase/performance/trace.h" +#include "performance/src/performance_common.h" + +#ifdef __ANDROID__ +#include "app/src/util_android.h" +#endif // __ANDROID__ + +#if defined(FIREBASE_ANDROID_FOR_DESKTOP) +#undef __ANDROID__ +#endif // defined(FIREBASE_ANDROID_FOR_DESKTOP) + +#include "app/src/util.h" +#include "gtest/gtest.h" +#include "gmock/gmock.h" +#include "testing/config.h" +#include "testing/reporter.h" +#include "testing/ticker.h" +#include "absl/memory/memory.h" + +namespace firebase { +namespace performance { + +class TraceTest : public ::testing::Test { + protected: + void SetUp() override { + firebase::testing::cppsdk::ConfigSet("{}"); + reporter_.reset(); + firebase_app_.reset(testing::CreateApp()); + AddExpectationAndroid("FirebasePerformance.getInstance", {}); + performance::Initialize(*firebase_app_); + } + + void TearDown() override { + firebase::testing::cppsdk::ConfigReset(); + Terminate(); + firebase_app_.reset(); + EXPECT_THAT(reporter_.getFakeReports(), + ::testing::Eq(reporter_.getExpectations())); + } + + void AddExpectationApple(const char* fake, + std::initializer_list args) { + reporter_.addExpectation(fake, "", firebase::testing::cppsdk::kIos, args); + } + + void AddExpectationAndroid(const char* fake, + std::initializer_list args) { + reporter_.addExpectation(fake, "", firebase::testing::cppsdk::kAndroid, + args); + } + + std::unique_ptr firebase_app_; + firebase::testing::cppsdk::Reporter reporter_; +}; + +TEST_F(TraceTest, TestCreateAndDestroyTrace) { + AddExpectationApple("-[FIRTrace initTraceWithName:]", {"my_codepath"}); + AddExpectationApple("-[FIRTrace start]", {}); + AddExpectationApple("-[FIRTrace stop]", {}); + + AddExpectationAndroid("new Trace", {"my_codepath"}); + AddExpectationAndroid("Trace.start", {}); + AddExpectationAndroid("Trace.stop", {}); + + Trace trace("my_codepath"); +} + +TEST_F(TraceTest, TestDelayedCreateTrace) { + AddExpectationApple("-[FIRTrace initTraceWithName:]", {"my_codepath"}); + AddExpectationApple("-[FIRTrace start]", {}); + AddExpectationApple("-[FIRTrace stop]", {}); + + AddExpectationAndroid("new Trace", {"my_codepath"}); + AddExpectationAndroid("Trace.start", {}); + AddExpectationAndroid("Trace.stop", {}); + + Trace trace; + trace.Start("my_codepath"); +} + +TEST_F(TraceTest, TestCreateTraceCppObject) { + Trace trace; + + // No expectations, it shouldn't call into the native implementations. +} + +TEST_F(TraceTest, TestTraceCreateButNotStart) { + AddExpectationApple("-[FIRTrace initTraceWithName:]", {"my_codepath"}); + AddExpectationAndroid("new Trace", {"my_codepath"}); + + Trace trace; + trace.Create("my_codepath"); +} + +TEST_F(TraceTest, TesteTraceStartAfterCreate) { + AddExpectationApple("-[FIRTrace initTraceWithName:]", {"my_codepath"}); + AddExpectationApple("-[FIRTrace start]", {}); + // Stop isn't called as expected. + + AddExpectationAndroid("new Trace", {"my_codepath"}); + AddExpectationAndroid("Trace.start", {}); + // Stop isn't called as expected. + + Trace trace; + trace.Create("my_codepath"); + trace.StartCreatedTrace(); +} + +TEST_F(TraceTest, TestCreateTraceWithNullName) { + EXPECT_NO_THROW(Trace trace(nullptr)); +} + +TEST_F(TraceTest, TestIsStarted) { + AddExpectationApple("-[FIRTrace initTraceWithName:]", {"my_codepath"}); + AddExpectationApple("-[FIRTrace start]", {}); + + AddExpectationAndroid("new Trace", {"my_codepath"}); + AddExpectationAndroid("Trace.start", {}); + + Trace trace("my_codepath"); + EXPECT_TRUE(trace.is_started()); + + AddExpectationApple("-[FIRTrace stop]", {}); + AddExpectationAndroid("Trace.stop", {}); + + trace.Stop(); + EXPECT_FALSE(trace.is_started()); +} + +TEST_F(TraceTest, TestSetAttribute) { + Trace trace("my_codepath"); + reporter_.reset(); + + AddExpectationApple("-[FIRTrace setValue:forAttribute:]", + {"my_value", "my_attribute"}); + AddExpectationApple("-[FIRTrace stop]", {}); + + AddExpectationAndroid("Trace.putAttribute", {"my_attribute", "my_value"}); + AddExpectationAndroid("Trace.stop", {}); + + trace.SetAttribute("my_attribute", "my_value"); +} + +TEST_F(TraceTest, TestSetAttributeNullName) { + Trace trace("my_codepath"); + reporter_.reset(); + + AddExpectationApple("-[FIRTrace stop]", {}); + AddExpectationAndroid("Trace.stop", {}); + + EXPECT_NO_THROW(trace.SetAttribute(nullptr, "my_value")); +} + +TEST_F(TraceTest, TestSetAttributeNotStarted) { + Trace trace("my_codepath"); + trace.Stop(); + reporter_.reset(); + + EXPECT_NO_THROW(trace.SetAttribute("my_attribute", "my_value")); +} + +TEST_F(TraceTest, TestGetAttribute) { + Trace trace("my_codepath"); + reporter_.reset(); + AddExpectationApple("-[FIRTrace valueForAttribute:]", {"my_attribute"}); + AddExpectationApple("-[FIRTrace stop]", {}); + + AddExpectationAndroid("Trace.getAttribute", {"my_attribute"}); + AddExpectationAndroid("Trace.stop", {}); + + trace.GetAttribute("my_attribute"), ::testing::Eq("my_value"); +} + +TEST_F(TraceTest, TestGetAttributeNullName) { + Trace trace("my_codepath"); + reporter_.reset(); + + AddExpectationApple("-[FIRTrace stop]", {}); + AddExpectationAndroid("Trace.stop", {}); + + EXPECT_NO_THROW(trace.GetAttribute(nullptr)); +} + +TEST_F(TraceTest, TestGetAttributeNotStarted) { + Trace trace("my_codepath"); + trace.Stop(); + reporter_.reset(); + + EXPECT_NO_THROW(trace.GetAttribute("my_attribute")); +} + +TEST_F(TraceTest, TestRemoveAttribute) { + Trace trace("my_codepath"); + reporter_.reset(); + AddExpectationApple("-[FIRTrace removeAttribute:]", {"my_attribute"}); + AddExpectationApple("-[FIRTrace stop]", {}); + + AddExpectationAndroid("Trace.removeAttribute", {"my_attribute"}); + AddExpectationAndroid("Trace.stop", {}); + + trace.SetAttribute("my_attribute", nullptr); +} + +TEST_F(TraceTest, TestRemoveAttributeNullName) { + Trace trace("my_codepath"); + reporter_.reset(); + + AddExpectationApple("-[FIRTrace stop]", {}); + AddExpectationAndroid("Trace.stop", {}); + + EXPECT_NO_THROW(trace.SetAttribute(nullptr, nullptr)); +} + +TEST_F(TraceTest, TestRemoveAttributeNotStarted) { + Trace trace("my_codepath"); + trace.Stop(); + reporter_.reset(); + + EXPECT_NO_THROW(trace.SetAttribute(nullptr, nullptr)); +} + +TEST_F(TraceTest, TestSetMetric) { + Trace trace("my_codepath"); + reporter_.reset(); + AddExpectationApple("-[FIRTrace setIntValue:forMetric:]", + {"my_metric", "2000"}); + AddExpectationApple("-[FIRTrace stop]", {}); + + AddExpectationAndroid("Trace.putMetric", {"my_metric", "2000"}); + AddExpectationAndroid("Trace.stop", {}); + + trace.SetMetric("my_metric", 2000); +} + +TEST_F(TraceTest, TestSetMetricNullName) { + Trace trace("my_codepath"); + reporter_.reset(); + + AddExpectationApple("-[FIRTrace stop]", {}); + AddExpectationAndroid("Trace.stop", {}); + + EXPECT_NO_THROW(trace.SetMetric(nullptr, 2000)); +} + +TEST_F(TraceTest, TestSetMetricNotStarted) { + Trace trace("my_codepath"); + trace.Stop(); + reporter_.reset(); + + EXPECT_NO_THROW(trace.SetMetric("my_metric", 2000)); +} + +TEST_F(TraceTest, TestGetLongMetric) { + Trace trace("my_codepath"); + trace.SetMetric("my_metric", 2000); + + reporter_.reset(); + AddExpectationApple("-[FIRTrace valueForIntMetric:]", {"my_metric"}); + AddExpectationApple("-[FIRTrace stop]", {}); + + AddExpectationAndroid("Trace.getLongMetric", {"my_metric"}); + AddExpectationAndroid("Trace.stop", {}); + + trace.GetLongMetric("my_metric"); +} + +TEST_F(TraceTest, TestGetLongMetricNullName) { + Trace trace("my_codepath"); + reporter_.reset(); + + AddExpectationApple("-[FIRTrace stop]", {}); + AddExpectationAndroid("Trace.stop", {}); + + EXPECT_NO_THROW(trace.GetLongMetric(nullptr)); +} + +TEST_F(TraceTest, TestGetLongMetricNotStarted) { + Trace trace("my_codepath"); + trace.Stop(); + reporter_.reset(); + + EXPECT_NO_THROW(trace.GetLongMetric("my_metric")); +} + +TEST_F(TraceTest, TestIncrementMetric) { + Trace trace("my_codepath"); + reporter_.reset(); + AddExpectationApple("-[FIRTrace incrementMetric:byInt:]", {"my_metric", "5"}); + AddExpectationApple("-[FIRTrace stop]", {}); + + AddExpectationAndroid("Trace.incrementMetric", {"my_metric", "5"}); + AddExpectationAndroid("Trace.stop", {}); + + trace.IncrementMetric("my_metric", 5); +} + +TEST_F(TraceTest, TestIncrementMetricNullName) { + Trace trace("my_codepath"); + reporter_.reset(); + + AddExpectationApple("-[FIRTrace stop]", {}); + AddExpectationAndroid("Trace.stop", {}); + + EXPECT_NO_THROW(trace.IncrementMetric(nullptr, 2000)); +} + +TEST_F(TraceTest, TestIncrementMetricNotStarted) { + Trace trace("my_codepath"); + trace.Stop(); + reporter_.reset(); + + EXPECT_NO_THROW(trace.IncrementMetric("my_metric", 2000)); +} + +} // namespace performance +} // namespace firebase diff --git a/performance/src_ios/fake/FIRHTTPMetric.h b/performance/src_ios/fake/FIRHTTPMetric.h new file mode 100644 index 0000000000..b9a8129fb9 --- /dev/null +++ b/performance/src_ios/fake/FIRHTTPMetric.h @@ -0,0 +1,68 @@ +#import + +#import "FIRPerformanceAttributable.h" + +/* Different HTTP methods. */ +typedef NS_ENUM(NSInteger, FIRHTTPMethod) { + FIRHTTPMethodGET NS_SWIFT_NAME(get), + FIRHTTPMethodPUT NS_SWIFT_NAME(put), + FIRHTTPMethodPOST NS_SWIFT_NAME(post), + FIRHTTPMethodDELETE NS_SWIFT_NAME(delete), + FIRHTTPMethodHEAD NS_SWIFT_NAME(head), + FIRHTTPMethodPATCH NS_SWIFT_NAME(patch), + FIRHTTPMethodOPTIONS NS_SWIFT_NAME(options), + FIRHTTPMethodTRACE NS_SWIFT_NAME(trace), + FIRHTTPMethodCONNECT NS_SWIFT_NAME(connect) +} NS_SWIFT_NAME(HTTPMethod); + +/** + * FIRHTTPMetric object can be used to make the SDK record information about a HTTP network request. + */ +NS_SWIFT_NAME(HTTPMetric) +@interface FIRHTTPMetric : NSObject + +/** + * Creates HTTPMetric object for a network request. + * @param URL The URL for which the metrics are recorded. + * @param httpMethod HTTP method used by the request. + */ +- (nullable instancetype)initWithURL:(nonnull NSURL *)URL + HTTPMethod:(FIRHTTPMethod)httpMethod NS_SWIFT_NAME(init(url:httpMethod:)); + +/** + * Use `initWithURL:HTTPMethod:` for Objective-C and `init(url:httpMethod:)` for Swift. + */ +- (nonnull instancetype)init NS_UNAVAILABLE; + +/** + * @brief HTTP Response code. Values are greater than 0. + */ +@property(nonatomic, assign) NSInteger responseCode; + +/** + * @brief Size of the request payload. + */ +@property(nonatomic, assign) long requestPayloadSize; + +/** + * @brief Size of the response payload. + */ +@property(nonatomic, assign) long responsePayloadSize; + +/** + * @brief HTTP Response content type. + */ +@property(nonatomic, nullable, copy) NSString *responseContentType; + +/** + * Marks the start time of the request. + */ +- (void)start; + +/** + * Marks the end time of the response and queues the network request metric on the device for + * transmission. Check the logs if the metric is valid. + */ +- (void)stop; + +@end diff --git a/performance/src_ios/fake/FIRHTTPMetric.mm b/performance/src_ios/fake/FIRHTTPMetric.mm new file mode 100644 index 0000000000..1bddfe2af5 --- /dev/null +++ b/performance/src_ios/fake/FIRHTTPMetric.mm @@ -0,0 +1,160 @@ +#import "performance/src_ios/fake/FIRHTTPMetric.h" + +#include "testing/reporter_impl.h" + +@interface FIRHTTPMetric () + +/* A placeholder URLRequest used for SDK metric tracking. */ +@property(nonatomic) NSURLRequest *URLRequest; + +/** Custom attributes. */ +@property(nonatomic) NSMutableDictionary *customAttributes; + +/** Network request start time. */ +@property(nonatomic, readwrite) NSDate *startTime; + +/** Network request stop time. */ +@property(nonatomic, readwrite) NSDate *stopTime; + +/** @return YES if http metric has been started, NO if it hasn't been started or has been stopped. + */ +@property(nonatomic, assign, getter=isStarted) BOOL started; + +@end + +@implementation FIRHTTPMetric + +- (nullable instancetype)initWithURL:(nonnull NSURL *)URL HTTPMethod:(FIRHTTPMethod)httpMethod { + NSMutableURLRequest *URLRequest = [[NSMutableURLRequest alloc] initWithURL:URL]; + NSString *HTTPMethodString = nil; + switch (httpMethod) { + case FIRHTTPMethodGET: + HTTPMethodString = @"GET"; + break; + + case FIRHTTPMethodPUT: + HTTPMethodString = @"PUT"; + break; + + case FIRHTTPMethodPOST: + HTTPMethodString = @"POST"; + break; + + case FIRHTTPMethodHEAD: + HTTPMethodString = @"HEAD"; + break; + + case FIRHTTPMethodDELETE: + HTTPMethodString = @"DELETE"; + break; + + case FIRHTTPMethodPATCH: + HTTPMethodString = @"PATCH"; + break; + + case FIRHTTPMethodOPTIONS: + HTTPMethodString = @"OPTIONS"; + break; + + case FIRHTTPMethodTRACE: + HTTPMethodString = @"TRACE"; + break; + + case FIRHTTPMethodCONNECT: + HTTPMethodString = @"CONNECT"; + break; + } + [URLRequest setHTTPMethod:HTTPMethodString]; + + if (HTTPMethodString) { + FakeReporter->AddReport("-[FIRHTTPMetric initWithUrl:HTTPMethod:]", + {[URL.absoluteString UTF8String], [HTTPMethodString UTF8String]}); + } + + if (URLRequest && HTTPMethodString != nil) { + self = [super init]; + _URLRequest = [URLRequest copy]; + return self; + } + + NSLog(@"Invalid URL"); + return nil; +} + +- (void)start { + if (!self.isStarted) { + self.startTime = [NSDate date]; + self.started = YES; + NSLog(@"Started http metric: %@", [self description]); + FakeReporter->AddReport("-[FIRHTTPMetric start]", {}); + } +} + +- (void)stop { + if (self.isStarted) { + self.stopTime = [NSDate date]; + self.started = NO; + NSLog(@"Stopped http metric: %@.", [self description]); + FakeReporter->AddReport("-[FIRHTTPMetric stop]", {}); + } +} + +#pragma mark - Custom attributes related methods + +- (NSDictionary *)attributes { + return [self.customAttributes copy]; +} + +- (void)setValue:(NSString *)value forAttribute:(nonnull NSString *)attribute { + FakeReporter->AddReport("-[FIRHTTPMetric setValue:forAttribute:]", + {[value UTF8String], [attribute UTF8String]}); + self.customAttributes[attribute] = value; +} + +- (NSString *)valueForAttribute:(NSString *)attribute { + FakeReporter->AddReport("-[FIRHTTPMetric valueForAttribute:]", {[attribute UTF8String]}); + return self.customAttributes[attribute]; +} + +- (void)removeAttribute:(NSString *)attribute { + FakeReporter->AddReport("-[FIRHTTPMetric removeAttribute:]", {[attribute UTF8String]}); + [self.customAttributes removeObjectForKey:attribute]; +} + +#pragma mark - Property setter overrides for unit tests + +- (void)setResponseCode:(NSInteger)responseCode { + FakeReporter->AddReport("-[FIRHTTPMetric setResponseCode:]", + {[[NSString stringWithFormat:@"%ld", responseCode] UTF8String]}); + _responseCode = responseCode; +} + +- (void)setRequestPayloadSize:(long)requestPayloadSize { + FakeReporter->AddReport("-[FIRHTTPMetric setRequestPayloadSize:]", + {[[NSString stringWithFormat:@"%ld", requestPayloadSize] UTF8String]}); + _requestPayloadSize = requestPayloadSize; +} + +- (void)setResponsePayloadSize:(long)responsePayloadSize { + FakeReporter->AddReport("-[FIRHTTPMetric setResponsePayloadSize:]", + {[[NSString stringWithFormat:@"%ld", responsePayloadSize] UTF8String]}); + _responsePayloadSize = responsePayloadSize; +} + +- (void)setResponseContentType:(NSString *)responseContentType { + FakeReporter->AddReport("-[FIRHTTPMetric setResponseContentType:]", + {[responseContentType UTF8String]}); + _responseContentType = responseContentType; +} + +- (NSString *)description { + return + [NSString stringWithFormat: + @"url: %@, response code: %ld, requestPayloadSize: %ld, responsePayloadSize: " + @"%ld, responseContentType: %@, startTime: %@, stopTime: %@", + self.URLRequest.URL.absoluteString, (long)self.responseCode, + self.requestPayloadSize, self.responsePayloadSize, self.responseContentType, + [self.startTime description], [self.stopTime description]]; +} + +@end diff --git a/performance/src_ios/fake/FIRPerformance.h b/performance/src_ios/fake/FIRPerformance.h new file mode 100644 index 0000000000..83fc04df60 --- /dev/null +++ b/performance/src_ios/fake/FIRPerformance.h @@ -0,0 +1,61 @@ +#import + +#import "FIRTrace.h" + +/** This class allows you to configure the Firebase Performance Reporting SDK. It also provides the + * interfaces to create timers and enable or disable automatic metrics capture. + * + * This SDK uses a Firebase Instance ID token to identify the app instance and periodically sends + * data to the Firebase backend. (see `[FIRInstanceID getIDWithHandler:]`). + * To stop the periodic sync, call `[FIRInstanceID deleteIDWithHandler:]` and + * either disable this SDK or set FIRPerformance.dataCollectionEnabled to NO. + */ +NS_EXTENSION_UNAVAILABLE("FirebasePerformance does not support app extensions at this time.") +NS_SWIFT_NAME(Performance) +@interface FIRPerformance : NSObject + +/** + * Controls the capture of performance data. When this value is set to NO, none of the performance + * data will sent to the server. Default is YES. + * + * This setting is persisted, and is applied on future invocations of your application. Once + * explicitly set, it overrides any settings in your Info.plist. + */ +@property(nonatomic, assign, getter=isDataCollectionEnabled) BOOL dataCollectionEnabled; + +/** + * Controls the instrumentation of the app to capture performance data. Setting this value to NO has + * immediate effect only if it is done so before calling [FIRApp configure]. Otherwise it takes + * effect after the app starts again the next time. + * + * If set to NO, the app will not be instrumented to collect performance + * data (in scenarios like app_start, networking monitoring). Default is YES. + * + * This setting is persisted, and is applied on future invocations of your application. Once + * explicitly set, it overrides any settings in your Info.plist. + */ +@property(nonatomic, assign, getter=isInstrumentationEnabled) BOOL instrumentationEnabled; + +/** @return The shared instance. */ ++ (nonnull instancetype)sharedInstance NS_SWIFT_NAME(sharedInstance()); + +/** + * Creates an instance of FIRTrace after creating the shared instance of FIRPerformance. The trace + * will automatically be started on a successful creation of the instance. The |name| of the trace + * cannot be an empty string. + * + * @param name The name of the Trace. + * @return The FIRTrace object. + */ ++ (nullable FIRTrace *)startTraceWithName:(nonnull NSString *)name NS_SWIFT_NAME(startTrace(name:)); + +/** + * Creates an instance of FIRTrace. This API does not start the trace. To start the trace, use the + * -start API on the returned |FIRTrace| object. The |name| cannot be an empty string. + * + * @param name The name of the Trace. + * @return The FIRTrace object. + */ +- (nullable FIRTrace *)traceWithName:(nonnull NSString *)name NS_SWIFT_NAME(trace(name:)); + +@end diff --git a/performance/src_ios/fake/FIRPerformance.mm b/performance/src_ios/fake/FIRPerformance.mm new file mode 100644 index 0000000000..0640896739 --- /dev/null +++ b/performance/src_ios/fake/FIRPerformance.mm @@ -0,0 +1,81 @@ +#import "performance/src_ios/fake/FIRPerformance.h" + +#import "performance/src_ios/fake/FIRTrace.h" +#include "testing/reporter_impl.h" + +@interface FIRTrace (Private_Methods) + +- (instancetype)initTraceWithName:(NSString *)name; + +@end + +@interface FIRPerformance () + +/** Dictionary for global custom attributes. */ +@property(nonatomic) NSMutableDictionary *customAttributes; + +@end + +@implementation FIRPerformance + +#pragma mark - Public methods + ++ (instancetype)sharedInstance { + static FIRPerformance *firebasePerformance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + firebasePerformance = [[FIRPerformance alloc] init]; + }); + return firebasePerformance; +} + ++ (FIRTrace *)startTraceWithName:(NSString *)name { + FIRTrace *trace = [[self sharedInstance] traceWithName:name]; + [trace start]; + return trace; +} + +- (FIRTrace *)traceWithName:(NSString *)name { + FIRTrace *trace = [[FIRTrace alloc] initTraceWithName:name]; + return trace; +} + +- (void)setDataCollectionEnabled:(BOOL)dataCollectionEnabled { + if (dataCollectionEnabled) { + FakeReporter->AddReport("-[FIRPerformance setDataCollectionEnabled:]", {"YES"}); + } else { + FakeReporter->AddReport("-[FIRPerformance setDataCollectionEnabled:]", {"NO"}); + } + + _dataCollectionEnabled = dataCollectionEnabled; +} + +#pragma mark - Internal methods + +- (instancetype)init { + self = [super init]; + if (self) { + _customAttributes = [[NSMutableDictionary alloc] init]; + } + return self; +} + +#pragma mark - Custom attributes related methods + +- (NSDictionary *)attributes { + return [self.customAttributes copy]; +} + +- (void)setValue:(NSString *)value forAttribute:(nonnull NSString *)attribute { + self.customAttributes[attribute] = value; +} + +- (NSString *)valueForAttribute:(NSString *)attribute { + return self.customAttributes[attribute]; +} + +- (void)removeAttribute:(NSString *)attribute { + [self.customAttributes removeObjectForKey:attribute]; +} + +@end diff --git a/performance/src_ios/fake/FIRPerformanceAttributable.h b/performance/src_ios/fake/FIRPerformanceAttributable.h new file mode 100644 index 0000000000..7f5e0d96ff --- /dev/null +++ b/performance/src_ios/fake/FIRPerformanceAttributable.h @@ -0,0 +1,37 @@ +#import + +/** Defines the interface that allows adding/removing attributes to any object. + */ +NS_SWIFT_NAME(PerformanceAttributable) +@protocol FIRPerformanceAttributable + +/** List of attributes. */ +@property(nonatomic, nonnull, readonly) NSDictionary *attributes; + +/** + * Sets a value as a string for the specified attribute. Updates the value of the attribute if a + * value had already existed. + * + * @param value The value that needs to be set/updated for an attribute. If the length of the value + * exceeds the maximum allowed, the value will be truncated to the maximum allowed. + * @param attribute The name of the attribute. If the length of the value exceeds the maximum + * allowed, the value will be truncated to the maximum allowed. + */ +- (void)setValue:(nonnull NSString *)value forAttribute:(nonnull NSString *)attribute; + +/** + * Reads the value for the specified attribute. If the attribute does not exist, returns nil. + * + * @param attribute The name of the attribute. + * @return The value for the attribute. Returns nil if the attribute does not exist. + */ +- (nullable NSString *)valueForAttribute:(nonnull NSString *)attribute; + +/** + * Removes an attribute from the list. Does nothing if the attribute does not exist. + * + * @param attribute The name of the attribute. + */ +- (void)removeAttribute:(nonnull NSString *)attribute; + +@end diff --git a/performance/src_ios/fake/FIRTrace.h b/performance/src_ios/fake/FIRTrace.h new file mode 100644 index 0000000000..1971df33fd --- /dev/null +++ b/performance/src_ios/fake/FIRTrace.h @@ -0,0 +1,91 @@ +#import + +#import "FIRPerformanceAttributable.h" + +/** + * FIRTrace objects contain information about a "Trace", which is a sequence of steps. Traces can be + * used to measure the time taken for a sequence of steps. + * Traces also include "Counters". Counters are used to track information which is cumulative in + * nature (e.g., Bytes downloaded). Counters are scoped to an FIRTrace object. + */ +NS_EXTENSION_UNAVAILABLE("FirebasePerformance does not support app extensions at this time.") +NS_SWIFT_NAME(Trace) +@interface FIRTrace : NSObject + +/** @brief Name of the trace. */ +@property(nonatomic, copy, readonly, nonnull) NSString *name; + +/** @brief Not a valid initializer. */ +- (nonnull instancetype)init NS_UNAVAILABLE; + +/** + * Starts the trace. + */ +- (void)start; + +/** + * Stops the trace if the trace is active. + */ +- (void)stop; + +/** + * Increments the counter for the provided counter name by 1. If it is a new counter name, the + * counter value will be initialized to 1. Does nothing if the trace has not been started or has + * already been stopped. + * + * Note: This API has been deprecated. Please use -incrementMetric:byInt: instead. + * + * @param counterName The name of the counter to increment. + */ +- (void)incrementCounterNamed:(nonnull NSString *)counterName + NS_SWIFT_NAME(incrementCounter(named:)) + DEPRECATED_MSG_ATTRIBUTE("Please use -incrementMetric:byInt: instead."); + +/** + * Increments the counter for the provided counter name with the provided value. If it is a new + * counter name, the counter value will be initialized to the value. Does nothing if the trace has + * not been started or has already been stopped. + * + * Note: This API has been deprecated. Please use -incrementMetric:byInt: instead. + * + * @param counterName The name of the counter to increment. + * @param incrementValue The value the counter would be incremented with. + */ +- (void)incrementCounterNamed:(nonnull NSString *)counterName + by:(NSInteger)incrementValue + NS_SWIFT_NAME(incrementCounter(named:by:)) + DEPRECATED_MSG_ATTRIBUTE("Please use -incrementMetric:byInt: instead."); + +#pragma mark - Metrics API + +/** + * Atomically increments the metric for the provided metric name with the provided value. If it is a + * new metric name, the metric value will be initialized to the value. Does nothing if the trace + * has not been started or has already been stopped. + * + * @param metricName The name of the metric to increment. + * @param incrementValue The value to increment the metric by. + */ +- (void)incrementMetric:(nonnull NSString *)metricName + byInt:(int64_t)incrementValue NS_SWIFT_NAME(incrementMetric(_:by:)); + +/** + * Gets the value of the metric for the provided metric name. If the metric doesn't exist, a 0 is + * returned. + * + * @param metricName The name of metric whose value to get. + * @return The value of the given metric or 0 if it hasn't yet been set. + */ +- (int64_t)valueForIntMetric:(nonnull NSString *)metricName NS_SWIFT_NAME(valueForMetric(_:)); + +/** + * Sets the value of the metric for the provided metric name to the provided value. Does nothing if + * the trace has not been started or has already been stopped. + * + * @param metricName The name of the metric to set. + * @param value The value to set the metric to. + */ +- (void)setIntValue:(int64_t)value + forMetric:(nonnull NSString *)metricName NS_SWIFT_NAME(setValue(_:forMetric:)); + +@end diff --git a/performance/src_ios/fake/FIRTrace.mm b/performance/src_ios/fake/FIRTrace.mm new file mode 100644 index 0000000000..55993d895d --- /dev/null +++ b/performance/src_ios/fake/FIRTrace.mm @@ -0,0 +1,173 @@ +#import "performance/src_ios/fake/FIRTrace.h" + +#import "performance/src_ios/fake/FIRPerformance.h" +#include "testing/reporter_impl.h" + +@interface FIRPerformance (Private_Methods) + +- (NSDictionary *)customAttributes; + +@end + +@interface FIRTrace () + +@property(nonatomic, copy, readwrite) NSString *name; + +/** Custom attributes managed internally. */ +@property(nonatomic) NSMutableDictionary *customAttributes; + +/** Counters/Metrics for this fake version of FIRTrace. */ +@property(nonatomic) NSMutableDictionary *counterList; + +/** Trace start time. */ +@property(nonatomic, readwrite) NSDate *startTime; + +/** Trace stop time. */ +@property(nonatomic, readwrite) NSDate *stopTime; + +@end + +@implementation FIRTrace + +- (instancetype)initTraceWithName:(NSString *)name { + self = [super init]; + if (self) { + _name = [name copy]; + _counterList = [[NSMutableDictionary alloc] init]; + _customAttributes = [[NSMutableDictionary alloc] init]; + } + + FakeReporter->AddReport("-[FIRTrace initTraceWithName:]", {[name UTF8String]}); + + return self; +} + +- (instancetype)init { + NSAssert(NO, @"Not a valid initializer."); + return nil; +} + +#pragma mark - Public instance methods + +- (void)start { + if (![self isTraceStarted]) { + self.startTime = [NSDate date]; + NSLog(@"Starting trace: %@", self); + } else { + NSLog(@"Trace has already been started."); + } + + FakeReporter->AddReport("-[FIRTrace start]", {}); +} + +- (void)stop { + if ([self isTraceActive]) { + self.stopTime = [NSDate date]; + NSLog(@"Stopping trace: %@", self); + } else { + NSLog(@"Trace hasn't been started or has already been stopped."); + } + + FakeReporter->AddReport("-[FIRTrace stop]", {}); +} + +#pragma mark - Counter related methods + +- (void)incrementCounterNamed:(nonnull NSString *)counterName { + [self incrementCounterNamed:counterName by:1]; +} + +- (void)incrementCounterNamed:(NSString *)counterName by:(NSInteger)incrementValue { + [self incrementMetric:counterName byInt:incrementValue]; +} + +#pragma mark - Metrics related methods + +- (int64_t)valueForIntMetric:(nonnull NSString *)metricName { + NSNumber *counterValue = self.counterList[metricName]; + int64_t counterLongLongValue = counterValue ? [counterValue longLongValue] : 0; + FakeReporter->AddReport("-[FIRTrace valueForIntMetric:]", {[metricName UTF8String]}); + return counterLongLongValue; +} + +- (void)setIntValue:(int64_t)value forMetric:(nonnull NSString *)metricName { + FakeReporter->AddReport( + "-[FIRTrace setIntValue:forMetric:]", + {[metricName UTF8String], [[NSString stringWithFormat:@"%lld", value] UTF8String]}); + if ([self isTraceActive]) { + self.counterList[metricName] = [NSNumber numberWithLongLong:value]; + } +} + +- (void)incrementMetric:(nonnull NSString *)metricName byInt:(int64_t)incrementValue { + FakeReporter->AddReport( + "-[FIRTrace incrementMetric:byInt:]", + {[metricName UTF8String], [[NSString stringWithFormat:@"%lld", incrementValue] UTF8String]}); + if ([self isTraceActive]) { + NSNumber *counterValue = self.counterList[metricName]; + counterValue = counterValue ? counterValue : [NSNumber numberWithLongLong:0]; + counterValue = [NSNumber numberWithLongLong:[counterValue longLongValue] + incrementValue]; + self.counterList[metricName] = counterValue; + } +} + +#pragma mark - Custom attributes related methods + +- (NSDictionary *)attributes { + return [self.customAttributes copy]; +} + +- (void)setValue:(NSString *)value forAttribute:(nonnull NSString *)attribute { + FakeReporter->AddReport("-[FIRTrace setValue:forAttribute:]", + {[value UTF8String], [attribute UTF8String]}); + self.customAttributes[attribute] = value; +} + +- (NSString *)valueForAttribute:(NSString *)attribute { + FakeReporter->AddReport("-[FIRTrace valueForAttribute:]", {[attribute UTF8String]}); + return self.customAttributes[attribute]; +} + +- (void)removeAttribute:(NSString *)attribute { + FakeReporter->AddReport("-[FIRTrace removeAttribute:]", {[attribute UTF8String]}); + [self.customAttributes removeObjectForKey:attribute]; +} + +#pragma mark - Utility methods + +/** + * Tells us if the trace has been started. + * + * @return YES if trace has been started, NO otherwise. + */ +- (BOOL)isTraceStarted { + return self.startTime != nil; +} + +/** + * Tells us if the trace has been stopped. + * + * @return YES if trace has been stopped, NO otherwise. + */ +- (BOOL)isTraceStopped { + return (self.startTime != nil && self.stopTime != nil); +} + +/** + * Tells us if the trace is active (has been started, but not stopped). + * + * @return YES if trace is active, NO otherwise. + */ +- (BOOL)isTraceActive { + return (self.startTime != nil && self.stopTime == nil); +} + +- (NSString *)description { + return [NSString stringWithFormat:@"Trace with name: %@, startTime: %@, stopTime: %@, metrics: " + @"%@, attributes: %@, global attributes: %@", + self.name, self.startTime, self.stopTime, self.counterList, + self.customAttributes, + [[FIRPerformance sharedInstance] customAttributes]]; +} + +@end diff --git a/performance/src_java/fake/com/google/firebase/perf/FirebasePerformance.java b/performance/src_java/fake/com/google/firebase/perf/FirebasePerformance.java new file mode 100644 index 0000000000..d5c25b384b --- /dev/null +++ b/performance/src_java/fake/com/google/firebase/perf/FirebasePerformance.java @@ -0,0 +1,118 @@ +package com.google.firebase.perf; + +import androidx.annotation.StringDef; +import com.google.firebase.perf.metrics.HttpMetric; +import com.google.firebase.perf.metrics.Trace; +import com.google.firebase.testing.cppsdk.FakeReporter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** The Firebase Performance API. This is a fake implementation. */ +public class FirebasePerformance { + private static volatile FirebasePerformance firebasePerformance; + private static boolean performanceCollectionEnabled = true; + + /** Valid HttpMethods for manual network APIs */ + @StringDef({ + HttpMethod.GET, + HttpMethod.PUT, + HttpMethod.POST, + HttpMethod.DELETE, + HttpMethod.HEAD, + HttpMethod.PATCH, + HttpMethod.OPTIONS, + HttpMethod.TRACE, + HttpMethod.CONNECT + }) + @Retention(RetentionPolicy.SOURCE) + public @interface HttpMethod { + String GET = "GET"; + String PUT = "PUT"; + String POST = "POST"; + String DELETE = "DELETE"; + String HEAD = "HEAD"; + String PATCH = "PATCH"; + String OPTIONS = "OPTIONS"; + String TRACE = "TRACE"; + String CONNECT = "CONNECT"; + } + + /** + * Returns a singleton of FirebasePerformance. + * + * @return the singleton FirebasePerformance object. + */ + public static FirebasePerformance getInstance() { + FakeReporter.addReport("FirebasePerformance.getInstance"); + if (firebasePerformance == null) { + synchronized (FirebasePerformance.class) { + if (firebasePerformance == null) { + firebasePerformance = new FirebasePerformance(); + } + } + } + return firebasePerformance; + } + + /** + * Creates a Trace object with given name and start the trace. + * + * @param traceName name of the trace. Requires no leading or trailing whitespace, no leading + * underscore [_] character, max length of {@value #MAX_TRACE_NAME_LENGTH} characters. + * @return the new Trace object. + */ + public static Trace startTrace(String traceName) { + Trace trace = Trace.create(traceName); + trace.start(); + return trace; + } + + /** + * Enables or disables performance monitoring. In the fake version this is just a boolean that's + * in memory that toggles this state. + * + * @param enable Should performance monitoring be enabled + */ + public void setPerformanceCollectionEnabled(boolean enable) { + FakeReporter.addReport( + "FirebasePerformance.setPerformanceCollectionEnabled", Boolean.toString(enable)); + performanceCollectionEnabled = enable; + } + + /** + * Determines whether performance monitoring is enabled or disabled. This respects the Firebase + * Performance specific values first, and if these aren't set, uses the Firebase wide data + * collection switch. + * + * @return 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 in Gradle properties. + */ + public boolean isPerformanceCollectionEnabled() { + FakeReporter.addReport("FirebasePerformance.isPerformanceCollectionEnabled"); + return performanceCollectionEnabled; + } + + /** + * Creates a Trace object with given name. + * + * @param traceName name of the trace, requires no leading or trailing whitespace, no leading + * underscore '_' character, max length is {@value #MAX_TRACE_NAME_LENGTH} characters. + * @return the new Trace object. + */ + public Trace newTrace(String traceName) { + return Trace.create(traceName); + } + + /** + * Creates a HttpMetric object for collecting network performance data for one request/response + * + * @param url a valid url String, cannot be empty + * @param httpMethod One of the values GET, PUT, POST, DELETE, HEAD, PATCH, OPTIONS, TRACE, or + * CONNECT + * @return the new HttpMetric object. + */ + public HttpMetric newHttpMetric(String url, @HttpMethod String httpMethod) { + return new HttpMetric(url, httpMethod); + } +} diff --git a/performance/src_java/fake/com/google/firebase/perf/FirebasePerformanceAttributable.java b/performance/src_java/fake/com/google/firebase/perf/FirebasePerformanceAttributable.java new file mode 100644 index 0000000000..fc38e5f714 --- /dev/null +++ b/performance/src_java/fake/com/google/firebase/perf/FirebasePerformanceAttributable.java @@ -0,0 +1,54 @@ +package com.google.firebase.perf; + +import androidx.annotation.Nullable; +import com.google.firebase.perf.metrics.Trace; +import java.util.Map; + +/** Attribute functions needed for Traces and HttpMetrics */ +public interface FirebasePerformanceAttributable { + + /** Maximum allowed number of attributes allowed in a trace. */ + int MAX_TRACE_CUSTOM_ATTRIBUTES = 5; + /** Maximum allowed length of the Key of the {@link Trace} attribute */ + int MAX_ATTRIBUTE_KEY_LENGTH = 40; + /** Maximum allowed length of the Value of the {@link Trace} attribute */ + int MAX_ATTRIBUTE_VALUE_LENGTH = 100; + /** Maximum allowed length of the name of the {@link Trace} */ + int MAX_TRACE_NAME_LENGTH = 100; + + /** + * Sets a String value for the specified attribute in the object's list of attributes. The maximum + * number of attributes that can be added are {@value #MAX_TRACE_CUSTOM_ATTRIBUTES}. + * + * @param attribute name of the attribute. Leading and trailing white spaces if any, will be + * removed from the name. The name must start with letter, must only contain alphanumeric + * characters and underscore and must not start with "firebase_", "google_" and "ga_. The max + * length is limited to {@value #MAX_ATTRIBUTE_KEY_LENGTH} + * @param value value of the attribute. The max length is limited to {@value + * #MAX_ATTRIBUTE_VALUE_LENGTH} + */ + void putAttribute(String attribute, String value); + + /** + * Returns the value of an attribute. + * + * @param attribute name of the attribute to fetch the value for + * @return The value of the attribute if it exists or null otherwise. + */ + @Nullable + String getAttribute(String attribute); + + /** + * Removes the attribute from the list of attributes. + * + * @param attribute name of the attribute to be removed from the global pool. + */ + void removeAttribute(String attribute); + + /** + * Returns the map of all the attributes currently added + * + * @return map of attributes and its values currently added + */ + Map getAttributes(); +} diff --git a/performance/src_java/fake/com/google/firebase/perf/metrics/HttpMetric.java b/performance/src_java/fake/com/google/firebase/perf/metrics/HttpMetric.java new file mode 100644 index 0000000000..18b305224d --- /dev/null +++ b/performance/src_java/fake/com/google/firebase/perf/metrics/HttpMetric.java @@ -0,0 +1,140 @@ +package com.google.firebase.perf.metrics; + +import androidx.annotation.Nullable; +import android.util.Log; +import com.google.apps.tiktok.testing.errorprone.SuppressViolation; +import com.google.firebase.perf.FirebasePerformance.HttpMethod; +import com.google.firebase.perf.FirebasePerformanceAttributable; +import com.google.firebase.testing.cppsdk.FakeReporter; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Metric used to collect data for network requests/responses. A new object must be used for every + * request/response. This class is not thread safe. + */ +public class HttpMetric implements FirebasePerformanceAttributable { + + private static final String LOG_TAG = "FirebasePerformanceFake"; + + private final Map attributes; + private boolean isStopped = false; + + /** Constructs a new NetworkRequestMetricBuilder object given a String url value */ + public HttpMetric(String url, @HttpMethod String httpMethod) { + FakeReporter.addReport("new HttpMetric", url, httpMethod); + attributes = new ConcurrentHashMap<>(); + } + + /** + * Sets the httpResponse code of the request + * + * @param unusedResponseCode valid values are greater than 0. Invalid usage will be logged. + */ + public void setHttpResponseCode(int unusedResponseCode) { + FakeReporter.addReport("HttpMetric.setHttpResponseCode", Integer.toString(unusedResponseCode)); + } + + /** + * Sets the size of the request payload + * + * @param bytes valid values are greater than or equal to 0. Invalid usage will be logged. + */ + public void setRequestPayloadSize(long bytes) { + FakeReporter.addReport("HttpMetric.setRequestPayloadSize", Long.toString(bytes)); + } + + /** + * Sets the size of the response payload + * + * @param bytes valid values are greater than or equal to 0. Invalid usage will be logged. + */ + public void setResponsePayloadSize(long bytes) { + FakeReporter.addReport("HttpMetric.setResponsePayloadSize", Long.toString(bytes)); + } + + /** + * Content type of the response such as text/html, application/json, etc... + * + * @param contentType valid string of MIME type. Invalid usage will be logged. + */ + public void setResponseContentType(@Nullable String contentType) { + FakeReporter.addReport("HttpMetric.setResponseContentType", contentType); + } + + /** Marks the start time of the request */ + public void start() { + FakeReporter.addReport("HttpMetric.start"); + } + + /** + * Marks the end time of the response and queues the network request metric on the device for + * transmission. Check logcat for transmission info. + */ + public void stop() { + FakeReporter.addReport("HttpMetric.stop"); + if (!isStopped) { + isStopped = true; + } else { + Log.w(LOG_TAG, "Tried to stop http metric after it had already been stopped."); + } + } + + /** + * Sets a String value for the specified attribute. Updates the value of the attribute if the + * attribute already exists. If the HttpMetric has been stopped, this method returns without + * adding the attribute. The maximum number of attributes that can be added to a HttpMetric are + * {@value #MAX_TRACE_CUSTOM_ATTRIBUTES}. + * + * @param attribute name of the attribute + * @param value value of the attribute + */ + @SuppressViolation("catch_specific_exceptions") + @Override + public void putAttribute(String attribute, String value) { + FakeReporter.addReport("HttpMetric.putAttribute", attribute, value); + attribute = attribute.trim(); + value = value.trim(); + attributes.put(attribute, value); + } + + /** + * Removes an already added attribute from the HttpMetric. If the HttpMetric has already been + * stopped, this method returns without removing the attribute. + * + * @param attribute name of the attribute to be removed from the running Traces. + */ + @Override + public void removeAttribute(String attribute) { + FakeReporter.addReport("HttpMetric.removeAttribute", attribute); + if (isStopped) { + Log.e(LOG_TAG, "Can't remove a attribute from a HttpMetric that's stopped."); + return; + } + attributes.remove(attribute); + } + + /** + * Returns the value of an attribute. + * + * @param attribute name of the attribute to fetch the value for + * @return The value of the attribute if it exists or null otherwise. + */ + @Override + @Nullable + public String getAttribute(String attribute) { + FakeReporter.addReport("HttpMetric.getAttribute", attribute); + return attributes.get(attribute); + } + + /** + * Returns the map of all the attributes added to this HttpMetric. + * + * @return map of attributes and its values currently added to this HttpMetric + */ + @Override + public Map getAttributes() { + return new HashMap<>(attributes); + } +} diff --git a/performance/src_java/fake/com/google/firebase/perf/metrics/Trace.java b/performance/src_java/fake/com/google/firebase/perf/metrics/Trace.java new file mode 100644 index 0000000000..5fbbd90546 --- /dev/null +++ b/performance/src_java/fake/com/google/firebase/perf/metrics/Trace.java @@ -0,0 +1,253 @@ +package com.google.firebase.perf.metrics; + +import android.support.annotation.Keep; +import androidx.annotation.Nullable; +import android.util.Log; +import com.google.firebase.perf.FirebasePerformanceAttributable; +import com.google.firebase.testing.cppsdk.FakeReporter; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * Trace allows you to set beginning and end of a certain action in your app. Fake implementation. + */ +public class Trace implements FirebasePerformanceAttributable { + private final String name; + private final Map counters; + private final Map attributes; + private static final String LOG_TAG = "FirebasePerformanceAndroidFake"; + + @Nullable private Date startTime = null; + @Nullable private Date endTime = null; + + /** + * Creates a Trace object with given name. + * + * @param name name of the trace object + */ + public static Trace create(String name) { + return new Trace(name); + } + + private Trace(String name) { + FakeReporter.addReport("new Trace", name); + this.name = name; + counters = new HashMap<>(); + attributes = new HashMap<>(); + } + + /** Starts this trace. */ + @Keep + public void start() { + FakeReporter.addReport("Trace.start"); + startTime = new Date(); + } + + /** Stops this trace. */ + @Keep + public void stop() { + FakeReporter.addReport("Trace.stop"); + if (!hasStarted()) { + Log.e(LOG_TAG, String.format("Trace '%s' has not been started so unable to stop!", name)); + return; + } + if (isStopped()) { + Log.e(LOG_TAG, String.format("Trace '%s' has already stopped, should not stop again!", name)); + return; + } + + endTime = new Date(); + if (!name.isEmpty()) { + Log.i(LOG_TAG, "Logged trace with name: " + name); + } else { + Log.e(LOG_TAG, "Trace name is empty, no log is sent to server"); + } + } + + /** + * Atomically increments the metric with the given name in this trace by the incrementBy value. 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. + * + * @param metricName Name of the metric to be incremented. Requires no leading or trailing + * whitespace, no leading underscore [_] character, max length of 32 characters. + * @param incrementBy Amount by which the metric has to be incremented. Note: The fake + * implementation is not thread safe. + */ + @Keep + public void incrementMetric(String metricName, long incrementBy) { + FakeReporter.addReport("Trace.incrementMetric", metricName, Long.toString(incrementBy)); + Long currentValue = counters.get(metricName); + currentValue = currentValue != null ? currentValue : 0; + counters.put(metricName, currentValue + incrementBy); + } + + /** + * Gets the value of the metric with the given name in the current trace. If a metric with the + * given name doesn't exist, it is NOT created and a 0 is returned. This method is atomic. + * + * @param metricName Name of the metric to get. Requires no leading or trailing whitespace, no + * leading underscore '_' character, max length is 32 characters. + * @return Value of the metric or 0 if it hasn't yet been set. + */ + @Keep + public long getLongMetric(String metricName) { + FakeReporter.addReport("Trace.getLongMetric", metricName); + if (metricName != null) { + Long metricValue = counters.get(metricName); + metricValue = metricValue != null ? metricValue : 0; + return metricValue; + } + + return 0; + } + + /** + * Sets the value of the metric with the given name in this trace to the value provided. 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. This method is + * atomic. + * + * @param metricName Name of the metric to set. Requires no leading or trailing whitespace, no + * leading underscore '_' character, max length is 32 characters. + * @param value The value to which the metric should be set to. + */ + @Keep + public void putMetric(String metricName, long value) { + FakeReporter.addReport("Trace.putMetric", metricName, Long.toString(value)); + if (!hasStarted()) { + Log.w( + LOG_TAG, + String.format( + "Cannot set value for metric '%s' for trace '%s' because it's not started", + metricName, name)); + return; + } + if (isStopped()) { + Log.w( + LOG_TAG, + String.format( + "Cannot set value for metric '%s' for trace '%s' because it's been stopped", + metricName, name)); + return; + } + + if (metricName != null) { + counters.put(metricName, value); + } + } + + /** Log a message if Trace is not stopped when finalize() is called. */ + @Override + protected void finalize() throws Throwable { + try { + // If trace is started but not stopped when it reaches finalize(), log a warning msg. + if (isActive()) { + Log.w( + LOG_TAG, + String.format("Trace '%s' is started but not stopped when it is destructed!", name)); + } + } finally { + super.finalize(); + } + } + + /** + * non-zero endTime indicates Trace's stop() method has been called already. + * + * @return true if trace is stopped. false if not stopped. + */ + private boolean isStopped() { + return endTime != null; + } + + /** + * non-zero startTime indicates that Trace's start() method has been called + * + * @return true if trace has started, false if it has not started + */ + private boolean hasStarted() { + return startTime != null; + } + + /** + * Returns whether the trace is active. + * + * @return true if trace has been started but not stopped. + */ + private boolean isActive() { + return hasStarted() && !isStopped(); + } + + /** + * Sets a String value for the specified attribute. Updates the value of the attribute if the + * attribute already exists. If the trace has been stopped, this method returns without adding the + * attribute. The maximum number of attributes that can be added to a Trace are {@value + * #MAX_TRACE_CUSTOM_ATTRIBUTES}. + * + * @param attribute Name of the attribute + * @param value Value of the attribute Note: This fake implementation doesn't validate the + * attributes to the extent to which the real implementation does. + */ + @Override + @Keep + public void putAttribute(String attribute, String value) { + FakeReporter.addReport("Trace.putAttribute", attribute, value); + boolean noError = true; + try { + attribute = attribute.trim(); + value = value.trim(); + } catch (Exception e) { + Log.e( + LOG_TAG, + String.format( + "Can not set attribute %s with value %s (%s)", attribute, value, e.getMessage())); + noError = false; + } + if (noError) { + attributes.put(attribute, value); + } + } + + /** + * Removes an already added attribute from the Traces. If the trace has been stopped, this method + * returns without removing the attribute. + * + * @param attribute Name of the attribute to be removed from the running Traces. + */ + @Override + @Keep + public void removeAttribute(String attribute) { + FakeReporter.addReport("Trace.removeAttribute", attribute); + if (isStopped()) { + Log.e(LOG_TAG, "Can't remove a attribute from a Trace that's stopped."); + return; + } + attributes.remove(attribute); + } + + /** + * Returns the value of an attribute. + * + * @param attribute name of the attribute to fetch the value for + * @return the value of the attribute if it exists or null otherwise. + */ + @Override + @Nullable + @Keep + public String getAttribute(String attribute) { + FakeReporter.addReport("Trace.getAttribute", attribute); + return attributes.get(attribute); + } + + /** + * Returns the map of all the attributes added to this Trace. + * + * @return map of attributes and its values currently added to this Trace + */ + @Override + public Map getAttributes() { + return new HashMap<>(attributes); + } +} diff --git a/performance/stubs/http_metric_stub.cc b/performance/stubs/http_metric_stub.cc new file mode 100644 index 0000000000..397bd5229a --- /dev/null +++ b/performance/stubs/http_metric_stub.cc @@ -0,0 +1,100 @@ +#include + +#include "performance/src/include/firebase/performance/http_metric.h" + +namespace firebase { +namespace performance { +namespace internal { + +// TODO(tdeshpande): Fill this in when this is converted to a fake. +class HttpMetricInternal {}; + +} // namespace internal +} // namespace performance +} // namespace firebase + +firebase::performance::HttpMetric::HttpMetric() { + internal_ = new internal::HttpMetricInternal(); + std::cout << "Constructing an http metric object." << std::endl; +} + +firebase::performance::HttpMetric::HttpMetric(const char* url, + HttpMethod http_method) { + std::cout << "Constructing http metric for url: " << url << std::endl; +} + +firebase::performance::HttpMetric::~HttpMetric() { + delete internal_; + internal_ = nullptr; + std::cout << "Destroyed an HttpMetric object" << std::endl; +} + +firebase::performance::HttpMetric::HttpMetric(HttpMetric&& other) { + internal_ = other.internal_; + other.internal_ = nullptr; +} + +firebase::performance::HttpMetric& firebase::performance::HttpMetric::operator=( + HttpMetric&& other) { + if (this != &other) { + delete internal_; + + internal_ = other.internal_; + other.internal_ = nullptr; + } + return *this; +} + +bool firebase::performance::HttpMetric::is_started() { return true; } + +void firebase::performance::HttpMetric::Cancel() { + std::cout << "Cancel()" << std::endl; +} + +void firebase::performance::HttpMetric::Stop() { + std::cout << "Stop()" << std::endl; +} + +void firebase::performance::HttpMetric::Start(const char* url, + HttpMethod http_method) { + std::cout << "Start http metric for url: " << url << std::endl; +} + +void firebase::performance::HttpMetric::SetAttribute( + const char* attribute_name, const char* attribute_value) { + std::cout << "Put attribute: " << attribute_value + << " for name: " << attribute_name << std::endl; +} + +std::string firebase::performance::HttpMetric::GetAttribute( + const char* attribute_name) const { + return "someValue"; +} + +void firebase::performance::HttpMetric::set_http_response_code( + int http_response_code) { + std::cout << "Set http response code to: " << http_response_code << std::endl; +} + +void firebase::performance::HttpMetric::set_request_payload_size( + int64_t bytes) { + std::cout << "Request payload size set to: " << bytes << std::endl; +} + +void firebase::performance::HttpMetric::set_response_content_type( + const char* content_type) {} + +void firebase::performance::HttpMetric::set_response_payload_size( + int64_t bytes) {} + +// Used only in SWIG. + +void firebase::performance::HttpMetric::Create(const char* url, + HttpMethod http_method) { + std::cout << "Created an underlying http metric object without starting it." + << std::endl; +} + +void firebase::performance::HttpMetric::StartCreatedHttpMetric() { + std::cout << "Started the previously created http metric." << std::endl; +} diff --git a/performance/stubs/performance_stub.cc b/performance/stubs/performance_stub.cc new file mode 100644 index 0000000000..a65495e7ff --- /dev/null +++ b/performance/stubs/performance_stub.cc @@ -0,0 +1,15 @@ +#include + +#include "app/src/include/firebase/app.h" +#include "performance/src/include/firebase/performance.h" + +firebase::InitResult firebase::performance::Initialize( + const firebase::App& app) { + return kInitResultSuccess; +} + +void firebase::performance::Terminate() {} + +bool firebase::performance::GetPerformanceCollectionEnabled() { return true; } + +void firebase::performance::SetPerformanceCollectionEnabled(bool enabled) {} diff --git a/performance/stubs/trace_stub.cc b/performance/stubs/trace_stub.cc new file mode 100644 index 0000000000..25070f2dbd --- /dev/null +++ b/performance/stubs/trace_stub.cc @@ -0,0 +1,90 @@ +#include + +#include "performance/src/include/firebase/performance/trace.h" + +namespace firebase { +namespace performance { +namespace internal { + +// TODO(tdeshpande): Fill this in when this is converted to a fake. +class TraceInternal {}; + +} // namespace internal +} // namespace performance +} // namespace firebase + +firebase::performance::Trace::Trace() { + internal_ = new internal::TraceInternal(); + std::cout << "Created a trace object. " << std::endl; +} + +firebase::performance::Trace::Trace(const char* name) { + std::cout << "Created trace with name: " << name << std::endl; +} + +firebase::performance::Trace::~Trace() { + delete internal_; + internal_ = nullptr; + std::cout << "Destroyed a trace." << std::endl; +} + +firebase::performance::Trace::Trace(Trace&& other) { + internal_ = other.internal_; + other.internal_ = nullptr; +} + +firebase::performance::Trace& firebase::performance::Trace::operator=( + Trace&& other) { + if (this != &other) { + delete internal_; + + internal_ = other.internal_; + other.internal_ = nullptr; + } + return *this; +} + +bool firebase::performance::Trace::is_started() { return true; } + +void firebase::performance::Trace::Cancel() { + std::cout << "Cancel()" << std::endl; +} + +void firebase::performance::Trace::Stop() { + std::cout << "Stop()" << std::endl; +} + +void firebase::performance::Trace::Start(const char* name) { + std::cout << "Start trace with name: " << name << std::endl; +} + +int64_t firebase::performance::Trace::GetLongMetric( + const char* metric_name) const { + return 200; +} + +void firebase::performance::Trace::IncrementMetric(const char* metric_name, + const int64_t increment_by) { +} + +void firebase::performance::Trace::SetMetric(const char* metric_name, + const int64_t metric_value) {} + +void firebase::performance::Trace::SetAttribute(const char* attribute_name, + const char* attribute_value) {} + +std::string firebase::performance::Trace::GetAttribute( + const char* attribute_name) const { + return "value"; +} + +// Used only in SWIG. + +void firebase::performance::Trace::Create(const char* name) { + std::cout << "Created an underlying trace object without starting it. " + << std::endl; +} + +void firebase::performance::Trace::StartCreatedTrace() { + std::cout << "Started the previously created trace." << std::endl; +}