| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/android/trace_event_binding.h" |
| |
| #include <jni.h> |
| |
| #include <set> |
| |
| #include "base/android/jni_string.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/trace_event/base_tracing.h" |
| #include "base/tracing_buildflags.h" |
| #include "build/robolectric_buildflags.h" |
| |
| #if BUILDFLAG(IS_ROBOLECTRIC) |
| #include "base/base_robolectric_jni/TraceEvent_jni.h" // nogncheck |
| #else |
| #include "base/tasks_jni/TraceEvent_jni.h" |
| #endif |
| |
| #if BUILDFLAG(ENABLE_BASE_TRACING) |
| #include "base/trace_event/trace_event_impl.h" // no-presubmit-check |
| #include "third_party/perfetto/include/perfetto/tracing/track.h" // no-presubmit-check nogncheck |
| #include "third_party/perfetto/protos/perfetto/config/chrome/chrome_config.gen.h" // nogncheck |
| #endif // BUILDFLAG(ENABLE_BASE_TRACING) |
| |
| namespace base { |
| namespace android { |
| |
| #if BUILDFLAG(ENABLE_BASE_TRACING) |
| |
| namespace { |
| |
| constexpr const char kAndroidViewHierarchyTraceCategory[] = |
| TRACE_DISABLED_BY_DEFAULT("android_view_hierarchy"); |
| constexpr const char kAndroidViewHierarchyEventName[] = "AndroidView"; |
| |
| class TraceEnabledObserver : public perfetto::TrackEventSessionObserver { |
| public: |
| static TraceEnabledObserver* GetInstance() { |
| static base::NoDestructor<TraceEnabledObserver> instance; |
| return instance.get(); |
| } |
| |
| // perfetto::TrackEventSessionObserver implementation |
| void OnSetup(const perfetto::DataSourceBase::SetupArgs& args) override { |
| trace_event::TraceConfig trace_config( |
| args.config->chrome_config().trace_config()); |
| event_name_filtering_per_session_[args.internal_instance_index] = |
| trace_config.IsEventPackageNameFilterEnabled(); |
| } |
| |
| void OnStart(const perfetto::DataSourceBase::StartArgs&) override { |
| JNIEnv* env = jni_zero::AttachCurrentThread(); |
| base::android::Java_TraceEvent_setEnabled(env, true); |
| base::android::Java_TraceEvent_setEventNameFilteringEnabled( |
| env, EventNameFilteringEnabled()); |
| } |
| |
| void OnStop(const perfetto::DataSourceBase::StopArgs& args) override { |
| event_name_filtering_per_session_.erase(args.internal_instance_index); |
| |
| JNIEnv* env = jni_zero::AttachCurrentThread(); |
| base::android::Java_TraceEvent_setEnabled( |
| env, !event_name_filtering_per_session_.empty()); |
| base::android::Java_TraceEvent_setEventNameFilteringEnabled( |
| env, EventNameFilteringEnabled()); |
| } |
| |
| private: |
| friend class base::NoDestructor<TraceEnabledObserver>; |
| TraceEnabledObserver() = default; |
| ~TraceEnabledObserver() override = default; |
| |
| // Return true if event name filtering is requested by at least one tracing |
| // session. |
| bool EventNameFilteringEnabled() const { |
| bool event_name_filtering_enabled = false; |
| for (const auto& entry : event_name_filtering_per_session_) { |
| if (entry.second) { |
| event_name_filtering_enabled = true; |
| } |
| } |
| return event_name_filtering_enabled; |
| } |
| |
| std::unordered_map<uint32_t, bool> event_name_filtering_per_session_; |
| }; |
| |
| |
| } // namespace |
| |
| static void JNI_TraceEvent_RegisterEnabledObserver(JNIEnv* env) { |
| base::android::Java_TraceEvent_setEnabled(env, base::TrackEvent::IsEnabled()); |
| base::TrackEvent::AddSessionObserver(TraceEnabledObserver::GetInstance()); |
| } |
| |
| static jboolean JNI_TraceEvent_ViewHierarchyDumpEnabled(JNIEnv* env) { |
| static const unsigned char* enabled = |
| TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED( |
| kAndroidViewHierarchyTraceCategory); |
| return *enabled; |
| } |
| |
| static void JNI_TraceEvent_InitViewHierarchyDump( |
| JNIEnv* env, |
| jlong id, |
| const JavaParamRef<jobject>& obj) { |
| TRACE_EVENT( |
| kAndroidViewHierarchyTraceCategory, kAndroidViewHierarchyEventName, |
| perfetto::TerminatingFlow::ProcessScoped(static_cast<uint64_t>(id)), |
| [&](perfetto::EventContext ctx) { |
| auto* event = ctx.event<perfetto::protos::pbzero::ChromeTrackEvent>(); |
| auto* dump = event->set_android_view_dump(); |
| Java_TraceEvent_dumpViewHierarchy(env, reinterpret_cast<jlong>(dump), |
| obj); |
| }); |
| } |
| |
| static jlong JNI_TraceEvent_StartActivityDump(JNIEnv* env, |
| const JavaParamRef<jstring>& name, |
| jlong dump_proto_ptr) { |
| auto* dump = reinterpret_cast<perfetto::protos::pbzero::AndroidViewDump*>( |
| dump_proto_ptr); |
| auto* activity = dump->add_activity(); |
| activity->set_name(ConvertJavaStringToUTF8(env, name)); |
| return reinterpret_cast<jlong>(activity); |
| } |
| |
| static void JNI_TraceEvent_AddViewDump( |
| JNIEnv* env, |
| jint id, |
| jint parent_id, |
| jboolean is_shown, |
| jboolean is_dirty, |
| const JavaParamRef<jstring>& class_name, |
| const JavaParamRef<jstring>& resource_name, |
| jlong activity_proto_ptr) { |
| auto* activity = reinterpret_cast<perfetto::protos::pbzero::AndroidActivity*>( |
| activity_proto_ptr); |
| auto* view = activity->add_view(); |
| view->set_id(id); |
| view->set_parent_id(parent_id); |
| view->set_is_shown(is_shown); |
| view->set_is_dirty(is_dirty); |
| view->set_class_name(ConvertJavaStringToUTF8(env, class_name)); |
| view->set_resource_name(ConvertJavaStringToUTF8(env, resource_name)); |
| } |
| |
| #else // BUILDFLAG(ENABLE_BASE_TRACING) |
| |
| // Empty implementations when TraceLog isn't available. |
| static void JNI_TraceEvent_RegisterEnabledObserver(JNIEnv* env) { |
| base::android::Java_TraceEvent_setEnabled(env, false); |
| // This code should not be reached when base tracing is disabled. Calling |
| // setEventNameFilteringEnabled to avoid "unused function" warning. |
| base::android::Java_TraceEvent_setEventNameFilteringEnabled(env, false); |
| } |
| static jboolean JNI_TraceEvent_ViewHierarchyDumpEnabled(JNIEnv* env) { |
| return false; |
| } |
| static void JNI_TraceEvent_InitViewHierarchyDump( |
| JNIEnv* env, |
| jlong id, |
| const JavaParamRef<jobject>& obj) { |
| DCHECK(false); |
| // This code should not be reached when base tracing is disabled. Calling |
| // dumpViewHierarchy to avoid "unused function" warning. |
| Java_TraceEvent_dumpViewHierarchy(env, 0, obj); |
| } |
| static jlong JNI_TraceEvent_StartActivityDump(JNIEnv* env, |
| const JavaParamRef<jstring>& name, |
| jlong dump_proto_ptr) { |
| return 0; |
| } |
| static void JNI_TraceEvent_AddViewDump( |
| JNIEnv* env, |
| jint id, |
| jint parent_id, |
| jboolean is_shown, |
| jboolean is_dirty, |
| const JavaParamRef<jstring>& class_name, |
| const JavaParamRef<jstring>& resource_name, |
| jlong activity_proto_ptr) {} |
| |
| #endif // BUILDFLAG(ENABLE_BASE_TRACING) |
| |
| namespace { |
| |
| // Boilerplate for safely converting Java data to TRACE_EVENT data. |
| class TraceEventDataConverter { |
| public: |
| TraceEventDataConverter(JNIEnv* env, jstring jarg) |
| : has_arg_(jarg != nullptr), |
| arg_(jarg ? ConvertJavaStringToUTF8(env, jarg) : "") {} |
| |
| TraceEventDataConverter(const TraceEventDataConverter&) = delete; |
| TraceEventDataConverter& operator=(const TraceEventDataConverter&) = delete; |
| |
| ~TraceEventDataConverter() = default; |
| |
| // Return saved values to pass to TRACE_EVENT macros. |
| const char* arg_name() { return has_arg_ ? "arg" : nullptr; } |
| const std::string& arg() { return arg_; } |
| |
| private: |
| bool has_arg_; |
| std::string arg_; |
| }; |
| |
| } // namespace |
| |
| static void JNI_TraceEvent_Instant(JNIEnv* env, |
| const JavaParamRef<jstring>& jname, |
| const JavaParamRef<jstring>& jarg) { |
| TraceEventDataConverter converter(env, jarg); |
| |
| if (converter.arg_name()) { |
| TRACE_EVENT_INSTANT( |
| internal::kJavaTraceCategory, nullptr, converter.arg_name(), |
| converter.arg(), [&](::perfetto::EventContext& ctx) { |
| ctx.event()->set_name(ConvertJavaStringToUTF8(env, jname)); |
| }); |
| } else { |
| TRACE_EVENT_INSTANT( |
| internal::kJavaTraceCategory, nullptr, |
| [&](::perfetto::EventContext& ctx) { |
| ctx.event()->set_name(ConvertJavaStringToUTF8(env, jname)); |
| }); |
| } |
| } |
| |
| static void JNI_TraceEvent_InstantAndroidIPC(JNIEnv* env, |
| const JavaParamRef<jstring>& jname, |
| jlong jdur) { |
| TRACE_EVENT_INSTANT( |
| internal::kJavaTraceCategory, "AndroidIPC", |
| [&](perfetto::EventContext ctx) { |
| auto* event = ctx.event<perfetto::protos::pbzero::ChromeTrackEvent>(); |
| auto* android_ipc = event->set_android_ipc(); |
| android_ipc->set_name(ConvertJavaStringToUTF8(env, jname)); |
| android_ipc->set_dur_ms(jdur); |
| }); |
| } |
| |
| #if BUILDFLAG(ENABLE_BASE_TRACING) |
| |
| static void JNI_TraceEvent_InstantAndroidToolbar(JNIEnv* env, |
| jint block_reason, |
| jint allow_reason, |
| jint snapshot_diff) { |
| using AndroidToolbar = perfetto::protos::pbzero::AndroidToolbar; |
| TRACE_EVENT_INSTANT( |
| internal::kJavaTraceCategory, "AndroidToolbar", |
| [&](perfetto::EventContext ctx) { |
| auto* event = ctx.event<perfetto::protos::pbzero::ChromeTrackEvent>(); |
| auto* android_toolbar = event->set_android_toolbar(); |
| if (block_reason >= 0) { |
| android_toolbar->set_block_capture_reason( |
| static_cast<AndroidToolbar::BlockCaptureReason>(block_reason)); |
| } |
| if (allow_reason >= 0) { |
| android_toolbar->set_allow_capture_reason( |
| static_cast<AndroidToolbar::AllowCaptureReason>(allow_reason)); |
| } |
| if (snapshot_diff >= 0) { |
| android_toolbar->set_snapshot_difference( |
| static_cast<AndroidToolbar::SnapshotDifference>(snapshot_diff)); |
| } |
| }); |
| } |
| |
| #else // BUILDFLAG(ENABLE_BASE_TRACING) |
| |
| // Empty implementations when TraceLog isn't available. |
| static void JNI_TraceEvent_InstantAndroidToolbar(JNIEnv* env, |
| jint block_reason, |
| jint allow_reason, |
| jint snapshot_diff) {} |
| |
| #endif // BUILDFLAG(ENABLE_BASE_TRACING) |
| |
| static void JNI_TraceEvent_WebViewStartupTotalFactoryInit(JNIEnv* env, |
| jlong start_time_ms, |
| jlong duration_ms) { |
| #if BUILDFLAG(ENABLE_BASE_TRACING) |
| auto t = perfetto::Track::ThreadScoped( |
| reinterpret_cast<void*>(trace_event::GetNextGlobalTraceId())); |
| TRACE_EVENT_BEGIN("android_webview.timeline", |
| "WebView.Startup.CreationTime.TotalFactoryInitTime", t, |
| TimeTicks() + Milliseconds(start_time_ms)); |
| TRACE_EVENT_END("android_webview.timeline", t, |
| TimeTicks() + Milliseconds(start_time_ms + duration_ms)); |
| #endif // BUILDFLAG(ENABLE_BASE_TRACING) |
| } |
| |
| static void JNI_TraceEvent_WebViewStartupStage1(JNIEnv* env, |
| jlong start_time_ms, |
| jlong duration_ms) { |
| #if BUILDFLAG(ENABLE_BASE_TRACING) |
| auto t = perfetto::Track::ThreadScoped( |
| reinterpret_cast<void*>(trace_event::GetNextGlobalTraceId())); |
| TRACE_EVENT_BEGIN("android_webview.timeline", |
| "WebView.Startup.CreationTime.Stage1.FactoryInit", t, |
| TimeTicks() + Milliseconds(start_time_ms)); |
| TRACE_EVENT_END("android_webview.timeline", t, |
| TimeTicks() + Milliseconds(start_time_ms + duration_ms)); |
| #endif // BUILDFLAG(ENABLE_BASE_TRACING) |
| } |
| |
| static void JNI_TraceEvent_WebViewStartupStage2(JNIEnv* env, |
| jlong start_time_ms, |
| jlong duration_ms, |
| jboolean is_cold_startup) { |
| #if BUILDFLAG(ENABLE_BASE_TRACING) |
| auto t = perfetto::Track::ThreadScoped( |
| reinterpret_cast<void*>(trace_event::GetNextGlobalTraceId())); |
| if (is_cold_startup) { |
| TRACE_EVENT_BEGIN("android_webview.timeline", |
| "WebView.Startup.CreationTime.Stage2.ProviderInit.Cold", |
| t, TimeTicks() + Milliseconds(start_time_ms)); |
| } else { |
| TRACE_EVENT_BEGIN("android_webview.timeline", |
| "WebView.Startup.CreationTime.Stage2.ProviderInit.Warm", |
| t, TimeTicks() + Milliseconds(start_time_ms)); |
| } |
| |
| TRACE_EVENT_END("android_webview.timeline", t, |
| TimeTicks() + Milliseconds(start_time_ms + duration_ms)); |
| #endif // BUILDFLAG(ENABLE_BASE_TRACING) |
| } |
| |
| static void JNI_TraceEvent_WebViewStartupStartChromiumLocked( |
| JNIEnv* env, |
| jlong start_time_ms, |
| jlong duration_ms, |
| jint call_site, |
| jboolean from_ui_thread) { |
| #if BUILDFLAG(ENABLE_BASE_TRACING) |
| auto t = perfetto::Track::ThreadScoped( |
| reinterpret_cast<void*>(trace_event::GetNextGlobalTraceId())); |
| TRACE_EVENT_BEGIN( |
| "android_webview.timeline", |
| "WebView.Startup.CreationTime.StartChromiumLocked", t, |
| TimeTicks() + Milliseconds(start_time_ms), |
| [&](perfetto::EventContext ctx) { |
| auto* webview_startup = |
| ctx.event<perfetto::protos::pbzero::ChromeTrackEvent>() |
| ->set_webview_startup(); |
| webview_startup->set_from_ui_thread((bool)from_ui_thread); |
| webview_startup->set_call_site( |
| (perfetto::protos::pbzero::perfetto_pbzero_enum_WebViewStartup:: |
| CallSite)call_site); |
| }); |
| TRACE_EVENT_END("android_webview.timeline", t, |
| TimeTicks() + Milliseconds(start_time_ms + duration_ms)); |
| #endif // BUILDFLAG(ENABLE_BASE_TRACING) |
| } |
| |
| static void JNI_TraceEvent_StartupActivityStart(JNIEnv* env, |
| jlong activity_id, |
| jlong start_time_ms) { |
| TRACE_EVENT_INSTANT( |
| "interactions", "Startup.ActivityStart", |
| TimeTicks() + Milliseconds(start_time_ms), |
| [&](perfetto::EventContext ctx) { |
| auto* start_up = ctx.event<perfetto::protos::pbzero::ChromeTrackEvent>() |
| ->set_startup(); |
| start_up->set_activity_id(activity_id); |
| }); |
| } |
| |
| static void JNI_TraceEvent_StartupLaunchCause(JNIEnv* env, |
| jlong activity_id, |
| jlong start_time_ms, |
| jint cause) { |
| #if BUILDFLAG(ENABLE_BASE_TRACING) |
| using Startup = perfetto::protos::pbzero::StartUp; |
| auto launchType = Startup::OTHER; |
| switch (cause) { |
| case Startup::CUSTOM_TAB: |
| launchType = Startup::CUSTOM_TAB; |
| break; |
| case Startup::TWA: |
| launchType = Startup::TWA; |
| break; |
| case Startup::RECENTS: |
| launchType = Startup::RECENTS; |
| break; |
| case Startup::RECENTS_OR_BACK: |
| launchType = Startup::RECENTS_OR_BACK; |
| break; |
| case Startup::FOREGROUND_WHEN_LOCKED: |
| launchType = Startup::FOREGROUND_WHEN_LOCKED; |
| break; |
| case Startup::MAIN_LAUNCHER_ICON: |
| launchType = Startup::MAIN_LAUNCHER_ICON; |
| break; |
| case Startup::MAIN_LAUNCHER_ICON_SHORTCUT: |
| launchType = Startup::MAIN_LAUNCHER_ICON_SHORTCUT; |
| break; |
| case Startup::HOME_SCREEN_WIDGET: |
| launchType = Startup::HOME_SCREEN_WIDGET; |
| break; |
| case Startup::OPEN_IN_BROWSER_FROM_MENU: |
| launchType = Startup::OPEN_IN_BROWSER_FROM_MENU; |
| break; |
| case Startup::EXTERNAL_SEARCH_ACTION_INTENT: |
| launchType = Startup::EXTERNAL_SEARCH_ACTION_INTENT; |
| break; |
| case Startup::NOTIFICATION: |
| launchType = Startup::NOTIFICATION; |
| break; |
| case Startup::EXTERNAL_VIEW_INTENT: |
| launchType = Startup::EXTERNAL_VIEW_INTENT; |
| break; |
| case Startup::OTHER_CHROME: |
| launchType = Startup::OTHER_CHROME; |
| break; |
| case Startup::WEBAPK_CHROME_DISTRIBUTOR: |
| launchType = Startup::WEBAPK_CHROME_DISTRIBUTOR; |
| break; |
| case Startup::WEBAPK_OTHER_DISTRIBUTOR: |
| launchType = Startup::WEBAPK_OTHER_DISTRIBUTOR; |
| break; |
| case Startup::HOME_SCREEN_SHORTCUT: |
| launchType = Startup::HOME_SCREEN_SHORTCUT; |
| break; |
| case Startup::SHARE_INTENT: |
| launchType = Startup::SHARE_INTENT; |
| break; |
| case Startup::NFC: |
| launchType = Startup::NFC; |
| break; |
| default: |
| break; |
| } |
| |
| TRACE_EVENT_INSTANT( |
| "interactions,startup", "Startup.LaunchCause", |
| TimeTicks() + Milliseconds(start_time_ms), |
| [&](perfetto::EventContext ctx) { |
| auto* start_up = ctx.event<perfetto::protos::pbzero::ChromeTrackEvent>() |
| ->set_startup(); |
| start_up->set_activity_id(activity_id); |
| start_up->set_launch_cause(launchType); |
| }); |
| #endif // BUILDFLAG(ENABLE_BASE_TRACING) |
| } |
| |
| static void JNI_TraceEvent_StartupTimeToFirstVisibleContent2( |
| JNIEnv* env, |
| jlong activity_id, |
| jlong start_time_ms, |
| jlong duration_ms) { |
| #if BUILDFLAG(ENABLE_BASE_TRACING) |
| [[maybe_unused]] const perfetto::Track track( |
| base::trace_event::GetNextGlobalTraceId(), |
| perfetto::ProcessTrack::Current()); |
| TRACE_EVENT_BEGIN( |
| "interactions,startup", "Startup.TimeToFirstVisibleContent2", track, |
| TimeTicks() + Milliseconds(start_time_ms), |
| [&](perfetto::EventContext ctx) { |
| auto* start_up = ctx.event<perfetto::protos::pbzero::ChromeTrackEvent>() |
| ->set_startup(); |
| start_up->set_activity_id(activity_id); |
| }); |
| |
| TRACE_EVENT_END("interactions,startup", track, |
| TimeTicks() + Milliseconds(start_time_ms + duration_ms)); |
| #endif // BUILDFLAG(ENABLE_BASE_TRACING) |
| } |
| |
| static void JNI_TraceEvent_Begin(JNIEnv* env, |
| const JavaParamRef<jstring>& jname, |
| const JavaParamRef<jstring>& jarg) { |
| TraceEventDataConverter converter(env, jarg); |
| if (converter.arg_name()) { |
| TRACE_EVENT_BEGIN( |
| internal::kJavaTraceCategory, nullptr, converter.arg_name(), |
| converter.arg(), [&](::perfetto::EventContext& ctx) { |
| ctx.event()->set_name(ConvertJavaStringToUTF8(env, jname)); |
| }); |
| } else { |
| TRACE_EVENT_BEGIN( |
| internal::kJavaTraceCategory, nullptr, |
| [&](::perfetto::EventContext& ctx) { |
| ctx.event()->set_name(ConvertJavaStringToUTF8(env, jname)); |
| }); |
| } |
| } |
| |
| static void JNI_TraceEvent_BeginWithIntArg(JNIEnv* env, |
| const JavaParamRef<jstring>& jname, |
| jint jarg) { |
| TRACE_EVENT_BEGIN( |
| internal::kJavaTraceCategory, nullptr, "arg", jarg, |
| [&](::perfetto::EventContext& ctx) { |
| ctx.event()->set_name(ConvertJavaStringToUTF8(env, jname)); |
| }); |
| } |
| |
| static void JNI_TraceEvent_End(JNIEnv* env, |
| const JavaParamRef<jstring>& jarg, |
| jlong jflow) { |
| TraceEventDataConverter converter(env, jarg); |
| bool has_arg = converter.arg_name(); |
| bool has_flow = jflow != 0; |
| if (has_arg && has_flow) { |
| TRACE_EVENT_END(internal::kJavaTraceCategory, |
| perfetto::Flow::ProcessScoped(static_cast<uint64_t>(jflow)), |
| converter.arg_name(), converter.arg()); |
| } else if (has_arg) { |
| TRACE_EVENT_END(internal::kJavaTraceCategory, converter.arg_name(), |
| converter.arg()); |
| } else if (has_flow) { |
| TRACE_EVENT_END( |
| internal::kJavaTraceCategory, |
| perfetto::Flow::ProcessScoped(static_cast<uint64_t>(jflow))); |
| } else { |
| TRACE_EVENT_END(internal::kJavaTraceCategory); |
| } |
| } |
| |
| static void JNI_TraceEvent_BeginToplevel(JNIEnv* env, |
| const JavaParamRef<jstring>& jtarget) { |
| TRACE_EVENT_BEGIN( |
| internal::kToplevelTraceCategory, nullptr, |
| [&](::perfetto::EventContext& ctx) { |
| ctx.event()->set_name(ConvertJavaStringToUTF8(env, jtarget)); |
| }); |
| } |
| |
| static void JNI_TraceEvent_EndToplevel(JNIEnv* env) { |
| TRACE_EVENT_END(internal::kToplevelTraceCategory); |
| } |
| |
| static void JNI_TraceEvent_StartAsync(JNIEnv* env, |
| const JavaParamRef<jstring>& jname, |
| jlong jid) { |
| TRACE_EVENT_BEGIN( |
| internal::kJavaTraceCategory, nullptr, |
| perfetto::Track(static_cast<uint64_t>(jid)), |
| [&](::perfetto::EventContext& ctx) { |
| ctx.event()->set_name(ConvertJavaStringToUTF8(env, jname)); |
| }); |
| } |
| |
| static void JNI_TraceEvent_FinishAsync(JNIEnv* env, |
| jlong jid) { |
| TRACE_EVENT_END(internal::kJavaTraceCategory, |
| perfetto::Track(static_cast<uint64_t>(jid))); |
| } |
| |
| } // namespace android |
| } // namespace base |