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
"fmt"
"log"
"net/http"
2021-09-15 05:04:32 +00:00
"strconv"
2021-09-14 03:36:48 +00:00
"strings"
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"
)
// 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-15 03:34:08 +00:00
Accounts [ ] Account
}
// Account represents a helium account
type Account struct {
hotspots [ ] Hotspot
hash string
lastRewardsUpdate time . Time
}
// Hotspot represent a helium hotspot
type Hotspot struct {
hash string
lastRewardsUpdate time . Time
2021-09-13 03:25:47 +00:00
}
const (
namespace = "helium"
)
var (
2021-09-14 03:36:48 +00:00
// lables
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-15 03:34:08 +00:00
prometheus . BuildFQName ( namespace , "stats" , "validators_total" ) ,
"The total number of validators." ,
2021-09-13 03:25:47 +00:00
nil , nil ,
) ,
2021-09-15 03:34:08 +00:00
prometheus . CounterValue ,
2021-09-13 03:25:47 +00:00
}
statsOuis = metricInfo {
prometheus . NewDesc (
2021-09-15 03:34:08 +00:00
prometheus . BuildFQName ( namespace , "stats" , "ouis_total" ) ,
"The total number of organization unique identifiers." ,
2021-09-13 03:25:47 +00:00
nil , nil ,
) ,
2021-09-15 03:34:08 +00:00
prometheus . CounterValue ,
2021-09-13 03:25:47 +00:00
}
statsHotspotsDataOnly = metricInfo {
prometheus . NewDesc (
2021-09-15 03:34:08 +00:00
prometheus . BuildFQName ( namespace , "stats" , "hotspots_dataonly_total" ) ,
"The total number of data only hotspots." ,
2021-09-13 03:25:47 +00:00
nil , nil ,
) ,
2021-09-15 03:34:08 +00:00
prometheus . CounterValue ,
2021-09-13 03:25:47 +00:00
}
statsBlocks = metricInfo {
prometheus . NewDesc (
2021-09-15 03:34:08 +00:00
prometheus . BuildFQName ( namespace , "stats" , "blocks_total" ) ,
"The total height/number of blocks in the blockchain." ,
2021-09-13 03:25:47 +00:00
nil , nil ,
) ,
2021-09-15 03:34:08 +00:00
prometheus . CounterValue ,
2021-09-13 03:25:47 +00:00
}
statsChallenges = metricInfo {
prometheus . NewDesc (
2021-09-15 03:34:08 +00:00
prometheus . BuildFQName ( namespace , "stats" , "challenges_total" ) ,
"The total number of challenges." ,
2021-09-13 03:25:47 +00:00
nil , nil ,
) ,
2021-09-15 03:34:08 +00:00
prometheus . CounterValue ,
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-15 03:34:08 +00:00
prometheus . BuildFQName ( namespace , "stats" , "consensus_groups_total" ) ,
"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-15 03:34:08 +00:00
prometheus . BuildFQName ( namespace , "stats" , "hotspots_total" ) ,
"The total number of hotspots." ,
2021-09-13 03:25:47 +00:00
nil , nil ,
) ,
2021-09-15 03:34:08 +00:00
prometheus . CounterValue ,
2021-09-13 03:25:47 +00:00
}
statsTokenSupply = metricInfo {
prometheus . NewDesc (
prometheus . BuildFQName ( namespace , "stats" , "token_supply" ) ,
"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-15 03:34:08 +00:00
prometheus . BuildFQName ( namespace , "account" , "rewards_hnt" ) ,
2021-09-15 05:04:32 +00:00
"The number of HNT token rewarded to an account." ,
2021-09-14 03:36:48 +00:00
commonAccountLabels , nil ,
) ,
prometheus . GaugeValue ,
}
2021-09-15 03:34:08 +00:00
// accountDepositsHnt = metricInfo{
// prometheus.NewDesc(
// prometheus.BuildFQName(namespace, "account", "deposits_hnt_total"),
// "The number of HNT token deposited to this account.",
// commonAccountLabels, nil,
// ),
// prometheus.CounterValue,
// }
// accountWithdrawalsHnt = metricInfo{
// prometheus.NewDesc(
// prometheus.BuildFQName(namespace, "account", "withdrawals_hnt_total"),
// "The number of HNT token withdrawn from this account.",
// 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 ,
}
hotspotListening = metricInfo {
prometheus . NewDesc (
prometheus . BuildFQName ( namespace , "hotspot" , "listening" ) ,
"Whether a hotspot is listening (not relayed)." ,
commonHotspotLabels , nil ,
) ,
prometheus . GaugeValue ,
}
hotspotBlockHeight = metricInfo {
prometheus . NewDesc (
prometheus . BuildFQName ( namespace , "hotspot" , "block_height" ) ,
"The block height of a Hotspot. Check on the hotspot itself for the most recent data." ,
commonHotspotLabels , nil ,
) ,
prometheus . CounterValue ,
}
hotspotRewardScale = metricInfo {
prometheus . NewDesc (
prometheus . BuildFQName ( namespace , "hotspot" , "reward_scale" ) ,
"The reward scale of a hotspot." ,
commonHotspotLabels , nil ,
) ,
prometheus . GaugeValue ,
}
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" ) ,
"Information on the location of a hotspot." ,
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 (
prometheus . BuildFQName ( namespace , "hotspot" , "rewards_hnt" ) ,
"The number of HNT token rewarded to a hotspot." ,
commonHotspotLabels , nil ,
) ,
prometheus . GaugeValue ,
}
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-15 03:34:08 +00:00
// ch <- accountDepositsHnt.Desc
// ch <- accountWithdrawalsHnt.Desc
2021-09-15 05:04:32 +00:00
ch <- hotspotUp . Desc
ch <- hotspotListening . Desc
ch <- hotspotBlockHeight . Desc
ch <- hotspotRewardScale . Desc
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 ) {
e . collectOracleMetrics ( ch )
e . collectStatsMetrics ( ch )
2021-09-14 03:36:48 +00:00
for _ , account := range e . Accounts {
2021-09-15 03:34:08 +00:00
e . collectAccountMetrics ( ch , & account )
2021-09-15 05:04:32 +00:00
e . collectHotspotMetrics ( ch , & account )
2021-09-14 03:36:48 +00:00
}
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-13 03:25:47 +00:00
func ( e * Exporter ) collectOracleMetrics ( ch chan <- prometheus . Metric ) {
currentOraclePrice , err := heliumapi . GetCurrentOraclePrice ( )
if err != nil {
fmt . Println ( err )
return
}
ch <- prometheus . MustNewConstMetric (
oraclePrice . Desc , oraclePrice . Type , float64 ( currentOraclePrice . Data . Price ) / 100000000 ,
)
}
2021-09-14 03:36:48 +00:00
// collectStatsMetrics collect metrics in the stats group from the helium api
2021-09-13 03:25:47 +00:00
func ( e * Exporter ) collectStatsMetrics ( ch chan <- prometheus . Metric ) {
blockchainStats , err := heliumapi . GetBlockchainStats ( )
if err != nil {
fmt . Println ( err )
return
}
ch <- prometheus . MustNewConstMetric (
statsValidators . Desc , statsValidators . Type , float64 ( blockchainStats . Data . Counts . Validators ) ,
)
ch <- prometheus . MustNewConstMetric (
statsOuis . Desc , statsOuis . Type , float64 ( blockchainStats . Data . Counts . Ouis ) ,
)
ch <- prometheus . MustNewConstMetric (
statsHotspotsDataOnly . Desc , statsHotspotsDataOnly . Type , float64 ( blockchainStats . Data . Counts . HotspotsDataonly ) ,
)
ch <- prometheus . MustNewConstMetric (
statsBlocks . Desc , statsBlocks . Type , float64 ( blockchainStats . Data . Counts . Blocks ) ,
)
ch <- prometheus . MustNewConstMetric (
statsChallenges . Desc , statsChallenges . Type , float64 ( blockchainStats . Data . Counts . Challenges ) ,
)
ch <- prometheus . MustNewConstMetric (
statsCities . Desc , statsCities . Type , float64 ( blockchainStats . Data . Counts . Cities ) ,
)
ch <- prometheus . MustNewConstMetric (
statsConsensusGroups . Desc , statsConsensusGroups . Type , float64 ( blockchainStats . Data . Counts . ConsensusGroups ) ,
)
ch <- prometheus . MustNewConstMetric (
statsCountries . Desc , statsCountries . Type , float64 ( blockchainStats . Data . Counts . Countries ) ,
)
ch <- prometheus . MustNewConstMetric (
statsHotspots . Desc , statsHotspots . Type , float64 ( blockchainStats . Data . Counts . Hotspots ) ,
)
ch <- prometheus . MustNewConstMetric (
statsTokenSupply . Desc , statsTokenSupply . Type , blockchainStats . Data . TokenSupply ,
)
}
2021-09-14 03:36:48 +00:00
// collectStatsMetrics collect metrics in the account group from the helium api
2021-09-15 03:34:08 +00:00
func ( e * Exporter ) collectAccountMetrics ( ch chan <- prometheus . Metric , account * Account ) {
accountForAddress , err := heliumapi . GetAccountForAddress ( account . hash )
if err != nil {
fmt . Println ( err )
return
}
accountActivityForAddress , err := heliumapi . GetActivityCountsForAccount ( account . hash )
if err != nil {
fmt . Println ( err )
return
}
accountRewardTotalsForAddress , err := heliumapi . GetRewardTotalsForAccount ( account . hash , & account . lastRewardsUpdate , nil )
2021-09-14 03:51:47 +00:00
if err != nil {
fmt . Println ( err )
return
}
ch <- prometheus . MustNewConstMetric (
accountBalanceHnt . Desc , accountBalanceHnt . Type , float64 ( accountForAddress . Data . Balance ) ,
2021-09-15 03:34:08 +00:00
account . hash ,
)
for accType , count := range accountActivityForAddress . Data {
ch <- prometheus . MustNewConstMetric (
accountActivity . Desc , accountActivity . Type , float64 ( count ) ,
account . hash , accType ,
)
}
ch <- prometheus . MustNewConstMetric (
accountRewardsHnt . Desc , accountRewardsHnt . Type , accountRewardTotalsForAddress . Data . Sum ,
account . hash ,
2021-09-14 03:51:47 +00:00
)
2021-09-15 03:34:08 +00:00
account . lastRewardsUpdate , err = time . Parse ( time . RFC3339 , accountRewardTotalsForAddress . Meta . MaxTime )
if err != nil {
fmt . Printf ( "failed to parse time \"%v\", value of %v will be bad: %v\n" , accountRewardTotalsForAddress . Meta . MaxTime , accountRewardsHnt . Desc . String ( ) , err )
}
2021-09-14 03:36:48 +00:00
}
2021-09-15 05:04:32 +00:00
// collectStatsMetrics collect metrics in the hotspot group from the helium api
func ( e * Exporter ) collectHotspotMetrics ( ch chan <- prometheus . Metric , account * Account ) {
hotspotsForAddress , err := heliumapi . GetHotspotsForAccount ( account . hash )
if err != nil {
fmt . Println ( err )
return
}
for _ , hotspotData := range hotspotsForAddress . Data {
ch <- prometheus . MustNewConstMetric (
hotspotUp . Desc , hotspotUp . Type , bool2Float64 ( hotspotData . Status . Online == "online" ) ,
account . hash , hotspotData . Address , hotspotData . Name ,
)
ch <- prometheus . MustNewConstMetric (
hotspotListening . Desc , hotspotListening . Type , bool2Float64 ( len ( hotspotData . Status . ListenAddrs ) != 0 ) ,
account . hash , hotspotData . Address , hotspotData . Name ,
)
ch <- prometheus . MustNewConstMetric (
hotspotBlockHeight . Desc , hotspotBlockHeight . Type , float64 ( hotspotData . Status . Height ) ,
account . hash , hotspotData . Address , hotspotData . Name ,
)
ch <- prometheus . MustNewConstMetric (
hotspotRewardScale . Desc , hotspotRewardScale . Type , float64 ( hotspotData . RewardScale ) ,
account . hash , hotspotData . Address , hotspotData . Name ,
)
ch <- prometheus . MustNewConstMetric (
hotspotGeocodeInfo . Desc , hotspotGeocodeInfo . Type , 1.0 ,
account . hash , 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 . hash , hotspotData . Address , hotspotData . Name , strconv . Itoa ( hotspotData . Gain ) , strconv . Itoa ( hotspotData . Elevation ) ,
)
}
}
2021-09-13 03:25:47 +00:00
// NewExporter returns an initialized Exporter
2021-09-15 03:34:08 +00:00
func NewExporter ( accountHashs [ ] string ) ( * Exporter , error ) {
accounts := make ( [ ] Account , 0 )
for _ , accountHash := range accountHashs {
if accountHash != "" {
accounts = append ( accounts , NewAccount ( accountHash ) )
}
}
2021-09-14 03:36:48 +00:00
return & Exporter {
Accounts : accounts ,
} , nil
2021-09-13 03:25:47 +00:00
}
2021-09-12 14:17:31 +00:00
2021-09-15 03:34:08 +00:00
func NewAccount ( hash string ) Account {
return Account {
hash : hash ,
lastRewardsUpdate : time . Now ( ) ,
}
}
2021-09-12 14:17:31 +00:00
func main ( ) {
2021-09-14 03:36:48 +00:00
fHeliumAccounts := flag . String ( "accounts" , "" , "A comma-delimited list of helium accounts to scrape." )
fMetricsPath := flag . String ( "metricpath" , "/metrics" , "The metrics path" )
fListenAddress := flag . String ( "listenAddress" , "0.0.0.0" , "The http server listen address" )
fListenPort := flag . String ( "listenPort" , "9111" , "The http server listen port" )
flag . Parse ( )
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-09-15 03:34:08 +00:00
fmt . Printf ( "listening on %v\n" , serverAddr )
2021-09-14 03:36:48 +00:00
http . ListenAndServe ( serverAddr , nil )
2021-09-12 14:17:31 +00:00
}