munin2graphite 0.1.2 → 0.1.6

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.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.2
1
+ 0.1.6
data/bin/munin2gdash ADDED
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.expand_path(File.join(File.dirname(__FILE__),"..","lib")))
3
+ require 'rubygems'
4
+ require 'munin2graphite'
5
+ require 'optparse'
6
+ require 'erb'
7
+
8
+ options = {}
9
+ optparse = OptionParser.new do |opts|
10
+ opts.banner = "Usage: #{__FILE__} [options]"
11
+
12
+ opts.on( '-t', '--template-dir TEMPLATE_DIR', 'The directory you want the template files to be placed' ) do |td|
13
+ options[:template_dir] = td
14
+ end
15
+
16
+ options[:config_file] = "/etc/munin2graphite/munin2graphite.conf"
17
+ opts.on( '-c', '--config CONFIG_FILE', 'The munin2graphite config file' ) do |td|
18
+ options[:config_file] = td
19
+ end
20
+
21
+ opts.on( '-n', '--node NODE', "The munin node you want the data to be extracted from") do |n|
22
+ options[:node] = n
23
+ end
24
+ end
25
+
26
+ optparse.parse!
27
+
28
+ if !options[:template_dir]
29
+ puts "Error, no template dir specified"
30
+ puts optparse.help
31
+ exit 1
32
+ end
33
+
34
+ Munin2Graphite::Config.config_file = options[:config_file]
35
+ scheduler = Munin2Graphite::Scheduler.new(Munin2Graphite::Config)
36
+
37
+ munin_config = scheduler.munin_config
38
+ munin_config[:workers].each do |worker|
39
+ time = Time.now
40
+ config = Munin2Graphite::Config.config_for_worker worker
41
+ munin_config[worker][:nodes].keys.each do |node|
42
+ config.log.info("Graphs for #{node}")
43
+ munin_config[worker][:nodes][node][:metrics].each do |metric,value|
44
+ munin_graph = MuninGraph.graph_for value[:raw_config]
45
+ munin_graph.config = config.merge("metric" => "#{metric}","hostname" => node.split(".").first)
46
+ File.open(File.join(options[:template_dir],metric.to_s + ".graph"), "w+") do |file|
47
+ file.write munin_graph.to_gdash
48
+ end
49
+ end
50
+ end
51
+ config.log.info("End : Sending Graph Information to Graphite for worker #{worker}, elapsed time (#{Time.now - time}s)")
52
+ end
53
+ #puts scheduler.munin_config
54
+ scheduler
@@ -1,8 +1,12 @@
1
1
  # Log config
2
2
  # log: The logfile, STDOUT if stdout is needed
3
3
  # log_level: Either DEBUG, INFO or WARN
4
+ # Rotation (shift_age and shift_size)
5
+ #
4
6
  log=/var/log/munin2graphite.log
5
7
  log_level=INFO
8
+ log_shift_age=1 # 0 if for no rotation
9
+ log_shift_size=100000
6
10
 
7
11
  # Carbon backend
8
12
  # This has to point to the carbon backend to submit metrics
data/lib/ast_node.rb CHANGED
@@ -96,6 +96,22 @@ class ASTNode
96
96
  url = "#{properties[:endpoint]}/render/?width=586&height=308&#{properties_to_url}&target=" + URI.escape(targets.map{|i| i.compile}.compact.join("&target="))
97
97
  end
98
98
 
99
+ def to_gdash
100
+ output = ""
101
+ self.compile
102
+ self.graph_properties.each do |k,v|
103
+ output += k.to_s + "\t\t" + '"' + v.to_s + '"' + "\n" unless k == :colorList || k == :yMin || k== :yMax
104
+ end
105
+ count = 0
106
+ targets.each do |tg|
107
+ metric_alias = tg.properties.delete(:alias)
108
+ tg.children.delete_if { |i| i.class == LabelFieldPropertyNode}
109
+ output += "field :#{tg.metric.split(".").last.to_sym},:alias => '#{metric_alias}', :data => \"#{tg.compile}\"\n"
110
+ count += 1
111
+ end
112
+ return output
113
+ end
114
+
99
115
  end
100
116
 
101
117
 
@@ -248,8 +264,9 @@ class TypeFieldPropertyNode < FieldPropertyNode
248
264
 
249
265
  def apply_function(operand)
250
266
  if @value == "DERIVE" || @value == "COUNTER"
251
- # The scaling is because of the minutes/seconds"
252
- return "scale(nonNegativeDerivative(#{operand}),0.0166666666666667)"
267
+ # The scaling is because of the minutes/(60*seconds)"
268
+ #return "scale(nonNegativeDerivative(#{operand}),0.0166666666666667)"
269
+ return "scale(nonNegativeDerivative(#{operand}),0.00333333333333333)"
253
270
  end
254
271
  operand
255
272
  end
@@ -89,11 +89,13 @@ module Munin2Graphite
89
89
  super
90
90
  end
91
91
 
92
- def log
92
+ def log
93
+ shift_age = self["log_shift_age"] ? self["log_shift_age"].to_i : 1
94
+ shift_size = self["log_shift_size"].to_i || 100000
93
95
  @log ||= if self["log"] == "STDOUT"
94
- Logger.new(STDOUT)
96
+ Logger.new(STDOUT, shift_age, shift_size)
95
97
  else
96
- Logger.new(self["log"])
98
+ Logger.new(self["log"], shift_age, shift_size)
97
99
  end
98
100
  @log.level = self["log_level"] == "DEBUG" ? Logger::DEBUG : Logger::INFO
99
101
  @log
@@ -26,43 +26,50 @@ module Munin2Graphite
26
26
  def initialize(config)
27
27
  @config = config
28
28
  end
29
-
29
+
30
30
  def category_from_config(config)
31
31
  config.each_line do |configline|
32
- if configline =~ /^graph_category ([0-9A-Za-z_-]+)$/
32
+ if configline =~ /^graph_category ([\w\-_\.]+)$/
33
33
  return configline.split[1]
34
34
  end
35
- end
36
- return "other"
35
+ end
37
36
  raise "CategoryNotFound in #{config}"
38
37
  end
39
38
 
40
39
  def munin_config
41
- return @munin_config if @munin_config
40
+ return @munin_config if @munin_config
42
41
  @munin_config = {}
43
-
42
+ @config.log.info("Obtaining metrics configuration")
43
+ @munin_config[:workers] = []
44
44
  workers.each do |worker|
45
- @munin_config[worker] = {}
45
+ current_config = {}
46
46
  config = @config.config_for_worker(worker)
47
- begin
48
- munin = Munin::Node.new(config["munin_hostname"],config["munin_port"])
49
- nodes = config["munin_nodes"] ? config["munin_nodes"].split(",") : munin.nodes
50
- @munin_config[worker][:nodes] = {}
51
- nodes.each do |node|
52
- @munin_config[worker][:nodes][node] = {:metrics => munin.list(node)}
53
- @munin_config[worker][:nodes][node][:categories] = {}
54
- @munin_config[worker][:nodes][node][:metrics].each do |metric|
55
- @munin_config[worker][:nodes][node][:config] = munin.config(metric)[metric]
56
- @munin_config[worker][:nodes][node][:raw_config] = munin.config(metric,true)[metric]
57
- @munin_config[worker][:nodes][node][:categories][metric] = category_from_config(@munin_config[worker][:nodes][node][:raw_config])
47
+ munin = Munin::Node.new(config["munin_hostname"],config["munin_port"])
48
+ nodes = config["munin_nodes"] ? config["munin_nodes"].split(",") : munin.nodes
49
+ current_config[:nodes] = {}
50
+ nodes.each do |node|
51
+ metrics = munin.list(node)
52
+ current_config[:nodes][node] = { :metrics => {} }
53
+ metrics.each do |metric|
54
+ begin
55
+ raw_config = munin.config(metric,true)[metric]
56
+ category = category_from_config(raw_config)
57
+ current_config[:nodes][node][:metrics][metric] = {
58
+ :config => munin.config(metric)[metric],
59
+ :raw_config => raw_config,
60
+ :category => category
61
+ }
62
+ rescue Exception
63
+ config.log.error("Error when trying to obtain graph conf. Ignored (config was #{raw_config})")
58
64
  end
59
65
  end
60
- munin.disconnect
61
- rescue Exception
62
- config.log.error("There was an error trying to obtain info from node #{config["munin_hostname"]}")
63
- config.log.error $!
64
66
  end
67
+ # @config.log.debug(current_config.inspect)
68
+ @munin_config[worker] = current_config
69
+ @munin_config[:workers] << worker
70
+ munin.disconnect
65
71
  end
72
+ # @config.log.debug(@munin_config.inspect)
66
73
  @munin_config
67
74
  end
68
75
 
@@ -72,112 +79,107 @@ module Munin2Graphite
72
79
 
73
80
  #
74
81
  # This is the loop of the metrics scheduling
75
- def obtain_metrics
82
+ def obtain_metrics(worker = "global")
76
83
  config = @config.config_for_worker("global")
