vernier 1.10.1 → 1.11.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: d1af998d7d2517b6c73e0ef7ec8a3e0762c9c0ca23dd859d3c268842f064612f
4
- data.tar.gz: 558ad40b4ca261719e241f22806112e9ff18b2ba0e1a65d5c24067deee0803e0
3
+ metadata.gz: 6e8d610b954485d3bccefa47408932dddd5806b618caa2958d689f8c0c8fbb3d
4
+ data.tar.gz: 2423bf5e06dd1168e21f0a5063050e897137c37bd09ce68a540af9d84b0a7546
5
5
  SHA512:
6
- metadata.gz: 58a0c75db2f1ffb90017f887c4e6813653ef908efc4601cbe6fe105aa1493c5344028355d1ec68658e59c34d36b885fcd59340de553bfffc1a72fcadd7668398
7
- data.tar.gz: a80cd4ab4c0da94d3799cbedfe93de02788dc48bb7626565eff62cfc7606aa75c93892f5dab17d6b076abf6ea361cbf9231940cfb40384c8116490aff5dee79e
6
+ metadata.gz: 942b097d843b63457650254b2e9717698939df9ee40c02ff367832d344121e6ffdafffbc2e0824d70229bf5d4187d1cf0eb64491402a4c54c4c5f6dd8fb0b98f
7
+ data.tar.gz: 2f710c6b9eeb8e4e352290902aed1a4142ea5b48df7039ee49de66dc7c9f7411e25cb77319addba69a15f072f08b3820a51535fe19070a4e77341aa6c000a57b
data/Gemfile CHANGED
@@ -12,3 +12,5 @@ gem "rake-compiler"
12
12
  gem "benchmark-ips"
13
13
 
14
14
  gem "minitest", "~> 5.0"
