github.com/crowdsecurity/crowdsec@v1.6.1/pkg/apiserver/apic_metrics.go (about)

     1  package apiserver
     2  
     3  import (
     4  	"context"
     5  	"time"
     6  
     7  	log "github.com/sirupsen/logrus"
     8  	"slices"
     9  
    10  	"github.com/crowdsecurity/go-cs-lib/ptr"
    11  	"github.com/crowdsecurity/go-cs-lib/trace"
    12  	"github.com/crowdsecurity/go-cs-lib/version"
    13  
    14  	"github.com/crowdsecurity/crowdsec/pkg/models"
    15  )
    16  
    17  func (a *apic) GetMetrics() (*models.Metrics, error) {
    18  	machines, err := a.dbClient.ListMachines()
    19  	if err != nil {
    20  		return nil, err
    21  	}
    22  
    23  	machinesInfo := make([]*models.MetricsAgentInfo, len(machines))
    24  
    25  	for i, machine := range machines {
    26  		machinesInfo[i] = &models.MetricsAgentInfo{
    27  			Version:    machine.Version,
    28  			Name:       machine.MachineId,
    29  			LastUpdate: machine.UpdatedAt.Format(time.RFC3339),
    30  			LastPush:   ptr.OrEmpty(machine.LastPush).Format(time.RFC3339),
    31  		}
    32  	}
    33  
    34  	bouncers, err := a.dbClient.ListBouncers()
    35  	if err != nil {
    36  		return nil, err
    37  	}
    38  
    39  	bouncersInfo := make([]*models.MetricsBouncerInfo, len(bouncers))
    40  
    41  	for i, bouncer := range bouncers {
    42  		bouncersInfo[i] = &models.MetricsBouncerInfo{
    43  			Version:    bouncer.Version,
    44  			CustomName: bouncer.Name,
    45  			Name:       bouncer.Type,
    46  			LastPull:   bouncer.LastPull.Format(time.RFC3339),
    47  		}
    48  	}
    49  
    50  	return &models.Metrics{
    51  		ApilVersion: ptr.Of(version.String()),
    52  		Machines:    machinesInfo,
    53  		Bouncers:    bouncersInfo,
    54  	}, nil
    55  }
    56  
    57  func (a *apic) fetchMachineIDs() ([]string, error) {
    58  	machines, err := a.dbClient.ListMachines()
    59  	if err != nil {
    60  		return nil, err
    61  	}
    62  
    63  	ret := make([]string, len(machines))
    64  	for i, machine := range machines {
    65  		ret[i] = machine.MachineId
    66  	}
    67  	// sorted slices are required for the slices.Equal comparison
    68  	slices.Sort(ret)
    69  
    70  	return ret, nil
    71  }
    72  
    73  // SendMetrics sends metrics to the API server until it receives a stop signal.
    74  //
    75  // Metrics are sent at start, then at the randomized metricsIntervalFirst,
    76  // then at regular metricsInterval. If a change is detected in the list
    77  // of machines, the next metrics are sent immediately.
    78  func (a *apic) SendMetrics(stop chan (bool)) {
    79  	defer trace.CatchPanic("lapi/metricsToAPIC")
    80  
    81  	// verify the list of machines every <checkInt> interval
    82  	const checkInt = 20 * time.Second
    83  
    84  	// intervals must always be > 0
    85  	metInts := []time.Duration{1 * time.Millisecond, a.metricsIntervalFirst, a.metricsInterval}
    86  
    87  	log.Infof("Start sending metrics to CrowdSec Central API (interval: %s once, then %s)",
    88  		metInts[1].Round(time.Second), metInts[2])
    89  
    90  	count := -1
    91  	nextMetInt := func() time.Duration {
    92  		if count < len(metInts)-1 {
    93  			count++
    94  		}
    95  
    96  		return metInts[count]
    97  	}
    98  
    99  	machineIDs := []string{}
   100  
   101  	reloadMachineIDs := func() {
   102  		ids, err := a.fetchMachineIDs()
   103  		if err != nil {
   104  			log.Debugf("unable to get machines (%s), will retry", err)
   105  
   106  			return
   107  		}
   108  
   109  		machineIDs = ids
   110  	}
   111  
   112  	// store the list of machine IDs to compare
   113  	// with the next list
   114  	reloadMachineIDs()
   115  
   116  	checkTicker := time.NewTicker(checkInt)
   117  	metTicker := time.NewTicker(nextMetInt())
   118  
   119  	for {
   120  		select {
   121  		case <-stop:
   122  			checkTicker.Stop()
   123  			metTicker.Stop()
   124  
   125  			return
   126  		case <-checkTicker.C:
   127  			oldIDs := machineIDs
   128  
   129  			reloadMachineIDs()
   130  
   131  			if !slices.Equal(oldIDs, machineIDs) {
   132  				log.Infof("capi metrics: machines changed, immediate send")
   133  				metTicker.Reset(1 * time.Millisecond)
   134  			}
   135  		case <-metTicker.C:
   136  			metTicker.Stop()
   137  
   138  			metrics, err := a.GetMetrics()
   139  			if err != nil {
   140  				log.Errorf("unable to get metrics (%s)", err)
   141  			}
   142  			// metrics are nil if they could not be retrieved
   143  			if metrics != nil {
   144  				log.Info("capi metrics: sending")
   145  
   146  				_, _, err = a.apiClient.Metrics.Add(context.Background(), metrics)
   147  				if err != nil {
   148  					log.Errorf("capi metrics: failed: %s", err)
   149  				}
   150  			}
   151  
   152  			metTicker.Reset(nextMetInt())
   153  		case <-a.metricsTomb.Dying(): // if one apic routine is dying, do we kill the others?
   154  			checkTicker.Stop()
   155  			metTicker.Stop()
   156  			a.pullTomb.Kill(nil)
   157  			a.pushTomb.Kill(nil)
   158  
   159  			return
   160  		}
   161  	}
   162  }