io-metrics 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ff485ebbe44dc9bae2117fa771ed89e7b2e42e3270d93094bae2fa4808405a5d
4
+ data.tar.gz: e724a731428b1c7d5219f6abcb518b99cddd572efcf0d9de02eff1d30f90400e
5
+ SHA512:
6
+ metadata.gz: 27ef4c0c0f75b3933bbd7cf6815997bf62d3d3cc05c6dbe0ca9e75a3db143f6fb122153d4d7b967fd2b04781b0c12dc1158a7998178cee6bc954d7c7e576a3b6
7
+ data.tar.gz: c1bfed9052fb9fbf92f25bf9952eb83d3dc0759bb55746cca68068942d772a145c43659888045315b58ba9b7c4b4209ce196889b21d0c0d17c4b7cd6cacff5b4
checksums.yaml.gz.sig ADDED
Binary file
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ class IO
7
+ module Metrics
8
+ # Darwin (macOS) implementation of listener statistics using netstat -L.
9
+ class Listener::Darwin
10
+ NETSTAT = "/usr/sbin/netstat"
11
+
12
+ # Whether listener listeners can be captured on this system.
13
+ def self.supported?
14
+ File.executable?(NETSTAT)
15
+ end
16
+
17
+ # Parse an address from netstat format to "ip:port" format.
18
+ # @parameter address [String] Address string from netstat, e.g. "127.0.0.1.50876" or "*.63703".
19
+ # @returns [String] Address in "ip:port" format, e.g. "127.0.0.1:50876" or "0.0.0.0:63703".
20
+ def self.parse_address(address)
21
+ # Handle wildcard addresses: *.port -> 0.0.0.0:port
22
+ if address.start_with?("*.")
23
+ port = address[2..-1]
24
+ return "0.0.0.0:#{port}"
25
+ end
26
+
27
+ # Handle IPv4 addresses: ip.port -> ip:port
28
+ if address =~ /^([0-9.]+)\.(\d+)$/
29
+ ip = $1
30
+ port = $2
31
+ return "#{ip}:#{port}"
32
+ end
33
+
34
+ # Handle IPv6 addresses (if present in future)
35
+ # For now, return as-is
36
+ return address
37
+ end
38
+
39
+ # Parse netstat -L output and extract listener statistics.
40
+ # @parameter addresses [Array(String) | Nil] Optional filter for specific addresses.
41
+ # @returns [Hash(String, Listener)] Hash mapping "ip:port" to Listener.
42
+ def self.capture_tcp(addresses = nil)
43
+ listeners = {}
44
+ address_filter = addresses ? addresses.map{|address| address.downcase}.to_set : nil
45
+
46
+ IO.popen([NETSTAT, "-L", "-an", "-p", "tcp"], "r") do |io|
47
+ # Skip header lines
48
+ io.each_line do |line|
49
+ # Skip header and empty lines
50
+ next if line.start_with?("Current") || line.start_with?("Listen") || line.strip.empty?
51
+
52
+ # Format: "queue_length/incomplete_queue_length/maximum_queue_length Local Address"
53
+ fields = line.split(/\s+/)
54
+ next if fields.size < 2
55
+
56
+ queue_statistics = fields[0]
57
+ local_address_raw = fields[1]
58
+
59
+ # Parse queue statistics: "queue_length/incomplete_queue_length/maximum_queue_length"
60
+ if queue_statistics =~ /^(\d+)\/(\d+)\/(\d+)$/
61
+ queue_length = $1.to_i
62
+ # incomplete_queue_length = $2.to_i # incomplete connections (SYN_RECV)
63
+ # maximum_queue_length = $3.to_i # maximum queue size
64
+
65
+ # Parse address
66
+ address = parse_address(local_address_raw)
67
+
68
+ # Apply filter if specified
69
+ next if address_filter && !address_filter.include?(address)
70
+
71
+ listeners[address] ||= Listener.zero
72
+ listeners[address].queue_size = queue_length
73
+ # active_connections set to 0 (can't reliably count per listener)
74
+ listeners[address].active_connections = 0
75
+ end
76
+ end
77
+ end
78
+
79
+ return listeners
80
+ rescue Errno::ENOENT, Errno::EACCES
81
+ return {}
82
+ end
83
+
84
+ # Capture listener listeners for TCP sockets.
85
+ # @parameter addresses [Array(String) | Nil] TCP address(es) to capture, e.g. ["0.0.0.0:80"]. If nil, captures all.
86
+ # @parameter paths [Array(String) | Nil] Unix socket path(s) to capture (not supported on Darwin).
87
+ # @returns [Hash(String, Listener)] Hash mapping addresses to Listener.
88
+ def self.capture(addresses: nil, paths: nil)
89
+ listeners = {}
90
+
91
+ # Capture TCP listeners (Unix sockets not supported on Darwin via netstat)
92
+ listeners.merge!(capture_tcp(addresses))
93
+
94
+ return listeners
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+ # Wire Listener.capture and Listener.supported? to this implementation on Darwin.
101
+ if IO::Metrics::Listener::Darwin.supported?
102
+ class << IO::Metrics::Listener
103
+ # Whether listener capture is supported on this platform.
104
+ # @returns [Boolean] True if netstat is executable.
105
+ def supported?
106
+ true
107
+ end
108
+
109
+ # Capture listener listeners for the given address(es).
110
+ # @parameter addresses [Array(String) | Nil] TCP address(es) to capture, e.g. ["0.0.0.0:80"]. If nil, captures all listening TCP sockets.
111
+ # @parameter paths [Array(String) | Nil] Unix socket path(s) to capture (not supported on Darwin).
112
+ # @returns [Hash(String, Listener) | Nil] A hash mapping addresses to Listener, or nil if not supported.
113
+ def capture(**options)
114
+ IO::Metrics::Listener::Darwin.capture(**options)
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,325 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ require "set"
7
+ require "ipaddr"
8
+
9
+ class IO
10
+ module Metrics
11
+ # Linux implementation of listener statistics using /proc/net/tcp, /proc/net/tcp6, and /proc/net/unix.
12
+ class Listener::Linux
13
+ # TCP socket states (from include/net/tcp_states.h)
14
+ TCP_ESTABLISHED = 0x01
15
+ TCP_SYN_SENT = 0x02
16
+ TCP_SYN_RECV = 0x03
17
+ TCP_FIN_WAIT1 = 0x04
18
+ TCP_FIN_WAIT2 = 0x05
19
+ TCP_TIME_WAIT = 0x06
20
+ TCP_CLOSE = 0x07
21
+ TCP_CLOSE_WAIT = 0x08
22
+ TCP_LAST_ACK = 0x09
23
+ TCP_LISTEN = 0x0A
24
+ TCP_CLOSING = 0x0B
25
+
26
+ # Unix socket states (from include/uapi/linux/net.h)
27
+ SS_UNCONNECTED = 0x01
28
+ SS_CONNECTING = 0x02
29
+ SS_CONNECTED = 0x03
30
+
31
+ # Regex pattern for parsing /proc/net/tcp and /proc/net/tcp6 lines.
32
+ # Captures: local_ip, local_port, remote_ip, remote_port, state, tx_queue, rx_queue
33
+ TCP_LINE_PATTERN = /\A\s*\d+:\s+([0-9A-Fa-f]+):([0-9A-Fa-f]+)\s+([0-9A-Fa-f]+):([0-9A-Fa-f]+)\s+([0-9A-Fa-f]+)\s+([0-9A-Fa-f]+):([0-9A-Fa-f]+)/
34
+
35
+ # Whether listener listeners can be captured on this system.
36
+ def self.supported?
37
+ File.readable?("/proc/net/tcp")
38
+ end
39
+
40
+ # Parse an IPv4 address from /proc/net/tcp format (hex, little-endian).
41
+ # @parameter hex [String] Hexadecimal address string, e.g. "0100007F" for 127.0.0.1.
42
+ # @returns [String] IP address in dotted decimal format, e.g. "127.0.0.1".
43
+ def self.parse_ipv4(hex)
44
+ raise ArgumentError, "Invalid IPv4 hex format: #{hex.inspect}" unless hex =~ /\A[0-9A-Fa-f]{8}\z/
45
+
46
+ # Each byte is 2 hex chars, read in reverse order (little-endian)
47
+ bytes = hex.scan(/../).reverse.map{|byte| byte.to_i(16)}
48
+ bytes.join(".")
49
+ end
50
+
51
+ # Parse an IPv6 address from /proc/net/tcp6 format (hex, little-endian).
52
+ # @parameter hex [String] Hexadecimal address string, 32 hex chars (16 bytes).
53
+ # @returns [String] IP address in compressed IPv6 format, e.g. "::1" or "2001:db8::1".
54
+ def self.parse_ipv6(hex)
55
+ raise ArgumentError, "Invalid IPv6 hex format: #{hex.inspect}" unless hex =~ /\A[0-9A-Fa-f]{32}\z/
56
+
57
+ # IPv6 is 16 bytes (32 hex chars) stored as 4-byte words in little-endian format
58
+ # Split into 4-byte words (8 hex chars each) and reverse bytes within each word
59
+ words = hex.scan(/.{8}/)
60
+
61
+ reversed_words = words.map do |word|
62
+ word.scan(/../).reverse.join("")
63
+ end
64
+
65
+ # Convert to 16-bit segments and create colon-separated format
66
+ segments = reversed_words.flat_map do |word|
67
+ [word[0..3], word[4..7]]
68
+ end
69
+
70
+ ipv6_expanded = segments.join(":")
71
+
72
+ # Use IPAddr to compress the address
73
+ IPAddr.new(ipv6_expanded).to_s
74
+ end
75
+
76
+ # Parse a port from /proc/net/tcp format (hex).
77
+ # @parameter hex [String] Hexadecimal port string, e.g. "0050" for port 80.
78
+ # @returns [Integer] Port number.
79
+ def self.parse_port(hex)
80
+ hex.to_i(16)
81
+ end
82
+
83
+ # Parse a socket state from /proc/net/tcp format.
84
+ # @parameter hex [String] Hexadecimal state string.
85
+ # @returns [Symbol] Socket state (:listen, :established, etc.)
86
+ def self.parse_state(hex)
87
+ state = hex.to_i(16)
88
+ case state
89
+ when TCP_LISTEN then :listen
90
+ when TCP_ESTABLISHED then :established
91
+ when TCP_SYN_SENT then :syn_sent
92
+ when TCP_SYN_RECV then :syn_recv
93
+ when TCP_FIN_WAIT1 then :fin_wait1
94
+ when TCP_FIN_WAIT2 then :fin_wait2
95
+ when TCP_TIME_WAIT then :time_wait
96
+ when TCP_CLOSE then :close
97
+ when TCP_CLOSE_WAIT then :close_wait
98
+ when TCP_LAST_ACK then :last_ack
99
+ when TCP_CLOSING then :closing
100
+ else :unknown
101
+ end
102
+ end
103
+
104
+ # Find the best matching listener for an ESTABLISHED connection.
105
+ # @parameter local_address [String] Local address in "ip:port" or "[ipv6]:port" format.
106
+ # @parameter listeners [Hash(String, Listener)] Hash of listener addresses to Listener objects.
107
+ # @returns [String | Nil] The address of the matching listener, or nil if no match.
108
+ def self.find_matching_listener(local_address, listeners)
109
+ # Try exact match first
110
+ return local_address if listeners.key?(local_address)
111
+
112
+ # Parse the address to extract IP and port
113
+ if local_address.start_with?("[")
114
+ # IPv6 format: [::1]:port
115
+ if match = local_address.match(/\A\[(.+)\]:(\d+)\z/)
116
+ local_ip = $1
117
+ local_port = $2
118
+ else
119
+ return nil
120
+ end
121
+ else
122
+ # IPv4 format: 127.0.0.1:port
123
+ local_ip, local_port = local_address.split(":", 2)
124
+ return nil unless local_port
125
+ end
126
+
127
+ # Determine address type using IPAddr for robust detection
128
+ begin
129
+ ip_address = IPAddr.new(local_ip)
130
+
131
+ if ip_address.ipv4?
132
+ # Try IPv4 wildcard match (0.0.0.0:port)
133
+ wildcard_address = "0.0.0.0:#{local_port}"
134
+ return wildcard_address if listeners.key?(wildcard_address)
135
+ else
136
+ # Try IPv6 wildcard match ([::]:port)
137
+ wildcard_address = "[::]:#{local_port}"
138
+ return wildcard_address if listeners.key?(wildcard_address)
139
+ end
140
+ rescue IPAddr::InvalidAddressError
141
+ # If IP parsing fails, return nil
142
+ return nil
143
+ end
144
+
145
+ return nil
146
+ end
147
+
148
+ # Parse /proc/net/tcp or /proc/net/tcp6 and extract listener statistics (optimized single-pass).
149
+ # @parameter file [String] Path to /proc/net/tcp or /proc/net/tcp6.
150
+ # @parameter addresses [Array(String) | Nil] Optional filter for specific addresses.
151
+ # @parameter ipv6 [Boolean] Whether parsing IPv6 addresses.
152
+ # @returns [Hash(String, Listener)] Hash mapping "ip:port" or "[ipv6]:port" to Listener.
153
+ def self.capture_tcp_file(file, addresses = nil, ipv6: false)
154
+ listeners = {}
155
+ address_filter = addresses ? addresses.map{|address| address.downcase}.to_set : nil
156
+ connections = []
157
+
158
+ # Single pass: collect LISTEN sockets and ESTABLISHED connections
159
+ File.foreach(file) do |line|
160
+ next if line.start_with?("sl")
161
+
162
+ if match = TCP_LINE_PATTERN.match(line)
163
+ local_ip_hex = match[1]
164
+ local_port_hex = match[2]
165
+ remote_ip_hex = match[3]
166
+ remote_port_hex = match[4]
167
+ state_hex = match[5]
168
+ tx_queue_hex = match[6]
169
+ rx_queue_hex = match[7]
170
+
171
+ state = parse_state(state_hex)
172
+
173
+ # Process LISTEN sockets
174
+ if state == :listen
175
+ if ipv6
176
+ local_ip = parse_ipv6(local_ip_hex)
177
+ local_address = "[#{local_ip}]:#{parse_port(local_port_hex)}"
178
+ else
179
+ local_ip = parse_ipv4(local_ip_hex)
180
+ local_port = parse_port(local_port_hex)
181
+ local_address = "#{local_ip}:#{local_port}"
182
+ end
183
+
184
+ # Apply filter if specified
185
+ next if address_filter && !address_filter.include?(local_address)
186
+
187
+ listeners[local_address] ||= Listener.zero
188
+ # rx_queue shows number of connections waiting to be accepted
189
+ listeners[local_address].queue_size = rx_queue_hex.to_i(16)
190
+ listeners[local_address].active_connections = 0
191
+ # Collect ESTABLISHED connections to count later
192
+ elsif state == :established
193
+ if ipv6
194
+ local_ip = parse_ipv6(local_ip_hex)
195
+ local_address = "[#{local_ip}]:#{parse_port(local_port_hex)}"
196
+ else
197
+ local_ip = parse_ipv4(local_ip_hex)
198
+ local_port = parse_port(local_port_hex)
199
+ local_address = "#{local_ip}:#{local_port}"
200
+ end
201
+ connections << local_address
202
+ end
203
+ end
204
+ end
205
+
206
+ # Count ESTABLISHED connections for each listener
207
+ connections.each do |local_address|
208
+ if listener_address = find_matching_listener(local_address, listeners)
209
+ listeners[listener_address].active_connections += 1
210
+ end
211
+ end
212
+
213
+ return listeners
214
+ rescue Errno::ENOENT, Errno::EACCES
215
+ return {}
216
+ end
217
+
218
+ # Parse /proc/net/unix and extract listener statistics for Unix domain sockets.
219
+ # @parameter paths [Array(String) | Nil] Optional filter for specific socket paths.
220
+ # @parameter file [String] Optional path to Unix socket file (defaults to "/proc/net/unix").
221
+ # @returns [Hash(String, Listener)] Hash mapping socket path to Listener.
222
+ def self.capture_unix(paths = nil, file: "/proc/net/unix")
223
+ listeners = {}
224
+ path_filter = paths ? paths.to_set : nil
225
+
226
+ File.foreach(file) do |line|
227
+ line = line.strip
228
+ next if line.start_with?("Num")
229
+
230
+ # Format: Num RefCount Protocol Flags Type St Inode Path
231
+ # Example (stripped): "00000000cf265b54: 00000003 00000000 00000000 0001 03 18324 /run/user/1000/wayland-0"
232
+ # After splitting by whitespace:
233
+ # [0] = "00000000cf265b54:", [1] = RefCount, [2] = Protocol, [3] = Flags, [4] = Type, [5] = St, [6] = Inode, [7+] = Path
234
+ fields = line.split(/\s+/)
235
+ next if fields.size < 7
236
+
237
+ # State field is at index 5 (St)
238
+ # 01 = SS_UNCONNECTED (listening), 02 = SS_CONNECTING (queued), 03 = SS_CONNECTED (active)
239
+ state_hex = fields[5]
240
+ # Path starts at index 7
241
+ path = fields[7..-1]&.join(" ") || ""
242
+
243
+ # Apply filter if specified
244
+ next if path_filter && !path_filter.include?(path)
245
+ next if path.empty?
246
+
247
+ state = state_hex.to_i(16)
248
+
249
+ listeners[path] ||= Listener.zero
250
+
251
+ case state
252
+ when SS_CONNECTING # Queued connections
253
+ listeners[path].queue_size += 1
254
+ when SS_CONNECTED # Active connections
255
+ listeners[path].active_connections += 1
256
+ end
257
+ end
258
+
259
+ return listeners
260
+ rescue Errno::ENOENT, Errno::EACCES
261
+ return {}
262
+ end
263
+
264
+ # Parse /proc/net/tcp and /proc/net/tcp6 and extract listener statistics.
265
+ # @parameter addresses [Array(String) | Nil] Optional filter for specific addresses.
266
+ # @returns [Hash(String, Listener)] Hash mapping "ip:port" or "[ipv6]:port" to Listener.
267
+ def self.capture_tcp(addresses = nil)
268
+ listeners = {}
269
+
270
+ # Capture IPv4 listeners and connections
271
+ if File.readable?("/proc/net/tcp")
272
+ listeners.merge!(capture_tcp_file("/proc/net/tcp", addresses, ipv6: false))
273
+ end
274
+
275
+ # Capture IPv6 listeners and connections
276
+ if File.readable?("/proc/net/tcp6")
277
+ listeners.merge!(capture_tcp_file("/proc/net/tcp6", addresses, ipv6: true))
278
+ end
279
+
280
+ return listeners
281
+ end
282
+
283
+ # Capture listener listeners for TCP and/or Unix domain sockets.
284
+ # @parameter addresses [Array(String) | Nil] TCP address(es) to capture, e.g. ["0.0.0.0:80"]. If nil and paths is nil, captures all. If nil but paths specified, captures none.
285
+ # @parameter paths [Array(String) | Nil] Unix socket path(s) to capture. If nil and addresses is nil, captures all. If nil but addresses specified, captures none.
286
+ # @parameter unix_file [String] Optional path to Unix socket file (defaults to "/proc/net/unix").
287
+ # @returns [Hash(String, Listener)] Hash mapping addresses/paths to Listener.
288
+ def self.capture(addresses: nil, paths: nil, unix_file: "/proc/net/unix")
289
+ listeners = {}
290
+
291
+ # If addresses are specified but paths is nil, don't capture Unix sockets
292
+ # Only capture Unix sockets if paths is explicitly provided or addresses is nil
293
+ tcp_addresses = addresses.nil? && !paths.nil? ? :skip : addresses
294
+ unix_paths = paths.nil? && !addresses.nil? ? :skip : paths
295
+
296
+ # Capture TCP listeners (only if not skipped)
297
+ listeners.merge!(capture_tcp(tcp_addresses)) unless tcp_addresses == :skip
298
+
299
+ # Capture Unix domain socket listeners (only if not skipped)
300
+ listeners.merge!(capture_unix(unix_paths, file: unix_file)) unless unix_paths == :skip
301
+
302
+ return listeners
303
+ end
304
+ end
305
+ end
306
+ end
307
+
308
+ # Wire Listener.capture and Listener.supported? to this implementation on Linux.
309
+ if IO::Metrics::Listener::Linux.supported?
310
+ class << IO::Metrics::Listener
311
+ # Whether listener capture is supported on this platform.
312
+ # @returns [Boolean] True if /proc/net/tcp is readable.
313
+ def supported?
314
+ true
315
+ end
316
+
317
+ # Capture listener listeners for the given address(es).
318
+ # @parameter addresses [Array(String) | Nil] TCP address(es) to capture, e.g. ["0.0.0.0:80"]. If nil, captures all listening TCP sockets.
319
+ # @parameter paths [Array(String) | Nil] Unix socket path(s) to capture. If nil and addresses is nil, captures all. If nil but addresses specified, captures none.
320
+ # @returns [Hash(String, Listener) | Nil] A hash mapping addresses/paths to Listener, or nil if not supported.
321
+ def capture(**options)
322
+ IO::Metrics::Listener::Linux.capture(**options)
323
+ end
324
+ end
325
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ require "json"
7
+
8
+ class IO
9
+ module Metrics
10
+ # Represents a network listener socket with its queue statistics.
11
+ # @attribute queue_size [Integer] Number of connections waiting to be accepted (queued).
12
+ # @attribute active_connections [Integer] Number of active connections (already accepted).
13
+ class Listener < Struct.new(:queue_size, :active_connections)
14
+ alias as_json to_h
15
+
16
+ # Convert the object to a JSON string.
17
+ def to_json(*arguments)
18
+ as_json.to_json(*arguments)
19
+ end
20
+
21
+ # Create a zero-initialized Listener instance.
22
+ # @returns [Listener] A new Listener object with all fields set to zero.
23
+ def self.zero
24
+ self.new(0, 0)
25
+ end
26
+
27
+ # Whether listener stats can be captured on this system.
28
+ def self.supported?
29
+ false
30
+ end
31
+
32
+ # Capture listener stats for the given address(es).
33
+ # @parameter addresses [Array(String) | Nil] TCP address(es) to capture, e.g. ["0.0.0.0:80"]. If nil, captures all listening TCP sockets.
34
+ # @parameter paths [Array(String) | Nil] Unix socket path(s) to capture. If nil and addresses is nil, captures all. If nil but addresses specified, captures none.
35
+ # @returns [Hash(String, Listener) | Nil] A hash mapping addresses/paths to Listener, or nil if not supported.
36
+ def self.capture(**options)
37
+ return nil
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ if RUBY_PLATFORM.include?("linux")
44
+ require_relative "listener/linux"
45
+ elsif RUBY_PLATFORM.include?("darwin")
46
+ require_relative "listener/darwin"
47
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ # @namespace
7
+ class IO
8
+ # @namespace
9
+ module Metrics
10
+ VERSION = "0.1.0"
11
+ end
12
+ end
data/lib/io/metrics.rb ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ require_relative "metrics/version"
7
+ require_relative "metrics/listener"
data/license.md ADDED
@@ -0,0 +1,21 @@
1
+ # MIT License
2
+
3
+ Copyright, 2026, by Samuel Williams.
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 all
13
+ 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 THE
21
+ SOFTWARE.
data/readme.md ADDED
@@ -0,0 +1,29 @@
1
+ # IO::Metrics
2
+
3
+ Extract I/O metrics from the host system, specifically listen queue statistics.
4
+
5
+ [![Development Status](https://github.com/socketry/io-metrics/workflows/Test/badge.svg)](https://github.com/socketry/io-metrics/actions?workflow=Test)
6
+
7
+ ## Usage
8
+
9
+ Please see the [project documentation](https://socketry.github.io/io-metrics/) for more details.
10
+
11
+ - [Getting Started](https://socketry.github.io/io-metrics/guides/getting-started/index) - This guide explains how to use `io-metrics` to capture listener queue statistics from the host operating system.
12
+
13
+ ## Contributing
14
+
15
+ We welcome contributions to this project.
16
+
17
+ 1. Fork it.
18
+ 2. Create your feature branch (`git checkout -b my-new-feature`).
19
+ 3. Commit your changes (`git commit -am 'Add some feature'`).
20
+ 4. Push to the branch (`git push origin my-new-feature`).
21
+ 5. Create new Pull Request.
22
+
23
+ ### Developer Certificate of Origin
24
+
25
+ In order to protect users of this project, we require all contributors to comply with the [Developer Certificate of Origin](https://developercertificate.org/). This ensures that all contributions are properly licensed and attributed.
26
+
27
+ ### Community Guidelines
28
+
29
+ This project is best served by a collaborative and respectful environment. Treat each other professionally, respect differing viewpoints, and engage constructively. Harassment, discrimination, or harmful behavior is not tolerated. Communicate clearly, listen actively, and support one another. If any issues arise, please inform the project maintainers.
data.tar.gz.sig ADDED
Binary file
metadata ADDED
@@ -0,0 +1,104 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: io-metrics
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Samuel Williams
8
+ bindir: bin
9
+ cert_chain:
10
+ - |
11
+ -----BEGIN CERTIFICATE-----
12
+ MIIE2DCCA0CgAwIBAgIBATANBgkqhkiG9w0BAQsFADBhMRgwFgYDVQQDDA9zYW11
13
+ ZWwud2lsbGlhbXMxHTAbBgoJkiaJk/IsZAEZFg1vcmlvbnRyYW5zZmVyMRIwEAYK
14
+ CZImiZPyLGQBGRYCY28xEjAQBgoJkiaJk/IsZAEZFgJuejAeFw0yMjA4MDYwNDUz
15
+ MjRaFw0zMjA4MDMwNDUzMjRaMGExGDAWBgNVBAMMD3NhbXVlbC53aWxsaWFtczEd
16
+ MBsGCgmSJomT8ixkARkWDW9yaW9udHJhbnNmZXIxEjAQBgoJkiaJk/IsZAEZFgJj
17
+ bzESMBAGCgmSJomT8ixkARkWAm56MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIB
18
+ igKCAYEAomvSopQXQ24+9DBB6I6jxRI2auu3VVb4nOjmmHq7XWM4u3HL+pni63X2
19
+ 9qZdoq9xt7H+RPbwL28LDpDNflYQXoOhoVhQ37Pjn9YDjl8/4/9xa9+NUpl9XDIW
20
+ sGkaOY0eqsQm1pEWkHJr3zn/fxoKPZPfaJOglovdxf7dgsHz67Xgd/ka+Wo1YqoE
21
+ e5AUKRwUuvaUaumAKgPH+4E4oiLXI4T1Ff5Q7xxv6yXvHuYtlMHhYfgNn8iiW8WN
22
+ XibYXPNP7NtieSQqwR/xM6IRSoyXKuS+ZNGDPUUGk8RoiV/xvVN4LrVm9upSc0ss
23
+ RZ6qwOQmXCo/lLcDUxJAgG95cPw//sI00tZan75VgsGzSWAOdjQpFM0l4dxvKwHn
24
+ tUeT3ZsAgt0JnGqNm2Bkz81kG4A2hSyFZTFA8vZGhp+hz+8Q573tAR89y9YJBdYM
25
+ zp0FM4zwMNEUwgfRzv1tEVVUEXmoFCyhzonUUw4nE4CFu/sE3ffhjKcXcY//qiSW
26
+ xm4erY3XAgMBAAGjgZowgZcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0O
27
+ BBYEFO9t7XWuFf2SKLmuijgqR4sGDlRsMC4GA1UdEQQnMCWBI3NhbXVlbC53aWxs
28
+ aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MC4GA1UdEgQnMCWBI3NhbXVlbC53aWxs
29
+ aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MA0GCSqGSIb3DQEBCwUAA4IBgQB5sxkE
30
+ cBsSYwK6fYpM+hA5B5yZY2+L0Z+27jF1pWGgbhPH8/FjjBLVn+VFok3CDpRqwXCl
31
+ xCO40JEkKdznNy2avOMra6PFiQyOE74kCtv7P+Fdc+FhgqI5lMon6tt9rNeXmnW/
32
+ c1NaMRdxy999hmRGzUSFjozcCwxpy/LwabxtdXwXgSay4mQ32EDjqR1TixS1+smp
33
+ 8C/NCWgpIfzpHGJsjvmH2wAfKtTTqB9CVKLCWEnCHyCaRVuKkrKjqhYCdmMBqCws
34
+ JkxfQWC+jBVeG9ZtPhQgZpfhvh+6hMhraUYRQ6XGyvBqEUe+yo6DKIT3MtGE2+CP
35
+ eX9i9ZWBydWb8/rvmwmX2kkcBbX0hZS1rcR593hGc61JR6lvkGYQ2MYskBveyaxt
36
+ Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
37
+ voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
38
+ -----END CERTIFICATE-----
39
+ date: 1980-01-02 00:00:00.000000000 Z
40
+ dependencies:
41
+ - !ruby/object:Gem::Dependency
42
+ name: console
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.8'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.8'
55
+ - !ruby/object:Gem::Dependency
56
+ name: json
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2'
69
+ executables: []
70
+ extensions: []
71
+ extra_rdoc_files: []
72
+ files:
73
+ - lib/io/metrics.rb
74
+ - lib/io/metrics/listener.rb
75
+ - lib/io/metrics/listener/darwin.rb
76
+ - lib/io/metrics/listener/linux.rb
77
+ - lib/io/metrics/version.rb
78
+ - license.md
79
+ - readme.md
80
+ homepage: https://github.com/socketry/io-metrics
81
+ licenses:
82
+ - MIT
83
+ metadata:
84
+ documentation_uri: https://socketry.github.io/io-metrics/
85
+ funding_uri: https://github.com/sponsors/ioquatix
86
+ source_code_uri: https://github.com/socketry/io-metrics.git
87
+ rdoc_options: []
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '3.2'
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ requirements: []
101
+ rubygems_version: 4.0.3
102
+ specification_version: 4
103
+ summary: Extract I/O metrics from the host system.
104
+ test_files: []
metadata.gz.sig ADDED
Binary file