• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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