| // Copyright 2013 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/android/tracing_controller_android.h" |
| |
| #include <string> |
| |
| #include "base/android/jni_android.h" |
| #include "base/android/jni_array.h" |
| #include "base/android/jni_string.h" |
| #include "base/functional/bind.h" |
| #include "base/json/json_writer.h" |
| #include "base/logging.h" |
| #include "base/trace_event/trace_event.h" |
| #include "content/browser/tracing/tracing_controller_impl.h" |
| #include "content/public/android/content_main_dex_jni/TracingControllerAndroidImpl_jni.h" |
| #include "content/public/browser/tracing_controller.h" |
| #include "services/tracing/public/cpp/perfetto/perfetto_config.h" |
| #include "services/tracing/public/cpp/perfetto/perfetto_session.h" |
| #include "services/tracing/public/cpp/perfetto/trace_packet_tokenizer.h" |
| #include "third_party/perfetto/include/perfetto/ext/tracing/core/trace_packet.h" |
| #include "third_party/perfetto/include/perfetto/tracing/tracing.h" |
| #include "third_party/perfetto/protos/perfetto/common/trace_stats.gen.h" |
| |
| using base::android::JavaParamRef; |
| using base::android::ScopedJavaLocalRef; |
| using base::android::ScopedJavaGlobalRef; |
| |
| namespace content { |
| namespace { |
| |
| // Currently active tracing session. |
| perfetto::TracingSession* g_tracing_session = nullptr; |
| |
| void ReadProtobufTraceData( |
| scoped_refptr<TracingController::TraceDataEndpoint> endpoint, |
| perfetto::TracingSession::ReadTraceCallbackArgs args) { |
| if (args.size) { |
| auto data_string = std::make_unique<std::string>(args.data, args.size); |
| endpoint->ReceiveTraceChunk(std::move(data_string)); |
| } |
| if (!args.has_more) |
| endpoint->ReceivedTraceFinalContents(); |
| } |
| |
| void ReadJsonTraceData( |
| scoped_refptr<TracingController::TraceDataEndpoint> endpoint, |
| tracing::TracePacketTokenizer& tokenizer, |
| perfetto::TracingSession::ReadTraceCallbackArgs args) { |
| if (args.size) { |
| auto packets = |
| tokenizer.Parse(reinterpret_cast<const uint8_t*>(args.data), args.size); |
| for (const auto& packet : packets) { |
| for (const auto& slice : packet.slices()) { |
| auto data_string = std::make_unique<std::string>( |
| reinterpret_cast<const char*>(slice.start), slice.size); |
| endpoint->ReceiveTraceChunk(std::move(data_string)); |
| } |
| } |
| } |
| if (!args.has_more) { |
| DCHECK(!tokenizer.has_more()); |
| endpoint->ReceivedTraceFinalContents(); |
| } |
| } |
| |
| } // namespace |
| |
| static jlong JNI_TracingControllerAndroidImpl_Init( |
| JNIEnv* env, |
| const JavaParamRef<jobject>& obj) { |
| TracingControllerAndroid* profiler = new TracingControllerAndroid(env, obj); |
| return reinterpret_cast<intptr_t>(profiler); |
| } |
| |
| TracingControllerAndroid::TracingControllerAndroid(JNIEnv* env, jobject obj) |
| : weak_java_object_(env, obj) {} |
| |
| TracingControllerAndroid::~TracingControllerAndroid() {} |
| |
| void TracingControllerAndroid::Destroy(JNIEnv* env, |
| const JavaParamRef<jobject>& obj) { |
| delete this; |
| } |
| |
| bool TracingControllerAndroid::StartTracing( |
| JNIEnv* env, |
| const JavaParamRef<jobject>& obj, |
| const JavaParamRef<jstring>& jcategories, |
| const JavaParamRef<jstring>& jtraceoptions, |
| bool use_protobuf) { |
| std::string categories = |
| base::android::ConvertJavaStringToUTF8(env, jcategories); |
| std::string options = |
| base::android::ConvertJavaStringToUTF8(env, jtraceoptions); |
| |
| // This log is required by adb_profile_chrome.py. |
| LOG(WARNING) << "Logging performance trace to file"; |
| |
| base::trace_event::TraceConfig trace_config(categories, options); |
| perfetto::TraceConfig perfetto_config = tracing::GetDefaultPerfettoConfig( |
| base::trace_event::TraceConfig(), /*privacy_filtering_enabled=*/false, |
| /*convert_to_legacy_json=*/!use_protobuf); |
| delete g_tracing_session; |
| g_tracing_session = |
| perfetto::Tracing::NewTrace(perfetto::BackendType::kCustomBackend) |
| .release(); |
| g_tracing_session->Setup(perfetto_config); |
| g_tracing_session->Start(); |
| return true; |
| } |
| |
| void TracingControllerAndroid::StopTracing( |
| JNIEnv* env, |
| const JavaParamRef<jobject>& obj, |
| const JavaParamRef<jstring>& jfilepath, |
| bool compress_file, |
| bool use_protobuf, |
| const base::android::JavaParamRef<jobject>& callback) { |
| base::FilePath file_path( |
| base::android::ConvertJavaStringToUTF8(env, jfilepath)); |
| ScopedJavaGlobalRef<jobject> global_callback(env, callback); |
| auto endpoint = TracingController::CreateFileEndpoint( |
| file_path, base::BindOnce(&TracingControllerAndroid::OnTracingStopped, |
| weak_factory_.GetWeakPtr(), global_callback)); |
| |
| if (!g_tracing_session) { |
| LOG(ERROR) << "Tried to stop a non-existent tracing session"; |
| OnTracingStopped(global_callback); |
| return; |
| } |
| |
| if (compress_file) { |
| endpoint = TracingControllerImpl::CreateCompressedStringEndpoint( |
| endpoint, /*compress_with_background_priority=*/true); |
| } |
| |
| auto session = base::MakeRefCounted< |
| base::RefCountedData<std::unique_ptr<perfetto::TracingSession>>>( |
| base::WrapUnique(g_tracing_session)); |
| g_tracing_session = nullptr; |
| if (use_protobuf) { |
| session->data->SetOnStopCallback([session, endpoint] { |
| session->data->ReadTrace( |
| [session, |
| endpoint](perfetto::TracingSession::ReadTraceCallbackArgs args) { |
| ReadProtobufTraceData(endpoint, args); |
| }); |
| }); |
| } else { |
| auto tokenizer = base::MakeRefCounted< |
| base::RefCountedData<std::unique_ptr<tracing::TracePacketTokenizer>>>( |
| std::make_unique<tracing::TracePacketTokenizer>()); |
| session->data->SetOnStopCallback([session, tokenizer, endpoint] { |
| session->data->ReadTrace( |
| [session, tokenizer, |
| endpoint](perfetto::TracingSession::ReadTraceCallbackArgs args) { |
| ReadJsonTraceData(endpoint, *tokenizer->data, args); |
| }); |
| }); |
| } |
| session->data->Stop(); |
| } |
| |
| base::FilePath TracingControllerAndroid::GenerateTracingFilePath( |
| const std::string& basename) { |
| JNIEnv* env = base::android::AttachCurrentThread(); |
| ScopedJavaLocalRef<jstring> jfilename = |
| Java_TracingControllerAndroidImpl_generateTracingFilePath( |
| env, base::android::ConvertUTF8ToJavaString(env, basename)); |
| return base::FilePath( |
| base::android::ConvertJavaStringToUTF8(env, jfilename.obj())); |
| } |
| |
| void TracingControllerAndroid::OnTracingStopped( |
| const base::android::ScopedJavaGlobalRef<jobject>& callback) { |
| JNIEnv* env = base::android::AttachCurrentThread(); |
| base::android::ScopedJavaLocalRef<jobject> obj = weak_java_object_.get(env); |
| if (obj.obj()) |
| Java_TracingControllerAndroidImpl_onTracingStopped(env, obj, callback); |
| } |
| |
| bool TracingControllerAndroid::GetKnownCategoriesAsync( |
| JNIEnv* env, |
| const JavaParamRef<jobject>& obj, |
| const JavaParamRef<jobject>& callback) { |
| ScopedJavaGlobalRef<jobject> global_callback(env, callback); |
| // TODO(skyostil): Get the categories from Perfetto instead. |
| return TracingController::GetInstance()->GetCategories( |
| base::BindOnce(&TracingControllerAndroid::OnKnownCategoriesReceived, |
| weak_factory_.GetWeakPtr(), global_callback)); |
| } |
| |
| void TracingControllerAndroid::OnKnownCategoriesReceived( |
| const ScopedJavaGlobalRef<jobject>& callback, |
| const std::set<std::string>& categories_received) { |
| base::Value::List category_list; |
| for (const std::string& category : categories_received) |
| category_list.Append(category); |
| std::string received_category_list; |
| base::JSONWriter::Write(base::Value(std::move(category_list)), |
| &received_category_list); |
| |
| // This log is required by adb_profile_chrome.py. |
| // TODO(crbug.com/40092856): Replace (users of) this with DevTools' Tracing |
| // API. |
| LOG(WARNING) << "{\"traceCategoriesList\": " << received_category_list << "}"; |
| |
| JNIEnv* env = base::android::AttachCurrentThread(); |
| base::android::ScopedJavaLocalRef<jobject> obj = weak_java_object_.get(env); |
| if (obj.obj()) { |
| std::vector<std::string> category_vector(categories_received.begin(), |
| categories_received.end()); |
| base::android::ScopedJavaLocalRef<jobjectArray> jcategories = |
| base::android::ToJavaArrayOfStrings(env, category_vector); |
| Java_TracingControllerAndroidImpl_onKnownCategoriesReceived( |
| env, obj, jcategories, callback); |
| } |
| } |
| |
| static ScopedJavaLocalRef<jstring> |
| JNI_TracingControllerAndroidImpl_GetDefaultCategories( |
| JNIEnv* env, |
| const JavaParamRef<jobject>& obj) { |
| base::trace_event::TraceConfig trace_config; |
| return base::android::ConvertUTF8ToJavaString( |
| env, trace_config.ToCategoryFilterString()); |
| } |
| |
| bool TracingControllerAndroid::GetTraceBufferUsageAsync( |
| JNIEnv* env, |
| const JavaParamRef<jobject>& obj, |
| const JavaParamRef<jobject>& callback) { |
| ScopedJavaGlobalRef<jobject> global_callback(env, callback); |
| auto weak_callback = |
| base::BindOnce(&TracingControllerAndroid::OnTraceBufferUsageReceived, |
| weak_factory_.GetWeakPtr(), global_callback); |
| |
| if (!g_tracing_session) { |
| std::move(weak_callback) |
| .Run(/*percent_full=*/0.f, /*approximate_event_count=*/0); |
| return true; |
| } |
| |
| // |weak_callback| is move-only, so in order to pass it through a copied |
| // lambda we need to temporarily move it on the heap. |
| auto shared_callback = base::MakeRefCounted< |
| base::RefCountedData<base::OnceCallback<void(float, size_t)>>>( |
| std::move(weak_callback)); |
| g_tracing_session->GetTraceStats( |
| [shared_callback]( |
| perfetto::TracingSession::GetTraceStatsCallbackArgs args) { |
| float percent_full = 0; |
| perfetto::protos::gen::TraceStats trace_stats; |
| if (args.success && |
| trace_stats.ParseFromArray(args.trace_stats_data.data(), |
| args.trace_stats_data.size())) { |
| percent_full = tracing::GetTraceBufferUsage(trace_stats); |
| } |
| // TODO(skyostil): Remove approximate_event_count since no-one is using |
| // it. |
| std::move(shared_callback->data) |
| .Run(percent_full, /*approximate_event_count=*/0); |
| }); |
| return true; |
| } |
| |
| void TracingControllerAndroid::OnTraceBufferUsageReceived( |
| const ScopedJavaGlobalRef<jobject>& callback, |
| float percent_full, |
| size_t approximate_event_count) { |
| JNIEnv* env = base::android::AttachCurrentThread(); |
| base::android::ScopedJavaLocalRef<jobject> obj = weak_java_object_.get(env); |
| if (obj.obj()) { |
| Java_TracingControllerAndroidImpl_onTraceBufferUsageReceived( |
| env, obj, percent_full, approximate_event_count, callback); |
| } |
| } |
| |
| } // namespace content |