Name: simulation/lib/expectations.rb 
1:
# Apache License, Version 2.0
2:
#
3:
# Copyright (c) 2013 Juergen Mangler
4:
#
5:
# Licensed under the Apache License, Version 2.0 (the "License");
6:
# you may not use this file except in compliance with the License.
7:
# You may obtain a copy of the License at
8:
#
9:
#     http://www.apache.org/licenses/LICENSE-2.0
10:
#
11:
# Unless required by applicable law or agreed to in writing, software
12:
# distributed under the License is distributed on an "AS IS" BASIS,
13:
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14:
# See the License for the specific language governing permissions and
15:
# limitations under the License.
16:
 
17:
require 'chronic_duration'
18:
 
19:
module TreeBasics #{{{
20:
  def <<(item)
21:
    @elements << item
22:
    item.parent = self  
23:
  end
24:
  def empty?
25:
    @elements.empty?
26:
  end
27:
  def any?
28:
    @elements.any?
29:
  end
30:
  def each
31:
    @elements.each do |ele|
32:
      yield ele
33:
    end  
34:
  end
35:
  def sort!
36:
    @elements.sort!{ |a,b| a.tid <=> b.tid }
37:
  end
38:
end #}}}
39:
 
40:
class Root #{{{
41:
  include TreeBasics
42:
  attr_accessor :elements
43:
  def initialize
44:
    @elements = []
45:
  end
46:
 
47:
  def to_s
48:
    "PROCESS TREE:\n" << print_tree(@elements)
49:
  end
50:
 
51:
  def print_tree(ele,indent='  ')
52:
    ret = ''
53:
    ele.each_with_index do |e,i|
54:
      last  = (i == ele.length - 1)
55:
      pchar = last ? '└' : '├'
56:
      if e.is_a?(BranchContainer)
57:
        ret << indent + pchar + ' ' + e.to_s + "\n"
58:
        ret << print_tree(e.elements,indent + (last ? '  ' : '│ '))
59:
      else
60:
        ret << indent + pchar + ' ' + e.to_s_nice + "\n"
61:
      end
62:
    end
63:
    ret
64:
  end
65:
  private :print_tree
66:
end #}}}
67:
 
68:
class BranchBase #{{{
69:
  attr_accessor :tid, :parent
70:
  def initialize(tid)
71:
    @tid = tid
72:
    @parent = nil
73:
  end
74:
end #}}}
75:
 
76:
class BranchContainer < BranchBase #{{{
77:
  include TreeBasics
78:
  attr_accessor :elements
79:
  def initialize(tid)
80:
    super tid
81:
    @elements = []
82:
    @open = true
83:
  end
84:
  def to_s
85:
    self.class.to_s.gsub(/[^:]*::/,'') + " #{@tid}"
86:
  end
87:
  def open?; @open; end
88:
  def close!; @open = false; end
89:
end #}}}
90:
 
91:
class Activity < BranchBase #{{{
92:
  attr_accessor :item
93:
  attr_reader :min, :avg, :max
94:
  attr_reader :parameters, :endpoints, :code, :label, :type
95:
  def initialize(tid, type, parameters, endpoints, code, min, avg, max, label)
96:
    super tid
97:
    @type = type
98:
    @parameters = parameters || {}
99:
    @endpoints = endpoints.is_a?(Array) ? endpoints : endpoints.nil? ? [] : [endpoints]
100:
    @code = code
101:
    @min = min.nil? ? 0 : ChronicDuration.parse(min)
102:
    @avg = avg.nil? ? 0 : ChronicDuration.parse(avg)
103:
    @max = max.nil? ? 0 : ChronicDuration.parse(max)
104:
    @min = @max if @min > @max
105:
    @avg = @min + (@max - @min) / 2 if @avg < @min || @avg > @max
106:
    @label = label
107:
  end
108:
  def to_s
109:
    "#{@tid}"
110:
  end
111:
  def to_s_nice
112:
    # "#{@tid} - #{@label} - #{@min}-#{@avg}-#{@max}"
113:
    "#{@tid} (#{@label})"
114:
  end
115:
  def inspect
116:
    to_s
117:
  end
118:
end #}}}
119:
 
120:
class Parallel < BranchContainer; end
121:
class ParallelBranch < BranchContainer #{{{
122:
  attr_accessor :proot
123:
  def initialize(tid)
124:
    super tid
125:
    @proot = nil
126:
  end
127:
  def parent=(what)
128:
    super(what)
129:
    par = self
130:
    until par.is_a?(Parallel) || par.parent.nil?
131:
      par = par.parent
132:
    end  
133:
    @proot = par
134:
  end
135:
  def to_s
136:
    super + " (#{@proot.to_s})"
137:
  end  
