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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c3190b81748262d9620de74e12f6adca9b2c4126741781f9860076d96c96a123
4
- data.tar.gz: b54848781f0b17c16074fd630d0aae6b6ea206e47679713ce858e6e3f08525a5
3
+ metadata.gz: da42588d9f2409a9d82a79439014490ecd81f00cb74c975267d6568a6e343c46
4
+ data.tar.gz: 921543f2b8cfec4e5803c4a47c8953df1acff567b88ddce2e1a65faad9e7c895
5
5
  SHA512:
6
- metadata.gz: de91010589471c0b4a7cfddb37bab92262392ef33da2af44930cdab848a6fd290468abe76c147485ba032d89cdac33b631937eea01bd15af8e89b03a5200e69e
7
- data.tar.gz: 82b40e4d93685ab8c560a995df31421c06ff00f8c27f33d6b37296bed96ca5b37cacda3be60e64464f535dd52d273f80d4885705f591e7246d7b3fb2269c151f
6
+ metadata.gz: a674536e944ef26f2db5893821d9d643fc8f651d45b7a72923ae9ab7c01f270431f0e5fadea76e3bb3feeabe30edde203b520310cbbbae6a53f03edb148db570
7
+ data.tar.gz: 1f39e9f04a2fd1c80525a72ce27aca6e8d614dfa9a4ba9f9721beff08f11a41431f6690c6215f0592dcc7923e49e516a4a0ddb812b493820877ceca81b4373ab
@@ -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 *markers;
783
+ unique_ptr<MarkerTable> markers;
784
784
 
785
- std::string name;
785
+ std::string name;
786
786
 
787
- // FIXME: don't use pthread at start
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
- //ruby_thread_id = ULL2NUM(ruby_thread);
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 = new MarkerTable();
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.mark();
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 &thread : list) {
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.emplace_back(new_state, pthread_self(), th);
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
- void record_sample(LiveSample &sample, pthread_t pthread_id) {
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
- if (pthread_kill(pthread_id, SIGPROF)) {
1238
- rb_bug("pthread_kill failed");
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.markers->list) {
1315
+ for (auto& marker: thread->markers->list) {
1313
1316
  VALUE ary = marker.to_array();
1314
- RARRAY_ASET(ary, 0, thread.ruby_thread_id);
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 &thread : threads.list) {
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 (sample.sample.gc) {
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.samples.write_result(hash);
1526
+ thread->samples.write_result(hash);
1524
1527
 
1525
- rb_hash_aset(threads, thread.ruby_thread_id, hash);
1526
- rb_hash_aset(hash, sym("tid"), ULL2NUM(thread.native_tid));
1527
- rb_hash_aset(hash, sym("started_at"), ULL2NUM(thread.started_at.nanoseconds()));
1528
- if (!thread.stopped_at.zero()) {
1529
- rb_hash_aset(hash, sym("stopped_at"), ULL2NUM(thread.stopped_at.nanoseconds()));
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.name.data(), thread.name.length()));
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Vernier
4
- VERSION = "0.4.0"
4
+ VERSION = "0.5.1"
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.4.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-01-15 00:00:00.000000000 Z
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.4.10
70
+ rubygems_version: 3.5.3
71
71
  signing_key:
72
72
  specification_version: 4
73
73
  summary: An experimental profiler