vernier 0.5.1 → 0.7.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: da42588d9f2409a9d82a79439014490ecd81f00cb74c975267d6568a6e343c46
4
- data.tar.gz: 921543f2b8cfec4e5803c4a47c8953df1acff567b88ddce2e1a65faad9e7c895
3
+ metadata.gz: 4988794520691870cb34178385be791e28fa4806eb1f6934b0d0abf52c8a070c
4
+ data.tar.gz: b1e63c6f5398f61fb68d634f17f33dc6aa7461344cb8acf446dd75ca52395818
5
5
  SHA512:
6
- metadata.gz: a674536e944ef26f2db5893821d9d643fc8f651d45b7a72923ae9ab7c01f270431f0e5fadea76e3bb3feeabe30edde203b520310cbbbae6a53f03edb148db570
7
- data.tar.gz: 1f39e9f04a2fd1c80525a72ce27aca6e8d614dfa9a4ba9f9721beff08f11a41431f6690c6215f0592dcc7923e49e516a4a0ddb812b493820877ceca81b4373ab
6
+ metadata.gz: '082561b2a381f88c540e607d1dd9c0d42d841a0de82fc61471fb2d67754ee14a4fd1e87fa71896b586077664e61fd1db623fd250cd13064d198c9dc91d3f13ed'
7
+ data.tar.gz: a28c197d405e4f52d999db3ae73187047886e1fcfb7d353d13f26ad5ba4b61f0675daf617119b37d72ad3760bd0dffcae234f2dcb0a0e960a60046dfb87dd784
data/Gemfile CHANGED
@@ -8,5 +8,6 @@ gemspec
8
8
  gem "rake", "~> 13.0"
9
9
 
10
10
  gem "rake-compiler"
11
+ gem "benchmark-ips"
11
12
 
12
13
  gem "minitest", "~> 5.0"
data/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  Next-generation Ruby 3.2.1+ sampling profiler. Tracks multiple threads, GVL activity, GC pauses, idle time, and more.
4
4
 
5
+ <img width="500" alt="Screenshot 2024-02-29 at 22 47 43" src="https://github.com/jhawthorn/vernier/assets/131752/aa995a41-d74f-405f-8ada-2522dd72c2c8">
6
+
5
7
  ## Examples
6
8
 
7
9
  [Livestreamed demo: Pairin' with Aaron (YouTube)](https://www.youtube.com/watch?v=9nvX3OHykGQ#t=27m43)
@@ -28,17 +30,33 @@ gem 'vernier'
28
30
 
29
31
  ## Usage
30
32
 
33
+ The output can be viewed in the web app at https://vernier.prof or locally using the [`profile-viewer` gem](https://github.com/tenderlove/profiler/tree/ruby) (both are lightly customized versions of the firefox profiler frontend, which profiles are also compatible with).
34
+
35
+ - **Flame Graph**: Shows proportionally how much time is spent within particular stack frames. Frames are grouped together, which means that x-axis / left-to-right order is not meaningful.
36
+ - **Stack Chart**: Shows the stack at each sample with the x-axis representing time and can be read left-to-right.
37
+
31
38
 
32
39
  ### Time
33
40
 
41
+
42
+ #### Command line
43
+
44
+ The easiest way to record a program or script is via the CLI
45
+
34
46
  ```
35
- Vernier.trace(out: "time_profile.json") { some_slow_method }
47
+ $ vernier run -- ruby -e 'sleep 1'
48
+ starting profiler with interval 500
49
+ #<Vernier::Result 1.001589 seconds, 1 threads, 2002 samples, 1 unique>
50
+ written to /tmp/profile20240328-82441-gkzffc.vernier.json
36
51
  ```
37
52
 
38
- The output can then be viewed in the Firefox Profiler (demo) or the [`profile-viewer` gem](https://github.com/tenderlove/profiler/tree/ruby) (a Ruby-customized version of the firefox profiler.
53
+ ### Block of code
39
54
 
40
- - **Flame Graph**: Shows proportionally how much time is spent within particular stack frames. Frames are grouped together, which means that x-axis / left-to-right order is not meaningful.
41
- - **Stack Chart**: Shows the stack at each sample with the x-axis representing time and can be read left-to-right.
55
+ ``` ruby
56
+ Vernier.run(out: "time_profile.json") do
57
+ some_slow_method
58
+ end
59
+ ```
42
60
 
43
61
  ### Retained memory
44
62
 
@@ -0,0 +1,62 @@
1
+ # Different (bad) ways to sleep
2
+
3
+ File.write("#{__dir__}/my_sleep.c", <<~EOF)
4
+ #include <time.h>
5
+ #include <sys/errno.h>
6
+
7
+ void my_sleep() {
8
+ struct timespec ts;
9
+ ts.tv_sec = 1;
10
+ ts.tv_nsec = 0;
11
+
12
+ int rc;
13
+ do {
14
+ rc = nanosleep(&ts, &ts);
15
+ } while (rc < 0 && errno == EINTR);
16
+ }
17
+ EOF
18
+
19
+ soext = RbConfig::CONFIG["SOEXT"]
20
+ system("gcc", "-shared", "-fPIC", "#{__dir__}/my_sleep.c", "-o", "#{__dir__}/my_sleep.#{soext}")
21
+
22
+ require "fiddle"
23
+
24
+ SLEEP_LIB = Fiddle.dlopen("./my_sleep.#{soext}")
25
+
26
+ def cfunc_sleep_gvl
27
+ Fiddle::Function.new(SLEEP_LIB['my_sleep'], [], Fiddle::TYPE_VOID, need_gvl: true).call
28
+ end
29
+
30
+ def cfunc_sleep_idle
31
+ Fiddle::Function.new(SLEEP_LIB['my_sleep'], [], Fiddle::TYPE_VOID, need_gvl: true).call
32
+ end
33
+
34
+ def ruby_sleep_gvl
35
+ target = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) + 1000
36
+ while Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) < target
37
+ i = 0
38
+ while i < 1000
39
+ i += 1
40
+ end
41
+ end
42
+ end
43
+
44
+ def sleep_idle
45
+ sleep 1
46
+ end
47
+
48
+ def run(name)
49
+ STDOUT.print "#{name}..."
50
+ STDOUT.flush
51
+
52
+ before = Process.clock_gettime(Process::CLOCK_MONOTONIC)
53
+ send(name)
54
+ after = Process.clock_gettime(Process::CLOCK_MONOTONIC)
55
+
56
+ STDOUT.puts " %.2fs" % (after - before)
57
+ end
58
+
59
+ run(:cfunc_sleep_gvl)
60
+ run(:cfunc_sleep_idle)
61
+ run(:ruby_sleep_gvl)
62
+ run(:sleep_idle)
@@ -0,0 +1,39 @@
1
+ require "benchmark/ips"
2
+ require "vernier"
3
+
4
+ def compare(**options, &block)
5
+ block.call
6
+
7
+ Benchmark.ips do |x|
8
+ x.report "no profiler" do |n|
9
+ n.times do
10
+ block.call
11
+ end
12
+ end
13
+
14
+ x.report "vernier" do |n|
15
+ Vernier.profile(**options) do
16
+ n.times do
17
+ block.call
18
+ end
19
+ end
20
+ end
21
+
22
+ x.compare!
23
+ end
24
+ end
25
+
26
+ compare do
27
+ i = 0
28
+ while i < 10_000
29
+ i += 1
30
+ end
31
+ end
32
+
33
+ compare(allocation_sample_rate: 1000) do
34
+ Object.new
35
+ end
36
+
37
+ compare(allocation_sample_rate: 1) do
38
+ Object.new
39
+ end
data/examples/rails.rb CHANGED
@@ -59,12 +59,17 @@ end
59
59
 
