instrumental_tools 1.0.0.rc2 → 1.0.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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/BUILD.md +50 -0
  3. data/CHANGELOG.md +5 -1
  4. data/CUSTOM_METRICS.md +9 -1
  5. data/INSTALL.md +89 -0
  6. data/LICENSE +1 -1
  7. data/README.md +23 -3
  8. data/Rakefile +257 -0
  9. data/TEST.md +9 -0
  10. data/bin/instrument_server +48 -7
  11. data/chef/.kitchen.yml +16 -0
  12. data/chef/Berksfile +4 -0
  13. data/chef/Berksfile.lock +9 -0
  14. data/chef/instrumental_tools/attributes/default.rb +2 -0
  15. data/chef/instrumental_tools/metadata.rb +3 -0
  16. data/chef/instrumental_tools/recipes/default.rb +21 -0
  17. data/chef/instrumental_tools/templates/instrumental.yml.erb +6 -0
  18. data/conf/instrumental.yml +6 -0
  19. data/debian/after-install.sh +6 -0
  20. data/debian/after-remove.sh +4 -0
  21. data/debian/before-remove.sh +4 -0
  22. data/debian/instrument_server +46 -0
  23. data/examples/mysql/mysql_status.rb +3 -3
  24. data/instrumental_tools.gemspec +32 -4
  25. data/lib/instrumental_tools/metric_script_executor.rb +38 -34
  26. data/lib/instrumental_tools/server_controller.rb +122 -35
  27. data/lib/instrumental_tools/system_inspector/linux.rb +80 -42
  28. data/lib/instrumental_tools/version.rb +1 -1
  29. data/puppet/.kitchen.yml +26 -0
  30. data/puppet/.librarian/puppet/config +2 -0
  31. data/puppet/Puppetfile +4 -0
  32. data/puppet/Puppetfile.lock +17 -0
  33. data/puppet/instrumental_tools/manifests/init.pp +29 -0
  34. data/puppet/instrumental_tools/metadata.json +11 -0
  35. data/puppet/instrumental_tools/templates/instrumental.yml.erb +6 -0
  36. data/puppet/manifests/site.pp +3 -0
  37. data/rpm/after-install.sh +7 -0
  38. data/rpm/after-remove.sh +4 -0
  39. data/rpm/before-remove.sh +5 -0
  40. data/rpm/instrument_server +46 -0
  41. data/systemd/instrument_server.service +13 -0
  42. data/test/integration/default/serverspec/instrumental_tools_spec.rb +29 -0
  43. metadata +164 -11
  44. data/.gitignore +0 -3
