1 /*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "common/debug.h"
18 #include "common/expected.h"
19 #include "manager/event_manager.h"
20 #include "perfetto/rx_producer.h"
21
22 #include <android-base/properties.h>
23 #include <rxcpp/rx.hpp>
24
25 #include <atomic>
26 #include <functional>
27
28 using rxcpp::observe_on_one_worker;
29
30 namespace iorap::manager {
31
32 using binder::RequestId;
33 using binder::AppLaunchEvent;
34 using perfetto::PerfettoStreamCommand;
35 using perfetto::PerfettoTraceProto;
36
37 struct AppComponentName {
38 std::string package;
39 std::string activity_name;
40
HasAppComponentNameiorap::manager::AppComponentName41 static bool HasAppComponentName(const std::string& s) {
42 return s.find('/') != std::string::npos;
43 }
44
45 // "com.foo.bar/.A" -> {"com.foo.bar", ".A"}
FromStringiorap::manager::AppComponentName46 static AppComponentName FromString(const std::string& s) {
47 constexpr const char delimiter = '/';
48 std::string package = s.substr(0, delimiter);
49
50 std::string activity_name = s;
51 activity_name.erase(0, s.find(delimiter) + sizeof(delimiter));
52
53 return {std::move(package), std::move(activity_name)};
54 }
55
56 // {"com.foo.bar", ".A"} -> "com.foo.bar/.A"
ToStringiorap::manager::AppComponentName57 std::string ToString() const {
58 return package + "/" + activity_name;
59 }
60
61 /*
62 * '/' is encoded into %2F
63 * '%' is encoded into %25
64 *
65 * This allows the component name to be be used as a file name
66 * ('/' is illegal due to being a path separator) with minimal
67 * munging.
68 */
69
70 // "com.foo.bar%2F.A%25" -> {"com.foo.bar", ".A%"}
FromUrlEncodedStringiorap::manager::AppComponentName71 static AppComponentName FromUrlEncodedString(const std::string& s) {
72 std::string cpy = s;
73 Replace(cpy, "%2F", "/");
74 Replace(cpy, "%25", "%");
75
76 return FromString(cpy);
77 }
78
79 // {"com.foo.bar", ".A%"} -> "com.foo.bar%2F.A%25"
ToUrlEncodedStringiorap::manager::AppComponentName80 std::string ToUrlEncodedString() const {
81 std::string s = ToString();
82 Replace(s, "%", "%25");
83 Replace(s, "/", "%2F");
84 return s;
85 }
86
87 private:
Replaceiorap::manager::AppComponentName88 static bool Replace(std::string& str, const std::string& from, const std::string& to) {
89 // TODO: call in a loop to replace all occurrences, not just the first one.
90 const size_t start_pos = str.find(from);
91 if (start_pos == std::string::npos) {
92 return false;
93 }
94
95 str.replace(start_pos, from.length(), to);
96
97 return true;
98 }
99 };
100
operator <<(std::ostream & os,const AppComponentName & name)101 std::ostream& operator<<(std::ostream& os, const AppComponentName& name) {
102 os << name.ToString();
103 return os;
104 }
105
106 // Main logic of the #OnAppLaunchEvent scan method.
107 //
108 // All functions are called from the same thread as the event manager
109 // functions.
110 //
111 // This is a data type, it's moved (std::move) around from one iteration
112 // of #scan to another.
113 struct AppLaunchEventState {
114 std::optional<AppComponentName> component_name_;
115
116 bool is_tracing_{false};
117 std::optional<rxcpp::composite_subscription> rx_lifetime_;
118 std::vector<rxcpp::composite_subscription> rx_in_flight_;
119
120 borrowed<perfetto::RxProducerFactory*> perfetto_factory_; // not null
121 borrowed<observe_on_one_worker*> thread_; // not null
122 borrowed<observe_on_one_worker*> io_thread_; // not null
123
AppLaunchEventStateiorap::manager::AppLaunchEventState124 explicit AppLaunchEventState(borrowed<perfetto::RxProducerFactory*> perfetto_factory,
125 borrowed<observe_on_one_worker*> thread,
126 borrowed<observe_on_one_worker*> io_thread) {
127 perfetto_factory_ = perfetto_factory;
128 DCHECK(perfetto_factory_ != nullptr);
129
130 thread_ = thread;
131 DCHECK(thread_ != nullptr);
132
133 io_thread_ = io_thread;
134 DCHECK(io_thread_ != nullptr);
135 }
136
137 // Updates the values in this struct only as a side effect.
138 //
139 // May create and fire a new rx chain on the same threads as passed
140 // in by the constructors.
OnNewEventiorap::manager::AppLaunchEventState141 void OnNewEvent(const AppLaunchEvent& event) {
142 LOG(VERBOSE) << "AppLaunchEventState#OnNewEvent: " << event;
143
144 using Type = AppLaunchEvent::Type;
145
146 switch (event.type) {
147 case Type::kIntentStarted: {
148 DCHECK(!IsTracing());
149 // Optimistically start tracing if we have the activity in the intent.
150 if (!event.intent_proto->has_component()) {
151 // Can't do anything if there is no component in the proto.
152 LOG(VERBOSE) << "AppLaunchEventState#OnNewEvent: no component, can't trace";
153 break;
154 }
155
156 const std::string& package_name = event.intent_proto->component().package_name();
157 const std::string& class_name = event.intent_proto->component().class_name();
158 AppComponentName component_name{package_name, class_name};
159
160 component_name_ = component_name;
161 rx_lifetime_ = StartTracing(std::move(component_name));
162
163 break;
164 }
165 case Type::kIntentFailed:
166 AbortTrace();
167 break;
168 case Type::kActivityLaunched: {
169 // Cancel tracing for warm/hot.
170 // Restart tracing if the activity was unexpected.
171
172 AppLaunchEvent::Temperature temperature = event.temperature;
173 if (temperature != AppLaunchEvent::Temperature::kCold) {
174 LOG(DEBUG) << "AppLaunchEventState#OnNewEvent aborting trace due to non-cold temperature";
175 AbortTrace();
176 } else if (!IsTracing()) { // and the temperature is Cold.
177 // Start late trace when intent didn't have a component name
178 LOG(VERBOSE) << "AppLaunchEventState#OnNewEvent need to start new trace";
179
180 const std::string& title = event.activity_record_proto->identifier().title();
181 if (!AppComponentName::HasAppComponentName(title)) {
182 // Proto comment claim this is sometimes a window title.
183 // We need the actual 'package/component' here, so just ignore it if it's a title.
184 LOG(WARNING) << "App launched without a component name: " << event;
185 break;
186 }
187
188 AppComponentName component_name = AppComponentName::FromString(title);
189
190 component_name_ = component_name;
191 rx_lifetime_ = StartTracing(std::move(component_name));
192 } else {
193 // FIXME: match actual component name against intent component name.
194 // abort traces if they don't match.
195
196 LOG(VERBOSE) << "AppLaunchEventState#OnNewEvent already tracing";
197 }
198 break;
199 }
200 case Type::kActivityLaunchFinished:
201 // Finish tracing and collect trace buffer.
202 //
203 // TODO: this happens automatically when perfetto finishes its
204 // trace duration.
205 if (IsTracing()) {
206 MarkPendingTrace();
207 }
208 break;
209 case Type::kActivityLaunchCancelled:
210 // Abort tracing.
211 AbortTrace();
212 break;
213 default:
214 DCHECK(false) << "invalid type: " << event; // binder layer should've rejected this.
215 LOG(ERROR) << "invalid type: " << event; // binder layer should've rejected this.
216 }
217 }
218
IsTracingiorap::manager::AppLaunchEventState219 bool IsTracing() const {
220 return is_tracing_;
221 }
222
StartTracingiorap::manager::AppLaunchEventState223 rxcpp::composite_subscription StartTracing(AppComponentName component_name) {
224 DCHECK(!IsTracing());
225
226 auto /*observable<PerfettoStreamCommand>*/ perfetto_commands =
227 rxcpp::observable<>::just(PerfettoStreamCommand::kStartTracing)
228 // wait 1x
229 .concat(
230 // Pick a value longer than the perfetto config delay_ms, so that we send
231 // 'kShutdown' after tracing has already finished.
232 rxcpp::observable<>::interval(std::chrono::milliseconds(10000))
233 .take(2) // kStopTracing, kShutdown.
234 .map([](int value) {
235 // value is 1,2,3,...
236 return static_cast<PerfettoStreamCommand>(value); // 1,2, ...
237 })
238 );
239
240 auto /*observable<PerfettoTraceProto>*/ trace_proto_stream =
241 perfetto_factory_->CreateTraceStream(perfetto_commands);
242 // This immediately connects to perfetto asynchronously.
243 //
244 // TODO: create a perfetto handle earlier, to minimize perfetto startup latency.
245
246 rxcpp::composite_subscription lifetime;
247
248 trace_proto_stream
249 .tap([](const PerfettoTraceProto& trace_proto) {
250 LOG(VERBOSE) << "StartTracing -- PerfettoTraceProto received (1)";
251 })
252 .observe_on(*thread_) // All work prior to 'observe_on' is handled on thread_.
253 .subscribe_on(*thread_) // All work prior to 'observe_on' is handled on thread_.
254 .observe_on(*io_thread_) // Write data on an idle-class-priority thread.
255 .tap([](const PerfettoTraceProto& trace_proto) {
256 LOG(VERBOSE) << "StartTracing -- PerfettoTraceProto received (2)";
257 })
258 .as_blocking() // TODO: remove.
259 .subscribe(/*out*/lifetime,
260 /*on_next*/[component_name]
261 (PerfettoTraceProto trace_proto) {
262 std::string file_path = "/data/misc/iorapd/";
263 file_path += component_name.ToUrlEncodedString();
264 file_path += ".perfetto_trace.pb";
265
266 // TODO: timestamp each file into a subdirectory.
267
268 if (!trace_proto.WriteFullyToFile(file_path)) {
269 LOG(ERROR) << "Failed to save TraceBuffer to " << file_path;
270 } else {
271 LOG(INFO) << "Perfetto TraceBuffer saved to file: " << file_path;
272 }
273 },
274 /*on_error*/[](rxcpp::util::error_ptr err) {
275 LOG(ERROR) << "Perfetto trace proto collection error: " << rxcpp::util::what(err);
276 });
277
278 is_tracing_ = true;
279
280 return lifetime;
281 }
282
AbortTraceiorap::manager::AppLaunchEventState283 void AbortTrace() {
284 LOG(VERBOSE) << "AppLaunchEventState - AbortTrace";
285 is_tracing_ = false;
286 if (rx_lifetime_) {
287 // TODO: it would be good to call perfetto Destroy.
288
289 LOG(VERBOSE) << "AppLaunchEventState - AbortTrace - Unsubscribe";
290 rx_lifetime_->unsubscribe();
291 rx_lifetime_.reset();
292 }
293 }
294
MarkPendingTraceiorap::manager::AppLaunchEventState295 void MarkPendingTrace() {
296 LOG(VERBOSE) << "AppLaunchEventState - MarkPendingTrace";
297 DCHECK(is_tracing_);
298 DCHECK(rx_lifetime_.has_value());
299
300 if (rx_lifetime_) {
301 LOG(VERBOSE) << "AppLaunchEventState - MarkPendingTrace - lifetime moved";
302 // Don't unsubscribe because that would cause the perfetto TraceBuffer
303 // to get dropped on the floor.
304 //
305 // Instead, we want to let it finish and write it out to a file.
306 rx_in_flight_.push_back(*std::move(rx_lifetime_));
307 rx_lifetime_.reset();
308 } else {
309 LOG(VERBOSE) << "AppLaunchEventState - MarkPendingTrace - lifetime was empty";
310 }
311
312 // FIXME: how do we clear this vector?
313 }
314 };
315
316 // Convert callback pattern into reactive pattern.
317 struct AppLaunchEventSubject {
318 using RefWrapper =
319 std::reference_wrapper<const AppLaunchEvent>;
320
AppLaunchEventSubjectiorap::manager::AppLaunchEventSubject321 AppLaunchEventSubject() {}
322
Subscribeiorap::manager::AppLaunchEventSubject323 void Subscribe(rxcpp::subscriber<RefWrapper> subscriber) {
324 DCHECK(ready_ != true) << "Cannot Subscribe twice";
325
326 subscriber_ = std::move(subscriber);
327
328 // Release edge of synchronizes-with AcquireIsReady.
329 ready_.store(true);
330 }
331
OnNextiorap::manager::AppLaunchEventSubject332 void OnNext(const AppLaunchEvent& e) {
333 if (!AcquireIsReady()) {
334 return;
335 }
336
337 if (!subscriber_->is_subscribed()) {
338 return;
339 }
340
341 /*
342 * TODO: fix upstream.
343 *
344 * Rx workaround: this fails to compile when
345 * the observable is a reference type:
346 *
347 * external/Reactive-Extensions/RxCpp/Rx/v2/src/rxcpp/rx-observer.hpp:354:18: error: multiple overloads of 'on_next' instantiate to the same signature 'void (const iorap::binder::AppLaunchEvent &) const'
348 * virtual void on_next(T&&) const {};
349 *
350 * external/Reactive-Extensions/RxCpp/Rx/v2/src/rxcpp/rx-observer.hpp:353:18: note: previous declaration is here
351 * virtual void on_next(T&) const {};
352 *
353 * (The workaround is to use reference_wrapper instead
354 * of const AppLaunchEvent&)
355 */
356 subscriber_->on_next(std::cref(e));
357
358 }
359
OnCompletediorap::manager::AppLaunchEventSubject360 void OnCompleted() {
361 if (!AcquireIsReady()) {
362 return;
363 }
364
365 subscriber_->on_completed();
366 }
367
368 private:
AcquireIsReadyiorap::manager::AppLaunchEventSubject369 bool AcquireIsReady() {
370 // Synchronizes-with the release-edge in Subscribe.
371 // This can happen much later, only once the subscription actually happens.
372
373 // However, as far as I know, 'rxcpp::subscriber' is not thread safe,
374 // (but the observable chain itself can be made thread-safe via #observe_on, etc).
375 // so we must avoid reading it until it has been fully synchronized.
376 //
377 // TODO: investigate rxcpp subscribers and see if we can get rid of this atomics,
378 // to make it simpler.
379 return ready_.load();
380 }
381
382 // TODO: also track the RequestId ?
383
384 std::atomic<bool> ready_{false};
385
386
387 std::optional<rxcpp::subscriber<RefWrapper>> subscriber_;
388 };
389
390 class EventManager::Impl {
391 public:
Impl(perfetto::RxProducerFactory & perfetto_factory)392 Impl(/*borrow*/perfetto::RxProducerFactory& perfetto_factory)
393 : perfetto_factory_(perfetto_factory),
394 worker_thread_(rxcpp::observe_on_new_thread()),
395 worker_thread2_(rxcpp::observe_on_new_thread()),
396 io_thread_(perfetto::ObserveOnNewIoThread()) {
397
398 // TODO: read all properties from one config class.
399 tracing_allowed_ = ::android::base::GetBoolProperty("iorapd.perfetto.enable", /*default*/false);
400
401 if (tracing_allowed_) {
402 rx_lifetime_ = InitializeRxGraph();
403 } else {
404 LOG(WARNING) << "Tracing disabled by iorapd.perfetto.enable=false";
405 }
406 }
407
OnAppLaunchEvent(RequestId request_id,const AppLaunchEvent & event)408 bool OnAppLaunchEvent(RequestId request_id,
409 const AppLaunchEvent& event) {
410 LOG(VERBOSE) << "EventManager::OnAppLaunchEvent("
411 << "request_id=" << request_id.request_id << ","
412 << event;
413
414 app_launch_event_subject_.OnNext(event);
415
416 return true;
417 }
418
InitializeRxGraph()419 rxcpp::composite_subscription InitializeRxGraph() {
420 LOG(VERBOSE) << "EventManager::InitializeRxGraph";
421
422 app_launch_events_ = rxcpp::observable<>::create<AppLaunchEventRefWrapper>(
423 [&](rxcpp::subscriber<AppLaunchEventRefWrapper> subscriber) {
424 app_launch_event_subject_.Subscribe(std::move(subscriber));
425 });
426
427 rxcpp::composite_subscription lifetime;
428
429 AppLaunchEventState initial_state{&perfetto_factory_, &worker_thread2_, &io_thread_};
430 app_launch_events_
431 .subscribe_on(worker_thread_)
432 .scan(std::move(initial_state),
433 [](AppLaunchEventState state, AppLaunchEventRefWrapper event) {
434 state.OnNewEvent(event.get());
435 return state;
436 })
437 .subscribe(/*out*/lifetime, [](const AppLaunchEventState& state) {
438 // Intentionally left blank.
439 (void)state;
440 });
441
442 return lifetime;
443 }
444
445 perfetto::RxProducerFactory& perfetto_factory_;
446 bool tracing_allowed_{true};
447
448 using AppLaunchEventRefWrapper = AppLaunchEventSubject::RefWrapper;
449 rxcpp::observable<AppLaunchEventRefWrapper> app_launch_events_;
450 AppLaunchEventSubject app_launch_event_subject_;
451
452 rxcpp::observable<RequestId> completed_requests_;
453
454 // regular-priority thread to handle binder callbacks.
455 observe_on_one_worker worker_thread_;
456 observe_on_one_worker worker_thread2_;
457 // low priority idle-class thread for IO operations.
458 observe_on_one_worker io_thread_;
459
460 rxcpp::composite_subscription rx_lifetime_;
461
462 //INTENTIONAL_COMPILER_ERROR_HERE:
463 // FIXME:
464 // ok so we want to expose a 'BlockingSubscribe' or a 'Subscribe' or some kind of function
465 // that the main thread can call. This would subscribe on all the observables we internally
466 // have here (probably on an event-manager-dedicated thread for simplicity).
467 //
468 // ideally we'd just reuse the binder thread to handle the events but I'm not super sure,
469 // maybe this already works with the identity_current_thread coordination?
470 };
471 using Impl = EventManager::Impl;
472
EventManager(perfetto::RxProducerFactory & perfetto_factory)473 EventManager::EventManager(perfetto::RxProducerFactory& perfetto_factory)
474 : impl_(new Impl(perfetto_factory)) {}
475
Create()476 std::shared_ptr<EventManager> EventManager::Create() {
477 static perfetto::PerfettoDependencies::Injector injector{
478 perfetto::PerfettoDependencies::CreateComponent
479 };
480 static perfetto::RxProducerFactory producer_factory{
481 /*borrow*/injector
482 };
483 return EventManager::Create(/*borrow*/producer_factory);
484 }
485
Create(perfetto::RxProducerFactory & perfetto_factory)486 std::shared_ptr<EventManager> EventManager::Create(perfetto::RxProducerFactory& perfetto_factory) {
487 std::shared_ptr<EventManager> p{new EventManager{/*borrow*/perfetto_factory}};
488 return p;
489 }
490
OnAppLaunchEvent(RequestId request_id,const AppLaunchEvent & event)491 bool EventManager::OnAppLaunchEvent(RequestId request_id,
492 const AppLaunchEvent& event) {
493 return impl_->OnAppLaunchEvent(request_id, event);
494 }
495
496 } // namespace iorap::manager
497