Skip to content

Commit 111a505

Browse files
committed
Fix mixed value types for disk watermark
The disk watermark values can be a ratio or percentage according to the docs[1], however when set to a percentage, the defaults become an object and therefore fails to parse. In that case, we really only care about what the user set. Adds a test to confirm a fix for prometheus-community#1044. Fixes prometheus-community#1044 [1] https://www.elastic.co/docs/reference/elasticsearch/configuration-reference/cluster-level-shard-allocation-routing-settings#disk-based-shard-allocation Signed-off-by: Joe Adams <github@joeadams.io>
1 parent 60f9e0c commit 111a505

File tree

2 files changed

+116
-62
lines changed

2 files changed

+116
-62
lines changed

collector/cluster_settings.go

Lines changed: 92 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,9 @@ type clusterSettingsDisk struct {
140140

141141
// clusterSettingsWatermark is representation of Elasticsearch Cluster shard routing disk allocation watermark settings
142142
type clusterSettingsWatermark struct {
143-
FloodStage string `json:"flood_stage"`
144-
High string `json:"high"`
145-
Low string `json:"low"`
143+
FloodStage interface{} `json:"flood_stage"`
144+
High interface{} `json:"high"`
145+
Low interface{} `json:"low"`
146146
}
147147

148148
func (c *ClusterSettingsCollector) Update(ctx context.Context, ch chan<- prometheus.Metric) error {
@@ -222,80 +222,110 @@ func (c *ClusterSettingsCollector) Update(ctx context.Context, ch chan<- prometh
222222
)
223223

224224
// Watermark bytes or ratio metrics
225-
if strings.HasSuffix(merged.Cluster.Routing.Allocation.Disk.Watermark.High, "b") {
226-
flooodStageBytes, err := getValueInBytes(merged.Cluster.Routing.Allocation.Disk.Watermark.FloodStage)
227-
if err != nil {
228-
c.logger.Error("failed to parse flood_stage bytes", "err", err)
229-
} else {
230-
ch <- prometheus.MustNewConstMetric(
231-
clusterSettingsDesc["floodStageBytes"],
232-
prometheus.GaugeValue,
233-
flooodStageBytes,
234-
)
235-
}
236-
237-
highBytes, err := getValueInBytes(merged.Cluster.Routing.Allocation.Disk.Watermark.High)
238-
if err != nil {
239-
c.logger.Error("failed to parse high bytes", "err", err)
240-
} else {
241-
ch <- prometheus.MustNewConstMetric(
242-
clusterSettingsDesc["highBytes"],
243-
prometheus.GaugeValue,
244-
highBytes,
245-
)
246-
}
247-
248-
lowBytes, err := getValueInBytes(merged.Cluster.Routing.Allocation.Disk.Watermark.Low)
249-
if err != nil {
250-
c.logger.Error("failed to parse low bytes", "err", err)
251-
} else {
252-
ch <- prometheus.MustNewConstMetric(
253-
clusterSettingsDesc["lowBytes"],
254-
prometheus.GaugeValue,
255-
lowBytes,
256-
)
257-
}
258-
259-
return nil
260-
}
261-
262-
// Watermark ratio metrics
263-
floodRatio, err := getValueAsRatio(merged.Cluster.Routing.Allocation.Disk.Watermark.FloodStage)
225+
watermarkFlood, err := parseWatermarkValue(merged.Cluster.Routing.Allocation.Disk.Watermark.FloodStage)
264226
if err != nil {
265-
c.logger.Error("failed to parse flood_stage ratio", "err", err)
227+
c.logger.Error("failed to parse flood stage watermark", "err", err)
266228
} else {
267-
ch <- prometheus.MustNewConstMetric(
268-
clusterSettingsDesc["floodStageRatio"],
269-
prometheus.GaugeValue,
270-
floodRatio,
271-
)
229+
if strings.HasSuffix(watermarkFlood, "b") {
230+
floodStageBytes, err := getValueInBytes(watermarkFlood)
231+
if err != nil {
232+
c.logger.Error("failed to parse flood_stage bytes", "err", err)
233+
} else {
234+
ch <- prometheus.MustNewConstMetric(
235+
clusterSettingsDesc["floodStageBytes"],
236+
prometheus.GaugeValue,
237+
floodStageBytes,
238+
)
239+
}
240+
} else {
241+
floodStageRatio, err := getValueAsRatio(watermarkFlood)
242+
if err != nil {
243+
c.logger.Error("failed to parse flood_stage ratio", "err", err)
244+
} else {
245+
ch <- prometheus.MustNewConstMetric(
246+
clusterSettingsDesc["floodStageRatio"],
247+
prometheus.GaugeValue,
248+
floodStageRatio,
249+
)
250+
}
251+
}
272252
}
273253

274-
highRatio, err := getValueAsRatio(merged.Cluster.Routing.Allocation.Disk.Watermark.High)
254+
watermarkHigh, err := parseWatermarkValue(merged.Cluster.Routing.Allocation.Disk.Watermark.High)
275255
if err != nil {
276-
c.logger.Error("failed to parse high ratio", "err", err)
256+
c.logger.Error("failed to parse high watermark", "err", err)
277257
} else {
278-
ch <- prometheus.MustNewConstMetric(
279-
clusterSettingsDesc["highRatio"],
280-
prometheus.GaugeValue,
281-
highRatio,
282-
)
258+
if strings.HasSuffix(watermarkHigh, "b") {
259+
highBytes, err := getValueInBytes(watermarkHigh)
260+
if err != nil {
261+
c.logger.Error("failed to parse high bytes", "err", err)
262+
} else {
263+
ch <- prometheus.MustNewConstMetric(
264+
clusterSettingsDesc["highBytes"],
265+
prometheus.GaugeValue,
266+
highBytes,
267+
)
268+
}
269+
} else {
270+
highRatio, err := getValueAsRatio(watermarkHigh)
271+
if err != nil {
272+
c.logger.Error("failed to parse high ratio", "err", err)
273+
} else {
274+
ch <- prometheus.MustNewConstMetric(
275+
clusterSettingsDesc["highRatio"],
276+
prometheus.GaugeValue,
277+
highRatio,
278+
)
279+
}
280+
}
283281
}
284282

285-
lowRatio, err := getValueAsRatio(merged.Cluster.Routing.Allocation.Disk.Watermark.Low)
283+
watermarkLow, err := parseWatermarkValue(merged.Cluster.Routing.Allocation.Disk.Watermark.Low)
286284
if err != nil {
287-
c.logger.Error("failed to parse low ratio", "err", err)
285+
c.logger.Error("failed to parse low watermark", "err", err)
288286
} else {
289-
ch <- prometheus.MustNewConstMetric(
290-
clusterSettingsDesc["lowRatio"],
291-
prometheus.GaugeValue,
292-
lowRatio,
293-
)
287+
if strings.HasSuffix(watermarkLow, "b") {
288+
lowBytes, err := getValueInBytes(watermarkLow)
289+
if err != nil {
290+
c.logger.Error("failed to parse low bytes", "err", err)
291+
} else {
292+
ch <- prometheus.MustNewConstMetric(
293+
clusterSettingsDesc["lowBytes"],
294+
prometheus.GaugeValue,
295+
lowBytes,
296+
)
297+
}
298+
} else {
299+
lowRatio, err := getValueAsRatio(watermarkLow)
300+
if err != nil {
301+
c.logger.Error("failed to parse low ratio", "err", err)
302+
} else {
303+
ch <- prometheus.MustNewConstMetric(
304+
clusterSettingsDesc["lowRatio"],
305+
prometheus.GaugeValue,
306+
lowRatio,
307+
)
308+
}
309+
}
294310
}
295311

296312
return nil
297313
}
298314

315+
func parseWatermarkValue(value interface{}) (string, error) {
316+
switch v := value.(type) {
317+
case string:
318+
return v, nil
319+
case map[string]interface{}:
320+
if val, ok := v["value"].(string); ok {
321+
return val, nil
322+
}
323+
return "", fmt.Errorf("unexpected structure in watermark value: %v", v)
324+
default:
325+
return "", fmt.Errorf("unsupported type for watermark value: %T", v)
326+
}
327+
}
328+
299329
func getValueInBytes(value string) (float64, error) {
300330
type UnitValue struct {
301331
unit string

collector/cluster_settings_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,30 @@ elasticsearch_clustersettings_allocation_watermark_high_bytes 2.147483648e+11
114114
# HELP elasticsearch_clustersettings_allocation_watermark_low_bytes Low watermark for disk usage in bytes.
115115
# TYPE elasticsearch_clustersettings_allocation_watermark_low_bytes gauge
116116
elasticsearch_clustersettings_allocation_watermark_low_bytes 5.24288e+07
117+
`,
118+
},
119+
{
120+
name: "8.9.1-persistent-watermark-percent",
121+
file: "../fixtures/settings-8.9.1-watermark.json",
122+
want: `
123+
# HELP elasticsearch_clustersettings_stats_max_shards_per_node Current maximum number of shards per node setting.
124+
# TYPE elasticsearch_clustersettings_stats_max_shards_per_node gauge
125+
elasticsearch_clustersettings_stats_max_shards_per_node 1000
126+
# HELP elasticsearch_clustersettings_stats_shard_allocation_enabled Current mode of cluster wide shard routing allocation settings.
127+
# TYPE elasticsearch_clustersettings_stats_shard_allocation_enabled gauge
128+
elasticsearch_clustersettings_stats_shard_allocation_enabled 0
129+
# HELP elasticsearch_clustersettings_allocation_threshold_enabled Is disk allocation decider enabled.
130+
# TYPE elasticsearch_clustersettings_allocation_threshold_enabled gauge
131+
elasticsearch_clustersettings_allocation_threshold_enabled 1
132+
# HELP elasticsearch_clustersettings_allocation_watermark_flood_stage_ratio Flood stage watermark as a ratio.
133+
# TYPE elasticsearch_clustersettings_allocation_watermark_flood_stage_ratio gauge
134+
elasticsearch_clustersettings_allocation_watermark_flood_stage_ratio 0.96
135+
# HELP elasticsearch_clustersettings_allocation_watermark_high_ratio High watermark for disk usage as a ratio.
136+
# TYPE elasticsearch_clustersettings_allocation_watermark_high_ratio gauge
137+
elasticsearch_clustersettings_allocation_watermark_high_ratio 0.92
138+
# HELP elasticsearch_clustersettings_allocation_watermark_low_ratio Low watermark for disk usage as a ratio.
139+
# TYPE elasticsearch_clustersettings_allocation_watermark_low_ratio gauge
140+
elasticsearch_clustersettings_allocation_watermark_low_ratio 0.88
117141
`,
118142
},
119143
}

0 commit comments

Comments
 (0)