Spaces:
Paused
Paused
Working metagraph dashboard
Browse files- meta_plotting.py +119 -19
- meta_utils.py +33 -5
- metadash.py +152 -45
- multigraph.py +16 -2
meta_plotting.py
CHANGED
@@ -1,31 +1,41 @@
|
|
1 |
import numpy as np
|
|
|
2 |
import plotly.express as px
|
|
|
3 |
|
4 |
-
|
|
|
|
|
5 |
|
6 |
if hotkeys is not None:
|
7 |
df = df.loc[df.hotkey.isin(hotkeys)]
|
8 |
if hotkey_regex is not None:
|
9 |
df = df.loc[df.hotkey.str.contains(hotkey_regex)]
|
10 |
-
|
11 |
-
top_miners = df.groupby('hotkey')[col].agg(agg).sort_values(ascending=False)
|
12 |
|
|
|
|
|
|
|
13 |
stats = df.loc[df.hotkey.isin(top_miners.index[:ntop])].sort_values(by=time_col)
|
|
|
|
|
|
|
14 |
|
15 |
stats['hotkey_abbrev'] = stats.hotkey.str[:abbrev]
|
16 |
stats['coldkey_abbrev'] = stats.coldkey.str[:abbrev]
|
17 |
stats['rank'] = stats.hotkey.map({k:i for i,k in enumerate(top_miners.index, start=1)})
|
|
|
18 |
|
|
|
19 |
return px.line(stats.sort_values(by=[time_col,'rank']),
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
|
27 |
|
28 |
-
def plot_cabals(df, sel_col='coldkey', count_col='hotkey', time_col='timestamp', values=None, ntop=10, abbr=8):
|
29 |
|
30 |
if values is None:
|
31 |
values = df[sel_col].value_counts().sort_values(ascending=False).index[:ntop].tolist()
|
@@ -33,16 +43,106 @@ def plot_cabals(df, sel_col='coldkey', count_col='hotkey', time_col='timestamp',
|
|
33 |
|
34 |
df = df.loc[df[sel_col].isin(values)]
|
35 |
rates = df.groupby([time_col,sel_col])[count_col].nunique().reset_index()
|
|
|
|
|
|
|
|
|
36 |
abbr_col = f'{sel_col} (first {abbr} chars)'
|
37 |
rates[abbr_col] = rates[sel_col].str[:abbr]
|
38 |
return px.line(rates.melt(id_vars=[time_col,sel_col,abbr_col]),
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
width=800, height=600, template='plotly_white',
|
46 |
-
)
|
47 |
|
48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import numpy as np
|
2 |
+
import pandas as pd
|
3 |
import plotly.express as px
|
4 |
+
from plotly.subplots import make_subplots
|
5 |
|
6 |
+
plotly_config = dict(width=800, height=600, template='plotly_white')
|
7 |
+
|
8 |
+
def plot_trace(df, col='emission', agg='mean', time_col='timestamp', ntop=10, hotkeys=None, hotkey_regex=None, abbrev=8, type='Miners', smooth=1, smooth_agg='mean', opacity=0.):
|
9 |
|
10 |
if hotkeys is not None:
|
11 |
df = df.loc[df.hotkey.isin(hotkeys)]
|
12 |
if hotkey_regex is not None:
|
13 |
df = df.loc[df.hotkey.str.contains(hotkey_regex)]
|
|
|
|
|
14 |
|
15 |
+
# select hotkeys with highest average value of col (e.g. emission) over time
|
16 |
+
top_miners = df.groupby('hotkey')[col].agg(agg).sort_values(ascending=False)
|
17 |
+
print(f'Top miners by {col!r}:\n{top_miners}')
|
18 |
stats = df.loc[df.hotkey.isin(top_miners.index[:ntop])].sort_values(by=time_col)
|
19 |
+
|
20 |
+
# smooth values of col (e.g. emission) over time
|
21 |
+
# stats[col] = stats.groupby('hotkey')[col].rolling(smooth).agg(smooth_agg).values
|
22 |
|
23 |
stats['hotkey_abbrev'] = stats.hotkey.str[:abbrev]
|
24 |
stats['coldkey_abbrev'] = stats.coldkey.str[:abbrev]
|
25 |
stats['rank'] = stats.hotkey.map({k:i for i,k in enumerate(top_miners.index, start=1)})
|
26 |
+
print(stats)
|
27 |
|
28 |
+
y_label = col.title().replace('_',' ') + f' ({agg})'
|
29 |
return px.line(stats.sort_values(by=[time_col,'rank']),
|
30 |
+
x=time_col, y=col, color='coldkey_abbrev', line_group='hotkey_abbrev',
|
31 |
+
hover_data=['hotkey','rank'],
|
32 |
+
labels={col:y_label,'timestamp':'','coldkey_abbrev':f'Coldkey (first {abbrev} chars)','hotkey_abbrev':f'Hotkey (first {abbrev} chars)'},
|
33 |
+
title=f'Top {ntop} {type}, by {y_label}',
|
34 |
+
**plotly_config
|
35 |
+
).update_traces(opacity=opacity)
|
36 |
|
37 |
|
38 |
+
def plot_cabals(df, sel_col='coldkey', count_col='hotkey', time_col='timestamp', values=None, ntop=10, abbr=8, smooth=1, smooth_agg='mean', opacity=0.):
|
39 |
|
40 |
if values is None:
|
41 |
values = df[sel_col].value_counts().sort_values(ascending=False).index[:ntop].tolist()
|
|
|
43 |
|
44 |
df = df.loc[df[sel_col].isin(values)]
|
45 |
rates = df.groupby([time_col,sel_col])[count_col].nunique().reset_index()
|
46 |
+
|
47 |
+
# smoothing is hard
|
48 |
+
# rates = rates.groupby(level=1).rolling(smooth, min_periods=1).agg(smooth_agg)
|
49 |
+
|
50 |
abbr_col = f'{sel_col} (first {abbr} chars)'
|
51 |
rates[abbr_col] = rates[sel_col].str[:abbr]
|
52 |
return px.line(rates.melt(id_vars=[time_col,sel_col,abbr_col]),
|
53 |
+
x=time_col, y='value', color=abbr_col,
|
54 |
+
labels={'value':f'Number of Unique {count_col.title()}s per {sel_col.title()}','timestamp':''},
|
55 |
+
category_orders={abbr_col:[ v[:abbr] for v in values]},
|
56 |
+
title=f'Unique {count_col.title()}s Associated with Top {ntop} {sel_col.title()}s',
|
57 |
+
**plotly_config
|
58 |
+
).update_traces(opacity=opacity)
|
|
|
|
|
59 |
|
60 |
+
def plot_churn(df, time_col='timestamp', type='changed', step=1, smooth=1, smooth_agg='mean', opacity=0.5):
|
61 |
+
"""
|
62 |
+
Produces a plotly figure which shows number of changed hotkeys in each step
|
63 |
+
"""
|
64 |
+
|
65 |
+
def churn(s):
|
66 |
+
results = [{'delta':np.nan}]
|
67 |
+
for i, idx in enumerate(s.index[1:]):
|
68 |
+
|
69 |
+
curr = s.loc[idx]
|
70 |
+
prev = s.iloc[i]
|
71 |
+
if type == 'changed':
|
72 |
+
delta = curr.symmetric_difference(prev)
|
73 |
+
elif type == 'added':
|
74 |
+
delta = curr.difference(prev)
|
75 |
+
elif type == 'removed':
|
76 |
+
delta = prev.difference(curr)
|
77 |
+
else:
|
78 |
+
raise ValueError(f'Unknown type {type!r}')
|
79 |
+
|
80 |
+
results.append({'delta': len(delta)})
|
81 |
+
|
82 |
+
return pd.DataFrame(results, index=s.index)
|
83 |
+
|
84 |
+
churn_frame = churn(df.iloc[::step].groupby(['block','timestamp']).hotkey.unique().apply(set))
|
85 |
+
|
86 |
+
return px.line(churn_frame.rolling(smooth, min_periods=1).agg(smooth_agg).reset_index(),
|
87 |
+
x=time_col, y='delta',
|
88 |
+
labels={'delta':f'Number of {type.title()} Hotkeys','timestamp':''},
|
89 |
+
hover_name='block',
|
90 |
+
**plotly_config
|
91 |
+
).update_traces(opacity=opacity)
|
92 |
+
|
93 |
+
|
94 |
+
def plot_occupancy(df, time_col='timestamp',step=1, smooth=1, smooth_agg='mean', opacity=0.5):
|
95 |
+
"""
|
96 |
+
Produces a plotly figure which shows number of unique hotkeys in each step
|
97 |
+
"""
|
98 |
+
occupancy_frame = df.iloc[::step].assign(
|
99 |
+
Type=df.iloc[::step].validator_trust.apply(lambda x: 'Miner' if x==0 else 'Validator')
|
100 |
+
).groupby(['Type','timestamp','block']).hotkey.nunique()
|
101 |
+
|
102 |
+
# make two plots, with a secondary y axis
|
103 |
+
fig = make_subplots(specs=[[{"secondary_y": True}]])
|
104 |
+
trace1 = px.line(occupancy_frame.loc['Miner'].rolling(smooth, min_periods=1).agg(smooth_agg).reset_index(),
|
105 |
+
x='timestamp',y='hotkey', hover_name='block')
|
106 |
+
trace2 = px.line(occupancy_frame.loc['Validator'].rolling(smooth, min_periods=1).agg(smooth_agg).reset_index(),
|
107 |
+
x='timestamp', y='hotkey', hover_name='block')
|
108 |
+
|
109 |
+
fig.add_trace(trace1.data[0])
|
110 |
+
fig.add_trace(trace2.update_traces(line_color='red').data[0], secondary_y=True, row=1,col=1)
|
111 |
+
|
112 |
+
fig.update_yaxes(title_text='Miner Hotkeys', secondary_y=False) # Customize primary y-axis title
|
113 |
+
fig.update_yaxes(title_text='Validator Hotkeys', secondary_y=True, tickfont=dict(color='red'), title=dict(font_color='red')) # Customize secondary y-axis title
|
114 |
+
|
115 |
+
return fig.update_layout( **plotly_config).update_traces(opacity=opacity)
|
116 |
+
|
117 |
+
def plot_animation(df, x='emission_sum', y='total_stake_sum', color='emission_mean', size='hotkey_nunique', step=10, opacity=0.5):
|
118 |
+
|
119 |
+
agg_dict = {}
|
120 |
+
for column_name in [x, y, color, size]:
|
121 |
+
column, agg_name = column_name.rsplit('_', 1)
|
122 |
+
|
123 |
+
if column not in agg_dict:
|
124 |
+
agg_dict[column] = [agg_name]
|
125 |
+
else:
|
126 |
+
agg_dict[column].append(agg_name)
|
127 |
+
|
128 |
+
# select every nth block
|
129 |
+
if step>1:
|
130 |
+
blocks_subset = df.block.unique()[::step]
|
131 |
+
df = df.loc[df.block.isin(blocks_subset)]
|
132 |
+
|
133 |
+
df_agg = df.groupby(['block','timestamp','coldkey']).agg({'hotkey':'nunique', 'ip':'nunique', **agg_dict})
|
134 |
+
df_agg.columns = ['_'.join(col).strip() for col in df_agg.columns]
|
135 |
+
print(df_agg.columns)
|
136 |
+
|
137 |
+
return px.scatter(df_agg.reset_index(),
|
138 |
+
x=x, range_x=[-df_agg[x].max()*0.1, df_agg[x].max()*1.1],
|
139 |
+
y=y, range_y=[-df_agg[y].max()*0.1, df_agg[y].max()*1.1],
|
140 |
+
size=size,
|
141 |
+
opacity=opacity,
|
142 |
+
color=color,
|
143 |
+
color_continuous_scale='BlueRed',
|
144 |
+
animation_frame='block',
|
145 |
+
animation_group='coldkey',
|
146 |
+
hover_name='coldkey',
|
147 |
+
**plotly_config
|
148 |
+
)
|
meta_utils.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1 |
import os
|
|
|
2 |
import glob
|
3 |
import tqdm
|
4 |
import dill as pickle
|
@@ -11,10 +12,37 @@ block_time_500k = datetime.datetime(2023, 5, 29, 5, 29, 0)
|
|
11 |
block_time_800k = datetime.datetime(2023, 7, 9, 21, 32, 48)
|
12 |
dt = (pd.Timestamp(block_time_800k)-pd.Timestamp(block_time_500k))/(800_000-500_000)
|
13 |
|
14 |
-
def run_subprocess(*args):
|
15 |
-
|
16 |
-
|
17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
18 |
|
19 |
def load_metagraph(path, extra_cols=None, rm_cols=None):
|
20 |
|
@@ -24,7 +52,7 @@ def load_metagraph(path, extra_cols=None, rm_cols=None):
|
|
24 |
df = pd.DataFrame(metagraph.axons)
|
25 |
df['block'] = metagraph.block.item()
|
26 |
df['timestamp'] = block_time_500k + dt*(df['block']-500_000)
|
27 |
-
df['difficulty'] = metagraph
|
28 |
for c in extra_cols:
|
29 |
vals = getattr(metagraph,c)
|
30 |
df[c] = vals
|
|
|
1 |
import os
|
2 |
+
import re
|
3 |
import glob
|
4 |
import tqdm
|
5 |
import dill as pickle
|
|
|
12 |
block_time_800k = datetime.datetime(2023, 7, 9, 21, 32, 48)
|
13 |
dt = (pd.Timestamp(block_time_800k)-pd.Timestamp(block_time_500k))/(800_000-500_000)
|
14 |
|
15 |
+
def run_subprocess(command='python multigraph.py', *args):
|
16 |
+
try:
|
17 |
+
# Run the subprocess with stdout and stderr pipes connected
|
18 |
+
command = command + " ".join(args)
|
19 |
+
print(f'{"===="*20}\nRunning: {command!r}')
|
20 |
+
process = subprocess.Popen(
|
21 |
+
command,
|
22 |
+
stdout=subprocess.PIPE,
|
23 |
+
stderr=subprocess.STDOUT,
|
24 |
+
universal_newlines=True, # Set to True for text mode
|
25 |
+
bufsize=1, # Line buffered, so output is available line by line
|
26 |
+
shell=True # Set to True to allow running shell commands (use with caution)
|
27 |
+
)
|
28 |
+
|
29 |
+
print(f'Subprocess started with pid {process.pid} and streaming output from stdout:')
|
30 |
+
with process.stdout as output:
|
31 |
+
for line in output:
|
32 |
+
print(line, end='', flush=True) # Print without adding an extra newline
|
33 |
+
if match := re.search('(?P<done>\\d+)/(?P<total>\\d+)',line):
|
34 |
+
print('---> match.groupdict():', match.groupdict())
|
35 |
+
# try yielding the line here
|
36 |
+
|
37 |
+
# Wait for the subprocess to finish and get the return code
|
38 |
+
process.wait()
|
39 |
+
|
40 |
+
print("===="*20)
|
41 |
+
return process.returncode
|
42 |
+
|
43 |
+
except subprocess.CalledProcessError as e:
|
44 |
+
# If the subprocess returns a non-zero exit code, this exception will be raised
|
45 |
+
return e.returncode
|
46 |
|
47 |
def load_metagraph(path, extra_cols=None, rm_cols=None):
|
48 |
|
|
|
52 |
df = pd.DataFrame(metagraph.axons)
|
53 |
df['block'] = metagraph.block.item()
|
54 |
df['timestamp'] = block_time_500k + dt*(df['block']-500_000)
|
55 |
+
df['difficulty'] = getattr(metagraph, 'difficulty', None)
|
56 |
for c in extra_cols:
|
57 |
vals = getattr(metagraph,c)
|
58 |
df[c] = vals
|
metadash.py
CHANGED
@@ -1,9 +1,10 @@
|
|
1 |
import os
|
|
|
2 |
import pandas as pd
|
3 |
import streamlit as st
|
4 |
from meta_utils import run_subprocess, load_metagraphs
|
5 |
# from opendashboards.assets import io, inspect, metric, plot
|
6 |
-
|
7 |
import asyncio
|
8 |
from functools import lru_cache
|
9 |
|
@@ -22,8 +23,9 @@ loop = asyncio.new_event_loop()
|
|
22 |
asyncio.set_event_loop(loop)
|
23 |
import bittensor
|
24 |
|
25 |
-
|
26 |
-
|
|
|
27 |
DEFAULT_SRC = 'miner'
|
28 |
DEFAULT_BLOCK_START = blockfiles[0]
|
29 |
DEFAULT_BLOCK_END = blockfiles[-1]
|
@@ -48,27 +50,29 @@ st.title('Metagraph :red[Analysis] Dashboard :eyes:')
|
|
48 |
st.markdown('#')
|
49 |
st.markdown('#')
|
50 |
|
51 |
-
netuid = 1
|
52 |
subtensor = bittensor.subtensor(network='finney')
|
53 |
current_block = subtensor.get_current_block()
|
54 |
|
55 |
-
@
|
56 |
-
def _metagraph(block):
|
57 |
|
58 |
-
print('rerunning cache')
|
59 |
return (
|
60 |
-
subtensor.metagraph(
|
61 |
-
subtensor.metagraph(
|
62 |
-
subtensor.burn(netuid=
|
63 |
-
subtensor.burn(netuid=
|
64 |
)
|
65 |
-
|
66 |
-
current_metagraph, yesterday_metagraph, current_burn, yesterday_burn = _metagraph(10*current_block//10)
|
67 |
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
|
|
|
|
|
|
|
|
|
|
72 |
|
73 |
mcol1, mcol2, mcol3, mcol4 = st.columns(4)
|
74 |
mcol1.metric('Block', current_block, delta='+7200 [24hr]')
|
@@ -77,30 +81,64 @@ mcol3.metric('Validators', current_vcount, delta=current_vcount-yesterday_vcount
|
|
77 |
mcol4.metric('Miners', current_mcount, delta=current_mcount-yesterday_mcount)
|
78 |
|
79 |
|
80 |
-
st.markdown('#')
|
81 |
st.markdown('#')
|
82 |
|
83 |
-
bcol1, bcol2, bcol3 = st.columns([0.6, 0.1, 0.2])
|
84 |
-
block_start, block_end = bcol1.select_slider(
|
85 |
-
'Select a **block range**',
|
86 |
-
options=blockfiles,
|
87 |
-
value=(DEFAULT_BLOCK_START, DEFAULT_BLOCK_END),
|
88 |
-
format_func=lambda x: f'{x:,}'
|
89 |
-
)
|
90 |
|
91 |
-
with
|
|
|
92 |
st.markdown('#')
|
93 |
-
|
|
|
94 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
95 |
|
96 |
with st.spinner(text=f'Loading data...'):
|
97 |
# df = load_metagraphs(block_start=block_start, block_end=block_end, block_step=DEFAULT_BLOCK_STEP)
|
98 |
-
df = pd.read_parquet('
|
99 |
|
100 |
blocks = df.block.unique()
|
101 |
|
102 |
-
df_sel = df.loc[df.block.between(block_start, block_end)]
|
103 |
-
|
|
|
104 |
|
105 |
# add vertical space
|
106 |
st.markdown('#')
|
@@ -108,33 +146,102 @@ st.markdown('#')
|
|
108 |
|
109 |
tab1, tab2, tab3, tab4 = st.tabs(["Overview", "Miners", "Validators", "Block"])
|
110 |
|
111 |
-
|
|
|
112 |
cabal_choices = ['hotkey','ip','coldkey']
|
|
|
113 |
|
114 |
### Overview ###
|
115 |
with tab1:
|
116 |
|
117 |
-
|
|
|
|
|
|
|
118 |
|
119 |
-
|
120 |
-
|
121 |
-
#horizontal list
|
122 |
-
miner_choice = acol2.radio('Select:', miner_choices, horizontal=True, index=0)
|
123 |
st.plotly_chart(
|
124 |
-
|
125 |
use_container_width=True
|
126 |
)
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
st.plotly_chart(
|
133 |
-
|
134 |
use_container_width=True
|
135 |
)
|
136 |
|
|
|
|
|
|
|
|
|
137 |
with tab2:
|
138 |
|
139 |
-
#
|
140 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import os
|
2 |
+
import time
|
3 |
import pandas as pd
|
4 |
import streamlit as st
|
5 |
from meta_utils import run_subprocess, load_metagraphs
|
6 |
# from opendashboards.assets import io, inspect, metric, plot
|
7 |
+
import meta_plotting as plotting
|
8 |
import asyncio
|
9 |
from functools import lru_cache
|
10 |
|
|
|
23 |
asyncio.set_event_loop(loop)
|
24 |
import bittensor
|
25 |
|
26 |
+
netuid = 1
|
27 |
+
datadir=f'data/metagraph/{netuid}/'
|
28 |
+
blockfiles = sorted(int(filename.split('.')[0]) for filename in os.listdir(datadir) if filename.split('.')[0].isdigit())
|
29 |
DEFAULT_SRC = 'miner'
|
30 |
DEFAULT_BLOCK_START = blockfiles[0]
|
31 |
DEFAULT_BLOCK_END = blockfiles[-1]
|
|
|
50 |
st.markdown('#')
|
51 |
st.markdown('#')
|
52 |
|
|
|
53 |
subtensor = bittensor.subtensor(network='finney')
|
54 |
current_block = subtensor.get_current_block()
|
55 |
|
56 |
+
@st.cache_data
|
57 |
+
def _metagraph(block, subnet):
|
58 |
|
59 |
+
print(f'rerunning cache with block {block}')
|
60 |
return (
|
61 |
+
subtensor.metagraph(subnet, block=block),
|
62 |
+
subtensor.metagraph(subnet, block=block - 7200),
|
63 |
+
subtensor.burn(netuid=subnet, block=block),
|
64 |
+
subtensor.burn(netuid=subnet, block=block - 7200),
|
65 |
)
|
|
|
|
|
66 |
|
67 |
+
current_metagraph, yesterday_metagraph, current_burn, yesterday_burn = _metagraph(10*(current_block//10), netuid)
|
68 |
+
current_validators = current_metagraph.validator_permit[current_metagraph.validator_trust > 0.0]
|
69 |
+
yesterday_validators = yesterday_metagraph.validator_permit[yesterday_metagraph.validator_trust > 0.0]
|
70 |
+
current_vcount = current_validators.sum().item()
|
71 |
+
current_mcount = (current_metagraph.trust > 0.0).sum().item()
|
72 |
+
yesterday_vcount = yesterday_validators.sum().item()
|
73 |
+
yesterday_mcount = (yesterday_metagraph.trust > 0.0).sum().item()
|
74 |
+
|
75 |
+
st.markdown('#')
|
76 |
|
77 |
mcol1, mcol2, mcol3, mcol4 = st.columns(4)
|
78 |
mcol1.metric('Block', current_block, delta='+7200 [24hr]')
|
|
|
81 |
mcol4.metric('Miners', current_mcount, delta=current_mcount-yesterday_mcount)
|
82 |
|
83 |
|
|
|
84 |
st.markdown('#')
|
85 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
86 |
|
87 |
+
with st.sidebar:
|
88 |
+
st.title('Options')
|
89 |
st.markdown('#')
|
90 |
+
|
91 |
+
netuid = st.selectbox('Netuid', [1,11], index=0)
|
92 |
|
93 |
+
st.markdown('#')
|
94 |
+
|
95 |
+
c1, c2 = st.columns([0.7,0.3])
|
96 |
+
staleness = current_block - blockfiles[-1]
|
97 |
+
msg = c1.warning(f'Out of date ({staleness})') if staleness >= 100 else c1.info('Up to date')
|
98 |
+
if c2.button('Update', type='primary'):
|
99 |
+
msg.info('Downloading')
|
100 |
+
return_code = run_subprocess()
|
101 |
+
if return_code == 0:
|
102 |
+
msg.success('Up to date')
|
103 |
+
time.sleep(1)
|
104 |
+
msg.empty()
|
105 |
+
else:
|
106 |
+
msg.error('Error')
|
107 |
+
|
108 |
+
st.markdown('#')
|
109 |
+
|
110 |
+
block_start, block_end = st.select_slider(
|
111 |
+
'Select a **block range**',
|
112 |
+
options=blockfiles,
|
113 |
+
value=(DEFAULT_BLOCK_START, DEFAULT_BLOCK_END),
|
114 |
+
format_func=lambda x: f'{x:,}'
|
115 |
+
)
|
116 |
+
|
117 |
+
st.markdown('#')
|
118 |
+
st.markdown('#')
|
119 |
+
# horizontal line
|
120 |
+
st.markdown('<hr>', unsafe_allow_html=True)
|
121 |
+
|
122 |
+
r1c1, r1c2 = st.columns(2)
|
123 |
+
x = r1c1.selectbox('**Time axis**', ['block','timestamp'], index=0)
|
124 |
+
color = r1c2.selectbox('**Color**', ['coldkey','hotkey','ip'], index=0)
|
125 |
+
r2c1, r2c2 = st.columns(2)
|
126 |
+
ntop = r2c1.slider('**Sample top**', min_value=1, max_value=50, value=10, key='sel_ntop')
|
127 |
+
opacity = r2c2.slider('**Opacity**', min_value=0., max_value=1., value=0.5, key='opacity')
|
128 |
+
r3c1, r3c2 = st.columns(2)
|
129 |
+
smooth = r3c1.slider('Smoothing', min_value=1, max_value=100, value=1, key='sel_churn_smooth')
|
130 |
+
smooth_agg = r3c2.radio('Smooth Aggregation', ['mean','std','max','min','sum'], horizontal=True, index=0)
|
131 |
+
|
132 |
|
133 |
with st.spinner(text=f'Loading data...'):
|
134 |
# df = load_metagraphs(block_start=block_start, block_end=block_end, block_step=DEFAULT_BLOCK_STEP)
|
135 |
+
df = pd.read_parquet(os.path.join(datadir,'df.parquet'))
|
136 |
|
137 |
blocks = df.block.unique()
|
138 |
|
139 |
+
df_sel = df.loc[df.block.between(block_start, block_end)].sort_values(by='block')
|
140 |
+
miners = df_sel.loc[df_sel.validator_trust == 0]
|
141 |
+
validators = df_sel.loc[df_sel.validator_trust > 0]
|
142 |
|
143 |
# add vertical space
|
144 |
st.markdown('#')
|
|
|
146 |
|
147 |
tab1, tab2, tab3, tab4 = st.tabs(["Overview", "Miners", "Validators", "Block"])
|
148 |
|
149 |
+
validator_choices = ['total_stake','incentive','emission','consensus','validator_trust','dividends']
|
150 |
+
miner_choices = ['total_stake','incentive','emission','consensus','trust']
|
151 |
cabal_choices = ['hotkey','ip','coldkey']
|
152 |
+
cabal_choices.remove(color)
|
153 |
|
154 |
### Overview ###
|
155 |
with tab1:
|
156 |
|
157 |
+
st.markdown('#')
|
158 |
+
st.markdown('#')
|
159 |
+
st.subheader('Hotkey Churn')
|
160 |
+
st.info('**Churn** *measures the change in network participants over time*')
|
161 |
|
162 |
+
churn_choice = st.radio('Hotkey event', ['changed','added','removed'], horizontal=True, index=0)
|
163 |
+
|
|
|
|
|
164 |
st.plotly_chart(
|
165 |
+
plotting.plot_churn(df_sel, time_col=x, type=churn_choice, smooth=smooth, smooth_agg=smooth_agg, opacity=opacity),
|
166 |
use_container_width=True
|
167 |
)
|
168 |
+
|
169 |
+
st.markdown('#')
|
170 |
+
st.markdown('#')
|
171 |
+
st.subheader('Network Occupancy')
|
172 |
+
st.info('**Occupancy** *measures the number of network participants at a given time*')
|
173 |
st.plotly_chart(
|
174 |
+
plotting.plot_occupancy(df_sel, time_col=x, smooth=smooth, smooth_agg=smooth_agg, opacity=opacity),
|
175 |
use_container_width=True
|
176 |
)
|
177 |
|
178 |
+
animation_aggs = ['mean','sum','std','max','min']
|
179 |
+
mac_choices = [f'{col}_{agg}' for col in miner_choices for agg in animation_aggs]+['hotkey_nunique','ip_nunique']
|
180 |
+
vac_choices = [f'{col}_{agg}' for col in validator_choices for agg in animation_aggs]+['hotkey_nunique','ip_nunique']
|
181 |
+
|
182 |
with tab2:
|
183 |
|
184 |
+
st.markdown('#')
|
185 |
+
st.markdown('#')
|
186 |
+
st.subheader('Miner Activity')
|
187 |
+
st.info('**Activity** *shows the change in stake and emission over time for **miners**, grouped by coldkey*')
|
188 |
+
|
189 |
+
mac1, mac2, mac3, mac4 = st.columns(4)
|
190 |
+
mac_x = mac1.selectbox('**x**', mac_choices, index=mac_choices.index('emission_mean'))
|
191 |
+
mac_y = mac2.selectbox('**y**', mac_choices, index=mac_choices.index('trust_mean'))
|
192 |
+
mac_size = mac3.selectbox('**marker size**', mac_choices, index=mac_choices.index('hotkey_nunique'))
|
193 |
+
mac_color = mac4.selectbox('**marker color**', mac_choices, index=mac_choices.index('total_stake_sum'))
|
194 |
+
|
195 |
+
st.plotly_chart(
|
196 |
+
plotting.plot_animation(miners, x=mac_x, y=mac_y, color=mac_color, size=mac_size, opacity=opacity),
|
197 |
+
use_container_width=True
|
198 |
+
)
|
199 |
+
|
200 |
+
miner_choice = st.radio('Select:', miner_choices, horizontal=True, index=0)
|
201 |
+
with st.expander(f'Show **{miner_choice}** trends for top **{ntop}** miners'):
|
202 |
+
|
203 |
+
st.plotly_chart(
|
204 |
+
plotting.plot_trace(miners, time_col=x, col=miner_choice, ntop=ntop, smooth=smooth, smooth_agg=smooth_agg, opacity=opacity),
|
205 |
+
use_container_width=True
|
206 |
+
)
|
207 |
+
|
208 |
+
count_col = st.radio('Count', cabal_choices, index=0, horizontal=True, key='sel_miner_count')
|
209 |
+
with st.expander(f'Show **{count_col}** trends for top **{ntop}** miners'):
|
210 |
+
|
211 |
+
st.plotly_chart(
|
212 |
+
plotting.plot_cabals(miners, time_col=x, count_col=count_col, sel_col=color, ntop=ntop, smooth=smooth, smooth_agg=smooth_agg, opacity=opacity),
|
213 |
+
use_container_width=True
|
214 |
+
)
|
215 |
+
|
216 |
+
with tab3:
|
217 |
+
|
218 |
+
st.markdown('#')
|
219 |
+
st.markdown('#')
|
220 |
+
st.subheader('Validator Activity')
|
221 |
+
st.info('**Activity** *shows the change in stake and emission over time for **validators**, grouped by coldkey*')
|
222 |
+
|
223 |
+
vac1, vac2, vac3, vac4 = st.columns(4)
|
224 |
+
vac_x = vac1.selectbox('**x**', vac_choices, index=vac_choices.index('incentive_mean'))
|
225 |
+
vac_y = vac2.selectbox('**y**', vac_choices, index=vac_choices.index('validator_trust_mean'))
|
226 |
+
vac_size = vac3.selectbox('**marker size**', vac_choices, index=vac_choices.index('hotkey_nunique'))
|
227 |
+
vac_color = vac4.selectbox('**marker color**', vac_choices, index=vac_choices.index('total_stake_sum'))
|
228 |
+
|
229 |
+
st.plotly_chart(
|
230 |
+
plotting.plot_animation(validators, x=vac_x, y=vac_y, color=vac_color, size=vac_size, opacity=opacity),
|
231 |
+
use_container_width=True
|
232 |
+
)
|
233 |
+
validator_choice = st.radio('Select:', validator_choices, horizontal=True, index=0)
|
234 |
+
with st.expander(f'Show **{validator_choice}** trends for top **{ntop}** validators'):
|
235 |
+
|
236 |
+
st.plotly_chart(
|
237 |
+
plotting.plot_trace(validators, time_col=x,col=validator_choice, ntop=ntop, smooth=smooth, smooth_agg=smooth_agg, opacity=opacity),
|
238 |
+
use_container_width=True
|
239 |
+
)
|
240 |
+
|
241 |
+
count_col = st.radio('Count', cabal_choices, index=0, horizontal=True, key='sel_validator_count')
|
242 |
+
with st.expander(f'Show **{count_col}** trends for top **{ntop}** validators'):
|
243 |
+
|
244 |
+
st.plotly_chart(
|
245 |
+
plotting.plot_cabals(validators, time_col=x, count_col=count_col, sel_col=color, ntop=ntop, smooth=smooth, smooth_agg=smooth_agg, opacity=opacity),
|
246 |
+
use_container_width=True
|
247 |
+
)
|
multigraph.py
CHANGED
@@ -115,5 +115,19 @@ if __name__ == '__main__':
|
|
115 |
|
116 |
|
117 |
if not args.no_dataframe:
|
118 |
-
|
119 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
115 |
|
116 |
|
117 |
if not args.no_dataframe:
|
118 |
+
save_path = f'data/metagraph/{netuid}/df.parquet'
|
119 |
+
blocks = range(start_block, end_block, step_size)
|
120 |
+
df_loaded = None
|
121 |
+
if os.path.exists(save_path):
|
122 |
+
df_loaded = pd.read_parquet(save_path)
|
123 |
+
blocks = [block for block in blocks if block not in df_loaded.block.unique()]
|
124 |
+
print(f'Loaded dataframe from {save_path!r}. {len(df_loaded)} rows. {len(blocks)} blocks to process.')
|
125 |
+
if len(blocks)==0:
|
126 |
+
print('No blocks to process.')
|
127 |
+
sys.exit(0)
|
128 |
+
|
129 |
+
df = load_metagraphs(blocks[0], blocks[-1], block_step=step_size, datadir=datadir)
|
130 |
+
if df_loaded is not None:
|
131 |
+
df = pd.concat([df, df_loaded], ignore_index=True)
|
132 |
+
df.to_parquet(save_path)
|
133 |
+
print(f'Saved dataframe to {save_path!r}')
|