Home | History | Annotate | Download | only in tuningfork
      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 &params, 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 &params_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