• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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 #define LOG_TAG "Telemetry"
18 
19 #include "Telemetry.h"
20 
21 #include <algorithm>
22 #include <limits>
23 #include <memory>
24 #include <string>
25 #include <utility>
26 #include <vector>
27 
28 #include "Manager.h"
29 #include "NeuralNetworks.h"
30 #include "Tracing.h"
31 
32 #if defined(__ANDROID__) && !defined(NN_COMPATIBILITY_LIBRARY_BUILD)
33 #include "TelemetryStatsd.h"
34 #endif  // defined(__ANDROID__) && !defined(NN_COMPATIBILITY_LIBRARY_BUILD)
35 
36 namespace android::nn::telemetry {
37 namespace {
38 
39 constexpr uint64_t kNoTimeReported = std::numeric_limits<uint64_t>::max();
40 
41 std::function<void(const DiagnosticCompilationInfo*)> gCompilationCallback;
42 std::function<void(const DiagnosticExecutionInfo*)> gExecutionCallback;
43 std::atomic_bool gLoggingCallbacksSet = false;
44 
45 // Convert list of Device object into a single string with all
46 // identifiers, sorted by name in form of "name1=version1,name2=version2,..."
makeDeviceId(const std::vector<std::shared_ptr<Device>> & devices)47 std::string makeDeviceId(const std::vector<std::shared_ptr<Device>>& devices) {
48     // Sort device identifiers in alphabetical order
49     std::vector<std::string> names;
50     names.reserve(devices.size());
51     size_t totalSize = 0;
52     for (size_t i = 0; i < devices.size(); ++i) {
53         if (!names.empty()) {
54             totalSize++;
55         }
56         names.push_back(devices[i]->getName() + "=" + devices[i]->getVersionString());
57         totalSize += names.back().size();
58     }
59     sort(names.begin(), names.end());
60 
61     // Concatenate them
62     std::string result;
63     result.reserve(totalSize);
64     for (auto& name : names) {
65         if (!result.empty()) {
66             result += ',';
67         }
68         result += name;
69     }
70     return result;
71 }
72 
73 // Generate logging session identifier based on millisecond timestamp and pid
generateSessionId()74 int32_t generateSessionId() {
75     auto now = std::chrono::system_clock::now();
76     auto duration = now.time_since_epoch();
77     // Taking millisecond timestamp and pid modulo a large prime to make the id less identifiable,
78     // but still unique within the device scope.
79     auto timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
80     return (getpid() * 123 + timestamp) % 999983;
81 }
82 
83 // Operand type to atom datatype
operandToDataClass(const OperandType & op)84 DataClass operandToDataClass(const OperandType& op) {
85     switch (op) {
86         case OperandType::TENSOR_FLOAT32:
87             return DataClass::FLOAT32;
88         case OperandType::TENSOR_FLOAT16:
89             return DataClass::FLOAT16;
90         case OperandType::TENSOR_QUANT8_ASYMM:
91         case OperandType::TENSOR_QUANT16_SYMM:
92         case OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL:
93         case OperandType::TENSOR_QUANT16_ASYMM:
94         case OperandType::TENSOR_QUANT8_SYMM:
95         case OperandType::TENSOR_QUANT8_ASYMM_SIGNED:
96             return DataClass::QUANT;
97         default:
98             // we ignore operand of other types
99             return DataClass::OTHER;
100     }
101 }
102 
103 // Evaluate a coarse category of model inputs
evalInputDataClass(const ModelBuilder * m)104 DataClass evalInputDataClass(const ModelBuilder* m) {
105     DataClass result = DataClass::UNKNOWN;
106     for (size_t i = 0; i < m->inputCount(); i++) {
107         result = evalDataClass(m->getInputOperand(i).type, result);
108     }
109     return result;
110 }
111 
112 // Evaluate a coarse category of model outputs
evalOutputDataClass(const ModelBuilder * m)113 DataClass evalOutputDataClass(const ModelBuilder* m) {
114     DataClass result = DataClass::UNKNOWN;
115     for (size_t i = 0; i < m->outputCount(); i++) {
116         result = evalDataClass(m->getOutputOperand(i).type, result);
117     }
118     return result;
119 }
120 
121 }  // namespace
122 
123 // Infer a data class from an operand type. Call iteratievly on operands set, previousDataClass is
124 // result of evalDataClass evaluation on previous operands or DataClass::UNKNOWN value if called on
125 // first operand
evalDataClass(const OperandType & op,DataClass previousDataClass)126 DataClass evalDataClass(const OperandType& op, DataClass previousDataClass) {
127     DataClass operandClass = operandToDataClass(op);
128     if (operandClass == DataClass::OTHER) {
129         if (previousDataClass == DataClass::UNKNOWN) {
130             return operandClass;
131         }
132         return previousDataClass;
133     }
134 
135     if (previousDataClass == DataClass::UNKNOWN || previousDataClass == DataClass::OTHER) {
136         return operandClass;
137     } else if (operandClass != previousDataClass) {
138         return DataClass::MIXED;
139     }
140     return operandClass;
141 }
142 
143 // Generate and store session identifier
getSessionId()144 int32_t getSessionId() {
145     static int32_t ident = generateSessionId();
146     return ident;
147 }
148 
onCompilationFinish(CompilationBuilder * c,int resultCode)149 void onCompilationFinish(CompilationBuilder* c, int resultCode) {
150     NNTRACE_RT(NNTRACE_PHASE_UNSPECIFIED, "onCompilationFinish");
151 
152     // Allow to emit even only if compilation was finished
153     if (!c->isFinished()) {
154         LOG(ERROR) << "telemetry::onCompilationFinish called on unfinished compilation";
155         return;
156     }
157 
158     const bool loggingCallbacksSet = gLoggingCallbacksSet;
159     if (!loggingCallbacksSet && !DeviceManager::get()->isPlatformTelemetryEnabled()) {
160         return;
161     }
162 
163     const DiagnosticCompilationInfo info{
164             .modelArchHash = c->getModel()->getModelArchHash(),
165             .deviceId = makeDeviceId(c->getDevices()),
166             .errorCode = resultCode,
167             .inputDataClass = evalInputDataClass(c->getModel()),
168             .outputDataClass = evalOutputDataClass(c->getModel()),
169             .compilationTimeNanos = c->getTelemetryInfo()->compilationTimeNanos,
170             .fallbackToCpuFromError = c->getTelemetryInfo()->fallbackToCpuFromError,
171             .introspectionEnabled = c->createdWithExplicitDeviceList(),
172             .cacheEnabled = c->isCacheInfoProvided(),
173             .hasControlFlow = c->getModel()->hasControlFlow(),
174             .hasDynamicTemporaries = c->hasDynamicTemporaries(),
175     };
176 
177 #if defined(__ANDROID__) && !defined(NN_COMPATIBILITY_LIBRARY_BUILD)
178     if (DeviceManager::get()->isPlatformTelemetryEnabled()) {
179         logCompilationToStatsd(&info);
180     }
181 #endif  // defined(__ANDROID__) && !defined(NN_COMPATIBILITY_LIBRARY_BUILD)
182 
183     if (loggingCallbacksSet) {
184         gCompilationCallback(&info);
185     }
186 }
187 
onExecutionFinish(ExecutionBuilder * e,ExecutionMode executionMode,int resultCode)188 void onExecutionFinish(ExecutionBuilder* e, ExecutionMode executionMode, int resultCode) {
189     NNTRACE_RT(NNTRACE_PHASE_UNSPECIFIED, "onExecutionFinish");
190 
191     // Allow to emit even only if execution was finished
192     if (!e->completed()) {
193         LOG(ERROR) << "telemetry::onExecutionFinish called on unfinished execution";
194         return;
195     }
196 
197     const bool loggingCallbacksSet = gLoggingCallbacksSet;
198     if (!loggingCallbacksSet && !DeviceManager::get()->isPlatformTelemetryEnabled()) {
199         return;
200     }
201 
202     auto compilation = e->getCompilation();
203     uint64_t duration_driver_ns = kNoTimeReported;
204     uint64_t duration_hardware_ns = kNoTimeReported;
205     uint64_t duration_runtime_ns = kNoTimeReported;
206 
207     if (e->measureTiming()) {
208         e->getDuration(ANEURALNETWORKS_DURATION_ON_HARDWARE, &duration_hardware_ns);
209         e->getDuration(ANEURALNETWORKS_DURATION_IN_DRIVER, &duration_driver_ns);
210     }
211 
212     // Ignore runtime execution time if the call was async with dependencies, because waiting for
213     // the result may have been much later than when the execution actually finished.
214     if (executionMode != ExecutionMode::ASYNC_WITH_DEPS) {
215         duration_runtime_ns = TimeNanoMeasurer::currentDuration(e->getComputeStartTimePoint());
216     }
217 
218     const DiagnosticExecutionInfo info{
219             .modelArchHash = e->getModel()->getModelArchHash(),
220             .deviceId = makeDeviceId(compilation->getDevices()),
221             .executionMode = executionMode,
222             .inputDataClass = evalInputDataClass(e->getModel()),
223             .outputDataClass = evalOutputDataClass(e->getModel()),
224             .errorCode = resultCode,
225             .durationRuntimeNanos = duration_runtime_ns,
226             .durationDriverNanos = duration_driver_ns,
227             .durationHardwareNanos = duration_hardware_ns,
228             .introspectionEnabled = compilation->createdWithExplicitDeviceList(),
229             .cacheEnabled = compilation->isCacheInfoProvided(),
230             .hasControlFlow = compilation->getModel()->hasControlFlow(),
231             .hasDynamicTemporaries = compilation->hasDynamicTemporaries(),
232     };
233 
234 #if defined(__ANDROID__) && !defined(NN_COMPATIBILITY_LIBRARY_BUILD)
235     if (DeviceManager::get()->isPlatformTelemetryEnabled()) {
236         logExecutionToStatsd(&info);
237     }
238 #endif  // defined(__ANDROID__) && !defined(NN_COMPATIBILITY_LIBRARY_BUILD)
239 
240     if (loggingCallbacksSet) {
241         gExecutionCallback(&info);
242     }
243 }
244 
registerTelemetryCallbacks(std::function<void (const DiagnosticCompilationInfo *)> compilation,std::function<void (const DiagnosticExecutionInfo *)> execution)245 void registerTelemetryCallbacks(std::function<void(const DiagnosticCompilationInfo*)> compilation,
246                                 std::function<void(const DiagnosticExecutionInfo*)> execution) {
247     gCompilationCallback = std::move(compilation);
248     gExecutionCallback = std::move(execution);
249     gLoggingCallbacksSet = true;
250 }
251 
clearTelemetryCallbacks()252 void clearTelemetryCallbacks() {
253     gLoggingCallbacksSet = false;
254 }
255 
256 }  // namespace android::nn::telemetry
257