1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/chromeos/arc/tracing/arc_tracing_bridge.h"
6
7 #include <utility>
8
9 #include "base/bind.h"
10 #include "base/callback_helpers.h"
11 #include "base/files/file.h"
12 #include "base/logging.h"
13 #include "base/memory/singleton.h"
14 #include "base/no_destructor.h"
15 #include "base/posix/unix_domain_socket.h"
16 #include "base/threading/thread_task_runner_handle.h"
17 #include "base/time/time.h"
18 #include "base/trace_event/trace_config.h"
19 #include "base/trace_event/trace_event.h"
20 #include "components/arc/arc_browser_context_keyed_service_factory_base.h"
21 #include "components/arc/arc_service_manager.h"
22 #include "components/arc/mojom/tracing.mojom.h"
23 #include "components/arc/session/arc_bridge_service.h"
24 #include "content/public/browser/browser_task_traits.h"
25 #include "content/public/browser/browser_thread.h"
26 #include "mojo/public/cpp/system/platform_handle.h"
27 #include "services/tracing/public/cpp/perfetto/perfetto_traced_process.h"
28 #include "services/tracing/public/cpp/perfetto/system_trace_writer.h"
29 #include "services/tracing/public/mojom/constants.mojom.h"
30 #include "services/tracing/public/mojom/perfetto_service.mojom.h"
31
32 namespace arc {
33
34 namespace {
35
36 // The maximum size used to store one trace event. The ad hoc trace format for
37 // atrace is 1024 bytes. Here we add additional size as we're using JSON and
38 // have additional data fields.
39 constexpr size_t kArcTraceMessageLength = 1024 + 512;
40
41 // The prefix of the categories to be shown on the trace selection UI.
42 // The space at the end of the string is intentional as the separator between
43 // the prefix and the real categories.
44 constexpr char kCategoryPrefix[] = TRACE_DISABLED_BY_DEFAULT("android ");
45
46 // Singleton factory for ArcTracingBridge.
47 class ArcTracingBridgeFactory
48 : public internal::ArcBrowserContextKeyedServiceFactoryBase<
49 ArcTracingBridge,
50 ArcTracingBridgeFactory> {
51 public:
52 // Factory name used by ArcBrowserContextKeyedServiceFactoryBase.
53 static constexpr const char* kName = "ArcTracingBridgeFactory";
54
GetInstance()55 static ArcTracingBridgeFactory* GetInstance() {
56 return base::Singleton<ArcTracingBridgeFactory>::get();
57 }
58
59 private:
60 friend base::DefaultSingletonTraits<ArcTracingBridgeFactory>;
61 ArcTracingBridgeFactory() = default;
62 ~ArcTracingBridgeFactory() override = default;
63 };
64
65 // Perfetto data source which coordinates ARC tracing sessions with perfetto's
66 // PerfettoProducer when perfetto is used as the tracing backend.
67 class ArcTracingDataSource
68 : public tracing::PerfettoTracedProcess::DataSourceBase {
69 public:
GetInstance()70 static ArcTracingDataSource* GetInstance() {
71 static base::NoDestructor<ArcTracingDataSource> instance;
72 return instance.get();
73 }
74
75 // Called after constructing |bridge|.
RegisterBridgeOnUI(ArcTracingBridge * bridge)76 void RegisterBridgeOnUI(ArcTracingBridge* bridge) {
77 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
78 DCHECK_EQ(ArcTracingBridge::State::kDisabled, bridge->state());
79 bool success = bridges_.insert(bridge).second;
80 DCHECK(success);
81
82 if (producer_ && !stop_complete_callback_) {
83 // We're currently tracing, so start the new bridge, too.
84 // |this| never gets destructed, so it's OK to bind an unretained pointer.
85 bridge->StartTracing(
86 data_source_config_.chrome_config().trace_config(),
87 base::BindOnce(&ArcTracingDataSource::OnTracingStartedOnUI,
88 base::Unretained(this)));
89 }
90 }
91
92 // Called when destructing |bridge|.
UnregisterBridgeOnUI(ArcTracingBridge * bridge)93 void UnregisterBridgeOnUI(ArcTracingBridge* bridge) {
94 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
95 const size_t erase_count = bridges_.erase(bridge);
96 DCHECK_EQ(1u, erase_count);
97
98 // Make sure we don't continue to wait for any of the bridge's callbacks.
99 OnTracingStartedOnUI(false /*success*/);
100 OnTraceDataOnUI(std::string() /*data*/);
101 }
102
103 private:
104 friend class base::NoDestructor<ArcTracingDataSource>;
105
ArcTracingDataSource()106 ArcTracingDataSource()
107 : DataSourceBase(tracing::mojom::kArcTraceDataSourceName),
108 perfetto_task_runner_(tracing::PerfettoTracedProcess::Get()
109 ->GetTaskRunner()
110 ->GetOrCreateTaskRunner()) {
111 tracing::PerfettoTracedProcess::Get()->AddDataSource(this);
112 }
113
114 // Note that ArcTracingDataSource is a singleton that's never destroyed.
115 ~ArcTracingDataSource() override = default;
116
117 // tracing::PerfettoProducer::DataSourceBase.
StartTracing(tracing::PerfettoProducer * producer,const perfetto::DataSourceConfig & data_source_config)118 void StartTracing(
119 tracing::PerfettoProducer* producer,
120 const perfetto::DataSourceConfig& data_source_config) override {
121 // |this| never gets destructed, so it's OK to bind an unretained pointer.
122 // |producer| is a singleton that is never destroyed.
123 content::GetUIThreadTaskRunner({})->PostTask(
124 FROM_HERE,
125 base::BindOnce(&ArcTracingDataSource::StartTracingOnUI,
126 base::Unretained(this), producer, data_source_config));
127 }
128
StopTracing(base::OnceClosure stop_complete_callback)129 void StopTracing(base::OnceClosure stop_complete_callback) override {
130 // |this| never gets destructed, so it's OK to bind an unretained pointer.
131 content::GetUIThreadTaskRunner({})->PostTask(
132 FROM_HERE, base::BindOnce(&ArcTracingDataSource::StopTracingOnUI,
133 base::Unretained(this),
134 std::move(stop_complete_callback)));
135 }
136
Flush(base::RepeatingClosure flush_complete_callback)137 void Flush(base::RepeatingClosure flush_complete_callback) override {
138 // ARC's tracing service doesn't currently support flushing while recording.
139 flush_complete_callback.Run();
140 }
141
142 // Starts all registered bridges.
StartTracingOnUI(tracing::PerfettoProducer * producer,const perfetto::DataSourceConfig & data_source_config)143 void StartTracingOnUI(tracing::PerfettoProducer* producer,
144 const perfetto::DataSourceConfig& data_source_config) {
145 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
146
147 data_source_config_ = data_source_config;
148
149 for (ArcTracingBridge* bridge : bridges_) {
150 // |this| never gets destructed, so it's OK to bind an unretained pointer.
151 bridge->StartTracing(
152 data_source_config_.chrome_config().trace_config(),
153 base::BindOnce(&ArcTracingDataSource::OnTracingStartedOnUI,
154 base::Unretained(this)));
155 }
156 }
157
158 // Stops all registered bridges. Calls |stop_complete_callback| when all
159 // bridges have stopped.
StopTracingOnUI(base::OnceClosure stop_complete_callback)160 void StopTracingOnUI(base::OnceClosure stop_complete_callback) {
161 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
162
163 // We may receive a StopTracing without StartTracing.
164 if (!producer_)
165 return;
166
167 // We may still be in startup. In this case, store a callback to rerun
168 // StopTracingOnUI() once startup is complete.
169 if (IsAnyBridgeStarting()) {
170 DCHECK(!pending_stop_tracing_);
171 pending_stop_tracing_ = base::BindOnce(
172 &ArcTracingDataSource::StopTracingOnUI, base::Unretained(this),
173 std::move(stop_complete_callback));
174 return;
175 }
176
177 stop_complete_callback_ = std::move(stop_complete_callback);
178
179 for (ArcTracingBridge* bridge : bridges_) {
180 // StopTracingOnUI should only be called once all bridges have completed
181 // or abandoned startup.
182 DCHECK_NE(ArcTracingBridge::State::kStarting, bridge->state());
183 if (bridge->state() != ArcTracingBridge::State::kEnabled)
184 continue;
185 // |this| never gets destructed, so it's OK to bind an unretained pointer.
186 bridge->StopAndFlush(base::BindOnce(
187 &ArcTracingDataSource::OnTraceDataOnUI, base::Unretained(this)));
188 }
189
190 // There may not have been any bridges left in State::kEnabled. This will
191 // call the callback if all bridges are already stopped.
192 OnTraceDataOnUI(std::string());
193 }
194
195 // Called by each bridge when it has started tracing. Also called when a
196 // bridge is unregisted.
OnTracingStartedOnUI(bool success)197 void OnTracingStartedOnUI(bool success) {
198 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
199 if (!IsAnyBridgeStarting() && pending_stop_tracing_)
200 std::move(pending_stop_tracing_).Run();
201 }
202
203 // Called by each bridge when it has stopped tracing. Also called when a
204 // bridge is unregisted. Records the supplied |data| into the
205 // |producer_|'s buffer.
OnTraceDataOnUI(const std::string & data)206 void OnTraceDataOnUI(const std::string& data) {
207 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
208
209 // When a bridge unregisters, we may not actually be stopping.
210 if (!stop_complete_callback_)
211 return;
212
213 DCHECK(producer_);
214
215 if (!data.empty()) {
216 if (!trace_writer_) {
217 trace_writer_ =
218 std::make_unique<tracing::SystemTraceWriter<std::string>>(
219 producer_, data_source_config_.target_buffer(),
220 tracing::SystemTraceWriter<std::string>::TraceType::kJson);
221 } else {
222 trace_writer_->WriteData(",");
223 }
224 trace_writer_->WriteData(data);
225 }
226
227 if (AreAllBridgesStopped()) {
228 if (!trace_writer_) {
229 OnTraceDataCommittedOnUI();
230 return;
231 }
232 trace_writer_->Flush(
233 base::BindOnce(&ArcTracingDataSource::OnTraceDataCommittedOnUI,
234 base::Unretained(this)));
235 }
236 }
237
OnTraceDataCommittedOnUI()238 void OnTraceDataCommittedOnUI() {
239 producer_ = nullptr;
240 perfetto_task_runner_->PostTask(FROM_HERE,
241 std::move(stop_complete_callback_));
242 }
243
IsAnyBridgeStarting() const244 bool IsAnyBridgeStarting() const {
245 for (ArcTracingBridge* bridge : bridges_) {
246 if (bridge->state() == ArcTracingBridge::State::kStarting)
247 return true;
248 }
249 return false;
250 }
251
AreAllBridgesStopped() const252 bool AreAllBridgesStopped() const {
253 for (ArcTracingBridge* bridge : bridges_) {
254 if (bridge->state() != ArcTracingBridge::State::kDisabled)
255 return false;
256 }
257 return true;
258 }
259
260 scoped_refptr<base::SequencedTaskRunner> perfetto_task_runner_;
261 std::set<ArcTracingBridge*> bridges_;
262 // In case StopTracing() is called before tracing was started for all bridges,
263 // this stores a callback to StopTracing() that's executed when all bridges
264 // have started.
265 base::OnceClosure pending_stop_tracing_;
266 // Called when all bridges have completed stopping, notifying
267 // PerfettoProducer.
268 base::OnceClosure stop_complete_callback_;
269 perfetto::DataSourceConfig data_source_config_;
270 std::unique_ptr<tracing::SystemTraceWriter<std::string>> trace_writer_;
271
272 DISALLOW_COPY_AND_ASSIGN(ArcTracingDataSource);
273 };
274
275 } // namespace
276
277 struct ArcTracingBridge::Category {
278 // The name used by Android to trigger tracing.
279 std::string name;
280 // The full name shown in the tracing UI in chrome://tracing.
281 std::string full_name;
282 };
283
284 // static
GetForBrowserContext(content::BrowserContext * context)285 ArcTracingBridge* ArcTracingBridge::GetForBrowserContext(
286 content::BrowserContext* context) {
287 return ArcTracingBridgeFactory::GetForBrowserContext(context);
288 }
289
ArcTracingBridge(content::BrowserContext * context,ArcBridgeService * bridge_service)290 ArcTracingBridge::ArcTracingBridge(content::BrowserContext* context,
291 ArcBridgeService* bridge_service)
292 : arc_bridge_service_(bridge_service),
293 agent_(this),
294 reader_(std::make_unique<ArcTracingReader>()) {
295 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
296 arc_bridge_service_->tracing()->AddObserver(this);
297 ArcTracingDataSource::GetInstance()->RegisterBridgeOnUI(this);
298 }
299
~ArcTracingBridge()300 ArcTracingBridge::~ArcTracingBridge() {
301 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
302 ArcTracingDataSource::GetInstance()->UnregisterBridgeOnUI(this);
303 arc_bridge_service_->tracing()->RemoveObserver(this);
304
305 // Delete the reader on the IO thread.
306 content::GetIOThreadTaskRunner({})->DeleteSoon(FROM_HERE, reader_.release());
307 }
308
GetCategories(std::set<std::string> * category_set)309 void ArcTracingBridge::GetCategories(std::set<std::string>* category_set) {
310 base::AutoLock lock(categories_lock_);
311 for (const auto& category : categories_) {
312 category_set->insert(category.full_name);
313 }
314 }
315
OnConnectionReady()316 void ArcTracingBridge::OnConnectionReady() {
317 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
318 mojom::TracingInstance* tracing_instance = ARC_GET_INSTANCE_FOR_METHOD(
319 arc_bridge_service_->tracing(), QueryAvailableCategories);
320 if (!tracing_instance)
321 return;
322 tracing_instance->QueryAvailableCategories(base::BindOnce(
323 &ArcTracingBridge::OnCategoriesReady, weak_ptr_factory_.GetWeakPtr()));
324 }
325
OnCategoriesReady(const std::vector<std::string> & categories)326 void ArcTracingBridge::OnCategoriesReady(
327 const std::vector<std::string>& categories) {
328 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
329
330 base::AutoLock lock(categories_lock_);
331 // There is no API in TraceLog to remove a category from the UI. As an
332 // alternative, the old category that is no longer in |categories_| will be
333 // ignored when calling |StartTracing|.
334 categories_.clear();
335 for (const auto& category : categories)
336 categories_.emplace_back(Category{category, kCategoryPrefix + category});
337 }
338
StartTracing(const std::string & config,SuccessCallback callback)339 void ArcTracingBridge::StartTracing(const std::string& config,
340 SuccessCallback callback) {
341 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
342
343 if (state_ != State::kDisabled) {
344 DLOG(WARNING) << "Cannot start tracing, it is already enabled.";
345 std::move(callback).Run(false /*success*/);
346 return;
347 }
348 state_ = State::kStarting;
349
350 base::trace_event::TraceConfig trace_config(config);
351
352 base::ScopedFD write_fd, read_fd;
353 bool success =
354 trace_config.IsSystraceEnabled() && CreateSocketPair(&read_fd, &write_fd);
355
356 if (!success) {
357 OnArcTracingStarted(std::move(callback), false /*success*/);
358 return;
359 }
360
361 mojom::TracingInstance* tracing_instance =
362 ARC_GET_INSTANCE_FOR_METHOD(arc_bridge_service_->tracing(), StartTracing);
363 if (!tracing_instance) {
364 OnArcTracingStarted(std::move(callback), false /*success*/);
365 return;
366 }
367
368 std::vector<std::string> selected_categories;
369 {
370 base::AutoLock lock(categories_lock_);
371 for (const auto& category : categories_) {
372 if (trace_config.IsCategoryGroupEnabled(category.full_name))
373 selected_categories.push_back(category.name);
374 }
375 }
376
377 tracing_instance->StartTracing(
378 selected_categories, mojo::WrapPlatformFile(std::move(write_fd)),
379 base::BindOnce(&ArcTracingBridge::OnArcTracingStarted,
380 weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
381
382 // |reader_| will be destroyed after us on the IO thread, so it's OK to use an
383 // unretained pointer.
384 content::GetIOThreadTaskRunner({})->PostTask(
385 FROM_HERE,
386 base::BindOnce(&ArcTracingReader::StartTracing,
387 base::Unretained(reader_.get()), std::move(read_fd)));
388 }
389
OnArcTracingStarted(SuccessCallback callback,bool success)390 void ArcTracingBridge::OnArcTracingStarted(SuccessCallback callback,
391 bool success) {
392 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
393 DCHECK_EQ(State::kStarting, state_);
394 state_ = success ? State::kEnabled : State::kDisabled;
395 std::move(callback).Run(success);
396 }
397
StopAndFlush(TraceDataCallback callback)398 void ArcTracingBridge::StopAndFlush(TraceDataCallback callback) {
399 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
400
401 if (state_ != State::kEnabled) {
402 DLOG(WARNING) << "Cannot stop tracing, it is not enabled.";
403 std::move(callback).Run(std::string());
404 return;
405 }
406 state_ = State::kStopping;
407
408 mojom::TracingInstance* tracing_instance =
409 ARC_GET_INSTANCE_FOR_METHOD(arc_bridge_service_->tracing(), StopTracing);
410 if (!tracing_instance) {
411 OnArcTracingStopped(std::move(callback), false);
412 return;
413 }
414 tracing_instance->StopTracing(
415 base::BindOnce(&ArcTracingBridge::OnArcTracingStopped,
416 weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
417 }
418
OnArcTracingStopped(TraceDataCallback tracing_stopped_callback,bool success)419 void ArcTracingBridge::OnArcTracingStopped(
420 TraceDataCallback tracing_stopped_callback,
421 bool success) {
422 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
423 DCHECK_EQ(State::kStopping, state_);
424 if (!success) {
425 DLOG(WARNING) << "Failed to stop ARC tracing, closing file anyway.";
426 }
427 // |reader_| will be destroyed after us on the IO thread, so it's OK to use an
428 // unretained pointer.
429 content::GetIOThreadTaskRunner({})->PostTaskAndReplyWithResult(
430 FROM_HERE,
431 base::BindOnce(&ArcTracingReader::StopTracing,
432 base::Unretained(reader_.get())),
433 base::BindOnce(&ArcTracingBridge::OnTracingReaderStopped,
434 weak_ptr_factory_.GetWeakPtr(),
435 std::move(tracing_stopped_callback)));
436 }
437
OnTracingReaderStopped(TraceDataCallback tracing_stopped_callback,const std::string & data)438 void ArcTracingBridge::OnTracingReaderStopped(
439 TraceDataCallback tracing_stopped_callback,
440 const std::string& data) {
441 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
442 DCHECK_EQ(State::kStopping, state_);
443 state_ = State::kDisabled;
444 std::move(tracing_stopped_callback).Run(data);
445 }
446
ArcTracingAgent(ArcTracingBridge * bridge)447 ArcTracingBridge::ArcTracingAgent::ArcTracingAgent(ArcTracingBridge* bridge)
448 : bridge_(bridge) {}
449
450 ArcTracingBridge::ArcTracingAgent::~ArcTracingAgent() = default;
451
GetCategories(std::set<std::string> * category_set)452 void ArcTracingBridge::ArcTracingAgent::GetCategories(
453 std::set<std::string>* category_set) {
454 bridge_->GetCategories(category_set);
455 }
456
457 ArcTracingBridge::ArcTracingReader::ArcTracingReader() = default;
458
~ArcTracingReader()459 ArcTracingBridge::ArcTracingReader::~ArcTracingReader() {
460 DCHECK(!fd_watcher_);
461 }
462
StartTracing(base::ScopedFD read_fd)463 void ArcTracingBridge::ArcTracingReader::StartTracing(base::ScopedFD read_fd) {
464 DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
465 read_fd_ = std::move(read_fd);
466 // We own |fd_watcher_|, so it's OK to use an unretained pointer.
467 fd_watcher_ = base::FileDescriptorWatcher::WatchReadable(
468 read_fd_.get(),
469 base::BindRepeating(&ArcTracingReader::OnTraceDataAvailable,
470 base::Unretained(this)));
471 }
472
OnTraceDataAvailable()473 void ArcTracingBridge::ArcTracingReader::OnTraceDataAvailable() {
474 DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
475
476 char buf[kArcTraceMessageLength];
477 std::vector<base::ScopedFD> unused_fds;
478 ssize_t n = base::UnixDomainSocket::RecvMsg(
479 read_fd_.get(), buf, kArcTraceMessageLength, &unused_fds);
480 if (n < 0) {
481 DPLOG(WARNING) << "Unexpected error while reading trace from client.";
482 // Do nothing here as StopTracing will do the clean up and the existing
483 // trace logs will be returned.
484 return;
485 }
486
487 if (n == 0) {
488 // When EOF is reached, stop listening for changes since there's never
489 // going to be any more data to be read. The rest of the cleanup will be
490 // done in StopTracing.
491 fd_watcher_.reset();
492 return;
493 }
494
495 if (n > static_cast<ssize_t>(kArcTraceMessageLength)) {
496 DLOG(WARNING) << "Unexpected data size when reading trace from client.";
497 return;
498 }
499 ring_buffer_.SaveToBuffer(std::string(buf, n));
500 }
501
StopTracing()502 std::string ArcTracingBridge::ArcTracingReader::StopTracing() {
503 DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
504 fd_watcher_.reset();
505 read_fd_.reset();
506
507 bool append_comma = false;
508 std::string data;
509 for (auto it = ring_buffer_.Begin(); it; ++it) {
510 if (append_comma)
511 data.append(",");
512 else
513 append_comma = true;
514 data.append(**it);
515 }
516 ring_buffer_.Clear();
517
518 return data;
519 }
520
521 } // namespace arc
522