Skip to content

Commit

Permalink
Merge pull request #1871 from flipt-io/boolean-otel-metrics
Browse files Browse the repository at this point in the history
feat(metrics): Add otel and general metrics for boolean evaluation
  • Loading branch information
yquansah committed Jul 15, 2023
2 parents d307321 + a1ec0b6 commit c33fba5
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 22 deletions.
35 changes: 24 additions & 11 deletions internal/server/evaluation/evaluation.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,10 @@ func (s *Server) Variant(ctx context.Context, v *rpcevaluation.EvaluationRequest
fliptotel.AttributeFlag.String(v.FlagKey),
fliptotel.AttributeEntityID.String(v.EntityId),
fliptotel.AttributeRequestID.String(v.RequestId),
}

if ver != nil {
spanAttrs = append(spanAttrs,
fliptotel.AttributeMatch.Bool(ver.Match),
fliptotel.AttributeValue.String(ver.VariantKey),
fliptotel.AttributeReason.String(ver.Reason.String()),
fliptotel.AttributeSegment.String(ver.SegmentKey),
)
fliptotel.AttributeMatch.Bool(ver.Match),
fliptotel.AttributeValue.String(ver.VariantKey),
fliptotel.AttributeReason.String(ver.Reason.String()),
fliptotel.AttributeSegment.String(ver.SegmentKey),
}

// add otel attributes to span
Expand Down Expand Up @@ -101,7 +96,25 @@ func (s *Server) Boolean(ctx context.Context, r *rpcevaluation.EvaluationRequest
return nil, errs.ErrInvalidf("flag type %s invalid", flag.Type)
}

return s.boolean(ctx, flag, r)
ber, err := s.boolean(ctx, flag, r)
if err != nil {
return nil, err
}

spanAttrs := []attribute.KeyValue{
fliptotel.AttributeNamespace.String(r.NamespaceKey),
fliptotel.AttributeFlag.String(r.FlagKey),
fliptotel.AttributeEntityID.String(r.EntityId),
fliptotel.AttributeRequestID.String(r.RequestId),
fliptotel.AttributeValue.Bool(ber.Value),
fliptotel.AttributeReason.String(ber.Reason.String()),
}

// add otel attributes to span
span := trace.SpanFromContext(ctx)
span.SetAttributes(spanAttrs...)

return ber, nil
}

func (s *Server) boolean(ctx context.Context, flag *flipt.Flag, r *rpcevaluation.EvaluationRequest) (*rpcevaluation.BooleanEvaluationResponse, error) {
Expand All @@ -110,7 +123,7 @@ func (s *Server) boolean(ctx context.Context, flag *flipt.Flag, r *rpcevaluation
return nil, err
}

Check warning on line 124 in internal/server/evaluation/evaluation.go

View check run for this annotation

Codecov / codecov/patch

internal/server/evaluation/evaluation.go#L123-L124

Added lines #L123 - L124 were not covered by tests

resp, err := s.evaluator.booleanMatch(r, flag.Enabled, rollouts)
resp, err := s.evaluator.booleanMatch(ctx, r, flag.Enabled, rollouts)
if err != nil {
return nil, err
}
Expand Down
60 changes: 49 additions & 11 deletions internal/server/evaluation/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ func (e *Evaluator) Evaluate(ctx context.Context, flag *flipt.Flag, r *flipt.Eva
metrics.AttributeSegment.String(resp.SegmentKey),
metrics.AttributeReason.String(resp.Reason.String()),
metrics.AttributeValue.String(resp.Value),
metrics.AttributeType.String("variant"),
),
),
)
Expand Down Expand Up @@ -192,8 +193,45 @@ func (e *Evaluator) Evaluate(ctx context.Context, flag *flipt.Flag, r *flipt.Eva
return resp, nil
}

