• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #ifndef INCLUDE_PERFETTO_TRACING_TRACK_EVENT_INTERNED_DATA_INDEX_H_
18 #define INCLUDE_PERFETTO_TRACING_TRACK_EVENT_INTERNED_DATA_INDEX_H_
19 
20 #include "perfetto/tracing/internal/track_event_internal.h"
21 
22 #include "perfetto/base/compiler.h"
23 #include "perfetto/tracing/event_context.h"
24 
25 #include <map>
26 #include <type_traits>
27 #include <unordered_map>
28 
29 // This file has templates for defining your own interned data types to be used
30 // with track event. Interned data can be useful for avoiding repeating the same
31 // constant data (e.g., strings) throughout the trace.
32 //
33 // =============
34 // Example usage
35 // =============
36 //
37 // First define an interning index for your type. It should map to a specific
38 // field of interned_data.proto and define how the interned data is written into
39 // that message.
40 //
41 //   struct MyInternedData
42 //       : public perfetto::TrackEventInternedDataIndex<
43 //           MyInternedData,
44 //           perfetto::protos::pbzero::InternedData::kMyInternedDataFieldNumber,
45 //           const char*> {
46 //     static void Add(perfetto::protos::pbzero::InternedData* interned_data,
47 //                      size_t iid,
48 //                      const char* value) {
49 //       auto my_data = interned_data->add_my_interned_data();
50 //       my_data->set_iid(iid);
51 //       my_data->set_value(value);
52 //     }
53 //   };
54 //
55 // Next, use your interned data in a trace point as shown below. The interned
56 // string will only be emitted the first time the trace point is hit.
57 //
58 //   TRACE_EVENT_BEGIN(
59 //      "category", "Event", [&](perfetto::EventContext ctx) {
60 //        auto my_message = ctx.event()->set_my_message();
61 //        size_t iid = MyInternedData::Get(&ctx, "Some data");
62 //        my_message->set_iid(iid);
63 //      });
64 //
65 
66 namespace perfetto {
67 
68 // By default, the interning index stores a full copy of the interned data. This
69 // ensures the same data is always mapped to the same interning id, and there is
70 // no danger of collisions. This comes at the cost of memory usage, however, so
71 // consider using HashedInternedDataTraits if that may be an issue.
72 //
73 // This type of index also performs hashing on the stored data for lookups; for
74 // types where this isn't necessary (e.g., raw const char*), use
75 // SmallInternedDataTraits.
76 struct BigInternedDataTraits {
77   template <typename ValueType>
78   class Index {
79    public:
LookUpOrInsertBigInternedDataTraits80     bool LookUpOrInsert(size_t* iid, const ValueType& value) {
81       size_t next_id = data_.size() + 1;
82       auto it_and_inserted = data_.insert(std::make_pair(value, next_id));
83       if (!it_and_inserted.second) {
84         *iid = it_and_inserted.first->second;
85         return true;
86       }
87       *iid = next_id;
88       return false;
89     }
90 
91    private:
92     std::unordered_map<ValueType, size_t> data_;
93   };
94 };
95 
96 // This type of interning index keeps full copies of interned data without
97 // hashing the values. This is a good fit for small types that can be directly
98 // used as index keys.
99 struct SmallInternedDataTraits {
100   template <typename ValueType>
101   class Index {
102    public:
LookUpOrInsertSmallInternedDataTraits103     bool LookUpOrInsert(size_t* iid, const ValueType& value) {
104       size_t next_id = data_.size() + 1;
105       auto it_and_inserted = data_.insert(std::make_pair(value, next_id));
106       if (!it_and_inserted.second) {
107         *iid = it_and_inserted.first->second;
108         return true;
109       }
110       *iid = next_id;
111       return false;
112     }
113 
114    private:
115     std::map<ValueType, size_t> data_;
116   };
117 };
118 
119 // This type of interning index only stores the hash of the interned values
120 // instead of the values themselves. This is more efficient in terms of memory
121 // usage, but assumes that there are no hash collisions. If a hash collision
122 // occurs, two or more values will be mapped to the same interning id.
123 //
124 // Note that the given type must have a specialization for std::hash.
125 struct HashedInternedDataTraits {
126   template <typename ValueType>
127   class Index {
128    public:
LookUpOrInsertHashedInternedDataTraits129     bool LookUpOrInsert(size_t* iid, const ValueType& value) {
130       auto key = std::hash<ValueType>()(value);
131       size_t next_id = data_.size() + 1;
132       auto it_and_inserted = data_.insert(std::make_pair(key, next_id));
133       if (!it_and_inserted.second) {
134         *iid = it_and_inserted.first->second;
135         return true;
136       }
137       *iid = next_id;
138       return false;
139     }
140 
141    private:
142     std::map<size_t, size_t> data_;
143   };
144 };
145 
146 // A templated base class for an interned data type which corresponds to a field
147 // in interned_data.proto.
148 //
149 // |InternedDataType| must be the type of the subclass.
150 // |FieldNumber| is the corresponding protobuf field in InternedData.
151 // |ValueType| is the type which is stored in the index. It must be copyable.
152 // |Traits| can be used to customize the storage and lookup mechanism.
153 //
154 // The subclass should define a static method with the following signature for
155 // committing interned data together with the interning id |iid| into the trace:
156 //
157 //   static void Add(perfetto::protos::pbzero::InternedData*,
158 //                   size_t iid,
159 //                   const ValueType& value);
160 //
161 template <typename InternedDataType,
162           size_t FieldNumber,
163           typename ValueType,
164           // Avoid unnecessary hashing for pointers by default.
165           typename Traits =
166               typename std::conditional<(std::is_pointer<ValueType>::value),
167                                         SmallInternedDataTraits,
168                                         BigInternedDataTraits>::type>
169 class TrackEventInternedDataIndex
170     : public internal::BaseTrackEventInternedDataIndex {
171  public:
172   // Return an interning id for |value|. The returned id can be immediately
173   // written to the trace. The optional |add_args| are passed to the Add()
174   // function.
175   template <typename... Args>
Get(EventContext * ctx,const ValueType & value,Args &&...add_args)176   static size_t Get(EventContext* ctx,
177                     const ValueType& value,
178                     Args&&... add_args) {
179     return Get(ctx->incremental_state_, value, std::forward<Args>(add_args)...);
180   }
181 
182   template <typename... Args>
Get(internal::TrackEventIncrementalState * incremental_state,const ValueType & value,Args &&...add_args)183   static size_t Get(internal::TrackEventIncrementalState* incremental_state,
184                     const ValueType& value,
185                     Args&&... add_args) {
186     // First check if the value exists in the dictionary.
187     auto index_for_field = GetOrCreateIndexForField(incremental_state);
188     size_t iid;
189     if (PERFETTO_LIKELY(index_for_field->index_.LookUpOrInsert(&iid, value))) {
190       PERFETTO_DCHECK(iid);
191       return iid;
192     }
193 
194     // If not, we need to serialize the definition of the interned value into
195     // the heap buffered message (which is committed to the trace when the
196     // packet ends).
197     PERFETTO_DCHECK(iid);
198     InternedDataType::Add(incremental_state->serialized_interned_data.get(),
199                           iid, std::move(value),
200                           std::forward<Args>(add_args)...);
201     return iid;
202   }
203 
204  protected:
205   // Some use cases require a custom Get implemention, so they need access to
206   // GetOrCreateIndexForField + the returned index.
GetOrCreateIndexForField(internal::TrackEventIncrementalState * incremental_state)207   static InternedDataType* GetOrCreateIndexForField(
208       internal::TrackEventIncrementalState* incremental_state) {
209     // Fast path: look for matching field number.
210     for (const auto& entry : incremental_state->interned_data_indices) {
211       if (entry.first == FieldNumber) {
212 #if PERFETTO_DCHECK_IS_ON()
213         if (strcmp(PERFETTO_DEBUG_FUNCTION_IDENTIFIER(),
214                    entry.second->type_id_)) {
215           PERFETTO_FATAL(
216               "Interned data accessed under different types! Previous type: "
217               "%s. New type: %s.",
218               entry.second->type_id_, PERFETTO_DEBUG_FUNCTION_IDENTIFIER());
219         }
220         // If an interned data index is defined in an anonymous namespace, we
221         // can end up with multiple copies of it in the same program. Because
222         // they will all share a memory address through TLS, this can lead to
223         // subtle data corruption if all the copies aren't exactly identical.
224         // Try to detect this by checking if the Add() function address remains
225         // constant.
226         if (reinterpret_cast<void*>(&InternedDataType::Add) !=
227             entry.second->add_function_ptr_) {
228           PERFETTO_FATAL(
229               "Inconsistent interned data index. Maybe the index was defined "
230               "in an anonymous namespace in a header or copied to multiple "
231               "files? Duplicate index definitions can lead to memory "
232               "corruption! Type id: %s",
233               entry.second->type_id_);
234         }
235 #endif  // PERFETTO_DCHECK_IS_ON()
236         return reinterpret_cast<InternedDataType*>(entry.second.get());
237       }
238     }
239     // No match -- add a new entry for this field.
240     for (auto& entry : incremental_state->interned_data_indices) {
241       if (!entry.first) {
242         entry.first = FieldNumber;
243         entry.second.reset(new InternedDataType());
244 #if PERFETTO_DCHECK_IS_ON()
245         entry.second->type_id_ = PERFETTO_DEBUG_FUNCTION_IDENTIFIER();
246         entry.second->add_function_ptr_ =
247             reinterpret_cast<void*>(&InternedDataType::Add);
248 #endif  // PERFETTO_DCHECK_IS_ON()
249         return reinterpret_cast<InternedDataType*>(entry.second.get());
250       }
251     }
252     // Out of space in the interned data index table.
253     PERFETTO_CHECK(false);
254   }
255 
256   // The actual interning dictionary for this type of interned data. The actual
257   // container type is defined by |Traits|, hence the extra layer of template
258   // indirection here.
259   typename Traits::template Index<ValueType> index_;
260 };
261 
262 }  // namespace perfetto
263 
264 #endif  // INCLUDE_PERFETTO_TRACING_TRACK_EVENT_INTERNED_DATA_INDEX_H_
265