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