vernier 0.8.0 → 1.0.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: b450ebff29010a5a95e3817bc67d925f99ecca003ceacf15bdb390805e314045
4
- data.tar.gz: c5c2ab7f3c3e29176685db787f9d4be9acbd5f798532ea571bd8710c78e05e62
3
+ metadata.gz: 682bc2ce2b915b0fa21db46232322f1bafba1916fc4e1b58cbaeafe3631ab3f2
4
+ data.tar.gz: af659f7fe18efa70c41fa178ff6ed52244292287f1b59e4950fcad6db0a1675e
5
5
  SHA512:
6
- metadata.gz: fe6d2aaba11eb9955fa95ec2bb2d0aaf14945b0fc70184251fc46a763a45f0fa14ede8bf7e959aa43160b96531fa11efc9919a7352f2d703382d32310dd739a5
7
- data.tar.gz: 6f0f28086118df8cefbb533a8ca822f14410f460e7fb933eed1655ecdbc07d19e823986c1035d9dfc10a728b6966fa9d90c474a60e4a2b4ac0dc9e02bda70719
6
+ metadata.gz: a373326fd6ddb7043b57b5e0b8f8ddc96c666368ae38fbd8398df29e09fffea9141bc9a4166e6f0970817685c86e93c9cf3fc1312ca49fb9ae9ba1ba49dd60f8
7
+ data.tar.gz: 59026602a1d4103e0b245e7fa63fd7168bfa9c57913d1d7a003349f4818c7b303da050b19c4c31523f853e81715ab56e425bccfa77eee8053486e4dc12ea864f
@@ -59,6 +59,8 @@ static VALUE rb_mVernierMarkerType;
59
59
  static VALUE rb_cVernierCollector;
60
60
  static VALUE rb_cStackTable;
61
61
 
62
+ static VALUE sym_state, sym_gc_by;
63
+
62
64
  static const char *gvl_event_name(rb_event_flag_t event) {
63
65
  switch (event) {
64
66
  case RUBY_INTERNAL_THREAD_EVENT_STARTED:
@@ -796,6 +798,12 @@ static native_thread_id_t get_native_thread_id() {
796
798
  #endif
797
799
  }
798
800
 
801
+ union MarkerInfo {
802
+ struct {
803
+ VALUE gc_by;
804
+ VALUE gc_state;
805
+ } gc_data;
806
+ };
799
807
 
800
808
  class Marker {
801
809
  public:
@@ -833,8 +841,10 @@ class Marker {
833
841
  //native_thread_id_t thread_id;
834
842
  int stack_index = -1;
835
843
 
844
+ MarkerInfo extra_info;
845
+
836
846
  VALUE to_array() {
837
- VALUE record[6] = {0};
847
+ VALUE record[7] = {0};
838
848
  record[0] = Qnil; // FIXME
839
849
  record[1] = INT2NUM(type);
840
850
  record[2] = INT2NUM(phase);
@@ -848,7 +858,14 @@ class Marker {
848
858
  }
849
859
  record[5] = stack_index == -1 ? Qnil : INT2NUM(stack_index);
850
860
 
851
- return rb_ary_new_from_values(6, record);
861
+ if (type == Marker::MARKER_GC_PAUSE) {
862
+ VALUE hash = rb_hash_new();
863
+ record[6] = hash;
864
+
865
+ rb_hash_aset(hash, sym_gc_by, extra_info.gc_data.gc_by);
866
+ rb_hash_aset(hash, sym_state, extra_info.gc_data.gc_state);
867
+ }
868
+ return rb_ary_new_from_values(7, record);
852
869
  }
853
870
  };
854
871
 
@@ -863,7 +880,7 @@ class MarkerTable {
863
880
  list.push_back({ type, Marker::INTERVAL, from, to, stack_index });
864
881
  }
865
882
 
866
- void record(Marker::Type type, int stack_index = -1) {
883
+ void record(Marker::Type type, int stack_index = -1, MarkerInfo extra_info = {}) {
867
884
  const std::lock_guard<std::mutex> lock(mutex);
868
885
 
869
886
  list.push_back({ type, Marker::INSTANT, TimeStamp::Now(), TimeStamp(), stack_index });
@@ -874,18 +891,41 @@ class GCMarkerTable: public MarkerTable {
874
891
  TimeStamp last_gc_entry;
875
892
 
876
893
  public:
894
+ void record_gc_start() {
895
+ record(Marker::Type::MARKER_GC_START);
896
+ }
897
+
877
898
  void record_gc_entered() {
878
899
  last_gc_entry = TimeStamp::Now();
879
900
  }
880
901
 
881
902
  void record_gc_leave() {
882
- list.push_back({ Marker::MARKER_GC_PAUSE, Marker::INTERVAL, last_gc_entry, TimeStamp::Now(), -1 });
903
+ VALUE gc_state = rb_gc_latest_gc_info(sym_state);
904
+ VALUE gc_by = rb_gc_latest_gc_info(sym_gc_by);
905
+ union MarkerInfo info = {
906
+ .gc_data = {
907
+ .gc_by = gc_by,
908
+ .gc_state = gc_state
909
+ }
910
+ };
911
+ list.push_back({ Marker::MARKER_GC_PAUSE, Marker::INTERVAL, last_gc_entry, TimeStamp::Now(), -1, info });
912
+ }
913
+
914
+ void record_gc_end_mark() {
915
+ record_gc_leave();
916
+ record(Marker::Type::MARKER_GC_END_MARK);
917
+ record_gc_entered();
918
+ }
919
+
920
+ void record_gc_end_sweep() {
921
+ record(Marker::Type::MARKER_GC_END_SWEEP);
883
922
  }
884
923
  };
885
924
 
886
925
  enum Category{
887
- CATEGORY_NORMAL,
888
- CATEGORY_IDLE
926
+ CATEGORY_NORMAL,
927
+ CATEGORY_IDLE,
928
+ CATEGORY_STALLED
889
929
  };
890
930
 
891
931
  class ObjectSampleList {
@@ -1002,7 +1042,8 @@ class Thread {
1002
1042
  RUNNING,
1003
1043
  READY,
1004
1044
  SUSPENDED,
1005
- STOPPED
1045
+ STOPPED,
1046
+ INITIAL,
1006
1047
  };
1007
1048
 
1008
1049
  VALUE ruby_thread;
@@ -1058,12 +1099,14 @@ class Thread {
1058
1099
  }
1059
1100
 
1060
1101
  switch (new_state) {
1102
+ case State::INITIAL:
1103
+ break;
1061
1104
  case State::STARTED:
1062
1105
  markers->record(Marker::Type::MARKER_GVL_THREAD_STARTED);
1063
1106
  return; // no mutation of current state
1064
1107
  break;
1065
1108
  case State::RUNNING:
1066
- assert(state == State::READY || state == State::RUNNING);
1109
+ assert(state == INITIAL || state == State::READY || state == State::RUNNING);
1067
1110
  pthread_id = pthread_self();
1068
1111
  native_tid = get_native_thread_id();
1069
1112
 
@@ -1081,7 +1124,7 @@ class Thread {
1081
1124
  // Threads can be preempted, which means they will have been in "Running"
1082
1125
  // state, and then the VM was like "no I need to stop you from working,
1083
1126
  // so I'll put you in the 'ready' (or stalled) state"
1084
- assert(state == State::STARTED || state == State::SUSPENDED || state == State::RUNNING);
1127
+ assert(state == State::INITIAL || state == State::STARTED || state == State::SUSPENDED || state == State::RUNNING);
1085
1128
  if (state == State::SUSPENDED) {
1086
1129
  markers->record_interval(Marker::Type::MARKER_THREAD_SUSPENDED, from, now, stack_on_suspend_idx);
1087
1130
  }
@@ -1091,12 +1134,12 @@ class Thread {
1091
1134
  break;
1092
1135
  case State::SUSPENDED:
1093
1136
  // We can go from RUNNING or STARTED to SUSPENDED
1094
- assert(state == State::RUNNING || state == State::STARTED || state == State::SUSPENDED);
1137
+ assert(state == State::INITIAL || state == State::RUNNING || state == State::STARTED || state == State::SUSPENDED);
1095
1138
  markers->record_interval(Marker::Type::MARKER_THREAD_RUNNING, from, now);
1096
1139
  break;
1097
1140
  case State::STOPPED:
1098
1141
  // We can go from RUNNING or STARTED or SUSPENDED to STOPPED
1099
- assert(state == State::RUNNING || state == State::STARTED || state == State::SUSPENDED);
1142
+ assert(state == State::INITIAL || state == State::RUNNING || state == State::STARTED || state == State::SUSPENDED);
1100
1143
  markers->record_interval(Marker::Type::MARKER_THREAD_RUNNING, from, now);
1101
1144
  markers->record(Marker::Type::MARKER_GVL_THREAD_EXITED);
1102
1145
 
@@ -1137,6 +1180,10 @@ class ThreadTable {
1137
1180
  }
1138
1181
  }
1139
1182
 
1183
+ void initial(VALUE th) {
1184
+ set_state(Thread::State::INITIAL, th);
1185
+ }
1186
+
1140
1187
  void started(VALUE th) {
1141
1188
  //list.push_back(Thread{pthread_self(), Thread::State::SUSPENDED});
1142
1189
  set_state(Thread::State::STARTED, th);
@@ -1172,7 +1219,7 @@ class ThreadTable {
1172
1219
  for (auto &threadptr : list) {
1173
1220
  auto &thread = *threadptr;
1174
1221
  if (thread_equal(th, thread.ruby_thread)) {
1175
- if (new_state == Thread::State::SUSPENDED) {
1222
+ if (new_state == Thread::State::SUSPENDED || new_state == Thread::State::READY && (thread.state != Thread::State::SUSPENDED)) {
1176
1223
 
1177
1224
  RawSample sample;
1178
1225
  sample.sample();
@@ -1636,6 +1683,11 @@ class TimeCollector : public BaseCollector {
1636
1683
  thread.stack_on_suspend_idx,
1637
1684
  sample_start,
1638
1685
  CATEGORY_IDLE);
1686
+ } else if (thread.state == Thread::State::READY) {
1687
+ thread.samples.record_sample(
1688
+ thread.stack_on_suspend_idx,
1689
+ sample_start,
1690
+ CATEGORY_STALLED);
1639
1691
  } else {
1640
1692
  }
1641
1693
  }
@@ -1688,13 +1740,13 @@ class TimeCollector : public BaseCollector {
1688
1740
 
1689
1741
  switch (event) {
1690
1742
  case RUBY_INTERNAL_EVENT_GC_START:
1691
- collector->gc_markers.record(Marker::Type::MARKER_GC_START);
1743
+ collector->gc_markers.record_gc_start();
1692
1744
  break;
1693
1745
  case RUBY_INTERNAL_EVENT_GC_END_MARK:
1694
- collector->gc_markers.record(Marker::Type::MARKER_GC_END_MARK);
1746
+ collector->gc_markers.record_gc_end_mark();
1695
1747
  break;
1696
1748
  case RUBY_INTERNAL_EVENT_GC_END_SWEEP:
1697
- collector->gc_markers.record(Marker::Type::MARKER_GC_END_SWEEP);
1749
+ collector->gc_markers.record_gc_end_sweep();
1698
1750
  break;
1699
1751
  case RUBY_INTERNAL_EVENT_GC_ENTER:
1700
1752
  collector->gc_markers.record_gc_entered();
@@ -1751,6 +1803,13 @@ class TimeCollector : public BaseCollector {
1751
1803
  return false;
1752
1804
  }
1753
1805
 
1806
+ // Record one sample from each thread
1807
+ VALUE all_threads = rb_funcall(rb_path2class("Thread"), rb_intern("list"), 0);
1808
+ for (int i = 0; i < RARRAY_LEN(all_threads); i++) {
1809
+ VALUE thread = RARRAY_AREF(all_threads, i);
1810
+ this->threads.initial(thread);
1811
+ }
1812
+
1754
1813
  if (allocation_sample_rate > 0) {
1755
1814
  tp_newobj = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_NEWOBJ, newobj_i, this);
1756
1815
  rb_tracepoint_enable(tp_newobj);
@@ -1978,6 +2037,10 @@ Init_consts(VALUE rb_mVernierMarkerPhase) {
1978
2037
  extern "C" void
1979
2038
  Init_vernier(void)
1980
2039
  {
2040
+ sym_state = sym("state");
2041
+ sym_gc_by = sym("gc_by");
2042
+ rb_gc_latest_gc_info(sym_state); // HACK: needs to be warmed so that it can be called during GC
2043
+
1981
2044
  rb_mVernier = rb_define_module("Vernier");
1982
2045
  rb_cVernierResult = rb_define_class_under(rb_mVernier, "Result", rb_cObject);
1983
2046
  VALUE rb_mVernierMarker = rb_define_module_under(rb_mVernier, "Marker");
@@ -30,8 +30,8 @@ module Vernier
30
30
  result = @collector.stop
31
31
  @collector = nil
32
32
  output_path = options[:output]
33
- output_path ||= Tempfile.create(["profile", ".vernier.json"]).path
34
- File.write(output_path, Vernier::Output::Firefox.new(result).output)
33
+ output_path ||= Tempfile.create(["profile", ".vernier.json.gz"]).path
34
+ result.write(out: output_path)
35
35
 
36
36
  STDERR.puts(result.inspect)
37
37
  STDERR.puts("written to #{output_path}")
@@ -58,7 +58,7 @@ end
58
58
  if signal = Vernier::Autorun.options[:signal]
59
59
  STDERR.puts "to toggle profiler: kill -#{signal} #{Process.pid}"
60
60
  trap(signal) do
61
- Vernier::Autorun.toggle
61
+ Thread.new { Vernier::Autorun.toggle }
62
62
  end
63
63
  end
64
64
 
@@ -94,11 +94,12 @@ module Vernier
94
94
 
95
95
  marker_strings = Marker.name_table
96
96
 
97
- markers = self.markers.map do |(tid, type, phase, ts, te, stack)|
97
+ markers = self.markers.map do |(tid, type, phase, ts, te, stack, extra_info)|
98
98
  name = marker_strings[type]
99
99
  sym = Marker::MARKER_SYMBOLS[type]
100
100
  data = { type: sym }
101
101
  data[:cause] = { stack: stack } if stack
102
+ data.merge!(extra_info) if extra_info
102
103
  [tid, name, ts, te, phase, data]
103
104
  end
104
105
 
@@ -18,10 +18,10 @@ module Vernier
18
18
  result = Vernier.trace(interval:, allocation_sample_rate:, hooks: [:rails]) do
19
19
  @app.call(env)
20
20
  end
21
- body = result.to_gecko
22
- filename = "#{request.path.gsub("/", "_")}_#{DateTime.now.strftime("%Y-%m-%d-%H-%M-%S")}.vernier.json"
21
+ body = result.to_gecko(gzip: true)
22
+ filename = "#{request.path.gsub("/", "_")}_#{DateTime.now.strftime("%Y-%m-%d-%H-%M-%S")}.vernier.json.gz"
23
23
  headers = {
24
- "Content-Type" => "application/json; charset=utf-8",
24
+ "Content-Type" => "application/octet-stream",
25
25
  "Content-Disposition" => "attachment; filename=\"#{filename}\"",
26
26
  "Content-Length" => body.bytesize.to_s
27
27
  }
@@ -14,35 +14,29 @@ module Vernier
14
14
  @categories = []
15
15
  @categories_by_name = {}
16
16
 
17
- add_category(name: "Default", color: "grey")
17
+ add_category(name: "Ruby", color: "grey") do |c|
18
+ rails_components = %w[ activesupport activemodel activerecord
19
+ actionview actionpack activejob actionmailer actioncable
20
+ activestorage actionmailbox actiontext railties ]
21
+ c.add_subcategory(
22
+ name: "Rails",
23
+ matcher: gem_path(*rails_components)
24
+ )
25
+ c.add_subcategory(
26
+ name: "gem",
27
+ matcher: starts_with(*Gem.path)
28
+ )
29
+ c.add_subcategory(
30
+ name: "stdlib",
31
+ matcher: starts_with(RbConfig::CONFIG["rubylibdir"])
32
+ )
33
+ end
18
34
  add_category(name: "Idle", color: "transparent")
35
+ add_category(name: "Stalled", color: "transparent")
19
36
 
20
37
  add_category(name: "GC", color: "red")
21
- add_category(
22
- name: "stdlib",
23
- color: "red",
24
- matcher: starts_with(RbConfig::CONFIG["rubylibdir"])
25
- )
26
38
  add_category(name: "cfunc", color: "yellow", matcher: "<cfunc>")
27
39
 
28
- rails_components = %w[ activesupport activemodel activerecord
29
- actionview actionpack activejob actionmailer actioncable
30
- activestorage actionmailbox actiontext railties ]
31
- add_category(
32
- name: "Rails",
33
- color: "green",
34
- matcher: gem_path(*rails_components)
35
- )
36
- add_category(
37
- name: "gem",
38
- color: "red",
39
- matcher: starts_with(*Gem.path)
40
- )
41
- add_category(
42
- name: "Application",
43
- color: "purple"
44
- )
45
-
46
40
  add_category(name: "Thread", color: "grey")
47
41
  end
48
42
 
@@ -50,6 +44,8 @@ module Vernier
50
44
  category = Category.new(@categories.length, name: name, **kw)
51
45
  @categories << category
52
46
  @categories_by_name[name] = category
47
+ category.add_subcategory(name: "Other")
48
+ yield category if block_given?
53
49
  category
54
50
  end
55
51
 
@@ -70,12 +66,17 @@ module Vernier
70
66
  end
71
67
 
72
68
  class Category
73
- attr_reader :idx, :name, :color, :matcher
69
+ attr_reader :idx, :name, :color, :matcher, :subcategories
74
70
  def initialize(idx, name:, color:, matcher: nil)
75
71
  @idx = idx
76
72
  @name = name
77
73
  @color = color
78
74
  @matcher = matcher
75
+ @subcategories = []
76
+ end
77
+
78
+ def add_subcategory(**args)
79
+ @subcategories << Category.new(@subcategories.size, color: nil, **args)
79
80
  end
80
81
 
81
82
  def matches?(path)
@@ -89,8 +90,13 @@ module Vernier
89
90
  @categorizer = Categorizer.new
90
91
  end
91
92
 
92
- def output
93
- ::JSON.fast_generate(data)
93
+ def output(gzip: false)
94
+ result = ::JSON.fast_generate(data)
95
+ if gzip
96
+ require "zlib"
97
+ result = Zlib.gzip(result)
98
+ end
99
+ result
94
100
  end
95
101
 
96
102
  private
@@ -132,7 +138,7 @@ module Vernier
132
138
  {
133
139
  name: category.name,
134
140
  color: category.color,
135
- subcategories: []
141
+ subcategories: category.subcategories.map(&:name)
136
142
  }
137
143
  end,
138
144
  sourceCodeIsNotOnSearchfox: true
@@ -188,7 +194,9 @@ module Vernier
188
194
  {
189
195
  label: "Description",
190
196
  value: "All threads are paused as GC is performed"
191
- }
197
+ },
198
+ { key: "state", format: "string" },
199
+ { key: "gc_by", format: "string" },
192
200
  ]
193
201
  },
194
202
  *hook_additions
@@ -251,12 +259,22 @@ module Vernier
251
259
  func_implementations[func_idx]
252
260
  end
253
261
 
262
+ cfunc_category = @categorizer.get_category("cfunc")
263
+ ruby_category = @categorizer.get_category("Ruby")
254
264
  func_categories = filenames.map do |filename|
255
- @categorizer.categorize(filename)
265
+ filename == "<cfunc>" ? cfunc_category : ruby_category
266
+ end
267
+ func_subcategories = filenames.map do |filename|
268
+ next 0 if filename == "<cfunc>"
269
+
270
+ (ruby_category.subcategories.detect {|c| c.matches?(filename) } || ruby_category.subcategories.first).idx
256
271
  end
257
272
  @frame_categories = profile.frame_table.fetch(:func).map do |func_idx|
258
273
  func_categories[func_idx]
259
274
  end
275
+ @frame_subcategories = profile.frame_table.fetch(:func).map do |func_idx|
276
+ func_subcategories[func_idx]
277
+ end
260
278
  end
261
279
 
262
280
  def filter_filenames(filenames)
@@ -412,11 +430,13 @@ module Vernier
412
430
  frames = profile.stack_table.fetch(:frame).dup
413
431
  prefixes = profile.stack_table.fetch(:parent).dup
414
432
  categories = frames.map{|idx| @frame_categories[idx].idx }
433
+ subcategories = frames.map{|idx| @frame_subcategories[idx] }
415
434
 
416
- @categorized_stacks.keys.each do |(stack, category)|
435
+ @categorized_stacks.each_key do |(stack, category)|
417
436
  frames << frames[stack]
418
437
  prefixes << prefixes[stack]
419
438
  categories << category
439
+ subcategories << 0
420
440
  end
421
441
 
422
442
  size = frames.length
@@ -425,7 +445,7 @@ module Vernier
425
445
  {
426
446
  frame: frames,
427
447
  category: categories,
428
- subcategory: [0] * size,
448
+ subcategory: subcategories,
429
449
  prefix: prefixes,
430
450
  length: prefixes.length
431
451
  }
@@ -37,12 +37,13 @@ module Vernier
37
37
  (current_time_real_ns - current_time_mono_ns + started_at_mono_ns)
38
38
  end
39
39
 
40
- def to_gecko
41
- Output::Firefox.new(self).output
40
+ def to_gecko(gzip: false)
41
+ Output::Firefox.new(self).output(gzip:)
42
42
  end
43
43
 
44
44
  def write(out:)
45
- File.write(out, to_gecko)
45
+ gzip = out.end_with?(".gz")
46
+ File.write(out, to_gecko(gzip:))
46
47
  end
47
48
 
48
49
  def elapsed_seconds
@@ -50,7 +51,7 @@ module Vernier
50
51
  end
51
52
 
52
53
  def inspect
53
- "#<#{self.class} #{elapsed_seconds} seconds, #{threads.count} threads, #{weights.sum} samples, #{samples.uniq.size} unique>"
54
+ "#<#{self.class} #{elapsed_seconds} seconds, #{threads.count} threads, #{samples.count} samples, #{samples.uniq.size} unique>"
54
55
  end
55
56
 
56
57
  def each_sample
@@ -1,5 +1,9 @@
1
1
  module Vernier
2
2
  class StackTable
3
+ def inspect
4
+ "#<#{self.class.name} #{stack_count} stacks, #{frame_count} frames, #{func_count} funcs>"
5
+ end
6
+
3
7
  def to_h
4
8
  {
5
9
  stack_table: {
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Vernier
4
- VERSION = "0.8.0"
4
+ VERSION = "1.0.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vernier
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Hawthorn
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-04-24 00:00:00.000000000 Z
11
+ date: 2024-05-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -103,7 +103,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
103
103
  - !ruby/object:Gem::Version
104
104
  version: '0'
105
105
  requirements: []
106
- rubygems_version: 3.5.9
106
+ rubygems_version: 3.4.19
107
107
  signing_key:
108
108
  specification_version: 4
109
109
  summary: A next generation CRuby profiler