epitools 0.5.106 → 0.5.107

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
  SHA1:
3
- metadata.gz: e62d7058f8223acc7bf84eb7c77fc32e78c94735
4
- data.tar.gz: c9b8d58d8dd39c12e78ccf29aeacc5a0c109edfc
3
+ metadata.gz: b2acadccead525875804ab124cff3c74f5557c49
4
+ data.tar.gz: 1f845c9016e66d67fe845d5760ecb648396bf579
5
5
  SHA512:
6
- metadata.gz: 89a89b7b72de4c1c71f46af1a2b27e2dc02811d82034c8e17cd84a36e44c0a14fb28ead350509f150b3ec6f258175d2a3714a5f9455409a84924735a659cf794
7
- data.tar.gz: 4962e60b4daf5eb39c623d606a04bc08846e24aae70f2e003028c96c2f4f6a76874d030adfb8972079bbd485de06d4bf2afac931e1fdbfe3d3b70113ed1f93d7
6
+ metadata.gz: 2091b4e7fa2c94c9b41da8ce0ba02b38f04fc5eeb7e40407ac5c8b2d5a0d7ad86c2a8be33109e9301fa8f75324114ae42058b3e266e9e11d7144789b2eaf948e
7
+ data.tar.gz: e96f90fa44265d05785d51fdd576c57d9ee792cd517d0f52f729cdce5c6dc794c849e74ace66196f561c9623f0e4adb5ffba9b42a38bf4f0fb826be0cf0065ee
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.106
1
+ 0.5.107
@@ -190,7 +190,7 @@ module Enumerable
190
190
  queue = Queue.new
191
191
  each { |e| queue.push e }
192
192
 
193
- Enumerator.new do |y|
193
+ Enumerator.new(queue.size) do |y|
194
194
  workers = (0...num_workers).map do
195
195
  Thread.new do
196
196
  begin
@@ -569,7 +569,7 @@ class Enumerator
569
569
  def +(other)
570
570
  raise "Can only concatenate Enumerable things to Enumerators" unless Enumerable === other
571
571
 
572
- Enumerator.new do |yielder|
572
+ Enumerator.new(count + other.count) do |yielder|
573
573
  each { |e| yielder << e }
574
574
  other.each { |e| yielder << e }
575
575
  end
@@ -601,7 +601,7 @@ class Enumerator
601
601
  # returning a new Enumerator. (The argument must be some kind of Enumerable.)
602
602
  #
603
603
  def cross_product(other)
604
- Enumerator.new do |yielder|
604
+ Enumerator.new(count + other.count) do |yielder|
605
605
  each { |a| other.each { |b| yielder << [a,b] } }
606
606
  end
607
607
  end
@@ -222,6 +222,14 @@ class String
222
222
  URI.unescape(self)
223
223
  end
224
224
 
225
+ #
226
+ # URI.parse the string and return an URI object
227
+ #
228
+ def to_uri
229
+ URI.parse self
230
+ end
231
+ alias_method :to_URI, :to_uri
232
+
225
233
  #
226
234
  # Convert a query string to a hash of params
227
235
  #
@@ -364,7 +364,9 @@ class Path
364
364
  end
365
365
 
366
366
  def size
367
- File.size path
367
+ File.size(path)
368
+ rescue Errno::ENOENT
369
+ -1
368
370
  end
369
371
 
370
372
  def lstat
@@ -4,494 +4,7 @@ require 'epitools/minimal'
4
4
  # Cross-platform operating system functions.
5
5
  # Includes: process listing, platform detection, etc.
6
6
  #
