From 7f8b73055e0bd5ae8fbb86da169b13f1bc5f1c82 Mon Sep 17 00:00:00 2001 From: Massaki Archambault Date: Fri, 17 Sep 2021 16:45:01 -0400 Subject: [PATCH] add hotspot activity and rewards metrics --- helium-blockchain-exporter.go | 84 +++++++++++++++++++++-------------- heliumapi/accounts.go | 31 +++---------- heliumapi/hotspots.go | 73 ++++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+), 60 deletions(-) create mode 100644 heliumapi/hotspots.go diff --git a/helium-blockchain-exporter.go b/helium-blockchain-exporter.go index f499b51..a0a7b18 100644 --- a/helium-blockchain-exporter.go +++ b/helium-blockchain-exporter.go @@ -27,15 +27,14 @@ type Exporter struct { // Account represents a helium account type Account struct { - hotspots []Hotspot - hash string - lastRewardsUpdate time.Time + hotspots []Hotspot + hash string } // Hotspot represent a helium hotspot type Hotspot struct { - hash string - lastRewardsUpdate time.Time + name string + hash string } const ( @@ -160,11 +159,11 @@ var ( } accountRewardsHnt = metricInfo{ prometheus.NewDesc( - prometheus.BuildFQName(namespace, "account", "rewards_hnt"), + prometheus.BuildFQName(namespace, "account", "rewards_hnt_total"), "The number of HNT token rewarded to an account.", commonAccountLabels, nil, ), - prometheus.GaugeValue, + prometheus.CounterValue, } // accountDepositsHnt = metricInfo{ // prometheus.NewDesc( @@ -192,25 +191,25 @@ var ( ), prometheus.GaugeValue, } - hotspotListening = metricInfo{ + hotspotRelayed = metricInfo{ prometheus.NewDesc( - prometheus.BuildFQName(namespace, "hotspot", "listening"), - "Whether a hotspot is listening (not relayed).", + prometheus.BuildFQName(namespace, "hotspot", "relayed"), + "Whether a hotspot is relayed.", commonHotspotLabels, nil, ), prometheus.GaugeValue, } - hotspotBlockHeight = metricInfo{ + hotspotBlocks = 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.", + prometheus.BuildFQName(namespace, "hotspot", "blocks_total"), + "The block height of a hotspot. Check on the hotspot itself for the most recent data.", commonHotspotLabels, nil, ), prometheus.CounterValue, } - hotspotRewardScale = metricInfo{ + hotspotRewardsScale = metricInfo{ prometheus.NewDesc( - prometheus.BuildFQName(namespace, "hotspot", "reward_scale"), + prometheus.BuildFQName(namespace, "hotspot", "rewards_scale"), "The reward scale of a hotspot.", commonHotspotLabels, nil, ), @@ -227,7 +226,7 @@ var ( hotspotAntennaInfo = metricInfo{ prometheus.NewDesc( prometheus.BuildFQName(namespace, "hotspot", "antenna_info"), - "Information on the location of a hotspot.", + "Information on the antenna of a hotspot.", append(commonHotspotLabels, "gain", "elevation"), nil, ), prometheus.GaugeValue, @@ -242,11 +241,11 @@ var ( } hotspotRewardsHnt = metricInfo{ prometheus.NewDesc( - prometheus.BuildFQName(namespace, "hotspot", "rewards_hnt"), + prometheus.BuildFQName(namespace, "hotspot", "rewards_hnt_total"), "The number of HNT token rewarded to a hotspot.", commonHotspotLabels, nil, ), - prometheus.GaugeValue, + prometheus.CounterValue, } ) @@ -280,9 +279,9 @@ func (e *Exporter) Describe(ch chan<- *prometheus.Desc) { // ch <- accountWithdrawalsHnt.Desc ch <- hotspotUp.Desc - ch <- hotspotListening.Desc - ch <- hotspotBlockHeight.Desc - ch <- hotspotRewardScale.Desc + ch <- hotspotRelayed.Desc + ch <- hotspotBlocks.Desc + ch <- hotspotRewardsScale.Desc ch <- hotspotGeocodeInfo.Desc ch <- hotspotAntennaInfo.Desc ch <- hotspotActivity.Desc @@ -294,9 +293,9 @@ func (e *Exporter) Describe(ch chan<- *prometheus.Desc) { 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) + for i := range e.Accounts { + e.collectAccountMetrics(ch, &e.Accounts[i]) + e.collectHotspotMetrics(ch, &e.Accounts[i]) } } @@ -367,7 +366,7 @@ func (e *Exporter) collectAccountMetrics(ch chan<- prometheus.Metric, account *A return } - accountRewardTotalsForAddress, err := heliumapi.GetRewardTotalsForAccount(account.hash, &account.lastRewardsUpdate, nil) + accountRewardTotalsForAddress, err := heliumapi.GetRewardTotalsForAccount(account.hash, &time.Time{}, nil) if err != nil { fmt.Println(err) return @@ -387,10 +386,6 @@ func (e *Exporter) collectAccountMetrics(ch chan<- prometheus.Metric, account *A 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 @@ -402,20 +397,32 @@ func (e *Exporter) collectHotspotMetrics(ch chan<- prometheus.Metric, account *A } for _, hotspotData := range hotspotsForAddress.Data { + hotspotActivityForAddress, err := heliumapi.GetHotspotActivityCount(hotspotData.Address) + if err != nil { + fmt.Println(err) + return + } + + hotspotRewardTotalsForAddress, err := heliumapi.GetRewardsTotalForHotspot(hotspotData.Address, &time.Time{}, nil) + if err != nil { + fmt.Println(err) + return + } + 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), + hotspotRelayed.Desc, hotspotRelayed.Type, bool2Float64(len(hotspotData.Status.ListenAddrs) > 0 && strings.HasPrefix(hotspotData.Status.ListenAddrs[0], "/p2p")), account.hash, hotspotData.Address, hotspotData.Name, ) ch <- prometheus.MustNewConstMetric( - hotspotBlockHeight.Desc, hotspotBlockHeight.Type, float64(hotspotData.Status.Height), + hotspotBlocks.Desc, hotspotBlocks.Type, float64(hotspotData.Status.Height), account.hash, hotspotData.Address, hotspotData.Name, ) ch <- prometheus.MustNewConstMetric( - hotspotRewardScale.Desc, hotspotRewardScale.Type, float64(hotspotData.RewardScale), + hotspotRewardsScale.Desc, hotspotRewardsScale.Type, float64(hotspotData.RewardScale), account.hash, hotspotData.Address, hotspotData.Name, ) ch <- prometheus.MustNewConstMetric( @@ -426,6 +433,16 @@ func (e *Exporter) collectHotspotMetrics(ch chan<- prometheus.Metric, account *A hotspotAntennaInfo.Desc, hotspotAntennaInfo.Type, 1.0, account.hash, hotspotData.Address, hotspotData.Name, strconv.Itoa(hotspotData.Gain), strconv.Itoa(hotspotData.Elevation), ) + for accType, count := range hotspotActivityForAddress.Data { + ch <- prometheus.MustNewConstMetric( + hotspotActivity.Desc, hotspotActivity.Type, float64(count), + account.hash, hotspotData.Address, hotspotData.Name, accType, + ) + } + ch <- prometheus.MustNewConstMetric( + hotspotRewardsHnt.Desc, hotspotRewardsHnt.Type, hotspotRewardTotalsForAddress.Data.Sum, + account.hash, hotspotData.Address, hotspotData.Name, + ) } } @@ -444,8 +461,7 @@ func NewExporter(accountHashs []string) (*Exporter, error) { func NewAccount(hash string) Account { return Account{ - hash: hash, - lastRewardsUpdate: time.Now(), + hash: hash, } } diff --git a/heliumapi/accounts.go b/heliumapi/accounts.go index 741c275..94c85cd 100644 --- a/heliumapi/accounts.go +++ b/heliumapi/accounts.go @@ -19,37 +19,16 @@ type Account struct { } `json:"data"` } -type ActivityCounts struct { - Data map[string]int -} - -type RewardTotal struct { - Meta struct { - MinTime string `json:"min_time"` - MaxTime string `json:"max_time"` - } `json:"meta"` - - Data struct { - Total float64 `json:"total"` - Sum float64 `json:"sum"` - Stddev float64 `json:"stddev"` - Min float64 `json:"min"` - Median float64 `json:"median"` - Max float64 `json:"max"` - Avg float64 `json:"avg"` - } `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 + Timestamp string `json:"timestamp"` + Online string `json:"online"` + ListenAddrs []string `json:"listen_addrs"` + Height int `json:"height"` } `json:"status"` RewardScale float64 `json:"reward_scale"` Payer string `json:"payer"` @@ -125,7 +104,7 @@ func GetRewardTotalsForAccount(account string, minTime *time.Time, maxTime *time params["min_time"] = minTime.UTC().Format("2006-01-02T15:04:05Z") } if maxTime != nil { - params["max_time"] = minTime.UTC().Format("2006-01-02T15:04:05Z") + params["max_time"] = maxTime.UTC().Format("2006-01-02T15:04:05Z") } // query the api diff --git a/heliumapi/hotspots.go b/heliumapi/hotspots.go new file mode 100644 index 0000000..8de129f --- /dev/null +++ b/heliumapi/hotspots.go @@ -0,0 +1,73 @@ +package heliumapi + +import ( + "encoding/json" + "fmt" + "time" +) + +type ActivityCounts struct { + Data map[string]int +} + +type RewardTotal struct { + Meta struct { + MinTime string `json:"min_time"` + MaxTime string `json:"max_time"` + } `json:"meta"` + + Data struct { + Total float64 `json:"total"` + Sum float64 `json:"sum"` + Stddev float64 `json:"stddev"` + Min float64 `json:"min"` + Median float64 `json:"median"` + Max float64 `json:"max"` + Avg float64 `json:"avg"` + } `json:"data"` +} + +func GetHotspotActivityCount(hotspot string) (*ActivityCounts, error) { + path := "/v1/hotspots/" + hotspot + "/activity/count" + + // query the api + respBody, err := getHeliumApi(path, nil) + if err != nil { + return nil, err + } + + // unmarshal the response + respobject := ActivityCounts{} + 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 +} + +func GetRewardsTotalForHotspot(hotspot string, minTime *time.Time, maxTime *time.Time) (*RewardTotal, error) { + path := "/v1/hotspots/" + hotspot + "/rewards/sum" + params := map[string]string{} + if minTime != nil { + params["min_time"] = minTime.UTC().Format("2006-01-02T15:04:05Z") + } + if maxTime != nil { + params["max_time"] = maxTime.UTC().Format("2006-01-02T15:04:05Z") + } + + // query the api + respBody, err := getHeliumApi(path, ¶ms) + if err != nil { + return nil, err + } + + // unmarshal the response + respobject := RewardTotal{} + 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 +}