sys-cpu 1.1.0 → 1.3.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: c2c9b0550fea7a97f78cb37062e8f347d1b945e53a2a885f2c4fa8cdd02e0dc5
4
- data.tar.gz: 6b4c3525653fd1a186078a533826795e3d2b168c2649d48fe549de33ca021edb
3
+ metadata.gz: 56f093dfd8f11c6ed9da97cc8020e687a76001dabef4596299041e6b875a2d3c
4
+ data.tar.gz: ac4b1d0ef5430c4d5078d9f4cbf12320f2900beab58d815202b32191209631ed
5
5
  SHA512:
6
- metadata.gz: c3efb6d399e22fff8380ec6d581c3997707bab5f99a7bbe001a86a058427e348066fce271348c9451f567bf6ffa7a9f2cf247de94ae82cd258d5ea6fa3d4bb9c
7
- data.tar.gz: 337fe45c0fe0463320c910aee118cf3496f71b9b4be7965d79204a109b12d273746f47afa50deb96519ec4118a3058e2aba00d912810bf065abee5e5fc8e0678
6
+ metadata.gz: 24a439eec1f866b22d52bd75665f284e8cd27351f474708b85f14900a3a77f4b5563b2961a3a5df7853fe10542048b7ff5c70eff1def7848da7eefa2964f28a2
7
+ data.tar.gz: e366fd2da77cf922abd384bb16e1c6bff84d4e3c11dc69d1732065971f80a2b5484db0cef28ae0d490492d4efb6173dea77a24b746302273eea7cf52d761716d
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGES.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## 1.3.0 - 24-Mar-2026
2
+ * Added the cpu_usage method.
3
+
4
+ ## 1.2.0 - 17-Feb-2026
5
+ * The win32ole gem is now a dependency since Ruby 4.x no longer bundles it.
6
+ * The freq method was updated for BSD platforms on aarch64. It now defaults
7
+ to the hardclock timer value as a best guess.
8
+ * The architecture method now recognizes ARM64 on Windows.
9
+
1
10
  ## 1.1.0 - 9-Jun-2024
2
11
  * Removed Solaris support.
3
12
  * Added DragonflyBSD support.
data/README.md CHANGED
@@ -72,7 +72,7 @@ https://github.com/djberg96/sys-cpu
72
72
  Apache-2.0
73
73
 
74
74
  ## Copyright
75
- (C) 2003-2024 Daniel J. Berger, All Rights Reserved
75
+ (C) 2003-2026 Daniel J. Berger, All Rights Reserved
76
76
 
77
77
  ## Warranty
78
78
  This package is provided "as is" and without any express or
data/Rakefile CHANGED
@@ -44,6 +44,9 @@ end
44
44
  RuboCop::RakeTask.new
45
45
 
46
46
  desc "Run the test suite"
47
- RSpec::Core::RakeTask.new(:spec)
47
+ RSpec::Core::RakeTask.new(:spec) do |t|
48
+ t.verbose = false
49
+ t.rspec_opts = '-f documentation -w'
50
+ end
48
51
 
49
52
  task :default => :spec
data/lib/sys/cpu.rb CHANGED
@@ -10,7 +10,7 @@ module Sys
10
10
  # This class is reopened for each of the supported platforms/operating systems.
11
11
  class CPU
12
12
  # The version of the sys-cpu gem.
13
- VERSION = '1.1.0'
13
+ VERSION = '1.3.0'
14
14
 
15
15
  private_class_method :new
16
16
  end
@@ -43,7 +43,7 @@ module Sys
43
43
  CPU_ARCH_ABI64 = 0x01000000
44
44
  CPU_TYPE_X86 = 7
45
45
  CPU_TYPE_X86_64 = (CPU_TYPE_X86 | CPU_ARCH_ABI64)
46
- CPU_TYPE_ARM = 12
46
+ CPU_TYPE_ARM = 12
47
47
  CPU_TYPE_SPARC = 14
48
48
  CPU_TYPE_POWERPC = 18
49
49
  CPU_TYPE_POWERPC64 = CPU_TYPE_POWERPC | CPU_ARCH_ABI64
@@ -74,6 +74,7 @@ module Sys
74
74
  private_class_method :getloadavg
75
75
  private_class_method :sysconf
76
76
 
77
+ # Private wrapper class for struct clockinfo
77
78
  class ClockInfo < FFI::Struct
