mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-03 09:22:44 +00:00
Add image pull duration metric with bucketed image size
Signed-off-by: ruiwen-zhao <ruiwen@google.com>
This commit is contained in:
parent
6eee80fa9a
commit
0f5cf6c1cd
@ -162,6 +162,8 @@ type ImageService interface {
|
|||||||
ImageStats(ctx context.Context) (*ImageStats, error)
|
ImageStats(ctx context.Context) (*ImageStats, error)
|
||||||
// ImageFsInfo returns a list of file systems for containers/images
|
// ImageFsInfo returns a list of file systems for containers/images
|
||||||
ImageFsInfo(ctx context.Context) (*runtimeapi.ImageFsInfoResponse, error)
|
ImageFsInfo(ctx context.Context) (*runtimeapi.ImageFsInfoResponse, error)
|
||||||
|
// GetImageSize returns the size of the image
|
||||||
|
GetImageSize(ctx context.Context, image ImageSpec) (uint64, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attacher interface allows to attach a container.
|
// Attacher interface allows to attach a container.
|
||||||
|
@ -359,6 +359,14 @@ func (f *FakeRuntime) GetImageRef(_ context.Context, image kubecontainer.ImageSp
|
|||||||
return "", f.InspectErr
|
return "", f.InspectErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *FakeRuntime) GetImageSize(_ context.Context, image kubecontainer.ImageSpec) (uint64, error) {
|
||||||
|
f.Lock()
|
||||||
|
defer f.Unlock()
|
||||||
|
|
||||||
|
f.CalledFunctions = append(f.CalledFunctions, "GetImageSize")
|
||||||
|
return 0, f.Err
|
||||||
|
}
|
||||||
|
|
||||||
func (f *FakeRuntime) ListImages(_ context.Context) ([]kubecontainer.Image, error) {
|
func (f *FakeRuntime) ListImages(_ context.Context) ([]kubecontainer.Image, error) {
|
||||||
f.Lock()
|
f.Lock()
|
||||||
defer f.Unlock()
|
defer f.Unlock()
|
||||||
|
@ -212,6 +212,21 @@ func (mr *MockRuntimeMockRecorder) GetImageRef(ctx, image interface{}) *gomock.C
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetImageRef", reflect.TypeOf((*MockRuntime)(nil).GetImageRef), ctx, image)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetImageRef", reflect.TypeOf((*MockRuntime)(nil).GetImageRef), ctx, image)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetImageSize mocks base method.
|
||||||
|
func (m *MockRuntime) GetImageSize(ctx context.Context, image container.ImageSpec) (uint64, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetImageSize", ctx, image)
|
||||||
|
ret0, _ := ret[0].(uint64)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetImageSize indicates an expected call of GetImageSize.
|
||||||
|
func (mr *MockRuntimeMockRecorder) GetImageSize(ctx, image interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetImageSize", reflect.TypeOf((*MockRuntime)(nil).GetImageSize), ctx, image)
|
||||||
|
}
|
||||||
|
|
||||||
// GetPodStatus mocks base method.
|
// GetPodStatus mocks base method.
|
||||||
func (m *MockRuntime) GetPodStatus(ctx context.Context, uid types.UID, name, namespace string) (*container.PodStatus, error) {
|
func (m *MockRuntime) GetPodStatus(ctx context.Context, uid types.UID, name, namespace string) (*container.PodStatus, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@ -538,6 +553,21 @@ func (mr *MockImageServiceMockRecorder) GetImageRef(ctx, image interface{}) *gom
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetImageRef", reflect.TypeOf((*MockImageService)(nil).GetImageRef), ctx, image)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetImageRef", reflect.TypeOf((*MockImageService)(nil).GetImageRef), ctx, image)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetImageSize mocks base method.
|
||||||
|
func (m *MockImageService) GetImageSize(ctx context.Context, image container.ImageSpec) (uint64, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetImageSize", ctx, image)
|
||||||
|
ret0, _ := ret[0].(uint64)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetImageSize indicates an expected call of GetImageSize.
|
||||||
|
func (mr *MockImageServiceMockRecorder) GetImageSize(ctx, image interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetImageSize", reflect.TypeOf((*MockImageService)(nil).GetImageSize), ctx, image)
|
||||||
|
}
|
||||||
|
|
||||||
// ImageFsInfo mocks base method.
|
// ImageFsInfo mocks base method.
|
||||||
func (m *MockImageService) ImageFsInfo(ctx context.Context) (*v10.ImageFsInfoResponse, error) {
|
func (m *MockImageService) ImageFsInfo(ctx context.Context) (*v10.ImageFsInfoResponse, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
|
@ -32,6 +32,7 @@ import (
|
|||||||
crierrors "k8s.io/cri-api/pkg/errors"
|
crierrors "k8s.io/cri-api/pkg/errors"
|
||||||
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/events"
|
"k8s.io/kubernetes/pkg/kubelet/events"
|
||||||
|
"k8s.io/kubernetes/pkg/kubelet/metrics"
|
||||||
"k8s.io/kubernetes/pkg/util/parsers"
|
"k8s.io/kubernetes/pkg/util/parsers"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -166,8 +167,10 @@ func (m *imageManager) EnsureImageExists(ctx context.Context, pod *v1.Pod, conta
|
|||||||
return "", msg, err
|
return "", msg, err
|
||||||
}
|
}
|
||||||
m.podPullingTimeRecorder.RecordImageFinishedPulling(pod.UID)
|
m.podPullingTimeRecorder.RecordImageFinishedPulling(pod.UID)
|
||||||
m.logIt(ref, v1.EventTypeNormal, events.PulledImage, logPrefix, fmt.Sprintf("Successfully pulled image %q in %v (%v including waiting)",
|
imagePullDuration := time.Since(startTime).Truncate(time.Millisecond)
|
||||||
container.Image, imagePullResult.pullDuration.Truncate(time.Millisecond), time.Since(startTime).Truncate(time.Millisecond)), klog.Info)
|
m.logIt(ref, v1.EventTypeNormal, events.PulledImage, logPrefix, fmt.Sprintf("Successfully pulled image %q in %v (%v including waiting). Image size: %v bytes.",
|
||||||
|
container.Image, imagePullResult.pullDuration.Truncate(time.Millisecond), imagePullDuration, imagePullResult.imageSize), klog.Info)
|
||||||
|
metrics.ImagePullDuration.WithLabelValues(metrics.GetImageSizeBucket(imagePullResult.imageSize)).Observe(imagePullDuration.Seconds())
|
||||||
m.backOff.GC()
|
m.backOff.GC()
|
||||||
return imagePullResult.imageRef, "", nil
|
return imagePullResult.imageRef, "", nil
|
||||||
}
|
}
|
||||||
|
@ -69,7 +69,7 @@ func pullerTestCases() []pullerTestCase {
|
|||||||
qps: 0.0,
|
qps: 0.0,
|
||||||
burst: 0,
|
burst: 0,
|
||||||
expected: []pullerExpects{
|
expected: []pullerExpects{
|
||||||
{[]string{"GetImageRef", "PullImage"}, nil, true, true},
|
{[]string{"GetImageRef", "PullImage", "GetImageSize"}, nil, true, true},
|
||||||
}},
|
}},
|
||||||
|
|
||||||
{ // image present, don't pull
|
{ // image present, don't pull
|
||||||
@ -94,9 +94,9 @@ func pullerTestCases() []pullerTestCase {
|
|||||||
qps: 0.0,
|
qps: 0.0,
|
||||||
burst: 0,
|
burst: 0,
|
||||||
expected: []pullerExpects{
|
expected: []pullerExpects{
|
||||||
{[]string{"GetImageRef", "PullImage"}, nil, true, true},
|
{[]string{"GetImageRef", "PullImage", "GetImageSize"}, nil, true, true},
|
||||||
{[]string{"GetImageRef", "PullImage"}, nil, true, true},
|
{[]string{"GetImageRef", "PullImage", "GetImageSize"}, nil, true, true},
|
||||||
{[]string{"GetImageRef", "PullImage"}, nil, true, true},
|
{[]string{"GetImageRef", "PullImage", "GetImageSize"}, nil, true, true},
|
||||||
}},
|
}},
|
||||||
// missing image, error PullNever
|
// missing image, error PullNever
|
||||||
{containerImage: "missing_image",
|
{containerImage: "missing_image",
|
||||||
@ -149,9 +149,9 @@ func pullerTestCases() []pullerTestCase {
|
|||||||
qps: 400.0,
|
qps: 400.0,
|
||||||
burst: 600,
|
burst: 600,
|
||||||
expected: []pullerExpects{
|
expected: []pullerExpects{
|
||||||
{[]string{"GetImageRef", "PullImage"}, nil, true, true},
|
{[]string{"GetImageRef", "PullImage", "GetImageSize"}, nil, true, true},
|
||||||
{[]string{"GetImageRef", "PullImage"}, nil, true, true},
|
{[]string{"GetImageRef", "PullImage", "GetImageSize"}, nil, true, true},
|
||||||
{[]string{"GetImageRef", "PullImage"}, nil, true, true},
|
{[]string{"GetImageRef", "PullImage", "GetImageSize"}, nil, true, true},
|
||||||
}},
|
}},
|
||||||
// image present, non-zero qps, try to pull when qps exceeded
|
// image present, non-zero qps, try to pull when qps exceeded
|
||||||
{containerImage: "present_image",
|
{containerImage: "present_image",
|
||||||
@ -356,7 +356,7 @@ func TestPullAndListImageWithPodAnnotations(t *testing.T) {
|
|||||||
inspectErr: nil,
|
inspectErr: nil,
|
||||||
pullerErr: nil,
|
pullerErr: nil,
|
||||||
expected: []pullerExpects{
|
expected: []pullerExpects{
|
||||||
{[]string{"GetImageRef", "PullImage"}, nil, true, true},
|
{[]string{"GetImageRef", "PullImage", "GetImageSize"}, nil, true, true},
|
||||||
}}
|
}}
|
||||||
|
|
||||||
useSerializedEnv := true
|
useSerializedEnv := true
|
||||||
@ -412,7 +412,7 @@ func TestPullAndListImageWithRuntimeHandlerInImageCriAPIFeatureGate(t *testing.T
|
|||||||
inspectErr: nil,
|
inspectErr: nil,
|
||||||
pullerErr: nil,
|
pullerErr: nil,
|
||||||
expected: []pullerExpects{
|
expected: []pullerExpects{
|
||||||
{[]string{"GetImageRef", "PullImage"}, nil, true, true},
|
{[]string{"GetImageRef", "PullImage", "GetImageSize"}, nil, true, true},
|
||||||
}}
|
}}
|
||||||
|
|
||||||
useSerializedEnv := true
|
useSerializedEnv := true
|
||||||
|
@ -28,6 +28,7 @@ import (
|
|||||||
|
|
||||||
type pullResult struct {
|
type pullResult struct {
|
||||||
imageRef string
|
imageRef string
|
||||||
|
imageSize uint64
|
||||||
err error
|
err error
|
||||||
pullDuration time.Duration
|
pullDuration time.Duration
|
||||||
}
|
}
|
||||||
@ -58,8 +59,14 @@ func (pip *parallelImagePuller) pullImage(ctx context.Context, spec kubecontaine
|
|||||||
}
|
}
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
imageRef, err := pip.imageService.PullImage(ctx, spec, pullSecrets, podSandboxConfig)
|
imageRef, err := pip.imageService.PullImage(ctx, spec, pullSecrets, podSandboxConfig)
|
||||||
|
var size uint64
|
||||||
|
if err == nil && imageRef != "" {
|
||||||
|
// Getting the image size with best effort, ignoring the error.
|
||||||
|
size, _ = pip.imageService.GetImageSize(ctx, spec)
|
||||||
|
}
|
||||||
pullChan <- pullResult{
|
pullChan <- pullResult{
|
||||||
imageRef: imageRef,
|
imageRef: imageRef,
|
||||||
|
imageSize: size,
|
||||||
err: err,
|
err: err,
|
||||||
pullDuration: time.Since(startTime),
|
pullDuration: time.Since(startTime),
|
||||||
}
|
}
|
||||||
@ -102,9 +109,16 @@ func (sip *serialImagePuller) processImagePullRequests() {
|
|||||||
for pullRequest := range sip.pullRequests {
|
for pullRequest := range sip.pullRequests {
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
imageRef, err := sip.imageService.PullImage(pullRequest.ctx, pullRequest.spec, pullRequest.pullSecrets, pullRequest.podSandboxConfig)
|
imageRef, err := sip.imageService.PullImage(pullRequest.ctx, pullRequest.spec, pullRequest.pullSecrets, pullRequest.podSandboxConfig)
|
||||||
|
var size uint64
|
||||||
|
if err == nil && imageRef != "" {
|
||||||
|
// Getting the image size with best effort, ignoring the error.
|
||||||
|
size, _ = sip.imageService.GetImageSize(pullRequest.ctx, pullRequest.spec)
|
||||||
|
}
|
||||||
pullRequest.pullChan <- pullResult{
|
pullRequest.pullChan <- pullResult{
|
||||||
imageRef: imageRef,
|
imageRef: imageRef,
|
||||||
err: err,
|
imageSize: size,
|
||||||
|
err: err,
|
||||||
|
// Note: pullDuration includes credential resolution and getting the image size.
|
||||||
pullDuration: time.Since(startTime),
|
pullDuration: time.Since(startTime),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,6 +96,18 @@ func (m *kubeGenericRuntimeManager) GetImageRef(ctx context.Context, image kubec
|
|||||||
return resp.Image.Id, nil
|
return resp.Image.Id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *kubeGenericRuntimeManager) GetImageSize(ctx context.Context, image kubecontainer.ImageSpec) (uint64, error) {
|
||||||
|
resp, err := m.imageService.ImageStatus(ctx, toRuntimeAPIImageSpec(image), false)
|
||||||
|
if err != nil {
|
||||||
|
klog.ErrorS(err, "Failed to get image status", "image", image.Image)
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if resp.Image == nil {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
return resp.Image.Size_, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ListImages gets all images currently on the machine.
|
// ListImages gets all images currently on the machine.
|
||||||
func (m *kubeGenericRuntimeManager) ListImages(ctx context.Context) ([]kubecontainer.Image, error) {
|
func (m *kubeGenericRuntimeManager) ListImages(ctx context.Context) ([]kubecontainer.Image, error) {
|
||||||
var images []kubecontainer.Image
|
var images []kubecontainer.Image
|
||||||
|
@ -149,6 +149,20 @@ func TestGetImageRef(t *testing.T) {
|
|||||||
assert.Equal(t, image, imageRef)
|
assert.Equal(t, image, imageRef)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestImageSize(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
_, fakeImageService, fakeManager, err := createTestRuntimeManager()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
const imageSize = uint64(64)
|
||||||
|
fakeImageService.SetFakeImageSize(imageSize)
|
||||||
|
image := "busybox"
|
||||||
|
fakeImageService.SetFakeImages([]string{image})
|
||||||
|
actualSize, err := fakeManager.GetImageSize(ctx, kubecontainer.ImageSpec{Image: image})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, imageSize, actualSize)
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetImageRefImageNotAvailableLocally(t *testing.T) {
|
func TestGetImageRefImageNotAvailableLocally(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
_, _, fakeManager, err := createTestRuntimeManager()
|
_, _, fakeManager, err := createTestRuntimeManager()
|
||||||
|
@ -70,6 +70,7 @@ const (
|
|||||||
WorkingPodCountKey = "working_pods"
|
WorkingPodCountKey = "working_pods"
|
||||||
OrphanedRuntimePodTotalKey = "orphaned_runtime_pods_total"
|
OrphanedRuntimePodTotalKey = "orphaned_runtime_pods_total"
|
||||||
RestartedPodTotalKey = "restarted_pods_total"
|
RestartedPodTotalKey = "restarted_pods_total"
|
||||||
|
ImagePullDurationKey = "image_pull_duration_seconds"
|
||||||
|
|
||||||
// Metrics keys of remote runtime operations
|
// Metrics keys of remote runtime operations
|
||||||
RuntimeOperationsKey = "runtime_operations_total"
|
RuntimeOperationsKey = "runtime_operations_total"
|
||||||
@ -126,8 +127,30 @@ const (
|
|||||||
EphemeralContainer = "ephemeral_container"
|
EphemeralContainer = "ephemeral_container"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type imageSizeBucket struct {
|
||||||
|
lowerBoundInBytes uint64
|
||||||
|
label string
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
podStartupDurationBuckets = []float64{0.5, 1, 2, 3, 4, 5, 6, 8, 10, 20, 30, 45, 60, 120, 180, 240, 300, 360, 480, 600, 900, 1200, 1800, 2700, 3600}
|
podStartupDurationBuckets = []float64{0.5, 1, 2, 3, 4, 5, 6, 8, 10, 20, 30, 45, 60, 120, 180, 240, 300, 360, 480, 600, 900, 1200, 1800, 2700, 3600}
|
||||||
|
imagePullDurationBuckets = []float64{1, 5, 10, 20, 30, 60, 120, 180, 240, 300, 360, 480, 600, 900, 1200, 1800, 2700, 3600}
|
||||||
|
// imageSizeBuckets has the labels to be associated with image_pull_duration_seconds metric. For example, if the size of
|
||||||
|
// an image pulled is between 1GB and 5GB, the label will be "1GB-5GB".
|
||||||
|
imageSizeBuckets = []imageSizeBucket{
|
||||||
|
{0, "0-10MB"},
|
||||||
|
{10 * 1024 * 1024, "10MB-100MB"},
|
||||||
|
{100 * 1024 * 1024, "100MB-500MB"},
|
||||||
|
{500 * 1024 * 1024, "500MB-1GB"},
|
||||||
|
{1 * 1024 * 1024 * 1024, "1GB-5GB"},
|
||||||
|
{5 * 1024 * 1024 * 1024, "5GB-10GB"},
|
||||||
|
{10 * 1024 * 1024 * 1024, "10GB-20GB"},
|
||||||
|
{20 * 1024 * 1024 * 1024, "20GB-30GB"},
|
||||||
|
{30 * 1024 * 1024 * 1024, "30GB-40GB"},
|
||||||
|
{40 * 1024 * 1024 * 1024, "40GB-60GB"},
|
||||||
|
{60 * 1024 * 1024 * 1024, "60GB-100GB"},
|
||||||
|
{100 * 1024 * 1024 * 1024, "GT100GB"},
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -822,6 +845,20 @@ var (
|
|||||||
StabilityLevel: metrics.ALPHA,
|
StabilityLevel: metrics.ALPHA,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ImagePullDuration is a Histogram that tracks the duration (in seconds) it takes for an image to be pulled,
|
||||||
|
// including the time spent in the waiting queue of image puller.
|
||||||
|
// The metric is broken down by bucketed image size.
|
||||||
|
ImagePullDuration = metrics.NewHistogramVec(
|
||||||
|
&metrics.HistogramOpts{
|
||||||
|
Subsystem: KubeletSubsystem,
|
||||||
|
Name: ImagePullDurationKey,
|
||||||
|
Help: "Duration in seconds to pull an image.",
|
||||||
|
Buckets: imagePullDurationBuckets,
|
||||||
|
StabilityLevel: metrics.ALPHA,
|
||||||
|
},
|
||||||
|
[]string{"image_size_in_bytes"},
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
var registerMetrics sync.Once
|
var registerMetrics sync.Once
|
||||||
@ -835,6 +872,7 @@ func Register(collectors ...metrics.StableCollector) {
|
|||||||
legacyregistry.MustRegister(PodStartDuration)
|
legacyregistry.MustRegister(PodStartDuration)
|
||||||
legacyregistry.MustRegister(PodStartSLIDuration)
|
legacyregistry.MustRegister(PodStartSLIDuration)
|
||||||
legacyregistry.MustRegister(PodStartTotalDuration)
|
legacyregistry.MustRegister(PodStartTotalDuration)
|
||||||
|
legacyregistry.MustRegister(ImagePullDuration)
|
||||||
legacyregistry.MustRegister(NodeStartupPreKubeletDuration)
|
legacyregistry.MustRegister(NodeStartupPreKubeletDuration)
|
||||||
legacyregistry.MustRegister(NodeStartupPreRegistrationDuration)
|
legacyregistry.MustRegister(NodeStartupPreRegistrationDuration)
|
||||||
legacyregistry.MustRegister(NodeStartupRegistrationDuration)
|
legacyregistry.MustRegister(NodeStartupRegistrationDuration)
|
||||||
@ -923,3 +961,18 @@ func SinceInSeconds(start time.Time) float64 {
|
|||||||
func SetNodeName(name types.NodeName) {
|
func SetNodeName(name types.NodeName) {
|
||||||
NodeName.WithLabelValues(string(name)).Set(1)
|
NodeName.WithLabelValues(string(name)).Set(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetImageSizeBucket(sizeInBytes uint64) string {
|
||||||
|
if sizeInBytes == 0 {
|
||||||
|
return "N/A"
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := len(imageSizeBuckets) - 1; i >= 0; i-- {
|
||||||
|
if sizeInBytes > imageSizeBuckets[i].lowerBoundInBytes {
|
||||||
|
return imageSizeBuckets[i].label
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// return empty string when sizeInBytes is 0 (error getting image size)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
77
pkg/kubelet/metrics/metrics_test.go
Normal file
77
pkg/kubelet/metrics/metrics_test.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2024 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package metrics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/component-base/metrics/testutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
const imagePullDurationKey = "kubelet_" + ImagePullDurationKey
|
||||||
|
|
||||||
|
func TestImagePullDurationMetric(t *testing.T) {
|
||||||
|
t.Run("register image pull duration", func(t *testing.T) {
|
||||||
|
Register()
|
||||||
|
defer clearMetrics()
|
||||||
|
|
||||||
|
// Pairs of image size in bytes and pull duration in seconds
|
||||||
|
dataPoints := [][]float64{
|
||||||
|
// 0 byets, 0 seconds
|
||||||
|
{0, 0},
|
||||||
|
// 5MB, 10 seconds
|
||||||
|
{5 * 1024 * 1024, 10},
|
||||||
|
// 15MB, 20 seconds
|
||||||
|
{15 * 1024 * 1024, 20},
|
||||||
|
// 500 MB, 200 seconds
|
||||||
|
{500 * 1024 * 1024, 200},
|
||||||
|
// 15 GB, 6000 seconds,
|
||||||
|
{15 * 1024 * 1024 * 1024, 6000},
|
||||||
|
// 200 GB, 10000 seconds
|
||||||
|
{200 * 1024 * 1024 * 1024, 10000},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dp := range dataPoints {
|
||||||
|
imageSize := int64(dp[0])
|
||||||
|
duration := dp[1]
|
||||||
|
t.Log(imageSize, duration)
|
||||||
|
t.Log(GetImageSizeBucket(uint64(imageSize)))
|
||||||
|
ImagePullDuration.WithLabelValues(GetImageSizeBucket(uint64(imageSize))).Observe(duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
wants, err := os.Open("testdata/image_pull_duration_metric")
|
||||||
|
defer func() {
|
||||||
|
if err := wants.Close(); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := testutil.GatherAndCompare(GetGather(), wants, imagePullDurationKey); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func clearMetrics() {
|
||||||
|
ImagePullDuration.Reset()
|
||||||
|
}
|
128
pkg/kubelet/metrics/testdata/image_pull_duration_metric
vendored
Normal file
128
pkg/kubelet/metrics/testdata/image_pull_duration_metric
vendored
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
# HELP kubelet_image_pull_duration_seconds [ALPHA] Duration in seconds to pull an image.
|
||||||
|
# TYPE kubelet_image_pull_duration_seconds histogram
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="0-10MB",le="1"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="0-10MB",le="5"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="0-10MB",le="10"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="0-10MB",le="20"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="0-10MB",le="30"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="0-10MB",le="60"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="0-10MB",le="120"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="0-10MB",le="180"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="0-10MB",le="240"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="0-10MB",le="300"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="0-10MB",le="360"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="0-10MB",le="480"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="0-10MB",le="600"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="0-10MB",le="900"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="0-10MB",le="1200"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="0-10MB",le="1800"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="0-10MB",le="2700"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="0-10MB",le="3600"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="0-10MB",le="Inf"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_sum{image_size_in_bytes="0-10MB"} 10
|
||||||
|
kubelet_image_pull_duration_seconds_count{image_size_in_bytes="0-10MB"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="100MB-500MB",le="1"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="100MB-500MB",le="5"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="100MB-500MB",le="10"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="100MB-500MB",le="20"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="100MB-500MB",le="30"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="100MB-500MB",le="60"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="100MB-500MB",le="120"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="100MB-500MB",le="180"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="100MB-500MB",le="240"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="100MB-500MB",le="300"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="100MB-500MB",le="360"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="100MB-500MB",le="480"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="100MB-500MB",le="600"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="100MB-500MB",le="900"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="100MB-500MB",le="1200"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="100MB-500MB",le="1800"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="100MB-500MB",le="2700"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="100MB-500MB",le="3600"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="100MB-500MB",le="Inf"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_sum{image_size_in_bytes="100MB-500MB"} 200
|
||||||
|
kubelet_image_pull_duration_seconds_count{image_size_in_bytes="100MB-500MB"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="10GB-20GB",le="1"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="10GB-20GB",le="5"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="10GB-20GB",le="10"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="10GB-20GB",le="20"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="10GB-20GB",le="30"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="10GB-20GB",le="60"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="10GB-20GB",le="120"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="10GB-20GB",le="180"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="10GB-20GB",le="240"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="10GB-20GB",le="300"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="10GB-20GB",le="360"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="10GB-20GB",le="480"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="10GB-20GB",le="600"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="10GB-20GB",le="900"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="10GB-20GB",le="1200"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="10GB-20GB",le="1800"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="10GB-20GB",le="2700"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="10GB-20GB",le="3600"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="10GB-20GB",le="Inf"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_sum{image_size_in_bytes="10GB-20GB"} 6000
|
||||||
|
kubelet_image_pull_duration_seconds_count{image_size_in_bytes="10GB-20GB"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="10MB-100MB",le="1"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="10MB-100MB",le="5"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="10MB-100MB",le="10"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="10MB-100MB",le="20"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="10MB-100MB",le="30"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="10MB-100MB",le="60"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="10MB-100MB",le="120"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="10MB-100MB",le="180"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="10MB-100MB",le="240"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="10MB-100MB",le="300"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="10MB-100MB",le="360"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="10MB-100MB",le="480"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="10MB-100MB",le="600"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="10MB-100MB",le="900"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="10MB-100MB",le="1200"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="10MB-100MB",le="1800"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="10MB-100MB",le="2700"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="10MB-100MB",le="3600"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="10MB-100MB",le="Inf"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_sum{image_size_in_bytes="10MB-100MB"} 20
|
||||||
|
kubelet_image_pull_duration_seconds_count{image_size_in_bytes="10MB-100MB"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="GT100GB",le="1"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="GT100GB",le="5"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="GT100GB",le="10"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="GT100GB",le="20"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="GT100GB",le="30"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="GT100GB",le="60"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="GT100GB",le="120"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="GT100GB",le="180"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="GT100GB",le="240"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="GT100GB",le="300"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="GT100GB",le="360"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="GT100GB",le="480"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="GT100GB",le="600"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="GT100GB",le="900"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="GT100GB",le="1200"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="GT100GB",le="1800"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="GT100GB",le="2700"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="GT100GB",le="3600"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="GT100GB",le="Inf"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_sum{image_size_in_bytes="GT100GB"} 10000
|
||||||
|
kubelet_image_pull_duration_seconds_count{image_size_in_bytes="GT100GB"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="N/A",le="1"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="N/A",le="5"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="N/A",le="10"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="N/A",le="20"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="N/A",le="30"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="N/A",le="60"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="N/A",le="120"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="N/A",le="180"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="N/A",le="240"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="N/A",le="300"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="N/A",le="360"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="N/A",le="480"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="N/A",le="600"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="N/A",le="900"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="N/A",le="1200"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="N/A",le="1800"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="N/A",le="2700"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="N/A",le="3600"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_bucket{image_size_in_bytes="N/A",le="Inf"} 1
|
||||||
|
kubelet_image_pull_duration_seconds_sum{image_size_in_bytes="N/A"} 0
|
||||||
|
kubelet_image_pull_duration_seconds_count{image_size_in_bytes="N/A"} 1
|
Loading…
Reference in New Issue
Block a user