wslc-wip 0.4.2 → 0.4.3

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: da7bb5fa632921c307adeeea727e792d38e688204393dfd3ee2c7759e40791b4
4
- data.tar.gz: 2e2a277a4ab2dcf0ce540778b6263a142553430452905924f67158dc13ffed54
3
+ metadata.gz: be995c6781c440a7fd84dd3c3bbbddd6799154b59694edce5aa653d80c7d6e53
4
+ data.tar.gz: 7fb1638a125b2bce92212e8ff525a38e31e2d8aa4386e3e74e379420d4df9c91
5
5
  SHA512:
6
- metadata.gz: f71156a19e193d0402f2fa4dc501aca0f32b27d63807c20b7a1a8dcddf1932f091af8ec12a0310aac36084a6c28034d843100abbf4adc3ac9c081d4e9304c18c
7
- data.tar.gz: 538f842fd08441c9ec1603fc147d7c5ce7a5c6f2f2ce22edd0673b12709da19a6c23d3e7f1231a73d69d675337f09a17bc312aa07792308df414f1130e11b2a5
6
+ metadata.gz: 539721780baa7f366f215581629a761c85e396823e05ff08a3da97c2d0de7808d1470c959240867a5a0ab762f4441ea8b2cb22c92f0063712a18c6b0f80dc4fe
7
+ data.tar.gz: fc00ceceeeb01cd63ac931ff2ffeea1f64848f38a1222b60b8c5de7a501e914a3820d61abbd7d437ebd412e2046958223edab7b46d582b5cb6ef693badf5cc3e
data/README.md CHANGED
@@ -148,14 +148,30 @@ exit, but the timestamp of the `+ ...` line tells you when wip finished its own
148
148
  off to `wslc`/`docker` — useful for telling wip-side overhead apart from time spent booting inside
149
149
  the container.
150
150
 
151
- While a step is still running, wip also prints a host resource snapshot (load average, memory
152
- used, and the top CPU-consuming processes) every 5 seconds, so a hang is visible even before the
153
- command has produced any output of its own:
151
+ While a step is still running, wip also prints a host resource snapshot (load average, memory,
152
+ disk I/O, and the top CPU-consuming processes) every 5 seconds, so a hang is visible even before
153
+ the command has produced any output of its own:
154
154
 
155
155
  ```console
156
- wip: [debug] still running (load 3.42 2.10 1.05 | mem 6.1G/15.6G | top: wslc.exe(8842) cpu 61.0%/mem 3.2%, ...): running: wslc.exe exec -it -w /app app bin/rails c
156
+ wip: [debug] still running (load 3.42 2.10 1.05 | mem 6.1G/15.6G | io read 12000KB/s write 400KB/s | top: wslc.exe(8842) cpu 61.0%/mem 3.2%, ...): running: wslc.exe exec -it -w /app app bin/rails c
157
157
  ```
158
158
 
159
+ The disk I/O figure is worth watching first if the host's CPU and memory look idle — a slow
160
+ `bundle`/`rails` boot is often WSL2's bind-mounted (`.:/app`-style) volumes doing a lot of small
161
+ reads, not the container starving for CPU.
162
+
163
+ For commands that hand the real terminal to the child (`-it`, e.g. `rails c`), these periodic
164
+ snapshots go to a log file instead of your terminal — wip prints the path once at the start —
165
+ since writing into a terminal the child controls in raw mode would garble both outputs. Commands
166
+ that don't need a TTY still get the snapshots printed live.
167
+
168
+ Override that choice with `--debug-log`:
169
+
170
+ - `--debug-log=-` forces snapshots inline even for `-it` commands (only useful if you know your
171
+ terminal/pager can tolerate the interleaving).
172
+ - `--debug-log=PATH` always writes snapshots to `PATH`, including for non-TTY commands, e.g. to
173
+ keep every run's snapshots in one place: `wip rails c --debug --debug-log=/tmp/wip-debug.log`.
174
+
159
175
  ## doctor
160
176
 
