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 #}}} |
