vernier 0.4.0 → 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/ext/vernier/vernier.cc +53 -50
- data/lib/vernier/output/firefox.rb +20 -1
- data/lib/vernier/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: da42588d9f2409a9d82a79439014490ecd81f00cb74c975267d6568a6e343c46
|
4
|
+
data.tar.gz: 921543f2b8cfec4e5803c4a47c8953df1acff567b88ddce2e1a65faad9e7c895
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a674536e944ef26f2db5893821d9d643fc8f651d45b7a72923ae9ab7c01f270431f0e5fadea76e3bb3feeabe30edde203b520310cbbbae6a53f03edb148db570
|
7
|
+
data.tar.gz: 1f39e9f04a2fd1c80525a72ce27aca6e8d614dfa9a4ba9f9721beff08f11a41431f6690c6215f0592dcc7923e49e516a4a0ddb812b493820877ceca81b4373ab
|
data/ext/vernier/vernier.cc
CHANGED
@@ -331,7 +331,7 @@ struct RawSample {
|
|
331
331
|
|
332
332
|
Frame frame(int i) const {
|
333
333
|
int idx = len - i - 1;
|
334
|
-
if (idx < 0) throw std::out_of_range("out of range");
|
334
|
+
if (idx < 0) throw std::out_of_range("VERNIER BUG: index out of range");
|
335
335
|
const Frame frame = {frames[idx], lines[idx]};
|
336
336
|
return frame;
|
337
337
|
}
|
@@ -446,7 +446,7 @@ struct FrameList {
|
|
446
446
|
|
447
447
|
int stack_index(const RawSample &stack) {
|
448
448
|
if (stack.empty()) {
|
449
|
-
throw std::runtime_error("empty stack");
|
449
|
+
throw std::runtime_error("VERNIER BUG: empty stack");
|
450
450
|
}
|
451
451
|
|
452
452
|
StackNode *node = &root_stack_node;
|
@@ -659,8 +659,8 @@ class Marker {
|
|
659
659
|
|
660
660
|
class MarkerTable {
|
661
661
|
public:
|
662
|
-
std::vector<Marker> list;
|
663
662
|
std::mutex mutex;
|
663
|
+
std::vector<Marker> list;
|
664
664
|
|
665
665
|
void record_interval(Marker::Type type, TimeStamp from, TimeStamp to, int stack_index = -1) {
|
666
666
|
const std::lock_guard<std::mutex> lock(mutex);
|
@@ -780,19 +780,18 @@ class Thread {
|
|
780
780
|
int stack_on_suspend_idx;
|
781
781
|
SampleTranslator translator;
|
782
782
|
|
783
|
-
MarkerTable
|
783
|
+
unique_ptr<MarkerTable> markers;
|
784
784
|
|
785
|
-
|
785
|
+
std::string name;
|
786
786
|
|
787
|
-
|
787
|
+
// FIXME: don't use pthread at start
|
788
788
|
Thread(State state, pthread_t pthread_id, VALUE ruby_thread) : pthread_id(pthread_id), ruby_thread(ruby_thread), state(state), stack_on_suspend_idx(-1) {
|
789
|
-
name = Qnil;
|
790
789
|
ruby_thread_id = rb_obj_id(ruby_thread);
|
791
|
-
|
790
|
+
//ruby_thread_id = ULL2NUM(ruby_thread);
|
792
791
|
native_tid = get_native_thread_id();
|
793
792
|
started_at = state_changed_at = TimeStamp::Now();
|
794
793
|
name = "";
|
795
|
-
markers =
|
794
|
+
markers = std::make_unique<MarkerTable>();
|
796
795
|
|
797
796
|
if (state == State::STARTED) {
|
798
797
|
markers->record(Marker::Type::MARKER_GVL_THREAD_STARTED);
|
@@ -853,13 +852,12 @@ class Thread {
|
|
853
852
|
markers->record_interval(Marker::Type::MARKER_THREAD_RUNNING, from, now);
|
854
853
|
break;
|
855
854
|
case State::STOPPED:
|
856
|
-
// We can go from RUNNING or STARTED to STOPPED
|
857
|
-
assert(state == State::RUNNING || state == State::STARTED);
|
855
|
+
// We can go from RUNNING or STARTED or SUSPENDED to STOPPED
|
856
|
+
assert(state == State::RUNNING || state == State::STARTED || state == State::SUSPENDED);
|
858
857
|
markers->record_interval(Marker::Type::MARKER_THREAD_RUNNING, from, now);
|
859
858
|
markers->record(Marker::Type::MARKER_GVL_THREAD_EXITED);
|
860
859
|
|
861
860
|
stopped_at = now;
|
862
|
-
capture_name();
|
863
861
|
|
864
862
|
break;
|
865
863
|
}
|
@@ -872,13 +870,6 @@ class Thread {
|
|
872
870
|
return state != State::STOPPED;
|
873
871
|
}
|
874
872
|
|
875
|
-
void capture_name() {
|
876
|
-
//char buf[128];
|
877
|
-
//int rc = pthread_getname_np(pthread_id, buf, sizeof(buf));
|
878
|
-
//if (rc == 0)
|
879
|
-
// name = std::string(buf);
|
880
|
-
}
|
881
|
-
|
882
873
|
void mark() {
|
883
874
|
}
|
884
875
|
};
|
@@ -887,15 +878,15 @@ class ThreadTable {
|
|
887
878
|
public:
|
888
879
|
FrameList &frame_list;
|
889
880
|
|
890
|
-
std::vector<Thread> list;
|
881
|
+
std::vector<std::unique_ptr<Thread> > list;
|
891
882
|
std::mutex mutex;
|
892
883
|
|
893
884
|
ThreadTable(FrameList &frame_list) : frame_list(frame_list) {
|
894
885
|
}
|
895
886
|
|
896
887
|
void mark() {
|
897
|
-
for (auto &thread : list) {
|
898
|
-
thread
|
888
|
+
for (const auto &thread : list) {
|
889
|
+
thread->mark();
|
899
890
|
}
|
900
891
|
}
|
901
892
|
|
@@ -931,7 +922,8 @@ class ThreadTable {
|
|
931
922
|
|
932
923
|
//fprintf(stderr, "th %p (tid: %i) from %s to %s\n", (void *)th, native_tid, gvl_event_name(state), gvl_event_name(new_state));
|
933
924
|
|
934
|
-
for (auto &
|
925
|
+
for (auto &threadptr : list) {
|
926
|
+
auto &thread = *threadptr;
|
935
927
|
if (thread_equal(th, thread.ruby_thread)) {
|
936
928
|
if (new_state == Thread::State::SUSPENDED) {
|
937
929
|
|
@@ -958,7 +950,7 @@ class ThreadTable {
|
|
958
950
|
}
|
959
951
|
|
960
952
|
//fprintf(stderr, "NEW THREAD: th: %p, state: %i\n", th, new_state);
|
961
|
-
list.
|
953
|
+
list.push_back(std::make_unique<Thread>(new_state, pthread_self(), th));
|
962
954
|
}
|
963
955
|
|
964
956
|
bool thread_equal(VALUE a, VALUE b) {
|
@@ -1082,6 +1074,12 @@ class RetainedCollector : public BaseCollector {
|
|
1082
1074
|
void record(VALUE obj) {
|
1083
1075
|
RawSample sample;
|
1084
1076
|
sample.sample();
|
1077
|
+
if (sample.empty()) {
|
1078
|
+
// During thread allocation we allocate one object without a frame
|
1079
|
+
// (as of Ruby 3.3)
|
1080
|
+
// Ideally we'd allow empty samples to be represented
|
1081
|
+
return;
|
1082
|
+
}
|
1085
1083
|
int stack_index = frame_list.stack_index(sample);
|
1086
1084
|
|
1087
1085
|
object_list.push_back(obj);
|
@@ -1228,17 +1226,22 @@ class GlobalSignalHandler {
|
|
1228
1226
|
if (count == 0) clear_signal_handler();
|
1229
1227
|
}
|
1230
1228
|
|
1231
|
-
|
1229
|
+
bool record_sample(LiveSample &sample, pthread_t pthread_id) {
|
1232
1230
|
const std::lock_guard<std::mutex> lock(mutex);
|
1233
1231
|
|
1234
1232
|
assert(pthread_id);
|
1235
1233
|
|
1236
1234
|
live_sample = &sample;
|
1237
|
-
|
1238
|
-
|
1235
|
+
int rc = pthread_kill(pthread_id, SIGPROF);
|
1236
|
+
if (rc) {
|
1237
|
+
fprintf(stderr, "VERNIER BUG: pthread_kill of %lu failed (%i)\n", (unsigned long)pthread_id, rc);
|
1238
|
+
live_sample = NULL;
|
1239
|
+
return false;
|
1240
|
+
} else {
|
1241
|
+
sample.wait();
|
1242
|
+
live_sample = NULL;
|
1243
|
+
return true;
|
1239
1244
|
}
|
1240
|
-
sample.wait();
|
1241
|
-
live_sample = NULL;
|
1242
1245
|
}
|
1243
1246
|
|
1244
1247
|
private:
|
@@ -1309,9 +1312,9 @@ class TimeCollector : public BaseCollector {
|
|
1309
1312
|
rb_ary_push(list, ary);
|
1310
1313
|
}
|
1311
1314
|
for (auto &thread : threads.list) {
|
1312
|
-
for (auto& marker: thread
|
1315
|
+
for (auto& marker: thread->markers->list) {
|
1313
1316
|
VALUE ary = marker.to_array();
|
1314
|
-
RARRAY_ASET(ary, 0, thread
|
1317
|
+
RARRAY_ASET(ary, 0, thread->ruby_thread_id);
|
1315
1318
|
rb_ary_push(list, ary);
|
1316
1319
|
}
|
1317
1320
|
}
|
@@ -1327,14 +1330,21 @@ class TimeCollector : public BaseCollector {
|
|
1327
1330
|
TimeStamp sample_start = TimeStamp::Now();
|
1328
1331
|
|
1329
1332
|
threads.mutex.lock();
|
1330
|
-
for (auto &
|
1333
|
+
for (auto &threadptr : threads.list) {
|
1334
|
+
auto &thread = *threadptr;
|
1335
|
+
|
1331
1336
|
//if (thread.state == Thread::State::RUNNING) {
|
1332
1337
|
//if (thread.state == Thread::State::RUNNING || (thread.state == Thread::State::SUSPENDED && thread.stack_on_suspend_idx < 0)) {
|
1333
1338
|
if (thread.state == Thread::State::RUNNING) {
|
1334
1339
|
//fprintf(stderr, "sampling %p on tid:%i\n", thread.ruby_thread, thread.native_tid);
|
1335
|
-
GlobalSignalHandler::get_instance()->record_sample(sample, thread.pthread_id);
|
1336
|
-
|
1337
|
-
if (
|
1340
|
+
bool signal_sent = GlobalSignalHandler::get_instance()->record_sample(sample, thread.pthread_id);
|
1341
|
+
|
1342
|
+
if (!signal_sent) {
|
1343
|
+
// The thread has died. We probably should have caught
|
1344
|
+
// that by the GVL instrumentation, but let's try to get
|
1345
|
+
// it to a consistent state and stop profiling it.
|
1346
|
+
thread.set_state(Thread::State::STOPPED);
|
1347
|
+
} else if (sample.sample.gc) {
|
1338
1348
|
// fprintf(stderr, "skipping GC sample\n");
|
1339
1349
|
} else {
|
1340
1350
|
record_sample(sample.sample, sample_start, thread, CATEGORY_NORMAL);
|
@@ -1467,7 +1477,7 @@ class TimeCollector : public BaseCollector {
|
|
1467
1477
|
int ret = pthread_create(&sample_thread, NULL, &sample_thread_entry, this);
|
1468
1478
|
if (ret != 0) {
|
1469
1479
|
perror("pthread_create");
|
1470
|
-
rb_bug("pthread_create");
|
1480
|
+
rb_bug("VERNIER: pthread_create failed");
|
1471
1481
|
}
|
1472
1482
|
|
1473
1483
|
// Set the state of the current Ruby thread to RUNNING, which we know it
|
@@ -1496,13 +1506,6 @@ class TimeCollector : public BaseCollector {
|
|
1496
1506
|
rb_remove_event_hook(internal_gc_event_cb);
|
1497
1507
|
rb_remove_event_hook(internal_thread_event_cb);
|
1498
1508
|
|
1499
|
-
// capture thread names
|
1500
|
-
for (auto& thread: this->threads.list) {
|
1501
|
-
if (thread.running()) {
|
1502
|
-
thread.capture_name();
|
1503
|
-
}
|
1504
|
-
}
|
1505
|
-
|
1506
1509
|
frame_list.finalize();
|
1507
1510
|
|
1508
1511
|
VALUE result = build_collector_result();
|
@@ -1520,15 +1523,15 @@ class TimeCollector : public BaseCollector {
|
|
1520
1523
|
|
1521
1524
|
for (const auto& thread: this->threads.list) {
|
1522
1525
|
VALUE hash = rb_hash_new();
|
1523
|
-
thread
|
1526
|
+
thread->samples.write_result(hash);
|
1524
1527
|
|
1525
|
-
rb_hash_aset(threads, thread
|
1526
|
-
rb_hash_aset(hash, sym("tid"), ULL2NUM(thread
|
1527
|
-
rb_hash_aset(hash, sym("started_at"), ULL2NUM(thread
|
1528
|
-
if (!thread
|
1529
|
-
rb_hash_aset(hash, sym("stopped_at"), ULL2NUM(thread
|
1528
|
+
rb_hash_aset(threads, thread->ruby_thread_id, hash);
|
1529
|
+
rb_hash_aset(hash, sym("tid"), ULL2NUM(thread->native_tid));
|
1530
|
+
rb_hash_aset(hash, sym("started_at"), ULL2NUM(thread->started_at.nanoseconds()));
|
1531
|
+
if (!thread->stopped_at.zero()) {
|
1532
|
+
rb_hash_aset(hash, sym("stopped_at"), ULL2NUM(thread->stopped_at.nanoseconds()));
|
1530
1533
|
}
|
1531
|
-
rb_hash_aset(hash, sym("name"), rb_str_new(thread
|
1534
|
+
rb_hash_aset(hash, sym("name"), rb_str_new(thread->name.data(), thread->name.length()));
|
1532
1535
|
|
1533
1536
|
}
|
1534
1537
|
|
@@ -163,7 +163,7 @@ module Vernier
|
|
163
163
|
@profile = profile
|
164
164
|
@categorizer = categorizer
|
165
165
|
@tid = tid
|
166
|
-
@name = name
|
166
|
+
@name = pretty_name(name)
|
167
167
|
|
168
168
|
timestamps ||= [0] * samples.size
|
169
169
|
@samples, @weights, @timestamps = samples, weights, timestamps
|
@@ -385,6 +385,25 @@ module Vernier
|
|
385
385
|
|
386
386
|
private
|
387
387
|
|
388
|
+
def pretty_name(name)
|
389
|
+
if name.empty?
|
390
|
+
begin
|
391
|
+
tr = ObjectSpace._id2ref(@ruby_thread_id)
|
392
|
+
name = tr.inspect if tr
|
393
|
+
rescue RangeError
|
394
|
+
# Thread was already GC'd
|
395
|
+
end
|
396
|
+
end
|
397
|
+
return name unless name.start_with?("#<Thread")
|
398
|
+
pretty = []
|
399
|
+
obj_address = name[/Thread:(0x\w+)/,1]
|
400
|
+
best_id = name[/\#<Thread:0x\w+@?\s?(.*)\s+\S+>/,1] || ""
|
401
|
+
Gem.path.each { |gem_dir| best_id = best_id.gsub(gem_dir, "...") }
|
402
|
+
pretty << best_id unless best_id.empty?
|
403
|
+
pretty << "(#{obj_address})"
|
404
|
+
pretty.join(' ')
|
405
|
+
end
|
406
|
+
|
388
407
|
def gc_category
|
389
408
|
@categorizer.get_category("GC")
|
390
409
|
end
|
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: 0.5.1
|
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-02-28 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: An experimental profiler
|
14
14
|
email:
|
@@ -67,7 +67,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
69
|
requirements: []
|
70
|
-
rubygems_version: 3.
|
70
|
+
rubygems_version: 3.5.3
|
71
71
|
signing_key:
|
72
72
|
specification_version: 4
|
73
73
|
summary: An experimental profiler
|