Skip to content

Commit a436baf

Browse files
committed
Add our remote info back
1 parent 36754ef commit a436baf

File tree

5 files changed

+281
-1
lines changed

5 files changed

+281
-1
lines changed

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.9.0
1+
2.9.0

collector/remote_info.go

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
// Copyright 2021 The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package collector
15+
16+
import (
17+
"encoding/json"
18+
"fmt"
19+
"log/slog"
20+
"net/http"
21+
"net/url"
22+
"path"
23+
24+
"github.com/prometheus/client_golang/prometheus"
25+
)
26+
27+
// Labels for remote info metrics
28+
var defaulRemoteInfoLabels = []string{"remote_cluster"}
29+
var defaultRemoteInfoLabelValues = func(remote_cluster string) []string {
30+
return []string{
31+
remote_cluster,
32+
}
33+
}
34+
35+
type remoteInfoMetric struct {
36+
Type prometheus.ValueType
37+
Desc *prometheus.Desc
38+
Value func(remoteStats RemoteCluster) float64
39+
Labels func(remote_cluster string) []string
40+
}
41+
42+
// RemoteInfo information struct
43+
type RemoteInfo struct {
44+
logger *slog.Logger
45+
client *http.Client
46+
url *url.URL
47+
48+
up prometheus.Gauge
49+
totalScrapes, jsonParseFailures prometheus.Counter
50+
51+
remoteInfoMetrics []*remoteInfoMetric
52+
}
53+
54+
// NewClusterSettings defines Cluster Settings Prometheus metrics
55+
func NewRemoteInfo(logger *slog.Logger, client *http.Client, url *url.URL) *RemoteInfo {
56+
57+
return &RemoteInfo{
58+
logger: logger,
59+
client: client,
60+
url: url,
61+
62+
up: prometheus.NewGauge(prometheus.GaugeOpts{
63+
Name: prometheus.BuildFQName(namespace, "remote_info_stats", "up"),
64+
Help: "Was the last scrape of the ElasticSearch remote info endpoint successful.",
65+
}),
66+
totalScrapes: prometheus.NewCounter(prometheus.CounterOpts{
67+
Name: prometheus.BuildFQName(namespace, "remote_info_stats", "total_scrapes"),
68+
Help: "Current total ElasticSearch remote info scrapes.",
69+
}),
70+
jsonParseFailures: prometheus.NewCounter(prometheus.CounterOpts{
71+
Name: prometheus.BuildFQName(namespace, "remote_info_stats", "json_parse_failures"),
72+
Help: "Number of errors while parsing JSON.",
73+
}),
74+
// Send all of the remote metrics
75+
remoteInfoMetrics: []*remoteInfoMetric{
76+
{
77+
Type: prometheus.GaugeValue,
78+
Desc: prometheus.NewDesc(
79+
prometheus.BuildFQName(namespace, "remote_info", "num_nodes_connected"),
80+
"Number of nodes connected", defaulRemoteInfoLabels, nil,
81+
),
82+
Value: func(remoteStats RemoteCluster) float64 {
83+
return float64(remoteStats.NumNodesConnected)
84+
},
85+
Labels: defaultRemoteInfoLabelValues,
86+
},
87+
{
88+
Type: prometheus.GaugeValue,
89+
Desc: prometheus.NewDesc(
90+
prometheus.BuildFQName(namespace, "remote_info", "num_proxy_sockets_connected"),
91+
"Number of proxy sockets connected", defaulRemoteInfoLabels, nil,
92+
),
93+
Value: func(remoteStats RemoteCluster) float64 {
94+
return float64(remoteStats.NumProxySocketsConnected)
95+
},
96+
Labels: defaultRemoteInfoLabelValues,
97+
},
98+
{
99+
Type: prometheus.GaugeValue,
100+
Desc: prometheus.NewDesc(
101+
prometheus.BuildFQName(namespace, "remote_info", "max_connections_per_cluster"),
102+
"Max connections per cluster", defaulRemoteInfoLabels, nil,
103+
),
104+
Value: func(remoteStats RemoteCluster) float64 {
105+
return float64(remoteStats.MaxConnectionsPerCluster)
106+
},
107+
Labels: defaultRemoteInfoLabelValues,
108+
},
109+
},
110+
}
111+
}
112+
113+
func (c *RemoteInfo) fetchAndDecodeRemoteInfoStats() (RemoteInfoResponse, error) {
114+
var rir RemoteInfoResponse
115+
116+
u := *c.url
117+
u.Path = path.Join(u.Path, "/_remote/info")
118+
119+
res, err := c.client.Get(u.String())
120+
if err != nil {
121+
return rir, fmt.Errorf("failed to get remote info from %s://%s:%s%s: %s",
122+
u.Scheme, u.Hostname(), u.Port(), u.Path, err)
123+
}
124+
125+
defer func() {
126+
err = res.Body.Close()
127+
if err != nil {
128+
c.logger.Warn(
129+
"failed to close http.Client",
130+
"err", err,
131+
)
132+
}
133+
}()
134+
135+
if res.StatusCode != http.StatusOK {
136+
return rir, fmt.Errorf("HTTP Request failed with code %d", res.StatusCode)
137+
}
138+
139+
if err := json.NewDecoder(res.Body).Decode(&rir); err != nil {
140+
c.jsonParseFailures.Inc()
141+
return rir, err
142+
}
143+
return rir, nil
144+
}
145+
146+
// Collect gets remote info values
147+
func (ri *RemoteInfo) Collect(ch chan<- prometheus.Metric) {
148+
ri.totalScrapes.Inc()
149+
defer func() {
150+
ch <- ri.up
151+
ch <- ri.totalScrapes
152+
ch <- ri.jsonParseFailures
153+
}()
154+
155+
remoteInfoResp, err := ri.fetchAndDecodeRemoteInfoStats()
156+
if err != nil {
157+
ri.up.Set(0)
158+
ri.logger.Warn(
159+
"failed to fetch and decode remote info",
160+
"err", err,
161+
)
162+
return
163+
}
164+
ri.totalScrapes.Inc()
165+
ri.up.Set(1)
166+
167+
// Remote Info
168+
for remote_cluster, remoteInfo := range remoteInfoResp {
169+
for _, metric := range ri.remoteInfoMetrics {
170+
ch <- prometheus.MustNewConstMetric(
171+
metric.Desc,
172+
metric.Type,
173+
metric.Value(remoteInfo),
174+
metric.Labels(remote_cluster)...,
175+
)
176+
}
177+
}
178+
}
179+
180+
// Describe add Indices metrics descriptions
181+
func (ri *RemoteInfo) Describe(ch chan<- *prometheus.Desc) {
182+
for _, metric := range ri.remoteInfoMetrics {
183+
ch <- metric.Desc
184+
}
185+
ch <- ri.up.Desc()
186+
ch <- ri.totalScrapes.Desc()
187+
ch <- ri.jsonParseFailures.Desc()
188+
}

