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