fluent-plugin-node-exporter-metrics 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.
Files changed (82) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/linux-test.yaml +42 -0
  3. data/.gitignore +2 -0
  4. data/CHANGELOG.md +6 -0
  5. data/Gemfile +3 -0
  6. data/LICENSE +202 -0
  7. data/README.md +167 -0
  8. data/Rakefile +13 -0
  9. data/docs/cpu.md +26 -0
  10. data/docs/cpufreq.md +43 -0
  11. data/docs/diskstats.md +27 -0
  12. data/docs/filefd.md +12 -0
  13. data/docs/loadavg.md +13 -0
  14. data/docs/meminfo.md +61 -0
  15. data/docs/netdev.md +26 -0
  16. data/docs/stat.md +17 -0
  17. data/docs/time.md +9 -0
  18. data/docs/uname.md +15 -0
  19. data/docs/vmstat.md +17 -0
  20. data/fluent-plugin-node-exporter-metrics.gemspec +31 -0
  21. data/lib/fluent/plugin/in_node_exporter_metrics.rb +138 -0
  22. data/lib/fluent/plugin/node_exporter/cmetrics_dataschema_parser.rb +84 -0
  23. data/lib/fluent/plugin/node_exporter/collector.rb +41 -0
  24. data/lib/fluent/plugin/node_exporter/cpu_collector.rb +130 -0
  25. data/lib/fluent/plugin/node_exporter/cpufreq_collector.rb +97 -0
  26. data/lib/fluent/plugin/node_exporter/diskstats_collector.rb +280 -0
  27. data/lib/fluent/plugin/node_exporter/filefd_collector.rb +60 -0
  28. data/lib/fluent/plugin/node_exporter/loadavg_collector.rb +64 -0
  29. data/lib/fluent/plugin/node_exporter/meminfo_collector.rb +61 -0
  30. data/lib/fluent/plugin/node_exporter/netdev_collector.rb +92 -0
  31. data/lib/fluent/plugin/node_exporter/stat_collector.rb +84 -0
  32. data/lib/fluent/plugin/node_exporter/time_collector.rb +50 -0
  33. data/lib/fluent/plugin/node_exporter/uname_collector.rb +64 -0
  34. data/lib/fluent/plugin/node_exporter/vmstat_collector.rb +56 -0
  35. data/lib/fluent/plugin/parser_node_exporter_metrics.rb +54 -0
  36. data/test/fixtures/cpu/with_thermal_throttle/proc/stat +3 -0
  37. data/test/fixtures/cpu/with_thermal_throttle/sys/devices/system/cpu/cpu0/thermal_throttle/core_throttle_count +1 -0
  38. data/test/fixtures/cpu/with_thermal_throttle/sys/devices/system/cpu/cpu0/thermal_throttle/package_throttle_count +1 -0
  39. data/test/fixtures/cpu/with_thermal_throttle/sys/devices/system/cpu/cpu0/topology/core_id +1 -0
  40. data/test/fixtures/cpu/with_thermal_throttle/sys/devices/system/cpu/cpu0/topology/physical_package_id +1 -0
  41. data/test/fixtures/cpu/with_thermal_throttle/sys/devices/system/cpu/cpu1/thermal_throttle/core_throttle_count +1 -0
  42. data/test/fixtures/cpu/with_thermal_throttle/sys/devices/system/cpu/cpu1/thermal_throttle/package_throttle_count +1 -0
  43. data/test/fixtures/cpu/with_thermal_throttle/sys/devices/system/cpu/cpu1/topology/core_id +1 -0
  44. data/test/fixtures/cpu/with_thermal_throttle/sys/devices/system/cpu/cpu1/topology/physical_package_id +1 -0
  45. data/test/fixtures/cpu/without_thermal_throttle/proc/stat +3 -0
  46. data/test/fixtures/cpufreq/with_cur_freq/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_cur_freq +1 -0
  47. data/test/fixtures/cpufreq/with_cur_freq/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq +1 -0
  48. data/test/fixtures/cpufreq/with_cur_freq/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq +1 -0
  49. data/test/fixtures/cpufreq/with_cur_freq/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq +1 -0
  50. data/test/fixtures/cpufreq/with_cur_freq/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq +1 -0
  51. data/test/fixtures/cpufreq/with_cur_freq/sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq +1 -0
  52. data/test/fixtures/cpufreq/with_cur_freq/sys/devices/system/cpu/cpu1/cpufreq/cpuinfo_cur_freq +1 -0
  53. data/test/fixtures/cpufreq/with_cur_freq/sys/devices/system/cpu/cpu1/cpufreq/cpuinfo_max_freq +1 -0
  54. data/test/fixtures/cpufreq/with_cur_freq/sys/devices/system/cpu/cpu1/cpufreq/cpuinfo_min_freq +1 -0
  55. data/test/fixtures/cpufreq/with_cur_freq/sys/devices/system/cpu/cpu1/cpufreq/scaling_cur_freq +1 -0
  56. data/test/fixtures/cpufreq/with_cur_freq/sys/devices/system/cpu/cpu1/cpufreq/scaling_max_freq +1 -0
  57. data/test/fixtures/cpufreq/with_cur_freq/sys/devices/system/cpu/cpu1/cpufreq/scaling_min_freq +1 -0
  58. data/test/fixtures/cpufreq/without_cur_freq/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq +1 -0
  59. data/test/fixtures/cpufreq/without_cur_freq/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq +1 -0
  60. data/test/fixtures/cpufreq/without_cur_freq/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq +1 -0
  61. data/test/fixtures/cpufreq/without_cur_freq/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq +1 -0
  62. data/test/fixtures/cpufreq/without_cur_freq/sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq +1 -0
  63. data/test/fixtures/cpufreq/without_cur_freq/sys/devices/system/cpu/cpu1/cpufreq/cpuinfo_max_freq +1 -0
  64. data/test/fixtures/cpufreq/without_cur_freq/sys/devices/system/cpu/cpu1/cpufreq/cpuinfo_min_freq +1 -0
  65. data/test/fixtures/cpufreq/without_cur_freq/sys/devices/system/cpu/cpu1/cpufreq/scaling_cur_freq +1 -0
  66. data/test/fixtures/cpufreq/without_cur_freq/sys/devices/system/cpu/cpu1/cpufreq/scaling_max_freq +1 -0
  67. data/test/fixtures/cpufreq/without_cur_freq/sys/devices/system/cpu/cpu1/cpufreq/scaling_min_freq +1 -0
  68. data/test/helper.rb +24 -0
  69. data/test/plugin/test_cmetrics_data_schema_parser.rb +83 -0
  70. data/test/plugin/test_cpu_collector.rb +84 -0
  71. data/test/plugin/test_cpufreq_collector.rb +75 -0
  72. data/test/plugin/test_diskstats_collector.rb +200 -0
  73. data/test/plugin/test_filefd_collector.rb +39 -0
  74. data/test/plugin/test_in_node_exporter_metrics.rb +583 -0
  75. data/test/plugin/test_loadavg_collector.rb +41 -0
  76. data/test/plugin/test_meminfo_collector.rb +47 -0
  77. data/test/plugin/test_netdev_collector.rb +35 -0
  78. data/test/plugin/test_stat_collector.rb +37 -0
  79. data/test/plugin/test_time_collector.rb +15 -0
  80. data/test/plugin/test_uname_collector.rb +47 -0
  81. data/test/plugin/test_vmstat_collector.rb +53 -0
  82. metadata +273 -0
data/docs/stat.md ADDED
@@ -0,0 +1,17 @@
1
+ # Stat Collector
2
+
3
+ ## Metric and label naming
4
+
5
+ * node_boot_time_seconds
6
+ * node_context_switches_total
7
+ * node_forks_total
8
+ * node_intr_total
9
+ * node_procs_blocked
10
+ * node_procs_running
11
+
12
+ ## Metric and its data sources
13
+
14
+ Stat collector access the following data sources.
15
+
16
+ * /proc/stat
17
+
data/docs/time.md ADDED
@@ -0,0 +1,9 @@
1
+ # Time Collector
2
+
3
+ ## Metric and label naming
4
+
5
+ * node_time_seconds
6
+
7
+ ## Metric and its data sources
8
+
9
+ * FluentEventTime.now
data/docs/uname.md ADDED
@@ -0,0 +1,15 @@
1
+ # Uname Collector
2
+
3
+ ## Metric and label naming
4
+
5
+ * node_uname_info {"sysname"=>..., "release"=>..., "version"=>..., "machine"=>..., "nodename"=>..., "domainname"=>...}
6
+
7
+
8
+ ## Metric and its data sources
9
+
10
+ Uname collector access the following data sources.
11
+
12
+ * Etc.uname
13
+
14
+ NOTE: `Etc.uname` returns at least sysname,release,version,machine,nodename
15
+ but it is not guaranteed to return domainname.
data/docs/vmstat.md ADDED
@@ -0,0 +1,17 @@
1
+ # Vmstat Collector
2
+
3
+ ## Metric and label naming
4
+
5
+ * node_vmstat_oom_kill
6
+ * node_vmstat_pgfault
7
+ * node_vmstat_pgmajfault
8
+ * node_vmstat_pgpgin
9
+ * node_vmstat_pgpgout
10
+ * node_vmstat_pswpin
11
+ * node_vmstat_pswpout
12
+
13
+ ## Metric and its data sources
14
+
15
+ Vmstat collector access the following data sources.
16
+
17
+ * /proc/vmstat
@@ -0,0 +1,31 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = "fluent-plugin-node-exporter-metrics"
6
+ spec.version = "0.1.0"
7
+ spec.authors = ["Kentaro Hayashi"]
8
+ spec.email = ["hayashi@clear-code.com"]
9
+
10
+ spec.summary = %q{Input plugin which collects metrics similar to Prometheus Node Exporter}
11
+ spec.description = %q{node exporter metrics input plugin implements 11 node exporter collectors}
12
+ spec.homepage = "https://github.com/fluent-plugins-nursery/fluent-plugin-node-exporter-metrics"
13
+ spec.license = "Apache-2.0"
14
+
15
+ test_files, files = `git ls-files -z`.split("\x0").partition do |f|
16
+ f.match(%r{^(test|spec|features)/})
17
+ end
18
+ spec.files = files
19
+ spec.executables = files.grep(%r{^bin/}) { |f| File.basename(f) }
20
+ spec.test_files = test_files
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_runtime_dependency "cmetrics", "~> 0.2.3"
24
+ spec.add_runtime_dependency "capng_c", "~> 0.2.2"
25
+
26
+ spec.add_development_dependency "bundler", "~> 2.2.0"
27
+ spec.add_development_dependency "rake", "~> 13.0.6"
28
+ spec.add_development_dependency "test-unit", "~> 3.4.4"
29
+ spec.add_development_dependency "test-unit-rr", "~> 1.0.5"
30
+ spec.add_runtime_dependency "fluentd", [">= 0.14.10", "< 2"]
31
+ end
@@ -0,0 +1,138 @@
1
+ #
2
+ # Copyright 2021- Kentaro Hayashi
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ require "cmetrics"
17
+ require "etc"
18
+ require "fluent/env"
19
+ require "fluent/capability"
20
+ require "fluent/plugin/input"
21
+ require "fluent/plugin/node_exporter/cpu_collector"
22
+ require "fluent/plugin/node_exporter/cpufreq_collector"
23
+ require "fluent/plugin/node_exporter/diskstats_collector"
24
+ require "fluent/plugin/node_exporter/filefd_collector"
25
+ require "fluent/plugin/node_exporter/loadavg_collector"
26
+ require "fluent/plugin/node_exporter/meminfo_collector"
27
+ require "fluent/plugin/node_exporter/netdev_collector"
28
+ require "fluent/plugin/node_exporter/stat_collector"
29
+ require "fluent/plugin/node_exporter/time_collector"
30
+ require "fluent/plugin/node_exporter/uname_collector"
31
+ require "fluent/plugin/node_exporter/vmstat_collector"
32
+
33
+ module Fluent
34
+ module Plugin
35
+ class NodeExporterMetricsInput < Fluent::Plugin::Input
36
+ Fluent::Plugin.register_input("node_exporter_metrics", self)
37
+
38
+ helpers :timer
39
+
40
+ desc "Interval to scrape metrics from node"
41
+ config_param :scrape_interval, :time, default: 5
42
+ desc "Path to mount point to collect process information and metrics"
43
+ config_param :procfs_path, :string, default: "/proc"
44
+ desc "Path to file-system to collect system metrics"
45
+ config_param :sysfs_path, :string, default: "/sys"
46
+ desc "Tag string"
47
+ config_param :tag, :string, default: nil
48
+ desc "Enable cpu collector"
49
+ config_param :cpu, :bool, default: true
50
+ desc "Enable cpufreq collector"
51
+ config_param :cpufreq, :bool, default: true
52
+ desc "Enable diskstats collector"
53
+ config_param :diskstats, :bool, default: true
54
+ desc "Enable filefd collector"
55
+ config_param :filefd, :bool, default: true
56
+ desc "Enable loadavg collector"
57
+ config_param :loadavg, :bool, default: true
58
+ desc "Enable meminfo collector"
59
+ config_param :meminfo, :bool, default: true
60
+ desc "Enable netdev collector"
61
+ config_param :netdev, :bool, default: true
62
+ desc "Enable stat collector"
63
+ config_param :stat, :bool, default: true
64
+ desc "Enable time collector"
65
+ config_param :time, :bool, default: true
66
+ desc "Enable uname collector"
67
+ config_param :uname, :bool, default: true
68
+ desc "Enable vmstat collector"
69
+ config_param :vmstat, :bool, default: true
70
+
71
+ def configure(conf)
72
+ super
73
+ @collectors = []
74
+ config = {
75
+ procfs_path: @procfs_path,
76
+ sysfs_path: @sysfs_path
77
+ }
78
+ @collectors << NodeExporter::CpuMetricsCollector.new(config) if @cpu
79
+ @collectors << NodeExporter::DiskstatsMetricsCollector.new(config) if @diskstats
80
+ @collectors << NodeExporter::FilefdMetricsCollector.new(config) if @filefd
81
+ @collectors << NodeExporter::LoadavgMetricsCollector.new(config) if @loadavg
82
+ @collectors << NodeExporter::MeminfoMetricsCollector.new(config) if @meminfo
83
+ @collectors << NodeExporter::NetdevMetricsCollector.new(config) if @netdev
84
+ @collectors << NodeExporter::StatMetricsCollector.new(config) if @stat
85
+ @collectors << NodeExporter::TimeMetricsCollector.new(config) if @time
86
+ @collectors << NodeExporter::UnameMetricsCollector.new(config) if @uname
87
+ @collectors << NodeExporter::VmstatMetricsCollector.new(config) if @vmstat
88
+
89
+ if Fluent.linux?
90
+ if @cpufreq
91
+ @capability = Fluent::Capability.new(:current_process)
92
+ unless Etc.getpwuid.uid != 0 and @capability.have_capability?(:effective, :dac_read_search)
93
+ raise ConfigError, "Linux capability CAP_DAC_READ_SEARCH must be enabled"
94
+ end
95
+ @collectors << NodeExporter::CpufreqMetricsCollector.new(config) if @cpufreq
96
+ end
97
+ elsif Fluent.windows?
98
+ raise ConfigError, "node_exporter_metrics is not supported"
99
+ end
100
+
101
+ if @collectors.empty?
102
+ raise ConfigError, "all collectors are disabled. Enable at least one collector."
103
+ end
104
+ end
105
+
106
+ def start
107
+ super
108
+ timer_execute(:execute_node_exporter_metrics, @scrape_interval, &method(:refresh_watchers))
109
+ end
110
+
111
+ def refresh_watchers
112
+ begin
113
+ @serde = CMetrics::Serde.new
114
+ @collectors.each do |collector|
115
+ begin
116
+ collector.run
117
+ collector.cmetrics.each do |key, cmetric|
118
+ @serde.concat(cmetric) if cmetric
119
+ end
120
+ rescue => e
121
+ $log.error(e.message)
122
+ end
123
+ end
124
+ record = {
125
+ "cmetrics" => @serde.to_msgpack
126
+ }
127
+ es = OneEventStream.new(Fluent::EventTime.now, record)
128
+ router.emit_stream(@tag, es)
129
+ end
130
+ rescue => e
131
+ $log.error(e.message)
132
+ end
133
+
134
+ def shutdown
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,84 @@
1
+ #
2
+ # Copyright 2021- Kentaro Hayashi
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ require "cmetrics"
17
+ require "fluent/plugin/input"
18
+
19
+ module Fluent
20
+ module Plugin
21
+ module NodeExporter
22
+ class CMetricsDataSchemaParser
23
+ def parse(metrics)
24
+ data = []
25
+ begin
26
+ metrics.each do |metric|
27
+ next if metric["values"].empty?
28
+ data << to_readable_hash(metric)
29
+ end
30
+ rescue => e
31
+ raise Fluent::ParserError.new(e.message)
32
+ end
33
+ data.flatten
34
+ end
35
+
36
+ # Parsed CMetrics Data Schema Format
37
+ # {
38
+ # "name" => metrics name
39
+ # "time" => Fluent::EventTime
40
+ # "labels" => {"key" => value, ...}
41
+ # "value" => ...
42
+ # }
43
+ #
44
+ # "labels" field is optional. It is available when {"meta"=>"label_dictionary"...} and
45
+ # "labels" in "values" => [{"ts"=>..., "labels"=>[...]}]..
46
+ #
47
+ def to_readable_hash(metrics)
48
+ opts = metrics["meta"]["opts"]
49
+
50
+ metric_name = if opts["ss"].size.zero?
51
+ "#{opts['ns']}_#{opts['name']}"
52
+ else
53
+ "#{opts['ns']}_#{opts['ss']}_#{opts['name']}"
54
+ end
55
+ cmetrics = []
56
+ labels = []
57
+ unless metrics["meta"]["labels"].empty?
58
+ metrics["meta"]["labels"].each do |v|
59
+ labels << metrics["meta"]["label_dictionary"][v]
60
+ end
61
+ end
62
+ metrics["values"].each do |entry|
63
+ cmetric = {
64
+ "name" => metric_name,
65
+ "value" => entry["value"],
66
+ "desc" => opts["desc"],
67
+ "ts" => entry["ts"]
68
+ }
69
+ unless metrics["meta"]["labels"].empty?
70
+ params = {}
71
+ entry["labels"].each_with_index do |v, index|
72
+ label = labels[index]
73
+ params[label] = metrics["meta"]["label_dictionary"][v]
74
+ end
75
+ cmetric["labels"] = params
76
+ end
77
+ cmetrics << cmetric
78
+ end
79
+ cmetrics
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,41 @@
1
+ #
2
+ # Copyright 2021- Kentaro Hayashi
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ require "cmetrics"
17
+ require "fluent/plugin/input"
18
+
19
+ module Fluent
20
+ module Plugin
21
+ module NodeExporter
22
+ class MetricsCollector
23
+ def initialize(config={})
24
+ @scrape_interval = config[:scrape_interval] || 5
25
+ @procfs_path = config[:procfs_path] || "/proc"
26
+ @sysfs_path = config[:sysfs_path] || "/sys"
27
+ end
28
+
29
+ def scan_sysfs_path(pattern)
30
+ Dir.glob(File.join(@sysfs_path, pattern)).sort do |a, b|
31
+ File.basename(a).delete("a-z").to_i <=> File.basename(b).delete("a-z").to_i
32
+ end
33
+ end
34
+
35
+ def cmetrics
36
+ raise NotImplementedError
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,130 @@
1
+ #
2
+ # Copyright 2021- Kentaro Hayashi
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ require "cmetrics"
17
+ require "etc"
18
+ require "fluent/plugin/input"
19
+ require "fluent/plugin/node_exporter/collector"
20
+
21
+ module Fluent
22
+ module Plugin
23
+ module NodeExporter
24
+ class CpuMetricsCollector < MetricsCollector
25
+ def initialize(config={})
26
+ super(config)
27
+
28
+ # It varies whether /sys/devices/system/cpu/cpuN/thermal_throttle exists or not
29
+ @thermal_throttle_path = File.join(@sysfs_path, "devices/system/cpu/cpu0/thermal_throttle")
30
+
31
+ @core_throttles_total = if File.exist?(File.join(@thermal_throttle_path, "core_throttle_count"))
32
+ CMetrics::Counter.new
33
+ else
34
+ nil
35
+ end
36
+ @core_throttles_total.create("node", "cpu", "core_throttles_total",
37
+ "Number of times this CPU core has been throttled.",
38
+ ["core", "package"]) if @core_throttles_total
39
+
40
+ @package_throttles_total = if File.exist?(File.join(@thermal_throttle_path, "package_throttle_count"))
41
+ CMetrics::Counter.new
42
+ else
43
+ nil
44
+ end
45
+ @package_throttles_total.create("node", "cpu", "package_throttles_total",
46
+ "Number of times this CPU package has been throttled.",
47
+ ["package"]) if @package_throttles_total
48
+
49
+ @seconds_total = CMetrics::Counter.new
50
+ @seconds_total.create("node", "cpu", "seconds_total",
51
+ "Seconds the CPUs spent in each mode.",
52
+ ["cpu", "mode"])
53
+
54
+ @guest_seconds_total = CMetrics::Counter.new
55
+ @guest_seconds_total.create("node", "cpu", "guest_seconds_total",
56
+ "Seconds the CPUs spent in guests (VMs) for each mode.",
57
+ ["cpu", "mode"])
58
+
59
+ @core_throttles_set = {}
60
+ @package_throttles_set = {}
61
+ end
62
+
63
+ def run
64
+ cpu_thermal_update
65
+ cpu_stat_update
66
+ end
67
+
68
+ def cpu_thermal_update
69
+ scan_sysfs_path("devices/system/cpu/cpu[0-9]*").each do |path|
70
+ next unless @core_throttles_total
71
+ next unless @package_throttles_total
72
+
73
+ core_id_path = File.join(path, "topology", "core_id")
74
+ physical_package_path = File.join(path, "topology", "physical_package_id")
75
+ core_id = File.read(core_id_path).strip
76
+ physical_package_id = File.read(physical_package_path).strip
77
+ next if @core_throttles_set[{physical_package_id: physical_package_id, core_id: core_id}]
78
+ @core_throttles_set[{physical_package_id: physical_package_id, core_id: core_id}] = true
79
+
80
+ core_throttle_count = File.read(File.join(path, "thermal_throttle", "core_throttle_count")).to_i
81
+ @core_throttles_total.set(core_throttle_count, [core_id, physical_package_id])
82
+
83
+ next if @package_throttles_set[physical_package_id]
84
+ @package_throttles_set[physical_package_id] = true
85
+
86
+ package_throttle_count = File.read(File.join(path, "thermal_throttle", "package_throttle_count")).to_i
87
+ @package_throttles_total.set(package_throttle_count, [physical_package_id])
88
+
89
+ end
90
+ end
91
+
92
+ STAT_CPU_PATTERN = /^cpu(?<cpuid>\d+)\s(?<user>\d+)\s(?<nice>\d+)\s(?<system>\d+)\s(?<idle>\d+)\s(?<iowait>\d+)\s(?<irq>\d+)\s(?<softirq>\d+)\s(?<steal>\d+)\s(?<guest>\d+)\s(?<guest_nice>\d+)/
93
+
94
+ def cpu_stat_update
95
+ stat_path = File.join(@procfs_path, "stat")
96
+ File.readlines(stat_path).each do |line|
97
+ if line.start_with?("cpu ")
98
+ # Ignore CPU total
99
+ next
100
+ elsif line.start_with?("cpu")
101
+ user_hz = Etc.sysconf(Etc::SC_CLK_TCK)
102
+ line.match(STAT_CPU_PATTERN) do |m|
103
+ @seconds_total.set(m[:idle].to_f / user_hz, [m[:cpuid], "idle"])
104
+ @seconds_total.set(m[:iowait].to_f / user_hz, [m[:cpuid], "iowait"])
105
+ @seconds_total.set(m[:irq].to_f / user_hz, [m[:cpuid], "irq"])
106
+ @seconds_total.set(m[:nice].to_f / user_hz, [m[:cpuid], "nice"])
107
+ @seconds_total.set(m[:softirq].to_f / user_hz, [m[:cpuid], "softirq"])
108
+ @seconds_total.set(m[:steal].to_f / user_hz, [m[:cpuid], "steal"])
109
+ @seconds_total.set(m[:system].to_f / user_hz, [m[:cpuid], "system"])
110
+ @seconds_total.set(m[:user].to_f / user_hz, [m[:cpuid], "user"])
111
+
112
+ @guest_seconds_total.set(m[:guest].to_f / user_hz, [m[:cpuid], "user"])
113
+ @guest_seconds_total.set(m[:guest_nice].to_f / user_hz, [m[:cpuid], "nice"])
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ def cmetrics
120
+ {
121
+ core_throttles_total: @core_throttles_total,
122
+ package_throttles_total: @package_throttles_total,
123
+ seconds_total: @seconds_total,
124
+ guest_seconds_total: @guest_seconds_total
125
+ }
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,97 @@
1
+ #
2
+ # Copyright 2021- Kentaro Hayashi
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ require "cmetrics"
17
+ require "etc"
18
+ require "fluent/plugin/input"
19
+ require "fluent/plugin/node_exporter/collector"
20
+
21
+ module Fluent
22
+ module Plugin
23
+ module NodeExporter
24
+ class CpufreqMetricsCollector < MetricsCollector
25
+ def initialize(config={})
26
+ super(config)
27
+
28
+ if Fluent.linux?
29
+ @frequency_hertz = CMetrics::Gauge.new
30
+ @frequency_hertz.create("node", "cpu", "frequency_hertz",
31
+ "Current cpu thread frequency in hertz.", ["cpu"])
32
+
33
+ @frequency_max_hertz = CMetrics::Gauge.new
34
+ @frequency_max_hertz.create("node", "cpu", "frequency_max_hertz",
35
+ "Maximum cpu thread frequency in hertz.", ["cpu"])
36
+
37
+ @frequency_min_hertz = CMetrics::Gauge.new
38
+ @frequency_min_hertz.create("node", "cpu", "frequency_min_hertz",
39
+ "Minimum cpu thread frequency in hertz.", ["cpu"])
40
+
41
+ @scaling_frequency_hertz = CMetrics::Gauge.new
42
+ @scaling_frequency_hertz.create("node", "cpu", "scaling_frequency_hertz",
43
+ "Current scaled CPU thread frequency in hertz.", ["cpu"])
44
+
45
+ @scaling_frequency_max_hertz = CMetrics::Gauge.new
46
+ @scaling_frequency_max_hertz.create("node", "cpu", "scaling_frequency_max_hertz",
47
+ "Maximum scaled CPU thread frequency in hertz.", ["cpu"])
48
+
49
+ @scaling_frequency_min_hertz = CMetrics::Gauge.new
50
+ @scaling_frequency_min_hertz.create("node", "cpu", "scaling_frequency_min_hertz",
51
+ "Minimum scaled CPU thread frequency in hertz.", ["cpu"])
52
+ end
53
+ end
54
+
55
+ def run
56
+ cpufreq_update
57
+ end
58
+
59
+ def cpuinfo_cur_freq_exist?
60
+ path = File.join(@sysfs_path, "devices/system/cpu/cpu0/cpufreq/cpuinfo_cur_freq")
61
+ File.exist?(path)
62
+ end
63
+
64
+ def cpufreq_update
65
+ scan_sysfs_path("devices/system/cpu/cpu[0-9]*").each do |path|
66
+ next unless Dir.exist?(File.join(path, "cpufreq"))
67
+
68
+ cpuinfo_cur_freq_path = File.join(path, "cpufreq", "cpuinfo_cur_freq")
69
+ cpuinfo_max_freq_path = File.join(path, "cpufreq", "cpuinfo_max_freq")
70
+ cpuinfo_min_freq_path = File.join(path, "cpufreq", "cpuinfo_min_freq")
71
+ scaling_cur_freq_path = File.join(path, "cpufreq", "scaling_cur_freq")
72
+ scaling_max_freq_path = File.join(path, "cpufreq", "scaling_max_freq")
73
+ scaling_min_freq_path = File.join(path, "cpufreq", "scaling_min_freq")
74
+ cpu_id = File.basename(path).sub(/cpu(\d+)/, "\\1")
75
+ @frequency_hertz.set(File.read(cpuinfo_cur_freq_path).to_f, [cpu_id]) if File.exist?(cpuinfo_cur_freq_path)
76
+ @frequency_max_hertz.set(File.read(cpuinfo_max_freq_path).to_f, [cpu_id]) if File.exist?(cpuinfo_max_freq_path)
77
+ @frequency_min_hertz.set(File.read(cpuinfo_min_freq_path).to_f, [cpu_id]) if File.exist?(cpuinfo_min_freq_path)
78
+ @scaling_frequency_hertz.set(File.read(scaling_cur_freq_path).to_f, [cpu_id]) if File.exist?(scaling_cur_freq_path)
79
+ @scaling_frequency_max_hertz.set(File.read(scaling_max_freq_path).to_f, [cpu_id]) if File.exist?(scaling_max_freq_path)
80
+ @scaling_frequency_min_hertz.set(File.read(scaling_min_freq_path).to_f, [cpu_id]) if File.exist?(scaling_min_freq_path)
81
+ end
82
+ end
83
+
84
+ def cmetrics
85
+ {
86
+ frequency_hertz: @frequency_hertz,
87
+ frequency_max_hertz: @frequency_max_hertz,
88
+ frequency_min_hertz: @frequency_min_hertz,
89
+ scaling_frequency_hertz: @scaling_frequency_hertz,
90
+ scaling_frequency_max_hertz: @scaling_frequency_max_hertz,
91
+ scaling_frequency_min_hertz: @scaling_frequency_min_hertz
92
+ }
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end