Skip to content

Commit e96c86f

Browse files
authored
Merge pull request #20555 from AwesomePatrol/add-detailed-lease-analysis
Expand Lease test case in trace analysis
2 parents afaa220 + c32c9f1 commit e96c86f

File tree

1 file changed

+130
-20
lines changed

1 file changed

+130
-20
lines changed

tests/robustness/coverage/coverage_test.go

Lines changed: 130 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,14 @@ type Row struct {
6161

6262
const notMatched byte = ' '
6363

64-
var referenceUsageOfEtcdAPI = map[string]refOp{
64+
type leaseRow struct {
65+
SumUses int
66+
SumTTL int
67+
Calls int
68+
}
69+
70+
var referenceUsageOfWatchAndKV = map[string]refOp{
6571
"etcdserverpb.KV/Range": {
66-
// All calls should go through etcd-k8s interface
6772
args: []column{
6873
{name: "limit", matcher: isLimitSet},
6974
{name: "rangeEnd", matcher: isRangeEndSet},
@@ -114,7 +119,6 @@ var referenceUsageOfEtcdAPI = map[string]refOp{
114119
},
115120
},
116121
"etcdserverpb.KV/Txn": {
117-
// All calls should go through etcd-k8s interface
118122
args: []column{
119123
{name: "getOnFailure", matcher: keyIsEqualInt("failure_len", 1)},
120124
{name: "readOnly", matcher: isReadOnly},
@@ -132,8 +136,6 @@ var referenceUsageOfEtcdAPI = map[string]refOp{
132136
},
133137
},
134138
"etcdserverpb.KV/Compact": {
135-
// Compaction should move to using internal Etcd mechanism
136-
// Discussed in https://github.com/kubernetes/kubernetes/issues/80513
137139
args: []column{
138140
{name: "rev", matcher: isRevisionSet},
139141
{name: "physical", matcher: boolAttrSet("is_physical")},
@@ -143,7 +145,6 @@ var referenceUsageOfEtcdAPI = map[string]refOp{
143145
},
144146
},
145147
"etcdserverpb.Watch/Watch": {
146-
// Not part of the contract interface (yet)
147148
args: []column{
148149
{name: "range_end", matcher: isRangeEndSet},
149150
{name: "start_rev", matcher: intAttrSet("start_rev")},
@@ -156,12 +157,6 @@ var referenceUsageOfEtcdAPI = map[string]refOp{
156157
{name: "Watch", matcher: notMatcher(keyIsEqualStr("key", "compact_rev_key"))},
157158
},
158159
},
159-
"etcdserverpb.Lease/LeaseGrant": {
160-
// Used to manage masterleases and events
161-
},
162-
"etcdserverpb.Maintenance/Status": {
163-
// Used to expose database size on apiserver's metrics endpoint
164-
},
165160
}
166161

167162
func TestInterfaceUse(t *testing.T) {
@@ -195,25 +190,35 @@ func testInterfaceUse(t *testing.T, filename string) {
195190
spansByID := spansMap(t, dump.Result.GetResourceSpans())
196191
callsByOperationName, countsByGRPC := callsMap(spansByID)
197192
t.Logf("\n%s", printableCallTable(countsByGRPC))
193+
spansByLeaseID := leaseMap(callsByOperationName)
198194

199195
t.Run("only_expected_methods_were_called", func(t *testing.T) {
196+
expectedEtcdMethodsCalled := map[string]bool{
197+
// All calls should go through etcd-k8s interface
198+
"etcdserverpb.KV/Range": true,
199+
"etcdserverpb.KV/Txn": true,
200+
// Not part of the contract interface (yet)
201+
"etcdserverpb.Watch/Watch": true,
202+
// Compaction should move to using internal Etcd mechanism
203+
// Discussed in https://github.com/kubernetes/kubernetes/issues/80513
204+
"etcdserverpb.KV/Compact": true,
205+
// Used to manage masterleases and events
206+
"etcdserverpb.Lease/LeaseGrant": true,
207+
// Used to expose database size on apiserver's metrics endpoint
208+
"etcdserverpb.Maintenance/Status": true,
209+
}
210+
200211
for opName, count := range countsByGRPC {
201-
if _, ok := referenceUsageOfEtcdAPI[opName]; !ok {
212+
if _, ok := expectedEtcdMethodsCalled[opName]; !ok {
202213
t.Errorf("unexpected %d calls to method: %s", count, opName)
203214
}
204215
}
205216
})
206-
207-
for op, td := range referenceUsageOfEtcdAPI {
217+
for op, td := range referenceUsageOfWatchAndKV {
208218
t.Run(op, func(t *testing.T) {
209219
if _, ok := countsByGRPC[op]; !ok {
210220
t.Fatalf("expected %q method to be called at least once", op)
211221
}
212-
213-
if len(td.args) == 0 {
214-
return
215-
}
216-
217222
// tracesWithNoMethod ensures that we print error only once when a
218223
// new call pattern is found.
219224
tracesWithNoMethod := make(map[string]bool)
@@ -245,6 +250,40 @@ func testInterfaceUse(t *testing.T, filename string) {
245250
t.Logf("\n%s", printableMatcherTable(td.args, callCounts, contractCallCounts))
246251
})
247252
}
253+
254+
t.Run("etcdserverpb.Lease/LeaseGrant", func(t *testing.T) {
255+
op := "etcdserverpb.Lease/LeaseGrant"
256+
if _, ok := countsByGRPC[op]; !ok {
257+
t.Fatalf("expected %q method to be called at least once", op)
258+
}
259+
callCounts := make(map[string]leaseRow)
260+
for _, span := range callsByOperationName[op] {
261+
leaseID, lFound := intAttr(span, "id")
262+
if !lFound {
263+
t.Errorf("Lease without ID: %v", span)
264+
continue
265+
}
266+
txns := spansByLeaseID[leaseID]
267+
268+
pattern, pFound := extractPatternFromTxns(txns)
269+
if !pFound {
270+
t.Errorf("New key pattern detected: %s", pattern)
271+
continue
272+
}
273+
row := callCounts[pattern]
274+
ttl, found := intAttr(span, "ttl")
275+
if !found {
276+
t.Errorf("Lease without TTL: %v", span)
277+
continue
278+
}
279+
row.SumTTL += ttl
280+
row.SumUses += len(txns)
281+
row.Calls++
282+
callCounts[pattern] = row
283+
}
284+
285+
t.Logf("\n%s", printableLeaseTable(callCounts))
286+
})
248287
}
249288

250289
func callsMap(spansByID map[string]*tracev1.Span) (map[string][]*tracev1.Span, map[string]int) {
@@ -310,6 +349,46 @@ func spansMap(t *testing.T, traces []*tracev1.ResourceSpans) map[string]*tracev1
310349
return spansByID
311350
}
312351

352+
func leaseMap(callsByOperationName map[string][]*tracev1.Span) map[int][]*tracev1.Span {
353+
ret := make(map[int][]*tracev1.Span)
354+
for _, leaseSpan := range callsByOperationName["etcdserverpb.Lease/LeaseGrant"] {
355+
leaseID, lFound := intAttr(leaseSpan, "id")
356+
if !lFound {
357+
continue
358+
}
359+
ret[leaseID] = nil
360+
}
361+
for _, txnSpan := range callsByOperationName["etcdserverpb.KV/Txn"] {
362+
leaseID, lFound := intAttr(txnSpan, "success_first_lease")
363+
if !lFound {
364+
continue
365+
}
366+
ret[leaseID] = append(ret[leaseID], txnSpan)
367+
}
368+
return ret
369+
}
370+
371+
func extractPatternFromTxns(txns []*tracev1.Span) (string, bool) {
372+
patterns := make(map[string]int)
373+
for _, txn := range txns {
374+
key, kFound := strAttr(txn, "success_first_key")
375+
if kFound {
376+
p, pFound := pattern(key)
377+
if !pFound {
378+
return key, false
379+
}
380+
patterns[p]++
381+
}
382+
}
383+
if len(patterns) > 1 {
384+
return "multiple key patterns", false
385+
}
386+
for p := range patterns {
387+
return p, true
388+
}
389+
return "no pattern found", false
390+
}
391+
313392
func extractPattern(span *tracev1.Span, key string) (string, bool) {
314393
if key == "" {
315394
return "", true
@@ -449,3 +528,34 @@ func printableMatcherTable(cols []column, res map[Row]int, contract map[Row]int)
449528
table.Render()
450529
return buf.String()
451530
}
531+
532+
func printableLeaseTable(callCounts map[string]leaseRow) string {
533+
hdr := []string{"pattern", "avg uses", "avg ttl", "calls", "percent"}
534+
buf := new(bytes.Buffer)
535+
alignment := make([]tw.Align, len(hdr))
536+
alignment[0] = tw.AlignLeft
537+
cfgBuilder := tablewriter.NewConfigBuilder().
538+
WithRowAlignment(tw.AlignRight).
539+
Row().Alignment().WithPerColumn(alignment).Build()
540+
table := tablewriter.NewTable(buf, tablewriter.WithConfig(cfgBuilder.Build()))
541+
table.Header(hdr)
542+
543+
totalCalls := 0
544+
for _, row := range callCounts {
545+
totalCalls += row.Calls
546+
}
547+
548+
for pattern, row := range callCounts {
549+
table.Append(
550+
pattern,
551+
fmt.Sprintf("%.1f", float64(row.SumUses)/float64(row.Calls)),
552+
fmt.Sprintf("%.1f", float64(row.SumTTL)/float64(row.Calls)),
553+
strconv.Itoa(row.Calls),
554+
fmt.Sprintf("%.2f%%", float64(row.Calls*100)/float64(totalCalls)),
555+
)
556+
}
557+
558+
table.Footer([]string{3: strconv.Itoa(totalCalls), 4: "100.00%"})
559+
table.Render()
560+
return buf.String()
561+
}

0 commit comments

Comments
 (0)