Observe computes the incremental cost of each step and records it into the CostTracker associated with the evaluation.
(vars Activation, id int64, programStep any, val ref.Val)
| 95 | // Observe computes the incremental cost of each step and records it into the CostTracker associated |
| 96 | // with the evaluation. |
| 97 | func (ct *costTrackerFactory) Observe(vars Activation, id int64, programStep any, val ref.Val) { |
| 98 | frame := AsFrame(vars) |
| 99 | state := ct.GetState(frame) |
| 100 | if state == nil { |
| 101 | return |
| 102 | } |
| 103 | tracker, ok := state.(*CostTracker) |
| 104 | if !ok { |
| 105 | // The state is configured with CostTrackFactory so this shouldn't happen. |
| 106 | return |
| 107 | } |
| 108 | switch t := programStep.(type) { |
| 109 | case ConstantQualifier: |
| 110 | // TODO: Push identifiers on to the stack before observing constant qualifiers that apply to them |
| 111 | // and enable the below pop. Once enabled this can case can be collapsed into the Qualifier case. |
| 112 | tracker.cost++ |
| 113 | case InterpretableConst: |
| 114 | // zero cost |
| 115 | case InterpretableAttribute: |
| 116 | switch a := t.Attr().(type) { |
| 117 | case *conditionalAttribute: |
| 118 | // Ternary has no direct cost. All cost is from the conditional and the true/false branch expressions. |
| 119 | tracker.stack.drop(a.falsy.ID(), a.truthy.ID(), a.expr.ID()) |
| 120 | default: |
| 121 | tracker.stack.drop(t.Attr().ID()) |
| 122 | tracker.cost += common.SelectAndIdentCost |
| 123 | } |
| 124 | if !tracker.presenceTestHasCost { |
| 125 | if _, isTestOnly := programStep.(*evalTestOnly); isTestOnly { |
| 126 | tracker.cost -= common.SelectAndIdentCost |
| 127 | } |
| 128 | } |
| 129 | case *evalExhaustiveConditional: |
| 130 | // Ternary has no direct cost. All cost is from the conditional and the true/false branch expressions. |
| 131 | tracker.stack.drop(t.attr.falsy.ID(), t.attr.truthy.ID(), t.attr.expr.ID()) |
| 132 | |
| 133 | // While the field names are identical, the boolean operation eval structs do not share an interface and so |
| 134 | // must be handled individually. |
| 135 | case *evalOr: |
| 136 | for _, term := range t.terms { |
| 137 | tracker.stack.drop(term.ID()) |
| 138 | } |
| 139 | case *evalAnd: |
| 140 | for _, term := range t.terms { |
| 141 | tracker.stack.drop(term.ID()) |
| 142 | } |
| 143 | case *evalExhaustiveOr: |
| 144 | for _, term := range t.terms { |
| 145 | tracker.stack.drop(term.ID()) |
| 146 | } |
| 147 | case *evalExhaustiveAnd: |
| 148 | for _, term := range t.terms { |
| 149 | tracker.stack.drop(term.ID()) |
| 150 | } |
| 151 | case *evalFold: |
| 152 | tracker.stack.drop(t.iterRange.ID()) |
| 153 | case Qualifier: |
| 154 | tracker.cost++ |