rorvswild 1.8.1 → 1.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d947482c8e791eebcf34ce670f5b35f4f6aa4bf547fec308119ed18839cfae4a
4
- data.tar.gz: 87f8a8ed97ea66cdc405209c92d03a637471b4507a22179ca2dde273f183ecda
3
+ metadata.gz: e4752356e6d6e1b7b9883b55c5f9f29223dedd2c31cc4abdac85065215b1b05b
4
+ data.tar.gz: 254eaff9c5412c5d1e121644d0993f0a8557748ac25370a9266ce1e61fe9f031
5
5
  SHA512:
6
- metadata.gz: 909542f1359138d44c8b8cec6ac8599e5b8e3eafb5d1eb27de0099be040a06e9753ff4068ba0b7f706c4b080aae18b7d340054679e9a6235fc82f8db05a0e0ea
7
- data.tar.gz: '080bdc4b330af84eb7aad989588a93a46b672c22725d8d6c534befddce6178765961b716915808bd7e4fe08d5d44f47c747402133573fce9135a9958fab92d25'
6
+ metadata.gz: b4b7acb9cc0fe303310648985dda337a3ab11c941af8c0e4d06199404064eee3c9a5cd93993f94e701fbc121f7f633123395fd89e5ddd3e28b52cd9cfa2e65e5
7
+ data.tar.gz: 404254bcfd4f9bb5c165da7955d712f1435514c061f70b019c84fbc1973626b71935a87ee8b1eae9b9b2741d79e74d2cfaef8378472f65b2a22ca78e03a99590
@@ -114,8 +114,8 @@ module RorVsWild
114
114
  end
115
115
  end
116
116
 
117
- def start_request
118
- current_data || initialize_data
117
+ def start_request(queue_time_ms = 0)
118
+ current_data || initialize_data(queue_time_ms)
119
119
  end
120
120
 
121
121
  def stop_request
@@ -195,9 +195,9 @@ module RorVsWild
195
195
 
196
196
  private
197
197
 
