/parse -> /cli

This commit is contained in:
Dmitry Sergeev 2018-06-22 00:08:53 +05:00
parent c6401002d7
commit 28adca9fe5
6 changed files with 324 additions and 0 deletions

122
cli/block_report.py Normal file
View File

@ -0,0 +1,122 @@
import sys, urllib, json, datetime, time
import matplotlib.pyplot as plt
from common_parse_utils import uvarint, parseutc, formatbytes, readjson, getmaxheight
def get_num_txs(json):
return json["result"]["block"]["header"]["num_txs"]
if len(sys.argv) < 2:
print "usage: python parse_block.py host:port [report_name [minheight [maxheight]]]"
sys.exit()
tmaddress = sys.argv[1]
report_name = sys.argv[2] if len(sys.argv) > 2 else ""
if len(sys.argv) > 4:
maxheight = int(sys.argv[4])
else:
maxheight = getmaxheight(tmaddress)
while maxheight >= 3 and get_num_txs(readjson(tmaddress + "/block?height=%d" % maxheight)) == 0:
maxheight -= 1
if len(sys.argv) > 3:
minheight = int(sys.argv[3])
else:
minheight = maxheight
while minheight >= 3 and get_num_txs(readjson(tmaddress + "/block?height=%d" % (minheight - 1))) > 0:
minheight -= 1
accsize = 0
acclatency = 0
minlatency = 1e20
maxlatency = 0
txcount = 0
blockcount = 0
firsttx = 1e20
lasttx = 0
firstblock = 1e20
lastblock = 0
maxblocksize = 0
txstat = []
for height in range(minheight, maxheight + 1):
data = readjson(tmaddress + "/block?height=%d" % height)
numtxs = get_num_txs(data)
blocktimetxt = data["result"]["block"]["header"]["time"]
blocktime = parseutc(blocktimetxt)
if numtxs > 0:
firstblock = min(firstblock, blocktime)
lastblock = max(lastblock, blocktime)
blockcount += 1
maxblocksize = max(maxblocksize, numtxs)
print height, numtxs, blocktimetxt
txs = data["result"]["block"]["data"]["txs"]
if txs:
for index, txhex in enumerate(txs):
txbytes = bytearray.fromhex(txhex)# if re.fullmatch(r"^[0-9a-fA-F]$", txhex) is not None
key = chr(txbytes[0]) if chr(txbytes[1]) == '=' else "*"
connindex = uvarint(txbytes[2:8])
txnumber = uvarint(txbytes[8:16])
hostnamehash = txhex[32:64]
txtime = uvarint(txbytes[32:40]) / 1e6
if txtime < 1e9:
txtime *= 1e6 # legacy support
latency = blocktime - txtime
accsize += len(txbytes)
acclatency += latency
minlatency = min(minlatency, latency)
maxlatency = max(maxlatency, latency)
txcount += 1
firsttx = min(firsttx, txtime)
lasttx = max(lasttx, txtime)
txtimetxt = datetime.datetime.fromtimestamp(txtime)
txstat.append((txtime, 1))
txstat.append((blocktime, -1))
if index < 5:
print txtimetxt, latency
#print key, connindex, txnumber, hostnamehash, txtimetxt, latency
print "Transactions: ", txcount, "=", formatbytes(accsize)
print " ", "%.3f s" % (lasttx - firsttx), "from", datetime.datetime.fromtimestamp(firsttx), "to", datetime.datetime.fromtimestamp(lasttx)
print "Blocks: ", "%d: from %d to %d" % (blockcount, minheight, maxheight)
print " ", "%.3f s" % (lastblock - firstblock), "from", datetime.datetime.fromtimestamp(firstblock), "to", datetime.datetime.fromtimestamp(lastblock)
print "Tx send rate: ", "%.3f tx/s" % (txcount / (lasttx - firsttx)), "=", formatbytes(accsize / (lasttx - firsttx)) + "/s"
print "Tx throughput: ", "%.3f tx/s" % (txcount / (lastblock - firsttx)), "=", formatbytes(accsize / (lastblock - firsttx)) + "/s"
print "Block throughput:", "%.3f block/s" % (blockcount / (lastblock - firsttx))
print "Avg tx latency: ", "%.3f s" % (acclatency / txcount)
print "Min tx latency: ", "%.3f s" % minlatency
print "Max tx latency: ", "%.3f s" % maxlatency
txstat = sorted(txstat)
cursum = 0
curindex = 0
steps = 1000
stepstat = []
for i in range(steps + 1):
t = firsttx + (lastblock - firsttx) / steps * i
while curindex < len(txstat) and txstat[curindex][0] <= t:
cursum += txstat[curindex][1]
curindex += 1
stepstat.append(cursum)
f = plt.figure(figsize=(15, 5))
plt.plot([i * (lastblock - firsttx) / steps for i in range(steps + 1)], stepstat)
long_title = "Duration: %.1f s, Tx size: %s, Tx send rate: %.3f tx/s = %s/s, Tx throughput: %.3f tx/s = %s/s" % \
(lasttx - firsttx, formatbytes(accsize / txcount), \
txcount / (lasttx - firsttx), formatbytes(accsize / (lasttx - firsttx)), \
txcount / (lastblock - firsttx), formatbytes(accsize / (lastblock - firsttx)))
#plt.title(long_title)
plt.title(report_name)
plt.xlabel("seconds from first tx")
plt.ylabel("txs in backlog")
if report_name != "":
long_filename = "tdmnt-stat-%d-%d-%d-%.1f-%.0f-%.0f.png" % \
(minheight, maxheight, maxblocksize, lasttx - firsttx, accsize / txcount, txcount / (lasttx - firsttx))
#f.savefig(long_filename, bbox_inches='tight')
f.savefig(report_name + ".png", bbox_inches='tight')
plt.show(block=True)