138:
end #}}}
139:
class Loop < BranchContainer #{{{
140:
  attr_reader :probability1, :repetition1, :probability2, :repetition2
141:
  def initialize(tid,p1,r1,p2,r2)
142:
    super tid
143:
    @probability1 = p1.to_f/100
144:
    @repetition1 = r1.to_i
145:
    @probability2 = p2.to_f/100
146:
    @repetition2 = r2.to_i
147:
  end  
148:
end #}}}
149:
class InfiniteLoop < BranchContainer #{{{
150:
  attr_reader :probability1, :repetition1, :probability2, :repetition2
151:
  def initialize(tid,p1,r1,p2,r2)
152:
    super tid
153:
    @probability1 = p1.to_f / 100
154:
    @repetition1 = r1.to_i
155:
    @probability2 = p2.to_f / 100
156:
    @repetition2 = r2.to_i
157:
  end  
158:
end #}}}
159:
class Choose < BranchContainer #{{{
160:
  attr_reader :mode
161:
  def initialize(tid,mode)
162:
    super tid
163:
    @mode = mode
164:
  end  
165:
end #}}}
166:
class Alternative < BranchContainer #{{{
167:
  attr_reader :probability
168:
  def initialize(tid,condition,probability)
169:
    super tid
170:
    @condition = condition
171:
    @probability = probability.to_f / 100
172:
  end
173:
end #}}}
174:
class Otherwise < BranchContainer #{{{
175:
  attr_reader :probability
176:
  def initialize(tid,probability)
177:
    super tid
178:
    @probability = probability.to_f / 100
179:
  end
180:
end #}}}
181:
 
182:
class TraceProbability #{{{
183:
  attr_reader :probability
184:
 
185:
  def initialize(prob)
186:
    @probability = prob
187:
  end
188:
  def to_s
189:
    "P(#{@probability})"
190:
  end
191:
  def inspect
192:
    to_s
193:
  end
194:
end #}}}
195:
class TraceGroupStart #{{{
196:
  attr_reader :group
197:
  def initialize(g)
198:
    @group = g
199:
  end
200:
  def to_s
201:
    "S{#{@group}}"
202:
  end
203:
  def inspect
204:
    to_s
205:
  end
206:
end #}}}
207:
class TraceGroupEnd #{{{
208:
  attr_reader :group
209:
  def initialize(g)
210:
    @group = g
211:
  end
212:
  def to_s
213:
    "E{#{@group}}"
214:
  end
215:
  def inspect
216:
    to_s
217:
  end
218:
end #}}}
219:
 
220:
class Traces < Array #{{{
221:
  def initialize
222:
    self << []
223:
  end
224:
 
225:
  def to_s
226:
    self.map do |t|
227:
      t.to_s
228:
    end.join("\n")
229:
  end
230:
 
231:
  def calculate
232:
    self.map do |t|
233:
      CTrace.new(t)
234:
    end
235:
  end
236:
 
237:
  def each_with_details
238:
    self.each do |t|
239:
      yield CTrace.new(t)
240:
    end
241:
  end
242:
end #}}}
243:
 
244:
class CTrace
245:
  attr_reader :probability, :min_time, :avg_time, :max_time, :trace, :base
246:
 
247:
  def initialize(t)
248:
    @base = t
249:
    @trace = t.dup
250:
    @probability = 1.0
251:
    @trace.each { |e| @probability *= e.is_a?(TraceProbability) ? e.probability : 1.0 }
252:
    @trace.delete_if{|t| t.is_a?(TraceProbability)}
253:
 
254:
    # Look for groups
255:
    grs = {}
256:
    tgmin, tgavg, tgmax = 0, 0, 0
257:
    @min_time, @avg_time, @max_time = 0, 0, 0
258:
    open = false
259:
    @trace.delete_if do |e|
260:
      if e.is_a?(TraceGroupStart)
261:
        tgmin, tgavg, tgmax = 0, 0, 0
262:
        open = true
263:
        true
264:
      elsif e.is_a?(TraceGroupEnd)
265:
        grs[e.group] ||= [0,0,0]
266:
        tgr = grs[e.group]
267:
        grs[e.group] = [
268:
          tgmin > tgr[0] ? tgmin : tgr[0],
269:
          tgavg > tgr[0] ? tgavg : tgr[1],
270:
          tgmax > tgr[0] ? tgmax : tgr[2]
271:
        ]
272:
        open = false
273:
        true
274:
      else  
275:
        if open
276:
          tgmin += e.min
277:
          tgavg += e.avg
278:
          tgmax += e.max
279:
        else  
280:
          @min_time += e.min
281:
          @avg_time += e.avg
282:
          @max_time += e.max
283:
        end
284:
        false
285:
      end
286:
    end
287:
    grs.each do |k,g|
288:
      @min_time += g[0]