15
+
16
+ gem "zlib", ">= 3.2.1"
@@ -29,6 +29,7 @@ module Vernier
29
29
  result.instance_variable_set(:@threads, {
30
30
  0 => {
31
31
  tid: 0,
32
+ is_main: true,
32
33
  name: "custom",
33
34
  started_at: @started_at,
34
35
  samples: @samples,
@@ -80,6 +81,7 @@ module Vernier
80
81
  result.instance_variable_set(:@threads, {
81
82
  0 => {
82
83
  tid: 0,
84
+ is_main: true,
83
85
  name: "retained memory",
84
86
  started_at: @started_at,
85
87
  samples: samples,
@@ -1,8 +1,12 @@
1
1
  require "json"
2
2
 
3
+ require_relative "../output_helpers"
4
+
3
5
  module Vernier
4
6
  module Output
5
7
  class Cpuprofile
8
+ include OutputHelpers
9
+
6
10
  def initialize(profile)
7
11
  @profile = profile
8
12
  end
@@ -98,7 +102,9 @@ module Vernier
98
102
  line = stack_table.frame_line_no(frame_idx) - 1
99
103
 
100
104
  func_name = stack_table.func_name(func_idx)
105
+ func_name = sanitize_string(func_name) if func_name
101
106
  filename = stack_table.func_filename(func_idx)
107
+ filename = sanitize_string(filename) if filename
102
108
 
103
109
  node_id = nodes.length
104
110
  node = {
@@ -1,23 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "filename_filter"
4
+ require_relative "../output_helpers"
4
5
  require "cgi/escape"
5
6
 
6
7
  module Vernier
7
8
  module Output
8
9
  class FileListing
10
+ include OutputHelpers
11
+
9
12
  class SamplesByLocation
10
13
  attr_accessor :self, :total
11
14
  def initialize
12
15
  @self = @total = 0
13
16
  end
14
-
15
- def +(other)
16
- ret = SamplesByLocation.new
17
- ret.self = @self + other.self
18
- ret.total = @total + other.total
19
- ret
20
- end
21
17
  end
22
18
 
23
19
  def initialize(profile)
@@ -25,6 +21,8 @@ module Vernier
25
21
  end
26
22
 
27
23
  def samples_by_file
24
+ return @samples_by_file if defined?(@samples_by_file)
25
+
28
26
  thread = @profile.main_thread
29
27
  if Hash === thread
30
28
  # live profile
@@ -38,22 +36,9 @@ module Vernier
38
36
  weights = thread[:weights]
39
37
  samples = thread[:samples]
40
38
 
41
- self_samples_by_frame = Hash.new do |h, k|
42
- h[k] = SamplesByLocation.new
43
- end
44
-
45
- samples.zip(weights).each do |stack_idx, weight|
46
- # self time
47
- top_frame_index = stack_table.stack_frame_idx(stack_idx)
48
- self_samples_by_frame[top_frame_index].self += weight
39
+ stack_weights = collapse_stack_weights(samples, weights)
49
40
 
50
- # total time
51
- while stack_idx
52
- frame_idx = stack_table.stack_frame_idx(stack_idx)
53
- self_samples_by_frame[frame_idx].total += weight
54
- stack_idx = stack_table.stack_parent_idx(stack_idx)
55
- end
56
- end
41
+ self_by_frame, total_by_frame = frame_weights(stack_table, stack_weights)
57
42
 
58
43
  samples_by_file = Hash.new do |h, k|
59
44
  h[k] = Hash.new do |h2, k2|
@@ -61,19 +46,64 @@ module Vernier
61
46
  end
62
47
  end
63
48
 
64
- self_samples_by_frame.each do |frame, samples|
49
+ self_by_frame.each_with_index do |self_weight, frame|
50
+ total_weight = total_by_frame[frame]
51
+ next if self_weight == 0 && total_weight == 0
52
+
65
53
  line = stack_table.frame_line_no(frame)
66
54
  func_index = stack_table.frame_func_idx(frame)
67
55
  filename = stack_table.func_filename(func_index)
68
56
 
69
- samples_by_file[filename][line] += samples
57
+ location = samples_by_file[filename][line]
58
+ location.self += self_weight
59
+ location.total += total_weight
70
60
  end
71
61
 
72
- samples_by_file.transform_keys! do |filename|
62
+ @samples_by_file = samples_by_file.transform_keys! do |filename|
73
63
  filename_filter.call(filename)
74
64
  end
75
65
  end
76
66
 
67
+ # Returns [self_weight, total_weight] arrays indexed by frame, from a
68
+ # hash of per-stack weights.
69
+ #
70
+ # The stack table is a prefix tree whose parent is always interned
71
+ # before its children (parent idx < child idx), so total weights are
72
+ # accumulated bottom-up in a single reverse pass.
73
+ private def frame_weights(stack_table, stack_weights)
74
+ # Scatter the sparse weights into an array indexed by stack.
75
+ stack_totals = Array.new(stack_table.stack_count, 0)
76
+ stack_weights.each do |stack_idx, weight|
77
+ stack_totals[stack_idx] = weight
78
+ end
79
+
80
+ # Accumulate each stack's total into its parent, children first,
81
+ # so a single descending pass completes every subtree's total.
82
+ (stack_totals.length - 1).downto(0) do |stack_idx|
83
+ parent_idx = stack_table.stack_parent_idx(stack_idx)
84
+ next unless parent_idx
85
+
86
+ if parent_idx >= stack_idx
87
+ raise "Invalid profile: stack table is not parent-before-child ordered"
88
+ end
89
+
90
+ stack_totals[parent_idx] += stack_totals[stack_idx]
91
+ end
92
+
93
+ self_by_frame = Array.new(stack_table.frame_count, 0)
94
+ total_by_frame = Array.new(stack_table.frame_count, 0)
95
+ stack_weights.each do |stack_idx, weight|
96
+ self_by_frame[stack_table.stack_frame_idx(stack_idx)] += weight
97
+ end
98
+ stack_totals.each_with_index do |total, stack_idx|
99
+ next if total == 0
100
+
101
+ total_by_frame[stack_table.stack_frame_idx(stack_idx)] += total
102
+ end
103
+
104
+ [self_by_frame, total_by_frame]
105
+ end
106
+
77
107
  def output(template: nil)
78
108
  output = +""
79
109
 
@@ -98,8 +128,7 @@ module Vernier
98
128
  end
99
129
 
100
130
  def total
101
- thread = @profile.main_thread
102
- thread[:weights].sum
131
+ @total ||= @profile.main_thread[:weights].sum
103
132
  end
104
133
 
105
134
  def format_file(output, filename, all_samples, total:)
@@ -4,6 +4,7 @@ require "json"
4
4
  require "rbconfig"
5
5
 
6
6
  require_relative "filename_filter"
7
+ require_relative "../output_helpers"
7
8
 
8
9
  module Vernier
9
10
  module Output
@@ -267,6 +268,8 @@ module Vernier
267
268
  end
268
269
 
269
270
  class Thread
271
+ include OutputHelpers
272
+
270
273
  SAMPLE_CATEGORY_NAMES = {
271
274
  1 => "Idle",
272
275
  2 => "Stalled"
@@ -638,18 +641,7 @@ module Vernier
638
641
 
639
642
  def string_table
640
643
  @strings.keys.map do |string|
641
- if string.ascii_only?
642
- string
643
- elsif string.encoding == Encoding::UTF_8
644
- if string.valid_encoding?
645
- string
646
- else
647
- string.scrub
648
- end
649
- else
650
- # TODO: We might want to guess UTF-8 and escape the binary more explicitly
651
- string.dup.force_encoding("UTF-8").scrub
652
- end
644
+ sanitize_string(string)
653
645
  end
654
646
  end
655
647
 
@@ -1,8 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../output_helpers"
4
+
3
5
  module Vernier
4
6
  module Output
5
7
  class Top
8
+ include OutputHelpers
9
+
6
10
  def initialize(profile, row_limit)
7
11
  @profile = profile
8
12
  @row_limit = row_limit
@@ -57,10 +61,10 @@ module Vernier
57
61
  @profile._stack_table
58
62
  end
59
63
 
60
- stack_weights = Hash.new(0)
61
- thread[:samples].zip(thread[:weights]) do |stack_idx, weight|
62
- stack_weights[stack_idx] += weight
63
- end
64
+ samples = thread[:samples]
65
+ weights = thread[:weights]
66
+
67
+ stack_weights = collapse_stack_weights(samples, weights)
64
68
 
65
69
  total = stack_weights.values.sum
66
70
 
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Vernier
4
+ module OutputHelpers
5
+ # Collapses parallel samples/weights arrays into a hash of total weight
6
+ # per stack index. Retained-mode profiles have one sample per object and
7
+ # many samples share the same stack, so collapsing first keeps later
8
+ # aggregation proportional to the number of unique stacks.
9
+ def collapse_stack_weights(samples, weights)
10
+ stack_weights = Hash.new(0)
11
+ samples.each_with_index do |stack_idx, idx|
12
+ stack_weights[stack_idx] += weights[idx]
13
+ end
14
+ stack_weights
15
+ end
16
+
17
+ # Returns a string safe to embed in JSON output: valid UTF-8 with any
18
+ # invalid bytes replaced. Method names and paths can carry arbitrary
19
+ # encodings (e.g. BINARY or Shift_JIS source), which would otherwise
20
+ # raise JSON::GeneratorError.
21
+ def sanitize_string(string)
22
+ if string.ascii_only?
23
+ string
24
+ elsif string.encoding == Encoding::UTF_8
25
+ string.valid_encoding? ? string : string.scrub
26
+ else
27
+ # We don't know the real encoding; interpret the bytes as UTF-8
28
+ # and replace anything invalid.
29
+ string.dup.force_encoding(Encoding::UTF_8).scrub
30
+ end
31
+ end
32
+ end
33
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Vernier
4
- VERSION = "1.10.1"
4
+ VERSION = "1.11.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vernier
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.10.1
4
+ version: 1.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Hawthorn
@@ -106,6 +106,7 @@ files:
106
106
  - lib/vernier/output/firefox.rb
107
107
  - lib/vernier/output/markdown.rb
108
108
  - lib/vernier/output/top.rb
109
+ - lib/vernier/output_helpers.rb
109
110
  - lib/vernier/parsed_profile.rb
110
111
  - lib/vernier/result.rb
111
112
  - lib/vernier/stack_table.rb
@@ -134,7 +135,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
134
135
  - !ruby/object:Gem::Version
135
136
  version: '0'
136
137
  requirements: []
137
- rubygems_version: 4.0.6
138
+ rubygems_version: 4.0.16
138
139
  specification_version: 4
139
140
  summary: A next generation CRuby profiler
140
141
  test_files: []