github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kbfs/libpages/stathat.go (about)

     1  // Copyright 2018 Keybase Inc. All rights reserved.
     2  // Use of this source code is governed by a BSD
     3  // license that can be found in the LICENSE file.
     4  
     5  package libpages
     6  
     7  import (
     8  	"strconv"
     9  	"strings"
    10  	"time"
    11  
    12  	stathat "github.com/stathat/go"
    13  	"go.uber.org/zap"
    14  )
    15  
    16  type stathatReporter struct {
    17  	logger   *zap.Logger
    18  	ezKey    string
    19  	reporter stathat.Reporter
    20  
    21  	statNameRequests      string
    22  	statNameAuthenticated string
    23  	statNameCloningShown  string
    24  	statNameInvalidConfig string
    25  	statPrefixProto       string
    26  	statPrefixStatus      string
    27  	statPrefixTlfType     string
    28  	statPrefixRootType    string
    29  
    30  	activityStats         ActivityStatsEnabler
    31  	statPrefixActiveHosts string
    32  	statPrefixActiveTlfs  string
    33  }
    34  
    35  func (s *stathatReporter) activityStatsReportLoop() {
    36  	if len(s.activityStats.Durations) == 0 || s.activityStats.Interval == 0 {
    37  		return
    38  	}
    39  
    40  	durations := make([]NameableDuration, len(s.activityStats.Durations))
    41  	copy(durations, s.activityStats.Durations)
    42  	statNamesHosts := make([]string, 0, len(durations))
    43  	statNamesTlfs := make([]string, 0, len(durations))
    44  	for _, d := range durations {
    45  		statNamesHosts = append(statNamesHosts,
    46  			s.statPrefixActiveHosts+"("+d.String()+")")
    47  		statNamesTlfs = append(statNamesTlfs,
    48  			s.statPrefixActiveTlfs+"("+d.String()+")")
    49  	}
    50  	reportTicker := time.NewTicker(s.activityStats.Interval)
    51  	defer reportTicker.Stop()
    52  
    53  	for range reportTicker.C {
    54  		getter, err := s.activityStats.Storer.GetActivesGetter()
    55  		if err != nil {
    56  			s.logger.Warn("GetActivesGetter", zap.Error(err))
    57  			continue
    58  		}
    59  		for i, d := range durations {
    60  			tlfs, hosts, err := getter.GetActives(d.Duration)
    61  			if err != nil {
    62  				s.logger.Warn("GetActives", zap.Error(err))
    63  			}
    64  			if err = s.reporter.PostEZValue(statNamesTlfs[i], s.ezKey,
    65  				float64(tlfs)); err != nil {
    66  				s.logger.Warn("PostEZValue", zap.Error(err))
    67  			}
    68  			if err = s.reporter.PostEZValue(statNamesHosts[i], s.ezKey,
    69  				float64(hosts)); err != nil {
    70  				s.logger.Warn("PostEZValue", zap.Error(err))
    71  			}
    72  		}
    73  	}
    74  }
    75  
    76  var _ StatsReporter = (*stathatReporter)(nil)
    77  
    78  const stathatReportInterval = time.Second * 10
    79  
    80  // NewStathatReporter create a new StatsReporter that reports stats to stathat.
    81  // If enableActivityBasedStats, if set to non-nil, causes the reporter to
    82  // generate activity-based stats. Caller should not modify
    83  // enableActivityBasedStats passed into this function.
    84  func NewStathatReporter(logger *zap.Logger, prefix string, ezKey string,
    85  	enableActivityBasedStats *ActivityStatsEnabler) StatsReporter {
    86  	if len(ezKey) == 0 {
    87  		return &stathatReporter{}
    88  	}
    89  
    90  	enabler := enableActivityBasedStats
    91  	if enabler == nil {
    92  		enabler = &ActivityStatsEnabler{
    93  			Storer:    nullActivityStatsStorer{},
    94  			Durations: nil,
    95  		}
    96  	}
    97  
    98  	prefix = strings.TrimSpace(prefix) + " "
    99  	reporter := &stathatReporter{
   100  		logger: logger,
   101  		ezKey:  ezKey,
   102  		reporter: stathat.NewBatchReporter(
   103  			stathat.DefaultReporter, stathatReportInterval),
   104  
   105  		statNameRequests:      prefix + "requests",
   106  		statNameAuthenticated: prefix + "authenticated",
   107  		statNameCloningShown:  prefix + "cloningShown",
   108  		statNameInvalidConfig: prefix + "invalidConfig",
   109  		statPrefixProto:       prefix + "proto:",
   110  		statPrefixStatus:      prefix + "status:",
   111  		statPrefixTlfType:     prefix + "tlfType:",
   112  		statPrefixRootType:    prefix + "rootType:",
   113  
   114  		activityStats:         *enabler,
   115  		statPrefixActiveHosts: prefix + "activeHosts:",
   116  		statPrefixActiveTlfs:  prefix + "activeTlfs:",
   117  	}
   118  	go reporter.activityStatsReportLoop()
   119  	return reporter
   120  }
   121  
   122  func (s *stathatReporter) postCountOneOrLog(statName string) {
   123  	if err := s.reporter.PostEZCountOne(statName, s.ezKey); err != nil {
   124  		s.logger.Warn("PostEZCountOne", zap.Error(err))
   125  	}
   126  }
   127  
   128  // ReportServedRequest implementes the StatsReporter interface.
   129  func (s *stathatReporter) ReportServedRequest(sri *ServedRequestInfo) {
   130  	s.postCountOneOrLog(s.statNameRequests)
   131  	s.postCountOneOrLog(s.statPrefixProto + sri.Proto)
   132  	s.postCountOneOrLog(s.statPrefixStatus + strconv.Itoa(sri.HTTPStatus))
   133  	if sri.Authenticated {
   134  		s.postCountOneOrLog(s.statNameAuthenticated)
   135  	}
   136  	if sri.CloningShown {
   137  		s.postCountOneOrLog(s.statNameCloningShown)
   138  	}
   139  	if sri.InvalidConfig {
   140  		s.postCountOneOrLog(s.statNameInvalidConfig)
   141  	}
   142  	s.postCountOneOrLog(s.statPrefixTlfType + sri.TlfType.String())
   143  	s.postCountOneOrLog(s.statPrefixRootType + sri.RootType.String())
   144  
   145  	s.activityStats.Storer.RecordActives(sri.TlfID, sri.Host)
   146  }