===== PeerInfo =====
A simple Python 3 script that parses the output from the getpeerinfo() RPC call. The getpeerinfo() call yields a JSON document that looks like:
[
{
"addr" : "200.152.124.103:28333",
"services" : "00000001",
"lastsend" : 1391707620,
"lastrecv" : 1391707647,
"bytessent" : 209998,
"bytesrecv" : 237591,
"conntime" : 1391507320,
"version" : 70002,
"subver" : "/twisterd:0.9.12/",
"inbound" : false,
"startingheight" : 22992,
"banscore" : 0
},
--- ✂ ---
{
"addr" : "86.13.244.154:65162",
"services" : "00000001",
"lastsend" : 1391707647,
"lastrecv" : 1391707620,
"bytessent" : 78120,
"bytesrecv" : 3919,
"conntime" : 1391707502,
"version" : 70002,
"subver" : "/twisterd:0.9.12/",
"inbound" : true,
"startingheight" : 23302,
"banscore" : 0
}
]
(some records have obviously been omitted)
#!/usr/bin/python3.3
# -*- mode: python; coding: utf-8 -*-
##############################################################################
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; either version 3 of the License, or (at your option) #
# any later version. #
# #
# This program is distributed in the hope that it will be useful, but with- #
# out any warranty; without even the implied warranty of merchantability or #
# fitness for a particular purpose. See the GNU General Public License for #
# more details. #
##############################################################################
__author__ = '@kseistrup (Klaus Alexander Seistrup)'
__revision__ = '2014-02-06'
__version__ = '0.3.1 (%s)' % __revision__
__copyright__ = """\
twc-peerinfo %s
Copyright ©2014 Klaus Alexander Seistrup (@kseistrup)
This is free software; see the source for copying conditions. There is no
warranty; not even for merchantability or fitness for a particular purpose."""\
% __version__
import sys
import os
import json
import argparse
import textwrap
# These are native modules and should all be available
from sys import (exit, stderr, argv)
from datetime import datetime
from ipaddress import ip_address
from locale import (setlocale, format, LC_ALL)
from argparse import ArgumentParser
# These, however, need be installed by the user
try:
from bitcoinrpc.authproxy import AuthServiceProxy
from prettytable import PrettyTable
except ImportError as error:
print(str(error) + '.', file=stderr)
exit(1)
# end try
dummy = setlocale(LC_ALL, '')
parser = argparse.ArgumentParser(
prog=os.path.basename(argv[0]),
formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument(
'-v', '--version', action='version',
version='%(prog)s/' + __version__,
help='print version information and exit'
)
parser.add_argument(
'-c', '--copying', action='version',
version=textwrap.dedent(__copyright__),
help='show copying policy and exit'
)
args = parser.parse_args()
# FIXME: Good for Linux, what about MacOS and Windows?
CONF_NAME = os.path.expanduser('~/.twister/twister.conf')
conf = dict(
connect='localhost',
password='pwd',
port='28332',
ssl='0',
user='user'
)
def dhms(s):
(d, s) = divmod(s+30, 86400)
(h, s) = divmod(s, 3600)
(m, s) = divmod(s, 60)
res = []
if d:
res.append('%dd' % d)
if h:
res.append('%2dh' % h)
if m:
res.append('%2dm' % m)
return ', '.join(res[:2]) or '<1m'
def kb(bytes, binary=False):
if binary:
factor = 1024
else:
factor = 1000
return format('%d', int(round(bytes / factor)), grouping=True)
try:
with open(CONF_NAME, 'r') as fp:
for line in fp:
# Consider only lines starting with 'rpc'
if not line.startswith('rpc'):
continue
if line.count('=') < 1:
continue
(key, val) = map(lambda s: s.strip(), line.split('=', 1))
if len(key) < 4 or not key or not val:
continue
# Skip the 'rpc' part…
conf[key[3:]] = val
except FileNotFoundError:
pass # Use default values if config doesn't exist
conf['ssl'] = {'1': 's', 't': 's'}.get(conf.get('ssl', '0'), '')
url = 'http%(ssl)s://%(user)s:%(password)s@%(connect)s:%(port)s' % conf
twc = AuthServiceProxy(url)
info = twc.getpeerinfo()
peers = {}
for peer in info:
# Caveat: untested for IPv6!
ip = ip_address((peer['addr'][::-1].split(':', 1)[1][::-1]).strip('[]'))
decor = (ip.version, int(ip))
(tx, rx) = (peer['bytessent'], peer['bytesrecv'])
lastseen = max(peer['lastsend'], peer['lastrecv'])
knownfor = lastseen - peer['conntime']
subver = peer.get('subver', '/???:?.?.?/')
mobile = {True: 'M'}.get('android' in subver, ' ')
subver = subver.split(':')[1].strip('/')
syncnode = {True: '*', False: ' '}[peer.get('syncnode', False)]
peers[decor] = (
ip.compressed + syncnode,
mobile + subver,
{True: 'I', False: 'O'}[peer.get('inbound', False)],
format('%d', peer['startingheight'], grouping=True),
kb(rx),
kb(tx),
kb(rx + tx),
datetime.fromtimestamp(lastseen).strftime('%H:%M'),
dhms(knownfor)
)
table = PrettyTable(
['#', 'IP address ', 'Version', 'I/O', 'Height',
'RX kB', 'TX kB', 'Total kB', 'Seen', 'Known']
)
table.align['#'] = 'r'
table.align['IP address '] = 'r'
table.align['Version'] = 'l'
table.align['Height'] = 'r'
table.align['RX kB'] = 'r'
table.align['TX kB'] = 'r'
table.align['Total kB'] = 'r'
table.align['Known'] = 'r'
seq = 0
for key in sorted(peers.keys()):
seq += 1
table.add_row([seq] + list(peers[key]))
print(table)
exit(0)
# eof
The twc-peerinfo script yields an output like this:
+----+------------------+---------+-----+--------+-------+-------+----------+-------+----------+
| # | IP address | Version | I/O | Height | RX kB | TX kB | Total kB | Seen | Known |
+----+------------------+---------+-----+--------+-------+-------+----------+-------+----------+
| 1 | 2.220.74.56 | 0.9.13 | I | 23.294 | 8 | 80 | 87 | 18:19 | 48m |
| 2 | 5.79.68.137 | 0.9.13 | O | 23.005 | 219 | 198 | 417 | 18:19 | 2d, 4h |
| 3 | 24.27.56.215 | 0.9.13 | I | 23.077 | 82 | 216 | 298 | 18:19 | 1d, 13h |
| 4 | 24.245.93.223 | 0.9.3 | I | 23.282 | 10 | 80 | 89 | 18:19 | 2h, 52m |
| 5 | 37.187.0.83 | 0.9.13 | O | 22.992 | 189 | 140 | 329 | 18:19 | 2d, 7h |
| 6 | 37.187.19.215 | 0.9.8 | O | 22.992 | 300 | 171 | 470 | 18:19 | 2d, 7h |
| 7 | 46.59.63.57 | 0.9.13 | I | 23.192 | 37 | 135 | 172 | 18:19 | 17h, 40m |
| 8 | 46.142.163.186 | 0.9.13 | I | 23.300 | 3 | 76 | 79 | 18:19 | 8m |
| 9 | 54.200.158.96 | 0.9.12 | O | 23.096 | 222 | 139 | 360 | 18:19 | 1d, 9h |
| 10 | 54.216.111.14 | 0.9.13 | O | 23.109 | 149 | 111 | 260 | 18:19 | 1d, 7h |
| 11 | 71.166.37.244 | 0.9.13 | I | 23.194 | 58 | 145 | 203 | 18:19 | 17h, 16m |
| 12 | 75.118.24.185 | M0.9.13 | I | 23.165 | 102 | 167 | 269 | 18:19 | 21h, 46m |
| 13 | 78.230.218.69 | 0.9.13 | O | 22.992 | 272 | 245 | 517 | 18:19 | 2d, 7h |
| 14 | 80.26.96.220 | 0.9.13 | I | 23.238 | 38 | 113 | 152 | 18:19 | 8h, 30m |
| 15 | 82.230.220.128 | 0.9.7 | I | 23.187 | 75 | 132 | 207 | 18:19 | 18h, 25m |
| 16 | 82.241.251.127 | 0.9.12 | I | 23.229 | 57 | 115 | 173 | 18:19 | 10h, 44m |
| 17 | 83.167.48.253 | 0.9.10 | I | 120 | 278 | 151 | 429 | 18:19 | 2d, 6h |
| 18 | 84.49.189.86 | 0.9.13 | I | 23.300 | 1 | 76 | 77 | 18:19 | 11m |
| 19 | 89.150.170.153 | 0.9.13 | I | 23.182 | 31 | 124 | 155 | 18:19 | 8h, 10m |
| 20 | 92.26.222.241 | 0.9.13 | I | 120 | 6 | 78 | 83 | 18:19 | 52m |
| 21 | 94.140.52.34 | M0.9.3 | I | 23.285 | 4 | 79 | 83 | 18:19 | 2h, 2m |
| 22 | 94.223.87.16 | M0.9.3 | I | 23.300 | 0 | 76 | 76 | 18:19 | 6m |
| 23 | 108.247.121.115 | 0.9.13 | I | 23.177 | 100 | 151 | 251 | 18:19 | 19h, 49m |
| 24 | 109.69.14.162 | 0.9.9 | I | 23.209 | 57 | 122 | 179 | 18:19 | 13h, 50m |
| 25 | 109.77.226.245 | 0.9.13 | I | 23.278 | 5 | 94 | 99 | 18:19 | 4h, 15m |
| 26 | 109.139.242.247 | M0.9.13 | I | 23.283 | 8 | 84 | 92 | 18:19 | 2h, 33m |
| 27 | 166.137.185.47 | M0.9.13 | I | 120 | 0 | 76 | 76 | 18:19 | 7m |
| 28 | 186.26.115.90 | 0.9.13 | I | 23.293 | 2 | 79 | 81 | 18:19 | 22m |
| 29 | 188.194.185.165 | 0.9.13 | I | 23.229 | 54 | 97 | 150 | 18:19 | 11h |
| 30 | 192.241.235.68 | 0.9.13 | I | 2.000 | 79 | 99 | 178 | 18:19 | 1h, 40m |
| 31 | 198.199.87.4 | 0.9.0 | I | 23.039 | 102 | 229 | 332 | 18:19 | 1d, 22h |
| 32 | 200.152.124.103* | 0.9.12 | O | 22.992 | 236 | 208 | 444 | 18:19 | 2d, 7h |
+----+------------------+---------+-----+--------+-------+-------+----------+-------+----------+
An asterisk behind an IP address shows the current sync node, and an M in front of the version string denotes a mobile client.
PS: The script was put here since I apparently don't have access rights to e.g. “Scripting examples”.