stackprof 0.2.13 → 0.2.14

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: 81201dbe84f93b79242ce9987c763b7edaa54b4f66a7005ff83d22a9b4e25cb4
4
- data.tar.gz: c80ad82b003cb989c83bc5882f4bcdeb6f6db697729315faf6d4507b7f3888fb
3
+ metadata.gz: 50313d95bc2bb3257ea5d4bc6b4c1889f7d6c4c2612b35b7a289de1d8abddaf8
4
+ data.tar.gz: ac748642b2298f1d23a0c950eed379dd6670eaef7ad300bc5ab5341886d22b39
5
5
  SHA512:
6
- metadata.gz: f4fec270902e0b6f21e9d37b8317a2c3614cf4647ad29956b52561bc6c86d7625c4ee3362388c1323c1b74fd2fbb6c99242e75cf0314e20f5be97491e9888122
7
- data.tar.gz: 01a18137afc847cba43882fbc7e22483e350490e655e9536f0dc1cb5275a49c222c4613b9a4f39cb0d3265a6e834560fc87cac4fae77324634bc2f68cde0c03a
6
+ metadata.gz: d790c7c34cc8f12e5e3d12cfc2476e94801e62f79a8ccb747b3d0743b9b8d0bb8ec8fd92757c5696814c028ddeaa4044bbaa0d242fe5796079f741d2fdbef2d6
7
+ data.tar.gz: b3e5d358816564a6a3a72bfd68c2d94f184a27430b9b75765cf733c4c5104e7c4020ce72bfdf6d3698bbf0456ca487314fb094a0faf6eed7040bce10726df017
@@ -7,7 +7,6 @@ language: general
7
7
 
8
8
  env:
9
9
  matrix:
10
- - RVM_RUBY_VERSION=2.1
11
10
  - RVM_RUBY_VERSION=2.2
12
11
  - RVM_RUBY_VERSION=2.3
13
12
  - RVM_RUBY_VERSION=2.4
@@ -1,15 +1,7 @@
1
- # 0.2.13
1
+ # 0.2.14
2
2
 
