2021-09-12 14:17:31 +00:00
package main
2021-09-13 03:25:47 +00:00
import (
2021-09-14 03:36:48 +00:00
"flag"
2021-09-13 03:25:47 +00:00
"log"
"net/http"
2021-09-26 04:08:10 +00:00
"os"
2021-09-15 05:04:32 +00:00
"strconv"
2021-09-14 03:36:48 +00:00
"strings"
2021-09-26 03:38:16 +00:00
"sync"
2021-09-15 03:34:08 +00:00
"time"
2021-09-13 03:25:47 +00:00
"github.com/helium-blockchain-exporter/heliumapi"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
2021-11-14 15:44:39 +00:00
"golang.org/x/time/rate"
2021-09-13 03:25:47 +00:00
)
// metricInfo is a metric exported by the helium blockchain exporter
type metricInfo struct {
Desc * prometheus . Desc
Type prometheus . ValueType
}
// Exporter collect metrics from the helium blockchain api and exports them as prometheus metrics.
type Exporter struct {
2021-09-25 01:30:22 +00:00
Accounts [ ] Account
StartTime time . Time
}
// NewExporter returns an initialized Exporter
func NewExporter ( accountAddress [ ] string ) ( * Exporter , error ) {
accounts := make ( [ ] Account , 0 )
for _ , accountAddress := range accountAddress {
if accountAddress != "" {
accounts = append ( accounts , NewAccount ( accountAddress ) )
}
}
return & Exporter {
Accounts : accounts ,
StartTime : time . Now ( ) ,
} , nil
2021-09-15 03:34:08 +00:00
}
// Account represents a helium account
type Account struct {
2021-10-14 19:24:06 +00:00
Address string
LastUpdate time . Time
RewardsTotal map [ string ] int
Tx AccountTx
Hotspots map [ string ] Hotspot
2021-12-24 20:58:38 +00:00
UpdateLock * sync . Mutex
2021-09-25 01:30:22 +00:00
}
func NewAccount ( address string ) Account {
return Account {
2021-10-14 19:24:06 +00:00
Address : address ,
LastUpdate : time . Now ( ) ,
RewardsTotal : map [ string ] int {
"data_credits" : 0 ,
"poc_challengers" : 0 ,
"poc_challengees" : 0 ,
"poc_witnesses" : 0 ,
} ,
2021-09-25 01:30:22 +00:00
Tx : AccountTx {
DepositTotal : 0 ,
WithdrawalTotal : 0 ,
} ,
2021-10-14 19:24:06 +00:00
Hotspots : map [ string ] Hotspot { } ,
2021-12-24 20:58:38 +00:00
UpdateLock : & sync . Mutex { } ,
2021-09-25 01:30:22 +00:00
}
2021-09-15 03:34:08 +00:00
}
2021-10-03 22:02:25 +00:00
type AccountTx struct {
DepositTotal int
WithdrawalTotal int
2021-10-14 19:24:06 +00:00
}
// Hotspot represents a hotspot
type Hotspot struct {
Address string
LastUpdate time . Time
RewardsTotal map [ string ] int
2021-12-24 20:58:38 +00:00
UpdateLock * sync . Mutex
2021-10-14 19:24:06 +00:00
}
func NewHotspot ( address string ) Hotspot {
return Hotspot {
Address : address ,
LastUpdate : time . Now ( ) ,
RewardsTotal : map [ string ] int {
"data_credits" : 0 ,
"poc_challengers" : 0 ,
"poc_challengees" : 0 ,
"poc_witnesses" : 0 ,
} ,
2021-12-24 20:58:38 +00:00
UpdateLock : & sync . Mutex { } ,
2021-10-14 19:24:06 +00:00
}
2021-10-03 22:02:25 +00:00
}
2021-09-13 03:25:47 +00:00
const (
2021-09-26 21:37:33 +00:00
namespace = "helium"
blockchain_oracle_factor = 100000000
blockchain_hnt_factor = 100000000
2021-09-13 03:25:47 +00:00
)
var (
2021-09-25 01:30:22 +00:00
// labels
2021-09-14 03:36:48 +00:00
commonAccountLabels = [ ] string { "account" }
2021-09-14 03:51:47 +00:00
commonHotspotLabels = append ( commonAccountLabels , "hotspot" , "hotspot_name" )
2021-09-14 03:36:48 +00:00
2021-09-13 03:25:47 +00:00
// exporter metrics
// helium oracle metrics
oraclePrice = metricInfo {
prometheus . NewDesc (
prometheus . BuildFQName ( namespace , "oracle" , "price_usd" ) ,
"The oracle price of an HNT token in USD." ,
nil , nil ,
) ,
prometheus . GaugeValue ,
}
// helium stats metrics
statsValidators = metricInfo {
prometheus . NewDesc (
2021-09-25 01:30:22 +00:00
prometheus . BuildFQName ( namespace , "stats" , "validators" ) ,
2021-09-15 03:34:08 +00:00
"The total number of validators." ,
2021-09-13 03:25:47 +00:00
nil , nil ,
) ,
2021-09-25 01:30:22 +00:00
prometheus . GaugeValue ,
2021-09-13 03:25:47 +00:00
}
statsOuis = metricInfo {
prometheus . NewDesc (
2021-09-25 01:30:22 +00:00
prometheus . BuildFQName ( namespace , "stats" , "ouis" ) ,
2021-09-15 03:34:08 +00:00
"The total number of organization unique identifiers." ,
2021-09-13 03:25:47 +00:00
nil , nil ,
) ,
2021-09-25 01:30:22 +00:00
prometheus . GaugeValue ,
2021-09-13 03:25:47 +00:00
}
statsHotspotsDataOnly = metricInfo {
prometheus . NewDesc (
2021-09-25 01:30:22 +00:00
prometheus . BuildFQName ( namespace , "stats" , "hotspots_dataonly" ) ,
2021-09-15 03:34:08 +00:00
"The total number of data only hotspots." ,
2021-09-13 03:25:47 +00:00
nil , nil ,
) ,
2021-09-25 01:30:22 +00:00
prometheus . GaugeValue ,
2021-09-13 03:25:47 +00:00
}
statsBlocks = metricInfo {
prometheus . NewDesc (
2021-09-25 01:30:22 +00:00
prometheus . BuildFQName ( namespace , "stats" , "blocks" ) ,
2021-10-12 13:19:58 +00:00
"The height of the blockchain." ,
2021-09-13 03:25:47 +00:00
nil , nil ,
) ,
2021-09-25 01:30:22 +00:00
prometheus . GaugeValue ,
2021-09-13 03:25:47 +00:00
}
statsChallenges = metricInfo {
prometheus . NewDesc (
2021-09-25 01:30:22 +00:00
prometheus . BuildFQName ( namespace , "stats" , "challenges" ) ,
2021-09-15 03:34:08 +00:00
"The total number of challenges." ,
2021-09-13 03:25:47 +00:00
nil , nil ,
) ,
2021-09-25 01:30:22 +00:00
prometheus . GaugeValue ,
2021-09-13 03:25:47 +00:00
}
statsCities = metricInfo {
prometheus . NewDesc (
prometheus . BuildFQName ( namespace , "stats" , "cities" ) ,
"The number of cities with at least one helium hotspot." ,
nil , nil ,
) ,
prometheus . GaugeValue ,
}
statsConsensusGroups = metricInfo {
prometheus . NewDesc (
2021-09-25 01:30:22 +00:00
prometheus . BuildFQName ( namespace , "stats" , "consensus_groups" ) ,
2021-09-15 03:34:08 +00:00
"The total number of consensus groups." ,
2021-09-13 03:25:47 +00:00
nil , nil ,
) ,
prometheus . GaugeValue ,
}
statsCountries = metricInfo {
prometheus . NewDesc (
prometheus . BuildFQName ( namespace , "stats" , "countries" ) ,
"The number of countries with at least on helium hotspot." ,
nil , nil ,
) ,
prometheus . GaugeValue ,
}
statsHotspots = metricInfo {
prometheus . NewDesc (
2021-09-25 01:30:22 +00:00
prometheus . BuildFQName ( namespace , "stats" , "hotspots" ) ,
2021-09-15 03:34:08 +00:00
"The total number of hotspots." ,
2021-09-13 03:25:47 +00:00
nil , nil ,
) ,
2021-09-25 01:30:22 +00:00
prometheus . GaugeValue ,
2021-09-13 03:25:47 +00:00
}
statsTokenSupply = metricInfo {
prometheus . NewDesc (
2021-09-25 01:30:22 +00:00
prometheus . BuildFQName ( namespace , "stats" , "token" ) ,
2021-09-13 03:25:47 +00:00
"The total supply of HNT tokens in circulation." ,
nil , nil ,
) ,
prometheus . GaugeValue ,
}
2021-09-14 03:36:48 +00:00
// helium account metrics
accountBalanceHnt = metricInfo {
prometheus . NewDesc (
prometheus . BuildFQName ( namespace , "account" , "balance_hnt" ) ,
2021-09-15 05:04:32 +00:00
"The number of HNT token owned by an account." ,
2021-09-14 03:36:48 +00:00
commonAccountLabels , nil ,
) ,
prometheus . GaugeValue ,
}
accountActivity = metricInfo {
prometheus . NewDesc (
prometheus . BuildFQName ( namespace , "account" , "activity_total" ) ,
2021-09-14 03:51:47 +00:00
"The total number of time an activity occurred in an account." ,
2021-09-14 03:36:48 +00:00
append ( commonAccountLabels , "type" ) , nil ,
) ,
prometheus . CounterValue ,
}
accountRewardsHnt = metricInfo {
prometheus . NewDesc (
2021-09-17 20:45:01 +00:00
prometheus . BuildFQName ( namespace , "account" , "rewards_hnt_total" ) ,
2021-09-15 05:04:32 +00:00
"The number of HNT token rewarded to an account." ,
2021-10-14 19:24:06 +00:00
append ( commonAccountLabels , "type" ) , nil ,
2021-09-14 03:36:48 +00:00
) ,
2021-09-17 20:45:01 +00:00
prometheus . CounterValue ,
2021-09-14 03:36:48 +00:00
}
2021-09-25 01:30:22 +00:00
accountDepositsHnt = metricInfo {
prometheus . NewDesc (
prometheus . BuildFQName ( namespace , "account" , "deposits_hnt_total" ) ,
2021-10-12 13:19:58 +00:00
"The number of HNT tokens deposited to an account." ,
2021-09-25 01:30:22 +00:00
commonAccountLabels , nil ,
) ,
prometheus . CounterValue ,
}
accountWithdrawalsHnt = metricInfo {
prometheus . NewDesc (
prometheus . BuildFQName ( namespace , "account" , "withdrawals_hnt_total" ) ,
2021-10-12 13:19:58 +00:00
"The number of HNT tokens withdrawn from an account." ,
2021-09-25 01:30:22 +00:00
commonAccountLabels , nil ,
) ,
prometheus . CounterValue ,
}
2021-09-13 03:25:47 +00:00
2021-09-15 05:04:32 +00:00
// helium hotspot metrics
hotspotUp = metricInfo {
prometheus . NewDesc (
prometheus . BuildFQName ( namespace , "hotspot" , "up" ) ,
"Whether a hotspot is online." ,
commonHotspotLabels , nil ,
) ,
prometheus . GaugeValue ,
}
2021-09-17 20:45:01 +00:00
hotspotRelayed = metricInfo {
2021-09-15 05:04:32 +00:00
prometheus . NewDesc (
2021-09-17 20:45:01 +00:00
prometheus . BuildFQName ( namespace , "hotspot" , "relayed" ) ,
"Whether a hotspot is relayed." ,
2021-09-15 05:04:32 +00:00
commonHotspotLabels , nil ,
) ,
prometheus . GaugeValue ,
}
2021-09-17 20:45:01 +00:00
hotspotBlocks = metricInfo {
2021-09-15 05:04:32 +00:00
prometheus . NewDesc (
2021-10-12 13:19:58 +00:00
prometheus . BuildFQName ( namespace , "hotspot" , "blocks" ) ,
2021-09-17 20:45:01 +00:00
"The block height of a hotspot. Check on the hotspot itself for the most recent data." ,
2021-09-15 05:04:32 +00:00
commonHotspotLabels , nil ,
) ,
2021-10-12 13:19:58 +00:00
prometheus . GaugeValue ,
}
hotspotBlocksDelta = metricInfo {
prometheus . NewDesc (
prometheus . BuildFQName ( namespace , "hotspot" , "blocks_delta" ) ,
"The gap between the height of a hotspot and the height of the blockchain. A large negative gap may indicate the hotspot is out of sync. Check on the hotspot itself for the most recent data." ,
commonHotspotLabels , nil ,
) ,
prometheus . GaugeValue ,
}
hotspotStatusTimestamp = metricInfo {
prometheus . NewDesc (
prometheus . BuildFQName ( namespace , "hotspot" , "status_timestamp" ) ,
"The the last time a hotspot gossiped its status on the blockchain." ,
commonHotspotLabels , nil ,
) ,
prometheus . GaugeValue ,
2021-09-15 05:04:32 +00:00
}
2021-09-17 20:45:01 +00:00
hotspotRewardsScale = metricInfo {
2021-09-15 05:04:32 +00:00
prometheus . NewDesc (
2021-09-17 20:45:01 +00:00
prometheus . BuildFQName ( namespace , "hotspot" , "rewards_scale" ) ,
2021-09-15 05:04:32 +00:00
"The reward scale of a hotspot." ,
commonHotspotLabels , nil ,
) ,
prometheus . GaugeValue ,
}
2021-10-07 14:24:46 +00:00
hotspot5dWitnesses = metricInfo {
prometheus . NewDesc (
prometheus . BuildFQName ( namespace , "hotspot" , "5d_witnesses" ) ,
"The number of hotspots that witnessed this hotspot in the last 5 days." ,
commonHotspotLabels , nil ,
) ,
prometheus . GaugeValue ,
}
hotspot5dWitnessed = metricInfo {
prometheus . NewDesc (
prometheus . BuildFQName ( namespace , "hotspot" , "5d_witnessed" ) ,
"The number of hotspots this hotspot witnessed in the last 5 days." ,
commonHotspotLabels , nil ,
) ,
prometheus . GaugeValue ,
}
2021-09-15 05:04:32 +00:00
hotspotGeocodeInfo = metricInfo {
prometheus . NewDesc (
prometheus . BuildFQName ( namespace , "hotspot" , "geocode_info" ) ,
"Information on the location of a hotspot." ,
append ( commonHotspotLabels , "lng" , "lat" , "street" , "state" , "country" , "city" ) , nil ,
) ,
prometheus . GaugeValue ,
}
hotspotAntennaInfo = metricInfo {
prometheus . NewDesc (
prometheus . BuildFQName ( namespace , "hotspot" , "antenna_info" ) ,
2021-09-17 20:45:01 +00:00
"Information on the antenna of a hotspot." ,
2021-09-15 05:04:32 +00:00
append ( commonHotspotLabels , "gain" , "elevation" ) , nil ,
) ,
prometheus . GaugeValue ,
}
hotspotActivity = metricInfo {
prometheus . NewDesc (
prometheus . BuildFQName ( namespace , "hotspot" , "activity_total" ) ,
"The total number of time an activity occurred in a hotspot." ,
append ( commonHotspotLabels , "type" ) , nil ,
) ,
prometheus . CounterValue ,
}
hotspotRewardsHnt = metricInfo {
prometheus . NewDesc (
2021-09-17 20:45:01 +00:00
prometheus . BuildFQName ( namespace , "hotspot" , "rewards_hnt_total" ) ,
2021-09-15 05:04:32 +00:00
"The number of HNT token rewarded to a hotspot." ,
2021-10-14 19:24:06 +00:00
append ( commonHotspotLabels , "type" ) , nil ,
2021-09-15 05:04:32 +00:00
) ,
2021-09-17 20:45:01 +00:00
prometheus . CounterValue ,
2021-09-15 05:04:32 +00:00
}
2021-09-13 03:25:47 +00:00
)
2021-09-15 05:04:32 +00:00
func bool2Float64 ( b bool ) float64 {
if b {
return 1.0
}
return 0.0
}
2021-09-13 03:25:47 +00:00
// Describe describes all the metrics ever exported by the helium blockchain exporter.
// implements prometheus.Collector.
func ( e * Exporter ) Describe ( ch chan <- * prometheus . Desc ) {
ch <- oraclePrice . Desc
ch <- statsValidators . Desc
ch <- statsOuis . Desc
ch <- statsHotspotsDataOnly . Desc
ch <- statsBlocks . Desc
ch <- statsChallenges . Desc
ch <- statsCities . Desc
ch <- statsConsensusGroups . Desc
ch <- statsCountries . Desc
ch <- statsHotspots . Desc
ch <- statsTokenSupply . Desc
2021-09-14 03:36:48 +00:00
ch <- accountBalanceHnt . Desc
ch <- accountActivity . Desc
ch <- accountRewardsHnt . Desc
2021-09-25 01:30:22 +00:00
ch <- accountDepositsHnt . Desc
ch <- accountWithdrawalsHnt . Desc
2021-09-15 05:04:32 +00:00
ch <- hotspotUp . Desc
2021-09-17 20:45:01 +00:00
ch <- hotspotRelayed . Desc
2021-10-12 13:19:58 +00:00
ch <- hotspotBlocksDelta . Desc
2021-09-17 20:45:01 +00:00
ch <- hotspotBlocks . Desc
2021-10-12 13:19:58 +00:00
ch <- hotspotStatusTimestamp . Desc
2021-09-17 20:45:01 +00:00
ch <- hotspotRewardsScale . Desc
2021-10-07 14:24:46 +00:00
ch <- hotspot5dWitnesses . Desc
ch <- hotspot5dWitnessed . Desc
2021-09-15 05:04:32 +00:00
ch <- hotspotGeocodeInfo . Desc
ch <- hotspotAntennaInfo . Desc
ch <- hotspotActivity . Desc
ch <- hotspotRewardsHnt . Desc
2021-09-13 03:25:47 +00:00
}
// Collect fetches the data from the helium blockchain api and delivers them as Prometheus metrics.
// implements prometheus.Collector.
func ( e * Exporter ) Collect ( ch chan <- prometheus . Metric ) {
2021-09-26 03:38:16 +00:00
wg := new ( sync . WaitGroup )
wg . Add ( 2 )
go e . collectOracleMetrics ( wg , ch )
2021-09-26 16:33:19 +00:00
go e . collectStatsMetrics ( wg , ch )
2021-09-17 20:45:01 +00:00
for i := range e . Accounts {
2021-10-14 19:24:06 +00:00
wg . Add ( 4 )
2021-09-26 03:38:16 +00:00
go e . collectAccountMetrics ( wg , ch , & e . Accounts [ i ] )
go e . collectAccountActivityMetrics ( wg , ch , & e . Accounts [ i ] )
go e . collectAccountTransactionsMetrics ( wg , ch , & e . Accounts [ i ] )
go e . collectHotspotMetrics ( wg , ch , & e . Accounts [ i ] )
2021-09-14 03:36:48 +00:00
}
2021-09-26 03:38:16 +00:00
wg . Wait ( )
2021-09-13 03:25:47 +00:00
}
2021-09-14 03:36:48 +00:00
// collectOracleMetrics collect metrics in the oracle group from the helium api
2021-09-26 03:38:16 +00:00
func ( e * Exporter ) collectOracleMetrics ( wg * sync . WaitGroup , ch chan <- prometheus . Metric ) {
defer wg . Done ( )
2021-09-13 03:25:47 +00:00
currentOraclePrice , err := heliumapi . GetCurrentOraclePrice ( )
if err != nil {
2021-10-03 21:06:16 +00:00
log . Println ( err )
2021-09-13 03:25:47 +00:00
return
}
ch <- prometheus . MustNewConstMetric (
2021-09-26 21:37:33 +00:00
oraclePrice . Desc , oraclePrice . Type , float64 ( currentOraclePrice . Price ) / blockchain_oracle_factor ,
2021-09-13 03:25:47 +00:00
)
}
2021-09-14 03:36:48 +00:00
// collectStatsMetrics collect metrics in the stats group from the helium api
2021-09-26 03:38:16 +00:00
func ( e * Exporter ) collectStatsMetrics ( wg * sync . WaitGroup , ch chan <- prometheus . Metric ) {
defer wg . Done ( )
2021-09-13 03:25:47 +00:00
blockchainStats , err := heliumapi . GetBlockchainStats ( )
if err != nil {
2021-10-03 21:06:16 +00:00
log . Println ( err )
2021-09-13 03:25:47 +00:00
return
}
ch <- prometheus . MustNewConstMetric (
2021-09-26 03:53:20 +00:00
statsValidators . Desc , statsValidators . Type , float64 ( blockchainStats . Counts . Validators ) ,
2021-09-13 03:25:47 +00:00
)
ch <- prometheus . MustNewConstMetric (
2021-09-26 03:53:20 +00:00
statsOuis . Desc , statsOuis . Type , float64 ( blockchainStats . Counts . Ouis ) ,
2021-09-13 03:25:47 +00:00
)
ch <- prometheus . MustNewConstMetric (
2021-09-26 03:53:20 +00:00
statsHotspotsDataOnly . Desc , statsHotspotsDataOnly . Type , float64 ( blockchainStats . Counts . HotspotsDataonly ) ,
2021-09-13 03:25:47 +00:00
)
ch <- prometheus . MustNewConstMetric (
2021-09-26 03:53:20 +00:00
statsBlocks . Desc , statsBlocks . Type , float64 ( blockchainStats . Counts . Blocks ) ,
2021-09-13 03:25:47 +00:00
)
ch <- prometheus . MustNewConstMetric (
2021-09-26 03:53:20 +00:00
statsChallenges . Desc , statsChallenges . Type , float64 ( blockchainStats . Counts . Challenges ) ,
2021-09-13 03:25:47 +00:00
)
ch <- prometheus . MustNewConstMetric (
2021-09-26 03:53:20 +00:00
statsCities . Desc , statsCities . Type , float64 ( blockchainStats . Counts . Cities ) ,
2021-09-13 03:25:47 +00:00
)
ch <- prometheus . MustNewConstMetric (
2021-09-26 03:53:20 +00:00
statsConsensusGroups . Desc , statsConsensusGroups . Type , float64 ( blockchainStats . Counts . ConsensusGroups ) ,
2021-09-13 03:25:47 +00:00
)
ch <- prometheus . MustNewConstMetric (
2021-09-26 03:53:20 +00:00
statsCountries . Desc , statsCountries . Type , float64 ( blockchainStats . Counts . Countries ) ,
2021-09-13 03:25:47 +00:00
)
ch <- prometheus . MustNewConstMetric (
2021-09-26 03:53:20 +00:00
statsHotspots . Desc , statsHotspots . Type , float64 ( blockchainStats . Counts . Hotspots ) ,
2021-09-13 03:25:47 +00:00
)
ch <- prometheus . MustNewConstMetric (
2021-09-26 03:53:20 +00:00
statsTokenSupply . Desc , statsTokenSupply . Type , blockchainStats . TokenSupply ,
2021-09-13 03:25:47 +00:00
)
}
2021-09-14 03:36:48 +00:00
// collectStatsMetrics collect metrics in the account group from the helium api
2021-09-26 03:38:16 +00:00
func ( e * Exporter ) collectAccountMetrics ( wg * sync . WaitGroup , ch chan <- prometheus . Metric , account * Account ) {
defer wg . Done ( )
2021-09-25 01:30:22 +00:00
accountForAddress , err := heliumapi . GetAccountForAddress ( account . Address )
2021-09-15 03:34:08 +00:00
if err != nil {
2021-10-03 21:06:16 +00:00
log . Println ( err )
2021-09-15 03:34:08 +00:00
return
}
2021-09-26 03:38:16 +00:00
ch <- prometheus . MustNewConstMetric (
2021-09-26 21:37:33 +00:00
accountBalanceHnt . Desc , accountBalanceHnt . Type , float64 ( accountForAddress . Balance ) / blockchain_hnt_factor ,
2021-09-26 03:38:16 +00:00
account . Address ,
)
}
2021-09-15 03:34:08 +00:00
2021-09-26 03:38:16 +00:00
// collectAccountActivityMetrics collect the total number of activities executed by an account from the helium api
func ( e * Exporter ) collectAccountActivityMetrics ( wg * sync . WaitGroup , ch chan <- prometheus . Metric , account * Account ) {
defer wg . Done ( )
accountActivityForAddress , err := heliumapi . GetActivityCountsForAccount ( account . Address )
2021-09-14 03:51:47 +00:00
if err != nil {
2021-10-03 21:06:16 +00:00
log . Println ( err )
2021-09-14 03:51:47 +00:00
return
}
2021-09-26 03:53:20 +00:00
for accType , count := range * accountActivityForAddress {
2021-09-15 03:34:08 +00:00
ch <- prometheus . MustNewConstMetric (
accountActivity . Desc , accountActivity . Type , float64 ( count ) ,
2021-09-25 01:30:22 +00:00
account . Address , accType ,
2021-09-15 03:34:08 +00:00
)
}
2021-09-14 03:36:48 +00:00
}
2021-09-26 03:38:16 +00:00
// collectAccountTransactionsMetrics collect the total deposited/withdrawn by an account from the helium api
func ( e * Exporter ) collectAccountTransactionsMetrics ( wg * sync . WaitGroup , ch chan <- prometheus . Metric , account * Account ) {
defer wg . Done ( )
2021-09-17 20:45:01 +00:00
2021-10-07 03:43:34 +00:00
// We optimize our queries by asking the api only the activities we car about.
2021-10-03 22:02:25 +00:00
activityTypes := [ ] string {
"add_gateway_v1" ,
"assert_location_v1" ,
"assert_location_v2" ,
"payment_v1" ,
"payment_v2" ,
"rewards_v1" ,
"rewards_v2" ,
"stake_validator_v1" ,
"token_burn_v1" ,
"transfer_hotspot_v1" ,
"unstake_validator_v1" ,
}
2021-10-07 03:43:34 +00:00
// We can only want to allow a single instance of the routine doing
// calculations on the deposited and widthdrawn total.
2021-10-14 19:24:06 +00:00
account . UpdateLock . Lock ( )
defer account . UpdateLock . Unlock ( )
2021-09-15 05:04:32 +00:00
2021-10-07 03:43:34 +00:00
// We want to keep in memory the timestamp of the last activity we
// received from the api. We cannot do something naive like [lastscrape, now]
// because the api can take a few seconds to sync with the chain and
// we can miss some activities by doing it that way.
2021-10-14 19:24:06 +00:00
lastActivityTime := account . LastUpdate . Unix ( )
2021-10-07 03:43:34 +00:00
updateLastActivityTime := func ( newTime int64 ) {
if lastActivityTime < newTime {
lastActivityTime = newTime
}
}
2021-12-24 17:45:17 +00:00
minTime := account . LastUpdate
2021-10-12 13:35:31 +00:00
// Explicitly set max_time, to avoid issues with server-side caching
maxTime := time . Now ( )
activities , err := heliumapi . GetActivityForAccount ( account . Address , activityTypes , & minTime , & maxTime )
2021-09-25 01:30:22 +00:00
if err != nil {
2021-10-03 21:06:16 +00:00
log . Println ( err )
2021-09-26 03:38:16 +00:00
return
2021-09-15 03:34:08 +00:00
}
2021-09-12 14:17:31 +00:00
2021-09-26 03:53:20 +00:00
// impl based on https://github.com/helium/hotspot-app/blob/918563fba84d1abf4554a43a4d42bb838d017bd3/src/features/wallet/root/useActivityItem.tsx#L336
2021-09-25 19:09:40 +00:00
for _ , activity := range activities . AddGatewayV1 {
2021-12-24 17:45:17 +00:00
if activity . Time > lastActivityTime {
account . Tx . WithdrawalTotal += activity . StakingFee
updateLastActivityTime ( activity . Time )
}
2021-09-25 19:09:40 +00:00
}
for _ , activity := range activities . AssertLocationV1 {
2021-12-24 17:45:17 +00:00
if activity . Time > lastActivityTime {
account . Tx . WithdrawalTotal += activity . StakingFee
updateLastActivityTime ( activity . Time )
}
2021-09-25 19:09:40 +00:00
}
for _ , activity := range activities . AssertLocationV2 {
2021-12-24 17:45:17 +00:00
if activity . Time > lastActivityTime {
account . Tx . WithdrawalTotal += activity . StakingFee
updateLastActivityTime ( activity . Time )
}
2021-09-25 19:09:40 +00:00
}
for _ , activity := range activities . PaymentV1 {
2021-12-24 17:45:17 +00:00
if activity . Time > lastActivityTime {
if activity . Payer == account . Address {
account . Tx . WithdrawalTotal += activity . Amount
} else {
account . Tx . DepositTotal += activity . Amount
}
updateLastActivityTime ( activity . Time )
2021-09-25 19:09:40 +00:00
}
}
for _ , activity := range activities . PaymentV2 {
2021-12-24 17:45:17 +00:00
if activity . Time > lastActivityTime {
if activity . Payer == account . Address {
paymentTotal := 0
for _ , payment := range activity . Payments {
paymentTotal += payment . Amount
}
account . Tx . WithdrawalTotal += paymentTotal
} else {
for _ , payment := range activity . Payments {
if payment . Payee == account . Address {
account . Tx . DepositTotal += payment . Amount
}
2021-09-25 19:09:40 +00:00
}
}
2021-12-24 17:45:17 +00:00
updateLastActivityTime ( activity . Time )
2021-09-25 19:09:40 +00:00
}
}
for _ , activity := range activities . RewardsV1 {
2021-12-24 17:45:17 +00:00
if activity . Time > lastActivityTime {
for _ , reward := range activity . Rewards {
account . RewardsTotal [ reward . Type ] += reward . Amount
account . Tx . DepositTotal += reward . Amount
}
updateLastActivityTime ( activity . Time )
2021-09-25 19:09:40 +00:00
}
}
for _ , activity := range activities . RewardsV2 {
2021-12-24 17:45:17 +00:00
if activity . Time > lastActivityTime {
for _ , reward := range activity . Rewards {
account . RewardsTotal [ reward . Type ] += reward . Amount
account . Tx . DepositTotal += reward . Amount
}
updateLastActivityTime ( activity . Time )
2021-09-25 19:09:40 +00:00
}
}
for _ , activity := range activities . StakeValidatorV1 {
2021-12-24 17:45:17 +00:00
if activity . Time > lastActivityTime {
account . Tx . WithdrawalTotal += activity . Stake
updateLastActivityTime ( activity . Time )
}
2021-09-25 19:09:40 +00:00
}
for _ , activity := range activities . TokenBurnV1 {
2021-12-24 17:45:17 +00:00
if activity . Time > lastActivityTime {
account . Tx . WithdrawalTotal += activity . Amount
updateLastActivityTime ( activity . Time )
}
2021-09-25 19:09:40 +00:00
}
for _ , activity := range activities . TransferHotspotV1 {
2021-12-24 17:45:17 +00:00
if activity . Time > lastActivityTime {
if activity . Buyer == account . Address {
account . Tx . WithdrawalTotal += activity . AmountToSeller
} else {
account . Tx . DepositTotal += activity . AmountToSeller
}
updateLastActivityTime ( activity . Time )
2021-09-25 19:09:40 +00:00
}
}
for _ , activity := range activities . UnstakeValidatorV1 {
2021-12-24 17:45:17 +00:00
if activity . Time > lastActivityTime {
account . Tx . WithdrawalTotal += activity . StakeAmount
updateLastActivityTime ( activity . Time )
}
2021-09-25 19:09:40 +00:00
}
2021-10-14 19:24:06 +00:00
account . LastUpdate = time . Unix ( lastActivityTime , 0 )
for rType , rTotal := range account . RewardsTotal {
ch <- prometheus . MustNewConstMetric (
accountRewardsHnt . Desc , accountRewardsHnt . Type , float64 ( rTotal ) / blockchain_hnt_factor ,
account . Address , rType ,
)
}
2021-09-25 19:09:40 +00:00
ch <- prometheus . MustNewConstMetric (
2021-09-26 21:37:33 +00:00
accountDepositsHnt . Desc , accountDepositsHnt . Type , float64 ( account . Tx . DepositTotal ) / blockchain_hnt_factor ,
2021-09-26 03:38:16 +00:00
account . Address ,
2021-09-25 19:09:40 +00:00
)
ch <- prometheus . MustNewConstMetric (
2021-09-26 21:37:33 +00:00
accountWithdrawalsHnt . Desc , accountWithdrawalsHnt . Type , float64 ( account . Tx . WithdrawalTotal ) / blockchain_hnt_factor ,
2021-09-26 03:38:16 +00:00
account . Address ,
2021-09-25 19:09:40 +00:00
)
2021-09-26 03:38:16 +00:00
}
// collectStatsMetrics collect metrics of the hotspot of an account from the helium api
func ( e * Exporter ) collectHotspotMetrics ( wg * sync . WaitGroup , ch chan <- prometheus . Metric , account * Account ) {
defer wg . Done ( )
hotspotsForAddress , err := heliumapi . GetHotspotsForAccount ( account . Address )
if err != nil {
2021-10-03 21:06:16 +00:00
log . Println ( err )
2021-09-26 03:38:16 +00:00
return
}
2021-09-25 19:09:40 +00:00
2021-10-12 13:19:58 +00:00
for _ , hotspotData := range hotspotsForAddress {
statusTime , err := time . Parse ( time . RFC3339Nano , hotspotData . Status . Timestamp )
if err != nil {
log . Println ( err )
continue
}
2021-12-24 20:46:04 +00:00
// create the hotspot object if it doesn't exist
if _ , ok := account . Hotspots [ hotspotData . Address ] ; ! ok {
account . Hotspots [ hotspotData . Address ] = NewHotspot ( hotspotData . Address )
}
2021-09-26 03:38:16 +00:00
// collect hotspot metric requiring extra queries in a new routine
2021-11-14 15:44:39 +00:00
wg . Add ( 4 )
2021-10-12 13:19:58 +00:00
go e . collectHotspotBlocksDeltaMetrics ( wg , ch , account , hotspotData )
2021-10-07 14:24:46 +00:00
go e . collectHotspotWitnessesMetrics ( wg , ch , account , hotspotData )
2021-11-14 15:44:39 +00:00
// go e.collectHotspotWitnessedMetrics(wg, ch, account, hotspotData)
2021-09-26 03:38:16 +00:00
go e . collectHotspotActivityMetrics ( wg , ch , account , hotspotData )
go e . collectHotspotRewardsMetrics ( wg , ch , account , hotspotData )
ch <- prometheus . MustNewConstMetric (
hotspotUp . Desc , hotspotUp . Type , bool2Float64 ( hotspotData . Status . Online == "online" ) ,
account . Address , hotspotData . Address , hotspotData . Name ,
)
ch <- prometheus . MustNewConstMetric (
hotspotRelayed . Desc , hotspotRelayed . Type , bool2Float64 ( len ( hotspotData . Status . ListenAddrs ) > 0 && strings . HasPrefix ( hotspotData . Status . ListenAddrs [ 0 ] , "/p2p" ) ) ,
account . Address , hotspotData . Address , hotspotData . Name ,
)
ch <- prometheus . MustNewConstMetric (
hotspotBlocks . Desc , hotspotBlocks . Type , float64 ( hotspotData . Status . Height ) ,
account . Address , hotspotData . Address , hotspotData . Name ,
)
2021-10-12 13:19:58 +00:00
ch <- prometheus . MustNewConstMetric (
hotspotStatusTimestamp . Desc , hotspotStatusTimestamp . Type , float64 ( statusTime . Unix ( ) ) ,
account . Address , hotspotData . Address , hotspotData . Name ,
)
2021-09-26 03:38:16 +00:00
ch <- prometheus . MustNewConstMetric (
hotspotRewardsScale . Desc , hotspotRewardsScale . Type , float64 ( hotspotData . RewardScale ) ,
account . Address , hotspotData . Address , hotspotData . Name ,
)
ch <- prometheus . MustNewConstMetric (
hotspotGeocodeInfo . Desc , hotspotGeocodeInfo . Type , 1.0 ,
account . Address , hotspotData . Address , hotspotData . Name , strconv . FormatFloat ( hotspotData . Lng , 'f' , 6 , 64 ) , strconv . FormatFloat ( hotspotData . Lat , 'f' , 6 , 64 ) , hotspotData . Geocode . LongStreet , hotspotData . Geocode . LongState , hotspotData . Geocode . LongCountry , hotspotData . Geocode . LongCity ,
)
ch <- prometheus . MustNewConstMetric (
hotspotAntennaInfo . Desc , hotspotAntennaInfo . Type , 1.0 ,
account . Address , hotspotData . Address , hotspotData . Name , strconv . Itoa ( hotspotData . Gain ) , strconv . Itoa ( hotspotData . Elevation ) ,
)
}
}
2021-10-12 13:19:58 +00:00
// collectHotspotBlocksDeltaMetrics calculate the gap between the block height of a hotstop and the height of the chain
func ( e * Exporter ) collectHotspotBlocksDeltaMetrics ( wg * sync . WaitGroup , ch chan <- prometheus . Metric , account * Account , hotspotData heliumapi . Hotspot ) {
2021-10-07 14:24:46 +00:00
defer wg . Done ( )
2021-10-12 13:19:58 +00:00
delta := 0.
2021-10-07 14:24:46 +00:00
if hotspotData . Status . Online == "online" && hotspotData . Status . Timestamp != "" {
statusTime , err := time . Parse ( time . RFC3339Nano , hotspotData . Status . Timestamp )
if err != nil {
log . Println ( err )
return
}
heightAtUpdate , err := heliumapi . GetHeight ( & statusTime )
if err != nil {
log . Println ( err )
return
}
2021-10-12 13:19:58 +00:00
delta = float64 ( hotspotData . Status . Height - heightAtUpdate )
2021-10-07 14:24:46 +00:00
}
ch <- prometheus . MustNewConstMetric (
2021-10-12 13:19:58 +00:00
hotspotBlocksDelta . Desc , hotspotBlocksDelta . Type , delta ,
2021-10-07 14:24:46 +00:00
account . Address , hotspotData . Address , hotspotData . Name ,
)
}
// collectHotspotWitnessesMetrics collect the total number witnesses of a hotspot in the last 5d
func ( e * Exporter ) collectHotspotWitnessesMetrics ( wg * sync . WaitGroup , ch chan <- prometheus . Metric , account * Account , hotspotData heliumapi . Hotspot ) {
defer wg . Done ( )
hotspotWitnesses , err := heliumapi . GetWitnessesForHotspot ( hotspotData . Address )
if err != nil {
log . Println ( err )
return
}
ch <- prometheus . MustNewConstMetric (
2021-10-12 13:19:58 +00:00
hotspot5dWitnesses . Desc , hotspotActivity . Type , float64 ( len ( hotspotWitnesses ) ) ,
2021-10-07 14:24:46 +00:00
account . Address , hotspotData . Address , hotspotData . Name ,
)
}
// collectHotspotWitnessedMetrics collect the total number hotspots witnessed in the last 5d
func ( e * Exporter ) collectHotspotWitnessedMetrics ( wg * sync . WaitGroup , ch chan <- prometheus . Metric , account * Account , hotspotData heliumapi . Hotspot ) {
defer wg . Done ( )
hotspotWitnessed , err := heliumapi . GetWitnessedForHotspot ( hotspotData . Address )
if err != nil {
log . Println ( err )
return
}
ch <- prometheus . MustNewConstMetric (
2021-10-12 13:19:58 +00:00
hotspot5dWitnessed . Desc , hotspotActivity . Type , float64 ( len ( hotspotWitnessed ) ) ,
2021-10-07 14:24:46 +00:00
account . Address , hotspotData . Address , hotspotData . Name ,
)
}
2021-09-26 03:38:16 +00:00
// collectHotspotActivityMetrics collect the total number of activities executed by a hotspot from the helium api
2021-10-07 14:24:46 +00:00
func ( e * Exporter ) collectHotspotActivityMetrics ( wg * sync . WaitGroup , ch chan <- prometheus . Metric , account * Account , hotspotData heliumapi . Hotspot ) {
2021-09-26 03:38:16 +00:00
defer wg . Done ( )
2021-09-26 04:42:34 +00:00
hotspotActivityForAddress , err := heliumapi . GetHotspotActivityCounts ( hotspotData . Address )
2021-09-26 03:38:16 +00:00
if err != nil {
2021-10-03 21:06:16 +00:00
log . Println ( err )
2021-09-26 03:38:16 +00:00
return
}
2021-09-26 03:53:20 +00:00
for accType , count := range * hotspotActivityForAddress {
2021-09-26 03:38:16 +00:00
ch <- prometheus . MustNewConstMetric (
hotspotActivity . Desc , hotspotActivity . Type , float64 ( count ) ,
account . Address , hotspotData . Address , hotspotData . Name , accType ,
)
}
}
// collectHotspotRewardsMetrics collect the total rewards accumulated by a hotspot from the helium api
2021-10-07 14:24:46 +00:00
func ( e * Exporter ) collectHotspotRewardsMetrics ( wg * sync . WaitGroup , ch chan <- prometheus . Metric , account * Account , hotspotData heliumapi . Hotspot ) {
2021-09-26 03:38:16 +00:00
defer wg . Done ( )
2021-10-14 19:24:06 +00:00
// We optimize our queries by asking the api only the activities we car about.
activityTypes := [ ] string {
"rewards_v1" ,
"rewards_v2" ,
}
hotspot , ok := account . Hotspots [ hotspotData . Address ]
if ! ok {
2021-12-24 20:46:04 +00:00
log . Fatalf ( "BUG: attempted to access hotspot %v before it was created" , hotspotData . Address )
return
2021-10-14 19:24:06 +00:00
}
2021-12-24 20:46:04 +00:00
hotspot . UpdateLock . Lock ( )
defer hotspot . UpdateLock . Unlock ( )
2021-10-14 19:24:06 +00:00
// We want to keep in memory the timestamp of the last activity we
// received from the api. We cannot do something naive like [lastscrape, now]
// because the api can take a few seconds to sync with the chain and
// we can miss some activities by doing it that way.
lastActivityTime := hotspot . LastUpdate . Unix ( )
updateLastActivityTime := func ( newTime int64 ) {
if lastActivityTime < newTime {
lastActivityTime = newTime
}
}
2021-12-24 17:45:17 +00:00
minTime := hotspot . LastUpdate
2021-10-14 19:24:06 +00:00
// Explicitly set max_time, to avoid issues with server-side caching
maxTime := time . Now ( )
activities , err := heliumapi . GetHotspotActivity ( hotspot . Address , activityTypes , & minTime , & maxTime )
2021-09-26 03:38:16 +00:00
if err != nil {
2021-10-03 21:06:16 +00:00
log . Println ( err )
2021-09-26 03:38:16 +00:00
return
}
2021-10-14 19:24:06 +00:00
for _ , activity := range activities . RewardsV1 {
2021-12-24 17:45:17 +00:00
if activity . Time > lastActivityTime {
for _ , reward := range activity . Rewards {
hotspot . RewardsTotal [ reward . Type ] += reward . Amount
}
updateLastActivityTime ( activity . Time )
2021-10-14 19:24:06 +00:00
}
}
for _ , activity := range activities . RewardsV2 {
2021-12-24 17:45:17 +00:00
if activity . Time > lastActivityTime {
for _ , reward := range activity . Rewards {
hotspot . RewardsTotal [ reward . Type ] += reward . Amount
}
updateLastActivityTime ( activity . Time )
2021-10-14 19:24:06 +00:00
}
}
hotspot . LastUpdate = time . Unix ( lastActivityTime , 0 )
for rType , rTotal := range hotspot . RewardsTotal {
ch <- prometheus . MustNewConstMetric (
hotspotRewardsHnt . Desc , hotspotRewardsHnt . Type , float64 ( rTotal ) / blockchain_hnt_factor ,
account . Address , hotspot . Address , hotspotData . Name , rType ,
)
}
account . Hotspots [ hotspot . Address ] = hotspot
2021-09-15 03:34:08 +00:00
}
2021-09-12 14:17:31 +00:00
func main ( ) {
2021-10-27 02:07:14 +00:00
var err error
fApiUrl := flag . String ( "apiUrl" , "https://helium-api.stakejoy.com" , "The helium api url" )
fApiTimeout := flag . String ( "apiTimeout" , "10s" , "The request timeout to the helium api." )
2021-11-14 15:44:39 +00:00
fApiRateLimit := flag . Int ( "maxApiRate" , 10 , "The maximum number of helium api calls per seconds." )
2021-09-25 19:09:40 +00:00
fHeliumAccounts := flag . String ( "accounts" , "" , "A comma-delimited list of helium accounts to scrape (optional)" )
2021-09-14 03:36:48 +00:00
fMetricsPath := flag . String ( "metricpath" , "/metrics" , "The metrics path" )
fListenAddress := flag . String ( "listenAddress" , "0.0.0.0" , "The http server listen address" )
2021-09-25 19:09:40 +00:00
fListenPort := flag . String ( "listenPort" , "9865" , "The http server listen port" )
2021-09-14 03:36:48 +00:00
flag . Parse ( )
2021-09-26 04:08:10 +00:00
heliumapi . ApiUrl = * fApiUrl
2021-11-14 15:44:39 +00:00
heliumapi . Limiter = rate . NewLimiter ( rate . Every ( time . Duration ( 1000 / ( * fApiRateLimit ) ) * time . Millisecond ) , * fApiRateLimit )
2021-10-27 02:07:14 +00:00
heliumapi . ApiTimeout , err = time . ParseDuration ( * fApiTimeout )
if err != nil {
2021-11-14 15:44:39 +00:00
log . Fatalf ( "failed to parse apiTimeout: %s" , err . Error ( ) )
2021-10-27 02:07:14 +00:00
}
2021-09-15 03:34:08 +00:00
heliumAccounts := strings . Split ( * fHeliumAccounts , "," )
2021-09-14 03:36:48 +00:00
serverAddr := * fListenAddress + ":" + * fListenPort
2021-09-13 03:25:47 +00:00
2021-09-14 03:36:48 +00:00
e , err := NewExporter ( heliumAccounts )
2021-09-13 03:25:47 +00:00
if err != nil {
log . Fatalf ( "failed to start exporter: %s" , err . Error ( ) )
}
r := prometheus . NewRegistry ( )
r . MustRegister ( e )
// setup http route
http . HandleFunc ( "/" , func ( w http . ResponseWriter , r * http . Request ) {
_ , _ = w . Write ( [ ] byte ( ` < html >
< head > < title > Helium blockchain exporter < / title > < / head >
< body >
< h1 > Helium blockchain exporter < / h1 >
2021-09-14 03:36:48 +00:00
< p > < a href = ' ` + *fMetricsPath + ` ' > Metrics < / a > < / p >
2021-09-13 03:25:47 +00:00
< / body >
< / html > ` ) )
} )
2021-09-14 03:36:48 +00:00
http . Handle ( * fMetricsPath , promhttp . HandlerFor ( r , promhttp . HandlerOpts { } ) )
2021-10-03 21:06:16 +00:00
log . Printf ( "listening on %v\n" , serverAddr )
2021-09-26 04:08:10 +00:00
if err = http . ListenAndServe ( serverAddr , nil ) ; err != nil {
2021-10-03 21:06:16 +00:00
log . Println ( err )
2021-09-26 04:08:10 +00:00
os . Exit ( 1 )
}
2021-09-12 14:17:31 +00:00
}