(call InterpretableCall, args []ref.Val, result ref.Val)
| 252 | } |
| 253 | |
| 254 | func (c *CostTracker) costCall(call InterpretableCall, args []ref.Val, result ref.Val) uint64 { |
| 255 | var cost uint64 |
| 256 | if len(c.overloadTrackers) != 0 { |
| 257 | if tracker, found := c.overloadTrackers[call.OverloadID()]; found { |
| 258 | callCost := tracker(args, result) |
| 259 | if callCost != nil { |
| 260 | cost += *callCost |
| 261 | return cost |
| 262 | } |
| 263 | } |
| 264 | } |
| 265 | if c.Estimator != nil { |
| 266 | callCost := c.Estimator.CallCost(call.Function(), call.OverloadID(), args, result) |
| 267 | if callCost != nil { |
| 268 | cost += *callCost |
| 269 | return cost |
| 270 | } |
| 271 | } |
| 272 | // if user didn't specify, the default way of calculating runtime cost would be used. |
| 273 | // if user has their own implementation of ActualCostEstimator, make sure to cover the mapping between overloadId and cost calculation |
| 274 | switch call.OverloadID() { |
| 275 | // O(n) functions |
| 276 | case overloads.StartsWithString, overloads.EndsWithString: |
| 277 | cost += uint64(math.Ceil(float64(actualSize(args[1])) * common.StringTraversalCostFactor)) |
| 278 | case overloads.StringToBytes, overloads.BytesToString, overloads.ExtQuoteString, overloads.ExtFormatString: |
| 279 | cost += uint64(math.Ceil(float64(actualSize(args[0])) * common.StringTraversalCostFactor)) |
| 280 | case overloads.InList: |
| 281 | // If a list is composed entirely of constant values this is O(1), but we don't account for that here. |
| 282 | // We just assume all list containment checks are O(n). |
| 283 | cost += actualSize(args[1]) |
| 284 | // O(min(m, n)) functions |
| 285 | case overloads.LessString, overloads.GreaterString, overloads.LessEqualsString, overloads.GreaterEqualsString, |
| 286 | overloads.LessBytes, overloads.GreaterBytes, overloads.LessEqualsBytes, overloads.GreaterEqualsBytes, |
| 287 | overloads.Equals, overloads.NotEquals: |
| 288 | // When we check the equality of 2 scalar values (e.g. 2 integers, 2 floating-point numbers, 2 booleans etc.), |
| 289 | // the CostTracker.ActualSize() function by definition returns 1 for each operand, resulting in an overall cost |
| 290 | // of 1. |
| 291 | lhsSize := actualSize(args[0]) |
| 292 | rhsSize := actualSize(args[1]) |
| 293 | minSize := lhsSize |
| 294 | if rhsSize < minSize { |
| 295 | minSize = rhsSize |
| 296 | } |
| 297 | cost += uint64(math.Ceil(float64(minSize) * common.StringTraversalCostFactor)) |
| 298 | // O(m+n) functions |
| 299 | case overloads.AddString, overloads.AddBytes: |
| 300 | // In the worst case scenario, we would need to reallocate a new backing store and copy both operands over. |
| 301 | cost += uint64(math.Ceil(float64(actualSize(args[0])+actualSize(args[1])) * common.StringTraversalCostFactor)) |
| 302 | // O(nm) functions |
| 303 | case overloads.Matches, overloads.MatchesString: |
| 304 | // https://swtch.com/~rsc/regexp/regexp1.html applies to RE2 implementation supported by CEL |
| 305 | // Add one to string length for purposes of cost calculation to prevent product of string and regex to be 0 |
| 306 | // in case where string is empty but regex is still expensive. |
| 307 | strCost := uint64(math.Ceil((1.0 + float64(actualSize(args[0]))) * common.StringTraversalCostFactor)) |
| 308 | // We don't know how many expressions are in the regex, just the string length (a huge |
| 309 | // improvement here would be to somehow get a count the number of expressions in the regex or |
| 310 | // how many states are in the regex state machine and use that to measure regex cost). |
| 311 | // For now, we're making a guess that each expression in a regex is typically at least 4 chars |
no test coverage detected