Skip to content

Commit 9d03b55

Browse files
authored
Merge pull request #599 from opentensor/feat/thewhaleking/add-latency-command
2 parents 96b5053 + fcdc1c9 commit 9d03b55

File tree

3 files changed

+108
-10
lines changed

3 files changed

+108
-10
lines changed

bittensor_cli/cli.py

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,10 @@
4242
from bittensor_cli.src.bittensor import utils
4343
from bittensor_cli.src.bittensor.balances import Balance
4444
from bittensor_cli.src.bittensor.chain_data import SubnetHyperparameters
45-
from bittensor_cli.src.bittensor.subtensor_interface import SubtensorInterface
45+
from bittensor_cli.src.bittensor.subtensor_interface import (
46+
SubtensorInterface,
47+
best_connection,
48+
)
4649
from bittensor_cli.src.bittensor.utils import (
4750
console,
4851
err_console,
@@ -619,7 +622,7 @@ class CLIManager:
619622
wallet_app: typer.Typer
620623
subnets_app: typer.Typer
621624
weights_app: typer.Typer
622-
utils_app = typer.Typer(epilog=_epilog)
625+
utils_app: typer.Typer
623626
view_app: typer.Typer
624627
asyncio_runner = asyncio
625628

@@ -692,6 +695,7 @@ def __init__(self):
692695
self.weights_app = typer.Typer(epilog=_epilog)
693696
self.view_app = typer.Typer(epilog=_epilog)
694697
self.liquidity_app = typer.Typer(epilog=_epilog)
698+
self.utils_app = typer.Typer(epilog=_epilog)
695699

696700
# config alias
697701
self.app.add_typer(
@@ -766,7 +770,7 @@ def __init__(self):
766770

767771
# utils app
768772
self.app.add_typer(
769-
self.utils_app, name="utils", no_args_is_help=True, hidden=True
773+
self.utils_app, name="utils", no_args_is_help=True, hidden=False
770774
)
771775

772776
# view app
@@ -1048,6 +1052,10 @@ def __init__(self):
10481052
"remove", rich_help_panel=HELP_PANELS["LIQUIDITY"]["LIQUIDITY_MGMT"]
10491053
)(self.liquidity_remove)
10501054

1055+
# utils app
1056+
self.utils_app.command("convert")(self.convert)
1057+
self.utils_app.command("latency")(self.best_connection)
1058+
10511059
def generate_command_tree(self) -> Tree:
10521060
"""
10531061
Generates a rich.Tree of the commands, subcommands, and groups of this app
@@ -6296,7 +6304,6 @@ def liquidity_modify(
62966304
)
62976305

62986306
@staticmethod
6299-
@utils_app.command("convert")
63006307
def convert(
63016308
from_rao: Optional[str] = typer.Option(
63026309
None, "--rao", help="Convert amount from Rao"
@@ -6326,6 +6333,66 @@ def convert(
63266333
f"{Balance.from_tao(tao).rao}{Balance.rao_unit}",
63276334
)
63286335

6336+
def best_connection(
6337+
self,
6338+
additional_networks: Optional[list[str]] = typer.Option(
6339+
None,
6340+
"--network",
6341+
help="Network(s) to test for the best connection",
6342+
),
6343+
):
6344+
"""
6345+
This command will give you the latency of all finney-like network in additional to any additional networks you specify via the '--network' flag
6346+
6347+
The results are three-fold. One column is the overall time to initialise a connection, send the requests, and wait for the results. The second column measures single ping-pong speed once connected. The third makes a real world call to fetch the chain head.
6348+
6349+
EXAMPLE
6350+
6351+
[green]$[/green] btcli utils latency --network ws://189.234.12.45 --network wss://mysubtensor.duckdns.org
6352+
6353+
"""
6354+
additional_networks = additional_networks or []
6355+
if any(not x.startswith("ws") for x in additional_networks):
6356+
err_console.print(
6357+
"Invalid network endpoint. Ensure you are specifying a valid websocket endpoint"
6358+
f" (starting with [{COLORS.G.LINKS}]ws://[/{COLORS.G.LINKS}] or "
6359+
f"[{COLORS.G.LINKS}]wss://[/{COLORS.G.LINKS}]).",
6360+
)
6361+
return False
6362+
results: dict[str, list[float]] = self._run_command(
6363+
best_connection(Constants.lite_nodes + additional_networks)
6364+
)
6365+
sorted_results = {
6366+
k: v for k, v in sorted(results.items(), key=lambda item: item[1][0])
6367+
}
6368+
table = Table(
6369+
Column("Network"),
6370+
Column("End to End Latency", style="cyan"),
6371+
Column("Single Request Ping", style="cyan"),
6372+
Column("Chain Head Request Latency", style="cyan"),
6373+
title="Connection Latencies (seconds)",
6374+
caption="lower value is faster",
6375+
)
6376+
for n_name, (
6377+
overall_latency,
6378+
single_request,
6379+
chain_head,
6380+
) in sorted_results.items():
6381+
table.add_row(
6382+
n_name, str(overall_latency), str(single_request), str(chain_head)
6383+
)
6384+
console.print(table)
6385+
fastest = next(iter(sorted_results.keys()))
6386+
if conf_net := self.config.get("network", ""):
6387+
if not conf_net.startswith("ws") and conf_net in Constants.networks:
6388+
conf_net = Constants.network_map[conf_net]
6389+
if conf_net != fastest:
6390+
console.print(
6391+
f"The fastest network is {fastest}. You currently have {conf_net} selected as your default network."
6392+
f"\nYou can update this with {arg__(f'btcli config set --network {fastest}')}"
6393+
)
6394+
return True
6395+
63296396
def run(self):
63306397
self.app()
63316398

bittensor_cli/src/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class Constants:
2323
dev_entrypoint = "wss://dev.chain.opentensor.ai:443"
2424
local_entrypoint = "ws://127.0.0.1:9944"
2525
latent_lite_entrypoint = "wss://lite.sub.latent.to:443"
26+
lite_nodes = [finney_entrypoint, subvortex_entrypoint, latent_lite_entrypoint]
2627
network_map = {
2728
"finney": finney_entrypoint,
2829
"test": finney_test_entrypoint,

bittensor_cli/src/bittensor/subtensor_interface.py

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
import asyncio
22
import os
3+
import time
34
from typing import Optional, Any, Union, TypedDict, Iterable
45

56
import aiohttp
7+
from async_substrate_interface.async_substrate import (
8+
DiskCachedAsyncSubstrateInterface,
9+
AsyncSubstrateInterface,
10+
)
11+
from async_substrate_interface.errors import SubstrateRequestException
612
from async_substrate_interface.utils.storage import StorageKey
713
from bittensor_wallet import Wallet
814
from bittensor_wallet.bittensor_wallet import Keypair
915
from bittensor_wallet.utils import SS58_FORMAT
1016
from scalecodec import GenericCall
11-
from async_substrate_interface.errors import SubstrateRequestException
1217
import typer
18+
import websockets
1319

14-
15-
from async_substrate_interface.async_substrate import (
16-
DiskCachedAsyncSubstrateInterface,
17-
AsyncSubstrateInterface,
18-
)
1920
from bittensor_cli.src.bittensor.chain_data import (
2021
DelegateInfo,
2122
StakeInfo,
@@ -1654,3 +1655,32 @@ async def get_subnet_prices(
16541655
map_[netuid_] = Balance.from_rao(int(current_price * 1e9))
16551656

16561657
return map_
1658+
1659+
1660+
async def best_connection(networks: list[str]):
1661+
"""
1662+
Basic function to compare the latency of a given list of websocket endpoints
1663+
Args:
1664+
networks: list of network URIs
1665+
1666+
Returns:
1667+
{network_name: [end_to_end_latency, single_request_latency, chain_head_request_latency]}
1668+
1669+
"""
1670+
results = {}
1671+
for network in networks:
1672+
try:
1673+
t1 = time.monotonic()
1674+
async with websockets.connect(network) as websocket:
1675+
pong = await websocket.ping()
1676+
latency = await pong
1677+
pt1 = time.monotonic()
1678+
await websocket.send(
1679+
"{'jsonrpc': '2.0', 'method': 'chain_getHead', 'params': [], 'id': '82'}"
1680+
)
1681+
await websocket.recv()
1682+
t2 = time.monotonic()
1683+
results[network] = [t2 - t1, latency, t2 - pt1]
1684+
except Exception as e:
1685+
err_console.print(f"Error attempting network {network}: {e}")
1686+
return results

0 commit comments

Comments
 (0)