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,24 @@
|
|
1
|
+
module Fiveruns::Dash::Store
|
2
|
+
|
3
|
+
module File
|
4
|
+
|
5
|
+
def store_file(*uris)
|
6
|
+
uris.each do |uri|
|
7
|
+
directory = uri.path
|
8
|
+
write_to filename(directory)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def write_to(path)
|
13
|
+
::File.open(path, 'w') { |f| f.write @payload.to_json }
|
14
|
+
end
|
15
|
+
|
16
|
+
def filename(directory)
|
17
|
+
kind = payload.class.name =~ /(\w+)Payload/
|
18
|
+
name = kind ? $1 : 'unknown'
|
19
|
+
::File.join(directory, "#{guid}.#{name}.json")
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,198 @@
|
|
1
|
+
require 'net/https'
|
2
|
+
#require 'resolv'
|
3
|
+
require 'ostruct'
|
4
|
+
|
5
|
+
if defined?(Socket)
|
6
|
+
Socket.do_not_reverse_lookup=true
|
7
|
+
end
|
8
|
+
|
9
|
+
module Fiveruns::Dash::Store
|
10
|
+
|
11
|
+
module HTTP
|
12
|
+
|
13
|
+
def resolved_hostnames
|
14
|
+
Thread.current[:resolved_hostnames] ||= {}
|
15
|
+
Thread.current[:resolved_hostnames]
|
16
|
+
end
|
17
|
+
|
18
|
+
def resolved_hostname(hostname)
|
19
|
+
if resolved_hostnames[hostname] && Time.now < resolved_hostnames[hostname].next_update
|
20
|
+
ip = resolved_hostnames[hostname].ip
|
21
|
+
else
|
22
|
+
ip = hostname == 'localhost' ? '127.0.0.1' : IPSocket.getaddress(hostname)
|
23
|
+
ip_struct = OpenStruct.new(:ip => ip, :next_update => Time.now + 23.hours + rand(60).minutes)
|
24
|
+
resolved_hostnames[hostname] = ip_struct
|
25
|
+
end
|
26
|
+
ip
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
def store_http(*uris)
|
31
|
+
Fiveruns::Dash.logger.info "Attempting to send #{payload.class}"
|
32
|
+
if (uri = uris.detect { |u| transmit_to(add_path_to(u)) })
|
33
|
+
Fiveruns::Dash.logger.info "Sent #{payload.class} to #{uri}"
|
34
|
+
uri
|
35
|
+
else
|
36
|
+
Fiveruns::Dash.logger.warn "Could not send #{payload.class}"
|
37
|
+
false
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def transmit_to(uri)
|
42
|
+
response = nil
|
43
|
+
safely do
|
44
|
+
http = Net::HTTP.new(resolved_hostname(uri.host), uri.port)
|
45
|
+
http.use_ssl = true if uri.scheme == 'https'
|
46
|
+
http.open_timeout = 10
|
47
|
+
http.read_timeout = 10
|
48
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
49
|
+
extra_params = extra_params_for(payload)
|
50
|
+
multipart = Multipart.new(payload.io, payload.params.merge(extra_params))
|
51
|
+
response = http.post(uri.request_uri, multipart.to_s, "Content-Type" => multipart.content_type, "Host" => uri.host)
|
52
|
+
end
|
53
|
+
check_response_of response
|
54
|
+
end
|
55
|
+
|
56
|
+
def safely
|
57
|
+
yield
|
58
|
+
rescue Exception => e
|
59
|
+
Fiveruns::Dash.logger.error "Could not access Dash service: #{e.message}"
|
60
|
+
Fiveruns::Dash.logger.error e.backtrace.join("\n\t")
|
61
|
+
false
|
62
|
+
end
|
63
|
+
|
64
|
+
def check_response_of(response)
|
65
|
+
unless response
|
66
|
+
Fiveruns::Dash.logger.debug "Received no response from Dash service"
|
67
|
+
return false
|
68
|
+
end
|
69
|
+
case response.code.to_i
|
70
|
+
when 201
|
71
|
+
data = JSON.load(response.body)
|
72
|
+
set_trace_contexts(data)
|
73
|
+
true
|
74
|
+
when 400..499
|
75
|
+
Fiveruns::Dash.logger.warn "Could not access Dash service (#{response.code.to_i}, #{response.body.inspect})"
|
76
|
+
false
|
77
|
+
else
|
78
|
+
Fiveruns::Dash.logger.debug "Received unknown response from Dash service (#{response.inspect})"
|
79
|
+
false
|
80
|
+
end
|
81
|
+
rescue JSON::ParserError => e
|
82
|
+
puts response.body
|
83
|
+
Fiveruns::Dash.logger.error "Received non-JSON response (#{response.inspect})"
|
84
|
+
false
|
85
|
+
end
|
86
|
+
|
87
|
+
def set_trace_contexts(data)
|
88
|
+
trace_contexts = data['traces']
|
89
|
+
if trace_contexts.is_a?(Array)
|
90
|
+
Fiveruns::Dash.trace_contexts = trace_contexts
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def add_path_to(uri)
|
95
|
+
returning uri.dup do |new_uri|
|
96
|
+
path = case payload
|
97
|
+
when Fiveruns::Dash::PingPayload
|
98
|
+
::File.join('/apps', app_token, "ping")
|
99
|
+
when Fiveruns::Dash::InfoPayload
|
100
|
+
::File.join('/apps', app_token, "processes.json")
|
101
|
+
when Fiveruns::Dash::DataPayload
|
102
|
+
::File.join('/apps', app_token, "metrics.json")
|
103
|
+
when Fiveruns::Dash::TracePayload
|
104
|
+
::File.join('/apps', app_token, "traces.json")
|
105
|
+
when Fiveruns::Dash::ExceptionsPayload
|
106
|
+
::File.join('/apps', app_token, "exceptions.json")
|
107
|
+
else
|
108
|
+
raise ArgumentError, 'Unknown payload type: #{payload.class}'
|
109
|
+
end
|
110
|
+
new_uri.path = path
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def extra_params_for(payload)
|
115
|
+
case payload
|
116
|
+
when Fiveruns::Dash::ExceptionsPayload
|
117
|
+
{:app_id => app_token}
|
118
|
+
else
|
119
|
+
Hash.new
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def normalize_key(key)
|
124
|
+
key.to_a.flatten.map(&:to_s).sort
|
125
|
+
end
|
126
|
+
|
127
|
+
def app_token
|
128
|
+
::Fiveruns::Dash.configuration.options[:app]
|
129
|
+
end
|
130
|
+
|
131
|
+
class Multipart
|
132
|
+
|
133
|
+
BOUNDARY_ROOT = 'B0UND~F0R~UPL0AD'
|
134
|
+
|
135
|
+
attr_reader :file, :params
|
136
|
+
def initialize(file, params={})
|
137
|
+
@file = file
|
138
|
+
@params = params
|
139
|
+
end
|
140
|
+
|
141
|
+
def content_type
|
142
|
+
%(multipart/form-data, boundary="#{boundary}")
|
143
|
+
end
|
144
|
+
|
145
|
+
def to_s
|
146
|
+
%(#{parts}\r\n#{separator}--)
|
147
|
+
end
|
148
|
+
|
149
|
+
#######
|
150
|
+
private
|
151
|
+
#######
|
152
|
+
|
153
|
+
def boundary
|
154
|
+
"#{BOUNDARY_ROOT}*#{nonce}"
|
155
|
+
end
|
156
|
+
|
157
|
+
def parts
|
158
|
+
params.merge(:file => file).map do |name, value|
|
159
|
+
[
|
160
|
+
separator,
|
161
|
+
headers_for(name, value)
|
162
|
+
].flatten.join(crlf) + crlf + crlf + content_of(value)
|
163
|
+
end.flatten.join(crlf)
|
164
|
+
end
|
165
|
+
|
166
|
+
def separator
|
167
|
+
%(--#{boundary})
|
168
|
+
end
|
169
|
+
|
170
|
+
def crlf
|
171
|
+
@crlf ||= "\r\n"
|
172
|
+
end
|
173
|
+
|
174
|
+
def headers_for(name, value)
|
175
|
+
if value.respond_to?(:read)
|
176
|
+
[
|
177
|
+
%(Content-Disposition: form-data; name="#{name}"; filename="metrics.json.gz"),
|
178
|
+
%(Content-Transfer-Encoding: binary),
|
179
|
+
%(Content-Type: application/octet-stream)
|
180
|
+
]
|
181
|
+
else
|
182
|
+
[ %(Content-Disposition: form-data; name="#{name}") ]
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def nonce
|
187
|
+
@nonce ||= (Time.now.utc.to_f * 1000).to_i
|
188
|
+
end
|
189
|
+
|
190
|
+
def content_of(value)
|
191
|
+
value.respond_to?(:read) ? value.read : value.to_s
|
192
|
+
end
|
193
|
+
|
194
|
+
end
|
195
|
+
|
196
|
+
end
|
197
|
+
|
198
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module Fiveruns::Dash
|
4
|
+
|
5
|
+
module Threads
|
6
|
+
|
7
|
+
def self.included(base)
|
8
|
+
base.extend ClassMethods
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
|
13
|
+
def monitor
|
14
|
+
@monitor ||= Monitor.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def sync(&block)
|
18
|
+
monitor.synchronize(&block)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Fiveruns::Dash
|
2
|
+
|
3
|
+
class Trace
|
4
|
+
|
5
|
+
attr_reader :context, :data, :stack
|
6
|
+
def initialize(context)
|
7
|
+
@context = context
|
8
|
+
@stack = []
|
9
|
+
validate!
|
10
|
+
end
|
11
|
+
|
12
|
+
def step(&block)
|
13
|
+
s = Step.new
|
14
|
+
@stack.last.children << s if !@stack.empty?
|
15
|
+
@stack << s
|
16
|
+
result = yield
|
17
|
+
last_step = @stack.pop
|
18
|
+
@data = last_step if @stack.empty?
|
19
|
+
result
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_data(metric, contexts, value)
|
23
|
+
unless @stack.empty?
|
24
|
+
@stack.last.metrics.push(
|
25
|
+
metric.key.merge({:value => value, :contexts => contexts})
|
26
|
+
)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_json
|
31
|
+
{ :context => context,
|
32
|
+
:data => (@data || {})
|
33
|
+
}.to_json
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def validate!
|
39
|
+
unless @context.is_a?(Array) && @context.size % 2 == 0
|
40
|
+
raise ArgumentError, 'Invalid context: #{@context.inspect} (must be an array with an even number of elements)'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class Step
|
45
|
+
|
46
|
+
def metrics
|
47
|
+
@metrics ||= []
|
48
|
+
end
|
49
|
+
|
50
|
+
def children
|
51
|
+
@children ||= []
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_json
|
55
|
+
{
|
56
|
+
:metrics => metrics,
|
57
|
+
:children => children,
|
58
|
+
}.to_json
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Fiveruns::Dash
|
2
|
+
|
3
|
+
module Typable
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
name = base.name.demodulize.underscore
|
7
|
+
base.class_eval %{
|
8
|
+
def self.#{name}_type
|
9
|
+
@#{name}_type ||= name.demodulize.underscore.sub(/_#{name}$/, '').to_sym
|
10
|
+
end
|
11
|
+
}
|
12
|
+
base.extend ClassMethods
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
|
17
|
+
def inherited(klass)
|
18
|
+
types[klass.__send__("#{name.demodulize.underscore}_type")] = klass
|
19
|
+
end
|
20
|
+
|
21
|
+
def types
|
22
|
+
@types ||= {}
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,215 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
|
3
|
+
require 'dash/store/http'
|
4
|
+
require 'dash/store/file'
|
5
|
+
|
6
|
+
module Fiveruns::Dash
|
7
|
+
|
8
|
+
class Pinger
|
9
|
+
|
10
|
+
attr_reader :payload
|
11
|
+
def initialize(payload)
|
12
|
+
@payload = payload
|
13
|
+
end
|
14
|
+
|
15
|
+
def ping(*urls)
|
16
|
+
try_urls(urls) do |url|
|
17
|
+
send_ping(url, payload)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def send_ping(url, payload)
|
22
|
+
begin
|
23
|
+
http = Net::HTTP.new(url.host, url.port)
|
24
|
+
http.use_ssl = true if url.scheme == 'https'
|
25
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
26
|
+
multipart = Fiveruns::Dash::Store::HTTP::Multipart.new(payload.io, payload.params)
|
27
|
+
response = http.post("/apps/#{token}/ping", multipart.to_s, "Content-Type" => multipart.content_type)
|
28
|
+
case response.code.to_i
|
29
|
+
when 201
|
30
|
+
data = JSON.load(response.body)
|
31
|
+
[:success, "Found application '#{data['name']}'"]
|
32
|
+
else
|
33
|
+
# Error message
|
34
|
+
[:failed, response.body.to_s]
|
35
|
+
end
|
36
|
+
rescue => e
|
37
|
+
[:error, e.message]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def token
|
42
|
+
::Fiveruns::Dash.configuration.options[:app]
|
43
|
+
end
|
44
|
+
|
45
|
+
def try_urls(urls)
|
46
|
+
results = urls.map do |u|
|
47
|
+
result = yield(URI.parse(u))
|
48
|
+
case result[0]
|
49
|
+
when :success
|
50
|
+
puts "OK: #{result[1]}"
|
51
|
+
true
|
52
|
+
when :failed
|
53
|
+
puts "Failed talking to #{u}: #{result[1]}"
|
54
|
+
false
|
55
|
+
when :error
|
56
|
+
puts "Error contacting #{u}: #{result[1]}"
|
57
|
+
false
|
58
|
+
end
|
59
|
+
end
|
60
|
+
results.all?
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class Update
|
65
|
+
|
66
|
+
include Store::HTTP
|
67
|
+
include Store::File
|
68
|
+
|
69
|
+
attr_reader :payload, :handler
|
70
|
+
def initialize(payload, &handler)
|
71
|
+
@payload = payload
|
72
|
+
@handler = handler
|
73
|
+
end
|
74
|
+
|
75
|
+
def store(*urls)
|
76
|
+
uris_by_scheme(urls).each do |scheme, uris|
|
77
|
+
value = __send__("store_#{storage_method_for(scheme)}", *uris)
|
78
|
+
return value if value
|
79
|
+
end
|
80
|
+
return false
|
81
|
+
end
|
82
|
+
|
83
|
+
def ping(*urls)
|
84
|
+
Pinger.new(payload).ping(*urls)
|
85
|
+
end
|
86
|
+
|
87
|
+
def guid
|
88
|
+
@guid ||= timestamp << "_#{Process.pid}"
|
89
|
+
end
|
90
|
+
|
91
|
+
#######
|
92
|
+
private
|
93
|
+
#######
|
94
|
+
|
95
|
+
def timestamp
|
96
|
+
Time.now.utc.strftime('%Y%m%d%H%M%S')
|
97
|
+
end
|
98
|
+
|
99
|
+
def uris_by_scheme(urls)
|
100
|
+
urls.map { |url| safe_parse(url) }.group_by(&:scheme)
|
101
|
+
end
|
102
|
+
|
103
|
+
def storage_method_for(scheme)
|
104
|
+
scheme =~ /^http/ ? :http : :file
|
105
|
+
end
|
106
|
+
|
107
|
+
def safe_parse(url)
|
108
|
+
url.respond_to?(:scheme) ? url : URI.parse(url)
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
class Payload
|
114
|
+
|
115
|
+
def initialize(data)
|
116
|
+
@version = Fiveruns::Dash::Version::STRING
|
117
|
+
@data = data
|
118
|
+
end
|
119
|
+
|
120
|
+
def io
|
121
|
+
returning StringIO.new do |io|
|
122
|
+
io.write compressed
|
123
|
+
io.rewind
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def params
|
128
|
+
{}
|
129
|
+
end
|
130
|
+
|
131
|
+
def to_json
|
132
|
+
@data.to_json
|
133
|
+
end
|
134
|
+
|
135
|
+
#######
|
136
|
+
private
|
137
|
+
#######
|
138
|
+
|
139
|
+
def timestamp
|
140
|
+
Time.now.utc.rfc2822
|
141
|
+
end
|
142
|
+
|
143
|
+
def compressed
|
144
|
+
Zlib::Deflate.deflate(to_json)
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
class InfoPayload < Payload
|
150
|
+
def initialize(data, started_at)
|
151
|
+
super(data)
|
152
|
+
@started_at = started_at
|
153
|
+
end
|
154
|
+
def params
|
155
|
+
@params ||= begin
|
156
|
+
params = {
|
157
|
+
:type => 'info',
|
158
|
+
:ip => Fiveruns::Dash.host.ip_address,
|
159
|
+
:hostname => Fiveruns::Dash.host.hostname,
|
160
|
+
:pid => Process.pid,
|
161
|
+
:os_name => Fiveruns::Dash.host.os_name,
|
162
|
+
:os_version => Fiveruns::Dash.host.os_version,
|
163
|
+
:arch => Fiveruns::Dash.host.architecture,
|
164
|
+
:dash_version => Fiveruns::Dash::Version::STRING,
|
165
|
+
:ruby_version => RUBY_VERSION,
|
166
|
+
:started_at => @started_at
|
167
|
+
}
|
168
|
+
if (scm = Fiveruns::Dash.scm)
|
169
|
+
params.update(
|
170
|
+
:scm_revision => scm.revision,
|
171
|
+
:scm_time => scm.time,
|
172
|
+
:scm_type => scm.class.scm_type,
|
173
|
+
:scm_url => scm.url
|
174
|
+
)
|
175
|
+
end
|
176
|
+
params
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
181
|
+
|
182
|
+
class PingPayload < InfoPayload
|
183
|
+
end
|
184
|
+
|
185
|
+
class ExceptionsPayload < Payload
|
186
|
+
def params
|
187
|
+
@params ||= {
|
188
|
+
:type => 'exceptions',
|
189
|
+
:collected_at => timestamp,
|
190
|
+
:hostname => Fiveruns::Dash.host.hostname,
|
191
|
+
}
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
class DataPayload < Payload
|
196
|
+
def params
|
197
|
+
@params ||= {
|
198
|
+
:type => 'data',
|
199
|
+
:collected_at => timestamp,
|
200
|
+
:hostname => Fiveruns::Dash.host.hostname,
|
201
|
+
}
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
class TracePayload < Payload
|
206
|
+
def params
|
207
|
+
@params ||= {
|
208
|
+
:type => 'trace',
|
209
|
+
:collected_at => timestamp,
|
210
|
+
:hostname => Fiveruns::Dash.host.hostname,
|
211
|
+
}
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|