vernier 1.4.0 → 1.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: faf587f758e0b9a68adc377bf27c0f35eb0605703b088e3e122f969f513c864d
4
- data.tar.gz: 6cbefbfa78fb3ef1e92c7cfdd5ba0dd43f59b1f63cce6583fe18f4e667fd61db
3
+ metadata.gz: 2a5b535bebdbcc91aa853b53657893c37531e8acffe5f00df1753ca1c52e7508
4
+ data.tar.gz: 7aa9509fdfb009956bd2eeab6b74bc8fa832e078d9c8fce92445c82a5699ea27
5
5
  SHA512:
6
- metadata.gz: 402ce92fbe0f408906fe368e0e2edc637dd68f08476a690877166cb1628068fd6bbfd11878d625276e8f3f785c20adc49ef19494d9e81162d201930b4c64c81f
7
- data.tar.gz: 8b7d6c687873ed991b23a508231471b445d6960b47bac2045f1f9eee9f5d82584490a0278f2344d0001e3f5367fffcdfc29fbc284bed448c4155e4381aa26292
6
+ metadata.gz: 45584b395431f8135928011d03d427f42aa747dda76c4bca3bc063837571e3cc168b3fbc04cd268353e17299f47c7be3c6c486e0885488c6e7b5aba94bcb4190
7
+ data.tar.gz: d8df8d3a0ac7ce752cfb4c8fc0f57e0af1bf7640a3f228f52a8cd4a32211cbe21686338ca6068257d49df0a2a0206773da89d93aa673a2915c5ea5881d1f4be2
@@ -0,0 +1,51 @@
1
+ require "bundler/inline"
2
+ gemfile do
3
+ source 'https://rubygems.org'
4
+
5
+ gem "async"
6
+ end
7
+
8
+ require "async"
9
+ require "async/queue"
10
+
11
+ def measure
12
+ x = Process.clock_gettime(Process::CLOCK_MONOTONIC)
13
+ yield
14
+ Process.clock_gettime(Process::CLOCK_MONOTONIC) - x
15
+ end
16
+
17
+ def fib(n)
18
+ if n < 2
19
+ n
20
+ else
21
+ fib(n-2) + fib(n-1)
22
+ end
23
+ end
24
+
25
+ # find fib that takes ~50ms
26
+ fib_i = 50.times.find { |i| measure { fib(i) } >= 0.05 }
27
+ sleep_i = measure { fib(fib_i) }
28
+
29
+ Async {
30
+ latch = Async::Queue.new
31
+
32
+ workers = [
33
+ Async {
34
+ latch.pop # block until ready to measure
35
+
36
+ 100.times {
37
+ sleep(sleep_i)
38
+ # stalls happen here. This worker wants to be scheduled so it can
39
+ # continue the loop, but will be blocked by another worker executing fib
40
+ }
41
+ },
42
+ Async {
43
+ latch.pop # block until ready to measure
44
+
45
+ 100.times { fib(fib_i) }
46
+ },
47
+ ]
48
+
49
+ 2.times { latch << nil }
50
+ workers.each(&:wait)
51
+ }
data/exe/vernier CHANGED
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
4
+
3
5
  require "optparse"
4
6
  require "vernier/version"
5
7
 
@@ -18,6 +20,9 @@ FLAGS:
18
20
  o.on('--output [FILENAME]', String, "output filename") do |s|
19
21
  options[:output] = s
20
22
  end
23
+ o.on('--output-dir [DIRECTORY]', String, "output directory (default .)") do |s|
24
+ options[:output_dir] = s
25
+ end
21
26
  o.on('--interval [MICROSECONDS]', Integer, "sampling interval (default 500)") do |i|
22
27
  options[:interval] = i
23
28
  end
@@ -52,60 +57,14 @@ FLAGS:
52
57
 
53
58
  def self.inverted_tree(top, file)
54
59
  # Print the inverted tree from a Vernier profile
