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 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