github.com/aporeto-inc/trireme-lib@v10.358.0+incompatible/controller/pkg/remoteenforcer/internal/client/statsclient/client.go (about) 1 package statsclient 2 3 import ( 4 "context" 5 "errors" 6 "os" 7 "time" 8 9 "go.aporeto.io/enforcerd/trireme-lib/collector" 10 "go.aporeto.io/enforcerd/trireme-lib/controller/constants" 11 "go.aporeto.io/enforcerd/trireme-lib/controller/internal/enforcer/utils/rpcwrapper" 12 "go.aporeto.io/enforcerd/trireme-lib/controller/pkg/remoteenforcer/internal/client" 13 "go.aporeto.io/enforcerd/trireme-lib/controller/pkg/remoteenforcer/internal/statscollector" 14 "go.uber.org/zap" 15 ) 16 17 const ( 18 defaultStatsIntervalMiliseconds = 1000 19 defaultUserRetention = 10 20 statsContextID = "UNUSED" 21 statsRPCCommand = "ProxyRPCServer.PostStats" 22 ) 23 24 // statsClient This is the struct for storing state for the rpc client 25 // which reports flow stats back to the controller process 26 type statsClient struct { 27 collector statscollector.Collector 28 rpchdl *rpcwrapper.RPCWrapper 29 secret string 30 statsChannel string 31 statsInterval time.Duration 32 userRetention time.Duration 33 stop chan bool 34 } 35 36 // NewStatsClient initializes a new stats client 37 func NewStatsClient(cr statscollector.Collector) (client.Reporter, error) { 38 39 sc := &statsClient{ 40 collector: cr, 41 rpchdl: rpcwrapper.NewRPCWrapper(), 42 secret: os.Getenv(constants.EnvStatsSecret), 43 statsChannel: os.Getenv(constants.EnvStatsChannel), 44 statsInterval: defaultStatsIntervalMiliseconds * time.Millisecond, 45 userRetention: defaultUserRetention * time.Minute, 46 stop: make(chan bool), 47 } 48 49 if sc.statsChannel == "" { 50 return nil, errors.New("no path to stats socket provided") 51 } 52 53 if sc.secret == "" { 54 return nil, errors.New("no secret provided for stats channel") 55 } 56 57 return sc, nil 58 } 59 60 // sendStats async function which makes a rpc call to send stats every STATS_INTERVAL 61 func (s *statsClient) sendStats(ctx context.Context) { 62 63 ticker := time.NewTicker(s.statsInterval) 64 userTicker := time.NewTicker(s.userRetention) 65 // nolint: gosimple 66 for { 67 select { 68 case <-ticker.C: 69 70 flows := s.collector.GetFlowRecords() 71 users := s.collector.GetUserRecords() 72 if flows == nil && users == nil { 73 continue 74 } 75 76 s.sendRequest(flows, users) 77 case <-userTicker.C: 78 s.collector.FlushUserCache() 79 case <-ctx.Done(): 80 return 81 } 82 } 83 84 } 85 86 func (s *statsClient) sendRequest(flows map[uint64]*collector.FlowRecord, users map[string]*collector.UserRecord) { 87 88 request := rpcwrapper.Request{ 89 Payload: &rpcwrapper.StatsPayload{ 90 Flows: flows, 91 Users: users, 92 }, 93 } 94 95 if err := s.rpchdl.RemoteCall( 96 statsContextID, 97 statsRPCCommand, 98 &request, 99 &rpcwrapper.Response{}, 100 ); err != nil { 101 zap.L().Error("RPC failure in sending statistics: Unable to send flows", zap.Error(err)) 102 } 103 } 104 105 // Send sends all the stats from the cache 106 func (s *statsClient) Send() error { 107 108 flows := s.collector.GetFlowRecords() 109 users := s.collector.GetUserRecords() 110 if flows == nil && users == nil { 111 zap.L().Debug("Flows and UserRecords are nil while sending stats to collector") 112 return nil 113 } 114 115 s.sendRequest(flows, users) 116 return nil 117 } 118 119 // Start This is an private function called by the remoteenforcer to connect back 120 // to the controller over a stats channel 121 func (s *statsClient) Run(ctx context.Context) error { 122 if err := s.rpchdl.NewRPCClient(statsContextID, s.statsChannel, s.secret); err != nil { 123 zap.L().Error("Stats RPC client cannot connect", zap.Error(err)) 124 return err 125 } 126 127 go s.sendStats(ctx) 128 129 return nil 130 }