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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 415713b61297b0808196eb07372db20deda2723a58c449ad2b9b9f5692a619c2
4
- data.tar.gz: 7f0ffe0e2cc8f5472a5ddc4239bf5fb2eceb6d1a81a14088a00fc5b4546052b2
3
+ metadata.gz: 0b594dd96d438936c08955b149f5a0a6d9abb4a044cbbaddd8ac1befe80bece4
4
+ data.tar.gz: 806439090aac36803dd00a6823aa87ab4896d8300e546bee00a225a256af8f84
5
5
  SHA512:
6
- metadata.gz: b92fba315640ddfdb3a3edded95fdf52886e0d546c32d8e985b1c8653fd598382eded803094271e7af3509a33e647c89ed36bf364bbfcd642e3317a7969eef75
7
- data.tar.gz: 83521f8200beea897272f42b622dff0f54e335364ed9452aad3661ce2ed5640f2a551f76e92b222570e07aa06f266e3919a1e676c4865d1406c59422940e5d76
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.2.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
- Exposes a monotonically increasing GC total_time metric.
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
- require 'gctime'
25
- >> GCTime.total_time
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
- >> GCTime.total_time
32
- => 127.42100000000012 # ms
33
- >> GCTime.disable
34
- => nil
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
- `GCTime.total_time` returns a `Float` representing the total number of milliseconds spent in GC.
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
- ## Incompatibilities
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: :test
21
+ task default: %i(compile test)
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mkmf"
4
+
5
+ if GC.respond_to?(:total_time)
6
+ File.write("Makefile", dummy_makefile($srcdir).join(""))
7
+ else
8
+ $CFLAGS << ' -O3 '
9
+ $CFLAGS << ' -std=c99'
10
+
11
+ create_makefile("gctime/gctime")
12
+ end
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GCTime
4
- VERSION = "0.2.0"
4
+ VERSION = "1.0.0"
5
5
  end
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
- @mutex = Mutex.new
7
- @total_time = 0.0
8
-
9
- begin
10
- GC.stat(:time) # Implemented on JRuby and TruffleRuby
11
- class << self
12
- def enable
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 disable
16
- end
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
- GC.stat(:time).to_f
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
- def disable
30
- GC::Profiler.disable
31
- end
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
- def total_time
34
- if GC::Profiler.total_time > 0.0
35
- @mutex.synchronize do
36
- @total_time += GC::Profiler.total_time * 1_000.0
37
- GC::Profiler.clear
38
- end
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.2.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-08-09 00:00:00.000000000 Z
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: