rperf 0.10.0 → 0.11.1

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: 0dd9ddc0ea22d51ff2ebcdae6ddfa2f5e531c4a7814552b16470668d04d1d617
4
- data.tar.gz: fce76e35ea6ccbca8e8b429da4b55574349f98b8731a2db3826efd908082dda6
3
+ metadata.gz: 3240adc0c0b301a502b663cd9b044e02661c64e072e5a6582ac30d273b349d54
4
+ data.tar.gz: a49b1fab25b96e80f17f942acba4a07aef745a61e565587f791f99c878a0d84e
5
5
  SHA512:
6
- metadata.gz: dceedb899f6b9249ae342e5701ef3280253c82d317ad73dea0bdd0c0c040a576740bb747c05c79b1f6118c82b05ad4756207ac046d9cc4805982a2f24ad09e1b
7
- data.tar.gz: 4353afbe9ca80126c25884e837aacdd902a1e9d20756d543d235415492e17932490711b348ee49eb9e5144f977027a1284876cbd4dea4de31c342a16db16d931
6
+ metadata.gz: 1e5c770faca5dfe03cb87eb83d3021f12b0bbf23433eac923656910fe7275ebe20c08aec7046ab82975ccf2b7b1574bb062493712bee6e2adb5d772e9f60beb6
7
+ data.tar.gz: 776f023349f55073383d78c81736a578cb31f8d1973c49a57b3eef596a132edb1a03e89734fad69f53395c91578a431c27e4952fd6c272b3a069b04dc577c0e7
data/README.md CHANGED
@@ -82,6 +82,12 @@ rperf report ./profiles/ # sidebar: per-commit list,
82
82
 
83
83
  # Flat tables for AI analysis (no Go required)
84
84
  rperf diff --format table base.json.gz head.json.gz | claude -p "analyze the regression"
