• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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:
NowNs()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:
TuningForkImpl(const Settings & settings,const ExtraUploadInfo & extra_upload_info,Backend * backend,ParamsLoader * loader,ITimeProvider * time_provider)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 
~TuningForkImpl()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 
GetInstrumentationKey(uint64_t compoundId)174     int GetInstrumentationKey(uint64_t compoundId) {
175         return compoundId % settings_.aggregation_strategy.max_instrumentation_keys;
176     }
177 
MakeCompoundId(InstrumentationKey k,AnnotationId a)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 
decodeAnnotationEnumSizes(pb_istream_t * stream,const pb_field_t * field,void ** arg)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 }
decodeHistograms(pb_istream_t * stream,const pb_field_t * field,void ** arg)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 
Init(const ProtobufSerialization & settings_ser,const ExtraUploadInfo & extra_upload_info,Backend * backend,ParamsLoader * loader,ITimeProvider * time_provider)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 
Init(const ProtobufSerialization & settings_ser,JNIEnv * env,jobject activity)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 
GetFidelityParameters(const ProtobufSerialization & defaultParams,ProtobufSerialization & params,size_t timeout_ms)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 
FrameTick(InstrumentationKey id)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 
FrameDeltaTimeNanos(InstrumentationKey id,Duration dt)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 
StartTrace(InstrumentationKey key)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 
EndTrace(TraceHandle h)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
SetCurrentAnnotation(const ProtobufSerialization & ann)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 
SetUploadCallback(void (* cbk)(const CProtobufSerialization *))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
SetCurrentAnnotation(const ProtobufSerialization & annotation)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 
DecodeAnnotationSerialization(const SerializedAnnotation & ser)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 
SerializeAnnotationId(AnnotationId id)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 
GetFidelityParameters(const ProtobufSerialization & defaultParams,ProtobufSerialization & params_ser,size_t timeout_ms)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 
StartTrace(InstrumentationKey key)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 
EndTrace(TraceHandle h)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 
FrameTick(InstrumentationKey key)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 
FrameDeltaTimeNanos(InstrumentationKey key,Duration dt)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 
TickNanos(uint64_t compound_id,TimePoint t)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 
TraceNanos(uint64_t compound_id,Duration dt)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 
SetUploadCallback(void (* cbk)(const CProtobufSerialization *))400 void TuningForkImpl::SetUploadCallback(void(*cbk)(const CProtobufSerialization*)) {
401     upload_thread_.SetUploadCallback(cbk);
402 }
403 
404 
ShouldSubmit(TimePoint t_ns,Prong * prong)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 
CheckForSubmit(TimePoint t_ns,Prong * prong)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 
InitHistogramSettings()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 
InitAnnotationRadixes()465 void TuningForkImpl::InitAnnotationRadixes() {
466     annotation_util::SetUpAnnotationRadixes(annotation_radix_mult_,
467                                             settings_.aggregation_strategy.annotation_enum_size);
468 }
469 
470 } // namespace tuningfork {
471