server: add /v2/stats/self

This commit is contained in:
Yicheng Qin 2014-08-13 14:28:16 -07:00
parent 5574b6e224
commit 9203f68894
8 changed files with 241 additions and 5 deletions

25
etcd/package_stats.go Normal file
View File

@ -0,0 +1,25 @@
package etcd
import (
"time"
)
// packageStats represent the stats we need for a package.
// It has sending time and the size of the package.
type packageStats struct {
sendingTime time.Time
size int
}
// NewPackageStats creates a pacakgeStats and return the pointer to it.
func NewPackageStats(now time.Time, size int) *packageStats {
return &packageStats{
sendingTime: now,
size: size,
}
}
// Time return the sending time of the package.
func (ps *packageStats) Time() time.Time {
return ps.sendingTime
}

View File

@ -48,6 +48,7 @@ const (
v2machinePrefix = "/v2/machines" v2machinePrefix = "/v2/machines"
v2peersPrefix = "/v2/peers" v2peersPrefix = "/v2/peers"
v2LeaderPrefix = "/v2/leader" v2LeaderPrefix = "/v2/leader"
v2SelfStatsPrefix = "/v2/stats/self"
v2LeaderStatsPrefix = "/v2/stats/leader" v2LeaderStatsPrefix = "/v2/stats/leader"
v2StoreStatsPrefix = "/v2/stats/store" v2StoreStatsPrefix = "/v2/stats/store"
v2adminConfigPrefix = "/v2/admin/config" v2adminConfigPrefix = "/v2/admin/config"
@ -77,6 +78,7 @@ type participant struct {
store.Store store.Store
rh *raftHandler rh *raftHandler
w *wal.WAL w *wal.WAL
serverStats *raftServerStats
stopNotifyc chan struct{} stopNotifyc chan struct{}
@ -98,12 +100,14 @@ func newParticipant(id int64, pubAddr string, raftPubAddr string, dir string, cl
result: make(map[wait]chan interface{}), result: make(map[wait]chan interface{}),
}, },
Store: store.New(), Store: store.New(),
serverStats: NewRaftServerStats(fmt.Sprint(id)),
stopNotifyc: make(chan struct{}), stopNotifyc: make(chan struct{}),
ServeMux: http.NewServeMux(), ServeMux: http.NewServeMux(),
} }
p.rh = newRaftHandler(peerHub, p.Store.Version()) p.rh = newRaftHandler(peerHub, p.Store.Version(), p.serverStats)
p.peerHub.setServerStats(p.serverStats)
walPath := path.Join(dir, "wal") walPath := path.Join(dir, "wal")
w, err := wal.Open(walPath) w, err := wal.Open(walPath)
@ -140,6 +144,7 @@ func newParticipant(id int64, pubAddr string, raftPubAddr string, dir string, cl
p.Handle(v2machinePrefix, handlerErr(p.serveMachines)) p.Handle(v2machinePrefix, handlerErr(p.serveMachines))
p.Handle(v2peersPrefix, handlerErr(p.serveMachines)) p.Handle(v2peersPrefix, handlerErr(p.serveMachines))
p.Handle(v2LeaderPrefix, handlerErr(p.serveLeader)) p.Handle(v2LeaderPrefix, handlerErr(p.serveLeader))
p.Handle(v2SelfStatsPrefix, handlerErr(p.serveSelfStats))
p.Handle(v2LeaderStatsPrefix, handlerErr(p.serveLeaderStats)) p.Handle(v2LeaderStatsPrefix, handlerErr(p.serveLeaderStats))
p.Handle(v2StoreStatsPrefix, handlerErr(p.serveStoreStats)) p.Handle(v2StoreStatsPrefix, handlerErr(p.serveStoreStats))
p.rh.Handle(v2adminConfigPrefix, handlerErr(p.serveAdminConfig)) p.rh.Handle(v2adminConfigPrefix, handlerErr(p.serveAdminConfig))

View File

@ -45,6 +45,7 @@ type peerHub struct {
peers map[int64]*peer peers map[int64]*peer
c *http.Client c *http.Client
followersStats *raftFollowersStats followersStats *raftFollowersStats
serverStats *raftServerStats
} }
func newPeerHub(id int64, c *http.Client) *peerHub { func newPeerHub(id int64, c *http.Client) *peerHub {
@ -57,6 +58,10 @@ func newPeerHub(id int64, c *http.Client) *peerHub {
return h return h
} }
func (h *peerHub) setServerStats(serverStats *raftServerStats) {
h.serverStats = serverStats
}
func (h *peerHub) setSeeds(seeds []string) { func (h *peerHub) setSeeds(seeds []string) {
for _, seed := range seeds { for _, seed := range seeds {
h.seeds[seed] = true h.seeds[seed] = true
@ -122,6 +127,9 @@ func (h *peerHub) send(msg raft.Message) error {
if err != nil { if err != nil {
return err return err
} }
if msg.IsMsgApp() {
h.serverStats.SendAppendReq(len(data))
}
return p.send(data) return p.send(data)
} }
return errUnknownPeer return errUnknownPeer

View File

@ -38,16 +38,18 @@ type raftHandler struct {
peerGetter peerGetter peerGetter peerGetter
storeVersion int storeVersion int
serverStats *raftServerStats
recv chan *raft.Message recv chan *raft.Message
*http.ServeMux *http.ServeMux
} }
func newRaftHandler(p peerGetter, version int) *raftHandler { func newRaftHandler(p peerGetter, version int, serverStats *raftServerStats) *raftHandler {
h := &raftHandler{ h := &raftHandler{
recv: make(chan *raft.Message, 512), recv: make(chan *raft.Message, 512),
peerGetter: p, peerGetter: p,
storeVersion: version, storeVersion: version,
serverStats: serverStats,
} }
h.ServeMux = http.NewServeMux() h.ServeMux = http.NewServeMux()
h.ServeMux.HandleFunc(raftPrefix+"/cfg/", h.serveCfg) h.ServeMux.HandleFunc(raftPrefix+"/cfg/", h.serveCfg)
@ -83,6 +85,9 @@ func (h *raftHandler) serveRaft(w http.ResponseWriter, r *http.Request) {
log.Printf("raftHandler.serve decodeErr=\"%v\"\n", err) log.Printf("raftHandler.serve decodeErr=\"%v\"\n", err)
return return
} }
if msg.IsMsgApp() {
h.serverStats.RecvAppendReq(fmt.Sprint(msg.From), int(r.ContentLength))
}
select { select {
case h.recv <- msg: case h.recv <- msg:

83
etcd/raft_server_stats.go Normal file
View File

@ -0,0 +1,83 @@
package etcd
import (
"sync"
"time"
)
type raftServerStats struct {
Name string `json:"name"`
State string `json:"state"`
StartTime time.Time `json:"startTime"`
LeaderInfo struct {
Name string `json:"leader"`
Uptime string `json:"uptime"`
StartTime time.Time `json:"startTime"`
} `json:"leaderInfo"`
RecvAppendRequestCnt uint64 `json:"recvAppendRequestCnt,"`
RecvingPkgRate float64 `json:"recvPkgRate,omitempty"`
RecvingBandwidthRate float64 `json:"recvBandwidthRate,omitempty"`
SendAppendRequestCnt uint64 `json:"sendAppendRequestCnt"`
SendingPkgRate float64 `json:"sendPkgRate,omitempty"`
SendingBandwidthRate float64 `json:"sendBandwidthRate,omitempty"`
sendRateQueue *statsQueue
recvRateQueue *statsQueue
sync.Mutex
}
func NewRaftServerStats(name string) *raftServerStats {
stats := &raftServerStats{
Name: name,
StartTime: time.Now(),
sendRateQueue: &statsQueue{
back: -1,
},
recvRateQueue: &statsQueue{
back: -1,
},
}
stats.LeaderInfo.StartTime = time.Now()
return stats
}
func (ss *raftServerStats) Reset() {
name := ss.Name
ss = NewRaftServerStats(name)
return
}
func (ss *raftServerStats) RecvAppendReq(leaderName string, pkgSize int) {
ss.Lock()
defer ss.Unlock()
ss.State = stateFollower
if leaderName != ss.LeaderInfo.Name {
ss.LeaderInfo.Name = leaderName
ss.LeaderInfo.StartTime = time.Now()
}
ss.recvRateQueue.Insert(NewPackageStats(time.Now(), pkgSize))
ss.RecvAppendRequestCnt++
}
func (ss *raftServerStats) SendAppendReq(pkgSize int) {
ss.Lock()
defer ss.Unlock()
now := time.Now()
if ss.State != stateLeader {
ss.State = stateLeader
ss.LeaderInfo.Name = ss.Name
ss.LeaderInfo.StartTime = now
}
ss.sendRateQueue.Insert(NewPackageStats(now, pkgSize))
ss.SendAppendRequestCnt++
}

89
etcd/stats_queue.go Normal file
View File

@ -0,0 +1,89 @@
package etcd
import (
"sync"
"time"
)
const (
queueCapacity = 200
)
type statsQueue struct {
items [queueCapacity]*packageStats
size int
front int
back int
totalPkgSize int
rwl sync.RWMutex
}
func (q *statsQueue) Len() int {
return q.size
}
func (q *statsQueue) PkgSize() int {
return q.totalPkgSize
}
// FrontAndBack gets the front and back elements in the queue
// We must grab front and back together with the protection of the lock
func (q *statsQueue) frontAndBack() (*packageStats, *packageStats) {
q.rwl.RLock()
defer q.rwl.RUnlock()
if q.size != 0 {
return q.items[q.front], q.items[q.back]
}
return nil, nil
}
// Insert function insert a packageStats into the queue and update the records
func (q *statsQueue) Insert(p *packageStats) {
q.rwl.Lock()
defer q.rwl.Unlock()
q.back = (q.back + 1) % queueCapacity
if q.size == queueCapacity { //dequeue
q.totalPkgSize -= q.items[q.front].size
q.front = (q.back + 1) % queueCapacity
} else {
q.size++
}
q.items[q.back] = p
q.totalPkgSize += q.items[q.back].size
}
// Rate function returns the package rate and byte rate
func (q *statsQueue) Rate() (float64, float64) {
front, back := q.frontAndBack()
if front == nil || back == nil {
return 0, 0
}
if time.Now().Sub(back.Time()) > time.Second {
q.Clear()
return 0, 0
}
sampleDuration := back.Time().Sub(front.Time())
pr := float64(q.Len()) / float64(sampleDuration) * float64(time.Second)
br := float64(q.PkgSize()) / float64(sampleDuration) * float64(time.Second)
return pr, br
}
// Clear function clear up the statsQueue
func (q *statsQueue) Clear() {
q.rwl.Lock()
defer q.rwl.Unlock()
q.back = -1
q.front = 0
q.size = 0
q.totalPkgSize = 0
}

View File

@ -23,6 +23,7 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"strings" "strings"
"time"
etcdErr "github.com/coreos/etcd/error" etcdErr "github.com/coreos/etcd/error"
) )
@ -75,6 +76,22 @@ func (p *participant) serveLeader(w http.ResponseWriter, r *http.Request) error
return fmt.Errorf("no leader") return fmt.Errorf("no leader")
} }
func (p *participant) serveSelfStats(w http.ResponseWriter, req *http.Request) error {
p.serverStats.LeaderInfo.Uptime = time.Now().Sub(p.serverStats.LeaderInfo.StartTime).String()
if p.node.IsLeader() {
p.serverStats.LeaderInfo.Name = fmt.Sprint(p.id)
}
queue := p.serverStats.sendRateQueue
p.serverStats.SendingPkgRate, p.serverStats.SendingBandwidthRate = queue.Rate()
queue = p.serverStats.recvRateQueue
p.serverStats.RecvingPkgRate, p.serverStats.RecvingBandwidthRate = queue.Rate()
return json.NewEncoder(w).Encode(p.serverStats)
}
func (p *participant) serveLeaderStats(w http.ResponseWriter, req *http.Request) error { func (p *participant) serveLeaderStats(w http.ResponseWriter, req *http.Request) error {
if !p.node.IsLeader() { if !p.node.IsLeader() {
return p.redirect(w, req, p.node.Leader()) return p.redirect(w, req, p.node.Leader())

View File

@ -80,6 +80,10 @@ type Message struct {
Snapshot Snapshot Snapshot Snapshot
} }
func (m Message) IsMsgApp() bool {
return m.Type == msgApp
}
func (m Message) String() string { func (m Message) String() string {
return fmt.Sprintf("type=%v from=%x to=%x term=%d logTerm=%d i=%d ci=%d len(ents)=%d", return fmt.Sprintf("type=%v from=%x to=%x term=%d logTerm=%d i=%d ci=%d len(ents)=%d",
m.Type, m.From, m.To, m.Term, m.LogTerm, m.Index, m.Commit, len(m.Entries)) m.Type, m.From, m.To, m.Term, m.LogTerm, m.Index, m.Commit, len(m.Entries))