fiveruns-dash-ruby 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +68 -0
- data/Rakefile +39 -0
- data/lib/fiveruns/dash.rb +144 -0
- data/lib/fiveruns/dash/configuration.rb +116 -0
- data/lib/fiveruns/dash/exception_recorder.rb +135 -0
- data/lib/fiveruns/dash/host.rb +173 -0
- data/lib/fiveruns/dash/instrument.rb +128 -0
- data/lib/fiveruns/dash/metric.rb +379 -0
- data/lib/fiveruns/dash/recipe.rb +63 -0
- data/lib/fiveruns/dash/reporter.rb +208 -0
- data/lib/fiveruns/dash/scm.rb +126 -0
- data/lib/fiveruns/dash/session.rb +81 -0
- data/lib/fiveruns/dash/store/file.rb +24 -0
- data/lib/fiveruns/dash/store/http.rb +198 -0
- data/lib/fiveruns/dash/threads.rb +24 -0
- data/lib/fiveruns/dash/trace.rb +65 -0
- data/lib/fiveruns/dash/typable.rb +29 -0
- data/lib/fiveruns/dash/update.rb +215 -0
- data/lib/fiveruns/dash/version.rb +86 -0
- data/recipes/jruby.rb +107 -0
- data/recipes/ruby.rb +34 -0
- data/test/collector_communication_test.rb +260 -0
- data/test/configuration_test.rb +97 -0
- data/test/exception_recorder_test.rb +112 -0
- data/test/file_store_test.rb +56 -0
- data/test/fixtures/http_store_test/response.json +6 -0
- data/test/http_store_test.rb +210 -0
- data/test/metric_test.rb +204 -0
- data/test/recipe_test.rb +146 -0
- data/test/reliability_test.rb +60 -0
- data/test/reporter_test.rb +46 -0
- data/test/scm_test.rb +70 -0
- data/test/session_test.rb +49 -0
- data/test/test_helper.rb +96 -0
- data/test/tracing_test.rb +68 -0
- data/test/update_test.rb +42 -0
- data/version.yml +3 -0
- metadata +112 -0
@@ -0,0 +1,173 @@
|
|
1
|
+
module Fiveruns::Dash
|
2
|
+
|
3
|
+
class Host
|
4
|
+
|
5
|
+
UNIXES = [:osx, :linux, :solaris]
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
configure_host
|
9
|
+
end
|
10
|
+
|
11
|
+
def architecture
|
12
|
+
@architecture
|
13
|
+
end
|
14
|
+
|
15
|
+
def os_name
|
16
|
+
@os_name
|
17
|
+
end
|
18
|
+
|
19
|
+
def os_version
|
20
|
+
@os_version
|
21
|
+
end
|
22
|
+
|
23
|
+
def big_endian?
|
24
|
+
@big_endian
|
25
|
+
end
|
26
|
+
|
27
|
+
def little_endian?
|
28
|
+
!@big_endian
|
29
|
+
end
|
30
|
+
|
31
|
+
def ip_addresses
|
32
|
+
@ip_addresses
|
33
|
+
end
|
34
|
+
|
35
|
+
def os_name_match?(name)
|
36
|
+
platform == name
|
37
|
+
end
|
38
|
+
|
39
|
+
def platform
|
40
|
+
execute_on_osx { return :osx }
|
41
|
+
execute_on_windows { return :windows }
|
42
|
+
execute_on_linux { return :linux }
|
43
|
+
execute_on_solaris { return :solaris }
|
44
|
+
:unknown
|
45
|
+
end
|
46
|
+
|
47
|
+
def execute_on_unix(&block)
|
48
|
+
UNIXES.each do |unix|
|
49
|
+
send "execute_on_#{unix}", &block
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def execute_on_osx(&block)
|
54
|
+
block.call if RUBY_PLATFORM =~ /darwin/
|
55
|
+
end
|
56
|
+
|
57
|
+
def execute_on_linux(&block)
|
58
|
+
block.call if RUBY_PLATFORM =~ /linux/
|
59
|
+
end
|
60
|
+
|
61
|
+
def execute_on_solaris(&block)
|
62
|
+
block.call if RUBY_PLATFORM =~ /solaris/
|
63
|
+
end
|
64
|
+
|
65
|
+
def execute_on_windows(&block)
|
66
|
+
block.call if RUBY_PLATFORM =~ /win32|i386-mingw32/
|
67
|
+
end
|
68
|
+
|
69
|
+
def hostname
|
70
|
+
@hostname
|
71
|
+
end
|
72
|
+
|
73
|
+
def ip_address
|
74
|
+
address = ip_addresses[0]
|
75
|
+
address ? address[1] : "127.0.0.1"
|
76
|
+
end
|
77
|
+
|
78
|
+
def mac_address
|
79
|
+
@mac_address
|
80
|
+
end
|
81
|
+
|
82
|
+
def configure_host
|
83
|
+
@hostname ||= `hostname`.strip!
|
84
|
+
@big_endian = ([123].pack("s") == [123].pack("n"))
|
85
|
+
case RUBY_PLATFORM
|
86
|
+
when /darwin|linux/
|
87
|
+
begin # use Sys::Uname library if present
|
88
|
+
require 'sys/uname'
|
89
|
+
@os_name = Sys::Uname.sysname
|
90
|
+
@architecture = Sys::Uname.machine
|
91
|
+
@os_version = Sys::Uname.release
|
92
|
+
rescue LoadError # otherwise shell out and scrape out the information
|
93
|
+
@os_name = `uname -s`.strip
|
94
|
+
@architecture = `uname -p`.strip
|
95
|
+
@os_version = `uname -r`.strip
|
96
|
+
end
|
97
|
+
when /win32|i386-mingw32/
|
98
|
+
require "dl/win32"
|
99
|
+
getVersionEx = Win32API.new("kernel32", "GetVersionExA", ['P'], 'L')
|
100
|
+
|
101
|
+
lpVersionInfo = [148, 0, 0, 0, 0].pack("LLLLL") + "�0" * 128
|
102
|
+
getVersionEx.Call lpVersionInfo
|
103
|
+
|
104
|
+
dwOSVersionInfoSize, dwMajorVersion, dwMinorVersion, dwBuildNumber, dwPlatformId, szCSDVersion = lpVersionInfo.unpack("LLLLLC128")
|
105
|
+
@os_name = ['Windows 3.1/3.11', 'Windows 95/98', 'Windows NT/XP'][dwPlatformId]
|
106
|
+
@os_version = "#{dwMajorVersion}.#{dwMinorVersion}"
|
107
|
+
@architecture = ENV['PROCESSOR_ARCHITECTURE']
|
108
|
+
end
|
109
|
+
|
110
|
+
@ip_addresses = []
|
111
|
+
begin # use Sys::Host library if present
|
112
|
+
require 'sys/host'
|
113
|
+
Sys::Host.ip_addr.each do |ip|
|
114
|
+
addresses << ip
|
115
|
+
end
|
116
|
+
rescue LoadError # otherwise shell out and scrape out the information
|
117
|
+
execute_on_osx do
|
118
|
+
ifconfig = `ifconfig`
|
119
|
+
x = 0
|
120
|
+
while true
|
121
|
+
if ifconfig =~ /en#{x}:/
|
122
|
+
x+=1
|
123
|
+
else
|
124
|
+
break
|
125
|
+
end
|
126
|
+
end
|
127
|
+
x.times do |dev|
|
128
|
+
ifconfig = `ifconfig en#{dev}`
|
129
|
+
ifconfig.scan(/ether ([0-9a-f\:]*) /) do |mac_address|
|
130
|
+
@mac_address ||= mac_address[0]
|
131
|
+
end
|
132
|
+
ifconfig.scan(/inet ([0-9\.]*) /) { |ip| @ip_addresses << ["en#{dev}", ip[0]] }
|
133
|
+
end
|
134
|
+
end
|
135
|
+
execute_on_solaris do
|
136
|
+
arp = `/usr/sbin/arp -an`.split("\n")
|
137
|
+
re = /^(\w+).*?(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}).*((([a-f0-9]{2}):){5}[a-f0-9]{2})/
|
138
|
+
arp.find do |line|
|
139
|
+
line =~ re
|
140
|
+
end
|
141
|
+
@ip_addresses << $2
|
142
|
+
@mac_address = $3
|
143
|
+
end
|
144
|
+
execute_on_linux do
|
145
|
+
ifconfig = `/sbin/ifconfig`
|
146
|
+
x = 0
|
147
|
+
while true
|
148
|
+
if ifconfig =~ /eth#{x} /
|
149
|
+
x+=1
|
150
|
+
else
|
151
|
+
break
|
152
|
+
end
|
153
|
+
end
|
154
|
+
x.times do |dev|
|
155
|
+
ifconfig = `/sbin/ifconfig eth#{dev}`
|
156
|
+
ifconfig.scan(/HWaddr ([0-9A-Fa-f\:]*) /) do |mac_address|
|
157
|
+
@mac_address ||= mac_address[0]
|
158
|
+
end
|
159
|
+
ifconfig.scan(/inet addr:([0-9\.]*) /) { |ip| @ip_addresses << ["eth#{dev}", ip[0]] }
|
160
|
+
end
|
161
|
+
@mac_address ||= "#{@hostname}-UNKNOWN-MAC"
|
162
|
+
end
|
163
|
+
execute_on_windows do
|
164
|
+
addrs = Socket.getaddrinfo(Socket.gethostname, 80)
|
165
|
+
addrs.each do |addr|
|
166
|
+
@ip_addresses << ['eth0', addr[3]]
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module Fiveruns::Dash
|
2
|
+
|
3
|
+
module Instrument
|
4
|
+
|
5
|
+
class Error < ::NameError; end
|
6
|
+
|
7
|
+
def self.handlers
|
8
|
+
@handlers ||= []
|
9
|
+
end
|
10
|
+
|
11
|
+
# call-seq:
|
12
|
+
# Instrument.add("ClassName#instance_method", ...) { |instance, time, *args| ... }
|
13
|
+
# Instrument.add("ClassName::class_method", ...) { |klass, time, *args| ... }
|
14
|
+
# Instrument.add("ClassName.class_method", ...) { |klass, time, *args| ... }
|
15
|
+
#
|
16
|
+
# Add a handler to be called every time a method is invoked
|
17
|
+
def self.add(*raw_targets, &handler)
|
18
|
+
options = raw_targets.last.is_a?(Hash) ? raw_targets.pop : {}
|
19
|
+
raw_targets.each do |raw_target|
|
20
|
+
begin
|
21
|
+
obj, meth = case raw_target
|
22
|
+
when /^(.+)#(.+)$/
|
23
|
+
[$1.constantize, $2]
|
24
|
+
when /^(.+)(?:\.|::)(.+)$/
|
25
|
+
[(class << $1.constantize; self; end), $2]
|
26
|
+
else
|
27
|
+
raise Error, "Bad target format: #{raw_target}"
|
28
|
+
end
|
29
|
+
instrument(obj, meth, options, &handler)
|
30
|
+
rescue Fiveruns::Dash::Instrument::Error => em
|
31
|
+
raise em
|
32
|
+
rescue => e
|
33
|
+
Fiveruns::Dash.logger.error "Unable to instrument '#{raw_target}': #{e.message}"
|
34
|
+
Fiveruns::Dash.logger.error e.backtrace.join("\n\t")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.reentrant_timing(token, offset, this, args)
|
40
|
+
# token allows us to handle re-entrant timing, see e.g. ar_time
|
41
|
+
Thread.current[token] = 0 if Thread.current[token].nil?
|
42
|
+
Thread.current[token] = Thread.current[token] + 1
|
43
|
+
begin
|
44
|
+
start = Time.now
|
45
|
+
result = yield
|
46
|
+
ensure
|
47
|
+
time = Time.now - start
|
48
|
+
Thread.current[token] = Thread.current[token] - 1
|
49
|
+
if Thread.current[token] == 0
|
50
|
+
::Fiveruns::Dash::Instrument.handlers[offset].call(this, time, *args)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
result
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.timing(offset, this, args)
|
57
|
+
start = Time.now
|
58
|
+
begin
|
59
|
+
result = yield
|
60
|
+
ensure
|
61
|
+
time = Time.now - start
|
62
|
+
::Fiveruns::Dash::Instrument.handlers[offset].call(this, time, *args)
|
63
|
+
end
|
64
|
+
result
|
65
|
+
end
|
66
|
+
|
67
|
+
#######
|
68
|
+
private
|
69
|
+
#######
|
70
|
+
|
71
|
+
def self.instrument(obj, meth, options = {}, &handler)
|
72
|
+
handlers << handler unless handlers.include?(handler)
|
73
|
+
offset = handlers.size - 1
|
74
|
+
identifier = "instrument_#{handler.hash}"
|
75
|
+
code = wrapping meth, identifier do |without|
|
76
|
+
if options[:exceptions]
|
77
|
+
<<-EXCEPTIONS
|
78
|
+
begin
|
79
|
+
#{without}(*args, &block)
|
80
|
+
rescue Exception => _e
|
81
|
+
_sample = ::Fiveruns::Dash::Instrument.handlers[#{offset}].call(_e, self, *args)
|
82
|
+
::Fiveruns::Dash.session.add_exception(_e, _sample)
|
83
|
+
raise
|
84
|
+
end
|
85
|
+
EXCEPTIONS
|
86
|
+
elsif options[:reentrant_token]
|
87
|
+
<<-REENTRANT
|
88
|
+
::Fiveruns::Dash::Instrument.reentrant_timing(:id#{options[:reentrant_token]}, #{offset}, self, args) do
|
89
|
+
#{without}(*args, &block)
|
90
|
+
end
|
91
|
+
REENTRANT
|
92
|
+
else
|
93
|
+
<<-PERFORMANCE
|
94
|
+
::Fiveruns::Dash::Instrument.timing(#{offset}, self, args) do
|
95
|
+
#{without}(*args, &block)
|
96
|
+
end
|
97
|
+
PERFORMANCE
|
98
|
+
end
|
99
|
+
end
|
100
|
+
obj.module_eval code, __FILE__, __LINE__
|
101
|
+
identifier
|
102
|
+
rescue SyntaxError => e
|
103
|
+
puts "Syntax error (#{e.message})\n#{code}"
|
104
|
+
raise
|
105
|
+
rescue => e
|
106
|
+
raise Error, "Could not attach (#{e.message})"
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.wrapping(meth, feature)
|
110
|
+
format = meth =~ /^(.*?)(\?|!|=)$/ ? "#{$1}_%s_#{feature}#{$2}" : "#{meth}_%s_#{feature}"
|
111
|
+
<<-DYNAMIC
|
112
|
+
def #{format % :with}(*args, &block)
|
113
|
+
_trace = Thread.current[:trace]
|
114
|
+
if _trace
|
115
|
+
_trace.step do
|
116
|
+
#{yield(format % :without)}
|
117
|
+
end
|
118
|
+
else
|
119
|
+
#{yield(format % :without)}
|
120
|
+
end
|
121
|
+
end
|
122
|
+
alias_method_chain :#{meth}, :#{feature}
|
123
|
+
DYNAMIC
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
@@ -0,0 +1,379 @@
|
|
1
|
+
require 'dash/typable'
|
2
|
+
|
3
|
+
module Fiveruns::Dash
|
4
|
+
|
5
|
+
class Metric
|
6
|
+
include Typable
|
7
|
+
|
8
|
+
attr_reader :name, :description, :help_text, :options
|
9
|
+
attr_accessor :recipe
|
10
|
+
def initialize(name, *args, &block)
|
11
|
+
@@warned = false
|
12
|
+
@name = name.to_s
|
13
|
+
@options = args.extract_options!
|
14
|
+
@description = args.shift || @name.titleize
|
15
|
+
@help_text = args.shift
|
16
|
+
@operation = block
|
17
|
+
@virtual = !!options[:sources]
|
18
|
+
@abstract = options[:abstract]
|
19
|
+
validate!
|
20
|
+
end
|
21
|
+
|
22
|
+
# Indicates that this metric is calculated based on the value(s)
|
23
|
+
# of other metrics.
|
24
|
+
def virtual?
|
25
|
+
@virtual
|
26
|
+
end
|
27
|
+
|
28
|
+
# Indicates that this metric is only used for virtual calculations
|
29
|
+
# and should not be sent to the server for storage.
|
30
|
+
def abstract?
|
31
|
+
@abstract
|
32
|
+
end
|
33
|
+
|
34
|
+
def data
|
35
|
+
return nil if virtual?
|
36
|
+
value_hash.merge(key)
|
37
|
+
end
|
38
|
+
|
39
|
+
def calculate(real_data)
|
40
|
+
return nil unless virtual?
|
41
|
+
|
42
|
+
datas = options[:sources].map {|met_name| real_data.detect { |hash| hash[:name] == met_name } }.compact
|
43
|
+
|
44
|
+
if datas.size != options[:sources].size && options[:sources].include?('response_time')
|
45
|
+
Fiveruns::Dash.logger.warn(<<-LOG
|
46
|
+
ActiveRecord utilization metrics require a time metric so Dash can calculate a percentage of time spent in the database.
|
47
|
+
Please set the :ar_total_time option when configuring Dash:
|
48
|
+
|
49
|
+
# Define an application-specific metric cooresponding to the total processing time for this app.
|
50
|
+
Fiveruns::Dash.register_recipe :loader, :url => 'http://dash.fiveruns.com' do |recipe|
|
51
|
+
recipe.time :total_time, 'Load Time', :method => 'Loader::Engine#load'
|
52
|
+
end
|
53
|
+
|
54
|
+
# Pass the name of this custom metric to Dash so it will be used in the AR metric calculations.
|
55
|
+
Fiveruns::Dash.configure :app => token, :ar_total_time => 'total_time' do |config|
|
56
|
+
config.add_recipe :activerecord
|
57
|
+
config.add_recipe :loader, :url => 'http://dash.fiveruns.com'
|
58
|
+
end
|
59
|
+
LOG
|
60
|
+
) unless @@warned
|
61
|
+
@@warned = true
|
62
|
+
return nil
|
63
|
+
else
|
64
|
+
raise ArgumentError, "Could not find one or more of #{options[:sources].inspect} in #{real_data.map { |h| h[:name] }.inspect}" unless datas.size == options[:sources].size
|
65
|
+
end
|
66
|
+
|
67
|
+
combine(datas.map { |hsh| hsh[:values] }).merge(key)
|
68
|
+
end
|
69
|
+
|
70
|
+
def reset
|
71
|
+
# Abstract
|
72
|
+
end
|
73
|
+
|
74
|
+
def info
|
75
|
+
key
|
76
|
+
end
|
77
|
+
|
78
|
+
def key
|
79
|
+
@key ||= begin
|
80
|
+
{
|
81
|
+
:name => name,
|
82
|
+
:recipe_url => recipe ? recipe.url : nil,
|
83
|
+
:recipe_name => recipe ? recipe.name.to_s : nil,
|
84
|
+
:data_type => self.class.metric_type,
|
85
|
+
:description => description,
|
86
|
+
:help_text => help_text,
|
87
|
+
}.merge(optional_info)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def ==(other)
|
92
|
+
key == other.key
|
93
|
+
end
|
94
|
+
|
95
|
+
# Set context finder
|
96
|
+
def find_context_with(&block)
|
97
|
+
@context_finder = block
|
98
|
+
end
|
99
|
+
|
100
|
+
#######
|
101
|
+
private
|
102
|
+
#######
|
103
|
+
|
104
|
+
def validate!
|
105
|
+
raise ArgumentError, "#{name} - Virtual metrics should have source metrics" if virtual? && options[:sources].blank?
|
106
|
+
raise ArgumentError, "#{name} - metrics should not have source metrics" if !virtual? && options[:sources]
|
107
|
+
end
|
108
|
+
|
109
|
+
def optional_info
|
110
|
+
returning({}) do |optional|
|
111
|
+
copy = optional.merge(@options[:unit] ? {:unit => @options[:unit].to_s} : {})
|
112
|
+
copy = copy.merge(@options[:scope] ? {:scope => @options[:scope].to_s} : {})
|
113
|
+
copy = copy.merge(abstract? ? {:abstract => true} : {})
|
114
|
+
optional.merge!(copy)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def combine(source_values)
|
119
|
+
# Get the intersection of contexts for all the source metrics.
|
120
|
+
# We combine the values for all shared contexts.
|
121
|
+
contexts = source_values.map { |values| values.map { |value| value[:context] }}
|
122
|
+
intersection = nil
|
123
|
+
contexts.each_with_index do |arr, idx|
|
124
|
+
if idx == 0
|
125
|
+
intersection = arr
|
126
|
+
else
|
127
|
+
intersection = intersection & arr
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
values = intersection.map do |context|
|
132
|
+
args = source_values.map do |values|
|
133
|
+
values.detect { |value| value[:context] == context }[:value]
|
134
|
+
end
|
135
|
+
|
136
|
+
{ :value => @operation.call(*args), :context => context }
|
137
|
+
end
|
138
|
+
|
139
|
+
{:values => values}
|
140
|
+
end
|
141
|
+
|
142
|
+
def value_hash
|
143
|
+
current_value = ::Fiveruns::Dash.sync { @operation.call }
|
144
|
+
{:values => parse_value(current_value)}
|
145
|
+
end
|
146
|
+
|
147
|
+
# Verifies value matches one of the following patterns:
|
148
|
+
# * A numeric value (indicates no namespace)
|
149
|
+
# * A hash of [namespace_kind, namespace_name, ...] => value pairs, eg:
|
150
|
+
# [:controller, 'FooController', :action, 'bar'] => 12
|
151
|
+
def parse_value(value)
|
152
|
+
case value
|
153
|
+
when Numeric
|
154
|
+
[{:context => [], :value => value}]
|
155
|
+
when Hash
|
156
|
+
value.inject([]) do |all, (key, val)|
|
157
|
+
case key
|
158
|
+
when nil
|
159
|
+
all.push :context => [], :value => val
|
160
|
+
when Array
|
161
|
+
if key.size % 2 == 0
|
162
|
+
all.push :context => key, :value => val
|
163
|
+
else
|
164
|
+
bad_value! "Contexts must have an even number of items"
|
165
|
+
end
|
166
|
+
else
|
167
|
+
bad_value! "Unknown context type"
|
168
|
+
end
|
169
|
+
all
|
170
|
+
end
|
171
|
+
else
|
172
|
+
bad_value! "Unknown value type"
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def bad_value!(message)
|
177
|
+
raise ArgumentError, "Bad data for `#{@name}' #{self.class.metric_type} metric: #{message}"
|
178
|
+
end
|
179
|
+
|
180
|
+
# Note: only to be used when the +@operation+
|
181
|
+
# block is used to set contexts
|
182
|
+
def find_containers(*args, &block) #:nodoc:
|
183
|
+
contexts = Array(current_context_for(*args))
|
184
|
+
if contexts.empty? || contexts == [[]]
|
185
|
+
contexts = [[]]
|
186
|
+
elsif contexts.all? { |item| !item.is_a?(Array) }
|
187
|
+
contexts = [contexts]
|
188
|
+
end
|
189
|
+
if Thread.current[:trace]
|
190
|
+
result = yield blank_data[[]]
|
191
|
+
Thread.current[:trace].add_data(self, contexts, result)
|
192
|
+
end
|
193
|
+
contexts.each do |context|
|
194
|
+
with_container_for_context(context, &block)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# Get the container for this context, allow modifications to it,
|
199
|
+
# and store it
|
200
|
+
# * Note: We sync here when looking up the container, while
|
201
|
+
# the block is being executed, and when it is stored
|
202
|
+
def with_container_for_context(context)
|
203
|
+
ctx = (context || []).dup # normalize nil context to empty
|
204
|
+
::Fiveruns::Dash.sync do
|
205
|
+
container = @data[ctx]
|
206
|
+
new_container = yield container
|
207
|
+
#Fiveruns::Dash.logger.info "#{name}/#{context.inspect}/#{new_container.inspect}"
|
208
|
+
@data[ctx] = new_container # For hash defaults
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def context_finder
|
213
|
+
@context_finder ||= begin
|
214
|
+
context_setting = @options[:context] || @options[:contexts]
|
215
|
+
context_setting.is_a?(Proc) ? context_setting : lambda { |*args| Array(context_setting) }
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
# Retrieve the context for the given arguments
|
220
|
+
# * Note: We need to sync here (and wherever the context is modified)
|
221
|
+
def current_context_for(*args)
|
222
|
+
::Fiveruns::Dash.sync { context_finder.call(*args) }
|
223
|
+
end
|
224
|
+
|
225
|
+
end
|
226
|
+
|
227
|
+
class TimeMetric < Metric
|
228
|
+
|
229
|
+
def initialize(*args)
|
230
|
+
super(*args)
|
231
|
+
reset
|
232
|
+
install_hook
|
233
|
+
end
|
234
|
+
|
235
|
+
def reset
|
236
|
+
::Fiveruns::Dash.sync do
|
237
|
+
@data = blank_data
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
#######
|
242
|
+
private
|
243
|
+
#######
|
244
|
+
|
245
|
+
def blank_data
|
246
|
+
Hash.new {{ :invocations => 0, :value => 0 }}
|
247
|
+
end
|
248
|
+
|
249
|
+
def value_hash
|
250
|
+
returning(:values => current_value) do
|
251
|
+
reset
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
def install_hook
|
256
|
+
@operation ||= lambda { nil }
|
257
|
+
methods_to_instrument.each do |meth|
|
258
|
+
Instrument.add meth, instrument_options do |obj, time, *args|
|
259
|
+
find_containers(obj, *args) do |container|
|
260
|
+
container[:invocations] += 1
|
261
|
+
container[:value] += time
|
262
|
+
container
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
def instrument_options
|
269
|
+
returning({}) do |options|
|
270
|
+
options[:reentrant_token] = self.object_id if @options[:reentrant]
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def methods_to_instrument
|
275
|
+
@methods_to_instrument ||= Array(@options[:method]) + Array(@options[:methods])
|
276
|
+
end
|
277
|
+
|
278
|
+
def validate!
|
279
|
+
super
|
280
|
+
raise ArgumentError, "Can not set :unit for `#{@name}' time metric" if @options[:unit]
|
281
|
+
if methods_to_instrument.blank?
|
282
|
+
raise ArgumentError, "Must set :method or :methods option for `#{@name}` time metric"
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
# Get the current value
|
287
|
+
# * Note: We sync here (and wherever @data is being written)
|
288
|
+
def current_value
|
289
|
+
::Fiveruns::Dash.sync do
|
290
|
+
@data.inject([]) do |all, (context, data)|
|
291
|
+
all.push(data.merge(:context => context))
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
end
|
297
|
+
|
298
|
+
class CounterMetric < Metric
|
299
|
+
|
300
|
+
def initialize(*args)
|
301
|
+
super(*args)
|
302
|
+
if incrementing_methods.any?
|
303
|
+
reset
|
304
|
+
install_hook
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
def value_hash
|
309
|
+
if incrementing_methods.any?
|
310
|
+
returning(:values => current_value) do
|
311
|
+
reset
|
312
|
+
end
|
313
|
+
else
|
314
|
+
super
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
def install_hook
|
319
|
+
if incrementing_methods.blank?
|
320
|
+
raise RuntimeError, "Bad configuration for `#{@name}` counter metric"
|
321
|
+
end
|
322
|
+
@operation ||= lambda { nil }
|
323
|
+
incrementing_methods.each do |meth|
|
324
|
+
Instrument.add meth do |obj, time, *args|
|
325
|
+
find_containers(obj, *args) do |container|
|
326
|
+
container += 1
|
327
|
+
container
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
# Reset the current value
|
334
|
+
# * Note: We sync here (and wherever @data is being written)
|
335
|
+
def reset
|
336
|
+
::Fiveruns::Dash.sync { @data = blank_data }
|
337
|
+
end
|
338
|
+
|
339
|
+
def blank_data
|
340
|
+
Hash.new(0)
|
341
|
+
end
|
342
|
+
|
343
|
+
def incrementing_methods
|
344
|
+
@incrementing_methods ||= Array(@options[:incremented_by])
|
345
|
+
end
|
346
|
+
|
347
|
+
def validate!
|
348
|
+
super
|
349
|
+
if !@options[:incremented_by]
|
350
|
+
raise ArgumentError, "No block given to capture counter `#{@name}'" unless @operation
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
# Get the current value
|
355
|
+
# * Note: We sync here (and wherever @data is being written)
|
356
|
+
def current_value
|
357
|
+
result = ::Fiveruns::Dash.sync do
|
358
|
+
# Ensure the empty context is stored with a default of 0
|
359
|
+
@data[[]] = @data.fetch([], 0)
|
360
|
+
@data
|
361
|
+
end
|
362
|
+
parse_value result
|
363
|
+
end
|
364
|
+
|
365
|
+
end
|
366
|
+
|
367
|
+
class PercentageMetric < Metric
|
368
|
+
|
369
|
+
def validate!
|
370
|
+
super
|
371
|
+
raise ArgumentError, "Can not set :unit for `#{@name}' percentage metric" if @options[:unit]
|
372
|
+
end
|
373
|
+
|
374
|
+
end
|
375
|
+
|
376
|
+
class AbsoluteMetric < Metric
|
377
|
+
end
|
378
|
+
|
379
|
+
end
|