Commit ab4673c6 authored by Sami Kyostila's avatar Sami Kyostila Committed by Commit Bot

TracingController: Switch from JSON to protobuf traces

Switch from the legacy JSON format to binary Perfetto traces (.pftrace)
in the mobile tracing UI. Perfetto traces can be viewed with the
Perfetto UI (https://ui.perfetto.dev) and converted back to JSON format
for the legacy Trace Viewer.

Bug: b/158460807
Change-Id: I2a2865717eb5bccee7545ea62b7f8d3a0f473366
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2292473
Commit-Queue: Sami Kyöstilä <skyostil@chromium.org>
Commit-Queue: Bo <boliu@chromium.org>
Reviewed-by: default avatarBo <boliu@chromium.org>
Reviewed-by: default avatarEric Seckler <eseckler@chromium.org>
Auto-Submit: Sami Kyöstilä <skyostil@chromium.org>
Cr-Commit-Position: refs/heads/master@{#793259}
parent a6a0c3ba
......@@ -71,7 +71,7 @@ public class TracingController {
private static final String TAG = "TracingController";
private static final String TEMP_FILE_DIR = "/traces";
private static final String TEMP_FILE_PREFIX = "chrome-trace-";
private static final String TEMP_FILE_EXT = ".json.gz";
private static final String TEMP_FILE_EXT = ".pftrace.gz";
private static final String TRACE_MIMETYPE = "application/gzip";
private static final long DELETE_AFTER_SHARE_TIMEOUT_MILLIS = DateUtils.HOUR_IN_MILLIS;
......@@ -242,8 +242,8 @@ public class TracingController {
String categories = TextUtils.join(",", TracingSettings.getEnabledCategories());
String options = TracingSettings.getSelectedTracingMode();
if (!mNativeController.startTracing(
mTracingTempFile.getPath(), false, categories, options, true)) {
if (!mNativeController.startTracing(mTracingTempFile.getPath(), false, categories, options,
/*compressFile=*/true, /*useProtobuf=*/true)) {
Log.e(TAG, "Native error while trying to start tracing");
showErrorToast();
setState(State.IDLE);
......
include_rules = [
"+v8", # To support unit tests.
]
specific_include_rules = {
"tracing_controller_android.*": [
"+third_party/perfetto", # For tracing using Perfetto.
]
}
......@@ -16,12 +16,56 @@
#include "content/browser/tracing/tracing_controller_impl.h"
#include "content/public/android/content_jni_headers/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,
......@@ -44,7 +88,8 @@ bool TracingControllerAndroid::StartTracing(
JNIEnv* env,
const JavaParamRef<jobject>& obj,
const JavaParamRef<jstring>& jcategories,
const JavaParamRef<jstring>& jtraceoptions) {
const JavaParamRef<jstring>& jtraceoptions,
bool use_protobuf) {
std::string categories =
base::android::ConvertJavaStringToUTF8(env, jcategories);
std::string options =
......@@ -53,16 +98,23 @@ bool TracingControllerAndroid::StartTracing(
// This log is required by adb_profile_chrome.py.
LOG(WARNING) << "Logging performance trace to file";
return TracingController::GetInstance()->StartTracing(
base::trace_event::TraceConfig(categories, options),
TracingController::StartTracingDoneCallback());
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().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 compressfile,
bool compress_file,
bool use_protobuf,
const base::android::JavaParamRef<jobject>& callback) {
base::FilePath file_path(
base::android::ConvertJavaStringToUTF8(env, jfilepath));
......@@ -70,14 +122,43 @@ void TracingControllerAndroid::StopTracing(
auto endpoint = TracingController::CreateFileEndpoint(
file_path, base::BindOnce(&TracingControllerAndroid::OnTracingStopped,
weak_factory_.GetWeakPtr(), global_callback));
if (compressfile) {
endpoint =
TracingControllerImpl::CreateCompressedStringEndpoint(endpoint, true);
}
if (!TracingController::GetInstance()->StopTracing(endpoint)) {
LOG(ERROR) << "EndTracingAsync failed, forcing an immediate stop";
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();
}
void TracingControllerAndroid::GenerateTracingFilePath(
......@@ -102,6 +183,7 @@ bool TracingControllerAndroid::GetKnownCategoriesAsync(
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));
......@@ -146,9 +228,37 @@ bool TracingControllerAndroid::GetTraceBufferUsageAsync(
const JavaParamRef<jobject>& obj,
const JavaParamRef<jobject>& callback) {
ScopedJavaGlobalRef<jobject> global_callback(env, callback);
return TracingController::GetInstance()->GetTraceBufferUsage(
auto weak_callback =
base::BindOnce(&TracingControllerAndroid::OnTraceBufferUsageReceived,
weak_factory_.GetWeakPtr(), global_callback));
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(
......
......@@ -23,11 +23,13 @@ class TracingControllerAndroid {
bool StartTracing(JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj,
const base::android::JavaParamRef<jstring>& categories,
const base::android::JavaParamRef<jstring>& trace_options);
const base::android::JavaParamRef<jstring>& trace_options,
bool use_protobuf);
void StopTracing(JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj,
const base::android::JavaParamRef<jstring>& jfilepath,
bool compressfile,
bool compress_file,
bool use_protobuf,
const base::android::JavaParamRef<jobject>& callback);
bool GetKnownCategoriesAsync(
JNIEnv* env,
......
......@@ -41,6 +41,7 @@
#include "content/public/common/content_client.h"
#include "content/public/common/url_constants.h"
#include "services/tracing/public/cpp/perfetto/perfetto_config.h"
#include "services/tracing/public/cpp/perfetto/perfetto_session.h"
#include "third_party/perfetto/include/perfetto/tracing/tracing.h"
#include "third_party/perfetto/protos/perfetto/common/trace_stats.gen.h"
......@@ -49,7 +50,7 @@ namespace {
constexpr char kStreamFormat[] = "stream_format";
constexpr char kStreamFormatProtobuf[] = "protobuf";
constexpr char kStreamFormatJSON[] = "json";
perfetto::TracingSession* g_tracing_session;
perfetto::TracingSession* g_tracing_session = nullptr;
void OnGotCategories(WebUIDataSource::GotDataCallback callback,
const std::set<std::string>& categorySet) {
......@@ -112,18 +113,8 @@ bool GetTraceBufferUsage(WebUIDataSource::GotDataCallback callback) {
perfetto::protos::gen::TraceStats trace_stats;
if (args.success &&
trace_stats.ParseFromArray(args.trace_stats_data.data(),
args.trace_stats_data.size()) &&
trace_stats.buffer_stats_size()) {
// Note: Chrome's configs only use a single buffer.
const perfetto::protos::gen::TraceStats::BufferStats& buf_stats =
trace_stats.buffer_stats()[0];
size_t bytes_in_buffer = buf_stats.bytes_written() -
buf_stats.bytes_read() -
buf_stats.bytes_overwritten() +
buf_stats.padding_bytes_written() -
buf_stats.padding_bytes_cleared();
double percent_full =
bytes_in_buffer / static_cast<double>(buf_stats.buffer_size());
args.trace_stats_data.size())) {
double percent_full = tracing::GetTraceBufferUsage(trace_stats);
usage = base::NumberToString(percent_full);
}
std::move(*shared_callback)
......
......@@ -74,6 +74,7 @@ public class TracingControllerAndroidImpl implements TracingControllerAndroid {
private String mFilename;
private boolean mCompressFile;
private boolean mUseProtobuf;
public TracingControllerAndroidImpl(Context context) {
mContext = context;
......@@ -146,12 +147,13 @@ public class TracingControllerAndroidImpl implements TracingControllerAndroid {
* Start profiling to a new file in the Downloads directory.
*
* Calls #startTracing(String, boolean, String, String, boolean) with a new timestamped
* filename. Doesn't compress the file.
* filename. Doesn't compress the file or generate protobuf trace data.
*
* @see #startTracing(String, boolean, String, String, boolean)
* @see #startTracing(String, boolean, String, String, boolean, boolean)
*/
public boolean startTracing(boolean showToasts, String categories, String traceOptions) {
return startTracing(null, showToasts, categories, traceOptions, false);
return startTracing(null, showToasts, categories, traceOptions, /*compressFile=*/false,
/*useProtobuf=*/false);
}
private void initializeNativeControllerIfNeeded() {
......@@ -163,7 +165,7 @@ public class TracingControllerAndroidImpl implements TracingControllerAndroid {
@Override
public boolean startTracing(String filename, boolean showToasts, String categories,
String traceOptions, boolean compressFile) {
String traceOptions, boolean compressFile, boolean useProtobuf) {
mShowToasts = showToasts;
if (filename == null) {
......@@ -183,7 +185,7 @@ public class TracingControllerAndroidImpl implements TracingControllerAndroid {
// Lazy initialize the native side, to allow construction before the library is loaded.
initializeNativeControllerIfNeeded();
if (!TracingControllerAndroidImplJni.get().startTracing(mNativeTracingControllerAndroid,
TracingControllerAndroidImpl.this, categories, traceOptions)) {
TracingControllerAndroidImpl.this, categories, traceOptions, useProtobuf)) {
logAndToastError(mContext.getString(R.string.profiler_error_toast));
return false;
}
......@@ -192,6 +194,7 @@ public class TracingControllerAndroidImpl implements TracingControllerAndroid {
showToast(mContext.getString(R.string.profiler_started_toast) + ": " + categories);
mFilename = filename;
mCompressFile = compressFile;
mUseProtobuf = useProtobuf;
mIsTracing = true;
return true;
}
......@@ -200,7 +203,8 @@ public class TracingControllerAndroidImpl implements TracingControllerAndroid {
public void stopTracing(Callback<Void> callback) {
if (isTracing()) {
TracingControllerAndroidImplJni.get().stopTracing(mNativeTracingControllerAndroid,
TracingControllerAndroidImpl.this, mFilename, mCompressFile, callback);
TracingControllerAndroidImpl.this, mFilename, mCompressFile, mUseProtobuf,
callback);
}
}
......@@ -324,7 +328,8 @@ public class TracingControllerAndroidImpl implements TracingControllerAndroid {
? "record-until-full" : "record-continuously";
String filename = intent.getStringExtra(FILE_EXTRA);
if (filename != null) {
startTracing(filename, true, categories, traceOptions, false);
startTracing(filename, true, categories, traceOptions, /*compressFile=*/false,
/*useProtobuf=*/false);
} else {
startTracing(true, categories, traceOptions);
}
......@@ -345,9 +350,11 @@ public class TracingControllerAndroidImpl implements TracingControllerAndroid {
long init(TracingControllerAndroidImpl caller);
void destroy(long nativeTracingControllerAndroid, TracingControllerAndroidImpl caller);
boolean startTracing(long nativeTracingControllerAndroid,
TracingControllerAndroidImpl caller, String categories, String traceOptions);
TracingControllerAndroidImpl caller, String categories, String traceOptions,
boolean useProtobuf);
void stopTracing(long nativeTracingControllerAndroid, TracingControllerAndroidImpl caller,
String filename, boolean compressFile, Callback<Void> callback);
String filename, boolean compressFile, boolean useProtobuf,
Callback<Void> callback);
boolean getKnownCategoriesAsync(long nativeTracingControllerAndroid,
TracingControllerAndroidImpl caller, Callback<String[]> callback);
String getDefaultCategories(TracingControllerAndroidImpl caller);
......
......@@ -54,10 +54,12 @@ public interface TracingControllerAndroid {
* TraceOptions::TraceOptions(const std::string& options_string)
* (in base/trace_event/trace_event_impl.h) for the format.
* @param compressFile Whether the trace file should be compressed (gzip).
* @param useProtobuf Whether to generate a binary protobuf trace or use
* the legacy JSON format.
* @return Whether tracing was started successfully.
*/
boolean startTracing(String filename, boolean showToasts, String categories,
String traceOptions, boolean compressFile);
String traceOptions, boolean compressFile, boolean useProtobuf);
/**
* Stop recording and run |callback| when stopped.
......
......@@ -139,8 +139,8 @@ public class TracingControllerAndroidImplTest {
Assert.assertNull(tracingController.getOutputPath());
TestThreadUtils.runOnUiThreadBlocking(() -> {
Assert.assertTrue(
tracingController.startTracing(null, true, "*", "record-until-full", true));
Assert.assertTrue(tracingController.startTracing(null, true, "*", "record-until-full",
/*compressFile=*/true, /*useProtobuf=*/false));
});
Assert.assertTrue(tracingController.isTracing());
......@@ -162,4 +162,42 @@ public class TracingControllerAndroidImplTest {
Assert.assertTrue(file.delete());
TestThreadUtils.runOnUiThreadBlocking(() -> tracingController.destroy());
}
@Test
@MediumTest
@Feature({"GPU"})
@DisableIf.Build(sdk_is_less_than = 21, message = "crbug.com/899894")
public void testProtobufTracing() throws Exception {
ContentShellActivity activity = mActivityTestRule.launchContentShellWithUrl("about:blank");
mActivityTestRule.waitForActiveShellToBeDoneLoading();
final TracingControllerAndroidImpl tracingController =
new TracingControllerAndroidImpl(activity);
Assert.assertFalse(tracingController.isTracing());
Assert.assertNull(tracingController.getOutputPath());
TestThreadUtils.runOnUiThreadBlocking(() -> {
Assert.assertTrue(tracingController.startTracing(null, true, "*", "record-until-full",
/*compressFile=*/false, /*useProtobuf=*/true));
});
Assert.assertTrue(tracingController.isTracing());
File file = new File(tracingController.getOutputPath());
TestCallback<Void> callback = new TestCallback<>();
TestThreadUtils.runOnUiThreadBlocking(() -> tracingController.stopTracing(callback));
// Callback should be run once stopped.
callback.waitForFirst(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
// Should have written the output file, which should start with the
// trace packet field descriptor (0x0a).
Assert.assertTrue(file.exists());
FileInputStream stream = new FileInputStream(file);
byte[] bytes = new byte[1];
Assert.assertEquals(1, stream.read(bytes));
Assert.assertEquals((byte) 0x0a, bytes[0]);
Assert.assertTrue(file.delete());
TestThreadUtils.runOnUiThreadBlocking(() -> tracingController.destroy());
}
}
......@@ -25,6 +25,7 @@
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "mojo/public/cpp/system/wait.h"
#include "services/tracing/perfetto/perfetto_service.h"
#include "services/tracing/public/cpp/perfetto/perfetto_session.h"
#include "services/tracing/public/cpp/trace_event_args_allowlist.h"
#include "third_party/perfetto/include/perfetto/ext/trace_processor/export_json.h"
#include "third_party/perfetto/include/perfetto/ext/tracing/core/observable_events.h"
......@@ -403,7 +404,11 @@ void ConsumerHost::TracingSession::DisableTracingAndEmitJson(
perfetto::trace_processor::TraceProcessorStorage::CreateInstance(
processor_config);
DisableTracing();
if (tracing_enabled_) {
DisableTracing();
} else {
host_->consumer_endpoint()->ReadBuffers();
}
}
void ConsumerHost::TracingSession::ExportJson() {
......@@ -550,19 +555,8 @@ void ConsumerHost::TracingSession::OnTraceStats(
std::move(request_buffer_usage_callback_).Run(false, 0.0f, false);
return;
}
const perfetto::TraceStats::BufferStats& buf_stats = stats.buffer_stats()[0];
size_t bytes_in_buffer = buf_stats.bytes_written() - buf_stats.bytes_read() -
buf_stats.bytes_overwritten() +
buf_stats.padding_bytes_written() -
buf_stats.padding_bytes_cleared();
double percent_full =
bytes_in_buffer / static_cast<double>(buf_stats.buffer_size());
percent_full = base::ClampToRange(percent_full, 0.0, 1.0);
bool data_loss = buf_stats.chunks_overwritten() > 0 ||
buf_stats.chunks_discarded() > 0 ||
buf_stats.abi_violations() > 0 ||
buf_stats.trace_writer_packet_loss() > 0;
double percent_full = GetTraceBufferUsage(stats);
bool data_loss = HasLostData(stats);
std::move(request_buffer_usage_callback_).Run(true, percent_full, data_loss);
}
......
......@@ -78,6 +78,8 @@ target(tracing_lib_type, "cpp") {
"perfetto/perfetto_platform.h",
"perfetto/perfetto_producer.cc",
"perfetto/perfetto_producer.h",
"perfetto/perfetto_session.cc",
"perfetto/perfetto_session.h",
"perfetto/perfetto_traced_process.cc",
"perfetto/perfetto_traced_process.h",
"perfetto/perfetto_tracing_backend.cc",
......
......@@ -24,7 +24,8 @@ perfetto::TraceConfig::DataSource* AddDataSourceConfig(
perfetto::TraceConfig* perfetto_config,
const char* name,
const std::string& chrome_config_string,
bool privacy_filtering_enabled) {
bool privacy_filtering_enabled,
bool convert_to_legacy_json) {
auto* data_source = perfetto_config->add_data_sources();
auto* source_config = data_source->mutable_config();
source_config->set_name(name);
......@@ -32,6 +33,7 @@ perfetto::TraceConfig::DataSource* AddDataSourceConfig(
auto* chrome_config = source_config->mutable_chrome_config();
chrome_config->set_trace_config(chrome_config_string);
chrome_config->set_privacy_filtering_enabled(privacy_filtering_enabled);
chrome_config->set_convert_to_legacy_json(convert_to_legacy_json);
return data_source;
}
......@@ -40,7 +42,8 @@ void AddDataSourceConfigs(
const base::trace_event::TraceConfig::ProcessFilterConfig& process_filters,
const base::trace_event::TraceConfig& stripped_config,
const std::set<std::string>& source_names,
bool privacy_filtering_enabled) {
bool privacy_filtering_enabled,
bool convert_to_legacy_json) {
const std::string chrome_config_string = stripped_config.ToString();
// Capture actual trace events.
......@@ -48,7 +51,8 @@ void AddDataSourceConfigs(
source_names.count(tracing::mojom::kTraceEventDataSourceName) == 1) {
auto* trace_event_data_source = AddDataSourceConfig(
perfetto_config, tracing::mojom::kTraceEventDataSourceName,
chrome_config_string, privacy_filtering_enabled);
chrome_config_string, privacy_filtering_enabled,
convert_to_legacy_json);
for (auto& enabled_pid : process_filters.included_process_ids()) {
*trace_event_data_source->add_producer_name_filter() = base::StrCat(
{mojom::kPerfettoProducerNamePrefix,
......@@ -64,7 +68,8 @@ void AddDataSourceConfigs(
source_names.count(tracing::mojom::kSystemTraceDataSourceName) == 1) {
AddDataSourceConfig(perfetto_config,
tracing::mojom::kSystemTraceDataSourceName,
chrome_config_string, privacy_filtering_enabled);
chrome_config_string, privacy_filtering_enabled,
convert_to_legacy_json);
}
#endif
......@@ -73,7 +78,8 @@ void AddDataSourceConfigs(
source_names.count(tracing::mojom::kArcTraceDataSourceName) == 1) {
AddDataSourceConfig(perfetto_config,
tracing::mojom::kArcTraceDataSourceName,
chrome_config_string, privacy_filtering_enabled);
chrome_config_string, privacy_filtering_enabled,
convert_to_legacy_json);
}
#endif
}
......@@ -82,7 +88,8 @@ void AddDataSourceConfigs(
if (source_names.empty() ||
source_names.count(tracing::mojom::kMetaDataSourceName) == 1) {
AddDataSourceConfig(perfetto_config, tracing::mojom::kMetaDataSourceName,
chrome_config_string, privacy_filtering_enabled);
chrome_config_string, privacy_filtering_enabled,
convert_to_legacy_json);
}
if (stripped_config.IsCategoryGroupEnabled(
......@@ -92,7 +99,8 @@ void AddDataSourceConfigs(
source_names.count(tracing::mojom::kSamplerProfilerSourceName));
AddDataSourceConfig(perfetto_config,
tracing::mojom::kSamplerProfilerSourceName,
chrome_config_string, privacy_filtering_enabled);
chrome_config_string, privacy_filtering_enabled,
convert_to_legacy_json);
}
if (stripped_config.IsCategoryGroupEnabled(
......@@ -102,14 +110,16 @@ void AddDataSourceConfigs(
tracing::mojom::kJavaHeapProfilerSourceName));
AddDataSourceConfig(perfetto_config,
tracing::mojom::kJavaHeapProfilerSourceName,
chrome_config_string, privacy_filtering_enabled);
chrome_config_string, privacy_filtering_enabled,
convert_to_legacy_json);
}
if (source_names.empty() ||
source_names.count(tracing::mojom::kReachedCodeProfilerSourceName) == 1) {
AddDataSourceConfig(perfetto_config,
tracing::mojom::kReachedCodeProfilerSourceName,
chrome_config_string, privacy_filtering_enabled);
chrome_config_string, privacy_filtering_enabled,
convert_to_legacy_json);
}
}
......@@ -117,16 +127,18 @@ void AddDataSourceConfigs(
perfetto::TraceConfig GetDefaultPerfettoConfig(
const base::trace_event::TraceConfig& chrome_config,
bool privacy_filtering_enabled) {
return GetPerfettoConfigWithDataSources(chrome_config, {},
privacy_filtering_enabled);
bool privacy_filtering_enabled,
bool convert_to_legacy_json) {
return GetPerfettoConfigWithDataSources(
chrome_config, {}, privacy_filtering_enabled, convert_to_legacy_json);
}
perfetto::TraceConfig COMPONENT_EXPORT(TRACING_CPP)
GetPerfettoConfigWithDataSources(
const base::trace_event::TraceConfig& chrome_config,
const std::set<std::string>& source_names,
bool privacy_filtering_enabled) {
bool privacy_filtering_enabled,
bool convert_to_legacy_json) {
perfetto::TraceConfig perfetto_config;
size_t size_limit = chrome_config.GetTraceBufferSizeInKb();
......@@ -187,8 +199,8 @@ perfetto::TraceConfig COMPONENT_EXPORT(TRACING_CPP)
stripped_config.SetTraceBufferSizeInEvents(0);
AddDataSourceConfigs(&perfetto_config, chrome_config.process_filter_config(),
stripped_config, source_names,
privacy_filtering_enabled);
stripped_config, source_names, privacy_filtering_enabled,
convert_to_legacy_json);
return perfetto_config;
}
......
......@@ -21,7 +21,8 @@ namespace tracing {
perfetto::TraceConfig COMPONENT_EXPORT(TRACING_CPP) GetDefaultPerfettoConfig(
const base::trace_event::TraceConfig& chrome_config,
bool privacy_filtering_enabled = false);
bool privacy_filtering_enabled = false,
bool convert_to_legacy_json = false);
// Creates a perfetto trace config with only the data sources included in
// |source_names| and enabled by |trace_config|. Passing empty set will add all
......@@ -31,7 +32,8 @@ perfetto::TraceConfig COMPONENT_EXPORT(TRACING_CPP)
GetPerfettoConfigWithDataSources(
const base::trace_event::TraceConfig& chrome_config,
const std::set<std::string>& source_names,
bool privacy_filtering_enabled = false);
bool privacy_filtering_enabled = false,
bool convert_to_legacy_json = false);
} // namespace tracing
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/tracing/public/cpp/perfetto/perfetto_session.h"
#include "third_party/perfetto/protos/perfetto/common/trace_stats.gen.h"
namespace tracing {
double GetTraceBufferUsage(const perfetto::protos::gen::TraceStats& stats) {
// Chrome always uses a single tracing buffer.
if (stats.buffer_stats_size() != 1u)
return 0.f;
const perfetto::protos::gen::TraceStats::BufferStats& buf_stats =
stats.buffer_stats()[0];
size_t bytes_in_buffer = buf_stats.bytes_written() - buf_stats.bytes_read() -
buf_stats.bytes_overwritten() +
buf_stats.padding_bytes_written() -
buf_stats.padding_bytes_cleared();
return bytes_in_buffer / static_cast<double>(buf_stats.buffer_size());
}
bool HasLostData(const perfetto::protos::gen::TraceStats& stats) {
// Chrome always uses a single tracing buffer.
if (stats.buffer_stats_size() != 1u)
return false;
const perfetto::protos::gen::TraceStats::BufferStats& buf_stats =
stats.buffer_stats()[0];
return buf_stats.chunks_overwritten() > 0 ||
buf_stats.chunks_discarded() > 0 || buf_stats.abi_violations() > 0 ||
buf_stats.trace_writer_packet_loss() > 0;
}
} // namespace tracing
\ No newline at end of file
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SERVICES_TRACING_PUBLIC_CPP_PERFETTO_PERFETTO_SESSION_H_
#define SERVICES_TRACING_PUBLIC_CPP_PERFETTO_PERFETTO_SESSION_H_
#include <set>
#include <string>
#include "base/component_export.h"
namespace perfetto {
namespace protos {
namespace gen {
class TraceStats;
} // namespace gen
} // namespace protos
} // namespace perfetto
namespace tracing {
// Helpers for deriving information from Perfetto's tracing session statistics.
double COMPONENT_EXPORT(TRACING_CPP)
GetTraceBufferUsage(const perfetto::protos::gen::TraceStats&);
bool COMPONENT_EXPORT(TRACING_CPP)
HasLostData(const perfetto::protos::gen::TraceStats&);
} // namespace tracing
#endif // SERVICES_TRACING_PUBLIC_CPP_PERFETTO_PERFETTO_SESSION_H_
\ No newline at end of file
......@@ -416,8 +416,23 @@ class ConsumerEndpoint : public perfetto::ConsumerEndpoint,
}
drainer_ = std::make_unique<mojo::DataPipeDrainer>(
this, std::move(consumer_handle));
tokenizer_ = std::make_unique<TracePacketTokenizer>();
trace_data_complete_ = false;
read_buffers_complete_ = false;
// Convert to legacy JSON if needed.
for (const auto& data_source : trace_config_.data_sources()) {
if (data_source.config().has_chrome_config() &&
data_source.config().chrome_config().convert_to_legacy_json()) {
tracing_session_host_->DisableTracingAndEmitJson(
/*agent_label_filter=*/"", std::move(producer_handle),
/*privacy_filter_enabled=*/false,
base::BindOnce(&ConsumerEndpoint::OnReadBuffersComplete,
base::Unretained(this)));
return;
}
}
tokenizer_ = std::make_unique<TracePacketTokenizer>();
tracing_session_host_->ReadBuffers(
std::move(producer_handle),
base::BindOnce(&ConsumerEndpoint::OnReadBuffersComplete,
......@@ -523,16 +538,28 @@ class ConsumerEndpoint : public perfetto::ConsumerEndpoint,
// mojo::DataPipeDrainer::Client implementation:
void OnDataAvailable(const void* data, size_t num_bytes) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto packets =
tokenizer_->Parse(reinterpret_cast<const uint8_t*>(data), num_bytes);
if (!packets.empty())
if (tokenizer_) {
// Protobuf-format data.
auto packets =
tokenizer_->Parse(reinterpret_cast<const uint8_t*>(data), num_bytes);
if (!packets.empty())
consumer_->OnTraceData(std::move(packets), /*has_more=*/true);
} else {
// Legacy JSON-format data.
std::vector<perfetto::TracePacket> packets;
packets.emplace_back();
packets.back().AddSlice(data, num_bytes);
consumer_->OnTraceData(std::move(packets), /*has_more=*/true);
}
}
void OnDataComplete() override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!tokenizer_->has_more());
tokenizer_.reset();
if (tokenizer_) {
DCHECK(!tokenizer_->has_more());
tokenizer_.reset();
}
trace_data_complete_ = true;
MaybeFinishTraceData();
}
......@@ -583,7 +610,7 @@ class ConsumerEndpoint : public perfetto::ConsumerEndpoint,
void MaybeFinishTraceData() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!read_buffers_complete_ || tokenizer_)
if (!read_buffers_complete_ || !trace_data_complete_)
return;
consumer_->OnTraceData({}, /*has_more=*/false);
}
......@@ -599,6 +626,7 @@ class ConsumerEndpoint : public perfetto::ConsumerEndpoint,
std::unique_ptr<TracePacketTokenizer> tokenizer_;
bool read_buffers_complete_ = false;
bool trace_data_complete_ = false;
uint32_t observed_events_mask_ = 0;
base::WeakPtrFactory<ConsumerEndpoint> weak_factory_{this};
......@@ -623,4 +651,4 @@ PerfettoTracingBackend::ConnectProducer(const ConnectProducerArgs& args) {
args.shmem_size_hint_bytes, args.shmem_page_size_hint_bytes);
}
} // namespace tracing
\ No newline at end of file
} // namespace tracing
......@@ -6,6 +6,7 @@
#include <utility>
#include "base/bind.h"
#include "base/json/json_reader.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
......@@ -20,6 +21,7 @@
#include "services/tracing/public/cpp/perfetto/perfetto_config.h"
#include "services/tracing/public/cpp/perfetto/perfetto_platform.h"
#include "services/tracing/public/cpp/perfetto/perfetto_traced_process.h"
#include "services/tracing/public/cpp/perfetto/trace_packet_tokenizer.h"
#include "services/tracing/public/cpp/traced_process_impl.h"
#include "services/tracing/public/cpp/tracing_features.h"
#include "services/tracing/public/mojom/perfetto_service.mojom.h"
......@@ -27,6 +29,7 @@
#include "services/tracing/public/mojom/tracing_service.mojom.h"
#include "services/tracing/tracing_service.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/perfetto/include/perfetto/ext/tracing/core/trace_packet.h"
#include "third_party/perfetto/include/perfetto/tracing/data_source.h"
#include "third_party/perfetto/include/perfetto/tracing/tracing.h"
#include "third_party/perfetto/protos/perfetto/common/trace_stats.pb.h"
......@@ -269,6 +272,53 @@ TEST_F(TracingServiceTest, PerfettoClientConsumer) {
EXPECT_EQ(kNumPackets, ReadAndCountTestPackets(*session));
}
TEST_F(TracingServiceTest, PerfettoClientConsumerLegacyJson) {
// Set up API bindings.
EnableClientApiConsumer();
// Start a tracing session with legacy JSON exporting.
auto session = perfetto::Tracing::NewTrace();
perfetto::TraceConfig perfetto_config = GetDefaultPerfettoConfig(
base::trace_event::TraceConfig(), /*privacy_filtering_enabled=*/false,
/*convert_to_legacy_json=*/true);
session->Setup(perfetto_config);
session->Start();
// Stop the session and wait for it to stop. Note that we can't use the
// blocking API here because the service runs on the current sequence.
base::RunLoop wait_for_stop_loop;
session->SetOnStopCallback(
[&wait_for_stop_loop] { wait_for_stop_loop.Quit(); });
session->Stop();
wait_for_stop_loop.Run();
// Read and verify the JSON data.
base::RunLoop wait_for_data_loop;
TracePacketTokenizer tokenizer;
std::string json;
session->ReadTrace([&wait_for_data_loop, &tokenizer, &json](
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()) {
json += std::string(reinterpret_cast<const char*>(slice.start),
slice.size);
}
}
}
if (!args.has_more)
wait_for_data_loop.Quit();
});
wait_for_data_loop.Run();
DCHECK(!tokenizer.has_more());
auto result = base::DictionaryValue::From(
base::Value::ToUniquePtrValue(*base::JSONReader::Read(json)));
EXPECT_TRUE(result->HasKey("traceEvents"));
}
class CustomDataSource : public perfetto::DataSource<CustomDataSource> {
public:
struct Events {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment