Skip to content

Commit 24b9143

Browse files
Mifrillroute
authored andcommitted
Implement tracing
1 parent 70b5d85 commit 24b9143

File tree

8 files changed

+389
-28
lines changed

8 files changed

+389
-28
lines changed

README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ based on Ferrum and Mechanize.
4848
* [Dialogs](https://github.com/rubycdp/ferrum#dialogs)
4949
* [Animation](https://github.com/rubycdp/ferrum#animation)
5050
* [Node](https://github.com/rubycdp/ferrum#node)
51+
* [Tracing](https://github.com/rubycdp/ferrum#tracing)
5152
* [Thread safety](https://github.com/rubycdp/ferrum#thread-safety)
5253
* [Development](https://github.com/rubycdp/ferrum#development)
5354
* [Contributing](https://github.com/rubycdp/ferrum#contributing)
@@ -1148,6 +1149,50 @@ browser.at_xpath("//*[select]").select(["1", "2"])
11481149
```
11491150

11501151

1152+
## Tracing
1153+
1154+
You can use `tracing.record` to create a trace file which can be opened in Chrome DevTools or [timeline viewer](https://chromedevtools.github.io/timeline-viewer/).
1155+
1156+
```ruby
1157+
browser.page.tracing.record(path: "trace.json") do
1158+
browser.go_to("https://www.google.com")
1159+
end
1160+
```
1161+
1162+
#### tracing.record(\*\*options) : `Hash`
1163+
1164+
- By default: returns Trace data from `Tracing.tracingComplete` event.
1165+
- When `path` specified: return `true` and store Trace data from `Tracing.tracingComplete` event to file.
1166+
1167+
* options `Hash`
1168+
* `:path` (String) - `String` to save a Trace data output on the disk, not specified by default.
1169+
* `:encoding` (Symbol) - `:base64` | `:binary` setting only for memory Trace data output, `:binary` by default.
1170+
* `:timeout` (Integer) - [Timeout of promise](https://github.com/ruby-concurrency/concurrent-ruby/blob/52c08fca13cc3811673ea2f6fdb244a0e42e0ebe/lib/concurrent-ruby/concurrent/promises.rb#L986) to wait til event `Tracing.Complete` triggered that fills buffer/file, `nil` by default, means "wait forever".
1171+
* `:screenshots` (Boolean) - When true - Captures screenshots in the trace, `false` by default.
1172+
* `:included_categories` (Array[String]) - An array of categories that be included to tracing data, by default:
1173+
```ruby
1174+
["devtools.timeline",
1175+
"v8.execute",
1176+
"disabled-by-default-devtools.timeline",
1177+
"disabled-by-default-devtools.timeline.frame",
1178+
"toplevel",
1179+
"blink.console",
1180+
"blink.user_timing",
1181+
"latencyInfo",
1182+
"disabled-by-default-devtools.timeline.stack",
1183+
"disabled-by-default-v8.cpu_profiler",
1184+
"disabled-by-default-v8.cpu_profiler.hires"]
1185+
```
1186+
* `:excluded_categories` (Array[String]) - An array of categories that be excluded from tracing data, by default:
1187+
```ruby
1188+
["*"]
1189+
```
1190+
1191+
See all categories by `browser.client.command("Tracing.getCategories")`
1192+
1193+
Only one trace can be active at a time per browser.
1194+
1195+
11511196
## Thread safety ##
11521197

11531198
Ferrum is fully thread-safe. You can create one browser or a few as you wish and

lib/ferrum.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
require "ferrum/utils/platform"
44
require "ferrum/utils/elapsed_time"
55
require "ferrum/utils/attempt"
6+
require "ferrum/utils/stream"
67
require "ferrum/errors"
78
require "ferrum/browser"
89
require "ferrum/node"

lib/ferrum/page.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
require "ferrum/page/frames"
1111
require "ferrum/page/screenshot"
1212
require "ferrum/page/animation"
13+
require "ferrum/page/tracing"
1314
require "ferrum/browser/client"
1415

1516
module Ferrum
@@ -215,6 +216,10 @@ def subscribed?(event)
215216
@client.subscribed?(event)
216217
end
217218

219+
def tracing
220+
@tracing ||= Tracing.new(client: @client)
221+
end
222+
218223
private
219224

220225
def subscribe

lib/ferrum/page/screenshot.rb

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,8 @@ def pdf(**opts)
4242
path, encoding = common_options(**opts)
4343
options = pdf_options(**opts).merge(transferMode: "ReturnAsStream")
4444
handle = command("Page.printToPDF", **options).fetch("stream")
45-
46-
if path
47-
stream_to_file(handle, path: path)
48-
else
49-
stream_to_memory(handle, encoding: encoding)
45+
Utils::Stream.fetch(encoding: encoding, path: path) do |read_stream|
46+
read_stream.call(client: self, handle: handle)
5047
end
5148
end
5249

@@ -78,29 +75,6 @@ def save_file(path, data)
7875
File.binwrite(path.to_s, data)
7976
end
8077

81-
def stream_to_file(handle, path:)
82-
File.open(path, "wb") { |f| stream_to(handle, f) }
83-
true
84-
end
85-
86-
def stream_to_memory(handle, encoding:)
87-
data = String.new("") # Mutable string has << and compatible to File
88-
stream_to(handle, data)
89-
encoding == :base64 ? Base64.encode64(data) : data
90-
end
91-
92-
def stream_to(handle, output)
93-
loop do
94-
result = command("IO.read", handle: handle, size: STREAM_CHUNK)
95-
96-
data_chunk = result["data"]
97-
data_chunk = Base64.decode64(data_chunk) if result["base64Encoded"]
98-
output << data_chunk
99-
100-
break if result["eof"]
101-
end
102-
end
103-
10478
def common_options(encoding: :base64, path: nil, **_)
10579
encoding = encoding.to_sym
10680
encoding = :binary if path

lib/ferrum/page/tracing.rb

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# frozen_string_literal: true
2+
3+
module Ferrum
4+
class Page
5+
class Tracing
6+
INCLUDED_CATEGORIES = %w[
7+
devtools.timeline
8+
v8.execute
9+
disabled-by-default-devtools.timeline
10+
disabled-by-default-devtools.timeline.frame
11+
toplevel
12+
blink.console
13+
blink.user_timing
14+
latencyInfo
15+
disabled-by-default-devtools.timeline.stack
16+
disabled-by-default-v8.cpu_profiler
17+
disabled-by-default-v8.cpu_profiler.hires
18+
].freeze
19+
EXCLUDED_CATEGORIES = %w[
20+
*
21+
].freeze
22+
23+
def initialize(client:)
24+
@client = client
25+
end
26+
27+
def record(options = {}, &block)
28+
@options = {
29+
timeout: nil,
30+
screenshots: false,
31+
encoding: :binary,
32+
included_categories: INCLUDED_CATEGORIES,
33+
excluded_categories: EXCLUDED_CATEGORIES,
34+
**options
35+
}
36+
@promise = Concurrent::Promises.resolvable_future
37+
subscribe_on_tracing_event
38+
start
39+
block.call
40+
@client.command("Tracing.end")
41+
@promise.value!(@options[:timeout])
42+
end
43+
44+
private
45+
46+
def start
47+
@client.command(
48+
"Tracing.start",
49+
transferMode: "ReturnAsStream",
50+
traceConfig: {
51+
includedCategories: included_categories,
52+
excludedCategories: @options[:excluded_categories]
53+
}
54+
)
55+
end
56+
57+
def included_categories
58+
included_categories = @options[:included_categories]
59+
if @options[:screenshots] == true
60+
included_categories = @options[:included_categories] | ["disabled-by-default-devtools.screenshot"]
61+
end
62+
included_categories
63+
end
64+
65+
def subscribe_on_tracing_event
66+
@client.on("Tracing.tracingComplete") do |event, index|
67+
next if index.to_i != 0
68+
69+
@promise.fulfill(stream(event.fetch("stream")))
70+
rescue StandardError => e
71+
@promise.reject(e)
72+
end
73+
end
74+
75+
def stream(handle)
76+
Utils::Stream.fetch(encoding: @options[:encoding], path: @options[:path]) do |read_stream|
77+
read_stream.call(client: @client, handle: handle)
78+
end
79+
end
80+
end
81+
end
82+
end

lib/ferrum/utils/stream.rb

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# frozen_string_literal: true
2+
3+
module Ferrum
4+
module Utils
5+
module Stream
6+
STREAM_CHUNK = 128 * 1024
7+
8+
module_function
9+
10+
def fetch(path:, encoding:, &block)
11+
if path.nil?
12+
stream_to_memory(encoding: encoding, &block)
13+
else
14+
stream_to_file(path: path, &block)
15+
end
16+
end
17+
18+
def stream_to_file(path:, &block)
19+
File.open(path, "wb") { |f| stream_to(f, &block) }
20+
true
21+
end
22+
23+
def stream_to_memory(encoding:, &block)
24+
data = String.new("") # Mutable string has << and compatible to File
25+
stream_to(data, &block)
26+
encoding == :base64 ? Base64.encode64(data) : data
27+
end
28+
29+
def stream_to(output, &block)
30+
loop do
31+
read_stream = lambda do |client:, handle:|
32+
client.command("IO.read", handle: handle, size: STREAM_CHUNK)
33+
end
34+
result = block.call(read_stream)
35+
data_chunk = result["data"]
36+
data_chunk = Base64.decode64(data_chunk) if result["base64Encoded"]
37+
output << data_chunk
38+
break if result["eof"]
39+
end
40+
end
41+
end
42+
end
43+
end

0 commit comments

Comments
 (0)