@@ -61,9 +61,14 @@ type Row struct {
61
61
62
62
const notMatched byte = ' '
63
63
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 {
65
71
"etcdserverpb.KV/Range" : {
66
- // All calls should go through etcd-k8s interface
67
72
args : []column {
68
73
{name : "limit" , matcher : isLimitSet },
69
74
{name : "rangeEnd" , matcher : isRangeEndSet },
@@ -114,7 +119,6 @@ var referenceUsageOfEtcdAPI = map[string]refOp{
114
119
},
115
120
},
116
121
"etcdserverpb.KV/Txn" : {
117
- // All calls should go through etcd-k8s interface
118
122
args : []column {
119
123
{name : "getOnFailure" , matcher : keyIsEqualInt ("failure_len" , 1 )},
120
124
{name : "readOnly" , matcher : isReadOnly },
@@ -132,8 +136,6 @@ var referenceUsageOfEtcdAPI = map[string]refOp{
132
136
},
133
137
},
134
138
"etcdserverpb.KV/Compact" : {
135
- // Compaction should move to using internal Etcd mechanism
136
- // Discussed in https://github.com/kubernetes/kubernetes/issues/80513
137
139
args : []column {
138
140
{name : "rev" , matcher : isRevisionSet },
139
141
{name : "physical" , matcher : boolAttrSet ("is_physical" )},
@@ -143,7 +145,6 @@ var referenceUsageOfEtcdAPI = map[string]refOp{
143
145
},
144
146
},
145
147
"etcdserverpb.Watch/Watch" : {
146
- // Not part of the contract interface (yet)
147
148
args : []column {
148
149
{name : "range_end" , matcher : isRangeEndSet },
149
150
{name : "start_rev" , matcher : intAttrSet ("start_rev" )},
@@ -156,12 +157,6 @@ var referenceUsageOfEtcdAPI = map[string]refOp{
156
157
{name : "Watch" , matcher : notMatcher (keyIsEqualStr ("key" , "compact_rev_key" ))},
157
158
},
158
159
},
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
- },
165
160
}
166
161
167
162
func TestInterfaceUse (t * testing.T ) {
@@ -195,25 +190,35 @@ func testInterfaceUse(t *testing.T, filename string) {
195
190
spansByID := spansMap (t , dump .Result .GetResourceSpans ())
196
191
callsByOperationName , countsByGRPC := callsMap (spansByID )
197
192
t .Logf ("\n %s" , printableCallTable (countsByGRPC ))
193
+ spansByLeaseID := leaseMap (callsByOperationName )
198
194
199
195
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
+
200
211
for opName , count := range countsByGRPC {
201
- if _ , ok := referenceUsageOfEtcdAPI [opName ]; ! ok {
212
+ if _ , ok := expectedEtcdMethodsCalled [opName ]; ! ok {
202
213
t .Errorf ("unexpected %d calls to method: %s" , count , opName )
203
214
}
204
215
}
205
216
})
206
-
207
- for op , td := range referenceUsageOfEtcdAPI {
217
+ for op , td := range referenceUsageOfWatchAndKV {
208
218
t .Run (op , func (t * testing.T ) {
209
219
if _ , ok := countsByGRPC [op ]; ! ok {
210
220
t .Fatalf ("expected %q method to be called at least once" , op )
211
221
}
212
-
213
- if len (td .args ) == 0 {
214
- return
215
- }
216
-
217
222
// tracesWithNoMethod ensures that we print error only once when a
218
223
// new call pattern is found.
219
224
tracesWithNoMethod := make (map [string ]bool )
@@ -245,6 +250,40 @@ func testInterfaceUse(t *testing.T, filename string) {
245
250
t .Logf ("\n %s" , printableMatcherTable (td .args , callCounts , contractCallCounts ))
246
251
})
247
252
}
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
+ })
248
287
}
249
288
250
289
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
310
349
return spansByID
311
350
}
312
351
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
+
313
392
func extractPattern (span * tracev1.Span , key string ) (string , bool ) {
314
393
if key == "" {
315
394
return "" , true
@@ -449,3 +528,34 @@ func printableMatcherTable(cols []column, res map[Row]int, contract map[Row]int)
449
528
table .Render ()
450
529
return buf .String ()
451
530
}
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