vernier 0.7.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|