@@ -0,0 +1,21 @@
1
+ packagecloud_repo "expectedbehavior/instrumental" do
2
+ case node["platform_family"]
3
+ when "debian"
4
+ type "deb"
5
+ when "rhel"
6
+ type "rpm"
7
+ end
8
+ end
9
+
10
+ package "instrumental-tools" do
11
+ action :upgrade
12
+ end
13
+
14
+ template "/etc/instrumental.yml" do
15
+ source "instrumental.yml.erb"
16
+ mode "0440"
17
+ owner "nobody"
18
+ variables(
19
+ :api_key => node[:instrumental][:api_key]
20
+ )
21
+ end
@@ -0,0 +1,6 @@
1
+ ---
2
+ # Replace YOUR_API_KEY with the API key for your Instrumental project and
3
+ # remove the # from the beginning of the following line to have instrument_server
4
+ # start sending metrics to your account.
5
+
6
+ api_key: <%= @api_key %>
@@ -0,0 +1,6 @@
1
+ ---
2
+ # Replace YOUR_API_KEY with the API key for your Instrumental project and
3
+ # remove the # from the beginning of the following line to have instrument_server
4
+ # start sending metrics to your account.
5
+
6
+ #api_key: YOUR_API_KEY
@@ -0,0 +1,6 @@
1
+ #!/bin/sh
2
+ set -e
3
+ update-rc.d instrument_server defaults
4
+ /etc/init.d/instrument_server start
5
+ echo "Remember to edit /etc/instrumental.yml with your Instrumental API key"
6
+ exit 0
@@ -0,0 +1,4 @@
1
+ #!/bin/sh
2
+ set -e
3
+ update-rc.d -f instrument_server remove
4
+ exit 0
@@ -0,0 +1,4 @@
1
+ #!/bin/sh
2
+ set -e
3
+ /etc/init.d/instrument_server stop
4
+ exit 0
@@ -0,0 +1,46 @@
1
+ #! /bin/sh
2
+ ### BEGIN INIT INFO
3
+ # Provides: instrument_server
4
+ # Required-Start: $all
5
+ # Required-Stop: $all
6
+ # Default-Start: 2 3 4 5
7
+ # Default-Stop: 0 1 6
8
+ # Short-Description: Start instrument_server at boot to provide system metrics
9
+ # Description: Report system level metrics to the Instrumental service (https://instrumentalapp.com/)
10
+ ### END INIT INFO
11
+
12
+ set -e
13
+
14
+ DIRECTORY="/opt/instrumental-tools/"
15
+ CONFIG_FILE="/etc/instrumental.yml"
16
+ TMPDIR=$DIRECTORY
17
+ PID="${DIRECTORY}instrument_server.pid"
18
+ LOG="${DIRECTORY}instrument_server.log"
19
+ SCRIPT_LOCATION="${DIRECTORY}.instrumental_scripts"
20
+ USER_TO_RUN_AS="nobody"
21
+ ARGS="-f ${CONFIG_FILE} -p ${PID} -l ${LOG} -s ${SCRIPT_LOCATION} -u ${USER_TO_RUN_AS} -t ${TMPDIR}"
22
+ PROCESS="${DIRECTORY}instrument_server ${ARGS}"
23
+
24
+ case "$1" in
25
+ start)
26
+ $PROCESS start
27
+ ;;
28
+ stop)
29
+ $PROCESS stop
30
+ ;;
31
+ restart)
32
+ $PROCESS restart
33
+ ;;
34
+ status)
35
+ $PROCESS status
36
+ ;;
37
+ force-reload)
38
+ $PROCESS stop && $PROCESS clean && $PROCESS start
39
+ ;;
40
+ *)
41
+ echo "Usage: /etc/init.d/instrumental-tools {start|stop|restart|status}"
42
+ exit 1
43
+ ;;
44
+ esac
45
+
46
+ exit 0
@@ -9,8 +9,8 @@ MYSQL_USER = ENV["MYSQL_USER"]
9
9
  MYSQL_DEFAULTS_FILE = ENV["MYSQL_DEFAULTS_FILE"]
10
10
  MYSQL_PASSWORD = ENV["MYSQL_PASSWORD"]
11
11
 
12
- RATE_METRICS_TO_INSPECT = %w{Queries Bytes_sent Bytes_received Connections Slow_queries}
13
- CANARY_METRIC = "Queries"
12
+ RATE_METRICS_TO_INSPECT = %w{queries bytes_sent bytes_received connections slow_queries}
13
+ CANARY_METRIC = "queries"
14
14
 
15
15
  env = {}
16
16
  args = []
@@ -61,7 +61,7 @@ if !exit_status.success?
61
61
  else
62
62
  output = stdout_r.read.lines # each line
63
63
  .map { |line| line.chomp.split } # split by space characters
64
- .map { |(name, value, _)| [name, value.to_f] } # with values coerced to floats
64
+ .map { |(name, value, _)| [name.downcase, value.to_f] } # with values coerced to floats
65
65
  stats = Hash[output]
66
66
  if (stats[CANARY_METRIC] < previous_values[CANARY_METRIC].to_i) || previous_values[CANARY_METRIC].nil?
67
67
  # The server has restarted, don't trust previous values for calculating difference
@@ -1,18 +1,37 @@
1
1
  $: << "./lib"
2
+ require 'find'
2
3
  require 'instrumental_tools/version'
3
4
 
