Skip to content

Commit 73d23aa

Browse files
iishabakaevШабакаев Илья Исмаилович
authored andcommitted
feat(*): add ilm metrics
Signed-off-by: iishabakaev <[email protected]>
1 parent 6bef1fc commit 73d23aa

File tree

7 files changed

+554
-0
lines changed

7 files changed

+554
-0
lines changed

collector/ilm_indices.go

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
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+
"net/http"
20+
"net/url"
21+
"path"
22+
23+
"github.com/go-kit/log"
24+
"github.com/go-kit/log/level"
25+
"github.com/prometheus/client_golang/prometheus"
26+
)
27+
28+
type ilmMetric struct {
29+
Type prometheus.ValueType
30+
Desc *prometheus.Desc
31+
Value func(timeMillis float64) float64
32+
Labels []string
33+
}
34+
35+
// Index Lifecycle Management information object
36+
type IlmIndiciesCollector struct {
37+
logger log.Logger
38+
client *http.Client
39+
url *url.URL
40+
41+
up prometheus.Gauge
42+
totalScrapes prometheus.Counter
43+
jsonParseFailures prometheus.Counter
44+
45+
ilmMetric ilmMetric
46+
}
47+
48+
var (
49+
defaultIlmIndicesMappingsLabels = []string{"index", "phase", "action", "step"}
50+
)
51+
52+
// NewIlmIndicies defines Index Lifecycle Management Prometheus metrics
53+
func NewIlmIndicies(logger log.Logger, client *http.Client, url *url.URL) *IlmIndiciesCollector {
54+
subsystem := "ilm_index"
55+
56+
return &IlmIndiciesCollector{
57+
logger: logger,
58+
client: client,
59+
url: url,
60+
61+
up: prometheus.NewGauge(prometheus.GaugeOpts{
62+
Name: prometheus.BuildFQName(namespace, subsystem, "up"),
63+
Help: "Was the last scrape of the ElasticSearch ILM endpoint successful.",
64+
}),
65+
totalScrapes: prometheus.NewCounter(prometheus.CounterOpts{
66+
Name: prometheus.BuildFQName(namespace, subsystem, "total_scrapes"),
67+
Help: "Current total ElasticSearch ILM scrapes.",
68+
}),
69+
jsonParseFailures: prometheus.NewCounter(prometheus.CounterOpts{
70+
Name: prometheus.BuildFQName(namespace, subsystem, "json_parse_failures"),
71+
Help: "Number of errors while parsing JSON.",
72+
}),
73+
ilmMetric: ilmMetric{
74+
Type: prometheus.GaugeValue,
75+
Desc: prometheus.NewDesc(
76+
prometheus.BuildFQName(namespace, subsystem, "status"),
77+
"Status of ILM policy for index",
78+
defaultIlmIndicesMappingsLabels, nil),
79+
Value: func(timeMillis float64) float64 {
80+
return timeMillis
81+
},
82+
},
83+
}
84+
}
85+
86+
// Describe adds metrics description
87+
func (i *IlmIndiciesCollector) Describe(ch chan<- *prometheus.Desc) {
88+
ch <- i.ilmMetric.Desc
89+
ch <- i.up.Desc()
90+
ch <- i.totalScrapes.Desc()
91+
ch <- i.jsonParseFailures.Desc()
92+
}
93+
94+
func (i *IlmIndiciesCollector) fetchAndDecodeIlm() (IlmResponse, error) {
95+
var ir IlmResponse
96+
97+
u := *i.url
98+
u.Path = path.Join(u.Path, "/_all/_ilm/explain")
99+
100+
res, err := i.client.Get(u.String())
101+
if err != nil {
102+
return ir, fmt.Errorf("failed to get index stats from %s://%s:%s%s: %s",
103+
u.Scheme, u.Hostname(), u.Port(), u.Path, err)
104+
}
105+
106+
defer func() {
107+
err = res.Body.Close()
108+
if err != nil {
109+
_ = level.Warn(i.logger).Log(
110+
"msg", "failed to close http.Client",
111+
"err", err,
112+
)
113+
}
114+
}()
115+
116+
if res.StatusCode != http.StatusOK {
117+
return ir, fmt.Errorf("HTTP Request failed with code %d", res.StatusCode)
118+
}
119+
120+
if err := json.NewDecoder(res.Body).Decode(&ir); err != nil {
121+
i.jsonParseFailures.Inc()
122+
return ir, err
123+
}
124+
125+
return ir, nil
126+
}
127+
128+
func bool2int(managed bool) float64 {
129+
if managed {
130+
return 1
131+
}
132+
return 0
133+
}
134+
135+
// Collect pulls metric values from Elasticsearch
136+
func (i *IlmIndiciesCollector) Collect(ch chan<- prometheus.Metric) {
137+
defer func() {
138+
ch <- i.up
139+
ch <- i.totalScrapes
140+
ch <- i.jsonParseFailures
141+
}()
142+
143+
// indices
144+
ilmResp, err := i.fetchAndDecodeIlm()
145+
if err != nil {
146+
i.up.Set(0)
147+
_ = level.Warn(i.logger).Log(
148+
"msg", "failed to fetch and decode ILM stats",
149+
"err", err,
150+
)
151+
return
152+
}
153+
i.totalScrapes.Inc()
154+
i.up.Set(1)
155+
156+
for indexName, indexIlm := range ilmResp.Indices {
157+
ch <- prometheus.MustNewConstMetric(
158+
i.ilmMetric.Desc,
159+
i.ilmMetric.Type,
160+
i.ilmMetric.Value(bool2int(indexIlm.Managed)),
161+
indexName, indexIlm.Phase, indexIlm.Action, indexIlm.Step,
162+
)
163+
}
164+
}

