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 ¶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
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 ¶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
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