blob: 87f054541a2c76a089ed9e407173fe2ea26aae6d [file] [log] [blame]
Avi Drissman4e1b7bc32022-09-15 14:03:501// Copyright 2012 The Chromium Authors
[email protected]bcb6ee23d2013-02-03 04:06:402// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "content/browser/tracing/tracing_ui.h"
6
avib7348942015-12-25 20:57:107#include <stddef.h>
8
dcheng59716272016-04-09 05:19:089#include <memory>
Arthur Sonzognic686e8f2024-01-11 08:36:3710#include <optional>
[email protected]b12387e62014-06-27 20:52:0611#include <set>
[email protected]bcb6ee23d2013-02-03 04:06:4012#include <string>
Oystein Eftevaag2386e052019-10-02 21:08:0913#include <utility>
[email protected]b12387e62014-06-27 20:52:0614#include <vector>
[email protected]bcb6ee23d2013-02-03 04:06:4015
[email protected]4a0a8f92013-11-21 06:10:3416#include "base/base64.h"
simonhatch6160a4b2015-07-23 13:30:4417#include "base/command_line.h"
[email protected]b12387e62014-06-27 20:52:0618#include "base/format_macros.h"
Avi Drissmanadac21992023-01-11 23:46:3919#include "base/functional/bind.h"
20#include "base/functional/callback_helpers.h"
[email protected]4a0a8f92013-11-21 06:10:3421#include "base/json/json_reader.h"
22#include "base/json/json_writer.h"
Ehsan Chiniforooshanc77b44e2017-10-10 18:12:1023#include "base/memory/ref_counted_memory.h"
[email protected]3ea1b182013-02-08 22:38:4124#include "base/strings/string_number_conversions.h"
[email protected]b12387e62014-06-27 20:52:0625#include "base/strings/string_split.h"
[email protected]348fbaac2013-06-11 06:31:5126#include "base/strings/string_util.h"
Etienne Pierre-doray764aab092025-07-14 18:59:3827#include "base/trace_event/trace_config.h"
ssid3e765612015-01-28 04:03:4228#include "base/trace_event/trace_event.h"
[email protected]bcb6ee23d2013-02-03 04:06:4029#include "base/values.h"
[email protected]97865022014-06-13 23:18:0430#include "content/browser/tracing/grit/tracing_resources.h"
[email protected]06d5b6e2014-01-14 14:55:5831#include "content/browser/tracing/tracing_controller_impl.h"
[email protected]b12387e62014-06-27 20:52:0632#include "content/public/browser/browser_context.h"
[email protected]bcb6ee23d2013-02-03 04:06:4033#include "content/public/browser/browser_thread.h"
oysteinec563c582015-02-13 19:19:5234#include "content/public/browser/content_browser_client.h"
jamb84299e2016-04-12 16:58:5935#include "content/public/browser/storage_partition.h"
[email protected]4a0a8f92013-11-21 06:10:3436#include "content/public/browser/tracing_controller.h"
[email protected]bcb6ee23d2013-02-03 04:06:4037#include "content/public/browser/web_contents.h"
[email protected]bcb6ee23d2013-02-03 04:06:4038#include "content/public/browser/web_ui.h"
39#include "content/public/browser/web_ui_data_source.h"
[email protected]b12387e62014-06-27 20:52:0640#include "content/public/common/content_client.h"
[email protected]bcb6ee23d2013-02-03 04:06:4041#include "content/public/common/url_constants.h"
Sami Kyostilab3f90412020-07-09 11:47:5442#include "services/tracing/public/cpp/perfetto/perfetto_config.h"
Sami Kyostilaab4673c2020-07-30 18:32:3343#include "services/tracing/public/cpp/perfetto/perfetto_session.h"
Sami Kyostilab3f90412020-07-09 11:47:5444#include "third_party/perfetto/include/perfetto/tracing/tracing.h"
45#include "third_party/perfetto/protos/perfetto/common/trace_stats.gen.h"
[email protected]bcb6ee23d2013-02-03 04:06:4046
47namespace content {
48namespace {
Sami Kyostilab3f90412020-07-09 11:47:5449constexpr char kStreamFormat[] = "stream_format";
50constexpr char kStreamFormatProtobuf[] = "protobuf";
51constexpr char kStreamFormatJSON[] = "json";
Sami Kyostilaab4673c2020-07-30 18:32:3352perfetto::TracingSession* g_tracing_session = nullptr;
[email protected]bcb6ee23d2013-02-03 04:06:4053
danakjf4b9e942019-11-29 15:43:0454void OnGotCategories(WebUIDataSource::GotDataCallback callback,
Lei Zhangfe109ac62022-12-15 17:38:4955 const std::set<std::string>& category_set) {
56 base::Value::List category_list;
57 for (const std::string& category : category_set) {
58 category_list.Append(category);
[email protected]bcb6ee23d2013-02-03 04:06:4059 }
60
Lei Zhangfe109ac62022-12-15 17:38:4961 auto res = base::MakeRefCounted<base::RefCountedString>();
danakj529a3eba2024-04-18 20:14:5662 base::JSONWriter::Write(category_list, &res->as_string());
danakjf4b9e942019-11-29 15:43:0463 std::move(callback).Run(res);
[email protected]4a0a8f92013-11-21 06:10:3464}
65
danakjf4b9e942019-11-29 15:43:0466void OnRecordingEnabledAck(WebUIDataSource::GotDataCallback callback);
[email protected]b83f3972014-01-08 12:16:5767
[email protected]5186ccb2014-01-16 16:12:5668bool BeginRecording(const std::string& data64,
danakjf4b9e942019-11-29 15:43:0469 WebUIDataSource::GotDataCallback callback) {
zhenwd601ddc52015-06-02 21:46:3470 base::trace_event::TraceConfig trace_config("", "");
Sami Kyostilab3f90412020-07-09 11:47:5471 std::string stream_format;
72 if (!TracingUI::GetTracingOptions(data64, trace_config, stream_format))
[email protected]b83f3972014-01-08 12:16:5773 return false;
[email protected]4a0a8f92013-11-21 06:10:3474
Sami Kyostilab3f90412020-07-09 11:47:5475 // TODO(skyostil): Migrate all use cases from TracingController to Perfetto.
76 if (stream_format == kStreamFormatProtobuf) {
Lei Zhangfe109ac62022-12-15 17:38:4977 delete g_tracing_session;
Lukasz Gniadzik34d355d2021-03-15 10:38:5978 g_tracing_session =
79 perfetto::Tracing::NewTrace(perfetto::BackendType::kCustomBackend)
80 .release();
Sami Kyostilab3f90412020-07-09 11:47:5481 g_tracing_session->Setup(tracing::GetDefaultPerfettoConfig(trace_config));
Sami Kyostilaf88c159202020-07-09 17:00:5682
Sami Kyostilab8f89f62020-07-31 10:06:3183 auto shared_callback = base::MakeRefCounted<
84 base::RefCountedData<WebUIDataSource::GotDataCallback>>(
85 std::move(callback));
Sami Kyostilaf88c159202020-07-09 17:00:5686 g_tracing_session->SetOnStartCallback([shared_callback] {
Sami Kyostilab8f89f62020-07-31 10:06:3187 OnRecordingEnabledAck(std::move(shared_callback->data));
Sami Kyostilaf88c159202020-07-09 17:00:5688 });
Sami Kyostilab3f90412020-07-09 11:47:5489 g_tracing_session->Start();
Sami Kyostilab3f90412020-07-09 11:47:5490 return true;
91 }
zhenw873bdff2015-11-11 22:16:5592 return TracingController::GetInstance()->StartTracing(
danakjf4b9e942019-11-29 15:43:0493 trace_config,
94 base::BindOnce(&OnRecordingEnabledAck, std::move(callback)));
[email protected]4a0a8f92013-11-21 06:10:3495}
96
danakjf4b9e942019-11-29 15:43:0497void OnTraceBufferUsageResult(WebUIDataSource::GotDataCallback callback,
yurysd57ba6fe62014-11-17 10:57:5698 float percent_full,
99 size_t approximate_event_count) {
Brett Wilson5ed06e72017-12-01 01:25:11100 std::string str = base::NumberToString(percent_full);
Jongmok Kimc5491082022-10-19 09:17:59101 std::move(callback).Run(
102 base::MakeRefCounted<base::RefCountedString>(std::move(str)));
[email protected]4a0a8f92013-11-21 06:10:34103}
104
Sami Kyostilab3f90412020-07-09 11:47:54105bool GetTraceBufferUsage(WebUIDataSource::GotDataCallback callback) {
106 if (g_tracing_session) {
107 // |callback| is move-only, so in order to pass it through a copied lambda
108 // we need to temporarily move it on the heap.
Sami Kyostilab8f89f62020-07-31 10:06:31109 auto shared_callback = base::MakeRefCounted<
110 base::RefCountedData<WebUIDataSource::GotDataCallback>>(
111 std::move(callback));
Sami Kyostilab3f90412020-07-09 11:47:54112 g_tracing_session->GetTraceStats(
113 [shared_callback](
114 perfetto::TracingSession::GetTraceStatsCallbackArgs args) {
115 std::string usage;
116 perfetto::protos::gen::TraceStats trace_stats;
117 if (args.success &&
118 trace_stats.ParseFromArray(args.trace_stats_data.data(),
Sami Kyostilaab4673c2020-07-30 18:32:33119 args.trace_stats_data.size())) {
120 double percent_full = tracing::GetTraceBufferUsage(trace_stats);
Sami Kyostilab3f90412020-07-09 11:47:54121 usage = base::NumberToString(percent_full);
122 }
Sami Kyostilab8f89f62020-07-31 10:06:31123 std::move(shared_callback->data)
Jongmok Kimc5491082022-10-19 09:17:59124 .Run(base::MakeRefCounted<base::RefCountedString>(
125 std::move(usage)));
Sami Kyostilab3f90412020-07-09 11:47:54126 });
127 return true;
128 }
129
130 return TracingController::GetInstance()->GetTraceBufferUsage(
131 base::BindOnce(OnTraceBufferUsageResult, std::move(callback)));
132}
133
134void ReadProtobufTraceData(
135 scoped_refptr<TracingController::TraceDataEndpoint> endpoint,
136 perfetto::TracingSession::ReadTraceCallbackArgs args) {
137 if (args.size) {
138 auto data_string = std::make_unique<std::string>(args.data, args.size);
139 endpoint->ReceiveTraceChunk(std::move(data_string));
140 }
141 if (!args.has_more)
142 endpoint->ReceivedTraceFinalContents();
143}
144
danakjf4b9e942019-11-29 15:43:04145void TracingCallbackWrapperBase64(WebUIDataSource::GotDataCallback callback,
146 std::unique_ptr<std::string> data) {
simonhatcha13b0a52015-06-23 20:23:08147 base::RefCountedString* data_base64 = new base::RefCountedString();
danakj529a3eba2024-04-18 20:14:56148 data_base64->as_string() = base::Base64Encode(*data);
danakjf4b9e942019-11-29 15:43:04149 std::move(callback).Run(data_base64);
simonhatcha13b0a52015-06-23 20:23:08150}
151
Sami Kyostilab3f90412020-07-09 11:47:54152bool EndRecording(WebUIDataSource::GotDataCallback callback) {
153 if (!TracingController::GetInstance()->IsTracing() && !g_tracing_session)
154 return false;
155
156 scoped_refptr<TracingController::TraceDataEndpoint> data_endpoint =
157 TracingControllerImpl::CreateCompressedStringEndpoint(
158 TracingControllerImpl::CreateCallbackEndpoint(base::BindOnce(
159 TracingCallbackWrapperBase64, std::move(callback))),
160 false /* compress_with_background_priority */);
161
162 if (g_tracing_session) {
163 perfetto::TracingSession* session = g_tracing_session;
164 g_tracing_session = nullptr;
165 session->SetOnStopCallback([session, data_endpoint] {
166 session->ReadTrace(
167 [session, data_endpoint](
168 perfetto::TracingSession::ReadTraceCallbackArgs args) {
169 ReadProtobufTraceData(data_endpoint, args);
170 if (!args.has_more)
171 delete session;
172 });
173 });
174 session->Stop();
175 return true;
176 }
177 return TracingController::GetInstance()->StopTracing(data_endpoint);
178}
179
180void OnRecordingEnabledAck(WebUIDataSource::GotDataCallback callback) {
181 std::move(callback).Run(base::MakeRefCounted<base::RefCountedString>());
182}
183
[email protected]e4f45cd42014-01-06 20:55:06184bool OnBeginJSONRequest(const std::string& path,
danakj88bb36b2020-01-09 15:35:00185 WebUIDataSource::GotDataCallback callback) {
[email protected]4a0a8f92013-11-21 06:10:34186 if (path == "json/categories") {
[email protected]e4f45cd42014-01-06 20:55:06187 return TracingController::GetInstance()->GetCategories(
danakj88bb36b2020-01-09 15:35:00188 base::BindOnce(OnGotCategories, std::move(callback)));
[email protected]bcb6ee23d2013-02-03 04:06:40189 }
[email protected]b83f3972014-01-08 12:16:57190
thestig418c5052016-04-11 19:39:19191 const char kBeginRecordingPath[] = "json/begin_recording?";
192 if (base::StartsWith(path, kBeginRecordingPath,
brettw95509312015-07-16 23:57:33193 base::CompareCase::SENSITIVE)) {
thestig418c5052016-04-11 19:39:19194 std::string data = path.substr(strlen(kBeginRecordingPath));
danakj88bb36b2020-01-09 15:35:00195 return BeginRecording(data, std::move(callback));
[email protected]bcb6ee23d2013-02-03 04:06:40196 }
[email protected]4a0a8f92013-11-21 06:10:34197 if (path == "json/get_buffer_percent_full") {
Sami Kyostilab3f90412020-07-09 11:47:54198 return GetTraceBufferUsage(std::move(callback));
yurysd57ba6fe62014-11-17 10:57:56199 }
simonhatcha13b0a52015-06-23 20:23:08200 if (path == "json/end_recording_compressed") {
Sami Kyostilab3f90412020-07-09 11:47:54201 return EndRecording(std::move(callback));
simonhatcha13b0a52015-06-23 20:23:08202 }
[email protected]b83f3972014-01-08 12:16:57203
[email protected]e4f45cd42014-01-06 20:55:06204 LOG(ERROR) << "Unhandled request to " << path;
205 return false;
206}
207
dpapad28dccc442019-04-09 22:06:40208bool OnShouldHandleRequest(const std::string& path) {
209 return base::StartsWith(path, "json/", base::CompareCase::SENSITIVE);
210}
211
212void OnTracingRequest(const std::string& path,
danakjf4b9e942019-11-29 15:43:04213 WebUIDataSource::GotDataCallback callback) {
dpapad28dccc442019-04-09 22:06:40214 DCHECK(OnShouldHandleRequest(path));
danakj88bb36b2020-01-09 15:35:00215 // OnBeginJSONRequest() only runs |callback| if it returns true. But it needs
216 // to take ownership of |callback| even though it won't call |callback|
217 // sometimes, as it binds |callback| into other callbacks before it makes that
218 // decision. So we must give it one copy and keep one ourselves.
danakj6989c6c2021-05-13 22:20:31219 auto split_callback = base::SplitOnceCallback(std::move(callback));
220 if (!OnBeginJSONRequest(path, std::move(split_callback.first))) {
dpapad28dccc442019-04-09 22:06:40221 std::string error("##ERROR##");
danakj6989c6c2021-05-13 22:20:31222 std::move(split_callback.second)
Jongmok Kimc5491082022-10-19 09:17:59223 .Run(base::MakeRefCounted<base::RefCountedString>(std::move(error)));
[email protected]e4f45cd42014-01-06 20:55:06224 }
[email protected]3dfd28c92013-03-24 05:36:56225}
226
[email protected]bcb6ee23d2013-02-03 04:06:40227} // namespace
228
229
230////////////////////////////////////////////////////////////////////////////////
231//
232// TracingUI
233//
234////////////////////////////////////////////////////////////////////////////////
235
Etienne Pierre-doraya7c28de2024-03-28 22:35:03236TracingUI::TracingUI(WebUI* web_ui) : WebUIController(web_ui) {
[email protected]bcb6ee23d2013-02-03 04:06:40237 // Set up the chrome://tracing/ source.
Juyoung Kimaa699c82022-02-08 01:55:29238 WebUIDataSource* source = WebUIDataSource::CreateAndAdd(
239 web_ui->GetWebContents()->GetBrowserContext(), kChromeUITracingHost);
Jun Kokatsu2e4a4902020-08-05 20:52:16240 source->DisableTrustedTypesCSP();
Dan Beame9f4007f2019-08-17 00:59:10241 source->UseStringsJs();
dpapad57c03ad02020-11-24 18:51:29242 source->SetDefaultResource(IDR_TRACING_ABOUT_TRACING_HTML);
243 source->AddResourcePath("tracing.js", IDR_TRACING_ABOUT_TRACING_JS);
244
dpapad28dccc442019-04-09 22:06:40245 source->SetRequestFilter(base::BindRepeating(OnShouldHandleRequest),
246 base::BindRepeating(OnTracingRequest));
[email protected]06d5b6e2014-01-14 14:55:58247}
248
Oystein Eftevaag2386e052019-10-02 21:08:09249TracingUI::~TracingUI() = default;
[email protected]b12387e62014-06-27 20:52:06250
erikchen3e164f72017-06-22 21:15:44251// static
Sami Kyostilab3f90412020-07-09 11:47:54252bool TracingUI::GetTracingOptions(const std::string& data64,
253 base::trace_event::TraceConfig& trace_config,
254 std::string& out_stream_format) {
erikchen3e164f72017-06-22 21:15:44255 std::string data;
256 if (!base::Base64Decode(data64, &data)) {
257 LOG(ERROR) << "Options were not base64 encoded.";
258 return false;
259 }
260
Arthur Sonzognic686e8f2024-01-11 08:36:37261 std::optional<base::Value> options = base::JSONReader::Read(data);
Scott Haseley02ebe052021-09-16 01:43:00262 if (!options) {
erikchen3e164f72017-06-22 21:15:44263 LOG(ERROR) << "Options were not valid JSON";
264 return false;
265 }
Avi Drissman89545b12023-02-02 05:13:08266 base::Value::Dict* options_dict = options->GetIfDict();
267 if (!options_dict) {
erikchen3e164f72017-06-22 21:15:44268 LOG(ERROR) << "Options must be dict";
269 return false;
270 }
271
Scott Haseley02ebe052021-09-16 01:43:00272 if (const std::string* stream_format =
Avi Drissman89545b12023-02-02 05:13:08273 options_dict->FindString(kStreamFormat)) {
Scott Haseley02ebe052021-09-16 01:43:00274 out_stream_format = *stream_format;
Sami Kyostilab3f90412020-07-09 11:47:54275 } else {
276 out_stream_format = kStreamFormatJSON;
erikchen3e164f72017-06-22 21:15:44277 }
278
Avi Drissman89545b12023-02-02 05:13:08279 // New-style options dictionary.
280 trace_config = base::trace_event::TraceConfig(*options_dict);
erikchen3e164f72017-06-22 21:15:44281 return true;
282}
283
[email protected]bcb6ee23d2013-02-03 04:06:40284} // namespace content