85
+
86
+ # Drive rperf without wrapping the command (CI/tooling): source the env, then
87
+ # run your command unchanged — `bundle exec …` stays bundler-managed, plain
88
+ # `ruby …` stays plain. (Run `rperf help` for details.)
89
+ eval "$(rperf record --snapshot-dir ./profiles --print-env)"
90
+ export RPERF_ROOT_PROCESS=$$ && exec ruby app.rb
85
91
  ```
86
92
 
87
93
  On `rperf report`, you can see the profile result like this page: [rperf viewer](https://ko1.github.io/rperf/examples/cpu_intensive_profile.html)
data/docs/help.md CHANGED
@@ -31,8 +31,28 @@ POSIX systems (Linux, macOS). Requires Ruby >= 3.4.0.
31
31
  --label KEY=VALUE Add a label to profile metadata (repeatable)
32
32
  --no-inherit Do not profile forked/spawned child processes
33
33
  --no-aggregate Disable C-level sample aggregation (raw per-sample data)
34
+ --print-env Print the env rperf would set (export KEY=VALUE lines)
35
+ and exit WITHOUT running a command — see below
34
36
  -v, --verbose Print sampling statistics to stderr
35
37
 
38
+ #### --print-env: drive rperf without wrapping the command
39
+
40
+ Instead of `rperf record [options] -- command`, `--print-env` prints the
41
+ environment a profiled process auto-starts from and exits without running
42
+ anything. A wrapper (e.g. a CI script) sources it, makes the process that runs
43
+ the command the session root, and runs the command unchanged — so a
44
+ `bundle exec …` command stays bundler-managed and a plain `ruby …` stays plain;
45
+ rperf is never inserted into the command line.
46
+
47
+ eval "$(rperf record --snapshot-dir ./profiles --print-env)"
48
+ export RPERF_ROOT_PROCESS=$$ # the pid that will exec the command is the root
49
+ exec ruby app.rb # runs verbatim; rperf auto-starts via RUBYOPT
50
+
51
+ `RPERF_ROOT_PROCESS` is not emitted (the caller sets it to the pid that execs
52
+ the command); `RPERF_META_LABELS` is not emitted (set it yourself if you want
53
+ per-run labels). Everything else (`RUBYOPT=-rrperf`, output path, mode,
54
+ frequency, fork session dir, …) is included.
55
+
36
56
  JSON output embeds `meta` (git commit, host, Ruby/rperf versions, labels)
37
57
  and `summary` (time, GC, allocation, top methods) — see "Profile metadata"
38
58
  under OUTPUT FORMATS.
data/exe/rperf CHANGED
@@ -368,6 +368,7 @@ stat_report = (subcommand == "exec")
368
368
  inherit = true
369
369
  meta_labels = {}
370
370
  snapshot_dir = nil
371
+ print_env = false
371
372
 
372
373
  parser = OptionParser.new do |opts|
373
374
  opts.banner = case subcommand
@@ -441,6 +442,13 @@ parser = OptionParser.new do |opts|
441
442
  inherit = false
442
443
  end
443
444
 
445
+ opts.on("--print-env",
446
+ "Print the environment rperf would set (one `export KEY=VALUE` per line), then exit " \
447
+ "WITHOUT running a command. The caller sources it, sets RPERF_ROOT_PROCESS to the pid " \
448
+ "that will exec the command, and runs the command itself — so rperf never wraps it.") do
449
+ print_env = true
450
+ end
451
+
444
452
  opts.on("-v", "--verbose", "Print sampling statistics to stderr") do
445
453
  verbose = true
446
454
  end
@@ -461,7 +469,7 @@ rescue OptionParser::ParseError => e
461
469
  exit 1
462
470
  end
463
471
 
464
- if ARGV.empty?
472
+ if ARGV.empty? && !print_env
465
473
  $stderr.puts "No command specified."
466
474
  $stderr.puts parser
467
475
  exit 1
@@ -557,9 +565,27 @@ session_dir = nil
557
565
  if inherit
558
566
  session_dir = Rperf.send(:_create_session_dir, clean_stale: true)
559
567
  if session_dir
560
- ENV["RPERF_ROOT_PROCESS"] = Process.pid.to_s
561
568
  ENV["RPERF_SESSION_DIR"] = session_dir
569
+ # In --print-env mode this short-lived process does not run the command, so
570
+ # it must not claim rootship: the caller sets RPERF_ROOT_PROCESS to the pid
571
+ # that will exec the command. Otherwise (normal exec) we are the root.
572
+ ENV["RPERF_ROOT_PROCESS"] = Process.pid.to_s unless print_env
573
+ end
574
+ end
575
+
576
+ # --print-env: emit the environment a profiled process inherits to auto-start
577
+ # rperf, then exit without running anything. RPERF_ROOT_PROCESS and
578
+ # RPERF_META_LABELS are deliberately omitted — the caller owns those (it sets
579
+ # the root pid, and labels per measurement run).
580
+ if print_env
581
+ require "shellwords"
582
+ %w[RUBYLIB RUBYOPT RPERF_ENABLED RPERF_OUTPUT RPERF_FREQUENCY RPERF_MODE
583
+ RPERF_FORMAT RPERF_VERBOSE RPERF_SIGNAL RPERF_AGGREGATE RPERF_META_GIT
584
+ RPERF_SESSION_DIR RPERF_STAT RPERF_STAT_COMMAND RPERF_STAT_REPORT].each do |k|
585
+ v = ENV[k]
586
+ puts "export #{k}=#{Shellwords.escape(v)}" unless v.nil?
562
587
  end
588
+ exit 0
563
589
  end
564
590
 
565
591
  begin
data/lib/rperf/meta.rb CHANGED
@@ -6,7 +6,11 @@
6
6
  # first two top-level keys, so Meta.read can decompress only the head of the
7
7
  # file and stop as soon as both are extracted.
8
8
 
9
- require "json"
9
+ # NOTE: json is NOT required here. rperf loads via `ruby -rrperf` at interpreter
10
+ # boot, before the profiled app runs `bundler/setup`. Requiring json eagerly
11
+ # would activate the default json gem and then clash with a bundle that pins a
12
+ # different json (Gem::LoadError). json is used only at profile write/read time
13
+ # (after the app has set up its bundle), so each user requires it lazily.
10
14
  require "time"
11
15
  require "zlib"
12
16
 
@@ -103,6 +107,7 @@ module Rperf
103
107
  v = ENV["RPERF_META_GIT"].to_s
104
108
  return nil if v.empty? || v == "null"
105
109
  begin
110
+ require "json"
106
111
  JSON.parse(v, symbolize_names: true)
107
112
  rescue JSON::ParserError
108
113
  nil
@@ -119,6 +124,7 @@ module Rperf
119
124
  v = ENV["RPERF_META_LABELS"]
120
125
  return nil unless v
121
126
  begin
127
+ require "json"
122
128
  labels = JSON.parse(v)
123
129
  labels.is_a?(Hash) ? labels : nil
124
130
  rescue JSON::ParserError
@@ -224,6 +230,7 @@ module Rperf
224
230
  # Returns { meta:, summary: }, nil (old format / malformed),
225
231
  # or :incomplete (need more input).
226
232
  def scan_prefix(buf)
233
+ require "json" # lazy: see the note at the top of this file
227
234
  n = buf.bytesize
228
235
  i = skip_ws(buf, 0, n)
229
236
  return :incomplete if i >= n
data/lib/rperf/table.rb CHANGED
@@ -5,8 +5,11 @@
5
5
  # TSV: header row, data rows, then a trailing "# summary" line of
6
6
  # tab-separated key=value pairs.
7
7
  # JSON: an array of row objects; the last element is { "summary": { ... } }.
8
-
9
- require "json"
8
+ #
9
+ # json is required lazily in the format methods, not here: rperf loads at
10
+ # interpreter boot via `-rrperf`, before the profiled app's bundler/setup, and
11
+ # eagerly activating the default json gem would clash with a bundle that pins a
12
+ # different json (see the note in meta.rb).
10
13
 
11
14
  module Rperf
12
15
  module Table
@@ -67,6 +70,7 @@ module Rperf
67
70
  end
68
71
 
69
72
  def report_json(data)
73
+ require "json"
70
74
  JSON.generate(report_rows(data) + [{ summary: report_summary(data) }])
71
75
  end
72
76
 
@@ -117,6 +121,7 @@ module Rperf
117
121
  end
118
122
 
119
123
  def diff_json(base, head)
124
+ require "json"
120
125
  JSON.generate(diff_rows(base, head) + [{ summary: diff_summary(base, head) }])
121
126
  end
122
127
 
data/lib/rperf/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Rperf
2
- VERSION = "0.10.0"
2
+ VERSION = "0.11.1"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rperf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.0
4
+ version: 0.11.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Koichi Sasada
@@ -80,7 +80,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
80
80
  - !ruby/object:Gem::Version
81
81
  version: '0'
82
82
  requirements: []
83
- rubygems_version: 4.0.6
83
+ rubygems_version: 4.0.10
84
84
  specification_version: 4
85
85
  summary: Safepoint-based sampling performance profiler for Ruby
86
86
  test_files: []