Skip to content

Commit 0a66364

Browse files
authored
Merge pull request #2 from rtkkroland/ee_ccs
[AWN-42236] Add remote info to Elasticsearch exporter
2 parents 6d76240 + b00bf2d commit 0a66364

File tree

7 files changed

+281
-2
lines changed

7 files changed

+281
-2
lines changed
Binary file not shown.

collector/remote_info.go

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
package collector
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"net/http"
7+
"net/url"
8+
"path"
9+
10+
"github.com/go-kit/kit/log"
11+
"github.com/go-kit/kit/log/level"
12+
"github.com/prometheus/client_golang/prometheus"
13+
)
14+
15+
// Labels for remote info metrics
16+
var defaulRemoteInfoLabels = []string{"remote_cluster"}
17+
var defaultRemoteInfoLabelValues = func(remote_cluster string) []string {
18+
return []string{
19+
remote_cluster,
20+
}
21+
}
22+
23+
type remoteInfoMetric struct {
24+
Type prometheus.ValueType
25+
Desc *prometheus.Desc
26+
Value func(remoteStats RemoteCluster) float64
27+
Labels func(remote_cluster string) []string
28+
}
29+
30+
// RemoteInfo information struct
31+
type RemoteInfo struct {
32+
logger log.Logger
33+
client *http.Client
34+
url *url.URL
35+
36+
up prometheus.Gauge
37+
totalScrapes, jsonParseFailures prometheus.Counter
38+
39+
remoteInfoMetrics []*remoteInfoMetric
40+
}
41+
42+
// NewClusterSettings defines Cluster Settings Prometheus metrics
43+
func NewRemoteInfo(logger log.Logger, client *http.Client, url *url.URL) *RemoteInfo {
44+
45+
return &RemoteInfo{
46+
logger: logger,
47+
client: client,
48+
url: url,
49+
50+
up: prometheus.NewGauge(prometheus.GaugeOpts{
51+
Name: prometheus.BuildFQName(namespace, "remote_info_stats", "up"),
52+
Help: "Was the last scrape of the ElasticSearch remote info endpoint successful.",
53+
}),
54+
totalScrapes: prometheus.NewCounter(prometheus.CounterOpts{
55+
Name: prometheus.BuildFQName(namespace, "remote_info_stats", "total_scrapes"),
56+
Help: "Current total ElasticSearch remote info scrapes.",
57+
}),
58+
jsonParseFailures: prometheus.NewCounter(prometheus.CounterOpts{
59+
Name: prometheus.BuildFQName(namespace, "remote_info_stats", "json_parse_failures"),
60+
Help: "Number of errors while parsing JSON.",
61+
}),
62+
// Send all of the remote metrics
63+
remoteInfoMetrics: []*remoteInfoMetric{
64+
{
65+
Type: prometheus.GaugeValue,
66+
Desc: prometheus.NewDesc(
67+
prometheus.BuildFQName(namespace, "remote_info", "num_nodes_connected"),
68+
"Number of nodes connected", defaulRemoteInfoLabels, nil,
69+
),
70+
Value: func(remoteStats RemoteCluster) float64 {
71+
return float64(remoteStats.NumNodesConnected)
72+
},
73+
Labels: defaultRemoteInfoLabelValues,
74+
},
75+
{
76+
Type: prometheus.GaugeValue,
77+
Desc: prometheus.NewDesc(
78+
prometheus.BuildFQName(namespace, "remote_info", "max_connections_per_cluster"),
79+
"Max connections per cluster", defaulRemoteInfoLabels, nil,
80+
),
81+
Value: func(remoteStats RemoteCluster) float64 {
82+
return float64(remoteStats.MaxConnectionsPerCluster)
83+
},
84+
Labels: defaultRemoteInfoLabelValues,
85+
},
86+
},
87+
}
88+
}
89+
90+
func (c *RemoteInfo) fetchAndDecodeRemoteInfoStats() (RemoteInfoResponse, error) {
91+
var rir RemoteInfoResponse
92+
93+
u := *c.url
94+
u.Path = path.Join(u.Path, "/_remote/info")
95+
96+
res, err := c.client.Get(u.String())
97+
if err != nil {
98+
return rir, fmt.Errorf("failed to get remote info from %s://%s:%s%s: %s",
99+
u.Scheme, u.Hostname(), u.Port(), u.Path, err)
100+
}
101+
102+
defer func() {
103+
err = res.Body.Close()
104+
if err != nil {
105+
_ = level.Warn(c.logger).Log(
106+
"msg", "failed to close http.Client",
107+
"err", err,
108+
)
109+
}
110+
}()
111+
112+
if res.StatusCode != http.StatusOK {
113+
return rir, fmt.Errorf("HTTP Request failed with code %d", res.StatusCode)
114+
}
115+
116+
if err := json.NewDecoder(res.Body).Decode(&rir); err != nil {
117+
c.jsonParseFailures.Inc()
118+
return rir, err
119+
}
120+
return rir, nil
121+
}
122+
123+
// Collect gets remote info values
124+
func (ri *RemoteInfo) Collect(ch chan<- prometheus.Metric) {
125+
ri.totalScrapes.Inc()
126+
defer func() {
127+
ch <- ri.up
128+
ch <- ri.totalScrapes
129+
ch <- ri.jsonParseFailures
130+
}()
131+
132+
remoteInfoResp, err := ri.fetchAndDecodeRemoteInfoStats()
133+
if err != nil {
134+
ri.up.Set(0)
135+
_ = level.Warn(ri.logger).Log(
136+
"msg", "failed to fetch and decode remote info",
137+
"err", err,
138+
)
139+
return
140+
}
141+
ri.totalScrapes.Inc()
142+
ri.up.Set(1)
143+
144+
// Remote Info
145+
for remote_cluster, remoteInfo := range remoteInfoResp {
146+
for _, metric := range ri.remoteInfoMetrics {
147+
ch <- prometheus.MustNewConstMetric(
148+
metric.Desc,
149+
metric.Type,
150+
metric.Value(remoteInfo),
151+
metric.Labels(remote_cluster)...,
152+
)
153+
}
154+
}
155+
}
156+
157+
// Describe add Indices metrics descriptions
158+
func (ri *RemoteInfo) Describe(ch chan<- *prometheus.Desc) {
159+
for _, metric := range ri.remoteInfoMetrics {
160+
ch <- metric.Desc
161+
}
162+
ch <- ri.up.Desc()
163+
ch <- ri.totalScrapes.Desc()
164+
ch <- ri.jsonParseFailures.Desc()
165+
}

collector/remote_info_response.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package collector
2+
3+
// RemoteInfoResponse is a representation of a Elasticsearch _remote/info
4+
type RemoteInfoResponse map[string]RemoteCluster
5+
6+
// RemoteClsuter defines the struct of the tree for the Remote Cluster
7+
type RemoteCluster struct {
8+
Seeds []string `json:"seeds"`
9+
Connected bool `json:"connected"`
10+
NumNodesConnected int64 `json:"num_nodes_connected"`
11+
MaxConnectionsPerCluster int64 `json:"max_connections_per_cluster"`
12+
InitialConnectTimeout string `json:"initial_connect_timeout"`
13+
SkipUnavailable bool `json:"skip_unavailable"`
14+
}

collector/remote_info_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package collector
2+
3+
import (
4+
"io"
5+
"net/http"
6+
"net/http/httptest"
7+
"net/url"
8+
"os"
9+
"testing"
10+
11+
"github.com/go-kit/kit/log"
12+
)
13+
14+
func TestRemoteInfoStats(t *testing.T) {
15+
// Testcases created using:
16+
// docker run -d -p 9200:9200 elasticsearch:VERSION-alpine
17+
// curl http://localhost:9200/_cluster/settings/?include_defaults=true
18+
files := []string{"../fixtures/settings-5.4.2.json", "../fixtures/settings-merge-5.4.2.json"}
19+
for _, filename := range files {
20+
f, _ := os.Open(filename)
21+
defer f.Close()
22+
for hn, handler := range map[string]http.Handler{
23+
"plain": http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
24+
io.Copy(w, f)
25+
}),
26+
} {
27+
ts := httptest.NewServer(handler)
28+
defer ts.Close()
29+
30+
u, err := url.Parse(ts.URL)
31+
if err != nil {
32+
t.Fatalf("Failed to parse URL: %s", err)
33+
}
34+
c := NewRemoteInfo(log.NewNopLogger(), http.DefaultClient, u)
35+
nsr, err := c.fetchAndDecodeRemoteInfoStats()
36+
if err != nil {
37+
t.Fatalf("Failed to fetch or decode remote info stats: %s", err)
38+
}
39+
t.Logf("[%s/%s] Remote Info Stats Response: %+v", hn, filename, nsr)
40+
41+
}
42+
}
43+
}

go.mod

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,18 @@ require (
77
github.com/blang/semver/v4 v4.0.0
88
github.com/go-kit/kit v0.10.0
99
github.com/golang/protobuf v1.4.2 // indirect
10+
github.com/google/go-github/v25 v25.1.3 // indirect
1011
github.com/imdario/mergo v0.3.9
11-
github.com/prometheus/client_golang v1.6.0
12+
github.com/pkg/errors v0.9.1 // indirect
13+
github.com/prometheus/client_golang v1.7.1
1214
github.com/prometheus/common v0.10.0
15+
github.com/prometheus/promu v0.5.0 // indirect
1316
github.com/stretchr/testify v1.5.1 // indirect
14-
golang.org/x/sys v0.0.0-20200523222454-059865788121 // indirect
17+
golang.org/x/net v0.0.0-20200707034311-ab3426394381 // indirect
18+
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect
19+
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae // indirect
20+
google.golang.org/appengine v1.6.6 // indirect
21+
google.golang.org/protobuf v1.25.0 // indirect
1522
gopkg.in/alecthomas/kingpin.v2 v2.2.6
1623
gopkg.in/yaml.v2 v2.3.0 // indirect
1724
)

0 commit comments

Comments
 (0)