Packages

A Gleam library for building and orchestrating agents on the BEAM.

Current section

Files

Jump to
pig src pig_obs_ffi.erl
Raw

src/pig_obs_ffi.erl

-module(pig_obs_ffi).
%% Minimal FFI: thin wrappers around :telemetry and :erlang.
%% All event naming and metadata shaping lives in Gleam.
-export([
execute/3,
system_time/0,
attach_listener/1,
get_captured_names/1,
get_captured_events/1,
get_captured_count/1,
detach_listener/1
]).
%% Execute a telemetry event.
%% Converts string-keyed maps (from Gleam Dict) to atom-keyed maps (required by :telemetry).
%% Converts string event name segments to atoms.
execute(NameStrs, Measurements, Metadata) ->
NameAtoms = [binary_to_atom(S, utf8) || S <- NameStrs],
telemetry:execute(NameAtoms, atomize_keys(Measurements), atomize_keys(Metadata)),
nil.
%% Erlang monotonic time for measurements, in milliseconds.
system_time() ->
erlang:system_time(millisecond).
%% Attach a listener that captures full event data into an ETS table.
%% Stores {Timestamp, NameStrs, Measurements, Metadata} per event.
%% Returns {HandlerId, TableId} as the opaque handle.
attach_listener(EventNamesStrs) ->
TableId = ets:new(pig_listener, [ordered_set, public]),
Handler = fun(EventName, Measurements, Metadata, _Config) ->
NameStrs = [atom_to_binary(E, utf8) || E <- EventName],
BinMeasurements = deatomize_keys(Measurements),
BinMetadata = deatomize_keys(Metadata),
ets:insert(TableId, {erlang:unique_integer([monotonic]), NameStrs, BinMeasurements, BinMetadata})
end,
AtomNames = [[binary_to_atom(S, utf8) || S <- Name] || Name <- EventNamesStrs],
HandlerId = {pig_listener, TableId},
telemetry:attach_many(HandlerId, AtomNames, Handler, undefined),
{HandlerId, TableId}.
%% Get the list of captured event names (in order).
get_captured_names({_, TableId}) ->
Rows = ets:tab2list(TableId),
[NameStrs || {_, NameStrs, _, _} <- Rows].
%% Get captured events as Gleam-compatible tuples.
%% Returns a list of {raw_captured_event, Name, Measurements, Metadata} tuples
%% which Gleam sees as List(RawCapturedEvent).
get_captured_events({_, TableId}) ->
Rows = ets:tab2list(TableId),
[{raw_captured_event, NameStrs, Measurements, Metadata}
|| {_, NameStrs, Measurements, Metadata} <- Rows].
%% Get the count of captured events.
get_captured_count({_, TableId}) ->
ets:info(TableId, size).
%% Detach the listener and clean up the ETS table.
detach_listener({HandlerId, TableId}) ->
telemetry:detach(HandlerId),
ets:delete(TableId),
nil.
%% Internal: convert string-keyed map to atom-keyed map (for emitting).
atomize_keys(Map) when is_map(Map) ->
maps:fold(fun(K, V, Acc) ->
AtomKey = if is_binary(K) -> binary_to_atom(K, utf8); true -> K end,
Acc#{AtomKey => V}
end, #{}, Map).
%% Internal: convert atom-keyed map to binary-keyed map (for capturing back to Gleam).
deatomize_keys(Map) when is_map(Map) ->
maps:fold(fun(K, V, Acc) ->
BinKey = if is_atom(K) -> atom_to_binary(K, utf8); true -> K end,
Acc#{BinKey => V}
end, #{}, Map).