rublique 0.0.1

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 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: