package mtprotoedge // RuntimeSnapshot is a point-in-time, identity-free view of the MTProto edge. // It deliberately exposes only bounded aggregate values so callers can publish // it through a metrics endpoint without leaking auth keys, sessions or remote // addresses. Values from independently locked components can differ by one // concurrent transition; every individual budget/count remains internally // consistent. type RuntimeSnapshot struct { RawConnections int64 RawConnectionLimit int64 Handshakes int64 HandshakeLimit int64 ActiveSessions int64 ProvisionalSessions int64 LogicalSessions int64 OfflineLogicalSessions int64 LogicalOutboxFrames int64 LogicalOutboxBytes int64 PendingPushBytes int64 InboundRPCTasks int64 InboundRPCBytes int64 InboundRPCReadyConnections int64 InboundRPCMaxTasks int64 InboundRPCMaxBytes int64 InboundFrameBytes int64 InboundFrameMaxBytes int64 OutboundTrackedBytes int64 OutboundTrackedMaxBytes int64 OutboundControlBytes int64 OutboundControlMaxBytes int64 OutboundWriteBytes int64 OutboundWriteMaxBytes int64 RPCExecutionOwners int64 RPCExecutionReservedEntries int64 RPCExecutionReceipts int64 RPCExecutionReceiptBudgetBytes int64 RPCExecutionSubscribers int64 } type sessionManagerRuntimeSnapshot struct { active int64 provisional int64 logical int64 offlineLogical int64 frames int64 bytes int64 pendingBytes int64 } func (m *SessionManager) runtimeSnapshot() sessionManagerRuntimeSnapshot { if m == nil { return sessionManagerRuntimeSnapshot{} } // Never hold SessionManager.mu while taking an outbound-state mutex. The // physical actor can publish/retire a Conn next to an outbox transition, and // metrics must not add a new cross-component lock order. m.mu.RLock() states := make([]*outboundState, 0, len(m.logicalSessions)) result := sessionManagerRuntimeSnapshot{ active: int64(len(m.bySession)), provisional: int64(len(m.claims)), logical: int64(len(m.logicalSessions)), } if m.pendingBudget != nil { result.pendingBytes = m.pendingBudget.snapshot() } for _, logical := range m.logicalSessions { if logical == nil { continue } if !logical.offlineAt.IsZero() { result.offlineLogical++ } if logical.outbound != nil { states = append(states, logical.outbound) } } m.mu.RUnlock() for _, state := range states { state.mu.Lock() result.frames += int64(len(state.pending)) result.bytes += int64(state.totalBytes) state.mu.Unlock() } return result } type admissionRuntimeSnapshot struct { connections int64 connectionLimit int64 handshakes int64 handshakeLimit int64 } func (a *admissionController) runtimeSnapshot() admissionRuntimeSnapshot { if a == nil { return admissionRuntimeSnapshot{} } a.mu.Lock() result := admissionRuntimeSnapshot{ connections: int64(a.connections), connectionLimit: int64(a.maxConnections), } a.mu.Unlock() if a.handshakes != nil { result.handshakes = int64(len(a.handshakes)) result.handshakeLimit = int64(cap(a.handshakes)) } return result } type inboundRPCRuntimeSnapshot struct { tasks int64 bytes int64 ready int64 } func (s *inboundRPCScheduler) runtimeSnapshot() inboundRPCRuntimeSnapshot { if s == nil { return inboundRPCRuntimeSnapshot{} } s.budgetMu.Lock() result := inboundRPCRuntimeSnapshot{tasks: int64(s.tasks), bytes: s.bytes} s.budgetMu.Unlock() s.readyMu.Lock() result.ready = int64(s.ready.Len()) s.readyMu.Unlock() return result } // RuntimeSnapshot returns aggregate MTProto ownership and capacity state. func (s *Server) RuntimeSnapshot() RuntimeSnapshot { if s == nil { return RuntimeSnapshot{} } sessions := s.conns.runtimeSnapshot() admission := s.admission.runtimeSnapshot() inbound := s.rpcScheduler.runtimeSnapshot() result := RuntimeSnapshot{ RawConnections: admission.connections, RawConnectionLimit: admission.connectionLimit, Handshakes: admission.handshakes, HandshakeLimit: admission.handshakeLimit, ActiveSessions: sessions.active, ProvisionalSessions: sessions.provisional, LogicalSessions: sessions.logical, OfflineLogicalSessions: sessions.offlineLogical, LogicalOutboxFrames: sessions.frames, LogicalOutboxBytes: sessions.bytes, PendingPushBytes: sessions.pendingBytes, InboundRPCTasks: inbound.tasks, InboundRPCBytes: inbound.bytes, InboundRPCReadyConnections: inbound.ready, } if s.rpcScheduler != nil { result.InboundRPCMaxTasks = int64(s.rpcScheduler.maxTasks) result.InboundRPCMaxBytes = s.rpcScheduler.maxBytes } if s.frameBudget != nil { result.InboundFrameBytes = s.frameBudget.usedBytes() result.InboundFrameMaxBytes = s.frameBudget.max } if s.outboundTrackedBudget != nil { result.OutboundTrackedBytes = s.outboundTrackedBudget.snapshot() result.OutboundTrackedMaxBytes = s.outboundTrackedBudget.maxBytes } if s.outboundControlBudget != nil { result.OutboundControlBytes = s.outboundControlBudget.snapshot() result.OutboundControlMaxBytes = s.outboundControlBudget.maxBytes } if s.outboundScratchPool != nil && s.outboundScratchPool.budget != nil { result.OutboundWriteBytes = s.outboundScratchPool.snapshot() result.OutboundWriteMaxBytes = s.outboundScratchPool.budget.maxBytes } if s.rpcResults != nil { result.RPCExecutionOwners = s.rpcResults.flightLimit.snapshot() result.RPCExecutionReservedEntries = s.rpcResults.reservedEntries.snapshot() result.RPCExecutionReceipts = s.rpcResults.receiptCount.Load() result.RPCExecutionReceiptBudgetBytes = s.rpcResults.receiptBudgetBytes() if s.rpcResults.subscriberBudget != nil { result.RPCExecutionSubscribers = s.rpcResults.subscriberBudget.global.snapshot() } } return result }