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



324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
# File 'lib/new_relic/agent/system_info.rb', line 324

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)


44
45
46
# File 'lib/new_relic/agent/system_info.rb', line 44

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

.clear_processor_infoObject



54
55
56
# File 'lib/new_relic/agent/system_info.rb', line 54

def self.clear_processor_info
  @processor_info = nil
end

.darwin?Boolean

Returns:

  • (Boolean)


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

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.



205
206
207
208
209
210
211
212
213
214
215
# File 'lib/new_relic/agent/system_info.rb', line 205

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



217
218
219
220
221
222
# File 'lib/new_relic/agent/system_info.rb', line 217

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



50
51
52
# File 'lib/new_relic/agent/system_info.rb', line 50

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

.linux?Boolean

Returns:

  • (Boolean)


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

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

.num_logical_processorsObject



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

def self.num_logical_processors; processor_info[:num_logical_processors] end

.num_physical_coresObject



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

def self.num_physical_cores; processor_info[:num_physical_cores] end

.num_physical_packagesObject



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

def self.num_physical_packages; processor_info[:num_physical_packages] end

.os_distributionObject



22
23
24
25
26
27
28
29
30
# File 'lib/new_relic/agent/system_info.rb', line 22

def self.os_distribution
  case
  when darwin? then :darwin
  when linux? then :linux
  when bsd? then :bsd
  when windows? then :windows
  else ruby_os_identifier
  end
end

.os_versionObject



187
188
189
# File 'lib/new_relic/agent/system_info.rb', line 187

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

.parse_cgroup_ids(cgroup_info) ⇒ Object



261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/new_relic/agent/system_info.rb', line 261

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



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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/new_relic/agent/system_info.rb', line 123

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



224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# File 'lib/new_relic/agent/system_info.rb', line 224

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



315
316
317
318
319
320
321
322
# File 'lib/new_relic/agent/system_info.rb', line 315

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.



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

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



183
184
185
# File 'lib/new_relic/agent/system_info.rb', line 183

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

.processor_infoObject



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/new_relic/agent/system_info.rb', line 58

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



100
101
102
103
104
105
106
# File 'lib/new_relic/agent/system_info.rb', line 100

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



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/new_relic/agent/system_info.rb', line 77

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



95
96
97
98
# File 'lib/new_relic/agent/system_info.rb', line 95

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

.ram_in_mibObject



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

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



108
109
110
111
112
113
114
115
116
# File 'lib/new_relic/agent/system_info.rb', line 108

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



118
119
120
121
# File 'lib/new_relic/agent/system_info.rb', line 118

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

.windows?Boolean

Returns:

  • (Boolean)


32
33
34
# File 'lib/new_relic/agent/system_info.rb', line 32

def self.windows?
  !!(ruby_os_identifier[/mingw|mswin/i])
end