ghost
3 years ago
7 changed files with 437 additions and 2 deletions
@ -1,2 +1,58 @@ |
|||||||
# twister-stat |
# twister-stat ⠀ |
||||||
Statistic tools for twister network |
Statistic tools for twister network ⠀ |
||||||
|
|
||||||
|
## Blockchain crawler ⠀ |
||||||
|
|
||||||
|
Script written in python v2, that scans the blockchain and dump the data to the MySQL database. ⠀ |
||||||
|
|
||||||
|
Index contain: ⠀ |
||||||
|
|
||||||
|
- block number ⠀ |
||||||
|
- block hash ⠀ |
||||||
|
- time created ⠀ |
||||||
|
- usernames ⠀ |
||||||
|
|
||||||
|
### Configuration ⠀ |
||||||
|
|
||||||
|
line 5 - MySQL database ⠀ |
||||||
|
line 13 - blocks per step ⠀ |
||||||
|
line 31 - twister API ⠀ |
||||||
|
|
||||||
|
### Requirements ⠀ |
||||||
|
|
||||||
|
`apt install python2 php-mysql mysql-server` ⠀ |
||||||
|
`pip install mysqlclient` ⠀ |
||||||
|
|
||||||
|
### Running ⠀ |
||||||
|
|
||||||
|
`python2 crawler/blockchain.py` ⠀ |
||||||
|
|
||||||
|
## Charts ⠀ |
||||||
|
|
||||||
|
Tools for the data dumped visualization written in PHP ⠀ |
||||||
|
|
||||||
|
### Configuration ⠀ |
||||||
|
|
||||||
|
line 8 - MySQL database ⠀ |
||||||
|
|
||||||
|
### Requirements ⠀ |
||||||
|
|
||||||
|
`php-fpm` |
||||||
|
`php-curl`⠀ |
||||||
|
|
||||||
|
### Running ⠀ |
||||||
|
|
||||||
|
create new server instance ⠀ |
||||||
|
|
||||||
|
`cd chart` ⠀ |
||||||
|
`php -S localhost:8081` ⠀ |
||||||
|
|
||||||
|
open in browser ⠀ |
||||||
|
|
||||||
|
`http://localhost:8081/index.php` ⠀ |
||||||
|
|
||||||
|
todo: ⠀ |
||||||
|
|
||||||
|
## Static dumps directory ⠀ |
||||||
|
|
||||||
|
`dump` |
@ -0,0 +1,126 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
// Debug level |
||||||
|
ini_set('display_errors', '1'); |
||||||
|
ini_set('display_startup_errors', '1'); |
||||||
|
error_reporting(E_ALL); |
||||||
|
|
||||||
|
// DB config |
||||||
|
define('DB_HOSTNAME', 'localhost'); |
||||||
|
define('DB_PORT', '3306'); |
||||||
|
define('DB_DATABASE', 'twister-stat'); |
||||||
|
define('DB_USERNAME', 'root'); |
||||||
|
define('DB_PASSWORD', 'password'); |
||||||
|
|
||||||
|
// Init DB connection |
||||||
|
try { |
||||||
|
$db = new PDO('mysql:dbname=' . DB_DATABASE . ';host=' . DB_HOSTNAME . ';port=' . DB_PORT . ';charset=utf8', DB_USERNAME, DB_PASSWORD, [PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8']); |
||||||
|
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); |
||||||
|
$db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); |
||||||
|
} catch(PDOException $e) { |
||||||
|
trigger_error($e->getMessage()); |
||||||
|
} |
||||||
|
|
||||||
|
// Get total blocks |
||||||
|
$query = $db->query('SELECT COUNT(*) AS `total` FROM `block`'); |
||||||
|
$blocksTotal = $query->fetch()['total']; |
||||||
|
|
||||||
|
// Get total users |
||||||
|
$query = $db->query('SELECT COUNT(*) AS `total` FROM `user`'); |
||||||
|
$usersTotal = $query->fetch()['total']; |
||||||
|
|
||||||
|
// Get stats |
||||||
|
$query = $db->query('SELECT YEAR(FROM_UNIXTIME(`time`)) AS `year`, |
||||||
|
MONTH(FROM_UNIXTIME(`time`)) AS `month`, |
||||||
|
COUNT(`block`.`blockId`) AS `blocks`, |
||||||
|
|
||||||
|
SUM((SELECT COUNT(`user`.`userId`) FROM `user` |
||||||
|
WHERE `user`.`blockId` = `block`.`blockId` |
||||||
|
GROUP BY `user`.`blockId`)) AS `users` |
||||||
|
|
||||||
|
FROM `block` |
||||||
|
|
||||||
|
GROUP BY `year`, `month` |
||||||
|
ORDER BY `year`, `month` |
||||||
|
'); |
||||||
|
|
||||||
|
$stats = $query->fetchAll(); |
||||||
|
|
||||||
|
?> |
||||||
|
|
||||||
|
<!DOCTYPE html> |
||||||
|
<html> |
||||||
|
<head> |
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script> |
||||||
|
<meta charset="UTF-8" /> |
||||||
|
<title>twister-stat</title> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<canvas id="chart"></canvas> |
||||||
|
<script> |
||||||
|
|
||||||
|
const labels = []; |
||||||
|
|
||||||
|
<?php foreach ($stats as $stat) { ?> |
||||||
|
labels.push('<?php echo $stat['year']; ?>.<?php echo $stat['month']; ?>'); |
||||||
|
<?php } ?> |
||||||
|
|
||||||
|
const blocks = [<?php foreach ($stats as $stat) { echo sprintf('%s,', $stat['blocks']); } ?>]; |
||||||
|
const users = [<?php foreach ($stats as $stat) { echo sprintf('%s,', $stat['users']); } ?>]; |
||||||
|
|
||||||
|
const data = { |
||||||
|
labels: labels, |
||||||
|
datasets: [ |
||||||
|
{ |
||||||
|
label: 'blocks', |
||||||
|
data: blocks, |
||||||
|
borderColor: '#555', |
||||||
|
fill: true, |
||||||
|
scaleOverride : false, |
||||||
|
}, { |
||||||
|
label: 'users', |
||||||
|
data: users, |
||||||
|
borderColor: '#999', |
||||||
|
fill: true, |
||||||
|
scaleOverride : false, |
||||||
|
} |
||||||
|
] |
||||||
|
}; |
||||||
|
|
||||||
|
const config = { |
||||||
|
type: 'line', |
||||||
|
data: data, |
||||||
|
options: { |
||||||
|
responsive: true, |
||||||
|
plugins: { |
||||||
|
title: { |
||||||
|
display: true, |
||||||
|
text: "blocks: <?php echo $blocksTotal; ?> | users: <?php echo $usersTotal; ?>" |
||||||
|
}, |
||||||
|
}, |
||||||
|
interaction: { |
||||||
|
intersect: false, |
||||||
|
}, |
||||||
|
scales: { |
||||||
|
x: { |
||||||
|
display: true, |
||||||
|
title: { |
||||||
|
display: true |
||||||
|
} |
||||||
|
}, |
||||||
|
y: { |
||||||
|
display: true, |
||||||
|
title: { |
||||||
|
display: true, |
||||||
|
text: 'Quantity' |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
}; |
||||||
|
|
||||||
|
const twisterStat = new Chart(document.getElementById('chart'), config); |
||||||
|
|
||||||
|
</script> |
||||||
|
</body> |
||||||
|
</html> |
@ -0,0 +1,68 @@ |
|||||||
|
#!/usr/bin/python |
||||||
|
|
||||||
|
import sys, MySQLdb |
||||||
|
|
||||||
|
db = MySQLdb.connect(host="localhost", # your host, usually localhost |
||||||
|
user="root", # your username |
||||||
|
passwd="password", # your password |
||||||
|
db="twister-stat") # name of the data base |
||||||
|
|
||||||
|
|
||||||
|
cursor = db.cursor() |
||||||
|
|
||||||
|
blocksInStep = 1000000 # blocks processing by the one step |
||||||
|
|
||||||
|
class MyDb: |
||||||
|
nextBlock = 0 |
||||||
|
|
||||||
|
process = MyDb() |
||||||
|
|
||||||
|
cursor.execute ("SELECT COUNT(*) + 1 AS nextBlock FROM block") |
||||||
|
row = cursor.fetchone() |
||||||
|
|
||||||
|
process.nextBlock = row[0] |
||||||
|
|
||||||
|
try: |
||||||
|
from bitcoinrpc.authproxy import AuthServiceProxy |
||||||
|
except ImportError as exc: |
||||||
|
sys.stderr.write("Error: install python-bitcoinrpc (https://github.com/jgarzik/python-bitcoinrpc)\n") |
||||||
|
exit(-1) |
||||||
|
|
||||||
|
serverUrl = "http://user:password@127.0.0.1:28332" |
||||||
|
if len(sys.argv) > 1: |
||||||
|
serverUrl = sys.argv[1] |
||||||
|
|
||||||
|
twister = AuthServiceProxy(serverUrl) |
||||||
|
|
||||||
|
print "blockchain reading..." |
||||||
|
|
||||||
|
while True: |
||||||
|
|
||||||
|
hash = twister.getblockhash(process.nextBlock) |
||||||
|
block = twister.getblock(hash) |
||||||
|
|
||||||
|
blocksInStep = blocksInStep - 1 |
||||||
|
|
||||||
|
if blocksInStep < 0: |
||||||
|
break |
||||||
|
|
||||||
|
print "add block", block["height"] |
||||||
|
cursor.execute("INSERT INTO block SET hash = %s, time = %s", (block["hash"], block["time"])) |
||||||
|
|
||||||
|
blockId = db.insert_id() |
||||||
|
|
||||||
|
for userName in block["usernames"]: |
||||||
|
|
||||||
|
print "add user", userName |
||||||
|
cursor.execute("INSERT INTO user SET blockId = %s, username = %s", (blockId, userName)) |
||||||
|
|
||||||
|
if block.has_key("nextblockhash"): |
||||||
|
process.nextBlock = process.nextBlock + 1 |
||||||
|
else: |
||||||
|
print "database is up to date..." |
||||||
|
break |
||||||
|
|
||||||
|
db.commit() |
||||||
|
db.close() |
||||||
|
|
||||||
|
print "task completed." |
Binary file not shown.
File diff suppressed because one or more lines are too long
@ -0,0 +1,172 @@ |
|||||||
|
|
||||||
|
<!DOCTYPE html> |
||||||
|
<html> |
||||||
|
<head> |
||||||
|
<script src="chart.js"></script> |
||||||
|
<meta charset="UTF-8" /> |
||||||
|
<title>twister-stat</title> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<canvas id="chart"></canvas> |
||||||
|
<script> |
||||||
|
|
||||||
|
const labels = []; |
||||||
|
|
||||||
|
labels.push('2013.11'); |
||||||
|
labels.push('2013.12'); |
||||||
|
labels.push('2014.1'); |
||||||
|
labels.push('2014.2'); |
||||||
|
labels.push('2014.3'); |
||||||
|
labels.push('2014.4'); |
||||||
|
labels.push('2014.5'); |
||||||
|
labels.push('2014.6'); |
||||||
|
labels.push('2014.7'); |
||||||
|
labels.push('2014.8'); |
||||||
|
labels.push('2014.9'); |
||||||
|
labels.push('2014.10'); |
||||||
|
labels.push('2014.11'); |
||||||
|
labels.push('2014.12'); |
||||||
|
labels.push('2015.1'); |
||||||
|
labels.push('2015.2'); |
||||||
|
labels.push('2015.3'); |
||||||
|
labels.push('2015.4'); |
||||||
|
labels.push('2015.5'); |
||||||
|
labels.push('2015.6'); |
||||||
|
labels.push('2015.7'); |
||||||
|
labels.push('2015.8'); |
||||||
|
labels.push('2015.9'); |
||||||
|
labels.push('2015.10'); |
||||||
|
labels.push('2015.11'); |
||||||
|
labels.push('2015.12'); |
||||||
|
labels.push('2016.1'); |
||||||
|
labels.push('2016.2'); |
||||||
|
labels.push('2016.3'); |
||||||
|
labels.push('2016.4'); |
||||||
|
labels.push('2016.5'); |
||||||
|
labels.push('2016.6'); |
||||||
|
labels.push('2016.7'); |
||||||
|
labels.push('2016.8'); |
||||||
|
labels.push('2016.9'); |
||||||
|
labels.push('2016.10'); |
||||||
|
labels.push('2016.11'); |
||||||
|
labels.push('2016.12'); |
||||||
|
labels.push('2017.1'); |
||||||
|
labels.push('2017.2'); |
||||||
|
labels.push('2017.3'); |
||||||
|
labels.push('2017.4'); |
||||||
|
labels.push('2017.5'); |
||||||
|
labels.push('2017.6'); |
||||||
|
labels.push('2017.7'); |
||||||
|
labels.push('2017.8'); |
||||||
|
labels.push('2017.9'); |
||||||
|
labels.push('2017.10'); |
||||||
|
labels.push('2017.11'); |
||||||
|
labels.push('2017.12'); |
||||||
|
labels.push('2018.1'); |
||||||
|
labels.push('2018.2'); |
||||||
|
labels.push('2018.3'); |
||||||
|
labels.push('2018.4'); |
||||||
|
labels.push('2018.5'); |
||||||
|
labels.push('2018.6'); |
||||||
|
labels.push('2018.7'); |
||||||
|
labels.push('2018.8'); |
||||||
|
labels.push('2018.9'); |
||||||
|
labels.push('2018.10'); |
||||||
|
labels.push('2018.11'); |
||||||
|
labels.push('2018.12'); |
||||||
|
labels.push('2019.1'); |
||||||
|
labels.push('2019.2'); |
||||||
|
labels.push('2019.3'); |
||||||
|
labels.push('2019.4'); |
||||||
|
labels.push('2019.5'); |
||||||
|
labels.push('2019.6'); |
||||||
|
labels.push('2019.7'); |
||||||
|
labels.push('2019.8'); |
||||||
|
labels.push('2019.9'); |
||||||
|
labels.push('2019.10'); |
||||||
|
labels.push('2019.11'); |
||||||
|
labels.push('2019.12'); |
||||||
|
labels.push('2020.1'); |
||||||
|
labels.push('2020.2'); |
||||||
|
labels.push('2020.3'); |
||||||
|
labels.push('2020.4'); |
||||||
|
labels.push('2020.5'); |
||||||
|
labels.push('2020.6'); |
||||||
|
labels.push('2020.7'); |
||||||
|
labels.push('2020.8'); |
||||||
|
labels.push('2020.9'); |
||||||
|
labels.push('2020.10'); |
||||||
|
labels.push('2020.11'); |
||||||
|
labels.push('2020.12'); |
||||||
|
labels.push('2021.1'); |
||||||
|
labels.push('2021.2'); |
||||||
|
labels.push('2021.3'); |
||||||
|
labels.push('2021.4'); |
||||||
|
labels.push('2021.5'); |
||||||
|
labels.push('2021.6'); |
||||||
|
labels.push('2021.7'); |
||||||
|
labels.push('2021.8'); |
||||||
|
labels.push('2021.9'); |
||||||
|
labels.push('2021.10'); |
||||||
|
labels.push('2021.11'); |
||||||
|
labels.push('2021.12'); |
||||||
|
|
||||||
|
const blocks = [9244,4322,8940,4368,4928,2160,5117,2920,5087,4548,4236,3921,4187,5088,2977,4062,4339,4470,4088,4243,4544,3569,4207,3632,4977,4320,3978,4280,4713,4402,3885,1412,4762,4966,3331,4370,4657,5023,4319,4238,4579,2806,3573,4315,4182,4366,4300,4369,4642,4284,4289,3719,4386,4544,4505,3994,4516,4583,4407,4400,4526,4491,3975,4664,4381,3460,4338,3917,4627,4400,2985,4041,4534,3763,4726,3378,4654,4289,4295,4811,4562,4393,4193,3937,4257,5273,4450,3880,3877,4854,4137,3902,4069,2787,4278,4523,4327,2846,]; |
||||||
|
const users = [12,537,34286,10633,2060,2019,1236,438,1482,5034,1859,967,2569,1218,5162,7424,1255,391,615,340,240,353,296,173,175,5573,240,196,166,154,94,895,530,69,72,81,63,79,67,60,51,71,50,70,101,45,57,124,62,42,50,38,86,39,55,34,38,40,517,243,31,23,22,23,13,14,10,28,15,19,7,45,14,17,12,14,10,11,10,4,15,3,1,1,8,30,29,8,6,5,5,8,1,,4,,2,3,]; |
||||||
|
|
||||||
|
const data = { |
||||||
|
labels: labels, |
||||||
|
datasets: [ |
||||||
|
{ |
||||||
|
label: 'blocks', |
||||||
|
data: blocks, |
||||||
|
borderColor: '#555', |
||||||
|
fill: true, |
||||||
|
scaleOverride : false, |
||||||
|
}, { |
||||||
|
label: 'users', |
||||||
|
data: users, |
||||||
|
borderColor: '#999', |
||||||
|
fill: true, |
||||||
|
scaleOverride : false, |
||||||
|
} |
||||||
|
] |
||||||
|
}; |
||||||
|
|
||||||
|
const config = { |
||||||
|
type: 'line', |
||||||
|
data: data, |
||||||
|
options: { |
||||||
|
responsive: true, |
||||||
|
plugins: { |
||||||
|
title: { |
||||||
|
display: true, |
||||||
|
text: "blocks: 420389 | users: 91397" |
||||||
|
}, |
||||||
|
}, |
||||||
|
interaction: { |
||||||
|
intersect: false, |
||||||
|
}, |
||||||
|
scales: { |
||||||
|
x: { |
||||||
|
display: true, |
||||||
|
title: { |
||||||
|
display: true |
||||||
|
} |
||||||
|
}, |
||||||
|
y: { |
||||||
|
display: true, |
||||||
|
title: { |
||||||
|
display: true, |
||||||
|
text: 'Quantity' |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
}; |
||||||
|
|
||||||
|
const twisterStat = new Chart(document.getElementById('chart'), config); |
||||||
|
|
||||||
|
</script> |
||||||
|
</body> |
||||||
|
</html> |
After Width: | Height: | Size: 92 KiB |
Loading…
Reference in new issue