1function buildLayersRecursive(self, idx, callers, ishostlayer)
3jobPosKey = zeros(lqn.nidx,1);
4curClassKey = cell(lqn.nidx,1);
5% Fan-out check: see _kb/06-solver-catalog.md (LN section)
for rationale
6rawReplicas = lqn.repl(idx);
8if rawReplicas > 1 && ~isempty(callers)
9 if ~ishostlayer && isfield(lqn,
'fanout') && ~isempty(lqn.fanout)
12 if lqn.fanout(c, idx) < rawReplicas
20 if lqn.repl(c) ~= rawReplicas
30 self.singleReplicaTasks(end+1) = idx;
33 nreplicas = rawReplicas;
36mult = lqn.maxmult; % this removes spare capacity that cannot be used
38callservtproc = self.callservtproc;
39model = Network(lqn.hashnames{idx});
40model.setChecks(
false); % fast mode
41model.attribute =
struct(
'hosts',[],
'tasks',[],
'entries',[],
'activities',[],
'calls',[],
'serverIdx',0);
42if ishostlayer | any(any(lqn.issynccaller(callers, lqn.entriesof{idx}))) | any(any(lqn.isasynccaller(callers, lqn.entriesof{idx}))) %#ok<OR2>
43 clientDelay = Delay(model,
'Clients');
44 model.attribute.clientIdx = 1;
45 model.attribute.serverIdx = 2;
46 model.attribute.sourceIdx = NaN;
48 model.attribute.serverIdx = 1;
49 model.attribute.clientIdx = NaN;
50 model.attribute.sourceIdx = NaN;
52serverStation = cell(1,nreplicas);
53isfunctionlayer = all(lqn.isfunction(callers)) && ishostlayer;
56 serverStation{m} = Queue(model,lqn.hashnames{idx}, lqn.sched(idx));
58 serverStation{m} = Queue(model,[lqn.hashnames{idx},
'.',num2str(m)], lqn.sched(idx));
60 serverStation{m}.setNumberOfServers(mult(idx));
61 serverStation{m}.attribute.ishost = ishostlayer;
62 serverStation{m}.attribute.idx = idx;
63 % see _kb/06-solver-catalog.md (LN section)
for rationale
64 serverStation{m}.setImmediateFeedback(
true);
67iscachelayer = all(lqn.iscache(callers)) && ishostlayer;
68hasRetrievalCache =
false;
71 cacheNode = Cache(model, lqn.hashnames{callers}, lqn.nitems(callers), lqn.itemcap{callers}, lqn.replacestrat(callers));
72 % Delayed-hit retrieval: a dedicated fetch station in the cache sublayer so the
73 % closed AMVA (da_cacheqn_retrieval) captures the finite-population coalescing.
74 hasRetrievalCache = isfield(lqn,
'hasretrieval') && any(lqn.hasretrieval(callers));
76 retrievalStation = Queue(model, [lqn.hashnames{callers},
'.Fetch'], SchedStrategy.PS);
81actsInCaller = [lqn.actsof{callers}];
82isPostAndAct = full(lqn.actposttype)==ActivityPrecedenceType.POST_AND;
83isPreAndAct = full(lqn.actpretype)==ActivityPrecedenceType.PRE_AND;
84hasfork = any(intersect(find(isPostAndAct),actsInCaller));
86maxfanout = 1; % maximum output parallelism level of fork
nodes
87for aidx = actsInCaller(:)
'
88 successors = find(lqn.graph(aidx,:));
89 if any(isPostAndAct(successors))
90 maxfanout = max(maxfanout, sum(isPostAndAct(successors)));
95 forkNode = Fork(model, 'Fork_PostAnd
');
97 forkOutputRouter{f} = Router(model, ['Fork_PostAnd_
',num2str(f)]);
99 forkClassStack = []; % stack with the entry class at the visited forks, the last visited is end of the list.
102isPreAndAct = full(lqn.actpretype)==ActivityPrecedenceType.PRE_AND;
103hasjoin = any(isPreAndAct(actsInCaller));
105 joinNode = Join(model, 'Join_PreAnd
', forkNode);
108aidxClass = cell(1, lqn.nidx);
109aidxThinkClass = cell(1, lqn.nidx); % auxiliary classes for activity think-time
110cidxClass = cell(1,0);
111cidxAuxClass = cell(1,0);
113self.servt_classes_updmap{idx} = zeros(0,4); % [modelidx, actidx, node, class] % server classes to update
114self.thinkt_classes_updmap{idx} = zeros(0,4); % [modelidx, actidx, node, class] % client classes to update
115self.actthinkt_classes_updmap{idx} = zeros(0,4); % [modelidx, actidx, node, class] % activity think-time classes to update
116self.arvproc_classes_updmap{idx} = zeros(0,4); % [modelidx, actidx, node, class] % classes to update in the next iteration for asynch calls
117self.call_classes_updmap{idx} = zeros(0,4); % [modelidx, callidx, node, class] % calls classes to update in the next iteration (includes calls in client classes)
118self.route_prob_updmap{idx} = zeros(0,7); % [modelidx, actidxfrom, actidxto, nodefrom, nodeto, classfrom, classto] % routing probabilities to update in the next iteration
121 model.attribute.hosts(end+1,:) = [NaN, model.attribute.serverIdx ];
123 model.attribute.tasks(end+1,:) = [NaN, model.attribute.serverIdx ];
126hasSource = false; % flag whether a source is needed
128entryOpenClasses = []; % track entry-level open arrivals
129% first pass: create the classes
130for tidx_caller = callers
131 % For host layers, check if the task has any entries with sync/async callers
132 % or has open arrivals, OR if any entry is a forwarding target.
133 hasDirectCallers = false;
134 isForwardingTarget = false;
136 % Check if the task is a reference task (always create closed class)
137 if lqn.isref(tidx_caller)
138 hasDirectCallers = true;
140 % Check if any entry of this task has sync or async callers
141 for eidx = lqn.entriesof{tidx_caller}
142 if any(full(lqn.issynccaller(:, eidx))) || any(full(lqn.isasynccaller(:, eidx)))
143 hasDirectCallers = true;
146 % Also check for open arrivals on this entry
147 if isfield(lqn, 'arrival
') && ~isempty(lqn.arrival) && ...
148 iscell(lqn.arrival) && eidx <= length(lqn.arrival) && ...
149 ~isempty(lqn.arrival{eidx})
150 hasDirectCallers = true;
153 % Check if this entry is a forwarding target
154 for cidx = 1:lqn.ncalls
155 if full(lqn.calltype(cidx)) == CallType.FWD && full(lqn.callpair(cidx, 2)) == eidx
156 isForwardingTarget = true;
163 if (ishostlayer && (hasDirectCallers || isForwardingTarget)) | any(any(lqn.issynccaller(tidx_caller, lqn.entriesof{idx}))) %#ok<OR2> % if it is only an asynch caller the closed classes are not needed
164 if self.njobs(tidx_caller,idx) == 0
165 % for each entry of the calling task
166 % determine job population
167 % this block matches the corresponding calculations in
169 % Use single-replica njobs if this layer or the caller is in single-replica mode
170 callerIsSingleReplica = reduceFanout || any(self.singleReplicaTasks == tidx_caller);
171 if callerIsSingleReplica
172 njobs = mult(tidx_caller);
174 njobs = mult(tidx_caller)*lqn.repl(tidx_caller);
177 callers_of_tidx_caller = find(lqn.taskgraph(:,tidx_caller));
178 njobs = sum(mult(callers_of_tidx_caller)); %#ok<FNDSB>
180 % if also the callers of tidx_caller are inf servers, then use
182 njobs = min(sum(mult(isfinite(mult)) .* lqn.repl(isfinite(mult))),1000); % Python parity: cap at 1000
185 self.njobs(tidx_caller,idx) = njobs;
187 njobs = self.njobs(tidx_caller,idx);
189 caller_name = lqn.hashnames{tidx_caller};
190 aidxClass{tidx_caller} = ClosedClass(model, caller_name, njobs, clientDelay);
191 clientDelay.setService(aidxClass{tidx_caller}, Disabled.getInstance());
193 serverStation{m}.setService(aidxClass{tidx_caller}, Disabled.getInstance());
195 aidxClass{tidx_caller}.completes = false;
196 aidxClass{tidx_caller}.setReferenceClass(true); % renormalize residence times using the visits to the task
197 aidxClass{tidx_caller}.attribute = [LayeredNetworkElement.TASK, tidx_caller];
198 model.attribute.tasks(end+1,:) = [aidxClass{tidx_caller}.index, tidx_caller];
199 clientDelay.setService(aidxClass{tidx_caller}, self.thinkproc{tidx_caller});
200 if ~lqn.isref(tidx_caller)
201 self.thinkt_classes_updmap{idx}(end+1,:) = [idx, tidx_caller, 1, aidxClass{tidx_caller}.index];
203 for eidx = lqn.entriesof{tidx_caller}
205 aidxClass{eidx} = ClosedClass(model, lqn.hashnames{eidx}, 0, clientDelay);
206 clientDelay.setService(aidxClass{eidx}, Disabled.getInstance());
208 serverStation{m}.setService(aidxClass{eidx}, Disabled.getInstance());
210 aidxClass{eidx}.completes = false;
211 aidxClass{eidx}.attribute = [LayeredNetworkElement.ENTRY, eidx];
212 model.attribute.entries(end+1,:) = [aidxClass{eidx}.index, eidx];
213 [singleton, javasingleton] = Immediate.getInstance();
214 if isempty(model.obj)
215 clientDelay.setService(aidxClass{eidx}, singleton);
217 clientDelay.setService(aidxClass{eidx}, javasingleton);
220 % Check for open arrival distribution on this entry
221 if isfield(lqn, 'arrival
') && ~isempty(lqn.arrival) && ...
222 iscell(lqn.arrival) && eidx <= length(lqn.arrival) && ...
223 ~isempty(lqn.arrival{eidx})
227 model.attribute.sourceIdx = length(model.nodes)+1;
228 sourceStation = Source(model,'Source
');
229 sinkStation = Sink(model,'Sink
');
232 % Create open class for this entry
233 openClassForEntry = OpenClass(model, [lqn.hashnames{eidx}, '_Open
'], 0);
234 sourceStation.setArrival(openClassForEntry, lqn.arrival{eidx});
235 clientDelay.setService(openClassForEntry, Disabled.getInstance());
237 % Use bound activity's service time (entries themselves have Immediate service)
238 % Find activities bound to this entry via graph
239 bound_act_indices = find(lqn.graph(eidx,:) > 0);
240 if ~isempty(bound_act_indices)
241 % Use first bound activity
's service time
242 bound_aidx = bound_act_indices(1);
244 serverStation{m}.setService(openClassForEntry, self.servtproc{bound_aidx});
247 % Fallback to entry service (should not happen in well-formed models)
249 serverStation{m}.setService(openClassForEntry, self.servtproc{eidx});
253 % Track for routing setup later: [class_index, entry_index]
254 entryOpenClasses(end+1,:) = [openClassForEntry.index, eidx];
256 % Track: Use negative entry index to distinguish from call arrivals
257 self.arvproc_classes_updmap{idx}(end+1,:) = [idx, -eidx, ...
258 model.getNodeIndex(sourceStation), openClassForEntry.index];
260 openClassForEntry.completes = false;
261 openClassForEntry.attribute = [LayeredNetworkElement.ENTRY, eidx];
266 % for each activity of the calling task
267 for aidx = lqn.actsof{tidx_caller}
268 if ishostlayer | any(any(lqn.issynccaller(tidx_caller, lqn.entriesof{idx}))) %#ok<OR2>
270 aidxClass{aidx} = ClosedClass(model, lqn.hashnames{aidx}, 0, clientDelay);
271 clientDelay.setService(aidxClass{aidx}, Disabled.getInstance());
273 serverStation{m}.setService(aidxClass{aidx}, Disabled.getInstance());
275 aidxClass{aidx}.completes = false;
276 aidxClass{aidx}.attribute = [LayeredNetworkElement.ACTIVITY, aidx];
277 model.attribute.activities(end+1,:) = [aidxClass{aidx}.index, aidx];
278 hidx = lqn.parent(lqn.parent(aidx)); % index of host processor
279 if ~(ishostlayer && (hidx == idx))
280 % set the host demand for the activity
281 clientDelay.setService(aidxClass{aidx}, self.servtproc{aidx});
283 if lqn.sched(tidx_caller)~=SchedStrategy.REF % in 'ref
' case the service activity is constant
284 % updmap(end+1,:) = [idx, aidx, 1, idxClass{aidx}.index];
286 if iscachelayer && full(lqn.graph(eidx,aidx))
287 clientDelay.setService(aidxClass{aidx}, self.servtproc{aidx});
290 % Auxiliary class carrying the activity think time, host layer only.
291 % see _kb/06-solver-catalog.md (LN section) for rationale
292 if ~isempty(lqn.actthink{aidx}) && lqn.actthink{aidx}.getMean() > GlobalConstants.FineTol ...
293 && (ishostlayer && (hidx == idx))
294 aidxThinkClass{aidx} = ClosedClass(model, [lqn.hashnames{aidx},'.Think
'], 0, clientDelay);
295 aidxThinkClass{aidx}.completes = false;
296 aidxThinkClass{aidx}.attribute = [LayeredNetworkElement.ACTIVITY, aidx];
297 clientDelay.setService(aidxThinkClass{aidx}, lqn.actthink{aidx});
299 serverStation{m}.setService(aidxThinkClass{aidx}, Disabled.getInstance());
301 self.actthinkt_classes_updmap{idx}(end+1,:) = [idx, aidx, 1, aidxThinkClass{aidx}.index];
304 % add a class for each outgoing call from this activity
305 for cidx = lqn.callsof{aidx}
306 callmean(cidx) = lqn.callproc{cidx}.getMean;
307 switch lqn.calltype(cidx)
309 if lqn.parent(lqn.callpair(cidx,2)) == idx % add only if the target is serverStation
310 if ~hasSource % we need to add source and sink to the model
312 model.attribute.sourceIdx = length(model.nodes)+1;
313 sourceStation = Source(model,'Source
');
314 sinkStation = Sink(model,'Sink
');
316 cidxClass{cidx} = OpenClass(model, lqn.callhashnames{cidx}, 0);
317 sourceStation.setArrival(cidxClass{cidx}, Immediate.getInstance());
318 clientDelay.setService(cidxClass{cidx}, Disabled.getInstance());
320 serverStation{m}.setService(cidxClass{cidx}, Immediate.getInstance());
322 openClasses(end+1,:) = [cidxClass{cidx}.index, callmean(cidx), cidx];
323 model.attribute.calls(end+1,:) = [cidxClass{cidx}.index, cidx, lqn.callpair(cidx,1), lqn.callpair(cidx,2)];
324 cidxClass{cidx}.completes = false;
325 cidxClass{cidx}.attribute = [LayeredNetworkElement.CALL, cidx];
327 for tidx_act = lqn.actsof{idx}
328 minRespT = minRespT + lqn.hostdem{tidx_act}.getMean; % upper bound, uses all activities not just the ones reachable by this entry
331 serverStation{m}.setService(cidxClass{cidx}, Exp.fitMean(minRespT));
335 cidxClass{cidx} = ClosedClass(model, lqn.callhashnames{cidx}, 0, clientDelay);
336 clientDelay.setService(cidxClass{cidx}, Disabled.getInstance());
338 serverStation{m}.setService(cidxClass{cidx}, Disabled.getInstance());
340 model.attribute.calls(end+1,:) = [cidxClass{cidx}.index, cidx, lqn.callpair(cidx,1), lqn.callpair(cidx,2)];
341 cidxClass{cidx}.completes = false;
342 cidxClass{cidx}.attribute = [LayeredNetworkElement.CALL, cidx];
344 for tidx_act = lqn.actsof{idx}
345 minRespT = minRespT + lqn.hostdem{tidx_act}.getMean; % upper bound, uses all activities not just the ones reachable by this entry
348 serverStation{m}.setService(cidxClass{cidx}, Exp.fitMean(minRespT));
352 if callmean(cidx) ~= nreplicas
353 switch lqn.calltype(cidx)
355 cidxAuxClass{cidx} = ClosedClass(model, [lqn.callhashnames{cidx},'.Aux
'], 0, clientDelay);
356 cidxAuxClass{cidx}.completes = false;
357 cidxAuxClass{cidx}.attribute = [LayeredNetworkElement.CALL, cidx];
358 clientDelay.setService(cidxAuxClass{cidx}, Immediate.getInstance());
360 serverStation{m}.setService(cidxAuxClass{cidx}, Disabled.getInstance());
365 % see _kb/06-solver-catalog.md (LN section) for rationale
370% see _kb/06-solver-catalog.md (LN section) for rationale
372 nClasses = model.getNumberOfClasses();
374 if k > length(sourceStation.input.sourceClasses) || isempty(sourceStation.input.sourceClasses{k})
375 sourceStation.input.sourceClasses{k} = {[], ServiceStrategy.LI, Disabled.getInstance()};
377 if k > length(sourceStation.arrivalProcess) || isempty(sourceStation.arrivalProcess{k})
378 sourceStation.arrivalProcess{k} = Disabled.getInstance();
383P = model.initRoutingMatrix;
385 for o = 1:size(openClasses,1)
386 oidx = openClasses(o,1);
387 p = 1 / openClasses(o,2); % divide by mean number of calls, they go to a server at random
389 P{model.classes{oidx}, model.classes{oidx}}(sourceStation,serverStation{m}) = 1/nreplicas;
391 P{model.classes{oidx}, model.classes{oidx}}(serverStation{m},serverStation{n}) = (1-p)/nreplicas;
393 P{model.classes{oidx}, model.classes{oidx}}(serverStation{m},sinkStation) = p;
395 cidx = openClasses(o,3); % 3 = source
396 self.arvproc_classes_updmap{idx}(end+1,:) = [idx, cidx, model.getNodeIndex(sourceStation), oidx];
398 self.call_classes_updmap{idx}(end+1,:) = [idx, cidx, model.getNodeIndex(serverStation{m}), oidx];
403%% job positions are encoded as follows: 1=client, 2=any of the nreplicas server stations, 3=cache node, 4=fork node, 5=join node
408jobPos = atClient; % start at client
409% second pass: setup the routing out of entries
410for tidx_caller = callers
411 % Use same condition as first pass - only process if closed class was created
412 hasDirectCallers = false;
413 isForwardingTarget = false;
415 if lqn.isref(tidx_caller)
416 hasDirectCallers = true;
418 for eidx_check = lqn.entriesof{tidx_caller}
419 if any(full(lqn.issynccaller(:, eidx_check))) || any(full(lqn.isasynccaller(:, eidx_check)))
420 hasDirectCallers = true;
423 if isfield(lqn, 'arrival
') && ~isempty(lqn.arrival) && ...
424 iscell(lqn.arrival) && eidx_check <= length(lqn.arrival) && ...
425 ~isempty(lqn.arrival{eidx_check})
426 hasDirectCallers = true;
429 % Check if this entry is a forwarding target
430 for cidx_fwd = 1:lqn.ncalls
431 if full(lqn.calltype(cidx_fwd)) == CallType.FWD && full(lqn.callpair(cidx_fwd, 2)) == eidx_check
432 isForwardingTarget = true;
439 if (ishostlayer && (hasDirectCallers || isForwardingTarget)) | any(any(lqn.issynccaller(tidx_caller, lqn.entriesof{idx}))) %#ok<OR2>
440 % for each entry of the calling task
441 ncaller_entries = length(lqn.entriesof{tidx_caller});
442 for eidx = lqn.entriesof{tidx_caller}
443 aidxClass_eidx = aidxClass{eidx};
444 aidxClass_tidx_caller = aidxClass{tidx_caller};
445 % initialize the probability to select an entry to be identical
446 P{aidxClass_tidx_caller, aidxClass_eidx}(clientDelay, clientDelay) = 1 / ncaller_entries;
447 if ncaller_entries > 1
448 % at successive iterations make sure to replace this with throughput ratio
449 self.route_prob_updmap{idx}(end+1,:) = [idx, tidx_caller, eidx, 1, 1, aidxClass_tidx_caller.index, aidxClass_eidx.index];
451 P = recurActGraph(P, tidx_caller, eidx, aidxClass_eidx, jobPos);
456% Setup routing for entry-level open arrivals (AFTER recurActGraph to avoid being overwritten)
457if hasSource && ~isempty(entryOpenClasses)
458 for e = 1:size(entryOpenClasses,1)
459 eoidx = entryOpenClasses(e,1); % class index
460 openClass = model.classes{eoidx};
462 % Explicitly set routing: ONLY Source → Server → Sink
463 % Zero out all routing for this class first
464 for node1 = 1:length(model.nodes)
465 for node2 = 1:length(model.nodes)
466 P{openClass, openClass}(node1, node2) = 0;
470 % Now set the correct routing
472 % Route: Source → ServerStation → Sink
473 P{openClass, openClass}(sourceStation,serverStation{m}) = 1/nreplicas;
474 P{openClass, openClass}(serverStation{m},sinkStation) = 1.0;
479% see _kb/06-solver-catalog.md (LN section) for rationale
481 nClasses = model.getNumberOfClasses();
483 if k > length(sourceStation.input.sourceClasses) || isempty(sourceStation.input.sourceClasses{k})
484 sourceStation.input.sourceClasses{k} = {[], ServiceStrategy.LI, Disabled.getInstance()};
486 if k > length(sourceStation.arrivalProcess) || isempty(sourceStation.arrivalProcess{k})
487 sourceStation.arrivalProcess{k} = Disabled.getInstance();
492% Delayed-hit retrieval cache wiring (EXPERIMENTAL).
493% see _kb/06-solver-catalog.md (LN section) for rationale
494if hasRetrievalCache && ~isempty(retrievalWiring)
495 retrievalStation.setService(retrievalWiring.readClass, self.servtproc{retrievalWiring.missaidx});
496 P{retrievalWiring.readClass, retrievalWiring.readClass}(cacheNode, retrievalStation) = 1.0;
497 P{retrievalWiring.readClass, retrievalWiring.readClass}(retrievalStation, cacheNode) = 1.0;
498 cacheNode.setRetrievalSystem(retrievalWiring.readClass, retrievalWiring.missClass, retrievalStation);
499 % see _kb/06-solver-catalog.md (LN section) for rationale
500 rcvals = unique(cacheNode.server.retrievalClasses(:));
501 rcvals = rcvals(rcvals > 0);
503 model.classes{rci}.attribute = [-1, -1];
504 model.classes{rci}.completes =
false;
506 % see _kb/06-solver-catalog.md (LN section)
for rationale
507 hc = cacheNode.server.hitClass; mc = cacheNode.server.missClass;
508 Lhm = max(numel(hc), numel(mc));
509 if numel(hc) < Lhm, hc(numel(hc)+1:Lhm) = 0; cacheNode.server.hitClass = hc; end
510 if numel(mc) < Lhm, mc(numel(mc)+1:Lhm) = 0; cacheNode.server.missClass = mc; end
511 % see _kb/06-solver-catalog.md (LN section)
for rationale
512 KfullSvc = model.getNumberOfClasses;
513 for istp = 1:numel(model.stations)
514 stp = model.stations{istp};
515 if isprop(stp,
'server') && ~strcmpi(
class(stp.server),
'ServiceTunnel')
517 if numel(stp.server.serviceProcess) < rp || isempty(stp.server.serviceProcess{rp})
518 stp.setService(model.classes{rp}, Disabled.getInstance());
523 % see _kb/06-solver-catalog.md (LN section)
for rationale
524 Kfull = model.getNumberOfClasses;
525 Icur = model.getNumberOfNodes;
526 if isa(
P,
'RoutingMatrix')
531 if size(Pc, 1) < Kfull
532 Pnew = cell(Kfull, Kfull);
534 Pnew(1:ro, 1:co) = Pc;
537 if isempty(Pnew{rr, ss})
538 Pnew{rr, ss} = zeros(Icur, Icur);
548self.ensemble{idx} = model;
550 function [
P, curClass, jobPos] = recurActGraph(
P, tidx_caller, aidx, curClass, jobPos)
551 jobPosKey(aidx) = jobPos;
552 curClassKey{aidx} = curClass;
553 nextaidxs = find(lqn.graph(aidx,:)); % these include the called entries
554 if ~isempty(nextaidxs)
555 isNextPrecFork(aidx) = any(isPostAndAct(nextaidxs)); % indexed on aidx to avoid losing it during the recursion
556 % Save curClass/jobPos before fork branch loop so each branch
557 % starts with the same pre-fork state (prevents curClass
558 % corruption across parallel branches)
559 if isNextPrecFork(aidx)
560 forkSaveCurClass = curClass;
561 forkSaveJobPos = jobPos;
565 for nextaidx = nextaidxs %
for all successor activities
566 if ~isempty(nextaidx)
567 % Restore pre-fork state
for each branch iteration
568 if isNextPrecFork(aidx)
569 curClass = forkSaveCurClass;
570 jobPos = forkSaveJobPos;
573 % in the activity graph, the following
if is entered only
574 % by an edge that
is the
return from a LOOP activity
575 if (lqn.graph(aidx,nextaidx) ~= lqn.dag(aidx,nextaidx))
578 if ~(lqn.parent(aidx) == lqn.parent(nextaidx)) %
if different parent task
579 %
if the successor activity
is an entry of another task,
this is a call
580 cidx = matchrow(lqn.callpair,[aidx,nextaidx]); % find the call index
581 switch lqn.calltype(cidx)
583 % Async calls don
't modify caller routing - caller continues immediately without blocking.
584 % Arrival rate at destination is handled via arvproc_classes_updmap (lines 170-195, 230-248).
586 [P, jobPos, curClass] = routeSynchCall(P, jobPos, curClass);
588 % see _kb/06-solver-catalog.md (LN section) for rationale
591 % at this point, we have processed all calls, let us do the
592 % activities local to the task next
593 if isempty(intersect(lqn.eshift+(1:lqn.nentries), nextaidxs))
594 % if next activity is not an entry
595 jobPos = jobPosKey(aidx);
596 curClass = curClassKey{aidx};
598 if ismember(nextaidxs(find(nextaidxs==nextaidx)-1), lqn.eshift+(1:lqn.nentries))
599 curClassC = curClass;
602 curClass = curClassC;
604 if jobPos == atClient % at client node
608 if isNextPrecFork(aidx)
609 % if next activity is a post-and
610 P{curClass, curClass}(clientDelay, forkNode) = 1.0;
611 f = find(nextaidx == nextaidxs(isPostAndAct(nextaidxs)));
612 forkClassStack(end+1) = curClass.index;
613 P{curClass, curClass}(forkNode, forkOutputRouter{f}) = 1.0;
614 P{curClass, aidxClass{nextaidx}}(forkOutputRouter{f}, serverStation{m}) = 1.0;
617 % before entering the job we go back to the entry class at the last fork
618 forkClass = model.classes{forkClassStack(end)};
619 forkClassStack(end) = [];
620 P{curClass, forkClass}(clientDelay,joinNode) = 1.0;
621 P{forkClass, aidxClass{nextaidx}}(joinNode,serverStation{m}) = 1.0;
623 P{curClass, aidxClass{nextaidx}}(clientDelay,serverStation{m}) = full(lqn.graph(aidx,nextaidx));
626 serverStation{m}.setService(aidxClass{nextaidx}, lqn.hostdem{nextaidx});
628 % see _kb/06-solver-catalog.md (LN section) for rationale
629 setupDist = lqn.setuptime{lqn.parent(nextaidx)};
630 if ~isempty(setupDist) && ~isa(setupDist, 'Immediate
') ...
631 && setupDist.getMean() > GlobalConstants.FineTol
632 serverStation{m}.setDelayOff(aidxClass{nextaidx}, setupDist, lqn.delayofftime{lqn.parent(nextaidx)});
637 curClass = aidxClass{nextaidx};
638 self.servt_classes_updmap{idx}(end+1,:) = [idx, nextaidx, 2, aidxClass{nextaidx}.index];
639 % see _kb/06-solver-catalog.md (LN section) for rationale
640 if ~isempty(aidxThinkClass{nextaidx})
642 P{curClass, aidxThinkClass{nextaidx}}(serverStation{m}, clientDelay) = 1.0;
644 curClass = aidxThinkClass{nextaidx};
648 P{curClass, aidxClass{nextaidx}}(clientDelay,cacheNode) = full(lqn.graph(aidx,nextaidx));
650 cacheNode.setReadItemEntry(aidxClass{nextaidx},lqn.itemproc{aidx},lqn.nitems(aidx));
651 lqn.hitmissaidx = find(lqn.graph(nextaidx,:));
652 lqn.hitaidx = lqn.hitmissaidx(1);
653 lqn.missaidx = lqn.hitmissaidx(2);
655 cacheNode.setHitClass(aidxClass{nextaidx},aidxClass{lqn.hitaidx});
656 cacheNode.setMissClass(aidxClass{nextaidx},aidxClass{lqn.missaidx});
659 % see _kb/06-solver-catalog.md (LN section) for rationale
660 retrievalWiring = struct('readClass
', aidxClass{nextaidx}, ...
661 'missClass
', aidxClass{lqn.missaidx}, 'missaidx
', lqn.missaidx);
664 jobPos = atCache; % cache
665 curClass = aidxClass{nextaidx};
666 %self.route_prob_updmap{idx}(end+1,:) = [idx, nextaidx, lqn.hitaidx, 3, 3, aidxClass{nextaidx}.index, aidxClass{lqn.hitaidx}.index];
667 %self.route_prob_updmap{idx}(end+1,:) = [idx, nextaidx, lqn.missaidx, 3, 3, aidxClass{nextaidx}.index, aidxClass{lqn.missaidx}.index];
669 else % not ishostlayer
670 if isNextPrecFork(aidx)
671 % if next activity is a post-and
672 P{curClass, curClass}(clientDelay, forkNode) = 1.0;
673 f = find(nextaidx == nextaidxs(isPostAndAct(nextaidxs)));
674 forkClassStack(end+1) = curClass.index;
675 P{curClass, curClass}(forkNode, forkOutputRouter{f}) = 1.0;
676 P{curClass, aidxClass{nextaidx}}(forkOutputRouter{f}, clientDelay) = 1.0;
679 % before entering the job we go back to the entry class at the last fork
680 forkClass = model.classes{forkClassStack(end)};
681 forkClassStack(end) = [];
682 P{curClass, forkClass}(clientDelay,joinNode) = 1.0;
683 P{forkClass, aidxClass{nextaidx}}(joinNode,clientDelay) = 1.0;
685 P{curClass, aidxClass{nextaidx}}(clientDelay,clientDelay) = full(lqn.graph(aidx,nextaidx));
689 curClass = aidxClass{nextaidx};
690 clientDelay.setService(aidxClass{nextaidx}, self.servtproc{nextaidx});
691 self.thinkt_classes_updmap{idx}(end+1,:) = [idx, nextaidx, 1, aidxClass{nextaidx}.index];
693 elseif jobPos == atServer || jobPos == atCache % at server station
696 curClass = aidxClass{nextaidx};
698 if isNextPrecFork(aidx)
699 % if next activity is a post-and
700 P{curClass, curClass}(cacheNode, forkNode) = 1.0;
701 f = find(nextaidx == nextaidxs(isPostAndAct(nextaidxs)));
702 forkClassStack(end+1) = curClass.index;
703 P{curClass, curClass}(forkNode, forkOutputRouter{f}) = 1.0;
704 P{curClass, aidxClass{nextaidx}}(forkOutputRouter{f}, serverStation{m}) = 1.0;
707 % before entering the job we go back to the entry class at the last fork
708 forkClass = model.classes{forkClassStack(end)};
709 forkClassStack(end) = [];
711 P{curClass, forkClass}(cacheNode,joinNode) = 1.0;
712 P{forkClass, aidxClass{nextaidx}}(joinNode,serverStation{m}) = 1.0;
714 P{curClass, aidxClass{nextaidx}}(cacheNode,serverStation{m}) = full(lqn.graph(aidx,nextaidx));
717 serverStation{m}.setService(aidxClass{nextaidx}, lqn.hostdem{nextaidx});
718 %self.route_prob_updmap{idx}(end+1,:) = [idx, nextaidx, nextaidx, 3, 2, aidxClass{nextaidx}.index, aidxClass{nextaidx}.index];
722 if isNextPrecFork(aidx)
723 % if next activity is a post-and
724 P{curClass, curClass}(serverStation{m}, forkNode) = 1.0;
725 f = find(nextaidx == nextaidxs(isPostAndAct(nextaidxs)));
726 forkClassStack(end+1) = curClass.index;
727 P{curClass, curClass}(forkNode, forkOutputRouter{f}) = 1.0;
728 P{curClass, aidxClass{nextaidx}}(forkOutputRouter{f}, serverStation{m}) = 1.0;
731 % before entering the job we go back to the entry class at the last fork
732 forkClass = model.classes{forkClassStack(end)};
733 forkClassStack(end) = [];
734 P{curClass, forkClass}(serverStation{m},joinNode) = 1.0;
735 P{forkClass, aidxClass{nextaidx}}(joinNode,serverStation{m}) = 1.0;
737 P{curClass, aidxClass{nextaidx}}(serverStation{m},serverStation{m}) = full(lqn.graph(aidx,nextaidx));
740 serverStation{m}.setService(aidxClass{nextaidx}, lqn.hostdem{nextaidx});
744 curClass = aidxClass{nextaidx};
745 self.servt_classes_updmap{idx}(end+1,:) = [idx, nextaidx, 2, aidxClass{nextaidx}.index];
748 if isNextPrecFork(aidx)
749 % if next activity is a post-and
750 P{curClass, curClass}(serverStation{m}, forkNode) = 1.0;
751 f = find(nextaidx == nextaidxs(isPostAndAct(nextaidxs)));
752 forkClassStack(end+1) = curClass.index;
753 P{curClass, curClass}(forkNode, forkOutputRouter{f}) = 1.0;
754 P{curClass, aidxClass{nextaidx}}(forkOutputRouter{f}, clientDelay) = 1.0;
757 % before entering the job we go back to the entry class at the last fork
758 forkClass = model.classes{forkClassStack(end)};
759 forkClassStack(end) = [];
760 P{curClass, forkClass}(serverStation{m},joinNode) = 1.0;
761 P{forkClass, aidxClass{nextaidx}}(joinNode,clientDelay) = 1.0;
763 P{curClass, aidxClass{nextaidx}}(serverStation{m},clientDelay) = full(lqn.graph(aidx,nextaidx));
768 curClass = aidxClass{nextaidx};
769 clientDelay.setService(aidxClass{nextaidx}, self.servtproc{nextaidx});
770 self.thinkt_classes_updmap{idx}(end+1,:) = [idx, nextaidx, 1, aidxClass{nextaidx}.index];
773 if aidx ~= nextaidx && ~isLoop
774 %% now recursively build the rest of the routing matrix graph
775 [P, curClass, jobPos] = recurActGraph(P, tidx_caller, nextaidx, curClass, jobPos);
777 % At this point curClassRec is the last class in the
778 % recursive branch, which we now close with a reply
779 if jobPos == atClient
780 P{curClass, aidxClass{tidx_caller}}(clientDelay,clientDelay) = 1;
781 if ~strcmp(curClass.name(end-3:end),'.Aux
')
782 curClass.completes = true;
786 P{curClass, aidxClass{tidx_caller}}(serverStation{m},clientDelay) = 1;
788 if ~strcmp(curClass.name(end-3:end),'.Aux
')
789 curClass.completes = true;
798 function [P, jobPos, curClass] = routeSynchCall(P, jobPos, curClass)
801 if lqn.parent(lqn.callpair(cidx,2)) == idx
802 % if a call to an entry of the server in this layer
803 if callmean(cidx) < nreplicas
804 P{curClass, cidxAuxClass{cidx}}(clientDelay,clientDelay) = 1 - callmean(cidx); % note that callmean(cidx) < nreplicas
806 % if isNextPrecFork(aidx)
808 % % if next activity is a post-and
809 % P{curClass, curClass}(serverStation{m}, forkNode) = 1.0;
810 % forkStackClass(end+1) = curClass.index;
811 % f = find(nextaidx == nextaidxs(isPostAndAct(nextaidxs)));
812 % P{curClass, curClass}(forkNode, forkOutputRouter{f}) = 1.0;
813 % P{curClass, aidxClass{nextaidx}}(forkOutputRouter{f}, clientDelay) = 1.0;
815 P{curClass, cidxClass{cidx}}(clientDelay,serverStation{m}) = callmean(cidx) / nreplicas;
816 P{cidxClass{cidx}, cidxClass{cidx}}(serverStation{m},clientDelay) = 1.0; % not needed, just to avoid leaving the Aux class disconnected
818 P{cidxAuxClass{cidx}, cidxClass{cidx}}(clientDelay,clientDelay) = 1.0; % not needed, just to avoid leaving the Aux class disconnected
819 elseif callmean(cidx) == nreplicas
821 P{curClass, cidxClass{cidx}}(clientDelay,serverStation{m}) = 1 / nreplicas;
822 P{cidxClass{cidx}, cidxClass{cidx}}(serverStation{m},clientDelay) = 1.0;
824 else % callmean(cidx) > nreplicas
826 P{curClass, cidxClass{cidx}}(clientDelay,serverStation{m}) = 1 / nreplicas;
827 P{cidxClass{cidx}, cidxAuxClass{cidx}}(serverStation{m},clientDelay) = 1.0 ;
828 P{cidxAuxClass{cidx}, cidxClass{cidx}}(clientDelay,serverStation{m}) = 1.0 - 1.0 / (callmean(cidx) / nreplicas);
830 P{cidxAuxClass{cidx}, cidxClass{cidx}}(clientDelay,clientDelay) = 1.0 / (callmean(cidx));
833 clientDelay.setService(cidxClass{cidx}, Immediate.getInstance());
835 serverStation{m}.setService(cidxClass{cidx}, callservtproc{cidx});
836 self.call_classes_updmap{idx}(end+1,:) = [idx, cidx, model.getNodeIndex(serverStation{m}), cidxClass{cidx}.index];
838 curClass = cidxClass{cidx};
840 % if it is not a call to an entry of the server
841 if callmean(cidx) < nreplicas
842 % see _kb/06-solver-catalog.md (LN section) for rationale
843 P{curClass, cidxClass{cidx}}(clientDelay,clientDelay) = 1;
844 P{cidxClass{cidx}, cidxAuxClass{cidx}}(clientDelay,clientDelay) = 1;
845 curClass = cidxAuxClass{cidx};
846 elseif callmean(cidx) == nreplicas
847 P{curClass, cidxClass{cidx}}(clientDelay,clientDelay) = 1;
848 curClass = cidxClass{cidx};
849 else % callmean(cidx) > nreplicas
850 P{curClass, cidxClass{cidx}}(clientDelay,clientDelay) = 1; % the mean number of calls is now embedded in the demand
851 P{cidxClass{cidx}, cidxAuxClass{cidx}}(clientDelay,clientDelay) = 1;% / (callmean(cidx)/nreplicas); % the mean number of calls is now embedded in the demand
852 curClass = cidxAuxClass{cidx};
855 clientDelay.setService(cidxClass{cidx}, callservtproc{cidx});
856 self.call_classes_updmap{idx}(end+1,:) = [idx, cidx, 1, cidxClass{cidx}.index];
858 case atServer % job at server
859 if lqn.parent(lqn.callpair(cidx,2)) == idx
860 % if it is a call to an entry of the server
861 if callmean(cidx) < nreplicas
863 P{curClass, cidxClass{cidx}}(serverStation{m},clientDelay) = 1 - callmean(cidx);
864 P{curClass, cidxClass{cidx}}(serverStation{m},serverStation{m}) = callmean(cidx);
865 serverStation{m}.setService(cidxClass{cidx}, callservtproc{cidx});
868 curClass = cidxAuxClass{cidx};
869 elseif callmean(cidx) == nreplicas
871 P{curClass, cidxClass{cidx}}(serverStation{m},serverStation{m}) = 1;
874 curClass = cidxClass{cidx};
875 else % callmean(cidx) > nreplicas
877 P{curClass, cidxClass{cidx}}(serverStation{m},serverStation{m}) = 1;
878 P{cidxClass{cidx}, cidxClass{cidx}}(serverStation{m},serverStation{m}) = 1 - 1 / (callmean(cidx));
879 P{cidxClass{cidx}, cidxAuxClass{cidx}}(serverStation{m},clientDelay) = 1 / (callmean(cidx));
882 curClass = cidxAuxClass{cidx};
885 serverStation{m}.setService(cidxClass{cidx}, callservtproc{cidx});
886 self.call_classes_updmap{idx}(end+1,:) = [idx, cidx, model.getNodeIndex(serverStation{m}), cidxClass{cidx}.index];
889 % if it is not a call to an entry of the server
890 % callmean not needed since we switched
891 % to ResidT to model service time at client
892 if callmean(cidx) < nreplicas
894 P{curClass, cidxClass{cidx}}(serverStation{m},clientDelay) = 1;
896 P{cidxClass{cidx}, cidxAuxClass{cidx}}(clientDelay,clientDelay) = 1;
897 curClass = cidxAuxClass{cidx};
898 elseif callmean(cidx) == nreplicas
900 P{curClass, cidxClass{cidx}}(serverStation{m},clientDelay) = 1;
902 curClass = cidxClass{cidx};
903 else % callmean(cidx) > nreplicas
905 P{curClass, cidxClass{cidx}}(serverStation{m},clientDelay) = 1;
907 P{cidxClass{cidx}, cidxAuxClass{cidx}}(clientDelay,clientDelay) = 1;
908 curClass = cidxAuxClass{cidx};
911 clientDelay.setService(cidxClass{cidx}, callservtproc{cidx});
912 self.call_classes_updmap{idx}(end+1,:) = [idx, cidx, 1, cidxClass{cidx}.index];