server_metrics 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +19 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +34 -0
- data/Rakefile +10 -0
- data/example_processes.rb +20 -0
- data/example_usage.rb +43 -0
- data/lib/server_metrics/collector.rb +147 -0
- data/lib/server_metrics/collectors/cpu.rb +137 -0
- data/lib/server_metrics/collectors/disk.rb +106 -0
- data/lib/server_metrics/collectors/memory.rb +156 -0
- data/lib/server_metrics/collectors/network.rb +26 -0
- data/lib/server_metrics/collectors/processes.rb +110 -0
- data/lib/server_metrics/multi_collector.rb +91 -0
- data/lib/server_metrics/system_info.rb +47 -0
- data/lib/server_metrics/version.rb +3 -0
- data/lib/server_metrics.rb +8 -0
- data/server_metrics.gemspec +29 -0
- data/test/fixtures/cpu.txt +54 -0
- data/test/fixtures/disk.txt +79 -0
- data/test/fixtures/memory.txt +43 -0
- data/test/fixtures/network.txt +13 -0
- data/test/test.rb +14 -0
- data/test/test_basics.rb +153 -0
- data/test/test_helper.rb +49 -0
- data/test/test_with_fixtures.rb +105 -0
- metadata +193 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Andre Lewis
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# SystemMetrics
|
2
|
+
|
3
|
+
Collects key metrics on CPU, disks, memory, network interfaces, and processes.
|
4
|
+
|
5
|
+
## Use
|
6
|
+
|
7
|
+
$ gem install server_metrics
|
8
|
+
|
9
|
+
require 'server_metrics'
|
10
|
+
cpu=ServerMetrics::Cpu.new
|
11
|
+
cpu.run
|
12
|
+
pp cpu.data.inspect
|
13
|
+
|
14
|
+
# same with:
|
15
|
+
|
16
|
+
ServerMetrics::Disk.new
|
17
|
+
ServerMetrics::Memory.new
|
18
|
+
ServerMetrics::Network.new
|
19
|
+
|
20
|
+
# also see:
|
21
|
+
|
22
|
+
ServerMetrics::SystemInfo.to_hash
|
23
|
+
|
24
|
+
## Creating a new collector
|
25
|
+
|
26
|
+
Inherit from either Collector (if you're generating just one set of data), or MultiCollector (if you will be generating N
|
27
|
+
sets of data -- for example if you're monitoring disks or network interfaces).
|
28
|
+
|
29
|
+
## TODOs
|
30
|
+
|
31
|
+
* better mac compatibility
|
32
|
+
* more test coverage
|
33
|
+
* rethink module hierarchy
|
34
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require File.dirname(__FILE__)+ "/lib/server_metrics"
|
3
|
+
require "pry"
|
4
|
+
require "awesome_print"
|
5
|
+
|
6
|
+
|
7
|
+
p = ServerMetrics::Processes.new(1)
|
8
|
+
puts "stating ..."
|
9
|
+
p.run
|
10
|
+
sleep 1
|
11
|
+
ap p.run
|
12
|
+
|
13
|
+
#p.get_overall_cpu
|
14
|
+
#ServerMetrics::ProcessList.add_cpu_time(ServerMetrics::ProcessList.group)
|
15
|
+
#puts "sleeping ..."
|
16
|
+
#sleep 1
|
17
|
+
#puts "#### overall"
|
18
|
+
#ap p.get_overall_cpu
|
19
|
+
#puts "#### individual processes"
|
20
|
+
#ap ServerMetrics::ProcessList.add_cpu_time(ServerMetrics::ProcessList.group)
|
data/example_usage.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require File.dirname(__FILE__)+ "/lib/server_metrics"
|
3
|
+
require "pry"
|
4
|
+
|
5
|
+
class Harness
|
6
|
+
attr_accessor :num_runs, :latest_run
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@num_runs=0
|
10
|
+
|
11
|
+
@collectors={:disks => ServerMetrics::Disk.new(), :cpu => ServerMetrics::Cpu.new(), :memory => ServerMetrics::Memory.new(), :network => ServerMetrics::Network.new(), :processes=>ServerMetrics::Processes.new(ServerMetrics::SystemInfo.num_processors)}
|
12
|
+
|
13
|
+
@system_info = ServerMetrics::SystemInfo.to_h
|
14
|
+
end
|
15
|
+
|
16
|
+
def run
|
17
|
+
collector_res={}
|
18
|
+
@collectors.each_pair do |name, collector|
|
19
|
+
collector_res[name] = collector.run
|
20
|
+
end
|
21
|
+
|
22
|
+
@latest_run = collector_res.merge(:system_info => @system_info)
|
23
|
+
|
24
|
+
@num_runs +=1
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
harness = Harness.new
|
29
|
+
|
30
|
+
harness.run
|
31
|
+
sleep 1
|
32
|
+
harness.run
|
33
|
+
pp harness.latest_run
|
34
|
+
|
35
|
+
#puts "starting"
|
36
|
+
#while(true) do
|
37
|
+
# harness.run
|
38
|
+
# puts "running at #{Time.now}"
|
39
|
+
# File.open("server_metrics.json","w") do |f|
|
40
|
+
# f.puts harness.latest_run
|
41
|
+
# end
|
42
|
+
# sleep 15
|
43
|
+
#end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
|
2
|
+
# The base class for SystemMetrics collectors.
|
3
|
+
#
|
4
|
+
# Some collects inherit directly from Collector, and some inherit from MultiCollector.
|
5
|
+
# The difference: if you're collecting for an arbitrary number of instances (say, disks), use MultiCollector.
|
6
|
+
# Otherwise, use Collector.
|
7
|
+
#
|
8
|
+
# Relative to Scout's plugins, Collectors have a few differences:
|
9
|
+
#
|
10
|
+
# 1. simplified: no options parsing. simpler interface to reporting and memory (these methods only take a hash)
|
11
|
+
# 2. intended to persist in memory: a collector maintains its own memory. Reuse the same instance as many times as needed.
|
12
|
+
# If you need to persist to disk, use the to_hash and from_hash methods.
|
13
|
+
#
|
14
|
+
module ServerMetrics
|
15
|
+
class Collector
|
16
|
+
attr_reader :collector_id
|
17
|
+
attr_accessor :data, :error
|
18
|
+
|
19
|
+
def initialize(options={})
|
20
|
+
@options = options
|
21
|
+
@data={}
|
22
|
+
@memory={}
|
23
|
+
@collector_id = self.class.name+'-'+@options.to_a.sort_by { |a| a.first }.flatten.join('-')
|
24
|
+
@error=nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def option(name)
|
28
|
+
@options[name] || @options[name.is_a?(String) ? name.to_sym : String(name)]
|
29
|
+
end
|
30
|
+
|
31
|
+
def run
|
32
|
+
@data={}
|
33
|
+
build_report
|
34
|
+
@data
|
35
|
+
end
|
36
|
+
|
37
|
+
def report(hash)
|
38
|
+
@data.merge!(hash)
|
39
|
+
end
|
40
|
+
|
41
|
+
# memory(:no_track)
|
42
|
+
# memory.delete(:no_track)
|
43
|
+
# memory.clear
|
44
|
+
#
|
45
|
+
def memory(name = nil)
|
46
|
+
if name.nil?
|
47
|
+
@memory
|
48
|
+
else
|
49
|
+
@memory[name] || @memory[name.is_a?(String) ? name.to_sym : String(name)]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
# remember(name1: value1, name2: value2)
|
55
|
+
#
|
56
|
+
def remember(hash)
|
57
|
+
@memory.merge!(hash)
|
58
|
+
end
|
59
|
+
|
60
|
+
# counter(:rkbps, stats['rsect'] / 2, :per => :second)
|
61
|
+
# counter(:rpm, request_counter, :per => :minute)
|
62
|
+
# counter(:swap_ins, vmstat['pswpin'], :per => :second, :round => true)
|
63
|
+
#
|
64
|
+
def counter(name, value, options = {}, &block)
|
65
|
+
current_time = Time.now
|
66
|
+
|
67
|
+
if data = memory("_counter_#{name}")
|
68
|
+
last_time, last_value = data[:time], data[:value]
|
69
|
+
elapsed_seconds = current_time - last_time
|
70
|
+
|
71
|
+
# We won't log it if the value has wrapped or enough time hasn't
|
72
|
+
# elapsed
|
73
|
+
if value >= last_value && elapsed_seconds >= 1
|
74
|
+
if block
|
75
|
+
result = block.call(last_value, value)
|
76
|
+
else
|
77
|
+
result = value - last_value
|
78
|
+
end
|
79
|
+
|
80
|
+
case options[:per]
|
81
|
+
when :second, 'second'
|
82
|
+
result = result / elapsed_seconds.to_f
|
83
|
+
when :minute, 'minute'
|
84
|
+
result = result / elapsed_seconds.to_f * 60.0
|
85
|
+
else
|
86
|
+
raise "Unknown option for ':per': #{options[:per].inspect}"
|
87
|
+
end
|
88
|
+
|
89
|
+
if options[:round]
|
90
|
+
result = (result * (10 ** options[:round])).round / (10 ** options[:round]).to_f
|
91
|
+
end
|
92
|
+
|
93
|
+
report(name => result)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
remember("_counter_#{name}" => {:time => current_time, :value => value})
|
98
|
+
end
|
99
|
+
|
100
|
+
# Convert strings containing 'T,G,M,or K' to MB. The result is a float only -- units are NOT returned
|
101
|
+
def convert_to_mb(value)
|
102
|
+
value = if value =~ /G/i
|
103
|
+
value.to_f*1024.0
|
104
|
+
elsif value =~ /M/i
|
105
|
+
value.to_f
|
106
|
+
elsif value =~ /K/i
|
107
|
+
(value.to_f/1024.0)
|
108
|
+
elsif value =~ /T/i
|
109
|
+
(value.to_f*1024.0*1024.0)
|
110
|
+
else
|
111
|
+
value.to_f
|
112
|
+
end
|
113
|
+
("%.1f" % [value]).to_f
|
114
|
+
end
|
115
|
+
|
116
|
+
#
|
117
|
+
def normalize_key(key)
|
118
|
+
(key.is_a?(String) ? key : key.to_s).downcase.gsub(" ", "_").gsub("%", "percent").to_sym
|
119
|
+
end
|
120
|
+
|
121
|
+
# returns a hash you can serialize and store on disk, or just hold onto and re-instantiate the collector later.
|
122
|
+
# Why you'd need to do this: to persist the memory (including counters) of a plugin instance.
|
123
|
+
#
|
124
|
+
# Collector.from_hash(h) is the flipside of this: Collector.from_hash(plugin.to_hash) gets you essentially the same instance
|
125
|
+
#
|
126
|
+
def to_hash
|
127
|
+
{:options => @options, :memory => @memory, :data => @data, :plugin_id => @plugin_id}
|
128
|
+
end
|
129
|
+
|
130
|
+
# see to_hash. The hash should contain :options and :memory keys
|
131
|
+
def self.from_hash(hash)
|
132
|
+
c=Collector.new(hash[:options])
|
133
|
+
c.instance_variable_set('@memory', hash[:memory])
|
134
|
+
c.instance_variable_set('@data', hash[:data])
|
135
|
+
c
|
136
|
+
end
|
137
|
+
|
138
|
+
def linux?
|
139
|
+
RbConfig::CONFIG['target_os'] == 'linux'
|
140
|
+
end
|
141
|
+
|
142
|
+
def osx?
|
143
|
+
RbConfig::CONFIG['target_os'] == 'darwin'
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require "time"
|
2
|
+
|
3
|
+
class ServerMetrics::Cpu < ServerMetrics::Collector
|
4
|
+
|
5
|
+
# Raised by #CpuStats when an error reading /proc/stats.
|
6
|
+
class ProcStatError < Exception
|
7
|
+
end
|
8
|
+
|
9
|
+
def build_report
|
10
|
+
begin
|
11
|
+
stats = CpuStats.fetch
|
12
|
+
|
13
|
+
if previous = memory(:cpu_stats)
|
14
|
+
previous_stats = CpuStats.from_hash(previous)
|
15
|
+
|
16
|
+
report stats.diff(previous_stats)
|
17
|
+
end
|
18
|
+
|
19
|
+
remember(:cpu_stats => stats.to_h)
|
20
|
+
rescue ProcStatError
|
21
|
+
@error = "could not retrieve CPU stats from /proc/stat"
|
22
|
+
end
|
23
|
+
|
24
|
+
ENV['LANG'] = 'C' # forcing english for parsing
|
25
|
+
uptime_output = `uptime`
|
26
|
+
matches = uptime_output.match(/load averages?: ([\d.]+),? ([\d.]+),? ([\d.]+)\Z/)
|
27
|
+
|
28
|
+
report("Last minute" => matches[1].to_f,
|
29
|
+
"Last five minutes" => matches[2].to_f,
|
30
|
+
"Last fifteen minutes" => matches[3].to_f)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Helper class
|
34
|
+
class CpuStats
|
35
|
+
attr_accessor :user, :system, :idle, :iowait, :interrupts, :procs_running, :procs_blocked, :time, :steal
|
36
|
+
|
37
|
+
def self.fetch
|
38
|
+
output = `cat /proc/stat 2>&1`
|
39
|
+
|
40
|
+
if $? and !$?.success?
|
41
|
+
raise ProcStatError, output
|
42
|
+
end
|
43
|
+
|
44
|
+
data = output.split(/\n/).collect { |line| line.split }
|
45
|
+
|
46
|
+
cpu_stats = CpuStats.new
|
47
|
+
|
48
|
+
if cpu = data.detect { |line| line[0] == 'cpu' }
|
49
|
+
cpu_stats.user, nice, cpu_stats.system, cpu_stats.idle, cpu_stats.iowait,
|
50
|
+
hardirq, softirq, cpu_stats.steal = *cpu[1..-1].collect { |c| c.to_i }
|
51
|
+
cpu_stats.user += nice
|
52
|
+
cpu_stats.system += hardirq + softirq
|
53
|
+
end
|
54
|
+
|
55
|
+
if interrupts = data.detect { |line| line[0] == 'intr' }
|
56
|
+
cpu_stats.interrupts, _ = *interrupts[1..-1].collect { |c| c.to_i }
|
57
|
+
end
|
58
|
+
|
59
|
+
if procs_running = data.detect { |line| line[0] == 'procs_running' }
|
60
|
+
cpu_stats.procs_running, _ = *procs_running[1..-1].collect { |c| c.to_i }
|
61
|
+
end
|
62
|
+
|
63
|
+
if procs_blocked = data.detect { |line| line[0] == 'procs_blocked' }
|
64
|
+
cpu_stats.procs_blocked, _ = *procs_blocked[1..-1].collect { |c| c.to_i }
|
65
|
+
end
|
66
|
+
|
67
|
+
cpu_stats
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.from_hash(h)
|
71
|
+
cpu_stats= CpuStats.new
|
72
|
+
hash = {}
|
73
|
+
h.each { |k, v| hash[k.to_sym] = v }
|
74
|
+
|
75
|
+
if time = hash.delete(:time)
|
76
|
+
cpu_stats.time = Time.parse(time) rescue time
|
77
|
+
end
|
78
|
+
|
79
|
+
hash.each do |k, v|
|
80
|
+
cpu_stats.send("#{k}=", v) if cpu_stats.respond_to?("#{k}=")
|
81
|
+
end
|
82
|
+
cpu_stats
|
83
|
+
end
|
84
|
+
|
85
|
+
def initialize
|
86
|
+
self.time = Time.now
|
87
|
+
end
|
88
|
+
|
89
|
+
def diff(other)
|
90
|
+
diff_user = user - other.user
|
91
|
+
diff_system = system - other.system
|
92
|
+
diff_idle = idle - other.idle
|
93
|
+
diff_iowait = iowait - other.iowait
|
94
|
+
|
95
|
+
div = diff_user + diff_system + diff_idle + diff_iowait
|
96
|
+
|
97
|
+
if steal && other.steal && steal > 0
|
98
|
+
diff_steal = steal - other.steal
|
99
|
+
div += diff_steal
|
100
|
+
end
|
101
|
+
|
102
|
+
divo2 = div / 2
|
103
|
+
|
104
|
+
results = {
|
105
|
+
"User" => (100.0 * diff_user + divo2) / div,
|
106
|
+
"System" => (100.0 * diff_system + divo2) / div,
|
107
|
+
"Idle" => (100.0 * diff_idle + divo2) / div,
|
108
|
+
"IO wait" => (100.0 * diff_iowait + divo2) / div,
|
109
|
+
"Procs running" => self.procs_running,
|
110
|
+
"Procs blocked" => self.procs_blocked
|
111
|
+
}
|
112
|
+
|
113
|
+
if diff_steal && steal > 0
|
114
|
+
results["Steal"] = (100.0 * diff_steal + divo2) / div
|
115
|
+
end
|
116
|
+
|
117
|
+
if self.time && other.time
|
118
|
+
diff_in_seconds = self.time.to_f - other.time.to_f
|
119
|
+
|
120
|
+
results["Interrupts"] = (self.interrupts.to_f - other.interrupts.to_f) / diff_in_seconds
|
121
|
+
end
|
122
|
+
|
123
|
+
results
|
124
|
+
end
|
125
|
+
|
126
|
+
def to_h
|
127
|
+
{
|
128
|
+
:user => user, :system => system, :idle => idle, :iowait => iowait,
|
129
|
+
:interrupts => interrupts, :procs_running => procs_running,
|
130
|
+
:procs_blocked => procs_blocked, :time => Time.now.to_s,
|
131
|
+
:steal => steal
|
132
|
+
}
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# Collects Disk metrics on eligible filesystems. Reports a hash of hashes, with the first hash keyed by device name.
|
2
|
+
#
|
3
|
+
# TODO: Currently, this reports on devices that begins with /dev as listed by `mount`. Revisit this.
|
4
|
+
# TODO: relies on /proc/diskstats, so not mac compatible. Figure out mac compatibility
|
5
|
+
#
|
6
|
+
class ServerMetrics::Disk < ServerMetrics::MultiCollector
|
7
|
+
|
8
|
+
def build_report
|
9
|
+
@df_output = `df -h`.split("\n")
|
10
|
+
@devices = `mount`.split("\n").grep(/^\/dev/).map{|l|l.split.first} # any device that starts with /dev
|
11
|
+
|
12
|
+
@devices.each do |device|
|
13
|
+
get_sizes(device) # does its own reporting
|
14
|
+
get_stats(device) if linux? # does its own reporting
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# called from build_report for each device
|
19
|
+
def get_sizes(device)
|
20
|
+
ENV['LANG'] = 'C' # forcing English for parsing
|
21
|
+
|
22
|
+
header_line=@df_output.first
|
23
|
+
num_columns = header_line.include?("iused") ? 9 : 6 # Mac has extra columns
|
24
|
+
headers = header_line.split(/\s+/,num_columns)
|
25
|
+
parsed_lines=[] # Each line will look like {"%iused" => "38%","Avail" => "289Gi", "Capacity=> "38%", "Filesystem"=> "/dev/disk0s2","Mounted => "/", "Size" => "465Gi", "Used" => "176Gi", "ifree" => "75812051", "iused" => "46116178"}
|
26
|
+
|
27
|
+
@df_output[1..@df_output.size-2].each do |line|
|
28
|
+
values=line.split(/\s+/,num_columns)
|
29
|
+
parsed_lines<<Hash[headers.zip(values)]
|
30
|
+
end
|
31
|
+
|
32
|
+
# select the right line
|
33
|
+
hash = parsed_lines.select{|l| l["Filesystem"] == device}.first
|
34
|
+
result = {}
|
35
|
+
hash.each_pair do |key,value|
|
36
|
+
#key=normalize_key(key) # downcase, make a symbol, etc
|
37
|
+
value = convert_to_mb(value) if [:avail,:capacity,:size,:used, :usepercent].include?(key)
|
38
|
+
result[key]=value
|
39
|
+
end
|
40
|
+
|
41
|
+
report(device, result)
|
42
|
+
end
|
43
|
+
|
44
|
+
# called from build_report for each device
|
45
|
+
def get_stats(device)
|
46
|
+
stats = iostat(device)
|
47
|
+
|
48
|
+
if stats
|
49
|
+
counter(device, "RPS", stats['rio'], :per => :second)
|
50
|
+
counter(device, "WPS", stats['wio'], :per => :second)
|
51
|
+
counter(device, "Kb RPS", stats['rsect'] / 2, :per => :second)
|
52
|
+
counter(device, "Kb WPS", stats['wsect'] / 2, :per => :second)
|
53
|
+
counter(device, "Utilization", stats['use'] / 10.0, :per => :second)
|
54
|
+
# Not 100% sure that average queue length is present on all distros.
|
55
|
+
if stats['aveq']
|
56
|
+
counter(device, "Average queue length", stats['aveq'], :per => :second)
|
57
|
+
end
|
58
|
+
|
59
|
+
if old = memory(device, "stats")
|
60
|
+
ios = (stats['rio'] - old['rio']) + (stats['wio'] - old['wio'])
|
61
|
+
|
62
|
+
if ios > 0
|
63
|
+
await = ((stats['ruse'] - old['ruse']) + (stats['wuse'] - old['wuse'])) / ios.to_f
|
64
|
+
|
65
|
+
report(device, "Await" => await)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
remember(device, "stats" => stats)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
COLUMNS = %w(major minor name rio rmerge rsect ruse wio wmerge wsect wuse running use aveq)
|
75
|
+
|
76
|
+
# Returns the /proc/diskstats line associated with device name +dev+. Logic:
|
77
|
+
#
|
78
|
+
# * If an exact match of the specified device is found, returns it.
|
79
|
+
# * If there isn't an exact match but there are /proc/diskstats lines that are included in +dev+,
|
80
|
+
# returns the first matching line. This is needed as the mount output used to find the default device doesn't always
|
81
|
+
# match /proc/diskstats output.
|
82
|
+
# * If there are no matches but an LVM is used, returns the line matching "dm-0".
|
83
|
+
def iostat(dev)
|
84
|
+
# if a LVM is used, `mount` output doesn't map to `/diskstats`. In this case, use dm-0 as the default device.
|
85
|
+
lvm = nil
|
86
|
+
retried = false
|
87
|
+
possible_devices = []
|
88
|
+
begin
|
89
|
+
%x(cat /proc/diskstats).split(/\n/).each do |line|
|
90
|
+
entry = Hash[*COLUMNS.zip(line.strip.split(/\s+/).collect { |v| Integer(v) rescue v }).flatten]
|
91
|
+
possible_devices << entry if dev.include?(entry['name'])
|
92
|
+
lvm = entry if (@default_device_used and 'dm-0'.include?(entry['name']))
|
93
|
+
end
|
94
|
+
rescue Errno::EPIPE
|
95
|
+
if retried
|
96
|
+
raise
|
97
|
+
else
|
98
|
+
retried = true
|
99
|
+
retry
|
100
|
+
end
|
101
|
+
end
|
102
|
+
found_device = possible_devices.find { |entry| dev == entry['name'] } || possible_devices.first
|
103
|
+
return found_device || lvm
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|