rorvswild 1.8.0 → 1.9.0

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: 1da086576b7ffa892064483efd18f9c545ff1ee6e960f23374e82e78722056c5
4
- data.tar.gz: cfc4561f2aec3a8880b80e81daf71492516d2d137901e649db588fe4a05988f1
3
+ metadata.gz: e4752356e6d6e1b7b9883b55c5f9f29223dedd2c31cc4abdac85065215b1b05b
4
+ data.tar.gz: 254eaff9c5412c5d1e121644d0993f0a8557748ac25370a9266ce1e61fe9f031
5
5
  SHA512:
6
- metadata.gz: bf988a2610372ae91dd61503005382f962b9656dcfd4f5edd48875a281e133cb333c6b1424d001dd76109522b71ea7c3818a69fffa687160bbc3b1ae992d6d9a
7
- data.tar.gz: 41ab36b5b9e3eb59bfd9f28edc6a96fce2081df3be66fc4ac3217d94de4606f0edc858f09ece8a7700b0a9b7be84b65e9f779d968294186ccd4b3cc1d6a43c80
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
 
@@ -1,4 +1,4 @@
1
- <div id="RorVsWild.Local" class="<%= widget_css %>" >
1
+ <div id="RorVsWild.Local" class="is-<%= widget_position %>" >
2
2
  <% if @current_request %>
3
3
  <div data-barber="RorVsWild.Local" data-editor-url="<%= editor_url %>" data-request-uuid="<%= @current_request["uuid"] %>"></div>
4
4
  <% else %>
@@ -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>
@@ -35,9 +35,9 @@ module RorVsWild
35
35
  if status >= 200 && status < 300 && headers["Content-Type"] && headers["Content-Type"].include?("text/html")
36
36
  if headers["Content-Encoding"]
37
37
  log_incompatible_middleware_warning
38
- elsif body.respond_to?(:each)
38
+ elsif body.respond_to?(:each) && widget_position != "hidden"
39
39
  content_length = 0
40
- @current_request = RorVsWild.agent.queue.requests.first
40
+ @current_request = RorVsWild.agent.queue.requests.first
41
41
  body.each do |string|
42
42
  inject_into(string)
43
43
  content_length += string.size
@@ -74,9 +74,8 @@ module RorVsWild
74
74
 
75
75
  private
76
76
 
77
- def widget_css
78
- config = RorVsWild.agent.config
79
- config && config[:widget] && "is-#{config[:widget]}"
77
+ def widget_position
78
+ (config = RorVsWild.agent.config) && config[:widget]
80
79
  end
81
80
 
82
81
  def editor_url
@@ -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 = {})
@@ -107,7 +108,7 @@ module RorVsWild
107
108
  COMMAND_MAX_SIZE = 5_000
108
109
 
109
110
  def command
110
- string = @commands.join("\n")
111
+ string = @commands.to_a.join("\n")
111
112
  string.size > COMMAND_MAX_SIZE ? string[0, COMMAND_MAX_SIZE] + " [TRUNCATED]" : string
112
113
  end
113
114
  end
@@ -1,3 +1,3 @@
1
1
  module RorVsWild
2
- VERSION = "1.8.0".freeze
2
+ VERSION = "1.9.0".freeze
3
3
  end
data/lib/rorvswild.rb CHANGED
@@ -83,7 +83,7 @@ module RorVsWild
83
83
  def self.check
84
84
  api_key = RorVsWild.agent.config[:api_key]
85
85
  agent.client.instance_variable_set(:@http_unauthorized, false)
86
- return puts "You API key is missing and has to be defined in config/rorvswild.yml." if !api_key || api_key.empty?
86
+ return puts "Your API key is missing and has to be defined in config/rorvswild.yml." if !api_key || api_key.empty?
87
87
  puts case response = agent.client.post("/jobs", jobs: [{sections: [], name: "RorVsWild.check", runtime: 0}])
88
88
  when Net::HTTPOK then "Connection to RorVsWild works fine !"
89
89
  when Net::HTTPUnauthorized then "Wrong API key"
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.0
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-06-14 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.2.22
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: []