dial 0.2.3 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: edead8fe405ee09b2ece6be004f6200ddd9394707a41da3509cbd168f3b88c67
4
- data.tar.gz: 506f4b258c2b0acf117b1f4885ede6cf93ec739c1f21e0dc82b1f3da086f9a2c
3
+ metadata.gz: 24787fba428e09eb96323d44b54df1487172d965b47e88df27b1855cefe8e02d
4
+ data.tar.gz: 520e95aae1ac2ca66bb09156a54203a200b94e401c35d269990d43978d2e08cb
5
5
  SHA512:
6
- metadata.gz: c8f6c3705814f0cc0ae49bddf462741cf74d6de0733bd08280f6b01123368afb6ad46a1f7bf09329ee1c16c53a1d651c34b39d7277efce651f72550cd8e06557
7
- data.tar.gz: 22236134d9731d47eacbc6d931ed2484b4e5451c875e554dcd5fc5fea3db84e3aaaf429db3f318939de30d52aa49ff49251f5300fc8243bf41e247d40c11c555
6
+ metadata.gz: dc6187fea22a1334c217461a5b5394ea23f8b1b6c821000ec2629d7537b0986b3626adb16685d4268dd572772fc3fb0ad4eaa4c827faa7c592040258610cad63
7
+ data.tar.gz: bc4ea0f5fabdece3ee0aef7b098035cecc66faad7d5714457b210510a3fa2e3ffd5b08e73ddeebb26f9704817e128f13cc8d2dbf5b03d4eae08b0a60c0d69781
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.5] - 2025-04-04
4
+
5
+ - Perf: Write prosopite logs to IO stream instead of file
6
+ - Remove upper bound on rails dependencies
7
+
8
+ ## [0.2.4] - 2025-03-03
9
+
10
+ - Add configuration option for setting script CSP nonce (thanks @matthaigh27)
11
+
3
12
  ## [0.2.3] - 2025-02-28
4
13
 
5
14
  - Add configuration API
data/README.md CHANGED
@@ -5,7 +5,14 @@
5
5
 
6
6
  WIP
7
7
 