46
cli/common_parse_utils.py Normal file
View File

@ -0,0 +1,46 @@
import sys, urllib, json, datetime, time
def uvarint(buf):
x = long(0)
s = 0
for b in buf:
if b < 0x80:
return x | long(b) << s
x |= long(b & 0x7f) << s
s += 7
return 0
def parseutc(utctxt):
#tz conversion may be wrong
now_timestamp = time.time()
offset = datetime.datetime.fromtimestamp(now_timestamp) - datetime.datetime.utcfromtimestamp(now_timestamp)
dt, _, tail = utctxt.partition(".")
if tail == "":
dt, _, _ = utctxt.partition("Z")
tail = "0Z"
pure = int((datetime.datetime.strptime(dt, '%Y-%m-%dT%H:%M:%S') + offset).strftime("%s"))
ns = int(tail.rstrip("Z").ljust(9, "0"), 10)
return pure + ns / 1e9
def formatbytes(value):
if value < 1024:
return "%.0f B" % value
elif value < 1024 * 1024:
return "%.3f KiB" % (value / 1024.0)
else:
return "%.3f MiB" % (value / 1024.0 / 1024.0)
def readjson(url):
response = urllib.urlopen("http://" + url)
return json.loads(response.read())
def getsyncinfo(tmaddress):
status = readjson(tmaddress + "/status")["result"]
if "sync_info" in status: # compatibility
return status["sync_info"]
else:
return status
def getmaxheight(tmaddress):
return getsyncinfo(tmaddress)["latest_block_height"]

10
cli/demo_queries.sh Executable file
View File

@ -0,0 +1,10 @@
python query.py localhost:46257 put a/b=10
python query.py localhost:46257 put "a/c=copy(a/b)"
python query.py localhost:46257 put "a/d=increment(a/c)"
python query.py localhost:46257 put "a/d=increment(a/c)###again"
python query.py localhost:46257 put "a/e=sum(a/c,a/d)"
python query.py localhost:46257 put "a/f=factorial(a/b)"
python query.py localhost:46257 put "c/asum=hiersum(a)"
python query.py localhost:46257 get a/e
python query.py localhost:46257 put "0-200:b/@1/@0=1"
python query.py localhost:46257 put "c/bsum=hiersum(b)"

48
cli/parse_chain.py Normal file
View File

@ -0,0 +1,48 @@
import sys, urllib, json, datetime, time
from common_parse_utils import parseutc, readjson, getmaxheight
if len(sys.argv) < 2:
print "usage: python parse_chain.py host:port [minheight]"
sys.exit()
blocks_fetch = 20 # tendermint can't return more blocks
tmaddress = sys.argv[1]
maxheight = getmaxheight(tmaddress)
minheight = int(sys.argv[2]) if len(sys.argv) > 2 else max(1, maxheight - 49)
lastnonempty = -1
last_fetched_height = minheight - 1
print "%6s %26s %7s %7s %8s %30s %30s %30s %30s %30s" % ("height", "block time", "txs", "acc.txs", "app_hash", "tx1", "tx2", "tx3", "tx4", "tx5")
for height in range(minheight, maxheight + 1):
if height > last_fetched_height:
last_fetched_height = min(height + blocks_fetch - 1, maxheight)
bulk_data = (readjson(tmaddress + "/blockchain?minHeight=%d&maxHeight=%d" % (height, last_fetched_height)))["result"]["block_metas"]
data = bulk_data[last_fetched_height - height]["header"]
numtxs = data["num_txs"]
totaltxs = data["total_txs"]
app_hash = data["app_hash"]
blocktimetxt = data["time"]
blocktime = parseutc(blocktimetxt)
if numtxs > 0 or height == maxheight or height == lastnonempty + 1:
blockdata = readjson(tmaddress + "/block?height=%d" % height)
txs = blockdata["result"]["block"]["data"]["txs"]
txsummary = ""
if txs:
lastnonempty = height
for tx in txs[0:5]:
txstr = tx.decode('base64')
if len(txstr) > 30:
txsummary += "%27s... " % txstr[0:27]
else:
txsummary += "%30s " % txstr
if len(txs) > 5:
txsummary += "..."
app_hash_to_show = "0x" + app_hash[0:6] if app_hash != "" else "--------"
print "%5s: %s %7d %7d" % (height, datetime.datetime.fromtimestamp(blocktime), numtxs, totaltxs), app_hash_to_show, txsummary
else:
if height == lastnonempty + 2:
print "..."