collector/remote_info_response.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright 2021 The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package collector
15+
16+
// RemoteInfoResponse is a representation of a Elasticsearch _remote/info
17+
type RemoteInfoResponse map[string]RemoteCluster
18+
19+
// RemoteClsuter defines the struct of the tree for the Remote Cluster
20+
type RemoteCluster struct {
21+
Seeds []string `json:"seeds"`
22+
Connected bool `json:"connected"`
23+
NumNodesConnected int64 `json:"num_nodes_connected"`
24+
NumProxySocketsConnected int64 `json:"num_proxy_sockets_connected"`
25+
MaxConnectionsPerCluster int64 `json:"max_connections_per_cluster"`
26+
InitialConnectTimeout string `json:"initial_connect_timeout"`
27+
SkipUnavailable bool `json:"skip_unavailable"`
28+
}

collector/remote_info_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright 2021 The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package collector
15+
16+
import (
17+
"io"
18+
"net/http"
19+
"net/http/httptest"
20+
"net/url"
21+
"os"
22+
"testing"
23+
24+
"github.com/prometheus/common/promslog"
25+
)
26+
27+
func TestRemoteInfoStats(t *testing.T) {
28+
// Testcases created using:
29+
// docker run -d -p 9200:9200 elasticsearch:VERSION-alpine
30+
// curl http://localhost:9200/_cluster/settings/?include_defaults=true
31+
files := []string{"../fixtures/settings-5.4.2.json", "../fixtures/settings-merge-5.4.2.json"}
32+
for _, filename := range files {
33+
f, _ := os.Open(filename)
34+
defer f.Close()
35+
for hn, handler := range map[string]http.Handler{
36+
"plain": http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
37+
io.Copy(w, f)
38+
}),
39+
} {
40+
ts := httptest.NewServer(handler)
41+
defer ts.Close()
42+
43+
u, err := url.Parse(ts.URL)
44+
if err != nil {
45+
t.Fatalf("Failed to parse URL: %s", err)
46+
}
47+
c := NewRemoteInfo(promslog.NewNopLogger(), http.DefaultClient, u)
48+
nsr, err := c.fetchAndDecodeRemoteInfoStats()
49+
if err != nil {
50+
t.Fatalf("Failed to fetch or decode remote info stats: %s", err)
51+
}
52+
t.Logf("[%s/%s] Remote Info Stats Response: %+v", hn, filename, nsr)
53+
54+
}
55+
}
56+
}

main.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ func main() {
9999
esInsecureSkipVerify = kingpin.Flag("es.ssl-skip-verify",
100100
"Skip SSL verification when connecting to Elasticsearch.").
101101
Default("false").Bool()
102+
esExportRemoteInfo = kingpin.Flag("es.remote_info",
103+
"Export stats associated with configured remote clusters.").
104+
Default("false").Envar("ES_REMOTE_INFO").Bool()
102105
logOutput = kingpin.Flag("log.output",
103106
"Sets the log output. Valid outputs are stdout and stderr").
104107
Default("stdout").String()
@@ -219,6 +222,11 @@ func main() {
219222
prometheus.MustRegister(collector.NewIndicesMappings(logger, httpClient, esURL))
220223
}
221224

225+
if *esExportRemoteInfo {
226+
// Create Remote info Collector
227+
prometheus.MustRegister(collector.NewRemoteInfo(logger, httpClient, esURL))
228+
}
229+
222230
// Create a context that is cancelled on SIGKILL or SIGINT.
223231
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
224232
defer cancel()

0 commit comments

Comments
 (0)