1 /* 2 * Copyright 2018 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 #include "tuningfork_internal.h" 18 19 #include <cinttypes> 20 #include <vector> 21 #include <map> 22 #include <memory> 23 #include <chrono> 24 #include <sstream> 25 26 #define LOG_TAG "TuningFork" 27 #include "Log.h" 28 #include "pb_decode.h" 29 #include "Trace.h" 30 31 #include "histogram.h" 32 #include "prong.h" 33 #include "uploadthread.h" 34 #include "clearcutserializer.h" 35 #include "tuningfork/protobuf_nano_util.h" 36 #include "clearcut_backend.h" 37 #include "annotation_util.h" 38 39 using PBSettings = com_google_tuningfork_Settings; 40 41 /* Annotations come into tuning fork as a serialized protobuf. The protobuf can only have 42 * enums in it. We form an integer annotation id from the annotation interpreted as a mixed-radix 43 * number. E.g. say we have the following in the proto: 44 * enum A { A_1 = 1, A_2 = 2, A_3 = 3}; 45 * enum B { B_1 = 1, B_2 = 2}; 46 * enum C { C_1 = 1}; 47 * extend Annotation { optional A a = 1; optional B b = 2; optional C c = 3}; 48 * Then a serialization might be: 49 * 0x16 0x01 50 * Here, 'a' and 'c' are missing and 'b' has the value B_1. Note the shift of 3 bits for the key. 51 * https://developers.google.com/protocol-buffers/docs/encoding 52 * 53 * Assume we have 2 possible instrumentation keys: NUM_IKEY = 2 54 * 55 * The annotation id then is (0 + 1*4 + 0)*NUM_IKEY = 8, where the factor of 4 comes from the radix 56 * associated with 'a', i.e. 3 values for enum A + option missing 57 * 58 * A compound id is formed from the annotation id and the instrument key: 59 * compound_id = annotation_id + instrument_key; 60 * 61 * So for instrument key 1, the compound_id with the above annotation is 9 62 * 63 * This compound_id is used to look up a histogram in the ProngCache. 64 * 65 * annotation_radix_mult_ stores the multiplied radixes, so for the above, it is: 66 * [4 4*3 4*3*2] = [4 12 24] 67 * and the maximum number of annotations is 24 68 * 69 * */ 70 71 namespace tuningfork { 72 73 typedef uint64_t AnnotationId; 74 75 class MonoTimeProvider : public ITimeProvider { 76 public: 77 virtual TimePoint NowNs() { 78 return std::chrono::steady_clock::now(); 79 } 80 }; 81 82 std::unique_ptr<MonoTimeProvider> s_mono_time_provider = std::make_unique<MonoTimeProvider>(); 83 84 class TuningForkImpl { 85 private: 86 Settings settings_; 87 std::unique_ptr<ProngCache> prong_caches_[2]; 88 ProngCache *current_prong_cache_; 89 TimePoint last_submit_time_ns_; 90 std::unique_ptr<gamesdk::Trace> trace_; 91 std::vector<TimePoint> live_traces_; 92 Backend *backend_; 93 ParamsLoader *loader_; 94 UploadThread upload_thread_; 95 SerializedAnnotation current_annotation_; 96 std::vector<int> annotation_radix_mult_; 97 AnnotationId current_annotation_id_; 98 ITimeProvider *time_provider_; 99 public: 100 TuningForkImpl(const Settings& settings, 101 const ExtraUploadInfo& extra_upload_info, 102 Backend *backend, 103 ParamsLoader *loader, 104 ITimeProvider *time_provider) : settings_(settings), 105 trace_(gamesdk::Trace::create()), 106 backend_(backend), 107 loader_(loader), 108 upload_thread_(backend, extra_upload_info), 109 current_annotation_id_(0), 110 time_provider_(time_provider) { 111 if (time_provider_ == nullptr) { 112 time_provider_ = s_mono_time_provider.get(); 113 } 114 last_submit_time_ns_ = time_provider_->NowNs(); 115 116 InitHistogramSettings(); 117 InitAnnotationRadixes(); 118 119 size_t max_num_prongs_ = 0; 120 int max_ikeys = settings.aggregation_strategy.max_instrumentation_keys; 121 122 if (annotation_radix_mult_.size() == 0 || max_ikeys == 0) 123 ALOGE("Neither max_annotations nor max_instrumentation_keys can be zero"); 124 else 125 max_num_prongs_ = max_ikeys * annotation_radix_mult_.back(); 126 auto serializeId = [this](uint64_t id) { return SerializeAnnotationId(id); }; 127 prong_caches_[0] = std::make_unique<ProngCache>(max_num_prongs_, max_ikeys, 128 settings_.histograms, serializeId); 129 prong_caches_[1] = std::make_unique<ProngCache>(max_num_prongs_, max_ikeys, 130 settings_.histograms, serializeId); 131 current_prong_cache_ = prong_caches_[0].get(); 132 live_traces_.resize(max_num_prongs_); 133 for (auto &t: live_traces_) t = TimePoint::min(); 134 135 ALOGI("TuningFork initialized"); 136 } 137 138 ~TuningForkImpl() { 139 } 140 141 void InitHistogramSettings(); 142 143 void InitAnnotationRadixes(); 144 145 // Returns true if the fidelity params were retrieved 146 bool GetFidelityParameters(const ProtobufSerialization& defaultParams, 147 ProtobufSerialization &fidelityParams, size_t timeout_ms); 148 149 // Returns the set annotation id or -1 if it could not be set 150 uint64_t SetCurrentAnnotation(const ProtobufSerialization &annotation); 151 152 void FrameTick(InstrumentationKey id); 153 154 void FrameDeltaTimeNanos(InstrumentationKey id, Duration dt); 155 156 // Returns the handle to be used by EndTrace 157 TraceHandle StartTrace(InstrumentationKey key); 158 159 void EndTrace(TraceHandle); 160 161 void SetUploadCallback(void(*cbk)(const CProtobufSerialization*)); 162 163 private: 164 Prong *TickNanos(uint64_t compound_id, TimePoint t); 165 166 Prong *TraceNanos(uint64_t compound_id, Duration dt); 167 168 void CheckForSubmit(TimePoint t_ns, Prong *prong); 169 170 bool ShouldSubmit(TimePoint t_ns, Prong *prong); 171 172 AnnotationId DecodeAnnotationSerialization(const SerializedAnnotation &ser); 173 174 int GetInstrumentationKey(uint64_t compoundId) { 175 return compoundId % settings_.aggregation_strategy.max_instrumentation_keys; 176 } 177 178 uint64_t MakeCompoundId(InstrumentationKey k, AnnotationId a) { 179 return k + a; 180 } 181 182 SerializedAnnotation SerializeAnnotationId(uint64_t); 183 }; 184 185 std::unique_ptr<TuningForkImpl> s_impl; 186 187 bool decodeAnnotationEnumSizes(pb_istream_t* stream, const pb_field_t *field, void** arg) { 188 Settings* settings = static_cast<Settings*>(*arg); 189 uint64_t a; 190 pb_decode_varint(stream, &a); 191 settings->aggregation_strategy.annotation_enum_size.push_back(a); 192 return true; 193 } 194 bool decodeHistograms(pb_istream_t* stream, const pb_field_t *field, void** arg) { 195 Settings* settings = static_cast<Settings*>(*arg); 196 com_google_tuningfork_Settings_Histogram hist; 197 pb_decode(stream, com_google_tuningfork_Settings_Histogram_fields, &hist); 198 settings->histograms.push_back({hist.instrument_key, hist.bucket_min, hist.bucket_max, 199 hist.n_buckets}); 200 return true; 201 } 202 203 void Init(const ProtobufSerialization &settings_ser, 204 const ExtraUploadInfo& extra_upload_info, 205 Backend *backend, 206 ParamsLoader *loader, 207 ITimeProvider *time_provider) { 208 Settings settings; 209 PBSettings pbsettings = com_google_tuningfork_Settings_init_zero; 210 pbsettings.aggregation_strategy.annotation_enum_size.funcs.decode = decodeAnnotationEnumSizes; 211 pbsettings.aggregation_strategy.annotation_enum_size.arg = &settings; 212 pbsettings.histograms.funcs.decode = decodeHistograms; 213 pbsettings.histograms.arg = &settings; 214 VectorStream str {const_cast<ProtobufSerialization*>(&settings_ser), 0}; 215 pb_istream_t stream = {VectorStream::Read, &str, settings_ser.size()}; 216 pb_decode(&stream, com_google_tuningfork_Settings_fields, &pbsettings); 217 if(pbsettings.aggregation_strategy.method 218 ==com_google_tuningfork_Settings_AggregationStrategy_Submission_TICK_BASED) 219 settings.aggregation_strategy.method = Settings::AggregationStrategy::TICK_BASED; 220 else 221 settings.aggregation_strategy.method = Settings::AggregationStrategy::TIME_BASED; 222 settings.aggregation_strategy.intervalms_or_count 223 = pbsettings.aggregation_strategy.intervalms_or_count; 224 settings.aggregation_strategy.max_instrumentation_keys 225 = pbsettings.aggregation_strategy.max_instrumentation_keys; 226 s_impl = std::make_unique<TuningForkImpl>(settings, extra_upload_info, backend, loader, 227 time_provider); 228 } 229 230 ClearcutBackend sBackend; 231 ProtoPrint sProtoPrint; 232 ParamsLoader sLoader; 233 234 void Init(const ProtobufSerialization &settings_ser, JNIEnv* env, jobject activity) { 235 bool backendInited = sBackend.Init(env, activity, &sProtoPrint); 236 237 ExtraUploadInfo extra_upload_info = UploadThread::GetExtraUploadInfo(env, activity); 238 if(backendInited) { 239 ALOGV("TuningFork.Clearcut: OK"); 240 Init(settings_ser, extra_upload_info, &sBackend, &sLoader); 241 } 242 else { 243 ALOGV("TuningFork.Clearcut: FAILED"); 244 Init(settings_ser, extra_upload_info); 245 } 246 } 247 248 bool GetFidelityParameters(const ProtobufSerialization &defaultParams, 249 ProtobufSerialization ¶ms, size_t timeout_ms) { 250 if (!s_impl) { 251 ALOGE("Failed to get TuningFork instance"); 252 return false; 253 } else 254 return s_impl->GetFidelityParameters(defaultParams, params, timeout_ms); 255 } 256 257 void FrameTick(InstrumentationKey id) { 258 if (!s_impl) { 259 ALOGE("Failed to get TuningFork instance"); 260 } else { 261 s_impl->FrameTick(id); 262 } 263 } 264 265 void FrameDeltaTimeNanos(InstrumentationKey id, Duration dt) { 266 if (!s_impl) { 267 ALOGE("Failed to get TuningFork instance"); 268 } else s_impl->FrameDeltaTimeNanos(id, dt); 269 } 270 271 TraceHandle StartTrace(InstrumentationKey key) { 272 if (!s_impl) { 273 ALOGE("Failed to get TuningFork instance"); 274 return 0; 275 } else return s_impl->StartTrace(key); 276 } 277 278 void EndTrace(TraceHandle h) { 279 if (!s_impl) { 280 ALOGE("Failed to get TuningFork instance"); 281 } else 282 s_impl->EndTrace(h); 283 } 284 285 // Return the set annotation id or -1 if it could not be set 286 uint64_t SetCurrentAnnotation(const ProtobufSerialization &ann) { 287 if (!s_impl) { 288 ALOGE("Failed to get TuningFork instance"); 289 return annotation_util::kAnnotationError; 290 } else 291 return s_impl->SetCurrentAnnotation(ann); 292 } 293 294 void SetUploadCallback(void(*cbk)(const CProtobufSerialization*)) { 295 if (!s_impl) { 296 ALOGE("Failed to get TuningFork instance"); 297 } else { 298 s_impl->SetUploadCallback(cbk); 299 } 300 } 301 302 // Return the set annotation id or -1 if it could not be set 303 uint64_t TuningForkImpl::SetCurrentAnnotation(const ProtobufSerialization &annotation) { 304 current_annotation_ = annotation; 305 auto id = DecodeAnnotationSerialization(annotation); 306 if (id == annotation_util::kAnnotationError) { 307 ALOGW("Error setting annotation of size %zu", annotation.size()); 308 current_annotation_id_ = 0; 309 return annotation_util::kAnnotationError; 310 } 311 else { 312 ALOGV("Set annotation id to %" PRIu64, id); 313 current_annotation_id_ = id; 314 return current_annotation_id_; 315 } 316 } 317 318 AnnotationId TuningForkImpl::DecodeAnnotationSerialization(const SerializedAnnotation &ser) { 319 auto id = annotation_util::DecodeAnnotationSerialization(ser, annotation_radix_mult_); 320 // Shift over to leave room for the instrument id 321 return id * settings_.aggregation_strategy.max_instrumentation_keys; 322 } 323 324 SerializedAnnotation TuningForkImpl::SerializeAnnotationId(AnnotationId id) { 325 SerializedAnnotation ann; 326 AnnotationId a = id / settings_.aggregation_strategy.max_instrumentation_keys; 327 annotation_util::SerializeAnnotationId(a, ann, annotation_radix_mult_); 328 return ann; 329 } 330 331 bool TuningForkImpl::GetFidelityParameters(const ProtobufSerialization& defaultParams, 332 ProtobufSerialization ¶ms_ser, size_t timeout_ms) { 333 if(loader_) { 334 auto result = loader_->GetFidelityParams(params_ser, timeout_ms); 335 if (result) { 336 upload_thread_.SetCurrentFidelityParams(params_ser); 337 } else { 338 upload_thread_.SetCurrentFidelityParams(defaultParams); 339 } 340 return result; 341 } 342 else 343 return false; 344 } 345 346 TraceHandle TuningForkImpl::StartTrace(InstrumentationKey key) { 347 trace_->beginSection("TFTrace"); 348 uint64_t h = MakeCompoundId(key, current_annotation_id_); 349 live_traces_[h] = time_provider_->NowNs(); 350 return h; 351 } 352 353 void TuningForkImpl::EndTrace(TraceHandle h) { 354 trace_->endSection(); 355 auto i = live_traces_[h]; 356 if (i != TimePoint::min()) 357 TraceNanos(h, time_provider_->NowNs() - i); 358 live_traces_[h] = TimePoint::min(); 359 } 360 361 void TuningForkImpl::FrameTick(InstrumentationKey key) { 362 trace_->beginSection("TFTick"); 363 auto t = time_provider_->NowNs(); 364 auto compound_id = MakeCompoundId(key, current_annotation_id_); 365 auto p = TickNanos(compound_id, t); 366 if (p) 367 CheckForSubmit(t, p); 368 369 trace_->endSection(); 370 } 371 372 void TuningForkImpl::FrameDeltaTimeNanos(InstrumentationKey key, Duration dt) { 373 374 auto compound_d = MakeCompoundId(key, current_annotation_id_); 375 auto p = TraceNanos(compound_d, dt); 376 if (p) 377 CheckForSubmit(time_provider_->NowNs(), p); 378 } 379 380 Prong *TuningForkImpl::TickNanos(uint64_t compound_id, TimePoint t) { 381 // Find the appropriate histogram and add this time 382 Prong *p = current_prong_cache_->Get(compound_id); 383 if (p) 384 p->Tick(t); 385 else 386 ALOGW("Bad id or limit of number of prongs reached"); 387 return p; 388 } 389 390 Prong *TuningForkImpl::TraceNanos(uint64_t compound_id, Duration dt) { 391 // Find the appropriate histogram and add this time 392 Prong *h = current_prong_cache_->Get(compound_id); 393 if (h) 394 h->Trace(dt); 395 else 396 ALOGW("Bad id or limit of number of prongs reached"); 397 return h; 398 } 399 400 void TuningForkImpl::SetUploadCallback(void(*cbk)(const CProtobufSerialization*)) { 401 upload_thread_.SetUploadCallback(cbk); 402 } 403 404 405 bool TuningForkImpl::ShouldSubmit(TimePoint t_ns, Prong *prong) { 406 auto method = settings_.aggregation_strategy.method; 407 auto count = settings_.aggregation_strategy.intervalms_or_count; 408 switch (settings_.aggregation_strategy.method) { 409 case Settings::AggregationStrategy::TIME_BASED: 410 return (t_ns - last_submit_time_ns_) >= 411 std::chrono::milliseconds(count); 412 case Settings::AggregationStrategy::TICK_BASED: 413 if (prong) 414 return prong->Count() >= count; 415 } 416 return false; 417 } 418 419 void TuningForkImpl::CheckForSubmit(TimePoint t_ns, Prong *prong) { 420 if (ShouldSubmit(t_ns, prong)) { 421 if (upload_thread_.Submit(current_prong_cache_)) { 422 if (current_prong_cache_ == prong_caches_[0].get()) { 423 prong_caches_[1]->Clear(); 424 current_prong_cache_ = prong_caches_[1].get(); 425 } else { 426 prong_caches_[0]->Clear(); 427 current_prong_cache_ = prong_caches_[0].get(); 428 } 429 } 430 last_submit_time_ns_ = t_ns; 431 } 432 } 433 434 void TuningForkImpl::InitHistogramSettings() { 435 Settings::Histogram default_histogram; 436 default_histogram.instrument_key = -1; 437 default_histogram.bucket_min = 10; 438 default_histogram.bucket_max = 40; 439 default_histogram.n_buckets = Histogram::kDefaultNumBuckets; 440 for(int i=0; i<settings_.aggregation_strategy.max_instrumentation_keys; ++i) { 441 if(settings_.histograms.size()<=i) { 442 ALOGW("Couldn't get histogram for key %d. Using default histogram", i); 443 settings_.histograms.push_back(default_histogram); 444 settings_.histograms.back().instrument_key = i; 445 } 446 else { 447 for(int j=i; j<settings_.aggregation_strategy.max_instrumentation_keys; ++j) { 448 auto& h = settings_.histograms[j]; 449 if(h.instrument_key==i) { 450 if(i!=j) { 451 std::swap(settings_.histograms[j], settings_.histograms[i]); 452 } 453 break; 454 } 455 } 456 } 457 } 458 ALOGV("Settings::histograms"); 459 for(int i=0; i< settings_.histograms.size();++i) { 460 auto& h = settings_.histograms[i]; 461 ALOGV("ikey: %d min: %f max: %f nbkts: %d", h.instrument_key, h.bucket_min, h.bucket_max, h.n_buckets); 462 } 463 } 464 465 void TuningForkImpl::InitAnnotationRadixes() { 466 annotation_util::SetUpAnnotationRadixes(annotation_radix_mult_, 467 settings_.aggregation_strategy.annotation_enum_size); 468 } 469 470 } // namespace tuningfork { 471