1
16
17 package metrics
18
19 import (
20 "context"
21 "fmt"
22 "time"
23
24 "k8s.io/klog/v2"
25
26 autoscaling "k8s.io/api/autoscaling/v2"
27 v1 "k8s.io/api/core/v1"
28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29 "k8s.io/apimachinery/pkg/labels"
30 "k8s.io/apimachinery/pkg/runtime/schema"
31 customapi "k8s.io/metrics/pkg/apis/custom_metrics/v1beta2"
32 metricsapi "k8s.io/metrics/pkg/apis/metrics/v1beta1"
33 resourceclient "k8s.io/metrics/pkg/client/clientset/versioned/typed/metrics/v1beta1"
34 customclient "k8s.io/metrics/pkg/client/custom_metrics"
35 externalclient "k8s.io/metrics/pkg/client/external_metrics"
36 )
37
38 const (
39 metricServerDefaultMetricWindow = time.Minute
40 )
41
42 func NewRESTMetricsClient(resourceClient resourceclient.PodMetricsesGetter, customClient customclient.CustomMetricsClient, externalClient externalclient.ExternalMetricsClient) MetricsClient {
43 return &restMetricsClient{
44 &resourceMetricsClient{resourceClient},
45 &customMetricsClient{customClient},
46 &externalMetricsClient{externalClient},
47 }
48 }
49
50
51
52
53 type restMetricsClient struct {
54 *resourceMetricsClient
55 *customMetricsClient
56 *externalMetricsClient
57 }
58
59
60
61 type resourceMetricsClient struct {
62 client resourceclient.PodMetricsesGetter
63 }
64
65
66
67 func (c *resourceMetricsClient) GetResourceMetric(ctx context.Context, resource v1.ResourceName, namespace string, selector labels.Selector, container string) (PodMetricsInfo, time.Time, error) {
68 metrics, err := c.client.PodMetricses(namespace).List(ctx, metav1.ListOptions{LabelSelector: selector.String()})
69 if err != nil {
70 return nil, time.Time{}, fmt.Errorf("unable to fetch metrics from resource metrics API: %v", err)
71 }
72
73 if len(metrics.Items) == 0 {
74 return nil, time.Time{}, fmt.Errorf("no metrics returned from resource metrics API")
75 }
76 var res PodMetricsInfo
77 if container != "" {
78 res, err = getContainerMetrics(metrics.Items, resource, container)
79 if err != nil {
80 return nil, time.Time{}, fmt.Errorf("failed to get container metrics: %v", err)
81 }
82 } else {
83 res = getPodMetrics(ctx, metrics.Items, resource)
84 }
85 timestamp := metrics.Items[0].Timestamp.Time
86 return res, timestamp, nil
87 }
88
89 func getContainerMetrics(rawMetrics []metricsapi.PodMetrics, resource v1.ResourceName, container string) (PodMetricsInfo, error) {
90 res := make(PodMetricsInfo, len(rawMetrics))
91 for _, m := range rawMetrics {
92 containerFound := false
93 for _, c := range m.Containers {
94 if c.Name == container {
95 containerFound = true
96 if val, resFound := c.Usage[resource]; resFound {
97 res[m.Name] = PodMetric{
98 Timestamp: m.Timestamp.Time,
99 Window: m.Window.Duration,
100 Value: val.MilliValue(),
101 }
102 }
103 break
104 }
105 }
106 if !containerFound {
107 return nil, fmt.Errorf("container %s not present in metrics for pod %s/%s", container, m.Namespace, m.Name)
108 }
109 }
110 return res, nil
111 }
112
113 func getPodMetrics(ctx context.Context, rawMetrics []metricsapi.PodMetrics, resource v1.ResourceName) PodMetricsInfo {
114 res := make(PodMetricsInfo, len(rawMetrics))
115 for _, m := range rawMetrics {
116 podSum := int64(0)
117 missing := len(m.Containers) == 0
118 for _, c := range m.Containers {
119 resValue, found := c.Usage[resource]
120 if !found {
121 missing = true
122 klog.FromContext(ctx).V(2).Info("Missing resource metric", "resourceMetric", resource, "pod", klog.KRef(m.Namespace, m.Name))
123 break
124 }
125 podSum += resValue.MilliValue()
126 }
127 if !missing {
128 res[m.Name] = PodMetric{
129 Timestamp: m.Timestamp.Time,
130 Window: m.Window.Duration,
131 Value: podSum,
132 }
133 }
134 }
135 return res
136 }
137
138
139
140 type customMetricsClient struct {
141 client customclient.CustomMetricsClient
142 }
143
144
145
146 func (c *customMetricsClient) GetRawMetric(metricName string, namespace string, selector labels.Selector, metricSelector labels.Selector) (PodMetricsInfo, time.Time, error) {
147 metrics, err := c.client.NamespacedMetrics(namespace).GetForObjects(schema.GroupKind{Kind: "Pod"}, selector, metricName, metricSelector)
148 if err != nil {
149 return nil, time.Time{}, fmt.Errorf("unable to fetch metrics from custom metrics API: %v", err)
150 }
151
152 if len(metrics.Items) == 0 {
153 return nil, time.Time{}, fmt.Errorf("no metrics returned from custom metrics API")
154 }
155
156 res := make(PodMetricsInfo, len(metrics.Items))
157 for _, m := range metrics.Items {
158 window := metricServerDefaultMetricWindow
159 if m.WindowSeconds != nil {
160 window = time.Duration(*m.WindowSeconds) * time.Second
161 }
162 res[m.DescribedObject.Name] = PodMetric{
163 Timestamp: m.Timestamp.Time,
164 Window: window,
165 Value: int64(m.Value.MilliValue()),
166 }
167
168 m.Value.MilliValue()
169 }
170
171 timestamp := metrics.Items[0].Timestamp.Time
172
173 return res, timestamp, nil
174 }
175
176
177
178 func (c *customMetricsClient) GetObjectMetric(metricName string, namespace string, objectRef *autoscaling.CrossVersionObjectReference, metricSelector labels.Selector) (int64, time.Time, error) {
179 gvk := schema.FromAPIVersionAndKind(objectRef.APIVersion, objectRef.Kind)
180 var metricValue *customapi.MetricValue
181 var err error
182 if gvk.Kind == "Namespace" && gvk.Group == "" {
183
184
185
186 metricValue, err = c.client.RootScopedMetrics().GetForObject(gvk.GroupKind(), namespace, metricName, metricSelector)
187 } else {
188 metricValue, err = c.client.NamespacedMetrics(namespace).GetForObject(gvk.GroupKind(), objectRef.Name, metricName, metricSelector)
189 }
190
191 if err != nil {
192 return 0, time.Time{}, fmt.Errorf("unable to fetch metrics from custom metrics API: %v", err)
193 }
194
195 return metricValue.Value.MilliValue(), metricValue.Timestamp.Time, nil
196 }
197
198
199
200 type externalMetricsClient struct {
201 client externalclient.ExternalMetricsClient
202 }
203
204
205
206 func (c *externalMetricsClient) GetExternalMetric(metricName, namespace string, selector labels.Selector) ([]int64, time.Time, error) {
207 metrics, err := c.client.NamespacedMetrics(namespace).List(metricName, selector)
208 if err != nil {
209 return []int64{}, time.Time{}, fmt.Errorf("unable to fetch metrics from external metrics API: %v", err)
210 }
211
212 if len(metrics.Items) == 0 {
213 return nil, time.Time{}, fmt.Errorf("no metrics returned from external metrics API")
214 }
215
216 res := make([]int64, 0)
217 for _, m := range metrics.Items {
218 res = append(res, m.Value.MilliValue())
219 }
220 timestamp := metrics.Items[0].Timestamp.Time
221 return res, timestamp, nil
222 }
223
View as plain text