55
- require "json"
56
-
57
- is_gzip = File.binread(file, 2) == "\x1F\x8B".b # check for gzip header
58
-
59
- json = if is_gzip
60
- require "zlib"
61
- Zlib::GzipReader.open(file) { |gz| gz.read }
62
- else
63
- File.read file
64
- end
65
-
66
- info = JSON.load json
67
-
68
- main = info["threads"].find { |thread| thread["isMainThread"] }
69
-
70
- weight_by_frame = Hash.new(0)
71
-
72
- stack_frames = main["stackTable"]["frame"]
73
- frame_table = main["frameTable"]["func"]
74
- func_table = main["funcTable"]["name"]
75
- string_array = main["stringArray"]
60
+ require "vernier/parsed_profile"
61
+ require "vernier/output/top"
62
+ require "vernier/output/file_listing"
76
63
 
77
- main["samples"]["stack"].zip(main["samples"]["weight"]).each do |stack, weight|
78
- top_frame_index = stack_frames[stack]
79
- func_index = frame_table[top_frame_index]
80
- string_index = func_table[func_index]
81
- str = string_array[string_index]
82
- weight_by_frame[str] += weight
83
- end
84
-
85
- total = weight_by_frame.values.inject :+
86
-
87
- header = ["Samples", "%", ""]
88
- widths = header.map(&:bytesize)
89
-
90
- columns = weight_by_frame.sort_by { |k,v| v }.reverse.first(top).map { |k,v|
91
- entry = [v.to_s, ((v / total.to_f) * 100).round(1).to_s, k]
92
- entry.each_with_index { |str, i| widths[i] = str.bytesize if widths[i] < str.bytesize }
93
- entry
94
- }
95
-
96
- print_separator widths
97
- print_row header, widths
98
- print_separator widths
99
- columns.each { print_row(_1, widths) }
100
- print_separator widths
101
- end
102
-
103
- def self.print_row(list, widths)
104
- puts("|" + list.map.with_index { |str, i| " " + str.ljust(widths[i] + 1) }.join("|") + "|")
105
- end
64
+ parsed_profile = Vernier::ParsedProfile.read_file(file)
106
65
 
107
- def self.print_separator(widths)
108
- puts("+" + widths.map { |i| "-" * (i + 2) }.join("+") + "+")
66
+ puts Vernier::Output::Top.new(parsed_profile).output
67
+ puts Vernier::Output::FileListing.new(parsed_profile).output
109
68
  end
110
69
  end
111
70
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "mkmf"
4
4
 
5
+ $CXXFLAGS += " -fvisibility=hidden "
5
6
  $CXXFLAGS += " -std=c++14 "
6
7
  $CXXFLAGS += " -ggdb3 -Og "
7
8
 
