Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,8 @@ The following metrics are collected and displayed:
- `pipelines_total` - Total number of pipelines (Unit: count)
- `pipeline_resolvers_total` - Total number of pipeline resolvers (Unit: count)
- `pipeline_resolver_steps_total` - Total number of pipeline resolver steps (Unit: count)
- `pipeline_resolver_graphql_steps_total` - Total number of Pipeline resolver GraphQL steps (Unit: count)
- `pipeline_resolver_function_steps_total` - Total number of Pipeline resolver Function steps (Unit: count)
- `pipeline_resolver_execution_paths_total` - Total number of pipeline resolver execution paths (Unit: count)
- Calculation: Based on the number of steps and tests in each resolver (steps \* 2^tests)
- Includes overflow detection: Reports error if negative values are encountered
Expand Down
23 changes: 23 additions & 0 deletions tailor/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package tailor
import (
"errors"
"math"

tailorv1 "buf.build/gen/go/tailor-inc/tailor/protocolbuffers/go/tailor/v1"
)

const (
Expand Down Expand Up @@ -64,6 +66,8 @@ func (c *Client) Metrics(resources *Resources) ([]Metric, error) {
})
resolversTotal := 0
stepsTotal := 0
graphQLStepsTotal := 0
functionStepsTotal := 0
executionPathsTotal := 0
for _, p := range resources.Pipelines {
resolversTotal += len(p.Resolvers)
Expand All @@ -74,6 +78,12 @@ func (c *Client) Metrics(resources *Resources) ([]Metric, error) {
if s.Operation.Test != "" {
testsCount++
}
switch s.Operation.Type {
case tailorv1.PipelineResolver_OPERATION_TYPE_GRAPHQL:
graphQLStepsTotal++
case tailorv1.PipelineResolver_OPERATION_TYPE_FUNCTION:
functionStepsTotal++
}
}
executionPathsTotal += len(r.Steps) * int(math.Pow(2, float64(testsCount)))
}
Expand All @@ -90,6 +100,19 @@ func (c *Client) Metrics(resources *Resources) ([]Metric, error) {
Value: float64(stepsTotal),
Unit: "",
})
metrics = append(metrics, Metric{
Key: "pipeline_resolver_graphql_steps_total",
Name: "Total number of Pipeline resolver GraphQL steps",
Value: float64(graphQLStepsTotal),
Unit: "",
})
metrics = append(metrics, Metric{
Key: "pipeline_resolver_function_steps_total",
Name: "Total number of Pipeline resolver Function steps",
Value: float64(functionStepsTotal),
Unit: "",
})

