1// Copyright 2015 The etcd Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package v2stats
16
17import (
18	"encoding/json"
19	"math"
20	"sync"
21	"time"
22
23	"go.uber.org/zap"
24)
25
26// LeaderStats is used by the leader in an etcd cluster, and encapsulates
27// statistics about communication with its followers
28type LeaderStats struct {
29	lg *zap.Logger
30	leaderStats
31	sync.Mutex
32}
33
34type leaderStats struct {
35	// Leader is the ID of the leader in the etcd cluster.
36	// TODO(jonboulle): clarify that these are IDs, not names
37	Leader    string                    `json:"leader"`
38	Followers map[string]*FollowerStats `json:"followers"`
39}
40
41// NewLeaderStats generates a new LeaderStats with the given id as leader
42func NewLeaderStats(lg *zap.Logger, id string) *LeaderStats {
43	if lg == nil {
44		lg = zap.NewNop()
45	}
46	return &LeaderStats{
47		lg: lg,
48		leaderStats: leaderStats{
49			Leader:    id,
50			Followers: make(map[string]*FollowerStats),
51		},
52	}
53}
54
55func (ls *LeaderStats) JSON() []byte {
56	ls.Lock()
57	stats := ls.leaderStats
58	ls.Unlock()
59	b, err := json.Marshal(stats)
60	// TODO(jonboulle): appropriate error handling?
61	if err != nil {
62		ls.lg.Error("failed to marshal leader stats", zap.Error(err))
63	}
64	return b
65}
66
67func (ls *LeaderStats) Follower(name string) *FollowerStats {
68	ls.Lock()
69	defer ls.Unlock()
70	fs, ok := ls.Followers[name]
71	if !ok {
72		fs = &FollowerStats{}
73		fs.Latency.Minimum = 1 << 63
74		ls.Followers[name] = fs
75	}
76	return fs
77}
78
79// FollowerStats encapsulates various statistics about a follower in an etcd cluster
80type FollowerStats struct {
81	Latency LatencyStats `json:"latency"`
82	Counts  CountsStats  `json:"counts"`
83
84	sync.Mutex
85}
86
87// LatencyStats encapsulates latency statistics.
88type LatencyStats struct {
89	Current           float64 `json:"current"`
90	Average           float64 `json:"average"`
91	averageSquare     float64
92	StandardDeviation float64 `json:"standardDeviation"`
93	Minimum           float64 `json:"minimum"`
94	Maximum           float64 `json:"maximum"`
95}
96
97// CountsStats encapsulates raft statistics.
98type CountsStats struct {
99	Fail    uint64 `json:"fail"`
100	Success uint64 `json:"success"`
101}
102
103// Succ updates the FollowerStats with a successful send
104func (fs *FollowerStats) Succ(d time.Duration) {
105	fs.Lock()
106	defer fs.Unlock()
107
108	total := float64(fs.Counts.Success) * fs.Latency.Average
109	totalSquare := float64(fs.Counts.Success) * fs.Latency.averageSquare
110
111	fs.Counts.Success++
112
113	fs.Latency.Current = float64(d) / (1000000.0)
114
115	if fs.Latency.Current > fs.Latency.Maximum {
116		fs.Latency.Maximum = fs.Latency.Current
117	}
118
119	if fs.Latency.Current < fs.Latency.Minimum {
120		fs.Latency.Minimum = fs.Latency.Current
121	}
122
123	fs.Latency.Average = (total + fs.Latency.Current) / float64(fs.Counts.Success)
124	fs.Latency.averageSquare = (totalSquare + fs.Latency.Current*fs.Latency.Current) / float64(fs.Counts.Success)
125
126	// sdv = sqrt(avg(x^2) - avg(x)^2)
127	fs.Latency.StandardDeviation = math.Sqrt(fs.Latency.averageSquare - fs.Latency.Average*fs.Latency.Average)
128}
129
130// Fail updates the FollowerStats with an unsuccessful send
131func (fs *FollowerStats) Fail() {
132	fs.Lock()
133	defer fs.Unlock()
134	fs.Counts.Fail++
135}
136