Skip to content

Commit

Permalink
RUBY-1259 Wrappers for snapshotting VM values
Browse files Browse the repository at this point in the history
In order to deal sanely across the various versions and implementations of Ruby
we support, this adds a layer that allows us to safely take snapshots of the VM
stats that we care about.

NewRelic::Agent::VM::Snapshot will have nil's for values that aren't supported
and represents the state of the VM at a given instant in time. It will be the
responsibility of the sampler to properly compare to prior snapshots and
calculate metrics based off these values.
  • Loading branch information
jasonrclark committed Jan 29, 2014
1 parent d5d7f69 commit dcaa09d
Show file tree
Hide file tree
Showing 8 changed files with 236 additions and 0 deletions.
32 changes: 32 additions & 0 deletions lib/new_relic/agent/vm.rb
@@ -0,0 +1,32 @@
# encoding: utf-8
# This file is distributed under New Relic's license terms.
# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.

require 'new_relic/language_support'
require 'new_relic/agent/vm/mri_vm'
require 'new_relic/agent/vm/jruby_vm'
require 'new_relic/agent/vm/rubinius_vm'

module NewRelic
module Agent
module VM
def self.snapshot
vm.snapshot
end

def self.vm
@vm ||= create_vm
end

def self.create_vm
if NewRelic::LanguageSupport.using_engine?('jruby')
JRubyVM.new
elsif NewRelic::LanguageSupport.using_engine?('rbx')
RubiniusVM.new
else
MriVM.new
end
end
end
end
end
17 changes: 17 additions & 0 deletions lib/new_relic/agent/vm/jruby_vm.rb
@@ -0,0 +1,17 @@
# encoding: utf-8
# This file is distributed under New Relic's license terms.
# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.

require 'new_relic/agent/vm/vm_base'

module NewRelic
module Agent
module VM
class JRubyVM < VMBase
def gather_stats(snap)
# TODO: Which can we gather here?
end
end
end
end
end
68 changes: 68 additions & 0 deletions lib/new_relic/agent/vm/mri_vm.rb
@@ -0,0 +1,68 @@
# encoding: utf-8
# This file is distributed under New Relic's license terms.
# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.

require 'new_relic/agent/vm/vm_base'

module NewRelic
module Agent
module VM
class MriVM < VMBase
def gather_stats(snap)
gather_gc_stats(snap)
gather_ruby_vm_stats(snap)
gather_thread_stats(snap)
end

def gather_gc_stats(snap)
if supports?(:gc_runs)
gc_stats = GC.stat
snap.gc_runs = gc_stats[:count]
snap.total_allocated_object = gc_stats[:total_allocated_object]
snap.major_gc_count = gc_stats[:major_gc_count]
snap.minor_gc_count = gc_stats[:minor_gc_count]
snap.heap_live = gc_stats[:heap_live_slot] || gc_stats[:heap_live_num]
snap.heap_free = gc_stats[:heap_free_slot] || gc_stats[:heap_free_num]
end
end

def gather_ruby_vm_stats(snap)
if supports?(:method_cache_invalidations)
vm_stats = RubyVM.stat
snap.method_cache_invalidations = vm_stats[:global_method_state]
snap.constant_cache_invalidations = vm_stats[:global_constant_state]
end
end

def gather_thread_stats(snap)
snap.thread_count = Thread.list.size
end

def supports?(key)
case key
when :gc_runs
RUBY_VERSION >= '1.9.2'
when :total_allocated_object
RUBY_VERSION >= '2.0.0'
when :major_gc_count
RUBY_VERSION >= '2.1.0'
when :minor_gc_count
RUBY_VERSION >= '2.1.0'
when :heap_live
RUBY_VERSION >= '1.9.2'
when :heap_free
RUBY_VERSION >= '1.9.2'
when :method_cache_invalidations
RUBY_VERSION >= '2.1.0'
when :constant_cache_invalidations
RUBY_VERSION >= '2.1.0'
when :thread_count
true
else
false
end
end
end
end
end
end
17 changes: 17 additions & 0 deletions lib/new_relic/agent/vm/rubinius_vm.rb
@@ -0,0 +1,17 @@
# encoding: utf-8
# This file is distributed under New Relic's license terms.
# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.

require 'new_relic/agent/vm/vm_base'

module NewRelic
module Agent
module VM
class RubiniusVM < VMBase
def gather_stats(snap)
# TODO: Which can we gather here?
end
end
end
end
end
16 changes: 16 additions & 0 deletions lib/new_relic/agent/vm/snapshot.rb
@@ -0,0 +1,16 @@
# encoding: utf-8
# This file is distributed under New Relic's license terms.
# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.

module NewRelic
module Agent
module VM
class Snapshot
attr_accessor :gc_runs, :major_gc_count, :minor_gc_count,
:total_allocated_object, :heap_live, :heap_free,
:method_cache_invalidations, :constant_cache_invalidations,
:thread_count
end
end
end
end
27 changes: 27 additions & 0 deletions lib/new_relic/agent/vm/vm_base.rb
@@ -0,0 +1,27 @@
# encoding: utf-8
# This file is distributed under New Relic's license terms.
# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.

require 'new_relic/agent/vm/snapshot'

module NewRelic
module Agent
module VM
class VMBase
def snapshot
snap = Snapshot.new
gather_stats(snap)
snap
end

def gather_stats(snap)
raise NotImplementedError("VM subclasses expected to implement gather_stats")
end

def supports?(key)
false
end
end
end
end
end
11 changes: 11 additions & 0 deletions test/new_relic/agent/vm/snapshot_test.rb
@@ -0,0 +1,11 @@
# encoding: utf-8
# This file is distributed under New Relic's license terms.
# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.

require 'new_relic/agent/vm/snapshot'

class NewRelic::Agent::VM::SnapshotTestCase < MiniTest::Unit::TestCase
def test_fail
#assert false
end
end
48 changes: 48 additions & 0 deletions test/new_relic/agent/vm_test.rb
@@ -0,0 +1,48 @@
# encoding: utf-8
# This file is distributed under New Relic's license terms.
# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.

require File.expand_path(File.join(File.dirname(__FILE__),'..','..','test_helper'))
require 'new_relic/agent/vm'

class NewRelic::Agent::VMTestCase < MiniTest::Unit::TestCase
attr_reader :vm, :snapshot

def setup
@vm = NewRelic::Agent::VM.vm
@snapshot = NewRelic::Agent::VM.snapshot
end

def test_gets_snapshot
refute_nil snapshot
end

EXPECTED_SNAPSHOT_VALUES = [
:gc_runs,
:total_allocated_object,
:major_gc_count,
:minor_gc_count,
:heap_live,
:heap_free,
:method_cache_invalidations,
:constant_cache_invalidations,
:thread_count
]

EXPECTED_SNAPSHOT_VALUES.each do |val|
define_method("test_snapshot_has_#{val}") do
assert_correct_value_for(val)
end
end

# A VM snapshot will either support a value and have something non-nil,
# or it will not support it in which case the method exists but must be nil!
def assert_correct_value_for(meth)
if vm.supports?(meth)
refute_nil snapshot.send(meth)
else
assert_nil snapshot.send(meth)
end
end
end

0 comments on commit dcaa09d

Please sign in to comment.