rublique 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,83 @@
1
+ Hello. I'm going to answer the one question I expect to get asked regarding
2
+ this package. Hopefully everything else you can figure out from the RDoc.
3
+
4
+ That question is:
5
+
6
+ How do I use this with Rails?
7
+
8
+ Well, for now, it's an ugly hack. I don't know Rails internals well enough
9
+ to come up with a better solution.
10
+
11
+ The Rublique distribution includes a rublique_dispatcher.rb file which contains
12
+ a replacement implementation for Dispatcher.dispatch compatible with
13
+ Rails 1.1.6 (other versions may work. I don't know)
14
+
15
+ If you know a better way to do this, than for the love of God please e-mail
16
+ me at tony@clickcaster.com (and no, sticking it in environment.rb won't work)
17
+
18
+ First, find your gems directory (/usr/lib/ruby/gems, /usr/local/lib/ruby/gems,
19
+ /opt/local/lib/ruby/gems, etc. depending on your platform)
20
+
21
+ Then, look underr gems/1.8/gems/rails-1.1.6/lib directory and you'll find
22
+ dispatcher.rb
23
+
24
+ At the BOTTOM of this file, add:
25
+
26
+ require 'rublique_dispatcher'
27
+
28
+ Then restart your Rails application.
29
+
30
+ This will log to RAILS_ROOT/log/rublique.log by default. The format is a
31
+ series of newline delimited JSON arrays, containing:
32
+
33
+ [timestamp,{section breakdown}]
34
+
35
+ The section breakdown is a JSON object which keys code section names (which
36
+ with the Rails instrumentation will be in the form of "controller/action" or
37
+ "rails" for anything done outside the dispatcher. Each section name keys
38
+ to another JSON object, with class names that key to an object count.
39
+
40
+ The object count represents how many objects of a given class were created
41
+ in that particular section minus how many objects of a given class originally
42
+ created within that section were garbage collected since the last time
43
+ the object counts were logged. By default these deltas are logged every
44
+ 10 requests, and right now there's no good way to change that besides
45
+ hand-editing rublique_dispatcher.rb (this is a 0.0.1 release, after all)
46
+
47
+ That information probably isn't very useful to you, but fortunately Rublique
48
+ includes a log analyzer tool that outputs CSV files you can import into
49
+ the graphing tool of your choice.
50
+
51
+ Run:
52
+
53
+ rublique_analyzer -s rublique.log
54
+
55
+ to output a breakdown of net object creation by code section over time. The
56
+ first column represents time in seconds, and the others object counts.
57
+
58
+ To inspect a particular section, use:
59
+
60
+ rublique_analyzer -c controller/action rublique.log
61
+
62
+ This will give you a CSV breakdown of object allocation by class per code
63
+ section over time.
64
+
65
+ ---
66
+
67
+ Known bugs:
68
+
69
+ Rublique is neither thread safe nor reentrant. This can lead to some erroneous
70
+ numbers when used in a threaded environment (which is pretty much guaranteed
71
+ with Rails)
72
+
73
+ Rublique provides no interface for nor keeps a stack of code sections. This
74
+ means it presently only works within a dispatcher-like framework where
75
+ you have an outer environment section which dispatches to various inner
76
+ processing sections. Sections can be no deeper than two levels.
77
+
78
+ Because of this, things like Rails components will mess up Rublique's
79
+ housekeeping. Rublique really needs to keep a stack of code sections, and have
80
+ a .pop method to move down a level. That will have to wait for a future
81
+ version, sorry folks.
82
+
83
+ For now: don't use components!
@@ -0,0 +1,33 @@
1
+ require 'rake'
2
+ require 'rake/rdoctask'
3
+ require 'rake/gempackagetask'
4
+ require 'spec/rake/spectask'
5
+
6
+ Spec::Rake::SpecTask.new(:spec) do |task|
7
+ task.spec_files = FileList['**/*_spec.rb']
8
+ end
9
+
10
+ Rake::RDocTask.new(:rdoc) do |task|
11
+ task.rdoc_dir = 'doc'
12
+ task.title = 'Rublique'
13
+ task.rdoc_files.include('lib/**/*.rb')
14
+ end
15
+
16
+ spec = Gem::Specification.new do |s|
17
+ s.name = %q{rublique}
18
+ s.version = "0.0.1"
19
+ s.date = %q{2006-12-19}
20
+ s.summary = %q{Rublique monitors object lifetimes across various code sections, outputting a logfile of object deltas which can be used to generate CSV files of object use over time}
21
+ s.email = %q{tony@clickcaster.com}
22
+ s.homepage = %q{http://rublique.rubyforge.org}
23
+ s.rubyforge_project = %q{rublique}
24
+ s.has_rdoc = true
25
+ s.authors = ["Tony Arcieri"]
26
+ s.files = ["README", "Rakefile", "lib", "lib/rublique.rb", "lib/rublique_logger.rb", "lib/rublique_dispatcher.rb", "bin", "bin/rublique_analyzer"]
27
+ s.add_dependency('json', '>= 0.4.0')
28
+ s.executables << 'rublique_analyzer'
29
+ end
30
+
31
+ Rake::GemPackageTask.new(spec) do |pkg|
32
+ pkg.need_tar = true
33
+ end
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+
4
+ begin
5
+ require 'fjson'
6
+ rescue
7
+ require 'json'
8
+ end
9
+
10
+ logfile_name = ARGV.pop
11
+ mode = nil
12
+
13
+ case ARGV.shift
14
+ when '-s'
15
+ mode = :sections
16
+ when '-c'
17
+ mode = :classes
18
+ section_name = ARGV.shift
19
+ end
20
+
21
+ if !logfile_name || !mode || (mode == :classes && !section_name)
22
+ puts <<EOD
23
+ Usage: #{$0} -s <logfile>
24
+ #{$0} -c <section> <logfile>
25
+
26
+ #{$0} outputs a CSV analysis of a Rublique log:
27
+
28
+ -s Outputs a timestamp followed by every section in the logfile and
29
+ the total objects allocated by that section at that time
30
+
31
+ -c section Outputs a timestamp followed by how many instances of every class
32
+ are presently allocated within that section
33
+ EOD
34
+ exit
35
+ end
36
+
37
+ logfile = File.open logfile_name, 'r'
38
+
39
+ initial_time = nil
40
+ output_data = []
41
+ previous_totals = Hash.new(0)
42
+
43
+ logfile.each_line do |line|
44
+ date, breakdown = JSON.parse line
45
+
46
+ if initial_time
47
+ tdelta = (Time.parse(date) - initial_time).to_i
48
+ else
49
+ tdelta = 0
50
+ initial_time = Time.parse(date)
51
+ end
52
+
53
+ case mode
54
+ when :sections
55
+ totals = breakdown.inject(previous_totals) do |h, action|
56
+ name, objects = action
57
+ h[name] += objects.values.inject(0) { |a,v| a + v }
58
+ h
59
+ end
60
+ when :classes
61
+ next if breakdown[section_name].nil?
62
+
63
+ totals = breakdown[section_name].inject(previous_totals) do |h, classcounts|
64
+ classname, count = classcounts
65
+ h[classname] += count
66
+ h
67
+ end
68
+ end
69
+
70
+ previous_totals = totals.dup
71
+ output_data << [tdelta, totals]
72
+ end
73
+
74
+ all_columns = output_data.last[1].keys.sort
75
+
76
+ lifetime_totals = all_columns.inject({}) do |h,c|
77
+ h[c] = output_data.inject(0) { |a,o| o[1][c] + a }
78
+ h
79
+ end
80
+
81
+ ranked_columns = lifetime_totals.sort { |a,b| b[1] <=> a[1] }.map { |a| a.shift }
82
+
83
+ puts "seconds," + ranked_columns.join(',')
84
+
85
+ output_data.each do |snapshot|
86
+ time, totals = snapshot
87
+ puts time.to_s + ',' + ranked_columns.map { |c| totals[c].to_s }.join(',')
88
+ end
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ class Rublique
4
+ class << self
5
+ # A map of what objects belong to what tag
6
+ @@object_map = {}
7
+
8
+ # A map of what classes belong(ed) to what ID
9
+ @@class_map = {}
10
+
11
+ # A list of object counts, broken down by tag
12
+ @@object_breakdown = {}
13
+
14
+ # Grab the #object_id method from Ojbect, in case it's been overridden
15
+ # Objects like Builder::XmlMarkup do this
16
+ @@object_id_method = Object.new.method(:object_id).unbind
17
+
18
+ # Grab the #class method from Object, in case it's been overridden
19
+ @@class_method = Object.new.method(:class).unbind
20
+
21
+ # Take a snapshot of the current ObjectSpace, returning active object IDs and their associated tag
22
+ def snapshot(tag)
23
+ # Unknown stuff in ObjectSpace gets dumped here
24
+ new_objects = {}
25
+
26
+ # A list of stuff we know about, so we can see what got GCed
27
+ object_list = @@object_map.dup
28
+
29
+ ObjectSpace.each_object do |obj|
30
+ # Bind Object#object_id to the object and call it to get its id
31
+ obj_id = @@object_id_method.bind(obj).call
32
+
33
+ if object_list.has_key? obj_id
34
+ # Delete known objects, since this list stores GCed objects
35
+ object_list.delete obj_id
36
+ next
37
+ end
38
+
39
+ # Add to the new objects collection if unknown, with the given tag
40
+ new_objects[obj_id] = tag
41
+
42
+ # Check to see if Object#class has been overridden
43
+ if obj.class.class == Class
44
+ @@class_map[obj_id] = obj.class
45
+ else
46
+ # Capture the real class of objects which override #class
47
+ @@class_map[obj_id] = @@class_method.bind(obj).call
48
+ end
49
+ end
50
+
51
+ # Remove objects that are no longer in ObjectSpace
52
+ object_list.each_key { |key| @@object_map.delete key }
53
+
54
+ @@object_map.merge! new_objects
55
+ end
56
+
57
+ # Return a hash of object IDs to their associated tag
58
+ def objects
59
+ @@object_map
60
+ end
61
+
62
+ # Build a hash of hashes which maps a tag to its respective object IDs and those IDs to their class
63
+ def breakdown
64
+ @@object_map.to_a.inject({}) do |tag_hash, object_mapping|
65
+ obj, tag = object_mapping
66
+
67
+ if tag_hash.has_key? tag
68
+ tag_hash[tag][obj] = @@class_map[obj]
69
+ else
70
+ tag_hash[tag] = { obj => @@class_map[obj] }
71
+ end
72
+
73
+ tag_hash
74
+ end
75
+ end
76
+
77
+ # Build a hash of hashes which records total objects created and destroyed since the last delta was taken, by class by code section
78
+ def delta
79
+ old_object_breakdown = @@object_breakdown
80
+ @@object_breakdown = nil
81
+
82
+ # Try to GC our own overhead
83
+ ObjectSpace.garbage_collect
84
+
85
+ @@object_breakdown = breakdown
86
+
87
+ @@object_breakdown.keys.inject({}) do |d, tag|
88
+ old_objects = old_object_breakdown[tag] || {}
89
+ new_objects = @@object_breakdown[tag]
90
+
91
+ # Find objects which have been garbage collected, note them, and delete them from the class_map
92
+ d[tag] = (old_objects.keys - new_objects.keys).inject(Hash.new(0)) do |h,obj|
93
+ h[@@class_map[obj]] -= 1
94
+ @@class_map.delete obj
95
+ h
96
+ end
97
+
98
+ new_objects.keys.inject(d[tag]) do |h,obj|
99
+ unless old_objects.has_key? obj
100
+ h[@@class_map[obj]] += 1
101
+ end
102
+ h
103
+ end.delete_if { |k,v| v == 0 }
104
+ d
105
+ end.delete_if { |k,v| v.empty? }
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,44 @@
1
+ require 'rublique'
2
+ require 'rublique_logger'
3
+
4
+ RUBLIQUE_LOG_INTERVAL = 10
5
+
6
+ # Override the Rails 1.1.6 dispatcher to use Rublique
7
+ class Dispatcher
8
+ # Set the Rublique logfile path if we haven't already
9
+ @@logfile_path_set = false
10
+
11
+ # Count the number of requests we've received before logging a delta
12
+ @@request_count = RUBLIQUE_LOG_INTERVAL
13
+
14
+ def self.dispatch(cgi = nil, session_options = ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, output = $stdout)
15
+ # Set the logfile path to within RAILS_ROOT
16
+ unless @@logfile_path_set
17
+ RubliqueLogger.file = RAILS_ROOT + '/log/rublique.log'
18
+ @@logfile_path_set = true
19
+ end
20
+
21
+ if cgi ||= new_cgi(output)
22
+ request, response = ActionController::CgiRequest.new(cgi, session_options), ActionController::CgiResponse.new(cgi)
23
+ prepare_application
24
+
25
+ # Locate the appropriate controller through routes
26
+ controller = ActionController::Routing::Routes.recognize!(request)
27
+
28
+ Rublique.snapshot('rails')
29
+ controller.process(request, response).out(output)
30
+ Rublique.snapshot(controller.controller_name + '/' + controller.action_name)
31
+
32
+ @@request_count += 1
33
+ @@request_count = 0 if @@request_count > RUBLIQUE_LOG_INTERVAL
34
+ RubliqueLogger.log if @@request_count == 0
35
+ end
36
+ rescue Object => exception
37
+ failsafe_response(output, '500 Internal Server Error', exception) do
38
+ ActionController::Base.process_with_exception(request, response, exception).out(output)
39
+ end
40
+ ensure
41
+ # Do not give a failsafe response here.
42
+ reset_after_dispatch
43
+ end
44
+ end
@@ -0,0 +1,38 @@
1
+ require 'rubygems'
2
+ require 'rublique'
3
+
4
+ begin
5
+ require 'fjson'
6
+ rescue
7
+ require 'json'
8
+ end
9
+
10
+ # A singleton for writing logs Rublique deltas in JSON format to be used in
11
+ # conjunction with rublique_analyzer
12
+ class RubliqueLogger
13
+ class << self
14
+ # Path to logfile
15
+ @@path = '/tmp/rublique.log'
16
+
17
+ # Logfile handle
18
+ @@log = nil
19
+
20
+ # Set the logfile name. May only be done before .log is called
21
+ def file=(path)
22
+ raise 'Cannot change Railique log path after logfile has been opened' unless @@log.nil?
23
+ @@path = path
24
+ end
25
+
26
+ # Log a Rublique delta
27
+ def log
28
+ if @@log.nil?
29
+ @@log = File.open(@@path, 'a+')
30
+ @@log.sync = true
31
+ end
32
+
33
+ delta = Rublique.delta
34
+ @@log.write [Time.now.strftime('%Y-%m-%d %H:%M:%S'), delta].to_json + "\n" unless delta.empty?
35
+ delta
36
+ end
37
+ end
38
+ end
metadata ADDED
@@ -0,0 +1,61 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.0
3
+ specification_version: 1
4
+ name: rublique
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.0.1
7
+ date: 2006-12-19 00:00:00 -07:00
8
+ summary: Rublique monitors object lifetimes across various code sections, outputting a logfile of object deltas which can be used to generate CSV files of object use over time
9
+ require_paths:
10
+ - lib
11
+ email: tony@clickcaster.com
12
+ homepage: http://rublique.rubyforge.org
13
+ rubyforge_project: rublique
14
+ description:
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Tony Arcieri
31
+ files:
32
+ - README
33
+ - Rakefile
34
+ - lib
35
+ - lib/rublique.rb
36
+ - lib/rublique_logger.rb
37
+ - lib/rublique_dispatcher.rb
38
+ - bin
39
+ - bin/rublique_analyzer
40
+ test_files: []
41
+
42
+ rdoc_options: []
43
+
44
+ extra_rdoc_files: []
45
+
46
+ executables:
47
+ - rublique_analyzer
48
+ extensions: []
49
+
50
+ requirements: []
51
+
52
+ dependencies:
53
+ - !ruby/object:Gem::Dependency
54
+ name: json
55
+ version_requirement:
56
+ version_requirements: !ruby/object:Gem::Version::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: 0.4.0
61
+ version: