vanken 0.1.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 +7 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +84 -0
- data/docs/filters.md +25 -0
- data/docs/performance.md +113 -0
- data/docs/releases.md +15 -0
- data/docs/upstream.md +32 -0
- data/exe/vanken +5 -0
- data/exe/vanken-capture +6 -0
- data/lib/vanken/app/capture_controller.rb +197 -0
- data/lib/vanken/app/document.rb +305 -0
- data/lib/vanken/app/document_jobs.rb +199 -0
- data/lib/vanken/capture/analyzer.rb +44 -0
- data/lib/vanken/capture/analyzer_process.rb +91 -0
- data/lib/vanken/capture/analyzer_wire.rb +42 -0
- data/lib/vanken/capture/analyzer_worker.rb +153 -0
- data/lib/vanken/capture/control_protocol.rb +32 -0
- data/lib/vanken/capture/filter_worker.rb +180 -0
- data/lib/vanken/capture/helper_main.rb +137 -0
- data/lib/vanken/capture/helper_options.rb +70 -0
- data/lib/vanken/capture/launcher.rb +181 -0
- data/lib/vanken/capture/privileges.rb +24 -0
- data/lib/vanken/capture/receiver.rb +39 -0
- data/lib/vanken/cli.rb +68 -0
- data/lib/vanken/config/log.rb +19 -0
- data/lib/vanken/config/preferences.rb +126 -0
- data/lib/vanken/core/display_filter/compiler.rb +108 -0
- data/lib/vanken/core/display_filter/errors.rb +19 -0
- data/lib/vanken/core/display_filter/field_resolver.rb +86 -0
- data/lib/vanken/core/display_filter/lexer.rb +124 -0
- data/lib/vanken/core/display_filter/parser.rb +157 -0
- data/lib/vanken/core/display_filter/types.rb +176 -0
- data/lib/vanken/core/frame.rb +33 -0
- data/lib/vanken/core/frame_store.rb +179 -0
- data/lib/vanken/core/frame_view.rb +68 -0
- data/lib/vanken/core/stores.rb +192 -0
- data/lib/vanken/errors.rb +9 -0
- data/lib/vanken/gateway/capture_filter.rb +30 -0
- data/lib/vanken/gateway/detail_builder.rb +63 -0
- data/lib/vanken/gateway/dissector.rb +176 -0
- data/lib/vanken/gateway/file_reader.rb +153 -0
- data/lib/vanken/gateway/file_writer.rb +64 -0
- data/lib/vanken/gateway/interfaces.rb +30 -0
- data/lib/vanken/gateway/live_capture.rb +79 -0
- data/lib/vanken/gateway/packet_snapshot.rb +89 -0
- data/lib/vanken/ui/actions.rb +50 -0
- data/lib/vanken/ui/application.rb +205 -0
- data/lib/vanken/ui/dialogs.rb +89 -0
- data/lib/vanken/ui/file_operations.rb +113 -0
- data/lib/vanken/ui/filter_operations.rb +120 -0
- data/lib/vanken/ui/main_view.rb +113 -0
- data/lib/vanken/ui/packet_source.rb +161 -0
- data/lib/vanken/ui/selection.rb +91 -0
- data/lib/vanken/version.rb +5 -0
- data/lib/vanken.rb +17 -0
- data/packaging/README.md +33 -0
- data/sig/generated/vanken/app/capture_controller.rbs +58 -0
- data/sig/generated/vanken/app/document.rbs +117 -0
- data/sig/generated/vanken/app/document_jobs.rbs +27 -0
- data/sig/generated/vanken/capture/analyzer.rbs +11 -0
- data/sig/generated/vanken/capture/analyzer_process.rbs +17 -0
- data/sig/generated/vanken/capture/analyzer_wire.rbs +19 -0
- data/sig/generated/vanken/capture/analyzer_worker.rbs +45 -0
- data/sig/generated/vanken/capture/control_protocol.rbs +17 -0
- data/sig/generated/vanken/capture/filter_worker.rbs +51 -0
- data/sig/generated/vanken/capture/helper_main.rbs +23 -0
- data/sig/generated/vanken/capture/helper_options.rbs +27 -0
- data/sig/generated/vanken/capture/launcher.rbs +60 -0
- data/sig/generated/vanken/capture/privileges.rbs +12 -0
- data/sig/generated/vanken/capture/receiver.rbs +15 -0
- data/sig/generated/vanken/cli.rbs +9 -0
- data/sig/generated/vanken/config/log.rbs +9 -0
- data/sig/generated/vanken/config/preferences.rbs +49 -0
- data/sig/generated/vanken/core/display_filter/compiler.rbs +45 -0
- data/sig/generated/vanken/core/display_filter/errors.rbs +16 -0
- data/sig/generated/vanken/core/display_filter/field_resolver.rbs +48 -0
- data/sig/generated/vanken/core/display_filter/lexer.rbs +49 -0
- data/sig/generated/vanken/core/display_filter/parser.rbs +95 -0
- data/sig/generated/vanken/core/display_filter/types.rbs +45 -0
- data/sig/generated/vanken/core/frame.rbs +63 -0
- data/sig/generated/vanken/core/frame_store.rbs +65 -0
- data/sig/generated/vanken/core/frame_view.rbs +36 -0
- data/sig/generated/vanken/core/stores.rbs +110 -0
- data/sig/generated/vanken/errors.rbs +18 -0
- data/sig/generated/vanken/gateway/capture_filter.rbs +17 -0
- data/sig/generated/vanken/gateway/detail_builder.rbs +21 -0
- data/sig/generated/vanken/gateway/dissector.rbs +113 -0
- data/sig/generated/vanken/gateway/file_reader.rbs +53 -0
- data/sig/generated/vanken/gateway/file_writer.rbs +27 -0
- data/sig/generated/vanken/gateway/interfaces.rbs +16 -0
- data/sig/generated/vanken/gateway/live_capture.rbs +48 -0
- data/sig/generated/vanken/gateway/packet_snapshot.rbs +48 -0
- data/sig/generated/vanken/ui/actions.rbs +9 -0
- data/sig/generated/vanken/ui/application.rbs +85 -0
- data/sig/generated/vanken/ui/dialogs.rbs +21 -0
- data/sig/generated/vanken/ui/file_operations.rbs +25 -0
- data/sig/generated/vanken/ui/filter_operations.rbs +33 -0
- data/sig/generated/vanken/ui/main_view.rbs +21 -0
- data/sig/generated/vanken/ui/packet_source.rbs +40 -0
- data/sig/generated/vanken/ui/selection.rbs +23 -0
- data/sig/generated/vanken/version.rbs +5 -0
- data/sig/vanken.rbs +25 -0
- metadata +192 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 6bfbb1b288a09cb5fb170bed462adb00f4ae86aa21ee62c247e9e2cefe2aaa97
|
|
4
|
+
data.tar.gz: 3f8f754621be04e6fc2a4dac72abae4c10f57a8d3524f018c67825abe18b4e8a
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: bc1c63f6df229fbb85db6e00cd69059faa16a69f91469f5cd8a3d7d6ad7b8e0bcce3068ed31ebb4533c77906188b3448026a6e5a1cfe581a2d1f5d4fb058cce0
|
|
7
|
+
data.tar.gz: 370e198b35f3c4bb080f773b026ae6005c3457bd4412d9be8b13e1ea868c182b0919cf8d4b041325231a44f1ec8bf2d556dc15eb01ba1bbff47893cde91c6d49
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yudai Takada
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Vanken
|
|
2
|
+
|
|
3
|
+
A packet capture and inspection desktop application written in Ruby. Vanken reads pcap and pcapng files, displays a virtual packet list alongside protocol details and bytes, and filters captures with its own display filter language.
|
|
4
|
+
|
|
5
|
+
Packet acquisition runs in a separate helper; ordered packet analysis runs in another unprivileged process. The desktop application runs as a regular user. Raw frames and summaries are spooled to private files; slow filters use worker processes.
|
|
6
|
+
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
- Ruby 3.3 or newer; Ruby 3.4 or 4.0 with YJIT is recommended.
|
|
10
|
+
- Linux with a desktop session, or macOS. File inspection is also available on Windows; live capture uses Linux packet sockets or macOS BPF.
|
|
11
|
+
- Linux desktop dependencies: Vulkan loader and drivers, fonts, and `zenity` for native file dialogs. On Ubuntu: `sudo apt install libvulkan1 mesa-vulkan-drivers fonts-dejavu-core fonts-noto-cjk zenity`.
|
|
12
|
+
|
|
13
|
+
## Run from source
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
git clone https://github.com/ydah/vanken.git
|
|
17
|
+
cd vanken
|
|
18
|
+
bundle install
|
|
19
|
+
bundle exec exe/vanken capture.pcapng
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Initial RubyGems publication is pending. To install a locally built gem:
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
gem build --strict vanken.gemspec
|
|
26
|
+
gem install ./vanken-0.1.0.gem
|
|
27
|
+
vanken capture.pcapng
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Strict gem builds require RubyGems 4.0.16 or newer; older versions reject the pinned redhound prerelease with a recommendation warning.
|
|
31
|
+
|
|
32
|
+
Open a capture from the toolbar, a recent file, or a file drop. Select a packet to inspect its protocol tree and bytes. Selecting a field highlights its bytes; selecting bytes finds the corresponding field. Columns can be sorted, resized, hidden, and reordered. Themes, font size, split positions, columns, and filter history persist between sessions.
|
|
33
|
+
|
|
34
|
+
Enter a display filter and choose Apply, or press Enter in the filter field:
|
|
35
|
+
|
|
36
|
+
```text
|
|
37
|
+
tcp.port == 443
|
|
38
|
+
ip.addr in {192.0.2.0/24, 198.51.100.5}
|
|
39
|
+
tcp.flags.syn == true && tcp.flags.ack == false
|
|
40
|
+
http.host contains "example"
|
|
41
|
+
frame.len > 1000 && !udp
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Vanken display filters (VDF) and acquisition filters (BPF) are separate languages. See [display filters](docs/filters.md) for operators, types, and repeated fields.
|
|
45
|
+
|
|
46
|
+
## Capture
|
|
47
|
+
|
|
48
|
+
Choose Start, select an interface, and optionally set a BPF acquisition filter. Stop preserves captured packets for inspection and saving. Vanken asks before discarding an unsaved capture.
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
bundle exec exe/vanken-capture --list-interfaces
|
|
52
|
+
bundle exec exe/vanken-capture --check --interface lo
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Direct acquisition works when the account already has permission. Privileged launching requires a root-owned installation with a fixed interpreter and dependencies; see [capture helper setup](packaging/README.md). Run the desktop application as a regular user.
|
|
56
|
+
|
|
57
|
+
## Command line
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
bundle exec exe/vanken --headless --read capture.pcap --print-columns --filter 'tcp.port == 443'
|
|
61
|
+
bundle exec exe/vanken --tui capture.pcapng
|
|
62
|
+
bundle exec exe/vanken --headless --smoke
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`--no-yjit` disables default YJIT activation. `--debug` enables debug logging. Preferences use safe YAML in the platform's user configuration directory; logs rotate without retaining raw packet bytes.
|
|
66
|
+
|
|
67
|
+
## Development
|
|
68
|
+
|
|
69
|
+
```sh
|
|
70
|
+
bundle install
|
|
71
|
+
bundle exec rake
|
|
72
|
+
bundle exec rake rbs
|
|
73
|
+
bundle exec ruby script/generate_fixtures.rb --performance
|
|
74
|
+
bundle exec ruby script/benchmark.rb
|
|
75
|
+
script/capture-ci.sh
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
To develop with a sibling Zaniah checkout, set `VANKEN_ZANIAH_PATH=../zaniah` when running Bundler. Production dependencies are redhound `2.0.0.rc2` and Zaniah `~> 0.12.3`, including fixes for growing memory use and repeated style allocations during redraws.
|
|
79
|
+
|
|
80
|
+
See [upstream contracts](docs/upstream.md), [measured performance](docs/performance.md), and [release procedure](docs/releases.md). Version 0.1 implements the file inspection, acquisition, and display filter milestones. Statistics, stream following, coloring, profiles, and later extensions are scheduled for subsequent releases.
|
|
81
|
+
|
|
82
|
+
## License
|
|
83
|
+
|
|
84
|
+
[MIT](LICENSE.txt).
|
data/docs/filters.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Display filters
|
|
2
|
+
|
|
3
|
+
VDF is parsed without evaluating Ruby. Acquisition uses BPF, compiled independently before the helper starts.
|
|
4
|
+
|
|
5
|
+
| Operation | Syntax |
|
|
6
|
+
| --- | --- |
|
|
7
|
+
| Presence | `tcp`, `http.host` |
|
|
8
|
+
| Boolean logic | `!tcp`, `tcp && ip`, `tcp || udp`; `not`, `and`, `or` also work |
|
|
9
|
+
| Comparisons | `==`, `!=`, `~=`, `<`, `<=`, `>`, `>=`; `eq`, `ne`, `any_ne`, `lt`, `le`, `gt`, `ge` also work |
|
|
10
|
+
| Contains | `http.host contains "example"` |
|
|
11
|
+
| Regular expression | `http.host matches "^www[.]"` |
|
|
12
|
+
| Membership and inclusive ranges | `tcp.port in {80, 443, 8000..8080}` |
|
|
13
|
+
| Nonzero bitmask | `tcp.flags & 0x12` |
|
|
14
|
+
|
|
15
|
+
Negation binds before conjunction, which binds before disjunction. Mixing conjunction and disjunction without parentheses produces a warning. Syntax errors identify the input position. Unknown fields warn and behave as missing fields.
|
|
16
|
+
|
|
17
|
+
Inputs are bounded to 8192 bytes, 511 tokens, and 64 nested expressions so malformed expressions cannot exhaust a worker stack.
|
|
18
|
+
|
|
19
|
+
Literals include decimal and hexadecimal integers, floats, booleans, IPv4/IPv6 addresses, CIDR prefixes, MAC addresses, quoted strings, and hexadecimal bytes. Strings support escaped quotes, backslashes, and `\xNN` byte escapes. Regular expressions have a 100 ms timeout.
|
|
20
|
+
|
|
21
|
+
Repeated layers and aliases can supply multiple values: `tcp.port` contains source and destination ports; `ip.addr` contains source and destination addresses. `==` succeeds when any value matches, `!=` when no value matches (including a missing field), and `~=` when any value differs. Other comparisons on missing fields are false. A present boolean field whose value is false passes a presence test.
|
|
22
|
+
|
|
23
|
+
Frame metadata, columns, protocol presence, and persisted analysis annotations can be filtered without dissection. Other fields use workers in 10,000-frame batches. Results appear progressively; replacing the expression cancels the preceding scan. Acquisition continues during filtering.
|
|
24
|
+
|
|
25
|
+
Select a protocol field to apply or prepare its expression from the Filter menu, including AND/OR/negation combinations. Preparing changes the input without applying it. Named filters and recent expressions persist.
|
data/docs/performance.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Performance measurements
|
|
2
|
+
|
|
3
|
+
Run the production receiver, analyzer process, display filters, and UI benchmark:
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
bundle exec ruby --yjit script/benchmark.rb 1000000 120 4
|
|
7
|
+
bundle exec ruby --yjit script/native-smoke.rb
|
|
8
|
+
script/capture-ci.sh
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The benchmark arguments are packet count, UI samples, and slow-filter worker count. Zero workers skips the slow filter. Functional failures raise; JSON reports each numeric performance target separately. For a sibling Zaniah checkout, set `VANKEN_ZANIAH_PATH=../zaniah` when installing and running the bundle.
|
|
12
|
+
|
|
13
|
+
## Workload and measurement boundaries
|
|
14
|
+
|
|
15
|
+
The synthetic source yields valid 58-byte Ethernet/IPv4/UDP frames individually, reusing one frozen byte string. Addresses and ports are constant. Receiver timing includes the production disk spool and flushes. Document timing includes reception, ordered stateful analysis in an unprivileged child process, packed IPC batches, catalog updates, and persisted annotations and summaries. The fast filter `udp && udp.port == 54321` matches every frame. The slow filter `ip.ttl == 64` uses four real named `Capture::FilterWorker` processes; timing includes cold worker startup.
|
|
16
|
+
|
|
17
|
+
Retained heap is the difference in `ObjectSpace.memsize_of_all` after GC, divided by frame count. It excludes reserved heap pages, native allocations, child heaps, and filesystem cache. It is not RSS. Actual RSS comes from `ps -o rss= -p PID`. Document analysis samples parent and analyzer RSS every 250 ms and reports their combined peak increment, subtracting parent RSS before ingest and the first child sample. Child startup after that first sample contributes to the increment. The receiver-only benchmark now runs in a separate process so its allocator pages do not inflate the document baseline. A 50-frame warm-up initializes document caches. After-filter RSS is also reported. Slow-filter worker RSS and filesystem cache are excluded; unavailable RSS is `null`, never a passing result.
|
|
18
|
+
|
|
19
|
+
The component UI measurement scrolls a production virtual table with one million logical rows and 20 visible rows. It uses the bundled Abel font, a 1200 × 420 viewport, and 20 warm-up frames. This measurement excludes application chrome and packet row IO.
|
|
20
|
+
|
|
21
|
+
Complete-application measurements use `Application`, `MainView`, real stored packets, system fonts, and a 1280 × 800 viewport. Packet 1 is selected, IPv4 is expanded, and TTL is highlighted in both details and bytes. After 100 warm-up frames, the benchmark measures 120 renders. The growing scenario releases 25,000 additional frames at 5,000 frames/s, follows the packet-list tail, and verifies actual source progress during sampling. Counts are checked after the whole capture drains.
|
|
22
|
+
|
|
23
|
+
`render(present: false)` includes layout, prepaint, scene paint, glyph shaping, and accessibility updates. Scene timing ends after paint and is reported separately, matching the design's `Inspection.snapshot(window).frame` criterion. Headless measurements exclude pixel rasterization, native events, and GPU presentation. Native smoke reports both scene time and total tick time, including presentation.
|
|
24
|
+
|
|
25
|
+
## Recorded arm64 results
|
|
26
|
+
|
|
27
|
+
Measured on 2026-10-02 using Ruby 4.0.6, YJIT, `arm64-darwin25`, and Zaniah 0.12.0. These are individual workstation runs, not isolated-machine medians. Receiver, analysis, memory, and fast-filter figures below come from fresh processes after removing the duplicate in-memory frame index. Slow-filter and UI figures come from the earlier complete benchmark; a six-second focused test overlapped the start of that run, but not its UI phases.
|
|
28
|
+
|
|
29
|
+
| Measurement | Result | Target |
|
|
30
|
+
| --- | ---: | ---: |
|
|
31
|
+
| Receiver, 1,000,000 frames | 380,823 frames/s | ≥ 100,000 |
|
|
32
|
+
| Ingest and analyze, 1,000,000 frames | 17,012 frames/s; 58.7827 s | ≥ 15,000 |
|
|
33
|
+
| Fast filter, 1,000,000 matches | 0.5473 s | ≤ 3 s |
|
|
34
|
+
| Slow filter, four workers, 1,000,000 matches | 5.2345 s | ≤ 60 s |
|
|
35
|
+
| Retained parent heap before/after fast filter | 67.22 / 75.23 B per frame | Heap proxy only |
|
|
36
|
+
| Parent RSS increment after analysis | 183.68 B per frame | ≤ 200 |
|
|
37
|
+
| Sampled parent-plus-analyzer RSS increment | 186.27 B per frame | ≤ 200 |
|
|
38
|
+
| Parent RSS increment after fast filtering | 209.88 B per frame | ≤ 200; exceeded |
|
|
39
|
+
| Virtual table, render p95 | 15.577 ms | ≤ 33 ms |
|
|
40
|
+
| Virtual table during ingestion, render p95 | 13.880 ms | ≤ 33 ms |
|
|
41
|
+
| Complete application, scene p95 | 29.186 ms | ≤ 33 ms |
|
|
42
|
+
| Complete application during ingestion, scene p95 | 30.373 ms | ≤ 33 ms |
|
|
43
|
+
| Complete application during ingestion, total render p95 | 31.479 ms | Reported separately |
|
|
44
|
+
|
|
45
|
+
All 120 growing-application samples overlapped actual traffic: 13,200 frames were produced in 2.6471 seconds (4,986.62 frames/s), while analyzed rows grew from 512 to 13,568. All 25,256 frames, including the initial 256, completed in 5.0219 seconds without loss.
|
|
46
|
+
|
|
47
|
+
A separate native 120-sample run produced 17,103 frames in 3.4840 seconds (4,909 frames/s, within the 256-frame pacing tolerance). Durable rows grew from 768 to 17,920 and analyzed rows from 768 to 7,357 during sampling. Scene p95 was 28.862 ms; total native tick p95 was 44.518 ms. The complete 25,000-frame capture drained without loss. This short run demonstrates scene responsiveness, but does not establish sustained backlog limits.
|
|
48
|
+
|
|
49
|
+
The former in-process analyzer exceeded 33 ms during correctly paced native ingestion. The default analyzer now runs in a separate unprivileged Ruby process, as required by the design's alternative. The child preserves analysis order and state, sends bounded batches through private pipes, and retains no complete second column store. The UI reads cached rows asynchronously.
|
|
50
|
+
|
|
51
|
+
The fresh parent RSS baseline was 48,922,624 bytes, rising to 232,603,648 bytes after analysis and 258,801,664 after filtering. Frame metadata now stays on disk, removing approximately 33.55 MB of retained heap for one million frames. IPC reserves its exact payload size and reuses one read buffer; fast port predicates read packed fields directly, and metadata is fetched only when a filter uses it. Historical filter results reserve their known maximum array capacity once. The filter added 8.01 MB of retained heap but 26.20 MB of RSS; GC pages and JIT code explain only part of the difference. The additional after-filter RSS target remains exceeded on this macOS run.
|
|
52
|
+
|
|
53
|
+
## Recorded x86_64 results
|
|
54
|
+
|
|
55
|
+
The Linux shared runner used Ruby 3.4.10 with YJIT and public Zaniah 0.12.1. [The complete benchmark and 5,000 deterministic fuzz cases](https://github.com/ydah/vanken/actions/runs/36975615332), at Vanken commit `5de8854`, finished without functional errors. These measurements precede the visible-row batching change. Numeric targets are reported separately from job success.
|
|
56
|
+
|
|
57
|
+
| Measurement | Result | Target |
|
|
58
|
+
| --- | ---: | ---: |
|
|
59
|
+
| Receiver, 1,000,000 frames | 219,407 frames/s | ≥ 100,000 |
|
|
60
|
+
| Ingest and analyze, 1,000,000 frames | 4,849 frames/s; 206.2345 s | ≥ 15,000; missed |
|
|
61
|
+
| Parent / combined / after-filter RSS increment | 81.56 / 85.64 / 92.03 B per frame | ≤ 200 |
|
|
62
|
+
| Fast / four-worker slow filter | 1.8402 / 21.0254 s | ≤ 3 / 60 s |
|
|
63
|
+
| Static full application, total / scene render p95 | 67.705 / 66.455 ms | ≤ 33 ms; missed |
|
|
64
|
+
| Growing full application, scene p95 | 67.410 ms | ≤ 33 ms; missed |
|
|
65
|
+
|
|
66
|
+
The synthetic growing source reached only 312.58 frames/s during sampling, so this run does not demonstrate responsiveness at 5,000 frames/s. An independent [native run](https://github.com/ydah/vanken/actions/runs/36979552426), at commit `8fcce51` after visible-row batching, delivered 4,972.28 frames/s within the pacing allowance. Active scene p95 was 53.057 ms, missing the target; all 25,256 frames completed without loss. Before the focus fix, the corresponding shared-runner scene p95 measurements were 331.801 ms for the growing application and 230.321 ms for native ingestion.
|
|
67
|
+
|
|
68
|
+
Investigation reproduced a Zaniah focus-tree retention bug: a three-row tree retained 18 row handles after six renders. The fix included in Zaniah 0.12.1 releases render-created parent links between frames while preserving manual hierarchies. The same Linux arm64 container, Ruby 3.4.11 with YJIT, UID 1000, fonts, 4,096-packet document, 100 warm renders, and 120 scrolling samples were measured before and after changing only the Dispatcher implementation:
|
|
69
|
+
|
|
70
|
+
| Measurement | Before fix | After fix |
|
|
71
|
+
| --- | ---: | ---: |
|
|
72
|
+
| Total render p95 | 170.473 ms | 23.626 ms |
|
|
73
|
+
| Scene p95 | 170.023 ms | 22.585 ms |
|
|
74
|
+
| Maximum total render | 207.739 ms | 26.533 ms |
|
|
75
|
+
| GC p95 | 148.697 ms | 6.289 ms |
|
|
76
|
+
| Major GC during 120 samples | 15 | 0 |
|
|
77
|
+
| Samples exceeding 33 ms | 43 | 0 |
|
|
78
|
+
|
|
79
|
+
Per-frame allocations remained approximately 84,600 objects, supporting retained old rows as the cause of expensive GC. The three-row reproduction now retained exactly three current handles after each render, with no obsolete handles. This static arm64 diagnostic establishes the fix's effect; it does not substitute for x86_64 or sustained live-capture verification.
|
|
80
|
+
|
|
81
|
+
Zaniah 0.12.2 avoids rebuilding default style values during merging; 0.12.3 removes a duplicate style merge from element updates. A separate macOS arm64 diagnostic used the actual 256-frame MainView, selected IPv4 TTL, the real text renderer, 40 warm renders, and 20 samples per variant. Applying and reverting each exact implementation changed allocations per render from 71,333 to 67,083 and back, then from 67,083 to 63,918 and back. The combined reduction was 10.4%. Nested element bounds and raw scene commands matched in both directions. Timing varied between runs, so this diagnostic establishes allocation savings, not a stable latency improvement or a passing live-capture target.
|
|
82
|
+
|
|
83
|
+
## Linux capture and operation latency
|
|
84
|
+
|
|
85
|
+
`script/capture-ci.sh` uses an isolated Linux network namespace, a root-owned capture helper, and a UID 1000 UI process. It sends 1,500,000 numbered UDP datagrams at 5,000 frames/s for 300 seconds. The helper uses an incoming packet socket and `udp dst port 54321`; a UDP sink prevents ICMP responses. After shutdown and drain, sent, kernel received, helper captured, durable, and analyzed counts must agree, and dropped, interface-dropped, and freeze counts must all be zero.
|
|
86
|
+
|
|
87
|
+
The live UI follows the production `Application.run` loop: drain foreground work, tick the window, then wait for work when the window is clean. Only actual rendered frames enter latency samples. Document growth is checked between rendered frames during the independently measured sender interval. Forced redraws remain in the component and static-render benchmarks above. A [previous five-minute forced-redraw stress run](https://github.com/ydah/vanken/actions/runs/36977817521), at commit `19b5fe1`, timed out with 1,485,655 kernel drops; it does not establish production-loop capture integrity.
|
|
88
|
+
|
|
89
|
+
The [first production-loop five-minute run](https://github.com/ydah/vanken/actions/runs/36979552791), at commit `8fcce51`, also failed. The sender delivered 1,500,000 frames at 4,999.9985 frames/s; 722,788 were captured and durable, 777,212 were dropped by the kernel, and 645,120 were analyzed when the 390-second deadline expired. The first file row took 735.646 ms, while the slowest selection took 147.265 ms. These results must not be treated as a passing live-capture or selection-latency gate.
|
|
90
|
+
|
|
91
|
+
After publishing tail counts together with prepared rows, the [next run](https://github.com/ydah/vanken/actions/runs/36981466284), at commit `a0215b4` with Zaniah 0.12.1, received, captured, and durably stored all 1,500,000 frames at 4,999.9986 frames/s. Kernel drops, interface drops, and freezes were all zero. Analysis reached 974,080 frames when the former 390-second deadline expired, so final analysis equality remained unverified in that run. Full-scene p95 across its 2,070 actual renders was 112.871 ms; this includes post-traffic drain frames and exceeds the 33 ms target. The first file row took 752.545 ms and the slowest selection 134.829 ms.
|
|
92
|
+
|
|
93
|
+
The integrity check now permits up to 600 seconds of analysis drain after the scheduled traffic duration, fails after 90 seconds without capture, spool, or analysis progress, and records actual post-traffic analysis time. This separates lossless capture verification from slower shared-runner analysis. Sender pacing, exact final count equality, drop checks, and reported numeric performance targets are unchanged; a long drain is not evidence that throughput or responsiveness targets passed.
|
|
94
|
+
|
|
95
|
+
The [completed integrity run](https://github.com/ydah/vanken/actions/runs/36982911969), at commit `a7f0182`, used Ruby 3.4.11 with YJIT, public Zaniah 0.12.1, and UID/EUID 1000 on the shared x86_64 Linux runner. The sender delivered all 1,500,000 frames over 300.0001 seconds at 4,999.9989 frames/s. Kernel received, helper captured, durable, and final analyzed counts were each exactly 1,500,000; kernel drops, interface drops, and freezes were zero. Analysis completed 301.557 seconds after traffic ended. The maximum sampled analyzer backlog was 864,256 frames. Capture integrity and five-minute sender pacing passed; analysis throughput remains below its target on this runner.
|
|
96
|
+
|
|
97
|
+
During actual traffic, 2,014 frames rendered and 1,992 showed analyzed-row growth. Scene p95 was 117.226 ms, exceeding 33 ms; total render p95 was 119.103 ms. The same run showed the first row of a 100,000,032-byte file in 641.359 ms, passing the one-second target, but its 20 selections had p95 121.997 ms and maximum 134.433 ms, exceeding 50 ms. A successful workflow therefore confirms functional integrity, not every numeric performance target. Shared-runner analysis and UI latency, together with the macOS after-filter RSS excess above, remain documented constraints for the initial release. Follow-up profiling should first address per-row analysis and layout allocations under the same measured workloads.
|
|
98
|
+
|
|
99
|
+
JSON evidence includes actual sender rate, UI scene p95 during traffic, and one-second backlog samples. Kernel pending derives from the helper's statistics timestamp. Helper-to-durable compares that sample with a later GUI count and can be negative; it is not an atomic pipe-depth measurement. Final counts are exact. The workflow uploads evidence even on failure.
|
|
100
|
+
|
|
101
|
+
The same script creates a capture of at least 100 MB, measures the open operation through the render showing its first UDP row, then selects 20 different packets while loading continues. The newly generated file is in the OS page cache; this is not a cold-disk test. Each selection must show both detail and byte panes; the elapsed time ends at the render that produced the verified scene. Subsequent Inspection assertion time is reported separately. Targets are first row ≤ 1 second and every selection ≤ 50 ms. To measure only these operations:
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
bundle exec ruby --yjit -Ilib script/capture-performance.rb --ui-only
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
On macOS arm64, Ruby 4.0.6 with YJIT and public Zaniah 0.12.1, a clean 100,000,032-byte capture containing 1,388,889 frames produced its first row in 300.377 ms, with 512 frames analyzed. Twenty different packet selections had p50 20.482 ms, p95 40.513 ms, and maximum 45.276 ms; the first selection took 40.403 ms. Both the first-row and every-selection targets passed in this single run. Pixel rasterization and native presentation were excluded.
|
|
108
|
+
|
|
109
|
+
The final single-run check with public Zaniah 0.12.3 and the same Ruby, platform, viewport, and clean file workload produced its first row in 287.933 ms, with 256 frames analyzed. Twenty distinct selections had p50 15.728 ms, p95 31.424 ms, and maximum 39.525 ms. Both operation targets passed. The report records the actual installed Zaniah version; this result does not replace the slower shared-Linux-runner measurements above.
|
|
110
|
+
|
|
111
|
+
Before visible-row batching, the same operation failed its first selection at 105.252 ms. A separate diagnostic reproduced a 107.234 ms background queue wait behind 11 row jobs, while actual detail analysis took 2.677 ms and byte reading 0.129 ms. Nine intermediate renders consumed most of that wait. PacketSource now collects visible-row requests after rendering, fetches them in one background job, and publishes their values in one foreground update. The passing run followed this runtime change; the earlier failure remains part of the evidence.
|
|
112
|
+
|
|
113
|
+
The native and synthetic runs above do not substitute for this five-minute Linux capture test. Varied addresses, large TCP flow sets, reassembly, and plugins are outside the repeated-UDP benchmark's scope.
|
data/docs/releases.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Releases
|
|
2
|
+
|
|
3
|
+
Work is committed on main. Release notes and CHANGELOG entries include only user-visible changes. Documentation and maintenance changes alone do not trigger a release.
|
|
4
|
+
|
|
5
|
+
Before tagging, run `bundle exec rake`, verify generated RBS is current, run privileged capture and benchmarks, perform native smoke, and build the gem strictly. Version, tag, gemspec dependencies, and lockfile must agree. Exclude captures, spools, test artifacts, local dependencies, and credentials from the gem.
|
|
6
|
+
|
|
7
|
+
Initial 0.1.0 notes are exactly `Initial release.`. Publication is paused until the owner publishes the first RubyGem and configures trusted publishing. No initial release tag is pushed automatically.
|
|
8
|
+
|
|
9
|
+
For the first publication, run `gem push pkg/vanken-0.1.0.gem`, then configure the publisher identity below. Tagging `v0.1.0` afterwards runs validation and creates the GitHub release; the job skips uploading a version already present on RubyGems.
|
|
10
|
+
|
|
11
|
+
Subsequent `vX.Y.Z` tags trigger `.github/workflows/release.yml`, based on the Canopus release job. It verifies version, runs checks, builds the gem, publishes with RubyGems trusted publishing, and creates GitHub release notes from CHANGELOG.
|
|
12
|
+
|
|
13
|
+
Trusted publisher identity: owner `ydah`, repository `vanken`, workflow `release.yml`, GitHub environment `release`.
|
|
14
|
+
|
|
15
|
+
The design milestones are 0.1 for file inspection, capture, and filters; 0.2 for coloring, search, stream following, statistics, and analysis extensions; and 0.3 for profiles, localization, permission packaging, recovery UI, and additional capture conveniences. Work stops at the initial publication gate before the later milestones.
|
data/docs/upstream.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Architecture and dependency contracts
|
|
2
|
+
|
|
3
|
+
The architecture test keeps redhound references inside Gateway and the helper, and Zaniah references inside UI. Core stores and VDF do not depend on rendering. App::Document coordinates reception, ordered analysis, filter jobs, selection data, and atomic saves.
|
|
4
|
+
|
|
5
|
+
The receiver writes frames.bin and frames.idx. A separate unprivileged Ruby process reads durable records and performs ordered, stateful analysis once per frame. Private pipes return packed batches of up to 256 frames; the document writes columns, annotations, summaries, and reassembled details before publishing the batch count. Filter changes reevaluate the current batch without repeating stateful analysis. Reception flushes every 256 frames or 50 ms, including idle periods. Final flush and analyzer drain retain the complete tail. UI notifications are limited to ten per second. Detail dissection, row reads, sorting, saving, and scanning run outside UI callbacks.
|
|
6
|
+
|
|
7
|
+
The disk index uses 32 bytes per frame: `Q<L<L<q<S<S<Cx3` packs offset, captured length, original length, nanosecond timestamp, link type, interface index, direction, and reserved bytes. A separate read handle accesses durable records without retaining a second index in memory or moving the writer. Recovery scans one record at a time. Columns and annotations use compact 32-byte records and intern strings. Summaries and their 8-byte offsets are stored on disk; ordinary details are lazy. Reassembled details preserve the original analysis result. Spool directories use mode 0700, files 0600. Recovery rejects symlinks and incomplete records.
|
|
8
|
+
|
|
9
|
+
Slow scans use a named ProcessPool handler with JSON-compatible immutable request data and a read-only spool reader. Workers do not run stateful analysis or modify the spool. Cancellation and generation checks stop old jobs from replacing current results.
|
|
10
|
+
|
|
11
|
+
## redhound 2.0.0.rc2
|
|
12
|
+
|
|
13
|
+
| Contract | Adapter and checks |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| `Redhound.open`, `next_packet(timeout:)`, `stopped?`, `stop`, `close` | FileReader; pcap/pcapng nanosecond round trips |
|
|
16
|
+
| `Redhound.dissect`, private registry copy, engine assignment | Dissector; decoding and field catalogs |
|
|
17
|
+
| Packet layers, field_values, summary, meta; fields, diagnostics, offsets | PacketView and DetailBuilder; stable IDs, escaped literals, repeated fields |
|
|
18
|
+
| `Analysis::Session.new(stats: [], max_state_bytes:, max_flows:)`, update, finish | Analyzer; stream, budget, relative sequence, expert annotations |
|
|
19
|
+
| Capture::Interface, writers, Capture::Stats | FileWriter; interface preservation and mixed-link pcap rejection |
|
|
20
|
+
| Capture.interfaces, Capture.open, BPF compiler/verifier | Interfaces, LiveCapture, CaptureFilter; privileged integration |
|
|
21
|
+
|
|
22
|
+
Regular files use the upstream reader directly. For pipes, FileReader supplies a buffered binary IO object to public `Redhound.open`, without replacing its parser or using private upstream classes. The adapter reads up to 64 KiB and waits only when the pipe has no data. One worker preserves partial records across consumer timeouts; its queue is limited to 256 packets and 64 MiB of packet payload. The parser can retain one additional packet of up to 16 MiB while waiting for that budget. EOF is published after queued packets drain. Stop wakes both IO and queue waits, joins the worker, and leaves caller-owned IO open.
|
|
23
|
+
|
|
24
|
+
IP reassembly exposes a logical packet through `meta[:reassembled_packet]`. TCP analysis exposes fields and logical protocol details, but no stable complete TCP byte-buffer API is used. Logical fields are labeled reassembled and do not point into unrelated original-frame bytes. Separate reassembled byte tabs await a supported upstream contract.
|
|
25
|
+
|
|
26
|
+
Helper stdout contains pcapng only; stderr contains versioned JSONL hello, started, stats, warning, error, and stopped events. SIGTERM and parent stdin EOF request orderly termination; signal handlers only change flags. Privileged launch checks root-owned wrappers and ancestors, uses argument arrays, and strips Ruby startup/dependency injection from inherited environment variables.
|
|
27
|
+
|
|
28
|
+
## Zaniah 0.12
|
|
29
|
+
|
|
30
|
+
Vanken uses upstream mutable lists, VirtualTable, TreeView, HexView, token completion, validation states, SplitPane, menus/actions, native dialogs, and ProcessPool. No duplicated incubator implementation remains. The upstream ADR and component docs cover resizing, tail following, TUI representation, and accessibility.
|
|
31
|
+
|
|
32
|
+
Upgrades require contract tests, then the full suite, types, privileged capture, benchmarks, and native smoke. Changes to private packet/session APIs belong inside Gateway.
|
data/exe/vanken
ADDED
data/exe/vanken-capture
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../errors"
|
|
4
|
+
require_relative "../capture/launcher"
|
|
5
|
+
require_relative "../capture/control_protocol"
|
|
6
|
+
require_relative "../gateway/file_reader"
|
|
7
|
+
require_relative "document"
|
|
8
|
+
|
|
9
|
+
module Vanken
|
|
10
|
+
module App
|
|
11
|
+
class CaptureController
|
|
12
|
+
class Error < Vanken::CaptureError
|
|
13
|
+
attr_reader :code
|
|
14
|
+
|
|
15
|
+
def initialize(message, code: "internal")
|
|
16
|
+
@code = code
|
|
17
|
+
super(message)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def initialize(launcher: nil, preferences: nil, on_update: nil, on_document: nil)
|
|
22
|
+
@preferences, @on_update, @on_document = preferences, on_update, on_document
|
|
23
|
+
@launcher = launcher || Capture::Launcher.new(strategy: preferences&.get("capture.launcher") || :auto)
|
|
24
|
+
@mutex = Mutex.new
|
|
25
|
+
@state, @stats = :idle, {}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def state = @mutex.synchronize { @state }
|
|
29
|
+
def stats = @mutex.synchronize { @stats.dup }
|
|
30
|
+
def error = @mutex.synchronize { @error }
|
|
31
|
+
def warning = @mutex.synchronize { @warning }
|
|
32
|
+
def document = @mutex.synchronize { @document }
|
|
33
|
+
def options = @mutex.synchronize { @options&.dup }
|
|
34
|
+
def capturing? = state == :capturing
|
|
35
|
+
def running? = @mutex.synchronize { !!@worker&.alive? }
|
|
36
|
+
def snapshot = @mutex.synchronize { {state: @state, stats: @stats.dup, error: @error, warning: @warning, document: @document} }
|
|
37
|
+
|
|
38
|
+
def start(options)
|
|
39
|
+
@mutex.synchronize do
|
|
40
|
+
raise Error, "capture controller is closed" if @closed
|
|
41
|
+
raise Error, "capture is already running" if @worker&.alive?
|
|
42
|
+
|
|
43
|
+
@options = if options.is_a?(Hash)
|
|
44
|
+
defaults = @preferences&.get("capture") || {}
|
|
45
|
+
defaults.merge(options.transform_keys(&:to_s)).reject { |key, _| key == "launcher" }
|
|
46
|
+
else
|
|
47
|
+
options.dup
|
|
48
|
+
end
|
|
49
|
+
@options = if @options.is_a?(Hash)
|
|
50
|
+
@options.transform_values { |value| value.is_a?(String) ? value.dup.freeze : value }.freeze
|
|
51
|
+
else
|
|
52
|
+
@options.map { |value| value.is_a?(String) ? value.dup.freeze : value }.freeze
|
|
53
|
+
end
|
|
54
|
+
@state, @stats, @error, @warning = :starting, {}, nil, nil
|
|
55
|
+
@stop_requested, @started, @stopped_event, @handle, @stopper = false, false, false, nil, nil
|
|
56
|
+
@worker = thread { run }
|
|
57
|
+
end
|
|
58
|
+
notify
|
|
59
|
+
self
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def stop
|
|
63
|
+
changed = @mutex.synchronize do
|
|
64
|
+
next false unless @worker&.alive?
|
|
65
|
+
|
|
66
|
+
@stop_requested = true
|
|
67
|
+
@state = :stopping unless @state == :failed
|
|
68
|
+
stop_helper
|
|
69
|
+
true
|
|
70
|
+
end
|
|
71
|
+
notify if changed
|
|
72
|
+
self
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def wait(timeout = nil, **options)
|
|
76
|
+
timeout = options.fetch(:timeout, timeout)
|
|
77
|
+
worker = @mutex.synchronize { @worker }
|
|
78
|
+
!worker || worker.join(timeout) ? self : nil
|
|
79
|
+
end
|
|
80
|
+
alias await wait
|
|
81
|
+
|
|
82
|
+
def close
|
|
83
|
+
@mutex.synchronize { @closed = true }
|
|
84
|
+
stop.wait
|
|
85
|
+
document&.close
|
|
86
|
+
self
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
def run
|
|
92
|
+
handle = @launcher.launch(@options)
|
|
93
|
+
@mutex.synchronize do
|
|
94
|
+
@handle = handle
|
|
95
|
+
stop_helper if @stop_requested
|
|
96
|
+
end
|
|
97
|
+
control = thread { consume_control(handle.stderr) }
|
|
98
|
+
doc = Document.new(preferences: @preferences, on_update: @on_update)
|
|
99
|
+
@mutex.synchronize { @document = doc }
|
|
100
|
+
@on_document&.call(doc)
|
|
101
|
+
reader = Gateway::FileReader.new(handle.stdout)
|
|
102
|
+
doc.ingest(reader, live: true)
|
|
103
|
+
doc.wait
|
|
104
|
+
unless control.join(3)
|
|
105
|
+
fail_capture(Error.new("capture stdout closed before the helper stopped"))
|
|
106
|
+
stop
|
|
107
|
+
control.join
|
|
108
|
+
end
|
|
109
|
+
status = handle.wait(timeout: 3) || handle.stop(timeout: 3)
|
|
110
|
+
raise Error, doc.error.message if doc.error
|
|
111
|
+
unless @mutex.synchronize { @error || (@started && @stopped_event && status&.success?) }
|
|
112
|
+
raise Error, "capture helper closed without a stopped message"
|
|
113
|
+
end
|
|
114
|
+
completed = true
|
|
115
|
+
rescue StandardError => error
|
|
116
|
+
fail_capture(error)
|
|
117
|
+
ensure
|
|
118
|
+
if handle
|
|
119
|
+
@mutex.synchronize { stop_helper } unless handle.status
|
|
120
|
+
@stopper&.join
|
|
121
|
+
end
|
|
122
|
+
control&.join
|
|
123
|
+
doc&.wait
|
|
124
|
+
reader&.close unless doc
|
|
125
|
+
handle&.close
|
|
126
|
+
@mutex.synchronize do
|
|
127
|
+
@handle = nil
|
|
128
|
+
@state = @error || !completed ? :failed : :stopped
|
|
129
|
+
end
|
|
130
|
+
notify
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def consume_control(io)
|
|
134
|
+
while (line = io.gets(Capture::ControlProtocol::MAX_LINE_BYTES + 1))
|
|
135
|
+
event = Capture::ControlProtocol.parse(line)
|
|
136
|
+
next unless event
|
|
137
|
+
|
|
138
|
+
case event["type"]
|
|
139
|
+
when "started"
|
|
140
|
+
@mutex.synchronize do
|
|
141
|
+
@started = true
|
|
142
|
+
@state = :capturing unless @stop_requested || @error
|
|
143
|
+
end
|
|
144
|
+
when "stats"
|
|
145
|
+
@mutex.synchronize { @stats = event.reject { |key, _| %w[v type].include?(key) }.transform_keys(&:to_sym) }
|
|
146
|
+
when "warning"
|
|
147
|
+
@mutex.synchronize { @warning = event["message"] }
|
|
148
|
+
when "error"
|
|
149
|
+
if event["fatal"] != false
|
|
150
|
+
fail_capture(Error.new(event["message"].to_s, code: event["code"].to_s), replace: true)
|
|
151
|
+
stop
|
|
152
|
+
else
|
|
153
|
+
@mutex.synchronize { @warning = event["message"] }
|
|
154
|
+
end
|
|
155
|
+
when "stopped"
|
|
156
|
+
@mutex.synchronize do
|
|
157
|
+
@stopped_event = true
|
|
158
|
+
@stats = event["stats"].transform_keys(&:to_sym) if event["stats"].is_a?(Hash)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
notify
|
|
162
|
+
end
|
|
163
|
+
unless @mutex.synchronize { @stopped_event || @error }
|
|
164
|
+
fail_capture(Error.new("capture control channel closed without a stopped message"))
|
|
165
|
+
stop
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Called under @mutex; the helper stop runs separately so UI callbacks never wait for it.
|
|
170
|
+
def stop_helper
|
|
171
|
+
return unless @handle && !@stopper
|
|
172
|
+
|
|
173
|
+
handle = @handle
|
|
174
|
+
@stopper = thread { handle.stop(timeout: 3) }
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def fail_capture(error, replace: false)
|
|
178
|
+
@mutex.synchronize do
|
|
179
|
+
@error = error.is_a?(Error) ? error : Error.new(error.message) if replace || !@error
|
|
180
|
+
@state = :failed
|
|
181
|
+
end
|
|
182
|
+
notify
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def thread(&block)
|
|
186
|
+
Thread.new do
|
|
187
|
+
Thread.current.report_on_exception = false
|
|
188
|
+
block.call
|
|
189
|
+
rescue StandardError => error
|
|
190
|
+
fail_capture(error)
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def notify = @on_update&.call(self)
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|