Module: OneApm::Support::SystemInfo

Defined in:
lib/one_apm/support/system_info.rb

Class Method Summary collapse

Class Method Details

.clear_processor_infoObject



12
13
14
# File 'lib/one_apm/support/system_info.rb', line 12

def self.clear_processor_info
  @processor_info = nil
end

.docker_container_idObject



138
139
140
141
142
143
144
145
# File 'lib/one_apm/support/system_info.rb', line 138

def self.docker_container_id
  return unless ruby_os_identifier =~ /linux/

  cgroup_info = proc_try_read('/proc/self/cgroup')
  return unless cgroup_info

  parse_docker_container_id(cgroup_info)
end

.get_processor_infoObject



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/one_apm/support/system_info.rb', line 16

def self.get_processor_info
  if @processor_info.nil?
    case ruby_os_identifier

    when /darwin/
      @processor_info = {
        :num_physical_packages  => sysctl_value('hw.packages').to_i,
        :num_physical_cores     => sysctl_value('hw.physicalcpu_max').to_i,
        :num_logical_processors => sysctl_value('hw.logicalcpu_max').to_i
      }
      # in case those don't work, try backup values
      if @processor_info[:num_physical_cores] <= 0
        @processor_info[:num_physical_cores] = sysctl_value('hw.physicalcpu').to_i
      end
      if @processor_info[:num_logical_processors] <= 0
        @processor_info[:num_logical_processors] = sysctl_value('hw.logicalcpu').to_i
      end
      if @processor_info[:num_logical_processors] <= 0
        @processor_info[:num_logical_processors] = sysctl_value('hw.ncpu').to_i
      end

    when /linux/
      cpuinfo = proc_try_read('/proc/cpuinfo')
      @processor_info = cpuinfo ? parse_cpuinfo(cpuinfo) : {}

    when /freebsd/
      @processor_info = {
        :num_physical_packages  => nil,
        :num_physical_cores     => nil,
        :num_logical_processors => sysctl_value('hw.ncpu').to_i
      }
    end

    # give nils for obviously wrong values
    @processor_info.keys.each do |key|
      value = @processor_info[key]
      if value.is_a?(Numeric) && value <= 0
        @processor_info[key] = nil
      end
    end
  end

  @processor_info
rescue
  {}
end

.num_logical_processorsObject



119
120
121
122
123
124
125
126
127
128
# File 'lib/one_apm/support/system_info.rb', line 119

def self.num_logical_processors
  processor_count = get_processor_info[:num_logical_processors]

  if processor_count.nil?
    OneApm::Manager.logger.warn("Failed to determine processor count, assuming 1")
    processor_count = 1
  end

  processor_count
end

.num_physical_coresObject



118
# File 'lib/one_apm/support/system_info.rb', line 118

def self.num_physical_cores    ; get_processor_info[:num_physical_cores    ] end

.num_physical_packagesObject



117
# File 'lib/one_apm/support/system_info.rb', line 117

def self.num_physical_packages ; get_processor_info[:num_physical_packages ] end

.os_versionObject



134
135
136
# File 'lib/one_apm/support/system_info.rb', line 134

def self.os_version
  proc_try_read('/proc/version')
end

.parse_cgroup_ids(cgroup_info) ⇒ Object



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/one_apm/support/system_info.rb', line 167

def self.parse_cgroup_ids(cgroup_info)
  cgroup_ids = {}

  cgroup_info.split("\n").each do |line|
    parts = line.split(':')
    next unless parts.size == 3
    _, subsystems, cgroup_id = parts
    subsystems = subsystems.split(',')
    subsystems.each do |subsystem|
      cgroup_ids[subsystem] = cgroup_id
    end
  end

  cgroup_ids
end

.parse_cpuinfo(cpuinfo) ⇒ Object



68
69
70
71
72
73
74
75
76
77
78
79
80
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
108
109
110
111
112
113
114
115
# File 'lib/one_apm/support/system_info.rb', line 68