77
- config.log.info("Obtaining metrics configuration")
78
- munin_config
79
- config.log.info("Getting metrics")
80
84
  time = Time.now
81
- workers.each do |worker|
82
- config = @config.config_for_worker(worker)
83
- config.log.info("Worker #{worker}")
84
-
85
- metric_base = config["graphite_metric_prefix"]
86
-
87
- threads = []
88
- munin_config[worker][:nodes].keys.each do |node|
89
- threads << Thread.new do
90
- node_name = metric_base + "." + node.split(".").first
91
- config.log.debug("Doing #{node_name}")
92
- values = {}
93
- config.log.debug("Asking for: #{node}")
94
- metric_time = Time.now
95
- metrics = munin_config[worker][:nodes][node][:metrics]
96
- config.log.debug("Metrics " + metrics.join(","))
97
- metrics_threads = []
98
- categories = {}
99
- metrics.each do |metric|
100
- metrics_threads << Thread.new do
101
- begin
102
- local_munin = Munin::Node.new(config["munin_hostname"],config["munin_port"])
103
- values[metric] = local_munin.fetch metric
104
- local_munin.disconnect
105
- rescue Exception
106
- config.log.error("Error when trying to obtain values for #{metric}. Ignored")
107
- config.log.error $!
108
- end
109
- end
110
- end
111
- metrics_threads.each {|i| i.join;i.kill}
112
- config.log.info("Done with: #{node} (#{Time.now - metric_time} s)")
113
- carbon = Carbon.new(config["carbon_hostname"],config["carbon_port"])
114
- string_to_send = ""
115
- values.each do |metric,results|
116
- category = munin_config[worker][:nodes][node][:categories][metric]
117
- results.each do |k,v|
118
- v.each do |c_metric,c_value|
119
- string_to_send += "#{node_name}.#{category}.#{metric}.#{c_metric} #{c_value} #{Time.now.to_i}\n".gsub("-","_") if c_value != "U"
120
- end
121
- end
85
+ config = @config.config_for_worker(worker)
86
+ config.log.info("Worker #{worker}")
87
+ metric_base = config["graphite_metric_prefix"]
88
+
89
+ munin_config[worker][:nodes].each do |node,node_info|
90
+ node_name = metric_base + "." + node.split(".").first
91
+ config.log.debug("Doing #{node_name}")
92
+ values = {}
93
+ config.log.debug("Asking for: #{node}")
94
+ metric_time = Time.now
95
+ metrics = node_info[:metrics].keys
96
+ config.log.debug("Metrics " + metrics.join(","))
97
+ metrics_threads = []
98
+ categories = {}
99
+ metrics.each do |metric|
100
+ metrics_threads << Thread.new do
101
+ begin
102
+ local_munin = Munin::Node.new(config["munin_hostname"],config["munin_port"])
103
+ values[metric] = local_munin.fetch metric
104
+ local_munin.disconnect
105
+ rescue
106
+ @config.log.error("There was a problem when getting the metric #{metric} for #{node} , Ignored")
122
107
  end
123
- send_time = Time.now
124
- carbon.send(string_to_send)
125
- carbon.flush
126
- carbon.close
127
- end
128
- end if munin_config[worker][:nodes]
129
- threads.each { |i| i.join }
130
- end
131
- @config.log.info("End getting metrics, elapsed time (#{Time.now - time}s)")
108
+ end
109
+ end
110
+ metrics_threads.each {|i| i.join;i.kill}
111
+ config.log.debug(values.inspect)
112
+ config.log.info("Done with: #{node} (#{Time.now - metric_time} s)")
113
+ carbon = Carbon.new(config["carbon_hostname"],config["carbon_port"])
114
+ string_to_send = ""
115
+ values.each do |metric,results|
116
+ category = node_info[:metrics][metric][:category]
117
+ results.each do |k,v|
118
+ v.each do |c_metric,c_value|
119
+ string_to_send += "#{node_name}.#{category}.#{metric}.#{c_metric} #{c_value} #{Time.now.to_i}\n".gsub("-","_") if c_value != "U"
120
+ end
121
+ end
122
+ end
123
+ @config.log.debug(string_to_send)
124
+ send_time = Time.now
125
+ carbon.send(string_to_send)
126
+ carbon.flush
127
+ carbon.close
128
+ end if munin_config[worker]
129
+ @config.log.info("End getting metrics for worker #{worker}, elapsed time (#{Time.now - time}s)")
132
130
  end
133
131
 
134
- ##
135
- # The loop of the graphics creation
136
132
  def obtain_graphs