289:
      @avg_time += g[1]
290:
      @max_time += g[2]
291:
    end
292:
  end
293:
end
294:
 
295:
class Expectations #{{{
296:
   attr_reader :trace
297:
 
298:
  def initialize(f)
299:
    @nodes = JSON.parse(File.read(f))
300:
    @tree = Root.new
301:
    @traces = Array.new
302:
  end
303:
 
304:
  def translate(type,tid,params)
305:
    case type
306:
      when :activity
307:
        Activity.new(tid,params['type'].to_sym,params["parameters"]["parameters"],params["endpoints"],params["code"],params["parameters"]["sim_min_time"],params["parameters"]["sim_avg_time"],params["parameters"]["sim_max_time"],params['parameters']['label'])
308:
      when :parallel
309:
        Parallel.new(tid)
310:
      when :loop
311:
        if params["condition"] == true
312:
          InfiniteLoop.new(tid,params['sim_probability1'],params['sim_repetition1'],params['sim_probability2'],params['sim_repetition2'])
313:
        else
314:
          Loop.new(tid,params['sim_probability1'],params['sim_repetition1'],params['sim_probability2'],params['sim_repetition2'])
315:
        end  
316:
      when :parallel_branch
317:
        ParallelBranch.new(tid)
318:
      when :choose
319:
        Choose.new(tid,params[:mode])
320:
      when :alternative
321:
        Alternative.new(tid,params['condition'],params['sim_probability'])
322:
      when :otherwise
323:
        Otherwise.new(tid,params['sim_probability'])
324:
    end  
325:
  end
326:
 
327:
  def tree
328:
    @tree = Root.new
329:
    tree_rec @tree, nil
330:
    @tree
331:
  end
332:
  
333:
  def tree_rec(tree,tparent)
334:
    @nodes.find_all{|t| t['parent'] == tparent }.each do |t|
335:
      type = t['type'].to_sym
336:
      parameters = t['parameters']
337:
      nesting = t['nesting'].to_sym
338:
      sequence = t['sequence']
339:
 
340:
      next if !(nesting == :none || nesting == :start)
341:
 
342:
      item = translate(type,sequence,parameters)
343:
      tree_rec(item,sequence) if item.is_a?(BranchContainer)
344:
      tree << item
345:
    end
346:
    tree.sort!
347:
  end
348:
 
349:
  def traces
350:
    @traces = Traces.new
351:
    rec_traces(@tree,@traces,@traces.last)
352:
    @traces
353:
  end
354:
 
355:
  def merge_into_trace_container(tc,stuff)
356:
    tct = Marshal.load(Marshal.dump(tc))
357:
    tc.delete_if{true}
358:
    stuff.each do |e|
359:
      tctt = Marshal.load(Marshal.dump(tct))
360:
      tctt.each{|t| t.push(*e) }
361:
      tc.push(*tctt)
362:
    end
363:
  end
364:
 
365:
  def loop_transform_container(tc,r1,p1,r2,p2)
366:
    ttc1 = Marshal.load(Marshal.dump(tc))
367:
    ttc2 = Marshal.load(Marshal.dump(tc))
368:
    ttc1 = ttc1.map { |t| (t * r1).unshift TraceProbability.new(p1) }
369:
    ttc2 = ttc2.map { |t| (t * r2).unshift TraceProbability.new(p2) }
370:
    ttc1 + ttc2
371:
  end
372:
 
373:
  def rec_traces(trace,tc,pchoose=nil)
374:
    trace.each do |e|
375:
      case e
376:
        when Activity
377:
          tc.each { |t| t << e }
378:
        when Parallel
379:
          rec_traces(e,tc,pchoose)
380:
        when ParallelBranch
381:
          tc.each { |t| t << TraceGroupStart.new(e.proot.tid) }
382:
          rec_traces(e,tc,pchoose)
383:
          tc.each { |t| t << TraceGroupEnd.new(e.proot.tid) }
384:
        when Choose
385:
          tchoose = []
386:
          rec_traces(e,tc,tchoose)
387:
          merge_into_trace_container(tc,tchoose)
388:
        when Alternative, Otherwise
389:
          nt = []
390:
          pchoose << nt
391:
          nt << TraceProbability.new(e.probability) if e.probability && e.probability != 0
392:
          rec_traces(e,[nt],nil)
393:
        when Loop, InfiniteLoop
394:
          ntc = [[]]
395:
          rec_traces(e,ntc,pchoose)
396:
          ntc.delete_if{|t| t.empty? }
397:
          ntc = loop_transform_container(ntc,e.repetition1,e.probability1,e.repetition2,e.probability2)
398:
          merge_into_trace_container(tc,ntc)
399:
        else  
400:
      end    
401:
    end
402:
  end
403:
end #}}}