stackprof 0.2.13 → 0.2.14

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