78
79
  layout(
79
80
  :hz, :int,
@@ -189,7 +190,7 @@ module Sys
189
190
  (optr.read_long * clock[:hz]) / 1_000_000
190
191
  else
191
192
  if sysctlbyname('hw.cpufrequency', optr, size, nil, 0) < 0
192
- raise Error, 'sysctlbyname failed on hw.cpufrequency' if result < 0
193
+ raise Error, 'sysctlbyname failed on hw.cpufrequency'
193
194
  end
194
195
  optr.read_long / 1_000_000
195
196
  end
@@ -207,5 +208,119 @@ module Sys
207
208
 
208
209
  loadavg.get_array_of_double(0, 3)
209
210
  end
211
+
212
+ # Returns CPU usage as a percentage.
213
+ #
214
+ # If +sample_time+ is positive, samples CPU times and calculates an average
215
+ # over that interval. You can also specify +samples+ to average multiple
216
+ # consecutive measurements.
217
+ #
218
+ # If +sample_time+ is 0 (default), uses a 1-second sample window by default.
219
+ # Default value for +samples+ is 2 (averages two measurements).
220
+ #
221
+ HOST_CPU_LOAD_INFO = 3
222
+ HOST_CPU_LOAD_INFO_COUNT = 4
223
+
224
+ private_constant :HOST_CPU_LOAD_INFO, :HOST_CPU_LOAD_INFO_COUNT
225
+
226
+ attach_function :mach_host_self, [], :uint
227
+ attach_function :host_statistics, %i[uint int pointer pointer], :int
228
+
229
+ private_class_method :mach_host_self, :host_statistics
230
+
231
+ # Returns the current CPU usage as a percentage, averaged over a sampling interval.
232
+ #
233
+ # By default, this method samples CPU usage over a 1-second interval and averages two measurements.
234
+ # You can customize the interval and number of samples by passing the +sample_time+ (in seconds)
235
+ # and +samples+ keyword arguments. For example, +cpu_usage(sample_time: 0.5, samples: 4)+ will take four
236
+ # samples, each 0.5 seconds apart, and return the average CPU usage over that period.
237
+ #
238
+ # Passing nil, 0, or a negative value for either argument falls back to the defaults (1.0 seconds
239
+ # and 2 samples) to keep behavior consistent across platforms.
240
+ #
241
+ # Returns a Float (percentage), rounded to one decimal place, or nil if CPU usage cannot be determined.
242
+ #
243
+ # Example usage:
244
+ # Sys::CPU.cpu_usage #=> 12.3
245
+ # Sys::CPU.cpu_usage(sample_time: 2, samples: 3) #=> 10.7
246
+ # Sys::CPU.cpu_usage(sample_time: 0, samples: 0) #=> 12.3 # zeros fall back to defaults
247
+ #
248
+ def self.cpu_usage(sample_time: 1.0, samples: 2)
249
+ sample_time = 1.0 if sample_time.nil? || sample_time <= 0
250
+ samples = 2 if samples.nil? || samples <= 0
251
+
252
+ usages = []
253
+
254
+ samples.times do
255
+ t1 = current_ticks
256
+ sleep(sample_time)
257
+ t2 = current_ticks
258
+ next unless t1 && t2
259
+
260
+ if (u = usage_between_ticks(t1, t2))
261
+ usages << u
262
+ end
263
+ end
264
+
265
+ return nil if usages.empty?
266
+
267
+ (usages.sum / usages.size.to_f).round(1)
268
+ rescue StandardError
269
+ nil
270
+ end
271
+
272
+ def self.current_ticks
273
+ cpu_ticks_sysctl || cpu_ticks_host
274
+ end
275
+
276
+ private_class_method :current_ticks
277
+
278
+ def self.usage_between_ticks(t1, t2)
279
+ diff = t2.map.with_index { |v, i| v - t1[i] }
280
+ total = diff.sum
281
+ return nil if total <= 0
282
+
283
+ # host_statistics returns [user, system, idle, nice]
284
+ idle = diff[2] || 0
285
+ (1.0 - (idle.to_f / total)) * 100
286
+ end
287
+
288
+ private_class_method :usage_between_ticks
289
+
290
+ def self.cpu_ticks_sysctl
291
+ cp_time = proc { |ptr|
292
+ len = 5
293
+ size = FFI::MemoryPointer.new(:size_t)
294
+ size.write_ulong(ptr.size)
295
+
296
+ if sysctlbyname('kern.cp_time', ptr, size, nil, 0) < 0
297
+ raise Error, 'sysctlbyname failed'
298
+ end
299
+
300
+ ptr.read_array_of_ulong(len)
301
+ }
302
+
303
+ cp_time.call(FFI::MemoryPointer.new(:ulong, 5))
304
+ rescue StandardError
305
+ nil
306
+ end
307
+
308
+ private_class_method :cpu_ticks_sysctl
309
+
310
+ def self.cpu_ticks_host
311
+ host = mach_host_self
312
+ info = FFI::MemoryPointer.new(:uint, HOST_CPU_LOAD_INFO_COUNT)
313
+ count = FFI::MemoryPointer.new(:uint)
314
+ count.write_uint(HOST_CPU_LOAD_INFO_COUNT)
315
+
316
+ kr = host_statistics(host, HOST_CPU_LOAD_INFO, info, count)
317
+ return nil unless kr == 0
318
+
319
+ info.read_array_of_uint(HOST_CPU_LOAD_INFO_COUNT)
320
+ rescue StandardError
321
+ nil
322
+ end
323
+
324
+ private_class_method :cpu_ticks_host
210
325
  end
211
326
  end
@@ -10,7 +10,10 @@ module Sys
10
10
 
11
11
  cpu_file = '/proc/cpuinfo'
12
12
  cpu_hash = {}
13
+
14
+ # rubocop:disable Style/MutableConstant
13
15
  CPU_ARRAY = []
16
+ # rubocop:enable Style/MutableConstant
14
17
 
15
18
  private_constant :CPU_ARRAY
16
19
 
@@ -97,8 +100,15 @@ module Sys
97
100
 
98
101
  # Returns a string indicating the CPU model.
99
102
  #
103
+ # Some systems may use slightly different keys in /proc/cpuinfo, so
104
+ # we fall back to other common names and ensure we always return a
105
+ # String.
100
106
  def self.model
101
- CPU_ARRAY.first['model_name']
107
+ CPU_ARRAY.first['model_name'] ||
108
+ CPU_ARRAY.first['model'] ||
109
+ CPU_ARRAY.first['cpu'] ||
110
+ CPU_ARRAY.first['processor'] ||
111
+ ''.dup
102
112
  end
103
113
 
104
114
  # Returns an integer indicating the speed of the CPU.
@@ -107,6 +117,62 @@ module Sys
107
117
  CPU_ARRAY.first['cpu_mhz'].to_f.round
108
118
  end
109
119
 
120
+ # Returns the current CPU usage as a percentage, averaged over a sampling interval.
121
+ #
122
+ # By default, this method samples CPU usage over a 1-second interval and averages two measurements.
123
+ # You can customize the interval and number of samples by passing the +sample_time+ (in seconds)
124
+ # and +samples+ keyword arguments. For example, +cpu_usage(sample_time: 0.5, samples: 4)+ will take four
125
+ # samples, each 0.5 seconds apart,
126
+ # and return the average CPU usage over that period.
127
+ #
128
+ # Passing nil, 0, or a negative value for either argument falls back to the defaults (1.0 seconds and
129
+ # 2 samples) for cross-platform consistency.
130
+ #
131
+ # Returns a Float (percentage), rounded to one decimal place, or nil if CPU usage cannot be determined.
132
+ #
133
+ # Example usage:
134
+ # Sys::CPU.cpu_usage #=> 12.3
135
+ # Sys::CPU.cpu_usage(sample_time: 2, samples: 3) #=> 10.7
136
+ # Sys::CPU.cpu_usage(sample_time: 0, samples: 0) #=> 12.3 # zeros fall back to defaults
137
+ #
138
+ def self.cpu_usage(sample_time: 1.0, samples: 2)
139
+ sample_time = 1.0 if sample_time.nil? || sample_time <= 0
140
+ samples = 2 if samples.nil? || samples <= 0
141
+
142
+ usages = []
143
+
144
+ samples.times do
145
+ stats1 = cpu_stats
146
+ sleep(sample_time)
147
+ stats2 = cpu_stats
148
+
149
+ total_diff = 0.0
150
+ idle_diff = 0.0
151
+
152
+ keys = stats1.key?('cpu') ? ['cpu'] : stats1.keys
153
+ keys.each do |key|
154
+ arr1 = stats1[key]
155
+ arr2 = stats2[key]
156
+ next unless arr1 && arr2
157
+ t1 = arr1.sum
158
+ t2 = arr2.sum
159
+ total = t2 - t1
160
+ idle = (arr2[3] || 0) - (arr1[3] || 0)
161
+ total_diff += total
162
+ idle_diff += idle
163
+ end
164
+
165
+ if total_diff > 0
166
+ usages << ((1.0 - (idle_diff / total_diff)) * 100)
167
+ end
168
+ end
169
+
170
+ return nil if usages.empty?
171
+ (usages.sum / usages.size.to_f).round(1)
172
+ rescue StandardError
173
+ nil
174
+ end
175
+
110
176
  # Create singleton methods for each of the attributes.
111
177
  #
112
178
  def self.method_missing(id, arg = 0)
@@ -165,7 +231,9 @@ module Sys
165
231
  next
166
232
  end
167
233
 
168
- vals = array[1..-1].map{ |e| e.to_i / 100 } # 100 jiffies/sec.
234
+ # Keep raw jiffies counts (do not scale by hz) so deltas over short
235
+ # intervals still produce meaningful values.
236
+ vals = array[1..-1].map{ |e| e.to_i }
169
237
  hash[array[0]] = vals
170
238
  end
171
239
 
@@ -19,10 +19,11 @@ module Sys
19
19
  HW_MODEL = 2 # Specific machine model
20
20
  HW_NCPU = 3 # Number of CPU's
21
21
  HW_CPU_FREQ = 15 # CPU frequency
22
+ HOST_OS = RbConfig::CONFIG['host_os']
22
23
 
23
24
  private_constant :CTL_HW, :HW_MACHINE, :HW_MODEL, :HW_NCPU, :HW_CPU_FREQ
24
25
 
25
- if RbConfig::CONFIG['host_os'] =~ /bsd|dragonfly/
26
+ if HOST_OS =~ /bsd|dragonfly/
26
27
  HW_MACHINE_ARCH = 11 # Machine architecture
27
28
  else
28
29
  HW_MACHINE_ARCH = 12 # Machine architecture
@@ -92,6 +93,7 @@ module Sys
92
93
  # Do nothing, not supported on this platform.
93
94
  end
94
95
 
96
+ # Private wrapper class for the procinfo struct
95
97
  class ProcInfo < FFI::Struct
96
98
  layout(
97
99
  :pi_state, :int,
@@ -101,6 +103,8 @@ module Sys
101
103
  )
102
104
  end
103
105
 
106
+ private_constant :ProcInfo
107
+
104
108
  # Returns the cpu's architecture. On most systems this will be identical
105
109
  # to the CPU.machine method. On OpenBSD it will be identical to the CPU.model
106
110
  # method.
@@ -238,6 +242,16 @@ module Sys
238
242
 
239
243
  # Returns an integer indicating the speed of the CPU.
240
244
  #
245
+ # Note that for BSD systems running on an aarch64 cpu this method
246
+ # will default to a hardclock timer value rather than the actual
247
+ # CPU frequency. This will typically be 1000 (or 100 on VM's).
248
+ #--
249
+ # If there's a better way please let me know. In my experiments locally
250
+ # stuff like dev.cpu.0 did NOT have the information that you might
251
+ # expect to find there, so we cannot rely on that either. In my defense,
252
+ # stuff like lscpu or dmesg did not have the freq information either,
253
+ # not on my aarch64 VM anyway.
254
+ #
241
255
  def self.freq
242
256
  if respond_to?(:sysctlbyname, true)
243
257
  optr = FFI::MemoryPointer.new(:long)
@@ -245,8 +259,12 @@ module Sys
245
259
 
246
260
  size.write_long(optr.size)
247
261
 
248
- if RbConfig::CONFIG['host_os'] =~ /bsd|dragonfly/i
249
- name = 'hw.clockrate'
262
+ if HOST_OS =~ /bsd|dragonfly/i
263
+ if architecture =~ /aarch/i
264
+ name = 'kern.hz'
265
+ else
266
+ name = 'hw.clockrate'
267
+ end
250
268
  else
251
269
  name = 'hw.cpufrequency'
252
270
  end
@@ -291,6 +309,52 @@ module Sys
291
309
  loadavg.get_array_of_double(0, 3)
292
310
  end
293
311
 
312
+ # Returns CPU usage as a percentage, averaged over a sampling interval.
313
+ #
314
+ # By default, samples CPU times twice, 1 second apart. Arguments are keyword-based
315
+ # (+sample_time:+, +samples:+). Passing nil, 0, or a negative value for either
316
+ # falls back to these defaults for cross-platform consistency.
317
+ #
318
+ def self.cpu_usage(sample_time: 1.0, samples: 2)
319
+ cp_time = proc { |ptr|
320
+ len = 5
321
+ size = FFI::MemoryPointer.new(:size_t)
322
+ size.write_ulong(ptr.size)
323
+
324
+ if sysctlbyname('kern.cp_time', ptr, size, nil, 0) < 0
325
+ raise Error, 'sysctlbyname failed'
326
+ end
327
+
328
+ ptr.read_array_of_ulong(len)
329
+ }
330
+
331
+ sample_time = 1.0 if sample_time.nil? || sample_time <= 0
332
+ samples = 2 if samples.nil? || samples <= 0
333
+
334
+ usages = []
335
+
336
+ samples.times do
337
+ t1 = cp_time.call(FFI::MemoryPointer.new(:ulong, 5))
338
+ sleep(sample_time)
339
+ t2 = cp_time.call(FFI::MemoryPointer.new(:ulong, 5))
340
+
341
+ total1 = t1.sum
342
+ total2 = t2.sum
343
+ idle1 = t1[4] || 0
344
+ idle2 = t2[4] || 0
345
+
346
+ total_diff = total2 - total1
347
+ idle_diff = idle2 - idle1
348
+
349
+ usages << ((1.0 - (idle_diff.to_f / total_diff)) * 100) if total_diff > 0
350
+ end
351
+
352
+ return nil if usages.empty?
353
+ (usages.sum / usages.size.to_f).round(1)
354
+ rescue StandardError
355
+ nil
356
+ end
357
+
294
358
  # Returns the floating point processor type.
295
359
  #
296
360
  # Not supported on all platforms.
@@ -117,6 +117,46 @@ module Sys
117
117
  end
118
118
  end
119
119
 
120
+ # Returns CPU usage as a percentage, averaged over multiple samples.
121
+ #
122
+ # The +sample_time+ keyword specifies the interval (in seconds) between samples.
123
+ # The +samples+ keyword specifies how many samples to take and average.
124
+ # The +cpu_num+ keyword selects which CPU to query (0 for total).
125
+ # The +host+ keyword specifies the target machine (defaults to local).
126
+ #
127
+ #--
128
+ # This method uses the _Total Win32_PerfFormattedData_PerfOS_Processor instance
129
+ # (unless a specific +cpu_num+ is requested) to better match Task Manager's total view.
130
+ #
131
+ # Note: Task Manager reports total CPU usage across all cores. Win32_Processor.LoadPercentage
132
+ # is per-processor (usually per physical socket), so it can differ from Task Manager if it falls back.
133
+ #
134
+ def self.cpu_usage(sample_time: 1.0, samples: 2, cpu_num: 0, host: Socket.gethostname)
135
+ sample_time = 1.0 if sample_time.nil? || sample_time <= 0
136
+ samples = 2 if samples.nil? || samples <= 0
137
+ cpu_num = cpu_num.to_i if cpu_num.respond_to?(:to_i)
138
+ instance = cpu_num == 0 ? '_Total' : cpu_num.to_s
139
+ cs = BASE_CS + "//#{host}/root/cimv2:Win32_PerfFormattedData_PerfOS_Processor='#{instance}'"
140
+
141
+ usages = []
142
+
143
+ samples.times do
144
+ begin
145
+ wmi = WIN32OLE.connect(cs)
146
+ rescue WIN32OLERuntimeError
147
+ usages << load_avg(cpu_num, host)
148
+ else
149
+ result = wmi.PercentProcessorTime
150
+ usages << result.to_i if result
151
+ end
152
+ sleep(sample_time)
153
+ end
154
+
155
+ usages.compact!
156
+ return nil if usages.empty?
157
+ (usages.sum / usages.size.to_f).round(1)
158
+ end
159
+
120
160
  # Returns a string indicating the cpu model, e.g. Intel Pentium 4.
121
161
  #
122
162
  def self.model(host = Socket.gethostname)
@@ -390,6 +430,8 @@ module Sys
390
430
  'IA64'
391
431
  when 9
392
432
  'x64'
433
+ when 12
434
+ 'ARM64'
393
435
  end
394
436
  end
395
437
 
@@ -8,14 +8,14 @@
8
8
  require 'sys/cpu'
9
9
  require 'spec_helper'
10
10
 
11
- RSpec.describe Sys::CPU, :bsd => true do
11
+ RSpec.describe Sys::CPU, :bsd do
12
12
  example 'architecture method basic functionality' do
13
13
  expect(described_class).to respond_to(:architecture)
14
14
  expect{ described_class.architecture }.not_to raise_error
15
15
  end
16
16
 
17
17
  example 'architecture method returns a sane value' do
18
- expect(described_class.architecture).to be_kind_of(String)
18
+ expect(described_class.architecture).to be_a(String)
19
19
  expect(described_class.architecture.size).to be > 0
20
20
  end
21
21
 
@@ -29,7 +29,7 @@ RSpec.describe Sys::CPU, :bsd => true do
29
29
  end
30
30
 
31
31
  example 'freq method returns expected value' do
32
- expect(described_class.freq).to be_kind_of(Integer)
32
+ expect(described_class.freq).to be_a(Integer)
33
33
  expect(described_class.freq).to be > 0
34
34
  end
35
35
 
@@ -43,22 +43,42 @@ RSpec.describe Sys::CPU, :bsd => true do
43
43
  end
44
44
 
45
45
  example 'load_avg returns the expected results' do
46
- expect(described_class.load_avg).to be_kind_of(Array)
46
+ expect(described_class.load_avg).to be_a(Array)
47
47
  expect(described_class.load_avg.length).to eq(3)
48
- expect(described_class.load_avg[0]).to be_kind_of(Float)
48
+ expect(described_class.load_avg[0]).to be_a(Float)
49
49
  end
50
50
 
51
51
  example 'load_avg does not accept any arguments' do
52
52
  expect{ described_class.load_avg(0) }.to raise_error(ArgumentError)
53
53
  end
54
54
 
55
+ example 'cpu_usage works as expected' do
56
+ expect(described_class).to respond_to(:cpu_usage)
57
+ expect{ described_class.cpu_usage }.not_to raise_error
58
+ expect{ described_class.cpu_usage(sample_time: 0.1) }.not_to raise_error
59
+ expect(described_class.cpu_usage).to be_a(Numeric).or be_nil
60
+ end
61
+
62
+ example 'cpu_usage falls back on non-positive values' do
63
+ expect{ described_class.cpu_usage(sample_time: 0, samples: 0) }.not_to raise_error
64
+ expect{ described_class.cpu_usage(sample_time: -0.5, samples: -1) }.not_to raise_error
65
+ expect(described_class.cpu_usage(sample_time: 0, samples: 0)).to be_a(Numeric).or be_nil
66
+ end
67
+
68
+ example 'cpu_usage sampling produces a valid range' do
69
+ result = described_class.cpu_usage(sample_time: 0.1)
70
+ expect(result).to be_a(Numeric).or be_nil
71
+ expect(result).to be >= 0 if result
72
+ expect(result).to be <= 100 if result
73
+ end
74
+
55
75
  example 'machine method basic functionality' do
56
76
  expect(described_class).to respond_to(:machine)
57
77
  expect{ described_class.machine }.not_to raise_error
58
78
  end
59
79
 
60
80
  example 'machine method returns sane value' do
61
- expect(described_class.machine).to be_kind_of(String)
81
+ expect(described_class.machine).to be_a(String)
62
82
  expect(described_class.machine.size).to be > 0
63
83
  end
64
84
 
@@ -72,7 +92,7 @@ RSpec.describe Sys::CPU, :bsd => true do
72
92
  end
73
93
 
74
94
  example 'model method returns sane value' do
75
- expect(described_class.model).to be_kind_of(String)
95
+ expect(described_class.model).to be_a(String)
76
96
  expect(described_class.model.length).to be > 0
77
97
  end
78
98
 
@@ -86,7 +106,7 @@ RSpec.describe Sys::CPU, :bsd => true do
86
106
  end
87
107
 
88
108
  example 'num_cpu method returns expected value' do
89
- expect(described_class.num_cpu).to be_kind_of(Integer)
109
+ expect(described_class.num_cpu).to be_a(Integer)
90
110
  expect(described_class.num_cpu).to be > 0
91
111
  end
92
112
 
@@ -94,8 +114,8 @@ RSpec.describe Sys::CPU, :bsd => true do
94
114
  expect{ described_class.num_cpu(0) }.to raise_error(ArgumentError)
95
115
  end
96
116
 
97
- context "ffi methods and constants are private" do
98
- example "ffi constants are private" do
117
+ context 'ffi methods and constants are private' do
118
+ example 'ffi constants are private' do
99
119
  constants = described_class.constants
100
120
  expect(constants).not_to include(:CTL_HW)
101
121
  expect(constants).not_to include(:CPU_TYPE_X86)
@@ -104,13 +124,13 @@ RSpec.describe Sys::CPU, :bsd => true do
104
124
  expect(constants).not_to include(:ClockInfo)
105
125
  end
106
126
 
107
- example "ffi core methods are private" do
127
+ example 'ffi core methods are private' do
108
128
  methods = described_class.methods(false)
109
129
  expect(methods).not_to include(:attach_function)
110
130
  expect(methods).not_to include(:bitmask)
111
131
  end
112
132
 
113
- example "ffi attached methods are private" do
133
+ example 'ffi attached methods are private' do
114
134
  methods = described_class.methods(false)
115
135
  expect(methods).not_to include(:sysctl)
116
136
  expect(methods).not_to include(:sysctlbyname)
@@ -9,30 +9,30 @@
9
9
  require 'sys/cpu'
10
10
  require 'spec_helper'
11
11
 
12
- RSpec.describe Sys::CPU, :hpux => true do
12
+ RSpec.describe Sys::CPU, :hpux do
13
13
  example 'cpu_freq' do
14
14
  expect(described_class).to respond_to(:freq)
15
15
  expect{ described_class.freq }.not_to raise_error
16
16
  expect{ described_class.freq(0) }.not_to raise_error
17
- expect(described_class.freq).to be_kind_of(Integer)
17
+ expect(described_class.freq).to be_a(Integer)
18
18
  end
19
19
 
20
20
  example 'num_cpu' do
21
21
  expect(described_class).to respond_to(:num_cpu)
22
22
  expect{ described_class.num_cpu }.not_to raise_error
23
- expect(described_class.num_cpu).to be_kind_of(Integer)
23
+ expect(described_class.num_cpu).to be_a(Integer)
24
24
  end
25
25
 
26
26
  example 'num_active_cpu' do
27
27
  expect(described_class).to respond_to(:num_active_cpu)
28
28
  expect{ described_class.num_active_cpu }.not_to raise_error
29
- expect(described_class.num_active_cpu).to be_kind_of(Integer)
29
+ expect(described_class.num_active_cpu).to be_a(Integer)
30
30
  end
31
31
 
32
32
  example 'cpu_architecture' do
33
33
  expect(described_class).to respond_to(:architecture)
34
34
  expect{ described_class.architecture }.not_to raise_error
35
- expect(described_class.architecture).to be_kind_of(String)
35
+ expect(described_class.architecture).to be_a(String)
36
36
  end
37
37
 
38
38
  example 'load_avg basic sanity check' do
@@ -47,9 +47,29 @@ RSpec.describe Sys::CPU, :hpux => true do
47
47
  end
48
48
 
49
49
  example 'load_avg expected results' do
50
- expect(described_class.load_avg).to be_kind_of(Array)
51
- expect(described_class.load_avg(0)).to be_kind_of(Array)
50
+ expect(described_class.load_avg).to be_a(Array)
51
+ expect(described_class.load_avg(0)).to be_a(Array)
52
52
  expect(described_class.load_avg.length).to eq(3)
53
53
  expect(described_class.load_avg(0).length).to eq(3)
54
54
  end
55
+
56
+ example 'cpu_usage works as expected' do
57
+ expect(described_class).to respond_to(:cpu_usage)
58
+ expect{ described_class.cpu_usage }.not_to raise_error
59
+ expect{ described_class.cpu_usage(sample_time: 0.1) }.not_to raise_error
60
+ expect(described_class.cpu_usage).to be_a(Numeric).or be_nil
61
+ end
62
+
63
+ example 'cpu_usage falls back on non-positive values' do
64
+ expect{ described_class.cpu_usage(sample_time: 0, samples: 0) }.not_to raise_error
65
+ expect{ described_class.cpu_usage(sample_time: -1, samples: -1) }.not_to raise_error
66
+ expect(described_class.cpu_usage(sample_time: 0, samples: 0)).to be_a(Numeric).or be_nil
67
+ end
68
+
69
+ example 'cpu_usage sampling produces a valid range' do
70
+ result = described_class.cpu_usage(sample_time: 0.1)
71
+ expect(result).to be_a(Numeric).or be_nil
72
+ expect(result).to be >= 0 if result
73
+ expect(result).to be <= 100 if result
74
+ end
55
75
  end
@@ -9,7 +9,7 @@
9
9
  require 'sys/cpu'
10
10
  require 'spec_helper'
11
11
 
12
- RSpec.describe Sys::CPU, :linux => true do
12
+ RSpec.describe Sys::CPU, :linux do
13
13
  example 'dynamic methods are defined as expected' do
14
14
  expect do
15
15
  described_class.processors do |cs|
@@ -25,28 +25,47 @@ RSpec.describe Sys::CPU, :linux => true do
25
25
 
26
26
  example 'cpu_stats works as expected' do
27
27
  expect{ described_class.cpu_stats }.not_to raise_error
28
- expect(described_class.cpu_stats).to be_kind_of(Hash)
28
+ expect(described_class.cpu_stats).to be_a(Hash)
29
29
  expect(described_class.cpu_stats['cpu0'].length).to be >= 4
30
30
  end
31
31
 
32
32
  example 'architecture works as expected' do
33
33
  expect{ described_class.architecture }.not_to raise_error
34
- expect(described_class.architecture).to be_kind_of(String)
34
+ expect(described_class.architecture).to be_a(String)
35
35
  end
36
36
 
37
37
  example 'model works as expected' do
38
38
  expect{ described_class.model }.not_to raise_error
39
- expect(described_class.model).to be_kind_of(String)
39
+ expect(described_class.model).to be_a(String)
40
40
  end
41
41
 
42
42
  example 'freq works as expected' do
43
43
  expect{ described_class.freq }.not_to raise_error
44
- expect(described_class.freq).to be_kind_of(Numeric)
44
+ expect(described_class.freq).to be_a(Numeric)
45
45
  end
46
46
 
47
47
  example 'num_cpu works as expected' do
48
48
  expect{ described_class.num_cpu }.not_to raise_error
49
- expect(described_class.num_cpu).to be_kind_of(Numeric)
49
+ expect(described_class.num_cpu).to be_a(Numeric)
50
+ end
51
+
52
+ example 'cpu_usage works as expected' do
53
+ expect{ described_class.cpu_usage }.not_to raise_error
54
+ expect(described_class.cpu_usage).to be_a(Numeric)
55
+ end
56
+
57
+ example 'cpu_usage falls back on non-positive values' do
58
+ expect{ described_class.cpu_usage(sample_time: 0, samples: 0) }.not_to raise_error
59
+ expect{ described_class.cpu_usage(sample_time: -1, samples: -2) }.not_to raise_error
60
+ expect(described_class.cpu_usage(sample_time: 0, samples: 0)).to be_a(Numeric)
61
+ end
62
+
63
+ example 'cpu_usage sampling produces a valid range' do
64
+ # Sampled usage should be a number between 0 and 100.
65
+ result = described_class.cpu_usage(sample_time: 0.1)
66
+ expect(result).to be_a(Numeric)
67
+ expect(result).to be >= 0
68
+ expect(result).to be <= 100
50
69
  end
51
70
 
52
71
  example 'bogus methods are not picked up by method_missing' do
@@ -11,7 +11,7 @@ require 'rspec'
11
11
 
12
12
  RSpec.shared_examples Sys::CPU do
13
13
  example 'version number is set to the expected value' do
14
- expect(Sys::CPU::VERSION).to eq('1.1.0')
14
+ expect(Sys::CPU::VERSION).to eq('1.3.0')
15
15
  end
16
16
 
17
17
  example 'version number is frozen' do
@@ -10,20 +10,20 @@ require 'spec_helper'
10
10
  require 'sys/cpu'
11
11
  require 'socket'
12
12
 
13
- RSpec.describe Sys::CPU, :windows => true do
13
+ RSpec.describe Sys::CPU, :windows do
14
14
  let(:host) { Socket.gethostname }
15
15
 
16
16
  example 'architecture' do
17
17
  expect(described_class).to respond_to(:architecture)
18
18
  expect{ described_class.architecture }.not_to raise_error
19
19
  expect{ described_class.architecture(host) }.not_to raise_error
20
- expect(described_class.architecture).to be_kind_of(String)
20
+ expect(described_class.architecture).to be_a(String)
21
21
  end
22
22
 
23
23
  example 'freq basic functionality' do
24
24
  expect(described_class).to respond_to(:freq)
25
25
  expect{ described_class.freq }.not_to raise_error
26
- expect(described_class.freq).to be_kind_of(Integer)
26
+ expect(described_class.freq).to be_a(Integer)
27
27
  end
28
28
 
29
29
  example 'freq with arguments' do
@@ -35,28 +35,49 @@ RSpec.describe Sys::CPU, :windows => true do
35
35
  expect(described_class).to respond_to(:model)
36
36
  expect{ described_class.model }.not_to raise_error
37
37
  expect{ described_class.model(host) }.not_to raise_error
38
- expect(described_class.model).to be_kind_of(String)
38
+ expect(described_class.model).to be_a(String)
39
39
  end
40
40
 
41
41
  example 'num_cpu' do
42
42
  expect(described_class).to respond_to(:num_cpu)
43
43
  expect{ described_class.num_cpu }.not_to raise_error
44
44
  expect{ described_class.num_cpu(host) }.not_to raise_error
45
- expect(described_class.num_cpu).to be_kind_of(Integer)
45
+ expect(described_class.num_cpu).to be_a(Integer)
46
46
  end
47
47
 
48
48
  example 'cpu_type' do
49
49
  expect(described_class).to respond_to(:cpu_type)
50
50
  expect{ described_class.cpu_type }.not_to raise_error
51
51
  expect{ described_class.cpu_type(host) }.not_to raise_error
52
- expect(described_class.cpu_type).to be_kind_of(String)
52
+ expect(described_class.cpu_type).to be_a(String)
53
53
  end
54
54
 
55
55
  example 'load_avg' do
56
56
  expect(described_class).to respond_to(:load_avg)
57
57
  expect{ described_class.load_avg }.not_to raise_error
58
58
  expect{ described_class.load_avg(0, host) }.not_to raise_error
59
- expect(described_class.load_avg).to be_kind_of(Integer).or be_kind_of(NilClass)
59
+ expect(described_class.load_avg).to be_a(Integer).or be_a(NilClass)
60
+ end
61
+
62
+ example 'cpu_usage works as expected' do
63
+ expect(described_class).to respond_to(:cpu_usage)
64
+ expect{ described_class.cpu_usage }.not_to raise_error
65
+ expect{ described_class.cpu_usage(sample_time: 0.1, samples: 0, host: host) }.not_to raise_error
66
+ expect(described_class.cpu_usage).to be_a(Numeric).or be_a(NilClass)
67
+ end
68
+
69
+ example 'cpu_usage falls back on non-positive values' do
70
+ expect{ described_class.cpu_usage(sample_time: 0, samples: 0, host: host) }.not_to raise_error
71
+ expect{ described_class.cpu_usage(sample_time: -1, samples: -1, host: host) }.not_to raise_error
72
+ expect(described_class.cpu_usage(sample_time: 0, samples: 0, host: host)).to be_a(Numeric).or be_a(NilClass)
73
+ end
74
+
75
+ example 'cpu_usage sampling produces a valid range' do
76
+ # Sampled usage should be a number between 0 and 100.
77
+ result = described_class.cpu_usage(sample_time: 0.1)
78
+ expect(result).to be_a(Numeric).or be_nil
79
+ expect(result).to be >= 0 if result
80
+ expect(result).to be <= 100 if result
60
81
  end
61
82
 
62
83
  example 'processors' do
data/sys-cpu.gemspec CHANGED
@@ -2,7 +2,7 @@ require 'rubygems'
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = 'sys-cpu'
5
- spec.version = '1.1.0'
5
+ spec.version = '1.3.0'
6
6
  spec.author = 'Daniel J. Berger'
7
7
  spec.email = 'djberg96@gmail.com'
8
8
  spec.license = 'Apache-2.0'
@@ -17,6 +17,11 @@ Gem::Specification.new do |spec|
17
17
  # and Linux was worth the tradeoff of not having to create 3 separate gems.
18
18
  spec.add_dependency('ffi', '~> 1.1')
19
19
 
20
+ if Gem.win_platform?
21
+ spec.platform = Gem::Platform.new(['universal', 'mingw32'])
22
+ spec.add_dependency('win32ole')
23
+ end
24
+
20
25
  spec.add_development_dependency('rake')
21
26
  spec.add_development_dependency('rubocop')
22
27
  spec.add_development_dependency('rspec', '~> 3.9')
@@ -30,7 +35,8 @@ Gem::Specification.new do |spec|
30
35
  'source_code_uri' => 'https://github.com/djberg96/sys-cpu',
31
36
  'wiki_uri' => 'https://github.com/djberg96/sys-cpu/wiki',
32
37
  'rubygems_mfa_required' => 'true',
33
- 'github_repo' => 'https://github.com/djberg96/sys-cpu'
38
+ 'github_repo' => 'https://github.com/djberg96/sys-cpu',
39
+ 'funding_uri' => 'https://github.com/sponsors/djberg96'
34
40
  }
