1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
|
module VagrantCloud
module Instrumentor
class Collection < Core
# @return [Set<Instrumentor::Core>]
attr_reader :instrumentors
# @return [Set<Callable>]
attr_reader :subscriptions
# Create a new instance
#
# @param [Array<Core>] instrumentors Instrumentors to add to collection
def initialize(instrumentors: [])
@lock = Mutex.new
@subscriptions = Set.new
@instrumentors = Set.new
# Add our default
@instrumentors << Logger.new
Array(instrumentors).each do |i|
if !i.is_a?(Core) && !i.respond_to?(:instrument)
raise TypeError, "Instrumentors must implement `#instrument`"
end
@instrumentors << i
end
@instrumentors.freeze
end
# Add a new instrumentor
#
# @param [Core] instrumentor New instrumentor to add
# @return [self]
def add(instrumentor)
@lock.synchronize do
if !instrumentor.is_a?(Core) && !instrumentor.respond_to?(:instrument)
raise TypeError, "Instrumentors must implement `#instrument`"
end
@instrumentors = (instrumentors + [instrumentor]).freeze
end
self
end
# Remove instrumentor
#
# @param [Core] instrumentor Remove instrumentor from collection
# @return [self]
def remove(instrumentor)
@lock.synchronize do
@instrumentors = instrumentors.dup.tap{|i| i.delete(instrumentor)}.freeze
end
self
end
# Add a subscription for events
#
# @param [Regexp, String] event Event to match
def subscribe(event, callable=nil, &block)
if callable && block
raise ArgumentError, "Callable argument or block expected, not both"
end
c = callable || block
if !c.respond_to?(:call)
raise TypeError, "Callable action is required for subscription"
end
entry = [event, c]
@lock.synchronize do
@subscriptions = (@subscriptions + [entry]).freeze
end
self
end
def unsubscribe(callable)
@lock.synchronize do
subscriptions = @subscriptions.dup
subscriptions.delete_if { |entry| entry.last == callable }
@subscriptions = subscriptions.freeze
end
self
end
# Call all instrumentors in collection with given parameters
def instrument(name, params = {})
# Log the start time
timing = {start_time: Time.now}
# Run the action
result = yield if block_given?
# Log the completion time and calculate duration
timing[:complete_time] = Time.now
timing[:duration] = timing[:complete_time] - timing[:start_time]
# Insert timing into params
params[:timing] = timing
# Call any instrumentors we know about
@lock.synchronize do
# Call our instrumentors first
instrumentors.each do |i|
i.instrument(name, params)
end
# Now call any matching subscriptions
subscriptions.each do |event, callable|
if event.is_a?(Regexp)
next if !event.match(name)
else
next if event != name
end
args = [name, params]
if callable.arity > -1
args = args[0, callable.arity]
end
callable.call(*args)
end
end
result
end
end
end
end
|