LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
buildLayersRecursive.m
1function buildLayersRecursive(self, idx, callers, ishostlayer)
2lqn = self.lqn;
3jobPosKey = zeros(lqn.nidx,1);
4curClassKey = cell(lqn.nidx,1);
5% Fan-out check: when all callers have fan-out >= nreplicas for this task,
6% each replica sees the full caller traffic (fork-join semantics).
7% Model a single representative replica; updateThinkTimes multiplies by K.
8% For host layers: if all caller tasks have the same replication as the host,
9% the host is co-replicated with the task, so also use single-replica modeling.
10rawReplicas = lqn.repl(idx);
11reduceFanout = false;
12if rawReplicas > 1 && ~isempty(callers)
13 if ~ishostlayer && isfield(lqn, 'fanout') && ~isempty(lqn.fanout)
14 reduceFanout = true;
15 for c = callers(:)'
16 if lqn.fanout(c, idx) < rawReplicas
17 reduceFanout = false;
18 break;
19 end
20 end
21 elseif ishostlayer
22 reduceFanout = true;
23 for c = callers(:)'
24 if lqn.repl(c) ~= rawReplicas
25 reduceFanout = false;
26 break;
27 end
28 end
29 end
30end
31if reduceFanout
32 nreplicas = 1;
33 if ~ishostlayer
34 self.singleReplicaTasks(end+1) = idx;
35 end
36else
37 nreplicas = rawReplicas;
38end
39%mult = lqn.mult;
40mult = lqn.maxmult; % this removes spare capacity that cannot be used
41lqn.mult = mult;
42callservtproc = self.callservtproc;
43model = Network(lqn.hashnames{idx});
44model.setChecks(false); % fast mode
45model.attribute = struct('hosts',[],'tasks',[],'entries',[],'activities',[],'calls',[],'serverIdx',0);
46if ishostlayer | any(any(lqn.issynccaller(callers, lqn.entriesof{idx}))) | any(any(lqn.isasynccaller(callers, lqn.entriesof{idx}))) %#ok<OR2>
47 clientDelay = Delay(model, 'Clients');
48 model.attribute.clientIdx = 1;
49 model.attribute.serverIdx = 2;
50 model.attribute.sourceIdx = NaN;
51else
52 model.attribute.serverIdx = 1;
53 model.attribute.clientIdx = NaN;
54 model.attribute.sourceIdx = NaN;
55end
56serverStation = cell(1,nreplicas);
57isfunctionlayer = all(lqn.isfunction(callers)) && ishostlayer;
58for m=1:nreplicas
59 if m == 1
60 serverStation{m} = Queue(model,lqn.hashnames{idx}, lqn.sched(idx));
61 else
62 serverStation{m} = Queue(model,[lqn.hashnames{idx},'.',num2str(m)], lqn.sched(idx));
63 end
64 serverStation{m}.setNumberOfServers(mult(idx));
65 serverStation{m}.attribute.ishost = ishostlayer;
66 serverStation{m}.attribute.idx = idx;
67 % LQN successive activities on the same host retain the server: model
68 % the resulting class-switching self-loops as immediate feedback so the
69 % simulator does not re-queue the job behind other waiting jobs.
70 serverStation{m}.setImmediateFeedback(true);
71end
72
73iscachelayer = all(lqn.iscache(callers)) && ishostlayer;
74hasRetrievalCache = false;
75retrievalStation = [];
76if iscachelayer
77 cacheNode = Cache(model, lqn.hashnames{callers}, lqn.nitems(callers), lqn.itemcap{callers}, lqn.replacestrat(callers));
78 % Delayed-hit retrieval: a dedicated fetch station in the cache sublayer so the
79 % closed AMVA (da_cacheqn_retrieval) captures the finite-population coalescing.
80 hasRetrievalCache = isfield(lqn,'hasretrieval') && any(lqn.hasretrieval(callers));
81 if hasRetrievalCache
82 retrievalStation = Queue(model, [lqn.hashnames{callers},'.Fetch'], SchedStrategy.PS);
83 end
84end
85retrievalWiring = [];
86
87actsInCaller = [lqn.actsof{callers}];
88isPostAndAct = full(lqn.actposttype)==ActivityPrecedenceType.POST_AND;
89isPreAndAct = full(lqn.actpretype)==ActivityPrecedenceType.PRE_AND;
90hasfork = any(intersect(find(isPostAndAct),actsInCaller));
91
92maxfanout = 1; % maximum output parallelism level of fork nodes
93for aidx = actsInCaller(:)'
94 successors = find(lqn.graph(aidx,:));
95 if any(isPostAndAct(successors))
96 maxfanout = max(maxfanout, sum(isPostAndAct(successors)));
97 end
98end
99
100if hasfork
101 forkNode = Fork(model, 'Fork_PostAnd');
102 for f=1:maxfanout
103 forkOutputRouter{f} = Router(model, ['Fork_PostAnd_',num2str(f)]);
104 end
105 forkClassStack = []; % stack with the entry class at the visited forks, the last visited is end of the list.
106end
107
108isPreAndAct = full(lqn.actpretype)==ActivityPrecedenceType.PRE_AND;
109hasjoin = any(isPreAndAct(actsInCaller));
110if hasjoin
111 joinNode = Join(model, 'Join_PreAnd', forkNode);
112end
113
114aidxClass = cell(1, lqn.nidx);
115aidxThinkClass = cell(1, lqn.nidx); % auxiliary classes for activity think-time
116cidxClass = cell(1,0);
117cidxAuxClass = cell(1,0);
118
119self.servt_classes_updmap{idx} = zeros(0,4); % [modelidx, actidx, node, class] % server classes to update
120self.thinkt_classes_updmap{idx} = zeros(0,4); % [modelidx, actidx, node, class] % client classes to update
121self.actthinkt_classes_updmap{idx} = zeros(0,4); % [modelidx, actidx, node, class] % activity think-time classes to update
122self.arvproc_classes_updmap{idx} = zeros(0,4); % [modelidx, actidx, node, class] % classes to update in the next iteration for asynch calls
123self.call_classes_updmap{idx} = zeros(0,4); % [modelidx, callidx, node, class] % calls classes to update in the next iteration (includes calls in client classes)
124self.route_prob_updmap{idx} = zeros(0,7); % [modelidx, actidxfrom, actidxto, nodefrom, nodeto, classfrom, classto] % routing probabilities to update in the next iteration
125
126if ishostlayer
127 model.attribute.hosts(end+1,:) = [NaN, model.attribute.serverIdx ];
128else
129 model.attribute.tasks(end+1,:) = [NaN, model.attribute.serverIdx ];
130end
131
132hasSource = false; % flag whether a source is needed
133openClasses = [];
134entryOpenClasses = []; % track entry-level open arrivals
135% first pass: create the classes
136for tidx_caller = callers
137 % For host layers, check if the task has any entries with sync/async callers
138 % or has open arrivals, OR if any entry is a forwarding target.
139 hasDirectCallers = false;
140 isForwardingTarget = false;
141 if ishostlayer
142 % Check if the task is a reference task (always create closed class)
143 if lqn.isref(tidx_caller)
144 hasDirectCallers = true;
145 else
146 % Check if any entry of this task has sync or async callers
147 for eidx = lqn.entriesof{tidx_caller}
148 if any(full(lqn.issynccaller(:, eidx))) || any(full(lqn.isasynccaller(:, eidx)))
149 hasDirectCallers = true;
150 break;
151 end
152 % Also check for open arrivals on this entry
153 if isfield(lqn, 'arrival') && ~isempty(lqn.arrival) && ...
154 iscell(lqn.arrival) && eidx <= length(lqn.arrival) && ...
155 ~isempty(lqn.arrival{eidx})
156 hasDirectCallers = true;
157 break;
158 end
159 % Check if this entry is a forwarding target
160 for cidx = 1:lqn.ncalls
161 if full(lqn.calltype(cidx)) == CallType.FWD && full(lqn.callpair(cidx, 2)) == eidx
162 isForwardingTarget = true;
163 break;
164 end
165 end
166 end
167 end
168 end
169 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
170 if self.njobs(tidx_caller,idx) == 0
171 % for each entry of the calling task
172 % determine job population
173 % this block matches the corresponding calculations in
174 % updateThinkTimes
175 % Use single-replica njobs if this layer or the caller is in single-replica mode
176 callerIsSingleReplica = reduceFanout || any(self.singleReplicaTasks == tidx_caller);
177 if callerIsSingleReplica
178 njobs = mult(tidx_caller);
179 else
180 njobs = mult(tidx_caller)*lqn.repl(tidx_caller);
181 end
182 if isinf(njobs)
183 callers_of_tidx_caller = find(lqn.taskgraph(:,tidx_caller));
184 njobs = sum(mult(callers_of_tidx_caller)); %#ok<FNDSB>
185 if isinf(njobs)
186 % if also the callers of tidx_caller are inf servers, then use
187 % an heuristic
188 njobs = min(sum(mult(isfinite(mult)) .* lqn.repl(isfinite(mult))),1000); % Python parity: cap at 1000
189 end
190 end
191 self.njobs(tidx_caller,idx) = njobs;
192 else
193 njobs = self.njobs(tidx_caller,idx);
194 end
195 caller_name = lqn.hashnames{tidx_caller};
196 aidxClass{tidx_caller} = ClosedClass(model, caller_name, njobs, clientDelay);
197 clientDelay.setService(aidxClass{tidx_caller}, Disabled.getInstance());
198 for m=1:nreplicas
199 serverStation{m}.setService(aidxClass{tidx_caller}, Disabled.getInstance());
200 end
201 aidxClass{tidx_caller}.completes = false;
202 aidxClass{tidx_caller}.setReferenceClass(true); % renormalize residence times using the visits to the task
203 aidxClass{tidx_caller}.attribute = [LayeredNetworkElement.TASK, tidx_caller];
204 model.attribute.tasks(end+1,:) = [aidxClass{tidx_caller}.index, tidx_caller];
205 clientDelay.setService(aidxClass{tidx_caller}, self.thinkproc{tidx_caller});
206 if ~lqn.isref(tidx_caller)
207 self.thinkt_classes_updmap{idx}(end+1,:) = [idx, tidx_caller, 1, aidxClass{tidx_caller}.index];
208 end
209 for eidx = lqn.entriesof{tidx_caller}
210 % create a class
211 aidxClass{eidx} = ClosedClass(model, lqn.hashnames{eidx}, 0, clientDelay);
212 clientDelay.setService(aidxClass{eidx}, Disabled.getInstance());
213 for m=1:nreplicas
214 serverStation{m}.setService(aidxClass{eidx}, Disabled.getInstance());
215 end
216 aidxClass{eidx}.completes = false;
217 aidxClass{eidx}.attribute = [LayeredNetworkElement.ENTRY, eidx];
218 model.attribute.entries(end+1,:) = [aidxClass{eidx}.index, eidx];
219 [singleton, javasingleton] = Immediate.getInstance();
220 if isempty(model.obj)
221 clientDelay.setService(aidxClass{eidx}, singleton);
222 else
223 clientDelay.setService(aidxClass{eidx}, javasingleton);
224 end
225
226 % Check for open arrival distribution on this entry
227 if isfield(lqn, 'arrival') && ~isempty(lqn.arrival) && ...
228 iscell(lqn.arrival) && eidx <= length(lqn.arrival) && ...
229 ~isempty(lqn.arrival{eidx})
230
231 if ~hasSource
232 hasSource = true;
233 model.attribute.sourceIdx = length(model.nodes)+1;
234 sourceStation = Source(model,'Source');
235 sinkStation = Sink(model,'Sink');
236 end
237
238 % Create open class for this entry
239 openClassForEntry = OpenClass(model, [lqn.hashnames{eidx}, '_Open'], 0);
240 sourceStation.setArrival(openClassForEntry, lqn.arrival{eidx});
241 clientDelay.setService(openClassForEntry, Disabled.getInstance());
242
243 % Use bound activity's service time (entries themselves have Immediate service)
244 % Find activities bound to this entry via graph
245 bound_act_indices = find(lqn.graph(eidx,:) > 0);
246 if ~isempty(bound_act_indices)
247 % Use first bound activity's service time
248 bound_aidx = bound_act_indices(1);
249 for m=1:nreplicas
250 serverStation{m}.setService(openClassForEntry, self.servtproc{bound_aidx});
251 end
252 else
253 % Fallback to entry service (should not happen in well-formed models)
254 for m=1:nreplicas
255 serverStation{m}.setService(openClassForEntry, self.servtproc{eidx});
256 end
257 end
258
259 % Track for routing setup later: [class_index, entry_index]
260 entryOpenClasses(end+1,:) = [openClassForEntry.index, eidx];
261
262 % Track: Use negative entry index to distinguish from call arrivals
263 self.arvproc_classes_updmap{idx}(end+1,:) = [idx, -eidx, ...
264 model.getNodeIndex(sourceStation), openClassForEntry.index];
265
266 openClassForEntry.completes = false;
267 openClassForEntry.attribute = [LayeredNetworkElement.ENTRY, eidx];
268 end
269 end
270 end
271
272 % for each activity of the calling task
273 for aidx = lqn.actsof{tidx_caller}
274 if ishostlayer | any(any(lqn.issynccaller(tidx_caller, lqn.entriesof{idx}))) %#ok<OR2>
275 % create a class
276 aidxClass{aidx} = ClosedClass(model, lqn.hashnames{aidx}, 0, clientDelay);
277 clientDelay.setService(aidxClass{aidx}, Disabled.getInstance());
278 for m=1:nreplicas
279 serverStation{m}.setService(aidxClass{aidx}, Disabled.getInstance());
280 end
281 aidxClass{aidx}.completes = false;
282 aidxClass{aidx}.attribute = [LayeredNetworkElement.ACTIVITY, aidx];
283 model.attribute.activities(end+1,:) = [aidxClass{aidx}.index, aidx];
284 hidx = lqn.parent(lqn.parent(aidx)); % index of host processor
285 if ~(ishostlayer && (hidx == idx))
286 % set the host demand for the activity
287 clientDelay.setService(aidxClass{aidx}, self.servtproc{aidx});
288 end
289 if lqn.sched(tidx_caller)~=SchedStrategy.REF % in 'ref' case the service activity is constant
290 % updmap(end+1,:) = [idx, aidx, 1, idxClass{aidx}.index];
291 end
292 if iscachelayer && full(lqn.graph(eidx,aidx))
293 clientDelay.setService(aidxClass{aidx}, self.servtproc{aidx});
294 end
295
296 % Auxiliary class carrying the activity think time, in the host layer
297 % only. An activity think time is a delay in series with the host
298 % demand, held at the activity's own task but not at the processor,
299 % so the host layer needs it explicitly: the think time is time spent
300 % at the task's server, which updateThinkTimes excludes from thinkt by
301 % construction, and without it the task would recycle to the processor
302 % too fast and overstate its utilization. Every other layer already
303 % carries the delay inside servtproc{aidx} (set above), so adding the
304 % class there too would charge the same delay twice at clientDelay.
305 if ~isempty(lqn.actthink{aidx}) && lqn.actthink{aidx}.getMean() > GlobalConstants.FineTol ...
306 && (ishostlayer && (hidx == idx))
307 aidxThinkClass{aidx} = ClosedClass(model, [lqn.hashnames{aidx},'.Think'], 0, clientDelay);
308 aidxThinkClass{aidx}.completes = false;
309 aidxThinkClass{aidx}.attribute = [LayeredNetworkElement.ACTIVITY, aidx];
310 clientDelay.setService(aidxThinkClass{aidx}, lqn.actthink{aidx});
311 for m=1:nreplicas
312 serverStation{m}.setService(aidxThinkClass{aidx}, Disabled.getInstance());
313 end
314 self.actthinkt_classes_updmap{idx}(end+1,:) = [idx, aidx, 1, aidxThinkClass{aidx}.index];
315 end
316 end
317 % add a class for each outgoing call from this activity
318 for cidx = lqn.callsof{aidx}
319 callmean(cidx) = lqn.callproc{cidx}.getMean;
320 switch lqn.calltype(cidx)
321 case CallType.ASYNC
322 if lqn.parent(lqn.callpair(cidx,2)) == idx % add only if the target is serverStation
323 if ~hasSource % we need to add source and sink to the model
324 hasSource = true;
325 model.attribute.sourceIdx = length(model.nodes)+1;
326 sourceStation = Source(model,'Source');
327 sinkStation = Sink(model,'Sink');
328 end
329 cidxClass{cidx} = OpenClass(model, lqn.callhashnames{cidx}, 0);
330 sourceStation.setArrival(cidxClass{cidx}, Immediate.getInstance());
331 clientDelay.setService(cidxClass{cidx}, Disabled.getInstance());
332 for m=1:nreplicas
333 serverStation{m}.setService(cidxClass{cidx}, Immediate.getInstance());
334 end
335 openClasses(end+1,:) = [cidxClass{cidx}.index, callmean(cidx), cidx];
336 model.attribute.calls(end+1,:) = [cidxClass{cidx}.index, cidx, lqn.callpair(cidx,1), lqn.callpair(cidx,2)];
337 cidxClass{cidx}.completes = false;
338 cidxClass{cidx}.attribute = [LayeredNetworkElement.CALL, cidx];
339 minRespT = 0;
340 for tidx_act = lqn.actsof{idx}
341 minRespT = minRespT + lqn.hostdem{tidx_act}.getMean; % upper bound, uses all activities not just the ones reachable by this entry
342 end
343 for m=1:nreplicas
344 serverStation{m}.setService(cidxClass{cidx}, Exp.fitMean(minRespT));
345 end
346 end
347 case CallType.SYNC
348 cidxClass{cidx} = ClosedClass(model, lqn.callhashnames{cidx}, 0, clientDelay);
349 clientDelay.setService(cidxClass{cidx}, Disabled.getInstance());
350 for m=1:nreplicas
351 serverStation{m}.setService(cidxClass{cidx}, Disabled.getInstance());
352 end
353 model.attribute.calls(end+1,:) = [cidxClass{cidx}.index, cidx, lqn.callpair(cidx,1), lqn.callpair(cidx,2)];
354 cidxClass{cidx}.completes = false;
355 cidxClass{cidx}.attribute = [LayeredNetworkElement.CALL, cidx];
356 minRespT = 0;
357 for tidx_act = lqn.actsof{idx}
358 minRespT = minRespT + lqn.hostdem{tidx_act}.getMean; % upper bound, uses all activities not just the ones reachable by this entry
359 end
360 for m=1:nreplicas
361 serverStation{m}.setService(cidxClass{cidx}, Exp.fitMean(minRespT));
362 end
363 end
364
365 if callmean(cidx) ~= nreplicas
366 switch lqn.calltype(cidx)
367 case CallType.SYNC
368 cidxAuxClass{cidx} = ClosedClass(model, [lqn.callhashnames{cidx},'.Aux'], 0, clientDelay);
369 cidxAuxClass{cidx}.completes = false;
370 cidxAuxClass{cidx}.attribute = [LayeredNetworkElement.CALL, cidx];
371 clientDelay.setService(cidxAuxClass{cidx}, Immediate.getInstance());
372 for m=1:nreplicas
373 serverStation{m}.setService(cidxAuxClass{cidx}, Disabled.getInstance());
374 end
375 end
376 end
377
378 % Forwarding chains are represented by caller-side pseudo
379 % rendezvous calls added by lqn_fwd_rendezvous (LQNS phase.cc
380 % port), which are ordinary SYNC calls handled above; FWD calls
381 % need no classes in the layers.
382 end
383 end
384end
385
386% Ensure Source's sourceClasses and arrivalProcess arrays are properly sized for all classes
387% This is needed because the Source may be created during class iteration
388% when only some classes exist, and new closed classes added afterwards
389% won't have corresponding entries in sourceClasses/arrivalProcess
390if hasSource
391 nClasses = model.getNumberOfClasses();
392 for k = 1:nClasses
393 if k > length(sourceStation.input.sourceClasses) || isempty(sourceStation.input.sourceClasses{k})
394 sourceStation.input.sourceClasses{k} = {[], ServiceStrategy.LI, Disabled.getInstance()};
395 end
396 if k > length(sourceStation.arrivalProcess) || isempty(sourceStation.arrivalProcess{k})
397 sourceStation.arrivalProcess{k} = Disabled.getInstance();
398 end
399 end
400end
401
402P = model.initRoutingMatrix;
403if hasSource
404 for o = 1:size(openClasses,1)
405 oidx = openClasses(o,1);
406 p = 1 / openClasses(o,2); % divide by mean number of calls, they go to a server at random
407 for m=1:nreplicas
408 P{model.classes{oidx}, model.classes{oidx}}(sourceStation,serverStation{m}) = 1/nreplicas;
409 for n=1:nreplicas
410 P{model.classes{oidx}, model.classes{oidx}}(serverStation{m},serverStation{n}) = (1-p)/nreplicas;
411 end
412 P{model.classes{oidx}, model.classes{oidx}}(serverStation{m},sinkStation) = p;
413 end
414 cidx = openClasses(o,3); % 3 = source
415 self.arvproc_classes_updmap{idx}(end+1,:) = [idx, cidx, model.getNodeIndex(sourceStation), oidx];
416 for m=1:nreplicas
417 self.call_classes_updmap{idx}(end+1,:) = [idx, cidx, model.getNodeIndex(serverStation{m}), oidx];
418 end
419 end
420end
421
422%% job positions are encoded as follows: 1=client, 2=any of the nreplicas server stations, 3=cache node, 4=fork node, 5=join node
423atClient = 1;
424atServer = 2;
425atCache = 3;
426
427jobPos = atClient; % start at client
428% second pass: setup the routing out of entries
429for tidx_caller = callers
430 % Use same condition as first pass - only process if closed class was created
431 hasDirectCallers = false;
432 isForwardingTarget = false;
433 if ishostlayer
434 if lqn.isref(tidx_caller)
435 hasDirectCallers = true;
436 else
437 for eidx_check = lqn.entriesof{tidx_caller}
438 if any(full(lqn.issynccaller(:, eidx_check))) || any(full(lqn.isasynccaller(:, eidx_check)))
439 hasDirectCallers = true;
440 break;
441 end
442 if isfield(lqn, 'arrival') && ~isempty(lqn.arrival) && ...
443 iscell(lqn.arrival) && eidx_check <= length(lqn.arrival) && ...
444 ~isempty(lqn.arrival{eidx_check})
445 hasDirectCallers = true;
446 break;
447 end
448 % Check if this entry is a forwarding target
449 for cidx_fwd = 1:lqn.ncalls
450 if full(lqn.calltype(cidx_fwd)) == CallType.FWD && full(lqn.callpair(cidx_fwd, 2)) == eidx_check
451 isForwardingTarget = true;
452 break;
453 end
454 end
455 end
456 end
457 end
458 if (ishostlayer && (hasDirectCallers || isForwardingTarget)) | any(any(lqn.issynccaller(tidx_caller, lqn.entriesof{idx}))) %#ok<OR2>
459 % for each entry of the calling task
460 ncaller_entries = length(lqn.entriesof{tidx_caller});
461 for eidx = lqn.entriesof{tidx_caller}
462 aidxClass_eidx = aidxClass{eidx};
463 aidxClass_tidx_caller = aidxClass{tidx_caller};
464 % initialize the probability to select an entry to be identical
465 P{aidxClass_tidx_caller, aidxClass_eidx}(clientDelay, clientDelay) = 1 / ncaller_entries;
466 if ncaller_entries > 1
467 % at successive iterations make sure to replace this with throughput ratio
468 self.route_prob_updmap{idx}(end+1,:) = [idx, tidx_caller, eidx, 1, 1, aidxClass_tidx_caller.index, aidxClass_eidx.index];
469 end
470 P = recurActGraph(P, tidx_caller, eidx, aidxClass_eidx, jobPos);
471 end
472 end
473end
474
475% Setup routing for entry-level open arrivals (AFTER recurActGraph to avoid being overwritten)
476if hasSource && ~isempty(entryOpenClasses)
477 for e = 1:size(entryOpenClasses,1)
478 eoidx = entryOpenClasses(e,1); % class index
479 openClass = model.classes{eoidx};
480
481 % Explicitly set routing: ONLY Source → Server → Sink
482 % Zero out all routing for this class first
483 for node1 = 1:length(model.nodes)
484 for node2 = 1:length(model.nodes)
485 P{openClass, openClass}(node1, node2) = 0;
486 end
487 end
488
489 % Now set the correct routing
490 for m=1:nreplicas
491 % Route: Source → ServerStation → Sink
492 P{openClass, openClass}(sourceStation,serverStation{m}) = 1/nreplicas;
493 P{openClass, openClass}(serverStation{m},sinkStation) = 1.0;
494 end
495 end
496end
497
498% Re-normalize Source class arrays: routing may have appended synthetic
499% classes (e.g. FwdMerge exit classes) after the earlier sizing pass, and
500% refreshRates indexes sourceClasses/arrivalProcess by class index.
501if hasSource
502 nClasses = model.getNumberOfClasses();
503 for k = 1:nClasses
504 if k > length(sourceStation.input.sourceClasses) || isempty(sourceStation.input.sourceClasses{k})
505 sourceStation.input.sourceClasses{k} = {[], ServiceStrategy.LI, Disabled.getInstance()};
506 end
507 if k > length(sourceStation.arrivalProcess) || isempty(sourceStation.arrivalProcess{k})
508 sourceStation.arrivalProcess{k} = Disabled.getInstance();
509 end
510 end
511end
512
513% Delayed-hit retrieval: attach the fetch station now that all activity classes
514% exist. The read class circulates cache<->fetch (the retrieval-class template);
515% da_cacheqn_retrieval rebuilds the specifics at solve time. Fetch service = the
516% miss activity's full service (host demand + backend call), current because
517% buildLayers is re-run each LN iteration.
518%
519% EXPERIMENTAL - FURTHER WORK NEEDED. This wiring makes LN(MVA)/LN(NC) produce the
520% correct cache hit/miss PROBABILITIES for LCQ models with retrieval and captures
521% the coalescing throughput benefit in DIRECTION only (understated magnitude vs
522% LDES; the delayed-hit fraction is not recovered - see da_cacheqn_retrieval
523% LIMITATIONS). It is also workaround-heavy: setRetrievalSystem mints per-item
524% retrieval classes that violate the LN sublayer-class == LQN-activity invariant,
525% so the block below must (defer setRetrievalSystem, grow P, pad hitClass/services,
526% tag the retrieval classes auxiliary) to keep the LN bookkeeping consistent. A
527% cleaner refactor and a JAR/Python port remain open.
528if hasRetrievalCache && ~isempty(retrievalWiring)
529 retrievalStation.setService(retrievalWiring.readClass, self.servtproc{retrievalWiring.missaidx});
530 P{retrievalWiring.readClass, retrievalWiring.readClass}(cacheNode, retrievalStation) = 1.0;
531 P{retrievalWiring.readClass, retrievalWiring.readClass}(retrievalStation, cacheNode) = 1.0;
532 cacheNode.setRetrievalSystem(retrievalWiring.readClass, retrievalWiring.missClass, retrievalStation);
533 % Tag the auto-generated retrieval classes as auxiliary: give them a non-LQN
534 % attribute and completes=false so the LN result aggregation (getEnsembleAvg)
535 % skips them (they map to no LQN activity), mirroring how the flat node table
536 % hides *_retrievalClass_* classes.
537 rcvals = unique(cacheNode.server.retrievalClasses(:));
538 rcvals = rcvals(rcvals > 0);
539 for rci = rcvals(:)'
540 model.classes{rci}.attribute = [-1, -1];
541 model.classes{rci}.completes = false;
542 end
543 % setRetrievalSystem extends missClass for the new retrieval classes but leaves
544 % hitClass shorter; pad both to equal length so the hitClass==missClass check in
545 % link (and downstream class-vector ops) does not hit a size mismatch.
546 hc = cacheNode.server.hitClass; mc = cacheNode.server.missClass;
547 Lhm = max(numel(hc), numel(mc));
548 if numel(hc) < Lhm, hc(numel(hc)+1:Lhm) = 0; cacheNode.server.hitClass = hc; end
549 if numel(mc) < Lhm, mc(numel(mc)+1:Lhm) = 0; cacheNode.server.missClass = mc; end
550 % Pad every station's service to the new class count: the auto-generated
551 % retrieval classes are served only at the fetch station, so disable them
552 % elsewhere (otherwise serviceProcess{r} indexing overruns downstream).
553 KfullSvc = model.getNumberOfClasses;
554 for istp = 1:numel(model.stations)
555 stp = model.stations{istp};
556 if isprop(stp,'server') && ~strcmpi(class(stp.server),'ServiceTunnel')
557 for rp = 1:KfullSvc
558 if numel(stp.server.serviceProcess) < rp || isempty(stp.server.serviceProcess{rp})
559 stp.setService(model.classes{rp}, Disabled.getInstance());
560 end
561 end
562 end
563 end
564 % setRetrievalSystem adds per-item retrieval classes; grow P to the new class
565 % count (empty routing for the retrieval classes - their circulation is applied
566 % by getStruct from the read class's cache<->fetch template) so link does not
567 % index past P. P is a RoutingMatrix object; convert to a cell first (link
568 % accepts a cell).
569 Kfull = model.getNumberOfClasses;
570 Icur = model.getNumberOfNodes;
571 if isa(P, 'RoutingMatrix')
572 Pc = P.getCell();
573 else
574 Pc = P;
575 end
576 if size(Pc, 1) < Kfull
577 Pnew = cell(Kfull, Kfull);
578 [ro, co] = size(Pc);
579 Pnew(1:ro, 1:co) = Pc;
580 for rr = 1:Kfull
581 for ss = 1:Kfull
582 if isempty(Pnew{rr, ss})
583 Pnew{rr, ss} = zeros(Icur, Icur);
584 end
585 end
586 end
587 Pc = Pnew;
588 end
589 P = Pc;
590end
591
592model.link(P);
593self.ensemble{idx} = model;
594
595 function [P, curClass, jobPos] = recurActGraph(P, tidx_caller, aidx, curClass, jobPos)
596 jobPosKey(aidx) = jobPos;
597 curClassKey{aidx} = curClass;
598 nextaidxs = find(lqn.graph(aidx,:)); % these include the called entries
599 if ~isempty(nextaidxs)
600 isNextPrecFork(aidx) = any(isPostAndAct(nextaidxs)); % indexed on aidx to avoid losing it during the recursion
601 % Save curClass/jobPos before fork branch loop so each branch
602 % starts with the same pre-fork state (prevents curClass
603 % corruption across parallel branches)
604 if isNextPrecFork(aidx)
605 forkSaveCurClass = curClass;
606 forkSaveJobPos = jobPos;
607 end
608 end
609
610 for nextaidx = nextaidxs % for all successor activities
611 if ~isempty(nextaidx)
612 % Restore pre-fork state for each branch iteration
613 if isNextPrecFork(aidx)
614 curClass = forkSaveCurClass;
615 jobPos = forkSaveJobPos;
616 end
617 isLoop = false;
618 % in the activity graph, the following if is entered only
619 % by an edge that is the return from a LOOP activity
620 if (lqn.graph(aidx,nextaidx) ~= lqn.dag(aidx,nextaidx))
621 isLoop = true;
622 end
623 if ~(lqn.parent(aidx) == lqn.parent(nextaidx)) % if different parent task
624 % if the successor activity is an entry of another task, this is a call
625 cidx = matchrow(lqn.callpair,[aidx,nextaidx]); % find the call index
626 switch lqn.calltype(cidx)
627 case CallType.ASYNC
628 % Async calls don't modify caller routing - caller continues immediately without blocking.
629 % Arrival rate at destination is handled via arvproc_classes_updmap (lines 170-195, 230-248).
630 case CallType.SYNC
631 [P, jobPos, curClass] = routeSynchCall(P, jobPos, curClass);
632 case CallType.FWD
633 % In the host layer, forwarding targets have their own
634 % jobs competing for the processor independently.
635 % The source task's job returns to think time after
636 % completing its own activities; no routing through
637 % the forwarding target's activities is needed.
638 % curClass and jobPos remain unchanged.
639 end
640 else
641 % at this point, we have processed all calls, let us do the
642 % activities local to the task next
643 if isempty(intersect(lqn.eshift+(1:lqn.nentries), nextaidxs))
644 % if next activity is not an entry
645 jobPos = jobPosKey(aidx);
646 curClass = curClassKey{aidx};
647 else
648 if ismember(nextaidxs(find(nextaidxs==nextaidx)-1), lqn.eshift+(1:lqn.nentries))
649 curClassC = curClass;
650 end
651 jobPos = atClient;
652 curClass = curClassC;
653 end
654 if jobPos == atClient % at client node
655 if ishostlayer
656 if ~iscachelayer
657 for m=1:nreplicas
658 if isNextPrecFork(aidx)
659 % if next activity is a post-and
660 P{curClass, curClass}(clientDelay, forkNode) = 1.0;
661 f = find(nextaidx == nextaidxs(isPostAndAct(nextaidxs)));
662 forkClassStack(end+1) = curClass.index;
663 P{curClass, curClass}(forkNode, forkOutputRouter{f}) = 1.0;
664 P{curClass, aidxClass{nextaidx}}(forkOutputRouter{f}, serverStation{m}) = 1.0;
665 else
666 if isPreAndAct(aidx)
667 % before entering the job we go back to the entry class at the last fork
668 forkClass = model.classes{forkClassStack(end)};
669 forkClassStack(end) = [];
670 P{curClass, forkClass}(clientDelay,joinNode) = 1.0;
671 P{forkClass, aidxClass{nextaidx}}(joinNode,serverStation{m}) = 1.0;
672 else
673 P{curClass, aidxClass{nextaidx}}(clientDelay,serverStation{m}) = full(lqn.graph(aidx,nextaidx));
674 end
675 end
676 serverStation{m}.setService(aidxClass{nextaidx}, lqn.hostdem{nextaidx});
677 if isfunctionlayer
678 % A FunctionTask defaults to an Immediate setup, which is
679 % indistinguishable from no setup at all. Lowering it onto
680 % the station would still mark the station as setup-bearing
681 % (sn.isfunction, and the layer-solver choice in SolverLN),
682 % sending the layer to the MAM setup QBD, where an Immediate
683 % setup enters as a 1e8 rate whose chain collapses onto the
684 % boundary level and qbd_setupdelayoff then overruns its
685 % level index. Such a task is an ordinary queue.
686 setupDist = lqn.setuptime{lqn.parent(nextaidx)};
687 if ~isempty(setupDist) && ~isa(setupDist, 'Immediate') ...
688 && setupDist.getMean() > GlobalConstants.FineTol
689 serverStation{m}.setDelayOff(aidxClass{nextaidx}, setupDist, lqn.delayofftime{lqn.parent(nextaidx)});
690 end
691 end
692 end
693 jobPos = atServer;
694 curClass = aidxClass{nextaidx};
695 self.servt_classes_updmap{idx}(end+1,:) = [idx, nextaidx, 2, aidxClass{nextaidx}.index];
696 % The think time follows the host demand, in
697 % series: leave the processor and hold the task
698 % for it at the client delay. Keyed on the
699 % activity itself, so an activity that issues no
700 % call still passes through it; the previous
701 % routing sat in routeSynchCall and so was
702 % unreachable without an outgoing call.
703 if ~isempty(aidxThinkClass{nextaidx})
704 for m=1:nreplicas
705 P{curClass, aidxThinkClass{nextaidx}}(serverStation{m}, clientDelay) = 1.0;
706 end
707 curClass = aidxThinkClass{nextaidx};
708 jobPos = atClient;
709 end
710 else
711 P{curClass, aidxClass{nextaidx}}(clientDelay,cacheNode) = full(lqn.graph(aidx,nextaidx));
712
713 cacheNode.setReadItemEntry(aidxClass{nextaidx},lqn.itemproc{aidx},lqn.nitems(aidx));
714 lqn.hitmissaidx = find(lqn.graph(nextaidx,:));
715 lqn.hitaidx = lqn.hitmissaidx(1);
716 lqn.missaidx = lqn.hitmissaidx(2);
717
718 cacheNode.setHitClass(aidxClass{nextaidx},aidxClass{lqn.hitaidx});
719 cacheNode.setMissClass(aidxClass{nextaidx},aidxClass{lqn.missaidx});
720
721 if hasRetrievalCache
722 % Record the fetch-station wiring; setRetrievalSystem
723 % adds per-item retrieval classes, so it is deferred to
724 % after the activity-graph traversal (before link) to
725 % avoid perturbing the class indexing mid-construction.
726 retrievalWiring = struct('readClass', aidxClass{nextaidx}, ...
727 'missClass', aidxClass{lqn.missaidx}, 'missaidx', lqn.missaidx);
728 end
729
730 jobPos = atCache; % cache
731 curClass = aidxClass{nextaidx};
732 %self.route_prob_updmap{idx}(end+1,:) = [idx, nextaidx, lqn.hitaidx, 3, 3, aidxClass{nextaidx}.index, aidxClass{lqn.hitaidx}.index];
733 %self.route_prob_updmap{idx}(end+1,:) = [idx, nextaidx, lqn.missaidx, 3, 3, aidxClass{nextaidx}.index, aidxClass{lqn.missaidx}.index];
734 end
735 else % not ishostlayer
736 if isNextPrecFork(aidx)
737 % if next activity is a post-and
738 P{curClass, curClass}(clientDelay, forkNode) = 1.0;
739 f = find(nextaidx == nextaidxs(isPostAndAct(nextaidxs)));
740 forkClassStack(end+1) = curClass.index;
741 P{curClass, curClass}(forkNode, forkOutputRouter{f}) = 1.0;
742 P{curClass, aidxClass{nextaidx}}(forkOutputRouter{f}, clientDelay) = 1.0;
743 else
744 if isPreAndAct(aidx)
745 % before entering the job we go back to the entry class at the last fork
746 forkClass = model.classes{forkClassStack(end)};
747 forkClassStack(end) = [];
748 P{curClass, forkClass}(clientDelay,joinNode) = 1.0;
749 P{forkClass, aidxClass{nextaidx}}(joinNode,clientDelay) = 1.0;
750 else
751 P{curClass, aidxClass{nextaidx}}(clientDelay,clientDelay) = full(lqn.graph(aidx,nextaidx));
752 end
753 end
754 jobPos = atClient;
755 curClass = aidxClass{nextaidx};
756 clientDelay.setService(aidxClass{nextaidx}, self.servtproc{nextaidx});
757 self.thinkt_classes_updmap{idx}(end+1,:) = [idx, nextaidx, 1, aidxClass{nextaidx}.index];
758 end
759 elseif jobPos == atServer || jobPos == atCache % at server station
760 if ishostlayer
761 if iscachelayer
762 curClass = aidxClass{nextaidx};
763 for m=1:nreplicas
764 if isNextPrecFork(aidx)
765 % if next activity is a post-and
766 P{curClass, curClass}(cacheNode, forkNode) = 1.0;
767 f = find(nextaidx == nextaidxs(isPostAndAct(nextaidxs)));
768 forkClassStack(end+1) = curClass.index;
769 P{curClass, curClass}(forkNode, forkOutputRouter{f}) = 1.0;
770 P{curClass, aidxClass{nextaidx}}(forkOutputRouter{f}, serverStation{m}) = 1.0;
771 else
772 if isPreAndAct(aidx)
773 % before entering the job we go back to the entry class at the last fork
774 forkClass = model.classes{forkClassStack(end)};
775 forkClassStack(end) = [];
776
777 P{curClass, forkClass}(cacheNode,joinNode) = 1.0;
778 P{forkClass, aidxClass{nextaidx}}(joinNode,serverStation{m}) = 1.0;
779 else
780 P{curClass, aidxClass{nextaidx}}(cacheNode,serverStation{m}) = full(lqn.graph(aidx,nextaidx));
781 end
782 end
783 serverStation{m}.setService(aidxClass{nextaidx}, lqn.hostdem{nextaidx});
784 %self.route_prob_updmap{idx}(end+1,:) = [idx, nextaidx, nextaidx, 3, 2, aidxClass{nextaidx}.index, aidxClass{nextaidx}.index];
785 end
786 else
787 for m=1:nreplicas
788 if isNextPrecFork(aidx)
789 % if next activity is a post-and
790 P{curClass, curClass}(serverStation{m}, forkNode) = 1.0;
791 f = find(nextaidx == nextaidxs(isPostAndAct(nextaidxs)));
792 forkClassStack(end+1) = curClass.index;
793 P{curClass, curClass}(forkNode, forkOutputRouter{f}) = 1.0;
794 P{curClass, aidxClass{nextaidx}}(forkOutputRouter{f}, serverStation{m}) = 1.0;
795 else
796 if isPreAndAct(aidx)
797 % before entering the job we go back to the entry class at the last fork
798 forkClass = model.classes{forkClassStack(end)};
799 forkClassStack(end) = [];
800 P{curClass, forkClass}(serverStation{m},joinNode) = 1.0;
801 P{forkClass, aidxClass{nextaidx}}(joinNode,serverStation{m}) = 1.0;
802 else
803 P{curClass, aidxClass{nextaidx}}(serverStation{m},serverStation{m}) = full(lqn.graph(aidx,nextaidx));
804 end
805 end
806 serverStation{m}.setService(aidxClass{nextaidx}, lqn.hostdem{nextaidx});
807 end
808 end
809 jobPos = atServer;
810 curClass = aidxClass{nextaidx};
811 self.servt_classes_updmap{idx}(end+1,:) = [idx, nextaidx, 2, aidxClass{nextaidx}.index];
812 else
813 for m=1:nreplicas
814 if isNextPrecFork(aidx)
815 % if next activity is a post-and
816 P{curClass, curClass}(serverStation{m}, forkNode) = 1.0;
817 f = find(nextaidx == nextaidxs(isPostAndAct(nextaidxs)));
818 forkClassStack(end+1) = curClass.index;
819 P{curClass, curClass}(forkNode, forkOutputRouter{f}) = 1.0;
820 P{curClass, aidxClass{nextaidx}}(forkOutputRouter{f}, clientDelay) = 1.0;
821 else
822 if isPreAndAct(aidx)
823 % before entering the job we go back to the entry class at the last fork
824 forkClass = model.classes{forkClassStack(end)};
825 forkClassStack(end) = [];
826 P{curClass, forkClass}(serverStation{m},joinNode) = 1.0;
827 P{forkClass, aidxClass{nextaidx}}(joinNode,clientDelay) = 1.0;
828 else
829 P{curClass, aidxClass{nextaidx}}(serverStation{m},clientDelay) = full(lqn.graph(aidx,nextaidx));
830 end
831 end
832 end
833 jobPos = atClient;
834 curClass = aidxClass{nextaidx};
835 clientDelay.setService(aidxClass{nextaidx}, self.servtproc{nextaidx});
836 self.thinkt_classes_updmap{idx}(end+1,:) = [idx, nextaidx, 1, aidxClass{nextaidx}.index];
837 end
838 end
839 if aidx ~= nextaidx && ~isLoop
840 %% now recursively build the rest of the routing matrix graph
841 [P, curClass, jobPos] = recurActGraph(P, tidx_caller, nextaidx, curClass, jobPos);
842
843 % At this point curClassRec is the last class in the
844 % recursive branch, which we now close with a reply
845 if jobPos == atClient
846 P{curClass, aidxClass{tidx_caller}}(clientDelay,clientDelay) = 1;
847 if ~strcmp(curClass.name(end-3:end),'.Aux')
848 curClass.completes = true;
849 end
850 else
851 for m=1:nreplicas
852 P{curClass, aidxClass{tidx_caller}}(serverStation{m},clientDelay) = 1;
853 end
854 if ~strcmp(curClass.name(end-3:end),'.Aux')
855 curClass.completes = true;
856 end
857 end
858 end
859 end
860 end
861 end % nextaidx
862 end
863
864 function [P, jobPos, curClass] = routeSynchCall(P, jobPos, curClass)
865 switch jobPos
866 case atClient
867 if lqn.parent(lqn.callpair(cidx,2)) == idx
868 % if a call to an entry of the server in this layer
869 if callmean(cidx) < nreplicas
870 P{curClass, cidxAuxClass{cidx}}(clientDelay,clientDelay) = 1 - callmean(cidx); % note that callmean(cidx) < nreplicas
871 for m=1:nreplicas
872 % if isNextPrecFork(aidx)
873 % end
874 % % if next activity is a post-and
875 % P{curClass, curClass}(serverStation{m}, forkNode) = 1.0;
876 % forkStackClass(end+1) = curClass.index;
877 % f = find(nextaidx == nextaidxs(isPostAndAct(nextaidxs)));
878 % P{curClass, curClass}(forkNode, forkOutputRouter{f}) = 1.0;
879 % P{curClass, aidxClass{nextaidx}}(forkOutputRouter{f}, clientDelay) = 1.0;
880 % else
881 P{curClass, cidxClass{cidx}}(clientDelay,serverStation{m}) = callmean(cidx) / nreplicas;
882 P{cidxClass{cidx}, cidxClass{cidx}}(serverStation{m},clientDelay) = 1.0; % not needed, just to avoid leaving the Aux class disconnected
883 end
884 P{cidxAuxClass{cidx}, cidxClass{cidx}}(clientDelay,clientDelay) = 1.0; % not needed, just to avoid leaving the Aux class disconnected
885 elseif callmean(cidx) == nreplicas
886 for m=1:nreplicas
887 P{curClass, cidxClass{cidx}}(clientDelay,serverStation{m}) = 1 / nreplicas;
888 P{cidxClass{cidx}, cidxClass{cidx}}(serverStation{m},clientDelay) = 1.0;
889 end
890 else % callmean(cidx) > nreplicas
891 for m=1:nreplicas
892 P{curClass, cidxClass{cidx}}(clientDelay,serverStation{m}) = 1 / nreplicas;
893 P{cidxClass{cidx}, cidxAuxClass{cidx}}(serverStation{m},clientDelay) = 1.0 ;
894 P{cidxAuxClass{cidx}, cidxClass{cidx}}(clientDelay,serverStation{m}) = 1.0 - 1.0 / (callmean(cidx) / nreplicas);
895 end
896 P{cidxAuxClass{cidx}, cidxClass{cidx}}(clientDelay,clientDelay) = 1.0 / (callmean(cidx));
897 end
898 jobPos = atClient;
899 clientDelay.setService(cidxClass{cidx}, Immediate.getInstance());
900 for m=1:nreplicas
901 serverStation{m}.setService(cidxClass{cidx}, callservtproc{cidx});
902 self.call_classes_updmap{idx}(end+1,:) = [idx, cidx, model.getNodeIndex(serverStation{m}), cidxClass{cidx}.index];
903 end
904 curClass = cidxClass{cidx};
905 else
906 % if it is not a call to an entry of the server
907 if callmean(cidx) < nreplicas
908 % The mean number of calls is embedded in the demand
909 % (callservt = callmean * W), so the class must be
910 % visited deterministically; a Bernoulli(callmean)
911 % visit would discount the call time twice.
912 P{curClass, cidxClass{cidx}}(clientDelay,clientDelay) = 1;
913 P{cidxClass{cidx}, cidxAuxClass{cidx}}(clientDelay,clientDelay) = 1;
914 curClass = cidxAuxClass{cidx};
915 elseif callmean(cidx) == nreplicas
916 P{curClass, cidxClass{cidx}}(clientDelay,clientDelay) = 1;
917 curClass = cidxClass{cidx};
918 else % callmean(cidx) > nreplicas
919 P{curClass, cidxClass{cidx}}(clientDelay,clientDelay) = 1; % the mean number of calls is now embedded in the demand
920 P{cidxClass{cidx}, cidxAuxClass{cidx}}(clientDelay,clientDelay) = 1;% / (callmean(cidx)/nreplicas); % the mean number of calls is now embedded in the demand
921 curClass = cidxAuxClass{cidx};
922 end
923 jobPos = atClient;
924 clientDelay.setService(cidxClass{cidx}, callservtproc{cidx});
925 self.call_classes_updmap{idx}(end+1,:) = [idx, cidx, 1, cidxClass{cidx}.index];
926 end
927 case atServer % job at server
928 if lqn.parent(lqn.callpair(cidx,2)) == idx
929 % if it is a call to an entry of the server
930 if callmean(cidx) < nreplicas
931 for m=1:nreplicas
932 P{curClass, cidxClass{cidx}}(serverStation{m},clientDelay) = 1 - callmean(cidx);
933 P{curClass, cidxClass{cidx}}(serverStation{m},serverStation{m}) = callmean(cidx);
934 serverStation{m}.setService(cidxClass{cidx}, callservtproc{cidx});
935 end
936 jobPos = atClient;
937 curClass = cidxAuxClass{cidx};
938 elseif callmean(cidx) == nreplicas
939 for m=1:nreplicas
940 P{curClass, cidxClass{cidx}}(serverStation{m},serverStation{m}) = 1;
941 end
942 jobPos = atServer;
943 curClass = cidxClass{cidx};
944 else % callmean(cidx) > nreplicas
945 for m=1:nreplicas
946 P{curClass, cidxClass{cidx}}(serverStation{m},serverStation{m}) = 1;
947 P{cidxClass{cidx}, cidxClass{cidx}}(serverStation{m},serverStation{m}) = 1 - 1 / (callmean(cidx));
948 P{cidxClass{cidx}, cidxAuxClass{cidx}}(serverStation{m},clientDelay) = 1 / (callmean(cidx));
949 end
950 jobPos = atClient;
951 curClass = cidxAuxClass{cidx};
952 end
953 for m=1:nreplicas
954 serverStation{m}.setService(cidxClass{cidx}, callservtproc{cidx});
955 self.call_classes_updmap{idx}(end+1,:) = [idx, cidx, model.getNodeIndex(serverStation{m}), cidxClass{cidx}.index];
956 end
957 else
958 % if it is not a call to an entry of the server
959 % callmean not needed since we switched
960 % to ResidT to model service time at client
961 if callmean(cidx) < nreplicas
962 for m=1:nreplicas
963 P{curClass, cidxClass{cidx}}(serverStation{m},clientDelay) = 1;
964 end
965 P{cidxClass{cidx}, cidxAuxClass{cidx}}(clientDelay,clientDelay) = 1;
966 curClass = cidxAuxClass{cidx};
967 elseif callmean(cidx) == nreplicas
968 for m=1:nreplicas
969 P{curClass, cidxClass{cidx}}(serverStation{m},clientDelay) = 1;
970 end
971 curClass = cidxClass{cidx};
972 else % callmean(cidx) > nreplicas
973 for m=1:nreplicas
974 P{curClass, cidxClass{cidx}}(serverStation{m},clientDelay) = 1;
975 end
976 P{cidxClass{cidx}, cidxAuxClass{cidx}}(clientDelay,clientDelay) = 1;
977 curClass = cidxAuxClass{cidx};
978 end
979 jobPos = atClient;
980 clientDelay.setService(cidxClass{cidx}, callservtproc{cidx});
981 self.call_classes_updmap{idx}(end+1,:) = [idx, cidx, 1, cidxClass{cidx}.index];
982 end
983 end
984
985 end
986end
Definition Station.m:287
Definition fjtag.m:157
Definition Station.m:245