jkr 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2 @@
1
+
2
+ require(File.expand_path('../stat/kmeans-1d', __FILE__))
@@ -0,0 +1,94 @@
1
+
2
+ class Cluster1D
3
+ attr_accessor :center, :points
4
+
5
+ # Constructor with a starting centerpoint
6
+ def initialize(center)
7
+ @center = center
8
+ @points = []
9
+ end
10
+
11
+ # Recenters the centroid point and removes all of the associated points
12
+ def recenter!
13
+ avg = 0
14
+ old_center = @center
15
+
16
+ # Sum up all x/y coords
17
+ @points.each do |point|
18
+ avg += point
19
+ end
20
+
21
+ # Average out data
22
+ if points.length == 0
23
+ avg = center
24
+ else
25
+ avg /= points.length
26
+ end
27
+
28
+ # Reset center and return distance moved
29
+ @center = avg
30
+ return (old_center - center).abs
31
+ end
32
+ end
33
+
34
+ #
35
+ # kmeans algorithm
36
+ #
37
+
38
+ def kmeans1d(data, k, delta=0.001)
39
+ clusters = []
40
+
41
+ # Assign intial values for all clusters
42
+ (1..k).each do |point|
43
+ index = (data.length * rand).to_i
44
+
45
+ rand_point = data[index]
46
+ c = Cluster1D.new(rand_point)
47
+
48
+ clusters.push c
49
+ end
50
+
51
+ # Loop
52
+ while true
53
+ # Assign points to clusters
54
+ data.each do |point|
55
+ min_dist = +Float::INFINITY
56
+ min_cluster = nil
57
+
58
+ # Find the closest cluster
59
+ clusters.each do |cluster|
60
+ dist = (point - cluster.center).abs
61
+
62
+ if dist < min_dist
63
+ min_dist = dist
64
+ min_cluster = cluster
65
+ end
66
+ end
67
+
68
+ # Add to closest cluster
69
+ min_cluster.points.push point
70
+ end
71
+
72
+ # Check deltas
73
+ max_delta = -Float::INFINITY
74
+
75
+ clusters.each do |cluster|
76
+ dist_moved = cluster.recenter!
77
+
78
+ # Get largest delta
79
+ if dist_moved > max_delta
80
+ max_delta = dist_moved
81
+ end
82
+ end
83
+
84
+ # Check exit condition
85
+ if max_delta < delta
86
+ return clusters
87
+ end
88
+
89
+ # Reset points for the next iteration
90
+ clusters.each do |cluster|
91
+ cluster.points = []
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,163 @@
1
+ #!/usr/bin/env python
2
+
3
+ import errno,os,pty,re,select,signal,string,sys
4
+
5
+ dbg=0
6
+ efd = None
7
+
8
+ def LOG(s):
9
+ os.write(efd, s)
10
+
11
+ #
12
+ # (1) ./su_cmd whoami ---> exec "su -c whoami"
13
+ # (2) wait for "Password: " prompt and send password
14
+ # (3) get stdout of the child process
15
+ #
16
+ # It may kill the child process when it waited until timeout
17
+ # got a strange string from the child process.
18
+ #
19
+ # Exit status is normally that of the child process (ssh), or
20
+ # 255 when the child was killed.
21
+ #
22
+
23
+ #
24
+ # Timeout for each 'read' operation.
25
+ # It should be long enough so distant hosts can respond.
26
+ #
27
+ read_timeout = 5.0
28
+ read_timeout_after_pw = 30.0
29
+
30
+ # Probably you need not modify parameters below.
31
+ write_timeout = 1.0
32
+
33
+ def safe_read(fd, bytes, timeout, show_error):
34
+ """
35
+ Read string from child or timeout.
36
+ """
37
+ if timeout is None:
38
+ R,W,E = select.select([fd],[],[])
39
+ else:
40
+ R,W,E = select.select([fd],[],[],timeout)
41
+ if len(R) == 0:
42
+ if dbg>=2: LOG("read: read timeout\n")
43
+ return (-1, None)
44
+ try:
45
+ x = os.read(fd, bytes)
46
+ except OSError,e:
47
+ if show_error:
48
+ if dbg>=2: LOG("read: %s\n" % (e.args, ))
49
+ return (-1, e)
50
+ if dbg>=2: LOG("read => '%s'\n" % x)
51
+ return (0, x)
52
+
53
+ def safe_write(fd, str, timeout, show_error):
54
+ """
55
+ Write a string to child or timeout.
56
+ """
57
+ R,W,E = select.select([],[fd],[],timeout)
58
+ if len(W) == 0:
59
+ if dbg>=2: LOG("write: write timeout\n")
60
+ return (-1, None)
61
+ try:
62
+ x = os.write(fd, str)
63
+ except OSError,e:
64
+ if show_error:
65
+ if dbg>=2: LOG("write: %s\n" % (e.args,))
66
+ return (-1, e)
67
+ if dbg>=2: LOG("write => %s\n" % x)
68
+ return (x, None)
69
+
70
+ def cleanup(pid):
71
+ """
72
+ Check if pid exists. If so, kill it with SIGKILL.
73
+ """
74
+ try:
75
+ os.kill(pid, 0)
76
+ except OSError,e:
77
+ if e.args[0] == errno.ESRCH: # no such process
78
+ return
79
+ try:
80
+ os.kill(pid, signal.SIGKILL)
81
+ except OSError,e:
82
+ if e.args[0] == errno.ESRCH: # no such process
83
+ return
84
+
85
+ def wait_for_password_prompt(fd, passphrase):
86
+ bytes_to_read = 1
87
+ output_s = ""
88
+ while 1:
89
+ r,s = safe_read(fd, bytes_to_read, read_timeout, 1)
90
+ if r == -1: return -1
91
+ output_s = output_s + s
92
+ if dbg>=2: LOG("[%s]\n" % output_s)
93
+ m = re.search("Password: ", output_s)
94
+ if m: break
95
+
96
+ r,s = safe_write(fd, "%s\r\n" % passphrase, write_timeout, 1)
97
+ if r == -1: return -1
98
+ while 1:
99
+ r,s = safe_read(fd, bytes_to_read, read_timeout, 1)
100
+ if r == -1: return -1
101
+ output_s = output_s + s
102
+ if dbg>=2: LOG("[%s]\n" % output_s)
103
+ m = re.search("Password: (.*)\r\n", output_s)
104
+ if m: break
105
+ return 0
106
+
107
+ def parent(fd, passphrase):
108
+ """
109
+ Main procedure of the parent.
110
+ Input passphrase if asked.
111
+ """
112
+ if wait_for_password_prompt(fd, passphrase) == -1:
113
+ return -1
114
+ bytes_to_read = 1
115
+ while 1:
116
+ r,s = safe_read(fd, bytes_to_read, read_timeout_after_pw, 1)
117
+ if r == -1: return -1
118
+ os.write(1, s)
119
+ return 0
120
+
121
+ def main(argv):
122
+ global efd
123
+ passphrase = os.environ.get("SU_PASSWORD")
124
+ if passphrase is None:
125
+ sys.stderr.write("su_cmd: SU_PASSWORD not set\n")
126
+ os._exit(255)
127
+ user = os.environ.get("SU_USER")
128
+ os.environ["LANG"] = "C"
129
+ # fork with pty
130
+ pid,master = pty.fork()
131
+ assert pid != -1
132
+ if pid == 0:
133
+ # child. run su
134
+ if user is None:
135
+ cmd = [ "su", "-c", string.join(argv[1:], " ") ]
136
+ else:
137
+ cmd = [ "su", user, "-c", string.join(argv[1:], " ") ]
138
+ os.execvp(cmd[0], cmd)
139
+ else:
140
+ if dbg>=2:
141
+ efd = 2 # os.open("su_cmd.log", os.O_WRONLY|os.O_CREAT|os.O_TRUNC)
142
+ # parent. talk to child.
143
+ r = parent(master, passphrase)
144
+ # ensure child is gone
145
+ if dbg>=2: LOG("cleanup\n")
146
+ cleanup(pid)
147
+ # write whatever we get from child
148
+ # os.write(1, s)
149
+ # wait for child to disappear
150
+ if dbg>=2: LOG("wait for child to terminate\n")
151
+ qid,status = os.wait()
152
+ assert pid == qid
153
+ if dbg>=2: LOG("child finished\n")
154
+ if os.WIFEXITED(status):
155
+ # child normally exited. forward its status
156
+ os._exit(os.WEXITSTATUS(status))
157
+ else:
158
+ # child was killed. return 255
159
+ os._exit(255)
160
+ if dbg>=2: os.close(efd)
161
+
162
+ if __name__ == "__main__":
163
+ main(sys.argv)
@@ -0,0 +1,34 @@
1
+
2
+ module Jkr
3
+ class SysInfo
4
+ class << self
5
+ def gather
6
+ ret = Hash.new
7
+ ret[:proc] = Hash.new
8
+
9
+ # gather infomation under /proc
10
+ ret[:proc][:cpuinfo] = `cat /proc/cpuinfo`
11
+ ret[:proc][:meminfo] = `cat /proc/meminfo`
12
+ ret[:proc][:interrupts] = `cat /proc/interrupts`
13
+ ret[:proc][:mdstat] = `cat /proc/mdstat`
14
+ ret[:proc][:mounts] = `cat /proc/mounts`
15
+
16
+ ret[:sys] = Hash.new
17
+ ret[:sys][:block] = Hash.new
18
+ Dir.glob("/sys/block/*").each do |block_path|
19
+ block = File.basename(block_path).to_sym
20
+ ret[:sys][:block][block] = Hash.new
21
+ ret[:sys][:block][block][:queue] = Hash.new
22
+ Dir.glob("#{block_path}/queue/scheduler") do |path|
23
+ ret[:sys][:block][block][:queue][:scheduler] = `cat #{path}`
24
+ end
25
+ Dir.glob("#{block_path}/queue/nr_requests") do |path|
26
+ ret[:sys][:block][block][:queue][:nr_requests] = `cat #{path}`
27
+ end
28
+ end
29
+
30
+ ret
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,11 +1,11 @@
1
1
 
2
2
  require 'fileutils'
3
+ require 'json'
3
4
  require 'jkr/utils'
4
5
 
5
- class Jkr
6
+ module Jkr
6
7
  class Trial
7
8
  attr_reader :params
8
-
9
9
  def self.make_trials(resultset_dir, plan)
10
10
  var_combs = [{}]
11
11
  plan.vars.each do |key, vals|
@@ -16,26 +16,81 @@ class Jkr
16
16
  }.flatten
17
17
  end
18
18
 
19
- var_combs.map do |var_comb|
19
+ params_list = var_combs.map{|var_comb| plan.params.merge(var_comb)}
20
+
21
+ plan.param_filters.each do |filter|
22
+ params_list = params_list.select(&filter)
23
+ end
24
+
25
+ params_list = params_list * plan.routine_nr_run
26
+
27
+ params_list.map do |params|
20
28
  result_dir = Utils.reserve_next_dir(resultset_dir)
21
- Trial.new(result_dir, plan, plan.params.merge(var_comb))
29
+ Trial.new(result_dir, plan, params)
22
30
  end
23
31
  end
24
32
 
25
- def self.run(env, plan)
33
+ def self.pretty_time(seconds)
34
+ hour = seconds / 3600
35
+ min = (seconds - hour * 3600) / 60
36
+ sec = seconds - hour * 3600 - min * 60
37
+
38
+ sprintf("%02d:%02d:%02d", hour, min, sec)
39
+ end
40
+
41
+ def self.run(env, plan, delete_files_on_error = true)
26
42
  plan_suffix = File.basename(plan.file_path, ".plan")
43
+ plan_suffix += "_#{plan.short_desc}" if plan.short_desc
27
44
  resultset_dir = Utils.reserve_next_dir(env.jkr_result_dir, plan_suffix)
28
- trials = self.make_trials(resultset_dir, plan)
29
-
30
- FileUtils.copy_file(plan.file_path,
31
- File.join(resultset_dir, File.basename(plan.file_path)))
32
- params = plan.params.merge(plan.vars)
33
- plan.freeze
34
- plan.prep.call(plan)
35
- trials.each do |trial|
36
- trial.run
45
+ plan.resultset_dir = resultset_dir
46
+ FileUtils.mkdir_p(File.join(resultset_dir, "plan"))
47
+ FileUtils.mkdir_p(File.join(resultset_dir, "script"))
48
+
49
+ begin
50
+ trials = self.make_trials(resultset_dir, plan)
51
+
52
+ plan_dest_dir = File.join(resultset_dir, "plan")
53
+ script_dest_dir = File.join(resultset_dir, "script")
54
+
55
+ _plan = plan
56
+ begin
57
+ if _plan == plan # copy main plan file
58
+ FileUtils.copy_file(_plan.file_path,
59
+ File.expand_path(File.basename(_plan.file_path), resultset_dir))
60
+ else
61
+ FileUtils.copy_file(_plan.file_path,
62
+ File.expand_path(File.basename(_plan.file_path),
63
+ plan_dest_dir))
64
+ end
65
+
66
+ plan.used_scripts.each do |script_path|
67
+ FileUtils.copy_file(script_path,
68
+ File.expand_path(File.basename(script_path),
69
+ script_dest_dir))
70
+ end
71
+ end while _plan = _plan.base_plan
72
+
73
+ params = plan.params.merge(plan.vars)
74
+ plan.freeze
75
+
76
+ # show estimated execution time if available
77
+ if plan.exec_time_estimate
78
+ puts("")
79
+ puts("== estimated execution time: #{pretty_time(plan.exec_time_estimate.call(plan))} ==")
80
+ puts("")
81
+ end
82
+
83
+ plan.do_prep()
84
+ trials.each do |trial|
85
+ trial.run
86
+ end
87
+ plan.do_cleanup()
88
+ rescue Exception => err
89
+ if delete_files_on_error
90
+ FileUtils.rm_rf(resultset_dir)
91
+ end
92
+ raise err
37
93
  end
