tuttle 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -0
  3. data/app/controllers/tuttle/active_model_serializers_controller.rb +26 -0
  4. data/app/controllers/tuttle/active_support_controller.rb +26 -0
  5. data/app/controllers/tuttle/cancancan_controller.rb +1 -0
  6. data/app/controllers/tuttle/gems_controller.rb +4 -0
  7. data/app/controllers/tuttle/home_controller.rb +1 -3
  8. data/app/controllers/tuttle/performance_tuning_controller.rb +14 -0
  9. data/app/controllers/tuttle/rails_controller.rb +20 -18
  10. data/app/controllers/tuttle/request_controller.rb +16 -0
  11. data/app/controllers/tuttle/ruby_controller.rb +2 -0
  12. data/app/helpers/tuttle/application_helper.rb +8 -0
  13. data/app/views/layouts/tuttle/application.html.erb +22 -7
  14. data/app/views/tuttle/active_model_serializers/index.html.erb +86 -0
  15. data/app/views/tuttle/active_model_serializers/index9.html.erb +68 -0
  16. data/app/views/tuttle/active_support/dependencies.html.erb +72 -0
  17. data/app/views/tuttle/active_support/index.html.erb +69 -0
  18. data/app/views/tuttle/{rails → active_support}/inflectors.html.erb +0 -0
  19. data/app/views/tuttle/active_support/time_zones.html.erb +24 -0
  20. data/app/views/tuttle/cancancan/rule_tester.html.erb +1 -20
  21. data/app/views/tuttle/devise/index.html.erb +62 -9
  22. data/app/views/tuttle/gems/get_process_mem.html.erb +92 -0
  23. data/app/views/tuttle/gems/other.html.erb +102 -0
  24. data/app/views/tuttle/home/index.html.erb +1 -1
  25. data/app/views/tuttle/performance_tuning/index.html.erb +79 -0
  26. data/app/views/tuttle/rails/assets.html.erb +38 -43
  27. data/app/views/tuttle/rails/database.html.erb +2 -2
  28. data/app/views/tuttle/rails/engines.html.erb +40 -0
  29. data/app/views/tuttle/rails/generators.html.erb +74 -0
  30. data/app/views/tuttle/rails/index.html.erb +62 -86
  31. data/app/views/tuttle/rails/models.html.erb +16 -1
  32. data/app/views/tuttle/rails/schema_cache.html.erb +3 -1
  33. data/app/views/tuttle/request/index.html.erb +37 -0
  34. data/app/views/tuttle/ruby/index.html.erb +65 -0
  35. data/config/routes.rb +19 -3
  36. data/lib/tuttle.rb +6 -1
  37. data/lib/tuttle/engine.rb +23 -61
  38. data/lib/tuttle/instrumenter.rb +38 -0
  39. data/lib/tuttle/middleware/request_profiler.rb +80 -0
  40. data/lib/tuttle/ruby_prof/fast_call_stack_printer.rb +164 -0
  41. data/lib/tuttle/version.rb +1 -1
  42. metadata +21 -21
  43. data/app/assets/images/tuttle/favicon.ico +0 -0
  44. data/app/assets/javascripts/tuttle/application.js +0 -12
  45. data/app/assets/stylesheets/tuttle/application.css +0 -71
@@ -46,7 +46,22 @@ No models were found in this application
46
46
  Stored attributes = <%= model.stored_attributes %><br/>
47
47
  Save callbacks = <%= model.send('_save_callbacks').count %><br/>
48
48
  Associations = <%= model.reflect_on_all_associations.count %><br/>
49
- Reflections = <%= model.reflections.keys.to_s %><br/>
49
+ Reflections = <%= model.reflections.keys.sort!.to_s %><br/>
50
+ Validators:<br/>
51
+ <table class="table table-compact">
52
+ <tr>
53
+ <th>Kind</th>
54
+ <th>Attributes</th>
55
+ <th>Options</th>
56
+ </tr>
57
+ <% model.validators.each do |validator| %>
58
+ <tr>
59
+ <td><%= validator.kind %></td>
60
+ <td><%= validator.attributes if validator.respond_to?(:attributes) %></td>
61
+ <td><%= validator.options %></td>
62
+ </tr>
63
+ <% end %>
64
+ </table>
50
65
 
51
66
  <% next if model.abstract_class? || ! model.table_exists? %>
52
67
  <% if model.respond_to?(:find_by_statement_cache) %>
@@ -42,7 +42,9 @@
42
42
  <p>Note: The schema cache is only loaded once on application startup. During development, with code reloading, it is likely to be reset.</p>
43
43
  <% end %>
44
44
 
45
- <% cached_tablenames = @connection_schema_cache.instance_variable_get(:@tables) %>
45
+ <%- # Rails 5.x switched to @data_sources from @tables for instance variable %>
46
+ <% cached_tablenames = @connection_schema_cache.instance_variable_defined?(:@data_sources) && instance_variable_get(:@data_sources) ||
47
+ @connection_schema_cache.instance_variable_get(:@tables) || [] %>
46
48
  <% cached_columns = @connection_schema_cache.instance_variable_get(:@columns) %>
47
49
  <% cached_columns_hash = @connection_schema_cache.instance_variable_get(:@columns_hash) %>
48
50
  <% cached_primary_keys = @connection_schema_cache.instance_variable_get(:@primary_keys) %>
@@ -0,0 +1,37 @@
1
+ <h1>Request Info</h1>
2
+
3
+ <h2>Session Variables</h2>
4
+ <% # TODO:
5
+ # show session store configuration (class and options)
6
+ # total session size
7
+ # session variable actions - unset, set(string), new(key, string)
8
+ # Cookie header string size, cookie count
9
+ # cookie actions (client and/or server) - clear, update, new(with paths, secure, etc.)
10
+ # Do secure cookies get accessed differently?
11
+ %>
12
+ <table class="table table-condensed">
13
+ <tr>
14
+ <th>Key</th>
15
+ <th>Value</th>
16
+ </tr>
17
+ <% @session_hash.each do |key, value| %>
18
+ <tr>
19
+ <td><%= key %></td>
20
+ <td><%= value %></td>
21
+ </tr>
22
+ <% end %>
23
+ </table>
24
+
25
+ <h2>Cookies</h2>
26
+ <table class="table table-condensed">
27
+ <tr>
28
+ <th>Key</th>
29
+ <th>Value</th>
30
+ </tr>
31
+ <% @cookies_hash.each do |key, value| %>
32
+ <tr>
33
+ <td><%= key %></td>
34
+ <td><%= value %></td>
35
+ </tr>
36
+ <% end %>
37
+ </table>
@@ -3,8 +3,12 @@
3
3
  <ul class="nav nav-tabs" role="tablist">
4
4
  <li class="active"><a href="#ruby" role="tab" data-toggle="tab">Ruby</a></li>
5
5
  <li><a href="#environment" role="tab" data-toggle="tab">Environment</a></li>
6
+ <li><a href="#rbconfig" role="tab" data-toggle="tab">Ruby Config</a></li>
6
7
  <li><a href="#bundler" role="tab" data-toggle="tab">Bundler</a></li>
7
8
  <li><a href="#gc" role="tab" data-toggle="tab">Garbage Collection</a></li>
9
+ <li><a href="#load_path" role="tab" data-toggle="tab">Load Path</a></li>
10
+ <li><a href="#loaded_features" role="tab" data-toggle="tab">Loaded Features</a></li>
11
+
8
12
  </ul>
9
13
 
10
14
  <div class="tab-content">
@@ -19,6 +23,45 @@
19
23
  <dt>RUBY_DESCRIPTION</dt><dd><%= RUBY_DESCRIPTION %></dd>
20
24
  <dt>RUBY_COPYRIGHT</dt><dd><%= RUBY_COPYRIGHT %></dd>
21
25
  </dl>
26
+ <h3>RUBYOPT</h3>
27
+ <p>The RUBYOPT environment variable can be set pass command line options to Ruby.</p>
28
+ <pre><%= ENV.fetch('RUBYOPT') { 'Not set'} %></pre>
29
+
30
+ <h3>RubyVM::DEFAULT_PARAMS</h3>
31
+ <p>The Ruby VM default parameters are the default values for this version of Ruby. They are not configurable.</p>
32
+ <table class="table table-condensed" style="width: auto;">
33
+ <tr>
34
+ <th>Setting</th>
35
+ <th>Value</th>
36
+ </tr>
37
+ <% RubyVM::DEFAULT_PARAMS.each do |k, v| %>
38
+ <tr>
39
+ <td><%= k.inspect %></td>
40
+ <td><%= v %></td>
41
+ </tr>
42
+ <% end %>
43
+ </table>
44
+
45
+ <h3>RubyVM::OPTS</h3>
46
+ <p>The RubyVM OPTS are build-time settings specified when Ruby is compiled.</p>
47
+ <pre><%= RubyVM::OPTS.inspect %></pre>
48
+ </div>
49
+
50
+ <div class="tab-pane" id="rbconfig">
51
+ <h3>RbConfig</h3>
52
+ <p>The RbConfig records the compile-time values of... </p>
53
+ <table class="table table-condensed" style="width: auto;">
54
+ <tr>
55
+ <th>Setting</th>
56
+ <th>Value</th>
57
+ </tr>
58
+ <% RbConfig::CONFIG.each do |k, v| %>
59
+ <tr>
60
+ <td><%= k %></td>
61
+ <td><code><%= v %></code></td>
62
+ </tr>
63
+ <% end %>
64
+ </table>
22
65
  </div>
