Skip to content

Commit

Permalink
feat(*): add ilm metrics
Browse files Browse the repository at this point in the history
Signed-off-by: iishabakaev <iishabakaev@gmail.com>
  • Loading branch information
iishabakaev authored and Шабакаев Илья Исмаилович committed Aug 18, 2022
1 parent 6bef1fc commit 73d23aa
Show file tree
Hide file tree
Showing 7 changed files with 554 additions and 0 deletions.
164 changes: 164 additions & 0 deletions collector/ilm_indices.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// Copyright 2021 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package collector

import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"path"

"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/prometheus/client_golang/prometheus"
)

type ilmMetric struct {
Type prometheus.ValueType
Desc *prometheus.Desc
Value func(timeMillis float64) float64
Labels []string
}

// Index Lifecycle Management information object
type IlmIndiciesCollector struct {
logger log.Logger
client *http.Client
url *url.URL

up prometheus.Gauge
totalScrapes prometheus.Counter
jsonParseFailures prometheus.Counter

ilmMetric ilmMetric
}

var (
defaultIlmIndicesMappingsLabels = []string{"index", "phase", "action", "step"}
)

// NewIlmIndicies defines Index Lifecycle Management Prometheus metrics
func NewIlmIndicies(logger log.Logger, client *http.Client, url *url.URL) *IlmIndiciesCollector {
subsystem := "ilm_index"

return &IlmIndiciesCollector{
logger: logger,
client: client,
url: url,

up: prometheus.NewGauge(prometheus.GaugeOpts{
Name: prometheus.BuildFQName(namespace, subsystem, "up"),
Help: "Was the last scrape of the ElasticSearch ILM endpoint successful.",
}),
totalScrapes: prometheus.NewCounter(prometheus.CounterOpts{
Name: prometheus.BuildFQName(namespace, subsystem, "total_scrapes"),
Help: "Current total ElasticSearch ILM scrapes.",
}),
jsonParseFailures: prometheus.NewCounter(prometheus.CounterOpts{
Name: prometheus.BuildFQName(namespace, subsystem, "json_parse_failures"),
Help: "Number of errors while parsing JSON.",
}),
ilmMetric: ilmMetric{
Type: prometheus.GaugeValue,
Desc: prometheus.NewDesc(
prometheus.BuildFQName(namespace, subsystem, "status"),
"Status of ILM policy for index",
defaultIlmIndicesMappingsLabels, nil),
Value: func(timeMillis float64) float64 {
return timeMillis
},
},
}
}

// Describe adds metrics description
func (i *IlmIndiciesCollector) Describe(ch chan<- *prometheus.Desc) {
ch <- i.ilmMetric.Desc
ch <- i.up.Desc()
ch <- i.totalScrapes.Desc()
ch <- i.jsonParseFailures.Desc()
}

func (i *IlmIndiciesCollector) fetchAndDecodeIlm() (IlmResponse, error) {
var ir IlmResponse

u := *i.url
u.Path = path.Join(u.Path, "/_all/_ilm/explain")

res, err := i.client.Get(u.String())
if err != nil {
return ir, fmt.Errorf("failed to get index stats from %s://%s:%s%s: %s",
u.Scheme, u.Hostname(), u.Port(), u.Path, err)
}

defer func() {
err = res.Body.Close()
if err != nil {
_ = level.Warn(i.logger).Log(
"msg", "failed to close http.Client",
"err", err,
)
}
}()

if res.StatusCode != http.StatusOK {
return ir, fmt.Errorf("HTTP Request failed with code %d", res.StatusCode)
}

if err := json.NewDecoder(res.Body).Decode(&ir); err != nil {
i.jsonParseFailures.Inc()
return ir, err
}

return ir, nil
}

func bool2int(managed bool) float64 {
if managed {
return 1
}
return 0
}