7
- module Sys
8
-
9
- #-----------------------------------------------------------------------------
10
-
11
- #
12
- # Return the current operating system: Darwin, Linux, or Windows.
13
- #
14
- def self.os
15
- return @os if @os
16
-
17
- require 'rbconfig'
18
- if defined? RbConfig
19
- host_os = RbConfig::CONFIG['host_os']
20
- else
21
- host_os = Config::CONFIG['host_os']
22
- end
23
-
24
- case host_os
25
- when /darwin/
26
- @os = "Darwin"
27
- when /bsd/
28
- @os = "BSD"
29
- when /linux/
30
- @os = "Linux"
31
- when /mingw|mswin|cygwin/
32
- @os = 'Windows'
33
- else
34
- #raise "Unknown OS: #{host_os.inspect}"
35
- end
36
-
37
- @os
38
- end
39
-
40
- #
41
- # Is this Linux?
42
- #
43
- def self.linux?
44
- os == "Linux"
45
- end
46
-
47
- #
48
- # Is this Windows?
49
- #
50
- def self.windows?
51
- os == "Windows"
52
- end
53
-
54
- #
55
- # Is this Darwin?
56
- #
57
- def self.darwin?
58
- os == "Darwin"
59
- end
60
-
61
- #
62
- # Is this a Mac? (aka. Darwin?)
63
- #
64
- def self.mac?; darwin?; end
65
-
66
- #
67
- # Is this BSD?
68
- #
69
- def self.bsd?
70
- os == "BSD" or os == "Darwin"
71
- end
72
-
73
- #-----------------------------------------------------------------------------
74
-
75
- PS_FIELD_TABLE = [
76
- [:pid, :to_i],
77
- [:ppid, :to_i],
78
- [:pcpu, :to_f],
79
- [:pmem, :to_f],
80
- [:stat, :to_s],
81
- [:rss, :to_i],
82
- [:vsz, :to_i],
83
- [:user, :to_s],
84
- [:majflt, :to_i],
85
- [:minflt, :to_i],
86
- [:command,:to_s],
87
- ]
88
-
89
- PS_FIELDS = PS_FIELD_TABLE.map { |name, func| name }
90
- PS_FIELD_TRANSFORMS = Hash[ *PS_FIELD_TABLE.flatten ]
91
-
92
- class ProcessNotFound < Exception; end
93
-
94
- #
95
- # Contains all the information that PS can report about a process for
96
- # the current platform.
97
- #
98
- # The following attribute accessor methods are available:
99
- #
100
- # pid (integer)
101
- # command (string -- the 'ps' name)
102
- # name (alias for 'command')
103
- # pcpu (float)
104
- # pmem (float)
105
- # stat (string)
106
- # rss (integer)
107
- # vsz (integer)
108
- # user (string)
109
- # majflt (integer)
110
- # minflt (integer)
111
- # state (array of symbols; see DARWIN_STATES or LINUX_STATES)
112
- #
113
- # Only on linux:
114
- # exename (string -- path to the binary)
115
- # fds (array -- list of open file descriptors)
116
- #
117
- class ProcessInfo < Struct.new(*PS_FIELDS+[:state])
118
-
119
- DARWIN_STATES = {
120
- "R"=>:running,
121
- "S"=>:sleeping,
122
- "I"=>:idle,
123
- "T"=>:stopped,
124
- "U"=>:wait,
125
- "Z"=>:zombie,
126
- "W"=>:swapped,
127
-
128
- "s"=>:session_leader,
129
- "X"=>:debugging,
130
- "E"=>:exiting,
131
- "<"=>:high_priority,
132
- "N"=>:low_priority,
133
- "+"=>:foreground,
134
- "L"=>:locked_pages,
135
- }
136
-
137
- LINUX_STATES = {
138
- "R"=>:running,
139
- "S"=>:sleeping,
140
- "T"=>:stopped,
141
- "D"=>:wait,
142
- "Z"=>:zombie,
143
- "W"=>:swapped,
144
- "X"=>:dead,
145
-
146
- "s"=>:session_leader,
147
- "<"=>:high_priority,
148
- "N"=>:low_priority,
149
- "+"=>:foreground,
150
- "L"=>:locked_pages,
151
- "l"=>:multithreaded,
152
- }
153
-
154
- def initialize(*args)
155
- @dead = false
156
- args << stat_to_state(args[PS_FIELDS.index(:stat)])
157
- super(*args)
158
- end
159
-
160
- def parent
161
- Sys.ps(ppid).first unless ppid < 1
162
- end
163
-
164
- def children
165
- @@parents ||= Sys.ps.group_by(&:ppid)
166
- @@parents[pid]
167
- end
168
-
169
- #
170
- # Convert all the process information to a hash.
171
- #
172
- def to_hash
173
- Hash[ *members.zip(values).flatten(1) ]
174
- end
175
-
176
- #
177
- # Send the TERM signal to this process.
178
- #
179
- def kill!(signal="TERM")
180
- puts "Killing #{pid} (#{signal})"
181
- Process.kill(signal, pid)
182
- # TODO: handle exception Errno::ESRCH (no such process)
183
- end
184
-
185
- #
186
- # Has this process been killed?
187
- #
188
- def dead?
189
- @dead ||= Sys.pid(pid).empty?
190
- end
191
-
192
- #
193
- # Refresh this process' statistics.
194
- #
195
- def refresh
196
- processes = Sys.ps(pid)
197
-
198
- if processes.empty?
199
- @dead = true
200
- raise ProcessNotFound
201
- end
202
-
203
- updated_process = processes.first
204
- members.each { |member| self[member] = updated_process[member] }
205
- self
206
- end
207
-
208
- alias_method :name, :command
209
-
210
- # Linux-specific methods
211
- if Sys.linux?
212
-
213
- def exename
214
- @exename ||= File.readlink("/proc/#{pid}/exe") rescue :unknown
215
- @exename == :unknown ? nil : @exename
216
- end
217
-
218
- def fds
219
- Dir["/proc/#{pid}/fd/*"].map { |fd| File.readlink(fd) rescue nil }
220
- end
221
-
222
- end
223
-
224
- private
225
-
226
- def stat_to_state(str)
227
- states = case Sys.os
228
- when "Linux" then LINUX_STATES
229
- when "Darwin" then DARWIN_STATES
230
- else raise "Unsupported platform: #{Sys.os}"
231
- end
232
-
233
- str.scan(/./).map { |char| states[char] }.compact
234
- end
235
- end
236
-
237
- #-----------------------------------------------------------------------------
238
-
239
- def self.tree
240
- tree = Sys.ps.group_by(&:ppid)
241
- Hash[tree.map do |ppid, children|
242
- kvs = children.map { |child| [child.pid, tree.delete(child.pid)] }
243
- [ppid, Hash[kvs]]
244
- end]
245
- end
246
-
247
- #
248
- # List all (or specified) processes, and return ProcessInfo objects.
249
- # (Takes an optional list of pids as arguments.)
250
- #
251
- def self.ps(*pids)
252
- #return @@cache if @@cache
253
-
254
- options = PS_FIELDS.join(',')
255
-
256
- pids = pids.map(&:to_i)
257
-
258
- if pids.any?
259
- command = "ps -p #{pids.join(',')} -o #{options}"
260
- else
261
- command = "ps awx -o #{options}"
262
- end
263
-
264
- lines = `#{command}`.lines.to_a
265
-
266
- lines[1..-1].map do |line|
267
- fields = line.split
268
- if fields.size > PS_FIELDS.size
269
- fields = fields[0..PS_FIELDS.size-2] + [fields[PS_FIELDS.size-1..-1].join(" ")]
270
- end
271
-
272
- fields = PS_FIELDS.zip(fields).map { |name, value| value.send(PS_FIELD_TRANSFORMS[name]) }
273
-
274
- ProcessInfo.new(*fields)
275
- end
276
- end
277
-
278
- #-----------------------------------------------------------------------------
279
-
280
- def self.refresh
281
- @@cache = nil
282
- end
283
-
284
- #
285
- # Trap signals!
286
- #
287
- # usage: trap("EXIT", "HUP", "ETC", :ignore=>["VTALRM"]) { |signal| puts "Got #{signal}!" }
288
- # (Execute Signal.list to see what's available.)
289
- #
290
- # No paramters defaults to all signals except VTALRM, CHLD, CLD, and EXIT.
291
- #
292
- def self.trap(*args, &block)
293
- options = if args.last.is_a?(Hash) then args.pop else Hash.new end
294
- args = [args].flatten
295
- signals = if args.any? then args else Signal.list.keys end
296
-
297
- ignore = %w[ VTALRM CHLD CLD EXIT ] unless ignore = options[:ignore]
298
- ignore = [ignore] unless ignore.is_a? Array
299
-
300
- signals = signals - ignore
301
-
302
- signals.each do |signal|
303
- p [:sig, signal]
304
- Signal.trap(signal) { yield signal }
305
- end
306
- end
307
-
308
- #-----------------------------------------------------------------------------
309
-
310
- #
311
- # A metaprogramming helper that allows you to write platform-specific methods
312
- # which the user can call with one name. Here's how to use it:
313
- #
314
- # Define these methods:
315
- # reboot_linux, reboot_darwin, reboot_windows
316
- #
317
- # Call the magic method:
318
- # cross_platform_method(:reboot)
319
- #
320
- # Now the user can execute "reboot" on any platform!
321
- #
322
- # (Note: If you didn't create a method for a specific platform, then you'll get
323
- # NoMethodError exception when the "reboot" method is called on that platform.)
324
- #
325
- def self.cross_platform_method(name)
326
- platform_method_name = "#{name}_#{os.downcase}"
327
- metaclass.instance_eval do
328
- define_method(name) do |*args|
329
- begin
330
- self.send(platform_method_name, *args)
331
- rescue NoMethodError
332
- raise NotImplementedError.new("#{name} is not yet supported on #{os}.")
333
- end
334
- end
335
- end
336
- end
337
-
338
- #-----------------------------------------------------------------------------
339
-
340
- cross_platform_method :hostname
341
-
342
- def self.hostname_linux
343
- `uname -n`.strip
344
- end
345
-
346
- def self.hostname_mac
347
- `uname -n`.strip.gsub(/\.local$/, '')
348
- end
349
-
350
- def self.hostname_windows
351
- raise NotImplementedError
352
- end
353
-
354
- #-----------------------------------------------------------------------------
355
-
356
- cross_platform_method :interfaces
357
-
358
- #
359
- # Darwin: Return a hash of (device, IP address) pairs.
360
- #
361
- # eg: {"en0"=>"192.168.1.101"}
362
- #
363
- def self.interfaces_bsd
364
- sections = `ifconfig`.split(/^(?=[^\t])/)
365
- sections_with_relevant_ip = sections.select {|i| i =~ /inet/ }
366
-
367
- device_ips = {}
368
- sections_with_relevant_ip.each do |section|
369
- device = section[/[^:]+/]
370
- ip = section[/inet ([^ ]+)/, 1]
371
- device_ips[device] = ip
372
- end
373
-
374
- device_ips
375
- end
376
-
377
- def self.interfaces_darwin; interfaces_bsd; end
378
-
379
- #
380
- # Linux: Return a hash of (device, IP address) pairs.
381
- #
382
- # eg: {"eth0"=>"192.168.1.101"}
383
- #
384
- def self.interfaces_linux
385
- sections = `/sbin/ifconfig`.split(/^(?=Link encap:Ethernet)/)
386
- sections_with_relevant_ip = sections.select {|i| i =~ /inet/ }
387
-
388
- device_ips = {}
389
- sections_with_relevant_ip.each do |section|
390
- device = section[/([\w\d]+)\s+Link encap:Ethernet/, 1]
391
- ip = section[/inet addr:([^\s]+)/, 1]
392
- device_ips[device] = ip
393
- end
394
-
395
- device_ips
396
- end
397
-
398
- #
399
- # Windows: Return a hash of (device name, IP address) pairs.
400
- #
401
- def self.interfaces_windows
402
- result = {}
403
- `ipconfig`.split_before(/^\w.+:/).each do |chunk|
404
- chunk.grep(/^Ethernet adapter (.+):\s*$/) do
405
- name = $1
406
- chunk.grep(/IPv[46] Address[\.\ ]+: (.+)$/) do
407
- address = $1.strip
408
- result[name] = address
409
- end
410
- end
411
- end
412
- result
413
- end
414
-
415
- #-----------------------------------------------------------------------------
416
-
417
- cross_platform_method :browser_open
418
-
419
- #
420
- # Linux: Open an URL in the default browser (using "gnome-open").
421
- #
422
- def browser_open_linux(url)
423
- system("gnome-open", url)
424
- end
425
-
426
- #
427
- # Darwin: Open the webpage in a new chrome tab.
428
- #
429
- def browser_open_darwin(url)
430
- system("open", "-a", "chrome", url)
431
- end
432
-
433
- #-----------------------------------------------------------------------------
434
-
435
- cross_platform_method :memstat
436
-
437
- def self.memstat_linux
438
- #$ free
439
- # total used free shared buffers cached
440
- #Mem: 4124380 3388548 735832 0 561888 968004
441
- #-/+ buffers/cache: 1858656 2265724
442
- #Swap: 2104504 166724 1937780
443
-
444
- #$ vmstat
445
- raise "Not implemented"
446
- end
447
-
448
- def self.memstat_darwin
449
- #$ vm_stat
450
- #Mach Virtual Memory Statistics: (page size of 4096 bytes)
451
- #Pages free: 198367.
452
- #Pages active: 109319.
453
- #Pages inactive: 61946.
454
- #Pages speculative: 18674.
455
- #Pages wired down: 70207.
456
- #"Translation faults": 158788687.
457
- #Pages copy-on-write: 17206973.
458
- #Pages zero filled: 54584525.
459
- #Pages reactivated: 8768.
460
- #Pageins: 176076.
461
- #Pageouts: 3757.
462
- #Object cache: 16 hits of 255782 lookups (0% hit rate)
463
-
464
- #$ iostat
465
- raise "Not implemented"
466
- end
467
-
468
- #-----------------------------------------------------------------------------
469
-
470
- def self.temperatures
471
- #/Applications/Utilities/TemperatureMonitor.app/Contents/MacOS/tempmonitor -a -l
472
- #CPU Core 1: 28 C
473
- #CPU Core 2: 28 C
474
- #SMART Disk Hitachi HTS543216L9SA02 (090831FBE200VCGH3D5F): 40 C
475
- #SMC CPU A DIODE: 41 C
476
- #SMC CPU A HEAT SINK: 42 C
477
- #SMC DRIVE BAY 1: 41 C
478
- #SMC NORTHBRIDGE POS 1: 46 C
479
- #SMC WLAN CARD: 45 C
480
- raise "Not implemented"
481
- end
482
-
483
- end
484
-
485
- if $0 == __FILE__
486
- require 'pp'
487
- procs = Sys.ps
488
- p [:processes, procs.size]
489
- # some = procs[0..3]
490
- # some.each{|ps| pp ps}
491
- # some.first.kill!
492
- # pp some.first.to_hash
493
- # p [:total_cpu, procs.map{|ps| ps.pcpu}.sum]
494
- # p [:total_mem, procs.map{|ps| ps.pmem}.sum]
495
-
496
- pp Sys.interfaces
497
- end
7
+ require 'epitools/sys/ps'
8
+ require 'epitools/sys/mounts'
9
+ require 'epitools/sys/misc'
10
+ require 'epitools/sys/net'
@@ -0,0 +1,38 @@
1
+ module Sys
2
+
3
+ #-----------------------------------------------------------------------------
4
+
5
+ cross_platform_method :memstat
6
+
7
+ def self.memstat_linux
8
+ #$ free
9
+ # total used free shared buffers cached
10
+ #Mem: 4124380 3388548 735832 0 561888 968004
11
+ #-/+ buffers/cache: 1858656 2265724
12
+ #Swap: 2104504 166724 1937780
13
+
14
+ #$ vmstat
15
+ raise "Not implemented"
16
+ end
17
+
18
+ def self.memstat_darwin
19
+ #$ vm_stat
20
+ #Mach Virtual Memory Statistics: (page size of 4096 bytes)
21
+ #Pages free: 198367.
22
+ #Pages active: 109319.
23
+ #Pages inactive: 61946.
24
+ #Pages speculative: 18674.
25
+ #Pages wired down: 70207.
26
+ #"Translation faults": 158788687.
27
+ #Pages copy-on-write: 17206973.
28
+ #Pages zero filled: 54584525.
29
+ #Pages reactivated: 8768.
30
+ #Pageins: 176076.
31
+ #Pageouts: 3757.
32
+ #Object cache: 16 hits of 255782 lookups (0% hit rate)
33
+
34
+ #$ iostat
35
+ raise "Not implemented"
36
+ end
37
+
38
+ end
@@ -0,0 +1,60 @@
1
+ module Sys
2
+
3
+ #
4
+ # Trap signals!
5
+ #
6
+ # usage: trap("EXIT", "HUP", "ETC", :ignore=>["VTALRM"]) { |signal| puts "Got #{signal}!" }
7
+ # (Execute Signal.list to see what's available.)
8
+ #
9
+ # No paramters defaults to all signals except VTALRM, CHLD, CLD, and EXIT.
10
+ #
11
+ def self.trap(*args, &block)
12
+ options = if args.last.is_a?(Hash) then args.pop else Hash.new end
13
+ args = [args].flatten
14
+ signals = if args.any? then args else Signal.list.keys end
15
+
16
+ ignore = %w[ VTALRM CHLD CLD EXIT ] unless ignore = options[:ignore]
17
+ ignore = [ignore] unless ignore.is_a? Array
18
+
19
+ signals = signals - ignore
20
+
21
+ signals.each do |signal|
22
+ p [:sig, signal]
23
+ Signal.trap(signal) { yield signal }
24
+ end
25
+ end
26
+
27
+ #-----------------------------------------------------------------------------
28
+
29
+ cross_platform_method :browser_open
30
+
31
+ #
32
+ # Linux: Open an URL in the default browser (using "xdg-open").
33
+ #
34
+ def browser_open_linux(url)
35
+ system("xdg-open", url)
36
+ end
37
+
38
+ #
39
+ # Darwin: Open the webpage in a new chrome tab.
40
+ #
41
+ def browser_open_darwin(url)
42
+ system("open", "-a", "chrome", url)
43
+ end
44
+
45
+ #-----------------------------------------------------------------------------
46
+
47
+ def self.temperatures
48
+ #/Applications/Utilities/TemperatureMonitor.app/Contents/MacOS/tempmonitor -a -l
49
+ #CPU Core 1: 28 C
50
+ #CPU Core 2: 28 C
51
+ #SMART Disk Hitachi HTS543216L9SA02 (090831FBE200VCGH3D5F): 40 C
52
+ #SMC CPU A DIODE: 41 C
53
+ #SMC CPU A HEAT SINK: 42 C
54
+ #SMC DRIVE BAY 1: 41 C
55
+ #SMC NORTHBRIDGE POS 1: 46 C
56
+ #SMC WLAN CARD: 45 C
57
+ raise NotImplementedError.new("Sorry")
58
+ end
59
+
60
+ end
@@ -0,0 +1,45 @@
1
+ module Sys
2
+
3
+ #
4
+ # Get an array of mounted filesystems (as fancy objects)
5
+ #
6
+ def self.mounts
7
+ if linux?
8
+ IO.popen(["findmnt", "--raw"]) { |io| io.drop(1).map { |line| Mount.new line } }
9
+ else
10
+ raise NotImplementedError.new("I dunno, how do you find mounts on #{os}?")
11
+ end
12
+ end
13
+
14
+
15
+ class Mount
16
+ attr_accessor :dev, :type, :options
17
+
18
+ def initialize(line)
19
+ @path, @dev, @type, @options = line.strip.split(' ')
20
+ @options = @options.split(",")
21
+ end
22
+
23
+ def system?
24
+ (path =~ %r{^/(sys|dev|proc|run/user|tmp)}) or dev == "systemd-1"
25
+ end
26
+
27
+ def inspect
28
+ "#{type}: #{path} (#{dev})"
29
+ end
30
+
31
+ def to_s
32
+ "#{path} (#{dev})"
33
+ end
34
+
35
+ def path
36
+ # Unescape findmnt's hex codes
37
+ Path.new "#{eval %{"#{@path}"}}/"
38
+ end
39
+
40
+ def dirname
41
+ path.dirs.last
42
+ end
43
+ end
44
+
45
+ end
@@ -0,0 +1,85 @@
1
+ module Sys
2
+
3
+ #-----------------------------------------------------------------------------
4
+
5
+ cross_platform_method :hostname
6
+
7
+ def self.hostname_linux
8
+ `uname -n`.strip
9
+ end
10
+
11
+ def self.hostname_mac
12
+ `uname -n`.strip.gsub(/\.local$/, '')
13
+ end
14
+
15
+ def self.hostname_windows
16
+ raise NotImplementedError
17
+ end
18
+
19
+ #-----------------------------------------------------------------------------
20
+
21
+ cross_platform_method :interfaces
22
+
23
+ #
24
+ # BSD: Return a hash of (device, IP address) pairs.
25
+ #
26
+ # eg: {"en0"=>"192.168.1.101"}
27
+ #
28
+ def self.interfaces_bsd
29
+ sections = `ifconfig`.split(/^(?=[^\t])/)
30
+ sections_with_relevant_ip = sections.select {|i| i =~ /inet/ }
31
+
32
+ device_ips = {}
33
+ sections_with_relevant_ip.each do |section|
34
+ device = section[/[^:]+/]
35
+ ip = section[/inet ([^ ]+)/, 1]
36
+ device_ips[device] = ip
37
+ end
38
+
39
+ device_ips
40
+ end
41
+
42
+ #
43
+ # Darwin: Do whatever BSD does
44
+ #
45
+ def self.interfaces_darwin
46
+ interfaces_bsd
47
+ end
48
+
49
+ #
50
+ # Linux: Return a hash of (device, IP address) pairs.
51
+ #
52
+ # eg: {"eth0"=>"192.168.1.101"}
53
+ #
54
+ def self.interfaces_linux
55
+ sections = `/sbin/ifconfig`.split(/^(?=Link encap:Ethernet)/)
56
+ sections_with_relevant_ip = sections.select {|i| i =~ /inet/ }
57
+
58
+ device_ips = {}
59
+ sections_with_relevant_ip.each do |section|
60
+ device = section[/([\w\d]+)\s+Link encap:Ethernet/, 1]
61
+ ip = section[/inet addr:([^\s]+)/, 1]
62
+ device_ips[device] = ip
63
+ end
64
+
65
+ device_ips
66
+ end
67
+
68
+ #
69
+ # Windows: Return a hash of (device name, IP address) pairs.
70
+ #
71
+ def self.interfaces_windows
72
+ result = {}
73
+ `ipconfig`.split_before(/^\w.+:/).each do |chunk|
74
+ chunk.grep(/^Ethernet adapter (.+):\s*$/) do
75
+ name = $1
76
+ chunk.grep(/IPv[46] Address[\.\ ]+: (.+)$/) do
77
+ address = $1.strip
78
+ result[name] = address
79
+ end
80
+ end
81
+ end
82
+ result
83
+ end
84
+
85
+ end
@@ -0,0 +1,94 @@
1
+ module Sys
2
+
3
+ #
4
+ # Return the current operating system: Darwin, Linux, or Windows.
5
+ #
6
+ def self.os
7
+ return @os if @os
8
+
9
+ require 'rbconfig'
10
+ if defined? RbConfig
11
+ host_os = RbConfig::CONFIG['host_os']
12
+ else
13
+ host_os = Config::CONFIG['host_os']
14
+ end
15
+
16
+ case host_os
17
+ when /darwin/
18
+ @os = "Darwin"
19
+ when /bsd/
20
+ @os = "BSD"
21
+ when /linux/
22
+ @os = "Linux"
23
+ when /mingw|mswin|cygwin/
24
+ @os = 'Windows'
25
+ else
26
+ #raise "Unknown OS: #{host_os.inspect}"
27
+ end
28
+
29
+ @os
30
+ end
31
+
32
+ #
33
+ # Is this Linux?
34
+ #
35
+ def self.linux?
36
+ os == "Linux"
37
+ end
38
+
39
+ #
40
+ # Is this Windows?
41
+ #
42
+ def self.windows?
43
+ os == "Windows"
44
+ end
45
+
46
+ #
47
+ # Is this Darwin?
48
+ #
49
+ def self.darwin?
50
+ os == "Darwin"
51
+ end
52
+
53
+ #
54
+ # Is this a Mac? (aka. Darwin?)
55
+ #
56
+ def self.mac?; darwin?; end
57
+
58
+ #
59
+ # Is this BSD?
60
+ #
61
+ def self.bsd?
62
+ os == "BSD" or os == "Darwin"
63
+ end
64
+
65
+
66
+ #
67
+ # A metaprogramming helper that allows you to write platform-specific methods
68
+ # which the user can call with one name. Here's how to use it:
69
+ #
70
+ # Define these methods:
71
+ # reboot_linux, reboot_darwin, reboot_windows
72
+ #
73
+ # Call the magic method:
74
+ # cross_platform_method(:reboot)
75
+ #
76
+ # Now the user can execute "reboot" on any platform!
77
+ #
78
+ # (Note: If you didn't create a method for a specific platform, then you'll get
79
+ # NoMethodError exception when the "reboot" method is called on that platform.)
80
+ #
81
+ def self.cross_platform_method(name)
82
+ platform_method_name = "#{name}_#{os.downcase}"
83
+ metaclass.instance_eval do
84
+ define_method(name) do |*args|
85
+ begin
86
+ self.send(platform_method_name, *args)
87
+ rescue NoMethodError
88
+ raise NotImplementedError.new("#{name} is not yet supported on #{os}.")
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ end
@@ -0,0 +1,210 @@
1
+ require_relative "os"
2
+
3
+ module Sys
4
+
5
+ #
6
+ # List all (or specified) processes, and return ProcessInfo objects.
7
+ # (Takes an optional list of pids as arguments.)
8
+ #
9
+ def self.ps(*pids)
10
+ raise "that's too many pids!" if pids.size > 999_999
11
+
12
+ options = PS_FIELDS.join(',')
13
+
14
+ pids = pids.map(&:to_i)
15
+
16
+ if pids.any?
17
+ command = "ps -p #{pids.join(',')} -o #{options}"
18
+ else
19
+ command = "ps awx -o #{options}"
20
+ end
21
+
22
+ lines = `#{command}`.lines.to_a
23
+
24
+ lines[1..-1].map do |line|
25
+ fields = line.split
26
+ if fields.size > PS_FIELDS.size
27
+ fields = fields[0..PS_FIELDS.size-2] + [fields[PS_FIELDS.size-1..-1].join(" ")]
28
+ end
29
+
30
+ fields = PS_FIELDS.zip(fields).map { |name, value| value.send(PS_FIELD_TRANSFORMS[name]) }
31
+
32
+ ProcessInfo.new(*fields)
33
+ end
34
+ end
35
+
36
+ #-----------------------------------------------------------------------------
37
+
38
+ def self.tree
39
+ tree = Sys.ps.group_by(&:ppid)
40
+ Hash[tree.map do |ppid, children|
41
+ kvs = children.map { |child| [child.pid, tree.delete(child.pid)] }
42
+ [ppid, Hash[kvs]]
43
+ end]
44
+ end
45
+
46
+ #-----------------------------------------------------------------------------
47
+
48
+ PS_FIELD_TABLE = [
49
+ [:pid, :to_i],
50
+ [:ppid, :to_i],
51
+ [:pcpu, :to_f],
52
+ [:pmem, :to_f],
53
+ [:stat, :to_s],
54
+ [:rss, :to_i],
55
+ [:vsz, :to_i],
56
+ [:user, :to_s],
57
+ [:majflt, :to_i],
58
+ [:minflt, :to_i],
59
+ [:command,:to_s],
60
+ ]
61
+
62
+ PS_FIELDS = PS_FIELD_TABLE.map { |name, func| name }
63
+ PS_FIELD_TRANSFORMS = Hash[ *PS_FIELD_TABLE.flatten ]
64
+
65
+ class ProcessNotFound < Exception; end
66
+
67
+ #
68
+ # Contains all the information that PS can report about a process for
69
+ # the current platform.
70
+ #
71
+ # The following attribute accessor methods are available:
72
+ #
73
+ # pid (integer)
74
+ # command (string -- the 'ps' name)
75
+ # name (alias for 'command')
76
+ # pcpu (float)
77
+ # pmem (float)
78
+ # stat (string)
79
+ # rss (integer)
80
+ # vsz (integer)
81
+ # user (string)
82
+ # majflt (integer)
83
+ # minflt (integer)
84
+ # state (array of symbols; see DARWIN_STATES or LINUX_STATES)
85
+ #
86
+ # Only on linux:
87
+ # exename (string -- path to the binary)
88
+ # fds (array -- list of open file descriptors)
89
+ #
90
+ class ProcessInfo < Struct.new(*PS_FIELDS+[:state])
91
+
92
+ DARWIN_STATES = {
93
+ "R"=>:running,
94
+ "S"=>:sleeping,
95
+ "I"=>:idle,
96
+ "T"=>:stopped,
97
+ "U"=>:wait,
98
+ "Z"=>:zombie,
99
+ "W"=>:swapped,
100
+
101
+ "s"=>:session_leader,
102
+ "X"=>:debugging,
103
+ "E"=>:exiting,
104
+ "<"=>:high_priority,
105
+ "N"=>:low_priority,
106
+ "+"=>:foreground,
107
+ "L"=>:locked_pages,
108
+ }
109
+
110
+ LINUX_STATES = {
111
+ "R"=>:running,
112
+ "S"=>:sleeping,
113
+ "T"=>:stopped,
114
+ "D"=>:wait,
115
+ "Z"=>:zombie,
116
+ "W"=>:swapped,
117
+ "X"=>:dead,
118
+
119
+ "s"=>:session_leader,
120
+ "<"=>:high_priority,
121
+ "N"=>:low_priority,
122
+ "+"=>:foreground,
123
+ "L"=>:locked_pages,
124
+ "l"=>:multithreaded,
125
+ }
126
+
127
+ def initialize(*args)
128
+ @dead = false
129
+ args << stat_to_state(args[PS_FIELDS.index(:stat)])
130
+ super(*args)
131
+ end
132
+
133
+ def parent
134
+ Sys.ps(ppid).first unless ppid < 1
135
+ end
136
+
137
+ def children
138
+ @@parents ||= Sys.ps.group_by(&:ppid)
139
+ @@parents[pid]
140
+ end
141
+
142
+ #
143
+ # Convert all the process information to a hash.
144
+ #
145
+ def to_hash
146
+ Hash[ *members.zip(values).flatten(1) ]
147
+ end
148
+
149
+ #
150
+ # Send the TERM signal to this process.
151
+ #
152
+ def kill!(signal="TERM")
153
+ puts "Killing #{pid} (#{signal})"
154
+ Process.kill(signal, pid)
155
+ # TODO: handle exception Errno::ESRCH (no such process)
156
+ end
157
+
158
+ #
159
+ # Has this process been killed?
160
+ #
161
+ def dead?
162
+ @dead ||= Sys.pid(pid).empty?
163
+ end
164
+
165
+ #
166
+ # Refresh this process' statistics.
167
+ #
168
+ def refresh
169
+ processes = Sys.ps(pid)
170
+
171
+ if processes.empty?
172
+ @dead = true
173
+ raise ProcessNotFound
174
+ end
175
+
176
+ updated_process = processes.first
177
+ members.each { |member| self[member] = updated_process[member] }
178
+ self
179
+ end
180
+
181
+ alias_method :name, :command
182
+
183
+ # Linux-specific methods
184
+ if Sys.linux?
185
+
186
+ def exename
187
+ @exename ||= File.readlink("/proc/#{pid}/exe") rescue :unknown
188
+ @exename == :unknown ? nil : @exename
189
+ end
190
+
191
+ def fds
192
+ Dir["/proc/#{pid}/fd/*"].map { |fd| File.readlink(fd) rescue nil }
193
+ end
194
+
195
+ end
196
+
197
+ private
198
+
199
+ def stat_to_state(str)
200
+ states = case Sys.os
201
+ when "Linux" then LINUX_STATES
202
+ when "Darwin" then DARWIN_STATES
203
+ else raise "Unsupported platform: #{Sys.os}"
204
+ end
205
+
206
+ str.scan(/./).map { |char| states[char] }.compact
207
+ end
208
+ end
209
+
210
+ end
@@ -60,6 +60,7 @@ module WM
60
60
  class Window < TypedStruct["window_id desktop_id:int pid:int x:int y:int w:int h:int hostname title"]
