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.
@@ -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