@@ -11,5 +12,6 @@ have_struct_member("rb_internal_thread_event_data_t", "thread", ["ruby/thread.h"
11
12
  have_func("rb_profile_thread_frames", "ruby/debug.h")
12
13
 
13
14
  have_func("pthread_setname_np")
15
+ have_func("pthread_condattr_setclock")
14
16
 
15
17
  create_makefile("vernier/vernier")
@@ -0,0 +1,144 @@
1
+ #include <mutex>
2
+ #include <stdio.h>
3
+ #include <unistd.h>
4
+ #include <vector>
5
+
6
+ #include "vernier.hh"
7
+ #include "timestamp.hh"
8
+ #include "periodic_thread.hh"
9
+
10
+ #if defined(__APPLE__)
11
+
12
+ // Based loosely on https://github.com/zombocom/get_process_mem
13
+ #include <libproc.h>
14
+
15
+ uint64_t memory_rss() {
16
+ pid_t pid = getpid();
17
+
18
+ struct proc_taskinfo tinfo;
19
+ int st = proc_pidinfo(pid, PROC_PIDTASKINFO, 0,
20
+ &tinfo, sizeof(tinfo));
21
+
22
+ if (st != sizeof(tinfo)) {
23
+ fprintf(stderr, "VERNIER: warning: proc_pidinfo failed\n");
24
+ return 0;
25
+ }
26
+
27
+ return tinfo.pti_resident_size;
28
+ }
29
+
30
+ #elif defined(__linux__)
31
+
32
+ uint64_t memory_rss() {
33
+ long rss = 0;
34
+
35
+ // I'd heard that you shouldn't read /proc/*/smaps with fopen and family,
36
+ // but maybe it's fine for statm which is much smaller and will almost
37
+ // certainly fit in any internal buffer.
38
+ FILE *file = fopen("/proc/self/statm", "r");
39
+ if (!file) return 0;
40
+ if (fscanf(file, "%*s%ld", &rss) != 1) {
41
+ fclose(file);
42
+ return 0;
43
+ }
44
+ fclose(file);
45
+ return rss * sysconf(_SC_PAGESIZE);
46
+ }
47
+
48
+ #else
49
+
50
+ // Unsupported
51
+ uint64_t memory_rss() {
52
+ return 0;
53
+ }
54
+
55
+ #endif
56
+
57
+ VALUE rb_cMemoryTracker;
58
+
59
+ static VALUE rb_memory_rss(VALUE self) {
60
+ return ULL2NUM(memory_rss());
61
+ }
62
+
63
+ class MemoryTracker : public PeriodicThread {
64
+ public:
65
+ struct Record {
66
+ TimeStamp timestamp;
67
+ uint64_t memory_rss;
68
+ };
69
+ std::vector<Record> results;
70
+ std::mutex mutex;
71
+
72
+ MemoryTracker() : PeriodicThread(TimeStamp::from_milliseconds(10)) {
73
+ }
74
+
75
+ void run_iteration() {
76
+ record();
77
+ }
78
+
79
+ void record() {
80
+ const std::lock_guard<std::mutex> lock(mutex);
81
+ results.push_back(Record{TimeStamp::Now(), memory_rss()});
82
+ }
83
+ };
84
+
85
+ static const rb_data_type_t rb_memory_tracker_type = {
86
+ .wrap_struct_name = "vernier/memory_tracker",
87
+ .function = {
88
+ //.dmemsize = memory_tracker_memsize,
89
+ //.dmark = memory_tracker_mark,
90
+ //.dfree = memory_tracker_free,
91
+ },
92
+ };
93
+
94
+ VALUE memory_tracker_start(VALUE self) {
95
+ MemoryTracker *memory_tracker;
96
+ TypedData_Get_Struct(self, MemoryTracker, &rb_memory_tracker_type, memory_tracker);
97
+ memory_tracker->start();
98
+ return self;
99
+ }
100
+
101
+ VALUE memory_tracker_stop(VALUE self) {
102
+ MemoryTracker *memory_tracker;
103
+ TypedData_Get_Struct(self, MemoryTracker, &rb_memory_tracker_type, memory_tracker);
104
+
105
+ memory_tracker->stop();
106
+ return self;
107
+ }
108
+
109
+ VALUE memory_tracker_record(VALUE self) {
110
+ MemoryTracker *memory_tracker;
111
+ TypedData_Get_Struct(self, MemoryTracker, &rb_memory_tracker_type, memory_tracker);
112
+ memory_tracker->record();
113
+ return self;
114
+ }
115
+
116
+ VALUE memory_tracker_results(VALUE self) {
117
+ MemoryTracker *memory_tracker;
118
+ TypedData_Get_Struct(self, MemoryTracker, &rb_memory_tracker_type, memory_tracker);
119
+ VALUE timestamps = rb_ary_new();
120
+ VALUE memory = rb_ary_new();
121
+ for (const auto& record: memory_tracker->results) {
122
+ rb_ary_push(timestamps, ULL2NUM(record.timestamp.nanoseconds()));
123
+ rb_ary_push(memory, ULL2NUM(record.memory_rss));
124
+ }
125
+ return rb_ary_new_from_args(2, timestamps, memory);
126
+ }
127
+
128
+ VALUE memory_tracker_alloc(VALUE self) {
129
+ auto memory_tracker = new MemoryTracker();
130
+ VALUE obj = TypedData_Wrap_Struct(self, &rb_memory_tracker_type, memory_tracker);
131
+ return obj;
132
+ }
133
+
134
+ void Init_memory() {
135
+ rb_cMemoryTracker = rb_define_class_under(rb_mVernier, "MemoryTracker", rb_cObject);
136
+ rb_define_alloc_func(rb_cMemoryTracker, memory_tracker_alloc);
137
+
138
+ rb_define_method(rb_cMemoryTracker, "start", memory_tracker_start, 0);
139
+ rb_define_method(rb_cMemoryTracker, "stop", memory_tracker_stop, 0);
140
+ rb_define_method(rb_cMemoryTracker, "results", memory_tracker_results, 0);
141
+ rb_define_method(rb_cMemoryTracker, "record", memory_tracker_record, 0);
142
+
143
+ rb_define_singleton_method(rb_mVernier, "memory_rss", rb_memory_rss, 0);
144
+ }
@@ -0,0 +1,141 @@
1
+ #include "ruby.h"
2
+
3
+ #include <atomic>
4
+ #include "timestamp.hh"
5
+
6
+ #ifdef __APPLE__
7
+
8
+ #include <mach/mach.h>
9
+ #include <mach/mach_time.h>
10
+ #include <pthread.h>
11
+
12
+ // https://developer.apple.com/library/archive/technotes/tn2169/_index.html
13
+ inline void upgrade_thread_priority(pthread_t pthread) {
14
+ mach_timebase_info_data_t timebase_info;
15
+ mach_timebase_info(&timebase_info);
16
+
17
+ const uint64_t NANOS_PER_MSEC = 1000000ULL;
18
+ double clock2abs = ((double)timebase_info.denom / (double)timebase_info.numer) * NANOS_PER_MSEC;
19
+
20
+ thread_time_constraint_policy_data_t policy;
21
+ policy.period = 0;
22
+
23
+ // FIXME: I really don't know what these value should be
24
+ policy.computation = (uint32_t)(5 * clock2abs); // 5 ms of work
25
+ policy.constraint = (uint32_t)(10 * clock2abs);
26
+ policy.preemptible = FALSE;
27
+
28
+ int kr = thread_policy_set(pthread_mach_thread_np(pthread_self()),
29
+ THREAD_TIME_CONSTRAINT_POLICY,
30
+ (thread_policy_t)&policy,
31
+ THREAD_TIME_CONSTRAINT_POLICY_COUNT);
32
+
33
+ if (kr != KERN_SUCCESS) {
34
+ mach_error("thread_policy_set:", kr);
35
+ exit(1);
36
+ }
37
+ }
38
+ #else
39
+ inline void upgrade_thread_priority(pthread_t pthread) {
40
+ }
41
+ #endif
42
+
43
+ class PeriodicThread {
44
+ pthread_t pthread;
45
+ TimeStamp interval;
46
+
47
+ pthread_mutex_t running_mutex = PTHREAD_MUTEX_INITIALIZER;
48
+ pthread_cond_t running_cv;
49
+ std::atomic_bool running;
50
+
51
+ public:
52
+ PeriodicThread(TimeStamp interval) : interval(interval), running(false) {
53
+ pthread_condattr_t attr;
54
+ pthread_condattr_init(&attr);
55
+ #if HAVE_PTHREAD_CONDATTR_SETCLOCK
56
+ pthread_condattr_setclock(&attr, CLOCK_MONOTONIC);
57
+ #endif
58
+ pthread_cond_init(&running_cv, &attr);
59
+ }
60
+
61
+ void set_interval(TimeStamp timestamp) {
62
+ interval = timestamp;
63
+ }
64
+
65
+ static void *thread_entrypoint(void *arg) {
66
+ upgrade_thread_priority(pthread_self());
67
+
68
+ static_cast<PeriodicThread *>(arg)->run();
69
+ return NULL;
70
+ }
71
+
72
+ void run() {
73
+ #if HAVE_PTHREAD_SETNAME_NP
74
+ #ifdef __APPLE__
75
+ pthread_setname_np("Vernier profiler");
76
+ #else
77
+ pthread_setname_np(pthread_self(), "Vernier profiler");
78
+ #endif
79
+ #endif
80
+
81
+ TimeStamp next_sample_schedule = TimeStamp::Now();
82
+ bool done = false;
83
+ while (!done) {
84
+ TimeStamp sample_complete = TimeStamp::Now();
85
+
86
+ run_iteration();
87
+
88
+ next_sample_schedule += interval;
89
+
90
+ if (next_sample_schedule < sample_complete) {
91
+ next_sample_schedule = sample_complete + interval;
92
+ }
93
+
94
+ pthread_mutex_lock(&running_mutex);
95
+ if (running) {
96
+ #if HAVE_PTHREAD_CONDATTR_SETCLOCK
97
+ struct timespec next_sample_ts = next_sample_schedule.timespec();
98
+ #else
99
+ auto offset = TimeStamp::NowRealtime() - TimeStamp::Now();
100
+ struct timespec next_sample_ts = (next_sample_schedule + offset).timespec();
101
+ #endif
102
+ int ret;
103
+ do {
104
+ ret = pthread_cond_timedwait(&running_cv, &running_mutex, &next_sample_ts);
105
+ } while(running && ret == EINTR);
106
+ }
107
+ done = !running;
108
+ pthread_mutex_unlock(&running_mutex);
109
+ }
110
+ }
111
+
112
+ virtual void run_iteration() = 0;
113
+
114
+ void start() {
115
+ pthread_mutex_lock(&running_mutex);
116
+ if (!running) {
117
+ running = true;
118
+
119
+ int ret = pthread_create(&pthread, NULL, &thread_entrypoint, this);
120
+ if (ret != 0) {
121
+ perror("pthread_create");
122
+ rb_bug("VERNIER: pthread_create failed");
123
+ }
124
+ }
125
+ pthread_mutex_unlock(&running_mutex);
126
+ }
127
+
128
+ void stop() {
129
+ pthread_mutex_lock(&running_mutex);
130
+ bool was_running = running;
131
+ if (running) {
132
+ running = false;
133
+ pthread_cond_broadcast(&running_cv);
134
+ }
135
+ pthread_mutex_unlock(&running_mutex);
136
+ if (was_running)
137
+ pthread_join(pthread, NULL);
138
+ pthread = 0;
139
+ }
140
+ };
141
+
@@ -0,0 +1,72 @@
1
+ #ifndef SIGNAL_SAFE_SEMAPHORE_HH
2
+ #define SIGNAL_SAFE_SEMAPHORE_HH
3
+
4
+ #if defined(__APPLE__)
5
+ /* macOS */
6
+ #include <dispatch/dispatch.h>
7
+ #elif defined(__FreeBSD__)
8
+ /* FreeBSD */
9
+ #include <pthread_np.h>
10
+ #include <semaphore.h>
11
+ #else
12
+ /* Linux */
13
+ #include <semaphore.h>
14
+ #include <sys/syscall.h> /* for SYS_gettid */
15
+ #endif
16
+
17
+ // A basic semaphore built on sem_wait/sem_post
18
+ // post() is guaranteed to be async-signal-safe
19
+ class SignalSafeSemaphore {
20
+ #ifdef __APPLE__
21
+ dispatch_semaphore_t sem;
22
+ #else
23
+ sem_t sem;
24
+ #endif
25
+
26
+ public:
27
+
28
+ SignalSafeSemaphore(unsigned int value = 0) {
29
+ #ifdef __APPLE__
30
+ sem = dispatch_semaphore_create(value);
31
+ #else
32
+ sem_init(&sem, 0, value);
33
+ #endif
34
+ };
35
+
36
+ ~SignalSafeSemaphore() {
37
+ #ifdef __APPLE__
38
+ dispatch_release(sem);
39
+ #else
40
+ sem_destroy(&sem);
41
+ #endif
42
+ };
43
+
44
+ void wait() {
45
+ #ifdef __APPLE__
46
+ dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
47
+ #else
48
+ // Use sem_timedwait so that we get a crash instead of a deadlock for
49
+ // easier debugging
50
+ struct timespec ts = (TimeStamp::NowRealtime() + TimeStamp::from_seconds(5)).timespec();
51
+
52
+ int ret;
53
+ do {
54
+ ret = sem_timedwait(&sem, &ts);
55
+ } while (ret && errno == EINTR);
56
+ if (ret != 0) {
57
+ rb_bug("VERNIER: sem_timedwait waited over 5 seconds");
58
+ }
59
+ assert(ret == 0);
60
+ #endif
61
+ }
62
+
63
+ void post() {
64
+ #ifdef __APPLE__
65
+ dispatch_semaphore_signal(sem);
66
+ #else
67
+ sem_post(&sem);
68
+ #endif
69
+ }
70
+ };
71
+
72
+ #endif
@@ -0,0 +1,138 @@
1
+ #ifndef TIMESTAMP_HH
2
+ #define TIMESTAMP_HH
3
+
4
+ #include <iostream>
5
+ #include <stdint.h>
6
+ #include <sys/time.h>
7
+ #include <unistd.h>
8
+
9
+ class TimeStamp {
10
+ static const uint64_t nanoseconds_per_second = 1000000000;
11
+ uint64_t value_ns;
12
+
13
+ TimeStamp(uint64_t value_ns) : value_ns(value_ns) {}
14
+
15
+ public:
16
+ TimeStamp() : value_ns(0) {}
17
+
18
+ static TimeStamp Now() {
19
+ struct timespec ts;
20
+ clock_gettime(CLOCK_MONOTONIC, &ts);
21
+ return TimeStamp(ts.tv_sec * nanoseconds_per_second + ts.tv_nsec);
22
+ }
23
+
24
+ static TimeStamp NowRealtime() {
25
+ struct timespec ts;
26
+ clock_gettime(CLOCK_REALTIME, &ts);
27
+ return TimeStamp(ts.tv_sec * nanoseconds_per_second + ts.tv_nsec);
28
+ }
29
+
30
+ static TimeStamp Zero() {
31
+ return TimeStamp(0);
32
+ }
33
+
34
+ // SleepUntil a specified timestamp
35
+ // Highly accurate manual sleep time
36
+ static void SleepUntil(const TimeStamp &target_time) {
37
+ if (target_time.zero()) return;
38
+ struct timespec ts = target_time.timespec();
39
+
40
+ int res;
41
+ do {
42
+ // do nothing until it's time :)
43
+ sleep(0);
44
+ } while (target_time > TimeStamp::Now());
45
+ }
46
+
47
+ static TimeStamp from_seconds(uint64_t s) {
48
+ return TimeStamp::from_milliseconds(s * 1000);
49
+ }
50
+
51
+ static TimeStamp from_milliseconds(uint64_t ms) {
52
+ return TimeStamp::from_microseconds(ms * 1000);
53
+ }
54
+
55
+ static TimeStamp from_microseconds(uint64_t us) {
56
+ return TimeStamp::from_nanoseconds(us * 1000);
57
+ }
58
+
59
+ static TimeStamp from_nanoseconds(uint64_t ns) {
60
+ return TimeStamp(ns);
61
+ }
62
+
63
+ TimeStamp operator-(const TimeStamp &other) const {
64
+ TimeStamp result = *this;
65
+ return result -= other;
66
+ }
67
+
68
+ TimeStamp &operator-=(const TimeStamp &other) {
69
+ if (value_ns > other.value_ns) {
70
+ value_ns = value_ns - other.value_ns;
71
+ } else {
72
+ // underflow
73
+ value_ns = 0;
74
+ }
75
+ return *this;
76
+ }
77
+
78
+ TimeStamp operator+(const TimeStamp &other) const {
79
+ TimeStamp result = *this;
80
+ return result += other;
81
+ }
82
+
83
+ TimeStamp &operator+=(const TimeStamp &other) {
84
+ uint64_t new_value = value_ns + other.value_ns;
85
+ value_ns = new_value;
86
+ return *this;
87
+ }
88
+
89
+ bool operator<(const TimeStamp &other) const {
90
+ return value_ns < other.value_ns;
91
+ }
92
+
93
+ bool operator<=(const TimeStamp &other) const {
94
+ return value_ns <= other.value_ns;
95
+ }
96
+
97
+ bool operator>(const TimeStamp &other) const {
98
+ return value_ns > other.value_ns;
99
+ }
100
+
101
+ bool operator>=(const TimeStamp &other) const {
102
+ return value_ns >= other.value_ns;
103
+ }
104
+
105
+ bool operator==(const TimeStamp &other) const {
106
+ return value_ns == other.value_ns;
107
+ }
108
+
109
+ bool operator!=(const TimeStamp &other) const {
110
+ return value_ns != other.value_ns;
111
+ }
112
+
113
+ uint64_t nanoseconds() const {
114
+ return value_ns;
115
+ }
116
+
117
+ uint64_t microseconds() const {
118
+ return value_ns / 1000;
119
+ }
120
+
121
+ bool zero() const {
122
+ return value_ns == 0;
123
+ }
124
+
125
+ struct timespec timespec() const {
126
+ struct timespec ts;
127
+ ts.tv_sec = nanoseconds() / nanoseconds_per_second;
128
+ ts.tv_nsec = (nanoseconds() % nanoseconds_per_second);
129
+ return ts;
130
+ }
131
+ };
132
+
133
+ inline std::ostream& operator<<(std::ostream& os, const TimeStamp& info) {
134
+ os << info.nanoseconds() << "ns";
135
+ return os;
136
+ }
137
+
138
+ #endif