Skip to main content

moire_types/objects/
entities.rs

1use facet::Facet;
2
3use crate::{BacktraceId, EntityId, Json, PTime, next_entity_id};
4
5// r[impl model.entity.fields]
6/// A: future, a lock, a channel end (tx, rx), a connection leg, a socket, etc.
7#[derive(Facet)]
8pub struct Entity {
9    /// Opaque entity identifier.
10    pub id: EntityId,
11
12    /// When we first started tracking this entity
13    pub birth: PTime,
14
15    /// When this entity was logically removed (deferred removal).
16    /// Present means the entity is dead but kept alive for event references.
17    #[facet(skip_unless_truthy)]
18    pub removed_at: Option<PTime>,
19
20    /// Backtrace when this edge was created
21    pub backtrace: BacktraceId,
22
23    /// Human-facing name for this entity.
24    pub name: String,
25
26    /// More specific info about the entity (depending on its kind)
27    pub body: EntityBody,
28}
29
30impl Entity {
31    /// Create a new entity: ID and birth time are generated automatically.
32    pub fn new(backtrace: BacktraceId, name: impl Into<String>, body: EntityBody) -> Entity {
33        Entity {
34            id: next_entity_id(),
35            birth: PTime::now(),
36            removed_at: None,
37            backtrace,
38            name: name.into(),
39            body,
40        }
41    }
42}
43
44// r[impl model.entity.kinds]
45crate::define_entity_body! {
46    pub enum EntityBody {
47        // Tokio core and sync primitives
48        Future(FutureEntity),
49        Lock(LockEntity),
50        MpscTx(MpscTxEntity),
51        MpscRx(MpscRxEntity),
52        BroadcastTx(BroadcastTxEntity),
53        BroadcastRx(BroadcastRxEntity),
54        WatchTx(WatchTxEntity),
55        WatchRx(WatchRxEntity),
56        OneshotTx(OneshotTxEntity),
57        OneshotRx(OneshotRxEntity),
58        Semaphore(SemaphoreEntity),
59        Notify(NotifyEntity),
60        OnceCell(OnceCellEntity),
61
62        // System and I/O boundaries
63        Command(CommandEntity),
64        FileOp(FileOpEntity),
65
66        // Network boundaries
67        NetConnect(NetConnectEntity),
68        NetAccept(NetAcceptEntity),
69        NetRead(NetReadEntity),
70        NetWrite(NetWriteEntity),
71
72        // RPC lifecycle
73        Request(RequestEntity),
74        Response(ResponseEntity),
75
76        // User-defined
77        Custom(CustomEntity),
78
79        // Synthetic entities for uninstrumented tasks
80        Aether(AetherEntity),
81    }
82}
83
84#[derive(Facet, Default)]
85pub struct FutureEntity {
86    /// Number of frames to skip from the top of the backtrace when displaying this future.
87    /// Set to 1 by `#[moire::instrument]` so the instrumented function itself is hidden
88    /// and the callsite is shown instead.
89    #[facet(skip_unless_truthy)]
90    pub skip_entry_frames: Option<u8>,
91}
92
93#[derive(Facet)]
94pub struct LockEntity {
95    /// Kind of lock primitive.
96    pub kind: LockKind,
97}
98
99#[derive(Facet)]
100#[repr(u8)]
101#[facet(rename_all = "snake_case")]
102pub enum LockKind {
103    Mutex,
104    RwLock,
105    Other,
106}
107
108#[derive(Facet)]
109pub struct MpscTxEntity {
110    /// Current queue length.
111    pub queue_len: u32,
112    /// Configured capacity (`None` for unbounded).
113    pub capacity: Option<u32>,
114}
115
116#[derive(Facet, Clone, Copy, Debug, PartialEq, Eq)]
117pub struct MpscRxEntity {}
118
119#[derive(Facet)]
120pub struct BroadcastTxEntity {
121    pub capacity: u32,
122}
123
124#[derive(Facet)]
125pub struct BroadcastRxEntity {
126    pub lag: u32,
127}
128
129#[derive(Facet)]
130pub struct WatchTxEntity {
131    pub last_update_at: Option<PTime>,
132}
133
134#[derive(Facet)]
135pub struct WatchRxEntity {}
136
137#[derive(Facet)]
138pub struct OneshotTxEntity {
139    pub sent: bool,
140}
141
142#[derive(Facet, Clone, Copy, Debug, PartialEq, Eq)]
143pub struct OneshotRxEntity {}
144
145#[derive(Facet)]
146pub struct SemaphoreEntity {
147    /// Total permits configured for this semaphore.
148    pub max_permits: u32,
149    /// Current number of permits acquired and not yet released.
150    pub handed_out_permits: u32,
151}
152
153#[derive(Facet)]
154pub struct NotifyEntity {
155    /// Number of tasks currently waiting on this notify.
156    pub waiter_count: u32,
157}
158
159#[derive(Facet)]
160pub struct OnceCellEntity {
161    /// Number of tasks currently waiting for initialization.
162    pub waiter_count: u32,
163    /// Current once-cell lifecycle state.
164    pub state: OnceCellState,
165}
166
167#[derive(Facet, Clone, Copy, Debug, PartialEq, Eq)]
168#[repr(u8)]
169#[facet(rename_all = "snake_case")]
170pub enum OnceCellState {
171    Empty,
172    Initializing,
173    Initialized,
174}
175
176#[derive(Facet)]
177pub struct CommandEntity {
178    /// Executable path or program name.
179    pub program: String,
180    /// Command-line arguments.
181    pub args: Vec<String>,
182    /// Environment entries in `KEY=VALUE` form.
183    pub env: Vec<String>,
184}
185
186#[derive(Facet)]
187pub struct FileOpEntity {
188    /// File operation type.
189    pub op: FileOpKind,
190    /// Absolute or process-relative file path.
191    pub path: String,
192}
193
194#[derive(Facet)]
195#[repr(u8)]
196#[facet(rename_all = "snake_case")]
197pub enum FileOpKind {
198    Open,
199    Read,
200    Write,
201    Sync,
202    Metadata,
203    Remove,
204    Rename,
205    Other,
206}
207
208#[derive(Facet)]
209pub struct NetConnectEntity {
210    /// Endpoint address string (for example `127.0.0.1:8080`).
211    pub addr: String,
212}
213
214#[derive(Facet)]
215pub struct NetAcceptEntity {
216    /// Endpoint address string (for example `127.0.0.1:8080`).
217    pub addr: String,
218}
219
220#[derive(Facet)]
221pub struct NetReadEntity {
222    /// Endpoint address string (for example `127.0.0.1:8080`).
223    pub addr: String,
224}
225
226#[derive(Facet)]
227pub struct NetWriteEntity {
228    /// Endpoint address string (for example `127.0.0.1:8080`).
229    pub addr: String,
230}
231
232/// Correlation token for RPC is the request entity id propagated in metadata.
233/// The receiver generates a fresh response entity id and emits `request -> response`.
234#[derive(Facet)]
235pub struct RequestEntity {
236    /// Service name portion of the RPC endpoint.
237    ///
238    /// Example: for `vfs.lookupItem`, this is `vfs`.
239    pub service_name: String,
240    /// Method name portion of the RPC endpoint.
241    ///
242    /// Example: for `vfs.lookupItem`, this is `lookupItem`.
243    pub method_name: String,
244    /// JSON-encoded request arguments.
245    ///
246    /// This is always valid JSON and should be `[]` when the method has no args.
247    pub args_json: Json,
248}
249
250#[derive(Facet)]
251pub struct ResponseEntity {
252    /// Service name portion of the RPC endpoint.
253    pub service_name: String,
254    /// Method name portion of the RPC endpoint.
255    pub method_name: String,
256    /// Response status and payload/error details.
257    pub status: ResponseStatus,
258}
259
260#[derive(Facet, Clone, Debug, PartialEq, Eq)]
261#[repr(u8)]
262#[facet(rename_all = "snake_case")]
263pub enum ResponseStatus {
264    /// Response has not completed yet.
265    Pending,
266    /// Handler completed successfully with a JSON result payload.
267    Ok(Json),
268    /// Handler failed with either internal or user-level JSON error data.
269    Error(ResponseError),
270    /// Request was cancelled before completion.
271    Cancelled,
272}
273
274#[derive(Facet, Clone, Debug, PartialEq, Eq)]
275#[repr(u8)]
276#[facet(rename_all = "snake_case")]
277pub enum ResponseError {
278    /// Runtime/transport/internal error rendered as text.
279    Internal(String),
280    /// Application/user error represented as JSON.
281    UserJson(Json),
282}
283
284/// A user-defined entity kind with arbitrary metadata.
285///
286/// Library consumers can create custom entity kinds without modifying moire source.
287/// All fields are user-controlled; the runtime treats them opaquely.
288#[derive(Facet)]
289pub struct CustomEntity {
290    /// Canonical kind identifier (e.g. "database_pool"). snake_case, non-empty.
291    pub kind: String,
292    /// Human-readable display name (e.g. "Database Pool").
293    pub display_name: String,
294    /// Category for UI grouping ("async"/"sync"/"channel"/"rpc"/"net"/"fs"/"time"/"meta").
295    pub category: String,
296    /// Phosphor icon name (e.g. "Database", "Cpu"). Empty string = default icon.
297    pub icon: String,
298    /// Arbitrary structured metadata as a JSON object string.
299    pub attrs: Json,
300}
301
302/// Synthetic entity for uninstrumented tasks.
303///
304/// Created automatically when a moire-instrumented primitive is used from a task
305/// that was not spawned via `moire::task::spawn`. Makes deadlocks in uninstrumented
306/// code visible on the dashboard.
307#[derive(Facet)]
308pub struct AetherEntity {
309    /// Tokio task ID that this aether represents.
310    pub task_id: String,
311}