198
- def initialize_data
198
+ def initialize_data(queue_time_ms = 0)
199
199
  Thread.current[:rorvswild_data] = {
200
- started_at: RorVsWild.clock_milliseconds,
200
+ started_at: RorVsWild.clock_milliseconds - queue_time_ms,
201
201
  gc_section: Section.start_gc_timing,
202
202
  environment: Host.to_h,
203
203
  section_stack: [],
@@ -91,7 +91,7 @@ module RorVsWild
91
91
  end
92
92
 
93
93
  def self.shell(command)
94
- stdout, stderr, process = Open3.capture3(command) rescue nil
94
+ stdout, _, process = Open3.capture3(command) rescue nil
95
95
  stdout if process && process.success?
96
96
  end
97
97
  end
@@ -8,7 +8,7 @@ module RorVsWild
8
8
 
9
9
  def self.os
10
10
  @os_description ||= `uname -sr`.strip
11
- rescue Exception => ex
11
+ rescue Exception
12
12
  @os_description = RbConfig::CONFIG["host_os"]
13
13
  end
14
14
 
@@ -189,11 +189,12 @@ RorVsWild.Local.Request.prototype.runtime = function() {
189
189
  RorVsWild.Local.Request.prototype.sections = function() {
190
190
  return this.data.sections.map(function(section) {
191
191
  var runtime = (section.total_runtime - section.children_runtime)
192
- var object = {
192
+ return {
193
193
  id: RorVsWild.Local.nextId(),
194
194
  impact: RorVsWild.Local.formatImpact(runtime * 100 / this.data.runtime),
195
195
  language: RorVsWild.Local.kindToLanguage(section.kind),
196
196
  totalRuntime: RorVsWild.Local.relevantRounding(section.total_runtime),
197
+ asyncRuntime: RorVsWild.Local.relevantRounding(section.async_runtime),
197
198
  childrenRuntime: RorVsWild.Local.relevantRounding(section.children_runtime),
198
199
  selfRuntime: RorVsWild.Local.relevantRounding(runtime),
199
200
  runtime: RorVsWild.Local.relevantRounding(runtime),
@@ -204,9 +205,9 @@ RorVsWild.Local.Request.prototype.sections = function() {
204
205
  file: section.file,
205
206
  line: section.line,
206
207
  location: section.kind == "view" ? section.file : section.file + ":" + section.line,
207
- locationUrl: RorVsWild.Local.pathToUrl(this.data.environment.cwd, section.file, section.line)
208
+ locationUrl: RorVsWild.Local.pathToUrl(this.data.environment.cwd, section.file, section.line),
209
+ isAsync: RorVsWild.Local.relevantRounding(section.async_runtime) > 0,
208
210
  }
209
- return object
210
211
  }.bind(this)).sort(function(a, b) { return b.selfRuntime - a.selfRuntime })
211
212
  }
212
213
 
@@ -179,6 +179,12 @@
179
179
 
180
180
  <div class="rorvswild-local-panel__details__section__code">
181
181
  <dl>
182
+ {{#isAsync}}
183
+ <div>
184
+ <dt title="Time spent as non blocking IO">Async runtime</dt>
185
+ <dd>{{asyncRuntime}}<small>ms</small></dd>
186
+ </div>
187
+ {{/isAsync}}
182
188
  <div>
183
189
  <dt title="self + children">Total runtime</dt>
184
190
  <dd title="{{selfRuntime}} + {{childrenRuntime}}">{{totalRuntime}}<small>ms</small></dd>
@@ -1,11 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RorVsWild
2
4
  module Plugin
3
5
  class ActionView
4
6
  def self.setup
5
7
  return if @installed
6
8
  return unless defined?(::ActiveSupport::Notifications.subscribe)
7
- ActiveSupport::Notifications.subscribe("render_partial.action_view", new)
8
- ActiveSupport::Notifications.subscribe("render_template.action_view", new)
9
+ ActiveSupport::Notifications.subscribe("render_partial.action_view", plugin = new)
10
+ ActiveSupport::Notifications.subscribe("render_template.action_view", plugin)
11
+ ActiveSupport::Notifications.subscribe("render_collection.action_view", plugin)
9
12
  @installed = true
10
13
  end
11
14
 
@@ -15,10 +18,11 @@ module RorVsWild
15
18
 
16
19
  def finish(name, id, payload)
17
20
  RorVsWild::Section.stop do |section|
18
- section.kind = "view".freeze
21
+ section.kind = "view"
19
22
  section.commands << RorVsWild.agent.locator.relative_path(payload[:identifier])
20
23
  section.file = section.command
21
- section.line = 1
24
+ section.line = 0
25
+ section.calls = payload[:count] if payload[:count] # render collection
22
26
  end
23
27
  end
24
28
  end
@@ -29,6 +29,18 @@ module RorVsWild
29
29
  RorVsWild::Section.stop
30
30
  end
31
31
 
32
+ # Async queries
33
+ def publish_event(event)
34
+ section = Section.new
35
+ section.total_ms = event.payload[:lock_wait]
36
+ section.async_ms = event.duration - event.payload[:lock_wait]
37
+ section.gc_time_ms = event.respond_to?(:gc_time) ? event.gc_time : 0 # gc_time since Rails 7.2.0
38
+ section.commands << normalize_sql_query(event.payload[:sql])
39
+ section.kind = "sql"
40
+ (parent = Section.current) && parent.children_ms += section.total_ms
41
+ RorVsWild.agent.add_section(section)
42
+ end
43
+
32
44
  SQL_STRING_REGEX = /'((?:''|\\'|[^'])*)'/
33
45
  SQL_NUMERIC_REGEX = /(?<!\w)\d+(\.\d+)?(?!\w)/
34
46
  SQL_PARAMETER_REGEX = /\$\d+/
@@ -3,6 +3,49 @@
3
3
  module RorVsWild
4
4
  module Plugin
5
5
  class Middleware
6
+ module RequestQueueTime
7
+
8
+ ACCEPTABLE_HEADERS = [
9
+ 'HTTP_X_REQUEST_START',
10
+ 'HTTP_X_QUEUE_START',
11
+ 'HTTP_X_MIDDLEWARE_START'
12
+ ].freeze
13
+
14
+ MINIMUM_TIMESTAMP = 1577836800.freeze # 2020/01/01 UTC
15
+ DIVISORS = [1_000_000, 1_000, 1].freeze
16
+
17
+ def parse_queue_time_header(env)
18
+ return unless env
19
+
20
+ earliest = nil
21
+
22
+ ACCEPTABLE_HEADERS.each do |header|
23
+ if (header_value = env[header])
24
+ timestamp = parse_timestamp(header_value.delete_prefix("t="))
25
+ if timestamp && (!earliest || timestamp < earliest)
26
+ earliest = timestamp
27
+ end
28
+ end
29
+ end
30
+
31
+ [earliest, Time.now.to_f].min if earliest
32
+ end
33
+
34
+ private
35
+
36
+ def parse_timestamp(timestamp)
37
+ timestamp = timestamp.to_f
38
+ return unless timestamp.finite?
39
+
40
+ DIVISORS.each do |divisor|
41
+ t = timestamp / divisor
42
+ return t if t > MINIMUM_TIMESTAMP
43
+ end
44
+ end
45
+ end
46
+
47
+ include RequestQueueTime
48
+
6
49
  def self.setup
7
50
  return if @installed
8
51
  Rails.application.config.middleware.unshift(RorVsWild::Plugin::Middleware, nil) if defined?(Rails)
@@ -14,8 +57,10 @@ module RorVsWild
14
57
  end
15
58
 
16
59
  def call(env)
17
- RorVsWild.agent.start_request
60
+ queue_time_ms = calculate_queue_time(env)
61
+ RorVsWild.agent.start_request(queue_time_ms || 0)
18
62
  RorVsWild.agent.current_data[:path] = env["ORIGINAL_FULLPATH"]
63
+ add_queue_time_section(queue_time_ms)
19
64
  section = RorVsWild::Section.start
20
65
  section.file, section.line = rails_engine_location
21
66
  section.commands << "Rails::Engine#call"
@@ -28,6 +73,25 @@ module RorVsWild
28
73
 
29
74
  private
30
75
 
76
+ def add_queue_time_section(queue_time_ms)
77
+ return unless queue_time_ms
78
+
79
+ section = Section.new
80
+ section.stop
81
+ section.total_ms = queue_time_ms
82
+ section.gc_time_ms = 0
83
+ section.file = "request-queue"
84
+ section.line = 0
85
+ section.kind = "queue"
86
+ RorVsWild.agent.add_section(section)
87
+ end
88
+
89
+ def calculate_queue_time(env)
90
+ queue_time_from_header = parse_queue_time_header(env)
91
+
92
+ ((Time.now.to_f - queue_time_from_header) * 1000).round if queue_time_from_header
93
+ end
94
+
31
95
  def rails_engine_location
32
96
  @rails_engine_location = ::Rails::Engine.instance_method(:call).source_location
33
97
  end
@@ -3,7 +3,7 @@
3
3
  module RorVsWild
4
4
  class Section
5
5
  attr_reader :start_ms, :commands, :gc_start_ms
6
- attr_accessor :kind, :file, :line, :calls, :children_ms, :total_ms, :gc_time_ms
6
+ attr_accessor :kind, :file, :line, :calls, :children_ms, :total_ms, :gc_time_ms, :async_ms
7
7
 
8
8
  def self.start(&block)
9
9
  section = Section.new
@@ -31,7 +31,7 @@ module RorVsWild
31
31
  def self.start_gc_timing
32
32
  section = Section.new
33
33
  section.calls = GC.count
34
- section.file, section.line = "ruby/gc.c", 42
34
+ section.file, section.line = "ruby/gc.c", 0
35
35
  section.add_command("GC.start")
36
36
  section.kind = "gc"
37
37
  section
@@ -62,6 +62,7 @@ module RorVsWild
62
62
  @calls = 1
63
63
  @total_ms = 0
64
64
  @children_ms = 0
65
+ @async_ms = 0
65
66
  @kind = "code"
66
67
  location = RorVsWild.agent.locator.find_most_relevant_location(caller_locations)
67
68
  @file = RorVsWild.agent.locator.relative_path(location.path)
@@ -93,7 +94,7 @@ module RorVsWild
93
94
  end
94
95
 
95
96
  def as_json(options = nil)
96
- {calls: calls, total_runtime: total_ms, children_runtime: children_ms, kind: kind, started_at: start_ms, file: file, line: line, command: command}
97
+ {calls: calls, total_runtime: total_ms, children_runtime: children_ms, async_runtime: async_ms, kind: kind, started_at: start_ms, file: file, line: line, command: command}
97
98
  end
98
99
 
99
100
  def to_json(options = {})
@@ -1,3 +1,3 @@
1
1
  module RorVsWild
2
- VERSION = "1.8.1".freeze
2
+ VERSION = "1.9.0".freeze
3
3
  end
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rorvswild
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.8.1
4
+ version: 1.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexis Bernard
8
8
  - Antoine Marguerie
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2024-10-17 00:00:00.000000000 Z
12
+ date: 2025-01-30 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: Performances and errors insights for rails developers.
15
15
  email:
@@ -71,7 +71,7 @@ licenses:
71
71
  metadata:
72
72
  source_code_uri: https://github.com/BaseSecrete/rorvswild
73
73
  changelog_uri: https://github.com/BaseSecrete/rorvswild/blob/master/CHANGELOG.md
74
- post_install_message:
74
+ post_install_message:
75
75
  rdoc_options: []
76
76
  require_paths:
77
77
  - lib
@@ -86,8 +86,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
86
86
  - !ruby/object:Gem::Version
87
87
  version: '0'
88
88
  requirements: []
89
- rubygems_version: 3.5.9
90
- signing_key:
89
+ rubygems_version: 3.5.22
90
+ signing_key:
91
91
  specification_version: 4
92
92
  summary: Ruby on Rails applications monitoring
93
93
  test_files: []