dial 0.1.3 → 0.1.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +17 -4
- data/dial.gemspec +3 -1
- data/lib/dial/constants.rb +8 -1
- data/lib/dial/middleware/panel.rb +62 -5
- data/lib/dial/middleware.rb +91 -17
- data/lib/dial/prosopite_logger.rb +6 -0
- data/lib/dial/railtie.rb +23 -0
- data/lib/dial/util.rb +12 -0
- data/lib/dial/version.rb +1 -1
- data/lib/dial.rb +3 -1
- metadata +35 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 82171550e05c2bc884c0ea9a55e6464f42d9bfef043af62e123c03fdbce943b6
|
4
|
+
data.tar.gz: f7c98017bbcaa66ad376c0b01570007f6b5b70c36ef25843a34254b0e4c82714
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 64e51ddaf1029231903836e08c8943afe75df9ec068f4506cba3a85e4c2a5059651af66e8ce3c797132f0ee1943c773abcd52a8ea9fc13fec8280253dbf5dfb3
|
7
|
+
data.tar.gz: cf6d09efec8cd1e581d0b13a91cd6bfce0c1d86612d9b2bf778c01ac6f1766c40fe122eeca19e6133749c396eec2bea34610be2e3d6cd231a4c6eaeebf0d99d6
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,14 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.1.5] - 2025-01-24
|
4
|
+
|
5
|
+
- UI: Fix overflow and add vertical scroll
|
6
|
+
|
7
|
+
## [0.1.4] - 2024-12-27
|
8
|
+
|
9
|
+
- Use vernier memory usage and rails hooks by default
|
10
|
+
- Add support for N+1 detection with prosopite
|
11
|
+
|
3
12
|
## [0.1.3] - 2024-11-14
|
4
13
|
|
5
14
|
- Enable allocation profiling by default
|
data/README.md
CHANGED
@@ -7,17 +7,30 @@ WIP
|
|
7
7
|
|
8
8
|
A modern profiler for Rails applications.
|
9
9
|
|
10
|
-
|
10
|
+
Check out the demo:
|
11
|
+
[](https://youtu.be/LPXtfJ0c284)
|
11
12
|
|
12
13
|
## Installation
|
13
14
|
|
14
|
-
|
15
|
+
1. Add the gem to your Rails application's Gemfile (adjust the `require` option to match your server of choice):
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
# require in just the server process
|
19
|
+
gem "dial", require: !!($PROGRAM_NAME =~ /puma/)
|
20
|
+
```
|
21
|
+
|
22
|
+
2. Install the gem:
|
15
23
|
|
16
24
|
```bash
|
17
|
-
bundle
|
25
|
+
bundle install
|
18
26
|
```
|
19
27
|
|
20
|
-
|
28
|
+
3. Mount the engine in your `config/routes.rb` file:
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
# this will mount the engine at /dial
|
32
|
+
mount Dial::Engine, at: "/" if Object.const_defined?("Dial::Engine")
|
33
|
+
```
|
21
34
|
|
22
35
|
## Development
|
23
36
|
|
data/dial.gemspec
CHANGED
@@ -22,5 +22,7 @@ Gem::Specification.new do |spec|
|
|
22
22
|
spec.add_dependency "railties", ">= 7", "< 8.2"
|
23
23
|
spec.add_dependency "activerecord", ">= 7", "< 8.2"
|
24
24
|
spec.add_dependency "actionpack", ">= 7", "< 8.2"
|
25
|
-
spec.add_dependency "vernier", "~> 1.
|
25
|
+
spec.add_dependency "vernier", "~> 1.5"
|
26
|
+
spec.add_dependency "prosopite", "~> 1.4"
|
27
|
+
spec.add_dependency "pg_query", "~> 5.1"
|
26
28
|
end
|
data/lib/dial/constants.rb
CHANGED
@@ -1,6 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "version"
|
4
|
+
|
3
5
|
module Dial
|
4
6
|
REQUEST_TIMING_HEADER = "dial_request_timing"
|
5
|
-
|
7
|
+
|
8
|
+
PROFILE_OUT_STALE_SECONDS = 60 * 60
|
9
|
+
PROFILE_OUT_RELATIVE_DIRNAME = "tmp/dial/profiles/"
|
10
|
+
|
11
|
+
PROSOPITE_IGNORE_QUERIES = [/schema_migrations/]
|
12
|
+
PROSOPITE_LOG_RELATIVE_PATHNAME = "log/dial/prosopite.log"
|
6
13
|
end
|
@@ -5,7 +5,7 @@ require "uri"
|
|
5
5
|
module Dial
|
6
6
|
class Panel
|
7
7
|
class << self
|
8
|
-
def html env, profile_out_filename, ruby_vm_stat, gc_stat, gc_stat_heap, server_timing
|
8
|
+
def html env, profile_out_filename, query_logs, ruby_vm_stat, gc_stat, gc_stat_heap, server_timing
|
9
9
|
<<~HTML
|
10
10
|
<style>#{style}</style>
|
11
11
|
|
@@ -21,7 +21,16 @@ module Dial
|
|
21
21
|
<span>#{formatted_ruby_version}</span>
|
22
22
|
</div>
|
23
23
|
|
24
|
+
<hr>
|
25
|
+
|
24
26
|
<div id="dial-details">
|
27
|
+
<details>
|
28
|
+
<summary>N+1s</summary>
|
29
|
+
<div class="section query-logs">
|
30
|
+
#{formatted_query_logs query_logs}
|
31
|
+
</div>
|
32
|
+
</details>
|
33
|
+
|
25
34
|
<hr>
|
26
35
|
|
27
36
|
<details>
|
@@ -31,6 +40,8 @@ module Dial
|
|
31
40
|
</div>
|
32
41
|
</details>
|
33
42
|
|
43
|
+
<hr>
|
44
|
+
|
34
45
|
<details>
|
35
46
|
<summary>RubyVM stat</summary>
|
36
47
|
<div class="section">
|
@@ -38,6 +49,8 @@ module Dial
|
|
38
49
|
</div>
|
39
50
|
</details>
|
40
51
|
|
52
|
+
<hr>
|
53
|
+
|
41
54
|
<details>
|
42
55
|
<summary>GC stat</summary>
|
43
56
|
<div class="section">
|
@@ -45,6 +58,8 @@ module Dial
|
|
45
58
|
</div>
|
46
59
|
</details>
|
47
60
|
|
61
|
+
<hr>
|
62
|
+
|
48
63
|
<details>
|
49
64
|
<summary>GC stat heap</summary>
|
50
65
|
<div class="section">
|
@@ -63,6 +78,8 @@ module Dial
|
|
63
78
|
def style
|
64
79
|
<<~CSS
|
65
80
|
#dial {
|
81
|
+
max-height: 50%;
|
82
|
+
max-width: 50%;
|
66
83
|
z-index: 9999;
|
67
84
|
position: fixed;
|
68
85
|
bottom: 0;
|
@@ -84,6 +101,7 @@ module Dial
|
|
84
101
|
|
85
102
|
#dial-details {
|
86
103
|
display: none;
|
104
|
+
overflow-y: auto;
|
87
105
|
}
|
88
106
|
|
89
107
|
.section {
|
@@ -92,6 +110,15 @@ module Dial
|
|
92
110
|
margin: 0.25rem 0 0 0;
|
93
111
|
}
|
94
112
|
|
113
|
+
.query-logs {
|
114
|
+
padding-left: 0.75rem;
|
115
|
+
|
116
|
+
details {
|
117
|
+
margin-top: 0;
|
118
|
+
margin-bottom: 0.25rem;
|
119
|
+
}
|
120
|
+
}
|
121
|
+
|
95
122
|
span {
|
96
123
|
text-align: left;
|
97
124
|
}
|
@@ -99,6 +126,7 @@ module Dial
|
|
99
126
|
hr {
|
100
127
|
width: -moz-available;
|
101
128
|
margin: 0.65rem 0 0 0;
|
129
|
+
background-color: black;
|
102
130
|
}
|
103
131
|
|
104
132
|
details {
|
@@ -118,10 +146,22 @@ module Dial
|
|
118
146
|
<<~JS
|
119
147
|
const dialPreview = document.getElementById("dial-preview");
|
120
148
|
const dialDetails = document.getElementById("dial-details");
|
149
|
+
|
121
150
|
dialPreview.addEventListener("click", () => {
|
122
151
|
const collapsed = ["", "none"].includes(dialDetails.style.display);
|
123
152
|
dialDetails.style.display = collapsed ? "block" : "none";
|
124
153
|
});
|
154
|
+
|
155
|
+
document.addEventListener("click", (event) => {
|
156
|
+
if (!dialPreview.contains(event.target) && !dialDetails.contains(event.target)) {
|
157
|
+
dialDetails.style.display = "none";
|
158
|
+
|
159
|
+
const detailsElements = dialDetails.querySelectorAll("details");
|
160
|
+
detailsElements.forEach(detail => {
|
161
|
+
detail.removeAttribute("open");
|
162
|
+
});
|
163
|
+
}
|
164
|
+
});
|
125
165
|
JS
|
126
166
|
end
|
127
167
|
|
@@ -140,10 +180,10 @@ module Dial
|
|
140
180
|
end
|
141
181
|
|
142
182
|
def formatted_profile_output env, profile_out_filename
|
183
|
+
url_base = ::Rails.application.routes.url_helpers.dial_url host: env[::Rack::HTTP_HOST]
|
184
|
+
prefix = "/" unless url_base.end_with? "/"
|
143
185
|
uuid = profile_out_filename.delete_suffix ".json"
|
144
|
-
|
145
|
-
base_url = ::Rails.application.routes.url_helpers.dial_url host: host
|
146
|
-
profile_out_url = URI.encode_www_form_component base_url + "dial/profile?uuid=#{uuid}"
|
186
|
+
profile_out_url = URI.encode_www_form_component url_base + "#{prefix}dial/profile?uuid=#{uuid}"
|
147
187
|
|
148
188
|
"<a href='https://vernier.prof/from-url/#{profile_out_url}' target='_blank'>View profile</a>"
|
149
189
|
end
|
@@ -163,7 +203,6 @@ module Dial
|
|
163
203
|
def formatted_server_timing server_timing
|
164
204
|
if server_timing.any?
|
165
205
|
server_timing
|
166
|
-
# TODO: Nested sorting
|
167
206
|
.sort_by { |_, timing| -timing }
|
168
207
|
.map { |event, timing| "<span><b>#{event}:</b> #{timing}</span>" }.join
|
169
208
|
else
|
@@ -171,6 +210,24 @@ module Dial
|
|
171
210
|
end
|
172
211
|
end
|
173
212
|
|
213
|
+
def formatted_query_logs query_logs
|
214
|
+
if query_logs.any?
|
215
|
+
query_logs.map do |(queries, stack_lines)|
|
216
|
+
<<~HTML
|
217
|
+
<details>
|
218
|
+
<summary>#{queries.shift}</summary>
|
219
|
+
<div class="section query-logs">
|
220
|
+
#{queries.map { |query| "<span>#{query}</span>" }.join}
|
221
|
+
#{stack_lines.map { |stack_line| "<span>#{stack_line}</span>" }.join}
|
222
|
+
</div>
|
223
|
+
</details>
|
224
|
+
HTML
|
225
|
+
end.join
|
226
|
+
else
|
227
|
+
"NA"
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
174
231
|
def formatted_ruby_vm_stat ruby_vm_stat
|
175
232
|
ruby_vm_stat.map { |key, value| "<span><b>#{key}:</b> #{value}</span>" }.join
|
176
233
|
end
|
data/lib/dial/middleware.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "securerandom"
|
4
|
+
|
3
5
|
require "vernier"
|
6
|
+
require "prosopite"
|
4
7
|
|
5
|
-
require_relative "constants"
|
6
8
|
require_relative "middleware/panel"
|
7
9
|
require_relative "middleware/ruby_stat"
|
8
10
|
require_relative "middleware/rails_stat"
|
@@ -19,38 +21,36 @@ module Dial
|
|
19
21
|
def call env
|
20
22
|
start_time = Process.clock_gettime Process::CLOCK_MONOTONIC
|
21
23
|
|
22
|
-
|
23
|
-
|
24
|
-
gc_stat_heap_before = GC.stat_heap
|
25
|
-
|
26
|
-
profile_out_dirname = String ::Rails.root.join PROFILE_OUT_RELATIVE_DIRNAME
|
27
|
-
FileUtils.mkdir_p profile_out_dirname
|
28
|
-
profile_out_filename = "#{SecureRandom.uuid}.json"
|
29
|
-
profile_out_pathname = "#{profile_out_dirname}#{profile_out_filename}"
|
24
|
+
profile_out_filename = "#{SecureRandom.uuid_v7}.json"
|
25
|
+
profile_out_pathname = "#{profile_out_dir_pathname}#{profile_out_filename}"
|
30
26
|
|
31
27
|
status, headers, rack_body = nil
|
32
|
-
|
33
|
-
|
28
|
+
ruby_vm_stat, gc_stat, gc_stat_heap = nil
|
29
|
+
::Prosopite.scan do
|
30
|
+
::Vernier.profile out: profile_out_pathname, interval: 500, allocation_interval: 1000, hooks: [:memory_usage, :rails] do
|
31
|
+
ruby_vm_stat, gc_stat, gc_stat_heap = with_diffed_ruby_stats do
|
32
|
+
status, headers, rack_body = @app.call env
|
33
|
+
end
|
34
|
+
end
|
34
35
|
end
|
36
|
+
server_timing = server_timing headers
|
35
37
|
|
36
38
|
unless headers[::Rack::CONTENT_TYPE]&.include? "text/html"
|
37
39
|
File.delete profile_out_pathname if File.exist? profile_out_pathname
|
38
40
|
return [status, headers, rack_body]
|
39
41
|
end
|
40
42
|
|
43
|
+
query_logs = clear_query_logs!
|
44
|
+
remove_stale_profile_out_files!
|
45
|
+
|
41
46
|
finish_time = Process.clock_gettime Process::CLOCK_MONOTONIC
|
42
47
|
env[REQUEST_TIMING_HEADER] = ((finish_time - start_time) * 1_000).round 2
|
43
48
|
|
44
|
-
ruby_vm_stat = ruby_vm_stat_diff ruby_vm_stat_before, RubyVM.stat
|
45
|
-
gc_stat = gc_stat_diff gc_stat_before, GC.stat
|
46
|
-
gc_stat_heap = gc_stat_heap_diff gc_stat_heap_before, GC.stat_heap
|
47
|
-
server_timing = server_timing headers
|
48
|
-
|
49
49
|
body = String.new.tap do |str|
|
50
50
|
rack_body.each { |chunk| str << chunk }
|
51
51
|
rack_body.close if rack_body.respond_to? :close
|
52
52
|
end.sub "</body>", <<~HTML
|
53
|
-
#{Panel.html env, profile_out_filename, ruby_vm_stat, gc_stat, gc_stat_heap, server_timing}
|
53
|
+
#{Panel.html env, profile_out_filename, query_logs, ruby_vm_stat, gc_stat, gc_stat_heap, server_timing}
|
54
54
|
</body>
|
55
55
|
HTML
|
56
56
|
|
@@ -58,5 +58,79 @@ module Dial
|
|
58
58
|
|
59
59
|
[status, headers, [body]]
|
60
60
|
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def with_diffed_ruby_stats
|
65
|
+
ruby_vm_stat_before = RubyVM.stat
|
66
|
+
gc_stat_before = GC.stat
|
67
|
+
gc_stat_heap_before = GC.stat_heap
|
68
|
+
yield
|
69
|
+
[
|
70
|
+
ruby_vm_stat_diff(ruby_vm_stat_before, RubyVM.stat),
|
71
|
+
gc_stat_diff(gc_stat_before, GC.stat),
|
72
|
+
gc_stat_heap_diff(gc_stat_heap_before, GC.stat_heap)
|
73
|
+
]
|
74
|
+
end
|
75
|
+
|
76
|
+
def remove_stale_profile_out_files!
|
77
|
+
stale_profile_out_files.each do |profile_out_file|
|
78
|
+
File.delete profile_out_file
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def stale_profile_out_files
|
83
|
+
Dir.glob("#{profile_out_dir_pathname}/*.json").select do |profile_out_file|
|
84
|
+
timestamp = Util.uuid_v7_timestamp File.basename profile_out_file
|
85
|
+
timestamp < Time.now - PROFILE_OUT_STALE_SECONDS
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def profile_out_dir_pathname
|
90
|
+
@_profile_out_dir_pathname ||= ::Rails.root.join PROFILE_OUT_RELATIVE_DIRNAME
|
91
|
+
end
|
92
|
+
|
93
|
+
def clear_query_logs!
|
94
|
+
[].tap do |query_logs|
|
95
|
+
File.open(query_log_pathname, "r+") do |file|
|
96
|
+
entry = section = count = nil
|
97
|
+
file.each_line do |line|
|
98
|
+
entry, section, count = process_query_log_line line, entry, section, count
|
99
|
+
query_logs << entry if entry && section.nil?
|
100
|
+
end
|
101
|
+
|
102
|
+
file.truncate 0
|
103
|
+
file.rewind
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def process_query_log_line line, entry, section, count
|
109
|
+
case line
|
110
|
+
when /N\+1 queries detected/
|
111
|
+
[[[],[]], :queries, 0]
|
112
|
+
when /Call stack/
|
113
|
+
entry.first << "+ #{count - 5} more queries" if count > 5
|
114
|
+
[entry, :call_stack, count]
|
115
|
+
else
|
116
|
+
case section
|
117
|
+
when :queries
|
118
|
+
count += 1
|
119
|
+
entry.first << line.strip if count <= 5
|
120
|
+
[entry, :queries, count]
|
121
|
+
when :call_stack
|
122
|
+
if line.strip.empty?
|
123
|
+
[entry, nil, count]
|
124
|
+
else
|
125
|
+
entry.last << line.strip
|
126
|
+
[entry, section, count]
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def query_log_pathname
|
133
|
+
@_query_log_dir_pathname ||= ::Rails.root.join PROSOPITE_LOG_RELATIVE_PATHNAME
|
134
|
+
end
|
61
135
|
end
|
62
136
|
end
|
data/lib/dial/railtie.rb
CHANGED
@@ -1,13 +1,36 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "rails"
|
4
|
+
require "prosopite"
|
4
5
|
|
5
6
|
require_relative "middleware"
|
7
|
+
require_relative "prosopite_logger"
|
6
8
|
|
7
9
|
module Dial
|
8
10
|
class Railtie < ::Rails::Railtie
|
9
11
|
initializer "dial.use_middleware" do |app|
|
10
12
|
app.middleware.insert_before 0, Middleware
|
11
13
|
end
|
14
|
+
|
15
|
+
initializer "dial.set_up_vernier" do |app|
|
16
|
+
app.config.after_initialize do
|
17
|
+
FileUtils.mkdir_p ::Rails.root.join PROFILE_OUT_RELATIVE_DIRNAME
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
initializer "dial.set_up_prosopite" do |app|
|
22
|
+
app.config.after_initialize do
|
23
|
+
if ::ActiveRecord::Base.connection.adapter_name == "PostgreSQL"
|
24
|
+
require "pg_query"
|
25
|
+
end
|
26
|
+
|
27
|
+
prosopite_log_pathname = ::Rails.root.join PROSOPITE_LOG_RELATIVE_PATHNAME
|
28
|
+
FileUtils.mkdir_p File.dirname prosopite_log_pathname
|
29
|
+
FileUtils.touch prosopite_log_pathname
|
30
|
+
::Prosopite.custom_logger = ProsopiteLogger.new prosopite_log_pathname
|
31
|
+
|
32
|
+
::Prosopite.ignore_queries = PROSOPITE_IGNORE_QUERIES
|
33
|
+
end
|
34
|
+
end
|
12
35
|
end
|
13
36
|
end
|
data/lib/dial/util.rb
ADDED
data/lib/dial/version.rb
CHANGED
data/lib/dial.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dial
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joshua Young
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date:
|
10
|
+
date: 2025-01-24 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: railties
|
@@ -75,14 +75,42 @@ dependencies:
|
|
75
75
|
requirements:
|
76
76
|
- - "~>"
|
77
77
|
- !ruby/object:Gem::Version
|
78
|
-
version: '1.
|
78
|
+
version: '1.5'
|
79
79
|
type: :runtime
|
80
80
|
prerelease: false
|
81
81
|
version_requirements: !ruby/object:Gem::Requirement
|
82
82
|
requirements:
|
83
83
|
- - "~>"
|
84
84
|
- !ruby/object:Gem::Version
|
85
|
-
version: '1.
|
85
|
+
version: '1.5'
|
86
|
+
- !ruby/object:Gem::Dependency
|
87
|
+
name: prosopite
|
88
|
+
requirement: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - "~>"
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '1.4'
|
93
|
+
type: :runtime
|
94
|
+
prerelease: false
|
95
|
+
version_requirements: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - "~>"
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '1.4'
|
100
|
+
- !ruby/object:Gem::Dependency
|
101
|
+
name: pg_query
|
102
|
+
requirement: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - "~>"
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '5.1'
|
107
|
+
type: :runtime
|
108
|
+
prerelease: false
|
109
|
+
version_requirements: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - "~>"
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '5.1'
|
86
114
|
email:
|
87
115
|
- djry1999@gmail.com
|
88
116
|
executables: []
|
@@ -102,7 +130,9 @@ files:
|
|
102
130
|
- lib/dial/middleware/panel.rb
|
103
131
|
- lib/dial/middleware/rails_stat.rb
|
104
132
|
- lib/dial/middleware/ruby_stat.rb
|
133
|
+
- lib/dial/prosopite_logger.rb
|
105
134
|
- lib/dial/railtie.rb
|
135
|
+
- lib/dial/util.rb
|
106
136
|
- lib/dial/version.rb
|
107
137
|
homepage: https://github.com/joshuay03/dial
|
108
138
|
licenses:
|
@@ -124,7 +154,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
124
154
|
- !ruby/object:Gem::Version
|
125
155
|
version: '0'
|
126
156
|
requirements: []
|
127
|
-
rubygems_version: 3.6.
|
157
|
+
rubygems_version: 3.6.2
|
128
158
|
specification_version: 4
|
129
159
|
summary: A modern Rails profiler
|
130
160
|
test_files: []
|