From 9a1735477b404c9eac62f8f51ca7622e0372210b Mon Sep 17 00:00:00 2001 From: Ken'ichiro Oyama Date: Thu, 11 Sep 2025 15:31:18 +0900 Subject: [PATCH 01/10] fix: modify the structure of tailor.Metric to output metrics including Coverage and Lint. --- cmd/coverage.go | 11 ++- cmd/metrics.go | 10 ++- tailor/metrics.go | 98 ++++++++++++++++------- tailor/metrics_test.go | 171 ++++++++++++++++++++++------------------- 4 files changed, 181 insertions(+), 109 deletions(-) diff --git a/cmd/coverage.go b/cmd/coverage.go index 2e4078e..039c63b 100644 --- a/cmd/coverage.go +++ b/cmd/coverage.go @@ -77,7 +77,10 @@ var coverageCmd = &cobra.Command{ var total, covered int for _, rc := range coverage { if fullReport { - cover := float64(float64(rc.CoveredSteps)/float64(rc.TotalSteps)) * 100 + var cover float64 + if rc.TotalSteps > 0 { + cover = float64(float64(rc.CoveredSteps)/float64(rc.TotalSteps)) * 100 + } fmt.Printf("%5s%% [%d/%d] %s\n", fmt.Sprintf("%.1f", cover), rc.CoveredSteps, rc.TotalSteps, rc.Name) } total += rc.TotalSteps @@ -86,7 +89,11 @@ var coverageCmd = &cobra.Command{ if fullReport { fmt.Println() } - fmt.Printf("%s %.1f%% [%d/%d]\n", "Pipeline Resolver Step Coverage", float64(float64(covered)/float64(total))*100, covered, total) + var coverTotal float64 + if total > 0 { + coverTotal = float64(float64(covered)/float64(total)) * 100 + } + fmt.Printf("%s %.1f%% [%d/%d]\n", "Pipeline resolver step coverage", coverTotal, covered, total) return nil }, diff --git a/cmd/metrics.go b/cmd/metrics.go index d5de04d..6b50c93 100644 --- a/cmd/metrics.go +++ b/cmd/metrics.go @@ -24,7 +24,9 @@ package cmd import ( "encoding/json" "fmt" + "time" + "github.com/k1LoW/duration" "github.com/spf13/cobra" "github.com/tailor-platform/patterner/config" "github.com/tailor-platform/patterner/tailor" @@ -49,7 +51,12 @@ var metricsCmd = &cobra.Command{ if err != nil { return err } - resources, err := c.Resources(cmd.Context()) + d, err := duration.Parse(since) + if err != nil { + return err + } + s := time.Now().Add(-d) + resources, err := c.Resources(cmd.Context(), tailor.WithExecutionResults(&s)) if err != nil { return err } @@ -69,4 +76,5 @@ var metricsCmd = &cobra.Command{ func init() { rootCmd.AddCommand(metricsCmd) + metricsCmd.Flags().StringVarP(&since, "since", "s", "30min", "only consider executions since the given duration (e.g., 24hours, 30min, 15sec)") } diff --git a/tailor/metrics.go b/tailor/metrics.go index 92634df..2bceea3 100644 --- a/tailor/metrics.go +++ b/tailor/metrics.go @@ -9,19 +9,56 @@ const ( ) type Metric struct { - Name string - Description string - Value float64 + Key string + Name string + Value float64 + Unit string } func (c *Client) Metrics(resources *Resources) ([]Metric, error) { var metrics []Metric + // Coverage Metrics + coverage, err := c.Coverage(resources) + if err != nil { + return nil, err + } + var total, covered int + for _, rc := range coverage { + total += rc.TotalSteps + covered += rc.CoveredSteps + } + var coverTotal float64 + if total > 0 { + coverTotal = float64(float64(covered)/float64(total)) * 100 + } else { + coverTotal = 0 + } + metrics = append(metrics, Metric{ + Key: "pipeline_resolver_step_coverage_percentage", + Name: "Pipeline resolver step coverage", + Value: coverTotal, + Unit: "%", + }) + + // Lint Metrics + warns, err := c.Lint(resources) + if err != nil { + return nil, err + } + metrics = append(metrics, Metric{ + Key: "lint_warnings_total", + Name: "Total number of lint warnings", + Value: float64(len(warns)), + Unit: "", + }) + // Pipeline Metrics metrics = append(metrics, Metric{ - Name: "pipelines_total", - Description: "Total number of pipelines", - Value: float64(len(resources.Pipelines)), + Key: "pipelines_total", + Name: "Total number of pipelines", + Value: float64(len(resources.Pipelines)), + Unit: "", }) resolversTotal := 0 stepsTotal := 0 @@ -40,26 +77,30 @@ func (c *Client) Metrics(resources *Resources) ([]Metric, error) { } } metrics = append(metrics, Metric{ - Name: "pipeline_resolvers_total", - Description: "Total number of pipeline resolvers", - Value: float64(resolversTotal), + Key: "pipeline_resolvers_total", + Name: "Total number of pipeline resolvers", + Value: float64(resolversTotal), + Unit: "", }) metrics = append(metrics, Metric{ - Name: "pipeline_resolver_steps_total", - Description: "Total number of pipeline resolver steps", - Value: float64(stepsTotal), + Key: "pipeline_resolver_steps_total", + Name: "Total number of pipeline resolver steps", + Value: float64(stepsTotal), + Unit: "", }) metrics = append(metrics, Metric{ - Name: "pipeline_resolver_execution_paths_total", - Description: "Total number of pipeline resolver execution paths", - Value: float64(executionPathsTotal), + Key: "pipeline_resolver_execution_paths_total", + Name: "Total number of pipeline resolver execution paths", + Value: float64(executionPathsTotal), + Unit: "", }) // TailorDB Metrics metrics = append(metrics, Metric{ - Name: "tailordbs_total", - Description: "Total number of TailorDBs", - Value: float64(len(resources.TailorDBs)), + Key: "tailordbs_total", + Name: "Total number of TailorDBs", + Value: float64(len(resources.TailorDBs)), + Unit: "", }) typesTotal := 0 fieldsTotal := 0 @@ -70,21 +111,24 @@ func (c *Client) Metrics(resources *Resources) ([]Metric, error) { } } metrics = append(metrics, Metric{ - Name: "tailordb_types_total", - Description: "Total number of TailorDB types", - Value: float64(typesTotal), + Key: "tailordb_types_total", + Name: "Total number of TailorDB types", + Value: float64(typesTotal), + Unit: "", }) metrics = append(metrics, Metric{ - Name: "tailordb_type_fields_total", - Description: "Total number of TailorDB type fields", - Value: float64(fieldsTotal), + Key: "tailordb_type_fields_total", + Name: "Total number of TailorDB type fields", + Value: float64(fieldsTotal), + Unit: "", }) // StateFlow Metrics metrics = append(metrics, Metric{ - Name: "stateflows_total", - Description: "Total number of StateFlows", - Value: float64(len(resources.StateFlows)), + Key: "stateflows_total", + Name: "Total number of StateFlows", + Value: float64(len(resources.StateFlows)), + Unit: "", }) return metrics, nil diff --git a/tailor/metrics_test.go b/tailor/metrics_test.go index 2f4257d..0ec9dd6 100644 --- a/tailor/metrics_test.go +++ b/tailor/metrics_test.go @@ -19,14 +19,16 @@ func TestClient_Metrics(t *testing.T) { StateFlows: []*StateFlow{}, }, expectedMetrics: map[string]float64{ - "pipelines_total": 0, - "pipeline_resolvers_total": 0, - "pipeline_resolver_steps_total": 0, - "pipeline_resolver_execution_paths_total": 0, // 0 resolvers = 0 paths - "tailordbs_total": 0, - "tailordb_types_total": 0, - "tailordb_type_fields_total": 0, - "stateflows_total": 0, + "pipeline_resolver_step_coverage_percentage": 0, + "lint_warnings_total": 0, + "pipelines_total": 0, + "pipeline_resolvers_total": 0, + "pipeline_resolver_steps_total": 0, + "pipeline_resolver_execution_paths_total": 0, // 0 resolvers = 0 paths + "tailordbs_total": 0, + "tailordb_types_total": 0, + "tailordb_type_fields_total": 0, + "stateflows_total": 0, }, }, { @@ -63,14 +65,16 @@ func TestClient_Metrics(t *testing.T) { }, }, expectedMetrics: map[string]float64{ - "pipelines_total": 1, - "pipeline_resolvers_total": 1, - "pipeline_resolver_steps_total": 1, - "pipeline_resolver_execution_paths_total": 1, // 1 * 2^0 = 1 (1 step, no tests) - "tailordbs_total": 1, - "tailordb_types_total": 1, - "tailordb_type_fields_total": 2, // id and name fields - "stateflows_total": 1, + "pipeline_resolver_step_coverage_percentage": 0, // no coverage data for test + "lint_warnings_total": 1, + "pipelines_total": 1, + "pipeline_resolvers_total": 1, + "pipeline_resolver_steps_total": 1, + "pipeline_resolver_execution_paths_total": 1, // 1 * 2^0 = 1 (1 step, no tests) + "tailordbs_total": 1, + "tailordb_types_total": 1, + "tailordb_type_fields_total": 2, // id and name fields + "stateflows_total": 1, }, }, { @@ -150,14 +154,16 @@ func TestClient_Metrics(t *testing.T) { }, }, expectedMetrics: map[string]float64{ - "pipelines_total": 2, // ns1, ns2 - "pipeline_resolvers_total": 3, // resolver1, resolver2, resolver3 - "pipeline_resolver_steps_total": 6, // 2+3+1 steps - "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 - "tailordb_type_fields_total": 9, // 3+2+4 fields - "stateflows_total": 3, // flow1, flow2, flow3 + "pipeline_resolver_step_coverage_percentage": 0, // no coverage data for test + "lint_warnings_total": 3, + "pipelines_total": 2, // ns1, ns2 + "pipeline_resolvers_total": 3, // resolver1, resolver2, resolver3 + "pipeline_resolver_steps_total": 6, // 2+3+1 steps + "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 + "tailordb_type_fields_total": 9, // 3+2+4 fields + "stateflows_total": 3, // flow1, flow2, flow3 }, }, { @@ -192,14 +198,16 @@ func TestClient_Metrics(t *testing.T) { }, }, expectedMetrics: map[string]float64{ - "pipelines_total": 0, - "pipeline_resolvers_total": 0, - "pipeline_resolver_steps_total": 0, - "pipeline_resolver_execution_paths_total": 0, // 0 resolvers = 0 paths - "tailordbs_total": 1, - "tailordb_types_total": 1, - "tailordb_type_fields_total": 2, // Only top-level fields are counted - "stateflows_total": 0, + "pipeline_resolver_step_coverage_percentage": 0, + "lint_warnings_total": 0, + "pipelines_total": 0, + "pipeline_resolvers_total": 0, + "pipeline_resolver_steps_total": 0, + "pipeline_resolver_execution_paths_total": 0, // 0 resolvers = 0 paths + "tailordbs_total": 1, + "tailordb_types_total": 1, + "tailordb_type_fields_total": 2, // Only top-level fields are counted + "stateflows_total": 0, }, }, } @@ -226,7 +234,7 @@ func TestClient_Metrics(t *testing.T) { // Create a map for easier assertion metricMap := make(map[string]float64) for _, m := range metrics { - metricMap[m.Name] = m.Value + metricMap[m.Key] = m.Value } // Verify all expected metrics are present @@ -287,11 +295,8 @@ func TestClient_Metrics_MetricStructure(t *testing.T) { if metric.Name == "" { t.Error("Metric name should not be empty") } - if metric.Description == "" { - t.Error("Metric description should not be empty") - } - if metric.Value < 0 { - t.Errorf("Metric value should be non-negative, got %f", metric.Value) + if metric.Key == "" { + t.Error("Metric key should not be empty") } } } @@ -355,7 +360,7 @@ func TestClient_Metrics_SpecificMetricValues(t *testing.T) { // Create metric lookup map metricMap := make(map[string]Metric) for _, m := range metrics { - metricMap[m.Name] = m + metricMap[m.Key] = m } // Test pipeline metrics @@ -419,7 +424,7 @@ func TestClient_Metrics_EdgeCases(t *testing.T) { metricMap := make(map[string]float64) for _, m := range metrics { - metricMap[m.Name] = m.Value + metricMap[m.Key] = m.Value } // All counts should be 0 @@ -470,7 +475,7 @@ func TestClient_Metrics_EdgeCases(t *testing.T) { metricMap := make(map[string]float64) for _, m := range metrics { - metricMap[m.Name] = m.Value + metricMap[m.Key] = m.Value } if metricMap["pipelines_total"] != float64(2) { @@ -506,7 +511,7 @@ func TestClient_Metrics_EdgeCases(t *testing.T) { metricMap := make(map[string]float64) for _, m := range metrics { - metricMap[m.Name] = m.Value + metricMap[m.Key] = m.Value } if metricMap["tailordbs_total"] != float64(2) { @@ -545,7 +550,7 @@ func TestClient_Metrics_EdgeCases(t *testing.T) { metricMap := make(map[string]float64) for _, m := range metrics { - metricMap[m.Name] = m.Value + metricMap[m.Key] = m.Value } if metricMap["tailordbs_total"] != float64(1) { @@ -578,7 +583,9 @@ func TestClient_Metrics_MetricNames(t *testing.T) { t.Fatalf("Unexpected error: %v", err) } - expectedMetricNames := []string{ + expectedMetricKeys := []string{ + "pipeline_resolver_step_coverage_percentage", + "lint_warnings_total", "pipelines_total", "pipeline_resolvers_total", "pipeline_resolver_steps_total", @@ -589,31 +596,31 @@ func TestClient_Metrics_MetricNames(t *testing.T) { "stateflows_total", } - actualNames := make([]string, len(metrics)) + actualKeys := make([]string, len(metrics)) for i, m := range metrics { - actualNames[i] = m.Name + actualKeys[i] = m.Key } - // Check that we have all expected metric names - for _, expectedName := range expectedMetricNames { + // Check that we have all expected metric keys + for _, expectedKey := range expectedMetricKeys { found := false - for _, actualName := range actualNames { - if actualName == expectedName { + for _, actualKey := range actualKeys { + if actualKey == expectedKey { found = true break } } if !found { - t.Errorf("Expected metric %s not found", expectedName) + t.Errorf("Expected metric %s not found", expectedKey) } } - if len(expectedMetricNames) != len(actualNames) { - t.Errorf("Expected %d metrics, got %d", len(expectedMetricNames), len(actualNames)) + if len(expectedMetricKeys) != len(actualKeys) { + t.Errorf("Expected %d metrics, got %d", len(expectedMetricKeys), len(actualKeys)) } } -func TestClient_Metrics_MetricDescriptions(t *testing.T) { +func TestClient_Metrics_MetricFields(t *testing.T) { cfg := createTestConfig(t) client, err := New(cfg) if err != nil { @@ -631,45 +638,51 @@ func TestClient_Metrics_MetricDescriptions(t *testing.T) { t.Fatalf("Unexpected error: %v", err) } - expectedDescriptions := map[string]string{ - "pipelines_total": "Total number of pipelines", - "pipeline_resolvers_total": "Total number of pipeline resolvers", - "pipeline_resolver_steps_total": "Total number of pipeline resolver steps", - "pipeline_resolver_execution_paths_total": "Total number of pipeline resolver execution paths", - "tailordbs_total": "Total number of TailorDBs", - "tailordb_types_total": "Total number of TailorDB types", - "tailordb_type_fields_total": "Total number of TailorDB type fields", - "stateflows_total": "Total number of StateFlows", + expectedKeys := map[string]string{ + "pipeline_resolver_step_coverage_percentage": "pipeline_resolver_step_coverage_percentage", + "lint_warnings_total": "lint_warnings_total", + "pipelines_total": "pipelines_total", + "pipeline_resolvers_total": "pipeline_resolvers_total", + "pipeline_resolver_steps_total": "pipeline_resolver_steps_total", + "pipeline_resolver_execution_paths_total": "pipeline_resolver_execution_paths_total", + "tailordbs_total": "tailordbs_total", + "tailordb_types_total": "tailordb_types_total", + "tailordb_type_fields_total": "tailordb_type_fields_total", + "stateflows_total": "stateflows_total", } for _, metric := range metrics { - expectedDesc, exists := expectedDescriptions[metric.Name] + expectedKey, exists := expectedKeys[metric.Key] if !exists { - t.Errorf("Unexpected metric: %s", metric.Name) + t.Errorf("Unexpected metric: %s", metric.Key) } - if expectedDesc != metric.Description { - t.Errorf("Wrong description for metric %s: expected '%s', got '%s'", - metric.Name, expectedDesc, metric.Description) + if expectedKey != metric.Key { + t.Errorf("Wrong key for metric %s: expected '%s', got '%s'", + metric.Key, expectedKey, metric.Key) } } } func TestMetric_Fields(t *testing.T) { metric := Metric{ - Name: "test_metric", - Description: "Test metric description", - Value: 42.5, + Key: "test_metric", + Name: "Test Metric", + Value: 42.5, + Unit: "count", } - if metric.Name != "test_metric" { - t.Errorf("Expected Name to be 'test_metric', got '%s'", metric.Name) + if metric.Key != "test_metric" { + t.Errorf("Expected Key to be 'test_metric', got '%s'", metric.Key) } - if metric.Description != "Test metric description" { - t.Errorf("Expected Description to be 'Test metric description', got '%s'", metric.Description) + if metric.Name != "Test Metric" { + t.Errorf("Expected Name to be 'Test Metric', got '%s'", metric.Name) } if metric.Value != 42.5 { t.Errorf("Expected Value to be 42.5, got %f", metric.Value) } + if metric.Unit != "count" { + t.Errorf("Expected Unit to be 'count', got '%s'", metric.Unit) + } } func TestClient_Metrics_LargeNumbers(t *testing.T) { @@ -712,7 +725,7 @@ func TestClient_Metrics_LargeNumbers(t *testing.T) { metricMap := make(map[string]float64) for _, m := range metrics { - metricMap[m.Name] = m.Value + metricMap[m.Key] = m.Value } // Verify calculations @@ -968,7 +981,7 @@ func TestClient_Metrics_ExecutionPaths(t *testing.T) { // Create a map for easier assertion metricMap := make(map[string]float64) for _, m := range metrics { - metricMap[m.Name] = m.Value + metricMap[m.Key] = m.Value } // Verify expected metrics @@ -1034,7 +1047,7 @@ func TestClient_Metrics_ExecutionPaths_EdgeCases(t *testing.T) { metricMap := make(map[string]float64) for _, m := range metrics { - metricMap[m.Name] = m.Value + metricMap[m.Key] = m.Value } // Expected: 3 steps, 2 tests (one empty string doesn't count, whitespace does count) @@ -1074,7 +1087,7 @@ func TestClient_Metrics_ExecutionPaths_EdgeCases(t *testing.T) { metricMap := make(map[string]float64) for _, m := range metrics { - metricMap[m.Name] = m.Value + metricMap[m.Key] = m.Value } // Expected: 1 step, 1 test -> 1 * 2^1 = 2 From 376612655c46f577b2f406334656e3cd4216182d Mon Sep 17 00:00:00 2001 From: Ken'ichiro Oyama Date: Thu, 11 Sep 2025 15:54:42 +0900 Subject: [PATCH 02/10] fix: fix metrics output (json -> table) --- cmd/metrics.go | 60 ++++++++++++++++++++++++++++++++++++++++++++++---- go.mod | 11 ++++++--- go.sum | 26 ++++++++++++++++------ 3 files changed, 83 insertions(+), 14 deletions(-) diff --git a/cmd/metrics.go b/cmd/metrics.go index 6b50c93..483a8ec 100644 --- a/cmd/metrics.go +++ b/cmd/metrics.go @@ -22,11 +22,15 @@ THE SOFTWARE. package cmd import ( - "encoding/json" "fmt" + "math" + "os" "time" "github.com/k1LoW/duration" + "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/renderer" + "github.com/olekukonko/tablewriter/tw" "github.com/spf13/cobra" "github.com/tailor-platform/patterner/config" "github.com/tailor-platform/patterner/tailor" @@ -65,11 +69,59 @@ var metricsCmd = &cobra.Command{ if err != nil { return err } - b, err := json.MarshalIndent(metrics, "", " ") - if err != nil { + table := tablewriter.NewTable(os.Stdout, + tablewriter.WithTrimSpace(tw.Off), + tablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{ + Borders: tw.BorderNone, + Symbols: tw.NewSymbols(tw.StyleNone), + Settings: tw.Settings{ + Lines: tw.Lines{ + ShowTop: tw.Off, + ShowBottom: tw.Off, + ShowHeaderLine: tw.Off, + ShowFooterLine: tw.Off, + }, + Separators: tw.Separators{ + ShowHeader: tw.Off, + ShowFooter: tw.Off, + BetweenRows: tw.Off, + BetweenColumns: tw.Off, + }, + }, + })), + tablewriter.WithHeaderConfig(tw.CellConfig{ + Formatting: tw.CellFormatting{ + AutoFormat: tw.Off, + Alignment: tw.AlignLeft, + }, + Padding: tw.CellPadding{ + Global: tw.Padding{Left: tw.Space, Right: tw.Space, Top: tw.Empty, Bottom: tw.Empty}, + }, + }), + tablewriter.WithRowConfig(tw.CellConfig{ + Formatting: tw.CellFormatting{ + AutoFormat: tw.Off, + }, + ColumnAligns: []tw.Align{tw.AlignLeft, tw.AlignRight}, + Padding: tw.CellPadding{ + Global: tw.Padding{Left: tw.Space, Right: tw.Space, Top: tw.Empty, Bottom: tw.Empty}, + }, + }), + ) + data := make([][]string, 0, len(metrics)) + for _, m := range metrics { + if m.Value == (math.Round(m.Value*10) / 10) { + data = append(data, []string{m.Name, fmt.Sprintf("%.0f%s", m.Value, m.Unit)}) + } else { + data = append(data, []string{m.Name, fmt.Sprintf("%.1f%s", m.Value, m.Unit)}) + } + } + if err := table.Bulk(data); err != nil { + return err + } + if err := table.Render(); err != nil { return err } - fmt.Println(string(b)) return nil }, } diff --git a/go.mod b/go.mod index e7feed7..c42ad5e 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/creasty/defaults v1.8.0 github.com/goccy/go-yaml v1.18.0 github.com/k1LoW/duration v1.2.0 + github.com/olekukonko/tablewriter v1.0.9 github.com/spf13/cobra v1.10.1 github.com/vektah/gqlparser/v2 v2.5.30 google.golang.org/protobuf v1.36.9 @@ -17,11 +18,15 @@ require ( require ( buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.9-20250717185734-6c6e0d3c608e.1 // indirect - github.com/fatih/color v1.7.0 // indirect + github.com/fatih/color v1.15.0 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/mattn/go-colorable v0.1.2 // indirect - github.com/mattn/go-isatty v0.0.8 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/olekukonko/errors v1.1.0 // indirect + github.com/olekukonko/ll v0.0.9 // indirect + github.com/rivo/uniseg v0.2.0 // indirect github.com/spf13/pflag v1.0.10 // indirect golang.org/x/net v0.42.0 // indirect golang.org/x/sys v0.34.0 // indirect diff --git a/go.sum b/go.sum index 7e0cceb..050bffe 100644 --- a/go.sum +++ b/go.sum @@ -15,8 +15,8 @@ github.com/creasty/defaults v1.8.0 h1:z27FJxCAa0JKt3utc0sCImAEb+spPucmKoOdLHvHYK github.com/creasty/defaults v1.8.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -25,12 +25,23 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/k1LoW/duration v1.2.0 h1:qq1gWtPh7YROFyerBufVP+ATR11mOOHDInrcC/Xe/6A= github.com/k1LoW/duration v1.2.0/go.mod h1:qUa0NptIiUl5EUsCc8wIiSaHuNjS4wmpYNMHp0l6pos= -github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM= +github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= +github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI= +github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g= +github.com/olekukonko/tablewriter v1.0.9 h1:XGwRsYLC2bY7bNd93Dk51bcPZksWZmLYuaTHR0FqfL8= +github.com/olekukonko/tablewriter v1.0.9/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= @@ -45,7 +56,8 @@ github.com/vektah/gqlparser/v2 v2.5.30 h1:EqLwGAFLIzt1wpx1IPpY67DwUujF1OfzgEyDsL github.com/vektah/gqlparser/v2 v2.5.30/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo= golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= From 4914e706c1f50594b7495a05d106afc95ca9ba89 Mon Sep 17 00:00:00 2001 From: Ken'ichiro Oyama Date: Thu, 11 Sep 2025 15:59:35 +0900 Subject: [PATCH 03/10] fix: handle overflow --- cmd/metrics.go | 4 ++++ tailor/metrics.go | 24 ++++++++++++++++++------ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/cmd/metrics.go b/cmd/metrics.go index 483a8ec..a5d1c05 100644 --- a/cmd/metrics.go +++ b/cmd/metrics.go @@ -110,6 +110,10 @@ var metricsCmd = &cobra.Command{ ) data := make([][]string, 0, len(metrics)) for _, m := range metrics { + if m.Error != nil { + data = append(data, []string{m.Name, fmt.Sprintf("Error: %v", m.Error)}) + continue + } if m.Value == (math.Round(m.Value*10) / 10) { data = append(data, []string{m.Name, fmt.Sprintf("%.0f%s", m.Value, m.Unit)}) } else { diff --git a/tailor/metrics.go b/tailor/metrics.go index 2bceea3..994e583 100644 --- a/tailor/metrics.go +++ b/tailor/metrics.go @@ -1,6 +1,7 @@ package tailor import ( + "errors" "math" ) @@ -13,6 +14,7 @@ type Metric struct { Name string Value float64 Unit string + Error error } func (c *Client) Metrics(resources *Resources) ([]Metric, error) { @@ -88,12 +90,22 @@ func (c *Client) Metrics(resources *Resources) ([]Metric, error) { Value: float64(stepsTotal), Unit: "", }) - metrics = append(metrics, Metric{ - Key: "pipeline_resolver_execution_paths_total", - Name: "Total number of pipeline resolver execution paths", - Value: float64(executionPathsTotal), - Unit: "", - }) + if executionPathsTotal < 0 { + metrics = append(metrics, Metric{ + Key: "pipeline_resolver_execution_paths_total", + Name: "Total number of pipeline resolver execution paths", + Value: float64(executionPathsTotal), + Unit: "", + Error: errors.New("overflow detected"), + }) + } else { + metrics = append(metrics, Metric{ + Key: "pipeline_resolver_execution_paths_total", + Name: "Total number of pipeline resolver execution paths", + Value: float64(executionPathsTotal), + Unit: "", + }) + } // TailorDB Metrics metrics = append(metrics, Metric{ From ff3874a92fe9c74c3810535dddeed2fc84a3000b Mon Sep 17 00:00:00 2001 From: Ken'ichiro Oyama Date: Thu, 11 Sep 2025 16:36:56 +0900 Subject: [PATCH 04/10] feat: add `--out-octocov-path` for octocov custom metrics --- cmd/metrics.go | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 16 +++++++-------- go.sum | 40 ++++++++++++++++++++++-------------- 3 files changed, 88 insertions(+), 23 deletions(-) diff --git a/cmd/metrics.go b/cmd/metrics.go index a5d1c05..604a364 100644 --- a/cmd/metrics.go +++ b/cmd/metrics.go @@ -22,6 +22,7 @@ THE SOFTWARE. package cmd import ( + "encoding/json" "fmt" "math" "os" @@ -36,6 +37,8 @@ import ( "github.com/tailor-platform/patterner/tailor" ) +var outOctocovPath string + var metricsCmd = &cobra.Command{ Use: "metrics", Short: "retrieve and display metrics about the resources in the workspace", @@ -126,6 +129,35 @@ var metricsCmd = &cobra.Command{ if err := table.Render(); err != nil { return err } + if outOctocovPath != "" { + metricSet := &CustomMetricSet{ + Key: "workspace_metrics", + Name: "Workspace metrics using [Patterner](https://github.com/tailor-platform/patterner)", + Metadata: []*MetadataKV{ + { + Key: "workspace_id", + Value: cfg.WorkspaceID, + }, + }, + } + for _, m := range metrics { + metricSet.Metrics = append(metricSet.Metrics, &CustomMetric{ + Key: m.Key, + Name: m.Name, + Value: m.Value, + Unit: m.Unit, + }) + } + csets := []*CustomMetricSet{metricSet} + b, err := json.MarshalIndent(csets, "", " ") + if err != nil { + return err + } + if err := os.WriteFile(outOctocovPath, b, 0644); err != nil { + return err + } + } + return nil }, } @@ -133,4 +165,27 @@ var metricsCmd = &cobra.Command{ func init() { rootCmd.AddCommand(metricsCmd) metricsCmd.Flags().StringVarP(&since, "since", "s", "30min", "only consider executions since the given duration (e.g., 24hours, 30min, 15sec)") + metricsCmd.Flags().StringVarP(&outOctocovPath, "out-octocov-path", "", "", "output the metrics in octocov custom metrics format to the specified file (e.g., ./metrics.json)") +} + +// copy from github.com/k1LoW/octocov/report +// because octocov use tablewriter v0 +type MetadataKV struct { + Key string `json:"key"` + Name string `json:"name,omitempty"` + Value string `json:"value"` +} + +type CustomMetricSet struct { + Key string `json:"key"` + Name string `json:"name,omitempty"` + Metadata []*MetadataKV `json:"metadata,omitempty"` + Metrics []*CustomMetric `json:"metrics"` +} + +type CustomMetric struct { + Key string `json:"key"` + Name string `json:"name,omitempty"` + Value float64 `json:"value"` + Unit string `json:"unit,omitempty"` } diff --git a/go.mod b/go.mod index c42ad5e..de80c6f 100644 --- a/go.mod +++ b/go.mod @@ -18,19 +18,19 @@ require ( require ( buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.9-20250717185734-6c6e0d3c608e.1 // indirect - github.com/fatih/color v1.15.0 // indirect - github.com/google/go-cmp v0.6.0 // indirect + github.com/fatih/color v1.18.0 // indirect + github.com/google/go-cmp v0.7.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/olekukonko/errors v1.1.0 // indirect github.com/olekukonko/ll v0.0.9 // indirect - github.com/rivo/uniseg v0.2.0 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/spf13/pflag v1.0.10 // indirect - golang.org/x/net v0.42.0 // indirect - golang.org/x/sys v0.34.0 // indirect - golang.org/x/term v0.33.0 // indirect - golang.org/x/text v0.28.0 // indirect + golang.org/x/net v0.43.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/term v0.34.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250826171959-ef028d996bc1 // indirect ) diff --git a/go.sum b/go.sum index 050bffe..17e9289 100644 --- a/go.sum +++ b/go.sum @@ -13,23 +13,27 @@ github.com/briandowns/spinner v1.23.2/go.mod h1:LaZeM4wm2Ywy6vO571mvhQNRcWfRUnXO github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creasty/defaults v1.8.0 h1:z27FJxCAa0JKt3utc0sCImAEb+spPucmKoOdLHvHYKk= github.com/creasty/defaults v1.8.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= -github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/k1LoW/duration v1.2.0 h1:qq1gWtPh7YROFyerBufVP+ATR11mOOHDInrcC/Xe/6A= github.com/k1LoW/duration v1.2.0/go.mod h1:qUa0NptIiUl5EUsCc8wIiSaHuNjS4wmpYNMHp0l6pos= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM= @@ -40,28 +44,31 @@ github.com/olekukonko/tablewriter v1.0.9 h1:XGwRsYLC2bY7bNd93Dk51bcPZksWZmLYuaTH github.com/olekukonko/tablewriter v1.0.9/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= -github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/vektah/gqlparser/v2 v2.5.30 h1:EqLwGAFLIzt1wpx1IPpY67DwUujF1OfzgEyDsLrN6kE= github.com/vektah/gqlparser/v2 v2.5.30/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= -golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= +golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= google.golang.org/genproto/googleapis/api v0.0.0-20250826171959-ef028d996bc1 h1:APHvLLYBhtZvsbnpkfknDZ7NyH4z5+ub/I0u8L3Oz6g= @@ -69,5 +76,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250826171959-ef028d996bc1/go. google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 390749a45b0d6abb88a4fb02cded0dbffe1a216b Mon Sep 17 00:00:00 2001 From: Ken'ichiro Oyama Date: Thu, 11 Sep 2025 16:46:36 +0900 Subject: [PATCH 05/10] feat: add `with-lint-warnings` for displaying lint warnings --- cmd/metrics.go | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/cmd/metrics.go b/cmd/metrics.go index 604a364..a89fb90 100644 --- a/cmd/metrics.go +++ b/cmd/metrics.go @@ -37,7 +37,10 @@ import ( "github.com/tailor-platform/patterner/tailor" ) -var outOctocovPath string +var ( + outOctocovPath string + withLintWarnings bool +) var metricsCmd = &cobra.Command{ Use: "metrics", @@ -68,6 +71,18 @@ var metricsCmd = &cobra.Command{ return err } spi.Disable() + + if withLintWarnings { + warns, err := c.Lint(resources) + if err != nil { + return err + } + for _, w := range warns { + fmt.Printf("[%s] %s: %s\n", w.Type, w.Name, w.Message) + } + fmt.Println() + } + metrics, err := c.Metrics(resources) if err != nil { return err @@ -166,6 +181,7 @@ func init() { rootCmd.AddCommand(metricsCmd) metricsCmd.Flags().StringVarP(&since, "since", "s", "30min", "only consider executions since the given duration (e.g., 24hours, 30min, 15sec)") metricsCmd.Flags().StringVarP(&outOctocovPath, "out-octocov-path", "", "", "output the metrics in octocov custom metrics format to the specified file (e.g., ./metrics.json)") + metricsCmd.Flags().BoolVarP(&withLintWarnings, "with-lint-wanings", "", false, "display the lint warnings along with the metrics") } // copy from github.com/k1LoW/octocov/report From 94e6c48652eef041be9799c71f935650629ac027 Mon Sep 17 00:00:00 2001 From: Ken'ichiro Oyama Date: Thu, 11 Sep 2025 16:54:29 +0900 Subject: [PATCH 06/10] docs: update for new features --- README.md | 44 +++++++++++++++++++++++++++++++++++++++++++- cmd/metrics.go | 2 +- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 02c32b8..3245ab0 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,38 @@ export TAILOR_TOKEN=your_access_token patterner metrics ``` -The metrics command outputs detailed JSON data about your workspace resources. +The metrics command displays metrics in a table format. Use the `--out-octocov-path` option to output metrics in octocov custom metrics format. + +#### Metrics Options + +- `--since, -s` (default: "30min") - Analyze execution results since the specified time period +- `--out-octocov-path` - Output metrics in octocov custom metrics format to the specified file +- `--with-lint-warnings` (default: false) - Display lint warnings along with metrics output + +#### Usage Examples + +```bash +# Basic metrics (last 30 minutes) +patterner metrics + +# Metrics for the past hour +patterner metrics --since 1hour + +# Display metrics with lint warnings +patterner metrics --with-lint-warnings + +# Output metrics to octocov custom metrics file +patterner metrics --out-octocov-path metrics.json + +# Metrics for the past 24 hours with octocov output and lint warnings +patterner metrics --since 24hours --out-octocov-path metrics.json --with-lint-warnings +``` + +**Implementation Notes:** +``` + +``` ### View Coverage @@ -158,6 +189,14 @@ The following metrics are collected and displayed: - `stateflows_total` - Total number of StateFlows +**Coverage Metrics:** + +- `pipeline_resolver_step_coverage_percentage` - Pipeline resolver step coverage percentage + +**Lint Metrics:** + +- `lint_warnings_total` - Total number of lint warnings + ## Configuration Patterner uses a `.patterner.yml` file for configuration. The configuration includes various lint rules for different Tailor Platform components: @@ -247,6 +286,9 @@ lint: - `patterner init` - Initialize configuration file - `patterner lint` - Lint workspace resources - `patterner metrics` - Display workspace metrics + - `--since, -s` (default: "30min") - Analyze execution results since the specified time period + - `--out-octocov-path` - Output metrics in octocov custom metrics format to the specified file + - `--with-lint-warnings` - Display lint warnings along with metrics output - `patterner coverage` - Display pipeline resolver step coverage --- diff --git a/cmd/metrics.go b/cmd/metrics.go index a89fb90..d2ae280 100644 --- a/cmd/metrics.go +++ b/cmd/metrics.go @@ -168,7 +168,7 @@ var metricsCmd = &cobra.Command{ if err != nil { return err } - if err := os.WriteFile(outOctocovPath, b, 0644); err != nil { + if err := os.WriteFile(outOctocovPath, b, 0644); err != nil { //nolint:gosec return err } } From d6dede18ccabeefff04bd4f482250d23742301c2 Mon Sep 17 00:00:00 2001 From: Ken'ichiro Oyama Date: Thu, 11 Sep 2025 16:58:18 +0900 Subject: [PATCH 07/10] feat: add `--with-coverage-full-report` --- README.md | 50 +++++++++++++++++++++++++++++++++++++------------- cmd/metrics.go | 32 +++++++++++++++++++++++++++++--- 2 files changed, 66 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 3245ab0..5f9d544 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,18 @@ The metrics command displays metrics in a table format. Use the `--out-octocov-p - `--since, -s` (default: "30min") - Analyze execution results since the specified time period - `--out-octocov-path` - Output metrics in octocov custom metrics format to the specified file - `--with-lint-warnings` (default: false) - Display lint warnings along with metrics output +- `--with-coverage-full-report` (default: false) - Display detailed pipeline resolver step coverage along with metrics output + +##### Option Details + +**--with-coverage-full-report vs --with-lint-warnings:** +- `--with-coverage-full-report`: Displays detailed pipeline resolver step coverage information for execution quality monitoring + - Shows coverage percentage and executed steps count for each resolver + - Format: `Coverage Rate% [Executed Steps/Total Steps] Resolver Name` +- `--with-lint-warnings`: Displays detailed lint warnings list for code quality monitoring + - Shows specific lint issues and recommendations for code improvements + +Both options can be used together to provide comprehensive analysis combining execution quality and code quality insights. #### Usage Examples @@ -99,11 +111,17 @@ patterner metrics --since 1hour # Display metrics with lint warnings patterner metrics --with-lint-warnings +# Display metrics with coverage details +patterner metrics --with-coverage-full-report + +# Display metrics with both coverage and lint warnings +patterner metrics --with-coverage-full-report --with-lint-warnings + # Output metrics to octocov custom metrics file patterner metrics --out-octocov-path metrics.json -# Metrics for the past 24 hours with octocov output and lint warnings -patterner metrics --since 24hours --out-octocov-path metrics.json --with-lint-warnings +# Comprehensive analysis with all options +patterner metrics --since 24hours --out-octocov-path metrics.json --with-coverage-full-report --with-lint-warnings ``` **Implementation Notes:** @@ -172,30 +190,35 @@ The following metrics are collected and displayed: **Pipeline Metrics:** -- `pipelines_total` - Total number of pipelines -- `pipeline_resolvers_total` - Total number of pipeline resolvers -- `pipeline_resolver_steps_total` - Total number of pipeline resolver steps -- `pipeline_resolver_execution_paths_total` - Total number of pipeline resolver execution paths - - Calculated based on the number of steps and tests in each resolver (steps \* 2^tests) +- `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_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 - Used to understand the total number of execution paths based on testable step combinations **TailorDB Metrics:** -- `tailordbs_total` - Total number of TailorDBs -- `tailordb_types_total` - Total number of TailorDB types -- `tailordb_type_fields_total` - Total number of TailorDB type fields +- `tailordbs_total` - Total number of TailorDBs (Unit: count) +- `tailordb_types_total` - Total number of TailorDB types (Unit: count) +- `tailordb_type_fields_total` - Total number of TailorDB type fields (Unit: count) **StateFlow Metrics:** -- `stateflows_total` - Total number of StateFlows +- `stateflows_total` - Total number of StateFlows (Unit: count) **Coverage Metrics:** -- `pipeline_resolver_step_coverage_percentage` - Pipeline resolver step coverage percentage +- `pipeline_resolver_step_coverage_percentage` - Pipeline resolver step coverage (Unit: %) + - Calculation: (covered steps / total steps) * 100 + - Provides percentage representation of pipeline resolver step execution coverage **Lint Metrics:** -- `lint_warnings_total` - Total number of lint warnings +- `lint_warnings_total` - Total number of lint warnings (Unit: count) + - Calculation: Number of warnings returned from the lint function + - Helps monitor code quality and adherence to best practices ## Configuration @@ -289,6 +312,7 @@ lint: - `--since, -s` (default: "30min") - Analyze execution results since the specified time period - `--out-octocov-path` - Output metrics in octocov custom metrics format to the specified file - `--with-lint-warnings` - Display lint warnings along with metrics output + - `--with-coverage-full-report` - Display detailed pipeline resolver step coverage along with metrics output - `patterner coverage` - Display pipeline resolver step coverage --- diff --git a/cmd/metrics.go b/cmd/metrics.go index d2ae280..90d9d0c 100644 --- a/cmd/metrics.go +++ b/cmd/metrics.go @@ -38,8 +38,9 @@ import ( ) var ( - outOctocovPath string - withLintWarnings bool + outOctocovPath string + withLintWarnings bool + withCoverageFullReport bool ) var metricsCmd = &cobra.Command{ @@ -73,6 +74,8 @@ var metricsCmd = &cobra.Command{ spi.Disable() if withLintWarnings { + fmt.Println("Lint warnings") + fmt.Println("============================================================") warns, err := c.Lint(resources) if err != nil { return err @@ -83,6 +86,28 @@ var metricsCmd = &cobra.Command{ fmt.Println() } + if withCoverageFullReport { + fmt.Println("Coverage") + fmt.Println("============================================================") + coverages, err := c.Coverage(resources) + if err != nil { + return err + } + for _, rc := range coverages { + var cover float64 + if rc.TotalSteps > 0 { + cover = float64(float64(rc.CoveredSteps)/float64(rc.TotalSteps)) * 100 + } + fmt.Printf("%5s%% [%d/%d] %s\n", fmt.Sprintf("%.1f", cover), rc.CoveredSteps, rc.TotalSteps, rc.Name) + } + fmt.Println() + } + + if withCoverageFullReport || withLintWarnings { + fmt.Println("Metrics") + fmt.Println("============================================================") + } + metrics, err := c.Metrics(resources) if err != nil { return err @@ -182,10 +207,11 @@ func init() { metricsCmd.Flags().StringVarP(&since, "since", "s", "30min", "only consider executions since the given duration (e.g., 24hours, 30min, 15sec)") metricsCmd.Flags().StringVarP(&outOctocovPath, "out-octocov-path", "", "", "output the metrics in octocov custom metrics format to the specified file (e.g., ./metrics.json)") metricsCmd.Flags().BoolVarP(&withLintWarnings, "with-lint-wanings", "", false, "display the lint warnings along with the metrics") + metricsCmd.Flags().BoolVarP(&withCoverageFullReport, "with-coverage-full-report", "", false, "display the coverage full report along with the metrics") } // copy from github.com/k1LoW/octocov/report -// because octocov use tablewriter v0 +// because octocov use tablewriter v0. type MetadataKV struct { Key string `json:"key"` Name string `json:"name,omitempty"` From bd0661486c72af5aa5cf99adff193d76dd8aaae0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ken=E2=80=99ichiro=20Oyama?= Date: Thu, 11 Sep 2025 18:05:41 +0900 Subject: [PATCH 08/10] fix: corrent flag name Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- cmd/metrics.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/metrics.go b/cmd/metrics.go index 90d9d0c..b0f43f4 100644 --- a/cmd/metrics.go +++ b/cmd/metrics.go @@ -206,7 +206,7 @@ func init() { rootCmd.AddCommand(metricsCmd) metricsCmd.Flags().StringVarP(&since, "since", "s", "30min", "only consider executions since the given duration (e.g., 24hours, 30min, 15sec)") metricsCmd.Flags().StringVarP(&outOctocovPath, "out-octocov-path", "", "", "output the metrics in octocov custom metrics format to the specified file (e.g., ./metrics.json)") - metricsCmd.Flags().BoolVarP(&withLintWarnings, "with-lint-wanings", "", false, "display the lint warnings along with the metrics") + metricsCmd.Flags().BoolVarP(&withLintWarnings, "with-lint-warnings", "", false, "display the lint warnings along with the metrics") metricsCmd.Flags().BoolVarP(&withCoverageFullReport, "with-coverage-full-report", "", false, "display the coverage full report along with the metrics") } From af5a0e48c84e5a57544bf070566583febb561eb0 Mon Sep 17 00:00:00 2001 From: Ken'ichiro Oyama Date: Thu, 11 Sep 2025 18:08:16 +0900 Subject: [PATCH 09/10] fix: fix dup creation --- tailor/metrics.go | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/tailor/metrics.go b/tailor/metrics.go index 994e583..04f320f 100644 --- a/tailor/metrics.go +++ b/tailor/metrics.go @@ -90,22 +90,16 @@ func (c *Client) Metrics(resources *Resources) ([]Metric, error) { Value: float64(stepsTotal), Unit: "", }) + pathsMetic := Metric{ + Key: "pipeline_resolver_execution_paths_total", + Name: "Total number of pipeline resolver execution paths", + Value: float64(executionPathsTotal), + Unit: "", + } if executionPathsTotal < 0 { - metrics = append(metrics, Metric{ - Key: "pipeline_resolver_execution_paths_total", - Name: "Total number of pipeline resolver execution paths", - Value: float64(executionPathsTotal), - Unit: "", - Error: errors.New("overflow detected"), - }) - } else { - metrics = append(metrics, Metric{ - Key: "pipeline_resolver_execution_paths_total", - Name: "Total number of pipeline resolver execution paths", - Value: float64(executionPathsTotal), - Unit: "", - }) + pathsMetic.Error = errors.New("overflow detected") } + metrics = append(metrics, pathsMetic) // TailorDB Metrics metrics = append(metrics, Metric{ From 923b979fa8f2d8828f11cdcec2be7d9cb18ae947 Mon Sep 17 00:00:00 2001 From: Ken'ichiro Oyama Date: Thu, 11 Sep 2025 22:29:04 +0900 Subject: [PATCH 10/10] fix: remove unnecessary tests --- tailor/metrics_test.go | 73 +++--------------------------------------- 1 file changed, 4 insertions(+), 69 deletions(-) diff --git a/tailor/metrics_test.go b/tailor/metrics_test.go index 0ec9dd6..9d55e22 100644 --- a/tailor/metrics_test.go +++ b/tailor/metrics_test.go @@ -565,7 +565,7 @@ func TestClient_Metrics_EdgeCases(t *testing.T) { }) } -func TestClient_Metrics_MetricNames(t *testing.T) { +func TestClient_Metrics_MetricKeys(t *testing.T) { cfg := createTestConfig(t) client, err := New(cfg) if err != nil { @@ -620,71 +620,6 @@ func TestClient_Metrics_MetricNames(t *testing.T) { } } -func TestClient_Metrics_MetricFields(t *testing.T) { - cfg := createTestConfig(t) - client, err := New(cfg) - if err != nil { - t.Fatalf("Failed to create client: %v", err) - } - - resources := &Resources{ - Applications: []*Application{}, - Pipelines: []*Pipeline{}, - TailorDBs: []*TailorDB{}, - StateFlows: []*StateFlow{}, - } - metrics, err := client.Metrics(resources) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - - expectedKeys := map[string]string{ - "pipeline_resolver_step_coverage_percentage": "pipeline_resolver_step_coverage_percentage", - "lint_warnings_total": "lint_warnings_total", - "pipelines_total": "pipelines_total", - "pipeline_resolvers_total": "pipeline_resolvers_total", - "pipeline_resolver_steps_total": "pipeline_resolver_steps_total", - "pipeline_resolver_execution_paths_total": "pipeline_resolver_execution_paths_total", - "tailordbs_total": "tailordbs_total", - "tailordb_types_total": "tailordb_types_total", - "tailordb_type_fields_total": "tailordb_type_fields_total", - "stateflows_total": "stateflows_total", - } - - for _, metric := range metrics { - expectedKey, exists := expectedKeys[metric.Key] - if !exists { - t.Errorf("Unexpected metric: %s", metric.Key) - } - if expectedKey != metric.Key { - t.Errorf("Wrong key for metric %s: expected '%s', got '%s'", - metric.Key, expectedKey, metric.Key) - } - } -} - -func TestMetric_Fields(t *testing.T) { - metric := Metric{ - Key: "test_metric", - Name: "Test Metric", - Value: 42.5, - Unit: "count", - } - - if metric.Key != "test_metric" { - t.Errorf("Expected Key to be 'test_metric', got '%s'", metric.Key) - } - if metric.Name != "Test Metric" { - t.Errorf("Expected Name to be 'Test Metric', got '%s'", metric.Name) - } - if metric.Value != 42.5 { - t.Errorf("Expected Value to be 42.5, got %f", metric.Value) - } - if metric.Unit != "count" { - t.Errorf("Expected Unit to be 'count', got '%s'", metric.Unit) - } -} - func TestClient_Metrics_LargeNumbers(t *testing.T) { // Test with large numbers to ensure proper handling cfg := createTestConfig(t) @@ -695,11 +630,11 @@ func TestClient_Metrics_LargeNumbers(t *testing.T) { // Create resources with many items pipelines := make([]*Pipeline, 100) - for i := 0; i < 100; i++ { + for i := range 100 { resolvers := make([]*PipelineResolver, 10) - for j := 0; j < 10; j++ { + for j := range 10 { steps := make([]*PipelineStep, 5) - for k := 0; k < 5; k++ { + for k := range 5 { steps[k] = &PipelineStep{Name: "step"} } resolvers[j] = &PipelineResolver{