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