257 lines
9.2 KiB
C++
257 lines
9.2 KiB
C++
/*
|
|
* Copyright (C) 2020 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.
|
|
*/
|
|
|
|
#define LOG_TAG "Telemetry"
|
|
|
|
#include "Telemetry.h"
|
|
|
|
#include <algorithm>
|
|
#include <limits>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "Manager.h"
|
|
#include "NeuralNetworks.h"
|
|
#include "Tracing.h"
|
|
|
|
#if defined(__ANDROID__) && !defined(NN_COMPATIBILITY_LIBRARY_BUILD)
|
|
#include "TelemetryStatsd.h"
|
|
#endif // defined(__ANDROID__) && !defined(NN_COMPATIBILITY_LIBRARY_BUILD)
|
|
|
|
namespace android::nn::telemetry {
|
|
namespace {
|
|
|
|
constexpr uint64_t kNoTimeReported = std::numeric_limits<uint64_t>::max();
|
|
|
|
std::function<void(const DiagnosticCompilationInfo*)> gCompilationCallback;
|
|
std::function<void(const DiagnosticExecutionInfo*)> gExecutionCallback;
|
|
std::atomic_bool gLoggingCallbacksSet = false;
|
|
|
|
// Convert list of Device object into a single string with all
|
|
// identifiers, sorted by name in form of "name1=version1,name2=version2,..."
|
|
std::string makeDeviceId(const std::vector<std::shared_ptr<Device>>& devices) {
|
|
// Sort device identifiers in alphabetical order
|
|
std::vector<std::string> names;
|
|
names.reserve(devices.size());
|
|
size_t totalSize = 0;
|
|
for (size_t i = 0; i < devices.size(); ++i) {
|
|
if (!names.empty()) {
|
|
totalSize++;
|
|
}
|
|
names.push_back(devices[i]->getName() + "=" + devices[i]->getVersionString());
|
|
totalSize += names.back().size();
|
|
}
|
|
sort(names.begin(), names.end());
|
|
|
|
// Concatenate them
|
|
std::string result;
|
|
result.reserve(totalSize);
|
|
for (auto& name : names) {
|
|
if (!result.empty()) {
|
|
result += ',';
|
|
}
|
|
result += name;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Generate logging session identifier based on millisecond timestamp and pid
|
|
int32_t generateSessionId() {
|
|
auto now = std::chrono::system_clock::now();
|
|
auto duration = now.time_since_epoch();
|
|
// Taking millisecond timestamp and pid modulo a large prime to make the id less identifiable,
|
|
// but still unique within the device scope.
|
|
auto timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
|
|
return (getpid() * 123 + timestamp) % 999983;
|
|
}
|
|
|
|
// Operand type to atom datatype
|
|
DataClass operandToDataClass(const OperandType& op) {
|
|
switch (op) {
|
|
case OperandType::TENSOR_FLOAT32:
|
|
return DataClass::FLOAT32;
|
|
case OperandType::TENSOR_FLOAT16:
|
|
return DataClass::FLOAT16;
|
|
case OperandType::TENSOR_QUANT8_ASYMM:
|
|
case OperandType::TENSOR_QUANT16_SYMM:
|
|
case OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL:
|
|
case OperandType::TENSOR_QUANT16_ASYMM:
|
|
case OperandType::TENSOR_QUANT8_SYMM:
|
|
case OperandType::TENSOR_QUANT8_ASYMM_SIGNED:
|
|
return DataClass::QUANT;
|
|
default:
|
|
// we ignore operand of other types
|
|
return DataClass::OTHER;
|
|
}
|
|
}
|
|
|
|
// Evaluate a coarse category of model inputs
|
|
DataClass evalInputDataClass(const ModelBuilder* m) {
|
|
DataClass result = DataClass::UNKNOWN;
|
|
for (size_t i = 0; i < m->inputCount(); i++) {
|
|
result = evalDataClass(m->getInputOperand(i).type, result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Evaluate a coarse category of model outputs
|
|
DataClass evalOutputDataClass(const ModelBuilder* m) {
|
|
DataClass result = DataClass::UNKNOWN;
|
|
for (size_t i = 0; i < m->outputCount(); i++) {
|
|
result = evalDataClass(m->getOutputOperand(i).type, result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// Infer a data class from an operand type. Call iteratievly on operands set, previousDataClass is
|
|
// result of evalDataClass evaluation on previous operands or DataClass::UNKNOWN value if called on
|
|
// first operand
|
|
DataClass evalDataClass(const OperandType& op, DataClass previousDataClass) {
|
|
DataClass operandClass = operandToDataClass(op);
|
|
if (operandClass == DataClass::OTHER) {
|
|
if (previousDataClass == DataClass::UNKNOWN) {
|
|
return operandClass;
|
|
}
|
|
return previousDataClass;
|
|
}
|
|
|
|
if (previousDataClass == DataClass::UNKNOWN || previousDataClass == DataClass::OTHER) {
|
|
return operandClass;
|
|
} else if (operandClass != previousDataClass) {
|
|
return DataClass::MIXED;
|
|
}
|
|
return operandClass;
|
|
}
|
|
|
|
// Generate and store session identifier
|
|
int32_t getSessionId() {
|
|
static int32_t ident = generateSessionId();
|
|
return ident;
|
|
}
|
|
|
|
void onCompilationFinish(CompilationBuilder* c, int resultCode) {
|
|
NNTRACE_RT(NNTRACE_PHASE_UNSPECIFIED, "onCompilationFinish");
|
|
|
|
// Allow to emit even only if compilation was finished
|
|
if (!c->isFinished()) {
|
|
LOG(ERROR) << "telemetry::onCompilationFinish called on unfinished compilation";
|
|
return;
|
|
}
|
|
|
|
const bool loggingCallbacksSet = gLoggingCallbacksSet;
|
|
if (!loggingCallbacksSet && !DeviceManager::get()->isPlatformTelemetryEnabled()) {
|
|
return;
|
|
}
|
|
|
|
const DiagnosticCompilationInfo info{
|
|
.modelArchHash = c->getModel()->getModelArchHash(),
|
|
.deviceId = makeDeviceId(c->getDevices()),
|
|
.errorCode = resultCode,
|
|
.inputDataClass = evalInputDataClass(c->getModel()),
|
|
.outputDataClass = evalOutputDataClass(c->getModel()),
|
|
.compilationTimeNanos = c->getTelemetryInfo()->compilationTimeNanos,
|
|
.fallbackToCpuFromError = c->getTelemetryInfo()->fallbackToCpuFromError,
|
|
.introspectionEnabled = c->createdWithExplicitDeviceList(),
|
|
.cacheEnabled = c->isCacheInfoProvided(),
|
|
.hasControlFlow = c->getModel()->hasControlFlow(),
|
|
.hasDynamicTemporaries = c->hasDynamicTemporaries(),
|
|
};
|
|
|
|
#if defined(__ANDROID__) && !defined(NN_COMPATIBILITY_LIBRARY_BUILD)
|
|
if (DeviceManager::get()->isPlatformTelemetryEnabled()) {
|
|
logCompilationToStatsd(&info);
|
|
}
|
|
#endif // defined(__ANDROID__) && !defined(NN_COMPATIBILITY_LIBRARY_BUILD)
|
|
|
|
if (loggingCallbacksSet) {
|
|
gCompilationCallback(&info);
|
|
}
|
|
}
|
|
|
|
void onExecutionFinish(ExecutionBuilder* e, ExecutionMode executionMode, int resultCode) {
|
|
NNTRACE_RT(NNTRACE_PHASE_UNSPECIFIED, "onExecutionFinish");
|
|
|
|
// Allow to emit even only if execution was finished
|
|
if (!e->completed()) {
|
|
LOG(ERROR) << "telemetry::onExecutionFinish called on unfinished execution";
|
|
return;
|
|
}
|
|
|
|
const bool loggingCallbacksSet = gLoggingCallbacksSet;
|
|
if (!loggingCallbacksSet && !DeviceManager::get()->isPlatformTelemetryEnabled()) {
|
|
return;
|
|
}
|
|
|
|
auto compilation = e->getCompilation();
|
|
uint64_t duration_driver_ns = kNoTimeReported;
|
|
uint64_t duration_hardware_ns = kNoTimeReported;
|
|
uint64_t duration_runtime_ns = kNoTimeReported;
|
|
|
|
if (e->measureTiming()) {
|
|
e->getDuration(ANEURALNETWORKS_DURATION_ON_HARDWARE, &duration_hardware_ns);
|
|
e->getDuration(ANEURALNETWORKS_DURATION_IN_DRIVER, &duration_driver_ns);
|
|
}
|
|
|
|
// Ignore runtime execution time if the call was async with dependencies, because waiting for
|
|
// the result may have been much later than when the execution actually finished.
|
|
if (executionMode != ExecutionMode::ASYNC_WITH_DEPS) {
|
|
duration_runtime_ns = TimeNanoMeasurer::currentDuration(e->getComputeStartTimePoint());
|
|
}
|
|
|
|
const DiagnosticExecutionInfo info{
|
|
.modelArchHash = e->getModel()->getModelArchHash(),
|
|
.deviceId = makeDeviceId(compilation->getDevices()),
|
|
.executionMode = executionMode,
|
|
.inputDataClass = evalInputDataClass(e->getModel()),
|
|
.outputDataClass = evalOutputDataClass(e->getModel()),
|
|
.errorCode = resultCode,
|
|
.durationRuntimeNanos = duration_runtime_ns,
|
|
.durationDriverNanos = duration_driver_ns,
|
|
.durationHardwareNanos = duration_hardware_ns,
|
|
.introspectionEnabled = compilation->createdWithExplicitDeviceList(),
|
|
.cacheEnabled = compilation->isCacheInfoProvided(),
|
|
.hasControlFlow = compilation->getModel()->hasControlFlow(),
|
|
.hasDynamicTemporaries = compilation->hasDynamicTemporaries(),
|
|
};
|
|
|
|
#if defined(__ANDROID__) && !defined(NN_COMPATIBILITY_LIBRARY_BUILD)
|
|
if (DeviceManager::get()->isPlatformTelemetryEnabled()) {
|
|
logExecutionToStatsd(&info);
|
|
}
|
|
#endif // defined(__ANDROID__) && !defined(NN_COMPATIBILITY_LIBRARY_BUILD)
|
|
|
|
if (loggingCallbacksSet) {
|
|
gExecutionCallback(&info);
|
|
}
|
|
}
|
|
|
|
void registerTelemetryCallbacks(std::function<void(const DiagnosticCompilationInfo*)> compilation,
|
|
std::function<void(const DiagnosticExecutionInfo*)> execution) {
|
|
gCompilationCallback = std::move(compilation);
|
|
gExecutionCallback = std::move(execution);
|
|
gLoggingCallbacksSet = true;
|
|
}
|
|
|
|
void clearTelemetryCallbacks() {
|
|
gLoggingCallbacksSet = false;
|
|
}
|
|
|
|
} // namespace android::nn::telemetry
|