mirror of
https://github.com/aljazceru/plugins.git
synced 2025-12-24 00:24:19 +01:00
summary: moves availability code and adds testcases
extracts the availability calculations to own testable module
This commit is contained in:
committed by
Christian Decker
parent
01b075117f
commit
5a6489c3ae
@@ -2,7 +2,7 @@
|
|||||||
from pyln.client import Plugin, Millisatoshi
|
from pyln.client import Plugin, Millisatoshi
|
||||||
from packaging import version
|
from packaging import version
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from datetime import datetime
|
from summary_avail import *
|
||||||
import pyln.client
|
import pyln.client
|
||||||
from math import floor, log10
|
from math import floor, log10
|
||||||
import requests
|
import requests
|
||||||
@@ -30,20 +30,6 @@ summary_description = "Gets summary information about this node.\n"\
|
|||||||
"Pass a list of scids to the {exclude} parameter"\
|
"Pass a list of scids to the {exclude} parameter"\
|
||||||
" to exclude some channels from the outputs."
|
" to exclude some channels from the outputs."
|
||||||
|
|
||||||
# Global state to measure online% and last_seen
|
|
||||||
peerstate = {}
|
|
||||||
|
|
||||||
|
|
||||||
# ensure an rpc peer is added
|
|
||||||
def addpeer(p):
|
|
||||||
pid = p['id']
|
|
||||||
if not pid in peerstate:
|
|
||||||
peerstate[pid] = {
|
|
||||||
'connected' : p['connected'],
|
|
||||||
'last_seen' : datetime.now() if p['connected'] else None,
|
|
||||||
'availability' : 1.0 if p['connected'] else 0.0
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class PeerThread(threading.Thread):
|
class PeerThread(threading.Thread):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -51,36 +37,16 @@ class PeerThread(threading.Thread):
|
|||||||
self.daemon = True
|
self.daemon = True
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
interval = 5 * 60 # collect peer state once in a while
|
|
||||||
window = 3 * 24 * 60 * 60 # 72hr availability window
|
|
||||||
count = 0
|
|
||||||
|
|
||||||
# delay initial execution, so peers have a chance to connect on startup
|
# delay initial execution, so peers have a chance to connect on startup
|
||||||
time.sleep(interval)
|
time.sleep(plugin.avail_interval)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
count += 1
|
|
||||||
leadwin = max(min(window, count * interval), interval)
|
|
||||||
samples = leadwin / interval
|
|
||||||
alpha = 1.0 / samples
|
|
||||||
beta = 1.0 - alpha
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
peers = plugin.rpc.listpeers()
|
rpcpeers = plugin.rpc.listpeers()
|
||||||
for p in peers['peers']:
|
trace_availability(plugin, rpcpeers)
|
||||||
pid = p['id']
|
time.sleep(plugin.avail_interval)
|
||||||
addpeer(p)
|
|
||||||
|
|
||||||
if p['connected']:
|
|
||||||
peerstate[pid]['last_seen'] = datetime.now()
|
|
||||||
peerstate[pid]['connected'] = True
|
|
||||||
peerstate[pid]['availability'] = 1.0 * alpha + peerstate[pid]['availability'] * beta
|
|
||||||
else:
|
|
||||||
peerstate[pid]['connected'] = False
|
|
||||||
peerstate[pid]['availability'] = 0.0 * alpha + peerstate[pid]['availability'] * beta
|
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
plugin.log("[PeerThread] " + str(ex), 'warn')
|
plugin.log("[PeerThread] " + str(ex), 'warn')
|
||||||
time.sleep(interval)
|
|
||||||
|
|
||||||
|
|
||||||
class PriceThread(threading.Thread):
|
class PriceThread(threading.Thread):
|
||||||
@@ -177,7 +143,7 @@ def summary(plugin, exclude=''):
|
|||||||
reply['num_gossipers'] = 0
|
reply['num_gossipers'] = 0
|
||||||
for p in peers['peers']:
|
for p in peers['peers']:
|
||||||
pid = p['id']
|
pid = p['id']
|
||||||
addpeer(p)
|
addpeer(plugin, p)
|
||||||
active_channel = False
|
active_channel = False
|
||||||
for c in p['channels']:
|
for c in p['channels']:
|
||||||
if c['state'] != 'CHANNELD_NORMAL':
|
if c['state'] != 'CHANNELD_NORMAL':
|
||||||
@@ -208,7 +174,7 @@ def summary(plugin, exclude=''):
|
|||||||
c['private'],
|
c['private'],
|
||||||
p['connected'],
|
p['connected'],
|
||||||
c['short_channel_id'],
|
c['short_channel_id'],
|
||||||
peerstate[pid]['availability']
|
plugin.avail_peerstate[pid]['avail']
|
||||||
))
|
))
|
||||||
|
|
||||||
if not active_channel and p['connected']:
|
if not active_channel and p['connected']:
|
||||||
@@ -288,6 +254,12 @@ def init(options, configuration, plugin):
|
|||||||
plugin.currency = options['summary-currency']
|
plugin.currency = options['summary-currency']
|
||||||
plugin.currency_prefix = options['summary-currency-prefix']
|
plugin.currency_prefix = options['summary-currency-prefix']
|
||||||
plugin.fiat_per_btc = 0
|
plugin.fiat_per_btc = 0
|
||||||
|
|
||||||
|
plugin.avail_peerstate = {}
|
||||||
|
plugin.avail_count = 0
|
||||||
|
plugin.avail_interval = float(options['summary-availability-interval'])
|
||||||
|
plugin.avail_window = 60 * 60 * int(options['summary-availability-window'])
|
||||||
|
|
||||||
info = plugin.rpc.getinfo()
|
info = plugin.rpc.getinfo()
|
||||||
|
|
||||||
# Measure availability
|
# Measure availability
|
||||||
@@ -323,4 +295,14 @@ plugin.add_option(
|
|||||||
'USD $',
|
'USD $',
|
||||||
'What prefix to use for currency'
|
'What prefix to use for currency'
|
||||||
)
|
)
|
||||||
|
plugin.add_option(
|
||||||
|
'summary-availability-interval',
|
||||||
|
300,
|
||||||
|
'How often in seconds the availability should be calculated.'
|
||||||
|
)
|
||||||
|
plugin.add_option(
|
||||||
|
'summary-availability-window',
|
||||||
|
72,
|
||||||
|
'How many hours the availability should be averaged over.'
|
||||||
|
)
|
||||||
plugin.run()
|
plugin.run()
|
||||||
|
|||||||
32
summary/summary_avail.py
Normal file
32
summary/summary_avail.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# ensure an rpc peer is added
|
||||||
|
def addpeer(p, rpcpeer):
|
||||||
|
pid = rpcpeer['id']
|
||||||
|
if not pid in p.avail_peerstate:
|
||||||
|
p.avail_peerstate[pid] = {
|
||||||
|
'connected' : rpcpeer['connected'],
|
||||||
|
'last_seen' : datetime.now() if rpcpeer['connected'] else None,
|
||||||
|
'avail' : 1.0 if rpcpeer['connected'] else 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# exponetially smooth online/offline states of peers
|
||||||
|
def trace_availability(p, rpcpeers):
|
||||||
|
p.avail_count += 1
|
||||||
|
leadwin = max(min(p.avail_window, p.avail_count * p.avail_interval), p.avail_interval)
|
||||||
|
samples = leadwin / p.avail_interval
|
||||||
|
alpha = 1.0 / samples
|
||||||
|
beta = 1.0 - alpha
|
||||||
|
|
||||||
|
for rpcpeer in rpcpeers['peers']:
|
||||||
|
pid = rpcpeer['id']
|
||||||
|
addpeer(p, rpcpeer)
|
||||||
|
|
||||||
|
if rpcpeer['connected']:
|
||||||
|
p.avail_peerstate[pid]['last_seen'] = datetime.now()
|
||||||
|
p.avail_peerstate[pid]['connected'] = True
|
||||||
|
p.avail_peerstate[pid]['avail'] = 1.0 * alpha + p.avail_peerstate[pid]['avail'] * beta
|
||||||
|
else:
|
||||||
|
p.avail_peerstate[pid]['connected'] = False
|
||||||
|
p.avail_peerstate[pid]['avail'] = 0.0 * alpha + p.avail_peerstate[pid]['avail'] * beta
|
||||||
@@ -1,12 +1,151 @@
|
|||||||
import subprocess
|
import subprocess
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
from pyln.client import Plugin
|
||||||
from pyln.testing.fixtures import * # noqa: F401,F403
|
from pyln.testing.fixtures import * # noqa: F401,F403
|
||||||
from pyln.testing.utils import DEVELOPER
|
from pyln.testing.utils import DEVELOPER
|
||||||
|
|
||||||
|
from summary_avail import *
|
||||||
|
|
||||||
pluginopt = {'plugin': os.path.join(os.path.dirname(__file__), "summary.py")}
|
pluginopt = {'plugin': os.path.join(os.path.dirname(__file__), "summary.py")}
|
||||||
|
|
||||||
|
|
||||||
|
# returns a test plugin stub
|
||||||
|
def get_stub():
|
||||||
|
plugin = Plugin()
|
||||||
|
plugin.avail_peerstate = {}
|
||||||
|
plugin.avail_count = 0
|
||||||
|
plugin.avail_interval = 60
|
||||||
|
plugin.avail_window = 3600
|
||||||
|
return plugin
|
||||||
|
|
||||||
|
|
||||||
|
# tests the 72hr exponential availibility tracing
|
||||||
|
# tests base algo and peerstate tracing
|
||||||
|
def test_summary_avail_101():
|
||||||
|
# given
|
||||||
|
plugin = get_stub()
|
||||||
|
rpcpeers = {
|
||||||
|
'peers' : [
|
||||||
|
{ 'id' : '1', 'connected' : True },
|
||||||
|
{ 'id' : '2', 'connected' : False },
|
||||||
|
{ 'id' : '3', 'connected' : True },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
# when
|
||||||
|
for i in range(100):
|
||||||
|
trace_availability(plugin, rpcpeers)
|
||||||
|
|
||||||
|
# then
|
||||||
|
assert(plugin.avail_peerstate['1']['avail'] == 1.0)
|
||||||
|
assert(plugin.avail_peerstate['2']['avail'] == 0.0)
|
||||||
|
assert(plugin.avail_peerstate['3']['avail'] == 1.0)
|
||||||
|
assert(plugin.avail_peerstate['1']['connected'] == True)
|
||||||
|
assert(plugin.avail_peerstate['2']['connected'] == False)
|
||||||
|
assert(plugin.avail_peerstate['3']['connected'] == True)
|
||||||
|
|
||||||
|
|
||||||
|
# tests for 50% downtime
|
||||||
|
def test_summary_avail_50():
|
||||||
|
# given
|
||||||
|
plugin = get_stub()
|
||||||
|
rpcpeers_on = {
|
||||||
|
'peers' : [
|
||||||
|
{ 'id' : '1', 'connected' : True },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
rpcpeers_off = {
|
||||||
|
'peers' : [
|
||||||
|
{ 'id' : '1', 'connected' : False },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
# when
|
||||||
|
for i in range(30):
|
||||||
|
trace_availability(plugin, rpcpeers_on)
|
||||||
|
for i in range(30):
|
||||||
|
trace_availability(plugin, rpcpeers_off)
|
||||||
|
|
||||||
|
# then
|
||||||
|
assert(round(plugin.avail_peerstate['1']['avail'], 3) == 0.5)
|
||||||
|
|
||||||
|
|
||||||
|
# tests for 2/3 downtime
|
||||||
|
def test_summary_avail_33():
|
||||||
|
# given
|
||||||
|
plugin = get_stub()
|
||||||
|
rpcpeers_on = {
|
||||||
|
'peers' : [
|
||||||
|
{ 'id' : '1', 'connected' : True },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
rpcpeers_off = {
|
||||||
|
'peers' : [
|
||||||
|
{ 'id' : '1', 'connected' : False },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
# when
|
||||||
|
for i in range(20):
|
||||||
|
trace_availability(plugin, rpcpeers_on)
|
||||||
|
for i in range(40):
|
||||||
|
trace_availability(plugin, rpcpeers_off)
|
||||||
|
|
||||||
|
# then
|
||||||
|
assert(round(plugin.avail_peerstate['1']['avail'], 3) == 0.333)
|
||||||
|
|
||||||
|
|
||||||
|
# tests for 1/3 downtime
|
||||||
|
def test_summary_avail_66():
|
||||||
|
# given
|
||||||
|
plugin = get_stub()
|
||||||
|
rpcpeers_on = {
|
||||||
|
'peers' : [
|
||||||
|
{ 'id' : '1', 'connected' : True },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
rpcpeers_off = {
|
||||||
|
'peers' : [
|
||||||
|
{ 'id' : '1', 'connected' : False },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
# when
|
||||||
|
for i in range(40):
|
||||||
|
trace_availability(plugin, rpcpeers_on)
|
||||||
|
for i in range(20):
|
||||||
|
trace_availability(plugin, rpcpeers_off)
|
||||||
|
|
||||||
|
# then
|
||||||
|
assert(round(plugin.avail_peerstate['1']['avail'], 3) == 0.667)
|
||||||
|
|
||||||
|
|
||||||
|
# checks the leading window is smaller if interval count is low
|
||||||
|
# when a node just started
|
||||||
|
def test_summary_avail_leadwin():
|
||||||
|
# given
|
||||||
|
plugin = get_stub()
|
||||||
|
rpcpeers_on = {
|
||||||
|
'peers' : [
|
||||||
|
{ 'id' : '1', 'connected' : True },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
rpcpeers_off = {
|
||||||
|
'peers' : [
|
||||||
|
{ 'id' : '1', 'connected' : False },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
# when
|
||||||
|
trace_availability(plugin, rpcpeers_on)
|
||||||
|
trace_availability(plugin, rpcpeers_on)
|
||||||
|
trace_availability(plugin, rpcpeers_off)
|
||||||
|
|
||||||
|
# then
|
||||||
|
assert(round(plugin.avail_peerstate['1']['avail'], 3) == 0.667)
|
||||||
|
|
||||||
|
|
||||||
def test_summary_start(node_factory):
|
def test_summary_start(node_factory):
|
||||||
l1 = node_factory.get_node(options=pluginopt)
|
l1 = node_factory.get_node(options=pluginopt)
|
||||||
s = l1.rpc.summary()
|
s = l1.rpc.summary()
|
||||||
|
|||||||
Reference in New Issue
Block a user