mirror of
https://github.com/etcd-io/etcd.git
synced 2024-09-27 06:25:44 +00:00

Calls to Status can be frequent and currently incur three heap allocations, but often the caller has no intention to hold on to the returned status. Add StatusWithoutProgress and WithProgress to allow avoiding heap allocations altogether. StatusWithoutProgress does what's on the tin and additionally returns a value (instead of a pointer) to avoid the associated heap allocation. By not returning a Progress map, it avoids all other allocations that Status incurs. To still introspect the Progress map, add WithProgress, which uses a simple visitor pattern. Add benchmarks to verify that this is indeed allocation free. ``` BenchmarkStatusProgress/members=1/Status-8 5000000 353 ns/op 784 B/op 3 allocs/op BenchmarkStatusProgress/members=1/Status-example-8 5000000 372 ns/op 784 B/op 3 allocs/op BenchmarkStatusProgress/members=1/StatusWithoutProgress-8 100000000 17.6 ns/op 0 B/op 0 allocs/op BenchmarkStatusProgress/members=1/WithProgress-8 30000000 48.6 ns/op 0 B/op 0 allocs/op BenchmarkStatusProgress/members=1/WithProgress-example-8 30000000 42.9 ns/op 0 B/op 0 allocs/op BenchmarkStatusProgress/members=3/Status-8 5000000 395 ns/op 784 B/op 3 allocs/op BenchmarkStatusProgress/members=3/Status-example-8 3000000 449 ns/op 784 B/op 3 allocs/op BenchmarkStatusProgress/members=3/StatusWithoutProgress-8 100000000 18.7 ns/op 0 B/op 0 allocs/op BenchmarkStatusProgress/members=3/WithProgress-8 20000000 78.1 ns/op 0 B/op 0 allocs/op BenchmarkStatusProgress/members=3/WithProgress-example-8 20000000 70.7 ns/op 0 B/op 0 allocs/op BenchmarkStatusProgress/members=5/Status-8 3000000 470 ns/op 784 B/op 3 allocs/op BenchmarkStatusProgress/members=5/Status-example-8 3000000 544 ns/op 784 B/op 3 allocs/op BenchmarkStatusProgress/members=5/StatusWithoutProgress-8 100000000 19.7 ns/op 0 B/op 0 allocs/op BenchmarkStatusProgress/members=5/WithProgress-8 20000000 105 ns/op 0 B/op 0 allocs/op BenchmarkStatusProgress/members=5/WithProgress-example-8 20000000 94.0 ns/op 0 B/op 0 allocs/op BenchmarkStatusProgress/members=100/Status-8 100000 11903 ns/op 22663 B/op 12 allocs/op BenchmarkStatusProgress/members=100/Status-example-8 100000 13330 ns/op 22669 B/op 12 allocs/op BenchmarkStatusProgress/members=100/StatusWithoutProgress-8 50000000 20.9 ns/op 0 B/op 0 allocs/op BenchmarkStatusProgress/members=100/WithProgress-8 1000000 1731 ns/op 0 B/op 0 allocs/op BenchmarkStatusProgress/members=100/WithProgress-example-8 1000000 1571 ns/op 0 B/op 0 allocs/op ```
95 lines
2.2 KiB
Go
95 lines
2.2 KiB
Go
// Copyright 2015 The etcd Authors
|
|
//
|
|
// 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 raft
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
pb "go.etcd.io/etcd/raft/raftpb"
|
|
)
|
|
|
|
type Status struct {
|
|
ID uint64
|
|
|
|
pb.HardState
|
|
SoftState
|
|
|
|
Applied uint64
|
|
Progress map[uint64]Progress
|
|
|
|
LeadTransferee uint64
|
|
}
|
|
|
|
func getProgressCopy(r *raft) map[uint64]Progress {
|
|
prs := make(map[uint64]Progress)
|
|
for id, p := range r.prs {
|
|
prs[id] = *p
|
|
}
|
|
|
|
for id, p := range r.learnerPrs {
|
|
prs[id] = *p
|
|
}
|
|
return prs
|
|
}
|
|
|
|
func getStatusWithoutProgress(r *raft) Status {
|
|
s := Status{
|
|
ID: r.id,
|
|
LeadTransferee: r.leadTransferee,
|
|
}
|
|
s.HardState = r.hardState()
|
|
s.SoftState = *r.softState()
|
|
s.Applied = r.raftLog.applied
|
|
return s
|
|
}
|
|
|
|
// getStatus gets a copy of the current raft status.
|
|
func getStatus(r *raft) Status {
|
|
s := getStatusWithoutProgress(r)
|
|
if s.RaftState == StateLeader {
|
|
s.Progress = getProgressCopy(r)
|
|
}
|
|
return s
|
|
}
|
|
|
|
// MarshalJSON translates the raft status into JSON.
|
|
// TODO: try to simplify this by introducing ID type into raft
|
|
func (s Status) MarshalJSON() ([]byte, error) {
|
|
j := fmt.Sprintf(`{"id":"%x","term":%d,"vote":"%x","commit":%d,"lead":"%x","raftState":%q,"applied":%d,"progress":{`,
|
|
s.ID, s.Term, s.Vote, s.Commit, s.Lead, s.RaftState, s.Applied)
|
|
|
|
if len(s.Progress) == 0 {
|
|
j += "},"
|
|
} else {
|
|
for k, v := range s.Progress {
|
|
subj := fmt.Sprintf(`"%x":{"match":%d,"next":%d,"state":%q},`, k, v.Match, v.Next, v.State)
|
|
j += subj
|
|
}
|
|
// remove the trailing ","
|
|
j = j[:len(j)-1] + "},"
|
|
}
|
|
|
|
j += fmt.Sprintf(`"leadtransferee":"%x"}`, s.LeadTransferee)
|
|
return []byte(j), nil
|
|
}
|
|
|
|
func (s Status) String() string {
|
|
b, err := s.MarshalJSON()
|
|
if err != nil {
|
|
raftLogger.Panicf("unexpected error: %v", err)
|
|
}
|
|
return string(b)
|
|
}
|