dial 0.1.1 → 0.1.3

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: f762021d933bef646eb988e55b0ec442340bfa99da4f4c1d66a14cbe26f25054
4
- data.tar.gz: bb66fb389dad9e99b60254082fc908db828cd67ea04db037ea964c7d34a5258a
3
+ metadata.gz: dcc8b8f15acf035328c3330930476af9621529608cabcb511b240cc7da9a9ecf
4
+ data.tar.gz: 05b4c3610edad8e1b6b4cb1cad387ea962d7e250c8cb165a845926bbd668e37c
5
5
  SHA512:
6
- metadata.gz: ab5085f5003418be961eb813f945949500e3a122cb90af764e28c2c2378e815362e00cc45fa146c5fe1daf179bfce17a5a86e2fd11e7d806c86d7779ca4b522e
7
- data.tar.gz: 6cba71872c47594b3964a8ffe17b802e7564694d83aceb9416f51048e68f42e0682e7b9546e304cdb7cc3ece8a8396e5142692381e93e68ba92bab15a8084e68
6
+ metadata.gz: 9bf37053b4f2bc59bd322646d7485326378c6d234d822571e59f14530ea4382830a6b842f514bef3f9fc7d6719b9b212251023b5c6a214304fb920aac0c4abd3
7
+ data.tar.gz: c5688daab1bd6d82310bab1270a243e507eb54bbc12cbf187c2d3237e43fe1d5022df34fa7f105b642ca563f10f7b343181a74bc5c1864a1dd36d5cca6d15f85
data/CHANGELOG.md CHANGED
@@ -1,9 +1,13 @@
1
1
  ## [Unreleased]
2
2
 
3
- ## [0.1.1] - 2024-11-08
3
+ ## [0.1.3] - 2024-11-14
4
+
5
+ - Enable allocation profiling by default
4
6
 
5
- - Set upper bound on dependencies
7
+ ## [0.1.2] - 2024-11-10
6
8
 
7
- ## [0.1.0] - 2024-11-08
9
+ - Add support for request profiling with vernier
10
+
11
+ ## [0.1.1] - 2024-11-08
8
12
 
9
13
  - Initial release
data/README.md CHANGED
@@ -1,25 +1,24 @@
1
1
  # Dial
2
2
 
