blob: 74edcce5b1dbca0c0a9bd02aa9010baa46ae2576 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
crate::client::types,
fuchsia_inspect::{self as inspect},
fuchsia_inspect_contrib::{
inspect_insert, inspect_log,
log::{InspectListClosure, WriteInspect},
make_inspect_loggable,
nodes::BoundedListNode,
nodes::NodeWriter,
},
rand::Rng as _,
std::collections::HashMap,
std::sync::{Arc, Mutex, RwLock},
};
const MAX_PAST_NETWORK_SELECTIONS: usize = 5;
const MAX_PAST_CONNECTIONS: usize = 5;
struct InspectNodes {
network_selections: BoundedListNode,
active_connections: inspect::Node,
past_connections: BoundedListNode,
}
#[derive(Clone)]
pub(crate) struct Telemetry {
inspect_nodes: Arc<Mutex<InspectNodes>>,
active_connections: Arc<RwLock<Vec<()>>>,
past_connections: Arc<RwLock<Vec<()>>>,
}
impl Telemetry {
pub fn new(root_node: inspect::Node) -> Self {
Self {
inspect_nodes: Arc::new(Mutex::new(InspectNodes {
network_selections: BoundedListNode::new(
root_node.create_child("network_selections"),
MAX_PAST_NETWORK_SELECTIONS,
),
active_connections: root_node.create_child("active_connections"),
past_connections: BoundedListNode::new(
root_node.create_child("past_connections"),
MAX_PAST_CONNECTIONS,
),
})),
active_connections: Arc::new(RwLock::new(vec![])),
past_connections: Arc::new(RwLock::new(vec![])),
}
}
fn generate_telemetry_id(&self) -> TelemetryId {
rand::thread_rng().gen()
}
}
pub(crate) trait ConnectionTrackerApi {
fn initialize_connection(&self) -> NetworkSelectionMetadata;
fn log_network_selection_decision(
&self,
metadata: NetworkSelectionMetadata,
search_type: NetworkSelectionType,
candidates: Vec<CandidateBss>,
selected: CandidateBss,
) -> ConnectionInitiationMetadata;
// fn log_connection_attempt_initiation(&self, metadata: &mut ConnectionMetadata);
// fn log_connection_attempt_result(&self, metadata: &mut ConnectionMetadata, result: Result);
// fn log_disconnection(&self, metadata: &mut ConnectionMetadata, reason: Reason);
// fn log_connection_completion(&self, metadata: ConnectionMetadata);
}
type TelemetryId = u32;
pub(crate) struct NetworkSelectionMetadata {
id: TelemetryId,
}
pub(crate) struct ConnectionInitiationMetadata {}
pub(crate) struct CandidateBss {
rssi: i8,
recent_failure_count: u8,
band: types::Band,
score: i8,
}
impl WriteInspect for CandidateBss {
fn write_inspect(&self, writer: &mut NodeWriter<'_>, key: &str) {
inspect_insert!(writer, var key: {
rssi: self.rssi,
recent_failure_count: self.recent_failure_count,
band: format!("{:?}", self.band),
score: self.score,
});
}
}
pub(crate) enum NetworkSelectionType {
Undirected, // Looking for the best BSS from any saved networks
Directed, // Looking for the best BSS for a particular network
}
impl WriteInspect for NetworkSelectionType {
fn write_inspect(&self, writer: &mut NodeWriter<'_>, key: &str) {
writer.create_string(
key,
match self {
NetworkSelectionType::Undirected => "Undirected",
NetworkSelectionType::Directed => "Directed",
},
);
}
}
impl ConnectionTrackerApi for Telemetry {
fn initialize_connection(&self) -> NetworkSelectionMetadata {
NetworkSelectionMetadata { id: self.generate_telemetry_id() }
}
fn log_network_selection_decision(
&self,
metadata: NetworkSelectionMetadata,
search_type: NetworkSelectionType,
candidates: Vec<CandidateBss>,
selected: CandidateBss,
) -> ConnectionInitiationMetadata {
let candidates_mapped =
InspectListClosure(&candidates, |mut node_writer, key, candidate| {
inspect_insert!(node_writer, var key: candidate);
});
inspect_log!(self.inspect_nodes.lock().unwrap().network_selections, {
search_type: search_type,
selected: selected,
candidates: candidates_mapped,
});
ConnectionInitiationMetadata {}
}
// fn log_connection_attempt_initiation(metadata: &mut ConnectionMetadata);
// fn log_connection_attempt_result(metadata: &mut ConnectionMetadata, result: Result);
// fn log_disconnection(metadata: &mut ConnectionMetadata, reason: Reason);
// fn log_connection_completion(metadata: ConnectionMetadata);
}
// BRAINSTORM
// I'd like the Telemetry module to be able to track uptime, expressed as (time connected) / (time the device was on and had a saved network in the vicinity)
// For this, I would need an event log for "connected" vs "not connected", along with an event log
// for "saved networks seen in area" vs "saved networks not seen in area".
// For these metrics, the Telemetry module can trivially operate as a MPSC. For
#[cfg(test)]
mod tests {
use {
super::*,
fuchsia_inspect::{assert_inspect_tree, testing::AnyProperty, Inspector},
wlan_common::assert_variant,
};
#[test]
fn test_log_network_selection_decision() {
let inspector = Inspector::new();
let test_node = inspector.root().create_child("test_node");
test_node.create_uint("foo", 1);
let telemetry = Telemetry::new(test_node);
telemetry.log_network_selection_decision(
NetworkSelectionMetadata { id: 20 },
NetworkSelectionType::Undirected,
vec![],
CandidateBss {
rssi: 20,
recent_failure_count: 10,
band: types::Band::WlanBand2Ghz,
score: 25,
},
);
assert_inspect_tree!(inspector, root: {
test_node: {
foo: 1u64
}
});
assert_inspect_tree!(inspector, root: {
test_node: {
"0": { "@time": AnyProperty, k1: "1", meaning_of_life: 42u64, k3: 3i64, k4: 4f64 },
"1": { "@time": AnyProperty, small_uint: 1u64, small_int: 2i64, float: 3f64 },
"2": { "@time": AnyProperty, s: "str", uint: 13u64 },
"3": { "@time": AnyProperty },
}
});
}
}