rails_watcher 0.1.0

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d8df209f203deec625e15a9826fe1a1a922150501df153d359b2a6f7b807f524
4
+ data.tar.gz: 4c6dcb7f591660a6c181e833c86081b96bc8264440cde3fc39d3539849570477
5
+ SHA512:
6
+ metadata.gz: 2fc3665f568e9c19ed8de9b1ddb5c7646095fbf21db7617971e41250ed24197b5a435e1c976f818744e2f2bf65baa1ab32697444d13ba7c0b52299f0f2edfd63
7
+ data.tar.gz: b495b98b57ce2f8f29b3176b619ae0d8a3db6bfebe7b696140579913d2a831a3cf7fecd0f946f7f596fa491c1eafeb1a395ffd2be02247095eca962828d24d7b
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2019 Zhang Kang
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.md ADDED
@@ -0,0 +1,56 @@
1
+ # Rails Watcher
2
+ Help you benchmark / analyze / understand your Rails application.
3
+
4
+ ## Usage
5
+ Add `rails_watcher` to your `Gemfile`.
6
+
7
+ ```ruby
8
+ # rails_watcher will slow down your application, suggest only enable on development environment.
9
+ gem 'rails_watcher', require: ENV['RAILS_WATCHER'], group: :development
10
+ ```
11
+
12
+ Start your Rails application with `RAILS_WATCHER`.
13
+ ```sh
14
+ $ RAILS_WATCHER=true rails s
15
+ ```
16
+
17
+ Then make some requests to your application, if the response time slow than 10 ms (could be configured), `Rails Watcher` will capture the all details for your request.
18
+
19
+ By default, `Rails Watcher` will save captured data to `#{Rails.root}/tmp/rails_watcher`.
20
+
21
+ ## View Result
22
+ If you use default configuration of `Rails Watcher`, you could use `rails_watcher_viewer` to view result:
23
+ ```sh
24
+ $ gem install -N rails_watcher_viewer
25
+ $ cd application_folder/tmp/rails_watcher
26
+ $ rails_watcher_viewer
27
+ ```
28
+ View `localhost:4567`, you could see the list of your requests:
29
+ [!list]()
30
+ You can navigate to details page, the `Expensive methods` table show the slow methods ordered by `net cost`:
31
+ [!detail1]()
32
+ ### How to calculate `net cost`
33
+ ```ruby
34
+ @a = 1
35
+ def sample_method
36
+ sleep @a
37
+ inner_method()
38
+ @a += 1
39
+ end
40
+
41
+ def inner_method
42
+ sleep @a * 3
43
+ end
44
+
45
+ sample_method() # will take 1 + 3 = 4 seconds
46
+ sample_method() # will take 2 + 6 = 8 seconds
47
+ ```
48
+ The net cost of sample_method should be 1 + 2 = 3 seconds, the total cost of sample_method should be 4 + 8 = 12 seconds.
49
+
50
+ [!detail2]()
51
+ The `Call Stack` section show the call stacks of your application.
52
+ Please notice that, it only show the methods inside your application plus any methods you want to watch, which means the Ruby build-in methods and methods inside any gem will not be captured by default. It is great helpful for trouble shooting.
53
+
54
+
55
+ ## License
56
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,34 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'RailsWatcher'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+
18
+
19
+
20
+
21
+
22
+ require 'bundler/gem_tasks'
23
+
24
+ require 'rake/testtask'
25
+
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << 'lib'
28
+ t.libs << 'test'
29
+ t.pattern = 'test/**/*_test.rb'
30
+ t.verbose = false
31
+ end
32
+
33
+
34
+ task default: :test
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+ require 'rails_watcher/engine'
3
+ require 'rails_watcher/configuration'
4
+ require 'rails_watcher/const_modifier'
5
+ require 'rails_watcher/call_stack'
6
+ require 'rails_watcher/patches'
7
+ require 'rails_watcher/watcher_middleware'
8
+
9
+ module RailsWatcher
10
+
11
+ autoload :DefaultInstanceHandler, 'rails_watcher/default_instance_handler'
12
+
13
+ def self.my_watch_begins
14
+ Kernel.singleton_class.prepend Patches::KernelLoad
15
+ end
16
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+ module RailsWatcher
3
+ class CallStack
4
+ class << self
5
+ def get_instance
6
+ Thread.current[:rails_watcher_call_stack_instance]
7
+ end
8
+
9
+ def set_instance request_path
10
+ Thread.current[:rails_watcher_call_stack_instance] = self.new request_path
11
+ end
12
+
13
+ def clear_instance_and_log duration
14
+ instance = self.get_instance
15
+ if duration > RailsWatcher.configuration.request_duration_threshold &&
16
+ RailsWatcher.configuration.ignored_request_path.all? { |path| path !~ instance.request_path }
17
+
18
+ if instance.stack.present?
19
+ instance.duration = duration
20
+ handler = RailsWatcher.configuration.instance_handler
21
+ handler = handler.constantize if handler.is_a? String
22
+
23
+ handler.log instance
24
+ end
25
+ end
26
+ Thread.current[:rails_watcher_call_stack_instance] = nil
27
+ end
28
+ end
29
+
30
+ attr_reader :request_path, :stack, :method_call_table
31
+ attr_reader :db_query_table, :read_cache_table, :render_stack
32
+ attr_accessor :duration
33
+
34
+ def initialize request_path
35
+ @request_path = request_path
36
+ @stack = []
37
+ @current_stack = []
38
+ @method_call_table = {}
39
+ @db_query_table = {}
40
+ @read_cache_table = {}
41
+ @render_stack = []
42
+ end
43
+
44
+ # def save
45
+ # folder_name = "#{Time.now.strftime("%Y%m%d_%H%M%S")}|#{@duration}|#{@request_path.gsub("/", "\\")}"
46
+ # path = File.join(RailsWatcher.configuration.output_path, folder_name)
47
+ # FileUtils.mkdir_p path
48
+ # %w[stack method_call_table db_query_table read_cache_table render_stack].each do |var|
49
+ # filename = File.join path, "#{var}.json"
50
+ # File.open(filename, 'w') { |f| f.puts instance_variable_get(:"@#{var}").to_json }
51
+ # end
52
+ # end
53
+
54
+ def log_method_call tag, &blk
55
+ id = SecureRandom.hex(10)
56
+
57
+ @method_call_table[id] = { tag: tag, children: [] }
58
+ parent_method_call = @current_stack.last
59
+ if parent_method_call
60
+ @method_call_table[parent_method_call][:children] << id
61
+ else
62
+ @stack << id
63
+ end
64
+
65
+ @current_stack.push id
66
+ duration = Benchmark.ms &blk
67
+ @method_call_table[id][:duration] = duration
68
+ @current_stack.delete id
69
+ end
70
+
71
+ def log_render_stack event_type, duration, payload
72
+ @render_stack << [event_type, duration, payload]
73
+ end
74
+
75
+ def log_db_query duration, payload
76
+ current_method_call = @current_stack.last
77
+ @db_query_table[current_method_call] ||= []
78
+ payload[:duration] = duration
79
+ @db_query_table[current_method_call] << payload
80
+ end
81
+
82
+ def log_catche_read duration, payload
83
+ current_method_call = @current_stack.last
84
+ @read_cache_table[current_method_call] ||= []
85
+ payload[:duration] = duration
86
+ @read_cache_table[current_method_call] << payload
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+ module RailsWatcher
3
+
4
+ class Configuration
5
+ attr_accessor *%i[
6
+ ignored_files
7
+ ignored_constants
8
+ ignored_paths
9
+ ignored_methods
10
+ ignored_request_path
11
+ file_constant_mapping
12
+ request_duration_threshold
13
+ rails_methods_i_want_to_watch
14
+ instance_handler
15
+ ]
16
+
17
+ def initialize
18
+ @ignored_files = []
19
+ @ignored_constants = []
20
+ @ignored_paths = []
21
+ @ignored_methods = {}
22
+ @file_constant_mapping = {}
23
+ @ignored_request_path = []
24
+ @request_duration_threshold = 10 # ms
25
+ @rails_methods_i_want_to_watch = {}
26
+ @instance_handler = "RailsWatcher::DefaultInstanceHandler"
27
+ end
28
+ end
29
+
30
+ def self.configuration
31
+ @@configuration ||= RailsWatcher::Configuration.new
32
+ if block_given?
33
+ yield @@configuration
34
+ else
35
+ @@configuration
36
+ end
37
+ end
38
+
39
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+ module RailsWatcher
3
+ class ConstModifier
4
+
5
+ def self.modify const_name, file_path
6
+ @instance ||= self.new
7
+ @instance.modify const_name, file_path
8
+ end
9
+
10
+ def modify const_name, file_path
11
+ const = const_name.constantize rescue return
12
+ configuration = RailsWatcher.configuration
13
+
14
+ ignored_methods = configuration.ignored_methods.with_indifferent_access
15
+
16
+ instance_methods_module = Module.new do
17
+ %w[public private protected].each do |method_type|
18
+ const.send(:"#{method_type}_instance_methods", false).each do |method_name|
19
+ source_code_file_path, _line_no = const.instance_method(method_name).source_location
20
+ next unless source_code_file_path == file_path
21
+ next if ignored_methods.dig(const_name, method_type)&.include?(method_name)
22
+ next if ignored_methods["WeirdMethods"]&.include?(method_name)
23
+
24
+ define_method method_name do |*args, &blk|
25
+ call_stack = RailsWatcher::CallStack.get_instance
26
+ if call_stack
27
+ ret = nil
28
+ method_tag = "#{const_name}##{method_name}"
29
+ call_stack.log_method_call method_tag do
30
+ ret = super(*args, &blk)
31
+ end
32
+ ret
33
+ else
34
+ super(*args, &blk)
35
+ end
36
+ end
37
+ __send__(method_type, method_name)
38
+ end
39
+ end # %w[public private protected].each do |method_type|
40
+ end # instance_methods_module = Module.new do
41
+
42
+ const.prepend instance_methods_module
43
+
44
+ # modifiy class methodes
45
+ private_class_methods = []
46
+
47
+ const.singleton_class.class_eval do
48
+ %w[public private].each do |method_type|
49
+ const.send(:"#{method_type}_methods", false).each do |method_name|
50
+ source_code_file_path, _line_no = const.method(method_name).source_location
51
+ next unless source_code_file_path == file_path
52
+ next if ignored_methods.dig(const_name, :"class_#{method_type}")&.include?(method_name)
53
+ next if ignored_methods["WeirdMethods"]&.include?(method_name)
54
+
55
+ aliased_method_name = :"origin_#{method_name}__rails_watcher"
56
+ alias_method aliased_method_name ,method_name
57
+
58
+ define_method method_name do |*args, &blk|
59
+ call_stack = RailsWatcher::CallStack.get_instance
60
+ if call_stack
61
+ ret = nil
62
+ method_tag = "#{const_name}.#{method_name}"
63
+ call_stack.log_method_call method_tag do
64
+ ret = __send__(aliased_method_name, *args, &blk)
65
+ end
66
+ return ret
67
+ else
68
+ __send__(aliased_method_name, *args, &blk)
69
+ end
70
+ end
71
+ private_class_methods << method_name if method_type == :private
72
+
73
+ end
74
+ end
75
+ end
76
+
77
+ const.class_eval { private_class_method *private_class_methods } if private_class_methods.present?
78
+ end
79
+ end
80
+
81
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+ module RailsWatcher
3
+ module DefaultInstanceHandler
4
+ def self.log call_stack_instacne
5
+ # save files in a new thread
6
+ # so that won't slow down the main thread
7
+ Thread.new do
8
+ @output_path ||= File.join(Rails.root, "tmp/rails_watcher")
9
+ now = Time.now
10
+ folder_name = "#{now.strftime("%Y%m%d_%H%M%S")}|#{call_stack_instacne.request_path.gsub("/", "_")}"
11
+
12
+ path = File.join(@output_path, folder_name)
13
+ FileUtils.mkdir_p path
14
+
15
+ %w[stack method_call_table db_query_table read_cache_table render_stack].each do |var|
16
+ filename = File.join path, "#{var}.json"
17
+ File.open(filename, 'w') do |f|
18
+ f.puts call_stack_instacne.instance_variable_get(:"@#{var}").to_json
19
+ end
20
+ end
21
+
22
+ summary_file = File.join path, "summary.json"
23
+ File.open(summary_file, 'w') do |f|
24
+ summary = {
25
+ duration: call_stack_instacne.duration,
26
+ request_path: call_stack_instacne.request_path,
27
+ logged_time: now
28
+ }
29
+ f.puts summary.to_json
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+ module RailsWatcher
3
+ class Engine < ::Rails::Engine
4
+ isolate_namespace RailsWatcher
5
+
6
+ if ENV["RAILS_WATCHER"] == 'true'
7
+
8
+ initializer "rails_watcher.middleware" do |app|
9
+ app.config.app_middleware.use RailsWatcher::WatcherMiddleware
10
+ end
11
+
12
+ initializer "rails_watcher.modify_rails_methods" do
13
+ ActiveSupport.on_load(:action_controller) do
14
+ prepend Patches::ActionControllerRender
15
+ end
16
+
17
+ ActiveSupport.on_load(:action_view) do
18
+ prepend Patches::ActionViewRender
19
+ end
20
+ end
21
+
22
+ config.after_initialize do
23
+ RailsWatcher.my_watch_begins
24
+
25
+ %w[render_template.action_view render_partial.action_view render_collection.action_view].each do |event_type|
26
+ ActiveSupport::Notifications.subscribe(event_type) do |*args|
27
+ call_stack = RailsWatcher::CallStack.get_instance
28
+ next unless call_stack
29
+ event = ActiveSupport::Notifications::Event.new *args
30
+ payload = event.payload
31
+ payload[:identifier] = payload[:identifier].sub(Rails.root.to_s, "")
32
+ call_stack.log_render_stack event_type, event.duration, payload
33
+ end
34
+ end
35
+
36
+ ActiveSupport::Notifications.subscribe('cache_read.active_support') do |*args|
37
+ call_stack = RailsWatcher::CallStack.get_instance
38
+ next unless call_stack
39
+ event = ActiveSupport::Notifications::Event.new *args
40
+
41
+ call_stack.log_catche_read event.duration, event.payload
42
+ end
43
+
44
+ ActiveSupport::Notifications.subscribe('sql.active_record') do |*args|
45
+ call_stack = RailsWatcher::CallStack.get_instance
46
+ next unless call_stack
47
+ event = ActiveSupport::Notifications::Event.new *args
48
+
49
+ call_stack.log_db_query event.duration, event.payload
50
+ end
51
+
52
+ RailsWatcher.configuration.rails_methods_i_want_to_watch.each do |component, instance_methods|
53
+ ActiveSupport.on_load(component) do
54
+ patch = Module.new do
55
+ instance_methods.each do |method_name|
56
+ define_method(method_name) do |*args, &blk|
57
+ call_stack = RailsWatcher::CallStack.get_instance
58
+ return super(*args, &blk) unless call_stack
59
+
60
+ ret = nil
61
+ call_stack.log_method_call("#{component}##{method_name}") do
62
+ ret = super(*args, &blk)
63
+ end
64
+ ret
65
+ end
66
+ end
67
+ end
68
+ prepend patch
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+ module RailsWatcher
3
+ module Patches
4
+ module KernelLoad
5
+ def load filename, wrap=false
6
+ ret = super
7
+
8
+ relative_path = begin
9
+ pn = Pathname.new filename
10
+ pn.relative_path_from(Rails.root).to_s
11
+ end
12
+
13
+ if !RailsWatcher.configuration.ignored_files.include?(relative_path) &&
14
+ !RailsWatcher.configuration.ignored_paths.any? { |ignored_path| relative_path.start_with?(ignored_path) }
15
+
16
+ const_name = RailsWatcher.configuration.file_constant_mapping[relative_path] ||
17
+ RailsWatcher.guess_const_name(relative_path)
18
+
19
+ return ret if RailsWatcher.configuration.ignored_constants.include? const_name
20
+
21
+ if (Object.const_defined?(const_name) rescue return ret)
22
+ ConstModifier.modify const_name, filename
23
+ elsif Object.const_defined? const_name.upcase
24
+ ConstModifier.modify const_name.upcase, filename
25
+ else
26
+ warn <<~WARNING
27
+ ============== Rails Watcher Warning ==============
28
+ Find constant failed:
29
+ File path: "#{relative_path}"
30
+ Guessed const name: "#{const_name}"
31
+ ============== Rails Watcher Warning ==============
32
+ WARNING
33
+ end
34
+ end
35
+
36
+ ret
37
+ end
38
+ end # module KernelLoad
39
+
40
+ module ActionControllerRender
41
+ def render *args
42
+ call_stack = RailsWatcher::CallStack.get_instance
43
+ if call_stack
44
+ ret = nil
45
+ method_tag = "render"
46
+ call_stack.log_method_call method_tag do
47
+ ret = super
48
+ end
49
+ ret
50
+ else
51
+ super
52
+ end
53
+ end
54
+ end
55
+
56
+ module ActionViewRender
57
+ def render options = {}, locals = {}, &block
58
+ call_stack = RailsWatcher::CallStack.get_instance
59
+ if call_stack
60
+ ret = nil
61
+ method_tag = "render_partial (#{options.to_s[0, 70]})"
62
+ call_stack.log_method_call method_tag do
63
+ ret = super
64
+ end
65
+ ret
66
+ else
67
+ super
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+
74
+ def self.guess_const_name file_path
75
+ @@load_paths ||= (
76
+ Rails.application.config.autoload_paths +
77
+ Rails.application.config.eager_load_paths
78
+ ).map do |autoload_path|
79
+ pn = Pathname.new autoload_path
80
+ pn.relative_path_from(Rails.root).to_s + "/"
81
+ end.to_set
82
+
83
+ @@load_paths.reduce(file_path) do |truncated_file_path, load_path|
84
+ truncated_file_path
85
+ .sub(/^#{load_path}/, "")
86
+ .sub(/^concerns/, "")
87
+ .sub(/\.rb$/, "")
88
+ end.camelize
89
+ end
90
+ end
@@ -0,0 +1,3 @@
1
+ module RailsWatcher
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+ module RailsWatcher
3
+ class WatcherMiddleware
4
+ def initialize app
5
+ @app = app
6
+ end
7
+
8
+ def call env
9
+ CallStack.set_instance env["REQUEST_PATH"]
10
+ status, headers, response = nil, nil, nil
11
+ duration = Benchmark.ms { status, headers, response = @app.call env }
12
+ CallStack.clear_instance_and_log duration
13
+ return status, headers, response
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,68 @@
1
+ if ENV["RAILS_WATCHER"]
2
+ RailsWatcher.configuration do |config|
3
+
4
+ #
5
+ # ignored_files : files don't want to watch, default is `[]`
6
+ #
7
+ # config.ignored_files = ['app/articles_controller.rb']
8
+ #
9
+
10
+
11
+ #
12
+ # ignored_constants: Constants don't want to watch, default is `[]`
13
+ #
14
+ # config.ignored_constants = ['ArticlesController']
15
+ #
16
+
17
+
18
+ #
19
+ # ignored_paths: Paths don't want to watch, default is `[]`
20
+ #
21
+ # config.ignored_constants = ['vendor']
22
+ #
23
+
24
+ #
25
+ # ignored_methods: Methods don't want to watch, default is `{}`
26
+ #
27
+ # config.ignored_methods = {
28
+ # "UsersController" => {
29
+ # public: %i[method_1, method_2]
30
+ # },
31
+ # "WeirdMethods" => %i[some_method_in_any_file]
32
+ # }
33
+ #
34
+
35
+ #
36
+ # ignored_request_path: Request pathes don't want to watch, default is `[]`
37
+ #
38
+ # config.ignored_request_path = []
39
+ #
40
+
41
+
42
+ #
43
+ # request_duration_threshold: don't watch request fast then `request_duration_threshold`ms, default is `10` (ms)
44
+ #
45
+ # config.request_duration_threshold = 10 # ms
46
+ #
47
+
48
+ #
49
+ # rails_methods_i_want_to_watch: rails methods want to watch, default is `{}`
50
+ # key: rails component, value: array of methods
51
+ #
52
+ # config.rails_methods_i_want_to_watch = {
53
+ # action_view: %i[
54
+ # javascript_include_tag
55
+ # stylesheet_link_tag
56
+ # javascript_tag
57
+ # content_for
58
+ # ]
59
+ # }
60
+ #
61
+
62
+ #
63
+ # instance_handler: how to handle result, default is `"RailsWatcher::DefaultInstanceHandler"`, see `rails_watcher/default_instance_handler.rb` for details.
64
+ #
65
+ # config.instance_handler = "RailsWatcher::DefaultInstanceHandler"
66
+ #
67
+ end
68
+ end
@@ -0,0 +1,9 @@
1
+ namespace :rails_watcher do
2
+ desc "Initialize rails watcher configuration file"
3
+ task :install do
4
+ template = File.join(__dir__, 'configuration.rb.tmp')
5
+ target_file = File.join(Rails.root, "config/initializers/rails_watcher_config.rb")
6
+ FileUtils.cp template, target_file
7
+ puts "Done: generated #{target_file}"
8
+ end
9
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rails_watcher
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - piecehealth
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-05-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5'
27
+ description: Help developers to understand, analyze your Rails application.
28
+ email:
29
+ - piecehealth@sina.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - MIT-LICENSE
35
+ - README.md
36
+ - Rakefile
37
+ - lib/rails_watcher.rb
38
+ - lib/rails_watcher/call_stack.rb
39
+ - lib/rails_watcher/configuration.rb
40
+ - lib/rails_watcher/const_modifier.rb
41
+ - lib/rails_watcher/default_instance_handler.rb
42
+ - lib/rails_watcher/engine.rb
43
+ - lib/rails_watcher/patches.rb
44
+ - lib/rails_watcher/version.rb
45
+ - lib/rails_watcher/watcher_middleware.rb
46
+ - lib/tasks/configuration.rb.tmp
47
+ - lib/tasks/rails_watcher_tasks.rake
48
+ homepage: https://github.com/piecehealth/rails_watcher
49
+ licenses:
50
+ - MIT
51
+ metadata: {}
52
+ post_install_message:
53
+ rdoc_options: []
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ requirements: []
67
+ rubygems_version: 3.0.1
68
+ signing_key:
69
+ specification_version: 4
70
+ summary: A profiling tool for Rails application.
71
+ test_files: []