Loop Introspection
Linux::Event exposes a query-oriented view of a Loop without turning diagnostics into permanent hot-path work. The public API is:
$loop->running;
$loop->count;
$loop->has($object);
$loop->objects;
$loop->inspect($object);
$loop->census;
$loop->resources;
$loop->why_alive;
$loop->pressure;
$loop->profile($boolean);
$loop->stats;
$loop->reset_stats;
Cost model
| Method | Cost | Notes |
| --- | --- | --- |
| running | O(1) | Reads native driver state. |
| count | O(n + r) | Enumerates authoritative object and fd registries. |
| has($object) | O(n + r) | Exact identity search with Loop/lifecycle validation. |
| objects | O(n + r) | Returns a new array reference in unspecified order. |
| inspect($object) | O(n + r) | Validates membership, then reads maintained state. |
| census | O(n + r) | Counts the current managed-object snapshot. |
| resources | O(r) | Scans the native fd registry. |
| why_alive | O(n + r) | Combines managed objects and public raw registrations. |
| pressure | O(r) | Uses current native capacities and existing statistics. |
| profile($boolean) | O(1) | Changes future timing collection only. |
| stats | O(1) | Copies the fixed native statistics structure. |
| reset_stats | O(1) | Clears counters without changing profile state. |
Here n is the number of managed public objects and r is the native fd
registry capacity. No introspection method runs implicitly during dispatch.
Queries enumerate the authoritative native Timer heap, watcher ownership, and
existing Signal, Wakeup, and resolver registries. There is no duplicate
managed-object registry to update during attachment or dispatch.
Managed objects
count, has, objects, and census describe public Stream, Listener,
Datagram, Timer, Signal, Wakeup, and Process objects. Opaque registrations
returned by watch() and private helper objects are not managed objects.
has($object) requires exact reference identity and current ownership by that
Loop. A terminal, detached, expired, cancelled, failed, or exited object is not
present. objects returns the actual objects in a new array reference; their
order is deliberately unspecified.
census always contains these singular keys, including zero values:
{
stream => 0,
listener => 0,
datagram => 0,
timer => 0,
signal => 0,
wakeup => 0,
process => 0,
}
Object inspection
inspect($object) returns a new hash reference. Every result contains type,
class, and registered. A supported object that is detached, terminal, or
owned by another Loop is described without exposing stale state:
{
type => 'timer',
class => 'MyTimer',
registered => 0,
}
Registered objects also include state and type-specific fields:
| Type | Additional fields |
| --- | --- |
| stream | fd, local, peer, transport, pending_bytes, read_paused, read_eof, write_ended, write_blocked |
| listener | fd, host, port, path, family, paused, accepted |
| datagram | fd, local, peer, connected, pending_bytes, pending_datagrams, read_paused |
| timer | deadline, interval, expirations |
| signal | signals |
| wakeup | no additional fields in the first API |
| process | pid, pending_stdin_bytes |
The hash is a snapshot. Address values are immutable Address objects; changing the returned hash does not change the inspected object.
Native resources
resources reports Linux and allocation state directly from the Loop:
{
epoll_fd => 3,
timer_fd => 5, # undef until first Timer
registered_fds => 4,
public_registrations => 1,
internal_registrations => 3,
public_registration_fds => [7],
active_timers => 2,
registry_capacity => 1024,
timer_heap_capacity => 64,
event_capacity => 8192,
}
Internal registrations back managed objects and services such as timerfd,
signalfd, resolver eventfd, pidfd, and sockets. A registration created directly
with public watch() is reported separately. timer_fd is undef until the
Loop first creates its shared timer source.
Liveness and pressure
why_alive returns an array reference of actionable user-visible reasons. A
managed-object reason is its inspection snapshot plus object, containing the
exact object. A direct raw registration reason contains type => 'registration', registered => 1, and fd. Private backing registrations do
not appear as duplicate reasons.
pressure returns conservative current indicators rather than a synthetic
health score:
{
registrations => { active => 4, capacity => 1024, utilization => 0.0039 },
timers => { active => 2, capacity => 64, utilization => 0.0312 },
event_batch => { maximum => 8, capacity => 8192, utilization => 0.0010 },
}
event_batch.maximum and its utilization are undef until at least one
epoll wait has completed. These keys describe implementation pressure only;
they do not predict callback latency or application health.
Profiling and statistics
$loop->profile(1); # returns $loop
# run the workload
my $stats = $loop->stats;
$loop->profile(0); # keeps accumulated values
$loop->reset_stats; # keeps the enabled/disabled state
Statistics remain readable while profiling is disabled. Profiling does not
reset them, and reset_stats does not change profiling state. The first API
times epoll waits, epoll control operations, watcher lookup, and dispatch
batches. It deliberately does not place a clock read around every callback.
Callback invocation counts remain available through stats.