istio.io/istio@v0.0.0-20240520182934-d79c90f27776/istioctl/pkg/metrics/metrics_test.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package metrics 16 17 import ( 18 "bytes" 19 "context" 20 "fmt" 21 "regexp" 22 "strings" 23 "testing" 24 "time" 25 26 promv1 "github.com/prometheus/client_golang/api/prometheus/v1" 27 prometheus_model "github.com/prometheus/common/model" 28 corev1 "k8s.io/api/core/v1" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 31 "istio.io/istio/istioctl/pkg/cli" 32 "istio.io/istio/istioctl/pkg/util/testutil" 33 ) 34 35 // mockPromAPI lets us mock calls to Prometheus API 36 type mockPromAPI struct { 37 cannedResponse map[string]prometheus_model.Value 38 } 39 40 func TestMetricsNoPrometheus(t *testing.T) { 41 cases := []testutil.TestCase{ 42 { // case 0 43 Args: []string{}, 44 ExpectedRegexp: regexp.MustCompile("Error: metrics requires workload name\n"), 45 WantException: true, 46 }, 47 { // case 1 48 Args: strings.Split("details", " "), 49 ExpectedOutput: "Error: no Prometheus pods found\n", 50 WantException: true, 51 }, 52 } 53 54 metricCmd := Cmd(cli.NewFakeContext(nil)) 55 for i, c := range cases { 56 t.Run(fmt.Sprintf("case %d %s", i, strings.Join(c.Args, " ")), func(t *testing.T) { 57 testutil.VerifyOutput(t, metricCmd, c) 58 }) 59 } 60 } 61 62 func TestMetrics(t *testing.T) { 63 cases := []testutil.TestCase{ 64 { // case 0 65 Args: strings.Split("details", " "), 66 ExpectedRegexp: regexp.MustCompile("could not build metrics for workload"), 67 WantException: true, 68 }, 69 } 70 71 ctx := cli.NewFakeContext(&cli.NewFakeContextOption{ 72 IstioNamespace: "istio-system", 73 }) 74 client, err := ctx.CLIClient() 75 if err != nil { 76 t.Fatal(err) 77 } 78 client.Kube().CoreV1().Pods("istio-system").Create(context.TODO(), &corev1.Pod{ 79 ObjectMeta: metav1.ObjectMeta{ 80 Name: "prometheus", 81 Namespace: "istio-system", 82 Labels: map[string]string{ 83 "app.kubernetes.io/name": "prometheus", 84 }, 85 }, 86 }, metav1.CreateOptions{}) 87 metricCmd := Cmd(ctx) 88 for i, c := range cases { 89 t.Run(fmt.Sprintf("case %d %s", i, strings.Join(c.Args, " ")), func(t *testing.T) { 90 testutil.VerifyOutput(t, metricCmd, c) 91 }) 92 } 93 } 94 95 func TestAPI(t *testing.T) { 96 _, _ = prometheusAPI(fmt.Sprintf("http://localhost:%d", 1234)) 97 } 98 99 var _ promv1.API = mockPromAPI{} 100 101 func TestPrintMetrics(t *testing.T) { 102 mockProm := mockPromAPI{ 103 cannedResponse: map[string]prometheus_model.Value{ 104 "sum(rate(istio_requests_total{destination_workload=~\"details.*\", destination_workload_namespace=~\".*\",reporter=\"destination\"}[1m0s]))": prometheus_model.Vector{ // nolint: lll 105 &prometheus_model.Sample{Value: 0.04}, 106 }, 107 "sum(rate(istio_requests_total{destination_workload=~\"details.*\", destination_workload_namespace=~\".*\",reporter=\"destination\",response_code=~\"[45][0-9]{2}\"}[1m0s]))": prometheus_model.Vector{}, // nolint: lll 108 "histogram_quantile(0.500000, sum(rate(istio_request_duration_milliseconds_bucket{destination_workload=~\"details.*\", destination_workload_namespace=~\".*\",reporter=\"destination\"}[1m0s])) by (le))": prometheus_model.Vector{ // nolint: lll 109 &prometheus_model.Sample{Value: 2.5}, 110 }, 111 "histogram_quantile(0.900000, sum(rate(istio_request_duration_milliseconds_bucket{destination_workload=~\"details.*\", destination_workload_namespace=~\".*\",reporter=\"destination\"}[1m0s])) by (le))": prometheus_model.Vector{ // nolint: lll 112 &prometheus_model.Sample{Value: 4.5}, 113 }, 114 "histogram_quantile(0.990000, sum(rate(istio_request_duration_milliseconds_bucket{destination_workload=~\"details.*\", destination_workload_namespace=~\".*\",reporter=\"destination\"}[1m0s])) by (le))": prometheus_model.Vector{ // nolint: lll 115 &prometheus_model.Sample{Value: 4.95}, 116 }, 117 }, 118 } 119 workload := "details" 120 121 sm, err := metrics(mockProm, workload, time.Minute) 122 if err != nil { 123 t.Fatalf("Unwanted exception %v", err) 124 } 125 126 var out bytes.Buffer 127 printHeader(&out) 128 printMetrics(&out, sm) 129 output := out.String() 130 131 expectedOutput := ` WORKLOAD TOTAL RPS ERROR RPS P50 LATENCY P90 LATENCY P99 LATENCY 132 details 0.040 0.000 2ms 4ms 4ms 133 ` 134 if output != expectedOutput { 135 t.Fatalf("Unexpected output; got:\n %q\nwant:\n %q", output, expectedOutput) 136 } 137 } 138 139 func (client mockPromAPI) Alerts(ctx context.Context) (promv1.AlertsResult, error) { 140 return promv1.AlertsResult{}, fmt.Errorf("TODO mockPromAPI doesn't mock Alerts") 141 } 142 143 func (client mockPromAPI) AlertManagers(ctx context.Context) (promv1.AlertManagersResult, error) { 144 return promv1.AlertManagersResult{}, fmt.Errorf("TODO mockPromAPI doesn't mock AlertManagers") 145 } 146 147 func (client mockPromAPI) CleanTombstones(ctx context.Context) error { 148 return nil 149 } 150 151 func (client mockPromAPI) Config(ctx context.Context) (promv1.ConfigResult, error) { 152 return promv1.ConfigResult{}, nil 153 } 154 155 func (client mockPromAPI) DeleteSeries(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) error { 156 return nil 157 } 158 159 func (client mockPromAPI) Flags(ctx context.Context) (promv1.FlagsResult, error) { 160 return nil, nil 161 } 162 163 func (client mockPromAPI) Query(ctx context.Context, query string, ts time.Time, opts ...promv1.Option) (prometheus_model.Value, promv1.Warnings, error) { 164 canned, ok := client.cannedResponse[query] 165 if !ok { 166 return prometheus_model.Vector{}, nil, nil 167 } 168 return canned, nil, nil 169 } 170 171 func (client mockPromAPI) TSDB(ctx context.Context) (promv1.TSDBResult, error) { 172 return promv1.TSDBResult{}, nil 173 } 174 175 func (client mockPromAPI) QueryRange( 176 ctx context.Context, 177 query string, 178 r promv1.Range, 179 opts ...promv1.Option, 180 ) (prometheus_model.Value, promv1.Warnings, error) { 181 canned, ok := client.cannedResponse[query] 182 if !ok { 183 return prometheus_model.Vector{}, nil, nil 184 } 185 return canned, nil, nil 186 } 187 188 func (client mockPromAPI) WalReplay(ctx context.Context) (promv1.WalReplayStatus, error) { 189 // TODO implement me 190 panic("implement me") 191 } 192 193 func (client mockPromAPI) Series(ctx context.Context, matches []string, 194 startTime time.Time, endTime time.Time, 195 ) ([]prometheus_model.LabelSet, promv1.Warnings, error) { 196 return nil, nil, nil 197 } 198 199 func (client mockPromAPI) Snapshot(ctx context.Context, skipHead bool) (promv1.SnapshotResult, error) { 200 return promv1.SnapshotResult{}, nil 201 } 202 203 func (client mockPromAPI) Rules(ctx context.Context) (promv1.RulesResult, error) { 204 return promv1.RulesResult{}, nil 205 } 206 207 func (client mockPromAPI) Targets(ctx context.Context) (promv1.TargetsResult, error) { 208 return promv1.TargetsResult{}, nil 209 } 210 211 func (client mockPromAPI) TargetsMetadata(ctx context.Context, matchTarget string, metric string, limit string) ([]promv1.MetricMetadata, error) { 212 return nil, nil 213 } 214 215 func (client mockPromAPI) Runtimeinfo(ctx context.Context) (promv1.RuntimeinfoResult, error) { 216 return promv1.RuntimeinfoResult{}, nil 217 } 218 219 func (client mockPromAPI) Metadata(ctx context.Context, metric string, limit string) (map[string][]promv1.Metadata, error) { 220 return nil, nil 221 } 222 223 func (client mockPromAPI) LabelNames(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]string, promv1.Warnings, error) { 224 return nil, nil, nil 225 } 226 227 func (client mockPromAPI) LabelValues(context.Context, string, []string, time.Time, time.Time) (prometheus_model.LabelValues, promv1.Warnings, error) { 228 return nil, nil, nil 229 } 230 231 func (client mockPromAPI) Buildinfo(ctx context.Context) (promv1.BuildinfoResult, error) { 232 return promv1.BuildinfoResult{}, nil 233 } 234 235 func (client mockPromAPI) QueryExemplars(ctx context.Context, query string, startTime time.Time, endTime time.Time) ([]promv1.ExemplarQueryResult, error) { 236 return nil, nil 237 }