Combining modules
Module constraints can be used together in one model. The job workload combines a finite-state lifecycle with bounded resource usage.
This example combines an FSM, event attributes, and measured resource usage. A worker publishes its thread limit. A job records how many threads it requests and how many it occupies while running.
YAML model
quent: alpha
model: job_workload
entities:
Worker:
# Generates WorkerUsage and WorkerBounds.
resource:
threads:
kind: occupancy
known-bounds: true
events:
ready:
attributes:
name: string
limits: { sets-resource-bounds: true }
fsms:
Job:
states:
queued:
initial: true
attributes:
name: string
requested_threads: u64
to: [running]
running:
attributes:
worker: { uses: Worker }
to: [completed]
completed: {}
Instrumentation API
The generated API distinguishes the worker’s WorkerBounds from the job’s
WorkerUsage. No event names or payload keys are assembled at runtime.
use instrumentation::{Context, Job, JobWorkload, Noop, Worker, WorkerBounds, WorkerUsage};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let context = Context::<JobWorkload>::try_new(Noop)?;
let mut worker = context.observer::<Worker>().handle();
worker.ready("worker-1".to_owned(), WorkerBounds { threads: 16 })?;
let _job = context
.observer::<Job>()
.handle()
.queued("compile".to_owned(), 4)
.running(worker.as_entity_ref_with(WorkerUsage { threads: 4 }))
.completed();
Ok(())
}
#include "quent-tutorial-job-workload-cpp-bridge/gen/quent.hpp"
int main() {
auto context = quent::Context::none();
auto worker = context.worker_observer()->handle();
worker.ready(quent::worker::Ready{
.name = "worker-1",
.limits = quent::records::WorkerBounds{.threads = 16},
});
auto queued = context.job_observer()->handle().queued(quent::job::Queued{
.name = "compile",
.requested_threads = 4,
});
auto running = std::move(queued).running(quent::job::Running{
.worker = quent::refs::WorkerUsageRef{
.target = worker.id(),
.data = quent::records::WorkerUsage{.threads = 4},
},
});
auto completed = std::move(running).completed();
return 0;
}
import quent_tutorial_job_workload as quent
def main() -> None:
with quent.Context() as context:
worker = context.worker_observer().handle()
worker.ready(name="worker-1", limits={"threads": 16})
job = context.job_observer().handle()
queued = job.queued(name="compile", requested_threads=4)
running = queued.running(
worker={
"target": worker,
"data": {"threads": 4},
}
)
completed = running.completed()
if __name__ == "__main__":
main()
Key point
The job records its requested thread count and its usage of a bounded worker resource.