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 +4 -4
- data/exe/vernier +3 -0
- data/ext/vernier/vernier.cc +78 -15
- data/lib/vernier/autorun.rb +5 -4
- data/lib/vernier/collector.rb +4 -3
- data/lib/vernier/hooks/active_support.rb +85 -3
- data/lib/vernier/middleware.rb +3 -3
- data/lib/vernier/output/firefox.rb +55 -35
- data/lib/vernier/result.rb +5 -4
- data/lib/vernier/stack_table.rb +4 -0
- data/lib/vernier/thread_names.rb +1 -1
- data/lib/vernier/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 682bc2ce2b915b0fa21db46232322f1bafba1916fc4e1b58cbaeafe3631ab3f2
|
4
|
+
data.tar.gz: af659f7fe18efa70c41fa178ff6ed52244292287f1b59e4950fcad6db0a1675e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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!
|
data/ext/vernier/vernier.cc
CHANGED
@@ -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[
|
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
|
-
|
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
|
-
|
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
|
-
|
888
|
-
|
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.
|
1743
|
+
collector->gc_markers.record_gc_start();
|
1692
1744
|
break;
|
1693
1745
|
case RUBY_INTERNAL_EVENT_GC_END_MARK:
|
1694
|
-
collector->gc_markers.
|
1746
|
+
collector->gc_markers.record_gc_end_mark();
|
1695
1747
|
break;
|
1696
1748
|
case RUBY_INTERNAL_EVENT_GC_END_SWEEP:
|
1697
|
-
collector->gc_markers.
|
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");
|
data/lib/vernier/autorun.rb
CHANGED
@@ -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
|
-
|
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
|
|
data/lib/vernier/collector.rb
CHANGED
@@ -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
|
|
data/lib/vernier/middleware.rb
CHANGED
@@ -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/
|
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: "
|
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"
|
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"
|
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"
|
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
|
-
|
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.
|
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:
|
448
|
+
subcategory: subcategories,
|
429
449
|
prefix: prefixes,
|
430
450
|
length: prefixes.length
|
431
451
|
}
|
data/lib/vernier/result.rb
CHANGED
@@ -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
|
-
|
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, #{
|
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
|
data/lib/vernier/stack_table.rb
CHANGED
data/lib/vernier/thread_names.rb
CHANGED
data/lib/vernier/version.rb
CHANGED
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.
|
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-
|
11
|
+
date: 2024-05-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|