mirror of
https://github.com/python-kasa/python-kasa.git
synced 2024-12-22 11:13:34 +00:00
Add benchmarks for speedups (#473)
* Add benchmarks for speedups * Update README.md * Update README.md Co-authored-by: Teemu R. <tpr@iki.fi> * relo * Update README.md * document benchmark * Update README.md --------- Co-authored-by: Teemu R. <tpr@iki.fi>
This commit is contained in:
parent
b83986bd51
commit
fde156c859
@ -23,6 +23,8 @@ If you are using cpython, it is recommended to install with `[speedups]` to enab
|
||||
pip install python-kasa[speedups]
|
||||
```
|
||||
|
||||
With `[speedups]`, the protocol overhead is roughly an order of magnitude lower (benchmarks available in devtools).
|
||||
|
||||
Alternatively, you can clone this repository and use poetry to install the development version:
|
||||
```
|
||||
git clone https://github.com/python-kasa/python-kasa.git
|
||||
|
@ -59,3 +59,13 @@ id
|
||||
<id>-HS110(EU) 5.0 0.055700 0.016174 0.042086 0.045578 0.048905 0.059869 0.082064
|
||||
<id>-KP303(UK) 5.0 0.010298 0.003765 0.007773 0.007968 0.008546 0.010439 0.016763
|
||||
```
|
||||
|
||||
## benchmark
|
||||
|
||||
* Benchmark the protocol
|
||||
|
||||
```shell
|
||||
% python3 devtools/bench/benchmark.py
|
||||
New parser, parsing 100000 messages took 0.6339647499989951 seconds
|
||||
Old parser, parsing 100000 messages took 9.473990250000497 seconds
|
||||
```
|
||||
|
30
devtools/bench/benchmark.py
Normal file
30
devtools/bench/benchmark.py
Normal file
@ -0,0 +1,30 @@
|
||||
"""Benchmark the new parser against the old parser."""
|
||||
|
||||
import json
|
||||
import timeit
|
||||
|
||||
import orjson
|
||||
from kasa_crypt import decrypt, encrypt
|
||||
from utils.data import REQUEST, WIRE_RESPONSE
|
||||
from utils.original import OriginalTPLinkSmartHomeProtocol
|
||||
|
||||
|
||||
def original_request_response() -> None:
|
||||
"""Benchmark the original parser."""
|
||||
OriginalTPLinkSmartHomeProtocol.encrypt(json.dumps(REQUEST))
|
||||
json.loads(OriginalTPLinkSmartHomeProtocol.decrypt(WIRE_RESPONSE[4:]))
|
||||
|
||||
|
||||
def new_request_response() -> None:
|
||||
"""Benchmark the new parser."""
|
||||
encrypt(orjson.dumps(REQUEST).decode())
|
||||
orjson.loads(decrypt(WIRE_RESPONSE[4:]))
|
||||
|
||||
|
||||
count = 100000
|
||||
|
||||
time = timeit.Timer(new_request_response).timeit(count)
|
||||
print(f"New parser, parsing {count} messages took {time} seconds")
|
||||
|
||||
time = timeit.Timer(original_request_response).timeit(count)
|
||||
print(f"Old parser, parsing {count} messages took {time} seconds")
|
1
devtools/bench/utils/__init__.py
Normal file
1
devtools/bench/utils/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""Benchmark utils."""
|
141
devtools/bench/utils/data.py
Normal file
141
devtools/bench/utils/data.py
Normal file
@ -0,0 +1,141 @@
|
||||
"""Test data for benchmarks."""
|
||||
|
||||
|
||||
import json
|
||||
|
||||
from .original import OriginalTPLinkSmartHomeProtocol
|
||||
|
||||
REQUEST = {
|
||||
"system": {"get_sysinfo": None},
|
||||
"anti_theft": {"get_rules": None, "get_next_action": None},
|
||||
"schedule": {
|
||||
"get_rules": None,
|
||||
"get_next_action": None,
|
||||
"get_realtime": None,
|
||||
"get_daystat": {"year": 2023, "month": 6},
|
||||
"get_monthstat": {"year": 2023},
|
||||
},
|
||||
"time": {"get_time": None, "get_timezone": None},
|
||||
"emeter": {
|
||||
"get_realtime": None,
|
||||
"get_daystat": {"year": 2023, "month": 6},
|
||||
"get_monthstat": {"year": 2023},
|
||||
},
|
||||
}
|
||||
RESPONSE = {
|
||||
"anti_theft": {
|
||||
"get_next_action": {"err_code": -2, "err_msg": "member not support"},
|
||||
"get_rules": {"enable": 0, "err_code": 0, "rule_list": [], "version": 2},
|
||||
},
|
||||
"emeter": {
|
||||
"get_daystat": {
|
||||
"day_list": [{"day": 30, "energy_wh": 0, "month": 6, "year": 2023}],
|
||||
"err_code": 0,
|
||||
},
|
||||
"get_monthstat": {
|
||||
"err_code": 0,
|
||||
"month_list": [{"energy_wh": 0, "month": 6, "year": 2023}],
|
||||
},
|
||||
"get_realtime": {
|
||||
"current_ma": 0,
|
||||
"err_code": 0,
|
||||
"power_mw": 0,
|
||||
"slot_id": 0,
|
||||
"total_wh": 0,
|
||||
"voltage_mv": 119390,
|
||||
},
|
||||
},
|
||||
"schedule": {
|
||||
"get_daystat": {
|
||||
"day_list": [{"day": 30, "month": 6, "time": 3, "year": 2023}],
|
||||
"err_code": 0,
|
||||
},
|
||||
"get_monthstat": {
|
||||
"err_code": 0,
|
||||
"month_list": [{"month": 6, "time": 3, "year": 2023}],
|
||||
},
|
||||
"get_next_action": {"err_code": 0, "type": -1},
|
||||
"get_realtime": {"err_code": -2, "err_msg": "member not support"},
|
||||
"get_rules": {"enable": 1, "err_code": 0, "rule_list": [], "version": 2},
|
||||
},
|
||||
"system": {
|
||||
"get_sysinfo": {
|
||||
"alias": "TP-LINK_Power Strip_5C33",
|
||||
"child_num": 6,
|
||||
"children": [
|
||||
{
|
||||
"alias": "Plug 1",
|
||||
"id": "8006AF35494E7DB13DDE9B8F40BF2E001E77031900",
|
||||
"next_action": {"type": -1},
|
||||
"on_time": 231,
|
||||
"state": 1,
|
||||
},
|
||||
{
|
||||
"alias": "Plug 2",
|
||||
"id": "8006AF35494E7DB13DDE9B8F40BF2E001E77031901",
|
||||
"next_action": {"type": -1},
|
||||
"on_time": 231,
|
||||
"state": 1,
|
||||
},
|
||||
{
|
||||
"alias": "Plug 3",
|
||||
"id": "8006AF35494E7DB13DDE9B8F40BF2E001E77031902",
|
||||
"next_action": {"type": -1},
|
||||
"on_time": 231,
|
||||
"state": 1,
|
||||
},
|
||||
{
|
||||
"alias": "Plug 4",
|
||||
"id": "8006AF35494E7DB13DDE9B8F40BF2E001E77031903",
|
||||
"next_action": {"type": -1},
|
||||
"on_time": 231,
|
||||
"state": 1,
|
||||
},
|
||||
{
|
||||
"alias": "Plug 5",
|
||||
"id": "8006AF35494E7DB13DDE9B8F40BF2E001E77031904",
|
||||
"next_action": {"type": -1},
|
||||
"on_time": 231,
|
||||
"state": 1,
|
||||
},
|
||||
{
|
||||
"alias": "Plug 6",
|
||||
"id": "8006AF35494E7DB13DDE9B8F40BF2E001E77031905",
|
||||
"next_action": {"type": -1},
|
||||
"on_time": 231,
|
||||
"state": 1,
|
||||
},
|
||||
],
|
||||
"deviceId": "8006AF35494E7DB13DDE9B8F40BF2E001E770319",
|
||||
"err_code": 0,
|
||||
"feature": "TIM:ENE",
|
||||
"hwId": "955F433CBA24823A248A59AA64571A73",
|
||||
"hw_ver": "2.0",
|
||||
"latitude_i": 297852,
|
||||
"led_off": 0,
|
||||
"longitude_i": -954074,
|
||||
"mac": "C0:06:C3:42:5C:33",
|
||||
"mic_type": "IOT.SMARTPLUGSWITCH",
|
||||
"model": "HS300(US)",
|
||||
"oemId": "32BD0B21AA9BF8E84737D1DB1C66E883",
|
||||
"rssi": -41,
|
||||
"status": "new",
|
||||
"sw_ver": "1.0.3 Build 201203 Rel.165457",
|
||||
"updating": 0,
|
||||
}
|
||||
},
|
||||
"time": {
|
||||
"get_time": {
|
||||
"err_code": 0,
|
||||
"hour": 9,
|
||||
"mday": 30,
|
||||
"min": 32,
|
||||
"month": 6,
|
||||
"sec": 54,
|
||||
"year": 2023,
|
||||
},
|
||||
"get_timezone": {"err_code": 0, "index": 13},
|
||||
},
|
||||
}
|
||||
|
||||
WIRE_RESPONSE = OriginalTPLinkSmartHomeProtocol.encrypt(json.dumps(RESPONSE))
|
47
devtools/bench/utils/original.py
Normal file
47
devtools/bench/utils/original.py
Normal file
@ -0,0 +1,47 @@
|
||||
"""Original implementation of the TP-Link Smart Home protocol."""
|
||||
import struct
|
||||
from typing import Generator
|
||||
|
||||
|
||||
class OriginalTPLinkSmartHomeProtocol:
|
||||
"""Original implementation of the TP-Link Smart Home protocol."""
|
||||
|
||||
INITIALIZATION_VECTOR = 171
|
||||
|
||||
@staticmethod
|
||||
def _xor_payload(unencrypted: bytes) -> Generator[int, None, None]:
|
||||
key = OriginalTPLinkSmartHomeProtocol.INITIALIZATION_VECTOR
|
||||
for unencryptedbyte in unencrypted:
|
||||
key = key ^ unencryptedbyte
|
||||
yield key
|
||||
|
||||
@staticmethod
|
||||
def encrypt(request: str) -> bytes:
|
||||
"""Encrypt a request for a TP-Link Smart Home Device.
|
||||
|
||||
:param request: plaintext request data
|
||||
:return: ciphertext to be send over wire, in bytes
|
||||
"""
|
||||
plainbytes = request.encode()
|
||||
return struct.pack(">I", len(plainbytes)) + bytes(
|
||||
OriginalTPLinkSmartHomeProtocol._xor_payload(plainbytes)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _xor_encrypted_payload(ciphertext: bytes) -> Generator[int, None, None]:
|
||||
key = OriginalTPLinkSmartHomeProtocol.INITIALIZATION_VECTOR
|
||||
for cipherbyte in ciphertext:
|
||||
plainbyte = key ^ cipherbyte
|
||||
key = cipherbyte
|
||||
yield plainbyte
|
||||
|
||||
@staticmethod
|
||||
def decrypt(ciphertext: bytes) -> str:
|
||||
"""Decrypt a response of a TP-Link Smart Home Device.
|
||||
|
||||
:param ciphertext: encrypted response data
|
||||
:return: plaintext response
|
||||
"""
|
||||
return bytes(
|
||||
OriginalTPLinkSmartHomeProtocol._xor_encrypted_payload(ciphertext)
|
||||
).decode()
|
Loading…
Reference in New Issue
Block a user