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 }