161
177
  Each check prints as `[OK]`, `[WARN]`, or `[FAIL]`. Warnings alone exit 0; a WSL2, interop, WSLC,
data/lib/wip/cli.rb CHANGED
@@ -10,6 +10,7 @@ module Wip
10
10
  class CLI < Thor
11
11
  class_option :config, type: :string, desc: 'Path to wip.yml'
12
12
  class_option :debug, type: :boolean, default: false, desc: 'Print progress and timing for each step'
13
+ class_option :debug_log, type: :string, desc: 'Where --debug snapshots go: a file path, or "-" for inline'
13
14
  default_task :dispatch
14
15
 
15
16
  def self.exit_on_failure? = true
@@ -121,7 +122,7 @@ module Wip
121
122
 
122
123
  def execute(command, interactive: false, exit_on_failure: true)
123
124
  runner = CommandRunner.new(debug: debug?)
124
- code = reporter.step("running: #{CommandDisplay.for_debug(command)}") do
125
+ code = reporter.step("running: #{CommandDisplay.for_debug(command)}", live: !interactive) do
125
126
  runner.run(command, interactive: interactive)
126
127
  end
127
128
  exit(code) if exit_on_failure && !code.zero?
@@ -143,7 +144,7 @@ module Wip
143
144
  end
144
145
 
145
146
  def debug? = options[:debug] || !ENV['WIP_DEBUG'].to_s.empty?
146
- def reporter = @reporter ||= DebugReporter.new(enabled: debug?)
147
+ def reporter = @reporter ||= DebugReporter.new(enabled: debug?, log: options[:debug_log])
147
148
 
148
149
  def ensure_network
149
150
  network = load_config.network
@@ -1,27 +1,59 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'tmpdir'
4
+
3
5
  module Wip
4
6
  # Prints each step wip takes and how long it took, when debug mode is enabled.
5
7
  class DebugReporter
6
- def initialize(enabled:, out: $stderr)
8
+ # `log:` overrides where resource snapshots go, regardless of `live`:
9
+ # nil - automatic (see `step`'s `live:`)
10
+ # '-' - always print inline, even for interactive steps
11
+ # path - always write to this file, even for non-interactive steps
12
+ def initialize(enabled:, out: $stderr, log: nil)
7
13
  @enabled = enabled
8
14
  @out = out
15
+ @log = log
9
16
  end
10
17
 
11
- def step(label)
18
+ # `live: false` is for steps that hand the real TTY to the child process
19
+ # (e.g. `wslc exec -it`). The child owns raw-mode cursor control there, so
20
+ # writing periodic snapshots straight into that same terminal races with
21
+ # it and garbles the output; those snapshots go to a log file instead,
22
+ # unless overridden by `log:` in the constructor.
23
+ def step(label, live: true)
12
24
  return yield unless @enabled
13
25
 
14
26
  started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
15
27
  @out.puts "wip: [debug] #{label}"
16
- monitor = ResourceMonitor.new(out: @out)
28
+ file = log_file(live)
29
+ monitor = ResourceMonitor.new(out: file || @out)
17
30
  monitor.start(label)
18
31
  begin
19
32
  yield
20
33
  ensure
21
34
  monitor.stop
35
+ file&.close
22
36
  elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started
23
37
  @out.puts format('wip: [debug] done in %<elapsed>.2fs: %<label>s', elapsed: elapsed, label: label)
24
38
  end
25
39
  end
40
+
41
+ private
42
+
43
+ def log_file(live)
44
+ return nil if @log == '-'
45
+ return open_log(@log, "streaming resource snapshots to #{@log}") if @log
46
+ return nil if live
47
+
48
+ path = File.join(Dir.tmpdir, "wip-debug-#{Process.pid}-#{Time.now.to_i}.log")
49
+ open_log(path, "command owns the terminal; streaming resource snapshots to #{path}")
50
+ end
51
+
52
+ def open_log(path, notice)
53
+ @out.puts "wip: [debug] #{notice}"
54
+ file = File.open(path, 'a') # rubocop:disable Style/FileOpen -- closed by `step`'s ensure block
55
+ file.sync = true
56
+ file
57
+ end
26
58
  end
