From cc251b10411865b5369e707899e5cc57c2633493 Mon Sep 17 00:00:00 2001 From: Massaki Archambault Date: Wed, 15 Sep 2021 01:04:32 -0400 Subject: [PATCH] add hotspot metrics --- helium-blockchain-exporter.go | 124 +++++++++++++++++++++++++++++++++- heliumapi/accounts.go | 61 ++++++++++++++++- 2 files changed, 180 insertions(+), 5 deletions(-) diff --git a/helium-blockchain-exporter.go b/helium-blockchain-exporter.go index 5bd8081..f499b51 100644 --- a/helium-blockchain-exporter.go +++ b/helium-blockchain-exporter.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "net/http" + "strconv" "strings" "time" @@ -144,7 +145,7 @@ var ( accountBalanceHnt = metricInfo{ prometheus.NewDesc( prometheus.BuildFQName(namespace, "account", "balance_hnt"), - "The number of HNT token owned by the account.", + "The number of HNT token owned by an account.", commonAccountLabels, nil, ), prometheus.GaugeValue, @@ -160,7 +161,7 @@ var ( accountRewardsHnt = metricInfo{ prometheus.NewDesc( prometheus.BuildFQName(namespace, "account", "rewards_hnt"), - "The number of HNT token rewarded to this account.", + "The number of HNT token rewarded to an account.", commonAccountLabels, nil, ), prometheus.GaugeValue, @@ -182,9 +183,80 @@ var ( // prometheus.CounterValue, // } -// helium hotspot metrics + // 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) { @@ -206,6 +278,15 @@ func (e *Exporter) Describe(ch chan<- *prometheus.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. @@ -215,6 +296,7 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) { e.collectStatsMetrics(ch) for _, account := range e.Accounts { e.collectAccountMetrics(ch, &account) + e.collectHotspotMetrics(ch, &account) } } @@ -311,6 +393,42 @@ func (e *Exporter) collectAccountMetrics(ch chan<- prometheus.Metric, account *A } } +// 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) diff --git a/heliumapi/accounts.go b/heliumapi/accounts.go index 0a87426..741c275 100644 --- a/heliumapi/accounts.go +++ b/heliumapi/accounts.go @@ -40,6 +40,46 @@ type RewardTotal struct { } `json:"data"` } +type AccountHotspots struct { + Data []struct { + Lng float64 `json:"lng"` + Lat float64 `json:"lat"` + TimestampAdded string `json:"timestamp_added"` + Status struct { + Timestamp string `json:"timestamp"` + Online string `json:"online"` + ListenAddrs []string + Height int + } `json:"status"` + RewardScale float64 `json:"reward_scale"` + Payer string `json:"payer"` + Owner string `json:"owner"` + Nonce int `json:"nonce"` + Name string `json:"name"` + Mode string `json:"mode"` + LocationHex string `json:"location_hex"` + Location string `json:"location"` + LastPocChallenge int `json:"last_poc_challenge"` + LastChangeBlock int `json:"last_change_block"` + Geocode struct { + ShortStreet string `json:"short_street"` + ShortState string `json:"short_state"` + ShortCountry string `json:"short_country"` + ShortCity string `json:"short_city"` + LongStreet string `json:"long_street"` + LongState string `json:"long_state"` + LongCountry string `json:"long_country"` + LongCity string `json:"long_city"` + CityID string `json:"city_id"` + } `json:"geocode"` + Gain int `json:"gain"` + Elevation int `json:"elevation"` + BlockAdded int `json:"block_added"` + Block int `json:"block"` + Address string `json:"address"` + } `json:"data"` +} + func GetAccountForAddress(account string) (*Account, error) { path := "/v1/accounts/" + account @@ -76,7 +116,6 @@ func GetActivityCountsForAccount(account string) (*ActivityCounts, error) { } return &respobject, nil - } func GetRewardTotalsForAccount(account string, minTime *time.Time, maxTime *time.Time) (*RewardTotal, error) { @@ -103,5 +142,23 @@ func GetRewardTotalsForAccount(account string, minTime *time.Time, maxTime *time } return &respobject, nil - +} + +func GetHotspotsForAccount(account string) (*AccountHotspots, error) { + path := "/v1/accounts/" + account + "/hotspots" + + // query the api + respBody, err := getHeliumApi(path, nil) + if err != nil { + return nil, err + } + + // unmarshal the response + respobject := AccountHotspots{} + err = json.Unmarshal(respBody, &respobject) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal response from path %v: %v", path, err) + } + + return &respobject, nil }