skylight 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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