Module: NewRelic::Agent::SystemInfo

Defined in:
lib/new_relic/agent/system_info.rb

Constant Summary collapse

DOCKER_CGROUPS_V2_PATTERN =
%r{.*/docker/containers/([0-9a-f]{64})/.*}.freeze

Class Method Summary collapse

Class Method Details

.boot_idObject



310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
# File 'lib/new_relic/agent/system_info.rb', line 310

def self.boot_id
  return nil unless linux?

  if bid = proc_try_read('/proc/sys/kernel/random/boot_id')
    bid.chomp!

    if bid.ascii_only?
      if bid.empty?
        ::NewRelic::Agent.logger.debug('boot_id not found in /proc/sys/kernel/random/boot_id')
        ::NewRelic::Agent.increment_metric('Supportability/utilization/boot_id/error')
        nil

      elsif bid.bytesize == 36
        bid

      else
        ::NewRelic::Agent.logger.debug("Found boot_id with invalid length: #{bid}")
        ::NewRelic::Agent.increment_metric('Supportability/utilization/boot_id/error')
        bid[0, 128]

      end
    else
      ::NewRelic::Agent.logger.debug("Found boot_id with non-ASCII characters: #{bid}")
      ::NewRelic::Agent.increment_metric('Supportability/utilization/boot_id/error')
      nil

    end
  else
    ::NewRelic::Agent.logger.debug('boot_id not found in /proc/sys/kernel/random/boot_id')
    ::NewRelic::Agent.increment_metric('Supportability/utilization/boot_id/error')
    nil

  end
end

.bsd?Boolean

Returns:

  • (Boolean)


30
31
32
# File 'lib/new_relic/agent/system_info.rb', line 30

def self.bsd?
  !!(ruby_os_identifier =~ /bsd/i)
end

.clear_processor_infoObject



40
41
42
# File 'lib/new_relic/agent/system_info.rb', line 40

def self.clear_processor_info
  @processor_info = nil
end

.darwin?Boolean

Returns:

  • (Boolean)


22
23
24
# File 'lib/new_relic/agent/system_info.rb', line 22

def self.darwin?
  !!(ruby_os_identifier =~ /darwin/i)
end

.docker_container_idObject

When operating within a Docker container, attempt to obtain the container id.

First look for ‘/proc/self/mountinfo` to exist on disk to signify cgroups v2. If that file exists, read it and expect it to contain one or more “/docker/containers/<container_id>/” lines from which the container id can be gleaned.

Next look for ‘/proc/self/cgroup` to exist on disk to signify cgroup v1. If that file exists, read it and parse the “cpu” group info in the hope of finding a 64 character container id value.

