vernier 0.5.1 → 0.7.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: 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