35
41
 
36
42
  spec.description = <<-EOF
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,11 +1,10 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sys-cpu
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel J. Berger
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain:
11
10
  - |
@@ -35,7 +34,7 @@ cert_chain:
35
34
  ORVCZpRuCPpmC8qmqxUnARDArzucjaclkxjLWvCVHeFa9UP7K3Nl9oTjJNv+7/jM
36
35
  WZs4eecIcUc4tKdHxcAJ0MO/Dkqq7hGaiHpwKY76wQ1+8xAh
37
36
  -----END CERTIFICATE-----
38
- date: 2024-06-10 00:00:00.000000000 Z
37
+ date: 1980-01-02 00:00:00.000000000 Z
39
38
  dependencies:
40
39
  - !ruby/object:Gem::Dependency
41
40
  name: ffi
@@ -157,7 +156,7 @@ metadata:
157
156
  wiki_uri: https://github.com/djberg96/sys-cpu/wiki
158
157
  rubygems_mfa_required: 'true'
159
158
  github_repo: https://github.com/djberg96/sys-cpu
160
- post_install_message:
159
+ funding_uri: https://github.com/sponsors/djberg96
161
160
  rdoc_options: []
162
161
  require_paths:
163
162
  - lib
@@ -172,8 +171,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
172
171
  - !ruby/object:Gem::Version
173
172
  version: '0'
174
173
  requirements: []
175
- rubygems_version: 3.3.26
176
- signing_key:
174
+ rubygems_version: 4.0.3
177
175
  specification_version: 4
178
176
  summary: A Ruby interface for providing CPU information
179
177
  test_files:
metadata.gz.sig CHANGED
Binary file