github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/system/mqtt/admin/store.go (about)

     1  // This file is part of the Smart Home
     2  // Program complex distribution https://github.com/e154/smart-home
     3  // Copyright (C) 2016-2023, Filippov Alex
     4  //
     5  // This library is free software: you can redistribute it and/or
     6  // modify it under the terms of the GNU Lesser General Public
     7  // License as published by the Free Software Foundation; either
     8  // version 3 of the License, or (at your option) any later version.
     9  //
    10  // This library is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    13  // Library General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Lesser General Public
    16  // License along with this library.  If not, see
    17  // <https://www.gnu.org/licenses/>.
    18  
    19  package admin
    20  
    21  import (
    22  	"container/list"
    23  	"strings"
    24  	"sync"
    25  	"time"
    26  
    27  	"github.com/DrmagicE/gmqtt"
    28  	"github.com/DrmagicE/gmqtt/config"
    29  	"github.com/DrmagicE/gmqtt/persistence/subscription"
    30  	"github.com/DrmagicE/gmqtt/server"
    31  	"github.com/e154/smart-home/common"
    32  )
    33  
    34  type store struct {
    35  	clientMu       sync.RWMutex
    36  	clientIndexer  *Indexer
    37  	subMu          sync.RWMutex
    38  	subIndexer     *Indexer
    39  	config         config.Config
    40  	statsReader    server.StatsReader
    41  	subStatsReader subscription.StatsReader
    42  	clientService  server.ClientService
    43  }
    44  
    45  func newStore(statsReader server.StatsReader,
    46  	subStatsReader subscription.StatsReader,
    47  	clientService server.ClientService) *store {
    48  	return &store{
    49  		clientIndexer:  NewIndexer(),
    50  		subIndexer:     NewIndexer(),
    51  		statsReader:    statsReader,
    52  		subStatsReader: subStatsReader,
    53  		clientService:  clientService,
    54  	}
    55  }
    56  
    57  func (s *store) addSubscription(clientID string, sub *gmqtt.Subscription) {
    58  	s.subMu.Lock()
    59  	defer s.subMu.Unlock()
    60  
    61  	subInfo := &SubscriptionInfo{
    62  		TopicName:         sub.GetFullTopicName(),
    63  		Id:                sub.ID,
    64  		Qos:               uint32(sub.QoS),
    65  		NoLocal:           sub.NoLocal,
    66  		RetainAsPublished: sub.RetainAsPublished,
    67  		RetainHandling:    uint32(sub.RetainHandling),
    68  		ClientID:          clientID,
    69  	}
    70  	key := clientID + "_" + sub.GetFullTopicName()
    71  	s.subIndexer.Set(key, subInfo)
    72  
    73  }
    74  
    75  func (s *store) removeSubscription(clientID string, topicName string) {
    76  	s.subMu.Lock()
    77  	defer s.subMu.Unlock()
    78  	s.subIndexer.Remove(clientID + "_" + topicName)
    79  }
    80  
    81  func (s *store) addClient(client server.Client) {
    82  	c := newClientInfo(client)
    83  	s.clientMu.Lock()
    84  	s.clientIndexer.Set(c.ClientID, c)
    85  	s.clientMu.Unlock()
    86  }
    87  
    88  func (s *store) setClientDisconnected(clientID string) {
    89  	s.clientMu.Lock()
    90  	defer s.clientMu.Unlock()
    91  	l := s.clientIndexer.GetByID(clientID)
    92  	if l == nil {
    93  		return
    94  	}
    95  	l.Value.(*ClientInfo).DisconnectedAt = common.Time(time.Now())
    96  }
    97  
    98  func (s *store) removeClient(clientID string) {
    99  	s.clientMu.Lock()
   100  	s.clientIndexer.Remove(clientID)
   101  	s.clientMu.Unlock()
   102  }
   103  
   104  // GetClientByID returns the client information for the given client id.
   105  func (s *store) GetClientByID(clientID string) *ClientInfo {
   106  	s.clientMu.RLock()
   107  	defer s.clientMu.RUnlock()
   108  	c := s.getClientByIDLocked(clientID)
   109  	fillClientInfo(c, s.statsReader)
   110  	return c
   111  }
   112  
   113  func (s *store) getClientByIDLocked(clientID string) *ClientInfo {
   114  	if i := s.clientIndexer.GetByID(clientID); i != nil {
   115  		return i.Value.(*ClientInfo)
   116  	}
   117  	return nil
   118  }
   119  
   120  func fillClientInfo(c *ClientInfo, stsReader server.StatsReader) {
   121  	if c == nil {
   122  		return
   123  	}
   124  	sts, ok := stsReader.GetClientStats(c.ClientID)
   125  	if !ok {
   126  		return
   127  	}
   128  	c.SubscriptionsCurrent = uint32(sts.SubscriptionStats.SubscriptionsCurrent)
   129  	c.SubscriptionsTotal = uint32(sts.SubscriptionStats.SubscriptionsTotal)
   130  	c.PacketsReceivedBytes = sts.PacketStats.BytesReceived.Total
   131  	c.PacketsReceivedNums = sts.PacketStats.ReceivedTotal.Total
   132  	c.PacketsSendBytes = sts.PacketStats.BytesSent.Total
   133  	c.PacketsSendNums = sts.PacketStats.SentTotal.Total
   134  	c.MessageDropped = sts.MessageStats.GetDroppedTotal()
   135  	c.InflightLen = uint32(sts.MessageStats.InflightCurrent)
   136  	c.QueueLen = uint32(sts.MessageStats.QueuedCurrent)
   137  }
   138  
   139  // GetClients ...
   140  func (s *store) GetClients(limit, offset uint) (rs []*ClientInfo, total uint32, err error) {
   141  	rs = make([]*ClientInfo, 0)
   142  	fn := func(elem *list.Element) {
   143  		c := elem.Value.(*ClientInfo)
   144  		fillClientInfo(c, s.statsReader)
   145  		rs = append(rs, elem.Value.(*ClientInfo))
   146  	}
   147  	s.clientMu.RLock()
   148  	defer s.clientMu.RUnlock()
   149  	s.clientIndexer.Iterate(fn, offset, limit)
   150  	return rs, uint32(s.clientIndexer.Len()), nil
   151  }
   152  
   153  func (s *store) newSessionInfo(client server.Client, c config.Config) *SessionInfo {
   154  	optsReader := client.ClientOptions()
   155  	stats, _ := s.statsReader.GetClientStats(client.ClientOptions().ClientID)
   156  	subStats, _ := s.subStatsReader.GetClientStats(optsReader.ClientID)
   157  	rs := &SessionInfo{
   158  		ClientID: optsReader.ClientID,
   159  		Status:   statusText(client),
   160  		//CleanSession:          optsReader.CleanSession(),
   161  		Subscriptions: subStats.SubscriptionsCurrent,
   162  		MaxInflight:   c.MQTT.MaxInflight,
   163  		InflightLen:   stats.MessageStats.InflightCurrent,
   164  		MaxMsgQueue:   c.MQTT.MaxQueuedMsg,
   165  		MsgQueueLen:   stats.MessageStats.QueuedCurrent,
   166  		//AwaitRelLen:           stats.AwaitRelCurrent,
   167  		Qos0MsgDroppedTotal:   stats.MessageStats.Qos0.DroppedTotal.QueueFull,
   168  		Qos1MsgDroppedTotal:   stats.MessageStats.Qos1.DroppedTotal.QueueFull,
   169  		Qos2MsgDroppedTotal:   stats.MessageStats.Qos2.DroppedTotal.QueueFull,
   170  		Qos0MsgDeliveredTotal: stats.MessageStats.Qos0.SentTotal,
   171  		Qos1MsgDeliveredTotal: stats.MessageStats.Qos1.SentTotal,
   172  		Qos2MsgDeliveredTotal: stats.MessageStats.Qos2.SentTotal,
   173  		ConnectedAt:           common.Time(client.ConnectedAt()),
   174  		//DisconnectedAt:        client.DisconnectedAt(),
   175  	}
   176  	return rs
   177  }
   178  
   179  // GetSessionByID ...
   180  func (s *store) GetSessionByID(clientID string) (*SessionInfo, error) {
   181  	client := s.GetClientByID(clientID)
   182  	if client == nil {
   183  		return nil, ErrNotFound
   184  	}
   185  	return s.newSessionInfo(s.clientService.GetClient(clientID), s.config), nil
   186  }
   187  
   188  // GetClientSubscriptions ...
   189  func (s *store) GetClientSubscriptions(clientID string, offset, n uint) ([]*SubscriptionInfo, int, error) {
   190  	s.subMu.Lock()
   191  	defer s.subMu.Unlock()
   192  	rs := make([]*SubscriptionInfo, 0)
   193  	var err error
   194  	var total int
   195  	fn := func(elem *list.Element) {
   196  		info := elem.Value.(*SubscriptionInfo)
   197  		if info.ClientID != clientID {
   198  			return
   199  		}
   200  		rs = append(rs, info)
   201  	}
   202  	s.subIndexer.Iterate(fn, offset, n)
   203  	total = s.subIndexer.Len()
   204  	return rs, total, err
   205  }
   206  
   207  // SearchTopic ...
   208  func (s *store) SearchTopic(query string) (rs []*SubscriptionInfo, err error) {
   209  	s.subMu.Lock()
   210  	defer s.subMu.Unlock()
   211  	rs = make([]*SubscriptionInfo, 0)
   212  	var info *SubscriptionInfo
   213  	fn := func(elem *list.Element) {
   214  		info = elem.Value.(*SubscriptionInfo)
   215  		if !strings.Contains(info.TopicName, query) {
   216  			return
   217  		}
   218  		rs = append(rs, info)
   219  	}
   220  	s.subIndexer.Iterate(fn, 0, 999)
   221  	return
   222  }
   223  
   224  // GetSessions ...
   225  func (s *store) GetSessions(offset, n uint) ([]*SessionInfo, int, error) {
   226  	rs := make([]*SessionInfo, 0)
   227  	fn := func(elem *list.Element) {
   228  		c := elem.Value.(*ClientInfo)
   229  		fillClientInfo(c, s.statsReader)
   230  		rs = append(rs, s.newSessionInfo(s.clientService.GetClient(c.ClientID), s.config))
   231  	}
   232  	s.clientMu.RLock()
   233  	defer s.clientMu.RUnlock()
   234  	s.clientIndexer.Iterate(fn, offset, n)
   235  	total := s.clientIndexer.Len()
   236  	return rs, total, nil
   237  }