fluent-plugin-elasticsearch-stats 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.gitlab-ci.yml +13 -0
- data/.rubocop.yml +36 -0
- data/.ruby-version +1 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +139 -0
- data/LICENSE +202 -0
- data/README.md +120 -0
- data/Rakefile +19 -0
- data/fluent-plugin-elasticsearch-stats.gemspec +40 -0
- data/lib/fluent/plugin/elasticsearch_stats/base_data.rb +123 -0
- data/lib/fluent/plugin/elasticsearch_stats/client.rb +150 -0
- data/lib/fluent/plugin/elasticsearch_stats/cluster_health_data.rb +25 -0
- data/lib/fluent/plugin/elasticsearch_stats/cluster_stats_data.rb +49 -0
- data/lib/fluent/plugin/elasticsearch_stats/collector.rb +97 -0
- data/lib/fluent/plugin/elasticsearch_stats/dangling_data.rb +32 -0
- data/lib/fluent/plugin/elasticsearch_stats/indices_stats_data.rb +34 -0
- data/lib/fluent/plugin/elasticsearch_stats/metadata.rb +47 -0
- data/lib/fluent/plugin/elasticsearch_stats/metric.rb +101 -0
- data/lib/fluent/plugin/elasticsearch_stats/nodes_stats_data.rb +42 -0
- data/lib/fluent/plugin/elasticsearch_stats/utils.rb +42 -0
- data/lib/fluent/plugin/elasticsearch_stats.rb +13 -0
- data/lib/fluent/plugin/in_elasticsearch_stats.rb +191 -0
- metadata +253 -0
@@ -0,0 +1,123 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'metadata'
|
4
|
+
require_relative 'metric'
|
5
|
+
|
6
|
+
module Fluent
|
7
|
+
module Plugin
|
8
|
+
module ElasticsearchStats
|
9
|
+
class BaseData
|
10
|
+
attr_reader :data
|
11
|
+
|
12
|
+
# FIXME
|
13
|
+
# skip_system_indices
|
14
|
+
def initialize(data, family: self.class::NAME, metadata: Metadata.new, metric: Metric.new)
|
15
|
+
@data = data
|
16
|
+
@family = family
|
17
|
+
@metadata = metadata
|
18
|
+
@metric = metric
|
19
|
+
|
20
|
+
initialize_metadata
|
21
|
+
end
|
22
|
+
|
23
|
+
def extract_metrics
|
24
|
+
[]
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
attr_reader :family, :metadata, :metric
|
30
|
+
|
31
|
+
def initialize_metadata
|
32
|
+
metadata.set(label: 'cluster_name', value: cluster_name) if cluster_name
|
33
|
+
end
|
34
|
+
|
35
|
+
def cluster_name
|
36
|
+
data['cluster_name']
|
37
|
+
end
|
38
|
+
|
39
|
+
def extract_indices_metrics
|
40
|
+
base_metrics = extract_indices_base_metrics
|
41
|
+
aggregated_metrics = extract_indices_aggregated_metrics(base_metrics)
|
42
|
+
|
43
|
+
base_metrics + aggregated_metrics
|
44
|
+
end
|
45
|
+
|
46
|
+
def extract_indices_base_metrics
|
47
|
+
return [] if !indices || indices.empty?
|
48
|
+
|
49
|
+
metrics = []
|
50
|
+
indices.each do |index_name, index_stats|
|
51
|
+
local_metadata = metadata.dup.set(label: 'index', value: index_name)
|
52
|
+
flattened_stats = Utils.hash_flatten_keys(index_stats, separator: metric.name_separator)
|
53
|
+
flattened_stats.each do |k, v|
|
54
|
+
metrics << metric.format(name: ['index', k], value: v, family: family, metadata: local_metadata)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
metrics.compact
|
58
|
+
end
|
59
|
+
|
60
|
+
def extract_indices_aggregated_metrics(base_metrics)
|
61
|
+
metrics = []
|
62
|
+
|
63
|
+
grouped_metrics = {}
|
64
|
+
base_metrics.each do |base_metric|
|
65
|
+
index_base = base_metric[metadata.label_for('index_base')]
|
66
|
+
next unless index_base
|
67
|
+
|
68
|
+
base_metric_name = base_metric[metric.name_label]
|
69
|
+
grouped_metrics[index_base] ||= {}
|
70
|
+
grouped_metrics[index_base][base_metric_name] ||= []
|
71
|
+
grouped_metrics[index_base][base_metric_name] << base_metric
|
72
|
+
end
|
73
|
+
|
74
|
+
grouped_metrics.each do |index_base, index_base_metrics|
|
75
|
+
local_metadata = metadata.dup
|
76
|
+
.set(label: 'index', value: index_base)
|
77
|
+
.set(label: 'aggregated', value: true)
|
78
|
+
|
79
|
+
index_base_metrics.each do |metric_name, metric_name_metrics|
|
80
|
+
metric_values = metric_name_metrics.map { |a_metric| a_metric[metric.value_label] }
|
81
|
+
count = metric_values.size
|
82
|
+
min = metric_values.min
|
83
|
+
max = metric_values.max
|
84
|
+
sum = metric_values.sum
|
85
|
+
avg = sum / count.to_f
|
86
|
+
|
87
|
+
metrics << metric.format(name: [metric_name, 'count'],
|
88
|
+
value: count,
|
89
|
+
family: family,
|
90
|
+
metadata: local_metadata)
|
91
|
+
metrics << metric.format(name: [metric_name, 'min'],
|
92
|
+
value: min,
|
93
|
+
family: family,
|
94
|
+
metadata: local_metadata)
|
95
|
+
metrics << metric.format(name: [metric_name, 'max'],
|
96
|
+
value: max,
|
97
|
+
family: family,
|
98
|
+
metadata: local_metadata)
|
99
|
+
metrics << metric.format(name: [metric_name, 'sum'],
|
100
|
+
value: sum,
|
101
|
+
family: family,
|
102
|
+
metadata: local_metadata)
|
103
|
+
metrics << metric.format(name: [metric_name, 'avg'],
|
104
|
+
value: avg,
|
105
|
+
family: family,
|
106
|
+
metadata: local_metadata)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
metrics.compact
|
111
|
+
end
|
112
|
+
|
113
|
+
def status
|
114
|
+
data['status']
|
115
|
+
end
|
116
|
+
|
117
|
+
def indices
|
118
|
+
data['indices']
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'faraday'
|
4
|
+
|
5
|
+
module Fluent
|
6
|
+
module Plugin
|
7
|
+
module ElasticsearchStats
|
8
|
+
class Client
|
9
|
+
class Error < StandardError
|
10
|
+
end
|
11
|
+
|
12
|
+
TIMEOUT = 10
|
13
|
+
USER_AGENT = 'elasticsearch_stats'
|
14
|
+
|
15
|
+
LOCAL = false
|
16
|
+
CLUSTER_HEALTH_LEVEL = 'cluster'
|
17
|
+
NODES_STATS_LEVEL = 'cluster'
|
18
|
+
INDICES_STATS_LEVEL = 'indices'
|
19
|
+
|
20
|
+
ALLOWED_CLUSTER_HEALTH_LEVELS = %i[cluster indices shards].freeze
|
21
|
+
ALLOWED_NODES_STATS_LEVELS = %i[nodes indices shards].freeze
|
22
|
+
ALLOWED_NODES_STATS_METRICS = %i[
|
23
|
+
adaptive_selection
|
24
|
+
breaker
|
25
|
+
discovery
|
26
|
+
fs
|
27
|
+
http
|
28
|
+
indexing_pressure
|
29
|
+
indices
|
30
|
+
ingest
|
31
|
+
jvm
|
32
|
+
os
|
33
|
+
process
|
34
|
+
repositories
|
35
|
+
thread_pool
|
36
|
+
transport
|
37
|
+
].freeze
|
38
|
+
ALLOWED_INDICES_LEVELS = %i[cluster indices shards].freeze
|
39
|
+
ALLOWED_INDICES_METRICS = %i[
|
40
|
+
_all
|
41
|
+
completion
|
42
|
+
docs
|
43
|
+
fielddata
|
44
|
+
flush
|
45
|
+
get
|
46
|
+
indexing
|
47
|
+
merge
|
48
|
+
query_cache
|
49
|
+
refresh
|
50
|
+
request_cache
|
51
|
+
search
|
52
|
+
segments
|
53
|
+
store
|
54
|
+
translog
|
55
|
+
].freeze
|
56
|
+
|
57
|
+
attr_reader :url, :timeout, :username, :password, :user_agent,
|
58
|
+
:ca_file, :verify_ssl, :log, :client
|
59
|
+
|
60
|
+
def initialize(url:, timeout: TIMEOUT, username: nil, password: nil,
|
61
|
+
user_agent: USER_AGENT, ca_file: nil, verify_ssl: true,
|
62
|
+
log: nil)
|
63
|
+
@url = url
|
64
|
+
@timeout = timeout
|
65
|
+
@username = username
|
66
|
+
@password = password
|
67
|
+
@user_agent = user_agent
|
68
|
+
@ca_file = ca_file
|
69
|
+
@verify_ssl = verify_ssl
|
70
|
+
@log = log
|
71
|
+
end
|
72
|
+
|
73
|
+
# https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-health.html
|
74
|
+
def cluster_health(level: CLUSTER_HEALTH_LEVEL, local: LOCAL)
|
75
|
+
endpoint = '/_cluster/health'
|
76
|
+
params = { level: level, local: local, timeout: "#{timeout}s" }
|
77
|
+
get(endpoint, params)
|
78
|
+
end
|
79
|
+
|
80
|
+
# https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-stats.html
|
81
|
+
def cluster_stats
|
82
|
+
endpoint = '/_cluster/stats'
|
83
|
+
params = { timeout: "#{timeout}s" }
|
84
|
+
get(endpoint, params)
|
85
|
+
end
|
86
|
+
|
87
|
+
# https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-nodes-stats.html
|
88
|
+
def nodes_stats(level: NODES_STATS_LEVEL, metrics: nil)
|
89
|
+
endpoint = '/_nodes/stats'
|
90
|
+
endpoint += "/#{metrics.join(',')}" if metrics&.any?
|
91
|
+
params = { level: level, timeout: "#{timeout}s" }
|
92
|
+
get(endpoint, params)
|
93
|
+
end
|
94
|
+
|
95
|
+
# https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-stats.html
|
96
|
+
def indices_stats(indices: [:_all], level: INDICES_STATS_LEVEL, metrics: nil)
|
97
|
+
endpoint = "/_stats"
|
98
|
+
endpoint = "/#{indices.join(',')}#{endpoint}" if !indices.nil? && !indices.empty?
|
99
|
+
endpoint += "/#{metrics.join(',')}" if metrics&.any?
|
100
|
+
params = {
|
101
|
+
level: level
|
102
|
+
}
|
103
|
+
get(endpoint, params)
|
104
|
+
end
|
105
|
+
|
106
|
+
# https://www.elastic.co/guide/en/elasticsearch/reference/current/dangling-indices-list.html
|
107
|
+
def dangling
|
108
|
+
endpoint = '/_dangling'
|
109
|
+
get(endpoint)
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def get(endpoint, params = nil, header = nil)
|
115
|
+
response = conn.get(endpoint, params, header)
|
116
|
+
response.body
|
117
|
+
rescue Faraday::Error => e
|
118
|
+
log.error("while get #{endpoint}: #{e}")
|
119
|
+
raise Error, "error on get #{endpoint}", e
|
120
|
+
end
|
121
|
+
|
122
|
+
def conn
|
123
|
+
faraday_options = {
|
124
|
+
request: {
|
125
|
+
open_timeout: timeout + 1,
|
126
|
+
read_timeout: timeout + 1,
|
127
|
+
write_timeout: timeout + 1
|
128
|
+
},
|
129
|
+
ssl: {
|
130
|
+
verify: verify_ssl,
|
131
|
+
ca_file: ca_file
|
132
|
+
},
|
133
|
+
headers: {
|
134
|
+
'User-Agent' => user_agent
|
135
|
+
}
|
136
|
+
}
|
137
|
+
|
138
|
+
@conn ||= Faraday.new(url: url, **faraday_options) do |config|
|
139
|
+
config.request :authorization, :basic, username, password if username && password
|
140
|
+
config.request :json
|
141
|
+
config.response :json
|
142
|
+
config.response :raise_error
|
143
|
+
config.response :logger, log, headers: false, bodies: false, log_level: :debug if log
|
144
|
+
config.adapter :net_http
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_data'
|
4
|
+
|
5
|
+
module Fluent
|
6
|
+
module Plugin
|
7
|
+
module ElasticsearchStats
|
8
|
+
class ClusterHealthData < BaseData
|
9
|
+
NAME = 'cluster_health'
|
10
|
+
|
11
|
+
def extract_metrics
|
12
|
+
extract_cluster_metrics + extract_indices_metrics
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def extract_cluster_metrics
|
18
|
+
data.each_with_object([]) do |(k, v), metrics|
|
19
|
+
metrics << metric.format(name: ['cluster', k], value: v, family: family, metadata: metadata)
|
20
|
+
end.compact
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_data'
|
4
|
+
|
5
|
+
module Fluent
|
6
|
+
module Plugin
|
7
|
+
module ElasticsearchStats
|
8
|
+
class ClusterStatsData < BaseData
|
9
|
+
NAME = 'cluster_stats'
|
10
|
+
|
11
|
+
def extract_metrics
|
12
|
+
extract_base_metrics + extract_indices_metrics + extract_nodes_metrics
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def extract_base_metrics
|
18
|
+
status_metric = metric.format(
|
19
|
+
name: %w[cluster status], value: status, family: family, metadata: metadata
|
20
|
+
)
|
21
|
+
[status_metric]
|
22
|
+
end
|
23
|
+
|
24
|
+
def extract_indices_metrics
|
25
|
+
metrics = []
|
26
|
+
|
27
|
+
flattened_data = Utils.hash_flatten_keys(indices, separator: metric.name_separator)
|
28
|
+
flattened_data.each do |k, v|
|
29
|
+
metrics << metric.format(name: ['indices', k], value: v, family: family, metadata: metadata)
|
30
|
+
end
|
31
|
+
metrics.compact
|
32
|
+
end
|
33
|
+
|
34
|
+
def extract_nodes_metrics
|
35
|
+
metrics = []
|
36
|
+
flattened_data = Utils.hash_flatten_keys(nodes, separator: metric.name_separator)
|
37
|
+
flattened_data.each do |k, v|
|
38
|
+
metrics << metric.format(name: ['nodes', k], value: v, family: family, metadata: metadata)
|
39
|
+
end
|
40
|
+
metrics.compact
|
41
|
+
end
|
42
|
+
|
43
|
+
def nodes
|
44
|
+
data['nodes']
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Fluent
|
4
|
+
module Plugin
|
5
|
+
module ElasticsearchStats
|
6
|
+
class Collector
|
7
|
+
attr_reader :client, :stats_config, :log
|
8
|
+
|
9
|
+
def initialize(client:,
|
10
|
+
stats_config: {},
|
11
|
+
log: nil)
|
12
|
+
@client = client
|
13
|
+
@stats_config = stats_config
|
14
|
+
@log = log
|
15
|
+
end
|
16
|
+
|
17
|
+
def collect_stats_metrics
|
18
|
+
metrics = []
|
19
|
+
metrics += collect_cluster_health_metrics
|
20
|
+
metrics += collect_cluster_stats_metrics
|
21
|
+
metrics += collect_nodes_stats_metrics
|
22
|
+
metrics += collect_indices_stats_metrics
|
23
|
+
metrics += collect_dangling_metrics
|
24
|
+
metrics.compact
|
25
|
+
end
|
26
|
+
|
27
|
+
def collect_cluster_health_metrics
|
28
|
+
return [] unless stats_config.cluster_health
|
29
|
+
|
30
|
+
without_error(rescue_return: []) do
|
31
|
+
data = client.cluster_health(
|
32
|
+
level: stats_config.cluster_health_level,
|
33
|
+
local: stats_config.cluster_health_local
|
34
|
+
)
|
35
|
+
ClusterHealthData.new(data).extract_metrics
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def collect_cluster_stats_metrics
|
40
|
+
return [] unless stats_config.cluster_stats
|
41
|
+
|
42
|
+
without_error(rescue_return: []) do
|
43
|
+
data = client.cluster_stats
|
44
|
+
ClusterStatsData.new(data).extract_metrics
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def collect_nodes_stats_metrics
|
49
|
+
return [] unless stats_config.nodes_stats
|
50
|
+
|
51
|
+
without_error(rescue_return: []) do
|
52
|
+
data = client.nodes_stats(
|
53
|
+
level: stats_config.nodes_stats_level,
|
54
|
+
metrics: stats_config.nodes_stats_metrics
|
55
|
+
)
|
56
|
+
NodesStatsData.new(data).extract_metrics
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def collect_indices_stats_metrics
|
61
|
+
return [] unless stats_config.indices_stats
|
62
|
+
|
63
|
+
without_error(rescue_return: []) do
|
64
|
+
data = client.indices_stats(
|
65
|
+
indices: stats_config.indices,
|
66
|
+
level: stats_config.indices_stats_level
|
67
|
+
)
|
68
|
+
IndicesStatsData.new(data).extract_metrics
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def collect_dangling_metrics
|
73
|
+
return [] unless stats_config.dangling
|
74
|
+
|
75
|
+
without_error(rescue_return: []) do
|
76
|
+
data = client.dangling
|
77
|
+
DanglingData.new(data).extract_metrics
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# FIXME: inject metadata !
|
82
|
+
# cluster metadata / info ?
|
83
|
+
def metadata
|
84
|
+
Metadata.new
|
85
|
+
.set(label: 'cluster_url', value: client.url)
|
86
|
+
end
|
87
|
+
|
88
|
+
def without_error(error: StandardError, rescue_return: nil)
|
89
|
+
yield
|
90
|
+
rescue error => e
|
91
|
+
log.error("while collecting metrics: #{e}")
|
92
|
+
rescue_return
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_data'
|
4
|
+
|
5
|
+
module Fluent
|
6
|
+
module Plugin
|
7
|
+
module ElasticsearchStats
|
8
|
+
class DanglingData < BaseData
|
9
|
+
NAME = 'dangling'
|
10
|
+
|
11
|
+
def extract_metrics
|
12
|
+
generate_dangling_indices_count
|
13
|
+
end
|
14
|
+
|
15
|
+
def generate_dangling_indices_count
|
16
|
+
metrics = []
|
17
|
+
metrics << metric.format(name: %w[dangling all count],
|
18
|
+
value: dangling_indices.size,
|
19
|
+
family: family,
|
20
|
+
metadata: metadata)
|
21
|
+
metrics
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def dangling_indices
|
27
|
+
data.fetch('dangling_indices', [])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_data'
|
4
|
+
|
5
|
+
module Fluent
|
6
|
+
module Plugin
|
7
|
+
module ElasticsearchStats
|
8
|
+
class IndicesStatsData < BaseData
|
9
|
+
NAME = 'indices_stats'
|
10
|
+
|
11
|
+
def extract_metrics
|
12
|
+
extract_all_metrics + extract_indices_metrics
|
13
|
+
end
|
14
|
+
|
15
|
+
def extract_all_metrics
|
16
|
+
return [] if !_all || _all.empty?
|
17
|
+
|
18
|
+
metrics = []
|
19
|
+
flattened = Utils.hash_flatten_keys(_all, separator: metric.name_separator)
|
20
|
+
flattened.each do |k, v|
|
21
|
+
metrics << metric.format(name: ['all_indices', k], value: v, family: family, metadata: metadata)
|
22
|
+
end
|
23
|
+
metrics.compact
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def _all
|
29
|
+
data['_all']
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Fluent
|
4
|
+
module Plugin
|
5
|
+
module ElasticsearchStats
|
6
|
+
class Metadata
|
7
|
+
class << self
|
8
|
+
attr_accessor :metadata_prefix
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :metadata, :metadata_prefix
|
12
|
+
|
13
|
+
def initialize(metadata = {}, metadata_prefix: self.class.metadata_prefix)
|
14
|
+
@metadata = {}.update(metadata)
|
15
|
+
@metadata_prefix = metadata_prefix
|
16
|
+
end
|
17
|
+
|
18
|
+
def set(label:, value:)
|
19
|
+
return self if label.nil? || label.to_s.empty?
|
20
|
+
|
21
|
+
@metadata[label_for(label)] = value
|
22
|
+
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
def get(label)
|
27
|
+
metadata[label_for(label)]
|
28
|
+
end
|
29
|
+
|
30
|
+
def dup
|
31
|
+
self.class.new(
|
32
|
+
metadata.clone,
|
33
|
+
metadata_prefix: metadata_prefix.clone
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
def label_for(label)
|
38
|
+
"#{metadata_prefix}#{label}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_h
|
42
|
+
metadata.clone
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Fluent
|
4
|
+
module Plugin
|
5
|
+
module ElasticsearchStats
|
6
|
+
class Metric
|
7
|
+
DEFAULT_NAME_SEPARATOR = '/'
|
8
|
+
DEFAULT_TIMESTAMP_FORMAT = :iso
|
9
|
+
|
10
|
+
class << self
|
11
|
+
attr_accessor :metric_prefix, :index_base_pattern, :index_base_replacement
|
12
|
+
|
13
|
+
def name_separator
|
14
|
+
@name_separator ||= DEFAULT_NAME_SEPARATOR
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_writer :name_separator, :timestamp_format
|
18
|
+
|
19
|
+
def timestamp_format
|
20
|
+
@timestamp_format ||= DEFAULT_TIMESTAMP_FORMAT
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :metric_prefix, :timestamp_format, :index_base_pattern, :index_base_replacement, :name_separator
|
25
|
+
|
26
|
+
def initialize(metric_prefix: nil, timestamp_format: nil, index_base_pattern: nil, index_base_replacement: nil,
|
27
|
+
name_separator: nil)
|
28
|
+
@metric_prefix = metric_prefix || self.class.metric_prefix
|
29
|
+
@timestamp_format = timestamp_format || self.class.timestamp_format
|
30
|
+
@index_base_pattern = index_base_pattern || self.class.index_base_pattern
|
31
|
+
@index_base_replacement = index_base_replacement || self.class.index_base_replacement
|
32
|
+
@name_separator = name_separator || self.class.name_separator
|
33
|
+
end
|
34
|
+
|
35
|
+
def convert_status(status)
|
36
|
+
case status.downcase
|
37
|
+
when 'green' then 1
|
38
|
+
when 'yellow' then 2
|
39
|
+
when 'red' then 3
|
40
|
+
else 0
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def timenow_epochmillis
|
45
|
+
(Time.now.utc.to_f * 1000).to_i
|
46
|
+
end
|
47
|
+
|
48
|
+
def timenow_iso
|
49
|
+
Time.now.utc.iso8601(3)
|
50
|
+
end
|
51
|
+
|
52
|
+
def timenow
|
53
|
+
send("timenow_#{timestamp_format}")
|
54
|
+
end
|
55
|
+
|
56
|
+
def format(name:, value:, family: nil, metadata: nil)
|
57
|
+
return if name.nil? || name.empty? || value.nil?
|
58
|
+
|
59
|
+
value = convert_status(value) if name == 'status' || name[-1] == 'status'
|
60
|
+
name = name.join(name_separator) if name.respond_to?(:join)
|
61
|
+
family = family.join(name_separator) if name.respond_to?(:join)
|
62
|
+
|
63
|
+
return unless value.is_a?(Numeric)
|
64
|
+
|
65
|
+
index_base = compute_index_base(metadata.get('index'))
|
66
|
+
local_metadata = metadata.dup
|
67
|
+
local_metadata.set(label: 'index_base', value: index_base) if index_base
|
68
|
+
|
69
|
+
metric = {}
|
70
|
+
metric.update(local_metadata.to_h)
|
71
|
+
metric['timestamp'] = timenow
|
72
|
+
metric[name_label] = name.to_s
|
73
|
+
metric[value_label] = value
|
74
|
+
metric[family_label] = family.to_s if family
|
75
|
+
metric
|
76
|
+
end
|
77
|
+
|
78
|
+
def name_label
|
79
|
+
"#{metric_prefix}name"
|
80
|
+
end
|
81
|
+
|
82
|
+
def value_label
|
83
|
+
"#{metric_prefix}value"
|
84
|
+
end
|
85
|
+
|
86
|
+
def family_label
|
87
|
+
"#{metric_prefix}family"
|
88
|
+
end
|
89
|
+
|
90
|
+
def compute_index_base(index_name)
|
91
|
+
return nil unless index_name
|
92
|
+
return nil if !index_base_pattern || !index_base_replacement
|
93
|
+
|
94
|
+
index_name.gsub(index_base_pattern, index_base_replacement)
|
95
|
+
rescue StandardError
|
96
|
+
nil
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_data'
|
4
|
+
|
5
|
+
module Fluent
|
6
|
+
module Plugin
|
7
|
+
module ElasticsearchStats
|
8
|
+
class NodesStatsData < BaseData
|
9
|
+
NAME = 'nodes_stats'
|
10
|
+
|
11
|
+
def extract_metrics
|
12
|
+
metrics = []
|
13
|
+
nodes.each_value do |node_stats|
|
14
|
+
metadata.dup
|
15
|
+
.set(label: 'hostname', value: node_stats['name'])
|
16
|
+
.set(label: 'host', value: node_stats['host'])
|
17
|
+
|
18
|
+
node_stats.each do |stat_family, stats|
|
19
|
+
next unless stats.is_a?(Hash)
|
20
|
+
|
21
|
+
stats.delete('timestamp')
|
22
|
+
stats.delete('indices') if stat_family == 'indices'
|
23
|
+
# FIXME: indices stats to extract ?
|
24
|
+
|
25
|
+
flattened_stats = Utils.hash_flatten_keys(stats, separator: metric.name_separator)
|
26
|
+
flattened_stats.each do |k, v|
|
27
|
+
metrics << metric.format(name: ['node', stat_family, k], value: v, family: family, metadata: metadata)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
metrics.compact
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def nodes
|
37
|
+
data['nodes']
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|