pathsMetic := Metric{
Key: "pipeline_resolver_execution_paths_total",
Name: "Total number of Pipeline resolver execution paths",
Expand Down
262 changes: 262 additions & 0 deletions tailor/metrics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package tailor

import (
"testing"

tailorv1 "buf.build/gen/go/tailor-inc/tailor/protocolbuffers/go/tailor/v1"
)

func TestClient_Metrics(t *testing.T) {
Expand All @@ -24,6 +26,8 @@ func TestClient_Metrics(t *testing.T) {
"pipelines_total": 0,
"pipeline_resolvers_total": 0,
"pipeline_resolver_steps_total": 0,
"pipeline_resolver_graphql_steps_total": 0,
"pipeline_resolver_function_steps_total": 0,
"pipeline_resolver_execution_paths_total": 0, // 0 resolvers = 0 paths
"tailordbs_total": 0,
"tailordb_types_total": 0,
Expand Down Expand Up @@ -70,6 +74,8 @@ func TestClient_Metrics(t *testing.T) {
"pipelines_total": 1,
"pipeline_resolvers_total": 1,
"pipeline_resolver_steps_total": 1,
"pipeline_resolver_graphql_steps_total": 0,
"pipeline_resolver_function_steps_total": 0,
"pipeline_resolver_execution_paths_total": 1, // 1 * 2^0 = 1 (1 step, no tests)
"tailordbs_total": 1,
"tailordb_types_total": 1,
Expand Down Expand Up @@ -159,6 +165,8 @@ func TestClient_Metrics(t *testing.T) {
"pipelines_total": 2, // ns1, ns2
"pipeline_resolvers_total": 3, // resolver1, resolver2, resolver3
"pipeline_resolver_steps_total": 6, // 2+3+1 steps
"pipeline_resolver_graphql_steps_total": 0,
"pipeline_resolver_function_steps_total": 0,
"pipeline_resolver_execution_paths_total": 6, // 2*2^0 + 3*2^0 + 1*2^0 = 2+3+1 (no tests)
"tailordbs_total": 2, // two TailorDB instances
"tailordb_types_total": 3, // User, Post, Comment
Expand Down Expand Up @@ -203,6 +211,8 @@ func TestClient_Metrics(t *testing.T) {
"pipelines_total": 0,
"pipeline_resolvers_total": 0,
"pipeline_resolver_steps_total": 0,
"pipeline_resolver_graphql_steps_total": 0,
"pipeline_resolver_function_steps_total": 0,
"pipeline_resolver_execution_paths_total": 0, // 0 resolvers = 0 paths
"tailordbs_total": 1,
"tailordb_types_total": 1,
Expand Down Expand Up @@ -589,6 +599,8 @@ func TestClient_Metrics_MetricKeys(t *testing.T) {
"pipelines_total",
"pipeline_resolvers_total",
"pipeline_resolver_steps_total",
"pipeline_resolver_graphql_steps_total",
"pipeline_resolver_function_steps_total",
"pipeline_resolver_execution_paths_total",
"tailordbs_total",
"tailordb_types_total",
Expand Down Expand Up @@ -1033,3 +1045,253 @@ func TestClient_Metrics_ExecutionPaths_EdgeCases(t *testing.T) {
}
})
}

func TestClient_Metrics_StepTypes(t *testing.T) {
tests := []struct {
name string
resources *Resources
expectedMetrics map[string]float64
}{
{
name: "GraphQL and Function steps counting",
resources: &Resources{
Pipelines: []*Pipeline{
{
NamespaceName: "test-namespace",
Resolvers: []*PipelineResolver{
{
Name: "mixed_resolver",
Steps: []*PipelineStep{
{
Name: "graphql_step1",
Operation: PipelineStepOperation{
Type: tailorv1.PipelineResolver_OPERATION_TYPE_GRAPHQL,
},
},
{
Name: "graphql_step2",
Operation: PipelineStepOperation{
Type: tailorv1.PipelineResolver_OPERATION_TYPE_GRAPHQL,
},
},
{
Name: "function_step1",
Operation: PipelineStepOperation{
Type: tailorv1.PipelineResolver_OPERATION_TYPE_FUNCTION,
},
},
},
},
},
},
},
},
expectedMetrics: map[string]float64{
"pipeline_resolver_graphql_steps_total": 2,
"pipeline_resolver_function_steps_total": 1,
"pipeline_resolver_steps_total": 3,
},
},
{
name: "only GraphQL steps",
resources: &Resources{
Pipelines: []*Pipeline{
{
NamespaceName: "graphql-namespace",
Resolvers: []*PipelineResolver{
{
Name: "graphql_resolver",
Steps: []*PipelineStep{
{
Name: "step1",
Operation: PipelineStepOperation{
Type: tailorv1.PipelineResolver_OPERATION_TYPE_GRAPHQL,
},
},
{
Name: "step2",
Operation: PipelineStepOperation{
Type: tailorv1.PipelineResolver_OPERATION_TYPE_GRAPHQL,
},
},
},
},
},
},
},
},
expectedMetrics: map[string]float64{
"pipeline_resolver_graphql_steps_total": 2,
"pipeline_resolver_function_steps_total": 0,
"pipeline_resolver_steps_total": 2,
},
},
{
name: "only Function steps",
resources: &Resources{
Pipelines: []*Pipeline{
{
NamespaceName: "function-namespace",
Resolvers: []*PipelineResolver{
{
Name: "function_resolver",
Steps: []*PipelineStep{
{
Name: "step1",
Operation: PipelineStepOperation{
Type: tailorv1.PipelineResolver_OPERATION_TYPE_FUNCTION,
},
},
{
Name: "step2",
Operation: PipelineStepOperation{
Type: tailorv1.PipelineResolver_OPERATION_TYPE_FUNCTION,
},
},
{
Name: "step3",
Operation: PipelineStepOperation{
Type: tailorv1.PipelineResolver_OPERATION_TYPE_FUNCTION,
},
},
},
},
},
},
},
},
expectedMetrics: map[string]float64{
"pipeline_resolver_graphql_steps_total": 0,
"pipeline_resolver_function_steps_total": 3,
"pipeline_resolver_steps_total": 3,
},
},
{
name: "multiple resolvers with mixed step types",
resources: &Resources{
Pipelines: []*Pipeline{
{
NamespaceName: "multi-namespace",
Resolvers: []*PipelineResolver{
{
Name: "resolver1",
Steps: []*PipelineStep{
{
Name: "graphql_step",
Operation: PipelineStepOperation{
Type: tailorv1.PipelineResolver_OPERATION_TYPE_GRAPHQL,
},
},
{
Name: "function_step",
Operation: PipelineStepOperation{
Type: tailorv1.PipelineResolver_OPERATION_TYPE_FUNCTION,
},
},
},
},
{
Name: "resolver2",
Steps: []*PipelineStep{
{
Name: "graphql_step",
Operation: PipelineStepOperation{
Type: tailorv1.PipelineResolver_OPERATION_TYPE_GRAPHQL,
},
},
},
},
{
Name: "resolver3",
Steps: []*PipelineStep{
{
Name: "function_step1",
Operation: PipelineStepOperation{
Type: tailorv1.PipelineResolver_OPERATION_TYPE_FUNCTION,
},
},
{
Name: "function_step2",
Operation: PipelineStepOperation{
Type: tailorv1.PipelineResolver_OPERATION_TYPE_FUNCTION,
},
},
},
},
},
},
},
},
expectedMetrics: map[string]float64{
"pipeline_resolver_graphql_steps_total": 2, // 1 + 1 + 0
"pipeline_resolver_function_steps_total": 3, // 1 + 0 + 2
"pipeline_resolver_steps_total": 5, // 2 + 1 + 2
},
},
{
name: "empty resources - zero counts",
resources: &Resources{
Pipelines: []*Pipeline{},
},
expectedMetrics: map[string]float64{
"pipeline_resolver_graphql_steps_total": 0,
"pipeline_resolver_function_steps_total": 0,
"pipeline_resolver_steps_total": 0,
},
},
{
name: "resolver with no steps",
resources: &Resources{
Pipelines: []*Pipeline{
{
NamespaceName: "empty-resolver-namespace",
Resolvers: []*PipelineResolver{
{
Name: "empty_resolver",
Steps: []*PipelineStep{},
},
},
},
},
},
expectedMetrics: map[string]float64{
"pipeline_resolver_graphql_steps_total": 0,
"pipeline_resolver_function_steps_total": 0,
"pipeline_resolver_steps_total": 0,
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := createTestConfig(t)
client, err := New(cfg)
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}

metrics, err := client.Metrics(tt.resources)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}

// Create a map for easier assertion
metricMap := make(map[string]float64)
for _, m := range metrics {
metricMap[m.Key] = m.Value
}

// Verify expected metrics
for expectedName, expectedValue := range tt.expectedMetrics {
actualValue, exists := metricMap[expectedName]
if !exists {
t.Errorf("Expected metric %s not found", expectedName)
continue
}
if actualValue != expectedValue {
t.Errorf("Metric %s: expected %.0f, got %.0f", expectedName, expectedValue, actualValue)
}
}
})
}
}