master
go 266 lines 6.05 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package elasticsearch
4
5 import (
6 "errors"
7 "fmt"
8 "slices"
9 "strconv"
10 "strings"
11 "sync"
12
13 "github.com/netdata/netdata/go/plugins/pkg/stm"
14 "github.com/netdata/netdata/go/plugins/pkg/web"
15 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/oldmetrix"
16 )
17
18 const (
19 urlPathLocalNodeStats = "/_nodes/_local/stats"
20 urlPathNodesStats = "/_nodes/stats"
21 urlPathIndicesStats = "/_cat/indices"
22 urlPathClusterHealth = "/_cluster/health"
23 urlPathClusterStats = "/_cluster/stats"
24 )
25
26 func (c *Collector) collect() (map[string]int64, error) {
27 if c.clusterName == "" {
28 name, err := c.getClusterName()
29 if err != nil {
30 return nil, err
31 }
32 c.clusterName = name
33 }
34
35 ms := c.scrapeElasticsearch()
36 if ms.empty() {
37 return nil, nil
38 }
39
40 mx := make(map[string]int64)
41
42 c.collectNodesStats(mx, ms)
43 c.collectClusterHealth(mx, ms)
44 c.collectClusterStats(mx, ms)
45 c.collectLocalIndicesStats(mx, ms)
46
47 return mx, nil
48 }
49
50 func (c *Collector) collectNodesStats(mx map[string]int64, ms *esMetrics) {
51 if !ms.hasNodesStats() {
52 return
53 }
54
55 seen := make(map[string]bool)
56
57 for nodeID, node := range ms.NodesStats.Nodes {
58 seen[nodeID] = true
59
60 if !c.nodes[nodeID] {
61 c.nodes[nodeID] = true
62 c.addNodeCharts(nodeID, node)
63 }
64
65 merge(mx, stm.ToMap(node), "node_"+nodeID)
66 }
67
68 for nodeID := range c.nodes {
69 if !seen[nodeID] {
70 delete(c.nodes, nodeID)
71 c.removeNodeCharts(nodeID)
72 }
73 }
74 }
75
76 func (c *Collector) collectClusterHealth(mx map[string]int64, ms *esMetrics) {
77 if !ms.hasClusterHealth() {
78 return
79 }
80
81 c.addClusterHealthChartsOnce.Do(c.addClusterHealthCharts)
82
83 merge(mx, stm.ToMap(ms.ClusterHealth), "cluster")
84
85 mx["cluster_status_green"] = oldmetrix.Bool(ms.ClusterHealth.Status == "green")
86 mx["cluster_status_yellow"] = oldmetrix.Bool(ms.ClusterHealth.Status == "yellow")
87 mx["cluster_status_red"] = oldmetrix.Bool(ms.ClusterHealth.Status == "red")
88 }
89
90 func (c *Collector) collectClusterStats(mx map[string]int64, ms *esMetrics) {
91 if !ms.hasClusterStats() {
92 return
93 }
94
95 c.addClusterStatsChartsOnce.Do(c.addClusterStatsCharts)
96
97 merge(mx, stm.ToMap(ms.ClusterStats), "cluster")
98 }
99
100 func (c *Collector) collectLocalIndicesStats(mx map[string]int64, ms *esMetrics) {
101 if !ms.hasLocalIndicesStats() {
102 return
103 }
104
105 seen := make(map[string]bool)
106
107 for _, v := range ms.LocalIndicesStats {
108 seen[v.Index] = true
109
110 if !c.indices[v.Index] {
111 c.indices[v.Index] = true
112 c.addIndexCharts(v.Index)
113 }
114
115 px := fmt.Sprintf("node_index_%s_stats_", v.Index)
116
117 mx[px+"health_green"] = oldmetrix.Bool(v.Health == "green")
118 mx[px+"health_yellow"] = oldmetrix.Bool(v.Health == "yellow")
119 mx[px+"health_red"] = oldmetrix.Bool(v.Health == "red")
120 mx[px+"shards_count"] = strToInt(v.Rep)
121 mx[px+"docs_count"] = strToInt(v.DocsCount)
122 mx[px+"store_size_in_bytes"] = convertIndexStoreSizeToBytes(v.StoreSize)
123 }
124
125 for index := range c.indices {
126 if !seen[index] {
127 delete(c.indices, index)
128 c.removeIndexCharts(index)
129 }
130 }
131 }
132
133 func (c *Collector) scrapeElasticsearch() *esMetrics {
134 ms := &esMetrics{}
135 wg := &sync.WaitGroup{}
136
137 if c.DoNodeStats {
138 wg.Go(func() { c.scrapeNodesStats(ms) })
139 }
140 if c.DoClusterHealth {
141 wg.Go(func() { c.scrapeClusterHealth(ms) })
142 }
143 if c.DoClusterStats {
144 wg.Go(func() { c.scrapeClusterStats(ms) })
145 }
146 if !c.ClusterMode && c.DoIndicesStats {
147 wg.Go(func() { c.scrapeLocalIndicesStats(ms) })
148 }
149 wg.Wait()
150
151 return ms
152 }
153
154 func (c *Collector) scrapeNodesStats(ms *esMetrics) {
155 var p string
156 if c.ClusterMode {
157 p = urlPathNodesStats
158 } else {
159 p = urlPathLocalNodeStats
160 }
161
162 req, _ := web.NewHTTPRequestWithPath(c.RequestConfig, p)
163
164 var stats esNodesStats
165 if err := web.DoHTTP(c.httpClient).RequestJSON(req, &stats); err != nil {
166 c.Warning(err)
167 return
168 }
169
170 ms.NodesStats = &stats
171 }
172
173 func (c *Collector) scrapeClusterHealth(ms *esMetrics) {
174 req, _ := web.NewHTTPRequestWithPath(c.RequestConfig, urlPathClusterHealth)
175
176 var health esClusterHealth
177 if err := web.DoHTTP(c.httpClient).RequestJSON(req, &health); err != nil {
178 c.Warning(err)
179 return
180 }
181
182 ms.ClusterHealth = &health
183 }
184
185 func (c *Collector) scrapeClusterStats(ms *esMetrics) {
186 req, _ := web.NewHTTPRequestWithPath(c.RequestConfig, urlPathClusterStats)
187
188 var stats esClusterStats
189 if err := web.DoHTTP(c.httpClient).RequestJSON(req, &stats); err != nil {
190 c.Warning(err)
191 return
192 }
193
194 ms.ClusterStats = &stats
195 }
196
197 func (c *Collector) scrapeLocalIndicesStats(ms *esMetrics) {
198 req, _ := web.NewHTTPRequestWithPath(c.RequestConfig, urlPathIndicesStats)
199 req.URL.RawQuery = "local=true&format=json"
200
201 var stats []esIndexStats
202 if err := web.DoHTTP(c.httpClient).RequestJSON(req, &stats); err != nil {
203 c.Warning(err)
204 return
205 }
206
207 ms.LocalIndicesStats = removeSystemIndices(stats)
208 }
209
210 func (c *Collector) getClusterName() (string, error) {
211 req, err := web.NewHTTPRequest(c.RequestConfig)
212 if err != nil {
213 return "", err
214 }
215
216 var info struct {
217 ClusterName string `json:"cluster_name"`
218 }
219 if err := web.DoHTTP(c.httpClient).RequestJSON(req, &info); err != nil {
220 return "", err
221 }
222
223 if info.ClusterName == "" {
224 return "", errors.New("empty cluster name")
225 }
226
227 return info.ClusterName, nil
228 }
229
230 func convertIndexStoreSizeToBytes(size string) int64 {
231 var num float64
232 switch {
233 case strings.HasSuffix(size, "kb"):
234 num, _ = strconv.ParseFloat(size[:len(size)-2], 64)
235 num *= 1024
236 case strings.HasSuffix(size, "mb"):
237 num, _ = strconv.ParseFloat(size[:len(size)-2], 64)
238 num *= 1024 * 1024
239 case strings.HasSuffix(size, "gb"):
240 num, _ = strconv.ParseFloat(size[:len(size)-2], 64)
241 num *= 1024 * 1024 * 1024
242 case strings.HasSuffix(size, "tb"):
243 num, _ = strconv.ParseFloat(size[:len(size)-2], 64)
244 num *= 1024 * 1024 * 1024 * 1024
245 case strings.HasSuffix(size, "b"):
246 num, _ = strconv.ParseFloat(size[:len(size)-1], 64)
247 }
248 return int64(num)
249 }
250
251 func strToInt(s string) int64 {
252 v, _ := strconv.Atoi(s)
253 return int64(v)
254 }
255
256 func removeSystemIndices(indices []esIndexStats) []esIndexStats {
257 return slices.DeleteFunc(indices, func(stats esIndexStats) bool {
258 return strings.HasPrefix(stats.Index, ".")
259 })
260 }
261
262 func merge(dst, src map[string]int64, prefix string) {
263 for k, v := range src {
264 dst[prefix+"_"+k] = v
265 }
266 }