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