23
66
 
24
67
  <div class="tab-pane" id="environment">
@@ -58,4 +101,26 @@
58
101
  </dl>
59
102
  </div>
60
103
 
104
+ <div class="tab-pane" id="load_path">
105
+ <p>
106
+ The the <code>$LOAD_PATH</code> (<%= $LOAD_PATH.size %> entries) lists
107
+ the directories that will be searched when code is loaded with
108
+ <code>require</code> or <code>load</code>.
109
+ </p>
110
+ <ul>
111
+ <%- $LOAD_PATH.each do |dir_name| %>
112
+ <li><%= dir_name %></li>
113
+ <%- end %>
114
+ </ul>
115
+ </div>
116
+
117
+ <div class="tab-pane" id="loaded_features">
118
+ <p>The following <b><%= $LOADED_FEATURES.size %></b> files have been loaded via <code>require</code> at some point.</p>
119
+ <ul>
120
+ <%- $LOADED_FEATURES.each do |filename| %>
121
+ <li><%= filename %></li>
122
+ <%- end %>
123
+ </ul>
124
+ </div>
125
+
61
126
  </div>
@@ -4,17 +4,33 @@ Tuttle::Engine.routes.draw do
4
4
 
5
5
  namespace :rails do
6
6
  get '', :action => :index
7
- get :controllers, :models, :database, :schema_cache, :helpers, :assets, :routes, :instrumentation, :inflectors, :cache
7
+ get :controllers, :models, :database, :schema_cache, :helpers, :assets,
8
+ :routes, :instrumentation, :cache, :generators, :engines
8
9
  end
9
10
 
10
11
  namespace :ruby do
11
12
  get '', :action => :index
12
- get :tuning, :miscellaneous
13
+ get :miscellaneous, :tuning
13
14
  end
14
15
 
15
16
  namespace :gems do
16
17
  get '', :action => :index
17
- get :http_clients, :json, :other
18
+ get :get_process_mem, :http_clients, :json, :other
19
+ end
20
+
21
+ namespace :active_support do
22
+ get '', :action => :index
23
+ get :dependencies, :inflectors, :time_zones
24
+ end
25
+
26
+ get '/performance_tuning' => 'performance_tuning#index'
27
+
28
+ get '/request' => 'request#index'
29
+
30
+ if defined?(ActiveModelSerializers)
31
+ get '/active_model_serializers' => 'active_model_serializers#index' # 0.10.x?
32
+ elsif defined?(ActiveModel::Serializer)
33
+ get '/active_model_serializers' => 'active_model_serializers#index' # 0.9.x?
18
34
  end
19
35
 
20
36
  if defined?(Paperclip)
@@ -1,5 +1,8 @@
1
- require 'tuttle/engine'
2
1
  require 'tuttle/version'
2
+ require 'tuttle/engine' if defined?(Rails)
3
+
4
+ # TODO: clean this up so that mattr_accessor is not needed
5
+ require 'active_support/core_ext/module/attribute_accessors'
3
6
 
4
7
  module Tuttle
5
8
 
@@ -10,6 +13,8 @@ module Tuttle
10
13
  @@automount_engine = @@enabled = nil
11
14
  @@track_notifications = false
12
15
 
16
+ autoload :Instrumenter, 'tuttle/instrumenter'
17
+
13
18
  def self.setup
14
19
  yield self
15
20
  end
@@ -1,88 +1,50 @@
1
+ require 'rails/engine'
2
+
1
3
  module Tuttle
2
4
  class Engine < ::Rails::Engine
3
5
  isolate_namespace Tuttle
4
- engine_name :tuttle
5
6
 
6
- config.tuttle = ActiveSupport::OrderedOptions.new
7
+ mattr_accessor :reload_needed, :session_start, :session_id
7
8
 
8
- attr_accessor :reload_needed
9
- attr_accessor :events, :event_counts, :cache_events
10
- attr_accessor :session_start, :session_id
9
+ mattr_reader :logger
11
10
 
12
- attr_reader :logger
11
+ config.tuttle = ActiveSupport::OrderedOptions.new
13
12
 
