blob: 9796cd7b8bd2ab4bf6106eb3d939526ae7993b92 [file] [log] [blame]
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "tuningfork_internal.h"
#include "tuningfork_utils.h"
#include "jni_helper.h"
#include "histogram.h"
#define LOG_TAG "TuningFork"
#include "Log.h"
#include "file_cache.h"
#include <algorithm>
#include <sstream>
#include <android/asset_manager_jni.h>
#include "tuningfork/protobuf_nano_util.h"
#include "pb_decode.h"
#include "nano/tuningfork.pb.h"
#include "nano/descriptor.pb.h"
using PBSettings = com_google_tuningfork_Settings;
namespace tuningfork {
static FileCache sFileCache;
constexpr char kPerformanceParametersBaseUri[] =
"https://performanceparameters.googleapis.com/v1/";
// Forward declaration
bool GetEnumSizesFromDescriptors( std::vector<uint32_t>& enum_sizes);
// Use the default persister if the one passed in is null
static void CheckPersister(const TFCache*& persister, std::string save_dir) {
if (persister == nullptr) {
if (save_dir.empty()) {
// If the save_dir is empty, try the app's cache dir or tmp storage.
if (jni::IsValid()) {
save_dir = file_utils::GetAppCacheDir();
}
if (save_dir.empty())
save_dir = "/data/local/tmp";
save_dir += "/tuningfork";
}
ALOGI("Using local file cache at %s", save_dir.c_str());
sFileCache.SetDir(save_dir);
persister = sFileCache.GetCCache();
}
}
void CheckSettings(Settings &settings, const std::string& save_dir) {
CheckPersister(settings.c_settings.persistent_cache, save_dir);
if (settings.base_uri.empty())
settings.base_uri = kPerformanceParametersBaseUri;
if (settings.base_uri.back()!='/')
settings.base_uri += '/';
if (settings.aggregation_strategy.intervalms_or_count==0) {
settings.aggregation_strategy.method
= Settings::AggregationStrategy::Submission::TIME_BASED;
#ifndef NDEBUG
// For debug builds, upload every 10 seconds
settings.aggregation_strategy.intervalms_or_count = 10000;
#else
// For non-debug builds, upload every 2 hours
settings.aggregation_strategy.intervalms_or_count = 7200000;
#endif
}
if (settings.initial_request_timeout_ms==0)
settings.initial_request_timeout_ms = 1000;
if (settings.ultimate_request_timeout_ms==0)
settings.ultimate_request_timeout_ms = 100000;
}
static bool decodeAnnotationEnumSizes(pb_istream_t* stream, const pb_field_t *field, void** arg) {
Settings* settings = static_cast<Settings*>(*arg);
uint64_t a;
if (pb_decode_varint(stream, &a)) {
settings->aggregation_strategy.annotation_enum_size.push_back((uint32_t)a);
return true;
} else {
return false;
}
}
static bool decodeHistograms(pb_istream_t* stream, const pb_field_t *field, void** arg) {
Settings* settings = static_cast<Settings*>(*arg);
com_google_tuningfork_Settings_Histogram hist;
if (pb_decode(stream, com_google_tuningfork_Settings_Histogram_fields, &hist)) {
settings->histograms.push_back(
{hist.instrument_key, hist.bucket_min, hist.bucket_max, hist.n_buckets });
return true;
} else {
return false;
}
}
static bool decode_string(pb_istream_t* stream, const pb_field_t *field, void** arg) {
std::string* out_str = static_cast<std::string*>(*arg);
out_str->resize(stream->bytes_left);
char* d = const_cast<char*>(out_str->data()); // C++17 would remove the need for cast
while(stream->bytes_left) {
uint64_t x;
if (!pb_decode_varint(stream, &x))
return false;
*d++ = (char)x;
}
return true;
}
static TFErrorCode DeserializeSettings(const ProtobufSerialization& settings_ser,
Settings* settings) {
PBSettings pbsettings = com_google_tuningfork_Settings_init_zero;
pbsettings.aggregation_strategy.annotation_enum_size.funcs.decode = decodeAnnotationEnumSizes;
pbsettings.aggregation_strategy.annotation_enum_size.arg = settings;
pbsettings.histograms.funcs.decode = decodeHistograms;
pbsettings.histograms.arg = settings;
pbsettings.base_uri.funcs.decode = decode_string;
pbsettings.base_uri.arg = (void*)&settings->base_uri;
pbsettings.api_key.funcs.decode = decode_string;
pbsettings.api_key.arg = (void*)&settings->api_key;
pbsettings.default_fidelity_parameters_filename.funcs.decode = decode_string;
pbsettings.default_fidelity_parameters_filename.arg =
(void*)&settings->default_fidelity_parameters_filename;
ByteStream str {const_cast<uint8_t*>(settings_ser.data()), settings_ser.size(), 0};
pb_istream_t stream = {ByteStream::Read, &str, settings_ser.size()};
if (!pb_decode(&stream, com_google_tuningfork_Settings_fields, &pbsettings))
return TFERROR_BAD_SETTINGS;
if(pbsettings.aggregation_strategy.method
==com_google_tuningfork_Settings_AggregationStrategy_Submission_TICK_BASED)
settings->aggregation_strategy.method = Settings::AggregationStrategy::Submission::TICK_BASED;
else
settings->aggregation_strategy.method = Settings::AggregationStrategy::Submission::TIME_BASED;
settings->aggregation_strategy.intervalms_or_count
= pbsettings.aggregation_strategy.intervalms_or_count;
settings->aggregation_strategy.max_instrumentation_keys
= pbsettings.aggregation_strategy.max_instrumentation_keys;
settings->initial_request_timeout_ms = pbsettings.initial_request_timeout_ms;
settings->ultimate_request_timeout_ms = pbsettings.ultimate_request_timeout_ms;
// Convert from 1-based to 0 based indices (-1 = not present)
settings->loading_annotation_index = pbsettings.loading_annotation_index - 1;
settings->level_annotation_index = pbsettings.level_annotation_index - 1;
return TFERROR_OK;
}
TFErrorCode FindSettingsInApk(Settings* settings) {
if (settings) {
ProtobufSerialization settings_ser;
if (apk_utils::GetAssetAsSerialization("tuningfork/tuningfork_settings.bin", settings_ser)) {
ALOGI("Got settings from tuningfork/tuningfork_settings.bin");
TFErrorCode err = DeserializeSettings(settings_ser, settings);
if (err!=TFERROR_OK) return err;
if (settings->aggregation_strategy.annotation_enum_size.size()==0) {
// If enum sizes are missing, use the descriptor in dev_tuningfork.descriptor
if (!GetEnumSizesFromDescriptors(
settings->aggregation_strategy.annotation_enum_size)) {
return TFERROR_NO_SETTINGS_ANNOTATION_ENUM_SIZES;
}
}
return TFERROR_OK;
}
else {
return TFERROR_NO_SETTINGS;
}
} else {
return TFERROR_BAD_PARAMETER;
}
}
// Default histogram, used e.g. for TF Scaled or when a histogram is missing in the settings.
TFHistogram DefaultHistogram(InstrumentationKey ikey) {
TFHistogram default_histogram;
if (ikey==TFTICK_SYSCPU) {
default_histogram.bucket_min = 6.54f;
default_histogram.bucket_max = 60.0f;
default_histogram.n_buckets = Histogram::kDefaultNumBuckets;
} else {
default_histogram.bucket_min = 0.0f;
default_histogram.bucket_max = 20.0f;
default_histogram.n_buckets = Histogram::kDefaultNumBuckets;
}
if (ikey>=TFTICK_SYSCPU) {
// Fill in the well-known keys
default_histogram.instrument_key = ikey;
} else {
default_histogram.instrument_key = -1;
}
return default_histogram;
}
// Structures and functions needed for the decoding of a dev_tuningfork.descriptor.
// Used to calculate the annotation_enum_size vector from the proto definition.
namespace {
struct EnumField {
int number;
std::string type_name;
};
struct MessageType {
std::string name;
std::vector<EnumField> fields;
};
struct EnumType {
std::string name;
std::vector<std::pair<std::string,int>> value;
};
struct File {
std::string package;
std::vector<MessageType> message_type;
std::vector<EnumType> enum_type;
};
static bool DecodeField(pb_istream_t* stream, const pb_field_t *field, void** arg) {
MessageType *message = static_cast<MessageType*>(*arg);
google_protobuf_FieldDescriptorProto f = google_protobuf_FieldDescriptorProto_init_default;
EnumField ef;
f.type_name.funcs.decode = decode_string;
f.type_name.arg = &ef.type_name;
if (!pb_decode(stream, google_protobuf_FieldDescriptorProto_fields, &f))
return false;
if (f.type == google_protobuf_FieldDescriptorProto_Type_TYPE_ENUM) {
ef.number = f.number;
message->fields.push_back(ef);
}
return true;
}
static bool DecodeMessageType(pb_istream_t* stream, const pb_field_t *field, void** arg) {
File* d = static_cast<File*>(*arg);
google_protobuf_DescriptorProto desc = google_protobuf_DescriptorProto_init_default;
MessageType t;
desc.name.funcs.decode = decode_string;
desc.name.arg = &t.name;
desc.field.funcs.decode = DecodeField;
desc.field.arg = &t;
if (!pb_decode(stream, google_protobuf_DescriptorProto_fields, &desc))
return false;
d->message_type.push_back(t);
return true;
}
static bool DecodeValue(pb_istream_t* stream, const pb_field_t *field, void** arg) {
EnumType* e = static_cast<EnumType*>(*arg);
google_protobuf_EnumValueDescriptorProto desc =
google_protobuf_EnumValueDescriptorProto_init_default;
std::string name;
desc.name.funcs.decode = decode_string;
desc.name.arg = &name;
if (!pb_decode(stream, google_protobuf_EnumValueDescriptorProto_fields, &desc))
return false;
if (desc.has_number)
e->value.push_back({name, desc.number});
return true;
}
static bool DecodeEnumType(pb_istream_t* stream, const pb_field_t *field, void** arg) {
File* d = static_cast<File*>(*arg);
google_protobuf_EnumDescriptorProto desc = google_protobuf_EnumDescriptorProto_init_default;
EnumType e;
desc.name.funcs.decode = decode_string;
desc.name.arg = &e.name;
desc.value.funcs.decode = DecodeValue;
desc.value.arg = &e;
if (!pb_decode(stream, google_protobuf_EnumDescriptorProto_fields, &desc))
return false;
d->enum_type.push_back(e);
return true;
}
static bool DecodeFile(pb_istream_t* stream, const pb_field_t *field, void** arg) {
File* d = static_cast<File*>(*arg);
google_protobuf_FileDescriptorProto fdesc = google_protobuf_FileDescriptorProto_init_default;
fdesc.message_type.funcs.decode = DecodeMessageType;
fdesc.message_type.arg = d;
fdesc.enum_type.funcs.decode = DecodeEnumType;
fdesc.enum_type.arg = d;
fdesc.package.funcs.decode = decode_string;
fdesc.package.arg = &d->package;
return pb_decode(stream, google_protobuf_FileDescriptorProto_fields, &fdesc);
}
} // anonymous namespace
// Parse the dev_tuningfork.descriptor file in order to find enum sizes.
// Returns true is successful, false if not.
bool GetEnumSizesFromDescriptors( std::vector<uint32_t>& enum_sizes) {
ProtobufSerialization descriptor_ser;
if (!apk_utils::GetAssetAsSerialization("tuningfork/dev_tuningfork.descriptor", descriptor_ser))
return false;
ByteStream str {const_cast<uint8_t*>(descriptor_ser.data()), descriptor_ser.size(), 0};
pb_istream_t stream = {ByteStream::Read, &str, descriptor_ser.size()};
google_protobuf_FileDescriptorSet descriptor = google_protobuf_FileDescriptorSet_init_default;
File f;
descriptor.file.funcs.decode = DecodeFile;
descriptor.file.arg = &f;
if (!pb_decode(&stream, google_protobuf_FileDescriptorSet_fields, &descriptor))
return false;
ALOGV("Searching for Annotation in TuningFork descriptor");
for(auto & m: f.message_type) {
if (m.name=="Annotation") {
std::sort(m.fields.begin(), m.fields.end(), [](const EnumField& a, const EnumField& b) {
return a.number< b.number;
});
for(auto const& e: m.fields) {
std::string n = e.type_name;
// Strip off this package name
std::string this_package = "." + f.package;
if (n.find_first_of(this_package)==0)
n = n.substr(this_package.size()+1);
for (auto const& enums_in_desc: f.enum_type) {
ALOGV("Looking for: %s, enum name: %s", n.c_str(), enums_in_desc.name.c_str());
if (enums_in_desc.name==n) {
int max_value = 0;
for(auto const& v: enums_in_desc.value) {
if (v.second > max_value)
max_value = v.second;
}
enum_sizes.push_back(max_value+1);
}
}
}
}
}
if (enum_sizes.size()==0)
return false;
std::stringstream ostr;
ostr << '[';
for(auto const& e: enum_sizes) {
ostr << e << ',';
}
ostr << ']';
ALOGI("Found annotation enum sizes in descriptor: %s", ostr.str().c_str());
return true;
}
} // namespace tuningfork