• 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     // First check if the value exists in the dictionary.
180     auto index_for_field = GetOrCreateIndexForField(ctx->incremental_state_);
181     size_t iid;
182     if (PERFETTO_LIKELY(index_for_field->index_.LookUpOrInsert(&iid, value))) {
183       PERFETTO_DCHECK(iid);
184       return iid;
185     }
186 
187     // If not, we need to serialize the definition of the interned value into
188     // the heap buffered message (which is committed to the trace when the
189     // packet ends).
190     PERFETTO_DCHECK(iid);
191     InternedDataType::Add(
192         ctx->incremental_state_->serialized_interned_data.get(), iid,
193         std::move(value), std::forward<Args>(add_args)...);
194     return iid;
195   }
196 
197  protected:
198   // Some use cases require a custom Get implemention, so they need access to
199   // GetOrCreateIndexForField + the returned index.
GetOrCreateIndexForField(internal::TrackEventIncrementalState * incremental_state)200   static InternedDataType* GetOrCreateIndexForField(
201       internal::TrackEventIncrementalState* incremental_state) {
202     // Fast path: look for matching field number.
203     for (const auto& entry : incremental_state->interned_data_indices) {
204       if (entry.first == FieldNumber) {
205 #if PERFETTO_DCHECK_IS_ON()
206         if (strcmp(PERFETTO_DEBUG_FUNCTION_IDENTIFIER(),
207                    entry.second->type_id_)) {
208           PERFETTO_FATAL(
209               "Interned data accessed under different types! Previous type: "
210               "%s. New type: %s.",
211               entry.second->type_id_, PERFETTO_DEBUG_FUNCTION_IDENTIFIER());
212         }
213         // If an interned data index is defined in an anonymous namespace, we
214         // can end up with multiple copies of it in the same program. Because
215         // they will all share a memory address through TLS, this can lead to
216         // subtle data corruption if all the copies aren't exactly identical.
217         // Try to detect this by checking if the Add() function address remains
218         // constant.
219         if (reinterpret_cast<void*>(&InternedDataType::Add) !=
220             entry.second->add_function_ptr_) {
221           PERFETTO_FATAL(
222               "Inconsistent interned data index. Maybe the index was defined "
223               "in an anonymous namespace in a header or copied to multiple "
224               "files? Duplicate index definitions can lead to memory "
225               "corruption! Type id: %s",
226               entry.second->type_id_);
227         }
228 #endif  // PERFETTO_DCHECK_IS_ON()
229         return reinterpret_cast<InternedDataType*>(entry.second.get());
230       }
231     }
232     // No match -- add a new entry for this field.
233     for (auto& entry : incremental_state->interned_data_indices) {
234       if (!entry.first) {
235         entry.first = FieldNumber;
236         entry.second.reset(new InternedDataType());
237 #if PERFETTO_DCHECK_IS_ON()
238         entry.second->type_id_ = PERFETTO_DEBUG_FUNCTION_IDENTIFIER();
239         entry.second->add_function_ptr_ =
240             reinterpret_cast<void*>(&InternedDataType::Add);
241 #endif  // PERFETTO_DCHECK_IS_ON()
242         return reinterpret_cast<InternedDataType*>(entry.second.get());
243       }
244     }
245     // Out of space in the interned data index table.
246     PERFETTO_CHECK(false);
247   }
248 
249   // The actual interning dictionary for this type of interned data. The actual
250   // container type is defined by |Traits|, hence the extra layer of template
251   // indirection here.
252   typename Traits::template Index<ValueType> index_;
253 };
254 
255 }  // namespace perfetto
256 
257 #endif  // INCLUDE_PERFETTO_TRACING_TRACK_EVENT_INTERNED_DATA_INDEX_H_
258