def self.parse_cpuinfo(cpuinfo)
  # Build a hash of the form
  #   { [phys_id, core_id] => num_logical_processors_on_this_core }
  cores = Hash.new(0)
  phys_id = core_id = nil

  total_processors = 0

  cpuinfo.split("\n").map(&:strip).each do |line|
    case line
    when /^processor\s*:/
      cores[[phys_id, core_id]] += 1 if phys_id && core_id
      phys_id = core_id = nil # reset these values
      total_processors += 1
    when /^physical id\s*:(.*)/
      phys_id = $1.strip.to_i
    when /^core id\s*:(.*)/
      core_id = $1.strip.to_i
    end
  end
  cores[[phys_id, core_id]] += 1 if phys_id && core_id

  num_physical_packages  = cores.keys.map(&:first).uniq.size
  num_physical_cores     = cores.size
  num_logical_processors = cores.values.reduce(0,:+)

  if num_physical_cores == 0
    num_logical_processors = total_processors

    if total_processors == 1
      # Some older, single-core processors might not list ids,
      # so we'll just mark them all 1.
      num_physical_packages = 1
      num_physical_cores    = 1
    else
      # We have no way of knowing how many packages or cores
      # we have, even though we know how many processors there are.
      num_physical_packages = nil
      num_physical_cores    = nil
    end
  end

  {
    :num_physical_packages  => num_physical_packages,
    :num_physical_cores     => num_physical_cores,
    :num_logical_processors => num_logical_processors
  }
end

.parse_docker_container_id(cgroup_info) ⇒ Object



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/one_apm/support/system_info.rb', line 147

def self.parse_docker_container_id(cgroup_info)
  cpu_cgroup = parse_cgroup_ids(cgroup_info)['cpu']
  return unless cpu_cgroup

  case cpu_cgroup
  # docker native driver w/out systemd (fs)
  when %r{^/docker/([0-9a-f]+)$}                      then $1
  # docker native driver with systemd
  when %r{^/system\.slice/docker-([0-9a-f]+)\.scope$} then $1
  # docker lxc driver
  when %r{^/lxc/([0-9a-f]+)$}                         then $1
  # not in any cgroup
  when '/'                                            then nil
  # in a cgroup, but we don't recognize its format
  else
    OneApm::Manager.logger.debug("Ignoring unrecognized cgroup ID format: '#{cpu_cgroup}'")
    nil
  end
end

.proc_try_read(path) ⇒ Object

A File.read against /(proc|sysfs)/* can hang with some older Linuxes. See bugzilla.redhat.com/show_bug.cgi?id=604887, RUBY-736, and github.com/opscode/ohai/commit/518d56a6cb7d021b47ed3d691ecf7fba7f74a6a7 for details on why we do it this way.



187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/one_apm/support/system_info.rb', line 187

def self.proc_try_read(path)
  return nil unless File.exist?(path)
  content = ''
  File.open(path) do |f|
    loop do
      begin
        content << f.read_nonblock(4096)
      rescue EOFError
        break
      rescue Errno::EWOULDBLOCK, Errno::EAGAIN
        content = nil
        break # don't select file handle, just give up
      end
    end
  end
  content
end

.processor_archObject



130
131
132
# File 'lib/one_apm/support/system_info.rb', line 130

def self.processor_arch
  RbConfig::CONFIG['target_cpu']
end

.ruby_os_identifierObject



8
9
10
# File 'lib/one_apm/support/system_info.rb', line 8

def self.ruby_os_identifier
  RbConfig::CONFIG['target_os']
end

.sysctl_value(name) ⇒ Object



63
64
65
66
# File 'lib/one_apm/support/system_info.rb', line 63

def self.sysctl_value(name)
  # make sure to redirect stderr so we don't spew if the name is unknown
  `sysctl -n #{name} 2>/dev/null`
end