vernier 1.4.0 → 1.6.0

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.
@@ -14,20 +14,11 @@
14
14
 
15
15
  #include <sys/time.h>
16
16
  #include <signal.h>
17
- #if defined(__APPLE__)
18
- /* macOS */
19
- #include <dispatch/dispatch.h>
20
- #elif defined(__FreeBSD__)
21
- /* FreeBSD */
22
- #include <pthread_np.h>
23
- #include <semaphore.h>
24
- #else
25
- /* Linux */
26
- #include <semaphore.h>
27
- #include <sys/syscall.h> /* for SYS_gettid */
28
- #endif
29
17
 
30
18
  #include "vernier.hh"
19
+ #include "timestamp.hh"
20
+ #include "periodic_thread.hh"
21
+ #include "signal_safe_semaphore.hh"
31
22
 
32
23
  #include "ruby/ruby.h"
33
24
  #include "ruby/encoding.h"
@@ -49,6 +40,7 @@
49
40
 
50
41
  #define RUBY_NORMAL_EVENTS \
51
42
  RUBY_EVENT_THREAD_BEGIN | \
43
+ RUBY_EVENT_FIBER_SWITCH | \
52
44
  RUBY_EVENT_THREAD_END
53
45
 
54
46
  #define sym(name) ID2SYM(rb_intern_const(name))
@@ -58,13 +50,13 @@ extern "C" size_t rb_obj_memsize_of(VALUE);
58
50
 
59
51
  using namespace std;
60
52
 
61
- static VALUE rb_mVernier;
53
+ VALUE rb_mVernier;
62
54
  static VALUE rb_cVernierResult;
63
55
  static VALUE rb_mVernierMarkerType;
64
56
  static VALUE rb_cVernierCollector;
65
57
  static VALUE rb_cStackTable;
66
58
 
67
- static VALUE sym_state, sym_gc_by;
59
+ static VALUE sym_state, sym_gc_by, sym_fiber_id;
68
60
 
69
61
  static const char *gvl_event_name(rb_event_flag_t event) {
70
62
  switch (event) {
@@ -82,133 +74,13 @@ static const char *gvl_event_name(rb_event_flag_t event) {
82
74
  return "no-event";
83
75
  }
84
76
 
85
- class TimeStamp {
86
- static const uint64_t nanoseconds_per_second = 1000000000;
87
- uint64_t value_ns;
88
-
89
- TimeStamp(uint64_t value_ns) : value_ns(value_ns) {}
90
-
91
- public:
92
- TimeStamp() : value_ns(0) {}
93
-
94
- static TimeStamp Now() {
95
- struct timespec ts;
96
- clock_gettime(CLOCK_MONOTONIC, &ts);
97
- return TimeStamp(ts.tv_sec * nanoseconds_per_second + ts.tv_nsec);
98
- }
99
-
100
- static TimeStamp Zero() {
101
- return TimeStamp(0);
102
- }
103
-
104
- // SleepUntil a specified timestamp
105
- // Highly accurate manual sleep time
106
- static void SleepUntil(const TimeStamp &target_time) {
107
- if (target_time.zero()) return;
108
- struct timespec ts = target_time.timespec();
109
-
110
- int res;
111
- do {
112
- // do nothing until it's time :)
113
- sleep(0);
114
- } while (target_time > TimeStamp::Now());
115
- }
116
-
117
- static TimeStamp from_seconds(uint64_t s) {
118
- return TimeStamp::from_milliseconds(s * 1000);
119
- }
120
-
121
- static TimeStamp from_milliseconds(uint64_t ms) {
122
- return TimeStamp::from_microseconds(ms * 1000);
123
- }
124
-
125
- static TimeStamp from_microseconds(uint64_t us) {
126
- return TimeStamp::from_nanoseconds(us * 1000);
127
- }
128
-
129
- static TimeStamp from_nanoseconds(uint64_t ns) {
130
- return TimeStamp(ns);
131
- }
132
-
133
- TimeStamp operator-(const TimeStamp &other) const {
134
- TimeStamp result = *this;
135
- return result -= other;
136
- }
137
-
138
- TimeStamp &operator-=(const TimeStamp &other) {
139
- if (value_ns > other.value_ns) {
140
- value_ns = value_ns - other.value_ns;
141
- } else {
142
- // underflow
143
- value_ns = 0;
144
- }
145
- return *this;
146
- }
147
-
148
- TimeStamp operator+(const TimeStamp &other) const {
149
- TimeStamp result = *this;
150
- return result += other;
151
- }
152
-
153
- TimeStamp &operator+=(const TimeStamp &other) {
154
- uint64_t new_value = value_ns + other.value_ns;
155
- value_ns = new_value;
156
- return *this;
157
- }
158
-
159
- bool operator<(const TimeStamp &other) const {
160
- return value_ns < other.value_ns;
161
- }
162
-
163
- bool operator<=(const TimeStamp &other) const {
164
- return value_ns <= other.value_ns;
165
- }
166
-
167
- bool operator>(const TimeStamp &other) const {
168
- return value_ns > other.value_ns;
169
- }
170
-
171
- bool operator>=(const TimeStamp &other) const {
172
- return value_ns >= other.value_ns;
173
- }
174
-
175
- bool operator==(const TimeStamp &other) const {
176
- return value_ns == other.value_ns;
177
- }
178
-
179
- bool operator!=(const TimeStamp &other) const {
180
- return value_ns != other.value_ns;
181
- }
182
-
183
- uint64_t nanoseconds() const {
184
- return value_ns;
185
- }
186
-
187
- uint64_t microseconds() const {
188
- return value_ns / 1000;
189
- }
190
-
191
- bool zero() const {
192
- return value_ns == 0;
193
- }
194
-
195
- struct timespec timespec() const {
196
- struct timespec ts;
197
- ts.tv_sec = nanoseconds() / nanoseconds_per_second;
198
- ts.tv_nsec = (nanoseconds() % nanoseconds_per_second);
199
- return ts;
200
- }
201
- };
202
-
203
- std::ostream& operator<<(std::ostream& os, const TimeStamp& info) {
204
- os << info.nanoseconds() << "ns";
205
- return os;
206
- }
207
-
208
77
  // TODO: Rename FuncInfo
209
78
  struct FrameInfo {
210
79
  static const char *label_cstr(VALUE frame) {
211
80
  VALUE label = rb_profile_frame_full_label(frame);
81
+ // Currently (2025-03-22, Ruby 3.4.2) this occurs when an iseq method
82
+ // entry is replaced with a refinement
83
+ if (NIL_P(label)) return "(nil)";
212
84
  return StringValueCStr(label);
213
85
  }
214
86
 
@@ -217,7 +89,7 @@ struct FrameInfo {
217
89
  if (NIL_P(file))
218
90
  file = rb_profile_frame_path(frame);
219
91
  if (NIL_P(file)) {
220
- return "";
92
+ return "(nil)";
221
93
  } else {
222
94
  return StringValueCStr(file);
223
95
  }
@@ -269,58 +141,6 @@ namespace std {
269
141
  };
270
142
  }
271
143
 
272
- // A basic semaphore built on sem_wait/sem_post
273
- // post() is guaranteed to be async-signal-safe
274
- class SignalSafeSemaphore {
275
- #ifdef __APPLE__
276
- dispatch_semaphore_t sem;
277
- #else
278
- sem_t sem;
279
- #endif
280
-
281
- public:
282
-
283
- SignalSafeSemaphore(unsigned int value = 0) {
284
- #ifdef __APPLE__
285
- sem = dispatch_semaphore_create(value);
286
- #else
287
- sem_init(&sem, 0, value);
288
- #endif
289
- };
290
-
291
- ~SignalSafeSemaphore() {
292
- #ifdef __APPLE__
293
- dispatch_release(sem);
294
- #else
295
- sem_destroy(&sem);
296
- #endif
297
- };
298
-
299
- void wait() {
300
- #ifdef __APPLE__
301
- dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
302
- #else
303
- // Use sem_timedwait so that we get a crash instead of a deadlock for
304
- // easier debugging
305
- auto ts = (TimeStamp::Now() + TimeStamp::from_seconds(5)).timespec();
306
-
307
- int ret;
308
- do {
309
- ret = sem_wait(&sem);
310
- } while (ret && errno == EINTR);
311
- assert(ret == 0);
312
- #endif
313
- }
314
-
315
- void post() {
316
- #ifdef __APPLE__
317
- dispatch_semaphore_signal(sem);
318
- #else
319
- sem_post(&sem);
320
- #endif
321
- }
322
- };
323
-
324
144
  class RawSample {
325
145
  public:
326
146
 
@@ -854,25 +674,35 @@ union MarkerInfo {
854
674
  VALUE gc_by;
855
675
  VALUE gc_state;
856
676
  } gc_data;
677
+ struct {
678
+ VALUE fiber_id;
679
+ } fiber_data;
857
680
  };
858
681
 
682
+ #define EACH_MARKER(XX) \
683
+ XX(GVL_THREAD_STARTED) \
684
+ XX(GVL_THREAD_EXITED) \
685
+ \
686
+ XX(GC_START) \
687
+ XX(GC_END_MARK) \
688
+ XX(GC_END_SWEEP) \
689
+ XX(GC_ENTER) \
690
+ XX(GC_EXIT) \
691
+ XX(GC_PAUSE) \
692
+ \
693
+ XX(THREAD_RUNNING) \
694
+ XX(THREAD_STALLED) \
695
+ XX(THREAD_SUSPENDED) \
696
+ \
697
+ XX(FIBER_SWITCH)
698
+
699
+
859
700
  class Marker {
860
701
  public:
861
702
  enum Type {
862
- MARKER_GVL_THREAD_STARTED,
863
- MARKER_GVL_THREAD_EXITED,
864
-
865
- MARKER_GC_START,
866
- MARKER_GC_END_MARK,
867
- MARKER_GC_END_SWEEP,
868
- MARKER_GC_ENTER,
869
- MARKER_GC_EXIT,
870
- MARKER_GC_PAUSE,
871
-
872
- MARKER_THREAD_RUNNING,
873
- MARKER_THREAD_STALLED,
874
- MARKER_THREAD_SUSPENDED,
875
-
703
+ #define XX(name) MARKER_ ## name,
704
+ EACH_MARKER(XX)
705
+ #undef XX
876
706
  MARKER_MAX,
877
707
  };
878
708
 
@@ -894,29 +724,33 @@ class Marker {
894
724
 
895
725
  MarkerInfo extra_info;
896
726
 
897
- VALUE to_array() {
898
- VALUE record[7] = {0};
899
- record[0] = Qnil; // FIXME
900
- record[1] = INT2NUM(type);
901
- record[2] = INT2NUM(phase);
902
- record[3] = ULL2NUM(timestamp.nanoseconds());
727
+ VALUE to_array() const {
728
+ VALUE record[6] = {0};
729
+ record[0] = INT2NUM(type);
730
+ record[1] = INT2NUM(phase);
731
+ record[2] = ULL2NUM(timestamp.nanoseconds());
903
732
 
904
733
  if (phase == Marker::Phase::INTERVAL) {
905
- record[4] = ULL2NUM(finish.nanoseconds());
734
+ record[3] = ULL2NUM(finish.nanoseconds());
906
735
  }
907
736
  else {
908
- record[4] = Qnil;
737
+ record[3] = Qnil;
909
738
  }
910
- record[5] = stack_index == -1 ? Qnil : INT2NUM(stack_index);
739
+ record[4] = stack_index == -1 ? Qnil : INT2NUM(stack_index);
911
740
 
912
741
  if (type == Marker::MARKER_GC_PAUSE) {
913
742
  VALUE hash = rb_hash_new();
914
- record[6] = hash;
743
+ record[5] = hash;
915
744
 
916
745
  rb_hash_aset(hash, sym_gc_by, extra_info.gc_data.gc_by);
917
746
  rb_hash_aset(hash, sym_state, extra_info.gc_data.gc_state);
747
+ } else if (type == Marker::MARKER_FIBER_SWITCH) {
748
+ VALUE hash = rb_hash_new();
749
+ record[5] = hash;
750
+
751
+ rb_hash_aset(hash, sym_fiber_id, extra_info.fiber_data.fiber_id);
918
752
  }
919
- return rb_ary_new_from_values(7, record);
753
+ return rb_ary_new_from_values(6, record);
920
754
  }
921
755
  };
922
756
 
@@ -934,7 +768,15 @@ class MarkerTable {
934
768
  void record(Marker::Type type, int stack_index = -1, MarkerInfo extra_info = {}) {
935
769
  const std::lock_guard<std::mutex> lock(mutex);
936
770
 
937
- list.push_back({ type, Marker::INSTANT, TimeStamp::Now(), TimeStamp(), stack_index });
771
+ list.push_back({ type, Marker::INSTANT, TimeStamp::Now(), TimeStamp(), stack_index, extra_info });
772
+ }
773
+
774
+ VALUE to_array() const {
775
+ VALUE ary = rb_ary_new();
776
+ for (auto& marker: list) {
777
+ rb_ary_push(ary, marker.to_array());
778
+ }
779
+ return ary;
938
780
  }
939
781
  };
940
782
 
@@ -1139,6 +981,15 @@ class Thread {
1139
981
  }
1140
982
  }
1141
983
 
984
+ void record_fiber(VALUE fiber, StackTable &frame_list) {
985
+ RawSample sample;
986
+ sample.sample();
987
+
988
+ int stack_idx = translator.translate(frame_list, sample);
989
+ VALUE fiber_id = rb_obj_id(fiber);
990
+ markers->record(Marker::Type::MARKER_FIBER_SWITCH, stack_idx, { .fiber_data = { .fiber_id = fiber_id } });
991
+ }
992
+
1142
993
  void set_state(State new_state) {
1143
994
  if (state == Thread::State::STOPPED) {
1144
995
  return;
@@ -1380,10 +1231,6 @@ class BaseCollector {
1380
1231
 
1381
1232
  virtual void compact() {
1382
1233
  };
1383
-
1384
- virtual VALUE get_markers() {
1385
- return rb_ary_new();
1386
- };
1387
1234
  };
1388
1235
 
1389
1236
  class CustomCollector : public BaseCollector {
@@ -1658,6 +1505,19 @@ class GlobalSignalHandler {
1658
1505
  LiveSample *GlobalSignalHandler::live_sample;
1659
1506
 
1660
1507
  class TimeCollector : public BaseCollector {
1508
+ class TimeCollectorThread : public PeriodicThread {
1509
+ TimeCollector &time_collector;
1510
+
1511
+ void run_iteration() {
1512
+ time_collector.run_iteration();
1513
+ }
1514
+
1515
+ public:
1516
+
1517
+ TimeCollectorThread(TimeCollector &tc, TimeStamp interval) : PeriodicThread(interval), time_collector(tc) {
1518
+ };
1519
+ };
1520
+
1661
1521
  GCMarkerTable gc_markers;
1662
1522
  ThreadTable threads;
1663
1523
 
@@ -1680,8 +1540,10 @@ class TimeCollector : public BaseCollector {
1680
1540
  collector->record_newobj(obj);
1681
1541
  }
1682
1542
 
1543
+ TimeCollectorThread collector_thread;
1544
+
1683
1545
  public:
1684
- TimeCollector(VALUE stack_table, TimeStamp interval, unsigned int allocation_interval) : BaseCollector(stack_table), interval(interval), allocation_interval(allocation_interval), threads(*get_stack_table(stack_table)) {
1546
+ TimeCollector(VALUE stack_table, TimeStamp interval, unsigned int allocation_interval) : BaseCollector(stack_table), interval(interval), allocation_interval(allocation_interval), threads(*get_stack_table(stack_table)), collector_thread(*this, interval) {
1685
1547
  }
1686
1548
 
1687
1549
  void record_newobj(VALUE obj) {
@@ -1703,6 +1565,18 @@ class TimeCollector : public BaseCollector {
1703
1565
 
1704
1566
  }
1705
1567
 
1568
+ void record_fiber(VALUE th, VALUE fiber) {
1569
+ threads.mutex.lock();
1570
+ for (auto &threadptr : threads.list) {
1571
+ auto &thread = *threadptr;
1572
+ if (th == thread.ruby_thread) {
1573
+ thread.record_fiber(fiber, threads.frame_list);
1574
+ break;
1575
+ }
1576
+ }
1577
+ threads.mutex.unlock();
1578
+ }
1579
+
1706
1580
  void write_meta(VALUE meta, VALUE result) {
1707
1581
  BaseCollector::write_meta(meta, result);
1708
1582
  rb_hash_aset(meta, sym("interval"), ULL2NUM(interval.microseconds()));
@@ -1723,103 +1597,55 @@ class TimeCollector : public BaseCollector {
1723
1597
  }
1724
1598
  }
1725
1599
 
1726
- VALUE get_markers() {
1727
- VALUE list = rb_ary_new();
1728
- VALUE main_thread = rb_thread_main();
1729
- VALUE main_thread_id = rb_obj_id(main_thread);
1730
-
1731
- for (auto& marker: this->gc_markers.list) {
1732
- VALUE ary = marker.to_array();
1600
+ void run_iteration() {
1601
+ TimeStamp sample_start = TimeStamp::Now();
1733
1602
 
1734
- RARRAY_ASET(ary, 0, main_thread_id);
1735
- rb_ary_push(list, ary);
1736
- }
1737
- for (auto &thread : threads.list) {
1738
- for (auto& marker: thread->markers->list) {
1739
- VALUE ary = marker.to_array();
1740
- RARRAY_ASET(ary, 0, thread->ruby_thread_id);
1741
- rb_ary_push(list, ary);
1742
- }
1743
- }
1744
-
1745
- return list;
1746
- }
1747
-
1748
- void sample_thread_run() {
1749
1603
  LiveSample sample;
1750
1604
 
1751
- TimeStamp next_sample_schedule = TimeStamp::Now();
1752
- while (running) {
1753
- TimeStamp sample_start = TimeStamp::Now();
1754
-
1755
- threads.mutex.lock();
1756
- for (auto &threadptr : threads.list) {
1757
- auto &thread = *threadptr;
1605
+ threads.mutex.lock();
1606
+ for (auto &threadptr : threads.list) {
1607
+ auto &thread = *threadptr;
1758
1608
 
1759
- //if (thread.state == Thread::State::RUNNING) {
1760
- //if (thread.state == Thread::State::RUNNING || (thread.state == Thread::State::SUSPENDED && thread.stack_on_suspend_idx < 0)) {
1761
- if (thread.state == Thread::State::RUNNING) {
1762
- //fprintf(stderr, "sampling %p on tid:%i\n", thread.ruby_thread, thread.native_tid);
1763
- bool signal_sent = GlobalSignalHandler::get_instance()->record_sample(sample, thread.pthread_id);
1764
-
1765
- if (!signal_sent) {
1766
- // The thread has died. We probably should have caught
1767
- // that by the GVL instrumentation, but let's try to get
1768
- // it to a consistent state and stop profiling it.
1769
- thread.set_state(Thread::State::STOPPED);
1770
- } else if (sample.sample.empty()) {
1771
- // fprintf(stderr, "skipping GC sample\n");
1772
- } else {
1773
- record_sample(sample.sample, sample_start, thread, CATEGORY_NORMAL);
1774
- }
1775
- } else if (thread.state == Thread::State::SUSPENDED) {
1776
- thread.samples.record_sample(
1777
- thread.stack_on_suspend_idx,
1778
- sample_start,
1779
- CATEGORY_IDLE);
1780
- } else if (thread.state == Thread::State::READY) {
1781
- thread.samples.record_sample(
1782
- thread.stack_on_suspend_idx,
1783
- sample_start,
1784
- CATEGORY_STALLED);
1609
+ //if (thread.state == Thread::State::RUNNING) {
1610
+ //if (thread.state == Thread::State::RUNNING || (thread.state == Thread::State::SUSPENDED && thread.stack_on_suspend_idx < 0)) {
1611
+ if (thread.state == Thread::State::RUNNING) {
1612
+ //fprintf(stderr, "sampling %p on tid:%i\n", thread.ruby_thread, thread.native_tid);
1613
+ bool signal_sent = GlobalSignalHandler::get_instance()->record_sample(sample, thread.pthread_id);
1614
+
1615
+ if (!signal_sent) {
1616
+ // The thread has died. We probably should have caught
1617
+ // that by the GVL instrumentation, but let's try to get
1618
+ // it to a consistent state and stop profiling it.
1619
+ thread.set_state(Thread::State::STOPPED);
1620
+ } else if (sample.sample.empty()) {
1621
+ // fprintf(stderr, "skipping GC sample\n");
1785
1622
  } else {
1623
+ record_sample(sample.sample, sample_start, thread, CATEGORY_NORMAL);
1786
1624
  }
1625
+ } else if (thread.state == Thread::State::SUSPENDED) {
1626
+ thread.samples.record_sample(
1627
+ thread.stack_on_suspend_idx,
1628
+ sample_start,
1629
+ CATEGORY_IDLE);
1630
+ } else if (thread.state == Thread::State::READY) {
1631
+ thread.samples.record_sample(
1632
+ thread.stack_on_suspend_idx,
1633
+ sample_start,
1634
+ CATEGORY_STALLED);
1635
+ } else {
1787
1636
  }
1788
-
1789
- threads.mutex.unlock();
1790
-
1791
- TimeStamp sample_complete = TimeStamp::Now();
1792
-
1793
- next_sample_schedule += interval;
1794
-
1795
- // If sampling falls behind, restart, and check in another interval
1796
- if (next_sample_schedule < sample_complete) {
1797
- next_sample_schedule = sample_complete + interval;
1798
- }
1799
-
1800
- TimeStamp::SleepUntil(next_sample_schedule);
1801
1637
  }
1802
1638
 
1803
- thread_stopped.post();
1804
- }
1805
-
1806
- static void *sample_thread_entry(void *arg) {
1807
- #if HAVE_PTHREAD_SETNAME_NP
1808
- #ifdef __APPLE__
1809
- pthread_setname_np("Vernier profiler");
1810
- #else
1811
- pthread_setname_np(pthread_self(), "Vernier profiler");
1812
- #endif
1813
- #endif
1814
- TimeCollector *collector = static_cast<TimeCollector *>(arg);
1815
- collector->sample_thread_run();
1816
- return NULL;
1639
+ threads.mutex.unlock();
1817
1640
  }
1818
1641
 
1819
1642
  static void internal_thread_event_cb(rb_event_flag_t event, VALUE data, VALUE self, ID mid, VALUE klass) {
1820
1643
  TimeCollector *collector = static_cast<TimeCollector *>((void *)NUM2ULL(data));
1821
1644
 
1822
1645
  switch (event) {
1646
+ case RUBY_EVENT_FIBER_SWITCH:
1647
+ collector->record_fiber(rb_thread_current(), rb_fiber_current());
1648
+ break;
1823
1649
  case RUBY_EVENT_THREAD_BEGIN:
1824
1650
  collector->threads.started(self);
1825
1651
  break;
@@ -1913,11 +1739,7 @@ class TimeCollector : public BaseCollector {
1913
1739
 
1914
1740
  running = true;
1915
1741
 
1916
- int ret = pthread_create(&sample_thread, NULL, &sample_thread_entry, this);
1917
- if (ret != 0) {
1918
- perror("pthread_create");
1919
- rb_bug("VERNIER: pthread_create failed");
1920
- }
1742
+ collector_thread.start();
1921
1743
 
1922
1744
  // Set the state of the current Ruby thread to RUNNING, which we know it
1923
1745
  // is as it must have held the GVL to start the collector. We want to
@@ -1936,8 +1758,7 @@ class TimeCollector : public BaseCollector {
1936
1758
  VALUE stop() {
1937
1759
  BaseCollector::stop();
1938
1760
 
1939
- running = false;
1940
- thread_stopped.wait();
1761
+ collector_thread.stop();
1941
1762
 
1942
1763
  GlobalSignalHandler::get_instance()->uninstall();
1943
1764
 
@@ -1962,6 +1783,8 @@ class TimeCollector : public BaseCollector {
1962
1783
  VALUE build_collector_result() {
1963
1784
  VALUE result = BaseCollector::build_collector_result();
1964
1785
 
1786
+ rb_ivar_set(result, rb_intern("@gc_markers"), this->gc_markers.to_array());
1787
+
1965
1788
  VALUE threads = rb_hash_new();
1966
1789
  rb_ivar_set(result, rb_intern("@threads"), threads);
1967
1790
 
@@ -1969,8 +1792,7 @@ class TimeCollector : public BaseCollector {
1969
1792
  VALUE hash = rb_hash_new();
1970
1793
  thread->samples.write_result(hash);
1971
1794
  thread->allocation_samples.write_result(hash);
1972
-
1973
- rb_hash_aset(threads, thread->ruby_thread_id, hash);
1795
+ rb_hash_aset(hash, sym("markers"), thread->markers->to_array());
1974
1796
  rb_hash_aset(hash, sym("tid"), ULL2NUM(thread->native_tid));
1975
1797
  rb_hash_aset(hash, sym("started_at"), ULL2NUM(thread->started_at.nanoseconds()));
1976
1798
  if (!thread->stopped_at.zero()) {
@@ -1979,6 +1801,7 @@ class TimeCollector : public BaseCollector {
1979
1801
  rb_hash_aset(hash, sym("is_main"), thread->is_main() ? Qtrue : Qfalse);
1980
1802
  rb_hash_aset(hash, sym("is_start"), thread->is_start(BaseCollector::start_thread) ? Qtrue : Qfalse);
1981
1803
 
1804
+ rb_hash_aset(threads, thread->ruby_thread_id, hash);
1982
1805
  }
1983
1806
 
1984
1807
  return result;
@@ -2051,13 +1874,6 @@ collector_stop(VALUE self) {
2051
1874
  return result;
2052
1875
  }
2053
1876
 
2054
- static VALUE
2055
- markers(VALUE self) {
2056
- auto *collector = get_collector(self);
2057
-
2058
- return collector->get_markers();
2059
- }
2060
-
2061
1877
  static VALUE
2062
1878
  collector_sample(VALUE self) {
2063
1879
  auto *collector = get_collector(self);
@@ -2111,24 +1927,10 @@ static VALUE collector_new(VALUE self, VALUE mode, VALUE options) {
2111
1927
 
2112
1928
  static void
2113
1929
  Init_consts(VALUE rb_mVernierMarkerPhase) {
2114
- #define MARKER_CONST(name) \
2115
- rb_define_const(rb_mVernierMarkerType, #name, INT2NUM(Marker::Type::MARKER_##name))
2116
-
2117
- MARKER_CONST(GVL_THREAD_STARTED);
2118
- MARKER_CONST(GVL_THREAD_EXITED);
2119
-
2120
- MARKER_CONST(GC_START);
2121
- MARKER_CONST(GC_END_MARK);
2122
- MARKER_CONST(GC_END_SWEEP);
2123
- MARKER_CONST(GC_ENTER);
2124
- MARKER_CONST(GC_EXIT);
2125
- MARKER_CONST(GC_PAUSE);
2126
-
2127
- MARKER_CONST(THREAD_RUNNING);
2128
- MARKER_CONST(THREAD_STALLED);
2129
- MARKER_CONST(THREAD_SUSPENDED);
2130
-
2131
- #undef MARKER_CONST
1930
+ #define XX(name) \
1931
+ rb_define_const(rb_mVernierMarkerType, #name, INT2NUM(Marker::Type::MARKER_##name));
1932
+ EACH_MARKER(XX)
1933
+ #undef XX
2132
1934
 
2133
1935
  #define PHASE_CONST(name) \
2134
1936
  rb_define_const(rb_mVernierMarkerPhase, #name, INT2NUM(Marker::Phase::name))
@@ -2140,11 +1942,12 @@ Init_consts(VALUE rb_mVernierMarkerPhase) {
2140
1942
  #undef PHASE_CONST
2141
1943
  }
2142
1944
 
2143
- extern "C" void
1945
+ extern "C" __attribute__ ((__visibility__("default"))) void
2144
1946
  Init_vernier(void)
2145
1947
  {
2146
1948
  sym_state = sym("state");
2147
1949
  sym_gc_by = sym("gc_by");
1950
+ sym_fiber_id = sym("fiber_id");
2148
1951
  rb_gc_latest_gc_info(sym_state); // HACK: needs to be warmed so that it can be called during GC
2149
1952
 
2150
1953
  rb_mVernier = rb_define_module("Vernier");
@@ -2160,7 +1963,6 @@ Init_vernier(void)
2160
1963
  rb_define_method(rb_cVernierCollector, "sample", collector_sample, 0);
2161
1964
  rb_define_method(rb_cVernierCollector, "stack_table", collector_stack_table, 0);
2162
1965
  rb_define_private_method(rb_cVernierCollector, "finish", collector_stop, 0);
2163
- rb_define_private_method(rb_cVernierCollector, "markers", markers, 0);
2164
1966
 
2165
1967
  rb_cStackTable = rb_define_class_under(rb_mVernier, "StackTable", rb_cObject);
2166
1968
  rb_undef_alloc_func(rb_cStackTable);
@@ -2179,6 +1981,7 @@ Init_vernier(void)
2179
1981
  rb_define_method(rb_cStackTable, "func_count", StackTable::stack_table_func_count, 0);
2180
1982
 
2181
1983
  Init_consts(rb_mVernierMarkerPhase);
1984
+ Init_memory();
2182
1985
 
2183
1986
  //static VALUE gc_hook = Data_Wrap_Struct(rb_cObject, collector_mark, NULL, &_collector);
2184
1987
  //rb_global_variable(&gc_hook);
@@ -3,4 +3,8 @@
3
3
 
4
4
  #include "ruby.h"
5
5
 
6
+ extern VALUE rb_mVernier;
7
+
8
+ void Init_memory();
9
+
6
10
  #endif /* VERNIER_H */