dial 0.1.1 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
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
  - - ">="