epitools 0.5.106 → 0.5.107

Sign up to get free protection for your applications and to get access to all the features.
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!