jkr 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +81 -0
- data/Guardfile +9 -0
- data/LICENSE.txt +674 -0
- data/{README.txt → README.rdoc} +0 -0
- data/Rakefile +4 -5
- data/bin/console +7 -0
- data/etc/example.plan +32 -0
- data/etc/zsh-comp.sh +82 -0
- data/exe/jkr +6 -0
- data/jkr.gemspec +31 -0
- data/lib/jkr.rb +13 -4
- data/lib/jkr/analysis.rb +5 -14
- data/lib/jkr/analytics.rb +75 -0
- data/lib/jkr/array.rb +47 -0
- data/lib/jkr/blktrace.rb +131 -0
- data/lib/jkr/cli.rb +110 -0
- data/lib/jkr/cpu_usage.rb +81 -0
- data/lib/jkr/cpufreq.rb +201 -0
- data/lib/jkr/dirlock.rb +9 -0
- data/lib/jkr/env.rb +17 -17
- data/lib/jkr/error.rb +5 -0
- data/lib/jkr/numeric.rb +28 -0
- data/lib/jkr/plan.rb +317 -26
- data/lib/jkr/planfinder.rb +40 -0
- data/lib/jkr/plot.rb +626 -0
- data/lib/jkr/stat.rb +2 -0
- data/lib/jkr/stat/kmeans-1d.rb +94 -0
- data/lib/jkr/su_cmd +163 -0
- data/lib/jkr/sysinfo.rb +34 -0
- data/lib/jkr/trial.rb +91 -16
- data/lib/jkr/userutils.rb +300 -22
- data/lib/jkr/utils.rb +38 -314
- data/lib/jkr/version.rb +3 -0
- data/sample-jkr.plan +52 -0
- metadata +171 -63
- data/bin/jkr +0 -224
- data/test/test_jkr.rb +0 -8
data/lib/jkr/cli.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'term/ansicolor'
|
3
|
+
|
4
|
+
module Jkr
|
5
|
+
class CLI < ::Thor
|
6
|
+
include Term::ANSIColor
|
7
|
+
|
8
|
+
class_option :debug, :type => :boolean
|
9
|
+
class_option :directory, :type => :string, :default => Dir.pwd, :aliases => :C
|
10
|
+
|
11
|
+
def self.exit_on_failure?
|
12
|
+
true
|
13
|
+
end
|
14
|
+
|
15
|
+
desc "init", "Initialize a Jkr environment"
|
16
|
+
def init()
|
17
|
+
dir = options[:directory]
|
18
|
+
|
19
|
+
jkr_dir = File.join(dir, "jkr")
|
20
|
+
result_dir = File.join(dir, "jkr", "result")
|
21
|
+
plan_dir = File.join(dir, "jkr", "plan")
|
22
|
+
script_dir = File.join(dir, "jkr", "script")
|
23
|
+
|
24
|
+
puts "Preparing a new Jkr environment ... @ #{dir}"
|
25
|
+
|
26
|
+
[jkr_dir, result_dir, plan_dir, script_dir].each do |dir|
|
27
|
+
puts " making directory: #{dir}"
|
28
|
+
FileUtils.mkdir(dir)
|
29
|
+
end
|
30
|
+
|
31
|
+
[result_dir, plan_dir, script_dir].each do |dir|
|
32
|
+
File.open(File.expand_path(".gitdir", dir), "w") do |_|
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
puts " preparing an example plan: example.plan"
|
37
|
+
FileUtils.cp(File.expand_path("../../etc/example.plan", __dir__),
|
38
|
+
File.join(plan_dir, "example.plan"))
|
39
|
+
|
40
|
+
puts ""
|
41
|
+
puts "... done"
|
42
|
+
end
|
43
|
+
|
44
|
+
desc "list", "List executable plans"
|
45
|
+
def list()
|
46
|
+
begin
|
47
|
+
@jkr_env = Jkr::Env.new(options[:directory])
|
48
|
+
rescue Errno::ENOENT
|
49
|
+
puts(red("[ERROR] jkr dir not found at #{@options[:directory]}"))
|
50
|
+
puts(red(" Maybe you are in a wrong directory."))
|
51
|
+
exit(false)
|
52
|
+
end
|
53
|
+
|
54
|
+
plans = Dir.glob("#{@jkr_env.jkr_plan_dir}/*.plan").map do |plan_file_path|
|
55
|
+
plan = Jkr::Plan.create_by_name(@jkr_env, File.basename(plan_file_path, ".plan"))
|
56
|
+
[File.basename(plan_file_path, ".plan"), plan.title]
|
57
|
+
end
|
58
|
+
|
59
|
+
if ENV["JKR_ZSHCOMP_HELPER"]
|
60
|
+
plans.each do |plan_name, plan_title|
|
61
|
+
puts "#{plan_name}[#{plan_title}]"
|
62
|
+
end
|
63
|
+
return
|
64
|
+
end
|
65
|
+
|
66
|
+
puts "Existing plans:"
|
67
|
+
puts
|
68
|
+
maxlen = plans.map{|plan| plan[0].size}.max
|
69
|
+
plans.each do |plan|
|
70
|
+
printf(" %#{maxlen}s : %s\n", plan[0], plan[1])
|
71
|
+
end
|
72
|
+
puts
|
73
|
+
end
|
74
|
+
|
75
|
+
desc "execute <plan> [<plan> ...]", "Execute plans"
|
76
|
+
def execute(*plan_names)
|
77
|
+
@jkr_env = Jkr::Env.new(options[:directory])
|
78
|
+
|
79
|
+
if options[:debug]
|
80
|
+
delete_files_on_error = false
|
81
|
+
else
|
82
|
+
delete_files_on_error = true
|
83
|
+
end
|
84
|
+
|
85
|
+
if plan_names.size > 0
|
86
|
+
plan_name = plan_names.first
|
87
|
+
plan = Jkr::Plan.create_by_name(@jkr_env, plan_name)
|
88
|
+
Jkr::Trial.run(@jkr_env, plan, delete_files_on_error)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
desc "analyze <result> [<result> ...]", "Run analysis script for executed results"
|
93
|
+
def analyze(*result_ids)
|
94
|
+
@jkr_env = Jkr::Env.new(options[:directory])
|
95
|
+
|
96
|
+
result_ids.each do |arg|
|
97
|
+
Jkr::Analysis.analyze(@jkr_env, arg)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
no_commands do
|
102
|
+
def find_plan_file(plan_name)
|
103
|
+
@jkr_env.plans.find do |plan_file_path|
|
104
|
+
File.basename(plan_file_path) == plan_name + ".plan"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
end # Jkr
|
@@ -0,0 +1,81 @@
|
|
1
|
+
|
2
|
+
module Jkr
|
3
|
+
class CpuUsageMonitor
|
4
|
+
def initialize
|
5
|
+
@checkpoint1 = nil
|
6
|
+
@checkpoint2 = nil
|
7
|
+
|
8
|
+
self.checkpoint
|
9
|
+
end
|
10
|
+
|
11
|
+
def read_stat
|
12
|
+
stat_str = `cat /proc/stat`
|
13
|
+
cpu_total = nil
|
14
|
+
cpus = Array.new
|
15
|
+
stat_str.each_line do |line|
|
16
|
+
case line
|
17
|
+
when /cpu (.*)$/
|
18
|
+
user, nice, sys, idle, *rest = $~[1].strip.split.map(&:to_i)
|
19
|
+
rest = rest.inject(&:+)
|
20
|
+
cpu_total = {:user => user, :sys => sys, :nice => nice, :idle => idle, :rest => rest}
|
21
|
+
when /cpu(\d+) (.*)$/
|
22
|
+
idx = $~[1].to_i
|
23
|
+
user, nice, sys, idle, *rest = $~[2].strip.split.map(&:to_i)
|
24
|
+
cpus[idx] = {:user => user, :sys => sys, :nice => nice, :idle => idle, :rest => rest}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
{:system => cpu_total,
|
29
|
+
:cpus => cpus}
|
30
|
+
end
|
31
|
+
|
32
|
+
def checkpoint
|
33
|
+
@checkpoint2 = @checkpoint1
|
34
|
+
@checkpoint1 = self.read_stat
|
35
|
+
end
|
36
|
+
|
37
|
+
def reset
|
38
|
+
@checkpoint2 = @checkpoint1 = nil
|
39
|
+
end
|
40
|
+
|
41
|
+
def checkpoint_and_get_usage
|
42
|
+
self.checkpoint
|
43
|
+
self.get_last_usage
|
44
|
+
end
|
45
|
+
|
46
|
+
def get_usage
|
47
|
+
unless @checkpoint1
|
48
|
+
raise RuntimeError.new("Checkpointing is required")
|
49
|
+
end
|
50
|
+
|
51
|
+
self.calc_usage(self.read_stat[:system], @checkpoint1[:system])
|
52
|
+
end
|
53
|
+
|
54
|
+
def get_last_usage
|
55
|
+
unless @checkpoint1 && @checkpoint2
|
56
|
+
raise RuntimeError.new("At least two checkpoints are required")
|
57
|
+
end
|
58
|
+
|
59
|
+
self.calc_usage(@checkpoint2[:system], @checkpoint1[:system])
|
60
|
+
end
|
61
|
+
|
62
|
+
def calc_usage(stat1, stat2)
|
63
|
+
stat1_clk, stat2_clk = [stat1, stat2].map{|stat|
|
64
|
+
stat.values.inject(&:+)
|
65
|
+
}
|
66
|
+
if stat1_clk > stat2_clk
|
67
|
+
stat1, stat2, stat1_clk, stat2_clk = [stat2, stat1, stat2_clk, stat1_clk]
|
68
|
+
elsif stat1_clk == stat2_clk
|
69
|
+
raise RuntimeError.new("Same clock count. cannot calc usage.")
|
70
|
+
end
|
71
|
+
clk_diff = (stat2_clk - stat1_clk).to_f
|
72
|
+
ret = Hash.new
|
73
|
+
[:user, :sys, :nice, :idle].map{|key|
|
74
|
+
ret[key] = (stat2[key] - stat1[key]) / clk_diff
|
75
|
+
}
|
76
|
+
ret[:total] = 1.0 - ret[:idle]
|
77
|
+
|
78
|
+
ret
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/lib/jkr/cpufreq.rb
ADDED
@@ -0,0 +1,201 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'jkr/array'
|
4
|
+
|
5
|
+
module Jkr
|
6
|
+
class Cpufreq
|
7
|
+
def self.cpupath(cpu_idx = nil)
|
8
|
+
if cpu_idx
|
9
|
+
self.cpupath() + "/cpu#{cpu_idx}"
|
10
|
+
else
|
11
|
+
"/sys/devices/system/cpu"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.cpufreqpath(cpu_idx = 0)
|
16
|
+
cpupath(cpu_idx) + "/cpufreq"
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.num_cpu()
|
20
|
+
Dir.glob(cpupath("*")).select{|file| file =~ /cpu\d+$/}.size
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.available?()
|
24
|
+
(0..(self.num_cpu() - 1)).to_a.every?{|cpu_idx|
|
25
|
+
File.exists?(cpufreqpath(cpu_idx))
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.config()
|
30
|
+
Config.get()
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.available_frequency(cpu_idx = 0)
|
34
|
+
if self.available?
|
35
|
+
`cat #{cpufreqpath(cpu_idx) + "/scaling_available_frequencies"}`.strip.split.map(&:to_i).sort
|
36
|
+
else
|
37
|
+
[]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class Config
|
42
|
+
attr_reader :cpuconfigs
|
43
|
+
|
44
|
+
def initialize(*args)
|
45
|
+
@cpuconfigs = \
|
46
|
+
if args.size == 0
|
47
|
+
self.current_config
|
48
|
+
elsif args.size == 1 && args.first.is_a?(Hash)
|
49
|
+
arg = args.first
|
50
|
+
if ! arg[:governor]
|
51
|
+
raise ArgumentError.new("governor must be specified.")
|
52
|
+
elsif arg[:governor] == "userspace" && ! arg[:frequency]
|
53
|
+
raise ArgumentError.new("parameter :frequency is required for userspece governor")
|
54
|
+
end
|
55
|
+
|
56
|
+
Array.new(Cpufreq.num_cpu()){|idx|
|
57
|
+
CpuConfig.new(idx, arg[:governor], arg)
|
58
|
+
}
|
59
|
+
elsif args.size == 1 && args.first.is_a?(Array) && args.first.every?{|arg| arg.is_a? CpuConfig}
|
60
|
+
args.first
|
61
|
+
elsif args.size == Cpufreq.num_cpu() && args.every?{|arg| arg.is_a? CpuConfig}
|
62
|
+
args
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.get()
|
67
|
+
cpuconfigs = Array.new(Cpufreq.num_cpu){|cpu_idx|
|
68
|
+
CpuConfig.read_config(cpu_idx)
|
69
|
+
}
|
70
|
+
self.new(cpuconfigs)
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.set(config)
|
74
|
+
config.cpuconfigs.each_with_index{|cpuconfig, idx|
|
75
|
+
CpuConfig.write_config(idx, cpuconfig)
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
def to_s
|
80
|
+
cpuconfig = @cpuconfigs.first
|
81
|
+
|
82
|
+
ret = cpuconfig.governor
|
83
|
+
|
84
|
+
suffix = case cpuconfig.governor
|
85
|
+
when /\Aperformance\Z/
|
86
|
+
nil
|
87
|
+
when /\Apowersave\Z/
|
88
|
+
nil
|
89
|
+
when /\Auserspace\Z/
|
90
|
+
cpuconfig.params[:frequency] / 1000000.0
|
91
|
+
when /\Aondemand\Z/
|
92
|
+
nil
|
93
|
+
end
|
94
|
+
|
95
|
+
if suffix
|
96
|
+
ret += ":freq=#{suffix}GHz"
|
97
|
+
end
|
98
|
+
|
99
|
+
ret
|
100
|
+
end
|
101
|
+
|
102
|
+
class CpuConfig
|
103
|
+
attr_accessor :governor
|
104
|
+
attr_accessor :params
|
105
|
+
|
106
|
+
# cpu_idx is just a hint for gathering information
|
107
|
+
def initialize(cpu_idx, gov, params = Hash.new)
|
108
|
+
@governor = gov.to_s
|
109
|
+
@freq = nil
|
110
|
+
@params = params
|
111
|
+
|
112
|
+
@cpu_idx = cpu_idx
|
113
|
+
@available_freqs = Cpufreq.available_frequency(cpu_idx)
|
114
|
+
|
115
|
+
case @governor
|
116
|
+
when /\Aperformance\Z/
|
117
|
+
# do nothing
|
118
|
+
when /\Apowersave\Z/
|
119
|
+
# do nothing
|
120
|
+
when /\Auserspace\Z/
|
121
|
+
if ! @freq = params[:frequency]
|
122
|
+
raise ArgumentError.new("parameter :frequency is required for userspece governor")
|
123
|
+
elsif ! @available_freqs.include?(params[:frequency])
|
124
|
+
raise ArgumentError.new("Frequency not available: #{params[:frequency]}")
|
125
|
+
end
|
126
|
+
when /\Aondemand\Z/
|
127
|
+
# TODO
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def frequency
|
132
|
+
case @governor
|
133
|
+
when /\Aperformance\Z/
|
134
|
+
@available_freqs.max
|
135
|
+
when /\Apowersave\Z/
|
136
|
+
@available_freqs.min
|
137
|
+
when /\Auserspace\Z/
|
138
|
+
@freq
|
139
|
+
when /\Aondemand\Z/
|
140
|
+
`cat #{Cpufreq.cpufreqpath(@cpu_idx) + "/scaling_cur_freq"}`.strip.to_i
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def frequency=(freq)
|
145
|
+
if @available_freqs.include?(freq)
|
146
|
+
@freq = freq
|
147
|
+
else
|
148
|
+
raise ArgumentError.new("Frequency not available: #{freq}")
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def self.read_config(cpu_idx)
|
153
|
+
gov = `cat #{Cpufreq.cpufreqpath(cpu_idx) + "/scaling_governor"}`.strip
|
154
|
+
freq = nil
|
155
|
+
|
156
|
+
case gov
|
157
|
+
when /\Aperformance\Z/
|
158
|
+
# do nothing
|
159
|
+
when /\Aperformance\Z/
|
160
|
+
# do nothing
|
161
|
+
when /\Auserspace\Z/
|
162
|
+
freq = `cat #{Cpufreq.cpufreqpath(cpu_idx) + "/scaling_cur_freq"}`.strip.to_i
|
163
|
+
when /\Aondemand\Z/
|
164
|
+
# TODO: read parameters
|
165
|
+
end
|
166
|
+
|
167
|
+
CpuConfig.new(cpu_idx, gov, {:frequency => freq})
|
168
|
+
end
|
169
|
+
|
170
|
+
def self.write_config(cpu_idx, cpuconfig)
|
171
|
+
`echo #{cpuconfig.governor} > #{Cpufreq.cpufreqpath(cpu_idx) + "/scaling_governor"}`
|
172
|
+
case cpuconfig.governor
|
173
|
+
when /\Aperformance\Z/
|
174
|
+
# do nothing
|
175
|
+
when /\Aperformance\Z/
|
176
|
+
# do nothing
|
177
|
+
when /\Auserspace\Z/
|
178
|
+
`echo #{cpuconfig.frequency} > #{Cpufreq.cpufreqpath(cpu_idx) + "/scaling_setspeed"}`
|
179
|
+
when /\Aondemand\Z/
|
180
|
+
if cpuconfig.params[:up_threshold]
|
181
|
+
`echo #{cpuconfig.params[:up_threshold]} > #{Cpufreq.cpufreqpath(cpu_idx) + "/ondemand/up_threshold"}`
|
182
|
+
end
|
183
|
+
if cpuconfig.params[:sampling_rate]
|
184
|
+
`echo #{cpuconfig.params[:sampling_rate]} > #{Cpufreq.cpufreqpath(cpu_idx) + "/ondemand/sampling_rate"}`
|
185
|
+
end
|
186
|
+
# TODO: parameters
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def to_s
|
191
|
+
case self.governor
|
192
|
+
when /\Auserspace\Z/
|
193
|
+
"#<CpuConfig: governor=#{self.governor}, frequency=#{self.frequency}>"
|
194
|
+
else
|
195
|
+
"#<CpuConfig: governor=#{self.governor}>"
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
data/lib/jkr/dirlock.rb
ADDED
data/lib/jkr/env.rb
CHANGED
@@ -1,38 +1,38 @@
|
|
1
1
|
|
2
2
|
require 'fileutils'
|
3
|
+
require 'term/ansicolor'
|
3
4
|
|
4
|
-
|
5
|
+
module Jkr
|
5
6
|
class Env
|
7
|
+
attr_reader :env_dir
|
6
8
|
attr_reader :jkr_dir
|
7
|
-
attr_reader :working_dir
|
8
9
|
attr_reader :jkr_result_dir
|
9
10
|
attr_reader :jkr_plan_dir
|
10
11
|
attr_reader :jkr_script_dir
|
11
|
-
|
12
|
+
|
12
13
|
PLAN_DIR = "plan"
|
13
14
|
RESULT_DIR = "result"
|
14
15
|
SCRIPT_DIR = "script"
|
15
16
|
|
16
|
-
def initialize(
|
17
|
-
@
|
18
|
-
@
|
17
|
+
def initialize(env_dir = Dir.pwd)
|
18
|
+
@env_dir = env_dir
|
19
|
+
@jkr_dir = File.join(@env_dir, "jkr")
|
19
20
|
@jkr_plan_dir = File.join(@jkr_dir, PLAN_DIR)
|
20
21
|
@jkr_result_dir = File.join(@jkr_dir, RESULT_DIR)
|
21
22
|
@jkr_script_dir = File.join(@jkr_dir, SCRIPT_DIR)
|
22
|
-
|
23
|
-
|
23
|
+
|
24
|
+
unless Dir.exists?(@jkr_dir)
|
25
|
+
raise Errno::ENOENT.new(@jkr_dir)
|
26
|
+
end
|
27
|
+
|
28
|
+
[@jkr_dir,
|
29
|
+
@jkr_result_dir,
|
30
|
+
@jkr_plan_dir,
|
31
|
+
@jkr_script_dir].each do |dir_path|
|
24
32
|
unless Dir.exists?(dir_path)
|
25
|
-
|
33
|
+
raise ArgumentError.new("Directory #{dir_path} not found")
|
26
34
|
end
|
27
35
|
end
|
28
36
|
end
|
29
|
-
|
30
|
-
def next_plan
|
31
|
-
self.plans.first
|
32
|
-
end
|
33
|
-
|
34
|
-
def plans
|
35
|
-
Dir.glob("#{@jkr_plan_dir}#{File::SEPARATOR}*.plan").sort
|
36
|
-
end
|
37
37
|
end
|
38
38
|
end
|