3
+ ![Version](https://img.shields.io/gem/v/dial)
4
+ ![Build](https://img.shields.io/github/actions/workflow/status/joshuay03/dial/.github/workflows/main.yml?branch=main)
5
+
3
6
  WIP
4
7
 
5
8
  A modern profiler for Rails applications.
6
9
 
7
- <details>
8
- <summary>POC</summary>
9
-
10
- ![Demo 1](https://github.com/user-attachments/assets/904daaf5-3f18-4c94-a7e4-9418539a19f3)
11
- ![Demo 2](https://github.com/user-attachments/assets/eb6ed9f5-b258-42df-8901-222c7d969fdd)
12
-
13
- </details>
10
+ https://github.com/user-attachments/assets/bae59681-ebeb-42b3-9489-9692c072c3dc
14
11
 
15
12
  ## Installation
16
13
 
17
- Install the gem and add it to your application's Gemfile by executing:
14
+ Install the gem and add it to your Rails application's Gemfile by executing:
18
15
 
19
16
  ```bash
20
- bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
17
+ bundle add dial
21
18
  ```
22
19
 
20
+ and everything should just work.
21
+
23
22
  ## Development
24
23
 
25
24
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake test` to run the
data/dial.gemspec CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
11
11
  spec.summary = "A modern Rails profiler"
12
12
  spec.homepage = "https://github.com/joshuay03/dial"
13
13
  spec.license = "MIT"
14
- spec.required_ruby_version = ">= 3.0.0"
14
+ spec.required_ruby_version = ">= 3.2.1"
15
15
 
16
16
  spec.metadata["source_code_uri"] = spec.homepage
17
17
  spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
@@ -19,7 +19,8 @@ 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.0.0", "<= 8.0"
23
- spec.add_dependency "activerecord", ">= 7.0.0", "<= 8.0"
24
- spec.add_dependency "actionpack", ">= 7.0.0", "<= 8.0"
22
+ spec.add_dependency "railties", ">= 7", "< 8.2"
23
+ spec.add_dependency "activerecord", ">= 7", "< 8.2"
24
+ spec.add_dependency "actionpack", ">= 7", "< 8.2"
25
+ spec.add_dependency "vernier", "~> 1.3"
25
26
  end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dial
4
- module Constants
5
- DIAL_REQUEST_TIMING = "dial_request_timing"
6
- end
4
+ REQUEST_TIMING_HEADER = "dial_request_timing"
5
+ PROFILE_OUT_RELATIVE_DIRNAME = "tmp/dial/profile/"
7
6
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ Dial::Engine.routes.draw do
4
+ scope path: "/dial", as: "dial" do
5
+ get "profile", to: lambda { |env|
6
+ uuid = env[::Rack::QUERY_STRING].sub "uuid=", ""
7
+ path = String ::Rails.root.join Dial::PROFILE_OUT_RELATIVE_DIRNAME, "#{uuid}.json"
8
+
9
+ if File.exist? path
10
+ [
11
+ 200,
12
+ { "Content-Type" => "application/json", "Access-Control-Allow-Origin" => "https://vernier.prof" },
13
+ [File.read(path)]
14
+ ]
15
+ else
16
+ [
17
+ 404,
18
+ { "Content-Type" => "text/plain" },
19
+ ["Not Found"]
20
+ ]
21
+ end
22
+ }
23
+ end
24
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails"
4
+
5
+ module Dial
6
+ class Engine < ::Rails::Engine
7
+ isolate_namespace Dial
8
+
9
+ paths["config/routes.rb"] = ["lib/dial/engine/routes.rb"]
10
+ end
11
+ end
@@ -1,19 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "constants"
3
+ require "uri"
4
4
 
5
5
  module Dial
6
6
  class Panel
7
7
  class << self
8
- include Constants
9
-
10
- def html env, ruby_vm_stat, gc_stat, gc_stat_heap, server_timing
8
+ def html env, profile_out_filename, ruby_vm_stat, gc_stat, gc_stat_heap, server_timing
11
9
  <<~HTML
12
10
  <style>#{style}</style>
13
11
 
14
12
  <div id="dial">
15
13
  <div id="dial-preview">
16
- <span>#{formatted_rails_route_info(env)} | #{formatted_request_timing(env)}</span>
14
+ <span>
15
+ #{formatted_rails_route_info env} |
16
+ #{formatted_request_timing env} |
17
+ #{formatted_profile_output env, profile_out_filename}
18
+ </span>
17
19
  <span>#{formatted_rails_version}</span>
18
20
  <span>#{formatted_rack_version}</span>
19
21
  <span>#{formatted_ruby_version}</span>
@@ -23,30 +25,30 @@ module Dial
23
25
  <hr>
24
26
 
25
27
  <details>
26
- <summary>RubyVM stat</summary>
28
+ <summary>Server timing</summary>
27
29
  <div class="section">
28
- #{formatted_ruby_vm_stat(ruby_vm_stat)}
30
+ #{formatted_server_timing server_timing}
29
31
  </div>
30
32
  </details>
31
33
 
32
34
  <details>
33
- <summary>GC stat</summary>
35
+ <summary>RubyVM stat</summary>
34
36
  <div class="section">
35
- #{formatted_gc_stat(gc_stat)}
37
+ #{formatted_ruby_vm_stat ruby_vm_stat}
36
38
  </div>
37
39
  </details>
38
40
 
39
41
  <details>
40
- <summary>GC stat heap</summary>
42
+ <summary>GC stat</summary>
41
43
  <div class="section">
42
- #{formatted_gc_stat_heap(gc_stat_heap)}
44
+ #{formatted_gc_stat gc_stat}
43
45
  </div>
44
46
  </details>
45
47
 
46
48
  <details>
47
- <summary>Server timing</summary>
49
+ <summary>GC stat heap</summary>
48
50
  <div class="section">
49
- #{formatted_server_timing(server_timing)}
51
+ #{formatted_gc_stat_heap gc_stat_heap}
50
52
  </div>
51
53
  </details>
52
54
  </div>
@@ -73,42 +75,41 @@ module Dial
73
75
  padding: 0.5rem;
74
76
  font-size: 0.85rem;
75
77
  color: black;
76
- cursor: pointer;
77
- }
78
-
79
- #dial-preview {
80
- display: flex;
81
- flex-direction: column;
82
- cursor: pointer;
83
- }
84
-
85
- #dial-details {
86
- display: none;
87
- }
88
-
89
- .section {
90
- display: flex;
91
- flex-direction: column;
92
- margin-top: 0.5rem;
93
- }
94
-
95
- span {
96
- text-align: left;
97
- }
98
-
99
- hr {
100
- width: -moz-available;
101
- margin-top: 0.5rem;
102
- }
103
-
104
- details {
105
- margin-top: 0.5rem;
106
- text-align: left;
107
- }
108
78
 
109
- summary {
110
- margin-bottom: 0.25rem;
111
- cursor: pointer;
79
+ #dial-preview {
80
+ display: flex;
81
+ flex-direction: column;
82
+ cursor: pointer;
83
+ }
84
+
85
+ #dial-details {
86
+ display: none;
87
+ }
88
+
89
+ .section {
90
+ display: flex;
91
+ flex-direction: column;
92
+ margin: 0.25rem 0 0 0;
93
+ }
94
+
95
+ span {
96
+ text-align: left;
97
+ }
98
+
99
+ hr {
100
+ width: -moz-available;
101
+ margin: 0.65rem 0 0 0;
102
+ }
103
+
104
+ details {
105
+ margin: 0.5rem 0 0 0;
106
+ text-align: left;
107
+ }
108
+
109
+ summary {
110
+ margin: 0.25rem 0 0 0;
111
+ cursor: pointer;
112
+ }
112
113
  }
113
114
  CSS
114
115
  end
@@ -135,7 +136,16 @@ module Dial
135
136
  end
136
137
 
137
138
  def formatted_request_timing env
138
- "<b>Request timing:</b> #{env[DIAL_REQUEST_TIMING]}ms"
139
+ "<b>Request timing:</b> #{env[REQUEST_TIMING_HEADER]}ms"
140
+ end
141
+
142
+ def formatted_profile_output env, profile_out_filename
143
+ uuid = profile_out_filename.delete_suffix ".json"
144
+ host = env[::Rack::HTTP_HOST]
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}"
147
+
148
+ "<a href='https://vernier.prof/from-url/#{profile_out_url}' target='_blank'>View profile</a>"
139
149
  end
140
150
 
141
151
  def formatted_rails_version
@@ -147,7 +157,18 @@ module Dial
147
157
  end
148
158
 
149
159
  def formatted_ruby_version
150
- "<b>Ruby version:</b> #{RUBY_DESCRIPTION}"
160
+ "<b>Ruby version:</b> #{::RUBY_DESCRIPTION}"
161
+ end
162
+
163
+ def formatted_server_timing server_timing
164
+ if server_timing.any?
165
+ server_timing
166
+ # TODO: Nested sorting
167
+ .sort_by { |_, timing| -timing }
168
+ .map { |event, timing| "<span><b>#{event}:</b> #{timing}</span>" }.join
169
+ else
170
+ "NA"
171
+ end
151
172
  end
152
173
 
153
174
  def formatted_ruby_vm_stat ruby_vm_stat
@@ -170,17 +191,6 @@ module Dial
170
191
  HTML
171
192
  end.join
172
193
  end
173
-
174
- def formatted_server_timing server_timing
175
- if server_timing.any?
176
- server_timing
177
- # TODO: Nested sorting
178
- .sort_by { |_, timing| -timing }
179
- .map { |event, timing| "<span><b>#{event}:</b> #{timing}</span>" }.join
180
- else
181
- "NA"
182
- end
183
- end
184
194
  end
185
195
  end
186
196
  end
@@ -5,8 +5,8 @@ module Dial
5
5
  private
6
6
 
7
7
  def server_timing headers
8
- timing = if (ActionDispatch.const_defined? :Constants) && (ActionDispatch::Constants.const_defined? :SERVER_TIMING)
9
- headers[ActionDispatch::Constants::SERVER_TIMING]
8
+ timing = if ::ActionDispatch.const_defined? "Constants::SERVER_TIMING"
9
+ headers[::ActionDispatch::Constants::SERVER_TIMING]
10
10
  else
11
11
  headers["Server-Timing"]
12
12
  end
@@ -5,11 +5,11 @@ module Dial
5
5
  private
6
6
 
7
7
  def ruby_vm_stat_diff before, after
8
- stat_diff before, after, except: [:next_shape_id]
8
+ stat_diff before, after, no_diff: [:next_shape_id]
9
9
  end
10
10
 
11
11
  def gc_stat_diff before, after
12
- stat_diff before, after, except: [
12
+ stat_diff before, after, no_diff: [
13
13
  :heap_allocatable_slots,
14
14
  :malloc_increase_bytes_limit,
15
15
  :remembered_wb_unprotected_objects_limit,
@@ -20,14 +20,14 @@ module Dial
20
20
 
21
21
  def gc_stat_heap_diff before, after
22
22
  after.each_with_object({}) do |(slot_index, slot_stat), diff|
23
- diff[slot_index] = stat_diff before[slot_index], slot_stat, except: [:slot_size]
23
+ diff[slot_index] = stat_diff before[slot_index], slot_stat, no_diff: [:slot_size]
24
24
  end
25
25
  end
26
26
 
27
- def stat_diff before, after, except: []
28
- after.except(*except).each_with_object({}) do |(key, value), diff|
27
+ def stat_diff before, after, no_diff: []
28
+ after.except(*no_diff).each_with_object({}) do |(key, value), diff|
29
29
  diff[key] = value - before[key]
30
- end
30
+ end.merge after.slice *no_diff
31
31
  end
32
32
  end
33
33
  end
@@ -1,43 +1,56 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "ruby_stat"
4
- require_relative "rails_stat"
3
+ require "vernier"
4
+
5
5
  require_relative "constants"
6
- require_relative "panel"
6
+ require_relative "middleware/panel"
7
+ require_relative "middleware/ruby_stat"
8
+ require_relative "middleware/rails_stat"
7
9
 
8
10
  module Dial
9
11
  class Middleware
10
12
  include RubyStat
11
13
  include RailsStat
12
- include Constants
13
14
 
14
15
  def initialize app
15
16
  @app = app
16
17
  end
17
18
 
18
19
  def call env
19
- start_time = ::Process.clock_gettime ::Process::CLOCK_MONOTONIC
20
+ start_time = Process.clock_gettime Process::CLOCK_MONOTONIC
20
21
 
21
22
  ruby_vm_stat_before = RubyVM.stat
22
23
  gc_stat_before = GC.stat
23
24
  gc_stat_heap_before = GC.stat_heap
24
25
 
25
- status, headers, rack_body = @app.call env
26
- return [status, headers, rack_body] unless headers[::Rack::CONTENT_TYPE]&.include? "text/html"
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}"
30
+
31
+ status, headers, rack_body = nil
32
+ ::Vernier.profile out: profile_out_pathname, interval: 500, allocation_interval: 1000 do
33
+ status, headers, rack_body = @app.call env
34
+ end
35
+
36
+ unless headers[::Rack::CONTENT_TYPE]&.include? "text/html"
37
+ File.delete profile_out_pathname if File.exist? profile_out_pathname
38
+ return [status, headers, rack_body]
39
+ end
27
40
 
28
- finish_time = ::Process.clock_gettime ::Process::CLOCK_MONOTONIC
29
- env[DIAL_REQUEST_TIMING] = ((finish_time - start_time) * 1_000).round 2
41
+ finish_time = Process.clock_gettime Process::CLOCK_MONOTONIC
42
+ env[REQUEST_TIMING_HEADER] = ((finish_time - start_time) * 1_000).round 2
30
43
 
31
- ruby_vm_stat_diff = ruby_vm_stat_diff ruby_vm_stat_before, RubyVM.stat
32
- gc_stat_diff = gc_stat_diff gc_stat_before, GC.stat
33
- gc_stat_heap_diff = gc_stat_heap_diff gc_stat_heap_before, GC.stat_heap
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
34
47
  server_timing = server_timing headers
35
48
 
36
49
  body = String.new.tap do |str|
37
50
  rack_body.each { |chunk| str << chunk }
38
- rack_body.close if body.respond_to? :close
51
+ rack_body.close if rack_body.respond_to? :close
39
52
  end.sub "</body>", <<~HTML
40
- #{Panel.html env, ruby_vm_stat_diff, gc_stat_diff, gc_stat_heap_diff, server_timing}
53
+ #{Panel.html env, profile_out_filename, ruby_vm_stat, gc_stat, gc_stat_heap, server_timing}
41
54
  </body>
42
55
  HTML
43
56
 
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.1.1"
4
+ VERSION = "0.1.3"
5
5
  end
data/lib/dial.rb CHANGED
@@ -2,3 +2,4 @@
2
2
 
3
3
  require_relative "dial/version"
4
4
  require_relative "dial/railtie"
5
+ require_relative "dial/engine"
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.1
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Young
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2024-11-08 00:00:00.000000000 Z
10
+ date: 2024-11-14 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: railties
@@ -15,60 +15,74 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: 7.0.0
19
- - - "<="
18
+ version: '7'
19
+ - - "<"
20
20
  - !ruby/object:Gem::Version
21
- version: '8.0'
21
+ version: '8.2'
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
25
25
  requirements:
26
26
  - - ">="
27
27
  - !ruby/object:Gem::Version
28
- version: 7.0.0
29
- - - "<="
28
+ version: '7'
29
+ - - "<"
30
30
  - !ruby/object:Gem::Version
31
- version: '8.0'
31
+ version: '8.2'
32
32
  - !ruby/object:Gem::Dependency
33
33
  name: activerecord
34
34
  requirement: !ruby/object:Gem::Requirement
35
35
  requirements:
36
36
  - - ">="
37
37
  - !ruby/object:Gem::Version
38
- version: 7.0.0
39
- - - "<="
38
+ version: '7'
39
+ - - "<"
40
40
  - !ruby/object:Gem::Version
41
- version: '8.0'
41
+ version: '8.2'
42
42
  type: :runtime
43
43
  prerelease: false
44
44
  version_requirements: !ruby/object:Gem::Requirement
45
45
  requirements:
46
46
  - - ">="
47
47
  - !ruby/object:Gem::Version
48
- version: 7.0.0
49
- - - "<="
48
+ version: '7'
49
+ - - "<"
50
50
  - !ruby/object:Gem::Version
51
- version: '8.0'
51
+ version: '8.2'
52
52
  - !ruby/object:Gem::Dependency
53
53
  name: actionpack
54
54
  requirement: !ruby/object:Gem::Requirement
55
55
  requirements:
56
56
  - - ">="
57
57
  - !ruby/object:Gem::Version
58
- version: 7.0.0
59
- - - "<="
58
+ version: '7'
59
+ - - "<"
60
60
  - !ruby/object:Gem::Version
61
- version: '8.0'
61
+ version: '8.2'
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: 7.0.0
69
- - - "<="
68
+ version: '7'
69
+ - - "<"
70
70
  - !ruby/object:Gem::Version
71
- version: '8.0'
71
+ version: '8.2'
72
+ - !ruby/object:Gem::Dependency
73
+ name: vernier
74
+ requirement: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - "~>"
77
+ - !ruby/object:Gem::Version
78
+ version: '1.3'
79
+ type: :runtime
80
+ prerelease: false
81
+ version_requirements: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - "~>"
84
+ - !ruby/object:Gem::Version
85
+ version: '1.3'
72
86
  email:
73
87
  - djry1999@gmail.com
74
88
  executables: []
@@ -82,11 +96,13 @@ files:
82
96
  - dial.gemspec
83
97
  - lib/dial.rb
84
98
  - lib/dial/constants.rb
99
+ - lib/dial/engine.rb
100
+ - lib/dial/engine/routes.rb
85
101
  - lib/dial/middleware.rb
86
- - lib/dial/panel.rb
87
- - lib/dial/rails_stat.rb
102
+ - lib/dial/middleware/panel.rb
103
+ - lib/dial/middleware/rails_stat.rb
104
+ - lib/dial/middleware/ruby_stat.rb
88
105
  - lib/dial/railtie.rb
89
- - lib/dial/ruby_stat.rb
90
106
  - lib/dial/version.rb
91
107
  homepage: https://github.com/joshuay03/dial
92
108
  licenses:
@@ -101,7 +117,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
101
117
  requirements:
102
118
  - - ">="
103
119
  - !ruby/object:Gem::Version
104
- version: 3.0.0
120
+ version: 3.2.1
105
121
  required_rubygems_version: !ruby/object:Gem::Requirement
106
122
  requirements:
107
123
  - - ">="