137
-
138
- workers = @config.workers
139
- workers = ["global"] if workers.empty?
140
-
141
- workers.each do |worker|
142
- time = Time.now
133
+ munin_config
134
+ munin_config[:workers].each do |worker|
135
+ time = Time.now
143
136
  config = @config.config_for_worker worker
144
- config.log.info("Begin : Sending Graph Information to Graphite for worker #{worker}")
145
- begin
146
- munin = Munin::Node.new(config["munin_hostname"],config["munin_port"])
147
- Graphite::Base.set_connection(config["graphite_endpoint"])
148
- Graphite::Base.authenticate(config["graphite_user"],config["graphite_password"])
149
-
150
- nodes = config["munin_nodes"] ? config["munin_nodes"].split(",") : munin.nodes
151
- nodes.each do |node|
152
- config.log.info("Graphs for #{node}")
153
- munin.list(node).each do |metric|
154
- config.log.info("Configuring #{metric}")
155
- munin_graph = MuninGraph.graph_for munin.config(metric,true)[metric]
156
- munin_graph.config = config.merge("metric" => "#{metric}","hostname" => node.split(".").first)
157
- config.log.debug("Saving graph #{metric}")
158
- munin_graph.to_graphite.save!
159
- end
137
+ @config.log.info("Begin : Sending Graph Information to Graphite for worker #{worker}")
138
+ Graphite::Base.set_connection(config["graphite_endpoint"])
139
+ Graphite::Base.authenticate(config["graphite_user"],config["graphite_password"])
140
+ munin_config[worker][:nodes].keys.each do |node|
141
+ @config.log.info("Graphs for #{node}")
142
+ munin_config[worker][:nodes][node][:metrics].each do |metric,value|
143
+ @config.log.info("Configuring #{metric}")
144
+ munin_graph = MuninGraph.graph_for value[:raw_config]
145
+ munin_graph.config = config.merge("metric" => "#{metric}","hostname" => node.split(".").first)
146
+ @config.log.debug("Saving graph #{metric}")
147
+ munin_graph.to_graphite.save!
160
148
  end
161
- config.log.info("End : Sending Graph Information to Graphite for worker #{worker}, elapsed time (#{Time.now - time}s)")
162
- munin_graph.to_graphite.save!
163
-
164
- munin.disconnect
165
- rescue Exception
166
- config.log.error("Error when trying to obtain graph conf. Ignored")
167
- config.log.error $!
168
149
  end
150
+ config.log.info("End : Sending Graph Information to Graphite for worker #{worker}, elapsed time (#{Time.now - time}s)")
169
151
  end
152
+ end
170
153
 
154
+ def metric_loop(worker)
155
+ config = @config.config_for_worker worker
156
+ retries = 3
157
+ begin
158
+ obtain_metrics(worker)
159
+ rescue => e
160
+ config.log.error("Exception found: (#{e.to_s})")
161
+ e.backtrace.each { |line| config.log.error(line) }
162
+ sleep 1
163
+ retries -= 1
164
+ config.log.error("Retrying")
165
+ retry unless retries < 0
166
+ config.log.error("Exitting, exception not solved")
167
+ exit(1)
168
+ end
171
169
  end
172
170
 
173
171
  def start
174
172
  @config.log.info("Scheduler started")
175
- @scheduler = Rufus::Scheduler.start_new
176
- obtain_metrics
177
- @scheduler.every @config["scheduler_metrics_period"] do
178
- obtain_metrics
179
- end
180
173
  obtain_graphs
174
+ @scheduler = Rufus::Scheduler.start_new
175
+ workers.each do |worker|
176
+ config = @config.config_for_worker worker
177
+ config.log.info("Scheduling worker #{worker} every #{config["scheduler_metrics_period"]} ")
178
+ metric_loop(worker)
179
+ @scheduler.every config["scheduler_metrics_period"] do
180
+ metric_loop(worker)
181
+ end
182
+ end
181
183
  end
182
184
  end
183
185
  end
data/lib/munin_graph.rb CHANGED
@@ -52,6 +52,11 @@ class MuninGraph
52
52
  return graph
53
53
  end
54
54
 
55
+ def to_gdash
56
+ self.root.compile
57
+ self.root.to_gdash
58
+ end
59
+
55
60
  attr_reader :root
56
61
 
57
62
  # This array of hashes will be used to match what kind of line we are dealing with and
@@ -5,14 +5,14 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "munin2graphite"
8
- s.version = "0.1.2"
8
+ s.version = "0.1.6"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Jose Fernandez (magec)"]
12
- s.date = "2011-12-14"
12
+ s.date = "2012-02-17"
13
13
  s.description = "This gem will install as a daemon and can be used to connect to a graphite and a carbon backend. It will not only post the data for the metrics but also create graphs into graphite, by means of a translation from munin-node."
