gctime 0.2.0 → 1.0.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 +4 -4
- data/Gemfile.lock +4 -1
- data/README.md +13 -28
- data/Rakefile +10 -1
- data/ext/gctime/extconf.rb +12 -0
- data/ext/gctime/gctime.c +91 -0
- data/gctime.gemspec +4 -0
- data/lib/gctime/gctime.bundle +0 -0
- data/lib/gctime/version.rb +1 -1
- data/lib/gctime.rb +38 -29
- metadata +22 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0b594dd96d438936c08955b149f5a0a6d9abb4a044cbbaddd8ac1befe80bece4
|
4
|
+
data.tar.gz: 806439090aac36803dd00a6823aa87ab4896d8300e546bee00a225a256af8f84
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fb9114576a8739d06b8eac360a44419db09555a9fa2f7f59c2b783f54390893b1088a4f4aaa126501a5dfebf230e00f8cc16860294956ffbd69b3c260a83bc09
|
7
|
+
data.tar.gz: 2bc82c46d079c05098fe9b51c0bf19a834401259d4dece2bab58c265a32cade1853bcac4c66d731cbdf76bfb6eb2c25826b6d711ce1a313930e25646344e9abd
|
data/Gemfile.lock
CHANGED
@@ -1,13 +1,15 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
gctime (0.
|
4
|
+
gctime (1.0.0)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
8
8
|
specs:
|
9
9
|
minitest (5.14.4)
|
10
10
|
rake (13.0.6)
|
11
|
+
rake-compiler (1.1.1)
|
12
|
+
rake
|
11
13
|
|
12
14
|
PLATFORMS
|
13
15
|
ruby
|
@@ -17,6 +19,7 @@ DEPENDENCIES
|
|
17
19
|
gctime!
|
18
20
|
minitest (~> 5.0)
|
19
21
|
rake (~> 13.0)
|
22
|
+
rake-compiler (~> 1.0)
|
20
23
|
|
21
24
|
BUNDLED WITH
|
22
25
|
2.2.25
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# GCTime
|
2
2
|
|
3
|
-
|
3
|
+
Backport Ruby 3.1's `GC.stat(:time)` and `GC.total_time`. This gem is a noop on Ruby 3.1+.
|
4
4
|
|
5
5
|
## Installation
|
6
6
|
|
@@ -21,38 +21,23 @@ Or install it yourself as:
|
|
21
21
|
## Usage
|
22
22
|
|
23
23
|
```ruby
|
24
|
-
|
25
|
-
|
26
|
-
=> 0.0
|
27
|
-
>> GCTime.enable
|
28
|
-
=> nil
|
24
|
+
>> GC.measure_total_time
|
25
|
+
=> true
|
29
26
|
>> 4.times { GC.start }
|
30
27
|
=> 4
|
31
|
-
>>
|
32
|
-
=>
|
33
|
-
>>
|
34
|
-
=>
|
28
|
+
>> GC.stat(:time)
|
29
|
+
=> 123 # milliseconds (returns a Float on JRuby and TruffleRuby)
|
30
|
+
>> GC.stat[:time]
|
31
|
+
=> 123 # milliseconds (returns a Float on JRuby and TruffleRuby)
|
32
|
+
>> GC.total_time
|
33
|
+
=> 123909999 # nanoseconds
|
34
|
+
>> GC.measure_total_time = false # Disable instrumentation
|
35
|
+
=> false
|
35
36
|
```
|
36
37
|
|
37
|
-
`
|
38
|
+
`GC.stat(:time)` and `GC.stat[:time]` returns the total number of milliseconds spent in GC as Integer on MRI and as Float on JRuby and TruffleRuby.
|
38
39
|
|
39
|
-
|
40
|
-
|
41
|
-
`CGTime` relies on the built-in [`GC::Profiler`](https://ruby-doc.org/core-3.0.0/GC/Profiler.html) which is a stateful datastructure. Which mean any other gem or application code using it
|
42
|
-
is likely incompatoble with `GCTime` and will likely lead to incorrect timings.
|
43
|
-
|
44
|
-
Known incompatible gems are:
|
45
|
-
|
46
|
-
- [`newrelic_rpm`](https://github.com/newrelic/newrelic-ruby-agent/blob/4baffe79b87e6ec725dfae9f5e76113a1f1d01ba/lib/new_relic/agent/vm/monotonic_gc_profiler.rb#L22-L38)
|
47
|
-
|
48
|
-
If you suspect that one of your gem might be using `GC::Profiler.clear`, you can check with `grep -R 'GC::Profiler.clear' $(bundle show --paths)`.
|
49
|
-
|
50
|
-
## Memory Leak
|
51
|
-
|
52
|
-
It is important to note that the underlying `GC::Profiler` keeps internal records of all GC triggers. Every time `GCTime.total_time` is called the datastructure is reset,
|
53
|
-
but if it isn't called on a regular basis, it might lead to a memory leak.
|
54
|
-
|
55
|
-
Make sure to either call it on a regular basis, or to disable it when it's no longer needed.
|
40
|
+
`GC.total_time` returns the total number of nanoseconds spent in GC as Integer.
|
56
41
|
|
57
42
|
## Development
|
58
43
|
|
data/Rakefile
CHANGED
@@ -1,12 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "rake/extensiontask"
|
3
4
|
require "bundler/gem_tasks"
|
4
5
|
require "rake/testtask"
|
5
6
|
|
7
|
+
gemspec = Gem::Specification.load("gctime.gemspec")
|
8
|
+
Rake::ExtensionTask.new do |ext|
|
9
|
+
ext.name = 'gctime'
|
10
|
+
ext.ext_dir = 'ext/gctime'
|
11
|
+
ext.lib_dir = 'lib/gctime'
|
12
|
+
ext.gem_spec = gemspec
|
13
|
+
end
|
14
|
+
|
6
15
|
Rake::TestTask.new(:test) do |t|
|
7
16
|
t.libs << "test"
|
8
17
|
t.libs << "lib"
|
9
18
|
t.test_files = FileList["test/**/*_test.rb"]
|
10
19
|
end
|
11
20
|
|
12
|
-
task default:
|
21
|
+
task default: %i(compile test)
|
data/ext/gctime/gctime.c
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include <ruby/debug.h>
|
3
|
+
#include <stdbool.h>
|
4
|
+
#include <errno.h>
|
5
|
+
#include <time.h>
|
6
|
+
#include <stdlib.h>
|
7
|
+
|
8
|
+
static uint64_t total_time_ns = 0;
|
9
|
+
static VALUE tracepoint = Qnil;
|
10
|
+
static struct timespec gc_entered_at;
|
11
|
+
|
12
|
+
inline void
|
13
|
+
gctime_get(struct timespec *time)
|
14
|
+
{
|
15
|
+
if (clock_gettime(CLOCK_MONOTONIC, time) == -1) {
|
16
|
+
rb_sys_fail("clock_gettime");
|
17
|
+
}
|
18
|
+
}
|
19
|
+
|
20
|
+
static void
|
21
|
+
gctime_hook(VALUE tpval, void *data)
|
22
|
+
{
|
23
|
+
switch (rb_tracearg_event_flag(rb_tracearg_from_tracepoint(tpval))) {
|
24
|
+
case RUBY_INTERNAL_EVENT_GC_ENTER: {
|
25
|
+
gctime_get(&gc_entered_at);
|
26
|
+
}
|
27
|
+
break;
|
28
|
+
|
29
|
+
case RUBY_INTERNAL_EVENT_GC_EXIT: {
|
30
|
+
struct timespec gc_exited_at;
|
31
|
+
gctime_get(&gc_exited_at);
|
32
|
+
total_time_ns += (gc_exited_at.tv_nsec - gc_entered_at.tv_nsec);
|
33
|
+
total_time_ns += (gc_exited_at.tv_sec - gc_entered_at.tv_sec) * 1000000000;
|
34
|
+
}
|
35
|
+
break;
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
static VALUE
|
40
|
+
gctime_rb_gc_total_time(VALUE klass)
|
41
|
+
{
|
42
|
+
return ULL2NUM(total_time_ns);
|
43
|
+
}
|
44
|
+
|
45
|
+
static VALUE
|
46
|
+
gctime_rb_time_ms(VALUE klass)
|
47
|
+
{
|
48
|
+
return ULL2NUM(total_time_ns / 1000000);
|
49
|
+
}
|
50
|
+
|
51
|
+
static VALUE
|
52
|
+
gctime_rb_measure_total_time(VALUE klass)
|
53
|
+
{
|
54
|
+
if (NIL_P(tracepoint)) return Qfalse;
|
55
|
+
return rb_tracepoint_enabled_p(tracepoint);
|
56
|
+
}
|
57
|
+
|
58
|
+
static VALUE
|
59
|
+
gctime_rb_measure_total_time_set(VALUE klass, VALUE enabled)
|
60
|
+
{
|
61
|
+
if (RTEST(enabled)) {
|
62
|
+
if (NIL_P(tracepoint)) {
|
63
|
+
tracepoint = rb_tracepoint_new(
|
64
|
+
0,
|
65
|
+
RUBY_INTERNAL_EVENT_GC_ENTER | RUBY_INTERNAL_EVENT_GC_EXIT,
|
66
|
+
gctime_hook,
|
67
|
+
(void *) NULL
|
68
|
+
);
|
69
|
+
}
|
70
|
+
rb_tracepoint_enable(tracepoint);
|
71
|
+
} else {
|
72
|
+
if (!NIL_P(tracepoint)) {
|
73
|
+
rb_tracepoint_disable(tracepoint);
|
74
|
+
}
|
75
|
+
}
|
76
|
+
return enabled;
|
77
|
+
}
|
78
|
+
|
79
|
+
void
|
80
|
+
Init_gctime()
|
81
|
+
{
|
82
|
+
rb_global_variable(&tracepoint);
|
83
|
+
|
84
|
+
VALUE mGC = rb_const_get(rb_cObject, rb_intern("GC"));
|
85
|
+
rb_define_module_function(mGC, "total_time", gctime_rb_gc_total_time, 0);
|
86
|
+
rb_define_module_function(mGC, "measure_total_time", gctime_rb_measure_total_time, 0);
|
87
|
+
rb_define_module_function(mGC, "measure_total_time=", gctime_rb_measure_total_time_set, 1);
|
88
|
+
|
89
|
+
VALUE mGCTime = rb_const_get(rb_cObject, rb_intern("GCTime"));
|
90
|
+
rb_define_module_function(mGCTime, "_time_ms", gctime_rb_time_ms, 0);
|
91
|
+
}
|
data/gctime.gemspec
CHANGED
@@ -24,4 +24,8 @@ Gem::Specification.new do |spec|
|
|
24
24
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
|
25
25
|
end
|
26
26
|
spec.require_paths = ["lib"]
|
27
|
+
|
28
|
+
spec.extensions = ['ext/gctime/extconf.rb']
|
29
|
+
|
30
|
+
spec.add_development_dependency 'rake-compiler', '~> 1.0'
|
27
31
|
end
|
Binary file
|
data/lib/gctime/version.rb
CHANGED
data/lib/gctime.rb
CHANGED
@@ -1,44 +1,53 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
3
2
|
require_relative "gctime/version"
|
4
3
|
|
5
4
|
module GCTime
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
end
|
5
|
+
if GC.respond_to?(:total_time)
|
6
|
+
# We're on Ruby 3.1+, nothing to backport.
|
7
|
+
elsif GC.stat.key?(:time)
|
8
|
+
# We're likely on JRuby or TruffleRuby
|
9
|
+
module Backport
|
10
|
+
def measure_total_time
|
11
|
+
true
|
12
|
+
end unless GC.respond_to?(:measure_total_time)
|
14
13
|
|
15
|
-
def
|
16
|
-
|
14
|
+
def measure_total_time=(enabled)
|
15
|
+
# noop
|
16
|
+
end unless GC.respond_to?(:measure_total_time=)
|
17
17
|
|
18
18
|
def total_time
|
19
|
-
|
20
|
-
end
|
19
|
+
(stat(:time) * 1_000_000.0).to_i # nanoseconds integer
|
20
|
+
end unless GC.respond_to?(:total_time)
|
21
21
|
end
|
22
|
-
rescue ArgumentError
|
23
|
-
# MRI Implementation
|
24
|
-
class << self
|
25
|
-
def enable
|
26
|
-
GC::Profiler.enable
|
27
|
-
end
|
28
22
|
|
29
|
-
|
30
|
-
|
31
|
-
|
23
|
+
unless Backport.instance_methods(:false).empty?
|
24
|
+
GC.singleton_class.prepend(Backport)
|
25
|
+
end
|
26
|
+
else
|
27
|
+
# We're on MRI 3.0 or older.
|
28
|
+
require "gctime/gctime"
|
29
|
+
GC.measure_total_time = true
|
32
30
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
31
|
+
module Backport
|
32
|
+
UNDEF = BasicObject.new
|
33
|
+
|
34
|
+
def stat(key = UNDEF)
|
35
|
+
if UNDEF.equal?(key)
|
36
|
+
stats = super()
|
37
|
+
stats[:time] = ::GCTime._time_ms # milliseconds integer
|
38
|
+
stats
|
39
|
+
elsif key == :time
|
40
|
+
::GCTime._time_ms # milliseconds integer
|
41
|
+
elsif Hash === key
|
42
|
+
stats = super
|
43
|
+
stats[:time] = ::GCTime._time_ms # milliseconds integer
|
44
|
+
stats
|
45
|
+
else
|
46
|
+
super
|
39
47
|
end
|
40
|
-
@total_time
|
41
48
|
end
|
42
49
|
end
|
50
|
+
|
51
|
+
GC.singleton_class.prepend(Backport)
|
43
52
|
end
|
44
53
|
end
|
metadata
CHANGED
@@ -1,20 +1,35 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gctime
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jean Boussier
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
12
|
-
dependencies:
|
11
|
+
date: 2021-11-19 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: '1.0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.0'
|
13
27
|
description:
|
14
28
|
email:
|
15
29
|
- jean.boussier@gmail.com
|
16
30
|
executables: []
|
17
|
-
extensions:
|
31
|
+
extensions:
|
32
|
+
- ext/gctime/extconf.rb
|
18
33
|
extra_rdoc_files: []
|
19
34
|
files:
|
20
35
|
- ".github/workflows/ci.yml"
|
@@ -26,8 +41,11 @@ files:
|
|
26
41
|
- Rakefile
|
27
42
|
- bin/console
|
28
43
|
- bin/setup
|
44
|
+
- ext/gctime/extconf.rb
|
45
|
+
- ext/gctime/gctime.c
|
29
46
|
- gctime.gemspec
|
30
47
|
- lib/gctime.rb
|
48
|
+
- lib/gctime/gctime.bundle
|
31
49
|
- lib/gctime/version.rb
|
32
50
|
homepage: https://github.com/Shopify/gctime
|
33
51
|
licenses:
|