fluent-plugin-elasticsearch-stats 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|