Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1.0 fixes, adds ability to monitor multiple servers, and future save file #22

Merged
merged 55 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
00ab2c0
FEATURE: start work on being able to monitor multiple servers
featheredtoast Oct 19, 2024
ce0f1ac
WIP: updating collections to also put out labels for url and save_name
featheredtoast Oct 19, 2024
5476a6c
first pass at updating tests
featheredtoast Oct 19, 2024
1476e33
fix test expectations
featheredtoast Oct 19, 2024
56c06a1
fix hostnames arg
featheredtoast Oct 19, 2024
508bc52
FIX: LatestRndTrip now returning float
featheredtoast Oct 19, 2024
44e7cf2
gofmt
featheredtoast Oct 19, 2024
197a7d4
AutoPilot got renamed to Autopilot
featheredtoast Oct 19, 2024
8f19890
rotation is float now
featheredtoast Oct 19, 2024
f625c37
fix: circuit ID is now CircuitID
featheredtoast Oct 19, 2024
32e48fb
FEATURE: make runnable with env vars
featheredtoast Oct 21, 2024
0e42eb2
FIX: copypaste typo
featheredtoast Oct 21, 2024
ea6fca6
FIX: power info, change CircuitID to CircuitGroupID
featheredtoast Oct 24, 2024
b3d5c3d
rename savename to session name
featheredtoast Oct 26, 2024
4774bb8
gofmt
featheredtoast Oct 26, 2024
f2cd22a
update go dependencies
featheredtoast Oct 26, 2024
52b1cad
implement metrics dropper - drops metrics when they disappear
featheredtoast Oct 26, 2024
506b6bd
Migrate to PowerInfo components on all things that use power.
featheredtoast Oct 26, 2024
6a1552b
add resource sink power metrics
featheredtoast Oct 26, 2024
0aac1a5
ensure resource sinks are properly dropped when a grid disappears
featheredtoast Oct 26, 2024
d4c240c
no resource sinks have names
featheredtoast Oct 26, 2024
7620a81
add collector to runners
featheredtoast Oct 26, 2024
eb6e137
drop cache on session name change
featheredtoast Oct 27, 2024
d0f2ff5
pull session name
featheredtoast Oct 27, 2024
5577a79
actually save session name
featheredtoast Oct 27, 2024
511b1e1
add logging
featheredtoast Oct 27, 2024
ea79754
update tests - use httptest package
featheredtoast Oct 27, 2024
c82d86a
add return error when response status code is not 200
featheredtoast Oct 27, 2024
b524fc2
Update fuel metrics for drones
featheredtoast Oct 27, 2024
7ba93c2
check for nonzero fuels on drones
featheredtoast Oct 27, 2024
edff4e5
fix vehicle fuel inventory json
featheredtoast Oct 27, 2024
9e5c1f5
check for resp statuscode after err
featheredtoast Oct 28, 2024
afbc806
CargoPlatforms -> CargoInventory
featheredtoast Oct 28, 2024
baa27ee
FIX: Fuel json needs quotes
featheredtoast Oct 28, 2024
0db791f
add last tracked - drop and reset timing if we haven't seen a vehicle…
featheredtoast Oct 28, 2024
484a7e7
trains: need to start tracking if it's been a while between last coll…
featheredtoast Oct 28, 2024
8b9181f
attempt to fix flakey tests
featheredtoast Oct 28, 2024
bd76dd4
use ticker rather than after
featheredtoast Oct 28, 2024
bf63a48
resort to sleeping to bump scheduler in tests
featheredtoast Oct 28, 2024
3995f4b
stop tracking drone port power if not seen
featheredtoast Oct 28, 2024
1b0f61b
FIX: max power info now comes from FRM
featheredtoast Oct 29, 2024
af0e85e
add pump power data
featheredtoast Oct 29, 2024
a2c1bae
enable pump collection
featheredtoast Oct 29, 2024
8e8f1f4
query extractor power metrics
featheredtoast Oct 29, 2024
14fd593
fix: typo
featheredtoast Oct 29, 2024
bbeaea7
add metrics for portals, hypertubes
featheredtoast Oct 29, 2024
768780e
add fracking power data
featheredtoast Oct 29, 2024
532e4c3
remove max building power calculation - raw number comes from FRM now
featheredtoast Oct 29, 2024
a127e0c
drop added metrics. Also add drone port power max
featheredtoast Oct 29, 2024
cd27b99
drop clock in favor of quartz for testable time
featheredtoast Oct 30, 2024
cce3ce7
FIX: sanitize session name
featheredtoast Nov 5, 2024
04d67d0
update readme generation
featheredtoast Oct 22, 2024
c31e297
resolve conflict
featheredtoast Nov 5, 2024
aff5391
gofmt
featheredtoast Nov 5, 2024
3737d15
add missing formatting string
featheredtoast Nov 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 55 additions & 18 deletions Companion/exporter/collector_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,83 @@ package exporter

