/* * 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 #include #include #include #include #include #include #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::max(); std::function gCompilationCallback; std::function 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>& devices) { // Sort device identifiers in alphabetical order std::vector 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(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 compilation, std::function execution) { gCompilationCallback = std::move(compilation); gExecutionCallback = std::move(execution); gLoggingCallbacksSet = true; } void clearTelemetryCallbacks() { gLoggingCallbacksSet = false; } } // namespace android::nn::telemetry