14
14
  s.email = "jfernandezperez@gmail.com"
15
- s.executables = ["munin2graphite", "munin2graphite-daemon"]
15
+ s.executables = ["munin2gdash", "munin2graphite", "munin2graphite-daemon"]
16
16
  s.extra_rdoc_files = [
17
17
  "LICENSE.txt",
18
18
  "README.markdown"
@@ -23,6 +23,7 @@ Gem::Specification.new do |s|
23
23
  "README.markdown",
24
24
  "Rakefile",
25
25
  "VERSION",
26
+ "bin/munin2gdash",
26
27
  "bin/munin2graphite",
27
28
  "bin/munin2graphite-daemon",
28
29
  "conf/munin2graphite.conf.example",
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: munin2graphite
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.6
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-12-14 00:00:00.000000000Z
12
+ date: 2012-02-17 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rufus-scheduler
16
- requirement: &23592960 !ruby/object:Gem::Requirement
16
+ requirement: &13760080 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - =
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 2.0.10
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *23592960
24
+ version_requirements: *13760080
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: daemons
27
- requirement: &23592440 !ruby/object:Gem::Requirement
27
+ requirement: &13744200 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - =
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: 1.1.4
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *23592440
35
+ version_requirements: *13744200
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: parseconfig
38
- requirement: &23591880 !ruby/object:Gem::Requirement
38
+ requirement: &13743300 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *23591880
46
+ version_requirements: *13743300
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: munin-ruby
49
- requirement: &23591340 !ruby/object:Gem::Requirement
49
+ requirement: &13742340 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ~>
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: 0.2.1
55
55
  type: :runtime
56
56
  prerelease: false
57
- version_requirements: *23591340
57
+ version_requirements: *13742340
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: json
60
- requirement: &23590860 !ruby/object:Gem::Requirement
60
+ requirement: &13741460 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ~>
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: 1.6.3
66
66
  type: :runtime
67
67
  prerelease: false
68
- version_requirements: *23590860
68
+ version_requirements: *13741460
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: bundler
71
- requirement: &23590320 !ruby/object:Gem::Requirement
71
+ requirement: &13740580 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ~>
@@ -76,10 +76,10 @@ dependencies:
76
76
  version: 1.0.0
77
77
  type: :development
78
78
  prerelease: false
79
- version_requirements: *23590320
79
+ version_requirements: *13740580
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: jeweler
82
- requirement: &23589820 !ruby/object:Gem::Requirement
82
+ requirement: &13740060 !ruby/object:Gem::Requirement
83
83
  none: false
84
84
  requirements:
85
85
  - - ~>
@@ -87,10 +87,10 @@ dependencies:
87
87
  version: 1.5.2
88
88
  type: :development
89
89
  prerelease: false
90
- version_requirements: *23589820
90
+ version_requirements: *13740060
91
91
  - !ruby/object:Gem::Dependency
92
92
  name: yard
93
- requirement: &23589320 !ruby/object:Gem::Requirement
93
+ requirement: &13739540 !ruby/object:Gem::Requirement
94
94
  none: false
95
95
  requirements:
96
96
  - - ~>
@@ -98,12 +98,13 @@ dependencies:
98
98
  version: 0.6.0
99
99
  type: :development
100
100
  prerelease: false
101
- version_requirements: *23589320
101
+ version_requirements: *13739540
102
102
  description: This gem will install as a daemon and can be used to connect to a graphite
103
103
  and a carbon backend. It will not only post the data for the metrics but also create
104
104
  graphs into graphite, by means of a translation from munin-node.
105
105
  email: jfernandezperez@gmail.com
106
106
  executables:
107
+ - munin2gdash
107
108
  - munin2graphite
108
109
  - munin2graphite-daemon
109
110
  extensions: []
@@ -116,6 +117,7 @@ files:
116
117
  - README.markdown
117
118
  - Rakefile
118
119
  - VERSION
120
+ - bin/munin2gdash
119
121
  - bin/munin2graphite
120
122
  - bin/munin2graphite-daemon
121
123
  - conf/munin2graphite.conf.example
@@ -155,7 +157,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
155
157
  version: '0'
156
158
  segments:
157
159
  - 0
158
- hash: 3397207126724814962
160
+ hash: -732552108967367199
159
161
  required_rubygems_version: !ruby/object:Gem::Requirement
160
162
  none: false
161
163
  requirements: