vernier 0.7.0 → 1.0.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: 4988794520691870cb34178385be791e28fa4806eb1f6934b0d0abf52c8a070c
4
- data.tar.gz: b1e63c6f5398f61fb68d634f17f33dc6aa7461344cb8acf446dd75ca52395818
3
+ metadata.gz: 682bc2ce2b915b0fa21db46232322f1bafba1916fc4e1b58cbaeafe3631ab3f2
4
+ data.tar.gz: af659f7fe18efa70c41fa178ff6ed52244292287f1b59e4950fcad6db0a1675e
5
5
  SHA512:
6
- metadata.gz: '082561b2a381f88c540e607d1dd9c0d42d841a0de82fc61471fb2d67754ee14a4fd1e87fa71896b586077664e61fd1db623fd250cd13064d198c9dc91d3f13ed'
7
- data.tar.gz: a28c197d405e4f52d999db3ae73187047886e1fcfb7d353d13f26ad5ba4b61f0675daf617119b37d72ad3760bd0dffcae234f2dcb0a0e960a60046dfb87dd784
6
+ metadata.gz: a373326fd6ddb7043b57b5e0b8f8ddc96c666368ae38fbd8398df29e09fffea9141bc9a4166e6f0970817685c86e93c9cf3fc1312ca49fb9ae9ba1ba49dd60f8
7
+ data.tar.gz: 59026602a1d4103e0b245e7fa63fd7168bfa9c57913d1d7a003349f4818c7b303da050b19c4c31523f853e81715ab56e425bccfa77eee8053486e4dc12ea864f
data/exe/vernier CHANGED
@@ -25,6 +25,9 @@ parser = OptionParser.new(banner) do |o|
25
25
  o.on('--start-paused', "don't automatically start the profiler") do
26
26
  options[:start_paused] = true
27
27
  end
28
+ o.on('--hooks [HOOKS]', String, "Enable instrumentation hooks. Currently supported: rails") do |s|
29
+ options[:hooks] = s
30
+ end
28
31
  end
29
32
 
30
33
  parser.parse!
@@ -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");
@@ -18,10 +18,11 @@ module Vernier
18
18
  def self.start
19
19
  interval = options.fetch(:interval, 500).to_i
20
20
  allocation_sample_rate = options.fetch(:allocation_sample_rate, 0).to_i
21
+ hooks = options.fetch(:hooks, "").split(",")
21
22
 
22
23
  STDERR.puts("starting profiler with interval #{interval}")
23
24
 
24
- @collector = Vernier::Collector.new(:wall, interval:, allocation_sample_rate:)
25
+ @collector = Vernier::Collector.new(:wall, interval:, allocation_sample_rate:, hooks:)
25
26
  @collector.start
26
27
  end
27
28
 
@@ -29,8 +30,8 @@ module Vernier
29
30
  result = @collector.stop
30
31
  @collector = nil
31
32
  output_path = options[:output]
32
- output_path ||= Tempfile.create(["profile", ".vernier.json"]).path
33
- 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)
34
35
 
35
36
  STDERR.puts(result.inspect)
36
37
  STDERR.puts("written to #{output_path}")
@@ -57,7 +58,7 @@ end
57
58
  if signal = Vernier::Autorun.options[:signal]
58
59
  STDERR.puts "to toggle profiler: kill -#{signal} #{Process.pid}"
59
60
  trap(signal) do
60
- Vernier::Autorun.toggle
61
+ Thread.new { Vernier::Autorun.toggle }
61
62
  end
62
63
  end
63
64
 
@@ -29,11 +29,11 @@ module Vernier
29
29
  end
30
30
 
31
31
  private def add_hook(hook)
32
- case hook
32
+ case hook.to_sym
33
33
  when :rails, :activesupport
34
34
  @hooks << Vernier::Hooks::ActiveSupport.new(self)
35
35
  else
36
- warn "Unknown hook: #{hook}"
36
+ warn "Unknown hook: #{hook.inspect}"
37
37
  end
38
38
  end
39
39
 
@@ -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
 
