Class: Origen::Tester::Time

Inherits:
Object show all
Defined in:
lib/origen/tester/time.rb

Overview

Class for handling test time analysis - implements the functionality exposed via the ‘origen time’ command

Constant Summary collapse

TT_LIB_DIR =
"#{Origen.root}/config/test_time/lib"
TT_FLOW_DIR =
"#{Origen.root}/config/test_time/flow"
DEFAULT_LIBRARY =
"#{TT_LIB_DIR}/default.yaml"
DEFAULT_FLOW =
"#{TT_FLOW_DIR}/default.yaml"
TEST_META_DATA =

If any new embedded hashes are added to this a default of {} must also be added to the sanitize method

{ 'rule'      => nil,
  'reference' => { 'rule_result' => nil,
                   'time'        => nil,
                   'target'      => nil
                 }
}

Instance Method Summary collapse

Instance Method Details

#calculate_time(flow, library, options = {}) ⇒ Object

Calculate the time for the given flow, using times from the given library



219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/origen/tester/time.rb', line 219

def calculate_time(flow, library, options = {})
  options = {
    silent:  false,
    summary: false
  }.merge(options)
  unless options[:silent] || options[:summary]
    Origen.log.info 'Test'.ljust(60) + 'Rule'.ljust(40) +
      library.first[1]['reference']['target'].ljust(30) + Origen.target.name
    orig = 0
  end
  forecasted = flow.reduce(0.0) do  |sum, test|
    if library[test]['include'] == false || library[test]['exclude'] == true
      sum
    else
      orig += library[test]['reference']['time'] unless options[:silent] || options[:summary]
      forecast = rules.forecast(test, library[test], options)
      unless options[:silent] || options[:summary]
        Origen.log.info test.ljust(60) + library[test]['rule'].to_s.ljust(40) +
                        "#{library[test]['reference']['time'].round(6)}".ljust(30) +
                        "#{forecast.round(6)}"
      end
      sum + forecast
    end
  end
  if options[:silent]
    forecasted.round(6)
  elsif options[:summary]
    Origen.log.info Origen.target.name.ljust(50) + "#{forecasted.round(6)}"
  else
    Origen.log.info ''
    Origen.log.info ''.ljust(100) + '---------------'.ljust(30) + '---------------'
    Origen.log.info ''.ljust(100) + "#{orig.round(6)}".ljust(30) + "#{forecasted.round(6)}"
    Origen.log.info ''.ljust(100) + '---------------'.ljust(30) + '==============='
    Origen.log.info ''
  end
end

#clear_statsObject



26
27
28
# File 'lib/origen/tester/time.rb', line 26

def clear_stats
  @stats = nil
end

#deep_merge(hash1, hash2) ⇒ Object

Deep merge two hashes, the first one should be the defaults, the second one will override any items from the defaults



203
204
205
206
207
208
209
# File 'lib/origen/tester/time.rb', line 203

def deep_merge(hash1, hash2)
  hash1.merge(hash2) do |_key, oldval, newval|
    oldval = oldval.to_hash if oldval.respond_to?(:to_hash)
    newval = newval.to_hash if newval.respond_to?(:to_hash)
    oldval.class.to_s == 'Hash' && newval.class.to_s == 'Hash' ? deep_merge(oldval, newval) : newval
  end
end

#export_flow(flow, options = {}) ⇒ Object



190
191
192
193
194
195
# File 'lib/origen/tester/time.rb', line 190

def export_flow(flow, options = {})
  Origen.file_handler.open_for_write(output_flow_file(options)) do |f|
    f.puts YAML.dump(flow)
  end
  puts "Test flow exported to: #{Origen.file_handler.relative_path_to(output_flow_file(options))}"
end

#export_library(lib, options = {}) ⇒ Object



175
176
177
178
179
180
181
182
183
184
# File 'lib/origen/tester/time.rb', line 175

def export_library(lib, options = {})
  tests = {}
  lib.each do |name, attrs|
    tests[name] = attrs
  end
  Origen.file_handler.open_for_write(output_library_file(options)) do |f|
    f.puts YAML.dump('tests' => tests)
  end
  puts "Test library exported to: #{Origen.file_handler.relative_path_to(output_library_file(options))}"
end

#extract_total_time(tests) ⇒ Object



