Skip to content

Commit dd6f7b1

Browse files
authored
Speedup diagnostics (#518)
1 parent 177dfc9 commit dd6f7b1

File tree

4 files changed

+48
-40
lines changed

4 files changed

+48
-40
lines changed

matter_server/client/client.py

+22-29
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,11 @@
1010
from aiohttp import ClientSession
1111
from chip.clusters import Objects as Clusters
1212

13-
from matter_server.common.errors import ERROR_MAP, MatterError, NodeNotExists
13+
from matter_server.common.errors import ERROR_MAP, NodeNotExists
1414

1515
from ..common.helpers.util import (
16-
convert_hex_string,
16+
convert_ip_address,
1717
convert_mac_address,
18-
create_attribute_path_from_attribute,
1918
dataclass_from_dict,
2019
dataclass_to_dict,
2120
)
@@ -51,7 +50,7 @@
5150

5251
SUB_WILDCARD: Final = "*"
5352

54-
# pylint: disable=too-many-public-methods
53+
# pylint: disable=too-many-public-methods,too-many-locals,too-many-branches
5554

5655

5756
class MatterClient:
@@ -216,17 +215,6 @@ async def get_matter_fabrics(self, node_id: int) -> list[MatterFabricData]:
216215
"""
217216

218217
node = self.get_node(node_id)
219-
if node.available:
220-
# try to refresh the OperationalCredentials.Fabric attribute
221-
# so we have the most accurate information
222-
attr_path = create_attribute_path_from_attribute(
223-
0, Clusters.OperationalCredentials.Attributes.Fabrics
224-
)
225-
try:
226-
await self.refresh_attribute(node_id, attr_path)
227-
except MatterError as err:
228-
self.logger.exception(err)
229-
230218
fabrics: list[
231219
Clusters.OperationalCredentials.Structs.FabricDescriptorStruct
232220
] = node.get_attribute_value(
@@ -270,13 +258,12 @@ async def ping_node(self, node_id: int) -> NodePingResult:
270258
async def node_diagnostics(self, node_id: int) -> NodeDiagnostics:
271259
"""Gather diagnostics for the given node."""
272260
node = self.get_node(node_id)
273-
# ping the node (will also refresh NetworkInterfaces data)
274-
ping_result = await self.ping_node(node_id)
275261
# grab some details from the first (operational) network interface
276262
network_type = NetworkType.UNKNOWN
277263
mac_address = None
278264
attribute = Clusters.GeneralDiagnostics.Attributes.NetworkInterfaces
279265
network_interface: Clusters.GeneralDiagnostics.Structs.NetworkInterface
266+
ip_addresses: list[str] = []
280267
for network_interface in node.get_attribute_value(
281268
0, cluster=None, attribute=attribute
282269
):
@@ -302,38 +289,44 @@ async def node_diagnostics(self, node_id: int) -> NodeDiagnostics:
302289
# unknown interface: ignore
303290
continue
304291
mac_address = convert_mac_address(network_interface.hardwareAddress)
292+
# enumerate ipv4 and ipv6 addresses
293+
for ipv4_address_hex in network_interface.IPv4Addresses:
294+
ipv4_address = convert_ip_address(ipv4_address_hex)
295+
ip_addresses.append(ipv4_address)
296+
for ipv6_address_hex in network_interface.IPv6Addresses:
297+
ipv6_address = convert_ip_address(ipv6_address_hex, True)
298+
ip_addresses.append(ipv6_address)
305299
break
306300
# get thread/wifi specific info
307301
node_type = NodeType.UNKNOWN
308302
network_name = None
309303
if network_type == NetworkType.THREAD:
310-
cluster: Clusters.ThreadNetworkDiagnostics = node.get_cluster(
304+
thread_cluster: Clusters.ThreadNetworkDiagnostics = node.get_cluster(
311305
0, Clusters.ThreadNetworkDiagnostics
312306
)
313-
network_name = convert_hex_string(cluster.networkName)
307+
network_name = thread_cluster.networkName
314308
# parse routing role to (diagnostics) node type
315309
if (
316-
cluster.routingRole
310+
thread_cluster.routingRole
317311
== Clusters.ThreadNetworkDiagnostics.Enums.RoutingRoleEnum.kSleepyEndDevice
318312
):
319313
node_type = NodeType.SLEEPY_END_DEVICE
320-
if cluster.routingRole in (
314+
if thread_cluster.routingRole in (
321315
Clusters.ThreadNetworkDiagnostics.Enums.RoutingRoleEnum.kLeader,
322316
Clusters.ThreadNetworkDiagnostics.Enums.RoutingRoleEnum.kRouter,
323317
):
324318
node_type = NodeType.ROUTING_END_DEVICE
325319
elif (
326-
cluster.routingRole
320+
thread_cluster.routingRole
327321
== Clusters.ThreadNetworkDiagnostics.Enums.RoutingRoleEnum.kEndDevice
328322
):
329323
node_type = NodeType.END_DEVICE
330324
elif network_type == NetworkType.WIFI:
331-
attr_value: bytes = node.get_attribute_value(
332-
0,
333-
cluster=None,
334-
attribute=Clusters.WiFiNetworkDiagnostics.Attributes.Bssid,
325+
wifi_cluster: Clusters.WiFiNetworkDiagnostics = node.get_cluster(
326+
0, Clusters.WiFiNetworkDiagnostics
335327
)
336-
network_name = convert_hex_string(attr_value)
328+
if wifi_cluster and wifi_cluster.bssid:
329+
network_name = wifi_cluster.bssid
337330
node_type = NodeType.END_DEVICE
338331
# override node type if node is a bridge
339332
if node.node_data.is_bridge:
@@ -345,9 +338,9 @@ async def node_diagnostics(self, node_id: int) -> NodeDiagnostics:
345338
network_type=network_type,
346339
node_type=node_type,
347340
network_name=network_name,
348-
ip_adresses=list(ping_result),
341+
ip_adresses=ip_addresses,
349342
mac_address=mac_address,
350-
reachable=any(ping_result.values()),
343+
available=node.available,
351344
active_fabrics=active_fabrics,
352345
)
353346

matter_server/client/models/node.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -400,5 +400,5 @@ class NodeDiagnostics:
400400
network_name: str | None # WiFi SSID or Thread network name
401401
ip_adresses: list[str]
402402
mac_address: str | None
403-
reachable: bool
403+
available: bool
404404
active_fabrics: list[MatterFabricData]

matter_server/server/device_controller.py

+10-8
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
0, Clusters.ThreadNetworkDiagnostics.Attributes.RoutingRole
6666
)
6767

68-
BASE_SUBSCRIBE_ATTRIBUTES: tuple[Attribute.AttributePath, Attribute.AttributePath] = (
68+
BASE_SUBSCRIBE_ATTRIBUTES: tuple[Attribute.AttributePath, ...] = (
6969
# all endpoints, BasicInformation cluster
7070
Attribute.AttributePath(
7171
EndpointId=None, ClusterId=Clusters.BasicInformation.id, Attribute=None
@@ -76,6 +76,15 @@
7676
ClusterId=Clusters.BridgedDeviceBasicInformation.id,
7777
Attribute=None,
7878
),
79+
# networkinterfaces attribute on general diagnostics cluster,
80+
# so we have the most accurate IP addresses for ping/diagnostics
81+
Attribute.AttributePath(
82+
EndpointId=0, Attribute=Clusters.GeneralDiagnostics.Attributes.NetworkInterfaces
83+
),
84+
# active fabrics attribute - to speedup node diagnostics
85+
Attribute.AttributePath(
86+
EndpointId=0, Attribute=Clusters.OperationalCredentials.Attributes.Fabrics
87+
),
7988
)
8089

8190
# pylint: disable=too-many-lines,too-many-locals,too-many-statements,too-many-branches
@@ -699,13 +708,6 @@ async def ping_node(self, node_id: int) -> NodePingResult:
699708
raise NodeNotExists(
700709
f"Node {node_id} does not exist or is not yet interviewed"
701710
)
702-
if node.available:
703-
# try to refresh the GeneralDiagnostics.NetworkInterface attribute
704-
# so we have the most accurate information before pinging
705-
try:
706-
await self.read_attribute(node_id, attr_path)
707-
except (NodeNotResolving, ChipStackError) as err:
708-
LOGGER.exception(err)
709711

710712
battery_powered = (
711713
node.attributes.get(ROUTING_ROLE_ATTRIBUTE_PATH, 0)

matter_server/server/helpers/utils.py

+15-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import asyncio
44
import platform
55

6+
import async_timeout
7+
68
PLATFORM_MAC = platform.system() == "Darwin"
79

810

@@ -16,7 +18,13 @@ async def ping_ip(ip_address: str, timeout: int = 2) -> bool:
1618
cmd = f"ping6 -c 1 -W {timeout} {ip_address}"
1719
else:
1820
cmd = f"ping -c 1 -W {timeout} {ip_address}"
19-
return (await check_output(cmd))[0] == 0
21+
try:
22+
# we add an additional timeout here as safeguard and to account for the fact
23+
# that macos does not seem to have timeout on ping6
24+
async with async_timeout.timeout(timeout + 2):
25+
return (await check_output(cmd))[0] == 0
26+
except asyncio.TimeoutError:
27+
return False
2028

2129

2230
async def check_output(shell_cmd: str) -> tuple[int | None, bytes]:
@@ -26,5 +34,10 @@ async def check_output(shell_cmd: str) -> tuple[int | None, bytes]:
2634
stderr=asyncio.subprocess.STDOUT,
2735
stdout=asyncio.subprocess.PIPE,
2836
)
29-
stdout, _ = await proc.communicate()
37+
try:
38+
stdout, _ = await proc.communicate()
39+
except asyncio.CancelledError:
40+
proc.terminate()
41+
await proc.communicate()
42+
raise
3043
return (proc.returncode, stdout)

0 commit comments

Comments
 (0)