Operating System
The Operating System semantic module identifies a Quent entity as a specific operating-system process or thread. This allows entities to be correlated with data from general-purpose event sources that identify processes and threads by their native operating-system IDs rather than Quent UUIDs, such as captured NVTX annotations or CPU stack samples.
Use this module when:
- An entity represents an entire process in a multi-process system, such as a worker process in a process pool.
- An entity represents a specific operating-system thread, such as a worker or main thread.
- The entity must be matched with captured NVTX annotations, CPU stack samples, or another source that identifies processes and threads by native IDs.
Do not use this module when:
- An error event only needs to report the process or thread in which the error occurred.
- A task, request, or connection event records which process or thread handled it, but the entity itself does not represent that process or thread.
- A process or thread ID is only a diagnostic field or metric dimension and does not define what the entity represents.
Use ordinary attributes for those values instead.
YAML model
quent: alpha
model: operating_system
entities:
Process:
events:
started:
attributes:
process: { os: process }
Thread:
events:
started:
attributes:
thread: { os: thread }
process: { scope-ref: Process }
Using the os as an event field type adds Quent’s standard operating-system
records to the schema and uses it as the field’s type:
{ os: process }adds thequent::os::Processrecord.{ os: thread }adds thequent::os::Threadrecord.
The generated event API requires values for these fields when the event is emitted.
We use these standard records so event analysis tools know that the entity itself represents a process or thread. Because every model uses the same record names and fields, generated APIs and other tools can handle them consistently.
These fields can also be used as attributes of an FSM state. That state must not be part of a cycle because states on a cycle may occur more than once.
Each identity record must be carried by a once-only event. A thread that does
not also represent a process must be scoped under its containing process. The
process reference on Thread.started establishes that relationship here.
Instrumentation API
The event producer reports IDs from the native platform APIs. The generated API then associates those IDs with the process and thread entities. These snippets focus on the instrumentation API; the full sources linked below contain the platform-specific code that reads the native IDs.
use instrumentation::{Context, Noop, OperatingSystem, Process, Thread};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let context = Context::<OperatingSystem>::try_new(Noop)?;
let mut process = context.observer::<Process>().handle();
// This is the native OS process ID, not the Quent entity ID.
process.started(instrumentation::quent::os::Process {
native_id: std::process::id(),
})?;
let process = process.as_entity_ref();
let mut thread = context.observer::<Thread>().handle();
std::thread::spawn(move || {
// This is the worker's native OS thread ID, not the Quent entity ID.
let native_id = current_native_thread_id()?;
thread
.started(instrumentation::quent::os::Thread { native_id }, process)
.map_err(std::io::Error::other)
})
.join()
.unwrap()?;
Ok(())
}
#include "quent-tutorial-operating-system-cpp-bridge/gen/quent.hpp"
#include <thread>
#include <utility>
int main() {
auto context = quent::Context::none();
auto process = context.process_observer()->handle();
// This is the native OS process ID, not the Quent entity ID.
process.started(quent::process::Started{
.process = quent::records::QuentOsProcess{
.native_id = native_process_id(),
},
});
// This is the Quent entity ID used to refer to the process entity.
auto process_id = process.id();
auto thread = context.thread_observer()->handle();
std::thread worker([thread = std::move(thread), process_id]() mutable {
// This is the worker's native OS thread ID, not the Quent entity ID.
thread.started(quent::thread::Started{
.thread = quent::records::QuentOsThread{
.native_id = native_thread_id(),
},
.process = process_id,
});
});
worker.join();
return 0;
}
import os
import threading
import quent_tutorial_operating_system as quent
def main() -> None:
with quent.Context() as context:
process = context.process_observer().handle()
# This is the native OS process ID, not the Quent entity ID.
process.started(process={"native_id": os.getpid()})
thread = context.thread_observer().handle()
def start_thread() -> None:
# This is the worker's native OS thread ID, not the Quent entity ID.
thread.started(
thread={"native_id": threading.get_native_id()},
process=process,
)
worker = threading.Thread(target=start_thread)
worker.start()
worker.join()
Platform considerations
Native IDs identify operating-system objects only within the scope and lifetime defined by the platform. They are not universally unique identifiers.
On Linux, the process ID and the main thread’s thread ID have the same numeric value. macOS and Windows obtain process and thread IDs through separate native APIs. Consumers must distinguish the two identities by their record types, not by comparing their numeric values.
An entity can carry both records when it represents both a process and its main thread. This example uses separate entities so the process-to-thread scope is explicit.
OS identity connects an entity to an external process or thread identity; it does not add IDs to every event.