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