blob: 8712dc6a45518d5e65cabb0358eee6fd14ea9ceb [file] [log] [blame]
Abhay Kumar40252eb2025-10-13 13:25:53 +00001// Copyright 2024 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
15//go:build with_tla
16
17package raft
18
19import (
20 "strconv"
21 "time"
22
23 "go.etcd.io/raft/v3/raftpb"
24 "go.etcd.io/raft/v3/tracker"
25)
26
27const StateTraceDeployed = true
28
29type stateMachineEventType int
30
31const (
32 rsmInitState stateMachineEventType = iota
33 rsmBecomeCandidate
34 rsmBecomeFollower
35 rsmBecomeLeader
36 rsmCommit
37 rsmReplicate
38 rsmChangeConf
39 rsmApplyConfChange
40 rsmReady
41 rsmSendAppendEntriesRequest
42 rsmReceiveAppendEntriesRequest
43 rsmSendAppendEntriesResponse
44 rsmReceiveAppendEntriesResponse
45 rsmSendRequestVoteRequest
46 rsmReceiveRequestVoteRequest
47 rsmSendRequestVoteResponse
48 rsmReceiveRequestVoteResponse
49 rsmSendSnapshot
50 rsmReceiveSnapshot
51)
52
53func (e stateMachineEventType) String() string {
54 return []string{
55 "InitState",
56 "BecomeCandidate",
57 "BecomeFollower",
58 "BecomeLeader",
59 "Commit",
60 "Replicate",
61 "ChangeConf",
62 "ApplyConfChange",
63 "Ready",
64 "SendAppendEntriesRequest",
65 "ReceiveAppendEntriesRequest",
66 "SendAppendEntriesResponse",
67 "ReceiveAppendEntriesResponse",
68 "SendRequestVoteRequest",
69 "ReceiveRequestVoteRequest",
70 "SendRequestVoteResponse",
71 "ReceiveRequestVoteResponse",
72 "SendSnapshot",
73 "ReceiveSnapshot",
74 }[e]
75}
76
77const (
78 ConfChangeAddNewServer string = "AddNewServer"
79 ConfChangeRemoveServer string = "RemoveServer"
80 ConfChangeAddLearner string = "AddLearner"
81)
82
83type TracingEvent struct {
84 Name string `json:"name"`
85 NodeID string `json:"nid"`
86 State TracingState `json:"state"`
87 Role string `json:"role"`
88 LogSize uint64 `json:"log"`
89 Conf [2][]string `json:"conf"`
90 Message *TracingMessage `json:"msg,omitempty"`
91 ConfChange *TracingConfChange `json:"cc,omitempty"`
92 Properties map[string]any `json:"prop,omitempty"`
93}
94
95type TracingState struct {
96 Term uint64 `json:"term"`
97 Vote string `json:"vote"`
98 Commit uint64 `json:"commit"`
99}
100
101type TracingMessage struct {
102 Type string `json:"type"`
103 Term uint64 `json:"term"`
104 From string `json:"from"`
105 To string `json:"to"`
106 EntryLength int `json:"entries"`
107 LogTerm uint64 `json:"logTerm"`
108 Index uint64 `json:"index"`
109 Commit uint64 `json:"commit"`
110 Vote string `json:"vote"`
111 Reject bool `json:"reject"`
112 RejectHint uint64 `json:"rejectHint"`
113}
114
115type SingleConfChange struct {
116 NodeID string `json:"nid"`
117 Action string `json:"action"`
118}
119
120type TracingConfChange struct {
121 Changes []SingleConfChange `json:"changes,omitempty"`
122 NewConf []string `json:"newconf,omitempty"`
123}
124
125func makeTracingState(r *raft) TracingState {
126 hs := r.hardState()
127 return TracingState{
128 Term: hs.Term,
129 Vote: strconv.FormatUint(hs.Vote, 10),
130 Commit: hs.Commit,
131 }
132}
133
134func makeTracingMessage(m *raftpb.Message) *TracingMessage {
135 if m == nil {
136 return nil
137 }
138
139 logTerm := m.LogTerm
140 entries := len(m.Entries)
141 index := m.Index
142 if m.Type == raftpb.MsgSnap {
143 index = 0
144 logTerm = 0
145 entries = int(m.Snapshot.Metadata.Index)
146 }
147 return &TracingMessage{
148 Type: m.Type.String(),
149 Term: m.Term,
150 From: strconv.FormatUint(m.From, 10),
151 To: strconv.FormatUint(m.To, 10),
152 EntryLength: entries,
153 LogTerm: logTerm,
154 Index: index,
155 Commit: m.Commit,
156 Vote: strconv.FormatUint(m.Vote, 10),
157 Reject: m.Reject,
158 RejectHint: m.RejectHint,
159 }
160}
161
162type TraceLogger interface {
163 TraceEvent(*TracingEvent)
164}
165
166func traceEvent(evt stateMachineEventType, r *raft, m *raftpb.Message, prop map[string]any) {
167 if r.traceLogger == nil {
168 return
169 }
170
171 r.traceLogger.TraceEvent(&TracingEvent{
172 Name: evt.String(),
173 NodeID: strconv.FormatUint(r.id, 10),
174 State: makeTracingState(r),
175 LogSize: r.raftLog.lastIndex(),
176 Conf: [2][]string{formatConf(r.trk.Voters[0].Slice()), formatConf(r.trk.Voters[1].Slice())},
177 Role: r.state.String(),
178 Message: makeTracingMessage(m),
179 Properties: prop,
180 })
181}
182
183func traceNodeEvent(evt stateMachineEventType, r *raft) {
184 traceEvent(evt, r, nil, nil)
185}
186
187func formatConf(s []uint64) []string {
188 if s == nil {
189 return []string{}
190 }
191
192 r := make([]string, len(s))
193 for i, v := range s {
194 r[i] = strconv.FormatUint(v, 10)
195 }
196 return r
197}
198
199// Use following helper functions to trace specific state and/or
200// transition at corresponding code lines
201func traceInitState(r *raft) {
202 if r.traceLogger == nil {
203 return
204 }
205
206 traceNodeEvent(rsmInitState, r)
207}
208
209func traceReady(r *raft) {
210 traceNodeEvent(rsmReady, r)
211}
212
213func traceCommit(r *raft) {
214 traceNodeEvent(rsmCommit, r)
215}
216
217func traceReplicate(r *raft, es ...raftpb.Entry) {
218 for i := range es {
219 if es[i].Type == raftpb.EntryNormal {
220 traceNodeEvent(rsmReplicate, r)
221 }
222 }
223}
224
225func traceBecomeFollower(r *raft) {
226 traceNodeEvent(rsmBecomeFollower, r)
227}
228
229func traceBecomeCandidate(r *raft) {
230 traceNodeEvent(rsmBecomeCandidate, r)
231}
232
233func traceBecomeLeader(r *raft) {
234 traceNodeEvent(rsmBecomeLeader, r)
235}
236
237func traceChangeConfEvent(cci raftpb.ConfChangeI, r *raft) {
238 cc2 := cci.AsV2()
239 cc := &TracingConfChange{
240 Changes: []SingleConfChange{},
241 NewConf: []string{},
242 }
243 for _, c := range cc2.Changes {
244 switch c.Type {
245 case raftpb.ConfChangeAddNode:
246 cc.Changes = append(cc.Changes, SingleConfChange{
247 NodeID: strconv.FormatUint(c.NodeID, 10),
248 Action: ConfChangeAddNewServer,
249 })
250 case raftpb.ConfChangeRemoveNode:
251 cc.Changes = append(cc.Changes, SingleConfChange{
252 NodeID: strconv.FormatUint(c.NodeID, 10),
253 Action: ConfChangeRemoveServer,
254 })
255 case raftpb.ConfChangeAddLearnerNode:
256 cc.Changes = append(cc.Changes, SingleConfChange{
257 NodeID: strconv.FormatUint(c.NodeID, 10),
258 Action: ConfChangeAddLearner,
259 })
260 }
261 }
262
263 if len(cc.Changes) == 0 {
264 return
265 }
266
267 p := map[string]any{}
268 p["cc"] = cc
269 traceEvent(rsmChangeConf, r, nil, p)
270}
271
272func traceConfChangeEvent(cfg tracker.Config, r *raft) {
273 if r.traceLogger == nil {
274 return
275 }
276
277 cc := &TracingConfChange{
278 Changes: []SingleConfChange{},
279 NewConf: formatConf(cfg.Voters[0].Slice()),
280 }
281
282 p := map[string]any{}
283 p["cc"] = cc
284 traceEvent(rsmApplyConfChange, r, nil, p)
285}
286
287func traceSendMessage(r *raft, m *raftpb.Message) {
288 if r.traceLogger == nil {
289 return
290 }
291
292 prop := map[string]any{}
293
294 var evt stateMachineEventType
295 switch m.Type {
296 case raftpb.MsgApp:
297 evt = rsmSendAppendEntriesRequest
298 if p, exist := r.trk.Progress[m.From]; exist {
299 prop["match"] = p.Match
300 prop["next"] = p.Next
301 }
302
303 case raftpb.MsgHeartbeat, raftpb.MsgSnap:
304 evt = rsmSendAppendEntriesRequest
305 case raftpb.MsgAppResp, raftpb.MsgHeartbeatResp:
306 evt = rsmSendAppendEntriesResponse
307 case raftpb.MsgVote:
308 evt = rsmSendRequestVoteRequest
309 case raftpb.MsgVoteResp:
310 evt = rsmSendRequestVoteResponse
311 default:
312 return
313 }
314
315 traceEvent(evt, r, m, prop)
316}
317
318func traceReceiveMessage(r *raft, m *raftpb.Message) {
319 if r.traceLogger == nil {
320 return
321 }
322
323 var evt stateMachineEventType
324 switch m.Type {
325 case raftpb.MsgApp, raftpb.MsgHeartbeat, raftpb.MsgSnap:
326 evt = rsmReceiveAppendEntriesRequest
327 case raftpb.MsgAppResp, raftpb.MsgHeartbeatResp:
328 evt = rsmReceiveAppendEntriesResponse
329 case raftpb.MsgVote:
330 evt = rsmReceiveRequestVoteRequest
331 case raftpb.MsgVoteResp:
332 evt = rsmReceiveRequestVoteResponse
333 default:
334 return
335 }
336
337 time.Sleep(time.Millisecond) // sleep 1ms to reduce time shift impact accross node
338 traceEvent(evt, r, m, nil)
339}