func (e *Evaluator) booleanMatch(r *rpcevaluation.EvaluationRequest, flagValue bool, rollouts []*storage.EvaluationRollout) (*rpcevaluation.BooleanEvaluationResponse, error) {
resp := &rpcevaluation.BooleanEvaluationResponse{}
func (e *Evaluator) booleanMatch(ctx context.Context, r *rpcevaluation.EvaluationRequest, flagValue bool, rollouts []*storage.EvaluationRollout) (resp *rpcevaluation.BooleanEvaluationResponse, err error) {
res := &rpcevaluation.BooleanEvaluationResponse{}

var (
startTime = time.Now().UTC()
namespaceAttr = metrics.AttributeNamespace.String(r.NamespaceKey)
flagAttr = metrics.AttributeFlag.String(r.FlagKey)
)

metrics.EvaluationsTotal.Add(ctx, 1, metric.WithAttributeSet(attribute.NewSet(namespaceAttr, flagAttr)))

defer func() {
if err == nil {
metrics.EvaluationResultsTotal.Add(ctx, 1,
metric.WithAttributeSet(
attribute.NewSet(
namespaceAttr,
flagAttr,
metrics.AttributeValue.Bool(resp.Value),
metrics.AttributeReason.String(resp.Reason.String()),
metrics.AttributeType.String("boolean"),
),
),
)
} else {
metrics.EvaluationErrorsTotal.Add(ctx, 1, metric.WithAttributeSet(attribute.NewSet(namespaceAttr, flagAttr)))
}

metrics.EvaluationLatency.Record(
ctx,
float64(time.Since(startTime).Nanoseconds())/1e6,
metric.WithAttributeSet(
attribute.NewSet(
namespaceAttr,
flagAttr,
),
),
)
}()

var lastRank int32

Expand All @@ -212,11 +250,11 @@ func (e *Evaluator) booleanMatch(r *rpcevaluation.EvaluationRequest, flagValue b

// if this case does not hold, fall through to the next rollout.
if normalizedValue < rollout.Threshold.Percentage {
resp.Value = rollout.Threshold.Value
resp.Reason = rpcevaluation.EvaluationReason_MATCH_EVALUATION_REASON
res.Value = rollout.Threshold.Value
res.Reason = rpcevaluation.EvaluationReason_MATCH_EVALUATION_REASON
e.logger.Debug("threshold based matched", zap.Int("rank", int(rollout.Rank)), zap.String("rollout_type", "threshold"))

return resp, nil
return res, nil
}
} else if rollout.Segment != nil {
matched, err := e.matchConstraints(r.Context, rollout.Segment.Constraints, rollout.Segment.MatchType)
Expand All @@ -229,21 +267,21 @@ func (e *Evaluator) booleanMatch(r *rpcevaluation.EvaluationRequest, flagValue b
continue

Check warning on line 267 in internal/server/evaluation/evaluator.go

View check run for this annotation

Codecov / codecov/patch

internal/server/evaluation/evaluator.go#L267

Added line #L267 was not covered by tests
}

resp.Value = rollout.Segment.Value
resp.Reason = rpcevaluation.EvaluationReason_MATCH_EVALUATION_REASON
res.Value = rollout.Segment.Value
res.Reason = rpcevaluation.EvaluationReason_MATCH_EVALUATION_REASON

e.logger.Debug("segment based matched", zap.Int("rank", int(rollout.Rank)), zap.String("segment", rollout.Segment.Key))

return resp, nil
return res, nil
}
}

// If we have exhausted all rollouts and we still don't have a match, return the default value.
resp.Reason = rpcevaluation.EvaluationReason_DEFAULT_EVALUATION_REASON
resp.Value = flagValue
res.Reason = rpcevaluation.EvaluationReason_DEFAULT_EVALUATION_REASON
res.Value = flagValue
e.logger.Debug("default rollout matched", zap.Bool("value", flagValue))

return resp, nil
return res, nil
}

// matchConstraints is a utility function that will return if all or any constraints have matched for a segment depending
Expand Down
1 change: 1 addition & 0 deletions internal/server/metrics/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,5 @@ var (
AttributeReason = attribute.Key("reason")
AttributeValue = attribute.Key("value")
AttributeNamespace = attribute.Key("namespace")
AttributeType = attribute.Key("type")
)

0 comments on commit c33fba5

Please sign in to comment.