61
61
 
62
62
  def self.all
63
+ # FIXME: Windows owned by linux-namespaced processes (firejail) report their namspaced pid to X11. `window.process` ends up pointing at either nil, or the wrong process.
63
64
  `wmctrl -lpG`.lines.map(&:strip).map { |line| Window.from_line(line) }
64
65
  end
65
66
 
@@ -674,9 +674,11 @@ describe Enumerator do
674
674
  it "concatenates" do
675
675
  e = [1,2,3].to_enum + [4,5,6].to_enum
676
676
  e.to_a.should == [1,2,3,4,5,6]
677
+ e.size.should == 6
677
678
 
678
679
  e = [1,2,3].to_enum + [4,5,6]
679
680
  e.to_a.should == [1,2,3,4,5,6]
681
+ e.size.should == 6
680
682
  end
681
683
 
682
684
  it "multiplies" do
@@ -21,6 +21,15 @@ describe Path do
21
21
  path2.path.should_not == path.path
22
22
  end
23
23
 
24
+ it "sizes" do
25
+ path = Path.tmpfile
26
+ path.write("asdfasdf")
27
+ path.size.should == 8
28
+
29
+ nonexistant = Path.new("/i/hope/this/doesn't/exist/man")
30
+ nonexistant.size.should == -1
31
+ end
32
+
24
33
  it "works with relative paths" do
