mirror of
https://github.com/etcd-io/etcd.git
synced 2024-09-27 06:25:44 +00:00
metrics: add /rafthttp/stream metrics
This commit is contained in:
parent
7c7d78a11f
commit
99821579bf
@ -19,6 +19,7 @@ package etcdhttp
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"expvar"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
@ -47,6 +48,7 @@ const (
|
|||||||
deprecatedMachinesPrefix = "/v2/machines"
|
deprecatedMachinesPrefix = "/v2/machines"
|
||||||
membersPrefix = "/v2/members"
|
membersPrefix = "/v2/members"
|
||||||
statsPrefix = "/v2/stats"
|
statsPrefix = "/v2/stats"
|
||||||
|
statsPath = "/stats"
|
||||||
healthPath = "/health"
|
healthPath = "/health"
|
||||||
versionPath = "/version"
|
versionPath = "/version"
|
||||||
)
|
)
|
||||||
@ -83,6 +85,7 @@ func NewClientHandler(server *etcdserver.EtcdServer) http.Handler {
|
|||||||
mux.HandleFunc(statsPrefix+"/store", sh.serveStore)
|
mux.HandleFunc(statsPrefix+"/store", sh.serveStore)
|
||||||
mux.HandleFunc(statsPrefix+"/self", sh.serveSelf)
|
mux.HandleFunc(statsPrefix+"/self", sh.serveSelf)
|
||||||
mux.HandleFunc(statsPrefix+"/leader", sh.serveLeader)
|
mux.HandleFunc(statsPrefix+"/leader", sh.serveLeader)
|
||||||
|
mux.HandleFunc(statsPath, serveStats)
|
||||||
mux.Handle(membersPrefix, mh)
|
mux.Handle(membersPrefix, mh)
|
||||||
mux.Handle(membersPrefix+"/", mh)
|
mux.Handle(membersPrefix+"/", mh)
|
||||||
mux.Handle(deprecatedMachinesPrefix, dmh)
|
mux.Handle(deprecatedMachinesPrefix, dmh)
|
||||||
@ -284,6 +287,21 @@ func (h *statsHandler) serveLeader(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Write(stats)
|
w.Write(stats)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func serveStats(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
// TODO: getting one key or a prefix of keys based on path
|
||||||
|
fmt.Fprintf(w, "{\n")
|
||||||
|
first := true
|
||||||
|
expvar.Do(func(kv expvar.KeyValue) {
|
||||||
|
if !first {
|
||||||
|
fmt.Fprintf(w, ",\n")
|
||||||
|
}
|
||||||
|
first = false
|
||||||
|
fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value)
|
||||||
|
})
|
||||||
|
fmt.Fprintf(w, "\n}\n")
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: change etcdserver to raft interface when we have it.
|
// TODO: change etcdserver to raft interface when we have it.
|
||||||
// add test for healthHeadler when we have the interface ready.
|
// add test for healthHeadler when we have the interface ready.
|
||||||
func healthHandler(server *etcdserver.EtcdServer) http.HandlerFunc {
|
func healthHandler(server *etcdserver.EtcdServer) http.HandlerFunc {
|
||||||
|
111
pkg/metrics/metrics.go
Normal file
111
pkg/metrics/metrics.go
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 CoreOS, Inc.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package metrics provides metrics view of variables which is exposed through
|
||||||
|
// expvar package.
|
||||||
|
//
|
||||||
|
// Naming conventions:
|
||||||
|
// 1. volatile path components should be kept as deep into the hierarchy as possible
|
||||||
|
// 2. each path component should have a clear and well-defined purpose
|
||||||
|
// 3. components.separated.with.dot, and put package prefix at the head
|
||||||
|
// 4. words_separated_with_underscore, and put clarifiers last, e.g., requests_total
|
||||||
|
package metrics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"expvar"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Counter is a number that increases over time monotonically.
|
||||||
|
type Counter struct{ i *expvar.Int }
|
||||||
|
|
||||||
|
func NewCounter(name string) *Counter {
|
||||||
|
return &Counter{i: expvar.NewInt(name)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Counter) Add() { c.i.Add(1) }
|
||||||
|
|
||||||
|
func (c *Counter) AddBy(delta int64) { c.i.Add(delta) }
|
||||||
|
|
||||||
|
func (c *Counter) String() string { return c.i.String() }
|
||||||
|
|
||||||
|
// Gauge returns instantaneous value that is expected to fluctuate over time.
|
||||||
|
type Gauge struct{ i *expvar.Int }
|
||||||
|
|
||||||
|
func NewGauge(name string) *Gauge {
|
||||||
|
return &Gauge{i: expvar.NewInt(name)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Gauge) Set(value int64) { g.i.Set(value) }
|
||||||
|
|
||||||
|
func (g *Gauge) String() string { return g.i.String() }
|
||||||
|
|
||||||
|
type nilVar struct{}
|
||||||
|
|
||||||
|
func (v *nilVar) String() string { return "nil" }
|
||||||
|
|
||||||
|
// Map aggregates Counters and Gauges.
|
||||||
|
type Map struct{ *expvar.Map }
|
||||||
|
|
||||||
|
func NewMap(name string) *Map {
|
||||||
|
return &Map{Map: expvar.NewMap(name)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMap returns the map if it exists, or inits the given name map if it does
|
||||||
|
// not exist.
|
||||||
|
func GetMap(name string) *Map {
|
||||||
|
if m, ok := expvar.Get(name).(*expvar.Map); ok {
|
||||||
|
return &Map{Map: m}
|
||||||
|
}
|
||||||
|
return NewMap(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Map) NewCounter(key string) *Counter {
|
||||||
|
c := &Counter{i: new(expvar.Int)}
|
||||||
|
m.Set(key, c)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Map) NewGauge(key string) *Gauge {
|
||||||
|
g := &Gauge{i: new(expvar.Int)}
|
||||||
|
m.Set(key, g)
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: remove the var from the map to avoid memory boom
|
||||||
|
func (m *Map) Delete(key string) { m.Set(key, &nilVar{}) }
|
||||||
|
|
||||||
|
// String returns JSON format string that represents the group.
|
||||||
|
// It does not print out nilVar.
|
||||||
|
func (m *Map) String() string {
|
||||||
|
var b bytes.Buffer
|
||||||
|
fmt.Fprintf(&b, "{")
|
||||||
|
first := true
|
||||||
|
m.Do(func(kv expvar.KeyValue) {
|
||||||
|
v := kv.Value.String()
|
||||||
|
if v == "nil" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !first {
|
||||||
|
fmt.Fprintf(&b, ", ")
|
||||||
|
}
|
||||||
|
fmt.Fprintf(&b, "%q: %v", kv.Key, v)
|
||||||
|
first = false
|
||||||
|
})
|
||||||
|
fmt.Fprintf(&b, "}")
|
||||||
|
return b.String()
|
||||||
|
}
|
48
pkg/metrics/metrics_test.go
Normal file
48
pkg/metrics/metrics_test.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 CoreOS, Inc.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package metrics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"expvar"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestMetrics tests the basic usage of metrics.
|
||||||
|
func TestMetrics(t *testing.T) {
|
||||||
|
m := &Map{Map: new(expvar.Map).Init()}
|
||||||
|
|
||||||
|
c := m.NewCounter("number")
|
||||||
|
c.Add()
|
||||||
|
c.AddBy(10)
|
||||||
|
if w := "11"; c.String() != w {
|
||||||
|
t.Errorf("counter = %s, want %s", c, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
g := m.NewGauge("price")
|
||||||
|
g.Set(100)
|
||||||
|
if w := "100"; g.String() != w {
|
||||||
|
t.Errorf("gauge = %s, want %s", g, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
if w := `{"number": 11, "price": 100}`; m.String() != w {
|
||||||
|
t.Errorf("map = %s, want %s", m, w)
|
||||||
|
}
|
||||||
|
m.Delete("price")
|
||||||
|
if w := `{"number": 11}`; m.String() != w {
|
||||||
|
t.Errorf("map after deletion = %s, want %s", m, w)
|
||||||
|
}
|
||||||
|
}
|
@ -20,11 +20,27 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
"github.com/coreos/etcd/pkg/metrics"
|
||||||
|
"github.com/coreos/etcd/pkg/types"
|
||||||
"github.com/coreos/etcd/raft/raftpb"
|
"github.com/coreos/etcd/raft/raftpb"
|
||||||
)
|
)
|
||||||
|
|
||||||
type entryReader struct {
|
type entryReader struct {
|
||||||
r io.Reader
|
r io.Reader
|
||||||
|
id types.ID
|
||||||
|
ents *metrics.Counter
|
||||||
|
bytes *metrics.Counter
|
||||||
|
lastIndex *metrics.Gauge
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEntryReader(r io.Reader, id types.ID) *entryReader {
|
||||||
|
return &entryReader{
|
||||||
|
r: r,
|
||||||
|
id: id,
|
||||||
|
ents: metrics.GetMap("rafthttp.stream.entries_received").NewCounter(id.String()),
|
||||||
|
bytes: metrics.GetMap("rafthttp.stream.bytes_received").NewCounter(id.String()),
|
||||||
|
lastIndex: metrics.GetMap("rafthttp.stream.last_index_received").NewGauge(id.String()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (er *entryReader) readEntries() ([]raftpb.Entry, error) {
|
func (er *entryReader) readEntries() ([]raftpb.Entry, error) {
|
||||||
@ -32,12 +48,15 @@ func (er *entryReader) readEntries() ([]raftpb.Entry, error) {
|
|||||||
if err := binary.Read(er.r, binary.BigEndian, &l); err != nil {
|
if err := binary.Read(er.r, binary.BigEndian, &l); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
er.bytes.AddBy(8)
|
||||||
ents := make([]raftpb.Entry, int(l))
|
ents := make([]raftpb.Entry, int(l))
|
||||||
for i := 0; i < int(l); i++ {
|
for i := 0; i < int(l); i++ {
|
||||||
if err := er.readEntry(&ents[i]); err != nil {
|
if err := er.readEntry(&ents[i]); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
er.ents.Add()
|
||||||
}
|
}
|
||||||
|
er.lastIndex.Set(int64(ents[l-1].Index))
|
||||||
return ents, nil
|
return ents, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,5 +69,12 @@ func (er *entryReader) readEntry(ent *raftpb.Entry) error {
|
|||||||
if _, err := io.ReadFull(er.r, buf); err != nil {
|
if _, err := io.ReadFull(er.r, buf); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
er.bytes.AddBy(8 + int64(l))
|
||||||
return ent.Unmarshal(buf)
|
return ent.Unmarshal(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (er *entryReader) stop() {
|
||||||
|
metrics.GetMap("rafthttp.stream.entries_received").Delete(er.id.String())
|
||||||
|
metrics.GetMap("rafthttp.stream.bytes_received").Delete(er.id.String())
|
||||||
|
metrics.GetMap("rafthttp.stream.last_index_received").Delete(er.id.String())
|
||||||
|
}
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/coreos/etcd/pkg/types"
|
||||||
"github.com/coreos/etcd/raft/raftpb"
|
"github.com/coreos/etcd/raft/raftpb"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -45,12 +46,12 @@ func TestEntsWriteAndRead(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for i, tt := range tests {
|
for i, tt := range tests {
|
||||||
b := &bytes.Buffer{}
|
b := &bytes.Buffer{}
|
||||||
ew := &entryWriter{w: b}
|
ew := newEntryWriter(b, types.ID(1))
|
||||||
if err := ew.writeEntries(tt); err != nil {
|
if err := ew.writeEntries(tt); err != nil {
|
||||||
t.Errorf("#%d: unexpected write ents error: %v", i, err)
|
t.Errorf("#%d: unexpected write ents error: %v", i, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
er := &entryReader{r: b}
|
er := newEntryReader(b, types.ID(1))
|
||||||
ents, err := er.readEntries()
|
ents, err := er.readEntries()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("#%d: unexpected read ents error: %v", i, err)
|
t.Errorf("#%d: unexpected read ents error: %v", i, err)
|
||||||
|
@ -20,23 +20,46 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
"github.com/coreos/etcd/pkg/metrics"
|
||||||
|
"github.com/coreos/etcd/pkg/types"
|
||||||
"github.com/coreos/etcd/raft/raftpb"
|
"github.com/coreos/etcd/raft/raftpb"
|
||||||
)
|
)
|
||||||
|
|
||||||
type entryWriter struct {
|
type entryWriter struct {
|
||||||
w io.Writer
|
w io.Writer
|
||||||
|
id types.ID
|
||||||
|
ents *metrics.Counter
|
||||||
|
bytes *metrics.Counter
|
||||||
|
lastIndex *metrics.Gauge
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEntryWriter(w io.Writer, id types.ID) *entryWriter {
|
||||||
|
ew := &entryWriter{
|
||||||
|
w: w,
|
||||||
|
id: id,
|
||||||
|
ents: metrics.GetMap("rafthttp.stream.entries_sent").NewCounter(id.String()),
|
||||||
|
bytes: metrics.GetMap("rafthttp.stream.bytes_sent").NewCounter(id.String()),
|
||||||
|
lastIndex: metrics.GetMap("rafthttp.stream.last_index_sent").NewGauge(id.String()),
|
||||||
|
}
|
||||||
|
return ew
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ew *entryWriter) writeEntries(ents []raftpb.Entry) error {
|
func (ew *entryWriter) writeEntries(ents []raftpb.Entry) error {
|
||||||
l := len(ents)
|
l := len(ents)
|
||||||
|
if l == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
if err := binary.Write(ew.w, binary.BigEndian, uint64(l)); err != nil {
|
if err := binary.Write(ew.w, binary.BigEndian, uint64(l)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
ew.bytes.AddBy(8)
|
||||||
for i := 0; i < l; i++ {
|
for i := 0; i < l; i++ {
|
||||||
if err := ew.writeEntry(&ents[i]); err != nil {
|
if err := ew.writeEntry(&ents[i]); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
ew.ents.Add()
|
||||||
}
|
}
|
||||||
|
ew.lastIndex.Set(int64(ents[l-1].Index))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,5 +73,12 @@ func (ew *entryWriter) writeEntry(ent *raftpb.Entry) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = ew.w.Write(b)
|
_, err = ew.w.Write(b)
|
||||||
|
ew.bytes.AddBy(8 + int64(size))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ew *entryWriter) stop() {
|
||||||
|
metrics.GetMap("rafthttp.stream.entries_sent").Delete(ew.id.String())
|
||||||
|
metrics.GetMap("rafthttp.stream.bytes_sent").Delete(ew.id.String())
|
||||||
|
metrics.GetMap("rafthttp.stream.last_index_sent").Delete(ew.id.String())
|
||||||
|
}
|
||||||
|
@ -160,7 +160,7 @@ type streamWriter struct {
|
|||||||
done chan struct{}
|
done chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// newStreamServer starts and returns a new started stream server.
|
// newStreamWriter starts and returns a new unstarted stream writer.
|
||||||
// The caller should call stop when finished, to shut it down.
|
// The caller should call stop when finished, to shut it down.
|
||||||
func newStreamWriter(to types.ID, term uint64) *streamWriter {
|
func newStreamWriter(to types.ID, term uint64) *streamWriter {
|
||||||
s := &streamWriter{
|
s := &streamWriter{
|
||||||
@ -193,7 +193,8 @@ func (s *streamWriter) handle(w WriteFlusher) {
|
|||||||
log.Printf("rafthttp: server streaming to %s at term %d has been stopped", s.to, s.term)
|
log.Printf("rafthttp: server streaming to %s at term %d has been stopped", s.to, s.term)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
ew := &entryWriter{w: w}
|
ew := newEntryWriter(w, s.to)
|
||||||
|
defer ew.stop()
|
||||||
for ents := range s.q {
|
for ents := range s.q {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
if err := ew.writeEntries(ents); err != nil {
|
if err := ew.writeEntries(ents); err != nil {
|
||||||
@ -280,7 +281,8 @@ func (s *streamReader) handle(r io.Reader) {
|
|||||||
log.Printf("rafthttp: client streaming to %s at term %d has been stopped", s.to, s.term)
|
log.Printf("rafthttp: client streaming to %s at term %d has been stopped", s.to, s.term)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
er := &entryReader{r: r}
|
er := newEntryReader(r, s.to)
|
||||||
|
defer er.stop()
|
||||||
for {
|
for {
|
||||||
ents, err := er.readEntries()
|
ents, err := er.readEntries()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user