fiveruns-dash-ruby 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,63 @@
1
+ module Fiveruns::Dash
2
+
3
+ class Recipe
4
+
5
+ class ConfigurationError < ::ArgumentError; end
6
+
7
+ def self.scope_stack
8
+ @scope_stack ||= []
9
+ end
10
+
11
+ def self.current
12
+ scope_stack.last
13
+ end
14
+
15
+ def self.in_scope(recipe)
16
+ scope_stack << recipe
17
+ yield
18
+ scope_stack.pop
19
+ end
20
+
21
+ attr_reader :name, :url, :options
22
+ def initialize(name, options = {}, &block)
23
+ @name = name
24
+ @options = options
25
+ @url = options[:url]
26
+ @block = block
27
+ validate!
28
+ end
29
+
30
+ def add_to(configuration)
31
+ self.class.in_scope self do
32
+ @block.call(configuration)
33
+ end
34
+ end
35
+
36
+ def matches?(criteria)
37
+ criteria.all? { |k, v| options[k] == v }
38
+ end
39
+
40
+ def ==(other)
41
+ name == other.name && other.options == options
42
+ end
43
+
44
+ def info
45
+ {
46
+ :name => name.to_s,
47
+ :url => url.to_s
48
+ }
49
+ end
50
+
51
+ #######
52
+ private
53
+ #######
54
+
55
+ def validate!
56
+ unless @url
57
+ raise ConfigurationError, "Recipe requires :url option"
58
+ end
59
+ end
60
+
61
+ end
62
+
63
+ end
@@ -0,0 +1,208 @@
1
+ require 'thread'
2
+
3
+ module Fiveruns::Dash
4
+
5
+ class ShutdownSignal < ::Exception; end
6
+
7
+ class Reporter
8
+
9
+ attr_accessor :interval
10
+ attr_reader :started_at
11
+ def initialize(session, interval = 60.seconds.to_i)
12
+ @session = session
13
+ @interval = interval
14
+ end
15
+
16
+ def revive!
17
+ return if !started? || foreground?
18
+ start if !@thread || !@thread.alive?
19
+ end
20
+
21
+ def alive?
22
+ @thread && @thread.alive? && started?
23
+ end
24
+
25
+ def start(run_in_background = true)
26
+ restarted = @started_at ? true : false
27
+ unless defined?(@started_at)
28
+ @started_at = ::Fiveruns::Dash::START_TIME
29
+ end
30
+ setup_for run_in_background
31
+ if @background
32
+ @thread = Thread.new { run(restarted) }
33
+ else
34
+ # Will it be run in foreground?
35
+ run(restarted)
36
+ end
37
+ end
38
+
39
+ def started?
40
+ @started_at
41
+ end
42
+
43
+ def foreground?
44
+ started? && !@background
45
+ end
46
+
47
+ def background?
48
+ started? && @background
49
+ end
50
+
51
+ def send_trace(trace)
52
+ if trace.data
53
+ payload = TracePayload.new(trace)
54
+ Fiveruns::Dash.logger.debug "Sending trace: #{payload.to_json}"
55
+ Thread.new { Update.new(payload).store(*update_locations) }
56
+ else
57
+ Fiveruns::Dash.logger.debug "No trace to send"
58
+ end
59
+ end
60
+
61
+ def ping
62
+ payload = PingPayload.new(@session.info, @started_at)
63
+ Update.new(payload).ping(*update_locations)
64
+ end
65
+
66
+ def stop
67
+ @thread && @thread.alive? && @thread.raise(ShutdownSignal.new)
68
+ end
69
+
70
+ #######
71
+ private
72
+ #######
73
+
74
+ TRAPS = {}
75
+
76
+ def install_signals
77
+ %w(INT TERM).each do |sym|
78
+ TRAPS[sym] = Signal.trap(sym) do
79
+ stop
80
+ TRAPS[sym].call if TRAPS[sym]
81
+ end
82
+ end
83
+ end
84
+
85
+ def run(restarted)
86
+ Fiveruns::Dash.logger.info "Starting reporter thread; endpoints are #{update_locations.inspect}"
87
+
88
+ install_signals
89
+ error_barrier do
90
+ total_time = 0
91
+ loop do
92
+ # account for the amount of time it took to upload, and adjust the sleep time accordingly
93
+ total_time += time_for { send_info_update }
94
+ rest(@interval - total_time)
95
+ total_time = 0
96
+ total_time += time_for do
97
+ send_data_update
98
+ send_exceptions_update
99
+ end
100
+ end
101
+ end
102
+ end
103
+
104
+ def error_barrier
105
+ begin
106
+ yield
107
+ rescue Fiveruns::Dash::ShutdownSignal => me
108
+ return
109
+ rescue Exception => e
110
+ Fiveruns::Dash.logger.error "#{e.class.name}: #{e.message}"
111
+ Fiveruns::Dash.logger.error e.backtrace.join("\n\t")
112
+ retry
113
+ end
114
+ end
115
+
116
+ def rest(amount)
117
+ amount > 0 ? sleep(amount) : nil
118
+ end
119
+
120
+ def time_for(&block)
121
+ a = Time.now
122
+ block.call
123
+ Time.now - a
124
+ end
125
+
126
+ def setup_for(run_in_background = true)
127
+ @background = run_in_background
128
+ end
129
+
130
+ def send_info_update
131
+ @info_update_sent ||= begin
132
+ payload = InfoPayload.new(@session.info, @started_at)
133
+ Fiveruns::Dash.logger.debug "Sending info: #{payload.to_json}"
134
+ result = Update.new(payload).store(*update_locations)
135
+ send_fake_info(payload)
136
+ result
137
+ end
138
+ end
139
+
140
+ def send_exceptions_update
141
+ if @info_update_sent
142
+ data = @session.exception_data
143
+ if data.empty?
144
+ Fiveruns::Dash.logger.debug "No exceptions for this interval"
145
+ else
146
+ payload = ExceptionsPayload.new(data)
147
+ Fiveruns::Dash.logger.debug "Sending exceptions: #{payload.to_json}"
148
+ Update.new(payload).store(*update_locations)
149
+ end
150
+ else
151
+ # Discard data
152
+ @session.reset
153
+ Fiveruns::Dash.logger.warn "Discarding interval exceptions"
154
+ end
155
+ end
156
+
157
+ def send_data_update
158
+ if @info_update_sent
159
+ data = @session.data
160
+ payload = DataPayload.new(data)
161
+ Fiveruns::Dash.logger.debug "Sending data: #{payload.to_json}"
162
+ result = Update.new(payload).store(*update_locations)
163
+ send_fake_data(payload)
164
+ result
165
+ else
166
+ # Discard data
167
+ @session.reset
168
+ Fiveruns::Dash.logger.warn "Discarding interval data"
169
+ end
170
+ end
171
+
172
+ def update_locations
173
+ @update_locations ||= if ENV['DASH_UPDATE']
174
+ ENV['DASH_UPDATE'].strip.split(/\s*,\s*/)
175
+ else
176
+ default_update_locations
177
+ end
178
+ end
179
+
180
+ def send_fake_data(payload)
181
+ fake_host_count.times do |idx|
182
+ payload.params[:process_id] = Fiveruns::Dash.process_ids[idx+1]
183
+ Fiveruns::Dash.logger.debug "Sending data: #{payload.to_json}"
184
+ Update.new(payload).store(*update_locations)
185
+ end
186
+ end
187
+
188
+ def send_fake_info(payload)
189
+ host = payload.params[:hostname]
190
+ fake_host_count.times do |idx|
191
+ payload.params[:mac] += idx.to_s
192
+ payload.params[:hostname] = host + idx.to_s
193
+ Fiveruns::Dash.logger.debug "Sending info: #{payload.to_json}"
194
+ Update.new(payload).store(*update_locations)
195
+ end
196
+ end
197
+
198
+ def fake_host_count
199
+ ENV['DASH_FAKE_HOST_COUNT'].to_i
200
+ end
201
+
202
+ def default_update_locations
203
+ %w(https://dash-collector.fiveruns.com https://dash-collector02.fiveruns.com)
204
+ end
205
+
206
+ end
207
+
208
+ end
@@ -0,0 +1,126 @@
1
+ require 'date'
2
+ module Fiveruns::Dash
3
+ class SCM
4
+ include Typable
5
+
6
+ def self.matching(startpath)
7
+ scm_hash = {}
8
+ types.each do |name, klass|
9
+ if path = locate_upwards(startpath, ".#{name}")
10
+ Fiveruns::Dash.logger.info "SCM: Found #{name} in #{path}"
11
+ scm_hash[path] = klass
12
+ end
13
+ end
14
+ winning_path = best_match(scm_hash.keys)
15
+ return nil if winning_path.nil?
16
+ Fiveruns::Dash.logger.info "SCM: Using #{scm_hash[winning_path].name} in #{winning_path}"
17
+ begin
18
+ scm_hash[winning_path].new(winning_path)
19
+ rescue Exception => e
20
+ Fiveruns::Dash.logger.warn "WARNING: #{e.message}"
21
+ nil
22
+ end
23
+ end
24
+
25
+ def self.best_match( scm_paths )
26
+ scm_paths.max{|a,b| a.split("/").length <=> b.split("/").length}
27
+ end
28
+
29
+ def initialize(path)
30
+ @path = path
31
+ require_binding
32
+ end
33
+
34
+ def time
35
+ raise NotImplementedError, 'Abstract'
36
+ end
37
+
38
+ def revision
39
+ raise NotImplementedError, 'Abstract'
40
+ end
41
+
42
+ #######
43
+ private
44
+ #######
45
+
46
+ def self.locate_upwards(startpath, target)
47
+ return nil unless startpath
48
+ startpath = File.expand_path(startpath)
49
+ return startpath if File.exist?(File.join( startpath, target ))
50
+ return locate_upwards( File.dirname(startpath), target) unless File.dirname(startpath) == startpath
51
+ nil
52
+ end
53
+
54
+ def require_binding
55
+ end
56
+
57
+ end
58
+
59
+ class GitSCM < SCM
60
+
61
+ def revision
62
+ commit.sha
63
+ end
64
+
65
+ def time
66
+ commit.date
67
+ end
68
+
69
+ def url
70
+ @url ||= begin
71
+ origin = repo.remotes.detect { |r| r.name == 'origin' }
72
+ origin.url if origin
73
+ end
74
+ end
75
+
76
+ #######
77
+ private
78
+ #######
79
+
80
+ def commit
81
+ @commit ||= repo.object('HEAD')
82
+ end
83
+
84
+ def repo
85
+ @repo ||= Git.open(@path)
86
+ end
87
+
88
+ def require_binding
89
+ require 'git'
90
+ rescue LoadError
91
+ raise LoadError, "Dash deployment tracking for Git apps requires the 'git' gem"
92
+ end
93
+
94
+ end
95
+
96
+ class SvnSCM < SCM
97
+
98
+ def revision
99
+ @yaml['Last Changed Rev'] || @yaml['Revision']
100
+ end
101
+
102
+ def time
103
+ datestring = @yaml['Last Changed Date']
104
+ datestring.nil? ? nil : DateTime.parse(datestring.split("(").first.strip)
105
+ end
106
+
107
+ def url
108
+ @url ||= @yaml['URL']
109
+ end
110
+
111
+ #######
112
+ private
113
+ #######
114
+
115
+ def require_binding
116
+ @yaml = YAML.load(svn_info)
117
+ @yaml = {} unless Hash === @yaml
118
+ end
119
+
120
+ def svn_info
121
+ `svn info #{@path}`
122
+ end
123
+
124
+ end
125
+
126
+ end
@@ -0,0 +1,81 @@
1
+ module Fiveruns::Dash
2
+
3
+ class Session
4
+
5
+ attr_reader :configuration, :reporter
6
+ def initialize(configuration)
7
+ @configuration = configuration
8
+ # eager create the host data in the main thread
9
+ # as it is dangerous to load in the reporter thread
10
+ Fiveruns::Dash.host
11
+ end
12
+
13
+ def start(background = true, &block)
14
+ reporter.start(background, &block)
15
+ end
16
+
17
+ def exceptions
18
+ @exceptions ||= []
19
+ end
20
+
21
+ # Trace and send metric collection
22
+ def trace(name)
23
+ Thread.current[:trace] = ::Fiveruns::Dash::Trace.new(name)
24
+ result = yield
25
+ reporter.send_trace(Thread.current[:trace])
26
+ Thread.current[:trace] = nil
27
+ result
28
+ end
29
+
30
+ def add_exception(exception, sample=nil)
31
+ exception_recorder.record(exception, sample)
32
+ end
33
+
34
+ def info
35
+ {
36
+ :recipes => recipe_metadata,
37
+ :metric_infos => metric_metadata
38
+ }
39
+ end
40
+
41
+ def recipe_metadata
42
+ configuration.recipes.inject([]) do |recipes, recipe|
43
+ recipes << recipe.info
44
+ end
45
+ end
46
+
47
+ def metric_metadata
48
+ configuration.metrics.inject([]) do |metrics, metric|
49
+ metrics << metric.info
50
+ end
51
+ end
52
+
53
+ def reset
54
+ exception_recorder.reset
55
+ configuration.metrics.each(&:reset)
56
+ end
57
+
58
+ def data
59
+ real_data = configuration.metrics.map { |metric| metric.data }.compact
60
+ virtual_data = configuration.metrics.map { |metric| metric.calculate(real_data) }.compact
61
+ # Return any metrics which are not abstract and should be sent to the server
62
+ metric_payload = (real_data + virtual_data).find_all { |data| !data[:abstract] }
63
+ #puts "Sending #{metric_payload.map { |met| [met[:name], met[:values].size] }.inspect} metrics"
64
+ metric_payload
65
+ end
66
+
67
+ def exception_data
68
+ exception_recorder.data
69
+ end
70
+
71
+ def exception_recorder
72
+ @exception_recorder ||= ExceptionRecorder.new(self)
73
+ end
74
+
75
+ def reporter
76
+ @reporter ||= Reporter.new(self)
77
+ end
78
+
79
+ end
80
+
81
+ end