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
 |