///|
/// Priority and preemptive resource model for L4B.
///|
pub(all) enum PreemptiveResourceEventKind {
Grant
Queue
Preempt
Interrupt
Cancel
DeadlineOverrun
ResourceExhaustion
Release
} derive(Eq, Debug)
///|
pub fn PreemptiveResourceEventKind::label(
self : PreemptiveResourceEventKind,
) -> String {
match self {
Grant => "grant"
Queue => "queue"
Preempt => "preempt"
Interrupt => "interrupt"
Cancel => "cancel"
DeadlineOverrun => "deadline-overrun"
ResourceExhaustion => "resource-exhaustion"
Release => "release"
}
}
///|
pub fn PreemptiveResourceEventKind::code(
self : PreemptiveResourceEventKind,
) -> Int {
match self {
Grant => 1
Queue => 2
Preempt => 3
Interrupt => 4
Cancel => 5
DeadlineOverrun => 6
ResourceExhaustion => 7
Release => 8
}
}
///|
pub(all) struct PriorityResourceRequest {
owner_id : String
priority : Int
amount : Int
deadline : @core.VTime?
preemptible : Bool
} derive(Eq, Debug)
///|
pub fn PriorityResourceRequest::make(
owner_id~ : String,
priority~ : Int,
amount? : Int = 1,
deadline? : @core.VTime,
preemptible? : Bool = true,
) -> PriorityResourceRequest {
{
owner_id,
priority,
amount: clamp_non_negative(amount),
deadline,
preemptible,
}
}
///|
pub(all) struct PriorityResourceLease {
resource_name : String
owner_id : String
amount : Int
priority : Int
granted_at : @core.VTime
deadline : @core.VTime?
preemptible : Bool
} derive(Eq, Debug)
///|
priv struct PreemptiveResourceWaiter {
request : PriorityResourceRequest
queued_at : @core.VTime
}
///|
pub(all) struct PreemptiveResourceEvent {
id : Int
kind : PreemptiveResourceEventKind
resource_name : String
owner_id : String
priority : Int
amount : Int
vtime : @core.VTime
note : String
} derive(Eq, Debug)
///|
pub fn PreemptiveResourceEvent::to_trace_event(
self : PreemptiveResourceEvent,
seed~ : Int,
backend? : @core.BackendProfile = @core.SimNative,
clock_domain? : String = "sim",
node_id? : String = "resource",
medium_id? : String = "lockwire-resource",
) -> @trace.TraceEvent {
@trace.TraceEvent::make(
event_id=self.id,
parent_id=None,
vtime=self.vtime,
clock_domain~,
raw_ns=self.vtime.ns(),
node_id~,
medium_id~,
channel_id=None,
direction=@trace.Resource,
payload_digest=Some(self.kind.code()),
rng_step=self.id,
seed~,
backend~,
label="resource." +
self.resource_name +
"." +
self.kind.label() +
"." +
self.owner_id,
)
}
///|
pub(all) struct PreemptiveResourceReport {
scenario : String
seed : Int
resource_name : String
capacity : Int
grant_count : Int
queue_count : Int
preemption_count : Int
interrupt_count : Int
cancel_count : Int
deadline_overrun_count : Int
resource_exhaustion_count : Int
max_queue_length : Int
active_count : Int
trace : @trace.TraceLog
digest : @core.SimDigest
real_os_scheduler : Bool
} derive(Debug)
///|
pub fn PreemptiveResourceReport::passes(
self : PreemptiveResourceReport,
) -> Bool {
self.capacity > 0 &&
self.grant_count >= 2 &&
self.preemption_count >= 1 &&
self.interrupt_count >= 1 &&
self.cancel_count >= 1 &&
self.deadline_overrun_count >= 1 &&
self.resource_exhaustion_count >= 1 &&
self.max_queue_length >= 1 &&
self.trace.len() == self.digest.event_count &&
!self.real_os_scheduler
}
///|
pub fn PreemptiveResourceReport::to_text(
self : PreemptiveResourceReport,
) -> String {
let buf = StringBuilder::new()
buf.write_string("schema=lockwire.preemptive-resource-report.v1\n")
buf.write_string("scenario=" + self.scenario + "\n")
buf.write_string("seed=" + self.seed.to_string() + "\n")
buf.write_string("resource_name=" + self.resource_name + "\n")
buf.write_string("capacity=" + self.capacity.to_string() + "\n")
buf.write_string("grant_count=" + self.grant_count.to_string() + "\n")
buf.write_string("queue_count=" + self.queue_count.to_string() + "\n")
buf.write_string(
"preemption_count=" + self.preemption_count.to_string() + "\n",
)
buf.write_string("interrupt_count=" + self.interrupt_count.to_string() + "\n")
buf.write_string("cancel_count=" + self.cancel_count.to_string() + "\n")
buf.write_string(
"deadline_overrun_count=" + self.deadline_overrun_count.to_string() + "\n",
)
buf.write_string(
"resource_exhaustion_count=" +
self.resource_exhaustion_count.to_string() +
"\n",
)
buf.write_string(
"max_queue_length=" + self.max_queue_length.to_string() + "\n",
)
buf.write_string("active_count=" + self.active_count.to_string() + "\n")
buf.write_string(
"digest_state=" + self.digest.state_digest.to_string() + "\n",
)
buf.write_string(
"digest_events=" + self.digest.event_count.to_string() + "\n",
)
buf.write_string(
"real_os_scheduler=" + bool_text(self.real_os_scheduler) + "\n",
)
for event in self.trace.events {
buf.write_string("trace=" + event.to_golden_line() + "\n")
}
buf.to_string()
}
///|
pub struct PriorityResource {
priv inner : PreemptiveResource
}
///|
pub fn PriorityResource::new(
name~ : String,
capacity~ : Int,
) -> PriorityResource {
{ inner: PreemptiveResource::new(name~, capacity~) }
}
///|
pub fn PriorityResource::request(
self : PriorityResource,
request : PriorityResourceRequest,
at~ : @core.VTime,
) -> PriorityResourceLease? {
self.inner.request_internal(request, at~, allow_preemption=false)
}
///|
pub fn PriorityResource::release(
self : PriorityResource,
owner_id~ : String,
at~ : @core.VTime,
) -> Bool {
self.inner.release(owner_id~, at~)
}
///|
pub fn PriorityResource::report(
self : PriorityResource,
scenario~ : String,
seed~ : Int,
) -> PreemptiveResourceReport {
self.inner.report(scenario~, seed~)
}
///|
pub struct PreemptiveResource {
priv name : String
priv capacity : Int
priv active : Array[PriorityResourceLease]
priv waiters : Array[PreemptiveResourceWaiter]
priv events : Array[PreemptiveResourceEvent]
priv mut next_event_id : Int
priv mut max_queue_length : Int
}
///|
pub fn PreemptiveResource::new(
name~ : String,
capacity~ : Int,
) -> PreemptiveResource {
{
name,
capacity: clamp_non_negative(capacity),
active: [],
waiters: [],
events: [],
next_event_id: 1,
max_queue_length: 0,
}
}
///|
pub fn PreemptiveResource::name(self : PreemptiveResource) -> String {
self.name
}
///|
pub fn PreemptiveResource::capacity(self : PreemptiveResource) -> Int {
self.capacity
}
///|
pub fn PreemptiveResource::in_use(self : PreemptiveResource) -> Int {
let mut total = 0
for lease in self.active {
total += lease.amount
}
total
}
///|
pub fn PreemptiveResource::available(self : PreemptiveResource) -> Int {
self.capacity - self.in_use()
}
///|
pub fn PreemptiveResource::queue_length(self : PreemptiveResource) -> Int {
self.waiters.length()
}
///|
pub fn PreemptiveResource::max_queue_length(self : PreemptiveResource) -> Int {
self.max_queue_length
}
///|
pub fn PreemptiveResource::events(
self : PreemptiveResource,
) -> Array[PreemptiveResourceEvent] {
self.events.copy()
}
///|
pub fn PreemptiveResource::request(
self : PreemptiveResource,
request : PriorityResourceRequest,
at~ : @core.VTime,
) -> PriorityResourceLease? {
self.request_internal(request, at~, allow_preemption=true)
}
///|
fn PreemptiveResource::request_internal(
self : PreemptiveResource,
request : PriorityResourceRequest,
at~ : @core.VTime,
allow_preemption~ : Bool,
) -> PriorityResourceLease? {
let request = sanitize_request(request)
if request.amount > self.capacity {
self.record_event(
ResourceExhaustion,
request,
at~,
note="amount-exceeds-capacity",
)
return None
}
if request.amount <= self.available() {
return Some(self.grant(request, at~))
}
if allow_preemption && self.try_preempt_for(request, at~) {
return Some(self.grant(request, at~))
}
self.waiters.push({ request, queued_at: at })
if self.waiters.length() > self.max_queue_length {
self.max_queue_length = self.waiters.length()
}
self.record_event(Queue, request, at~, note="waiting-for-capacity")
None
}
///|
pub fn PreemptiveResource::release(
self : PreemptiveResource,
owner_id~ : String,
at~ : @core.VTime,
) -> Bool {
match self.active_index(owner_id) {
None => false
Some(index) => {
let lease = self.active.remove(index)
self.record_lease_event(Release, lease, at~, note="released")
self.grant_waiters(at~)
true
}
}
}
///|
pub fn PreemptiveResource::check_deadlines(
self : PreemptiveResource,
at~ : @core.VTime,
) -> Unit {
let mut index = 0
while index < self.waiters.length() {
let waiter = self.waiters[index]
match waiter.request.deadline {
Some(deadline) if deadline < at => {
ignore(self.waiters.remove(index))
self.record_event(
DeadlineOverrun,
waiter.request,
at~,
note="queued-deadline-overrun",
)
self.record_event(
Cancel,
waiter.request,
at~,
note="cancelled-after-deadline-overrun",
)
}
_ => index += 1
}
}
}
///|
pub fn PreemptiveResource::trace_log(
self : PreemptiveResource,
seed~ : Int,
) -> @trace.TraceLog {
let log = @trace.TraceLog::new()
for event in self.events {
log.append(event.to_trace_event(seed~))
}
log
}
///|
pub fn PreemptiveResource::report(
self : PreemptiveResource,
scenario~ : String,
seed~ : Int,
) -> PreemptiveResourceReport {
let trace = self.trace_log(seed~)
{
scenario,
seed,
resource_name: self.name,
capacity: self.capacity,
grant_count: self.count_events(Grant),
queue_count: self.count_events(Queue),
preemption_count: self.count_events(Preempt),
interrupt_count: self.count_events(Interrupt),
cancel_count: self.count_events(Cancel),
deadline_overrun_count: self.count_events(DeadlineOverrun),
resource_exhaustion_count: self.count_events(ResourceExhaustion),
max_queue_length: self.max_queue_length,
active_count: self.active.length(),
trace,
digest: trace.portable_digest(seed~),
real_os_scheduler: false,
}
}
///|
pub fn preemptive_resource_contention_fixture(
seed? : Int = 744,
) -> PreemptiveResourceReport {
let resource = PreemptiveResource::new(name="rt-cpu", capacity=1)
ignore(
resource.request(
PriorityResourceRequest::make(
owner_id="low-cycle",
priority=5,
deadline=@core.VTime::from_ns(100L),
),
at=@core.VTime::from_ns(0L),
),
)
ignore(
resource.request(
PriorityResourceRequest::make(
owner_id="high-cycle",
priority=-10,
deadline=@core.VTime::from_ns(40L),
),
at=@core.VTime::from_ns(10L),
),
)
ignore(
resource.request(
PriorityResourceRequest::make(
owner_id="background",
priority=20,
deadline=@core.VTime::from_ns(15L),
),
at=@core.VTime::from_ns(12L),
),
)
resource.check_deadlines(at=@core.VTime::from_ns(20L))
ignore(
resource.request(
PriorityResourceRequest::make(
owner_id="oversize-dma",
priority=0,
amount=2,
deadline=@core.VTime::from_ns(50L),
),
at=@core.VTime::from_ns(21L),
),
)
resource.report(scenario="l4b.preemptive-resource-contention", seed~)
}
///|
fn PreemptiveResource::grant(
self : PreemptiveResource,
request : PriorityResourceRequest,
at~ : @core.VTime,
) -> PriorityResourceLease {
let lease : PriorityResourceLease = {
resource_name: self.name,
owner_id: request.owner_id,
amount: request.amount,
priority: request.priority,
granted_at: at,
deadline: request.deadline,
preemptible: request.preemptible,
}
self.active.push(lease)
self.record_lease_event(Grant, lease, at~, note="granted")
lease
}
///|
fn PreemptiveResource::grant_waiters(
self : PreemptiveResource,
at~ : @core.VTime,
) -> Unit {
let mut keep_granting = true
while keep_granting {
match self.best_waiter_index() {
None => keep_granting = false
Some(index) => {
let waiter = self.waiters[index]
if waiter.request.amount > self.available() {
keep_granting = false
} else {
ignore(self.waiters.remove(index))
ignore(self.grant(waiter.request, at~))
}
}
}
}
}
///|
fn PreemptiveResource::try_preempt_for(
self : PreemptiveResource,
request : PriorityResourceRequest,
at~ : @core.VTime,
) -> Bool {
match self.preemptable_victim_index(request) {
None => false
Some(index) => {
let victim = self.active.remove(index)
self.record_lease_event(
Interrupt,
victim,
at~,
note="interrupted-by-" + request.owner_id,
)
self.record_lease_event(
Cancel,
victim,
at~,
note="cancelled-by-" + request.owner_id,
)
self.record_event(
Preempt,
request,
at~,
note="preempted-" + victim.owner_id,
)
request.amount <= self.available()
}
}
}
///|
fn PreemptiveResource::preemptable_victim_index(
self : PreemptiveResource,
request : PriorityResourceRequest,
) -> Int? {
let mut best : Int? = None
for index, lease in self.active {
if lease.preemptible &&
lease.priority > request.priority &&
request.amount <= self.available() + lease.amount {
match best {
None => best = Some(index)
Some(current) =>
if lease.priority > self.active[current].priority {
best = Some(index)
}
}
}
}
best
}
///|
fn PreemptiveResource::active_index(
self : PreemptiveResource,
owner_id : String,
) -> Int? {
for index, lease in self.active {
if lease.owner_id == owner_id {
return Some(index)
}
}
None
}
///|
fn PreemptiveResource::best_waiter_index(self : PreemptiveResource) -> Int? {
let mut best : Int? = None
for index, waiter in self.waiters {
match best {
None => best = Some(index)
Some(current) => {
let current_waiter = self.waiters[current]
if waiter.request.priority < current_waiter.request.priority ||
(
waiter.request.priority == current_waiter.request.priority &&
waiter.queued_at < current_waiter.queued_at
) {
best = Some(index)
}
}
}
}
best
}
///|
fn PreemptiveResource::record_event(
self : PreemptiveResource,
kind : PreemptiveResourceEventKind,
request : PriorityResourceRequest,
at~ : @core.VTime,
note~ : String,
) -> Unit {
self.events.push({
id: self.next_event_id,
kind,
resource_name: self.name,
owner_id: request.owner_id,
priority: request.priority,
amount: request.amount,
vtime: at,
note,
})
self.next_event_id += 1
}
///|
fn PreemptiveResource::record_lease_event(
self : PreemptiveResource,
kind : PreemptiveResourceEventKind,
lease : PriorityResourceLease,
at~ : @core.VTime,
note~ : String,
) -> Unit {
self.events.push({
id: self.next_event_id,
kind,
resource_name: self.name,
owner_id: lease.owner_id,
priority: lease.priority,
amount: lease.amount,
vtime: at,
note,
})
self.next_event_id += 1
}
///|
fn PreemptiveResource::count_events(
self : PreemptiveResource,
kind : PreemptiveResourceEventKind,
) -> Int {
let mut count = 0
for event in self.events {
if event.kind == kind {
count += 1
}
}
count
}
///|
fn sanitize_request(
request : PriorityResourceRequest,
) -> PriorityResourceRequest {
{ ..request, amount: clamp_non_negative(request.amount) }
}
///|
fn bool_text(value : Bool) -> String {
if value {
"true"
} else {
"false"
}
}