256
257
258
# File 'lib/origen/tester/time.rb', line 256

def extract_total_time(tests)
  tests.reduce(0.0) { |sum, test| sum + test[:time] }
end

#filterObject



319
320
321
322
323
324
325
326
# File 'lib/origen/tester/time.rb', line 319

def filter
  return @filter if defined?(@filter)
  if defined?(TestTimeFilter)
    @filter = TestTimeFilter.new
  else
    @filter = false
  end
end

#forecast_test_time(options = {}) ⇒ Object



100
101
102
103
104
105
106
107
# File 'lib/origen/tester/time.rb', line 100

def forecast_test_time(options = {})
  clear_stats
  @options = options
  time = 0.0
  flow = import_flow(input_flow_file(options))
  library = import_library(input_library_file(options))['tests']
  calculate_time(flow, library, options)
end

#import?(test) ⇒ Boolean

Returns:

  • (Boolean)


311
312
313
314
315
316
317
# File 'lib/origen/tester/time.rb', line 311

def import?(test)
  if filter
    filter.import?(test)
  else
    true
  end
end

#import_flow(flow, _options = {}) ⇒ Object



197
198
199
# File 'lib/origen/tester/time.rb', line 197

def import_flow(flow, _options = {})
  YAML.load(File.open(flow))
end

#import_library(lib, _options = {}) ⇒ Object



186
187
188
# File 'lib/origen/tester/time.rb', line 186

def import_library(lib, _options = {})
  YAML.load(File.open(lib))
end

#import_test_flow(file, options = {}) ⇒ Object

Import a flow, this can be from either a datalog or an execution time



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/origen/tester/time.rb', line 31

def import_test_flow(file, options = {})
  clear_stats
  @options = options
  if Origen.tester.respond_to?('read_test_times')
    tests = Origen.tester.read_test_times(file, options)
    flow = []
    merge_indexed_tests(tests) do |name, _attrs|
      if import?(name)
        Origen.log.info "imported... #{name}"
        flow << name
      end
    end
    puts ''
    puts 'Import complete!'
    puts ''
    export_flow(flow, options)
  else
    error 'Sorry, no test time import method is defined for the current tester'
  end
end

#import_test_time(file, options = {}) ⇒ Object



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/origen/tester/time.rb', line 52

def import_test_time(file, options = {})
  clear_stats
  @options = options
  if Origen.tester.respond_to?('read_test_times')
    tests = Origen.tester.read_test_times(file, options)
    total = extract_total_time(tests)
    flow = []
    library = {}
    imported = 0.0
    merge_indexed_tests(tests) do |name, attrs|
      attrs = sanitize(attrs)

      if import?(name)
        Origen.log.info "importing... #{name}"
        flow << name
        if library[name]
          library[name] = merge(library[name], attrs)
        else
          library[name] = populate(name, attrs)
          stats[:imported] += 1
        end
        imported += attrs['reference']['time']
        # puts name
      end
    end
    puts ''
    puts 'Import complete!'
    puts ''
    puts 'Some stats...'
    puts ''
    puts "Tests imported:        #{stats[:imported]}"
    puts "Rules assigned:        #{stats[:rules_assigned]}"
    puts "Ref rules calculated:  #{stats[:reference_rules_evaluated]}"
    puts ''
    puts 'Total time:            ' + total.round(6).to_s + 's'
    puts 'Total filtered time:   ' + imported.round(6).to_s + 's'
    if stats[:imported] == stats[:rules_assigned]
      puts 'Forecasted:            ' + calculate_time(flow, library, options.merge(silent: true)).to_s + 's'
    else
      puts 'Forecasted:            SOME TESTS HAVE NO RULES ASSIGNED!'
    end
    puts ''
    export_library(library, options)
  else
    error 'Sorry, no test time import method is defined for the current tester'
  end
end

#input_flow_file(options = {}) ⇒ Object



129
130
131
# File 'lib/origen/tester/time.rb', line 129

def input_flow_file(options = {})
  output_flow_file(options)
end

#input_library_file(options = {}) ⇒ Object



117
118
119
# File 'lib/origen/tester/time.rb', line 117

def input_library_file(options = {})
  output_library_file(options)
end

#merge(t1, t2) ⇒ Object

Merge two sets of attributes for the same test, generally this means that the time will be averaged and all other attributes will remain the same



213
214
215
216
# File 'lib/origen/tester/time.rb', line 213

def merge(t1, t2)
  t1['reference']['time'] = (t1['reference']['time'] + t2['reference']['time']) / 2
  t1
end

#merge_indexed_tests(tests) ⇒ Object

This combines the test time from indexed tests and removes the :index and :group keys from all tests.

If it is an indexed test then a single hash will be returned containing the total time and the key: => true.



264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
# File 'lib/origen/tester/time.rb', line 264

def merge_indexed_tests(tests)
  ix_counter = false
  ix_group = false
  ix_test = false
  ix_total = false

  tests.each do |t|
    i = t.delete(:index)
    g = t.delete(:group)
    process = true
    if ix_counter
      if ix_test == t[:name]
        process = false
        warning "Incomplete index data from test: #{ix_test}" if i != ix_counter + 1
        ix_counter = i
        ix_total += t[:time]
        # If the last test in the index
        if i == ix_group
          yield(ix_test, { time: ix_total, indexed: true })
          ix_counter = false
        end
      else
        warning "Incomplete index data from test: #{ix_test}"
        yield(ix_test, { time: ix_total, indexed: true })
        ix_counter = false
      end
    end
    # Don't combine this with the above via an else, it is required to be separate to generate the
    # next entry in the case where an index group was incomplete
    if process
      if i
        # Ignore tests with an invalid index and a very short time, these occur from tests which
        # are in the flow, but have not been executed in this run
        unless i != 1 && t[:time] < 0.0001
          ix_counter = i
          ix_group = g
          ix_test = t[:name]
          ix_total = t[:time]
          warning "Incomplete index data from test: #{t[:name]}" if ix_counter != 1
        end
      else
        yield t.delete(:name), t
      end
    end
  end
end

#output_flow_file(options = {}) ⇒ Object



121
122
123
124
125
126
127
# File 'lib/origen/tester/time.rb', line 121

def output_flow_file(options = {})
  if options[:ref_name]
    "#{TT_FLOW_DIR}/#{options[:ref_name]}.yaml"
  else
    DEFAULT_FLOW
  end
end

#output_library_file(options = {}) ⇒ Object



109
110
111
112
113
114
115
# File 'lib/origen/tester/time.rb', line 109

def output_library_file(options = {})
  if options[:ref_name]
    "#{TT_LIB_DIR}/#{options[:ref_name]}.yaml"
  else
    DEFAULT_LIBRARY
  end
end

#populate(name, attrs, options = {}) ⇒ Object

Populate the attributes based on user specified rules



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/origen/tester/time.rb', line 152

def populate(name, attrs, options = {})
  if rules
    r = rules.assign(name, attrs, options)
    if r
      stats[:rules_assigned] += 1
      attrs['rule'] = r
    else
      warn "No rule assigned to: #{name}"
      attrs.delete('rule')
    end
    r = rules.evaluate(name, attrs, options)
    if r
      stats[:reference_rules_evaluated] += 1
      attrs['reference']['rule_result'] = r
    else
      warn "No reference rule result assigned to: #{name}"
      attrs['reference'].delete('rule_result')
    end
  end
  attrs['reference']['target'] = Origen.target.name
  attrs
end

#rulesObject



328
329
330
331
332
333
334
335
# File 'lib/origen/tester/time.rb', line 328

def rules
  return @rules if defined?(@rules)
  if defined?(TestTimeRules)
    @rules = TestTimeRules.new
  else
    @rules = false
  end
end

#sanitize(attrs) ⇒ Object

Force the imported test data from the tester into a YAML compliant form



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/origen/tester/time.rb', line 134

def sanitize(attrs)
  # Force all keys to strings...
  attrs.keys.each do |key|
    begin
      attrs[key.to_s] = attrs.delete(key)
    rescue
      # No problem
    end
  end
  attrs['reference'] ||= {}
  # attrs["opportunity"] ||= {}
  if attrs['time']
    attrs['reference']['time'] = attrs.delete('time')
  end
  deep_merge(TEST_META_DATA, attrs)
end

#statsObject



22
23
24
# File 'lib/origen/tester/time.rb', line 22

def stats
  @stats ||= { imported: 0, rules_assigned: 0, reference_rules_evaluated: 0 }
end