laborantin 0.0.9 → 0.0.10
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.
- data/Rakefile +2 -2
- data/TODO +0 -2
- data/bin/labor +1 -1
- data/lib/laborantin.rb +20 -1
- data/lib/laborantin/core/environment.rb +57 -16
- data/lib/laborantin/core/monkey_patches.rb +6 -0
- data/lib/laborantin/core/parameter.rb +16 -2
- data/lib/laborantin/core/parameter_hash.rb +9 -5
- data/lib/laborantin/core/scenario.rb +88 -3
- metadata +3 -3
data/Rakefile
CHANGED
@@ -3,7 +3,7 @@ require 'rake/gempackagetask'
|
|
3
3
|
|
4
4
|
spec = Gem::Specification.new do |s|
|
5
5
|
s.name = 'laborantin'
|
6
|
-
s.version = '0.0.
|
6
|
+
s.version = '0.0.10'
|
7
7
|
s.platform = Gem::Platform::RUBY
|
8
8
|
s.summary = "A measurement batch facilitator"
|
9
9
|
|
@@ -33,7 +33,7 @@ spec = Gem::Specification.new do |s|
|
|
33
33
|
s.bindir = 'bin'
|
34
34
|
s.executables = ['labor']
|
35
35
|
|
36
|
-
s.has_rdoc =
|
36
|
+
s.has_rdoc = true
|
37
37
|
end
|
38
38
|
|
39
39
|
Rake::GemPackageTask.new(spec) do |pkg|
|
data/TODO
CHANGED
data/bin/labor
CHANGED
data/lib/laborantin.rb
CHANGED
@@ -28,8 +28,27 @@ require File.join(File.dirname(__FILE__), 'laborantin', 'core', 'environment')
|
|
28
28
|
require File.join(File.dirname(__FILE__), 'laborantin', 'core', 'monkey_patches')
|
29
29
|
|
30
30
|
module Laborantin
|
31
|
-
VERSION = '0.0.
|
31
|
+
VERSION = '0.0.10'
|
32
32
|
AUTHORS = ['Lucas Di Cioccio']
|
33
33
|
WEBSITE = 'http://dicioccio.fr'
|
34
34
|
LICENSE = 'GNU GPL version 3'
|
35
|
+
|
36
|
+
@@rootdir = '.'
|
37
|
+
|
38
|
+
# The root of the arborescence for Laborantin. Usually the directory created
|
39
|
+
# via the scripts.
|
40
|
+
def self.rootdir
|
41
|
+
@@rootdir || '.'
|
42
|
+
end
|
43
|
+
|
44
|
+
# Specifies the rootdir, dir is a path (instance of String, not a Dir object).
|
45
|
+
def self.rootdir=(dir='.')
|
46
|
+
@@rootdir = dir
|
47
|
+
end
|
48
|
+
|
49
|
+
# The path to the results (needs the Laborantin.rootdir).
|
50
|
+
def self.resultdir
|
51
|
+
File.join(Laborantin.rootdir, 'results')
|
52
|
+
end
|
53
|
+
|
35
54
|
end
|
@@ -26,10 +26,22 @@ require 'logger'
|
|
26
26
|
require 'fileutils'
|
27
27
|
|
28
28
|
module Laborantin
|
29
|
+
|
30
|
+
# An Environment represents the surrounding of an experiment. Basically, it
|
31
|
+
# should only contains things that are hard to change during an experiment.
|
32
|
+
# Let's say you have two computers, A and B. A can be a server and B a client
|
33
|
+
# or vice-versa. Hence, this is a good choice for two differents environments.
|
34
|
+
#
|
35
|
+
# As a normal user, you should only subclass Environment, and let the script
|
36
|
+
# creates it for you. But it is easy to monkey-patch or to subclass by hand.
|
37
|
+
# If you want to do that, you must know that Environment @@all class variable
|
38
|
+
# holds a reference to every child class from Environment.
|
29
39
|
class Environment
|
30
40
|
@@all = []
|
31
|
-
@@rootdir = '.'
|
32
41
|
|
42
|
+
# Populates loaded (i.e. put in @@all class variable when self.inherited
|
43
|
+
# is called) environment classes from existing results that are stored in
|
44
|
+
# the dir parameter.
|
33
45
|
def self.scan_resdir(dir)
|
34
46
|
ret = []
|
35
47
|
Dir.entries(dir).each do |f|
|
@@ -48,20 +60,22 @@ module Laborantin
|
|
48
60
|
end
|
49
61
|
|
50
62
|
class << self
|
51
|
-
attr_accessor :verifications, :description, :envdir, :hooks
|
52
63
|
|
53
|
-
|
54
|
-
|
55
|
-
|
64
|
+
# An array of methods called to ensure that the environment run is the
|
65
|
+
# wanted one (e.g. a way to specify a RUBY_PLATFORM).
|
66
|
+
# CURRENTLY NOT HERITED
|
67
|
+
attr_accessor :verifications
|
56
68
|
|
57
|
-
|
58
|
-
|
59
|
-
|
69
|
+
# A description used for printing summary and debug purposes. Will be
|
70
|
+
# used to create .tex report in the future.
|
71
|
+
# CURRENTLY NOT HERITED
|
72
|
+
attr_accessor :description
|
60
73
|
|
61
|
-
|
62
|
-
|
63
|
-
|
74
|
+
# A hash to store setup/teardown hooks.
|
75
|
+
# CURRENTLY NOT HERITED
|
76
|
+
attr_accessor :hooks
|
64
77
|
|
78
|
+
# Prepares attributes' default values whenever a subclass is created.
|
65
79
|
def inherited(klass)
|
66
80
|
klass.verifications = []
|
67
81
|
klass.description = ''
|
@@ -69,18 +83,23 @@ module Laborantin
|
|
69
83
|
@@all << klass
|
70
84
|
end
|
71
85
|
|
86
|
+
# Registers new verifiers.
|
72
87
|
def verify(*args)
|
73
88
|
self.verifications = [*args].flatten
|
74
89
|
end
|
75
90
|
|
91
|
+
# Sets the description.
|
76
92
|
def describe(str)
|
77
93
|
self.description = str
|
78
94
|
end
|
79
95
|
|
96
|
+
# Registers setup hooks, called before any scenario is instantiated.
|
80
97
|
def setup(*args)
|
81
98
|
self.hooks[:setup] = [*args].flatten
|
82
99
|
end
|
83
100
|
|
101
|
+
# Registers teardown hooks, called after every scenarii has been
|
102
|
+
# performed and analyzed.
|
84
103
|
def teardown(*args)
|
85
104
|
self.hooks[:teardown] = [*args].flatten
|
86
105
|
end
|
@@ -89,20 +108,31 @@ module Laborantin
|
|
89
108
|
"#{self.name}:\n\t#{self.description}"
|
90
109
|
end
|
91
110
|
|
111
|
+
# Returns all the known subklasses of Environment.
|
92
112
|
def all
|
93
113
|
@@all
|
94
114
|
end
|
95
115
|
|
116
|
+
# The path where the results for instance of a subklass of Environment
|
117
|
+
# are stored (needs the Laborantin.resultdir).
|
96
118
|
def envdir
|
97
|
-
File.join(resultdir, self.name.duck_case)
|
119
|
+
File.join(Laborantin.resultdir, self.name.duck_case)
|
98
120
|
end
|
99
|
-
|
100
121
|
end
|
101
122
|
|
102
|
-
#
|
103
|
-
|
123
|
+
# An attribute that holds the directory where the logfile and the scenarii results
|
124
|
+
# are stored. Can be overridden (e.g. Environment.scan_resdir does that).
|
125
|
+
attr_accessor :rundir
|
126
|
+
|
127
|
+
# A date that holds the creation of the instance, it is not meaningful
|
128
|
+
# when an env was created by a call to Environment.scan_resdir.
|
129
|
+
# TODO better
|
130
|
+
attr_accessor :date
|
131
|
+
|
132
|
+
# An array of loggers objects.
|
133
|
+
attr_accessor :loggers
|
104
134
|
|
105
|
-
def initialize
|
135
|
+
def initialize
|
106
136
|
@date = Time.now
|
107
137
|
@rundir = File.join(self.class.envdir, date_str)
|
108
138
|
@loggers = []
|
@@ -112,6 +142,13 @@ module Laborantin
|
|
112
142
|
self.class.verifications.find{|v| not send(v)}.nil?
|
113
143
|
end
|
114
144
|
|
145
|
+
# In the following order:
|
146
|
+
# * Creates the envdir if needed.
|
147
|
+
# * Adds some loggers.
|
148
|
+
# * Calls the setup hooks methods
|
149
|
+
# BEWARE : currently does not ensure unicity of envdir,
|
150
|
+
# so wait one sec between several runs of same env
|
151
|
+
#
|
115
152
|
def prepare!
|
116
153
|
FileUtils.mkdir_p(rundir) #TODO: ensure unicity
|
117
154
|
@loggers << Logger.new(File.join(rundir, 'environment.log'))
|
@@ -121,14 +158,18 @@ module Laborantin
|
|
121
158
|
call_hooks :setup
|
122
159
|
end
|
123
160
|
|
161
|
+
# Calls the teardown hooks methods.
|
124
162
|
def teardown!
|
125
163
|
call_hooks :teardown
|
126
164
|
end
|
127
165
|
|
166
|
+
# Send str log message at the levele lvl to every loggers.
|
128
167
|
def log(str, lvl=:debug)
|
129
168
|
@loggers.each{|l| l.send(lvl, str)}
|
130
169
|
end
|
131
170
|
|
171
|
+
# Returns an array of Scenario objects from the results in the envdir.
|
172
|
+
# The scenarii classes must be loaded before, else, some results might be ignored.
|
132
173
|
def populate
|
133
174
|
Laborantin::Scenario.scan_env(self)
|
134
175
|
end
|
@@ -21,10 +21,16 @@ Copyright (c) 2009, Lucas Di Cioccio
|
|
21
21
|
|
22
22
|
=end
|
23
23
|
|
24
|
+
# Some monkey patches on String. Will be moved later to a subclass.
|
24
25
|
class String
|
26
|
+
# Returns a camelized version of a duck_case self
|
27
|
+
# 'some_string'.camelize # => 'SomeString'
|
25
28
|
def camelize
|
26
29
|
self.split('_').map{|i| i.capitalize}.join('')
|
27
30
|
end
|
31
|
+
|
32
|
+
# Returns a duck cased version of camel-like self
|
33
|
+
# 'SomeString'.duck_case # => 'some_string'
|
28
34
|
def duck_case
|
29
35
|
self.gsub(/([A-Z])/){|s| "_#{$1.downcase}"}.sub(/^_/,'')
|
30
36
|
end
|
@@ -22,25 +22,39 @@ Copyright (c) 2009, Lucas Di Cioccio
|
|
22
22
|
=end
|
23
23
|
|
24
24
|
module Laborantin
|
25
|
+
|
26
|
+
# A ParameterRange instance is more or less a wrapper over an Array of allowed values.
|
25
27
|
class ParameterRange
|
26
|
-
|
28
|
+
|
29
|
+
# The name of the parameter (should be unique in a Scenario's parameters)
|
30
|
+
# Usually a symbol.
|
31
|
+
attr_accessor :name
|
32
|
+
|
33
|
+
# A description used for printing summary and debug purposes. Will be
|
34
|
+
# used to create .tex report in the future.
|
35
|
+
attr_accessor :description
|
36
|
+
|
27
37
|
def initialize(name)
|
28
38
|
@name = name
|
29
39
|
@values = []
|
30
40
|
@description = ''
|
31
41
|
end
|
32
42
|
|
43
|
+
# Sets the allowed values. Currently only supports Array like.
|
44
|
+
# It is planned to support Iterable, but deterministic ones only.
|
33
45
|
def values(*args)
|
34
46
|
@values = args.flatten unless args.empty?
|
35
47
|
@values
|
36
48
|
end
|
37
49
|
|
50
|
+
# Iterates on allowed values for this parameter.
|
38
51
|
def each
|
39
52
|
@values.each do |v|
|
40
|
-
|
53
|
+
yield v
|
41
54
|
end
|
42
55
|
end
|
43
56
|
|
57
|
+
# Sets the description.
|
44
58
|
def describe(str)
|
45
59
|
@description = str
|
46
60
|
end
|
@@ -23,16 +23,20 @@ Copyright (c) 2009, Lucas Di Cioccio
|
|
23
23
|
|
24
24
|
|
25
25
|
module Laborantin
|
26
|
+
# A kind of Hash that can yields all possible configuration recursively.
|
27
|
+
# It should contains ParameterRange like definitions objects.
|
26
28
|
class ParameterHash < Hash
|
29
|
+
# Recursively yields all the possible configurations of parameters (a new hash).
|
30
|
+
# No order is supported on the recursion, and it is not planned to.
|
27
31
|
def each_config(remaining=self.keys, cfg={}, &blk)
|
28
32
|
key = remaining.pop
|
29
33
|
if key
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
+
self[key].each do |val|
|
35
|
+
cfg[key] = val
|
36
|
+
each_config(remaining.dup, cfg, &blk)
|
37
|
+
end
|
34
38
|
else
|
35
|
-
|
39
|
+
yield cfg
|
36
40
|
end
|
37
41
|
end
|
38
42
|
|
@@ -28,9 +28,25 @@ require 'fileutils'
|
|
28
28
|
require 'yaml'
|
29
29
|
|
30
30
|
module Laborantin
|
31
|
+
|
32
|
+
# A Scenario represents a measurement done in a given environment. Some of
|
33
|
+
# its parameters will change, and we are interested in varying these parameters and
|
34
|
+
# then study their impact.
|
35
|
+
#
|
36
|
+
# An user will usually creates a Scenario subklass which represents such
|
37
|
+
# a measurement. For that he must defines a run method that will yield consecutive
|
38
|
+
# lines added to the raw result file. Then this file can be processed to give
|
39
|
+
# intermediary or final results.
|
40
|
+
#
|
41
|
+
# Like the Environment, all the subklasses will be stored in a @@all
|
42
|
+
# class variable for convenience purpose.
|
31
43
|
class Scenario
|
32
44
|
@@all = []
|
33
45
|
|
46
|
+
# Scans the env's envdir (should be an Environment) for scenarii results.
|
47
|
+
# It will set their configuration according to the stored config.yaml
|
48
|
+
# in YAML format.
|
49
|
+
# Returns an array of such built scenarii.
|
34
50
|
def self.scan_env(env)
|
35
51
|
scs = []
|
36
52
|
Dir.entries(env.rundir).each do |s|
|
@@ -50,8 +66,26 @@ module Laborantin
|
|
50
66
|
end
|
51
67
|
|
52
68
|
class << self
|
53
|
-
attr_accessor :description, :parameters, :products, :hooks
|
54
69
|
|
70
|
+
# A description used for printing summary and debug purposes. Will be
|
71
|
+
# used to create .tex report in the future.
|
72
|
+
# CURRENTLY NOT HERITED
|
73
|
+
attr_accessor :description
|
74
|
+
|
75
|
+
# A hash to store setup/teardown hooks.
|
76
|
+
# CURRENTLY NOT HERITED
|
77
|
+
attr_accessor :hooks
|
78
|
+
|
79
|
+
# The set of parameters that will vary for this Scenario.
|
80
|
+
attr_accessor :parameters
|
81
|
+
|
82
|
+
# Some special products that are done after an analysis on a measurement
|
83
|
+
# scenario. The intended way is to store raw results (e.g. a command output) in
|
84
|
+
# a file and then parse them and store the parsed result in another file etc.
|
85
|
+
# TODO : products that compares scenarii
|
86
|
+
attr_accessor :products
|
87
|
+
|
88
|
+
# Prepares attributes' default values whenever a subclass is created.
|
55
89
|
def inherited(klass)
|
56
90
|
klass.parameters = ParameterHash.new
|
57
91
|
klass.description = ''
|
@@ -60,19 +94,30 @@ module Laborantin
|
|
60
94
|
@@all << klass
|
61
95
|
end
|
62
96
|
|
97
|
+
# Sets the description.
|
63
98
|
def describe(str)
|
64
99
|
self.description = str
|
65
100
|
end
|
66
101
|
|
102
|
+
# Registers setup hooks.
|
67
103
|
def setup(*args)
|
68
104
|
self.hooks[:setup] = [*args].flatten
|
69
105
|
end
|
70
106
|
|
107
|
+
# Register teardown hooks.
|
71
108
|
def teardown(*args)
|
72
109
|
self.hooks[:teardown] = [*args].flatten
|
73
110
|
end
|
74
111
|
|
75
|
-
|
112
|
+
# Defines a new ParameterRange instance for this Scenario.
|
113
|
+
# A block should be passed that will be evaluated in this
|
114
|
+
# ParameterRange instance's context.
|
115
|
+
#
|
116
|
+
# parameter(:size) do
|
117
|
+
# values 10, 20, 30
|
118
|
+
# describe "We expect a linear RTT increase with the size"
|
119
|
+
# end
|
120
|
+
#
|
76
121
|
def parameter(name, &blk)
|
77
122
|
raise ArgumentError.new("Parameter #{name} already exists") if self.parameters[name]
|
78
123
|
param = ParameterRange.new(name)
|
@@ -80,6 +125,9 @@ module Laborantin
|
|
80
125
|
self.parameters[name] = param
|
81
126
|
end
|
82
127
|
|
128
|
+
# Defines the products names.
|
129
|
+
# IMPORTANT: products are built in the provided order, and they must be
|
130
|
+
# valid instance methods name for a Scenario object (hence user defined).
|
83
131
|
def produces(*args)
|
84
132
|
self.products = [*args].flatten
|
85
133
|
end
|
@@ -88,17 +136,34 @@ module Laborantin
|
|
88
136
|
"#{self.name}:\n\t#{self.description}\n#{self.parameters}"
|
89
137
|
end
|
90
138
|
|
139
|
+
# Returns all the known subklasses of Scenario.
|
91
140
|
def all
|
92
141
|
@@all
|
93
142
|
end
|
94
143
|
|
144
|
+
# Returns the path where the results of the instances of this Scenario
|
145
|
+
# will be stored given that we are in the env Environment. If env is nil,
|
146
|
+
# will use '.' as rootdir for the Scenario results.
|
95
147
|
def scenardir(env=nil)
|
96
148
|
envdir = env.rundir || '.'
|
97
149
|
File.join(envdir, self.name.duck_case)
|
98
150
|
end
|
99
151
|
end # class <<
|
100
152
|
|
101
|
-
|
153
|
+
# A hash of parameters for this run.
|
154
|
+
attr_accessor :params
|
155
|
+
|
156
|
+
# The environment in which we run this scenario.
|
157
|
+
attr_accessor :environment
|
158
|
+
|
159
|
+
# A date that holds the creation of the instance, it is not meaningful
|
160
|
+
# when an env was created by a call to Environment.scan_resdir.
|
161
|
+
# TODO better
|
162
|
+
attr_accessor :date
|
163
|
+
|
164
|
+
# An attribute that holds the directory where the config and the results
|
165
|
+
# are stored. Can be overridden (e.g. Scenario.scan_env does that).
|
166
|
+
attr_accessor :rundir
|
102
167
|
|
103
168
|
def initialize(env, params={})
|
104
169
|
@environment = env
|
@@ -107,6 +172,13 @@ module Laborantin
|
|
107
172
|
@rundir = File.join(self.class.scenardir(environment), date_str)
|
108
173
|
end
|
109
174
|
|
175
|
+
# In the following order:
|
176
|
+
# * Log some info in the environment
|
177
|
+
# * Creates the rundir to store the result and the config
|
178
|
+
# * Stores the configuration as well as the run date
|
179
|
+
# BEWARE : currently does not ensure unicity of rundir,
|
180
|
+
# so wait one sec between several runs of same Scenario
|
181
|
+
#
|
110
182
|
def prepare!
|
111
183
|
log(self.class.description, :info) unless self.class.description.empty?
|
112
184
|
log self.params.inspect, :info
|
@@ -116,6 +188,15 @@ module Laborantin
|
|
116
188
|
File.open( config_path, 'w') {|f| f.puts YAML.dump( [date, params] ) }
|
117
189
|
end
|
118
190
|
|
191
|
+
# In the following order:
|
192
|
+
# * Calls the setup hooks
|
193
|
+
# * Logs some info
|
194
|
+
# * Creates the raw result file
|
195
|
+
# * Calls the run method (user defined)
|
196
|
+
# * ... for each yielded line, store it into the raw result file
|
197
|
+
# * once completed (or on error) closes the raw result file
|
198
|
+
# * Logs some info
|
199
|
+
# * Calls the teardown hooks
|
119
200
|
def perform!
|
120
201
|
call_hooks :setup
|
121
202
|
log "Starting measurement"
|
@@ -128,6 +209,10 @@ module Laborantin
|
|
128
209
|
call_hooks :teardown
|
129
210
|
end
|
130
211
|
|
212
|
+
# For each product define with Scenario.produces, and in its order,
|
213
|
+
# create a file with a canonic name in the scenario rundir.
|
214
|
+
# Call the instance method which has the product name.
|
215
|
+
# Appends each yielded line from this method.
|
131
216
|
def analyze!
|
132
217
|
self.class.products.each do |name|
|
133
218
|
log "Producing #{name}"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: laborantin
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Di Cioccio Lucas
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-07-
|
12
|
+
date: 2009-07-28 00:00:00 +02:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -40,7 +40,7 @@ files:
|
|
40
40
|
- lib/laborantin/core/parameter_hash.rb
|
41
41
|
- lib/laborantin/core/scenario.rb
|
42
42
|
- lib/laborantin/core/monkey_patches.rb
|
43
|
-
has_rdoc:
|
43
|
+
has_rdoc: true
|
44
44
|
homepage: http://rubyforge.org/projects/laborantin
|
45
45
|
post_install_message:
|
46
46
|
rdoc_options: []
|