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 +4 -4
- data/README.md +20 -4
- data/lib/wip/cli.rb +3 -2
- data/lib/wip/debug_reporter.rb +35 -3
- data/lib/wip/resource_monitor.rb +46 -6
- data/lib/wip/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: be995c6781c440a7fd84dd3c3bbbddd6799154b59694edce5aa653d80c7d6e53
|
|
4
|
+
data.tar.gz: 7fb1638a125b2bce92212e8ff525a38e31e2d8aa4386e3e74e379420d4df9c91
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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
|
data/lib/wip/debug_reporter.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
data/lib/wip/resource_monitor.rb
CHANGED
|
@@ -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
|
|
5
|
-
# running, so a hung or slow step is visible even before it produces
|
|
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 (#{
|
|
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)
|
|
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
|
|
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