package main import ( "flag" "fmt" "log" "net/http" "strings" "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 { Accounts []string } const ( namespace = "helium" ) var ( // lables commonAccountLabels = []string{"account"} commonHotspotLabels = append(commonAccountLabels, "hotspot", "hotspot_name") // 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( prometheus.BuildFQName(namespace, "stats", "validators"), "The number of validators.", nil, nil, ), prometheus.GaugeValue, } statsOuis = metricInfo{ prometheus.NewDesc( prometheus.BuildFQName(namespace, "stats", "ouis"), "The number of organization unique identifiers.", nil, nil, ), prometheus.GaugeValue, } statsHotspotsDataOnly = metricInfo{ prometheus.NewDesc( prometheus.BuildFQName(namespace, "stats", "hotspots_dataonly"), "The number of data only hotspots.", nil, nil, ), prometheus.GaugeValue, } statsBlocks = metricInfo{ prometheus.NewDesc( prometheus.BuildFQName(namespace, "stats", "blocks"), "The height/number of blocks in the blockchain.", nil, nil, ), prometheus.GaugeValue, } statsChallenges = metricInfo{ prometheus.NewDesc( prometheus.BuildFQName(namespace, "stats", "challenges"), "The number of challenges.", nil, nil, ), prometheus.GaugeValue, } 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( prometheus.BuildFQName(namespace, "stats", "consensus_groups"), "The number of consensus groups.", 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( prometheus.BuildFQName(namespace, "stats", "hotspots"), "The number of hotspots.", nil, nil, ), prometheus.GaugeValue, } statsTokenSupply = metricInfo{ prometheus.NewDesc( prometheus.BuildFQName(namespace, "stats", "token_supply"), "The total supply of HNT tokens in circulation.", nil, nil, ), prometheus.GaugeValue, } // helium account metrics accountBalanceHnt = metricInfo{ prometheus.NewDesc( prometheus.BuildFQName(namespace, "account", "balance_hnt"), "The number of HNT token owned by the account.", commonAccountLabels, nil, ), prometheus.GaugeValue, } accountActivity = metricInfo{ prometheus.NewDesc( prometheus.BuildFQName(namespace, "account", "activity_total"), "The total number of time an activity occurred in an account.", append(commonAccountLabels, "type"), nil, ), prometheus.CounterValue, } accountRewardsHnt = metricInfo{ prometheus.NewDesc( prometheus.BuildFQName(namespace, "account", "reward_hnt"), "The number of HNT token rewarded to this account.", commonAccountLabels, nil, ), prometheus.GaugeValue, } 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, } // helium hotspot metrics ) // 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 ch <- accountBalanceHnt.Desc ch <- accountActivity.Desc ch <- accountRewardsHnt.Desc ch <- accountDepositsHnt.Desc ch <- accountWithdrawalsHnt.Desc } // 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) for _, account := range e.Accounts { e.collectAccountMetrics(ch, account) } } // collectOracleMetrics collect metrics in the oracle group from the helium api 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, ) } // collectStatsMetrics collect metrics in the stats group from the helium api 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, ) } // collectStatsMetrics collect metrics in the account group from the helium api func (e *Exporter) collectAccountMetrics(ch chan<- prometheus.Metric, account string) { accountForAddress, err := heliumapi.GetAccountForAddress(account) if err != nil { fmt.Println(err) return } ch <- prometheus.MustNewConstMetric( accountBalanceHnt.Desc, accountBalanceHnt.Type, float64(accountForAddress.Data.Balance), account, ) } // NewExporter returns an initialized Exporter func NewExporter(accounts []string) (*Exporter, error) { return &Exporter{ Accounts: accounts, }, nil } func main() { 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() heliumAccounts := make([]string, 0) if *fHeliumAccounts != "" { heliumAccounts = strings.Split(*fHeliumAccounts, ",") } serverAddr := *fListenAddress + ":" + *fListenPort e, err := NewExporter(heliumAccounts) 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(` Helium blockchain exporter

Helium blockchain exporter

Metrics

`)) }) http.Handle(*fMetricsPath, promhttp.HandlerFor(r, promhttp.HandlerOpts{})) fmt.Printf("exporter listening on %v\n", serverAddr) http.ListenAndServe(serverAddr, nil) }