process-metrics 0.13.0 → 0.14.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2822c0b3ad1a6e79d294cc8607359b1a8042818a8b07306c5685064addd4b7fd
4
- data.tar.gz: f945a7b3bcf467f696c9823a11ecefa996aae3f7e9d075bcfcd6bba87edd13d1
3
+ metadata.gz: 86b26a801c95a764b0f98139e25ad1ac12b455b1b44b664722d1ff189bce8be4
4
+ data.tar.gz: 7591aff04fdbc4e9b6eabdd42ac8bcdf6ffbeea605f812b1d00255a57cba49c5
5
5
  SHA512:
6
- metadata.gz: 5869ec2e258caad4625fc45f9ee572ebbf9803592f0b8ed1115e80a5f21ad7b36acc3122c9d17aa5c8816c33d76bc4393c3d6620f2ec15f44e065701168ee952
7
- data.tar.gz: 7b0a434ceba0c1b0d89297825a5bde80999542e10060e5aa89bafc34e44529e75323ad5de3b3ad792e7bdaee87856e64111ba209026e597203eeed8c6ca3aad9
6
+ metadata.gz: 1a688f8a4db31df31adbbc5d5513716785ff61047f0b010aba506064d4c981dc5582fe99784eef0d244f877643034325d0996a323cc91e3ee1d6e6caa0e0f8e9
7
+ data.tar.gz: 72d8c7499461b35deafd4af20ba6c065de44f76d8868971e3ab70795febbb2863df98f9cb06c1e82e94fc1cdc7867275d58e1e03f9aa9ea890d50c5a37cf05bb
checksums.yaml.gz.sig CHANGED
Binary file
@@ -112,3 +112,17 @@ sample.utilization
112
112
  ```
113
113
 
114
114
  Both interfaces use the same core-unit scale. A utilization of `0.5` means half of one core on average, while `2.0` means two cores were fully occupied. Utilization is therefore not limited to the range `0.0..1.0`.
115
+
116
+ ## Processor Capacity
117
+
118
+ Use {ruby Process::Metrics::Processor.count} to get the number of processors available to the current process. On Linux, this takes CPU affinity into account.
119
+
120
+ Use {ruby Process::Metrics::Processor.quota} to get the available processor capacity as a `Float`. On Linux, it also takes cgroup v2 CPU bandwidth limits into account. On other systems, when no finite quota is configured, or when the quota cannot be determined, it returns {ruby Process::Metrics::Processor.count} as a `Float`.
121
+
122
+ ``` ruby
123
+ Process::Metrics::Processor.count
124
+ # => 8
125
+
126
+ Process::Metrics::Processor.quota
127
+ # => 1.5
128
+ ```
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ module Process
7
+ module Metrics
8
+ class Processor
9
+ # @private
10
+ module Linux
11
+ DEFAULT_CGROUP_ROOT = "/sys/fs/cgroup"
12
+ DEFAULT_CGROUP_PATH = "/proc/self/cgroup"
13
+
14
+ class << self
15
+ # Read the effective processor quota for the current cgroup.
16
+ # @parameter cgroup_root [String] The root of the cgroup v2 filesystem.
17
+ # @parameter cgroup_path [String] The process cgroup membership file.
18
+ # @returns [Float | Nil] The smallest finite quota in the cgroup hierarchy, if available.
19
+ def quota(cgroup_root: DEFAULT_CGROUP_ROOT, cgroup_path: DEFAULT_CGROUP_PATH)
20
+ unless relative_path = current_path(cgroup_path)
21
+ return nil
22
+ end
23
+
24
+ root = File.expand_path(cgroup_root)
25
+ directory = File.expand_path(relative_path.delete_prefix("/"), root)
26
+ return nil unless directory == root || directory.start_with?("#{root}/")
27
+
28
+ quota = nil
29
+
30
+ loop do
31
+ if current = read_quota(directory)
32
+ quota = [quota, current].compact.min
33
+ end
34
+
35
+ break if directory == root
36
+ directory = File.dirname(directory)
37
+ end
38
+
39
+ return quota
40
+ rescue Errno::EACCES, Errno::EINVAL, Errno::ENOENT, Errno::ENOTDIR, ArgumentError
41
+ return nil
42
+ end
43
+
44
+ private
45
+
46
+ def current_path(path)
47
+ File.foreach(path) do |line|
48
+ hierarchy, controllers, relative_path = line.strip.split(":", 3)
49
+
50
+ if hierarchy == "0" && controllers == "" && relative_path
51
+ return relative_path
52
+ end
53
+ end
54
+
55
+ return nil
56
+ end
57
+
58
+ def read_quota(directory)
59
+ maximum, period = File.read(File.join(directory, "cpu.max")).split
60
+ return nil if maximum == "max" || !maximum || !period
61
+
62
+ maximum = Integer(maximum)
63
+ period = Integer(period)
64
+ return nil unless maximum.positive? && period.positive?
65
+
66
+ return maximum.to_f / period
67
+ rescue Errno::EACCES, Errno::EINVAL, Errno::ENOENT, Errno::ENOTDIR, ArgumentError
68
+ return nil
69
+ end
70
+ end
71
+ end
72
+
73
+ # The processor capacity available to the current process.
74
+ # This takes cgroup v2 CPU bandwidth limits into account, and otherwise returns {count} as a `Float`.
75
+ # @returns [Float] The available processor capacity in core units.
76
+ def self.quota
77
+ count = self.count.to_f
78
+
79
+ if quota = Linux.quota
80
+ if quota < count
81
+ return quota
82
+ end
83
+ end
84
+
85
+ return count
86
+ end
87
+ end
88
+ end
89
+ end
@@ -3,10 +3,26 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2026, by Samuel Williams.
5
5
 
6
+ require "etc"
7
+
6
8
  module Process
7
9
  module Metrics
8
10
  # Computes interval CPU utilization from cumulative process metrics.
9
11
  class Processor
12
+ # The number of processors available to the current process.
13
+ # On Linux, this takes the process CPU affinity into account.
14
+ # @returns [Integer] The number of available processors.
15
+ def self.count
16
+ Etc.nprocessors
17
+ end
18
+
19
+ # The processor capacity available to the current process.
20
+ # On Linux, this takes cgroup v2 CPU bandwidth limits into account. Otherwise, it returns {count} as a `Float`.
21
+ # @returns [Float] The available processor capacity in core units.
22
+ def self.quota
23
+ return self.count.to_f
24
+ end
25
+
10
26
  # An immutable measurement of process CPU usage over an interval.
11
27
  # @attribute [Integer] The process ID.
12
28
  # @attribute [Float] The elapsed monotonic time in seconds.
@@ -79,3 +95,7 @@ module Process
79
95
  end
80
96
  end
81
97
  end
98
+
99
+ if RUBY_PLATFORM.include?("linux")
100
+ require_relative "processor/linux"
101
+ end
@@ -7,6 +7,6 @@
7
7
  module Process
8
8
  # @namespace
9
9
  module Metrics
10
- VERSION = "0.13.0"
10
+ VERSION = "0.14.0"
11
11
  end
12
12
  end
data/readme.md CHANGED
@@ -16,6 +16,10 @@ Please see the [project documentation](https://socketry.github.io/process-metric
16
16
 
17
17
  Please see the [project releases](https://socketry.github.io/process-metrics/releases/index) for all releases.
18
18
 
19
+ ### v0.14.0
20
+
21
+ - Add `Process::Metrics::Processor.count` and `.quota` for affinity-aware processor counts and cgroup v2-aware processor capacity.
22
+
19
23
  ### v0.13.0
20
24
 
21
25
  - Normalize processor utilization to core units, where `1.0` represents one fully occupied CPU core, and restore Linux reporting.
@@ -56,10 +60,6 @@ Please see the [project releases](https://socketry.github.io/process-metrics/rel
56
60
 
57
61
  - Be more proactive about returning nil if memory capture failed.
58
62
 
59
- ### v0.6.1
60
-
61
- - Handle `Errno::ESRCH: No such process @ io_fillbuf - fd:xxx /proc/xxx/smaps_rollup` by ignoring it.
62
-
63
63
  ## Contributing
64
64
 
65
65
  We welcome contributions to this project.
data/releases.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Releases
2
2
 
3
+ ## v0.14.0
4
+
5
+ - Add `Process::Metrics::Processor.count` and `.quota` for affinity-aware processor counts and cgroup v2-aware processor capacity.
6
+
3
7
  ## v0.13.0
4
8
 
5
9
  - Normalize processor utilization to core units, where `1.0` represents one fully occupied CPU core, and restore Linux reporting.
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: process-metrics
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.0
4
+ version: 0.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -89,6 +89,7 @@ files:
89
89
  - lib/process/metrics/memory/darwin.rb
90
90
  - lib/process/metrics/memory/linux.rb
91
91
  - lib/process/metrics/processor.rb
92
+ - lib/process/metrics/processor/linux.rb
92
93
  - lib/process/metrics/version.rb
93
94
  - license.md
94
95
  - readme.md
metadata.gz.sig CHANGED
Binary file