rails-profiler 0.24.0 → 0.26.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 +4 -4
- data/app/assets/builds/profiler.css +24 -0
- data/app/assets/builds/profiler.js +994 -26
- data/app/controllers/profiler/api/console_controller.rb +46 -0
- data/app/controllers/profiler/api/profiles_controller.rb +1 -1
- data/app/controllers/profiler/api/test_runner_controller.rb +115 -0
- data/app/controllers/profiler/api/tests_controller.rb +46 -0
- data/app/controllers/profiler/test_runner_controller.rb +11 -0
- data/app/views/profiler/test_runner/index.html.erb +1 -0
- data/config/routes.rb +13 -0
- data/lib/profiler/collectors/console_collector.rb +57 -0
- data/lib/profiler/collectors/database_collector.rb +1 -1
- data/lib/profiler/collectors/test_collector.rb +75 -0
- data/lib/profiler/configuration.rb +14 -1
- data/lib/profiler/console_profiler.rb +102 -0
- data/lib/profiler/instrumentation/irb_instrumentation.rb +21 -0
- data/lib/profiler/mcp/resources/failing_tests.rb +40 -0
- data/lib/profiler/mcp/resources/slow_tests.rb +45 -0
- data/lib/profiler/mcp/server.rb +77 -6
- data/lib/profiler/mcp/tools/get_test_profile_detail.rb +126 -0
- data/lib/profiler/mcp/tools/query_test_profiles.rb +109 -0
- data/lib/profiler/mcp/tools/run_tests.rb +112 -0
- data/lib/profiler/railtie.rb +21 -1
- data/lib/profiler/test_helpers/minitest_support.rb +39 -0
- data/lib/profiler/test_helpers/reporter.rb +121 -0
- data/lib/profiler/test_helpers/rspec_support.rb +33 -0
- data/lib/profiler/test_profiler.rb +140 -0
- data/lib/profiler/test_runner/discovery.rb +57 -0
- data/lib/profiler/test_runner/run_store.rb +120 -0
- data/lib/profiler/test_runner/runner.rb +106 -0
- data/lib/profiler/version.rb +1 -1
- metadata +23 -2
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../test_profiler"
|
|
4
|
+
require_relative "reporter"
|
|
5
|
+
|
|
6
|
+
module Profiler
|
|
7
|
+
module TestHelpers
|
|
8
|
+
# RSpec integration for the profiler.
|
|
9
|
+
#
|
|
10
|
+
# Usage in spec_helper.rb:
|
|
11
|
+
#
|
|
12
|
+
# require 'profiler/test_helpers/rspec_support'
|
|
13
|
+
# RSpec.configure do |config|
|
|
14
|
+
# Profiler::TestHelpers::RSpecSupport.install(config)
|
|
15
|
+
# end
|
|
16
|
+
module RSpecSupport
|
|
17
|
+
def self.install(config)
|
|
18
|
+
config.around(:each) do |example|
|
|
19
|
+
Profiler::TestProfiler.profile(
|
|
20
|
+
test_name: example.full_description,
|
|
21
|
+
test_file: example.file_path,
|
|
22
|
+
test_line: example.line_number,
|
|
23
|
+
framework: :rspec
|
|
24
|
+
) { example.run }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
config.after(:suite) do
|
|
28
|
+
Profiler::TestHelpers::Reporter.print
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "models/profile"
|
|
4
|
+
require_relative "current_context"
|
|
5
|
+
require_relative "collectors/test_collector"
|
|
6
|
+
require_relative "collectors/database_collector"
|
|
7
|
+
require_relative "collectors/cache_collector"
|
|
8
|
+
require_relative "collectors/exception_collector"
|
|
9
|
+
require_relative "collectors/env_collector"
|
|
10
|
+
require_relative "collectors/flamegraph_collector"
|
|
11
|
+
|
|
12
|
+
module Profiler
|
|
13
|
+
class TestProfiler
|
|
14
|
+
TEST_COLLECTOR_CLASSES = [
|
|
15
|
+
Collectors::DatabaseCollector,
|
|
16
|
+
Collectors::CacheCollector,
|
|
17
|
+
Collectors::ExceptionCollector,
|
|
18
|
+
Collectors::EnvCollector,
|
|
19
|
+
Collectors::FlameGraphCollector
|
|
20
|
+
].freeze
|
|
21
|
+
|
|
22
|
+
def self.profile(test_name:, test_file:, test_line:, framework:, &block)
|
|
23
|
+
return block.call unless Profiler.enabled?
|
|
24
|
+
|
|
25
|
+
new(
|
|
26
|
+
test_name: test_name,
|
|
27
|
+
test_file: test_file,
|
|
28
|
+
test_line: test_line,
|
|
29
|
+
framework: framework
|
|
30
|
+
).run(&block)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def initialize(test_name:, test_file:, test_line:, framework:)
|
|
34
|
+
@test_name = test_name
|
|
35
|
+
@test_file = test_file
|
|
36
|
+
@test_line = test_line
|
|
37
|
+
@framework = framework
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def run(&block)
|
|
41
|
+
profile = Models::Profile.new
|
|
42
|
+
profile.profile_type = "test"
|
|
43
|
+
profile.path = @test_file
|
|
44
|
+
profile.method = "TEST"
|
|
45
|
+
|
|
46
|
+
test_collector = Collectors::TestCollector.new(
|
|
47
|
+
profile,
|
|
48
|
+
test_name: @test_name,
|
|
49
|
+
test_file: @test_file,
|
|
50
|
+
test_line: @test_line,
|
|
51
|
+
framework: @framework
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
collectors = [test_collector] + TEST_COLLECTOR_CLASSES.map { |klass| klass.new(profile) }
|
|
55
|
+
collectors.each { |c| c.subscribe if c.respond_to?(:subscribe) }
|
|
56
|
+
|
|
57
|
+
exception_collector = collectors.find { |c| c.is_a?(Collectors::ExceptionCollector) }
|
|
58
|
+
|
|
59
|
+
memory_before = current_memory if Profiler.configuration.track_memory
|
|
60
|
+
|
|
61
|
+
test_status = "passed"
|
|
62
|
+
error_message = nil
|
|
63
|
+
|
|
64
|
+
previous_token = Profiler::CurrentContext.token
|
|
65
|
+
Profiler::CurrentContext.token = profile.token
|
|
66
|
+
|
|
67
|
+
begin
|
|
68
|
+
result = block.call
|
|
69
|
+
|
|
70
|
+
# Minitest: Test#run catches assertion errors internally and returns self.
|
|
71
|
+
# Failures don't propagate — detect them from the result object.
|
|
72
|
+
if result.respond_to?(:passed?)
|
|
73
|
+
unless result.passed?
|
|
74
|
+
test_status = result.skipped? ? "pending" : "failed"
|
|
75
|
+
msg = result.failure&.message.to_s
|
|
76
|
+
error_message = msg.empty? ? nil : msg
|
|
77
|
+
end
|
|
78
|
+
test_collector.update_extra(
|
|
79
|
+
assertions: result.respond_to?(:assertions) ? result.assertions : nil,
|
|
80
|
+
skip_reason: (result.skipped? && result.failure) ? result.failure.message : nil
|
|
81
|
+
)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# RSpec: example.run catches errors internally and stores them in execution_result.
|
|
85
|
+
if result.respond_to?(:execution_result)
|
|
86
|
+
er = result.execution_result
|
|
87
|
+
rspec_status = er.status&.to_s
|
|
88
|
+
if rspec_status && rspec_status != "passed" && test_status == "passed"
|
|
89
|
+
test_status = rspec_status
|
|
90
|
+
msg = er.exception&.message.to_s
|
|
91
|
+
error_message = msg.empty? ? nil : msg
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
result
|
|
96
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
97
|
+
# Capture all exceptions (including test failures which may subclass Exception)
|
|
98
|
+
test_status = "failed"
|
|
99
|
+
error_message = "#{e.class}: #{e.message}"
|
|
100
|
+
exception_collector&.capture(e) if e.is_a?(StandardError)
|
|
101
|
+
raise
|
|
102
|
+
ensure
|
|
103
|
+
Profiler::CurrentContext.token = previous_token
|
|
104
|
+
|
|
105
|
+
if Profiler.configuration.track_memory
|
|
106
|
+
profile.memory = current_memory - memory_before
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
test_collector.update_status(test_status, error_message)
|
|
110
|
+
profile.finish(test_status == "passed" ? 200 : 500)
|
|
111
|
+
|
|
112
|
+
collectors.each do |collector|
|
|
113
|
+
begin
|
|
114
|
+
collector.collect if collector.respond_to?(:collect)
|
|
115
|
+
profile.add_collector_metadata(collector)
|
|
116
|
+
rescue => e
|
|
117
|
+
warn "Profiler TestProfiler: Collector #{collector.class} failed: #{e.message}"
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
Profiler.storage.save(profile.token, profile)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
private
|
|
126
|
+
|
|
127
|
+
def current_memory
|
|
128
|
+
return 0 unless defined?(GC.stat)
|
|
129
|
+
|
|
130
|
+
stats = GC.stat
|
|
131
|
+
if stats.key?(:total_allocated_size)
|
|
132
|
+
stats[:total_allocated_size]
|
|
133
|
+
elsif stats.key?(:total_allocated_objects)
|
|
134
|
+
stats[:total_allocated_objects] * 40
|
|
135
|
+
else
|
|
136
|
+
0
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Profiler
|
|
4
|
+
module TestRunner
|
|
5
|
+
class Discovery
|
|
6
|
+
SPEC_GLOB = "spec/**/*_spec.rb"
|
|
7
|
+
TEST_GLOB = "test/**/*_test.rb"
|
|
8
|
+
|
|
9
|
+
def self.frameworks
|
|
10
|
+
frameworks = []
|
|
11
|
+
frameworks << :rspec if rspec_available?
|
|
12
|
+
frameworks << :minitest if minitest_available?
|
|
13
|
+
frameworks
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.files(framework: nil)
|
|
17
|
+
root = defined?(Rails) ? Rails.root.to_s : Dir.pwd
|
|
18
|
+
result = {}
|
|
19
|
+
|
|
20
|
+
globs = case framework&.to_sym
|
|
21
|
+
when :rspec then [SPEC_GLOB]
|
|
22
|
+
when :minitest then [TEST_GLOB]
|
|
23
|
+
else [SPEC_GLOB, TEST_GLOB]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
globs.each do |glob|
|
|
27
|
+
Dir.glob(File.join(root, glob)).each do |path|
|
|
28
|
+
relative = path.sub("#{root}/", "")
|
|
29
|
+
parts = relative.split("/")
|
|
30
|
+
dir = parts[0..-2].join("/")
|
|
31
|
+
result[dir] ||= []
|
|
32
|
+
result[dir] << { path: relative, name: parts.last }
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
result.map do |dir, files|
|
|
37
|
+
{
|
|
38
|
+
directory: dir,
|
|
39
|
+
files: files.sort_by { |f| f[:name] }
|
|
40
|
+
}
|
|
41
|
+
end.sort_by { |d| d[:directory] }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def self.rspec_available?
|
|
45
|
+
defined?(RSpec) || Gem.loaded_specs.key?("rspec-core")
|
|
46
|
+
rescue
|
|
47
|
+
false
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.minitest_available?
|
|
51
|
+
defined?(Minitest) || Gem.loaded_specs.key?("minitest")
|
|
52
|
+
rescue
|
|
53
|
+
false
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "concurrent"
|
|
4
|
+
require "securerandom"
|
|
5
|
+
|
|
6
|
+
module Profiler
|
|
7
|
+
module TestRunner
|
|
8
|
+
class RunStore
|
|
9
|
+
TTL = 3600 # 1 hour
|
|
10
|
+
|
|
11
|
+
Run = Struct.new(:id, :status, :pid, :started_at, :finished_at, :output_lines, :exit_code, :files, :framework, keyword_init: true) do
|
|
12
|
+
def to_h
|
|
13
|
+
super.merge(
|
|
14
|
+
started_at: started_at&.iso8601,
|
|
15
|
+
finished_at: finished_at&.iso8601,
|
|
16
|
+
output: output_lines.join,
|
|
17
|
+
duration: finished_at && started_at ? ((finished_at - started_at) * 1000).round(2) : nil
|
|
18
|
+
).except(:output_lines)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
TERMINAL_STATUSES = %w[passed failed killed error].freeze
|
|
23
|
+
|
|
24
|
+
def initialize
|
|
25
|
+
@runs = Concurrent::Hash.new
|
|
26
|
+
@locks = Concurrent::Hash.new
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def create(files:, framework:)
|
|
30
|
+
id = SecureRandom.hex(8)
|
|
31
|
+
run = Run.new(
|
|
32
|
+
id: id,
|
|
33
|
+
status: "pending",
|
|
34
|
+
pid: nil,
|
|
35
|
+
started_at: Time.now,
|
|
36
|
+
finished_at: nil,
|
|
37
|
+
output_lines: Concurrent::Array.new,
|
|
38
|
+
exit_code: nil,
|
|
39
|
+
files: files,
|
|
40
|
+
framework: framework.to_s
|
|
41
|
+
)
|
|
42
|
+
@runs[id] = run
|
|
43
|
+
@locks[id] = { mutex: Mutex.new, cond: ConditionVariable.new }
|
|
44
|
+
cleanup_old_runs
|
|
45
|
+
run
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def find(id)
|
|
49
|
+
@runs[id]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def update(id, **attrs)
|
|
53
|
+
run = @runs[id]
|
|
54
|
+
return unless run
|
|
55
|
+
|
|
56
|
+
attrs.each { |k, v| run.send(:"#{k}=", v) }
|
|
57
|
+
signal(id)
|
|
58
|
+
run
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def append_output(id, chunk)
|
|
62
|
+
run = @runs[id]
|
|
63
|
+
return unless run
|
|
64
|
+
|
|
65
|
+
run.output_lines.push(chunk)
|
|
66
|
+
signal(id)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Block until new output is available at +position+ or the run terminates.
|
|
70
|
+
# Returns { chunks: [...], status: "...", position: N, finished: bool }.
|
|
71
|
+
def wait_for_output(id, position:, timeout: 10)
|
|
72
|
+
run = @runs[id]
|
|
73
|
+
return { chunks: [], status: "not_found", position: 0, finished: true } unless run
|
|
74
|
+
|
|
75
|
+
lock = @locks[id]
|
|
76
|
+
return snapshot(run, position) unless lock
|
|
77
|
+
|
|
78
|
+
lock[:mutex].synchronize do
|
|
79
|
+
current = run.output_lines.size
|
|
80
|
+
if current <= position && !TERMINAL_STATUSES.include?(run.status)
|
|
81
|
+
lock[:cond].wait(lock[:mutex], timeout)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
snapshot(run, position)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def all
|
|
89
|
+
@runs.values.sort_by { |r| r.started_at || Time.at(0) }.reverse
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
def signal(id)
|
|
95
|
+
lock = @locks[id]
|
|
96
|
+
return unless lock
|
|
97
|
+
lock[:mutex].synchronize { lock[:cond].broadcast }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def snapshot(run, position)
|
|
101
|
+
lines = run.output_lines.dup
|
|
102
|
+
{
|
|
103
|
+
chunks: lines[position..] || [],
|
|
104
|
+
status: run.status,
|
|
105
|
+
position: lines.size,
|
|
106
|
+
finished: TERMINAL_STATUSES.include?(run.status)
|
|
107
|
+
}
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def cleanup_old_runs
|
|
111
|
+
cutoff = Time.now - TTL
|
|
112
|
+
@runs.delete_if { |id, r| r.finished_at && r.finished_at < cutoff && @locks.delete(id) }
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def self.run_store
|
|
117
|
+
@run_store ||= RunStore.new
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "run_store"
|
|
4
|
+
require_relative "discovery"
|
|
5
|
+
|
|
6
|
+
module Profiler
|
|
7
|
+
module TestRunner
|
|
8
|
+
class Runner
|
|
9
|
+
def self.start(files:, framework:)
|
|
10
|
+
run = Profiler::TestRunner.run_store.create(files: files, framework: framework)
|
|
11
|
+
spawn_async(run)
|
|
12
|
+
run
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.kill(run_id)
|
|
16
|
+
run = Profiler::TestRunner.run_store.find(run_id)
|
|
17
|
+
return false unless run && run.pid && run.status == "running"
|
|
18
|
+
|
|
19
|
+
begin
|
|
20
|
+
Process.kill("TERM", run.pid)
|
|
21
|
+
Profiler::TestRunner.run_store.update(run_id, status: "killed", finished_at: Time.now)
|
|
22
|
+
true
|
|
23
|
+
rescue Errno::ESRCH
|
|
24
|
+
# Process already exited
|
|
25
|
+
false
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def self.spawn_async(run)
|
|
32
|
+
Thread.new do
|
|
33
|
+
run_process(run)
|
|
34
|
+
rescue => e
|
|
35
|
+
Profiler::TestRunner.run_store.update(
|
|
36
|
+
run.id,
|
|
37
|
+
status: "error",
|
|
38
|
+
finished_at: Time.now
|
|
39
|
+
)
|
|
40
|
+
Profiler::TestRunner.run_store.append_output(run.id, "\n[Profiler] Error: #{e.message}\n")
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def self.run_process(run)
|
|
45
|
+
cmd = build_command(run.files, run.framework)
|
|
46
|
+
env = build_env
|
|
47
|
+
|
|
48
|
+
Profiler::TestRunner.run_store.update(run.id, status: "running", started_at: Time.now)
|
|
49
|
+
|
|
50
|
+
IO.popen([env, *cmd, err: [:child, :out]], "r") do |io|
|
|
51
|
+
Profiler::TestRunner.run_store.update(run.id, pid: io.pid)
|
|
52
|
+
|
|
53
|
+
while (chunk = io.read(256))
|
|
54
|
+
break if chunk.empty?
|
|
55
|
+
|
|
56
|
+
Profiler::TestRunner.run_store.append_output(run.id, chunk)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
exit_code = $?.exitstatus || 0
|
|
61
|
+
status = exit_code == 0 ? "passed" : "failed"
|
|
62
|
+
|
|
63
|
+
Profiler::TestRunner.run_store.update(
|
|
64
|
+
run.id,
|
|
65
|
+
status: status,
|
|
66
|
+
finished_at: Time.now,
|
|
67
|
+
exit_code: exit_code
|
|
68
|
+
)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def self.build_command(files, framework)
|
|
72
|
+
root = defined?(Rails) ? Rails.root.to_s : Dir.pwd
|
|
73
|
+
absolute_files = files.map { |f| File.join(root, f) }
|
|
74
|
+
|
|
75
|
+
case framework.to_sym
|
|
76
|
+
when :rspec
|
|
77
|
+
["bundle", "exec", "rspec", "--format", "progress", "--color", *absolute_files]
|
|
78
|
+
when :minitest
|
|
79
|
+
["bundle", "exec", "rails", "test", *absolute_files]
|
|
80
|
+
else
|
|
81
|
+
["bundle", "exec", "rspec", "--format", "progress", "--color", *absolute_files]
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
BLOCKED_ENV_KEYS = %w[RAILS_ENV RACK_ENV DATABASE_URL SECRET_KEY_BASE].freeze
|
|
86
|
+
|
|
87
|
+
def self.build_env
|
|
88
|
+
base = ENV.to_h
|
|
89
|
+
|
|
90
|
+
# Inject env var overrides configured in the profiler — skip blocked keys
|
|
91
|
+
overrides = Profiler.env_override_store.all_overrides
|
|
92
|
+
overrides.each do |key, entry|
|
|
93
|
+
next if BLOCKED_ENV_KEYS.include?(key.upcase)
|
|
94
|
+
value = entry.is_a?(Hash) ? entry["value"] : entry
|
|
95
|
+
base[key] = value
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Ensure test environment regardless of overrides
|
|
99
|
+
base["RAILS_ENV"] = "test"
|
|
100
|
+
base["RACK_ENV"] = "test"
|
|
101
|
+
|
|
102
|
+
base
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
data/lib/profiler/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails-profiler
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.26.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sébastien Duplessy
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-06-
|
|
11
|
+
date: 2026-06-02 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -107,26 +107,32 @@ files:
|
|
|
107
107
|
- app/assets/builds/profiler.css
|
|
108
108
|
- app/assets/builds/profiler.js
|
|
109
109
|
- app/controllers/profiler/api/ajax_controller.rb
|
|
110
|
+
- app/controllers/profiler/api/console_controller.rb
|
|
110
111
|
- app/controllers/profiler/api/env_vars_controller.rb
|
|
111
112
|
- app/controllers/profiler/api/explain_controller.rb
|
|
112
113
|
- app/controllers/profiler/api/function_profiling_controller.rb
|
|
113
114
|
- app/controllers/profiler/api/jobs_controller.rb
|
|
114
115
|
- app/controllers/profiler/api/outbound_http_controller.rb
|
|
115
116
|
- app/controllers/profiler/api/profiles_controller.rb
|
|
117
|
+
- app/controllers/profiler/api/test_runner_controller.rb
|
|
118
|
+
- app/controllers/profiler/api/tests_controller.rb
|
|
116
119
|
- app/controllers/profiler/api/toolbar_controller.rb
|
|
117
120
|
- app/controllers/profiler/application_controller.rb
|
|
118
121
|
- app/controllers/profiler/assets_controller.rb
|
|
119
122
|
- app/controllers/profiler/profiles_controller.rb
|
|
123
|
+
- app/controllers/profiler/test_runner_controller.rb
|
|
120
124
|
- app/views/layouts/profiler/application.html.erb
|
|
121
125
|
- app/views/layouts/profiler/embedded.html.erb
|
|
122
126
|
- app/views/profiler/profiles/index.html.erb
|
|
123
127
|
- app/views/profiler/profiles/show.html.erb
|
|
128
|
+
- app/views/profiler/test_runner/index.html.erb
|
|
124
129
|
- config/routes.rb
|
|
125
130
|
- exe/profiler-mcp
|
|
126
131
|
- lib/profiler.rb
|
|
127
132
|
- lib/profiler/collectors/ajax_collector.rb
|
|
128
133
|
- lib/profiler/collectors/base_collector.rb
|
|
129
134
|
- lib/profiler/collectors/cache_collector.rb
|
|
135
|
+
- lib/profiler/collectors/console_collector.rb
|
|
130
136
|
- lib/profiler/collectors/database_collector.rb
|
|
131
137
|
- lib/profiler/collectors/dump_collector.rb
|
|
132
138
|
- lib/profiler/collectors/env_collector.rb
|
|
@@ -140,13 +146,16 @@ files:
|
|
|
140
146
|
- lib/profiler/collectors/mailer_collector.rb
|
|
141
147
|
- lib/profiler/collectors/request_collector.rb
|
|
142
148
|
- lib/profiler/collectors/routes_collector.rb
|
|
149
|
+
- lib/profiler/collectors/test_collector.rb
|
|
143
150
|
- lib/profiler/collectors/view_collector.rb
|
|
144
151
|
- lib/profiler/configuration.rb
|
|
152
|
+
- lib/profiler/console_profiler.rb
|
|
145
153
|
- lib/profiler/current_context.rb
|
|
146
154
|
- lib/profiler/engine.rb
|
|
147
155
|
- lib/profiler/env_override_store.rb
|
|
148
156
|
- lib/profiler/explain_runner.rb
|
|
149
157
|
- lib/profiler/instrumentation/active_job_instrumentation.rb
|
|
158
|
+
- lib/profiler/instrumentation/irb_instrumentation.rb
|
|
150
159
|
- lib/profiler/instrumentation/net_http_instrumentation.rb
|
|
151
160
|
- lib/profiler/instrumentation/sidekiq_middleware.rb
|
|
152
161
|
- lib/profiler/instrumentation/thread_context_propagation.rb
|
|
@@ -154,10 +163,12 @@ files:
|
|
|
154
163
|
- lib/profiler/mcp/body_formatter.rb
|
|
155
164
|
- lib/profiler/mcp/file_cache.rb
|
|
156
165
|
- lib/profiler/mcp/path_extractor.rb
|
|
166
|
+
- lib/profiler/mcp/resources/failing_tests.rb
|
|
157
167
|
- lib/profiler/mcp/resources/n1_patterns.rb
|
|
158
168
|
- lib/profiler/mcp/resources/recent_jobs.rb
|
|
159
169
|
- lib/profiler/mcp/resources/recent_requests.rb
|
|
160
170
|
- lib/profiler/mcp/resources/slow_queries.rb
|
|
171
|
+
- lib/profiler/mcp/resources/slow_tests.rb
|
|
161
172
|
- lib/profiler/mcp/server.rb
|
|
162
173
|
- lib/profiler/mcp/tools/analyze_queries.rb
|
|
163
174
|
- lib/profiler/mcp/tools/clear_profiles.rb
|
|
@@ -167,12 +178,15 @@ files:
|
|
|
167
178
|
- lib/profiler/mcp/tools/get_profile_detail.rb
|
|
168
179
|
- lib/profiler/mcp/tools/get_profile_dumps.rb
|
|
169
180
|
- lib/profiler/mcp/tools/get_profile_http.rb
|
|
181
|
+
- lib/profiler/mcp/tools/get_test_profile_detail.rb
|
|
170
182
|
- lib/profiler/mcp/tools/list_env_vars.rb
|
|
171
183
|
- lib/profiler/mcp/tools/query_jobs.rb
|
|
172
184
|
- lib/profiler/mcp/tools/query_mailers.rb
|
|
173
185
|
- lib/profiler/mcp/tools/query_profiles.rb
|
|
186
|
+
- lib/profiler/mcp/tools/query_test_profiles.rb
|
|
174
187
|
- lib/profiler/mcp/tools/reset_all_env_vars.rb
|
|
175
188
|
- lib/profiler/mcp/tools/reset_env_var.rb
|
|
189
|
+
- lib/profiler/mcp/tools/run_tests.rb
|
|
176
190
|
- lib/profiler/mcp/tools/set_env_var.rb
|
|
177
191
|
- lib/profiler/middleware/cors_middleware.rb
|
|
178
192
|
- lib/profiler/middleware/profiler_middleware.rb
|
|
@@ -188,6 +202,13 @@ files:
|
|
|
188
202
|
- lib/profiler/storage/redis_store.rb
|
|
189
203
|
- lib/profiler/storage/sqlite_store.rb
|
|
190
204
|
- lib/profiler/tasks/profiler.rake
|
|
205
|
+
- lib/profiler/test_helpers/minitest_support.rb
|
|
206
|
+
- lib/profiler/test_helpers/reporter.rb
|
|
207
|
+
- lib/profiler/test_helpers/rspec_support.rb
|
|
208
|
+
- lib/profiler/test_profiler.rb
|
|
209
|
+
- lib/profiler/test_runner/discovery.rb
|
|
210
|
+
- lib/profiler/test_runner/run_store.rb
|
|
211
|
+
- lib/profiler/test_runner/runner.rb
|
|
191
212
|
- lib/profiler/version.rb
|
|
192
213
|
homepage: https://git.duplessy.eu/sebastien/rails-profiler-gem
|
|
193
214
|
licenses:
|