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 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: