Skip to content

Commit 7e09bf3

Browse files
Merge pull request #119807 from jpbetz/automated-cherry-pick-of-#119800-origin-release-1.28
Automated cherry pick of #119800: Fixes CEL estimated cost to propagate result sizes correctly Kubernetes-commit: ab3cebfdb2cd1054f34f4287a757755810ede009
2 parents bf038b7 + 915c09d commit 7e09bf3

File tree

4 files changed

+187
-12
lines changed

4 files changed

+187
-12
lines changed

go.mod

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ require (
1111
github.com/evanphx/json-patch v4.12.0+incompatible
1212
github.com/fsnotify/fsnotify v1.6.0
1313
github.com/gogo/protobuf v1.3.2
14-
github.com/google/cel-go v0.16.0
14+
github.com/google/cel-go v0.16.1
1515
github.com/google/gnostic-models v0.6.8
1616
github.com/google/go-cmp v0.5.9
1717
github.com/google/gofuzz v1.2.0
@@ -43,7 +43,7 @@ require (
4343
gopkg.in/square/go-jose.v2 v2.6.0
4444
k8s.io/api v0.0.0-20230904104028-546e4253e738
4545
k8s.io/apimachinery v0.0.0-20230904102823-bc548d1d2406
46-
k8s.io/client-go v0.0.0-20230904110526-29a840dbdf2a
46+
k8s.io/client-go v0.0.0-20230904110526-745513ad7b37
4747
k8s.io/component-base v0.0.0-20230904111932-ef6aa9891ad3
4848
k8s.io/klog/v2 v2.100.1
4949
k8s.io/kms v0.0.0-20230904112654-85054a342003
@@ -128,7 +128,7 @@ require (
128128
replace (
129129
k8s.io/api => k8s.io/api v0.0.0-20230904104028-546e4253e738
130130
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20230904102823-bc548d1d2406
131-
k8s.io/client-go => k8s.io/client-go v0.0.0-20230904110526-29a840dbdf2a
131+
k8s.io/client-go => k8s.io/client-go v0.0.0-20230904110526-745513ad7b37
132132
k8s.io/component-base => k8s.io/component-base v0.0.0-20230904111932-ef6aa9891ad3
133133
k8s.io/kms => k8s.io/kms v0.0.0-20230904112654-85054a342003
134134
)

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,8 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z
160160
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
161161
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
162162
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
163-
github.com/google/cel-go v0.16.0 h1:DG9YQ8nFCFXAs/FDDwBxmL1tpKNrdlGUM9U3537bX/Y=
164-
github.com/google/cel-go v0.16.0/go.mod h1:HXZKzB0LXqer5lHHgfWAnlYwJaQBDKMjxjulNQzhwhY=
163+
github.com/google/cel-go v0.16.1 h1:3hZfSNiAU3KOiNtxuFXVp5WFy4hf/Ly3Sa4/7F8SXNo=
164+
github.com/google/cel-go v0.16.1/go.mod h1:HXZKzB0LXqer5lHHgfWAnlYwJaQBDKMjxjulNQzhwhY=
165165
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
166166
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
167167
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@@ -674,8 +674,8 @@ k8s.io/api v0.0.0-20230904104028-546e4253e738 h1:0PmEjeZX6g4bLgztHHEVVj0XrMQ+F8u
674674
k8s.io/api v0.0.0-20230904104028-546e4253e738/go.mod h1:4GpdFbUbYEFNj6e44T5yz6hE7p8yAn4+dYvmqTPVuOs=
675675
k8s.io/apimachinery v0.0.0-20230904102823-bc548d1d2406 h1:/YP5PLKVrV5WDzScq2agCBHPP0FCLVrU/4d9+j8uEFs=
676676
k8s.io/apimachinery v0.0.0-20230904102823-bc548d1d2406/go.mod h1:RdzF87y/ngqk9H4z3EL2Rppv5jj95vGS/HaFXrLDApU=
677-
k8s.io/client-go v0.0.0-20230904110526-29a840dbdf2a h1:Qgr4OBvwwsxyKonHVJCGGOJH4Zee2uMD1v2st+fVByY=
678-
k8s.io/client-go v0.0.0-20230904110526-29a840dbdf2a/go.mod h1:9oac6LWqvfKcphw6P69YH6Ns8xgf4S3ciKrBfqXxq3c=
677+
k8s.io/client-go v0.0.0-20230904110526-745513ad7b37 h1:+SF0CtBoQI6Eso+8poN4i55d4oUahClerZwALYSz6Tw=
678+
k8s.io/client-go v0.0.0-20230904110526-745513ad7b37/go.mod h1:9oac6LWqvfKcphw6P69YH6Ns8xgf4S3ciKrBfqXxq3c=
679679
k8s.io/component-base v0.0.0-20230904111932-ef6aa9891ad3 h1:6J1OGuJeD5K+0YHFN5lOfVIIEpg+Iw+0w4VEw+EipVE=
680680
k8s.io/component-base v0.0.0-20230904111932-ef6aa9891ad3/go.mod h1:idL61MaRYcyCDRF1/WNvCkmTbPkAeViaCwM8CA3jRRQ=
681681
k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg=

pkg/cel/library/cost.go

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -126,13 +126,51 @@ func (l *CostEstimator) EstimateCallCost(function, overloadId string, target *ch
126126
sz := l.sizeEstimate(*target)
127127
toReplaceSz := l.sizeEstimate(args[0])
128128
replaceWithSz := l.sizeEstimate(args[1])
129-
// smallest possible result: smallest input size composed of the largest possible substrings being replaced by smallest possible replacement
130-
minSz := uint64(math.Ceil(float64(sz.Min)/float64(toReplaceSz.Max))) * replaceWithSz.Min
131-
// largest possible result: largest input size composed of the smallest possible substrings being replaced by largest possible replacement
132-
maxSz := uint64(math.Ceil(float64(sz.Max)/float64(toReplaceSz.Min))) * replaceWithSz.Max
129+
130+
var replaceCount, retainedSz checker.SizeEstimate
131+
// find the longest replacement:
132+
if toReplaceSz.Min == 0 {
133+
// if the string being replaced is empty, replace surrounds all characters in the input string with the replacement.
134+
if sz.Max < math.MaxUint64 {
135+
replaceCount.Max = sz.Max + 1
136+
} else {
137+
replaceCount.Max = sz.Max
138+
}
139+
// Include the length of the longest possible original string length.
140+
retainedSz.Max = sz.Max
141+
} else if replaceWithSz.Max <= toReplaceSz.Min {
142+
// If the replacement does not make the result longer, use the original string length.
143+
replaceCount.Max = 0
144+
retainedSz.Max = sz.Max
145+
} else {
146+
// Replace the smallest possible substrings with the largest possible replacement
147+
// as many times as possible.
148+
replaceCount.Max = uint64(math.Ceil(float64(sz.Max) / float64(toReplaceSz.Min)))
149+
}
150+
151+
// find the shortest replacement:
152+
if toReplaceSz.Max == 0 {
153+
// if the string being replaced is empty, replace surrounds all characters in the input string with the replacement.
154+
if sz.Min < math.MaxUint64 {
155+
replaceCount.Min = sz.Min + 1
156+
} else {
157+
replaceCount.Min = sz.Min
158+
}
159+
// Include the length of the shortest possible original string length.
160+
retainedSz.Min = sz.Min
161+
} else if toReplaceSz.Max <= replaceWithSz.Min {
162+
// If the replacement does not make the result shorter, use the original string length.
163+
replaceCount.Min = 0
164+
retainedSz.Min = sz.Min
165+
} else {
166+
// Replace the largest possible substrings being with the smallest possible replacement
167+
// as many times as possible.
168+
replaceCount.Min = uint64(math.Ceil(float64(sz.Min) / float64(toReplaceSz.Max)))
169+
}
170+
size := replaceCount.Multiply(replaceWithSz).Add(retainedSz)
133171

134172
// cost is the traversal plus the construction of the result
135-
return &checker.CallEstimate{CostEstimate: sz.MultiplyByCostFactor(2 * common.StringTraversalCostFactor), ResultSize: &checker.SizeEstimate{Min: minSz, Max: maxSz}}
173+
return &checker.CallEstimate{CostEstimate: sz.MultiplyByCostFactor(2 * common.StringTraversalCostFactor), ResultSize: &size}
136174
}
137175
case "split":
138176
if target != nil {

pkg/cel/library/cost_test.go

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,18 +227,54 @@ func TestStringLibrary(t *testing.T) {
227227
expectEsimatedCost: checker.CostEstimate{Min: 3, Max: 3},
228228
expectRuntimeCost: 3,
229229
},
230+
{
231+
name: "lowerAsciiEquals",
232+
expr: "'ABCDEFGHIJ abcdefghij'.lowerAscii() == 'abcdefghij ABCDEFGHIJ'.lowerAscii()",
233+
expectEsimatedCost: checker.CostEstimate{Min: 7, Max: 9},
234+
expectRuntimeCost: 9,
235+
},
230236
{
231237
name: "upperAscii",
232238
expr: "'ABCDEFGHIJ abcdefghij'.upperAscii()",
233239
expectEsimatedCost: checker.CostEstimate{Min: 3, Max: 3},
234240
expectRuntimeCost: 3,
235241
},
242+
{
243+
name: "upperAsciiEquals",
244+
expr: "'ABCDEFGHIJ abcdefghij'.upperAscii() == 'abcdefghij ABCDEFGHIJ'.upperAscii()",
245+
expectEsimatedCost: checker.CostEstimate{Min: 7, Max: 9},
246+
expectRuntimeCost: 9,
247+
},
248+
{
249+
name: "quote",
250+
expr: "strings.quote('ABCDEFGHIJ abcdefghij')",
251+
expectEsimatedCost: checker.CostEstimate{Min: 3, Max: 3},
252+
expectRuntimeCost: 3,
253+
},
254+
{
255+
name: "quoteEquals",
256+
expr: "strings.quote('ABCDEFGHIJ abcdefghij') == strings.quote('ABCDEFGHIJ abcdefghij')",
257+
expectEsimatedCost: checker.CostEstimate{Min: 7, Max: 11},
258+
expectRuntimeCost: 9,
259+
},
236260
{
237261
name: "replace",
238262
expr: "'abc 123 def 123'.replace('123', '456')",
239263
expectEsimatedCost: checker.CostEstimate{Min: 3, Max: 3},
240264
expectRuntimeCost: 3,
241265
},
266+
{
267+
name: "replace between all chars",
268+
expr: "'abc 123 def 123'.replace('', 'x')",
269+
expectEsimatedCost: checker.CostEstimate{Min: 3, Max: 3},
270+
expectRuntimeCost: 3,
271+
},
272+
{
273+
name: "replace with empty",
274+
expr: "'abc 123 def 123'.replace('123', '')",
275+
expectEsimatedCost: checker.CostEstimate{Min: 3, Max: 3},
276+
expectRuntimeCost: 3,
277+
},
242278
{
243279
name: "replace with limit",
244280
expr: "'abc 123 def 123'.replace('123', '456', 1)",
@@ -413,6 +449,107 @@ func testCost(t *testing.T, expr string, expectEsimatedCost checker.CostEstimate
413449
}
414450
}
415451

452+
func TestSize(t *testing.T) {
453+
exactSize := func(size int) checker.SizeEstimate {
454+
return checker.SizeEstimate{Min: uint64(size), Max: uint64(size)}
455+
}
456+
exactSizes := func(sizes ...int) []checker.SizeEstimate {
457+
results := make([]checker.SizeEstimate, len(sizes))
458+
for i, size := range sizes {
459+
results[i] = exactSize(size)
460+
}
461+
return results
462+
}
463+
cases := []struct {
464+
name string
465+
function string
466+
overload string
467+
targetSize checker.SizeEstimate
468+
argSizes []checker.SizeEstimate
469+
expectSize checker.SizeEstimate
470+
}{
471+
{
472+
name: "replace empty with char",
473+
function: "replace",
474+
targetSize: exactSize(3), // e.g. abc
475+
argSizes: exactSizes(0, 1), // e.g. replace "" with "_"
476+
expectSize: exactSize(7), // e.g. _a_b_c_
477+
},
478+
{
479+
name: "maybe replace char with empty",
480+
function: "replace",
481+
targetSize: exactSize(3),
482+
argSizes: exactSizes(1, 0),
483+
expectSize: checker.SizeEstimate{Min: 0, Max: 3},
484+
},
485+
{
486+
name: "maybe replace repeated",
487+
function: "replace",
488+
targetSize: exactSize(4),
489+
argSizes: exactSizes(2, 4),
490+
expectSize: checker.SizeEstimate{Min: 4, Max: 8},
491+
},
492+
{
493+
name: "maybe replace empty",
494+
function: "replace",
495+
targetSize: exactSize(4),
496+
argSizes: []checker.SizeEstimate{{Min: 0, Max: 1}, {Min: 0, Max: 2}},
497+
expectSize: checker.SizeEstimate{Min: 0, Max: 14}, // len(__a__a__a__a__) == 14
498+
},
499+
{
500+
name: "replace non-empty size range, maybe larger",
501+
function: "replace",
502+
targetSize: exactSize(4),
503+
argSizes: []checker.SizeEstimate{{Min: 1, Max: 1}, {Min: 1, Max: 2}},
504+
expectSize: checker.SizeEstimate{Min: 4, Max: 8},
505+
},
506+
{
507+
name: "replace non-empty size range, maybe smaller",
508+
function: "replace",
509+
targetSize: exactSize(4),
510+
argSizes: []checker.SizeEstimate{{Min: 1, Max: 2}, {Min: 1, Max: 1}},
511+
expectSize: checker.SizeEstimate{Min: 2, Max: 4},
512+
},
513+
}
514+
est := &CostEstimator{SizeEstimator: &testCostEstimator{}}
515+
for _, tc := range cases {
516+
t.Run(tc.name, func(t *testing.T) {
517+
var targetNode checker.AstNode = testSizeNode{size: tc.targetSize}
518+
argNodes := make([]checker.AstNode, len(tc.argSizes))
519+
for i, arg := range tc.argSizes {
520+
argNodes[i] = testSizeNode{size: arg}
521+
}
522+
result := est.EstimateCallCost(tc.function, tc.overload, &targetNode, argNodes)
523+
if result.ResultSize == nil {
524+
t.Fatalf("Expected ResultSize but got none")
525+
}
526+
if *result.ResultSize != tc.expectSize {
527+
t.Fatalf("Expected %+v but got %+v", tc.expectSize, *result.ResultSize)
528+
}
529+
})
530+
}
531+
}
532+
533+
type testSizeNode struct {
534+
size checker.SizeEstimate
535+
}
536+
537+
func (t testSizeNode) Path() []string {
538+
return nil // not needed
539+
}
540+
541+
func (t testSizeNode) Type() *expr.Type {
542+
return nil // not needed
543+
}
544+
545+
func (t testSizeNode) Expr() *expr.Expr {
546+
return nil // not needed
547+
}
548+
549+
func (t testSizeNode) ComputedSize() *checker.SizeEstimate {
550+
return &t.size
551+
}
552+
416553
type testCostEstimator struct {
417554
}
418555

0 commit comments

Comments
 (0)