gctime 0.2.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|