fluent-plugin-kubernetes-metrics 1.1.7 → 1.1.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +10 -10
- data/VERSION +1 -1
- data/lib/fluent/plugin/in_kubernetes_metrics.rb +77 -21
- data/test/plugin/test_in_kubernetes_metrics.rb +15 -14
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7cba1f817de8b2ebcb68a6f96dcb0776105c9330ed7b7e6d46888bd1fe2a8290
|
4
|
+
data.tar.gz: 79b5be5e27f54edad68981e9f48f8827fe698c93584115287ea41b594591e928
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4d4fd43d51357507698126408a618d38e81d7a5a9a956a3aafe4c46521b9d95bf22d680c52fca119d9c3f236b60dcdf028368948cc258981a64e34cd0f1e4ce8
|
7
|
+
data.tar.gz: 03ff9d6abf9511af983f52c1083062bc389fd976e0baf3045609df3a81e833e8ae2c787c5078870921a76246c37f3f2e01c3e20270ba0e0ca502aaa7105d8011
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
fluent-plugin-kubernetes-metrics (1.1.
|
4
|
+
fluent-plugin-kubernetes-metrics (1.1.10)
|
5
5
|
fluentd (>= 1.9.1)
|
6
6
|
kubeclient (~> 4.6.0)
|
7
7
|
multi_json (~> 1.14.1)
|
@@ -19,14 +19,14 @@ GEM
|
|
19
19
|
docile (1.4.0)
|
20
20
|
domain_name (0.5.20190701)
|
21
21
|
unf (>= 0.0.5, < 1.0.0)
|
22
|
-
ffi (1.15.
|
22
|
+
ffi (1.15.5)
|
23
23
|
ffi-compiler (1.0.1)
|
24
24
|
ffi (>= 1.0.0)
|
25
25
|
rake
|
26
|
-
fluentd (1.14.
|
26
|
+
fluentd (1.14.4)
|
27
27
|
bundler
|
28
28
|
cool.io (>= 1.4.5, < 2.0.0)
|
29
|
-
http_parser.rb (>= 0.5.1, < 0.
|
29
|
+
http_parser.rb (>= 0.5.1, < 0.9.0)
|
30
30
|
msgpack (>= 1.3.1, < 2.0.0)
|
31
31
|
serverengine (>= 2.2.2, < 3.0.0)
|
32
32
|
sigdump (~> 0.2.2)
|
@@ -47,16 +47,16 @@ GEM
|
|
47
47
|
http-form_data (2.3.0)
|
48
48
|
http-parser (1.2.3)
|
49
49
|
ffi-compiler (>= 1.0, < 2.0)
|
50
|
-
http_parser.rb (0.
|
50
|
+
http_parser.rb (0.8.0)
|
51
51
|
json (2.6.1)
|
52
52
|
kubeclient (4.6.0)
|
53
53
|
http (>= 3.0, < 5.0)
|
54
54
|
recursive-open-struct (~> 1.0, >= 1.0.4)
|
55
55
|
rest-client (~> 2.0)
|
56
|
-
mime-types (3.
|
56
|
+
mime-types (3.4.1)
|
57
57
|
mime-types-data (~> 3.2015)
|
58
|
-
mime-types-data (3.
|
59
|
-
msgpack (1.4.
|
58
|
+
mime-types-data (3.2022.0105)
|
59
|
+
msgpack (1.4.4)
|
60
60
|
multi_json (1.14.1)
|
61
61
|
netrc (0.11.0)
|
62
62
|
oj (3.10.18)
|
@@ -70,7 +70,7 @@ GEM
|
|
70
70
|
mime-types (>= 1.16, < 4.0)
|
71
71
|
netrc (~> 0.8)
|
72
72
|
rexml (3.2.5)
|
73
|
-
serverengine (2.2.
|
73
|
+
serverengine (2.2.5)
|
74
74
|
sigdump (~> 0.2.2)
|
75
75
|
sigdump (0.2.4)
|
76
76
|
simplecov (0.16.1)
|
@@ -107,4 +107,4 @@ DEPENDENCIES
|
|
107
107
|
webmock (~> 3.5.1)
|
108
108
|
|
109
109
|
BUNDLED WITH
|
110
|
-
2.
|
110
|
+
2.3.9
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.1.
|
1
|
+
1.1.10
|
@@ -90,8 +90,13 @@ module Fluent
|
|
90
90
|
super
|
91
91
|
|
92
92
|
timer_execute :metric_scraper, @interval, &method(:scrape_metrics)
|
93
|
-
timer_execute :stats_metric_scraper, @interval, &method(:scrape_stats_metrics)
|
94
93
|
timer_execute :cadvisor_metric_scraper, @interval, &method(:scrape_cadvisor_metrics)
|
94
|
+
# It is done to optionally fetch from 'stats' for k8s version <1.21
|
95
|
+
if is_stats_endpoint_available?
|
96
|
+
timer_execute :stats_metric_scraper, @interval, &method(:scrape_stats_metrics)
|
97
|
+
else
|
98
|
+
log.info "'/stats' endpoint is not available. It has been deprecated since k8s v1.15, disabled since v1.18, and removed in v1.21 and onwards"
|
99
|
+
end
|
95
100
|
end
|
96
101
|
|
97
102
|
def close
|
@@ -310,7 +315,7 @@ module Fluent
|
|
310
315
|
unless metrics['time'].nil?
|
311
316
|
time = parse_time metrics['time']
|
312
317
|
if usage_rate = metrics['usageNanoCores']
|
313
|
-
router.emit generate_tag("#{tag}.cpu.usage_rate"), time, labels.merge('value' => usage_rate / 1_000_000)
|
318
|
+
router.emit generate_tag("#{tag}.cpu.usage_rate"), time, labels.merge('value' => usage_rate / 1_000_000.0)
|
314
319
|
end
|
315
320
|
if usage = metrics['usageNanoCores']
|
316
321
|
router.emit generate_tag("#{tag}.cpu.usage"), time, labels.merge('value' => usage)
|
@@ -411,13 +416,13 @@ module Fluent
|
|
411
416
|
|
412
417
|
def emit_cpu_metrics_stats(tag:, metrics:, labels:, time:)
|
413
418
|
if cpu_usage_total = metrics['usage']['total']
|
414
|
-
router.emit generate_tag("#{tag}.cpu.usage.total"), time, labels.merge('value' => cpu_usage_total / 1_000_000)
|
419
|
+
router.emit generate_tag("#{tag}.cpu.usage.total"), time, labels.merge('value' => cpu_usage_total / 1_000_000.0)
|
415
420
|
end
|
416
421
|
if cpu_usage_user = metrics['usage']['user']
|
417
|
-
router.emit generate_tag("#{tag}.cpu.usage.user"), time, labels.merge('value' => cpu_usage_user / 1_000_000)
|
422
|
+
router.emit generate_tag("#{tag}.cpu.usage.user"), time, labels.merge('value' => cpu_usage_user / 1_000_000.0)
|
418
423
|
end
|
419
424
|
if cpu_usage_system = metrics['usage']['system']
|
420
|
-
router.emit generate_tag("#{tag}.cpu.usage.system"), time, labels.merge('value' => cpu_usage_system / 1_000_000)
|
425
|
+
router.emit generate_tag("#{tag}.cpu.usage.system"), time, labels.merge('value' => cpu_usage_system / 1_000_000.0)
|
421
426
|
end
|
422
427
|
|
423
428
|
if cpu_cfs_periods = metrics['cfs']['periods']
|
@@ -571,8 +576,42 @@ module Fluent
|
|
571
576
|
|
572
577
|
unless pod['startTime'].nil?
|
573
578
|
emit_uptime tag: tag, start_time: pod['startTime'], labels: labels
|
574
|
-
|
575
|
-
|
579
|
+
if pod['cpu'].nil?
|
580
|
+
if pod['containers'].nil? or Array(pod['containers']).empty?
|
581
|
+
log.warn "Summary API response has no pod cpu metrics information"
|
582
|
+
else
|
583
|
+
usageNanoCores = 0
|
584
|
+
usageCoreNanoSeconds = 0
|
585
|
+
time = nil
|
586
|
+
Array(pod['containers']).each do |container|
|
587
|
+
time = container['time'] unless container['time'].nil?
|
588
|
+
usageNanoCores += container['usageNanoCores']
|
589
|
+
usageCoreNanoSeconds += container['usageCoreNanoSeconds']
|
590
|
+
end
|
591
|
+
pod['cpu'] = { 'time' => time, 'usageNanoCores' => usageNanoCores, 'usageCoreNanoSeconds' => usageCoreNanoSeconds }
|
592
|
+
end
|
593
|
+
end
|
594
|
+
emit_cpu_metrics tag: tag, metrics: pod['cpu'], labels: labels unless pod['cpu'].nil?
|
595
|
+
if pod['memory'].nil?
|
596
|
+
if pod['containers'].nil? or Array(pod['containers']).empty?
|
597
|
+
log.warn "Summary API response has no pod memory metrics information"
|
598
|
+
else
|
599
|
+
Array(pod['containers']).each do |container|
|
600
|
+
time = nil
|
601
|
+
memory_metrics = {}
|
602
|
+
%w[availableBytes usageBytes workingSetBytes rssBytes pageFaults majorPageFaults].each do |name|
|
603
|
+
time = container['time'] unless container['time'].nil?
|
604
|
+
if value = metrics[name]
|
605
|
+
memory_metrics[name] = 0 if memory_metrics[name].nil?
|
606
|
+
memory_metrics[name] += value
|
607
|
+
end
|
608
|
+
end
|
609
|
+
end
|
610
|
+
memory_metrics['time'] = time
|
611
|
+
pod['memory'] = memory_metrics
|
612
|
+
end
|
613
|
+
end
|
614
|
+
emit_memory_metrics tag: tag, metrics: pod['memory'], labels: labels unless pod['memory'].nil?
|
576
615
|
emit_network_metrics tag: tag, metrics: pod['network'], labels: labels unless pod['network'].nil?
|
577
616
|
emit_fs_metrics tag: "#{tag}.ephemeral-storage", metrics: pod['ephemeral-storage'], labels: labels unless pod['ephemeral-storage'].nil?
|
578
617
|
unless pod['volume'].nil?
|
@@ -590,6 +629,7 @@ module Fluent
|
|
590
629
|
|
591
630
|
def emit_metrics(metrics)
|
592
631
|
emit_node_metrics(metrics['node']) unless metrics['node'].nil?
|
632
|
+
log.warn "Summary API received empty pods info" if (metrics['pods'].nil? or metrics['pods'].empty?)
|
593
633
|
Array(metrics['pods']).each &method(:emit_pod_metrics).curry.call(metrics['node']['nodeName']) unless metrics['pods'].nil?
|
594
634
|
end
|
595
635
|
|
@@ -597,33 +637,35 @@ module Fluent
|
|
597
637
|
emit_stats_breakdown(metrics['stats']) unless metrics['stats'].nil?
|
598
638
|
end
|
599
639
|
|
640
|
+
# Make sure regex has only one capturing group
|
641
|
+
def grep_using_regex(metric, regex)
|
642
|
+
match = metric.match(regex)
|
643
|
+
return nil if match.nil?
|
644
|
+
match[1]
|
645
|
+
end
|
646
|
+
|
600
647
|
def emit_cadvisor_metrics(metrics)
|
601
648
|
metrics = metrics.split("\n")
|
602
649
|
metrics.each do |metric|
|
603
|
-
|
604
|
-
|
605
|
-
next
|
606
|
-
|
650
|
+
|
651
|
+
next if metric[0] == '#' or not container_name = grep_using_regex(metric, /container(?:_name)?="([^"]*)"/)
|
652
|
+
next if container_name.empty?
|
653
|
+
|
607
654
|
metric_str, metric_val = metric.split(' ')
|
608
655
|
metric_val = metric_val.to_f if metric_val.is_a? String
|
609
656
|
first_occur = metric_str.index('{')
|
610
657
|
metric_name = metric_str[0..first_occur - 1]
|
611
|
-
pod_name = metric
|
612
|
-
|
613
|
-
|
614
|
-
image_name = image_name.split('"')[1]
|
615
|
-
namespace = metric.match(/namespace="\S*"/).to_s
|
616
|
-
namespace = namespace.split('"')[1]
|
658
|
+
pod_name = grep_using_regex(metric, /pod(?:_name)?="([^"]*)"/).to_s
|
659
|
+
image_name = grep_using_regex(metric, /image="([^"]*)"/).to_s
|
660
|
+
namespace = grep_using_regex(metric, /namespace="([^"]*)"/).to_s
|
617
661
|
metric_labels = { 'pod_name' => pod_name, 'image' => image_name, 'namespace' => namespace, 'value' => metric_val, 'node' => @node_name }
|
618
|
-
if
|
662
|
+
if container_name=="POD"
|
619
663
|
tag = 'pod'
|
620
664
|
tag = generate_tag("#{tag}#{metric_name.tr('_', '.')}")
|
621
665
|
tag = tag.gsub('container', '')
|
622
666
|
else
|
623
|
-
container_name = metric.match(/container_name="\S*"/).to_s
|
624
|
-
container_name = container_name.split('"')[1]
|
625
667
|
container_label = { 'container_name' => container_name }
|
626
|
-
metric_labels.merge(container_label)
|
668
|
+
metric_labels.merge!(container_label)
|
627
669
|
tag = generate_tag(metric_name.tr('_', '.').to_s)
|
628
670
|
end
|
629
671
|
router.emit tag, @scraped_at_cadvisor, metric_labels
|
@@ -642,6 +684,20 @@ module Fluent
|
|
642
684
|
end
|
643
685
|
end
|
644
686
|
|
687
|
+
def is_stats_endpoint_available?
|
688
|
+
if @use_rest_client
|
689
|
+
response_stats = RestClient::Request.execute request_options_stats
|
690
|
+
else
|
691
|
+
@node_names.each do |node|
|
692
|
+
@node_name = node
|
693
|
+
response_stats = stats_proxy_api(node).get(@client.headers)
|
694
|
+
end
|
695
|
+
end
|
696
|
+
true
|
697
|
+
rescue RestClient::NotFound
|
698
|
+
false
|
699
|
+
end
|
700
|
+
|
645
701
|
def scrape_stats_metrics
|
646
702
|
if @use_rest_client
|
647
703
|
response_stats = RestClient::Request.execute request_options_stats
|
@@ -35,6 +35,9 @@ class KubernetesMetricsInputTest < Test::Unit::TestCase
|
|
35
35
|
).freeze
|
36
36
|
|
37
37
|
setup do
|
38
|
+
stub_k8s_requests
|
39
|
+
|
40
|
+
return unless @@hash_map_test.empty?
|
38
41
|
Fluent::Test.setup
|
39
42
|
|
40
43
|
@@parsed_unit_string = JSON.parse(get_unit_parsed_string)
|
@@ -45,8 +48,6 @@ class KubernetesMetricsInputTest < Test::Unit::TestCase
|
|
45
48
|
get_cadvisor_parsed_string = f.read
|
46
49
|
end.close
|
47
50
|
|
48
|
-
stub_k8s_requests
|
49
|
-
|
50
51
|
@@ca_driver = create_driver
|
51
52
|
@@ca_driver.run timeout: 20, expect_emits: 1, shutdown: true
|
52
53
|
|
@@ -56,8 +57,12 @@ class KubernetesMetricsInputTest < Test::Unit::TestCase
|
|
56
57
|
metrics = get_cadvisor_parsed_string.split("\n")
|
57
58
|
metrics.each do |metric|
|
58
59
|
next unless metric.include? 'container_name='
|
60
|
+
next unless metric[0] != '#'
|
59
61
|
|
60
|
-
|
62
|
+
container_name = metric.match(/container_name="\S*"/).to_s
|
63
|
+
container_name = container_name.split('"')[1]
|
64
|
+
|
65
|
+
next if container_name.empty?
|
61
66
|
|
62
67
|
metric_str, metric_val = metric.split(' ')
|
63
68
|
metric_val = metric_val.to_f if metric_val.is_a? String
|
@@ -70,13 +75,11 @@ class KubernetesMetricsInputTest < Test::Unit::TestCase
|
|
70
75
|
namespace = metric.match(/namespace="\S*"/).to_s
|
71
76
|
namespace = namespace.split('"')[1]
|
72
77
|
metric_labels = { 'pod_name' => pod_name, 'image' => image_name, 'namespace' => namespace, 'value' => metric_val, 'node' => @node_name }
|
73
|
-
if
|
78
|
+
if container_name == 'POD'
|
74
79
|
tag = 'pod'
|
75
80
|
tag = generate_tag("#{tag}#{metric_name.tr('_', '.')}", @@driver.instance.tag)
|
76
81
|
tag = tag.gsub('container', '')
|
77
82
|
else
|
78
|
-
container_name = metric.match(/container_name="\S*"/).to_s
|
79
|
-
container_name = container_name.split('"')[1]
|
80
83
|
container_label = { 'container_name' => container_name }
|
81
84
|
metric_labels.merge(container_label)
|
82
85
|
tag = generate_tag(metric_name.tr('_', '.').to_s, @@driver.instance.tag)
|
@@ -112,7 +115,7 @@ class KubernetesMetricsInputTest < Test::Unit::TestCase
|
|
112
115
|
assert_equal @@parsed_unit_string['node']['cpu']['usageNanoCores'], @@hash_map_test['kube.node.cpu.usage'][2]['value']
|
113
116
|
|
114
117
|
assert_not_nil @@hash_map_test.key?('kube.node.cpu.usage_rate')
|
115
|
-
assert_equal @@parsed_unit_string['node']['cpu']['usageNanoCores'] / 1_000_000, @@hash_map_test['kube.node.cpu.usage_rate'][2]['value']
|
118
|
+
assert_equal @@parsed_unit_string['node']['cpu']['usageNanoCores'] / 1_000_000.0, @@hash_map_test['kube.node.cpu.usage_rate'][2]['value']
|
116
119
|
end
|
117
120
|
|
118
121
|
test 'test_emit_memory_metrics' do
|
@@ -252,11 +255,10 @@ class KubernetesMetricsInputTest < Test::Unit::TestCase
|
|
252
255
|
assert_true @@hash_map_cadvisor.key?('kube.container.fs.read.seconds.total')
|
253
256
|
assert_equal @@hash_map_cadvisor['kube.container.fs.read.seconds.total'], @@hash_map_test['kube.container.fs.read.seconds.total'][2]['value']
|
254
257
|
end
|
255
|
-
|
256
|
-
# TODO: Current Test does not work - metric present in metrics_cadvisor.txt but not being parsed by connector in test/working in production
|
258
|
+
|
257
259
|
test 'Test - metrics cadvisor: container_fs_reads_bytes_total' do
|
258
|
-
|
259
|
-
|
260
|
+
assert_true @@hash_map_cadvisor.key?('kube.container.fs.reads.bytes.total')
|
261
|
+
assert_equal @@hash_map_cadvisor['kube.container.fs.reads.bytes.total'], @@hash_map_test["kube.container.fs.reads.bytes.total"][2]["value"]
|
260
262
|
end
|
261
263
|
|
262
264
|
test 'Test - metrics cadvisor: container_fs_reads_merged_total' do
|
@@ -289,10 +291,9 @@ class KubernetesMetricsInputTest < Test::Unit::TestCase
|
|
289
291
|
assert_equal @@hash_map_cadvisor['kube.container.fs.write.seconds.total'], @@hash_map_test['kube.container.fs.write.seconds.total'][2]['value']
|
290
292
|
end
|
291
293
|
|
292
|
-
# TODO: Current Test does not work - metric present in metrics_cadvisor.txt but not being parsed by connector in test/working in production
|
293
294
|
test 'Test - metrics cadvisor: container_fs_writes_bytes_total' do
|
294
|
-
|
295
|
-
|
295
|
+
assert_true @@hash_map_cadvisor.key?('kube.container.fs.writes.bytes.total')
|
296
|
+
assert_equal @@hash_map_cadvisor['kube.container.fs.writes.bytes.total'], @@hash_map_test["kube.container.fs.writes.bytes.total"][2]["value"]
|
296
297
|
end
|
297
298
|
|
298
299
|
test 'Test - metrics cadvisor: container_fs_writes_merged_total' do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fluent-plugin-kubernetes-metrics
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Splunk Inc.
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-03-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -180,6 +180,6 @@ signing_key:
|
|
180
180
|
specification_version: 4
|
181
181
|
summary: A fluentd input plugin that collects kubernetes cluster metrics.
|
182
182
|
test_files:
|
183
|
-
- test/plugin/test_missing_timestamps.rb
|
184
|
-
- test/plugin/test_in_kubernetes_metrics.rb
|
185
183
|
- test/helper.rb
|
184
|
+
- test/plugin/test_in_kubernetes_metrics.rb
|
185
|
+
- test/plugin/test_missing_timestamps.rb
|