memory_tracker 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8642b4f864b9dc88a373232dd35b13010e2bd38d
4
+ data.tar.gz: ab6461e8b38e5b8c83a3f10d0336f6100b433bb6
5
+ SHA512:
6
+ metadata.gz: 728e6cab2a895542bacba8b1b6baf40d84a7888ea12b1faa3e2d3c88e2bc002725213e70eac08fd6dee03946a7ac27d141d767f2ffd5ea6b5a5555d0469ae44e
7
+ data.tar.gz: 2e866401d7468d068c9802a907d55de301d99b9b3dbe1b7da5cc41914b977c623530599104db6ea0ee63daa8bcbfaebc78232a1e0e9afc8635ee8fe27a49157d
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem 'sys-proctable'
4
+
5
+ # Add dependencies to develop your gem here.
6
+ # Include everything needed to run rake, tests, features, etc.
7
+ group :development, :test do
8
+ gem "debugger"
9
+ gem "rspec", "~> 2.14.0"
10
+ gem "rdoc", "~> 3.12"
11
+ gem "bundler", "~> 1.0"
12
+ gem "jeweler", "~> 1.8.7"
13
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,73 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ addressable (2.3.5)
5
+ builder (3.2.2)
6
+ columnize (0.3.6)
7
+ debugger (1.6.2)
8
+ columnize (>= 0.3.1)
9
+ debugger-linecache (~> 1.2.0)
10
+ debugger-ruby_core_source (~> 1.2.3)
11
+ debugger-linecache (1.2.0)
12
+ debugger-ruby_core_source (1.2.3)
13
+ diff-lcs (1.2.4)
14
+ faraday (0.8.8)
15
+ multipart-post (~> 1.2.0)
16
+ git (1.2.6)
17
+ github_api (0.10.1)
18
+ addressable
19
+ faraday (~> 0.8.1)
20
+ hashie (>= 1.2)
21
+ multi_json (~> 1.4)
22
+ nokogiri (~> 1.5.2)
23
+ oauth2
24
+ hashie (2.0.5)
25
+ highline (1.6.20)
26
+ httpauth (0.2.0)
27
+ jeweler (1.8.8)
28
+ builder
29
+ bundler (~> 1.0)
30
+ git (>= 1.2.5)
31
+ github_api (= 0.10.1)
32
+ highline (>= 1.6.15)
33
+ nokogiri (= 1.5.10)
34
+ rake
35
+ rdoc
36
+ json (1.8.1)
37
+ jwt (0.1.8)
38
+ multi_json (>= 1.5)
39
+ multi_json (1.8.2)
40
+ multi_xml (0.5.5)
41
+ multipart-post (1.2.0)
42
+ nokogiri (1.5.10)
43
+ oauth2 (0.9.2)
44
+ faraday (~> 0.8)
45
+ httpauth (~> 0.2)
46
+ jwt (~> 0.1.4)
47
+ multi_json (~> 1.0)
48
+ multi_xml (~> 0.5)
49
+ rack (~> 1.2)
50
+ rack (1.5.2)
51
+ rake (10.1.0)
52
+ rdoc (3.12.2)
53
+ json (~> 1.4)
54
+ rspec (2.14.1)
55
+ rspec-core (~> 2.14.0)
56
+ rspec-expectations (~> 2.14.0)
57
+ rspec-mocks (~> 2.14.0)
58
+ rspec-core (2.14.7)
59
+ rspec-expectations (2.14.3)
60
+ diff-lcs (>= 1.1.3, < 2.0)
61
+ rspec-mocks (2.14.4)
62
+ sys-proctable (0.9.3)
63
+
64
+ PLATFORMS
65
+ ruby
66
+
67
+ DEPENDENCIES
68
+ bundler (~> 1.0)
69
+ debugger
70
+ jeweler (~> 1.8.7)
71
+ rdoc (~> 3.12)
72
+ rspec (~> 2.14.0)
73
+ sys-proctable
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2013 Philippe Le Rohellec
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,31 @@
1
+ = MemoryTracker
2
+
3
+ MemoryTracker collects and analyzes memory usage data for each controller action of a Rails application. It's implemented as a Rails engine with a middleware that captures stats before and after each query.
4
+
5
+ == How?
6
+ It uses GC.stats (Ruby garbage collector) to find how many objects were allocated, how many heaps were created and how many times the garbage collector ran.
7
+
8
+ == Features
9
+ * Collect stats on each controller action memory consumption.
10
+ * Write a log file with GC.stat data.
11
+ * The engine includes a couple of web pages that shows a summary of memory utilization per controller action.
12
+
13
+ == Enable MemoryTracker in a Rails application
14
+ In config/routes.rb
15
+ mount MemoryTracker::Engine, at: "/memory_tracker"
16
+
17
+ == Contributing to memory_tracker
18
+
19
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
20
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
21
+ * Fork the project.
22
+ * Start a feature/bugfix branch.
23
+ * Commit and push until you are happy with your contribution.
24
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
25
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
26
+
27
+ == Copyright
28
+
29
+ Copyright (c) 2013 Philippe Le Rohellec. See LICENSE.txt for
30
+ further details.
31
+
data/Rakefile ADDED
@@ -0,0 +1,50 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "memory_tracker"
18
+ gem.homepage = "http://github.com/plerohellec/memory_tracker"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Rails memory allocations tracker}
21
+ gem.description = %Q{Collect and analyze memory usage data for each individual Rails action controller.}
22
+ gem.email = "philippe@lerohellec.com"
23
+ gem.authors = ["Philippe Le Rohellec"]
24
+ # dependencies defined in Gemfile
25
+ gem.add_dependency 'sys-proctable'
26
+ end
27
+ Jeweler::RubygemsDotOrgTasks.new
28
+
29
+ require 'rspec/core'
30
+ require 'rspec/core/rake_task'
31
+ RSpec::Core::RakeTask.new(:spec) do |spec|
32
+ spec.pattern = FileList['spec/**/*_spec.rb']
33
+ end
34
+
35
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
36
+ spec.pattern = 'spec/**/*_spec.rb'
37
+ spec.rcov = true
38
+ end
39
+
40
+ task :default => :spec
41
+
42
+ require 'rdoc/task'
43
+ Rake::RDocTask.new do |rdoc|
44
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
45
+
46
+ rdoc.rdoc_dir = 'rdoc'
47
+ rdoc.title = "memory_tracker #{version}"
48
+ rdoc.rdoc_files.include('README*')
49
+ rdoc.rdoc_files.include('lib/**/*.rb')
50
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1,24 @@
1
+ module MemoryTracker
2
+ class DashboardsController < ActionController::Base
3
+
4
+ layout 'memory_tracker'
5
+
6
+ def index
7
+ stats = MemoryTracker.instance.stats(:memory)
8
+ @data = stats.to_a
9
+ sort_by = params[:sort_by] ? params[:sort_by].to_sym : :count
10
+ if @data.any? && @data.first.keys.include?(sort_by)
11
+ @data = @data.sort{ |a,b| b[sort_by].to_f/b[:num] <=> a[sort_by].to_f/a[:num] }
12
+ end
13
+
14
+ respond_to do |format|
15
+ format.json do
16
+ render :json => @data.to_json
17
+ end
18
+ format.html do
19
+ render
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,7 @@
1
+ module MemoryTracker
2
+ module DashboardsHelper
3
+ def link_to_dashboards(name)
4
+ link_to(name, memory_tracker.dashboards_path(:sort_by => name))
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,29 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>MemoryTracker Dashboard</title>
5
+ <%= csrf_meta_tag %>
6
+
7
+ <style>
8
+ body, td, th {
9
+ font-family: Verdana, Arial, sans-serif;
10
+ font-size: 14px }
11
+ th {
12
+ text-align: right;
13
+ font: bold
14
+ }
15
+ tr { border-top: 1px solid #444;
16
+ text-align: right
17
+ }
18
+ td { text-align: right }
19
+ </style>
20
+ </head>
21
+ <body>
22
+
23
+ <%= yield %>
24
+
25
+ <br>
26
+ <p><%= link_to 'Back to the application', '/' %></p>
27
+
28
+ </body>
29
+ </html>
@@ -0,0 +1,21 @@
1
+ <% keys = [ :rss, :vsize, :count, :total_allocated_object, :heap_used ] %>
2
+ <table width="100%">
3
+ <tr>
4
+ <th><%= link_to_dashboards('Controller') %></th>
5
+ <th><%= link_to_dashboards('Action') %></th>
6
+ <th><%= link_to_dashboards('num') %></th>
7
+ <% keys.each do |key| %>
8
+ <th><%= link_to_dashboards(key) %></th>
9
+ <% end %>
10
+ </tr>
11
+ <% @data.each do |row| %>
12
+ <tr>
13
+ <td><%= "#{row[:controller]}" %></td>
14
+ <td><%= "#{row[:action]}" %></td>
15
+ <td><%= row[:num] %></td>
16
+ <% keys.each do |key| %>
17
+ <td><%= '%.3f' % (row[key].to_f/row[:num]) %></td>
18
+ <% end %>
19
+ </tr>
20
+ <% end %>
21
+ </table>
data/config/routes.rb ADDED
@@ -0,0 +1,3 @@
1
+ MemoryTracker::Engine.routes.draw do
2
+ get '/' => 'dashboards#index', :as => :dashboards
3
+ end
data/docs/design.rb ADDED
@@ -0,0 +1,111 @@
1
+ module MemoryTracker
2
+ class Middleware
3
+ end
4
+
5
+ class Engine
6
+ def initialize_stores
7
+ MemoryTracker.instance.add_store(InMemoryStore.new)
8
+ MemoryTracker.instance.add_store(LogfileStore.new)
9
+ end
10
+ end
11
+
12
+ class MemoryTracker
13
+ GCSTAT_LOGFILE = "#{Rails.root}/log/gcstat.log"
14
+
15
+ include Singleton
16
+
17
+ def stores
18
+ @stores ||= {}
19
+ end
20
+
21
+ def add_stores
22
+ end
23
+
24
+ def start_request(env)
25
+ @request = Request.new(env)
26
+ end
27
+
28
+ def end_request(status)
29
+ @request.close
30
+ stores.each { |store| store.push(@request) }
31
+ end
32
+ end
33
+
34
+ class Request
35
+ attr_reader :controller, :action, :gcstat_delta
36
+ def initialize(env)
37
+ @start_gcstat = GcStat.new(rss, vsize)
38
+ end
39
+
40
+ def close
41
+ @end_gcstat = GcStat.new(rss, vsize)
42
+ @gcstat_delta = GcStatDelta.new(@start_gcstat, @end_gcstat)
43
+ end
44
+ end
45
+
46
+ class GcStat
47
+ attr_reader :stat
48
+ def initialize(rss, vsize)
49
+ @stat = GC.stat.merge({ :rss => rss, :vsize => vsize})
50
+ end
51
+ end
52
+
53
+ class GcStatDelta
54
+ def initialize(before, after)
55
+ @stat = after.inject({}) do |h, (k, v)|
56
+ h[k] = after[k] - before [k]
57
+ h
58
+ end
59
+ end
60
+ end
61
+
62
+ module Stores
63
+ class InMemoryStore::Manager
64
+ def initialize(window_length)
65
+ @window1 = StatInterval.new(Time.now - length/2)
66
+ @window2 = StatInterval.new(Time.now)
67
+ end
68
+
69
+ def name
70
+ :memory
71
+ end
72
+
73
+ def rotate_windows
74
+ if @window1.start_time + length > Time.now
75
+ @window1 = @window2
76
+ @window2 = Window.new(Time.now)
77
+ end
78
+ end
79
+
80
+ def push
81
+ rotate_windows
82
+ @window1.push(request)
83
+ @window2.push(request)
84
+ end
85
+
86
+ def stats
87
+ @window1.stats
88
+ end
89
+ end
90
+
91
+ class InMemoryStore::StatInterval
92
+ attr_reader :start_time, :duration, :size
93
+ attr_accessor :data # {}
94
+
95
+ def initialize
96
+ @data = {}
97
+ end
98
+
99
+ def push(request)
100
+ @size += 1
101
+ delta = request.gcstat_delta
102
+ delta.each do |key|
103
+ increment_action_counter(request.controller, request.action, key, delta[key])
104
+ end
105
+ end
106
+
107
+ def increment_action_counter(controller, action, key, value)
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,3 @@
1
+ require 'memory_tracker/memory_tracker'
2
+ require 'memory_tracker/engine' if defined?(Rails)
3
+
@@ -0,0 +1,23 @@
1
+ module MemoryTracker
2
+ class Engine < Rails::Engine
3
+
4
+ isolate_namespace MemoryTracker
5
+
6
+ engine_base_dir = File.expand_path("../../..", __FILE__)
7
+ app_base_dir = File.expand_path("../../../app", __FILE__)
8
+ lib_base_dir = File.expand_path("../../../lib", __FILE__)
9
+
10
+ config.autoload_paths << lib_base_dir
11
+
12
+ initializer "memory_tracker.add_middleware" do |app|
13
+ app.middleware.use MemoryTracker::Middleware
14
+ end
15
+
16
+ initializer "memory_tracker.setup_stores" do |app|
17
+ MemoryTracker.instance.add_store(Stores::UrlLogfileStore.new(ActiveSupport::BufferedLogger, "#{Rails.root}/log/memtracker_urls.log"))
18
+ MemoryTracker.instance.add_store(Stores::GcstatLogfileStore.new(ActiveSupport::BufferedLogger, "#{Rails.root}/log/memtracker_gcstat.log"))
19
+ MemoryTracker.instance.add_store(Stores::InMemoryStore::Manager.new)
20
+ end
21
+ end
22
+ end
23
+
@@ -0,0 +1,14 @@
1
+ module MemoryTracker
2
+ class Env
3
+ attr_reader :path, :controller, :action
4
+
5
+ def initialize(env)
6
+ @path = env['PATH_INFO']
7
+
8
+ routes_env = { :method => env['REQUEST_METHOD'] }
9
+ request = Rails.application.routes.recognize_path(env['REQUEST_URI'], routes_env)
10
+ @controller = request[:controller]
11
+ @action = request[:action]
12
+ end
13
+ end
14
+ end