Add stats package and node related statistics model

This commit is contained in:
Audrius Butkevicius 2014-08-21 23:45:40 +01:00
parent eee702f299
commit 0cdb0daa8c
6 changed files with 156 additions and 6 deletions

View File

@ -6,6 +6,7 @@ package main
import (
"bytes"
"crypto/tls"
"encoding/base64"
"encoding/json"
"fmt"
@ -24,7 +25,6 @@ import (
"sync"
"time"
"crypto/tls"
"code.google.com/p/go.crypto/bcrypt"
"github.com/syncthing/syncthing/auto"
"github.com/syncthing/syncthing/config"

View File

@ -125,6 +125,7 @@ The following enviroment variables are interpreted by syncthing:
- "net" (the main package; connections & network messages)
- "model" (the model package)
- "scanner" (the scanner package)
- "stats" (the stats package)
- "upnp" (the upnp package)
- "xdr" (the xdr package)
- "all" (all of the above)

View File

@ -22,6 +22,7 @@ import (
"github.com/syncthing/syncthing/lamport"
"github.com/syncthing/syncthing/protocol"
"github.com/syncthing/syncthing/scanner"
"github.com/syncthing/syncthing/stats"
"github.com/syndtr/goleveldb/leveldb"
)
@ -72,11 +73,12 @@ type Model struct {
clientName string
clientVersion string
repoCfgs map[string]config.RepositoryConfiguration // repo -> cfg
repoFiles map[string]*files.Set // repo -> files
repoNodes map[string][]protocol.NodeID // repo -> nodeIDs
nodeRepos map[protocol.NodeID][]string // nodeID -> repos
rmut sync.RWMutex // protects the above
repoCfgs map[string]config.RepositoryConfiguration // repo -> cfg
repoFiles map[string]*files.Set // repo -> files
repoNodes map[string][]protocol.NodeID // repo -> nodeIDs
nodeRepos map[protocol.NodeID][]string // nodeID -> repos
nodeStatRefs map[protocol.NodeID]*stats.NodeStatisticsReference // nodeID -> statsRef
rmut sync.RWMutex // protects the above
repoState map[string]repoState // repo -> state
repoStateChanged map[string]time.Time // repo -> time when state changed
@ -114,6 +116,7 @@ func NewModel(indexDir string, cfg *config.Configuration, nodeName, clientName,
repoFiles: make(map[string]*files.Set),
repoNodes: make(map[string][]protocol.NodeID),
nodeRepos: make(map[protocol.NodeID][]string),
nodeStatRefs: make(map[protocol.NodeID]*stats.NodeStatisticsReference),
repoState: make(map[string]repoState),
repoStateChanged: make(map[string]time.Time),
protoConn: make(map[protocol.NodeID]protocol.Connection),
@ -122,6 +125,10 @@ func NewModel(indexDir string, cfg *config.Configuration, nodeName, clientName,
sentLocalVer: make(map[protocol.NodeID]map[string]uint64),
}
for _, node := range cfg.Nodes {
m.nodeStatRefs[node.NodeID] = stats.NewNodeStatisticsReference(db, node.NodeID)
}
var timeout = 20 * 60 // seconds
if t := os.Getenv("STDEADLOCKTIMEOUT"); len(t) > 0 {
it, err := strconv.Atoi(t)
@ -199,6 +206,15 @@ func (m *Model) ConnectionStats() map[string]ConnectionInfo {
return res
}
// Returns statistics about each node
func (m *Model) NodeStatistics() map[string]stats.NodeStatistics {
var res = make(map[string]stats.NodeStatistics)
for _, node := range m.cfg.Nodes {
res[node.NodeID.String()] = m.nodeStatRefs[node.NodeID].GetStatistics()
}
return res
}
// Returns the completion status, in percent, for the given node and repo.
func (m *Model) Completion(node protocol.NodeID, repo string) float64 {
var tot int64
@ -535,6 +551,9 @@ func (cf cFiler) CurrentFile(file string) protocol.FileInfo {
func (m *Model) ConnectedTo(nodeID protocol.NodeID) bool {
m.pmut.RLock()
_, ok := m.protoConn[nodeID]
if ok {
m.nodeStatRefs[nodeID].WasSeen()
}
m.pmut.RUnlock()
return ok
}
@ -563,6 +582,7 @@ func (m *Model) AddConnection(rawConn io.Closer, protoConn protocol.Connection)
fs := m.repoFiles[repo]
go sendIndexes(protoConn, repo, fs)
}
m.nodeStatRefs[nodeID].WasSeen()
m.rmut.RUnlock()
m.pmut.Unlock()
}

17
stats/debug.go Executable file
View File

@ -0,0 +1,17 @@
// Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
// All rights reserved. Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package stats
import (
"os"
"strings"
"github.com/syncthing/syncthing/logger"
)
var (
debug = strings.Contains(os.Getenv("STTRACE"), "stats") || os.Getenv("STTRACE") == "all"
l = logger.DefaultLogger
)

10
stats/leveldb.go Executable file
View File

@ -0,0 +1,10 @@
// Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
// All rights reserved. Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package stats
// Same key space as files/leveldb.go keyType* constants
const (
keyTypeNodeStatistic = iota + 30
)

102
stats/node.go Executable file
View File

@ -0,0 +1,102 @@
// Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
// All rights reserved. Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package stats
import (
"time"
"github.com/syncthing/syncthing/protocol"
"github.com/syndtr/goleveldb/leveldb"
)
const (
nodeStatisticTypeLastSeen = iota
)
var nodeStatisticsTypes = []byte{
nodeStatisticTypeLastSeen,
}
type NodeStatistics struct {
LastSeen time.Time
}
type NodeStatisticsReference struct {
db *leveldb.DB
node protocol.NodeID
}
func NewNodeStatisticsReference(db *leveldb.DB, node protocol.NodeID) *NodeStatisticsReference {
return &NodeStatisticsReference{
db: db,
node: node,
}
}
func (s *NodeStatisticsReference) key(stat byte) []byte {
k := make([]byte, 1+1+32)
k[0] = keyTypeNodeStatistic
k[1] = stat
copy(k[1+1:], s.node[:])
return k
}
func (s *NodeStatisticsReference) GetLastSeen() time.Time {
value, err := s.db.Get(s.key(nodeStatisticTypeLastSeen), nil)
if err != nil {
if err != leveldb.ErrNotFound {
l.Warnln("NodeStatisticsReference: Failed loading last seen value for", s.node, ":", err)
}
return time.Unix(0, 0)
}
rtime := time.Time{}
err = rtime.UnmarshalBinary(value)
if err != nil {
l.Warnln("NodeStatisticsReference: Failed parsing last seen value for", s.node, ":", err)
return time.Unix(0, 0)
}
if debug {
l.Debugln("stats.NodeStatisticsReference.GetLastSeen:", s.node, rtime)
}
return rtime
}
func (s *NodeStatisticsReference) WasSeen() {
if debug {
l.Debugln("stats.NodeStatisticsReference.WasSeen:", s.node)
}
value, err := time.Now().MarshalBinary()
if err != nil {
l.Warnln("NodeStatisticsReference: Failed serializing last seen value for", s.node, ":", err)
return
}
err = s.db.Put(s.key(nodeStatisticTypeLastSeen), value, nil)
if err != nil {
l.Warnln("Failed serializing last seen value for", s.node, ":", err)
}
}
// Never called, maybe because it's worth while to keep the data
// or maybe because we have no easy way of knowing that a node has been removed.
func (s *NodeStatisticsReference) Delete() error {
for _, stype := range nodeStatisticsTypes {
err := s.db.Delete(s.key(stype), nil)
if debug && err == nil {
l.Debugln("stats.NodeStatisticsReference.Delete:", s.node, stype)
}
if err != nil && err != leveldb.ErrNotFound {
return err
}
}
return nil
}
func (s *NodeStatisticsReference) GetStatistics() NodeStatistics {
return NodeStatistics{
LastSeen: s.GetLastSeen(),
}
}