1
0
Fork 0
This repository has been archived on 2024-07-04. You can view files and clone it, but cannot push or open issues or pull requests.
helium-blockchain-exporter/helium-blockchain-exporter.go

485 lines
14 KiB
Go

package main
import (
"flag"
"fmt"
"log"
"net/http"
"strconv"
"strings"
"time"
"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 []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
}
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_total"),
"The total number of validators.",
nil, nil,
),
prometheus.CounterValue,
}
statsOuis = metricInfo{
prometheus.NewDesc(
prometheus.BuildFQName(namespace, "stats", "ouis_total"),
"The total number of organization unique identifiers.",
nil, nil,
),
prometheus.CounterValue,
}
statsHotspotsDataOnly = metricInfo{
prometheus.NewDesc(
prometheus.BuildFQName(namespace, "stats", "hotspots_dataonly_total"),
"The total number of data only hotspots.",
nil, nil,
),
prometheus.CounterValue,
}
statsBlocks = metricInfo{
prometheus.NewDesc(
prometheus.BuildFQName(namespace, "stats", "blocks_total"),
"The total height/number of blocks in the blockchain.",
nil, nil,
),
prometheus.CounterValue,
}
statsChallenges = metricInfo{
prometheus.NewDesc(
prometheus.BuildFQName(namespace, "stats", "challenges_total"),
"The total number of challenges.",
nil, nil,
),
prometheus.CounterValue,
}
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_total"),
"The total 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_total"),
"The total number of hotspots.",
nil, nil,
),
prometheus.CounterValue,
}
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 an 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", "rewards_hnt"),
"The number of HNT token rewarded to an 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
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,
}
)
func bool2Float64(b bool) float64 {
if b {
return 1.0
}
return 0.0
}
// 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
ch <- hotspotUp.Desc
ch <- hotspotListening.Desc
ch <- hotspotBlockHeight.Desc
ch <- hotspotRewardScale.Desc
ch <- hotspotGeocodeInfo.Desc
ch <- hotspotAntennaInfo.Desc
ch <- hotspotActivity.Desc
ch <- hotspotRewardsHnt.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)
e.collectHotspotMetrics(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 *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)
if err != nil {
fmt.Println(err)
return
}
ch <- prometheus.MustNewConstMetric(
accountBalanceHnt.Desc, accountBalanceHnt.Type, float64(accountForAddress.Data.Balance),
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,
)
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)
}
}
// 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),
)
}
}
// NewExporter returns an initialized Exporter
func NewExporter(accountHashs []string) (*Exporter, error) {
accounts := make([]Account, 0)
for _, accountHash := range accountHashs {
if accountHash != "" {
accounts = append(accounts, NewAccount(accountHash))
}
}
return &Exporter{
Accounts: accounts,
}, nil
}
func NewAccount(hash string) Account {
return Account{
hash: hash,
lastRewardsUpdate: time.Now(),
}
}
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 := 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(`<html>
<head><title>Helium blockchain exporter</title></head>
<body>
<h1>Helium blockchain exporter</h1>
<p><a href='` + *fMetricsPath + `'>Metrics</a></p>
</body>
</html>`))
})
http.Handle(*fMetricsPath, promhttp.HandlerFor(r, promhttp.HandlerOpts{}))
fmt.Printf("listening on %v\n", serverAddr)
http.ListenAndServe(serverAddr, nil)
}