Module: NewRelic::Agent::SystemInfo

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

Class Method Summary collapse

Class Method Details

.boot_idObject



284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
# File 'lib/new_relic/agent/system_info.rb', line 284

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)


28
29
30
# File 'lib/new_relic/agent/system_info.rb', line 28

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

.clear_processor_infoObject



38
39
40
# File 'lib/new_relic/agent/system_info.rb', line 38

def self.clear_processor_info
  @processor_info = nil
end

.darwin?Boolean

Returns:

  • (Boolean)


20
21
22
# File 'lib/new_relic/agent/system_info.rb', line 20

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

.docker_container_idObject



175
176
177
178
179
180
181
182
# File 'lib/new_relic/agent/system_info.rb', line 175

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

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

  parse_docker_container_id(cgroup_info)
end

.ip_addressesObject



34
35
36
# File 'lib/new_relic/agent/system_info.rb', line 34

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

.linux?Boolean

Returns:

  • (Boolean)


24
25
26
# File 'lib/new_relic/agent/system_info.rb', line 24

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

.num_logical_processorsObject



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

def self.num_logical_processors; processor_info[:num_logical_processors] end

.num_physical_coresObject



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

def self.num_physical_cores; processor_info[:num_physical_cores] end

.num_physical_packagesObject



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

def self.num_physical_packages; processor_info[:num_physical_packages] end

.os_versionObject



171
172
173
# File 'lib/new_relic/agent/system_info.rb', line 171

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

.parse_cgroup_ids(cgroup_info) ⇒ Object



221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/new_relic/agent/system_info.rb', line 221

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



107
108
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
# File 'lib/new_relic/agent/system_info.rb', line 107

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



184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/new_relic/agent/system_info.rb', line 184

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



275
276
277
278
279
280
281
282
# File 'lib/new_relic/agent/system_info.rb', line 275

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.



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 242

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



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

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

.processor_infoObject



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

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



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

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



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

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



79
80
81
82
# File 'lib/new_relic/agent/system_info.rb', line 79

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

.ram_in_mibObject



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

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



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

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



16
17
18
# File 'lib/new_relic/agent/system_info.rb', line 16

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

.sysctl_value(name) ⇒ Object



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

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