skylight 0.0.2

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.
@@ -0,0 +1,20 @@
1
+ # Direwolf Agent
2
+
3
+ Collect instrumentation from a rails application and send it off to the
4
+ direwolf servers.
5
+
6
+ ## Traces
7
+
8
+ An conceptual overview of a trace.
9
+
10
+ * Trace ID (UUID)
11
+ * Tiers
12
+ * Name
13
+ * Spans (max 256 unique per tier)
14
+ * Category (multi-level, example: cache.redis)
15
+ * Description
16
+ * Annotations
17
+
18
+ ## TODO
19
+
20
+ * Cap the max number of spans per trace (2048k?)
@@ -0,0 +1,33 @@
1
+ require 'thread'
2
+ require 'socket'
3
+ require 'openssl'
4
+ require 'net/http'
5
+ require 'active_support/notifications'
6
+
7
+ module Skylight
8
+ class Error < RuntimeError; end
9
+
10
+ # First require all util files
11
+ require 'skylight/util/atomic'
12
+ require 'skylight/util/bytes'
13
+ require 'skylight/util/clock'
14
+ require 'skylight/util/ewma'
15
+ require 'skylight/util/gzip'
16
+ require 'skylight/util/queue'
17
+ require 'skylight/util/uniform_sample'
18
+ require 'skylight/util/uuid'
19
+
20
+ # Then require the rest
21
+ require 'skylight/compat'
22
+ require 'skylight/config'
23
+ require 'skylight/instrumenter'
24
+ require 'skylight/middleware'
25
+ require 'skylight/json_proto'
26
+ require 'skylight/subscriber'
27
+ require 'skylight/trace'
28
+ require 'skylight/worker'
29
+ end
30
+
31
+ if defined?(Rails)
32
+ require 'skylight/railtie'
33
+ end
@@ -0,0 +1,9 @@
1
+ module Skylight
2
+ # Catch-up out of date software
3
+ #
4
+ # TODO: Add smarter detection
5
+ if true
6
+ require 'skylight/compat/notifications'
7
+ end
8
+
9
+ end
@@ -0,0 +1,175 @@
1
+ require 'skylight/compat/notifications/instrumenter'
2
+ require 'skylight/compat/notifications/fanout'
3
+
4
+ module ActiveSupport
5
+ # = Notifications
6
+ #
7
+ # <tt>ActiveSupport::Notifications</tt> provides an instrumentation API for Ruby.
8
+ #
9
+ # == Instrumenters
10
+ #
11
+ # To instrument an event you just need to do:
12
+ #
13
+ # ActiveSupport::Notifications.instrument("render", :extra => :information) do
14
+ # render :text => "Foo"
15
+ # end
16
+ #
17
+ # That executes the block first and notifies all subscribers once done.
18
+ #
19
+ # In the example above "render" is the name of the event, and the rest is called
20
+ # the _payload_. The payload is a mechanism that allows instrumenters to pass
21
+ # extra information to subscribers. Payloads consist of a hash whose contents
22
+ # are arbitrary and generally depend on the event.
23
+ #
24
+ # == Subscribers
25
+ #
26
+ # You can consume those events and the information they provide by registering
27
+ # a subscriber. For instance, let's store all "render" events in an array:
28
+ #
29
+ # events = []
30
+ #
31
+ # ActiveSupport::Notifications.subscribe("render") do |*args|
32
+ # events << ActiveSupport::Notifications::Event.new(*args)
33
+ # end
34
+ #
35
+ # That code returns right away, you are just subscribing to "render" events.
36
+ # The block is saved and will be called whenever someone instruments "render":
37
+ #
38
+ # ActiveSupport::Notifications.instrument("render", :extra => :information) do
39
+ # render :text => "Foo"
40
+ # end
41
+ #
42
+ # event = events.first
43
+ # event.name # => "render"
44
+ # event.duration # => 10 (in milliseconds)
45
+ # event.payload # => { :extra => :information }
46
+ #
47
+ # The block in the <tt>subscribe</tt> call gets the name of the event, start
48
+ # timestamp, end timestamp, a string with a unique identifier for that event
49
+ # (something like "535801666f04d0298cd6"), and a hash with the payload, in
50
+ # that order.
51
+ #
52
+ # If an exception happens during that particular instrumentation the payload will
53
+ # have a key <tt>:exception</tt> with an array of two elements as value: a string with
54
+ # the name of the exception class, and the exception message.
55
+ #
56
+ # As the previous example depicts, the class <tt>ActiveSupport::Notifications::Event</tt>
57
+ # is able to take the arguments as they come and provide an object-oriented
58
+ # interface to that data.
59
+ #
60
+ # It is also possible to pass an object as the second parameter passed to the
61
+ # <tt>subscribe</tt> method instead of a block:
62
+ #
63
+ # module ActionController
64
+ # class PageRequest
65
+ # def call(name, started, finished, unique_id, payload)
66
+ # Rails.logger.debug ["notification:", name, started, finished, unique_id, payload].join(" ")
67
+ # end
68
+ # end
69
+ # end
70
+ #
71
+ # ActiveSupport::Notifications.subscribe('process_action.action_controller', ActionController::PageRequest.new)
72
+ #
73
+ # resulting in the following output within the logs including a hash with the payload:
74
+ #
75
+ # notification: process_action.action_controller 2012-04-13 01:08:35 +0300 2012-04-13 01:08:35 +0300 af358ed7fab884532ec7 {
76
+ # :controller=>"Devise::SessionsController",
77
+ # :action=>"new",
78
+ # :params=>{"action"=>"new", "controller"=>"devise/sessions"},
79
+ # :format=>:html,
80
+ # :method=>"GET",
81
+ # :path=>"/login/sign_in",
82
+ # :status=>200,
83
+ # :view_runtime=>279.3080806732178,
84
+ # :db_runtime=>40.053
85
+ # }
86
+ #
87
+ # You can also subscribe to all events whose name matches a certain regexp:
88
+ #
89
+ # ActiveSupport::Notifications.subscribe(/render/) do |*args|
90
+ # ...
91
+ # end
92
+ #
93
+ # and even pass no argument to <tt>subscribe</tt>, in which case you are subscribing
94
+ # to all events.
95
+ #
96
+ # == Temporary Subscriptions
97
+ #
98
+ # Sometimes you do not want to subscribe to an event for the entire life of
99
+ # the application. There are two ways to unsubscribe.
100
+ #
101
+ # WARNING: The instrumentation framework is designed for long-running subscribers,
102
+ # use this feature sparingly because it wipes some internal caches and that has
103
+ # a negative impact on performance.
104
+ #
105
+ # === Subscribe While a Block Runs
106
+ #
107
+ # You can subscribe to some event temporarily while some block runs. For
108
+ # example, in
109
+ #
110
+ # callback = lambda {|*args| ... }
111
+ # ActiveSupport::Notifications.subscribed(callback, "sql.active_record") do
112
+ # ...
113
+ # end
114
+ #
115
+ # the callback will be called for all "sql.active_record" events instrumented
116
+ # during the execution of the block. The callback is unsubscribed automatically
117
+ # after that.
118
+ #
119
+ # === Manual Unsubscription
120
+ #
121
+ # The +subscribe+ method returns a subscriber object:
122
+ #
123
+ # subscriber = ActiveSupport::Notifications.subscribe("render") do |*args|
124
+ # ...
125
+ # end
126
+ #
127
+ # To prevent that block from being called anymore, just unsubscribe passing
128
+ # that reference:
129
+ #
130
+ # ActiveSupport::Notifications.unsubscribe(subscriber)
131
+ #
132
+ # == Default Queue
133
+ #
134
+ # Notifications ships with a queue implementation that consumes and publish events
135
+ # to log subscribers in a thread. You can use any queue implementation you want.
136
+ #
137
+ module Notifications
138
+ class << self
139
+ attr_accessor :notifier
140
+
141
+ def publish(name, *args)
142
+ notifier.publish(name, *args)
143
+ end
144
+
145
+ def instrument(name, payload = {}, &blk)
146
+ if notifier.listening?(name)
147
+ instrumenter.instrument(name, payload, &blk)
148
+ else
149
+ yield payload if block_given?
150
+ end
151
+ end
152
+
153
+ def subscribe(*args, &block)
154
+ notifier.subscribe(*args, &block)
155
+ end
156
+
157
+ def subscribed(callback, *args, &block)
158
+ subscriber = subscribe(*args, &callback)
159
+ yield
160
+ ensure
161
+ unsubscribe(subscriber)
162
+ end
163
+
164
+ def unsubscribe(args)
165
+ notifier.unsubscribe(args)
166
+ end
167
+
168
+ def instrumenter
169
+ Thread.current[:"instrumentation_#{notifier.object_id}"] ||= Instrumenter.new(notifier)
170
+ end
171
+ end
172
+
173
+ self.notifier = Fanout.new
174
+ end
175
+ end
@@ -0,0 +1,166 @@
1
+ require 'mutex_m'
2
+
3
+ module ActiveSupport
4
+ module Notifications
5
+ # This is a default queue implementation that ships with Notifications.
6
+ # It just pushes events to all registered log subscribers.
7
+ #
8
+ # This class is thread safe. All methods are reentrant.
9
+ class Fanout
10
+ include Mutex_m
11
+
12
+ def initialize
13
+ @subscribers = []
14
+ @listeners_for = {}
15
+ super
16
+ end
17
+
18
+ def subscribe(pattern = nil, block = Proc.new)
19
+ subscriber = Subscribers.new pattern, block
20
+ synchronize do
21
+ @subscribers << subscriber
22
+ @listeners_for.clear
23
+ end
24
+ subscriber
25
+ end
26
+
27
+ def unsubscribe(subscriber)
28
+ synchronize do
29
+ @subscribers.reject! { |s| s.matches?(subscriber) }
30
+ @listeners_for.clear
31
+ end
32
+ end
33
+
34
+ def start(name, id, payload)
35
+ listeners_for(name).each { |s| s.start(name, id, payload) }
36
+ end
37
+
38
+ def finish(name, id, payload)
39
+ listeners_for(name).each { |s| s.finish(name, id, payload) }
40
+ end
41
+
42
+ def measure(name, id, payload)
43
+ listeners_for(name).each { |s| s.measure(name, id, payload) }
44
+ end
45
+
46
+ def publish(name, *args)
47
+ listeners_for(name).each { |s| s.publish(name, *args) }
48
+ end
49
+
50
+ def listeners_for(name)
51
+ synchronize do
52
+ @listeners_for[name] ||= @subscribers.select { |s| s.subscribed_to?(name) }
53
+ end
54
+ end
55
+
56
+ def listening?(name)
57
+ listeners_for(name).any?
58
+ end
59
+
60
+ # This is a sync queue, so there is no waiting.
61
+ def wait
62
+ end
63
+
64
+ module Subscribers # :nodoc:
65
+ def self.new(pattern, listener)
66
+ if listener.respond_to?(:call)
67
+ subscriber = Timed.new pattern, listener
68
+ elsif listener.respond_to?(:measure)
69
+ subscriber = Evented.new pattern, listener
70
+ else
71
+ subscriber = LegacyEvented.new pattern, listener
72
+ end
73
+
74
+ unless pattern
75
+ AllMessages.new(subscriber)
76
+ else
77
+ subscriber
78
+ end
79
+ end
80
+
81
+ class Evented #:nodoc:
82
+ def initialize(pattern, delegate)
83
+ @pattern = pattern
84
+ @delegate = delegate
85
+ end
86
+
87
+ def start(name, id, payload)
88
+ @delegate.start name, id, payload
89
+ end
90
+
91
+ def finish(name, id, payload)
92
+ @delegate.finish name, id, payload
93
+ end
94
+
95
+ def measure(name, id, payload)
96
+ @delegate.measure(name, id, payload)
97
+ end
98
+
99
+ def subscribed_to?(name)
100
+ @pattern === name.to_s
101
+ end
102
+
103
+ def matches?(subscriber_or_name)
104
+ self === subscriber_or_name ||
105
+ @pattern && @pattern === subscriber_or_name
106
+ end
107
+ end
108
+
109
+ class LegacyEvented < Evented
110
+ def measure(name, id, payload)
111
+ start(name, id, payload)
112
+ finish(name, id, payload)
113
+ end
114
+ end
115
+
116
+ class Timed < LegacyEvented
117
+ def initialize(pattern, delegate)
118
+ @timestack = []
119
+ super
120
+ end
121
+
122
+ def publish(name, *args)
123
+ @delegate.call name, *args
124
+ end
125
+
126
+ def start(name, id, payload)
127
+ @timestack.push Time.now
128
+ end
129
+
130
+ def finish(name, id, payload)
131
+ started = @timestack.pop
132
+ @delegate.call(name, started, Time.now, id, payload)
133
+ end
134
+ end
135
+
136
+ class AllMessages # :nodoc:
137
+ def initialize(delegate)
138
+ @delegate = delegate
139
+ end
140
+
141
+ def start(name, id, payload)
142
+ @delegate.start name, id, payload
143
+ end
144
+
145
+ def finish(name, id, payload)
146
+ @delegate.finish name, id, payload
147
+ end
148
+
149
+ def measure(name, id, payload)
150
+ @delegate.measure name, id, payload
151
+ end
152
+
153
+ def publish(name, *args)
154
+ @delegate.publish name, *args
155
+ end
156
+
157
+ def subscribed_to?(name)
158
+ true
159
+ end
160
+
161
+ alias :matches? :===
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,65 @@
1
+ require 'securerandom'
2
+
3
+ module ActiveSupport
4
+ module Notifications
5
+ # Instrumentors are stored in a thread local.
6
+ class Instrumenter
7
+ attr_reader :id
8
+
9
+ def initialize(notifier)
10
+ @id = unique_id
11
+ @notifier = notifier
12
+ end
13
+
14
+ # Instrument the given block by measuring the time taken to execute it
15
+ # and publish it. Notice that events get sent even if an error occurs
16
+ # in the passed-in block
17
+ def instrument(name, payload={})
18
+ if block_given?
19
+ @notifier.start(name, @id, payload)
20
+ begin
21
+ yield payload
22
+ rescue Exception => e
23
+ payload[:exception] = [e.class.name, e.message]
24
+ raise e
25
+ ensure
26
+ @notifier.finish(name, @id, payload)
27
+ end
28
+ else
29
+ @notifier.measure(name, @id, payload)
30
+ end
31
+ end
32
+
33
+ private
34
+ def unique_id
35
+ SecureRandom.hex(10)
36
+ end
37
+ end
38
+
39
+ class Event
40
+ attr_reader :name, :time, :transaction_id, :payload, :children
41
+ attr_accessor :end
42
+
43
+ def initialize(name, start, ending, transaction_id, payload)
44
+ @name = name
45
+ @payload = payload.dup
46
+ @time = start
47
+ @transaction_id = transaction_id
48
+ @end = ending
49
+ @children = []
50
+ end
51
+
52
+ def duration
53
+ 1000.0 * (self.end - time)
54
+ end
55
+
56
+ def <<(event)
57
+ @children << event
58
+ end
59
+
60
+ def parent_of?(event)
61
+ @children.include? event
62
+ end
63
+ end
64
+ end
65
+ end