rorvswild 1.7.0 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -18,7 +18,7 @@ module RorVsWild
18
18
  RorVsWild.agent.current_data[:path] = env["ORIGINAL_FULLPATH"]
19
19
  section = RorVsWild::Section.start
20
20
  section.file, section.line = rails_engine_location
21
- section.command = "Rails::Engine#call"
21
+ section.commands << "Rails::Engine#call"
22
22
  code, headers, body = @app.call(env)
23
23
  [code, headers, body]
24
24
  ensure
@@ -26,23 +26,53 @@ module RorVsWild
26
26
  inject_server_timing(RorVsWild.agent.stop_request, headers)
27
27
  end
28
28
 
29
+ private
30
+
29
31
  def rails_engine_location
30
32
  @rails_engine_location = ::Rails::Engine.instance_method(:call).source_location
31
33
  end
32
34
 
33
- def format_server_timing(sections)
34
- sections.sort_by(&:self_runtime).reverse.map do |section|
35
+ def format_server_timing_header(sections)
36
+ sections.map do |section|
35
37
  if section.kind == "view"
36
- "#{section.kind};dur=#{section.self_runtime};desc=\"#{section.file}\""
38
+ "#{section.kind};dur=#{section.self_ms.round};desc=\"#{section.file}\""
37
39
  else
38
- "#{section.kind};dur=#{section.self_runtime};desc=\"#{section.file}:#{section.line}\""
40
+ "#{section.kind};dur=#{section.self_ms.round};desc=\"#{section.file}:#{section.line}\""
39
41
  end
40
42
  end.join(", ")
41
43
  end
42
44
 
45
+ def format_server_timing_ascii(sections, total_width = 80)
46
+ max_time = sections.map(&:self_ms).max
47
+ chart_width = (total_width * 0.25).to_i
48
+ rows = sections.map { |section|
49
+ [
50
+ section.kind == "view" ? section.file : "#{section.file}:#{section.line}",
51
+ "█" * (section.self_ms * (chart_width-1) / max_time),
52
+ "%.1fms" % section.self_ms,
53
+ ]
54
+ }
55
+ time_width = rows.map { |cols| cols[2].size }.max + 1
56
+ label_width = total_width - chart_width - time_width
57
+ rows.each { |cols| cols[0] = truncate_backwards(cols[0], label_width) }
58
+ template = "%-#{label_width}s%#{chart_width}s%#{time_width}s"
59
+ rows.map { |cols| format(template, *cols) }.join("\n")
60
+ end
61
+
62
+ def truncate_backwards(string, width)
63
+ string.size > width ? "…" + string[-(width - 1)..-1] : string
64
+ end
65
+
43
66
  def inject_server_timing(data, headers)
44
67
  return if !data || !data[:send_server_timing] || !(sections = data[:sections])
45
- headers["Server-Timing"] = format_server_timing(sections)
68
+ sections = sections.sort_by(&:self_ms).reverse[0,10]
69
+ headers["Server-Timing"] = format_server_timing_header(sections)
70
+ if data[:name] && RorVsWild.logger.level <= Logger::Severity::DEBUG
71
+ RorVsWild.logger.debug(["┤ #{data[:name]} ├".center(80, "─"),
72
+ format_server_timing_ascii(sections),
73
+ "─" * 80, nil].join("\n")
74
+ )
75
+ end
46
76
  end
47
77
  end
48
78
  end
@@ -15,8 +15,7 @@ module RorVsWild
15
15
  module V4
16
16
  def process(commands, &block)
17
17
  string = commands.map(&:first).join("\n")
18
- appendable = APPENDABLE_COMMANDS.include?(commands[0][0])
19
- RorVsWild.agent.measure_section(string, appendable_command: appendable, kind: "redis") do
18
+ RorVsWild.agent.measure_section(string, kind: "redis") do
20
19
  super(commands, &block)
21
20
  end
22
21
  end
@@ -24,8 +23,7 @@ module RorVsWild
24
23
 
25
24
  module V5
26
25
  def send_command(command, &block)
27
- appendable = APPENDABLE_COMMANDS.include?(command)
28
- RorVsWild.agent.measure_section(command[0], appendable_command: appendable, kind: "redis") do
26
+ RorVsWild.agent.measure_section(command[0], kind: "redis") do
29
27
  super(command, &block)
30
28
  end
31
29
  end
@@ -38,8 +36,6 @@ module RorVsWild
38
36
  RorVsWild.agent.measure_section("multi", kind: "redis") { super }
39
37
  end
40
38
  end
41
-
42
- APPENDABLE_COMMANDS = [:auth, :select]
43
39
  end
44
40
  end
45
41
  end
@@ -27,6 +27,10 @@ module RorVsWild
27
27
  push_to(requests, data) if !@request_sampling_rate || rand <= @request_sampling_rate
28
28
  end
29
29
 
30
+ def push_error(data)
31
+ client.post_async("/errors", error: data)
32
+ end
33
+
30
34
  def push_to(array, data)
31
35
  mutex.synchronize do
32
36
  wakeup_thread if array.push(data).size >= FLUSH_TRESHOLD || !thread
@@ -2,8 +2,8 @@
2
2
 
3
3
  module RorVsWild
4
4
  class Section
5
- attr_reader :started_at
6
- attr_accessor :kind, :file, :line, :calls, :command, :children_runtime, :total_runtime, :appendable_command
5
+ attr_reader :start_ms, :commands, :gc_start_ms
6
+ attr_accessor :kind, :file, :line, :calls, :children_ms, :total_ms, :gc_time_ms
7
7
 
8
8
  def self.start(&block)
9
9
  section = Section.new
@@ -15,8 +15,8 @@ module RorVsWild
15
15
  def self.stop(&block)
16
16
  return unless stack && section = stack.pop
17
17
  block.call(section) if block_given?
18
- section.total_runtime = RorVsWild.clock_milliseconds - section.started_at
19
- current.children_runtime += section.total_runtime if current
18
+ section.stop
19
+ current.children_ms += section.total_ms if current
20
20
  RorVsWild.agent.add_section(section)
21
21
  end
22
22
 
@@ -28,17 +28,52 @@ module RorVsWild
28
28
  (sections = stack) && sections.last
29
29
  end
30
30
 
31
+ def self.start_gc_timing
32
+ section = Section.new
33
+ section.calls = GC.count
34
+ section.file, section.line = "ruby/gc.c", 42
35
+ section.add_command("GC.start")
36
+ section.kind = "gc"
37
+ section
38
+ end
39
+
40
+ def self.stop_gc_timing(section)
41
+ section.total_ms = gc_total_ms - section.gc_start_ms
42
+ section.calls = GC.count - section.calls
43
+ section
44
+ end
45
+
46
+ if GC.respond_to?(:total_time)
47
+ def self.gc_total_ms
48
+ GC.total_time / 1_000_000.0 # nanosecond -> millisecond
49
+ end
50
+ else
51
+ def self.gc_total_ms
52
+ GC::Profiler.total_time * 1000 # second -> millisecond
53
+ end
54
+ end
55
+
31
56
  def initialize
57
+ @start_ms = RorVsWild.clock_milliseconds
58
+ @end_ms = nil
59
+ @gc_start_ms = Section.gc_total_ms
60
+ @gc_end_ms = nil
61
+ @gc_time_ms = 0
32
62
  @calls = 1
33
- @total_runtime = 0
34
- @children_runtime = 0
63
+ @total_ms = 0
64
+ @children_ms = 0
35
65
  @kind = "code"
36
- @started_at = RorVsWild.clock_milliseconds
37
66
  location = RorVsWild.agent.locator.find_most_relevant_location(caller_locations)
38
67
  @file = RorVsWild.agent.locator.relative_path(location.path)
39
68
  @line = location.lineno
