1 /*
2 * Copyright 2022 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 #include "metrics_collector.h"
18
19 #include <chrono>
20 #include <memory>
21 #include <vector>
22
23 #include "common/metrics.h"
24
25 namespace le_audio {
26
27 using ClockTimePoint =
28 std::chrono::time_point<std::chrono::high_resolution_clock>;
29 using bluetooth::le_audio::ConnectionState;
30 using le_audio::types::LeAudioContextType;
31
32 const static ClockTimePoint kInvalidTimePoint{};
33
34 MetricsCollector* MetricsCollector::instance = nullptr;
35
get_timedelta_nanos(const ClockTimePoint & t1,const ClockTimePoint & t2)36 inline int64_t get_timedelta_nanos(const ClockTimePoint& t1,
37 const ClockTimePoint& t2) {
38 if (t1 == kInvalidTimePoint || t2 == kInvalidTimePoint) {
39 return -1;
40 }
41 return std::abs(
42 std::chrono::duration_cast<std::chrono::nanoseconds>(t1 - t2).count());
43 }
44
45 const static std::unordered_map<LeAudioContextType, LeAudioMetricsContextType>
46 kContextTypeTable = {
47 {LeAudioContextType::UNINITIALIZED, LeAudioMetricsContextType::INVALID},
48 {LeAudioContextType::UNSPECIFIED,
49 LeAudioMetricsContextType::UNSPECIFIED},
50 {LeAudioContextType::CONVERSATIONAL,
51 LeAudioMetricsContextType::COMMUNICATION},
52 {LeAudioContextType::MEDIA, LeAudioMetricsContextType::MEDIA},
53 {LeAudioContextType::GAME, LeAudioMetricsContextType::GAME},
54 {LeAudioContextType::INSTRUCTIONAL,
55 LeAudioMetricsContextType::INSTRUCTIONAL},
56 {LeAudioContextType::VOICEASSISTANTS,
57 LeAudioMetricsContextType::MAN_MACHINE},
58 {LeAudioContextType::LIVE, LeAudioMetricsContextType::LIVE},
59 {LeAudioContextType::SOUNDEFFECTS,
60 LeAudioMetricsContextType::ATTENTION_SEEKING},
61 {LeAudioContextType::NOTIFICATIONS,
62 LeAudioMetricsContextType::ATTENTION_SEEKING},
63 {LeAudioContextType::RINGTONE, LeAudioMetricsContextType::RINGTONE},
64 {LeAudioContextType::ALERTS,
65 LeAudioMetricsContextType::IMMEDIATE_ALERT},
66 {LeAudioContextType::EMERGENCYALARM,
67 LeAudioMetricsContextType::EMERGENCY_ALERT},
68 {LeAudioContextType::RFU, LeAudioMetricsContextType::RFU},
69 };
70
to_atom_context_type(const LeAudioContextType stack_type)71 inline int32_t to_atom_context_type(const LeAudioContextType stack_type) {
72 auto it = kContextTypeTable.find(stack_type);
73 if (it != kContextTypeTable.end()) {
74 return static_cast<int32_t>(it->second);
75 }
76 return static_cast<int32_t>(LeAudioMetricsContextType::INVALID);
77 }
78
79 class DeviceMetrics {
80 public:
81 RawAddress address_;
82 ClockTimePoint connecting_timepoint_ = kInvalidTimePoint;
83 ClockTimePoint connected_timepoint_ = kInvalidTimePoint;
84 ClockTimePoint disconnected_timepoint_ = kInvalidTimePoint;
85 int32_t connection_status_ = 0;
86 int32_t disconnection_status_ = 0;
87
DeviceMetrics(const RawAddress & address)88 DeviceMetrics(const RawAddress& address) : address_(address) {}
89
AddStateChangedEvent(ConnectionState state,ConnectionStatus status)90 void AddStateChangedEvent(ConnectionState state, ConnectionStatus status) {
91 switch (state) {
92 case ConnectionState::CONNECTING:
93 connecting_timepoint_ = std::chrono::high_resolution_clock::now();
94 break;
95 case ConnectionState::CONNECTED:
96 connected_timepoint_ = std::chrono::high_resolution_clock::now();
97 connection_status_ = static_cast<int32_t>(status);
98 break;
99 case ConnectionState::DISCONNECTED:
100 disconnected_timepoint_ = std::chrono::high_resolution_clock::now();
101 disconnection_status_ = static_cast<int32_t>(status);
102 break;
103 case ConnectionState::DISCONNECTING:
104 // Ignore
105 break;
106 }
107 }
108 };
109
110 class GroupMetricsImpl : public GroupMetrics {
111 private:
112 static constexpr int32_t kInvalidGroupId = -1;
113 int32_t group_id_;
114 int32_t group_size_;
115 std::vector<std::unique_ptr<DeviceMetrics>> device_metrics_;
116 std::unordered_map<RawAddress, DeviceMetrics*> opened_devices_;
117 ClockTimePoint beginning_timepoint_;
118 std::vector<int64_t> streaming_offset_nanos_;
119 std::vector<int64_t> streaming_duration_nanos_;
120 std::vector<int32_t> streaming_context_type_;
121
122 public:
GroupMetricsImpl()123 GroupMetricsImpl() : group_id_(kInvalidGroupId), group_size_(0) {
124 beginning_timepoint_ = std::chrono::high_resolution_clock::now();
125 }
GroupMetricsImpl(int32_t group_id,int32_t group_size)126 GroupMetricsImpl(int32_t group_id, int32_t group_size)
127 : group_id_(group_id), group_size_(group_size) {
128 beginning_timepoint_ = std::chrono::high_resolution_clock::now();
129 }
130
AddStateChangedEvent(const RawAddress & address,le_audio::ConnectionState state,ConnectionStatus status)131 void AddStateChangedEvent(const RawAddress& address,
132 le_audio::ConnectionState state,
133 ConnectionStatus status) override {
134 auto it = opened_devices_.find(address);
135 if (it == opened_devices_.end()) {
136 device_metrics_.push_back(std::make_unique<DeviceMetrics>(address));
137 it = opened_devices_.insert(std::begin(opened_devices_),
138 {address, device_metrics_.back().get()});
139 }
140 it->second->AddStateChangedEvent(state, status);
141 if (state == le_audio::ConnectionState::DISCONNECTED ||
142 (state == le_audio::ConnectionState::CONNECTED &&
143 status != ConnectionStatus::SUCCESS)) {
144 opened_devices_.erase(it);
145 }
146 }
147
AddStreamStartedEvent(le_audio::types::LeAudioContextType context_type)148 void AddStreamStartedEvent(
149 le_audio::types::LeAudioContextType context_type) override {
150 int32_t atom_context_type = to_atom_context_type(context_type);
151 // Make sure events aligned
152 if (streaming_offset_nanos_.size() - streaming_duration_nanos_.size() !=
153 0) {
154 // Allow type switching
155 if (!streaming_context_type_.empty() &&
156 streaming_context_type_.back() != atom_context_type) {
157 AddStreamEndedEvent();
158 } else {
159 return;
160 }
161 }
162 streaming_offset_nanos_.push_back(get_timedelta_nanos(
163 std::chrono::high_resolution_clock::now(), beginning_timepoint_));
164 streaming_context_type_.push_back(atom_context_type);
165 }
166
AddStreamEndedEvent()167 void AddStreamEndedEvent() override {
168 // Make sure events aligned
169 if (streaming_offset_nanos_.size() - streaming_duration_nanos_.size() !=
170 1) {
171 return;
172 }
173 streaming_duration_nanos_.push_back(
174 get_timedelta_nanos(std::chrono::high_resolution_clock::now(),
175 beginning_timepoint_) -
176 streaming_offset_nanos_.back());
177 }
178
SetGroupSize(int32_t group_size)179 void SetGroupSize(int32_t group_size) override { group_size_ = group_size; }
180
IsClosed()181 bool IsClosed() override { return opened_devices_.empty(); }
182
WriteStats()183 void WriteStats() override {
184 int64_t connection_duration_nanos = get_timedelta_nanos(
185 beginning_timepoint_, std::chrono::high_resolution_clock::now());
186
187 int len = device_metrics_.size();
188 std::vector<int64_t> device_connecting_offset_nanos(len);
189 std::vector<int64_t> device_connected_offset_nanos(len);
190 std::vector<int64_t> device_connection_duration_nanos(len);
191 std::vector<int32_t> device_connection_statuses(len);
192 std::vector<int32_t> device_disconnection_statuses(len);
193 std::vector<RawAddress> device_address(len);
194
195 while (streaming_duration_nanos_.size() < streaming_offset_nanos_.size()) {
196 AddStreamEndedEvent();
197 }
198
199 for (int i = 0; i < len; i++) {
200 auto device_metric = device_metrics_[i].get();
201 device_connecting_offset_nanos[i] = get_timedelta_nanos(
202 device_metric->connecting_timepoint_, beginning_timepoint_);
203 device_connected_offset_nanos[i] = get_timedelta_nanos(
204 device_metric->connected_timepoint_, beginning_timepoint_);
205 device_connection_duration_nanos[i] =
206 get_timedelta_nanos(device_metric->disconnected_timepoint_,
207 device_metric->connected_timepoint_);
208 device_connection_statuses[i] = device_metric->connection_status_;
209 device_disconnection_statuses[i] = device_metric->disconnection_status_;
210 device_address[i] = device_metric->address_;
211 }
212
213 bluetooth::common::LogLeAudioConnectionSessionReported(
214 group_size_, group_id_, connection_duration_nanos,
215 device_connecting_offset_nanos, device_connected_offset_nanos,
216 device_connection_duration_nanos, device_connection_statuses,
217 device_disconnection_statuses, device_address, streaming_offset_nanos_,
218 streaming_duration_nanos_, streaming_context_type_);
219 }
220
Flush()221 void Flush() {
222 for (auto& p : opened_devices_) {
223 p.second->AddStateChangedEvent(
224 bluetooth::le_audio::ConnectionState::DISCONNECTED,
225 ConnectionStatus::SUCCESS);
226 }
227 WriteStats();
228 }
229 };
230
231 /* Metrics Colloctor */
232
Get()233 MetricsCollector* MetricsCollector::Get() {
234 if (MetricsCollector::instance == nullptr) {
235 MetricsCollector::instance = new MetricsCollector();
236 }
237 return MetricsCollector::instance;
238 }
239
OnGroupSizeUpdate(int32_t group_id,int32_t group_size)240 void MetricsCollector::OnGroupSizeUpdate(int32_t group_id, int32_t group_size) {
241 group_size_table_[group_id] = group_size;
242 auto it = opened_groups_.find(group_id);
243 if (it != opened_groups_.end()) {
244 it->second->SetGroupSize(group_size);
245 }
246 }
247
OnConnectionStateChanged(int32_t group_id,const RawAddress & address,bluetooth::le_audio::ConnectionState state,ConnectionStatus status)248 void MetricsCollector::OnConnectionStateChanged(
249 int32_t group_id, const RawAddress& address,
250 bluetooth::le_audio::ConnectionState state, ConnectionStatus status) {
251 if (address.IsEmpty() || group_id <= 0) {
252 return;
253 }
254 auto it = opened_groups_.find(group_id);
255 if (it == opened_groups_.end()) {
256 it = opened_groups_.insert(
257 std::begin(opened_groups_),
258 {group_id, std::make_unique<GroupMetricsImpl>(
259 group_id, group_size_table_[group_id])});
260 }
261 it->second->AddStateChangedEvent(address, state, status);
262
263 if (it->second->IsClosed()) {
264 it->second->WriteStats();
265 opened_groups_.erase(it);
266 }
267 }
268
OnStreamStarted(int32_t group_id,le_audio::types::LeAudioContextType context_type)269 void MetricsCollector::OnStreamStarted(
270 int32_t group_id, le_audio::types::LeAudioContextType context_type) {
271 if (group_id <= 0) return;
272 auto it = opened_groups_.find(group_id);
273 if (it != opened_groups_.end()) {
274 it->second->AddStreamStartedEvent(context_type);
275 }
276 }
277
OnStreamEnded(int32_t group_id)278 void MetricsCollector::OnStreamEnded(int32_t group_id) {
279 if (group_id <= 0) return;
280 auto it = opened_groups_.find(group_id);
281 if (it != opened_groups_.end()) {
282 it->second->AddStreamEndedEvent();
283 }
284 }
285
Flush()286 void MetricsCollector::Flush() {
287 LOG(INFO) << __func__;
288 for (auto& p : opened_groups_) {
289 p.second->Flush();
290 }
291 opened_groups_.clear();
292 }
293
294 } // namespace le_audio
295