5
+ gitignore = Array(File.exists?(".gitignore") ? File.read(".gitignore").split("\n") : []) + [".git", ".gitignore"]
6
+ all_files = []
7
+
8
+ Find.find(".") do |path|
9
+ scrubbed_path = path.gsub(/\A\.\//, "")
10
+ if gitignore.any? { |glob| File.fnmatch(glob, scrubbed_path) }
11
+ Find.prune
12
+ else
13
+ if !File.directory?(scrubbed_path)
14
+ all_files << scrubbed_path
15
+ end
16
+ end
17
+ end
18
+
19
+ test_files = all_files.select { |path| path =~ /\A(test|spec|features)\//i }
20
+ bin_files = all_files.select { |path| path.index("bin") == 0 }.map { |path| File.basename(path) }
21
+
4
22
  Gem::Specification.new do |s|
5
23
  s.name = "instrumental_tools"
6
24
  s.version = Instrumental::Tools::VERSION
7
- s.authors = ["Elijah Miller", "Christopher Zelenak", "Kristopher Chambers", "Matthew Hassfurder"]
25
+ s.authors = ["Expected Behavior"]
8
26
  s.email = ["support@instrumentalapp.com"]
9
27
  s.homepage = "http://github.com/expectedbehavior/instrumental_tools"
10
28
  s.summary = %q{Command line tools for Instrumental}
11
29
  s.description = %q{A collection of scripts useful for monitoring servers and services with Instrumental (instrumentalapp.com)}
30
+ s.licenses = ["MIT"]
12
31
 
13
- s.files = `git ls-files`.split("\n")
14
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
32
+ s.files = all_files
33
+ s.test_files = test_files
34
+ s.executables = bin_files
16
35
  s.require_paths = ["lib"]
17
36
 
18
37
  s.required_ruby_version = '>= 1.9'
@@ -20,4 +39,13 @@ Gem::Specification.new do |s|
20
39
  s.add_runtime_dependency(%q<instrumental_agent>, [">=0.12.6"])
21
40
  s.add_runtime_dependency(%q<pidly>, [">=0.1.3"])
22
41
  s.add_development_dependency(%q<rake>, [">=0"])
42
+ s.add_development_dependency(%q<fpm>, [">=1.3.3"])
43
+ s.add_development_dependency(%q<package_cloud>, [">=0"])
44
+ s.add_development_dependency(%q<test-kitchen>, [">=0"])
45
+ s.add_development_dependency(%q<kitchen-vagrant>, [">=0"])
46
+ s.add_development_dependency(%q<kitchen-puppet>, [">=0"])
47
+ s.add_development_dependency(%q<berkshelf>, [">=0"])
48
+ s.add_development_dependency(%q<librarian-puppet>, [">=0"])
49
+ s.add_development_dependency(%q<puppet>, [">=0"])
50
+ s.add_development_dependency(%q<serverspec>, [">=0"])
23
51
  end
@@ -23,51 +23,55 @@ class MetricScriptExecutor
23
23
  file_stat.owned? && ((file_stat.mode & 0xFFF) ^ 0O700) == 0
24
24
  end
25
25
 
26
- def run
27
- process_to_output = {}
28
- if can_execute_in_directory?(directory)
29
- current = Dir[File.join(directory, "*")].map do |path|
30
- full_path = File.expand_path(path)
31
- if can_execute_file?(path)
32
- stdin_r, stdin_w = IO.pipe
33
- stdout_r, stdout_w = IO.pipe
34
- stderr_r, stderr_w = IO.pipe
26
+ def execute_custom_script(full_path)
27
+ stdin_r, stdin_w = IO.pipe
28
+ stdout_r, stdout_w = IO.pipe
29
+ stderr_r, stderr_w = IO.pipe
35
30
 
36
- previous_status, previous_time, previous_output = previous[full_path]
31
+ previous_status, previous_time, previous_output = previous[full_path]
37
32
 
38
- stdin_w.write(previous_output || "")
39
- stdin_w.close
33
+ stdin_w.write(previous_output || "")
34
+ stdin_w.close
40
35
 
41
36
 
42
- cmd = [full_path, (previous_time || 0).to_i, (previous_status && previous_status.to_i)].compact.map(&:to_s)
37
+ cmd = [full_path, (previous_time || 0).to_i, (previous_status && previous_status.to_i)].compact.map(&:to_s)
43
38
 
44
- pid = Process.spawn(*cmd,
45
- :chdir => File.dirname(full_path),
46
- :in => stdin_r,
47
- :out => stdout_w,
48
- :err => stderr_w)
39
+ pid = Process.spawn(*cmd,
40
+ :chdir => File.dirname(full_path),
41
+ :in => stdin_r,
42
+ :out => stdout_w,
43
+ :err => stderr_w)
49
44
 
50
- exit_status = nil
51
- exec_time = Benchmark.realtime do
52
- pid, exit_status = Process.wait2(pid)
53
- end
45
+ exit_status = nil
46
+ exec_time = Benchmark.realtime do
47
+ pid, exit_status = Process.wait2(pid)
48
+ end
54
49
 
55
- if exec_time > 1.0
56
- puts "[SLOW SCRIPT] Time to execute process #{full_path} took #{exec_time} seconds"
57
- end
50
+ if exec_time > 1.0
51
+ puts "[SLOW SCRIPT] Time to execute process #{full_path} took #{exec_time} seconds"
52
+ end
58
53
 
59
- [stdin_r, stdout_w, stderr_w].each(&:close)
54
+ [stdin_r, stdout_w, stderr_w].each(&:close)
60
55
 
61
- output = stdout_r.read.to_s.chomp
56
+ output = stdout_r.read.to_s.chomp
62
57
 
63
- stderr = stderr_r.read.to_s.chomp
64
- unless stderr.empty?
65
- puts "[STDERR] #{full_path} (PID:#{pid}) [#{Time.now.to_s}]:: #{stderr}"
66
- end
58
+ stderr = stderr_r.read.to_s.chomp
59
+ unless stderr.empty?
60
+ puts "[STDERR] #{full_path} (PID:#{pid}) [#{Time.now.to_s}]:: #{stderr}"
61
+ end
62
+
63
+ [stdout_r, stderr_r].each(&:close)
67
64
 
68
- [stdout_r, stderr_r].each(&:close)
65
+ [full_path, [exit_status, Time.now, output]]
66
+ end
69
67
 
70
- [full_path, [exit_status, Time.now, output]]
68
+ def run
69
+ process_to_output = {}
70
+ if can_execute_in_directory?(directory)
71
+ current = Dir[File.join(directory, "*")].map do |path|
72
+ full_path = File.expand_path(path)
73
+ if can_execute_file?(path)
74
+ execute_custom_script(full_path)
71
75
  else
72
76
  if !File.directory?(full_path)
73
77
  uid = Process.uid
@@ -84,7 +88,7 @@ class MetricScriptExecutor
84
88
  end
85
89
  process_to_output.flat_map do |path, (status, time, output)|
86
90
  if status && status.success?
87
- prefix = File.basename(path).split(".")[0..-2].join(".").gsub(/[^a-z0-9\-\_\.]/i, "_")
91
+ prefix = File.basename(path).split(".")[0..-2].join(".").gsub(/[^\d\w\-\_\.]/i, "_")
88
92
  output.lines # each line
89
93
  .map { |line| line.chomp.split } # split by whitespace
90
94
  .select { |data| (2..3).include?(data.size) } # and only valid name value time? pairs
@@ -1,11 +1,13 @@
1
- require 'pidly'
2
1
  require 'instrumental_tools/metric_script_executor'
3
2
  require 'instrumental_tools/system_inspector'
3
+ require 'pidly'
4
+ require 'yaml'
4
5
 
5
6
  class ServerController < Pidly::Control
6
7
  COMMANDS = [:start, :stop, :status, :restart, :clean, :kill, :foreground]
7
8
 
8
9
  attr_accessor :run_options, :pid
10
+ attr_reader :current_api_key
9
11
 
10
12
  before_start do
11
13
  extra_info = if run_options[:daemon]
@@ -24,49 +26,134 @@ class ServerController < Pidly::Control
24
26
  puts 'Error encountered'
25
27
  end
26
28
 
27
- def self.run(options)
28
- agent = Instrumental::Agent.new(options[:api_key], :collector => [options[:collector], options[:port]].compact.join(':'))
29
+ def initialize(options={})
30
+ @run_options = options.delete(:run_options) || {}
31
+ super(options)
32
+ end
33
+
34
+ def foreground
35
+ run
36
+ end
37
+
38
+ def collector_address
39
+ [run_options[:collector], run_options[:port]].compact.join(':')
40
+ end
41
+
42
+ def user_specified_api_key
43
+ run_options[:api_key]
44
+ end
45
+
46
+ def config_file_api_key
47
+ if config_file_available?
48
+ config_contents = YAML.load(File.read(run_options[:config_file]))
49
+ if config_contents.is_a?(Hash)
50
+ config_contents['api_key']
51
+ end
52
+ end
53
+ rescue Exception => e
54
+ puts "Error loading config file %s: %s" % [run_options[:config_file], e.message]
55
+ nil
56
+ end
57
+
58
+ def configured_api_key
59
+ (user_specified_api_key || config_file_api_key).to_s.strip
60
+ end
61
+
62
+ def build_agent(key, address, enabled)
63
+ Instrumental::Agent.new(key, collector: address, enabled: enabled)
64
+ end
65
+
66
+ def set_new_agent(key, address)
67
+ key = key.to_s.strip
68
+ @current_api_key = key
69
+ @agent = build_agent(key, collector_address, key.size > 0)
70
+ end
71
+
72
+ def agent
73
+ if key_has_changed?
74
+ set_new_agent(configured_api_key, collector_address)
75
+ end
76
+ @agent
77
+ end
78
+
79
+ def report_interval
80
+ run_options[:report_interval]
81
+ end
82
+
83
+ def hostname
84
+ run_options[:hostname]
85
+ end
86
+
87
+ def script_location
88
+ run_options[:script_location]
89
+ end
90
+
91
+ def script_executor
92
+ @executor ||= MetricScriptExecutor.new(script_location)
93
+ end
94
+
95
+ def next_run_at(at_moment = Time.now.to_i)
96
+ (at_moment - at_moment % report_interval) + report_interval
97
+ end
98
+
99
+ def time_to_sleep
100
+ t = Time.now.to_i
101
+ [next_run_at(t) - t, 0].max
102
+ end
103
+
104
+ def config_file_available?
105
+ File.exists?(run_options[:config_file])
106
+ end
107
+
108
+ def enabled?
109
+ agent.enabled
110
+ end
111
+
112
+ def debug?
113
+ !!run_options[:debug]
114
+ end
115
+
116
+ def enable_scripts?
117
+ !!run_options[:enable_scripts]
118
+ end
119
+
120
+ def key_has_changed?
121
+ current_api_key != configured_api_key
122
+ end
123
+
124
+ def run
29
125
  puts "instrument_server version #{Instrumental::Tools::VERSION} started at #{Time.now.utc}"
30
- puts "Collecting stats under the hostname: #{options[:hostname]}"
31
- report_interval = options[:report_interval]
32
- custom_metrics = MetricScriptExecutor.new(options[:script_location])
126
+ puts "Collecting stats under the hostname: #{hostname}"
33
127
  loop do
34
- t = Time.now.to_i
35
- next_run_at = (t - t % report_interval) + report_interval
36
- sleep [next_run_at - t, 0].max
37
- inspector = SystemInspector.new
38
- inspector.load_all
39
- count = 0
40
- inspector.gauges.each do |stat, value|
41
- metric = "#{options[:hostname]}.#{stat}"
42
- agent.gauge(metric, value)
43
- if options[:debug]
44
- puts [metric, value].join(":")
45
- end
46
- count += 1
47
- end
48
- if options[:enable_scripts]
49
- custom_metrics.run.each do |(stat, value, time)|
50
- metric = "#{options[:hostname]}.#{stat}"
51
- agent.gauge(metric, value, time)
52
- if options[:debug]
128
+ sleep time_to_sleep
129
+ if enabled?
130
+ inspector = SystemInspector.new
131
+ inspector.load_all
132
+ count = 0
133
+ inspector.gauges.each do |stat, value|
134
+ metric = [hostname, stat].join(".")
135
+ agent.gauge(metric, value)
136
+ if debug?
53
137
  puts [metric, value].join(":")
54
138
  end
55
139
  count += 1
56
140
  end
141
+ if enable_scripts?
142
+ script_executor.run.each do |(stat, value, time)|
143
+ metric = [hostname, stat].join(".")
144
+ agent.gauge(metric, value, time)
145
+ if debug?
146
+ puts [metric, value].join(":")
147
+ end
148
+ count += 1
149
+ end
150
+ end
151
+ if debug?
152
+ puts "Sent #{count} metrics"
153
+ end
57
154
  end
58
- puts "Sent #{count} metrics"
59
155
  end
60
156
  end
61
157
 
62
- def initialize(options={})
63
- @run_options = options.delete(:run_options) || {}
64
- super(options)
65
- end
66
-
67
- def foreground
68
- self.class.run(run_options)
69
- end
70
-
71
158
  alias_method :clean, :clean!
72
159
  end
@@ -7,29 +7,60 @@ class SystemInspector
7
7
  output
8
8
  end
9
9
 
10
+ def self.cpu_file
11
+ "/proc/stat"
12
+ end
13
+
14
+ def self.load_file
15
+ "/proc/loadavg"
16
+ end
17
+
18
+ def self.disk_file
19
+ "/proc/diskstats"
20
+ end
21
+
22
+ def self.mount_file
23
+ "/proc/mounts"
24
+ end
25
+
26
+ def self.open_files_file
27
+ "/proc/sys/fs/file-nr"
28
+ end
29
+
30
+ def self.memory_file
31
+ "/proc/meminfo"
32
+ end
33
+
10
34
  def self.cpu
11
- categories = [:user, :nice, :system, :idle, :iowait]
12
- values = `cat /proc/stat | grep cpu[^0-9]`.chomp.split.slice(1, 5).map { |v| v.to_f }
13
- SystemInspector.memory.store(:cpu_values, values.dup)
14
- if previous_values = SystemInspector.memory.retrieve(:cpu_values)
15
- index = -1
16
- values.collect! { |value| (previous_values[index += 1] - value).abs }
17
- end
18
- data = Hash[*categories.zip(values).flatten]
19
- total = values.inject { |memo, value| memo + value }
35
+ agg_cpu_stat = File.read(cpu_file).lines.map { |line| line.split }.detect { |values| values.first == "cpu" }
36
+ output = {}
20
37
 
21
- output = {}
22
- if previous_values
23
- data.each do |category, value|
24
- output["cpu.#{category}"] = value / total * 100
38
+ if agg_cpu_stat
39
+ categories = [:user, :nice, :system, :idle, :iowait]
40
+ values = agg_cpu_stat.slice(1, 5).map { |v| v.to_f }
41
+ SystemInspector.memory.store(:cpu_values, values.dup)
42
+ if previous_values = SystemInspector.memory.retrieve(:cpu_values)
43
+ index = -1
44
+ values.collect! { |value| (previous_values[index += 1] - value).abs }
25
45
  end
46
+
47
+ data = Hash[*categories.zip(values).flatten]
48
+ total = values.inject { |memo, value| memo + value }
49
+
50
+ if previous_values
51
+ data.each do |category, value|
52
+ output["cpu.#{category}"] = value / total * 100
53
+ end
54
+ end
55
+
56
+ output["cpu.in_use"] = 100 - data[:idle] / total * 100
26
57
  end
27
- output["cpu.in_use"] = 100 - data[:idle] / total * 100
58
+
28
59
  output
29
60
  end
30
61
 
31
62
  def self.loadavg
32
- min_1, min_5, min_15 = `cat /proc/loadavg`.split
63
+ min_1, min_5, min_15 = File.read(load_file).split
33
64
  {
34
65
  'load.1min' => min_1.to_f,
35
66
  'load.5min' => min_5.to_f,
@@ -39,34 +70,41 @@ class SystemInspector
39
70
 
40
71
  def self.load_memory
41
72
  output = { :gauges => {} }
42
- if SystemInspector.command_present?('free', 'memory')
73
+ if File.exists?(memory_file)
43
74
  output[:gauges].merge!(memory)
44
75
  end
45
- if SystemInspector.command_present?('free', 'swap')
46
- output[:gauges].merge!(swap)
47
- end
48
76
  output
49
77
  end
50
78
 
51
79
  def self.memory
52
- _, total, used, free, shared, buffers, cached = `free -k -o | grep Mem`.chomp.split
53
- {
54
- 'memory.used_mb' => used.to_f / 1024,
55
- 'memory.free_mb' => free.to_f / 1024,
56
- 'memory.buffers_mb' => buffers.to_f / 1024,
57
- 'memory.cached_mb' => cached.to_f / 1024,
58
- 'memory.free_percent' => (free.to_f / total.to_f) * 100
59
- }
60
- end
80
+ memory_stats = Hash[File.read(memory_file).lines.map { |line| line.chomp.strip.split(/:\s+/) }.reject { |l| l.size != 2 } ]
81
+ total = memory_stats["MemTotal"].to_f
82
+ free = memory_stats["MemFree"].to_f
83
+ used = total - free
84
+ buffers = memory_stats["Buffers"].to_f
85
+ cached = memory_stats["Cached"].to_f
86
+ swaptotal = memory_stats["SwapTotal"].to_f
87
+ swapfree = memory_stats["SwapFree"].to_f
88
+ swapused = swaptotal - swapfree
89
+
90
+ stats_to_record = {
91
+ 'memory.used_mb' => used / 1024,
92
+ 'memory.free_mb' => free / 1024,
93
+ 'memory.buffers_mb' => buffers / 1024,
94
+ 'memory.cached_mb' => cached / 1024,
95
+ 'memory.free_percent' => (free / total) * 100,
61
96
 
62
- def self.swap
63
- _, total, used, free = `free -k -o | grep Swap`.chomp.split
64
- return {} if total.to_i == 0
65
- {
66
- 'swap.used_mb' => used.to_f / 1024,
67
- 'swap.free_mb' => free.to_f / 1024,
68
- 'swap.free_percent' => (free.to_f / total.to_f) * 100
69
97
  }
98
+
99
+ if swaptotal > 0
100
+ stats_to_record.merge!({
101
+ 'swap.used_mb' => swapused / 1024,
102
+ 'swap.free_mb' => swapfree / 1024,
103
+ 'swap.free_percent' => (swapfree / swaptotal) * 100
104
+ })
105
+ end
106
+
107
+ stats_to_record
70
108
  end
71
109
 
72
110
  def self.load_disks
@@ -74,7 +112,7 @@ class SystemInspector
74
112
  if SystemInspector.command_present?('df', 'disk storage')
75
113
  output[:gauges].merge!(disk_storage)
76
114
  end
77
- if SystemInspector.command_present?('mount', 'disk IO')
115
+ if File.exists?(mount_file) && File.exists?(disk_file)
78
116
  output[:gauges].merge!(disk_io)
79
117
  end
80
118
  output
@@ -104,10 +142,10 @@ class SystemInspector
104
142
 
105
143
  def self.disk_io
106
144
  output = {}
107
- mounted_devices = `mount`.lines.grep(/^\/dev\/(\w+)/) { $1 }
108
- diskstats_lines = `cat /proc/diskstats`.lines.grep(/#{mounted_devices.join('|')}/)
109
- entries = diskstats_lines.map do |line|
110
- values = line.chomp.split
145
+ device_root = "/dev/"
146
+ mounted_devices = File.read(mount_file).lines.map { |l| l.split.first }.select { |device| device.index(device_root) }.map { |device| File.realpath(device) }
147
+ diskstats_lines = File.read(disk_file).lines.map(&:split).select { |values| mounted_devices.include?(File.join(device_root, values[2])) }
148
+ entries = diskstats_lines.map do |values|
111
149
  entry = {}
112
150
  entry[:time] = Time.now
113
151
  entry[:device] = values[2]
@@ -127,14 +165,14 @@ class SystemInspector
127
165
 
128
166
  def self.load_filesystem
129
167
  output = { :gauges => {} }
130
- if SystemInspector.command_present?('sysctl', 'filesystem')
168
+ if File.exists?(open_files_file)
131
169
  output[:gauges].merge!(filesystem)
132
170
  end
133
171
  output
134
172
  end
135
173
 
136
174
  def self.filesystem
137
- allocated, unused, max = `sysctl fs.file-nr`.split[-3..-1].map { |v| v.to_i }
175
+ allocated, unused, max = File.read(open_files_file).split.map(&:to_i)
138
176
  open_files = allocated - unused
139
177
  {
140
178
  'filesystem.open_files' => open_files,
@@ -1,5 +1,5 @@
1
1
  module Instrumental
2
2
  module Tools
3
- VERSION = "1.0.0.rc2"
3
+ VERSION = "1.0.0"
4
4
  end
5
5
  end
@@ -0,0 +1,26 @@
1
+ ---
2
+ driver:
3
+ name: vagrant
4
+ provider: vmware_fusion
5
+
6
+ provisioner:
7
+ name: puppet_apply
8
+ manifests_path: manifests
9
+ modules_path: modules
10
+ hiera_data_path: hieradata
11
+
12
+ platforms:
13
+ - name: nocm_ubuntu-12.04
14
+ driver_plugin: vagrant
15
+ driver_config:
16
+ box: nocm_ubuntu-12.04
17
+ box_url: https://atlas.hashicorp.com/puppetlabs/boxes/ubuntu-12.04-64-nocm/versions/1.0.1/providers/vmware_fusion.box
18
+ - name: nocm_centos-6.6
19
+ driver_plugin: vagrant
20
+ driver_config:
21
+ box: nocm_centos-6.6
22
+ box_url: https://atlas.hashicorp.com/puppetlabs/boxes/centos-6.6-64-nocm/versions/1.0.1/providers/vmware_fusion.box
23
+
24
+ suites:
25
+ - name: default
26
+ manifest: site.pp