fluent-plugin-node-exporter-metrics 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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