// Collect pulls metric values from Elasticsearch
func (i *IlmIndiciesCollector) Collect(ch chan<- prometheus.Metric) {
defer func() {
ch <- i.up
ch <- i.totalScrapes
ch <- i.jsonParseFailures
}()

// indices
ilmResp, err := i.fetchAndDecodeIlm()
if err != nil {
i.up.Set(0)
_ = level.Warn(i.logger).Log(
"msg", "failed to fetch and decode ILM stats",
"err", err,
)
return
}
i.totalScrapes.Inc()
i.up.Set(1)

for indexName, indexIlm := range ilmResp.Indices {
ch <- prometheus.MustNewConstMetric(
i.ilmMetric.Desc,
i.ilmMetric.Type,
i.ilmMetric.Value(bool2int(indexIlm.Managed)),
indexName, indexIlm.Phase, indexIlm.Action, indexIlm.Step,
)
}
}
27 changes: 27 additions & 0 deletions collector/ilm_indices_response.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright 2021 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package collector

type IlmResponse struct {
Indices map[string]IlmIndexResponse `json:"indices"`
}

type IlmIndexResponse struct {
Index string `json:"index"`
Managed bool `json:"managed"`
Phase string `json:"phase"`
Action string `json:"action"`
Step string `json:"step"`
StepTimeMillis float64 `json:"step_time_millis"`
}
117 changes: 117 additions & 0 deletions collector/ilm_indices_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Copyright 2021 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package collector

import (
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"testing"

"github.com/go-kit/log"
)

func TestILMMetrics(t *testing.T) {
// Testcases created using:
// docker run -d -p 9200:9200 elasticsearch:VERSION
// curl -XPUT http://localhost:9200/twitter
// curl -X PUT "localhost:9200/_ilm/policy/my_policy?pretty" -H 'Content-Type: application/json' -d'
// {
// "policy": {
// "phases": {
// "warm": {
// "min_age": "10d",
// "actions": {
// "forcemerge": {
// "max_num_segments": 1
// }
// }
// },
// "delete": {
// "min_age": "30d",
// "actions": {
// "delete": {}
// }
// }
// }
// }
// }
// '
// curl -X PUT "localhost:9200/facebook?pretty" -H 'Content-Type: application/json' -d'
// {
// "settings": {
// "index": {
// "lifecycle": {
// "name": "my_policy"
// }
// }
// }
// }
// '
// curl http://localhost:9200/_all/_ilm/explain
tcs := map[string]string{
"6.6.0": `{
"indices": {
"twitter": { "index": "twitter", "managed": false },
"facebook": {
"index": "facebook",
"managed": true,
"policy": "my_policy",
"lifecycle_date_millis": 1660799138565,
"phase": "new",
"phase_time_millis": 1660799138651,
"action": "complete",
"action_time_millis": 1660799138651,
"step": "complete",
"step_time_millis": 1660799138651
}
}
}`,
}
for ver, out := range tcs {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, out)
}))
defer ts.Close()

u, err := url.Parse(ts.URL)
if err != nil {
t.Fatalf("Failed to parse URL: %s", err)
}
c := NewIlmIndicies(log.NewNopLogger(), http.DefaultClient, u)
chr, err := c.fetchAndDecodeIlm()
if err != nil {
t.Fatalf("Failed to fetch or decode indices ilm metrics: %s", err)
}
t.Logf("[%s] indices ilm metrics Response: %+v", ver, chr)

if chr.Indices["twitter"].Managed != false {
t.Errorf("Invalid ilm metrics at twitter.managed")
}
if chr.Indices["facebook"].Managed != true {
t.Errorf("Invalid ilm metrics at facebook.managed")
}
if chr.Indices["facebook"].Phase != "new" {
t.Errorf("Invalid ilm metrics at facebook.phase")
}
if chr.Indices["facebook"].Action != "complete" {
t.Errorf("Invalid ilm metrics at facebook.action")
}
if chr.Indices["facebook"].Step != "complete" {
t.Errorf("Invalid ilm metrics at facebook.step")
}

}
}
Loading

0 comments on commit 73d23aa

Please sign in to comment.