8
- A modern profiler for Rails applications.
8
+ A modern profiler for your Rails application.
9
+
10
+ Utilizes [vernier](https://github.com/jhawthorn/vernier) for profiling and
11
+ [prosopite](https://github.com/charkost/prosopite) for N+1 query detection.
12
+
13
+ > [!NOTE]
14
+ > Check out the resources in the [Vernier](https://github.com/jhawthorn/vernier) project for more information on how to
15
+ > interpret the viewer, as well as comparisons with other profilers, including `stackprof`.
9
16
 
10
17
  Check out the demo:
11
18
  [![Demo](https://img.youtube.com/vi/LPXtfJ0c284/maxresdefault.jpg)](https://youtu.be/LPXtfJ0c284)
@@ -39,12 +46,40 @@ mount Dial::Engine, at: "/" if Rails.env.development?
39
46
  # config/initializers/dial.rb
40
47
 
41
48
  Dial.configure do |config|
42
- config.vernier_interval = 100 # default: 200
43
- config.vernier_allocation_interval = 10_000 # default: 20_000
44
- config.prosopite_ignore_queries += [/pg_sleep/i] # default: [/schema_migrations/i]
49
+ config.vernier_interval = 100
50
+ config.vernier_allocation_interval = 10_000
51
+ config.prosopite_ignore_queries += [/pg_sleep/i]
45
52
  end
46
53
  ```
47
54
 
55
+ ## Options
56
+
57
+ Option | Description | Default
58
+ :- | :- | :-
59
+ `vernier_interval` | Sets the `interval` option for vernier. | `200`
60
+ `vernier_allocation_interval` | Sets the `allocation_interval` option for vernier. | `20_000`
61
+ `prosopite_ignore_queries` | Sets the `ignore_queries` option for prosopite. | `[/schema_migrations/i]`
62
+ `content_security_policy_nonce` | Sets the content security policy nonce to use when inserting Dial's script. Can be a string, or a Proc which receives `env` and response `headers` as arguments and returns the nonce string. | Rails generated nonce or `nil`
63
+
64
+ ## Comparison with [rack-mini-profiler](https://github.com/MiniProfiler/rack-mini-profiler)
65
+
66
+ | | rack-mini-profiler | Dial |
67
+ | :------------------------ | :--------------------------------- | :------------------------------------------------------ |
68
+ | Compatibility | Any Rack application | Only Rails applications |
69
+ | Database Profiling | Yes | Yes (via vernier hook - marker table, chart) |
70
+ | N+1 Query Detection | Yes (*needs to be inferred) | Yes (via prosopite) |
71
+ | Ruby Profiling | Yes (with stackprof - flame graph) | Yes (via vernier - flame graph, stack chart, call tree) |
72
+ | Ruby Allocation Profiling | Yes (with stackprof - flame graph) | Yes (via vernier - flame graph, stack chart, call tree) |
73
+ | Memory Profiling | Yes (with memory_profiler) | Yes (*overall usage only) (via vernier hook - graph) |
74
+ | View Profiling | Yes | Yes (via vernier hook - marker table, chart) |
75
+ | Snapshot Sampling | Yes | No |
76
+ | Production Support | Yes | No (WIP) |
77
+
78
+ > [!NOTE]
79
+ > SQL queries displayed in the profile are not annotated with the caller location by default. If you're not using the
80
+ > [marginalia](https://github.com/basecamp/marginalia) gem to annotate your queries, you will need to extend your
81
+ > application's [ActiveRecord QueryLogs](https://edgeapi.rubyonrails.org/classes/ActiveRecord/QueryLogs.html) yourself.
82
+
48
83
  ## Development
49
84
 
50
85
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake test` to run the
@@ -60,5 +95,5 @@ The gem is available as open source under the terms of the [MIT License](https:/
60
95
 
61
96
  ## Code of Conduct
62
97
 
63
- Everyone interacting in the Dial project's codebases, issue trackers, chat rooms and mailing lists is expected to follow
64
- the [code of conduct](https://github.com/joshuay03/dial/blob/main/CODE_OF_CONDUCT.md).
98
+ Everyone interacting in the Dial project's codebase and issue tracker is expected to follow the
99
+ [code of conduct](https://github.com/joshuay03/dial/blob/main/CODE_OF_CONDUCT.md).
data/dial.gemspec CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
8
8
  spec.authors = ["Joshua Young"]
9
9
  spec.email = ["djry1999@gmail.com"]
10
10
 
11
- spec.summary = "A modern Rails profiler"
11
+ spec.summary = "A modern profiler for your Rails application"
12
12
  spec.homepage = "https://github.com/joshuay03/dial"
13
13
  spec.license = "MIT"
14
14
  spec.required_ruby_version = ">= 3.3.0"
@@ -19,9 +19,9 @@ Gem::Specification.new do |spec|
19
19
  spec.files = Dir["{lib}/**/*", "**/*.{gemspec,md,txt}"]
20
20
  spec.require_paths = ["lib"]
21
21
 
22
- spec.add_dependency "railties", ">= 7", "< 8.2"
23
- spec.add_dependency "activerecord", ">= 7", "< 8.2"
24
- spec.add_dependency "actionpack", ">= 7", "< 8.2"
22
+ spec.add_dependency "railties", ">= 7"
23
+ spec.add_dependency "activerecord", ">= 7"
24
+ spec.add_dependency "actionpack", ">= 7"
25
25
  spec.add_dependency "vernier"
26
26
  spec.add_dependency "prosopite"
27
27
  spec.add_dependency "pg_query"
@@ -15,14 +15,15 @@ module Dial
15
15
  vernier_interval: VERNIER_INTERVAL,
16
16
  vernier_allocation_interval: VERNIER_ALLOCATION_INTERVAL,
17
17
  prosopite_ignore_queries: PROSOPITE_IGNORE_QUERIES,
18
+ content_security_policy_nonce: -> (env, _headers) { env[NONCE] || "" },
18
19
  }
19
20
 
20
21
  @options.keys.each do |key|
21
- define_singleton_method(key) do
22
+ define_singleton_method key do
22
23
  @options[key]
23
24
  end
24
25
 
25
- define_singleton_method("#{key}=") do |value|
26
+ define_singleton_method "#{key}=" do |value|
26
27
  @options[key] = value
27
28
  end
28
29
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "rack"
4
+ require "action_dispatch"
4
5
 
5
6
  require_relative "version"
6
7
 
@@ -10,6 +11,7 @@ module Dial
10
11
  HTTP_ACCEPT = "HTTP_ACCEPT"
11
12
  CONTENT_TYPE = ::Rack::CONTENT_TYPE
12
13
  CONTENT_LENGTH = ::Rack::CONTENT_LENGTH
14
+ NONCE = ::ActionDispatch::ContentSecurityPolicy::Request::NONCE
13
15
  REQUEST_TIMING = "dial_request_timing"
14
16
 
15
17
  FILE_STALE_SECONDS = 60 * 60
@@ -17,8 +19,8 @@ module Dial
17
19
  VERNIER_INTERVAL = 200
18
20
  VERNIER_ALLOCATION_INTERVAL = 20_000
19
21
  VERNIER_PROFILE_OUT_RELATIVE_DIRNAME = "tmp/dial/profiles"
22
+ VERNIER_VIEWER_URL = "https://vernier.prof"
20
23
 
21
24
  PROSOPITE_IGNORE_QUERIES = [/schema_migrations/i].freeze
22
- PROSOPITE_LOG_RELATIVE_DIRNAME = "log/dial"
23
- PROSOPITE_LOG_FILENAME = "#{Util.uuid}_prosopite_#{PROGRAM_ID}.log".freeze
25
+ PROSOPITE_LOG_IO = StringIO.new
24
26
  end
@@ -9,7 +9,7 @@ Dial::Engine.routes.draw do
9
9
  if File.exist? path
10
10
  [
11
11
  200,
12
- { "Content-Type" => "application/json", "Access-Control-Allow-Origin" => "https://vernier.prof" },
12
+ { "Content-Type" => "application/json", "Access-Control-Allow-Origin" => Dial::VERNIER_VIEWER_URL },
13
13
  [File.read(path)]
14
14
  ]
15
15
  else
@@ -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, query_logs, ruby_vm_stat, gc_stat, gc_stat_heap, server_timing
8
+ def html env, headers, 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
 
@@ -69,7 +69,9 @@ module Dial
69
69
  </div>
70
70
  </div>
71
71
 
72
- <script>#{script}</script>
72
+ <script nonce="#{configured_nonce env, headers}">
73
+ #{script}
74
+ </script>
73
75
  HTML
74
76
  end
75
77
 
@@ -171,7 +173,7 @@ module Dial
171
173
  end
172
174
 
173
175
  def formatted_rails_route_info env
174
- rails_route_info = begin
176
+ begin
175
177
  ::Rails.application.routes.recognize_path env[::Rack::PATH_INFO], method: env[::Rack::REQUEST_METHOD]
176
178
  rescue ::ActionController::RoutingError
177
179
  {}
@@ -253,6 +255,15 @@ module Dial
253
255
  HTML
254
256
  end.join
255
257
  end
258
+
259
+ def configured_nonce env, headers
260
+ config_nonce = Dial._configuration.content_security_policy_nonce
261
+ if config_nonce.instance_of? Proc
262
+ config_nonce.call env, headers
263
+ else
264
+ config_nonce
265
+ end
266
+ end
256
267
  end
257
268
  end
258
269
  end
@@ -27,7 +27,7 @@ module Dial
27
27
  def stat_diff before, after, no_diff: []
28
28
  after.except(*no_diff).each_with_object({}) do |(key, value), diff|
29
29
  diff[key] = value - before[key]
30
- end.merge after.slice *no_diff
30
+ end.merge after.slice(*no_diff)
31
31
  end
32
32
  end
33
33
  end
@@ -51,10 +51,12 @@ module Dial
51
51
  body = String.new.tap do |str|
52
52
  rack_body.each { |chunk| str << chunk }
53
53
  rack_body.close if rack_body.respond_to? :close
54
- end.sub "</body>", <<~HTML
55
- #{Panel.html env, profile_out_filename, query_logs, ruby_vm_stat, gc_stat, gc_stat_heap, server_timing}
56
- </body>
57
- HTML
54
+
55
+ str.sub! "</body>", <<~HTML
56
+ #{Panel.html env, headers, profile_out_filename, query_logs, ruby_vm_stat, gc_stat, gc_stat_heap, server_timing}
57
+ </body>
58
+ HTML
59
+ end
58
60
 
59
61
  headers[CONTENT_LENGTH] = body.bytesize.to_s
60
62
 
@@ -86,16 +88,14 @@ module Dial
86
88
 
87
89
  def clear_query_logs!
88
90
  [].tap do |query_logs|
89
- File.open("#{query_log_dir_pathname}/#{PROSOPITE_LOG_FILENAME}", "r+") do |file|
90
- entry = section = count = nil
91
- file.each_line do |line|
92
- entry, section, count = process_query_log_line line, entry, section, count
93
- query_logs << entry if entry && section.nil?
94
- end
95
-
96
- file.truncate 0
97
- file.rewind
91
+ entry = section = count = nil
92
+ PROSOPITE_LOG_IO.string.lines.each do |line|
93
+ entry, section, count = process_query_log_line line, entry, section, count
94
+ query_logs << entry if entry && section.nil?
98
95
  end
96
+
97
+ PROSOPITE_LOG_IO.truncate 0
98
+ PROSOPITE_LOG_IO.rewind
99
99
  end
100
100
  end
101
101
 
@@ -126,9 +126,5 @@ module Dial
126
126
  def profile_out_dir_pathname
127
127
  ::Rails.root.join VERNIER_PROFILE_OUT_RELATIVE_DIRNAME
128
128
  end
129
-
130
- def query_log_dir_pathname
131
- ::Rails.root.join PROSOPITE_LOG_RELATIVE_DIRNAME
132
- end
133
129
  end
134
130
  end
data/lib/dial/railtie.rb CHANGED
@@ -31,16 +31,7 @@ module Dial
31
31
  require "pg_query"
32
32
  end
33
33
 
34
- prosopite_log_pathname = "#{query_log_dir_pathname}/#{PROSOPITE_LOG_FILENAME}"
35
- FileUtils.mkdir_p File.dirname prosopite_log_pathname
36
- FileUtils.touch prosopite_log_pathname
37
- ::Prosopite.custom_logger = ProsopiteLogger.new prosopite_log_pathname
38
- end
39
- end
40
-
41
- initializer "dial.clean_up_prosopite_log_files", after: :load_config_initializers do |app|
42
- stale_files("#{query_log_dir_pathname}/*.log").each do |query_log_file|
43
- File.delete query_log_file rescue nil
34
+ ::Prosopite.custom_logger = ProsopiteLogger.new PROSOPITE_LOG_IO
44
35
  end
45
36
  end
46
37
 
@@ -48,7 +39,6 @@ module Dial
48
39
  app.config.after_initialize do
49
40
  Dial._configuration.freeze
50
41
 
51
- # set static configuration options
52
42
  ::Prosopite.ignore_queries = Dial._configuration.prosopite_ignore_queries
53
43
  end
54
44
  end
@@ -65,9 +55,5 @@ module Dial
65
55
  def profile_out_dir_pathname
66
56
  ::Rails.root.join VERNIER_PROFILE_OUT_RELATIVE_DIRNAME
67
57
  end
68
-
69
- def query_log_dir_pathname
70
- ::Rails.root.join PROSOPITE_LOG_RELATIVE_DIRNAME
71
- end
72
58
  end
73
59
  end
data/lib/dial/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dial
4
- VERSION = "0.2.3"
4
+ VERSION = "0.2.5"
5
5
  end
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.2.3
4
+ version: 0.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Young
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-02-28 00:00:00.000000000 Z
10
+ date: 2025-04-04 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: railties
@@ -16,9 +16,6 @@ dependencies:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
18
  version: '7'
19
- - - "<"
20
- - !ruby/object:Gem::Version
21
- version: '8.2'
22
19
  type: :runtime
23
20
  prerelease: false
24
21
  version_requirements: !ruby/object:Gem::Requirement
@@ -26,9 +23,6 @@ dependencies:
26
23
  - - ">="
27
24
  - !ruby/object:Gem::Version
28
25
  version: '7'
29
- - - "<"
30
- - !ruby/object:Gem::Version
31
- version: '8.2'
32
26
  - !ruby/object:Gem::Dependency
33
27
  name: activerecord
34
28
  requirement: !ruby/object:Gem::Requirement
@@ -36,9 +30,6 @@ dependencies:
36
30
  - - ">="
37
31
  - !ruby/object:Gem::Version
38
32
  version: '7'
39
- - - "<"
40
- - !ruby/object:Gem::Version
41
- version: '8.2'
42
33
  type: :runtime
43
34
  prerelease: false
44
35
  version_requirements: !ruby/object:Gem::Requirement
@@ -46,9 +37,6 @@ dependencies:
46
37
  - - ">="
47
38
  - !ruby/object:Gem::Version
48
39
  version: '7'
49
- - - "<"
50
- - !ruby/object:Gem::Version
51
- version: '8.2'
52
40
  - !ruby/object:Gem::Dependency
53
41
  name: actionpack
54
42
  requirement: !ruby/object:Gem::Requirement
@@ -56,9 +44,6 @@ dependencies:
56
44
  - - ">="
57
45
  - !ruby/object:Gem::Version
58
46
  version: '7'
59
- - - "<"
60
- - !ruby/object:Gem::Version
61
- version: '8.2'
62
47
  type: :runtime
63
48
  prerelease: false
64
49
  version_requirements: !ruby/object:Gem::Requirement
@@ -66,9 +51,6 @@ dependencies:
66
51
  - - ">="
67
52
  - !ruby/object:Gem::Version
68
53
  version: '7'
69
- - - "<"
70
- - !ruby/object:Gem::Version
71
- version: '8.2'
72
54
  - !ruby/object:Gem::Dependency
73
55
  name: vernier
74
56
  requirement: !ruby/object:Gem::Requirement
@@ -157,5 +139,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
157
139
  requirements: []
158
140
  rubygems_version: 3.6.2
159
141
  specification_version: 4
160
- summary: A modern Rails profiler
142
+ summary: A modern profiler for your Rails application
161
143
  test_files: []