96
cli/query.py Normal file
View File

@ -0,0 +1,96 @@
import sys, urllib, json, datetime, time, hashlib, sha3
from common_parse_utils import readjson, getsyncinfo, getmaxheight
CMD_PUT = "fastput"
CMD_CHECKED_PUT = "put"
CMD_RUN = "run"
CMD_GET_QUERY = "get"
CMD_LS_QUERY = "ls"
def verify_merkle_proof(result, proof, app_hash):
parts = proof.split(", ")
parts_len = len(parts)
for index in range(parts_len, -1, -1):
low_string = parts[index] if index < parts_len else result
low_hash = hashlib.sha3_256(low_string).hexdigest()
high_hashes = parts[index - 1].split(" ") if index > 0 else [app_hash.lower()]
if not any(low_hash in s for s in high_hashes):
return False
return True
def checked_abci_query(tmaddress, height, command, query, tentative_info):
if getmaxheight(tmaddress) < height + 1:
return (height, None, None, None, False, "Cannot verify tentative '%s'! Height is not verifiable" % (info or ""))
apphash = readjson('%s/block?height=%d' % (tmaddress, height + 1))["result"]["block"]["header"]["app_hash"]
response = readjson('%s/abci_query?height=%d&data="%s:%s"' % (tmaddress, height, command, query))["result"]["response"]
(result, proof) = (
response["value"].decode('base64') if "value" in response else None,
response["proof"].decode('base64') if "proof" in response else None
)
if result is None:
return (height, result, proof, apphash, False, "Result is empty")
elif tentative_info is not None and result != tentative_info:
return (height, result, proof, apphash, False, "Verified result '%s' doesn't match tentative '%s'!" % (result, info))
elif proof is None:
return (height, result, proof, apphash, False, "No proof")
elif not verify_merkle_proof(result, proof, apphash) :
return (height, result, proof, apphash, False, "Proof is invalid")
else:
return (height, result, proof, apphash, True, "")
def print_checked_abci_query(tmaddress, height, command, query, tentative_info):
(height, result, proof, apphash, success, message) = checked_abci_query(tmaddress, height, command, query, tentative_info)
print "HEIGHT:", height
print "HASH :", apphash or "NOT_READY"
print "PROOF :", (proof or "NO_PROOF").upper()
print "RESULT:", result or "EMPTY"
if success:
print "OK"
else:
print "BAD :", message
def latest_provable_height(tmaddress):
return getsyncinfo(tmaddress)["latest_block_height"] - 1
def wait_for_height(tmaddress, height):
for w in range(0, 5):
if getmaxheight(tmaddress) >= height:
break
time.sleep(1)
if len(sys.argv) < 3:
print "usage: python query.py host:port command arg"
sys.exit()
tmaddress = sys.argv[1]
command = sys.argv[2]
arg = sys.argv[3]
if command in {CMD_PUT, CMD_CHECKED_PUT, CMD_RUN}:
if command == CMD_RUN:
query_key = "optarg"
tx = query_key + "=" + arg
else:
tx = arg
query_key = tx.split("=")[0]
response = readjson(tmaddress + '/broadcast_tx_commit?tx="' + tx + '"')
if "error" in response:
print "ERROR :", response["error"]["data"]
else:
height = response["result"]["height"]
if response["result"].get("deliver_tx", {}).get("code", "0") != "0":
print "HEIGHT:", height
print "BAD :", log or "NO_LOG"
else:
info = response["result"].get("deliver_tx", {}).get("info")
if command in {CMD_CHECKED_PUT, CMD_RUN} and info is not None:
wait_for_height(tmaddress, height + 1)
print_checked_abci_query(tmaddress, height, "get", query_key, info)
else:
print "HEIGHT:", height
print "INFO: ", info or "EMPTY"
print "OK"
elif command in {CMD_GET_QUERY, CMD_LS_QUERY}:
height = latest_provable_height(tmaddress)
print_checked_abci_query(tmaddress, height, command, arg, None)

2
cli/report_to_file.sh Executable file
View File

@ -0,0 +1,2 @@
#!/bin/bash
python block_report.py $1 "$2" $3 $4 | tee "$2.txt"