@@ -16,7 +16,9 @@ import (
1616 "time"
1717
1818 "github.com/gofrs/uuid"
19+ "github.com/golang/protobuf/ptypes"
1920 "github.com/gorilla/websocket"
21+ "github.com/patrickmn/go-cache"
2022 "github.com/pkg/errors"
2123 log "github.com/sirupsen/logrus"
2224
@@ -43,9 +45,10 @@ type Backend struct {
4345 scheme string
4446 isClosed bool
4547
46- pingInterval time.Duration
47- readTimeout time.Duration
48- writeTimeout time.Duration
48+ statsInterval time.Duration
49+ pingInterval time.Duration
50+ readTimeout time.Duration
51+ writeTimeout time.Duration
4952
5053 gateways gateways
5154
@@ -62,10 +65,11 @@ type Backend struct {
6265 frequencyMax uint32
6366 routerConfig structs.RouterConfig
6467
65- // diidMap stores the mapping of diid to UUIDs. This should take ~ 1MB of
66- // memory. Optionaly this could be optimized by letting keys expire after
67- // a given time.
68- diidMap map [uint16 ][]byte
68+ // Cache to store stats.
69+ statsCache * cache.Cache
70+
71+ // Cache to store diid to UUIDs.
72+ diidCache * cache.Cache
6973}
7074
7175// NewBackend creates a new Backend.
@@ -83,15 +87,17 @@ func NewBackend(conf config.Config) (*Backend, error) {
8387 gatewayStatsChan : make (chan gw.GatewayStats ),
8488 rawPacketForwarderEventChan : make (chan gw.RawPacketForwarderEvent ),
8589
86- pingInterval : conf .Backend .BasicStation .PingInterval ,
87- readTimeout : conf .Backend .BasicStation .ReadTimeout ,
88- writeTimeout : conf .Backend .BasicStation .WriteTimeout ,
90+ statsInterval : conf .Backend .BasicStation .StatsInterval ,
91+ pingInterval : conf .Backend .BasicStation .PingInterval ,
92+ readTimeout : conf .Backend .BasicStation .ReadTimeout ,
93+ writeTimeout : conf .Backend .BasicStation .WriteTimeout ,
8994
9095 region : band .Name (conf .Backend .BasicStation .Region ),
9196 frequencyMin : conf .Backend .BasicStation .FrequencyMin ,
9297 frequencyMax : conf .Backend .BasicStation .FrequencyMax ,
9398
94- diidMap : make (map [uint16 ][]byte ),
99+ diidCache : cache .New (time .Minute , time .Minute ),
100+ statsCache : cache .New (conf .Backend .BasicStation .StatsInterval * 2 , conf .Backend .BasicStation .StatsInterval * 2 ),
95101 }
96102
97103 for _ , n := range conf .Filters .NetIDs {
@@ -238,8 +244,10 @@ func (b *Backend) SendDownlinkFrame(df gw.DownlinkFrame) error {
238244 copy (gatewayID [:], df .GetGatewayId ())
239245 copy (downID [:], df .GetDownlinkId ())
240246
247+ b .incrementTxStats (gatewayID )
248+
241249 // store token to UUID mapping
242- b .diidMap [ uint16 ( df .Token )] = df .GetDownlinkId ()
250+ b .diidCache . SetDefault ( fmt . Sprintf ( "%d" , df .Token ), df .GetDownlinkId () )
243251
244252 websocketSendCounter ("dnmsg" ).Inc ()
245253 if err := b .sendToGateway (gatewayID , pl ); err != nil {
@@ -382,15 +390,69 @@ func (b *Backend) handleGateway(r *http.Request, c *websocket.Conn) {
382390 "remote_addr" : r .RemoteAddr ,
383391 }).Info ("backend/basicstation: gateway connected" )
384392
393+ done := make (chan struct {})
394+
385395 // remove the gateway on return
386396 defer func () {
397+ done <- struct {}{}
387398 b .gateways .remove (gatewayID )
388399 log .WithFields (log.Fields {
389400 "gateway_id" : gatewayID ,
390401 "remote_addr" : r .RemoteAddr ,
391402 }).Info ("backend/basicstation: gateway disconnected" )
392403 }()
393404
405+ statsTicker := time .NewTicker (b .statsInterval )
406+ defer statsTicker .Stop ()
407+
408+ // stats publishing loop
409+ go func () {
410+ gwIDStr := gatewayID .String ()
411+
412+ for {
413+ select {
414+ case <- statsTicker .C :
415+ id , err := uuid .NewV4 ()
416+ if err != nil {
417+ log .WithError (err ).Error ("backend/basicstation: new uuid error" )
418+ continue
419+ }
420+
421+ var rx , rxOK , tx , txOK uint32
422+ if v , ok := b .statsCache .Get (gwIDStr + ":rx" ); ok {
423+ rx = v .(uint32 )
424+ }
425+ if v , ok := b .statsCache .Get (gwIDStr + ":rxOK" ); ok {
426+ rxOK = v .(uint32 )
427+ }
428+ if v , ok := b .statsCache .Get (gwIDStr + ":tx" ); ok {
429+ tx = v .(uint32 )
430+ }
431+ if v , ok := b .statsCache .Get (gwIDStr + ":txOK" ); ok {
432+ txOK = v .(uint32 )
433+ }
434+
435+ b .statsCache .Delete (gwIDStr + ":rx" )
436+ b .statsCache .Delete (gwIDStr + ":rxOK" )
437+ b .statsCache .Delete (gwIDStr + ":tx" )
438+ b .statsCache .Delete (gwIDStr + ":txOK" )
439+
440+ b .gatewayStatsChan <- gw.GatewayStats {
441+ GatewayId : gatewayID [:],
442+ Time : ptypes .TimestampNow (),
443+ StatsId : id [:],
444+ RxPacketsReceived : rx ,
445+ RxPacketsReceivedOk : rxOK ,
446+ TxPacketsReceived : tx ,
447+ TxPacketsEmitted : txOK ,
448+ }
449+ case <- done :
450+ return
451+ }
452+ }
453+
454+ }()
455+
394456 // receive data
395457 for {
396458 mt , msg , err := c .ReadMessage ()
@@ -447,6 +509,7 @@ func (b *Backend) handleGateway(r *http.Request, c *websocket.Conn) {
447509 b .handleVersion (gatewayID , pl )
448510 case structs .UplinkDataFrameMessage :
449511 // handle uplink
512+ b .incrementRxStats (gatewayID )
450513 var pl structs.UplinkDataFrame
451514 if err := json .Unmarshal (msg , & pl ); err != nil {
452515 log .WithError (err ).WithFields (log.Fields {
@@ -459,6 +522,7 @@ func (b *Backend) handleGateway(r *http.Request, c *websocket.Conn) {
459522 b .handleUplinkDataFrame (gatewayID , pl )
460523 case structs .JoinRequestMessage :
461524 // handle join-request
525+ b .incrementRxStats (gatewayID )
462526 var pl structs.JoinRequest
463527 if err := json .Unmarshal (msg , & pl ); err != nil {
464528 log .WithError (err ).WithFields (log.Fields {
@@ -471,6 +535,7 @@ func (b *Backend) handleGateway(r *http.Request, c *websocket.Conn) {
471535 b .handleJoinRequest (gatewayID , pl )
472536 case structs .ProprietaryDataFrameMessage :
473537 // handle proprietary uplink
538+ b .incrementRxStats (gatewayID )
474539 var pl structs.UplinkProprietaryFrame
475540 if err := json .Unmarshal (msg , & pl ); err != nil {
476541 log .WithError (err ).WithFields (log.Fields {
@@ -483,6 +548,7 @@ func (b *Backend) handleGateway(r *http.Request, c *websocket.Conn) {
483548 b .handleProprietaryDataFrame (gatewayID , pl )
484549 case structs .DownlinkTransmittedMessage :
485550 // handle downlink transmitted
551+ b .incrementTxOkStats (gatewayID )
486552 var pl structs.DownlinkTransmitted
487553 if err := json .Unmarshal (msg , & pl ); err != nil {
488554 log .WithError (err ).WithFields (log.Fields {
@@ -584,7 +650,10 @@ func (b *Backend) handleDownlinkTransmittedMessage(gatewayID lorawan.EUI64, v st
584650 }).Error ("backend/basicstation: error converting downlink transmitted to protobuf message" )
585651 return
586652 }
587- txack .DownlinkId = b .diidMap [uint16 (v .DIID )]
653+
654+ if v , ok := b .diidCache .Get (fmt .Sprintf ("%d" , v .DIID )); ok {
655+ txack .DownlinkId = v .([]byte )
656+ }
588657
589658 var downID uuid.UUID
590659 copy (downID [:], txack .GetDownlinkId ())
@@ -723,3 +792,31 @@ func (b *Backend) websocketWrap(handler func(*http.Request, *websocket.Conn), w
723792 handler (r , conn )
724793 done <- struct {}{}
725794}
795+
796+ func (b * Backend ) incrementRxStats (id lorawan.EUI64 ) {
797+ idStr := id .String ()
798+
799+ if _ , err := b .statsCache .IncrementUint32 (idStr + ":rx" , uint32 (1 )); err != nil {
800+ b .statsCache .SetDefault (idStr + ":rx" , uint32 (1 ))
801+ }
802+
803+ if _ , err := b .statsCache .IncrementUint32 (idStr + ":rxOK" , uint32 (1 )); err != nil {
804+ b .statsCache .SetDefault (idStr + ":rxOK" , uint32 (1 ))
805+ }
806+ }
807+
808+ func (b * Backend ) incrementTxOkStats (id lorawan.EUI64 ) {
809+ idStr := id .String ()
810+
811+ if _ , err := b .statsCache .IncrementUint32 (idStr + "txOK" , uint32 (1 )); err != nil {
812+ b .statsCache .SetDefault (idStr + ":txOK" , uint32 (1 ))
813+ }
814+ }
815+
816+ func (b * Backend ) incrementTxStats (id lorawan.EUI64 ) {
817+ idStr := id .String ()
818+
819+ if _ , err := b .statsCache .IncrementUint32 (idStr + "tx" , uint32 (1 )); err != nil {
820+ b .statsCache .SetDefault (idStr + ":tx" , uint32 (1 ))
821+ }
822+ }
0 commit comments