cloudscale-benchmark 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +33 -0
- data/bin/prepare_benchmark.sh +193 -0
- data/bin/run_benchmark.sh +4 -0
- data/lib/cloudscale/benchmark/agent/init.rb +74 -0
- data/lib/cloudscale/benchmark/model/agent/benchmark_agent_instance.rb +32 -0
- data/lib/cloudscale/benchmark/model/benchmark/benchmark_run.rb +67 -0
- data/lib/cloudscale/benchmark/model/benchmark/benchmark_run_result.rb +32 -0
- data/lib/cloudscale/benchmark/model/benchmark/benchmark_run_result_value.rb +28 -0
- data/lib/cloudscale/benchmark/version.rb +5 -0
- data/lib/cloudscale/benchmark.rb +98 -0
- data/lib/cloudscale/constants/agent_instance_store.rb +42 -0
- data/lib/cloudscale/constants/agent_store.rb +25 -0
- data/lib/cloudscale/plugins/phoronix/phoronix_bulk_execution.rb +259 -0
- data/lib/cloudscale/plugins/plugin.rb +38 -0
- data/lib/cloudscale/plugins/preops/plugin_preop.rb +48 -0
- data/lib/cloudscale/plugins/preops/preop.rb +46 -0
- data/lib/cloudscale/rest/rest_client.rb +200 -0
- data/lib/cloudscale/rest/rest_client_helper.rb +65 -0
- data/lib/cloudscale/store/agent/agent.yml +2 -0
- data/lib/cloudscale/store/agent/agent_instance.yml +6 -0
- data/lib/cloudscale/store/plugin/development +1 -0
- data/lib/cloudscale/store/plugin/numberOfRuns +1 -0
- data/lib/cloudscale/store/plugin/op_timeout +1 -0
- data/lib/cloudscale/store/rest/settings.yml +4 -0
- data/lib/cloudscale/store/settings/phoronix.yml +9 -0
- metadata +229 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
require "hashable"
|
2
|
+
|
3
|
+
##
|
4
|
+
#
|
5
|
+
# @author Johannes Hiemer, cloudscale.
|
6
|
+
#
|
7
|
+
##
|
8
|
+
module Cloudscale
|
9
|
+
module Benchmark
|
10
|
+
|
11
|
+
class BenchmarkRunResultValue
|
12
|
+
include Hashable
|
13
|
+
|
14
|
+
attr_accessor :name, :description, :arguments, :value, :rawString, :scale, :timestamp
|
15
|
+
|
16
|
+
def initialize()
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_h
|
20
|
+
hash = {}
|
21
|
+
instance_variables.each {|var| hash[var.to_s.delete("@")] = instance_variable_get(var) }
|
22
|
+
return hash
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/benchmark/version"
|
2
|
+
require "http"
|
3
|
+
require "optparse"
|
4
|
+
require "singleton"
|
5
|
+
require "yaml"
|
6
|
+
require "json"
|
7
|
+
require "#{File.dirname(__FILE__)}/benchmark/agent/init"
|
8
|
+
require "#{File.dirname(__FILE__)}/plugins/plugin"
|
9
|
+
require "#{File.dirname(__FILE__)}/plugins/preops/plugin_preop"
|
10
|
+
Dir["#{File.dirname(__FILE__)}/plugins/*/*.rb"].each { | f | require f }
|
11
|
+
|
12
|
+
##
|
13
|
+
#
|
14
|
+
# @author Johannes Hiemer, cloudscale.
|
15
|
+
#
|
16
|
+
##
|
17
|
+
module Cloudscale
|
18
|
+
module Benchmark
|
19
|
+
options = {}
|
20
|
+
mandatory = Array.new
|
21
|
+
|
22
|
+
agent_instance = Cloudscale::Benchmark::Init.new
|
23
|
+
agent_instance_id = agent_instance.agent_instance_id()
|
24
|
+
|
25
|
+
Plugins::PluginPreop.instance.init
|
26
|
+
|
27
|
+
parser = OptionParser.new do | parser |
|
28
|
+
|
29
|
+
parser.banner = "Usage: benchmark COMMAND [OPTIONS]"
|
30
|
+
parser.separator ""
|
31
|
+
parser.separator "Commands"
|
32
|
+
parser.separator " start: Start cloudscale benchmark agent"
|
33
|
+
parser.separator " stop: Stop cloudscale benchmark agent"
|
34
|
+
parser.separator ""
|
35
|
+
parser.separator "Options"
|
36
|
+
|
37
|
+
parser.on("-v", "--version", "Show version information about this program and quit.") do
|
38
|
+
puts "Cloudscale Benchmarking " + VERSION
|
39
|
+
exit
|
40
|
+
end
|
41
|
+
|
42
|
+
parser.separator "\n"
|
43
|
+
parser.separator "Plugin specific Settings"
|
44
|
+
|
45
|
+
Plugins::PluginPreop.instance.options.each do | key, value |
|
46
|
+
if (value[:required])
|
47
|
+
mandatory.push(key)
|
48
|
+
end
|
49
|
+
parser.on(value[:argument][1,2] + " n", value[:argument] + "=n", value[:description]) do | value |
|
50
|
+
Plugins::PluginPreop.instance.set_option_value(key, value)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
begin
|
56
|
+
parser.parse!
|
57
|
+
missing = mandatory.select{ | param |
|
58
|
+
Plugins::PluginPreop.instance.get_option_value(param).nil?
|
59
|
+
}
|
60
|
+
if not missing.empty?
|
61
|
+
puts "\n"
|
62
|
+
puts "Error during execution: "
|
63
|
+
puts " Missing options: #{missing.join(', ')}"
|
64
|
+
puts "\n"
|
65
|
+
puts parser
|
66
|
+
exit
|
67
|
+
else
|
68
|
+
Plugins::PluginPreop.instance.save_options
|
69
|
+
end
|
70
|
+
rescue OptionParser::InvalidOption, OptionParser::MissingArgument
|
71
|
+
puts $!.to_s
|
72
|
+
puts parser
|
73
|
+
exit
|
74
|
+
end
|
75
|
+
|
76
|
+
case ARGV[0]
|
77
|
+
when "start"
|
78
|
+
plugins = Set.new
|
79
|
+
|
80
|
+
Plugins::Plugin.plugins.each do | plugin_class |
|
81
|
+
plugin = plugin_class.new
|
82
|
+
plugins << plugin
|
83
|
+
end
|
84
|
+
|
85
|
+
plugins.each do | plugin |
|
86
|
+
if plugin.respond_to?('run')
|
87
|
+
plugin.run(agent_instance_id)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
when "stop"
|
92
|
+
puts "call stop on options #{options.inspect}"
|
93
|
+
else
|
94
|
+
puts parser
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "yaml/store"
|
2
|
+
|
3
|
+
##
|
4
|
+
#
|
5
|
+
# @author Johannes Hiemer, cloudscale.
|
6
|
+
#
|
7
|
+
##
|
8
|
+
module Cloudscale
|
9
|
+
module Constants
|
10
|
+
class AgentInstance
|
11
|
+
|
12
|
+
@@AGENT_INSTANCE_STORE_PATH = "#{File.dirname(__FILE__)}/../store/agent/agent_instance.yml"
|
13
|
+
|
14
|
+
def self.create(hash)
|
15
|
+
settings = YAML::Store.new(@@AGENT_INSTANCE_STORE_PATH)
|
16
|
+
|
17
|
+
settings.transaction {
|
18
|
+
hash.each { | key, value |
|
19
|
+
settings[key] = value
|
20
|
+
}
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.load
|
25
|
+
begin
|
26
|
+
YAML.load(File.read(@@AGENT_INSTANCE_STORE_PATH))
|
27
|
+
rescue
|
28
|
+
return nil
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
def self.remove
|
34
|
+
file = File.read(@@AGENT_INSTANCE_STORE_PATH)
|
35
|
+
if (file)
|
36
|
+
File.delete(@@AGENT_INSTANCE_STORE_PATH)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "yaml/store"
|
2
|
+
|
3
|
+
##
|
4
|
+
#
|
5
|
+
# @author Johannes Hiemer, cloudscale.
|
6
|
+
#
|
7
|
+
##
|
8
|
+
module Cloudscale
|
9
|
+
|
10
|
+
module Constants
|
11
|
+
class Agent
|
12
|
+
|
13
|
+
@@AGENT_STORE = "#{File.dirname(__FILE__)}/../store/agent/agent.yml"
|
14
|
+
|
15
|
+
def self.load
|
16
|
+
begin
|
17
|
+
YAML.load(File.read(@@AGENT_STORE))
|
18
|
+
rescue
|
19
|
+
return nil
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,259 @@
|
|
1
|
+
require "popen4"
|
2
|
+
require "#{File.dirname(__FILE__)}/../../benchmark/model/benchmark/benchmark_run"
|
3
|
+
require "#{File.dirname(__FILE__)}/../../benchmark/model/benchmark/benchmark_run_result"
|
4
|
+
require "#{File.dirname(__FILE__)}/../../benchmark/model/benchmark/benchmark_run_result_value"
|
5
|
+
require "#{File.dirname(__FILE__)}/../../plugins/preops/plugin_preop"
|
6
|
+
require "#{File.dirname(__FILE__)}/../../plugins/plugin"
|
7
|
+
require "#{File.dirname(__FILE__)}/../../rest/rest_client"
|
8
|
+
require "xmlsimple"
|
9
|
+
require "socket"
|
10
|
+
|
11
|
+
|
12
|
+
##
|
13
|
+
#
|
14
|
+
# @author Johannes Hiemer, cloudscale.
|
15
|
+
# @author Joerg Gottschlich, cloudscale.
|
16
|
+
#
|
17
|
+
##
|
18
|
+
module Cloudscale
|
19
|
+
module Plugins
|
20
|
+
class PhoronixBulkExecutor < Plugins::Plugin
|
21
|
+
attr_reader :none, :homeDir, :fileSeparator, :development, :numberOfRuns, :properties, :nanoSeconds
|
22
|
+
attr_accessor :benchmarkRun, :benchmarkRunResult, :benchmarkTest, :environment
|
23
|
+
|
24
|
+
def log
|
25
|
+
@log = Logger.new(STDOUT)
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
super
|
30
|
+
@options = Plugins::PluginPreop.instance.options
|
31
|
+
@settings = load_hash()
|
32
|
+
@homeDir = Dir.home
|
33
|
+
@fileSeparator = File::Separator
|
34
|
+
@nanoSeconds = 1000000000
|
35
|
+
@environment = []
|
36
|
+
# select first non-loopback ip address
|
37
|
+
@ip_address = Socket.ip_address_list.find { |ai| ai.ipv4? && !ai.ipv4_loopback? }.ip_address
|
38
|
+
@hostname = Socket.gethostname
|
39
|
+
end
|
40
|
+
|
41
|
+
def load_hash()
|
42
|
+
begin
|
43
|
+
YAML.load(File.read("#{File.dirname(__FILE__)}/../../store/settings/phoronix.yml"))
|
44
|
+
rescue
|
45
|
+
return nil
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def resultDir
|
50
|
+
@homeDir + @fileSeparator + ".phoronix-test-suite" + @fileSeparator + "test-results"
|
51
|
+
end
|
52
|
+
|
53
|
+
def lockFile
|
54
|
+
@homeDir + @fileSeparator + ".phoronix-test-suite" + @fileSeparator + "run_lock"
|
55
|
+
end
|
56
|
+
|
57
|
+
def compileParams(benchmarkCommand, testLabel)
|
58
|
+
benchmarkCommand + " " + testLabel
|
59
|
+
end
|
60
|
+
|
61
|
+
def testLabel(benchmarkTest)
|
62
|
+
"pts/" + benchmarkTest["identifier"] + "-" + benchmarkTest["version"]
|
63
|
+
end
|
64
|
+
|
65
|
+
def isRunning
|
66
|
+
File.exists?(lockFile)
|
67
|
+
end
|
68
|
+
|
69
|
+
def run(agentInstanceId)
|
70
|
+
|
71
|
+
begin # main loop
|
72
|
+
|
73
|
+
if (isRunning())
|
74
|
+
log.info("There are currently test cases running on this machine. Don't run two instances at a time.")
|
75
|
+
abort
|
76
|
+
else
|
77
|
+
puts "Querying server for scheduled benchmark tests..."
|
78
|
+
benchmarkRuns = client.searchAny('benchmarkRuns', 'findByBenchmarkInstanceAgentInstanceIdAndStatus',
|
79
|
+
{ :agentInstanceId => agentInstanceId, :status => 'scheduled' })
|
80
|
+
|
81
|
+
benchmarkRuns = benchmarkRuns["content"]; # TODO: better put to rest client helper
|
82
|
+
|
83
|
+
# if we have benchmarks => run
|
84
|
+
if (benchmarkRuns.any?)
|
85
|
+
puts "Received " + benchmarkRuns.length.to_s + " scheduled tests, starting execution..."
|
86
|
+
benchmarkRuns.each do |benchmarkRun|
|
87
|
+
reset()
|
88
|
+
@benchmarkRun = Benchmark::BenchmarkRun.new(benchmarkRun)
|
89
|
+
executeBenchmark()
|
90
|
+
end
|
91
|
+
else
|
92
|
+
puts Time.now.utc.to_s + ": There are currently no tests scheduled for this agent. Running execution again in two minutes."
|
93
|
+
sleep 120
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end until false
|
97
|
+
end
|
98
|
+
|
99
|
+
def executeBenchmark()
|
100
|
+
@benchmarkTest = @benchmarkRun.benchmarkTest
|
101
|
+
@environment.push(@settings["env"]["numberOfRunsVar"] + "=" + @benchmarkTest["numberOfRuns"].to_s)
|
102
|
+
@benchmarkRunResult = Benchmark::BenchmarkRunResult.new(@benchmarkRun)
|
103
|
+
|
104
|
+
|
105
|
+
puts "Executing BenchmarkTest " + @benchmarkTest["name"];
|
106
|
+
|
107
|
+
# install benchmark test
|
108
|
+
# update status on server
|
109
|
+
@benchmarkRun.running!("Installing BenchmarkTest " + @benchmarkTest["name"]);
|
110
|
+
post_status_update()
|
111
|
+
batchInstall()
|
112
|
+
|
113
|
+
# execute benchmark test
|
114
|
+
# update status on server
|
115
|
+
@benchmarkRun.running!("Running BenchmarkTest " + @benchmarkTest["name"]);
|
116
|
+
post_status_update()
|
117
|
+
batchRun()
|
118
|
+
|
119
|
+
# Post results
|
120
|
+
# Replace object with url
|
121
|
+
@benchmarkRunResult.benchmarkRun = RestClientWrapper.load_entity_ref(@benchmarkRunResult.benchmarkRun.to_h)["href"]
|
122
|
+
|
123
|
+
# post result entity
|
124
|
+
client.post("benchmarkRunResults", @benchmarkRunResult)
|
125
|
+
|
126
|
+
puts "Results posted to server."
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
def batchInstall
|
131
|
+
installCommand = compileParams("batch-install", testLabel(@benchmarkTest))
|
132
|
+
status = POpen4.popen4("sh") do | stdout, stderr, stdin, pid |
|
133
|
+
stdin.puts @settings["executable"] + " " + installCommand
|
134
|
+
stdin.close
|
135
|
+
|
136
|
+
puts "pid : #{ pid }"
|
137
|
+
puts "stdout : #{ stdout.read.strip }"
|
138
|
+
puts "stderr : #{ stderr.read.strip }"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def batchRun
|
143
|
+
installCommand = compileParams("batch-run", testLabel(@benchmarkTest))
|
144
|
+
startStamp = Time.now
|
145
|
+
puts "Start time: "+startStamp.utc.to_s
|
146
|
+
if (!@options["development"])
|
147
|
+
status = POpen4.popen4("sh") do | stdout, stderr, stdin, pid |
|
148
|
+
stdin.puts @settings["executable"] + " " + installCommand
|
149
|
+
stdin.close
|
150
|
+
|
151
|
+
puts "pid : #{ pid }"
|
152
|
+
puts "stdout : #{ stdout.read.strip }"
|
153
|
+
puts "stderr : #{ stderr.read.strip }"
|
154
|
+
end
|
155
|
+
end
|
156
|
+
stopStamp = Time.now
|
157
|
+
puts "End time: "+stopStamp.utc.to_s
|
158
|
+
executionTime = (stopStamp - startStamp) # / @nanoSeconds;
|
159
|
+
|
160
|
+
puts "Benchmark " + testLabel(@benchmarkTest) + " executed in " + executionTime.to_s + " seconds"
|
161
|
+
|
162
|
+
|
163
|
+
# storing benchmarking execution gross timer
|
164
|
+
resultValueTimer = Benchmark::BenchmarkRunResultValue.new()
|
165
|
+
resultValueTimer.name = "ExecRuntime"
|
166
|
+
resultValueTimer.description = "Benchmark runtime measured by execution handler"
|
167
|
+
resultValueTimer.value = executionTime.to_s
|
168
|
+
resultValueTimer.scale = "Seconds"
|
169
|
+
# add timer value to result
|
170
|
+
@benchmarkRunResult.pushResultValue(resultValueTimer)
|
171
|
+
|
172
|
+
# get results here
|
173
|
+
puts "Collecting results from xml"
|
174
|
+
# walk result files (usually only one)
|
175
|
+
files = resultFiles()
|
176
|
+
files.each do |f|
|
177
|
+
puts "Processing #{f}..."
|
178
|
+
|
179
|
+
# read result file contents
|
180
|
+
resultxml = File.read(f)
|
181
|
+
if (resultxml == nil)
|
182
|
+
@benchmarkRun.failed!("ERROR, no xml results found")
|
183
|
+
post_status_update()
|
184
|
+
return
|
185
|
+
end
|
186
|
+
|
187
|
+
# convert xml to result objects
|
188
|
+
results = compileResults(resultxml)
|
189
|
+
if (! results.empty?)
|
190
|
+
@benchmarkRun.succeed!("OK, finished successfully.")
|
191
|
+
else
|
192
|
+
@benchmarkRun.failed!("ERROR, no results")
|
193
|
+
end
|
194
|
+
post_status_update()
|
195
|
+
|
196
|
+
end
|
197
|
+
|
198
|
+
end # batchRun
|
199
|
+
|
200
|
+
|
201
|
+
def compileResults(xml)
|
202
|
+
|
203
|
+
@benchmarkRunResult.rawBenchmarkOutput = xml
|
204
|
+
@benchmarkRunResult.ip_address = @ip_address
|
205
|
+
@benchmarkRunResult.hostname = @hostname
|
206
|
+
|
207
|
+
# parse result xml and convert to object
|
208
|
+
results = XmlSimple.xml_in(xml)
|
209
|
+
|
210
|
+
# loop over results and create
|
211
|
+
results['Result'].each do |res|
|
212
|
+
|
213
|
+
res['Data'].each do |entry|
|
214
|
+
|
215
|
+
# create BenchmarkRunResultValue - copy values from result XML
|
216
|
+
value = Benchmark::BenchmarkRunResultValue.new()
|
217
|
+
value.name = results['System'][0]['Identifier'][0]
|
218
|
+
value.timestamp = results['System'][0]['Timestamp']
|
219
|
+
value.description = res['Description'][0] || results["Generated"][0]["TestClient"][0] + "\n" + results["Generated"][0]["Description"][0]
|
220
|
+
value.arguments = res['Arguments'][0].empty? ? "" : res['Arguments'][0]
|
221
|
+
value.scale = res['Scale'][0]
|
222
|
+
value.value = entry['Entry'][0]['Value'][0]
|
223
|
+
value.rawString = entry['Entry'][0]['RawString'][0]
|
224
|
+
|
225
|
+
# add benchmark to result instance var
|
226
|
+
@benchmarkRunResult.pushResultValue(value)
|
227
|
+
|
228
|
+
value
|
229
|
+
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
end
|
234
|
+
|
235
|
+
|
236
|
+
def resultFiles
|
237
|
+
puts "Loading files from " + resultDir + @fileSeparator + "**/test-1.xml"
|
238
|
+
Dir[resultDir() + @fileSeparator + "**/test-1.xml"] ## TODO CHECK if this yields one file only and the right one, too :)
|
239
|
+
end
|
240
|
+
|
241
|
+
def reset()
|
242
|
+
@benchmarkRun = nil
|
243
|
+
@benchmarkRunResult = nil
|
244
|
+
if (!@options["development"] && File.exists?(resultDir))
|
245
|
+
FileUtils.rm_rf(resultDir)
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
def post_status_update
|
250
|
+
# post benchmarkRun status update
|
251
|
+
client.patch("benchmarkRuns", @benchmarkRun.id, @benchmarkRun.get_status_hash())
|
252
|
+
# TODO: error handling
|
253
|
+
end
|
254
|
+
|
255
|
+
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|