LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
ldes_station.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2012-2026, QORE Lab, Imperial College London
3 * All rights reserved.
4 */
5#ifndef LINE_SOLVERS_LDES_LDES_STATION_H
6#define LINE_SOLVERS_LDES_LDES_STATION_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * The scheduling disciplines of the native LDES engine.
12 *
13 * TWO FAMILIES, and they are structurally different simulators sharing one
14 * station:
15 *
16 * - THE BUFFERED disciplines (FCFS, LCFS, SIRO, the priority variants, SJF,
17 * LJF, SEPT, LEPT) hold waiting jobs in an ORDER and hand the head to a
18 * free server. The discipline IS the order, so it lives entirely in
19 * `WaitCmp` below; everything downstream is the same code.
20 * - THE SHARING disciplines (PS, DPS, GPS, their priority variants and LPS)
21 * have no order at all. Every job in service advances at its own share of
22 * the server, so the station has no head and no service-start event: the
23 * residual work of EVERY job must be integrated forward and EVERY departure
24 * rescheduled whenever the population changes. `PsStation` does that.
25 *
26 * THE SERVICE TIME IS SAMPLED AT ARRIVAL, not at service start. This is the
27 * reference's choice (`customer.serviceTime = generateServiceTime(...)` in the
28 * arrival handlers) and it is forced: SJF, LJF and the size-based orders
29 * compare service times of jobs that have not started, so the sample must
30 * exist before the job is ordered. It is invisible under FCFS, where arrival
31 * and service order coincide, and visible under LCFS with a MAP service
32 * process, whose phase then advances in ARRIVAL order.
33 *
34 * PRIORITY ORDERS ASCENDING: a lower value is more urgent, 0 highest. That is
35 * LINE's convention throughout (`SaveHandlers` inverts it when exporting to
36 * JMT, which orders the other way), and every comparator here follows it.
37 */
38
39#include <algorithm>
40#include <cmath>
41#include <cstddef>
42#include <cstdint>
43#include <limits>
44#include <set>
45#include <vector>
46
48
49namespace line {
50namespace ldes {
51namespace engine {
52
53/**
54 * A job held by a station.
55 *
56 * `service`, `remaining` and `elapsed` are the three quantities the size-based
57 * orders need and are NOT redundant. `service` is the ORIGINAL requirement
58 * PSJF orders by; `remaining` is the residual SRPT and LRPT order by, equal to
59 * `service` until the job is first preempted; `elapsed` is the attained
60 * service FB and SETF order by. The reference keeps them in a side table
61 * (`preemptedJobHistory`, keyed by the arrival instants) because its wait
62 * queue holds a plain `Customer`; carrying them on the job is the same
63 * information without the lookup, and without the key collision two jobs
64 * arriving at the same instant would produce.
65 */
66struct Job {
67 std::size_t cls = 0;
68 int priority = 0;
69 double t_arr = 0.0; ///< arrival instant at this station
70 /**
71 * Time spent parked outside a WAITQ region before entering this station. It is part of the
72 * visit's response time (JMT and Solver_ssj keep the original arrival instant) but not of
73 * `t_arr`, which orders the buffer by the RELEASE instant, as the Java engine's orderTime does.
74 */
75 double region_wait = 0.0;
76 double t_sys = 0.0; ///< instant the current passage started
77 double service = 0.0; ///< the sampled requirement, drawn at arrival
78 double remaining = 0.0; ///< residual work; equals `service` before any preemption
79 double elapsed = 0.0; ///< attained service across all its service intervals
80 double rank = 0.0; ///< SIRO's random key, drawn at arrival
81 double deadline = std::numeric_limits<double>::infinity(); ///< EDD/EDF key
82 double vft = 0.0; ///< FSP's virtual finish time, recomputed on demand
83 /**
84 * Identity of this job while it waits, so a reneging timer can find it.
85 *
86 * A timer names the job it was armed for, and the job may by then have
87 * entered service, been preempted back into the queue, or already left. An
88 * identity is the only thing that survives all three; the arrival instant
89 * cannot serve as one, because two jobs admitted at the same instant --
90 * routine on a closed model placed at t=0 -- would share it.
91 */
92 std::uint64_t id = 0;
93 /**
94 * The fork synchronization this job is a sibling of, 0 when it is not one.
95 *
96 * It rides on the JOB and not on the station because siblings of different
97 * parents interleave freely at every station between the Fork and the Join.
98 */
99 std::uint64_t parent = 0;
100 /**
101 * How many times this job has already retried from an orbit.
102 *
103 * It rides on the job because `maxAttempts` bounds the retries of ONE job,
104 * not of the station: a per-station counter would cap the orbit's total
105 * traffic instead and would let a job retry forever as long as others gave
106 * up.
107 */
108 int attempts = 0;
109 /**
110 * The SYNCHRONOUS CALL this job belongs to, 0 when it belongs to none.
111 *
112 * It rides on the job because the call is answered by a REPLY that comes
113 * back from somewhere else entirely: this identity is the only link between
114 * that reply and the server still held for it, and `id` cannot serve as one,
115 * being reassigned at every admission.
116 */
117 std::uint64_t call = 0;
118 /**
119 * The generation its currently scheduled completion was pushed under.
120 *
121 * Only an INFINITE SERVER needs it: a buffered station keeps the generation
122 * on the slot (`server_tag`) and a sharing one on its `PsJob`, but a Delay
123 * holds neither, so a think time re-timed by a global dependence has no
124 * other way to invalidate the departure already in the event list.
125 */
126 std::uint64_t tag = 0;
127 /**
128 * `options.init_jobs`: the caller's tag for this job, -1 when untagged. It
129 * does not survive a hop, because the next station receives a new `Job`, so
130 * `jobDepartures` records the departure from the station it was placed at.
131 */
132 std::int64_t ext = -1;
133 /** An init_jobs residual: `service` is the work still owed, not a fresh draw. */
134 bool preset = false;
135 /** A Delay job's completion instant, read by the track_jobs export. */
136 double due = 0.0;
137};
138
139/** True for the disciplines that interrupt a job already in service. */
141 switch (s) {
157 // EDF is the PREEMPTIVE deadline order (EDD is its non-preemptive twin): an arrival with an earlier absolute
158 // deadline displaces the in-service job with the latest one, preemptive resume, as `Solver_ssj` does.
160 return true;
161 default:
162 return false;
163 }
164}
165
166/**
167 * PREEMPTIVE RESUME, as against preemptive restart.
168 *
169 * A resumed job continues from its residual work; a restarted one draws a
170 * FRESH requirement and discards what it had done. That distinction is the
171 * whole of the PR/PI split and it is not cosmetic: under exponential service
172 * the two agree in distribution (memorylessness) and under any other law they
173 * do not, so a model that uses PI to represent lost work is silently served
174 * as PR on a Coxian. Every size-based policy is RESUME -- restarting a job
175 * would make its own scheduling key meaningless.
176 */
178 switch (s) {
183 return false;
184 default:
185 return is_preemptive(s);
186 }
187}
188
189/** True for the orders whose key is the job's size, residual or attained work. */
191 switch (s) {
199 return true;
200 default:
201 return false;
202 }
203}
204
205/** True for the disciplines that share the server instead of ordering a queue. */
207 switch (s) {
215 return true;
216 default:
217 return false;
218 }
219}
220
221/**
222 * The waiting-room order of a buffered discipline.
223 *
224 * Used as a `std::priority_queue` comparator, which pops the GREATEST element,
225 * so every rule below is inverted relative to the reference's `Comparator`
226 * (which orders ascending and polls the least). Getting that backwards turns
227 * FCFS into LCFS silently -- both run, both conserve jobs, and only the
228 * response-time variance tells them apart.
229 */
230struct WaitCmp {
232 const std::vector<double>* class_mean = 0; ///< mean service per class, for SEPT/LEPT
233
234 bool operator()(const Job& a, const Job& b) const {
235 switch (sched) {
239 return a.t_arr < b.t_arr; // latest first
241 return a.rank > b.rank;
245 // Plain FCFSPR/FCFSPI are DELIBERATELY absent: a policy that did
246 // not declare itself priority-aware must not read `priority`, so
247 // they fall to the default arm and queue in arrival order.
248 if (a.priority != b.priority) return a.priority > b.priority;
249 return a.t_arr > b.t_arr;
253 if (a.priority != b.priority) return a.priority > b.priority;
254 return a.t_arr < b.t_arr;
256 if (a.service != b.service) return a.service > b.service;
257 return a.t_arr > b.t_arr;
259 if (a.service != b.service) return a.service < b.service;
260 return a.t_arr > b.t_arr;
262 const double ma = mean_of(a), mb = mean_of(b);
263 if (ma != mb) return ma > mb; // shortest expected first
264 return a.t_arr > b.t_arr;
265 }
267 const double ma = mean_of(a), mb = mean_of(b);
268 if (ma != mb) return ma < mb;
269 return a.t_arr > b.t_arr;
270 }
273 if (a.deadline != b.deadline) return a.deadline > b.deadline;
274 return a.t_arr > b.t_arr;
276 if (a.remaining != b.remaining) return a.remaining > b.remaining;
277 return a.t_arr > b.t_arr;
279 if (a.priority != b.priority) return a.priority > b.priority;
280 if (a.remaining != b.remaining) return a.remaining > b.remaining;
281 return a.t_arr > b.t_arr;
283 if (a.remaining != b.remaining) return a.remaining < b.remaining;
284 return a.service < b.service;
286 // The ORIGINAL requirement, not the residual: a job that has
287 // already been served keeps the rank its full size gave it.
288 if (a.service != b.service) return a.service > b.service;
289 return a.t_arr > b.t_arr;
292 // Least attained service first. FB and SETF share this order;
293 // they differ in WHEN the running job is interrupted, not in
294 // how the waiting ones are ranked.
295 if (a.elapsed != b.elapsed) return a.elapsed > b.elapsed;
296 return a.t_arr > b.t_arr;
298 if (a.vft != b.vft) return a.vft > b.vft;
299 return a.t_arr > b.t_arr;
300 default: // FCFS and anything ordered like it
301 return a.t_arr > b.t_arr;
302 }
303 }
304
305private:
306 double mean_of(const Job& j) const {
307 if (class_mean == 0 || j.cls >= class_mean->size()) return 0.0;
308 return (*class_mean)[j.cls];
309 }
310};
311
312/** A job being served by a sharing discipline, with the work it still owes. */
313struct PsJob {
314 std::size_t cls = 0;
315 int priority = 0;
316 double t_arr = 0.0;
317 double region_wait = 0.0; ///< Job::region_wait, carried across the sharing station
318 double t_sys = 0.0;
319 double total = 0.0; ///< the sampled requirement
320 double remaining = 0.0; ///< residual work, in service-time units
321 std::uint64_t tag = 0; ///< identifies the departure event scheduled for it
322 /**
323 * The fork the job belongs to, carried across the sharing station.
324 *
325 * A buffered station keeps the whole `Job` in its server slot and hands it
326 * back at the completion, so the identity survives; a sharing station holds
327 * this reduced record instead and would hand back a DEFAULT-CONSTRUCTED one.
328 * A sibling leaving with parent 0 matches no fork at the Join and is
329 * discarded there, which drains a closed fork-join model after one pass.
330 */
331 std::uint64_t parent = 0;
332 std::int64_t ext = -1; ///< Job::ext, carried across the sharing station
333};
334
335/**
336 * The per-job shares of a sharing discipline, in units of one server.
337 *
338 * `c` is the station's effective server count: with n <= c every job runs at
339 * rate 1 and the station is an infinite server; above that the capacity is
340 * split. Each rule is capped at 1 because ONE job cannot consume more than ONE
341 * server however the weights fall -- dropping that cap is what makes a
342 * two-class DPS station report a utilization above one.
343 *
344 * Transcribed from `calculatePSRates` and the five rules under it.
345 */
346inline std::vector<double> ps_shares(lang::SchedStrategy sched, const std::vector<PsJob>& jobs,
347 double c, const std::vector<double>& weight,
348 std::size_t nclasses) {
349 const std::size_t n = jobs.size();
350 std::vector<double> rates(n, 0.0);
351 if (n == 0) return rates;
352
353 const bool prio = (sched == lang::SchedStrategy::PSPRIO ||
356 const bool weighted_by_job = (sched == lang::SchedStrategy::DPS ||
358 const bool weighted_by_class = (sched == lang::SchedStrategy::GPS ||
360
361 if (!prio) {
362 if (weighted_by_job) {
363 double tot = 0.0;
364 for (const PsJob& j : jobs) tot += weight[j.cls];
365 if (tot > 0.0) {
366 for (std::size_t i = 0; i < n; ++i)
367 rates[i] = std::min(1.0, weight[jobs[i].cls] * c / tot);
368 return rates;
369 }
370 } else if (weighted_by_class) {
371 std::vector<int> per_class(nclasses, 0);
372 for (const PsJob& j : jobs) ++per_class[j.cls];
373 double tot = 0.0;
374 for (std::size_t k = 0; k < nclasses; ++k)
375 if (per_class[k] > 0) tot += weight[k];
376 if (tot > 0.0) {
377 for (std::size_t i = 0; i < n; ++i) {
378 const std::size_t k = jobs[i].cls;
379 rates[i] = std::min(1.0, (weight[k] / tot / per_class[k]) * c);
380 }
381 return rates;
382 }
383 }
384 // PS, LPS, and the degenerate all-zero-weight case: equal shares.
385 const double share = std::min(1.0, c / static_cast<double>(n));
386 for (std::size_t i = 0; i < n; ++i) rates[i] = share;
387 return rates;
388 }
389
390 // The priority variants give the whole station to the most urgent class
391 // present, and only what is left over to the next -- so an uncongested
392 // station (n <= c) has nothing to arbitrate and every job runs at 1.
393 if (static_cast<double>(n) <= c) {
394 for (std::size_t i = 0; i < n; ++i) rates[i] = 1.0;
395 return rates;
396 }
397 std::set<int> prios;
398 for (const PsJob& j : jobs) prios.insert(j.priority);
399 double remaining = c;
400 for (std::set<int>::const_iterator it = prios.begin(); it != prios.end(); ++it) {
401 if (remaining <= 0.0) break;
402 const int p = *it;
403 int count = 0;
404 double tot_job_weight = 0.0;
405 std::vector<int> per_class(nclasses, 0);
406 for (const PsJob& j : jobs)
407 if (j.priority == p) {
408 ++count;
409 tot_job_weight += weight[j.cls];
410 ++per_class[j.cls];
411 }
412 const double allocated = std::min(remaining, static_cast<double>(count));
413 double tot_class_weight = 0.0;
414 for (std::size_t k = 0; k < nclasses; ++k)
415 if (per_class[k] > 0) tot_class_weight += weight[k];
416
417 for (std::size_t i = 0; i < n; ++i) {
418 if (jobs[i].priority != p) continue;
419 const std::size_t k = jobs[i].cls;
420 if (sched == lang::SchedStrategy::DPSPRIO && tot_job_weight > 0.0)
421 rates[i] = std::min(1.0, weight[k] * allocated / tot_job_weight);
422 else if (sched == lang::SchedStrategy::GPSPRIO && tot_class_weight > 0.0)
423 rates[i] = std::min(1.0, (weight[k] / tot_class_weight / per_class[k]) * allocated);
424 else
425 rates[i] = allocated / count;
426 }
427 remaining -= allocated;
428 }
429 return rates;
430}
431
432/**
433 * FSP's virtual finish time: when `target` would finish if the station ran
434 * processor sharing from now on.
435 *
436 * Sort every residual work at the station, then walk it: while n jobs remain
437 * each advances at min(1, c/n), so the wall time to clear the k-th smallest
438 * residual is the sum of (w_k - w_{k-1}) / rate over the ranks below it. FSP
439 * then serves in that order, which is what makes it dominate PS job by job.
440 * Transcribes `computeFSPVirtualFinishTime`.
441 */
442inline double fsp_virtual_finish(const std::vector<double>& residuals, double target_work,
443 double c, double now) {
444 std::vector<double> works = residuals;
445 std::sort(works.begin(), works.end());
446 const std::size_t n = works.size();
447 double cumulative = 0.0, prev = 0.0;
448 for (std::size_t rank = 0; rank < n; ++rank) {
449 const double w = works[rank];
450 const double rate = std::min(1.0, c / static_cast<double>(n - rank));
451 if (rate > 0.0) cumulative += (w - prev) / rate;
452 if (w >= target_work) return now + cumulative;
453 prev = w;
454 }
455 return now + cumulative;
456}
457
458/**
459 * Which job in service `arriving` displaces, or `in_service.size()` for none.
460 *
461 * ONE RULE PER FAMILY, transcribed from `shouldPreemptForLCFS`,
462 * `shouldPreemptForFCFS` and `shouldPreemptForSizeBasedPolicy` together with
463 * their `findJobToPreempt*` counterparts. The two halves are fused here because
464 * they ask the same question twice in the reference -- once to decide and once
465 * to choose -- and answering it once cannot disagree with itself.
466 *
467 * THE PRIORITY VARIANTS PREEMPT ONLY STRICTLY LOWER PRIORITY, AND ONLY THEY DO.
468 * There are FOUR distinct tests here, not one:
469 * - `LCFSPR`/`LCFSPI` preempt UNCONDITIONALLY. Last-come-first-served keeps the
470 * NEWEST job in service, so the newcomer takes the head with no class ordering.
471 * - `LCFSPRPRIO`/`LCFSPIPRIO` preempt a job whose priority value is AT LEAST the
472 * arrival's, a strictly lower one first. `started[j]` is when `in_service[j]`
473 * last took its server, the tie-break among equals.
474 * - `FCFSPRPRIO`/`FCFSPIPRIO` preempt only one STRICTLY GREATER.
475 * - `FCFSPR`/`FCFSPI` preempt NOTHING AT ALL. Preemption under the FCFS families
476 * is a PRIORITY act, so it belongs to the PRIO names alone and only ACROSS
477 * priority groups: the base rule serves in ARRIVAL ORDER, and a plain variant
478 * is absent from the model's priority-aware set (the user is warned that
479 * "Priorities will be ignored"), so every class sits in ONE group and the
480 * arrival can only WAIT. Reading `priority` here anyway -- which this function
481 * did until 2026-09-12, sharing an arm with the PRIO pair -- infers a policy
482 * the model never declared; it is invisible for flat priorities and silently
483 * turns a declared-FCFS station into a preemptive-priority one otherwise.
484 * Collapsing those four tests into fewer turns a priority station into a
485 * thrashing one, or into a non-preemptive one, depending which way it falls.
486 */
487inline std::size_t preemption_victim(lang::SchedStrategy sched, const std::vector<Job>& in_service,
488 const std::vector<double>& started, const Job& arriving,
489 double c, double now) {
490 const std::size_t none = in_service.size();
491 if (in_service.empty()) return none;
492
493 switch (sched) {
496 // Displace the OLDEST job in service: the newcomer takes the head
497 // of a last-come-first-served station unconditionally.
498 std::size_t best = none;
499 double oldest = std::numeric_limits<double>::infinity();
500 for (std::size_t j = 0; j < in_service.size(); ++j)
501 if (in_service[j].t_arr < oldest) {
502 oldest = in_service[j].t_arr;
503 best = j;
504 }
505 return best;
506 }
509 // Two tiers, as findJobToPreempt: the least urgent STRICTLY lower job (earliest arrival
510 // among ties), else the equal one that STARTED earliest. A single oldest-eligible pass
511 // displaces a more urgent job while a less urgent one keeps its server whenever c > 1.
512 std::size_t best = none;
513 for (std::size_t j = 0; j < in_service.size(); ++j) {
514 if (in_service[j].priority <= arriving.priority) continue;
515 if (best == none || in_service[j].priority > in_service[best].priority ||
516 (in_service[j].priority == in_service[best].priority &&
517 in_service[j].t_arr < in_service[best].t_arr))
518 best = j;
519 }
520 if (best != none) return best;
521 for (std::size_t j = 0; j < in_service.size(); ++j)
522 if (in_service[j].priority == arriving.priority &&
523 (best == none || started[j] < started[best]))
524 best = j;
525 return best;
526 }
529 // NOTHING. An FCFS-PR/PI arrival never preempts: the base rule serves
530 // in ARRIVAL ORDER and the plain variants declare no priorities, so
531 // every class is in one group and the arrival can only wait.
532 return none;
535 // The least urgent job in service, tie-broken by the LATEST service
536 // start: among equals, the one that has invested least is displaced.
537 std::size_t best = none;
538 for (std::size_t j = 0; j < in_service.size(); ++j) {
539 if (in_service[j].priority <= arriving.priority) continue;
540 if (best == none || in_service[j].priority > in_service[best].priority ||
541 (in_service[j].priority == in_service[best].priority &&
542 in_service[j].t_arr > in_service[best].t_arr))
543 best = j;
544 }
545 return best;
546 }
549 // The job ranked LAST: under SRPTPRIO the lowest priority (largest
550 // value) first, then the most remaining work, as in WaitCmp. A
551 // higher-priority arrival displaces a lower-priority job whatever
552 // their sizes; picking the largest residual alone let a high-priority
553 // job wait behind a low-priority one with less work left.
554 const bool prio = (sched == lang::SchedStrategy::SRPTPRIO);
555 std::size_t worst = 0;
556 for (std::size_t j = 1; j < in_service.size(); ++j) {
557 if (prio && in_service[j].priority != in_service[worst].priority) {
558 if (in_service[j].priority > in_service[worst].priority) worst = j;
559 } else if (in_service[j].remaining > in_service[worst].remaining) {
560 worst = j;
561 }
562 }
563 if (prio && arriving.priority != in_service[worst].priority)
564 return (arriving.priority < in_service[worst].priority) ? worst : none;
565 return (arriving.remaining < in_service[worst].remaining) ? worst : none;
566 }
568 std::size_t largest = 0;
569 for (std::size_t j = 1; j < in_service.size(); ++j)
570 if (in_service[j].service > in_service[largest].service) largest = j;
571 return (arriving.service < in_service[largest].service) ? largest : none;
572 }
575 // A fresh arrival has attained nothing, so it displaces whichever
576 // job has attained the most -- provided that is more than nothing.
577 std::size_t most = 0;
578 for (std::size_t j = 1; j < in_service.size(); ++j)
579 if (in_service[j].elapsed > in_service[most].elapsed) most = j;
580 return (in_service[most].elapsed > arriving.elapsed) ? most : none;
581 }
583 std::size_t least = 0;
584 for (std::size_t j = 1; j < in_service.size(); ++j)
585 if (in_service[j].remaining < in_service[least].remaining) least = j;
586 return (arriving.remaining > in_service[least].remaining) ? least : none;
587 }
589 std::vector<double> works;
590 for (const Job& j : in_service) works.push_back(j.remaining);
591 works.push_back(arriving.remaining);
592 const double arrival_vft = fsp_virtual_finish(works, arriving.remaining, c, now);
593 std::size_t worst = 0;
594 double worst_vft = -std::numeric_limits<double>::infinity();
595 for (std::size_t j = 0; j < in_service.size(); ++j) {
596 const double v = fsp_virtual_finish(works, in_service[j].remaining, c, now);
597 if (v > worst_vft) {
598 worst_vft = v;
599 worst = j;
600 }
601 }
602 return (arrival_vft < worst_vft) ? worst : none;
603 }
605 // The in-service job with the LATEST absolute deadline (first among ties), displaced only by an arrival
606 // whose deadline is strictly earlier, as `Solver_ssj.shouldPreempt`.
607 std::size_t latest = 0;
608 for (std::size_t j = 1; j < in_service.size(); ++j)
609 if (in_service[j].deadline > in_service[latest].deadline) latest = j;
610 return (arriving.deadline < in_service[latest].deadline) ? latest : none;
611 }
612 default:
613 return none;
614 }
615}
616
617} // namespace engine
618} // namespace ldes
619} // namespace line
620
621#endif // LINE_SOLVERS_LDES_LDES_STATION_H
Enumerations and the minimal distribution descriptor shared by the model layer of the C++ port.
SchedStrategy
Scheduling disciplines, with the values of MATLAB SchedStrategy.
Definition lang_types.h:185
std::size_t preemption_victim(lang::SchedStrategy sched, const std::vector< Job > &in_service, const std::vector< double > &started, const Job &arriving, double c, double now)
Which job in service arriving displaces, or in_service.size() for none.
bool is_preemptive(lang::SchedStrategy s)
True for the disciplines that interrupt a job already in service.
double fsp_virtual_finish(const std::vector< double > &residuals, double target_work, double c, double now)
FSP's virtual finish time: when target would finish if the station ran processor sharing from now on.
std::vector< double > ps_shares(lang::SchedStrategy sched, const std::vector< PsJob > &jobs, double c, const std::vector< double > &weight, std::size_t nclasses)
The per-job shares of a sharing discipline, in units of one server.
bool is_preemptive_resume(lang::SchedStrategy s)
PREEMPTIVE RESUME, as against preemptive restart.
bool is_ps_family(lang::SchedStrategy s)
True for the disciplines that share the server instead of ordering a queue.
bool is_size_based(lang::SchedStrategy s)
True for the orders whose key is the job's size, residual or attained work.
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
A job held by a station.
double due
A Delay job's completion instant, read by the track_jobs export.
double deadline
EDD/EDF key.
double region_wait
Time spent parked outside a WAITQ region before entering this station.
double t_arr
arrival instant at this station
std::uint64_t call
The SYNCHRONOUS CALL this job belongs to, 0 when it belongs to none.
double rank
SIRO's random key, drawn at arrival.
double vft
FSP's virtual finish time, recomputed on demand.
bool preset
An init_jobs residual: service is the work still owed, not a fresh draw.
int attempts
How many times this job has already retried from an orbit.
double t_sys
instant the current passage started
std::uint64_t tag
The generation its currently scheduled completion was pushed under.
double elapsed
attained service across all its service intervals
double service
the sampled requirement, drawn at arrival
std::int64_t ext
options.init_jobs: the caller's tag for this job, -1 when untagged.
double remaining
residual work; equals service before any preemption
std::uint64_t parent
The fork synchronization this job is a sibling of, 0 when it is not one.
A job being served by a sharing discipline, with the work it still owes.
double total
the sampled requirement
std::uint64_t tag
identifies the departure event scheduled for it
double region_wait
Job::region_wait, carried across the sharing station.
double remaining
residual work, in service-time units
std::uint64_t parent
The fork the job belongs to, carried across the sharing station.
std::int64_t ext
Job::ext, carried across the sharing station.
The waiting-room order of a buffered discipline.
lang::SchedStrategy sched
bool operator()(const Job &a, const Job &b) const
const std::vector< double > * class_mean
mean service per class, for SEPT/LEPT