14
- initializer :tuttle_assets_precompile do |app|
15
- app.config.assets.precompile += %w(tuttle/application.css tuttle/application.js tuttle/favicon.ico)
13
+ config.before_configuration do
14
+ Tuttle::Engine.session_start = Time.now
15
+ Tuttle::Engine.session_id = SecureRandom.uuid
16
16
  end
17
17
 
18
- initializer :tuttle_set_configuration do |app|
19
- app.config.tuttle.each do |k,v|
18
+ initializer 'tuttle' do |app|
19
+ app.config.tuttle.each do |k, v|
20
20
  Tuttle.send("#{k}=", v)
21
21
  end
22
- # Tuttle will be automatically enabled in development if not configured explicitly
23
- Tuttle.enabled= Rails.env.development? if Tuttle.enabled==nil
24
- Tuttle.automount_engine= true if Tuttle.automount_engine==nil
25
- end
26
-
27
- initializer :tuttle_startup do
28
- next unless Tuttle.enabled
29
22
 
30
- Tuttle::Engine.session_start = Time.now
31
- Tuttle::Engine.session_id = SecureRandom.uuid
32
- @logger = ::Logger.new("#{Rails.root}/log/tuttle.log")
33
- @logger.info('Tuttle engine started')
34
- end
23
+ # Tuttle will be enabled automatically in development if not configured explicitly
24
+ Tuttle.enabled = Rails.env.development? if Tuttle.enabled.nil?
35
25
 
36
- initializer :tuttle_track_reloads, group: :all do
37
26
  next unless Tuttle.enabled
38
27
 
39
- ActionDispatch::Reloader.to_prepare do
40
- Tuttle::Engine.logger.warn('ActionDispatch::Reloader called to_prepare') unless Tuttle::Engine.reload_needed.nil?
41
- Tuttle::Engine.reload_needed = true
42
- end
43
- end
28
+ @@logger = ::Logger.new("#{Rails.root}/log/tuttle.log")
29
+ Tuttle::Engine.logger.info('Tuttle engine started')
44
30
 
45
- initializer :tuttle_global_instrumenter, group: :all do
46
- Tuttle::Engine.events = []
47
- Tuttle::Engine.event_counts = Hash.new(0)
48
- Tuttle::Engine.cache_events = []
49
- next unless Tuttle.enabled && Tuttle.track_notifications
31
+ Tuttle.automount_engine = true if Tuttle.automount_engine == nil
50
32
 
51
- # For now, only instrument non-production mode
52
- unless Rails.env.production?
53
- ActiveSupport::Notifications.subscribe(/.*/) do |*args|
54
- event = ActiveSupport::Notifications::Event.new(*args)
55
- Tuttle::Engine.events << event
56
- Tuttle::Engine.event_counts[event.name] += 1
33
+ if Tuttle.automount_engine
34
+ Rails.application.routes.prepend do
35
+ Tuttle::Engine.logger.info('Auto-mounting /tuttle routes')
36
+ mount Tuttle::Engine, at: "tuttle"
57
37
  end
58
38
  end
59
39
 
60
- # Note: For Rails < 4.2 instrumentation is not enabled by default. Hitting the cache inspector page will enable it for that session.
61
- Tuttle::Engine.logger.info('Initializing cache_read subscriber')
62
- ActiveSupport::Notifications.subscribe('cache_read.active_support') do |*args|
63
- cache_call_location = caller_locations.detect { |cl| cl.path.start_with?("#{Rails.root}/app".freeze) }
64
- event = ActiveSupport::Notifications::Event.new(*args)
65
-
66
- Tuttle::Engine.logger.info("Cache Read called: #{cache_call_location.path} on line #{cache_call_location.lineno} :: #{event.payload.inspect}")
67
-
68
- event.payload.merge!({:call_location_path => cache_call_location.path, :call_location_lineno => cache_call_location.lineno })
69
- Tuttle::Engine.cache_events << event
70
- end
71
- ActiveSupport::Notifications.subscribe('cache_generate.active_support') do |*args|
72
- Tuttle::Engine.logger.info('Cache Generate called')
40
+ if Tuttle.track_notifications
41
+ Tuttle::Instrumenter.initialize_tuttle_instrumenter
73
42
  end
74
43
 
75
44
  end
76
45
 
77
- initializer :tuttle_automounter do
78
- next unless Tuttle.enabled
79
-
80
- if Tuttle.automount_engine
81
- Rails.application.routes.prepend do
82
- Tuttle::Engine.logger.info('Auto-mounting /tuttle routes')
83
- mount Tuttle::Engine, at: "tuttle"
84
- end
85
- end
46
+ config.to_prepare do
47
+ Tuttle::Engine.reload_needed = true
86
48
  end
87
49
 
88
50
  end
