ghost
3 years ago
7 changed files with 437 additions and 2 deletions
@ -1,2 +1,58 @@
@@ -1,2 +1,58 @@
|
||||
# twister-stat |
||||
Statistic tools for twister network |
||||
# twister-stat ⠀ |
||||
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 @@
@@ -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 @@
@@ -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 @@
@@ -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