40
- @command = nil
41
- @appendable_command = false
69
+ @commands = Set.new
70
+ end
71
+
72
+ def stop
73
+ @gc_end_ms = self.class.gc_total_ms
74
+ @gc_time_ms = @gc_end_ms - @gc_start_ms
75
+ @end_ms = RorVsWild.clock_milliseconds
76
+ @total_ms = @end_ms - @start_ms - gc_time_ms
42
77
  end
43
78
 
44
79
  def sibling?(section)
@@ -47,35 +82,33 @@ module RorVsWild
47
82
 
48
83
  def merge(section)
49
84
  self.calls += section.calls
50
- self.total_runtime += section.total_runtime
51
- self.children_runtime += section.children_runtime
52
- if section
53
- if appendable_command
54
- self.command = self.command.dup if self.command.frozen?
55
- self.command << "\n" + section.command
56
- end
57
- else
58
- self.command = section.command
59
- end
60
- self.appendable_command = appendable_command && section.appendable_command
85
+ self.total_ms += section.total_ms
86
+ self.children_ms += section.children_ms
87
+ self.gc_time_ms += section.gc_time_ms
88
+ commands.merge(section.commands)
61
89
  end
62
90
 
63
- def self_runtime
64
- total_runtime - children_runtime
91
+ def self_ms
92
+ total_ms - children_ms
65
93
  end
66
94
 
67
- COMMAND_MAX_SIZE = 5_000
95
+ 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
+ end
68
98
 
69
- def command=(value)
70
- @command = value && value.size > COMMAND_MAX_SIZE ? value[0, COMMAND_MAX_SIZE] + " [TRUNCATED]" : value
99
+ def to_json(options = {})
100
+ as_json.to_json(options)
71
101
  end
72
102
 
73
- def to_h
74
- {calls: calls, total_runtime: total_runtime, children_runtime: children_runtime, kind: kind, started_at: started_at, file: file, line: line, command: command}
103
+ def add_command(command)
104
+ commands << command
75
105
  end
76
106
 
77
- def to_json(options = {})
78
- to_h.to_json(options)
107
+ COMMAND_MAX_SIZE = 5_000
108
+
109
+ def command
110
+ string = @commands.join("\n")
111
+ string.size > COMMAND_MAX_SIZE ? string[0, COMMAND_MAX_SIZE] + " [TRUNCATED]" : string
79
112
  end
80
113
  end
81
114
  end
@@ -1,3 +1,3 @@
1
1
  module RorVsWild
2
- VERSION = "1.7.0".freeze
2
+ VERSION = "1.8.0".freeze
3
3
  end
data/lib/rorvswild.rb CHANGED
@@ -26,8 +26,14 @@ module RorVsWild
26
26
  @logger ||= initialize_logger
27
27
  end
28
28
 
29
- def self.measure(code_or_name = nil, &block)
30
- block ? measure_block(code_or_name, &block) : measure_code(code_or_name)
29
+ def self.measure(method_or_code = nil, &block)
30
+ if block
31
+ measure_block(method_or_code, &block)
32
+ elsif method_or_code.is_a?(Method) || method_or_code.is_a?(UnboundMethod)
33
+ measure_method(method_or_code)
34
+ else
35
+ measure_code(method_or_code)
36
+ end
31
37
  end
32
38
 
33
39
  def self.measure_code(code)
@@ -38,6 +44,10 @@ module RorVsWild
38
44
  agent ? agent.measure_block(name , &block) : block.call
39
45
  end
40
46
 
47
+ def self.measure_method(method)
48
+ agent.measure_method(method) if agent
49
+ end
50
+
41
51
  def self.catch_error(context = nil, &block)
42
52
  agent ? agent.catch_error(context, &block) : block.call
43
53
  end
@@ -67,7 +77,7 @@ module RorVsWild
67
77
  end
68
78
 
69
79
  def self.clock_milliseconds
70
- Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
80
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
71
81
  end
72
82
 
73
83
  def self.check
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rorvswild
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.0
4
+ version: 1.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexis Bernard
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2024-04-22 00:00:00.000000000 Z
12
+ date: 2024-06-14 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: Performances and errors insights for rails developers.
15
15
  email: