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