tuttle 0.0.4 → 0.0.5

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