27
59
  end
@@ -1,19 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Wip
4
- # Periodically prints host CPU/memory/process info while a debug step is still
5
- # running, so a hung or slow step is visible even before it produces output.
4
+ # Periodically prints host CPU/memory/disk-IO/process info while a debug step
5
+ # is still running, so a hung or slow step is visible even before it produces
6
+ # output of its own.
6
7
  class ResourceMonitor
7
8
  def initialize(interval: 5, out: $stderr)
8
9
  @interval = interval
9
10
  @out = out
11
+ @last_disk = disk_sectors
12
+ @last_sampled_at = monotonic
10
13
  end
11
14
 
12
15
  def start(label)
13
16
  @thread = Thread.new do
14
17
  loop do
15
18
  sleep @interval
16
- @out.puts "wip: [debug] still running (#{load_average} | #{memory} | top: #{top_processes}): #{label}"
19
+ @out.puts "wip: [debug] still running (#{snapshot}): #{label}"
17
20
  end
18
21
  end
19
22
  end
@@ -25,6 +28,12 @@ module Wip
25
28
 
26
29
  private
27
30
 
31
+ def monotonic = Process.clock_gettime(Process::CLOCK_MONOTONIC)
32
+
33
+ def snapshot
34
+ [load_average, memory, disk_io, top_processes].join(' | ')
35
+ end
36
+
28
37
  def load_average
29
38
  "load #{File.read('/proc/loadavg').split[0..2].join(' ')}"
30
39
  rescue Errno::ENOENT, IOError
@@ -40,15 +49,46 @@ module Wip
40
49
  'mem n/a'
41
50
  end
42
51
 
52
+ # WSL2's bind-mounted (9p) volumes are often the real bottleneck behind a
53
+ # slow `bundle`/`rails` boot, and that shows up here rather than in CPU.
54
+ def disk_io
55
+ now = monotonic
56
+ elapsed = [now - @last_sampled_at, 0.001].max
57
+ current = disk_sectors
58
+ read_kbps = (current[:read] - @last_disk[:read]) * 0.5 / elapsed
59
+ write_kbps = (current[:write] - @last_disk[:write]) * 0.5 / elapsed
60
+ @last_disk = current
61
+ @last_sampled_at = now
62
+ format('io read %<read>.0fKB/s write %<write>.0fKB/s', read: read_kbps, write: write_kbps)
63
+ rescue Errno::ENOENT, IOError
64
+ 'io n/a'
65
+ end
66
+
67
+ def disk_sectors
68
+ totals = { read: 0, write: 0 }
69
+ File.readlines('/proc/diskstats').each do |line|
70
+ fields = line.split
71
+ next if fields[2].to_s.match?(/\A(loop|ram)/)
72
+
73
+ totals[:read] += fields[5].to_i
74
+ totals[:write] += fields[9].to_i
75
+ end
76
+ totals
77
+ rescue Errno::ENOENT, IOError
78
+ { read: 0, write: 0 }
79
+ end
80
+
43
81
  def top_processes
44
- `ps -eo pid,pcpu,pmem,comm --sort=-pcpu 2>/dev/null`.lines.drop(1).first(3).filter_map do |line|
82
+ lines = `ps -eo pid,pcpu,pmem,comm --sort=-pcpu 2>/dev/null`.lines.drop(1).first(3)
83
+ entries = lines.filter_map do |line|
45
84
  pid, cpu, mem, comm = line.split(nil, 4)
46
85
  next unless comm
47
86
 
48
87
  "#{comm.strip}(#{pid}) cpu #{cpu}%/mem #{mem}%"
49
- end.join(', ')
88
+ end
89
+ "top: #{entries.join(', ')}"
50
90
  rescue StandardError
51
- 'n/a'
91
+ 'top: n/a'
52
92
  end
53
93
  end
54
94
  end
data/lib/wip/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Wip
4
- VERSION = '0.4.2'
4
+ VERSION = '0.4.3'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wslc-wip
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: 0.4.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wip contributors