import (
"context"
"log"
"regexp"
"time"

"github.com/prometheus/client_golang/prometheus"
)

func SanitizeSessionName(sessionName string) string {
re := regexp.MustCompile(`[^\w\s]`)
return re.ReplaceAllString(sessionName, "")
}

type CollectorRunner struct {
collectors []Collector
ctx context.Context
cancel context.CancelFunc
collectors []Collector
ctx context.Context
cancel context.CancelFunc
frmBaseUrl string
sessionName string
}

type Collector interface {
Collect()
Collect(string, string)
DropCache()
}

type SessionInfo struct {
SessionName string `json:"SessionName"`
}

func NewCollectorRunner(ctx context.Context, collectors ...Collector) *CollectorRunner {
func NewCollectorRunner(ctx context.Context, frmBaseUrl string, collectors ...Collector) *CollectorRunner {
ctx, cancel := context.WithCancel(ctx)
return &CollectorRunner{
ctx: ctx,
cancel: cancel,
collectors: collectors,
ctx: ctx,
cancel: cancel,
collectors: collectors,
frmBaseUrl: frmBaseUrl,
sessionName: "default",
}
}

func (c *CollectorRunner) Start() {
c.Collect()
for {
select {
case <-c.ctx.Done():
return
case <-Clock.After(5 * time.Second):
c.Collect()
func (c *CollectorRunner) updateSessionName() {
details := SessionInfo{}
err := retrieveData(c.frmBaseUrl+"/getSessionInfo", &details)
if err != nil {
log.Printf("error reading session name from FRM: %s\n", err)
return
}
newSessionName := SanitizeSessionName(details.SessionName)
if newSessionName != "" && newSessionName != c.sessionName {
log.Printf("%s has a new session name: %s\n", c.frmBaseUrl, newSessionName)
for _, metric := range RegisteredMetrics {
metric.DeletePartialMatch(prometheus.Labels{"url": c.frmBaseUrl, "session_name": c.sessionName})
}
for _, collector := range c.collectors {
collector.DropCache()
}
c.sessionName = newSessionName
}
}

func (c *CollectorRunner) Start() error {
c.updateSessionName()
c.Collect(c.frmBaseUrl, c.sessionName)
t := Clock.TickerFunc(c.ctx, 5*time.Second, func() error {
c.updateSessionName()
c.Collect(c.frmBaseUrl, c.sessionName)
return nil
})
return t.Wait()
}

func (c *CollectorRunner) Stop() {
c.cancel()
}

func (c *CollectorRunner) Collect() {
func (c *CollectorRunner) Collect(server string, sessionName string) {
for _, collector := range c.collectors {
collector.Collect()
collector.Collect(server, sessionName)
}
}
51 changes: 29 additions & 22 deletions Companion/exporter/collector_runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ package exporter_test

import (
"context"
"time"

"github.com/AP-Hunt/FicsitRemoteMonitoringCompanion/Companion/exporter"
"github.com/benbjohnson/clock"
. "github.com/onsi/ginkgo/v2"
"github.com/coder/quartz"
. "github.com/onsi/gomega"
"time"
)

type TestCollector struct {
Expand All @@ -19,42 +19,49 @@ func NewTestCollector() *TestCollector {
counter: 0,
}
}
func (t *TestCollector) Collect() {
func (t *TestCollector) Collect(url string, sessionName string) {
t.counter = t.counter + 1
}
func (t *TestCollector) DropCache() {}

var _ = Describe("CollectorRunner", func() {
var url string

BeforeEach(func() {
FRMServer.Reset()
url = FRMServer.server.URL
FRMServer.ReturnsSessionInfoData(exporter.SessionInfo{
SessionName: "test",
})
})

Describe("Basic Functionality", func() {
It("runs on init and on each timeout", func() {
ctx, cancel := context.WithCancel(context.Background())
testTime := clock.NewMock()
timeout, _ := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
testTime := quartz.NewMock(GinkgoTB())
exporter.Clock = testTime
trap := testTime.Trap().TickerFunc()
defer trap.Close()

c1 := NewTestCollector()
c2 := NewTestCollector()
runner := exporter.NewCollectorRunner(ctx, c1, c2)
runner := exporter.NewCollectorRunner(ctx, url, c1, c2)
go runner.Start()
testTime.Add(5 * time.Second)
testTime.Add(5 * time.Second)
testTime.Add(5 * time.Second)
cancel()
call := trap.MustWait(timeout)
call.Release()

for i := 0; i < 2; i++ {
_, w := testTime.AdvanceNext()
w.MustWait(ctx)
}
Expect(c1.counter).To(Equal(3))
Expect(c2.counter).To(Equal(3))
})

It("does not run after being canceled", func() {
ctx, cancel := context.WithCancel(context.Background())
testTime := clock.NewMock()
exporter.Clock = testTime

c1 := NewTestCollector()
runner := exporter.NewCollectorRunner(ctx, c1)
go runner.Start()
testTime.Add(5 * time.Second)
cancel()
testTime.Add(5 * time.Second)
testTime.Add(5 * time.Second)
Expect(c1.counter).To(Equal(1))
It("sanitizes session name", func() {
Expect(exporter.SanitizeSessionName(`it's giving -- 123456!@#$%^&*() yo hollar "'"`)).To(Equal(`its giving 123456 yo hollar ` ))
})
})
})
9 changes: 7 additions & 2 deletions Companion/exporter/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ package exporter

import (
"encoding/json"
"github.com/benbjohnson/clock"
"fmt"
"github.com/coder/quartz"
"log"
"net/http"
"regexp"
Expand All @@ -12,7 +13,7 @@ import (

var timeRegex = regexp.MustCompile(`\d\d:\d\d:\d\d`)

var Clock = clock.New()
var Clock = quartz.NewReal()

func parseTimeSeconds(timeStr string) *float64 {
match := timeRegex.FindStringSubmatch(timeStr)
Expand Down Expand Up @@ -42,6 +43,10 @@ func retrieveData(frmAddress string, details any) error {
return err
}

if resp.StatusCode != 200 {
return fmt.Errorf("non-200 returned when retireving data: %d", resp.StatusCode)
}

defer resp.Body.Close()

decoder := json.NewDecoder(resp.Body)
Expand Down
116 changes: 73 additions & 43 deletions Companion/exporter/drone_station_collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,81 +3,111 @@ package exporter
import (
"log"
"strconv"

"github.com/prometheus/client_golang/prometheus"
)

type DroneStationCollector struct {
FRMAddress string
endpoint string
metricsDropper *MetricsDropper
}

type DroneFuelInventory struct {
Name string `json:"Name"`
Amount float64 `json:"Amount"`
}

type DroneActiveFuel struct {
Name string `json:"FuelName"`
Rate float64 `json:"EstimatedFuelCostRate"`
}

type DroneStationDetails struct {
Id string `json:"ID"`
HomeStation string `json:"Name"`
PairedStation string `json:"PairedStation"`
DroneStatus string `json:"DroneStatus"`
AvgIncRate float64 `json:"AvgIncRate"`
AvgIncStack float64 `json:"AvgIncStack"`
AvgOutRate float64 `json:"AvgOutRate"`
AvgOutStack float64 `json"AvgOutStack"`
AvgRndTrip string `json:"AvgRndTrip"`
AvgTotalIncRate float64 `json:"AvgTotalIncRate"`
AvgTotalIncStack float64 `json:"AvgTotalIncStack"`
AvgTotalOutRate float64 `json:"AvgTotalOutRate"`
AvgTotalOutStack float64 `json:"AvgTotalOutStack"`
AvgTripIncAmt float64 `json:"AvgTripIncAmt"`
EstRndTrip string `json:"EstRndTrip"`
EstTotalTransRate float64 `json:"EstTotalTransRate"`
EstTransRate float64 `json:"EstTransRate"`
EstLatestTotalIncStack float64 `json:"EstLatestTotalIncStack"`
EstLatestTotalOutStack float64 `json:"EstLatestTotalOutStack"`
LatestIncStack float64 `json:"LatestIncStack"`
LatestOutStack float64 `json:"LatestOutStack"`
LatestRndTrip string `json:"LatestRndTrip"`
LatestTripIncAmt float64 `json:"LatestTripIncAmt"`
LatestTripOutAmt float64 `json:"LatestTripOutAmt"`
MedianRndTrip string `json:"MedianRndTrip"`
MedianTripIncAmt float64 `json:"MedianTripIncAmt"`
MedianTripOutAmt float64 `json:"MedianTripOutAmt"`
EstBatteryRate float64 `json:"EstBatteryRate"`
PowerInfo PowerInfo `json:"PowerInfo"`
Id string `json:"ID"`
HomeStation string `json:"Name"`
PairedStation string `json:"PairedStation"`
DroneStatus string `json:"DroneStatus"`
AvgIncRate float64 `json:"AvgIncRate"`
AvgIncStack float64 `json:"AvgIncStack"`
AvgOutRate float64 `json:"AvgOutRate"`
AvgOutStack float64 `json"AvgOutStack"`
AvgRndTrip string `json:"AvgRndTrip"`
AvgTotalIncRate float64 `json:"AvgTotalIncRate"`
AvgTotalIncStack float64 `json:"AvgTotalIncStack"`
AvgTotalOutRate float64 `json:"AvgTotalOutRate"`
AvgTotalOutStack float64 `json:"AvgTotalOutStack"`
AvgTripIncAmt float64 `json:"AvgTripIncAmt"`
EstTotalTransRate float64 `json:"EstTotalTransRate"`
EstTransRate float64 `json:"EstTransRate"`
EstLatestTotalIncStack float64 `json:"EstLatestTotalIncStack"`
EstLatestTotalOutStack float64 `json:"EstLatestTotalOutStack"`
LatestIncStack float64 `json:"LatestIncStack"`
LatestOutStack float64 `json:"LatestOutStack"`
LatestRndTrip float64 `json:"LatestRndTrip"`
LatestTripIncAmt float64 `json:"LatestTripIncAmt"`
LatestTripOutAmt float64 `json:"LatestTripOutAmt"`
MedianRndTrip string `json:"MedianRndTrip"`
MedianTripIncAmt float64 `json:"MedianTripIncAmt"`
MedianTripOutAmt float64 `json:"MedianTripOutAmt"`
PowerInfo PowerInfo `json:"PowerInfo"`
Fuel []DroneFuelInventory `json:"FuelInventory"`
ActiveFuel DroneActiveFuel `json:"ActiveFuel"`
}

func NewDroneStationCollector(frmAddress string) *DroneStationCollector {
func NewDroneStationCollector(endpoint string) *DroneStationCollector {
return &DroneStationCollector{
FRMAddress: frmAddress,
endpoint: endpoint,
metricsDropper: NewMetricsDropper(
DronePortFuelRate,
DronePortFuelAmount,
DronePortRndTrip,
),
}
}

func (c *DroneStationCollector) Collect() {
func (c *DroneStationCollector) Collect(frmAddress string, sessionName string) {
details := []DroneStationDetails{}
err := retrieveData(c.FRMAddress, &details)
err := retrieveData(frmAddress+c.endpoint, &details)
if err != nil {
c.metricsDropper.DropStaleMetricLabels()
log.Printf("error reading drone station statistics from FRM: %s\n", err)
return
}

powerInfo := map[float64]float64{}
maxPowerInfo := map[float64]float64{}
for _, d := range details {
c.metricsDropper.CacheFreshMetricLabel(prometheus.Labels{"url": frmAddress, "session_name": sessionName, "id": d.Id})
id := d.Id
home := d.HomeStation
paired := d.PairedStation

DronePortBatteryRate.WithLabelValues(id, home, paired).Set(d.EstBatteryRate)

roundTrip := parseTimeSeconds(d.LatestRndTrip)
if roundTrip != nil {
DronePortRndTrip.WithLabelValues(id, home, paired).Set(*roundTrip)
if len(d.Fuel) > 0 {
DronePortFuelAmount.WithLabelValues(id, home, d.Fuel[0].Name, frmAddress, sessionName).Set(d.Fuel[0].Amount)
DronePortFuelRate.WithLabelValues(id, home, d.Fuel[0].Name, frmAddress, sessionName).Set(d.ActiveFuel.Rate)
DronePortRndTrip.WithLabelValues(id, home, paired, frmAddress, sessionName).Set(d.LatestRndTrip)
}

val, ok := powerInfo[d.PowerInfo.CircuitId]
val, ok := powerInfo[d.PowerInfo.CircuitGroupId]
if ok {
powerInfo[d.PowerInfo.CircuitId] = val + d.PowerInfo.PowerConsumed
powerInfo[d.PowerInfo.CircuitGroupId] = val + d.PowerInfo.PowerConsumed
} else {
powerInfo[d.PowerInfo.CircuitId] = d.PowerInfo.PowerConsumed
powerInfo[d.PowerInfo.CircuitGroupId] = d.PowerInfo.PowerConsumed
}
val, ok = maxPowerInfo[d.PowerInfo.CircuitGroupId]
if ok {
maxPowerInfo[d.PowerInfo.CircuitGroupId] = val + d.PowerInfo.MaxPowerConsumed
} else {
maxPowerInfo[d.PowerInfo.CircuitGroupId] = d.PowerInfo.MaxPowerConsumed
}
}

for circuitId, powerConsumed := range powerInfo {
cid := strconv.FormatFloat(circuitId, 'f', -1, 64)
DronePortPower.WithLabelValues(cid).Set(powerConsumed)
DronePortPower.WithLabelValues(cid, frmAddress, sessionName).Set(powerConsumed)
}

c.metricsDropper.DropStaleMetricLabels()
}

func (c *DroneStationCollector) DropCache() {}
Loading
Loading