For non-cgroups based containers, use a ‘nil` value for the container id without generating any warnings or errors.



191
192
193
194
195
196
197
198
199
200
201
# File 'lib/new_relic/agent/system_info.rb', line 191

def self.docker_container_id
  return unless ruby_os_identifier.include?('linux')

  cgroupsv2_based_id = docker_container_id_for_cgroupsv2
  return cgroupsv2_based_id if cgroupsv2_based_id

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

  parse_docker_container_id(cgroup_info)
end

.docker_container_id_for_cgroupsv2Object



203
204
205
206
207
208
# File 'lib/new_relic/agent/system_info.rb', line 203

def self.docker_container_id_for_cgroupsv2
  mountinfo = proc_try_read('/proc/self/mountinfo')
  return unless mountinfo

  Regexp.last_match(1) if mountinfo =~ DOCKER_CGROUPS_V2_PATTERN
end

.ip_addressesObject



36
37
38
# File 'lib/new_relic/agent/system_info.rb', line 36

def self.ip_addresses
  Socket.ip_address_list.map(&:ip_address)
end

.linux?Boolean

Returns:

  • (Boolean)


26
27
28
# File 'lib/new_relic/agent/system_info.rb', line 26

def self.linux?
  !!(ruby_os_identifier =~ /linux/i)
end

.num_logical_processorsObject



167
# File 'lib/new_relic/agent/system_info.rb', line 167

def self.num_logical_processors; processor_info[:num_logical_processors] end

.num_physical_coresObject



165
# File 'lib/new_relic/agent/system_info.rb', line 165

def self.num_physical_cores; processor_info[:num_physical_cores] end

.num_physical_packagesObject



163
# File 'lib/new_relic/agent/system_info.rb', line 163

def self.num_physical_packages; processor_info[:num_physical_packages] end

.os_versionObject



173
174
175
# File 'lib/new_relic/agent/system_info.rb', line 173

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

.parse_cgroup_ids(cgroup_info) ⇒ Object



247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/new_relic/agent/system_info.rb', line 247

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



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/new_relic/agent/system_info.rb', line 109

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.sum

  if num_physical_cores == 0
    num_logical_processors = total_processors

    if total_processors == 0
      # Likely a malformed file.
      num_logical_processors = nil
    end

    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



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/new_relic/agent/system_info.rb', line 210

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

  container_id = case cpu_cgroup
  # docker native driver w/out systemd (fs)
  when /[0-9a-f]{64,}/
    if $&.length == 64
      $&
    else # container ID is too long
      ::NewRelic::Agent.logger.debug("Ignoring docker ID of invalid length: '#{cpu_cgroup}'")
      return
    end
  # docker native driver with systemd
  when '/' then nil
  # in a cgroup, but we don't recognize its format
  when /docker\/.*[^0-9a-f]/
    ::NewRelic::Agent.logger.debug("Cgroup indicates docker but container_id has invalid characters: '#{cpu_cgroup}'")
    return
  when /docker/
    ::NewRelic::Agent.logger.debug("Cgroup indicates docker but container_id unrecognized: '#{cpu_cgroup}'")
    ::NewRelic::Agent.increment_metric('Supportability/utilization/docker/error')
    return
  else
    ::NewRelic::Agent.logger.debug("Ignoring unrecognized cgroup ID format: '#{cpu_cgroup}'")
    return
  end

  if container_id && container_id.size != 64
    ::NewRelic::Agent.logger.debug("Found docker container_id with invalid length: #{container_id}")
    ::NewRelic::Agent.increment_metric('Supportability/utilization/docker/error')
    nil
  else
    container_id
  end
end

.parse_linux_meminfo_in_mib(meminfo) ⇒ Object



301
302
303
304
305
306
307
308
# File 'lib/new_relic/agent/system_info.rb', line 301

def self.parse_linux_meminfo_in_mib(meminfo)
  if meminfo && mem_total = meminfo[/MemTotal:\s*(\d*)\skB/, 1]
    (mem_total.to_i / 1024).to_i
  else
    ::NewRelic::Agent.logger.debug("Failed to parse MemTotal from /proc/meminfo: #{meminfo}")
    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.



268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'lib/new_relic/agent/system_info.rb', line 268

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



169
170
171
# File 'lib/new_relic/agent/system_info.rb', line 169

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

.processor_infoObject



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/new_relic/agent/system_info.rb', line 44

def self.processor_info
  return @processor_info if @processor_info

  if darwin?
    processor_info_darwin
  elsif linux?
    processor_info_linux
  elsif bsd?
    processor_info_bsd
  else
    raise "Couldn't determine OS"
  end
  remove_bad_values

  @processor_info
rescue
  @processor_info = NewRelic::EMPTY_HASH
end

.processor_info_bsdObject



86
87
88
89
90
91
92
# File 'lib/new_relic/agent/system_info.rb', line 86

def self.processor_info_bsd
  @processor_info = {
    num_physical_packages: nil,
    num_physical_cores: nil,
    num_logical_processors: sysctl_value('hw.ncpu')
  }
end

.processor_info_darwinObject



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/new_relic/agent/system_info.rb', line 63

def self.processor_info_darwin
  @processor_info = {
    num_physical_packages: sysctl_value('hw.packages'),
    num_physical_cores: sysctl_value('hw.physicalcpu_max'),
    num_logical_processors: sysctl_value('hw.logicalcpu_max')
  }
  # 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')
  end
  if @processor_info[:num_logical_processors] <= 0
    @processor_info[:num_logical_processors] = sysctl_value('hw.logicalcpu')
  end
  if @processor_info[:num_logical_processors] <= 0
    @processor_info[:num_logical_processors] = sysctl_value('hw.ncpu')
  end
end

.processor_info_linuxObject



81
82
83
84
# File 'lib/new_relic/agent/system_info.rb', line 81

def self.processor_info_linux
  cpuinfo = proc_try_read('/proc/cpuinfo')
  @processor_info = cpuinfo ? parse_cpuinfo(cpuinfo) : NewRelic::EMPTY_HASH
end

.ram_in_mibObject



287
288
289
290
291
292
293
294
295
296
297
298
299
# File 'lib/new_relic/agent/system_info.rb', line 287

def self.ram_in_mib
  if darwin?
    (sysctl_value('hw.memsize') / (1024**2))
  elsif linux?
    meminfo = proc_try_read('/proc/meminfo')
    parse_linux_meminfo_in_mib(meminfo)
  elsif bsd?
    (sysctl_value('hw.realmem') / (1024**2))
  else
    ::NewRelic::Agent.logger.debug("Unable to determine ram_in_mib for host os: #{ruby_os_identifier}")
    nil
  end
end

.remove_bad_valuesObject



94
95
96
97
98
99
100
101
102
# File 'lib/new_relic/agent/system_info.rb', line 94

def self.remove_bad_values
  # 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

.ruby_os_identifierObject



18
19
20
# File 'lib/new_relic/agent/system_info.rb', line 18

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

.sysctl_value(name) ⇒ Object



104
105
106
107
# File 'lib/new_relic/agent/system_info.rb', line 104

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`.to_i
end