60
60
  silence do
61
61
  ActiveRecord::Schema.define do
62
+ create_table :users, force: true do |t|
63
+ t.string :login
64
+ end
62
65
  create_table :posts, force: true do |t|
66
+ t.belongs_to :user
63
67
  t.string :title
64
68
  t.text :body
65
69
  t.integer :likes
66
70
  end
67
71
  create_table :comments, force: true do |t|
72
+ t.belongs_to :user
68
73
  t.belongs_to :post
69
74
  t.string :title
70
75
  t.text :body
@@ -73,25 +78,33 @@ silence do
73
78
  end
74
79
  end
75
80
 
81
+ class User < ActiveRecord::Base
82
+ end
83
+
76
84
  class Post < ActiveRecord::Base
77
85
  has_many :comments
86
+ belongs_to :user
78
87
  end
79
88
 
80
89
  class Comment < ActiveRecord::Base
81
90
  belongs_to :post
91
+ belongs_to :user
82
92
  end
83
93
 
94
+ users = 0.upto(100).map do |i|
95
+ User.create!(login: "user#{i}")
96
+ end
84
97
  0.upto(100) do |i|
85
- post = Post.create!(title: "Post number #{i}", body: "blog " * 50, likes: ((i * 1337) % 30))
98
+ post = Post.create!(title: "Post number #{i}", body: "blog " * 50, likes: ((i * 1337) % 30), user: users.sample)
86
99
  5.times do
87
- post.comments.create!(post: post, title: "nice post!", body: "keep it up!", posted_at: Time.now)
100
+ post.comments.create!(post: post, title: "nice post!", body: "keep it up!", posted_at: Time.now, user: users.sample)
88
101
  end
89
102
  end
90
103
 
91
104
  class HomeController < ActionController::Base
92
105
  def show
93
- posts = Post.order(likes: :desc).includes(:comments).first(10)
94
- render json: posts
106
+ posts = Post.order(likes: :desc).includes(:user, comments: :user).first(10)
107
+ render json: posts, include: [:user, comments: { include: :user } ]
95
108
  end
96
109
  end
97
110
 
@@ -110,15 +123,7 @@ end
110
123
  # warm up
111
124
  make_request.call
112
125
 
113
- Vernier.trace(out: "rails.json") do |collector|
114
- ActiveSupport::Notifications.monotonic_subscribe do |name, start, finish, id, payload|
115
- collector.add_marker(
116
- name:,
117
- start: (start * 1_000_000_000).to_i,
118
- finish: (finish * 1_000_000_000).to_i,
119
- )
120
- end
121
-
126
+ Vernier.trace(out: "rails.json", hooks: [:rails], allocation_sample_rate: 100) do |collector|
122
127
  1000.times do
123
128
  make_request.call
124
129
  end
@@ -1,6 +1,6 @@
1
1
  require "vernier"
2
2
 
3
- Vernier.trace(out: "http_requests.json") do
3
+ Vernier.trace(out: "http_requests.json", allocation_sample_rate: 100) do
4
4
 
5
5
  require "net/http"
6
6
  require "uri"
data/exe/vernier CHANGED
@@ -16,6 +16,9 @@ parser = OptionParser.new(banner) do |o|
16
16
  o.on('--interval [MICROSECONDS]', Integer, "sampling interval (default 500)") do |i|
17
17
  options[:interval] = i
18
18
  end
19
+ o.on('--allocation_sample_rate [ALLOCATIONS]', Integer, "allocation sampling interval (default 0 disabled)") do |i|
20
+ options[:allocation_sample_rate] = i
21
+ end
19
22
  o.on('--signal [NAME]', String, "specify a signal to start and stop the profiler") do |s|
20
23
  options[:signal] = s
21
24
  end