1 /*
2  * Copyright (C) 2020 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 #ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_ASYNC_TRACK_SET_TRACKER_H_
18 #define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_ASYNC_TRACK_SET_TRACKER_H_
19 
20 #include <unordered_map>
21 
22 #include "src/trace_processor/storage/trace_storage.h"
23 
24 namespace perfetto {
25 namespace trace_processor {
26 
27 class TraceProcessorContext;
28 class AsyncTrackSetTrackerUnittest;
29 
30 // Tracker used to reduce the number of trace processor tracks corresponding
31 // to a single "UI track".
32 //
33 // UIs using trace processor want to display all slices in the same context
34 // (e.g. same upid) and same name into a single track. However, because trace
35 // processor does not allow parallel slices on a single track (because it breaks
36 // things like span join, self time computation etc.), at the trace processor
37 // level these parallel slices are put on different tracks.
38 //
39 // Creating a new track for every event, however, leads to an explosion of
40 // tracks which is undesirable. This class exists to multiplex slices so that
41 // n events correspond to a single track in a way which minimises the number of
42 // tracks which needs to be merged by the UI.
43 //
44 // The intended usage of this class is for callers to first call one of the
45 // Intern* methods to obtain a TrackSetId followed by Begin/End just before
46 // calling into SliceTracker's Begin/End respectively. For example:
47 //  TrackSetId set_id = track_set_tracker->InternAndroidSet(upid, name);
48 //  if (event.begin) {
49 //    TrackId id = track_set_tracker->Begin(set_id, cookie);
50 //    slice_tracker->Begin(ts, id, ...)
51 //  } else {
52 //    ... (same thing with end)
53 //  }
54 // Alternatively, instead of Begin/End, Scoped can also be called if supported
55 // by the track type.
56 class AsyncTrackSetTracker {
57  public:
58   using TrackSetId = uint32_t;
59 
60   explicit AsyncTrackSetTracker(TraceProcessorContext* context);
61   ~AsyncTrackSetTracker() = default;
62 
63   // Interns a set of global async slice tracks associated with the given name.
64   TrackSetId InternGlobalTrackSet(StringId name);
65 
66   // Interns a set of Android async slice tracks associated with the given
67   // upid and name.
68   // Scoped is *not* supported for this track set type.
69   TrackSetId InternAndroidSet(UniquePid, StringId name);
70 
71   // Starts a new slice on the given async track set which has the given cookie.
72   TrackId Begin(TrackSetId id, int64_t cookie);
73 
74   // Interns the expected and actual timeline tracks coming from FrameTimeline
75   // producer for the associated upid.
76   TrackSetId InternFrameTimelineSet(UniquePid, StringId name);
77 
78   // Ends a new slice on the given async track set which has the given cookie.
79   TrackId End(TrackSetId id, int64_t cookie);
80 
81   // Creates a scoped slice on the given async track set.
82   // This method makes sure that any other slice in this track set does
83   // not happen simultaneously on the returned track.
84   // Only supported on selected track set types; read the documentation for
85   // the Intern* method for your track type to check if supported.
86   TrackId Scoped(TrackSetId id, int64_t ts, int64_t dur);
87 
88  private:
89   friend class AsyncTrackSetTrackerUnittest;
90 
91   struct AndroidTuple {
92     UniquePid upid;
93     StringId name;
94 
95     friend bool operator<(const AndroidTuple& l, const AndroidTuple& r) {
96       return std::tie(l.upid, l.name) < std::tie(r.upid, r.name);
97     }
98   };
99 
100   struct FrameTimelineTuple {
101     UniquePid upid;
102     StringId name;
103 
104     friend bool operator<(const FrameTimelineTuple& l,
105                           const FrameTimelineTuple& r) {
106       return std::tie(l.upid, l.name) < std::tie(r.upid, r.name);
107     }
108   };
109 
110   // Indicates the nesting behaviour of slices associated to a single slice
111   // stack.
112   enum class NestingBehaviour {
113     // Indicates that slices are unnestable; that is, it is an error
114     // to call Begin -> Begin with a single cookie without End inbetween.
115     // This pattern should be the default behaviour that most async slices
116     // should use.
117     kUnnestable,
118 
119     // Indicates that slices are unnestable but also saturating; that is
120     // calling Begin -> Begin only causes a single Begin to be recorded.
121     // This is only really useful for Android async slices which have this
122     // behaviour for legacy reasons. See the comment in
123     // SystraceParser::ParseSystracePoint for information on why
124     // this behaviour exists.
125     kLegacySaturatingUnnestable,
126   };
127 
128   enum class TrackSetType {
129     kGlobal,
130     kAndroid,
131     kFrameTimeline,
132   };
133 
134   struct TrackState {
135     TrackId id;
136 
137     enum class SliceType { kCookie, kTimestamp };
138     SliceType slice_type;
139 
140     union {
141       // Only valid for |slice_type| == |SliceType::kCookie|.
142       int64_t cookie;
143 
144       // Only valid for |slice_type| == |SliceType::kTimestamp|.
145       int64_t ts_end;
146     };
147 
148     // Only used for |slice_type| == |SliceType::kCookie|.
149     uint32_t nest_count;
150   };
151 
152   struct TrackSet {
153     TrackSetType type;
154     union {
155       StringId global_track_name;
156       // Only set when |type| == |TrackSetType::kAndroid|.
157       AndroidTuple android_tuple;
158       // Only set when |type| == |TrackSetType::kFrameTimeline|.
159       FrameTimelineTuple frame_timeline_tuple;
160     };
161     NestingBehaviour nesting_behaviour;
162     std::vector<TrackState> tracks;
163   };
164 
CreateUnnestableTrackSetForTesting(UniquePid upid,StringId name)165   TrackSetId CreateUnnestableTrackSetForTesting(UniquePid upid, StringId name) {
166     AsyncTrackSetTracker::TrackSet set;
167     set.android_tuple = AndroidTuple{upid, name};
168     set.type = AsyncTrackSetTracker::TrackSetType::kAndroid;
169     set.nesting_behaviour = NestingBehaviour::kUnnestable;
170     track_sets_.emplace_back(set);
171     return static_cast<TrackSetId>(track_sets_.size() - 1);
172   }
173 
174   // Returns the state for a track using the following algorithm:
175   // 1. If a track exists with the given cookie in the track set, returns
176   //    that track.
177   // 2. Otherwise, looks for any track in the set which is "open" (i.e.
178   //    does not have another slice currently scheduled).
179   // 3. Otherwise, creates a new track and associates it with the set.
180   TrackState& GetOrCreateTrackForCookie(TrackSet& set, int64_t cookie);
181 
182   TrackId CreateTrackForSet(const TrackSet& set);
183 
184   std::map<StringId, TrackSetId> global_track_set_ids_;
185   std::map<AndroidTuple, TrackSetId> android_track_set_ids_;
186   std::map<FrameTimelineTuple, TrackSetId> frame_timeline_track_set_ids_;
187   std::vector<TrackSet> track_sets_;
188 
189   TraceProcessorContext* const context_;
190 };
191 
192 }  // namespace trace_processor
193 }  // namespace perfetto
194 
195 #endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_ASYNC_TRACK_SET_TRACKER_H_
196