@@ -7,9 +7,12 @@ module Vernier
7
7
  {
8
8
  name: "sql.active_record",
9
9
  display: [ "marker-chart", "marker-table" ],
10
+ tooltipLabel: "{marker.data.name}",
11
+ chartLabel: "{marker.data.name}",
12
+ tableLabel: "{marker.data.sql}",
10
13
  data: [
11
- { key: "sql", format: "string" },
12
- { key: "name", format: "string" },
14
+ { key: "sql", format: "string", searchable: true },
15
+ { key: "name", format: "string", searchable: true },
13
16
  { key: "type_casted_binds", label: "binds", format: "string"
14
17
  }
15
18
  ]
@@ -17,25 +20,50 @@ module Vernier
17
20
  {
18
21
  name: "instantiation.active_record",
19
22
  display: [ "marker-chart", "marker-table" ],
23
+ tooltipLabel: "{marker.data.record_count} × {marker.data.class_name}",
24
+ chartLabel: "{marker.data.record_count} × {marker.data.class_name}",
25
+ tableLabel: "Instantiate {marker.data.record_count} × {marker.data.class_name}",
20
26
  data: [
21
27
  { key: "record_count", format: "integer" },
22
28
  { key: "class_name", format: "string" }
23
29
  ]
24
30
  },
31
+ {
32
+ name: "start_processing.action_controller",
33
+ display: [ "marker-chart", "marker-table" ],
34
+ tooltipLabel: '{marker.data.method} {marker.data.controller}#{marker.data.action}',
35
+ chartLabel: '{marker.data.method} {marker.data.controller}#{marker.data.action}',
36
+ tableLabel: '{marker.data.method} {marker.data.controller}#{marker.data.action}',
37
+ data: [
38
+ { key: "controller", format: "string" },
39
+ { key: "action", format: "string" },
40
+ { key: "status", format: "integer" },
41
+ { key: "path", format: "string" },
42
+ { key: "method", format: "string" },
43
+ { key: "format", format: "string" }
44
+ ]
45
+ },
25
46
  {
26
47
  name: "process_action.action_controller",
27
48
  display: [ "marker-chart", "marker-table" ],
49
+ tooltipLabel: '{marker.data.method} {marker.data.controller}#{marker.data.action}',
50
+ chartLabel: '{marker.data.method} {marker.data.controller}#{marker.data.action}',
51
+ tableLabel: '{marker.data.method} {marker.data.controller}#{marker.data.action}',
28
52
  data: [
29
53
  { key: "controller", format: "string" },
30
54
  { key: "action", format: "string" },
31
55
  { key: "status", format: "integer" },
32
56
  { key: "path", format: "string" },
33
- { key: "method", format: "string" }
57
+ { key: "method", format: "string" },
58
+ { key: "format", format: "string" }
34
59
  ]
35
60
  },
36
61
  {
37
62
  name: "cache_read.active_support",
38
63
  display: [ "marker-chart", "marker-table" ],
64
+ tooltipLabel: '{marker.data.super_operation} {marker.data.key}',
65
+ chartLabel: '{marker.data.super_operation} {marker.data.key}',
66
+ tableLabel: '{marker.data.super_operation} {marker.data.key}',
39
67
  data: [
40
68
  { key: "key", format: "string" },
41
69
  { key: "store", format: "string" },
@@ -55,11 +83,65 @@ module Vernier
55
83
  },
56
84
  {
57
85
  name: "cache_fetch_hit.active_support",
86
+ tooltipLabel: 'HIT {marker.data.key}',
87
+ chartLabel: 'HIT {marker.data.key}',
88
+ tableLabel: 'HIT {marker.data.key}',
58
89
  display: [ "marker-chart", "marker-table" ],
59
90
  data: [
60
91
  { key: "key", format: "string" },
61
92
  { key: "store", format: "string" }
62
93
  ]
94
+ },
95
+ {
96
+ name: "render_template.action_view",
97
+ display: [ "marker-chart", "marker-table" ],
98
+ tooltipLabel: '{marker.data.identifier}',
99
+ chartLabel: '{marker.data.identifier}',
100
+ tableLabel: '{marker.data.identifier}',
101
+ data: [
102
+ { key: "identifier", format: "string" }
103
+ ]
104
+ },
105
+ {
106
+ name: "render_layout.action_view",
107
+ display: [ "marker-chart", "marker-table" ],
108
+ tooltipLabel: '{marker.data.identifier}',
109
+ chartLabel: '{marker.data.identifier}',
110
+ tableLabel: '{marker.data.identifier}',
111
+ data: [
112
+ { key: "identifier", format: "string" }
113
+ ]
114
+ },
115
+ {
116
+ name: "render_partial.action_view",
117
+ display: [ "marker-chart", "marker-table" ],
118
+ tooltipLabel: '{marker.data.identifier}',
119
+ chartLabel: '{marker.data.identifier}',
120
+ tableLabel: '{marker.data.identifier}',
121
+ data: [
122
+ { key: "identifier", format: "string" }
123
+ ]
124
+ },
125
+ {
126
+ name: "render_collection.action_view",
127
+ display: [ "marker-chart", "marker-table" ],
128
+ tooltipLabel: '{marker.data.identifier}',
129
+ chartLabel: '{marker.data.identifier}',
130
+ tableLabel: '{marker.data.identifier}',
131
+ data: [
132
+ { key: "identifier", format: "string" },
133
+ { key: "count", format: "integer" }
134
+ ]
135
+ },
136
+ {
137
+ name: "load_config_initializer.railties",
138
+ display: [ "marker-chart", "marker-table" ],
139
+ tooltipLabel: '{marker.data.initializer}',
140
+ chartLabel: '{marker.data.initializer}',
141
+ tableLabel: '{marker.data.initializer}',
142
+ data: [
143
+ { key: "initializer", format: "string" }
144
+ ]
63
145
  }
64
146
  ])
65
147
 
@@ -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
@@ -152,7 +158,7 @@ module Vernier
152
158
  [
153
159
  {
154
160
  name: "THREAD_RUNNING",
155
- display: [ "marker-chart", "marker-table" ],
161
+ display: [ "marker-chart" ],
156
162
  data: [
157
163
  {
158
164
  label: "Description",
@@ -162,7 +168,7 @@ module Vernier
162
168
  },
163
169
  {
164
170
  name: "THREAD_STALLED",
165
- display: [ "marker-chart", "marker-table" ],
171
+ display: [ "marker-chart" ],
166
172
  data: [
167
173
  {
168
174
  label: "Description",
@@ -172,7 +178,7 @@ module Vernier
172
178
  },
173
179
  {
174
180
  name: "THREAD_SUSPENDED",
175
- display: [ "marker-chart", "marker-table" ],
181
+ display: [ "marker-chart" ],
176
182
  data: [
177
183
  {
178
184
  label: "Description",
@@ -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: {
@@ -35,7 +35,7 @@ module Vernier
35
35
  return name if name && !name.empty?
36
36
 
37
37
  if thread == Thread.main
38
- return "main"
38
+ return $0
39
39
  end
40
40
 
41
41
  name = Thread.instance_method(:inspect).bind_call(thread)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Vernier
4
- VERSION = "0.7.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.7.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-13 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