Class: ServerMetrics::Disk

Inherits:
MultiCollector show all
Defined in:
lib/server_metrics/collectors/disk.rb

Overview

Collects Disk metrics on eligible filesystems. Reports a hash of hashes, with the first hash keyed by device name.

TODO: Currently, this reports on devices that begins with /dev as listed by ‘mount`. Revisit this. TODO: relies on /proc/diskstats, so not mac compatible. Figure out mac compatibility

Instance Attribute Summary

Attributes inherited from Collector

#collector_id, #data, #error

Instance Method Summary collapse

Methods inherited from MultiCollector

#counter, #memory, #remember, #report

Methods inherited from Collector

#convert_to_mb, #counter, from_hash, #initialize, #linux?, #memory, #option, #osx?, #remember, #report, #run, #to_hash

Constructor Details

This class inherits a constructor from ServerMetrics::Collector

Instance Method Details

#build_reportObject



10
11
12
13
14
15
16
17
18
19
# File 'lib/server_metrics/collectors/disk.rb', line 10

def build_report
  # forcing English for parsing
  ENV['LC_ALL'] = 'C'
  ENV['LANG'] = 'C'
  
  devices.each do |device|
    get_sizes(device) # does its own reporting
    get_io_stats(device[:name]) if linux? # does its own reporting
  end
end

#devicesObject

System calls are slow. Read once every minute and not on every innvocation.



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/server_metrics/collectors/disk.rb', line 32

def devices
  if @devices.nil? or @last_devices_output < (Time.now-@options[:ttl].to_i*60)
    @last_devices_output = Time.now
    # if running inside a docker container, we want the devices mounted on the host
    mount_output = dockerized_agent? ? `cat /host/etc/mtab` : `mount`
    @devices = mount_output.split("\n").grep(/^\/dev/).map{|l| {:name => l.split.first, :aliases => []}} # any device that starts with /dev   
    if dockerized_agent?
      `blkid`.split("\n").grep(/ UUID=/).each do |device|
        name = device.match(/\A[^\:]*/)[0]
        uuid = device.match(/\ UUID="(.+?)"/)[1]
        if host_device = @devices.find { |dn| dn[:name] == name }
          host_device[:aliases] << "/dev/disk/by-uuid/#{uuid}"
        end
      end
    end
  end
  @devices
end

#df_outputObject

System calls are slow. Read once every minute and not on every innvocation.



22
23
24
25
26
27
28
29
# File 'lib/server_metrics/collectors/disk.rb', line 22

def df_output
  if @last_df_output.nil? or @last_df_output < (Time.now-@options[:ttl].to_i*60)
    @last_df_output = Time.now
    @df_output = `df -Pkh`.lines.to_a
  else
    @df_output
  end
end

#get_io_stats(device_name) ⇒ Object

called from build_report for each device



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/server_metrics/collectors/disk.rb', line 81

def get_io_stats(device_name)
  stats = iostat(device_name)

  if stats
    counter(device_name, :rps,   stats['rio'],        :per => :second)
    counter(device_name, :wps,   stats['wio'],        :per => :second)
    counter(device_name, :rps_kb, stats['rsect'] / 2,  :per => :second)
    counter(device_name, :wps_kb, stats['wsect'] / 2,  :per => :second)
    counter(device_name, :utilization,  stats['use'] / 10.0, :per => :second)
    # Not 100% sure that average queue length is present on all distros.
    if stats['aveq']
      counter(device_name, :average_queue_length,  stats['aveq'], :per => :second)
    end

    if old = memory(device_name, "stats")
      ios = (stats['rio'] - old['rio']) + (stats['wio']  - old['wio'])

      if ios > 0
        await = ((stats['ruse'] - old['ruse']) + (stats['wuse'] - old['wuse'])) / ios.to_f

        report(device_name, :await => await)
      end
    end

    remember(device_name, "stats" => stats)
  end
end

#get_sizes(device) ⇒ Object

called from build_report for each device



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/server_metrics/collectors/disk.rb', line 52

def get_sizes(device)
  header_line=df_output.first
  headers = header_line.split(/\s+/,6) # limit to 6 columns - last column is "mounted on"
  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"}

  df_output[1..df_output.size-1].each do |line|
    values=line.split(/\s+/,6)
    parsed_lines<<Hash[*headers.zip(values).flatten]
  end

  # select the right line
  hash = parsed_lines.find {|l| l["Filesystem"] == device[:name]}
  # device wasn't found. check device aliases
  if hash.nil?
    hash = parsed_lines.find {|l| device[:aliases].include?(l["Filesystem"])}
  end
  # device wasn't found. could be a mapped device. skip over. 
  return if hash.nil?
  result = {}
  hash.each_pair do |key,value|
    key=normalize_key(key) # downcase, make a symbol, etc
    value = convert_to_mb(value) if [:avail, :capacity, :size, :used].include?(key)
    result[key]=value
  end

  report(device[:name], result)
end