-
Notifications
You must be signed in to change notification settings - Fork 204
/
metricsProvider.go
138 lines (115 loc) · 3.3 KB
/
metricsProvider.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package provider
import (
"encoding/json"
"io/ioutil"
"net/http"
"strings"
"time"
"github.com/ElrondNetwork/elrond-go-logger"
)
var log = logger.GetOrCreate("termui/provider")
const statusMetricsUrlSuffix = "/node/status"
type statusMetricsResponseData struct {
Response map[string]interface{} `json:"metrics"`
}
type responseFromApi struct {
Data statusMetricsResponseData `json:"data"`
Error string `json:"error"`
Code string `json:"code"`
}
// StatusMetricsProvider is the struct that will handle initializing the presenter and fetching updated metrics from the node
type StatusMetricsProvider struct {
presenter PresenterHandler
nodeAddress string
fetchInterval int
}
// NewStatusMetricsProvider will return a new instance of a StatusMetricsProvider
func NewStatusMetricsProvider(presenter PresenterHandler, nodeAddress string, fetchInterval int) (*StatusMetricsProvider, error) {
if len(nodeAddress) == 0 {
return nil, ErrInvalidAddressLength
}
if fetchInterval < 1 {
return nil, ErrInvalidFetchInterval
}
if presenter == nil {
return nil, ErrNilTermuiPresenter
}
return &StatusMetricsProvider{
presenter: presenter,
nodeAddress: formatUrlAddress(nodeAddress),
fetchInterval: fetchInterval,
}, nil
}
// StartUpdatingData will update data from the API at a given interval
func (smp *StatusMetricsProvider) StartUpdatingData() {
go func() {
for {
metricsMap, err := smp.loadMetricsFromApi()
if err != nil {
log.Debug("fetch from API",
"error", err.Error())
} else {
smp.applyMetricsToPresenter(metricsMap)
}
time.Sleep(time.Duration(smp.fetchInterval) * time.Millisecond)
}
}()
}
func (smp *StatusMetricsProvider) loadMetricsFromApi() (map[string]interface{}, error) {
client := http.Client{}
statusMetricsUrl := smp.nodeAddress + statusMetricsUrlSuffix
resp, err := client.Get(statusMetricsUrl)
if err != nil {
return nil, err
}
responseBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
defer func() {
err = resp.Body.Close()
if err != nil {
log.Error("close response body", "error", err.Error())
}
}()
var metricsResponse responseFromApi
err = json.Unmarshal(responseBytes, &metricsResponse)
if err != nil {
return nil, err
}
return metricsResponse.Data.Response, nil
}
func (smp *StatusMetricsProvider) applyMetricsToPresenter(metricsMap map[string]interface{}) {
var err error
for key, value := range metricsMap {
err = smp.setPresenterValue(key, value)
if err != nil {
log.Debug("termui metric set",
"error", err.Error())
}
}
}
func (smp *StatusMetricsProvider) setPresenterValue(key string, value interface{}) error {
switch v := value.(type) {
case float64:
// json unmarshal treats all the numbers (in a field interface{}) as floats so we need to cast it to uint64
// because it is the numeric type used by the presenter
smp.presenter.SetUInt64Value(key, uint64(v))
case string:
smp.presenter.SetStringValue(key, v)
default:
return ErrTypeAssertionFailed
}
return nil
}
func formatUrlAddress(address string) string {
httpPrefix := "http://"
if !strings.HasPrefix(address, httpPrefix) {
address = httpPrefix + address
}
suffix := "/"
if strings.HasSuffix(address, suffix) {
address = address[:len(address)-1]
}
return address
}