3
- * Remove /ext from .gitignore
4
- * update gemfile
5
- * Add ruby 2.5 to CI targets
6
- * comment some of the inner workings
7
- * feature: add --json format
8
- * Add test coverage around the string branch in result writing
9
- * Flip conditional to use duck typing
10
- * Allow Pathname objects for Stackprof :out
11
- * Fix a compilation error and a compilation warning
12
- * Add `--alphabetical-flamegraph` for population-based instead of timeline
13
- * Add `--d3-flamegraph` to output html using d3-flame-graph
14
- * Avoid JSON::NestingError when processing deep stacks
15
- * Use docker for CI
3
+ * Relax threshold of missed_samples to <= 25 in GC test
4
+ * Support custom metadata option in stackprof
5
+ * Drop support for Ruby 2.1
6
+ * More generic handling of "fake" frame names
7
+ * Record marking and sweeping as fake gc calls
data/README.md CHANGED
@@ -6,7 +6,7 @@ Inspired heavily by [gperftools](https://code.google.com/p/gperftools/), and wri
6
6
 
7
7
  ## Requirements
8
8
 
9
- * Ruby 2.1+
9
+ * Ruby 2.2+
10
10
  * Linux-based OS
11
11
 
12
12
  ## Getting Started
@@ -330,6 +330,7 @@ Option | Meaning
330
330
  `interval` | mode-relative sample rate [c.f.](#sampling)
331
331
  `aggregate` | defaults: `true` - if `false` disables [aggregation](#aggregation)
332
332
  `raw` | defaults `false` - if `true` collects the extra data required by the `--flamegraph` and `--stackcollapse` report types
333
+ `metadata` | defaults to `{}`. Must be a `Hash`. metadata associated with this profile
333
334
  `save_every`| (rack middleware only) write the target file after this many requests
334
335
 
335
336
  ## Todo
@@ -17,6 +17,18 @@
17
17
 
18
18
  #define BUF_SIZE 2048
19
19
 
20
+ #define FAKE_FRAME_GC INT2FIX(0)
21
+ #define FAKE_FRAME_MARK INT2FIX(1)
22
+ #define FAKE_FRAME_SWEEP INT2FIX(2)
23
+
24
+ static const char *fake_frame_cstrs[] = {
25
+ "(garbage collection)",
26
+ "(marking)",
27
+ "(sweeping)",
28
+ };
29
+
30
+ #define TOTAL_FAKE_FRAMES (sizeof(fake_frame_cstrs) / sizeof(char *))
31
+
20
32
  typedef struct {
21
33
  size_t total_samples;
22
34
  size_t caller_samples;
@@ -33,6 +45,7 @@ static struct {
33
45
  VALUE mode;
34
46
  VALUE interval;
35
47
  VALUE out;
48
+ VALUE metadata;
36
49
 
37
50
  VALUE *raw_samples;
38
51
  size_t raw_samples_len;
@@ -48,10 +61,11 @@ static struct {
48
61
  size_t overall_samples;
49
62
  size_t during_gc;
50
63
  size_t unrecorded_gc_samples;
64
+ size_t unrecorded_gc_marking_samples;
65
+ size_t unrecorded_gc_sweeping_samples;
51
66
  st_table *frames;
52
67
 
53
- VALUE fake_gc_frame;
54
- VALUE fake_gc_frame_name;
68
+ VALUE fake_frame_names[TOTAL_FAKE_FRAMES];
55
69
  VALUE empty_string;
56
70
  VALUE frames_buffer[BUF_SIZE];
57
71
  int lines_buffer[BUF_SIZE];
@@ -59,7 +73,8 @@ static struct {
59
73
 
60
74
  static VALUE sym_object, sym_wall, sym_cpu, sym_custom, sym_name, sym_file, sym_line;
61
75
  static VALUE sym_samples, sym_total_samples, sym_missed_samples, sym_edges, sym_lines;
62
- static VALUE sym_version, sym_mode, sym_interval, sym_raw, sym_frames, sym_out, sym_aggregate, sym_raw_timestamp_deltas;
76
+ static VALUE sym_version, sym_mode, sym_interval, sym_raw, sym_metadata, sym_frames, sym_out, sym_aggregate, sym_raw_timestamp_deltas;
77
+ static VALUE sym_state, sym_marking, sym_sweeping;
63
78
  static VALUE sym_gc_samples, objtracer;
64
79
  static VALUE gc_hook;
65
80
  static VALUE rb_mStackProf;
@@ -72,7 +87,7 @@ stackprof_start(int argc, VALUE *argv, VALUE self)
72
87
  {
73
88
  struct sigaction sa;
74
89
  struct itimerval timer;
75
- VALUE opts = Qnil, mode = Qnil, interval = Qnil, out = Qfalse;
90
+ VALUE opts = Qnil, mode = Qnil, interval = Qnil, metadata = rb_hash_new(), out = Qfalse;
76
91
  int raw = 0, aggregate = 1;
77
92
 
78
93
  if (_stackprof.running)
@@ -85,6 +100,14 @@ stackprof_start(int argc, VALUE *argv, VALUE self)
85
100
  interval = rb_hash_aref(opts, sym_interval);
86
101
  out = rb_hash_aref(opts, sym_out);
87
102
 
103
+ VALUE metadata_val = rb_hash_aref(opts, sym_metadata);
104
+ if (RTEST(metadata_val)) {
105
+ if (!RB_TYPE_P(metadata_val, T_HASH))
106
+ rb_raise(rb_eArgError, "metadata should be a hash");
107
+
108
+ metadata = metadata_val;
109
+ }
110
+
88
111
  if (RTEST(rb_hash_aref(opts, sym_raw)))
89
112
  raw = 1;
90
113
  if (rb_hash_lookup2(opts, sym_aggregate, Qundef) == Qfalse)
@@ -128,6 +151,7 @@ stackprof_start(int argc, VALUE *argv, VALUE self)
128
151
  _stackprof.aggregate = aggregate;
129
152
  _stackprof.mode = mode;
130
153
  _stackprof.interval = interval;
154
+ _stackprof.metadata = metadata;
131
155
  _stackprof.out = out;
132
156
 
133
157
  if (raw) {
@@ -201,8 +225,8 @@ frame_i(st_data_t key, st_data_t val, st_data_t arg)
201
225
 
202
226
  rb_hash_aset(results, rb_obj_id(frame), details);
203
227
 
204
- if (frame == _stackprof.fake_gc_frame) {
205
- name = _stackprof.fake_gc_frame_name;
228
+ if (FIXNUM_P(frame)) {
229
+ name = _stackprof.fake_frame_names[FIX2INT(frame)];
206
230
  file = _stackprof.empty_string;
207
231
  line = INT2FIX(0);
208
232
  } else {
@@ -258,6 +282,7 @@ stackprof_results(int argc, VALUE *argv, VALUE self)
258
282
  rb_hash_aset(results, sym_samples, SIZET2NUM(_stackprof.overall_samples));
259
283
  rb_hash_aset(results, sym_gc_samples, SIZET2NUM(_stackprof.during_gc));
260
284
  rb_hash_aset(results, sym_missed_samples, SIZET2NUM(_stackprof.overall_signals - _stackprof.overall_samples));
285
+ rb_hash_aset(results, sym_metadata, _stackprof.metadata);
261
286
 
262
287
  frames = rb_hash_new();
263
288
  rb_hash_aset(results, sym_frames, frames);
@@ -522,15 +547,37 @@ stackprof_record_gc_samples()
522
547
  }
523
548
  }
524
549
 
525
- _stackprof.frames_buffer[0] = _stackprof.fake_gc_frame;
526
- _stackprof.lines_buffer[0] = 0;
527
550
 
528
551
  for (i = 0; i < _stackprof.unrecorded_gc_samples; i++) {
529
552
  int timestamp_delta = i == 0 ? delta_to_first_unrecorded_gc_sample : NUM2LONG(_stackprof.interval);
530
- stackprof_record_sample_for_stack(1, timestamp_delta);
553
+
554
+ if (_stackprof.unrecorded_gc_marking_samples) {
555
+ _stackprof.frames_buffer[0] = FAKE_FRAME_MARK;
556
+ _stackprof.lines_buffer[0] = 0;
557
+ _stackprof.frames_buffer[1] = FAKE_FRAME_GC;
558
+ _stackprof.lines_buffer[1] = 0;
559
+ _stackprof.unrecorded_gc_marking_samples--;
560
+
561
+ stackprof_record_sample_for_stack(2, timestamp_delta);
562
+ } else if (_stackprof.unrecorded_gc_sweeping_samples) {
563
+ _stackprof.frames_buffer[0] = FAKE_FRAME_SWEEP;
564
+ _stackprof.lines_buffer[0] = 0;
565
+ _stackprof.frames_buffer[1] = FAKE_FRAME_GC;
566
+ _stackprof.lines_buffer[1] = 0;
567
+
568
+ _stackprof.unrecorded_gc_sweeping_samples--;
569
+
570
+ stackprof_record_sample_for_stack(2, timestamp_delta);
571
+ } else {
572
+ _stackprof.frames_buffer[0] = FAKE_FRAME_GC;
573
+ _stackprof.lines_buffer[0] = 0;
574
+ stackprof_record_sample_for_stack(1, timestamp_delta);
575
+ }
531
576
  }
532
577
  _stackprof.during_gc += _stackprof.unrecorded_gc_samples;
533
578
  _stackprof.unrecorded_gc_samples = 0;
579
+ _stackprof.unrecorded_gc_marking_samples = 0;
580
+ _stackprof.unrecorded_gc_sweeping_samples = 0;
534
581
  }
535
582
 
536
583
  static void
@@ -562,6 +609,12 @@ stackprof_signal_handler(int sig, siginfo_t *sinfo, void *ucontext)
562
609
  {
563
610
  _stackprof.overall_signals++;
564
611
  if (rb_during_gc()) {
612
+ VALUE mode = rb_gc_latest_gc_info(sym_state);
613
+ if (mode == sym_marking) {
614
+ _stackprof.unrecorded_gc_marking_samples++;
615
+ } else if (mode == sym_sweeping) {
616
+ _stackprof.unrecorded_gc_sweeping_samples++;
617
+ }
565
618
  _stackprof.unrecorded_gc_samples++;
566
619
  rb_postponed_job_register_one(0, stackprof_gc_job_handler, (void*)0);
567
620
  } else {
@@ -642,6 +695,7 @@ stackprof_atfork_child(void)
642
695
  void
643
696
  Init_stackprof(void)
644
697
  {
698
+ size_t i;
645
699
  #define S(name) sym_##name = ID2SYM(rb_intern(#name));
646
700
  S(object);
647
701
  S(custom);
@@ -662,10 +716,17 @@ Init_stackprof(void)
662
716
  S(raw);
663
717
  S(raw_timestamp_deltas);
664
718
  S(out);
719
+ S(metadata);
665
720
  S(frames);
666
721
  S(aggregate);
722
+ S(state);
723
+ S(marking);
724
+ S(sweeping);
667
725
  #undef S
668
726
 
727
+ /* Need to run this to warm the symbol table before we call this during GC */
728
+ rb_gc_latest_gc_info(sym_state);
729
+
669
730
  gc_hook = Data_Wrap_Struct(rb_cObject, stackprof_gc_mark, NULL, &_stackprof);
670
731
  rb_global_variable(&gc_hook);
671
732
 
@@ -678,12 +739,14 @@ Init_stackprof(void)
678
739
  _stackprof.raw_timestamp_deltas_len = 0;
679
740
  _stackprof.raw_timestamp_deltas_capa = 0;
680
741
 
681
- _stackprof.fake_gc_frame = INT2FIX(0x9C);
682
742
  _stackprof.empty_string = rb_str_new_cstr("");
683
- _stackprof.fake_gc_frame_name = rb_str_new_cstr("(garbage collection)");
684
- rb_global_variable(&_stackprof.fake_gc_frame_name);
685
743
  rb_global_variable(&_stackprof.empty_string);
686
744
 
745
+ for (i = 0; i < TOTAL_FAKE_FRAMES; i++) {
746
+ _stackprof.fake_frame_names[i] = rb_str_new_cstr(fake_frame_cstrs[i]);
747
+ rb_global_variable(&_stackprof.fake_frame_names[i]);
748
+ }
749
+
687
750
  rb_mStackProf = rb_define_module("StackProf");
688
751
  rb_define_singleton_method(rb_mStackProf, "running?", stackprof_running_p, 0);
689
752
  rb_define_singleton_method(rb_mStackProf, "run", stackprof_run, -1);
@@ -1,7 +1,7 @@
1
1
  require "stackprof/stackprof"
2
2
 
3
3
  module StackProf
4
- VERSION = '0.2.13'
4
+ VERSION = '0.2.14'
5
5
  end
6
6
 
7
7
  StackProf.autoload :Report, "stackprof/report.rb"
@@ -13,12 +13,18 @@ module StackProf
13
13
  Middleware.enabled = options[:enabled]
14
14
  options[:path] = 'tmp/' if options[:path].to_s.empty?
15
15
  Middleware.path = options[:path]
16
+ Middleware.metadata = options[:metadata] || {}
16
17
  at_exit{ Middleware.save } if options[:save_at_exit]
17
18
  end
18
19
 
19
20
  def call(env)
20
21
  enabled = Middleware.enabled?(env)
21
- StackProf.start(mode: Middleware.mode, interval: Middleware.interval, raw: Middleware.raw) if enabled
22
+ StackProf.start(
23
+ mode: Middleware.mode,
24
+ interval: Middleware.interval,
25
+ raw: Middleware.raw,
26
+ metadata: Middleware.metadata,
27
+ ) if enabled
22
28
  @app.call(env)
23
29
  ensure
24
30
  if enabled
@@ -31,7 +37,7 @@ module StackProf
31
37
  end
32
38
 
33
39
  class << self
34
- attr_accessor :enabled, :mode, :interval, :raw, :path
40
+ attr_accessor :enabled, :mode, :interval, :raw, :path, :metadata
35
41
 
36
42
  def enabled?(env)
37
43
  if enabled.respond_to?(:call)
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'stackprof'
3
- s.version = '0.2.13'
3
+ s.version = '0.2.14'
4
4
  s.homepage = 'http://github.com/tmm1/stackprof'
5
5
 
6
6
  s.authors = 'Aman Gupta'
@@ -14,9 +14,11 @@ Gem::Specification.new do |s|
14
14
  s.executables << 'stackprof-flamegraph.pl'
15
15
  s.executables << 'stackprof-gprof2dot.py'
16
16
 
17
- s.summary = 'sampling callstack-profiler for ruby 2.1+'
17
+ s.summary = 'sampling callstack-profiler for ruby 2.2+'
18
18
  s.description = 'stackprof is a fast sampling profiler for ruby code, with cpu, wallclock and object allocation samplers.'
19
19
 
20
+ s.required_ruby_version = '>= 2.2'
21
+
20
22
  s.license = 'MIT'
21
23
 
22
24
  s.add_development_dependency 'rake-compiler', '~> 0.9'
@@ -64,4 +64,10 @@ class StackProf::MiddlewareTest < MiniTest::Test
64
64
  StackProf::Middleware.new(Object.new, raw: true)
65
65
  assert StackProf::Middleware.raw
66
66
  end
67
+
68
+ def test_metadata
69
+ metadata = { key: 'value' }
70
+ StackProf::Middleware.new(Object.new, metadata: metadata)
71
+ assert_equal metadata, StackProf::Middleware.metadata
72
+ end
67
73
  end
@@ -109,6 +109,36 @@ class StackProfTest < MiniTest::Test
109
109
  assert_equal 10, profile[:raw_timestamp_deltas].size
110
110
  end
111
111
 
112
+ def test_metadata
113
+ metadata = {
114
+ path: '/foo/bar',
115
+ revision: '5c0b01f1522ae8c194510977ae29377296dd236b',
116
+ }
117
+ profile = StackProf.run(mode: :cpu, metadata: metadata) do
118
+ math
119
+ end
120
+
121
+ assert_equal metadata, profile[:metadata]
122
+ end
123
+
124
+ def test_empty_metadata
125
+ profile = StackProf.run(mode: :cpu) do
126
+ math
127
+ end
128
+
129
+ assert_equal({}, profile[:metadata])
130
+ end
131
+
132
+ def test_raises_if_metadata_is_not_a_hash
133
+ exception = assert_raises ArgumentError do
134
+ StackProf.run(mode: :cpu, metadata: 'foobar') do
135
+ math
136
+ end
137
+ end
138
+
139
+ assert_equal 'metadata should be a hash', exception.message
140
+ end
141
+
112
142
  def test_fork
113
143
  StackProf.run do
114
144
  pid = fork do
@@ -150,10 +180,18 @@ class StackProfTest < MiniTest::Test
150
180
 
151
181
  raw = profile[:raw]
152
182
  gc_frame = profile[:frames].values.find{ |f| f[:name] == "(garbage collection)" }
183
+ marking_frame = profile[:frames].values.find{ |f| f[:name] == "(marking)" }
184
+ sweeping_frame = profile[:frames].values.find{ |f| f[:name] == "(sweeping)" }
185
+
153
186
  assert gc_frame
154
- assert_equal gc_frame[:samples], profile[:gc_samples]
187
+ assert marking_frame
188
+ assert sweeping_frame
189
+
190
+ assert_equal gc_frame[:total_samples], profile[:gc_samples]
191
+ assert_equal profile[:gc_samples], [gc_frame, marking_frame, sweeping_frame].map{|x| x[:samples] }.inject(:+)
192
+
155
193
  assert_operator profile[:gc_samples], :>, 0
156
- assert_operator profile[:missed_samples], :<=, 10
194
+ assert_operator profile[:missed_samples], :<=, 25
157
195
  end
158
196
 
159
197
  def test_out
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stackprof
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.13
4
+ version: 0.2.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aman Gupta
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-10-01 00:00:00.000000000 Z
11
+ date: 2019-12-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake-compiler
@@ -103,15 +103,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
103
103
  requirements:
104
104
  - - ">="
105
105
  - !ruby/object:Gem::Version
106
- version: '0'
106
+ version: '2.2'
107
107
  required_rubygems_version: !ruby/object:Gem::Requirement
108
108
  requirements:
109
109
  - - ">="
110
110
  - !ruby/object:Gem::Version
111
111
  version: '0'
112
112
  requirements: []
113
- rubygems_version: 3.1.0.pre1
113
+ rubygems_version: 3.1.0.pre3
114
114
  signing_key:
115
115
  specification_version: 4
116
- summary: sampling callstack-profiler for ruby 2.1+
116
+ summary: sampling callstack-profiler for ruby 2.2+
117
117
  test_files: []