jkr 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|