-
Notifications
You must be signed in to change notification settings - Fork 304
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Discovered devices improvements #1047
Conversation
This changes all scanner backends so that they only create a BLEDevice object once per scan. For example, in several backends, a new BLEDevice was created for each discovered device each time the discovered_devices property was called, which could be significant if many (~100) devices were discovered. This also caches the last advertisement data along with the BLEDevice which is expected to be used as an alternative to BLEDevice.metadata in the future.
This adds an `rssi` attribute to the AdvertisementData type. This is done so we can eventually deprecate the `rssi` on the BLEDevice object (#1025). While we are touching this, also change AdvertisementData to a named tuple so we don't have to initialize all of the values ourselves.
This reduces code duplication and helps ensure that all backends work the same.
This adds a new discovered_devices_and_advertisement_data property to BleakScanner that can be used to get the last advertisement data in addition to the BLEDevice of the devices discovered during the scan session. A new argument is added to the BleakScanner.discover() class method to allow this method to return the new property as well.
Hi @bdraco, could you please take a look? |
Should be able to take a look a bit later today. 👍 |
tx_power=tx_power, | ||
rssi=props.get("RSSI", -127), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice!
Returns: | ||
A Bluetooth address as a string. | ||
""" | ||
return ":".join(device_path[-17:].split("_")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be faster to grab it and use .replace
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently, this isn't used in any critical paths.
|
||
from ..exc import BleakError | ||
from .device import BLEDevice | ||
|
||
|
||
class AdvertisementData: | ||
class AdvertisementData(NamedTuple): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if it would be faster as a dataclass?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope. Named tuple is faster
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Found this while digging around https://medium.com/@jacktator/dataclass-vs-namedtuple-vs-object-for-performance-optimization-in-python-691e234253b9
But will get better data when I run in through the profiler since we will get actual use case data vs theory
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Part of the reason for using NamedTuple was for the immutability, but if there is a way to speed it up, we can consider alternatives.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense. I don't think it's going to be an issue. Will know for sure once I do the profiles on the production systems. Identifying this as an area to target for additional profiling and testing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't forget to add frozen=True
to dataclasses when profiling 👍
This looks awesome. Cleans things up quite a bit 👍 I added some first impression thoughts above. Some need to be validated with testing I'll do testing and profiling when other stuff calms down later tonight and I don't have as many distractions |
@@ -127,6 +160,45 @@ def detection_callback(s, d): | |||
|
|||
self._callback = detection_callback | |||
|
|||
def create_or_update_device( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For profiling this later, assume this is hot path
|
||
from ..exc import BleakError | ||
from .device import BLEDevice | ||
|
||
|
||
class AdvertisementData: | ||
class AdvertisementData(NamedTuple): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if it can be avoided but
Sep 30 23:52:42 homeassistant homeassistant[487]: TypeError: AdvertisementData.__new__() missing 1 required positional argument: 'rssi'
I think this could be made more performant by storing the Maybe slice for (callback, adapter_path) in self._advertisement_callbacks:
# filter messages from other adapters
if not device_path.startswith(adapter_path):
continue
# TODO: this should be deep copy, not shallow
callback(device_path, device.copy()) |
How much of this difference could just be noise/variance between runs? For example The part that was changed - |
I'll do a few more. Its not an exact science since we can't control how many advertisements come in. |
Only thing that still stands out as easy to optimize is |
Comparing relative values instead of absolute values, it looks like "before", |
All of these are 1 min snapshots with % is run time on a single core of |
I'll revert and do another one to compare. The system is mostly idle otherwise but its a real world performance test so everything is affected by everything else |
Some good news about the hot unmarshalling path, it’s now much faster as it has an optional (auto fallback to pure python if not available) which makes this path now the hottest one in production. I’ll see if I can get unpack variants exposed in dbus-fast since it was already added in a previous PR and add an optional (auto fallback to pure python) cython backend to shave that off a bit. #1055 |
This fixes a regression from #1047.
In hbldh#1047, the call to getRssi() was broken by using the wrong object. This fixes the mistake. Fixes hbldh#1085.
seen_devices: Dict[str, Tuple[BLEDevice, AdvertisementData]] | ||
""" | ||
Map of device identifier to BLEDevice and most recent advertisement data. | ||
|
||
This map must be cleared when scanning starts. | ||
""" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the point of this class variable in addition to the instance variable defined below?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since nothing is assigned to the name here, this is just a type hint rather than a class variable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Of course, my apologies 🤦 I suppose moved here to not clutter the __init__()
method with docstring?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes
rssi
attribute toAdvertisemntData
Additional details in the commit messages.