Skip to content

Commit c677146

Browse files
authored
Merge pull request #1815 from bitoku/list-metric-descriptors
Add metricdescs subcommand for ListMetricDescriptors API
2 parents 801d04c + b00094d commit c677146

File tree

6 files changed

+179
-0
lines changed

6 files changed

+179
-0
lines changed

cmd/crictl/main.go

+1
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ func main() {
222222
statsCommand,
223223
podStatsCommand,
224224
podMetricsCommand,
225+
metricDescriptorsCommand,
225226
completionCommand,
226227
checkpointContainerCommand,
227228
runtimeConfigCommand,

cmd/crictl/metric_descs.go

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import (
20+
"context"
21+
"fmt"
22+
23+
"github.com/sirupsen/logrus"
24+
"github.com/urfave/cli/v2"
25+
cri "k8s.io/cri-api/pkg/apis"
26+
pb "k8s.io/cri-api/pkg/apis/runtime/v1"
27+
)
28+
29+
type metricDescriptorsOptions struct {
30+
// output format
31+
output string
32+
}
33+
34+
var metricDescriptorsCommand = &cli.Command{
35+
Name: "metricdescs",
36+
Usage: "List metric descriptors. Returns information about the metrics available through the CRI.",
37+
UseShortOptionHandling: true,
38+
Flags: []cli.Flag{
39+
&cli.StringFlag{
40+
Name: "output",
41+
Aliases: []string{"o"},
42+
Usage: "Output format, One of: json|yaml",
43+
},
44+
},
45+
Action: func(c *cli.Context) error {
46+
if c.NArg() > 0 {
47+
return cli.ShowSubcommandHelp(c)
48+
}
49+
50+
client, err := getRuntimeService(c, 0)
51+
if err != nil {
52+
return fmt.Errorf("get runtime service: %w", err)
53+
}
54+
55+
opts := metricDescriptorsOptions{
56+
output: c.String("output"),
57+
}
58+
59+
switch opts.output {
60+
case outputTypeJSON, outputTypeYAML, "":
61+
default:
62+
return cli.ShowSubcommandHelp(c)
63+
}
64+
65+
if err := metricDescriptors(c.Context, client, opts); err != nil {
66+
return fmt.Errorf("get metric descriptors: %w", err)
67+
}
68+
69+
return nil
70+
},
71+
}
72+
73+
func metricDescriptors(
74+
c context.Context,
75+
client cri.RuntimeService,
76+
opts metricDescriptorsOptions,
77+
) error {
78+
d := metricDescriptorsDisplayer{opts}
79+
80+
return d.displayMetricDescriptors(c, client)
81+
}
82+
83+
type metricDescriptorsDisplayer struct {
84+
opts metricDescriptorsOptions
85+
}
86+
87+
func (m *metricDescriptorsDisplayer) displayMetricDescriptors(
88+
c context.Context,
89+
client cri.RuntimeService,
90+
) error {
91+
descriptors, err := listMetricDescriptors(c, client)
92+
if err != nil {
93+
return err
94+
}
95+
96+
response := &pb.ListMetricDescriptorsResponse{Descriptors: descriptors}
97+
98+
switch m.opts.output {
99+
case outputTypeJSON, "":
100+
return outputProtobufObjAsJSON(response)
101+
case outputTypeYAML:
102+
return outputProtobufObjAsYAML(response)
103+
}
104+
105+
return nil
106+
}
107+
108+
func listMetricDescriptors(ctx context.Context, client cri.RuntimeService) ([]*pb.MetricDescriptor, error) {
109+
descriptors, err := InterruptableRPC(ctx, func(ctx context.Context) ([]*pb.MetricDescriptor, error) {
110+
return client.ListMetricDescriptors(ctx)
111+
})
112+
if err != nil {
113+
return nil, fmt.Errorf("list metric descriptors: %w", err)
114+
}
115+
116+
logrus.Debugf("MetricDescriptors: %v", descriptors)
117+
118+
return descriptors, nil
119+
}

docs/crictl.1

+4
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ COMMANDS:
7272
.IP \(bu 2
7373
\fBlogs\fR: Fetch the logs of a container
7474
.IP \(bu 2
75+
\fBmetricsp\fR: List pod metrics. Metrics are unstructured key/value pairs gathered by CRI meant to replace cAdvisor's /metrics/cadvisor endpoint.
76+
.IP \(bu 2
77+
\fBmetricdescs\fR: List metric descriptors. Returns information about the metrics available through the CRI.
78+
.IP \(bu 2
7579
\fBport-forward\fR: Forward local port to a pod
7680
.IP \(bu 2
7781
\fBps\fR: List containers

docs/crictl.md

+2
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ COMMANDS:
4949
- `imagefsinfo`: Return image filesystem info
5050
- `inspectp`: Display the status of one or more pods
5151
- `logs`: Fetch the logs of a container
52+
- `metricsp`: List pod metrics. Metrics are unstructured key/value pairs gathered by CRI meant to replace cAdvisor's /metrics/cadvisor endpoint.
53+
- `metricdescs`: List metric descriptors. Returns information about the metrics available through the CRI.
5254
- `port-forward`: Forward local port to a pod
5355
- `ps`: List containers
5456
- `pull`: Pull an image from a registry

test/e2e/metricdescs_test.go

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package e2e
18+
19+
import (
20+
"encoding/json"
21+
22+
. "github.com/onsi/ginkgo/v2"
23+
. "github.com/onsi/gomega"
24+
. "github.com/onsi/gomega/gexec"
25+
)
26+
27+
// The actual test suite.
28+
var _ = t.Describe("metricdescs", func() {
29+
It("should list metric descriptors", func() {
30+
if t.IsContainerd() {
31+
Skip("ListMetricDescriptors is not supported by containerd")
32+
}
33+
// Run the command with JSON output format
34+
res := t.Crictl("metricdescs")
35+
Expect(res).To(Exit(0))
36+
contents := res.Out.Contents()
37+
38+
// Verify JSON output is valid
39+
var response map[string]any
40+
Expect(json.Unmarshal(contents, &response)).NotTo(HaveOccurred())
41+
42+
// Verify response has expected structure
43+
Expect(response).To(HaveKey("descriptors"))
44+
45+
// Validate descriptors are an array (even if empty)
46+
_, ok := response["descriptors"].([]any)
47+
Expect(ok).To(BeTrue())
48+
})
49+
})

test/framework/framework.go

+4
Original file line numberDiff line numberDiff line change
@@ -158,3 +158,7 @@ func (t *TestFramework) CrictlRemovePauseImages() {
158158
t.CrictlExpectSuccess("rmi "+strings.TrimSpace(strings.Join(output, " ")), "Deleted")
159159
}
160160
}
161+
162+
func (t *TestFramework) IsContainerd() bool {
163+
return strings.Contains(crictlRuntimeEndpoint, "containerd")
164+
}

0 commit comments

Comments
 (0)