procfs_rb 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/.opencode.json +11 -0
- data/AGENTS.md +35 -0
- data/CONTRIBUTING.md +39 -0
- data/LICENSE.txt +21 -0
- data/README.md +104 -0
- data/Rakefile +13 -0
- data/lib/procfs_rb/proc_fs/parser/net_tcp_parser.rb +65 -0
- data/lib/procfs_rb/proc_fs/parser/parser_interface.rb +11 -0
- data/lib/procfs_rb/proc_fs/proc_fs_root.rb +13 -0
- data/lib/procfs_rb/proc_fs/state/net_tcp.rb +94 -0
- data/lib/procfs_rb/proc_fs/state/snapshot.rb +15 -0
- data/lib/procfs_rb/proc_fs/state/tcp_socket.rb +112 -0
- data/lib/procfs_rb/proc_fs/state/timestamped.rb +19 -0
- data/lib/procfs_rb/proc_fs.rb +9 -0
- data/lib/procfs_rb/version.rb +5 -0
- data/lib/procfs_rb.rb +8 -0
- data/sig/procfs_rb.rbs +4 -0
- metadata +231 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 19b629e77fea8aaca91e9508f6699c0a1aaac45b4cc5bd0eb4c15673fe462225
|
|
4
|
+
data.tar.gz: 3fafda51d24256ba5dc465ff23faa6619867ef30878d1723bc4c54c750441c01
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 29caacfbc2428f324dbbae4c0362f9131e55a2ec6298f5e51d29e5ef38ee64774d22d8bffe4177d1b9f00ea44095548f2cd8a9681aca4f0e73077d3bb0a208ff
|
|
7
|
+
data.tar.gz: b15568d0cd9b6282838f9bb07172b1d7084677da80cf4bba4d65271c73840a47756ec39fbb4699e67d60e7ce0918473a2a39aa1d182f8d37104ef4bc0fb8257b
|
data/.opencode.json
ADDED
data/AGENTS.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# AI Agent Guidelines for procfs_rb
|
|
2
|
+
|
|
3
|
+
This project is a Ruby GEM designed to provide a programmatic interface for browsing and analyzing the `/proc` filesystem on Linux.
|
|
4
|
+
|
|
5
|
+
## Project Context
|
|
6
|
+
- **Purpose:** Provide an idiomatic Ruby wrapper for Linux procfs.
|
|
7
|
+
- **Tech Stack:**
|
|
8
|
+
- Ruby >= 3.2.0
|
|
9
|
+
- Minitest (Testing)
|
|
10
|
+
- RuboCop (Linting/Styling)
|
|
11
|
+
- RBS (Type signatures)
|
|
12
|
+
- **Hosting:** Primary development and CI/CD on GitLab; mirrored on GitHub.
|
|
13
|
+
|
|
14
|
+
## Coding Standards
|
|
15
|
+
- **Idiomatic Ruby:** Follow the Ruby Style Guide.
|
|
16
|
+
- **Linting:** All code must pass `bundle exec rubocop`.
|
|
17
|
+
- **Typing:** Update `.rbs` files in the `sig/` directory when adding new public methods.
|
|
18
|
+
- **Error Handling:** Do not swallow exceptions; provide meaningful custom error classes for procfs-specific failures (e.g., missing files in `/proc`).
|
|
19
|
+
- **Documentation:** Use YARD style comments for public API methods.
|
|
20
|
+
|
|
21
|
+
## Testing Requirements
|
|
22
|
+
- **Framework:** Minitest.
|
|
23
|
+
- **Coverage:** New features must include tests. Run `rake test` to verify.
|
|
24
|
+
- **Environment:** Tests expect a Linux environment with a populated `/proc` filesystem.
|
|
25
|
+
|
|
26
|
+
## Tooling
|
|
27
|
+
- `rake test`: Run all tests.
|
|
28
|
+
- `bundle exec rubocop`: Run linting.
|
|
29
|
+
- `bundle exec reek`: Run code smell analysis.
|
|
30
|
+
- `bin/console`: Interactive console for exploration.
|
|
31
|
+
|
|
32
|
+
## Workflow
|
|
33
|
+
- **Branching:** Feature Branch workflow.
|
|
34
|
+
- **Commits:** Follow Conventional Commits.
|
|
35
|
+
- **Verification:** Always run linting and tests before proposing a change.
|
data/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Contributing to procfs_rb
|
|
2
|
+
|
|
3
|
+
Thank you for contributing to `procfs_rb`! This gem provides a Ruby interface to the Linux `/proc` filesystem.
|
|
4
|
+
|
|
5
|
+
## Development Workflow
|
|
6
|
+
|
|
7
|
+
### 1. Setup
|
|
8
|
+
Ensure you are on a Linux system. Install dependencies:
|
|
9
|
+
```bash
|
|
10
|
+
bundle install
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### 2. Feature Branching
|
|
14
|
+
We use a feature branch workflow. Please create a new branch for every change:
|
|
15
|
+
- `feat/description` for new features.
|
|
16
|
+
- `fix/description` for bug fixes.
|
|
17
|
+
- `docs/description` for documentation updates.
|
|
18
|
+
|
|
19
|
+
### 3. Coding Standards
|
|
20
|
+
- **Style:** We use RuboCop for linting. Run `bundle exec rubocop` to check your code.
|
|
21
|
+
- **Types:** We use RBS for type signatures. Please update `sig/procfs_rb.rbs` accordingly.
|
|
22
|
+
- **Commits:** We follow [Conventional Commits](https://www.conventionalcommits.org/).
|
|
23
|
+
- Example: `feat: add memory info parsing` or `fix: resolve nil value in cpu_stats`
|
|
24
|
+
|
|
25
|
+
### 4. Testing
|
|
26
|
+
All contributions must include tests.
|
|
27
|
+
- Run tests using: `rake test`
|
|
28
|
+
- Ensure that your changes do not decrease test coverage.
|
|
29
|
+
|
|
30
|
+
### 5. Submission
|
|
31
|
+
1. Push your branch to GitLab.
|
|
32
|
+
2. Create a Merge Request (MR) on GitLab (the primary repository).
|
|
33
|
+
3. Provide a clear description of the change and the motivation behind it.
|
|
34
|
+
|
|
35
|
+
## Repository Structure
|
|
36
|
+
- `lib/`: Core logic.
|
|
37
|
+
- `sig/`: RBS type signatures.
|
|
38
|
+
- `test/`: Minitest suite.
|
|
39
|
+
- `bin/`: Helper scripts.
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Thomas Tych
|
|
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,104 @@
|
|
|
1
|
+
# ProcfsRb
|
|
2
|
+
|
|
3
|
+
A Ruby interface for browsing and analyzing the Linux `/proc` filesystem.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
Once loaded, it provides ProcFS module.
|
|
8
|
+
|
|
9
|
+
``` ruby
|
|
10
|
+
require 'procfs_rb'
|
|
11
|
+
|
|
12
|
+
procfs = ProcFS.new()
|
|
13
|
+
|
|
14
|
+
# get cpuinfo
|
|
15
|
+
cpuinfo = procfs.cpuinfo
|
|
16
|
+
|
|
17
|
+
# get all sockets
|
|
18
|
+
sockets = procfs.net.tcp.sockets
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## ProcFS
|
|
23
|
+
|
|
24
|
+
### Core
|
|
25
|
+
|
|
26
|
+
* It is Virtual: /proc does not exist on disk. It is an interface to kernel data structures.
|
|
27
|
+
* It is Generated on Read: Files do not "contain" static text. When a read() syscall occurs, the kernel executes a handler function that formats the current state into text on the fly.
|
|
28
|
+
* No Cross-File Atomicity: Because each file is generated independently, reading multiple files sequentially does not guarantee a consistent point-in-time view. State may change between read 1 and read 2.
|
|
29
|
+
|
|
30
|
+
### Node Abstractions
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
| Node Type | Filesystem Representation | Semantic Role | Interface Contract |
|
|
34
|
+
|---------------------|---------------------------|----------------------------------|-------------------------------------------------------------------------------------------------------------|
|
|
35
|
+
| Directory | dr-xr-xr-x | Logical grouping / Namespace. | Yields a list of child nodes. |
|
|
36
|
+
| Observable File | -r--r--r-- | Telemetry / State observation. | Read-only. Returns a parsed, typed data structure. |
|
|
37
|
+
| Control File | -rw-r--r-- | Tunable parameter / Mutation. | Read/Write. Getter returns current value; Setter applies new value and may return success/error. |
|
|
38
|
+
| Reference (Symlink) | lrwxrwxrwx | Resolved relationship / Pointer. | Returns the target path (string) or resolves to the target object (e.g., resolving an FD to a socket/file). |
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
### Implementation rules
|
|
42
|
+
|
|
43
|
+
#### Rule 1: Explicit Snapshot Boundaries
|
|
44
|
+
Because /proc files are generated on the fly, an interface method like get_process_info(pid) should ideally read all required files for that specific request in a single pass, package them into a single immutable struct (a "Snapshot"), and return it.
|
|
45
|
+
|
|
46
|
+
Warning: Document clearly that even a single Snapshot is not strictly atomic across all kernel subsystems.
|
|
47
|
+
|
|
48
|
+
#### Rule 2: Handle Volatility Gracefully (Lifecycle)
|
|
49
|
+
Processes are born and die constantly.
|
|
50
|
+
|
|
51
|
+
A PID listed in a directory scan may cease to exist before its files are read.
|
|
52
|
+
The interface must treat ENOENT (No such file or directory) and ESRCH (No such process) not as fatal errors, but as expected lifecycle events (e.g., returning None, null, or throwing a specific ProcessVanishedException).
|
|
53
|
+
|
|
54
|
+
#### Rule 3: Encapsulate Raw Parsing
|
|
55
|
+
/proc files are notoriously difficult to parse reliably.
|
|
56
|
+
|
|
57
|
+
Example: /proc/<pid>/stat is space-separated, but the second field (comm) is wrapped in parentheses and can contain spaces or closing parentheses itself.
|
|
58
|
+
|
|
59
|
+
The interface must hide raw string parsing behind strongly-typed objects. Consumers should call process.get_memory_usage(), never parse_proc_pid_stat().
|
|
60
|
+
|
|
61
|
+
#### Rule 4: Separate Observation from Mutation
|
|
62
|
+
Do not mix read-only state getters with control setters in the same conceptual API surface without clear naming.
|
|
63
|
+
|
|
64
|
+
Example: process.get_oom_score() (Observation) vs process.set_oom_score_adj(500) (Mutation).
|
|
65
|
+
|
|
66
|
+
## Implementation
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
## Installation
|
|
71
|
+
|
|
72
|
+
Install the gem by executing:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
gem install procfs_rb
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Or add it to your Gemfile:
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
gem 'procfs_rb'
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Usage
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
require 'procfs_rb'
|
|
88
|
+
|
|
89
|
+
# Your usage examples here
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Development
|
|
93
|
+
|
|
94
|
+
Run `bin/setup` to install dependencies, `rake test` to run tests, and `bin/console` for an interactive prompt.
|
|
95
|
+
|
|
96
|
+
To install locally: `bundle exec rake install`.
|
|
97
|
+
|
|
98
|
+
## Contributing
|
|
99
|
+
|
|
100
|
+
Bug reports and pull requests are welcome on GitLab. Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details.
|
|
101
|
+
|
|
102
|
+
## License
|
|
103
|
+
|
|
104
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'parser_interface'
|
|
4
|
+
require 'ipaddr'
|
|
5
|
+
require_relative '../state/net_tcp'
|
|
6
|
+
|
|
7
|
+
module ProcFS
|
|
8
|
+
module Parser
|
|
9
|
+
class NetTcpParser
|
|
10
|
+
DEFAULT_SOURCE = '/proc/net/tcp'
|
|
11
|
+
|
|
12
|
+
include ParserInterface
|
|
13
|
+
|
|
14
|
+
def parse(content, source: DEFAULT_SOURCE)
|
|
15
|
+
read_at = Time.now
|
|
16
|
+
lines = content.lines.drop(1) # Skip the header line
|
|
17
|
+
|
|
18
|
+
sockets = lines.map { |line| parse_socket_line(line) }.compact
|
|
19
|
+
|
|
20
|
+
State::NetTcp.new(
|
|
21
|
+
sockets: sockets,
|
|
22
|
+
timestamp: read_at,
|
|
23
|
+
source: source
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def parse_socket_line(line)
|
|
30
|
+
parts = line.split
|
|
31
|
+
return nil if parts.empty?
|
|
32
|
+
|
|
33
|
+
local_ip, local_port = parse_address(parts[1])
|
|
34
|
+
remote_ip, remote_port = parse_address(parts[2])
|
|
35
|
+
|
|
36
|
+
tx_hex, rx_hex = parts[4].split(':')
|
|
37
|
+
|
|
38
|
+
State::TcpSocket.new(
|
|
39
|
+
local_address: local_ip,
|
|
40
|
+
local_port: local_port,
|
|
41
|
+
remote_address: remote_ip,
|
|
42
|
+
remote_port: remote_port,
|
|
43
|
+
connection_state: State::TcpSocket::STATES.fetch(parts[3], :unknown),
|
|
44
|
+
tx_queue: tx_hex.to_i(16),
|
|
45
|
+
rx_queue: rx_hex.to_i(16),
|
|
46
|
+
uid: parts[7].to_i,
|
|
47
|
+
inode: parts[9].to_i
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Parses "0100007F:0050" (little-endian IPv4 hex + port hex)
|
|
52
|
+
# into [IPAddr, Integer]
|
|
53
|
+
def parse_address(hex_addr)
|
|
54
|
+
ip_hex, port_hex = hex_addr.split(':')
|
|
55
|
+
|
|
56
|
+
# IPv4: 8 hex chars, stored in little-endian 32-bit
|
|
57
|
+
ip_int = ip_hex.to_i(16)
|
|
58
|
+
ip_bytes = [ip_int].pack('V').unpack('C4')
|
|
59
|
+
ip_string = ip_bytes.join('.')
|
|
60
|
+
|
|
61
|
+
[IPAddr.new(ip_string), port_hex.to_i(16)]
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require_relative 'snapshot'
|
|
5
|
+
require_relative 'timestamped'
|
|
6
|
+
require_relative 'tcp_socket'
|
|
7
|
+
|
|
8
|
+
module ProcFS
|
|
9
|
+
module State
|
|
10
|
+
class NetTcp
|
|
11
|
+
include Snapshot
|
|
12
|
+
include Timestamped
|
|
13
|
+
include Enumerable
|
|
14
|
+
|
|
15
|
+
MEMBERS = %i[sockets timestamp source].freeze
|
|
16
|
+
|
|
17
|
+
attr_reader(*MEMBERS)
|
|
18
|
+
|
|
19
|
+
def initialize(sockets:, timestamp: Time.now, source: '/proc/net/tcp')
|
|
20
|
+
@sockets = sockets
|
|
21
|
+
@timestamp = timestamp
|
|
22
|
+
@source = source
|
|
23
|
+
freeze
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def members
|
|
27
|
+
MEMBERS
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def to_h
|
|
31
|
+
members.to_h { |m| [m, send(m)] }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def with(**changes)
|
|
35
|
+
self.class.new(**to_h, **changes)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def ==(other)
|
|
39
|
+
other.is_a?(self.class) && to_h == other.to_h
|
|
40
|
+
end
|
|
41
|
+
alias eql? ==
|
|
42
|
+
|
|
43
|
+
def hash
|
|
44
|
+
[self.class, to_h].hash
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def to_s
|
|
48
|
+
"TcpInfo(#{sockets.size} sockets @ #{timestamp.strftime('%H:%M:%S.%3N')})"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def inspect
|
|
52
|
+
"#<ProcFS::State::TcpInfo source=#{source.inspect} " \
|
|
53
|
+
"count=#{sockets.size} timestamp=#{timestamp.iso8601(3)}>"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def each(&)
|
|
57
|
+
sockets.each(&)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def listening
|
|
61
|
+
sockets.select(&:listening?)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def established
|
|
65
|
+
sockets.select(&:established?)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def closed
|
|
69
|
+
sockets.select(&:closed?)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def on_port(port)
|
|
73
|
+
sockets.select { |s| s.local_port == port || s.remote_port == port }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def bound_on(address)
|
|
77
|
+
addr = address.is_a?(IPAddr) ? address : IPAddr.new(address)
|
|
78
|
+
sockets.select { |s| s.local_address == addr }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def by_state(state)
|
|
82
|
+
sockets.select { |s| s.connection_state == state }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def empty?
|
|
86
|
+
sockets.empty?
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def size
|
|
90
|
+
sockets.size
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'ipaddr'
|
|
4
|
+
require 'json'
|
|
5
|
+
require_relative 'snapshot'
|
|
6
|
+
|
|
7
|
+
module ProcFS
|
|
8
|
+
module State
|
|
9
|
+
class TcpSocket
|
|
10
|
+
include Snapshot
|
|
11
|
+
|
|
12
|
+
# TCP state constants (from linux/tcp_states.h)
|
|
13
|
+
STATES = {
|
|
14
|
+
'01' => :established,
|
|
15
|
+
'02' => :syn_sent,
|
|
16
|
+
'03' => :syn_recv,
|
|
17
|
+
'04' => :fin_wait1,
|
|
18
|
+
'05' => :fin_wait2,
|
|
19
|
+
'06' => :time_wait,
|
|
20
|
+
'07' => :close,
|
|
21
|
+
'08' => :close_wait,
|
|
22
|
+
'09' => :last_ack,
|
|
23
|
+
'0A' => :listen,
|
|
24
|
+
'0B' => :closing
|
|
25
|
+
}.freeze
|
|
26
|
+
|
|
27
|
+
MEMBERS = %i[
|
|
28
|
+
local_address local_port
|
|
29
|
+
remote_address remote_port
|
|
30
|
+
connection_state
|
|
31
|
+
tx_queue rx_queue
|
|
32
|
+
uid inode
|
|
33
|
+
].freeze
|
|
34
|
+
|
|
35
|
+
attr_reader(*MEMBERS)
|
|
36
|
+
|
|
37
|
+
def initialize(local_address:, local_port:, remote_address:, remote_port:,
|
|
38
|
+
connection_state:, tx_queue:, rx_queue:, uid:, inode:)
|
|
39
|
+
@local_address = local_address
|
|
40
|
+
@local_port = local_port
|
|
41
|
+
@remote_address = remote_address
|
|
42
|
+
@remote_port = remote_port
|
|
43
|
+
@connection_state = connection_state
|
|
44
|
+
@tx_queue = tx_queue
|
|
45
|
+
@rx_queue = rx_queue
|
|
46
|
+
@uid = uid
|
|
47
|
+
@inode = inode
|
|
48
|
+
|
|
49
|
+
freeze
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# --- Value semantics ---
|
|
53
|
+
|
|
54
|
+
def members
|
|
55
|
+
MEMBERS
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def to_h
|
|
59
|
+
members.to_h { |m| [m, send(m)] }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def with(**changes)
|
|
63
|
+
self.class.new(**to_h, **changes)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def ==(other)
|
|
67
|
+
other.is_a?(self.class) && to_h == other.to_h
|
|
68
|
+
end
|
|
69
|
+
alias eql? ==
|
|
70
|
+
|
|
71
|
+
def hash
|
|
72
|
+
[self.class, to_h].hash
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def to_s
|
|
76
|
+
"#{local_address}:#{local_port} -> #{remote_address}:#{remote_port} [#{connection_state}]"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def inspect
|
|
80
|
+
"#<ProcFS::State::TcpSocket #{self} tx=#{tx_queue} rx=#{rx_queue} uid=#{uid} inode=#{inode}>"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def listening?
|
|
84
|
+
connection_state == :listen
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def established?
|
|
88
|
+
connection_state == :established
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def closed?
|
|
92
|
+
%i[close time_wait close_wait last_ack closing].include?(connection_state)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def ipv4?
|
|
96
|
+
local_address.ipv4?
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def ipv6?
|
|
100
|
+
local_address.ipv6?
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def local_endpoint
|
|
104
|
+
"#{local_address}:#{local_port}"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def remote_endpoint
|
|
108
|
+
"#{remote_address}:#{remote_port}"
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ProcFS
|
|
4
|
+
module State
|
|
5
|
+
module Timestamped
|
|
6
|
+
def age
|
|
7
|
+
Time.now - timestamp
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def recent?(within_seconds: 5)
|
|
11
|
+
age < within_seconds
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def stale?(after_seconds: 60)
|
|
15
|
+
age > after_seconds
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/procfs_rb.rb
ADDED
data/sig/procfs_rb.rbs
ADDED
metadata
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: procfs_rb
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Thomas Tych
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: bump
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 0.10.0
|
|
19
|
+
type: :development
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 0.10.0
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: bundler
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: 4.0.21
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: 4.0.21
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: byebug
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '13.0'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '13.0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: irb
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '1.18'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '1.18'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: minitest
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '6.0'
|
|
75
|
+
- - ">="
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: 6.0.6
|
|
78
|
+
type: :development
|
|
79
|
+
prerelease: false
|
|
80
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
81
|
+
requirements:
|
|
82
|
+
- - "~>"
|
|
83
|
+
- !ruby/object:Gem::Version
|
|
84
|
+
version: '6.0'
|
|
85
|
+
- - ">="
|
|
86
|
+
- !ruby/object:Gem::Version
|
|
87
|
+
version: 6.0.6
|
|
88
|
+
- !ruby/object:Gem::Dependency
|
|
89
|
+
name: rake
|
|
90
|
+
requirement: !ruby/object:Gem::Requirement
|
|
91
|
+
requirements:
|
|
92
|
+
- - "~>"
|
|
93
|
+
- !ruby/object:Gem::Version
|
|
94
|
+
version: '13.4'
|
|
95
|
+
- - ">="
|
|
96
|
+
- !ruby/object:Gem::Version
|
|
97
|
+
version: 13.4.2
|
|
98
|
+
type: :development
|
|
99
|
+
prerelease: false
|
|
100
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
101
|
+
requirements:
|
|
102
|
+
- - "~>"
|
|
103
|
+
- !ruby/object:Gem::Version
|
|
104
|
+
version: '13.4'
|
|
105
|
+
- - ">="
|
|
106
|
+
- !ruby/object:Gem::Version
|
|
107
|
+
version: 13.4.2
|
|
108
|
+
- !ruby/object:Gem::Dependency
|
|
109
|
+
name: reek
|
|
110
|
+
requirement: !ruby/object:Gem::Requirement
|
|
111
|
+
requirements:
|
|
112
|
+
- - "~>"
|
|
113
|
+
- !ruby/object:Gem::Version
|
|
114
|
+
version: '6.5'
|
|
115
|
+
type: :development
|
|
116
|
+
prerelease: false
|
|
117
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
118
|
+
requirements:
|
|
119
|
+
- - "~>"
|
|
120
|
+
- !ruby/object:Gem::Version
|
|
121
|
+
version: '6.5'
|
|
122
|
+
- !ruby/object:Gem::Dependency
|
|
123
|
+
name: rubocop
|
|
124
|
+
requirement: !ruby/object:Gem::Requirement
|
|
125
|
+
requirements:
|
|
126
|
+
- - "~>"
|
|
127
|
+
- !ruby/object:Gem::Version
|
|
128
|
+
version: '1.91'
|
|
129
|
+
type: :development
|
|
130
|
+
prerelease: false
|
|
131
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
132
|
+
requirements:
|
|
133
|
+
- - "~>"
|
|
134
|
+
- !ruby/object:Gem::Version
|
|
135
|
+
version: '1.91'
|
|
136
|
+
- !ruby/object:Gem::Dependency
|
|
137
|
+
name: rubocop-minitest
|
|
138
|
+
requirement: !ruby/object:Gem::Requirement
|
|
139
|
+
requirements:
|
|
140
|
+
- - "~>"
|
|
141
|
+
- !ruby/object:Gem::Version
|
|
142
|
+
version: 0.40.0
|
|
143
|
+
type: :development
|
|
144
|
+
prerelease: false
|
|
145
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
146
|
+
requirements:
|
|
147
|
+
- - "~>"
|
|
148
|
+
- !ruby/object:Gem::Version
|
|
149
|
+
version: 0.40.0
|
|
150
|
+
- !ruby/object:Gem::Dependency
|
|
151
|
+
name: rubocop-rake
|
|
152
|
+
requirement: !ruby/object:Gem::Requirement
|
|
153
|
+
requirements:
|
|
154
|
+
- - "~>"
|
|
155
|
+
- !ruby/object:Gem::Version
|
|
156
|
+
version: 0.7.1
|
|
157
|
+
type: :development
|
|
158
|
+
prerelease: false
|
|
159
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
160
|
+
requirements:
|
|
161
|
+
- - "~>"
|
|
162
|
+
- !ruby/object:Gem::Version
|
|
163
|
+
version: 0.7.1
|
|
164
|
+
- !ruby/object:Gem::Dependency
|
|
165
|
+
name: simplecov
|
|
166
|
+
requirement: !ruby/object:Gem::Requirement
|
|
167
|
+
requirements:
|
|
168
|
+
- - "~>"
|
|
169
|
+
- !ruby/object:Gem::Version
|
|
170
|
+
version: '1.3'
|
|
171
|
+
- - ">="
|
|
172
|
+
- !ruby/object:Gem::Version
|
|
173
|
+
version: 1.3.0
|
|
174
|
+
type: :development
|
|
175
|
+
prerelease: false
|
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
177
|
+
requirements:
|
|
178
|
+
- - "~>"
|
|
179
|
+
- !ruby/object:Gem::Version
|
|
180
|
+
version: '1.3'
|
|
181
|
+
- - ">="
|
|
182
|
+
- !ruby/object:Gem::Version
|
|
183
|
+
version: 1.3.0
|
|
184
|
+
email:
|
|
185
|
+
- thomas.tych@gmail.com
|
|
186
|
+
executables: []
|
|
187
|
+
extensions: []
|
|
188
|
+
extra_rdoc_files: []
|
|
189
|
+
files:
|
|
190
|
+
- ".opencode.json"
|
|
191
|
+
- AGENTS.md
|
|
192
|
+
- CONTRIBUTING.md
|
|
193
|
+
- LICENSE.txt
|
|
194
|
+
- README.md
|
|
195
|
+
- Rakefile
|
|
196
|
+
- lib/procfs_rb.rb
|
|
197
|
+
- lib/procfs_rb/proc_fs.rb
|
|
198
|
+
- lib/procfs_rb/proc_fs/parser/net_tcp_parser.rb
|
|
199
|
+
- lib/procfs_rb/proc_fs/parser/parser_interface.rb
|
|
200
|
+
- lib/procfs_rb/proc_fs/proc_fs_root.rb
|
|
201
|
+
- lib/procfs_rb/proc_fs/state/net_tcp.rb
|
|
202
|
+
- lib/procfs_rb/proc_fs/state/snapshot.rb
|
|
203
|
+
- lib/procfs_rb/proc_fs/state/tcp_socket.rb
|
|
204
|
+
- lib/procfs_rb/proc_fs/state/timestamped.rb
|
|
205
|
+
- lib/procfs_rb/version.rb
|
|
206
|
+
- sig/procfs_rb.rbs
|
|
207
|
+
homepage: https://gitlab.com/ttych/procfs_rb
|
|
208
|
+
licenses:
|
|
209
|
+
- MIT
|
|
210
|
+
metadata:
|
|
211
|
+
homepage_uri: https://gitlab.com/ttych/procfs_rb
|
|
212
|
+
source_code_uri: https://gitlab.com/ttych/procfs_rb
|
|
213
|
+
rubygems_mfa_required: 'true'
|
|
214
|
+
rdoc_options: []
|
|
215
|
+
require_paths:
|
|
216
|
+
- lib
|
|
217
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
218
|
+
requirements:
|
|
219
|
+
- - ">="
|
|
220
|
+
- !ruby/object:Gem::Version
|
|
221
|
+
version: 3.2.0
|
|
222
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
223
|
+
requirements:
|
|
224
|
+
- - ">="
|
|
225
|
+
- !ruby/object:Gem::Version
|
|
226
|
+
version: '0'
|
|
227
|
+
requirements: []
|
|
228
|
+
rubygems_version: 4.0.21
|
|
229
|
+
specification_version: 4
|
|
230
|
+
summary: ProcFS interface.
|
|
231
|
+
test_files: []
|