25
34
  path = Path.new("../hello.mp3/blah")
26
35
 
@@ -2,7 +2,7 @@ require 'epitools'
2
2
 
3
3
  describe Sys::ProcessInfo do
4
4
 
5
- specify "checks OS" do
5
+ specify "OS type" do
6
6
  proc { Sys.os }.should_not raise_error
7
7
  proc { Sys.linux? }.should_not raise_error
8
8
  proc { Sys.mac? }.should_not raise_error
@@ -12,58 +12,41 @@ describe Sys::ProcessInfo do
12
12
 
13
13
  %w[Linux Windows Darwin BSD].include?(Sys.os).should == true
14
14
 
15
- [:linux?, :mac?, :windows?, :bsd?].any?{|os| Sys.send(os)}.should == true
15
+ [:linux?, :mac?, :windows?, :bsd?].any? { |os| Sys.send(os) }.should == true
16
16
  end
17
17
 
18
18
 
19
19
  specify "list all processes" do
20
- # procs = Sys.ps
21
- #
22
- # procs.first.state.is_a?(Array).should == true
23
- #
24
- # pids = procs.map{ |process| process.pid }
25
- #
26
- # p2s = Hash[ *Sys.ps(*pids).map { |process| [process.pid, process] }.flatten ]
27
- # matches = 0
28
- # procs.each do |p1|
29
- # if p2 = p2s[p1.pid]
30
- # matches += 1
31
- # p1.command.should == p2.command
32
- # end
33
- # end
34
- #
35
- # matches.should > 1
36
- end
20
+ # Sys.ps.should_not be_blank
21
+ procs = Sys.ps
37
22
 
