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_COMMON_TRACK_COMPRESSOR_H_
18 #define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_TRACK_COMPRESSOR_H_
19
20 #include <cstddef>
21 #include <cstdint>
22 #include <string_view>
23 #include <tuple>
24 #include <type_traits>
25 #include <utility>
26 #include <vector>
27
28 #include "perfetto/ext/base/flat_hash_map.h"
29 #include "perfetto/ext/base/hash.h"
30 #include "src/trace_processor/importers/common/track_tracker.h"
31 #include "src/trace_processor/importers/common/tracks.h"
32 #include "src/trace_processor/importers/common/tracks_internal.h"
33 #include "src/trace_processor/storage/trace_storage.h"
34 #include "src/trace_processor/types/trace_processor_context.h"
35
36 namespace perfetto::trace_processor {
37
38 class TrackCompressorUnittest;
39
40 namespace internal {
41
42 template <typename Ds, size_t r, size_t... Is>
UncompressedDimensions(Ds,std::integral_constant<size_t,r>,std::index_sequence<Is...>)43 constexpr auto UncompressedDimensions(Ds,
44 std::integral_constant<size_t, r>,
45 std::index_sequence<Is...>) {
46 static_assert(r > 0,
47 "Wrong blueprint passed to TrackCompressor Intern* function. "
48 "Make sure Blueprint was created using "
49 "TrackCompressor::SliceBlueprint *not* tracks::SliceBlueprint");
50 return tracks::Dimensions(std::tuple_element_t<Is, Ds>()...);
51 }
52
53 template <typename BlueprintT>
54 using uncompressed_dimensions_t = decltype(UncompressedDimensions(
55 typename BlueprintT::dimensions_t(),
56 std::integral_constant<
57 size_t,
58 std::tuple_size_v<typename BlueprintT::dimensions_t>>(),
59 std::make_index_sequence<
60 std::tuple_size_v<typename BlueprintT::dimensions_t> == 0
61 ? 0
62 : std::tuple_size_v<typename BlueprintT::dimensions_t> - 1>()));
63
64 } // namespace internal
65
66 // "Compresses" and interns trace processor tracks for a given track type.
67 //
68 // When writing traces, sometimes it's not possible to reuse tracks meaning
69 // people create one track per event. Creating a new track for every event,
70 // however, leads to an explosion of tracks which is undesirable. This class
71 // exists to multiplex slices so that multiple events correspond to a single
72 // track in a way which minimises the number of tracks.
73 //
74 // WARNING: the usage of this class SHOULD BE VERY RARE. These days, this class
75 // mainly exists for legacy usage due to how the Perfetto UI used to work rather
76 // than an active choice. Prefer making tracks peers and adding a UI plugin if
77 // you want custom visualization instead of using this class.
78 class TrackCompressor {
79 public:
80 explicit TrackCompressor(TraceProcessorContext* context);
81 ~TrackCompressor() = default;
82
83 // Starts a new slice which has the given cookie.
84 template <typename BlueprintT>
85 TrackId InternBegin(
86 const BlueprintT& bp,
87 const internal::uncompressed_dimensions_t<BlueprintT>& dims,
88 int64_t cookie,
89 const typename BlueprintT::name_t& name = tracks::BlueprintName()) {
90 uint64_t hash = tracks::HashFromBlueprintAndDimensions(bp, dims);
91 auto final_dims = std::tuple_cat(
92 dims, std::make_tuple(BeginInternal(TypeToNestingBehaviour(bp.type),
93 hash, cookie)));
94 return context_->track_tracker->InternTrack(bp, final_dims, name);
95 }
96
97 // Ends a new slice which has the given cookie.
98 template <typename BlueprintT>
99 TrackId InternEnd(
100 BlueprintT bp,
101 const internal::uncompressed_dimensions_t<BlueprintT>& dims,
102 int64_t cookie,
103 const typename BlueprintT::name_t& name = tracks::BlueprintName()) {
104 uint64_t hash = tracks::HashFromBlueprintAndDimensions(bp, dims);
105 auto final_dims =
106 std::tuple_cat(dims, std::make_tuple(EndInternal(hash, cookie)));
107 return context_->track_tracker->InternTrack(bp, final_dims, name);
108 }
109
110 // Creates a scoped slice.
111 // This method makes sure that any other slice in this track set does
112 // not happen simultaneously on the returned track.
113 template <typename BlueprintT>
114 TrackId InternScoped(
115 BlueprintT bp,
116 const internal::uncompressed_dimensions_t<BlueprintT>& dims,
117 int64_t ts,
118 int64_t dur,
119 const typename BlueprintT::name_t& name = tracks::BlueprintName()) {
120 uint64_t hash = tracks::HashFromBlueprintAndDimensions(bp, dims);
121 auto final_dims =
122 std::tuple_cat(dims, std::make_tuple(ScopedInternal(hash, ts, dur)));
123 return context_->track_tracker->InternTrack(bp, final_dims, name);
124 }
125
126 // Wrapper around tracks::SliceBlueprint which makes the blueprint eligible
127 // for compression with TrackCompressor. Please see documentation of
128 // tracks::SliceBlueprint for usage.
129 template <typename NB = tracks::NameBlueprintT::Auto, typename... D>
130 static constexpr auto SliceBlueprint(
131 const char type[],
132 tracks::DimensionBlueprintsT<D...> dimensions = {},
133 NB name = NB{}) {
134 auto blueprint = tracks::SliceBlueprint(type, dimensions, name);
135 using BT = decltype(blueprint);
136 constexpr auto kCompressorIdxDimensionIndex =
137 std::tuple_size_v<typename BT::dimension_blueprints_t>;
138 return std::apply(
139 [&](auto... x) {
140 auto blueprints = blueprint.dimension_blueprints;
141 blueprints[kCompressorIdxDimensionIndex] =
142 tracks::UintDimensionBlueprint("track_compressor_idx");
143
144 if constexpr (std::is_base_of_v<tracks::NameBlueprintT::FnBase,
145 typename BT::name_blueprint_t>) {
146 using F = decltype(blueprint.name_blueprint.fn);
147 auto fn =
148 MakeNameFn<F, decltype(x)...>(blueprint.name_blueprint.fn);
149 return tracks::BlueprintT<
150 decltype(fn), typename BT::unit_blueprint_t, decltype(x)...,
151 tracks::DimensionBlueprintT<uint32_t>>{
152 {
153 blueprint.event_type,
154 blueprint.type,
155 blueprint.hasher,
156 blueprints,
157 },
158 fn,
159 blueprint.unit_blueprint,
160 };
161 } else {
162 return tracks::BlueprintT<
163 typename BT::name_blueprint_t, typename BT::unit_blueprint_t,
164 decltype(x)..., tracks::DimensionBlueprintT<uint32_t>>{
165 {
166 blueprint.event_type,
167 blueprint.type,
168 blueprint.hasher,
169 blueprints,
170 },
171 blueprint.name_blueprint,
172 blueprint.unit_blueprint,
173 };
174 }
175 },
176 typename BT::dimension_blueprints_t());
177 }
178
179 private:
180 friend class TrackCompressorUnittest;
181
182 // Indicates the nesting behaviour of slices associated to a single slice
183 // stack.
184 enum class NestingBehaviour {
185 // Indicates that slices are nestable; that is, a stack of slices with
186 // the same cookie should stack properly, not merely overlap.
187 //
188 // This pattern should be the default behaviour that most async slices
189 // should use.
190 kNestable,
191
192 // Indicates that slices are unnestable but also saturating; that is
193 // calling Begin -> Begin only causes a single Begin to be recorded.
194 // This is only really useful for Android async slices which have this
195 // behaviour for legacy reasons. See the comment in
196 // SystraceParser::ParseSystracePoint for information on why
197 // this behaviour exists.
198 kLegacySaturatingUnnestable,
199 };
200
201 struct TrackState {
202 enum class SliceType { kCookie, kTimestamp };
203 SliceType slice_type;
204
205 union {
206 // Only valid for |slice_type| == |SliceType::kCookie|.
207 int64_t cookie;
208
209 // Only valid for |slice_type| == |SliceType::kTimestamp|.
210 int64_t ts_end;
211 };
212
213 // Only used for |slice_type| == |SliceType::kCookie|.
214 uint32_t nest_count;
215 };
216
217 struct TrackSetNew {
218 std::vector<TrackState> tracks;
219 };
220
221 uint32_t BeginInternal(NestingBehaviour, uint64_t hash, int64_t cookie);
222
223 uint32_t EndInternal(uint64_t hash, int64_t cookie);
224
225 uint32_t ScopedInternal(uint64_t hash, int64_t ts, int64_t dur);
226
TypeToNestingBehaviour(std::string_view type)227 static constexpr NestingBehaviour TypeToNestingBehaviour(
228 std::string_view type) {
229 if (type == "atrace_async_slice") {
230 return NestingBehaviour::kLegacySaturatingUnnestable;
231 }
232 return NestingBehaviour::kNestable;
233 }
234
235 template <typename F, typename... T>
MakeNameFn(F fn)236 static constexpr auto MakeNameFn(F fn) {
237 auto f = [fn](typename T::type... y, uint32_t) { return fn(y...); };
238 return tracks::NameBlueprintT::Fn<decltype(f)>{{}, f};
239 }
240
241 // Returns the state for a track using the following algorithm:
242 // 1. If a track exists with the given cookie in the vector, returns
243 // that track.
244 // 2. Otherwise, looks for any track in the set which is "open" (i.e.
245 // does not have another slice currently scheduled).
246 // 3. Otherwise, creates a new track and adds it to the vector.
247 static TrackState& GetOrCreateTrackForCookie(std::vector<TrackState>& tracks,
248 int64_t cookie);
249
250 base::FlatHashMap<uint64_t, TrackSetNew, base::AlreadyHashed<uint64_t>> sets_;
251
252 TraceProcessorContext* const context_;
253 };
254
255 } // namespace perfetto::trace_processor
256
257 #endif // SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_TRACK_COMPRESSOR_H_
258