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,86 @@
1
+ require 'yaml'
2
+
3
+ # (The MIT License)
4
+ #
5
+ # Copyright (c) 2008 Jamis Buck <jamis@37signals.com>,
6
+ # with modifications by Bruce Williams <bruce@fiveruns.com>
7
+ #
8
+ # Permission is hereby granted, free of charge, to any person obtaining
9
+ # a copy of this software and associated documentation files (the
10
+ # 'Software'), to deal in the Software without restriction, including
11
+ # without limitation the rights to use, copy, modify, merge, publish,
12
+ # distribute, sublicense, and/or sell copies of the Software, and to
13
+ # permit persons to whom the Software is furnished to do so, subject to
14
+ # the following conditions:
15
+ #
16
+ # The above copyright notice and this permission notice shall be
17
+ # included in all copies or substantial portions of the Software.
18
+ #
19
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
20
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
22
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
23
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
24
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
25
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26
+ module Fiveruns
27
+
28
+ module Dash
29
+
30
+ # A class for describing the current version of a library. The version
31
+ # consists of three parts: the +major+ number, the +minor+ number, and the
32
+ # +tiny+ (or +patch+) number.
33
+ class Version
34
+
35
+ include Comparable
36
+
37
+ # A convenience method for instantiating a new Version instance with the
38
+ # given +major+, +minor+, and +tiny+ components.
39
+ def self.[](major, minor, tiny)
40
+ new(major, minor, tiny)
41
+ end
42
+
43
+ attr_reader :major, :minor, :tiny
44
+
45
+ # Create a new Version object with the given components.
46
+ def initialize(major, minor, tiny)
47
+ @major, @minor, @tiny = major, minor, tiny
48
+ end
49
+
50
+ # Compare this version to the given +version+ object.
51
+ def <=>(version)
52
+ to_i <=> version.to_i
53
+ end
54
+
55
+ # Converts this version object to a string, where each of the three
56
+ # version components are joined by the '.' character. E.g., 2.0.0.
57
+ def to_s
58
+ @to_s ||= [@major, @minor, @tiny].join(".")
59
+ end
60
+
61
+ # Converts this version to a canonical integer that may be compared
62
+ # against other version objects.
63
+ def to_i
64
+ @to_i ||= @major * 1_000_000 + @minor * 1_000 + @tiny
65
+ end
66
+
67
+ def to_a
68
+ [@major, @minor, @tiny]
69
+ end
70
+
71
+ PARSED = YAML.load(File.read(File.dirname(__FILE__) << "/../../../version.yml"))
72
+
73
+ MAJOR = PARSED['major']
74
+ MINOR = PARSED['minor']
75
+ TINY = PARSED['patch']
76
+
77
+ # The current version as a Version instance
78
+ CURRENT = new(MAJOR, MINOR, TINY)
79
+ # The current version as a String
80
+ STRING = CURRENT.to_s
81
+
82
+ end
83
+
84
+ end
85
+
86
+ end
data/recipes/jruby.rb ADDED
@@ -0,0 +1,107 @@
1
+ # TODO Error Handling
2
+ # TODO cache VM and/or connection
3
+ # TODO singleton class around vm instance to avoid passing it all over the place
4
+ # should be able to do vm.connector_avaialble? and vm.load_management_agent
5
+ # TODO cache mbeans
6
+ # TODO more metprogramming in metrics area, pick up names and descriptions
7
+ # of attributes direct from mbean and expose all as metrics
8
+
9
+ if RUBY_PLATFORM[/java/]
10
+
11
+ require 'java'
12
+
13
+ import com.sun.tools.attach.VirtualMachine
14
+ import javax.management.remote.JMXServiceURL
15
+ import javax.management.remote.JMXConnectorFactory
16
+ import java.lang.management.ManagementFactory
17
+ import java.util.HashMap
18
+
19
+ module Fiveruns
20
+ module Dash
21
+ module JRuby
22
+
23
+ JMX_ADDRESS_PROPERTY = "com.sun.management.jmxremote.localConnectorAddress"
24
+
25
+ def self.connector_address_property( vm )
26
+ vm.getAgentProperties().getProperty(JMX_ADDRESS_PROPERTY)
27
+ end
28
+
29
+ def self.connector_available?( vm )
30
+ !connector_address_property( vm ).nil?
31
+ end
32
+
33
+ def self.load_management_agent( vm )
34
+ agent = vm.getSystemProperties().getProperty("java.home") + "/" + "lib" + "/" + "management-agent.jar"
35
+ vm.loadAgent( agent )
36
+ end
37
+
38
+ def self.connector_address( vm )
39
+ load_management_agent( vm ) unless connector_available?( vm )
40
+ connector_address_property( vm )
41
+ end
42
+
43
+ def self.connection #cache somehow ? instance variable? Not sure we're in a class ?
44
+ vm = VirtualMachine::attach( Process.pid.to_s )
45
+ url = JMXServiceURL.new( connector_address( vm ) )
46
+ connector = JMXConnectorFactory::connect url, HashMap.new
47
+ server_connection = connector.mbean_server_connection
48
+ server_connection
49
+ end
50
+
51
+ def self.object_name(service_name)
52
+ "org.jruby:type=Runtime,name=#{::JRuby.runtime.hashCode},service=#{service_name}"
53
+ end
54
+ # e.g. mbean( "ClassCache", ClassCacheMBean::java_class)
55
+ # shold be able to DRY further - How to turn "ClassCache" into ClassCacheMBean::java_class
56
+ # not sure the pattern holds for everything, but it hold for everything here so far.
57
+ def self.mbean( service_name, java_class) # cache somehow? map lookup ? only need each bean proxy once.
58
+ ManagementFactory::newPlatformMXBeanProxy(connection, object_name(service_name), java_class)
59
+ end
60
+
61
+ end
62
+ end
63
+ end
64
+
65
+ Fiveruns::Dash.register_recipe :jruby, :url => 'http://dash.fiveruns.com' do |metrics|
66
+
67
+ # ############
68
+ # Class Cache MBean
69
+ # ############
70
+
71
+ import org.jruby.management.ClassCacheMBean
72
+
73
+ metrics.absolute :live_class_count, "Live Class Count", "Number of active classes" do
74
+ Integer(Fiveruns::Dash::JRuby.mbean("ClassCache", ClassCacheMBean::java_class).get_live_class_count)
75
+ end
76
+ metrics.absolute :class_load_count, "Class Load Count", "Number of loaded classes" do
77
+ Integer(Fiveruns::Dash::JRuby.mbean("ClassCache", ClassCacheMBean::java_class).get_class_load_count)
78
+ end
79
+ metrics.absolute :class_reuse_count, "Class Reuse Count", "Number of reused classes" do
80
+ Integer(Fiveruns::Dash::JRuby.mbean("ClassCache", ClassCacheMBean::java_class).get_class_reuse_count)
81
+ end
82
+
83
+ # ############
84
+ # JITCompiler MBean
85
+ # ############
86
+
87
+ import org.jruby.compiler.JITCompilerMBean
88
+
89
+ metrics.absolute :average_code_size, "Average Code Size", "Average Code Size" do
90
+ Integer(Fiveruns::Dash::JRuby.mbean("JITCompiler", JITCompilerMBean::java_class).average_code_size)
91
+ end
92
+ metrics.absolute :compile_count, "Compile Count", "Compile Count" do
93
+ Integer(Fiveruns::Dash::JRuby.mbean("JITCompiler", JITCompilerMBean::java_class).compile_count)
94
+ end
95
+ metrics.absolute :compile_success_count, "Compile Success Count", "Compile Success Count" do
96
+ Integer(Fiveruns::Dash::JRuby.mbean("JITCompiler", JITCompilerMBean::java_class).success_count)
97
+ end
98
+ metrics.absolute :compile_fail_count, "Compile Fail Count", "Compile Fail Count" do
99
+ Integer(Fiveruns::Dash::JRuby.mbean("JITCompiler", JITCompilerMBean::java_class).fail_count)
100
+ end
101
+ metrics.absolute :average_compile_time, "Average Compile Time", "Average Compile Time" do
102
+ Integer(Fiveruns::Dash::JRuby.mbean("JITCompiler", JITCompilerMBean::java_class).average_compile_time)
103
+ end
104
+
105
+ end
106
+
107
+ end
data/recipes/ruby.rb ADDED
@@ -0,0 +1,34 @@
1
+ Fiveruns::Dash.register_recipe :ruby, :url => 'http://dash.fiveruns.com' do |metrics|
2
+ metrics.absolute :vsz,
3
+ "Virtual Memory Usage", "The amount of virtual memory used by this process",
4
+ :unit => 'kbytes', :scope => :host do
5
+ Integer(`ps -o vsz -p #{Process.pid}`[/(\d+)/, 1])
6
+ end
7
+ metrics.absolute :rss, "Resident Memory Usage",
8
+ "The amount of physical memory used by this process",
9
+ :unit => 'kbytes',
10
+ :scope => :host do
11
+ Integer(`ps -o rss -p #{Process.pid}`[/(\d+)/, 1])
12
+ end
13
+ metrics.percentage :pmem,
14
+ "Resident Memory Usage",
15
+ "Percentage of Resident Memory Usage",
16
+ :scope => :host do
17
+ Float(`ps -o pmem -p #{Process.pid}`[/(\d+\.\d+)/, 1])
18
+ end
19
+
20
+ if RUBY_PLATFORM == 'java'
21
+ Fiveruns::Dash.logger.warn "Cannot collect CPU usage data on JRuby"
22
+ else
23
+ metrics.percentage :cpu,
24
+ 'CPU Usage',
25
+ 'Percentage CPU Usage',
26
+ :scope => :host do
27
+ before = Thread.current[:dash_utime] ||= Process.times.utime
28
+ after = Process.times.utime
29
+ this_minute = after - before
30
+ Thread.current[:dash_utime] = after
31
+ (this_minute / 60) * 100.00
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,260 @@
1
+ #require File.dirname(__FILE__) << "/test_helper"
2
+ require 'test/unit'
3
+ require 'rubygems'
4
+ require 'shoulda'
5
+ require 'flexmock/test_unit'
6
+ require 'thread'
7
+ #require 'json'
8
+ require 'thin'
9
+
10
+ $:.unshift(File.dirname(__FILE__) << '/../lib')
11
+ # Require library files
12
+ require 'fiveruns/dash'
13
+
14
+ class Test::Unit::TestCase
15
+ include Fiveruns::Dash
16
+ end
17
+
18
+ class Integer
19
+ def intervals
20
+ self * 5 + 0.1
21
+ end
22
+ end
23
+
24
+ module Fiveruns::Dash::Store::HTTP
25
+ def check_response_of(response)
26
+ unless response
27
+ Fiveruns::Dash.logger.debug "Received no response from Dash service"
28
+ return false
29
+ end
30
+ case response.code.to_i
31
+ when 201
32
+ true
33
+ when 400..499
34
+ Fiveruns::Dash.logger.warn "Could not access Dash service (#{response.code.to_i}, #{response.body.inspect})"
35
+ false
36
+ else
37
+ Fiveruns::Dash.logger.debug "Received unknown response from Dash service (#{response.inspect})"
38
+ false
39
+ end
40
+ rescue JSON::ParserError => e
41
+ puts response.body
42
+ Fiveruns::Dash.logger.error "Received non-JSON response (#{response.inspect})"
43
+ false
44
+ end
45
+ end
46
+
47
+ class DummyCollector
48
+ attr_accessor :sleep_time, :data_payload_count, :info_payload_count, :post_times
49
+
50
+ def initialize(options = {})
51
+ @startup_delay = options[:startup_delay]
52
+ @response_delay = options[:response_delay]
53
+ @info_payload_count = 0
54
+ @data_payload_count = 0
55
+ @post_times = []
56
+ end
57
+
58
+ def call(t)
59
+ sleep(@sleep_time) if @sleep_time
60
+ puts "sleeping for #{@sleep_time}" if @sleep_time
61
+ @post_times << Time.now
62
+ res = nil
63
+ if t["rack.input"].read =~ /name=\"type\"\r\n\r\ninfo\r\n/
64
+ @info_payload_count += 1
65
+ res = info_response
66
+ else
67
+ @data_payload_count += 1
68
+ res = data_response
69
+ end
70
+ puts "BOOM! workers: info: #{@info_payload_count} data: #{@data_payload_count} #{Time.now} - #{Time.now.to_f}"
71
+ return [201, {"Content-Type" => "application/json; charset=utf-8" }, res]
72
+ end
73
+
74
+
75
+ def info_response
76
+ data = {"process_id"=>774736468, "metric_infos"=>[{"name"=>"vsz", "id"=>932254202, "recipe_name"=>"ruby", "recipe_url"=>"http://dash.fiveruns.com"}, {"name"=>"rss", "id"=>932254199, "recipe_name"=>"ruby", "recipe_url"=>"http://dash.fiveruns.com"}, {"name"=>"pmem", "id"=>932254200, "recipe_name"=>"ruby", "recipe_url"=>"http://dash.fiveruns.com"}, {"name"=>"cpu", "id"=>932254201, "recipe_name"=>"ruby", "recipe_url"=>"http://dash.fiveruns.com"}, {"name"=>"activity", "id"=>932254194, "recipe_name"=>"activerecord", "recipe_url"=>"http://dash.fiveruns.com"}, {"name"=>"response_time", "id"=>932254195, "recipe_name"=>"actionpack", "recipe_url"=>"http://dash.fiveruns.com"}, {"name"=>"requests", "id"=>932254196, "recipe_name"=>"actionpack", "recipe_url"=>"http://dash.fiveruns.com"}, {"name"=>"render_time", "id"=>932254197, "recipe_name"=>"actionpack", "recipe_url"=>"http://dash.fiveruns.com"}, {"name"=>"queue_size", "id"=>932254198, "recipe_name"=>"rails", "recipe_url"=>"http://dash.fiveruns.com"}], "traces"=>[]}
77
+ return data.to_json
78
+ end
79
+ def data_response
80
+ data = {"process_id"=>774736448, "metric_infos"=>[{"name"=>"rss", "id"=>932254199, "recipe_name"=>"ruby", "recipe_url"=>"http://dash.fiveruns.com"}, {"name"=>"pmem", "id"=>932254200, "recipe_name"=>"ruby", "recipe_url"=>"http://dash.fiveruns.com"}, {"name"=>"cpu", "id"=>932254201, "recipe_name"=>"ruby", "recipe_url"=>"http://dash.fiveruns.com"}, {"name"=>"activity", "id"=>932254194, "recipe_name"=>"activerecord", "recipe_url"=>"http://dash.fiveruns.com"}, {"name"=>"response_time", "id"=>932254195, "recipe_name"=>"actionpack", "recipe_url"=>"http://dash.fiveruns.com"}, {"name"=>"requests", "id"=>932254196, "recipe_name"=>"actionpack", "recipe_url"=>"http://dash.fiveruns.com"}, {"name"=>"render_time", "id"=>932254197, "recipe_name"=>"actionpack", "recipe_url"=>"http://dash.fiveruns.com"}, {"name"=>"queue_size", "id"=>932254198, "recipe_name"=>"rails", "recipe_url"=>"http://dash.fiveruns.com"}], "traces"=>[]}
81
+ return data.to_json
82
+ end
83
+ end
84
+
85
+
86
+ class CollectorCommunicationTest < Test::Unit::TestCase
87
+
88
+ attr_reader :payload
89
+ context "FiveRuns Dash Gem" do
90
+
91
+ setup do
92
+ ##no_recipe_loading!
93
+ flexmock(Fiveruns::Dash).should_receive(:load_recipes)
94
+
95
+ ##mock_configuration!
96
+ @metrics = []
97
+ @metric_class = Class.new(Metric) do
98
+ def self.metric_type
99
+ :test
100
+ end
101
+ def info_id
102
+ 1
103
+ end
104
+ end
105
+ 3.times do |i|
106
+ @metrics << @metric_class.new("Metric#{i}") { 1 }
107
+ end
108
+ @recipes = []
109
+ @recipes << Fiveruns::Dash::Recipe.new(:foo, :url => 'http://foo.com')
110
+ @recipes << Fiveruns::Dash::Recipe.new(:foo2, :url => 'http://foo2.com')
111
+ @metrics << @metric_class.new("NonCustomMetric") { 2 }
112
+ @configuration = flexmock(:configuration) do |mock|
113
+ mock.should_receive(:metrics).and_return(@metrics)
114
+ mock.should_receive(:recipes).and_return(@recipes)
115
+ end
116
+
117
+
118
+ ##create_session!
119
+ @session = Session.new(@configuration)
120
+
121
+ # other stuff
122
+ flexmock(@configuration).should_receive(:options).and_return(:app => '123')
123
+ flexmock(::Fiveruns::Dash).should_receive(:configuration).and_return(@configuration)
124
+ flexmock(@session.reporter).should_receive(:update_locations).returns(["http://localhost:9999"])
125
+
126
+ @collector = DummyCollector.new()
127
+ @thin = Thin::Server.new('127.0.0.1', 9999, @collector)
128
+ @prev_value = Thread.abort_on_exception
129
+ Thread.abort_on_exception = true
130
+ end
131
+
132
+ teardown do
133
+ Thread.abort_on_exception = @prev_value
134
+ end
135
+
136
+ should "have no tests in here" do
137
+ assert true
138
+ end
139
+
140
+ should_eventually "act properly" do
141
+ # When the reporter starts, it immediately sends an info packet,
142
+ # along with a regular payload
143
+ @t = Thread.new { @thin.start }
144
+ @session.reporter.interval = 5
145
+ @session.reporter.start
146
+ sleep(2.intervals) #enough for 2 cycles
147
+ assert_equal @collector.info_payload_count, 1
148
+ assert_equal @collector.data_payload_count, 2
149
+ @thin.stop
150
+ assert_equal @collector.post_times.size, 3
151
+ @collector.post_times.size.times do |i|
152
+ unless i == (@collector.post_times.size - 1)
153
+ assert_equal (@collector.post_times[i+1].to_i - @collector.post_times[i].to_i), 5
154
+ end
155
+ end
156
+ @thin.stop!
157
+ puts "stopped!"
158
+ end
159
+
160
+ should_eventually "compensate correctly for delayed post times" do
161
+ @collector.sleep_time = 2
162
+ @t = Thread.new { @thin.start }
163
+ @session.reporter.interval = 5
164
+ @session.reporter.start
165
+ sleep(2.intervals + @collector.sleep_time ) #enough for 2 cycles
166
+ assert_equal @collector.info_payload_count, 1
167
+ assert_equal @collector.data_payload_count, 2
168
+ @thin.stop
169
+ assert_equal @collector.post_times.size, 3
170
+ @collector.post_times.size.times do |i|
171
+ unless i == (@collector.post_times.size - 1)
172
+ assert_equal (@collector.post_times[i+1].to_i - @collector.post_times[i].to_i), 5
173
+ end
174
+ end
175
+ @thin.stop!
176
+
177
+ end
178
+
179
+ should_eventually "continue to report if the first payload fails" do
180
+ @session.reporter.interval = 5
181
+ @session.reporter.start
182
+ sleep(1.intervals)
183
+ assert_equal @collector.data_payload_count, 0
184
+ assert_equal @collector.data_payload_count, 0
185
+ #puts "STARTING COLLECTOR #{Time.now}"
186
+ @t = Thread.new { @thin.start }
187
+ #puts "COLLECTOR STARTED #{Time.now} - #{Time.now.to_f}"
188
+ #puts "STARTING SLEEP #{Time.now} - #{Time.now.to_f}"
189
+ sleep(2.intervals)
190
+ #puts "DONE SLEEPING #{Time.now} - #{Time.now.to_f}"
191
+ assert_equal @collector.info_payload_count, 1
192
+ assert_equal @collector.data_payload_count, 1
193
+ @thin.stop!
194
+
195
+ end
196
+
197
+ should_eventually "continue to report if collector dies at a random time" do
198
+ @t = Thread.new { @thin.start }
199
+ @session.reporter.interval = 5
200
+ @session.reporter.start
201
+ sleep(2.intervals)
202
+ puts "STOPPING!!! #{Time.now}"
203
+ @thin.stop!
204
+ @t.kill
205
+ puts "STOPPED #{Time.now}"
206
+ assert_equal @collector.info_payload_count, 1
207
+ assert_equal @collector.data_payload_count, 2
208
+ sleep(3.intervals) #stop for a while
209
+ puts "STARTING BACK UP #{Time.now}"
210
+ @t = Thread.new { @thin.start }
211
+ puts "STARTED BACK UP #{Time.now}"
212
+ sleep(3.intervals)
213
+ puts "STOPPING AGAIN!!! #{Time.now}"
214
+ @thin.stop
215
+ puts "STOPPED FINALLY #{Time.now}"
216
+ assert_equal @collector.data_payload_count, 5
217
+
218
+
219
+
220
+ # check the post time intervals to make sure they match what we expect
221
+ (0..1).each do |i|
222
+ assert_equal((@collector.post_times[i+1].to_i - @collector.post_times[i].to_i), 5)
223
+ end
224
+ # because we shut down for 3 intervals, the next post wouldn't happen until the 4th interval
225
+ # thus, 20 seconds
226
+ assert_equal (@collector.post_times[3].to_i - @collector.post_times[2].to_i), 20
227
+ (3..4).each do |i|
228
+ assert_equal((@collector.post_times[i+1].to_i - @collector.post_times[i].to_i), 5)
229
+ end
230
+
231
+ end
232
+
233
+ end
234
+
235
+
236
+ #######
237
+ private
238
+ #######
239
+
240
+ def full_urls(service)
241
+ full_uris(service).map(&:to_s)
242
+ end
243
+
244
+ def full_uris(service)
245
+ @urls.map do |url|
246
+ uri = URI.parse(url)
247
+ uri.path = if service == :exceptions
248
+ '/exceptions.json'
249
+ else
250
+ "/apps/123/#{service}.json"
251
+ end
252
+ uri
253
+ end
254
+ end
255
+
256
+ def uris
257
+ @urls.map { |url| URI.parse(url) }
258
+ end
259
+
260
+ end