@@ -0,0 +1,38 @@
1
+ module Tuttle
2
+ class Instrumenter
3
+
4
+ mattr_accessor :events, :event_counts, :cache_events
5
+ @@events = []
6
+ @@event_counts = Hash.new(0)
7
+ @@cache_events = []
8
+
9
+ def self.initialize_tuttle_instrumenter
10
+ # For now, only instrument non-production mode
11
+ unless Rails.env.production?
12
+ ActiveSupport::Notifications.subscribe(/.*/) do |*args|
13
+ event = ActiveSupport::Notifications::Event.new(*args)
14
+ Tuttle::Instrumenter.events << event
15
+ Tuttle::Instrumenter.event_counts[event.name] += 1
16
+ end
17
+ end
18
+
19
+ # Note: For Rails < 4.2 instrumentation is not enabled by default. Hitting the cache inspector page will enable it for that session.
20
+ Tuttle::Engine.logger.info('Initializing cache_read subscriber')
21
+ ActiveSupport::Notifications.subscribe('cache_read.active_support') do |*args|
22
+ cache_call_location = caller_locations.detect { |cl| cl.path.start_with?("#{Rails.root}/app".freeze) }
23
+ event = ActiveSupport::Notifications::Event.new(*args)
24
+
25
+ Tuttle::Engine.logger.info("Cache Read called: #{cache_call_location.path} on line #{cache_call_location.lineno} :: #{event.payload.inspect}")
26
+
27
+ event.payload.merge!({:call_location_path => cache_call_location.path, :call_location_lineno => cache_call_location.lineno })
28
+ Tuttle::Instrumenter.cache_events << event
29
+ end
30
+
31
+ ActiveSupport::Notifications.subscribe('cache_generate.active_support') do |*args|
32
+ Tuttle::Engine.logger.info('Cache Generate called')
33
+ end
34
+
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,80 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Tuttle
4
+ module Middleware
5
+ class RequestProfiler
6
+
7
+ def initialize(app)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ query_string = env['QUERY_STRING']
13
+
14
+ tuttle_profiler_action = /tuttle\-profiler=([\w\-]*)/.match(query_string) { $1.to_sym }
15
+
16
+ case tuttle_profiler_action
17
+ when :'memory_profiler', :'memory'
18
+ profile_memory(env, query_string)
19
+ when :'ruby-prof', :'cpu'
20
+ profile_cpu(env, query_string)
21
+ else
22
+ @app.call(env)
23
+ end
24
+
25
+ end
26
+
27
+ private
28
+
29
+ def profile_memory(env, query_string)
30
+ require 'memory_profiler'
31
+
32
+ query_params = Rack::Utils.parse_nested_query(query_string)
33
+ options = {
34
+ :ignore_files => query_params['memory_profiler_ignore_files'],
35
+ :allow_files => query_params['memory_profiler_allow_files'],
36
+ }
37
+ options[:top]= Integer(query_params['memory_profiler_top']) if query_params.key?('memory_profiler_top')
38
+
39
+ report = MemoryProfiler.report(options) do
40
+ _,_,body = @app.call(env)
41
+ body.close if body.respond_to? :close
42
+ end
43
+
44
+ result = StringIO.new
45
+ report.pretty_print(result)
46
+
47
+ [200, { 'Content-Type' => 'text/plain' }, ["Report from Tuttle::Middeware::RequestProfiler\n", result.string]]
48
+ end
49
+
50
+ def profile_cpu(env, query_string)
51
+ require 'ruby-prof'
52
+
53
+ data = ::RubyProf::Profile.profile do
54
+ _, _, body = @app.call(env)
55
+ body.close if body.respond_to? :close
56
+ end
57
+
58
+ result = StringIO.new
59
+ rubyprof_printer = /ruby\-prof_printer=([\w]*)/.match(query_string) { $1.to_sym }
60
+ content_type = 'text/html'
61
+
62
+ case rubyprof_printer
63
+ when :flat
64
+ ::RubyProf::FlatPrinter.new(data).print(result)
65
+ content_type = 'text/plain'
66
+ when :graph
67
+ ::RubyProf::GraphHtmlPrinter.new(data).print(result)
68
+ when :fast_stack
69
+ require 'tuttle/ruby_prof/fast_call_stack_printer'
70
+ ::Tuttle::RubyProf::FastCallStackPrinter.new(data).print(result, { :application => env['REQUEST_URI']})
71
+ else
72
+ ::RubyProf::CallStackPrinter.new(data).print(result)
73
+ end
74
+
75
+ [200, { 'Content-Type' => content_type }, [result.string]]
76
+ end
77
+
78
+ end
79
+ end
80
+ end