Skip to content

Commit

Permalink
Export stats struct/publisher interface for monitoring
Browse files Browse the repository at this point in the history
Currently, exporting monitoring metrics would require patching the ecu
package to add the necessary functionality. Exporting the types allows
an implementation to provide a statistics publisher via the ECU config.
This PR also makes a few changes to how ecu/stats.go operates.
  • Loading branch information
DrJosh9000 committed Jul 13, 2020
1 parent 7bed913 commit cc4c166
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 84 deletions.
18 changes: 10 additions & 8 deletions ecu/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,15 @@ var defaultConfig = Config{

// Config provides configuration details for a Seesaw ECU.
type Config struct {
Authenticator Authenticator
CACertsFile string
ControlAddress string
ECUCertFile string
ECUKeyFile string
EngineSocket string
MonitorAddress string
UpdateInterval time.Duration
Authenticator Authenticator
CACertsFile string
ControlAddress string
ECUCertFile string
ECUKeyFile string
EngineSocket string
MonitorAddress string
StatsPublishers []Publisher
UpdateInterval time.Duration
}

// DefaultConfig returns the default ECU configuration.
Expand Down Expand Up @@ -94,6 +95,7 @@ func (e *ECU) Run() {
}

stats := newECUStats(e)
stats.notify(e.cfg.StatsPublishers...)
go stats.run()

go e.control()
Expand Down
143 changes: 67 additions & 76 deletions ecu/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,133 +32,124 @@ import (
log "github.com/golang/glog"
)

// publisher implements an interface for a statistics publisher.
type publisher interface {
update(s *stats)
// Publisher is an interface for a statistics publisher. Publishers are
// notified of updates to the statistics with the Update method.
type Publisher interface {
Update(s *Stats)
}

// stats contains the statistics collected from the Seesaw Engine.
type stats struct {
lock sync.RWMutex

lastUpdate time.Time
lastSuccess time.Time

seesaw.ClusterStatus
seesaw.ConfigStatus
seesaw.HAStatus
neighbors []*quagga.Neighbor
vlans []*seesaw.VLAN
vservers map[string]*seesaw.Vserver
// Stats contains the statistics collected from the Seesaw Engine.
type Stats struct {
LastUpdate time.Time
LastSuccess time.Time

ClusterStatus seesaw.ClusterStatus
ConfigStatus seesaw.ConfigStatus
HAStatus seesaw.HAStatus
Neighbors []*quagga.Neighbor
VLANs []*seesaw.VLAN
Vservers map[string]*seesaw.Vserver
}

// ecuStats contains that data needed to the ECU stats collector.
type ecuStats struct {
ecu *ECU
publishers []publisher
stats *stats
ecu *ECU

publishersMu sync.RWMutex
publishers []Publisher

lastStats *Stats
}

// newECUStats returns an initialised ecuStats struct.
func newECUStats(ecu *ECU) *ecuStats {
return &ecuStats{
ecu: ecu,
stats: &stats{
lastUpdate: time.Unix(0, 0),
lastSuccess: time.Unix(0, 0),
},
}
return &ecuStats{ecu: ecu}
}

// notify registers a publisher for update notifications.
func (e *ecuStats) notify(p publisher) {
e.publishers = append(e.publishers, p)
// notify registers publishers for update notifications.
func (e *ecuStats) notify(pubs ...Publisher) {
e.publishersMu.Lock()
defer e.publishersMu.Unlock()
e.publishers = append(e.publishers, pubs...)
}

func (e *ecuStats) runOnce() {
log.Info("Updating ECU statistics from Seesaw Engine...")
start := time.Now()
s, err := e.stats()
if err != nil {
log.Warningf("Couldn't update statistics: %v", err)
s = e.lastStats
}
s.LastUpdate = start
e.lastStats = s
e.publishersMu.RLock()
defer e.publishersMu.RUnlock()
for _, p := range e.publishers {
t := *s
p.Update(&t)
}
}

// run attempts to update the cached statistics from the Seesaw Engine at
// regular intervals.
func (e *ecuStats) run() {
ticker := time.NewTicker(e.ecu.cfg.UpdateInterval)
for {
e.stats.lock.Lock()
e.stats.lastUpdate = time.Now()
e.stats.lock.Unlock()

log.Info("Updating ECU statistics from Seesaw Engine...")
if err := e.update(); err != nil {
log.Warning(err)
} else {
e.stats.lock.Lock()
e.stats.lastSuccess = time.Now()
e.stats.lock.Unlock()
}
for _, p := range e.publishers {
p.update(e.stats)
}
<-ticker.C
e.runOnce()
for range time.Tick(e.ecu.cfg.UpdateInterval) {
e.runOnce()
}
}

// update attempts to update the cached statistics from the Seesaw Engine.
func (e *ecuStats) update() error {
// stats attempts to gather statistics from the Seesaw Engine.
func (e *ecuStats) stats() (*Stats, error) {
// TODO(jsing): Make this untrusted.
ctx := ipc.NewTrustedContext(seesaw.SCECU)
seesawConn, err := conn.NewSeesawIPC(ctx)
if err != nil {
return fmt.Errorf("Failed to connect to engine: %v", err)
return nil, fmt.Errorf("connect to engine: %v", err)
}
if err := seesawConn.Dial(e.ecu.cfg.EngineSocket); err != nil {
return fmt.Errorf("Failed to connect to engine: %v", err)
return nil, fmt.Errorf("connect to engine: %v", err)
}
defer seesawConn.Close()

clusterStatus, err := seesawConn.ClusterStatus()
if err != nil {
return fmt.Errorf("Failed to get cluster status: %v", err)
return nil, fmt.Errorf("get cluster status: %v", err)
}
e.stats.lock.Lock()
e.stats.ClusterStatus = *clusterStatus
e.stats.lock.Unlock()

configStatus, err := seesawConn.ConfigStatus()
if err != nil {
return fmt.Errorf("Failed to get config status: %v", err)
return nil, fmt.Errorf("get config status: %v", err)
}
e.stats.lock.Lock()
e.stats.ConfigStatus = *configStatus
e.stats.lock.Unlock()

ha, err := seesawConn.HAStatus()
if err != nil {
return fmt.Errorf("Failed to get HA status: %v", err)
return nil, fmt.Errorf("get HA status: %v", err)
}
e.stats.lock.Lock()
e.stats.HAStatus = *ha
e.stats.lock.Unlock()

neighbors, err := seesawConn.BGPNeighbors()
if err != nil {
return fmt.Errorf("Failed to get BGP neighbors: %v", err)
return nil, fmt.Errorf("get BGP neighbors: %v", err)
}
e.stats.lock.Lock()
e.stats.neighbors = neighbors
e.stats.lock.Unlock()

vlans, err := seesawConn.VLANs()
if err != nil {
return fmt.Errorf("Failed to get VLANs: %v", err)
return nil, fmt.Errorf("get VLANs: %v", err)
}
e.stats.lock.Lock()
e.stats.vlans = vlans.VLANs
e.stats.lock.Unlock()

vservers, err := seesawConn.Vservers()
if err != nil {
return fmt.Errorf("Failed to get vservers: %v", err)
return nil, fmt.Errorf("get vservers: %v", err)
}
e.stats.lock.Lock()
e.stats.vservers = vservers
e.stats.lock.Unlock()

return nil
return &Stats{
LastSuccess: time.Now(),
ClusterStatus: *clusterStatus,
ConfigStatus: *configStatus,
HAStatus: *ha,
Neighbors: neighbors,
VLANs: vlans.VLANs,
Vservers: vservers,
}, nil
}

0 comments on commit cc4c166

Please sign in to comment.