Quellcode durchsuchen

Add stats package and node related statistics model

Audrius Butkevicius vor 11 Jahren
Ursprung
Commit
0cdb0daa8c
6 geänderte Dateien mit 156 neuen und 6 gelöschten Zeilen
  1. 1 1
      cmd/syncthing/gui.go
  2. 1 0
      cmd/syncthing/main.go
  3. 25 5
      model/model.go
  4. 17 0
      stats/debug.go
  5. 10 0
      stats/leveldb.go
  6. 102 0
      stats/node.go

+ 1 - 1
cmd/syncthing/gui.go

@@ -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"

+ 1 - 0
cmd/syncthing/main.go

@@ -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)

+ 25 - 5
model/model.go

@@ -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 - 0
stats/debug.go

@@ -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 - 0
stats/leveldb.go

@@ -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 - 0
stats/node.go

@@ -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(),
+	}
+}