gvltools 0.1.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 +7 -0
- data/.rubocop.yml +41 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +47 -0
- data/LICENSE.txt +21 -0
- data/README.md +138 -0
- data/Rakefile +39 -0
- data/ext/gvltools/extconf.rb +9 -0
- data/ext/gvltools/instrumentation.c +175 -0
- data/gvltools.gemspec +35 -0
- data/lib/gvltools/version.rb +5 -0
- data/lib/gvltools.rb +114 -0
- metadata +74 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 42d6e3f8ea2cc420e17a413a62fba51ccb6c2b90dac11138186e9f4d203bed51
|
4
|
+
data.tar.gz: 97576f44774bb991699152d36140714b792e17ced8a7ea48d1210f0c8d94b1df
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ae23c00ab1f94c2af17753f5ba847690ebea31cb0d3f23134080ad34a475686ac0d9872fa56025a1fab8135c5ee57c58e19675bbc08160c9444a8d30edb847db
|
7
|
+
data.tar.gz: 54e1694fd5d16935c98a6b838fdf188427cf645f0fe00ac08a7323609c70f01721d9836b80c6bdf2096ee7341d4d39a44f713b20a770470072c72c48887564c3
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 3.0
|
3
|
+
NewCops: enable
|
4
|
+
SuggestExtensions: false
|
5
|
+
|
6
|
+
Lint/DuplicateMethods:
|
7
|
+
Exclude:
|
8
|
+
- lib/gvltools.rb # we need the self alias trick to avoid warnings
|
9
|
+
|
10
|
+
Style/Alias:
|
11
|
+
EnforcedStyle: prefer_alias_method
|
12
|
+
|
13
|
+
Style/StringLiterals:
|
14
|
+
Enabled: true
|
15
|
+
EnforcedStyle: double_quotes
|
16
|
+
|
17
|
+
Style/StringLiteralsInInterpolation:
|
18
|
+
Enabled: true
|
19
|
+
EnforcedStyle: double_quotes
|
20
|
+
|
21
|
+
Style/GlobalVars:
|
22
|
+
Exclude:
|
23
|
+
- ext/**/extconf.rb
|
24
|
+
|
25
|
+
Style/Documentation:
|
26
|
+
Enabled: false
|
27
|
+
|
28
|
+
Layout/LineLength:
|
29
|
+
Max: 120
|
30
|
+
|
31
|
+
Gemspec/RequiredRubyVersion:
|
32
|
+
Enabled: false
|
33
|
+
|
34
|
+
Gemspec/RequireMFA:
|
35
|
+
Enabled: false
|
36
|
+
|
37
|
+
Metrics/AbcSize:
|
38
|
+
Enabled: false
|
39
|
+
|
40
|
+
Metrics/MethodLength:
|
41
|
+
Enabled: false
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
gvltools (0.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
ast (2.4.2)
|
10
|
+
minitest (5.15.0)
|
11
|
+
parallel (1.22.1)
|
12
|
+
parser (3.1.2.0)
|
13
|
+
ast (~> 2.4.1)
|
14
|
+
rainbow (3.1.1)
|
15
|
+
rake (13.0.6)
|
16
|
+
rake-compiler (1.2.0)
|
17
|
+
rake
|
18
|
+
regexp_parser (2.5.0)
|
19
|
+
rexml (3.2.5)
|
20
|
+
rubocop (1.30.1)
|
21
|
+
parallel (~> 1.10)
|
22
|
+
parser (>= 3.1.0.0)
|
23
|
+
rainbow (>= 2.2.2, < 4.0)
|
24
|
+
regexp_parser (>= 1.8, < 3.0)
|
25
|
+
rexml (>= 3.2.5, < 4.0)
|
26
|
+
rubocop-ast (>= 1.18.0, < 2.0)
|
27
|
+
ruby-progressbar (~> 1.7)
|
28
|
+
unicode-display_width (>= 1.4.0, < 3.0)
|
29
|
+
rubocop-ast (1.18.0)
|
30
|
+
parser (>= 3.1.1.0)
|
31
|
+
ruby-progressbar (1.11.0)
|
32
|
+
unicode-display_width (2.1.0)
|
33
|
+
|
34
|
+
PLATFORMS
|
35
|
+
arm64-darwin-21
|
36
|
+
arm64-darwin-22
|
37
|
+
x86_64-linux
|
38
|
+
|
39
|
+
DEPENDENCIES
|
40
|
+
gvltools!
|
41
|
+
minitest (~> 5.0)
|
42
|
+
rake (~> 13.0)
|
43
|
+
rake-compiler
|
44
|
+
rubocop (~> 1.21)
|
45
|
+
|
46
|
+
BUNDLED WITH
|
47
|
+
2.3.14
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2022 Shopify
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
# GVLTools
|
2
|
+
|
3
|
+
Set of GVL instrumentation tools
|
4
|
+
|
5
|
+
## Requirements
|
6
|
+
|
7
|
+
GVLTools uses the GVL instrumentation API added in Ruby 3.2.0.
|
8
|
+
|
9
|
+
To make it easier to use, on older Rubies the gem will install and expose the same methods, but they won't have any effect and all metrics will report `0`.
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Install the gem and add to the application's Gemfile by executing:
|
14
|
+
|
15
|
+
$ bundle add gvltools
|
16
|
+
|
17
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
18
|
+
|
19
|
+
$ gem install gvltools
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
GVLTools expose metric modules with a common interface, for instance `GVLTools::LocalTimer`:
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
GVLTools::LocalTimer.enable
|
27
|
+
GVLTools::LocalTimer.disable
|
28
|
+
GVLTools::LocalTimer.enabled? # => true / false
|
29
|
+
GVLTools::LocalTimer.reset
|
30
|
+
```
|
31
|
+
|
32
|
+
By default, all metrics are disabled.
|
33
|
+
|
34
|
+
Note that using the GVL instrumentation API adds some overhead each time Ruby has to switch threads.
|
35
|
+
In production it is recommended to only enable it on a subset of processes or for small periods of time as to not impact
|
36
|
+
the average latency too much.
|
37
|
+
|
38
|
+
The exact overhead is not yet known, early testing showed a `1-5%` slowdown on a fully saturated multi-threaded app.
|
39
|
+
|
40
|
+
### LocalTimer
|
41
|
+
|
42
|
+
`LocalTimer` records the overall time spent waiting on the GVL by the current thread.
|
43
|
+
It is particularly useful to detect wether an application use too many threads, and how much latency is impacted.
|
44
|
+
For instance as a Rack middleware:
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
class GVLInstrumentationMiddleware
|
48
|
+
def initialize(app)
|
49
|
+
@app = app
|
50
|
+
end
|
51
|
+
|
52
|
+
def call(env)
|
53
|
+
before = GVLTools::LocalTimer.monotonic_time
|
54
|
+
response = @app.call(env)
|
55
|
+
diff = GVLTools::LocalTimer.monotonic_time - before
|
56
|
+
puts "Waited #{diff} nanoseconds on the GVL"
|
57
|
+
response
|
58
|
+
end
|
59
|
+
end
|
60
|
+
```
|
61
|
+
|
62
|
+
### GlobalTimer
|
63
|
+
|
64
|
+
`GlobalTimer` records the overall time spent waiting on the GVL by all threads combined.
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
def fibonacci(number)
|
68
|
+
number <= 1 ? number : fibonacci(number - 1) + fibonacci(number - 2)
|
69
|
+
end
|
70
|
+
|
71
|
+
before = GVLTools::GlobalTimer.monotonic_time
|
72
|
+
threads = 5.times.map do
|
73
|
+
Thread.new do
|
74
|
+
5.times do
|
75
|
+
fibonacci(30)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
threads.each(&:join)
|
80
|
+
diff = GVLTools::GlobalTimer.monotonic_time - before
|
81
|
+
puts "Waited #{(diff / 1_000_000.0).round(1)}ms on the GVL"
|
82
|
+
```
|
83
|
+
|
84
|
+
outputs:
|
85
|
+
|
86
|
+
```
|
87
|
+
Waited 4122.8ms on the GVL
|
88
|
+
```
|
89
|
+
|
90
|
+
### WaitingThreads
|
91
|
+
|
92
|
+
`WaitingThreads` records how many threads are currently waiting on the GVL to start executing code.
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
def fibonacci(number)
|
96
|
+
number <= 1 ? number : fibonacci(number - 1) + fibonacci(number - 2)
|
97
|
+
end
|
98
|
+
|
99
|
+
Thread.new do
|
100
|
+
10.times do
|
101
|
+
sleep 0.001
|
102
|
+
p GVLTools::WaitingThreads.count
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
threads = 5.times.map do
|
107
|
+
Thread.new do
|
108
|
+
5.times do
|
109
|
+
fibonacci(30)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
threads.each(&:join)
|
114
|
+
```
|
115
|
+
|
116
|
+
Outputs:
|
117
|
+
|
118
|
+
```
|
119
|
+
5
|
120
|
+
5
|
121
|
+
4
|
122
|
+
```
|
123
|
+
|
124
|
+
It's less precise than timers, but the instrumentation overhead is lower.
|
125
|
+
|
126
|
+
## Development
|
127
|
+
|
128
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
129
|
+
|
130
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
131
|
+
|
132
|
+
## Contributing
|
133
|
+
|
134
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/Shopify/gvltools.
|
135
|
+
|
136
|
+
## License
|
137
|
+
|
138
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "rake/testtask"
|
5
|
+
|
6
|
+
has_ext = RUBY_ENGINE == "ruby" && RUBY_VERSION >= "3.2"
|
7
|
+
|
8
|
+
Rake::TestTask.new(:test) do |t|
|
9
|
+
t.libs << "test"
|
10
|
+
t.libs << "lib"
|
11
|
+
t.test_files = has_ext ? FileList["test/**/test_*.rb"] : FileList["test/fallback/**/test_*.rb"]
|
12
|
+
t.warning = true
|
13
|
+
t.verbose = true
|
14
|
+
end
|
15
|
+
|
16
|
+
if has_ext
|
17
|
+
require "rake/extensiontask"
|
18
|
+
|
19
|
+
Rake::ExtensionTask.new("instrumentation") do |ext|
|
20
|
+
ext.ext_dir = "ext/gvltools"
|
21
|
+
ext.lib_dir = "lib/gvltools"
|
22
|
+
end
|
23
|
+
else
|
24
|
+
task :compile do
|
25
|
+
# noop
|
26
|
+
end
|
27
|
+
|
28
|
+
task :clean do
|
29
|
+
# noop
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
Rake::Task["test"].enhance(["compile"])
|
34
|
+
|
35
|
+
require "rubocop/rake_task"
|
36
|
+
|
37
|
+
RuboCop::RakeTask.new
|
38
|
+
|
39
|
+
task default: %i[test rubocop]
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "mkmf"
|
4
|
+
if RUBY_ENGINE == "ruby" && have_func("rb_internal_thread_add_event_hook", ["ruby/thread.h"])
|
5
|
+
$CFLAGS << " -O3 -Wall "
|
6
|
+
create_makefile("gvltools/instrumentation")
|
7
|
+
else
|
8
|
+
File.write("Makefile", dummy_makefile($srcdir).join)
|
9
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
#include <time.h>
|
2
|
+
#include <ruby.h>
|
3
|
+
#include <ruby/thread.h>
|
4
|
+
#include <ruby/atomic.h>
|
5
|
+
|
6
|
+
// Metrics
|
7
|
+
static rb_internal_thread_event_hook_t *gt_hook = NULL;
|
8
|
+
|
9
|
+
static unsigned int enabled_mask = 0;
|
10
|
+
#define TIMER_GLOBAL 1 << 0
|
11
|
+
#define TIMER_LOCAL 1 << 1
|
12
|
+
#define WAITING_THREADS 1 << 2
|
13
|
+
|
14
|
+
#define ENABLED(metric) (enabled_mask & (metric))
|
15
|
+
|
16
|
+
#if __STDC_VERSION__ >= 201112
|
17
|
+
#define THREAD_LOCAL_SPECIFIER _Thread_local
|
18
|
+
#elif defined(__GNUC__) && !defined(RB_THREAD_LOCAL_SPECIFIER_IS_UNSUPPORTED)
|
19
|
+
/* note that ICC (linux) and Clang are covered by __GNUC__ */
|
20
|
+
#define THREAD_LOCAL_SPECIFIER __thread
|
21
|
+
#endif
|
22
|
+
|
23
|
+
// Common
|
24
|
+
#define SECONDS_TO_NANOSECONDS (1000 * 1000 * 1000)
|
25
|
+
|
26
|
+
static THREAD_LOCAL_SPECIFIER bool was_ready = 0;
|
27
|
+
|
28
|
+
static inline void gt_gettime(struct timespec *time) {
|
29
|
+
if (clock_gettime(CLOCK_MONOTONIC, time) == -1) {
|
30
|
+
rb_sys_fail("clock_gettime");
|
31
|
+
}
|
32
|
+
}
|
33
|
+
|
34
|
+
static inline rb_atomic_t gt_time_diff_ns(struct timespec before, struct timespec after) {
|
35
|
+
rb_atomic_t total = 0;
|
36
|
+
total += (after.tv_nsec - before.tv_nsec);
|
37
|
+
total += (after.tv_sec - before.tv_sec) * SECONDS_TO_NANOSECONDS;
|
38
|
+
return total;
|
39
|
+
}
|
40
|
+
|
41
|
+
static VALUE gt_metric_enabled_p(VALUE module, VALUE metric) {
|
42
|
+
return ENABLED(NUM2UINT(metric)) ? Qtrue : Qfalse;
|
43
|
+
}
|
44
|
+
|
45
|
+
static void gt_thread_callback(rb_event_flag_t event, const rb_internal_thread_event_data_t *event_data, void *user_data);
|
46
|
+
static VALUE gt_enable_metric(VALUE module, VALUE metric) {
|
47
|
+
enabled_mask |= NUM2UINT(metric);
|
48
|
+
if (!gt_hook) {
|
49
|
+
gt_hook = rb_internal_thread_add_event_hook(
|
50
|
+
gt_thread_callback,
|
51
|
+
RUBY_INTERNAL_THREAD_EVENT_EXITED | RUBY_INTERNAL_THREAD_EVENT_READY | RUBY_INTERNAL_THREAD_EVENT_RESUMED,
|
52
|
+
NULL
|
53
|
+
);
|
54
|
+
}
|
55
|
+
return Qtrue;
|
56
|
+
}
|
57
|
+
|
58
|
+
static VALUE gt_disable_metric(VALUE module, VALUE metric) {
|
59
|
+
if (ENABLED(NUM2UINT(metric))) {
|
60
|
+
enabled_mask -= NUM2UINT(metric);
|
61
|
+
}
|
62
|
+
if (!enabled_mask && gt_hook) {
|
63
|
+
rb_internal_thread_remove_event_hook(gt_hook);
|
64
|
+
gt_hook = NULL;
|
65
|
+
}
|
66
|
+
return Qtrue;
|
67
|
+
}
|
68
|
+
|
69
|
+
// GVLTools::LocalTimer and GVLTools::GlobalTimer
|
70
|
+
static rb_atomic_t global_timer_total = 0;
|
71
|
+
static THREAD_LOCAL_SPECIFIER uint64_t local_timer_total = 0;
|
72
|
+
static THREAD_LOCAL_SPECIFIER struct timespec timer_ready_at = {0};
|
73
|
+
|
74
|
+
static VALUE global_timer_monotonic_time(VALUE module) {
|
75
|
+
return UINT2NUM(global_timer_total);
|
76
|
+
}
|
77
|
+
|
78
|
+
static VALUE global_timer_reset(VALUE module) {
|
79
|
+
RUBY_ATOMIC_SET(global_timer_total, 0);
|
80
|
+
return Qtrue;
|
81
|
+
}
|
82
|
+
|
83
|
+
static VALUE local_timer_monotonic_time(VALUE module) {
|
84
|
+
return ULL2NUM(local_timer_total);
|
85
|
+
}
|
86
|
+
|
87
|
+
static VALUE local_timer_reset(VALUE module) {
|
88
|
+
local_timer_total = 0;
|
89
|
+
return Qtrue;
|
90
|
+
}
|
91
|
+
|
92
|
+
// Thread counts
|
93
|
+
static rb_atomic_t waiting_threads_total = 0;
|
94
|
+
|
95
|
+
static VALUE waiting_threads_count(VALUE module) {
|
96
|
+
return UINT2NUM(waiting_threads_total);
|
97
|
+
}
|
98
|
+
|
99
|
+
static VALUE waiting_threads_reset(VALUE module) {
|
100
|
+
RUBY_ATOMIC_SET(waiting_threads_total, 0);
|
101
|
+
return Qtrue;
|
102
|
+
}
|
103
|
+
|
104
|
+
// General callback
|
105
|
+
static void gt_reset_thread_local_state(void) {
|
106
|
+
// MRI can re-use native threads, so we need to reset thread local state,
|
107
|
+
// otherwise it will leak from one Ruby thread from another.
|
108
|
+
was_ready = false;
|
109
|
+
local_timer_total = 0;
|
110
|
+
}
|
111
|
+
|
112
|
+
static void gt_thread_callback(rb_event_flag_t event, const rb_internal_thread_event_data_t *event_data, void *user_data) {
|
113
|
+
switch(event) {
|
114
|
+
case RUBY_INTERNAL_THREAD_EVENT_STARTED:
|
115
|
+
case RUBY_INTERNAL_THREAD_EVENT_EXITED: {
|
116
|
+
gt_reset_thread_local_state();
|
117
|
+
}
|
118
|
+
break;
|
119
|
+
case RUBY_INTERNAL_THREAD_EVENT_READY: {
|
120
|
+
if (!was_ready) was_ready = true;
|
121
|
+
|
122
|
+
if (ENABLED(WAITING_THREADS)) {
|
123
|
+
RUBY_ATOMIC_INC(waiting_threads_total);
|
124
|
+
}
|
125
|
+
|
126
|
+
if (ENABLED(TIMER_GLOBAL | TIMER_LOCAL)) {
|
127
|
+
gt_gettime(&timer_ready_at);
|
128
|
+
}
|
129
|
+
}
|
130
|
+
break;
|
131
|
+
case RUBY_INTERNAL_THREAD_EVENT_RESUMED: {
|
132
|
+
if (!was_ready) break; // In case we registered the hook while some threads were already waiting on the GVL
|
133
|
+
|
134
|
+
if (ENABLED(WAITING_THREADS)) {
|
135
|
+
RUBY_ATOMIC_DEC(waiting_threads_total);
|
136
|
+
}
|
137
|
+
|
138
|
+
if (ENABLED(TIMER_GLOBAL | TIMER_LOCAL)) {
|
139
|
+
struct timespec current_time;
|
140
|
+
gt_gettime(¤t_time);
|
141
|
+
rb_atomic_t diff = gt_time_diff_ns(timer_ready_at, current_time);
|
142
|
+
|
143
|
+
if (ENABLED(TIMER_LOCAL)) {
|
144
|
+
local_timer_total += diff;
|
145
|
+
}
|
146
|
+
|
147
|
+
if (ENABLED(TIMER_GLOBAL)) {
|
148
|
+
RUBY_ATOMIC_ADD(global_timer_total, diff);
|
149
|
+
}
|
150
|
+
}
|
151
|
+
}
|
152
|
+
break;
|
153
|
+
}
|
154
|
+
}
|
155
|
+
|
156
|
+
void Init_instrumentation(void) {
|
157
|
+
VALUE rb_mGVLTools = rb_const_get(rb_cObject, rb_intern("GVLTools"));
|
158
|
+
|
159
|
+
VALUE rb_mNative = rb_const_get(rb_mGVLTools, rb_intern("Native"));
|
160
|
+
rb_define_singleton_method(rb_mNative, "enable_metric", gt_enable_metric, 1);
|
161
|
+
rb_define_singleton_method(rb_mNative, "disable_metric", gt_disable_metric, 1);
|
162
|
+
rb_define_singleton_method(rb_mNative, "metric_enabled?", gt_metric_enabled_p, 1);
|
163
|
+
|
164
|
+
VALUE rb_mGlobalTimer = rb_const_get(rb_mGVLTools, rb_intern("GlobalTimer"));
|
165
|
+
rb_define_singleton_method(rb_mGlobalTimer, "reset", global_timer_reset, 0);
|
166
|
+
rb_define_singleton_method(rb_mGlobalTimer, "monotonic_time", global_timer_monotonic_time, 0);
|
167
|
+
|
168
|
+
VALUE rb_mLocalTimer = rb_const_get(rb_mGVLTools, rb_intern("LocalTimer"));
|
169
|
+
rb_define_singleton_method(rb_mLocalTimer, "reset", local_timer_reset, 0);
|
170
|
+
rb_define_singleton_method(rb_mLocalTimer, "monotonic_time", local_timer_monotonic_time, 0);
|
171
|
+
|
172
|
+
VALUE rb_mWaitingThreads = rb_const_get(rb_mGVLTools, rb_intern("WaitingThreads"));
|
173
|
+
rb_define_singleton_method(rb_mWaitingThreads, "reset", waiting_threads_reset, 0);
|
174
|
+
rb_define_singleton_method(rb_mWaitingThreads, "count", waiting_threads_count, 0);
|
175
|
+
}
|
data/gvltools.gemspec
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/gvltools/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "gvltools"
|
7
|
+
spec.version = GVLTools::VERSION
|
8
|
+
spec.authors = ["Jean Boussier"]
|
9
|
+
spec.email = ["jean.boussier@gmail.com"]
|
10
|
+
|
11
|
+
spec.summary = "Set of GVL instrumentation tools"
|
12
|
+
spec.homepage = "https://github.com/Shopify/gvltools"
|
13
|
+
spec.license = "MIT"
|
14
|
+
spec.required_ruby_version = ">= 2.6.0"
|
15
|
+
|
16
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
17
|
+
|
18
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
19
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
20
|
+
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/master/CHANGELOG.md"
|
21
|
+
|
22
|
+
# Specify which files should be added to the gem when it is released.
|
23
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
24
|
+
spec.files = Dir.chdir(__dir__) do
|
25
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
26
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
27
|
+
end
|
28
|
+
end
|
29
|
+
spec.bindir = "exe"
|
30
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
31
|
+
spec.require_paths = ["lib"]
|
32
|
+
spec.extensions = ["ext/gvltools/extconf.rb"]
|
33
|
+
|
34
|
+
spec.add_development_dependency "rake-compiler"
|
35
|
+
end
|
data/lib/gvltools.rb
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "gvltools/version"
|
4
|
+
|
5
|
+
module GVLTools
|
6
|
+
class Error < StandardError; end
|
7
|
+
|
8
|
+
module Native
|
9
|
+
class << self
|
10
|
+
def enable_metric(_metric)
|
11
|
+
false
|
12
|
+
end
|
13
|
+
alias_method :enable_metric, :enable_metric
|
14
|
+
|
15
|
+
def disable_metric(_metric)
|
16
|
+
false
|
17
|
+
end
|
18
|
+
alias_method :disable_metric, :disable_metric
|
19
|
+
|
20
|
+
def metric_enabled?(_metric)
|
21
|
+
false
|
22
|
+
end
|
23
|
+
alias_method :metric_enabled?, :metric_enabled?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
module AbstractInstrumenter
|
28
|
+
TIMER_GLOBAL = 1 << 0
|
29
|
+
TIMER_LOCAL = 1 << 1
|
30
|
+
WAITING_THREADS = 1 << 2
|
31
|
+
|
32
|
+
def enabled?
|
33
|
+
Native.metric_enabled?(metric)
|
34
|
+
end
|
35
|
+
|
36
|
+
def enable
|
37
|
+
Native.enable_metric(metric)
|
38
|
+
end
|
39
|
+
|
40
|
+
def disable
|
41
|
+
Native.disable_metric(metric)
|
42
|
+
end
|
43
|
+
|
44
|
+
def reset
|
45
|
+
false
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def metric
|
51
|
+
raise NotImplementedError
|
52
|
+
end
|
53
|
+
end
|
54
|
+
private_constant :AbstractInstrumenter
|
55
|
+
|
56
|
+
module GlobalTimer
|
57
|
+
extend AbstractInstrumenter
|
58
|
+
|
59
|
+
class << self
|
60
|
+
def monotonic_time
|
61
|
+
0
|
62
|
+
end
|
63
|
+
alias_method :monotonic_time, :monotonic_time
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def metric
|
68
|
+
TIMER_GLOBAL
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
module LocalTimer
|
74
|
+
extend AbstractInstrumenter
|
75
|
+
|
76
|
+
class << self
|
77
|
+
def monotonic_time
|
78
|
+
0
|
79
|
+
end
|
80
|
+
alias_method :monotonic_time, :monotonic_time
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def metric
|
85
|
+
TIMER_LOCAL
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
module WaitingThreads
|
91
|
+
extend AbstractInstrumenter
|
92
|
+
|
93
|
+
class << self
|
94
|
+
def count
|
95
|
+
0
|
96
|
+
end
|
97
|
+
alias_method :count, :count
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def metric
|
102
|
+
WAITING_THREADS
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
begin
|
108
|
+
require "gvltools/instrumentation"
|
109
|
+
rescue LoadError
|
110
|
+
# No native ext.
|
111
|
+
end
|
112
|
+
|
113
|
+
private_constant :Native
|
114
|
+
end
|
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gvltools
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jean Boussier
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-01-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake-compiler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description:
|
28
|
+
email:
|
29
|
+
- jean.boussier@gmail.com
|
30
|
+
executables: []
|
31
|
+
extensions:
|
32
|
+
- ext/gvltools/extconf.rb
|
33
|
+
extra_rdoc_files: []
|
34
|
+
files:
|
35
|
+
- ".rubocop.yml"
|
36
|
+
- CHANGELOG.md
|
37
|
+
- Gemfile
|
38
|
+
- Gemfile.lock
|
39
|
+
- LICENSE.txt
|
40
|
+
- README.md
|
41
|
+
- Rakefile
|
42
|
+
- ext/gvltools/extconf.rb
|
43
|
+
- ext/gvltools/instrumentation.c
|
44
|
+
- gvltools.gemspec
|
45
|
+
- lib/gvltools.rb
|
46
|
+
- lib/gvltools/version.rb
|
47
|
+
homepage: https://github.com/Shopify/gvltools
|
48
|
+
licenses:
|
49
|
+
- MIT
|
50
|
+
metadata:
|
51
|
+
allowed_push_host: https://rubygems.org
|
52
|
+
homepage_uri: https://github.com/Shopify/gvltools
|
53
|
+
source_code_uri: https://github.com/Shopify/gvltools
|
54
|
+
changelog_uri: https://github.com/Shopify/gvltools/blob/master/CHANGELOG.md
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options: []
|
57
|
+
require_paths:
|
58
|
+
- lib
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: 2.6.0
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
requirements: []
|
70
|
+
rubygems_version: 3.4.1
|
71
|
+
signing_key:
|
72
|
+
specification_version: 4
|
73
|
+
summary: Set of GVL instrumentation tools
|
74
|
+
test_files: []
|