23
+ procs.first.state.is_a?(Array).should == true
38
24
 
39
- specify "refresh processes" do
25
+ pids = procs.map{ |process| process.pid }
40
26
 
41
- # STDOUT.sync = true
42
- #
43
- # procs = Sys.ps
44
- # procs.shuffle!
45
- # procs.each do |process|
46
- # proc do
47
- # begin
48
- # process.refresh
49
- # print "."
50
- # rescue Sys::ProcessNotFound
51
- # end
52
- # end.should_not raise_error
53
- # end
54
- #
55
- # puts
27
+ p2s = Hash[ *Sys.ps(*pids).map { |process| [process.pid, process] }.flatten ]
28
+ matches = 0
29
+ procs.each do |p1|
30
+ if p2 = p2s[p1.pid]
31
+ matches += 1
32
+ p1.command.should == p2.command
33
+ end
34
+ end
56
35
 
36
+ matches.should > 1
57
37
  end
58
38
 
59
-
60
- specify "cross-platform method" do
39
+ specify "cross-platform methods" do
61
40
  Sys.cross_platform_method(:cross_platform_test)
62
- proc{ Sys.cross_platform_test }.should raise_error
41
+ proc { Sys.cross_platform_test }.should raise_error(NotImplementedError)
63
42
  end