38
- plan.cleanup.call(plan)
39
94
  end
40
95
 
41
96
  def initialize(result_dir, plan, params)
@@ -46,9 +101,29 @@ class Jkr
46
101
  private :initialize
47
102
 
48
103
  def run()
104
+ plan = @plan
105
+ File.open("#{@result_dir}/params.json", "w") do |f|
106
+ f.puts(@params.to_json)
107
+ end
108
+
109
+ # reset plan.metastore
110
+ plan.metastore.clear
111
+
112
+ # define utility functions for plan.routine object
49
113
  Jkr::TrialUtils.define_routine_utils(@result_dir, @plan, @params)
50
- @plan.routine.call(@plan, @params)
114
+
115
+ @plan.metastore[:trial_start_time] = Time.now
116
+ @plan.do_routine(@plan, @params)
117
+ @plan.metastore[:trial_end_time] = Time.now
118
+
51
119
  Jkr::TrialUtils.undef_routine_utils(@plan)
120
+
121
+ # save plan.metastore
122
+ Marshal.dump(@plan.metastore,
123
+ File.open("#{@result_dir}/metastore.msh", "w"))
124
+ File.open("#{@result_dir}/metastore.json", "w") do |f|
125
+ f.puts(@plan.metastore.to_json)
126
+ end
52
127
  end
53
128
  end
54
129
  end
@@ -4,7 +4,7 @@ require 'time'
4
4
  require 'date'
5
5
  require 'csv'
6
6
 
7
- class Jkr
7
+ module Jkr
8
8
  class SysUtils
9
9
  def self.cpu_cores()
10
10
  self.num_cores()
@@ -34,19 +34,22 @@ class Jkr
34
34
  end
35
35
  end
36
36
 
37
- result = []
38
- bufstr = ""
39
- while ! file.eof?
40
- bufstr += file.read(BLOCKSIZE)
41
- blocks = bufstr.split(separator)
42
- bufstr = blocks.pop
43
- blocks.each do |block|
44
- ret = proc.call(block)
45
- result.push(ret) if ret
46
- end
47
- end
48
- ret = proc.call(bufstr)
49
- result.push(ret) if ret
37
+ #result = []
38
+ #bufstr = ""
39
+ #while ! file.eof?
40
+ # bufstr += file.read(BLOCKSIZE)
41
+ # blocks = bufstr.split(separator)
42
+ # bufstr = blocks.pop
43
+ # blocks.each do |block|
44
+ # ret = proc.call(block)
45
+ # result.push(ret) if ret
46
+ # end
47
+ #end
48
+ #ret = proc.call(bufstr)
49
+ #result.push(ret) if ret
50
+ result = file.read.split(separator).map do |x|
51
+ proc.call(x)
52
+ end.compact
50
53
 
51
54
  result
52
55
  end
@@ -55,6 +58,67 @@ class Jkr
55
58
  self.read_blockseq(io_or_filepath, "\n", &block)
56
59
  end
57
60
 
61
+ def self.read_sar(sar_filepath)
62
+ labels = nil
63
+ date = nil
64
+ last_time = nil
65
+ idx = 0
66
+ self.read_rowseq(sar_filepath){|rowstr|
67
+ if rowstr =~ /^Linux/ && rowstr =~ /(\d{2})\/(\d{2})\/(\d{2})/
68
+ y = $~[3].to_i; m = $~[1].to_i; d = $~[2].to_i
69
+ date = Date.new(2000 + y, m, d)
70
+ next
71
+ else
72
+ row = Hash.new
73
+
74
+ time, *vals = rowstr.split
75
+
76
+ if vals.size == 0
77
+ next
78
+ end
79
+ if vals.every?{|val| val =~ /\A\d+(?:\.\d+)?\Z/ }
80
+ vals = vals.map(&:to_f)
81
+ else
82
+ # label line
83
+ labels = vals
84
+ next
85
+ end
86
+
87
+ unless date
88
+ raise StandardError.new("cannot find date information in sar log")
89
+ end
90
+ unless labels
91
+ raise StandardError.new("no label information")
92
+ end
93
+
94
+ unless time =~ /(\d{2}):(\d{2}):(\d{2})/
95
+ if time =~ /Average/
96
+ next
97
+ end
98
+ raise StandardError.new("Invalid time format: #{time}")
99
+ else
100
+ time = Time.local(date.year, date.month, date.day,
101
+ $~[1].to_i, $~[2].to_i, $~[3].to_i)
102
+ if last_time && time < last_time
103
+ date += 1
104
+ time = Time.local(date.year, date.month, date.day,
105
+ $~[1].to_i, $~[2].to_i, $~[3].to_i)
106
+ end
107
+
108
+ row[:time] = time
109
+ row[:data] = Hash.new
110
+ labels.each_with_index do |label,idx|
111
+ row[:data][label] = vals[idx]
112
+ end
113
+ row[:labels] = labels
114
+
115
+ last_time = time
116
+ end
117
+ end
118
+ row
119
+ }
120
+ end
121
+
58
122
  def self.read_mpstat_avg(io_or_filepath)
59
123
  self.read_blockseq(io_or_filepath){|blockstr|
60
124
  if blockstr =~ /^Average:/
@@ -82,13 +146,23 @@ class Jkr
82
146
  }.last
83
147
  end
84
148
 
149
+ # Format of returned value
150
+ # [{
151
+ # :time => <Time>,
152
+ # :labels => [:cpu, :usr, :nice, :sys, :iowait, :irq, ...],
153
+ # :data => [{:cpu => "all", :usr => 0.11, :nice => 0.00, ...],
154
+ # {:cpu => 0, :usr => 0.12, :nice => 0.00, ...},
155
+ # ...]
156
+ # },
157
+ # ...]
158
+ #
85
159
  def self.read_mpstat(io_or_filepath)
86
160
  hostname = `hostname`.strip
87
161
 
88
162
  date = nil
89
163
  last_time = nil
90
164
  self.read_blockseq(io_or_filepath) do |blockstr|
91
- if blockstr =~ /^Linux/ && blockstr =~ /(\d{2})\/(\d{2})\/(\d{2})$/
165
+ if blockstr =~ /Linux/ && blockstr =~ /(\d{2})\/(\d{2})\/(\d{2})/
92
166
  # the first line
93
167
  y = $~[3].to_i; m = $~[1].to_i; d = $~[2].to_i
94
168
  date = Date.new(2000 + y, m, d)
@@ -104,11 +178,25 @@ class Jkr
104
178
  rows = blockstr.lines.map(&:strip)
105
179
  header = rows.shift.split
106
180
  next if header.shift =~ /Average/
107
- result[:labels] = header
181
+ if header.first =~ /\AAM|PM\Z/
182
+ header.shift
183
+ end
184
+ result[:labels] = header.map do |label|
185
+ {
186
+ "CPU" => :cpu, "%usr" => :usr, "%user" => :usr,
187
+ "%nice" => :nice, "%sys" => :sys, "%iowait" => :iowait,
188
+ "%irq" => :irq, "%soft" => :soft, "%steal" => :steal,
189
+ "%guest" => :guest, "%idle" => :idle
190
+ }[label] || label
191
+ end
108
192
  time = nil
109
193
  result[:data] = rows.map { |row|
110
194
  vals = row.split
111
195
  wallclock = vals.shift
196
+ if vals.first =~ /\AAM|PM\Z/
197
+ vals.shift
198
+ end
199
+
112
200
  unless time
113
201
  unless wallclock =~ /(\d{2}):(\d{2}):(\d{2})/
114
202
  raise RuntimeError.new("Cannot extract wallclock time from mpstat data")
@@ -126,19 +214,121 @@ class Jkr
126
214
  if vals.size != result[:labels].size
127
215
  raise RuntimeError.new("Invalid mpstat data")
128
216
  end
129
- vals.map{|val|
130
- begin
131
- Float(val)
132
- rescue ArgumentError
133
- val
134
- end
217
+
218
+ record = Hash.new
219
+ vals.each_with_index{|val, idx|
220
+ label = result[:labels][idx]
221
+ val = if val =~ /\A\d+\Z/
222
+ val.to_i
223
+ else
224
+ begin
225
+ Float(val)
226
+ rescue ArgumentError
227
+ val
228
+ end
229
+ end
230
+ record[label] = val
135
231
  }
232
+
233
+ record
136
234
  }
137
235
  result
138
236
  end
139
237
  end
140
238
  end
141
239
 
240
+ #
241
+ # This function parses _io_or_filepath_ as an iostat log and
242
+ # returns the parsed result.
243
+ #
244
+ # _block_ :: If given, invoked for each iostat record like
245
+ # block.call(t, record)
246
+ # t ... wallclock time of the record
247
+ # record ... e.g. {"sda" => {"rrqm/s" => 0.0, ...}, ...}
248
+ #
249
+ def self.read_iostat(io_or_filepath, &block)
250
+ hostname = `hostname`.strip
251
+ date = nil
252
+ last_time = nil
253
+ sysname_regex = Regexp.new(Regexp.quote("#{`uname -s`.strip}"))
254
+ self.read_blockseq(io_or_filepath) do |blockstr|
255
+ if blockstr =~ sysname_regex
256
+ # the first line
257
+ if blockstr =~ /(\d{2})\/(\d{2})\/(\d{2,4})/
258
+ if $~[3].size == 2
259
+ y = $~[3].to_i + 2000
260
+ else
261
+ y = $~[3].to_i
262
+ end
263
+ m = $~[1].to_i
264
+ d = $~[2].to_i
265
+ date = Date.new(y, m, d)
266
+ next
267
+ end
268
+ else
269
+ rows = blockstr.lines.map(&:strip)
270
+ timestamp = rows.shift
271
+ time = nil
272
+ unless date
273
+ raise RuntimeError.new("Cannot detect date: #{io_or_filepath}")
274
+ end
275
+
276
+ if timestamp =~ /(\d{2})\/(\d{2})\/(\d{2}) (\d{2}):(\d{2}):(\d{2})/
277
+ y = $~[3].to_i; m = $~[1].to_i; d = $~[2].to_i
278
+ time = Time.local(date.year, date.month, date.day, $~[4].to_i, $~[5].to_i, $~[6].to_i)
279
+ elsif date && timestamp =~ /Time: (\d{2}):(\d{2}):(\d{2}) (AM|PM)/
280
+ if $~[4] == "PM"
281
+ hour = $~[1].to_i
282
+ if $~[1].to_i != 12
283
+ hour += 12
284
+ end
285
+ elsif $~[4] == "AM" && $~[1].to_i == 12
286
+ hour = 0
287
+ else
288
+ hour = $~[1].to_i
289
+ end
290
+ time = Time.local(date.year, date.month, date.day,
291
+ hour, $~[2].to_i, $~[3].to_i)
292
+ elsif date && timestamp =~ /Time: (\d{2}):(\d{2}):(\d{2})/
293
+ time = Time.local(date.year, date.month, date.day,
294
+ $~[1].to_i, $~[2].to_i, $~[3].to_i)
295
+ end
296
+
297
+ unless time
298
+ unless date
299
+ raise StandardError.new("Cannot find date in your iostat log: #{io_or_filepath}")
300
+ end
301
+ raise StandardError.new("Cannot find timestamp in your iostat log: #{io_or_filepath}")
302
+ end
303
+
304
+ labels = rows.shift.split
305
+ unless labels.shift =~ /Device:/
306
+ raise StandardError.new("Invalid iostat log: #{io_or_filepath}")
307
+ end
308
+
309
+ record = Hash.new
310
+ rows.each do |row|
311
+ vals = row.split
312
+ dev = vals.shift
313
+ unless vals.size == labels.size
314
+ raise StandardError.new("Invalid iostat log: #{io_or_filepath}")
315
+ end
316
+ record_item = Hash.new
317
+ labels.each do |label|
318
+ record_item[label] = vals.shift.to_f
319
+ end
320
+ record[dev] = record_item
321
+ end
322
+
323
+ if block.is_a? Proc
324
+ block.call(time, record)
325
+ end
326
+
327
+ [time, record]
328
+ end
329
+ end
330
+ end
331
+
142
332
  def self.read_csv(io_or_filepath, fs = ",", rs = nil, &proc)
143
333
  if io_or_filepath.is_a?(String) && File.exists?(io_or_filepath)
144
334
  io_or_filepath = File.open(io_or_filepath, "r")
@@ -152,5 +342,93 @@ class Jkr
152
342
  end
153
343
  result
154
344
  end
345
+
346
+ class << self
347
+ def read_top(io_or_filepath, opt = {}, &proc)
348
+ opt[:start_time] ||= Time.now
349
+
350
+ def block_filter(filter, block)
351
+ if filter.is_a? Symbol
352
+ filters = {
353
+ :kernel_process => /\A(kworker|ksoftirqd|migration|watchdog|kintegrityd|kblockd|events|kondemand|aio|crypto|ata|kmpathd|kconservative|rpciod|xfslogd|xfsdatad|xfsconvertd)\//
354
+ }
355
+ raise ArgumentError.new("Invalid filter: #{filter.inspect}") unless filters[filter]
356
+ filter = filters[filter]
357
+ elsif filter.is_a? Regexp
358
+ filter = filter
359
+ else
360
+ raise ArgumentError.new("Invalid filter: #{filter.inspect}")
361
+ end
362
+
363
+ block = block.select do |record|
364
+ ! (record[:command] =~ filter)
365
+ end
366
+ end
367
+
368
+ def parse_block(block, opt)
369
+ y = opt[:start_time].year
370
+ m = opt[:start_time].month
371
+ d = opt[:start_time].day
372
+
373
+ lines = block.lines.map(&:strip)
374
+ head_line = lines.shift
375
+
376
+ unless head_line =~ /(\d{2}):(\d{2}):(\d{2})/
377
+ raise ArgumentError.new("Invalid top(3) data")
378
+ end
379
+ time = Time.local(y, m, d, $~[1].to_i, $~[2].to_i, $~[3].to_i)
380
+
381
+ while ! (lines[0] =~ /\APID/)
382
+ line = lines.shift
383
+ end
384
+ labels = lines.shift.split.map do |key|
385
+ {"PID" => :pid, "USER" => :user, "PR" => :pr, "NI" => :ni,
386
+ "VIRT" => :virt, "RES" => :res, "SHR" => :shr, "S" => :s,
387
+ "%CPU" => :cpu, "%MEM" => :mem, "TIME+" => :time_plus,
388
+ "COMMAND" => :command}[key] || key
389
+ end
390
+
391
+ lines = lines.select{|line| ! line.empty?}
392
+ records = lines.map do |line|
393
+ record = Hash.new
394
+ record[:time] = time
395
+ line.split.each_with_index do |val, idx|
396
+ key = labels[idx]
397
+ if val =~ /\A(\d+)([mg]?)\Z/
398
+ record[key] = Integer($~[1])
399
+ if ! $~[2].empty?
400
+ record[key] *= {'g' => 2**20, 'm' => 2**10}[$~[2]]
401
+ end
402
+ elsif val =~ /\A(\d+\.\d+)([mg]?)\Z/
403
+ record[key] = Float($~[1])
404
+ if ! $~[2].empty?
405
+ record[key] *= {'g' => 2**20, 'm' => 2**10}[$~[2]]
406
+ end
407
+ elsif val =~ /\A(\d+):(\d+\.\d+)\Z/
408
+ record[key] = Integer($~[1])*60 + Float($~[2])
409
+ else
410
+ record[key] = val
411
+ end
412
+ end
413
+
414
+ record
415
+ end
416
+
417
+ if opt[:filter]
418
+ records = block_filter(opt[:filter], records)
419
+ end
420
+
421
+ if opt[:top_k]
422
+ records = records.first(opt[:top_k])
423
+ end
424
+
425
+ records
426
+ end
427
+
428
+ File.open(io_or_filepath, "r").read.split("\n\n\n").map do |block|
429
+ parse_block(block, opt)
430
+ end
431
+ end
432
+ end
155
433
  end
156
434
  end