vernier 0.4.0 → 0.5.1
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/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
|