collector/ilm_indices_response.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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+
type IlmResponse struct {
17+
Indices map[string]IlmIndexResponse `json:"indices"`
18+
}
19+
20+
type IlmIndexResponse struct {
21+
Index string `json:"index"`
22+
Managed bool `json:"managed"`
23+
Phase string `json:"phase"`
24+
Action string `json:"action"`
25+
Step string `json:"step"`
26+
StepTimeMillis float64 `json:"step_time_millis"`
27+
}

collector/ilm_indices_test.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
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+
"fmt"
18+
"net/http"
19+
"net/http/httptest"
20+
"net/url"
21+
"testing"
22+
23+
"github.com/go-kit/log"
24+
)
25+
26+
func TestILMMetrics(t *testing.T) {
27+
// Testcases created using:
28+
// docker run -d -p 9200:9200 elasticsearch:VERSION
29+
// curl -XPUT http://localhost:9200/twitter
30+
// curl -X PUT "localhost:9200/_ilm/policy/my_policy?pretty" -H 'Content-Type: application/json' -d'
31+
// {
32+
// "policy": {
33+
// "phases": {
34+
// "warm": {
35+
// "min_age": "10d",
36+
// "actions": {
37+
// "forcemerge": {
38+
// "max_num_segments": 1
39+
// }
40+
// }
41+
// },
42+
// "delete": {
43+
// "min_age": "30d",
44+
// "actions": {
45+
// "delete": {}
46+
// }
47+
// }
48+
// }
49+
// }
50+
// }
51+
// '
52+
// curl -X PUT "localhost:9200/facebook?pretty" -H 'Content-Type: application/json' -d'
53+
// {
54+
// "settings": {
55+
// "index": {
56+
// "lifecycle": {
57+
// "name": "my_policy"
58+
// }
59+
// }
60+
// }
61+
// }
62+
// '
63+
// curl http://localhost:9200/_all/_ilm/explain
64+
tcs := map[string]string{
65+
"6.6.0": `{
66+
"indices": {
67+
"twitter": { "index": "twitter", "managed": false },
68+
"facebook": {
69+
"index": "facebook",
70+
"managed": true,
71+
"policy": "my_policy",
72+
"lifecycle_date_millis": 1660799138565,
73+
"phase": "new",
74+
"phase_time_millis": 1660799138651,
75+
"action": "complete",
76+
"action_time_millis": 1660799138651,
77+
"step": "complete",
78+
"step_time_millis": 1660799138651
79+
}
80+
}
81+
}`,
82+
}
83+
for ver, out := range tcs {
84+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
85+
fmt.Fprintln(w, out)
86+
}))
87+
defer ts.Close()
88+
89+
u, err := url.Parse(ts.URL)
90+
if err != nil {
91+
t.Fatalf("Failed to parse URL: %s", err)
92+
}
93+
c := NewIlmIndicies(log.NewNopLogger(), http.DefaultClient, u)
94+
chr, err := c.fetchAndDecodeIlm()
95+
if err != nil {
96+
t.Fatalf("Failed to fetch or decode indices ilm metrics: %s", err)
97+
}
98+
t.Logf("[%s] indices ilm metrics Response: %+v", ver, chr)
99+
100+
if chr.Indices["twitter"].Managed != false {
101+
t.Errorf("Invalid ilm metrics at twitter.managed")
102+
}
103+
if chr.Indices["facebook"].Managed != true {
104+
t.Errorf("Invalid ilm metrics at facebook.managed")
105+
}
106+
if chr.Indices["facebook"].Phase != "new" {
107+
t.Errorf("Invalid ilm metrics at facebook.phase")
108+
}
109+
if chr.Indices["facebook"].Action != "complete" {
110+
t.Errorf("Invalid ilm metrics at facebook.action")
111+
}
112+
if chr.Indices["facebook"].Step != "complete" {
113+
t.Errorf("Invalid ilm metrics at facebook.step")
114+
}
115+
116+
}
117+
}

0 commit comments

Comments
 (0)