64
43
 
65
- specify "interfaces" do
44
+ specify "network interfaces" do
66
45
  Sys.interfaces.should_not be_blank
67
46
  end
68
47
 
48
+ specify "mounts" do
49
+ Sys.mounts.should_not be_blank
50
+ end
51
+
69
52
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: epitools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.106
4
+ version: 0.5.107
5
5
  platform: ruby
6
6
  authors:
7
7
  - epitron
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-04-10 00:00:00.000000000 Z
11
+ date: 2017-06-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -80,6 +80,12 @@ files:
80
80
  - lib/epitools/rash.rb
81
81
  - lib/epitools/ratio.rb
82
82
  - lib/epitools/sys.rb
83
+ - lib/epitools/sys/mem.rb
84
+ - lib/epitools/sys/misc.rb
85
+ - lib/epitools/sys/mounts.rb
86
+ - lib/epitools/sys/net.rb
87
+ - lib/epitools/sys/os.rb
88
+ - lib/epitools/sys/ps.rb
83
89
  - lib/epitools/term.rb
84
90
  - lib/epitools/trie.rb
85
91
  - lib/epitools/typed_struct.rb
@@ -124,7 +130,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
124
130
  version: '0'
125
131
  requirements: []
126
132
  rubyforge_project:
127
- rubygems_version: 2.6.8
133
+ rubygems_version: 2.6.11
128
134
  signing_key:
129
135
  specification_version: 3
130
136
  summary: Not utils... METILS!