@@ -106,16 +106,30 @@ async fn main() -> anyhow::Result<()> {
106106 ) ;
107107
108108 let mut sigint = tokio:: signal:: unix:: signal ( tokio:: signal:: unix:: SignalKind :: interrupt ( ) ) ?;
109+ let mut stats = Stats :: default ( ) ;
110+ let mut report = tokio:: time:: interval ( Duration :: from_secs ( 60 ) ) ;
111+ report. tick ( ) . await ; // consume the immediate first tick
109112 loop {
110113 tokio:: select! {
111114 _ = sigint. recv( ) => break ,
115+ _ = report. tick( ) => {
116+ tracing:: info!(
117+ exec = stats. exec,
118+ egress = stats. egress,
119+ dns = stats. dns,
120+ file = stats. file,
121+ llm = stats. llm,
122+ "a3s-observer: events processed in the last 60s"
123+ ) ;
124+ stats = Stats :: default ( ) ;
125+ }
112126 // Drain all rings every 20ms. Adequate for production at moderate volume; the
113127 // rings (64-256 KiB) absorb bursts between ticks. For sustained extreme volume,
114128 // switch to AsyncFd (epoll) on the ring fds and/or enlarge the rings.
115129 _ = tokio:: time:: sleep( Duration :: from_millis( 20 ) ) => {
116130 while let Some ( item) = exec_ring. next( ) {
117131 if let Some ( ev) = read_pod:: <ExecEvent >( & item) {
118- exporter. export ( & EnrichedEvent {
132+ emit ( exporter. as_ref ( ) , & mut stats , EnrichedEvent {
119133 identity: identity_for( & resolver, ev. pid, & ev. comm) ,
120134 provider: None ,
121135 event: AgentEvent :: ToolExec {
@@ -135,7 +149,7 @@ async fn main() -> anyhow::Result<()> {
135149 peers. clear( ) ; // ponytail: crude cap; LRU if it ever matters
136150 }
137151 peers. insert( sock_key( ev. pid, ev. fd) , peer) ;
138- exporter. export ( & EnrichedEvent {
152+ emit ( exporter. as_ref ( ) , & mut stats , EnrichedEvent {
139153 identity: identity_for( & resolver, ev. pid, & ev. comm) ,
140154 provider: None ,
141155 event: AgentEvent :: Egress {
@@ -164,7 +178,7 @@ async fn main() -> anyhow::Result<()> {
164178 llm_meta. clear( ) ; // ponytail: crude cap; LRU if it ever matters
165179 }
166180 llm_meta. insert( sock_key( ev. pid, ev. fd) , ( sni. clone( ) , provider. clone( ) , peer) ) ;
167- exporter. export ( & EnrichedEvent {
181+ emit ( exporter. as_ref ( ) , & mut stats , EnrichedEvent {
168182 identity: identity_for( & resolver, ev. pid, & ev. comm) ,
169183 provider,
170184 event: AgentEvent :: Egress {
@@ -180,7 +194,7 @@ async fn main() -> anyhow::Result<()> {
180194 if let Some ( ev) = read_pod:: <DnsEvent >( & item) {
181195 let len = ( ev. len as usize ) . min( ev. data. len( ) ) ;
182196 if let Some ( query) = parse_dns_qname( & ev. data[ ..len] ) {
183- exporter. export ( & EnrichedEvent {
197+ emit ( exporter. as_ref ( ) , & mut stats , EnrichedEvent {
184198 identity: identity_for( & resolver, ev. pid, & ev. comm) ,
185199 provider: None ,
186200 event: AgentEvent :: Dns { pid: ev. pid, query } ,
@@ -192,7 +206,7 @@ async fn main() -> anyhow::Result<()> {
192206 if let Some ( ev) = read_pod:: <FileEvent >( & item) {
193207 let path = cstr( & ev. path) ;
194208 if !path. is_empty( ) {
195- exporter. export ( & EnrichedEvent {
209+ emit ( exporter. as_ref ( ) , & mut stats , EnrichedEvent {
196210 identity: identity_for( & resolver, ev. pid, & ev. comm) ,
197211 provider: None ,
198212 event: AgentEvent :: FileAccess {
@@ -212,7 +226,7 @@ async fn main() -> anyhow::Result<()> {
212226 llm_meta. remove( & sock_key( ev. pid, ev. fd) )
213227 {
214228 if provider. is_some( ) {
215- exporter. export ( & EnrichedEvent {
229+ emit ( exporter. as_ref ( ) , & mut stats , EnrichedEvent {
216230 identity: identity_for( & resolver, ev. pid, & ev. comm) ,
217231 provider,
218232 event: AgentEvent :: LlmCall {
@@ -279,6 +293,28 @@ fn identity_for(r: &impl IdentityResolver, pid: u32, comm: &[u8; 16]) -> Identit
279293 id
280294}
281295
296+ /// Per-kind event counters for periodic throughput logging (collector operability).
297+ #[ derive( Default ) ]
298+ struct Stats {
299+ exec : u64 ,
300+ egress : u64 ,
301+ dns : u64 ,
302+ file : u64 ,
303+ llm : u64 ,
304+ }
305+
306+ /// Export an event and count it by kind for the throughput report.
307+ fn emit ( exporter : & dyn Exporter , stats : & mut Stats , ev : EnrichedEvent ) {
308+ match & ev. event {
309+ AgentEvent :: ToolExec { .. } => stats. exec += 1 ,
310+ AgentEvent :: Egress { .. } => stats. egress += 1 ,
311+ AgentEvent :: Dns { .. } => stats. dns += 1 ,
312+ AgentEvent :: FileAccess { .. } => stats. file += 1 ,
313+ AgentEvent :: LlmCall { .. } => stats. llm += 1 ,
314+ }
315+ exporter. export ( & ev) ;
316+ }
317+
282318fn peer_ip ( ev : & ConnectEvent ) -> IpAddr {
283319 if ev. family == 2 {
284320 IpAddr :: V4 ( Ipv4Addr :: new (
0 commit comments