volume_stat_calculator.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. /*
  2. Copyright 2016 The Kubernetes Authors.
  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. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package stats
  14. import (
  15. "sync"
  16. "sync/atomic"
  17. "time"
  18. "k8s.io/api/core/v1"
  19. "k8s.io/apimachinery/pkg/util/wait"
  20. stats "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
  21. "k8s.io/kubernetes/pkg/kubelet/util/format"
  22. "k8s.io/kubernetes/pkg/volume"
  23. "k8s.io/klog"
  24. )
  25. // volumeStatCalculator calculates volume metrics for a given pod periodically in the background and caches the result
  26. type volumeStatCalculator struct {
  27. statsProvider Provider
  28. jitterPeriod time.Duration
  29. pod *v1.Pod
  30. stopChannel chan struct{}
  31. startO sync.Once
  32. stopO sync.Once
  33. latest atomic.Value
  34. }
  35. // PodVolumeStats encapsulates the VolumeStats for a pod.
  36. // It consists of two lists, for local ephemeral volumes, and for persistent volumes respectively.
  37. type PodVolumeStats struct {
  38. EphemeralVolumes []stats.VolumeStats
  39. PersistentVolumes []stats.VolumeStats
  40. }
  41. // newVolumeStatCalculator creates a new VolumeStatCalculator
  42. func newVolumeStatCalculator(statsProvider Provider, jitterPeriod time.Duration, pod *v1.Pod) *volumeStatCalculator {
  43. return &volumeStatCalculator{
  44. statsProvider: statsProvider,
  45. jitterPeriod: jitterPeriod,
  46. pod: pod,
  47. stopChannel: make(chan struct{}),
  48. }
  49. }
  50. // StartOnce starts pod volume calc that will occur periodically in the background until s.StopOnce is called
  51. func (s *volumeStatCalculator) StartOnce() *volumeStatCalculator {
  52. s.startO.Do(func() {
  53. go wait.JitterUntil(func() {
  54. s.calcAndStoreStats()
  55. }, s.jitterPeriod, 1.0, true, s.stopChannel)
  56. })
  57. return s
  58. }
  59. // StopOnce stops background pod volume calculation. Will not stop a currently executing calculations until
  60. // they complete their current iteration.
  61. func (s *volumeStatCalculator) StopOnce() *volumeStatCalculator {
  62. s.stopO.Do(func() {
  63. close(s.stopChannel)
  64. })
  65. return s
  66. }
  67. // getLatest returns the most recent PodVolumeStats from the cache
  68. func (s *volumeStatCalculator) GetLatest() (PodVolumeStats, bool) {
  69. result := s.latest.Load()
  70. if result == nil {
  71. return PodVolumeStats{}, false
  72. }
  73. return result.(PodVolumeStats), true
  74. }
  75. // calcAndStoreStats calculates PodVolumeStats for a given pod and writes the result to the s.latest cache.
  76. // If the pod references PVCs, the prometheus metrics for those are updated with the result.
  77. func (s *volumeStatCalculator) calcAndStoreStats() {
  78. // Find all Volumes for the Pod
  79. volumes, found := s.statsProvider.ListVolumesForPod(s.pod.UID)
  80. if !found {
  81. return
  82. }
  83. // Get volume specs for the pod - key'd by volume name
  84. volumesSpec := make(map[string]v1.Volume)
  85. for _, v := range s.pod.Spec.Volumes {
  86. volumesSpec[v.Name] = v
  87. }
  88. // Call GetMetrics on each Volume and copy the result to a new VolumeStats.FsStats
  89. var ephemeralStats []stats.VolumeStats
  90. var persistentStats []stats.VolumeStats
  91. for name, v := range volumes {
  92. metric, err := v.GetMetrics()
  93. if err != nil {
  94. // Expected for Volumes that don't support Metrics
  95. if !volume.IsNotSupported(err) {
  96. klog.V(4).Infof("Failed to calculate volume metrics for pod %s volume %s: %+v", format.Pod(s.pod), name, err)
  97. }
  98. continue
  99. }
  100. // Lookup the volume spec and add a 'PVCReference' for volumes that reference a PVC
  101. volSpec := volumesSpec[name]
  102. var pvcRef *stats.PVCReference
  103. if pvcSource := volSpec.PersistentVolumeClaim; pvcSource != nil {
  104. pvcRef = &stats.PVCReference{
  105. Name: pvcSource.ClaimName,
  106. Namespace: s.pod.GetNamespace(),
  107. }
  108. }
  109. volumeStats := s.parsePodVolumeStats(name, pvcRef, metric, volSpec)
  110. if isVolumeEphemeral(volSpec) {
  111. ephemeralStats = append(ephemeralStats, volumeStats)
  112. } else {
  113. persistentStats = append(persistentStats, volumeStats)
  114. }
  115. }
  116. // Store the new stats
  117. s.latest.Store(PodVolumeStats{EphemeralVolumes: ephemeralStats,
  118. PersistentVolumes: persistentStats})
  119. }
  120. // parsePodVolumeStats converts (internal) volume.Metrics to (external) stats.VolumeStats structures
  121. func (s *volumeStatCalculator) parsePodVolumeStats(podName string, pvcRef *stats.PVCReference, metric *volume.Metrics, volSpec v1.Volume) stats.VolumeStats {
  122. var available, capacity, used, inodes, inodesFree, inodesUsed uint64
  123. if metric.Available != nil {
  124. available = uint64(metric.Available.Value())
  125. }
  126. if metric.Capacity != nil {
  127. capacity = uint64(metric.Capacity.Value())
  128. }
  129. if metric.Used != nil {
  130. used = uint64(metric.Used.Value())
  131. }
  132. if metric.Inodes != nil {
  133. inodes = uint64(metric.Inodes.Value())
  134. }
  135. if metric.InodesFree != nil {
  136. inodesFree = uint64(metric.InodesFree.Value())
  137. }
  138. if metric.InodesUsed != nil {
  139. inodesUsed = uint64(metric.InodesUsed.Value())
  140. }
  141. return stats.VolumeStats{
  142. Name: podName,
  143. PVCRef: pvcRef,
  144. FsStats: stats.FsStats{Time: metric.Time, AvailableBytes: &available, CapacityBytes: &capacity,
  145. UsedBytes: &used, Inodes: &inodes, InodesFree: &inodesFree, InodesUsed: &inodesUsed},
  146. }
  147. }
  148. func isVolumeEphemeral(volume v1.Volume) bool {
  149. if (volume.EmptyDir != nil && volume.EmptyDir.Medium == v1.StorageMediumDefault) ||
  150. volume.ConfigMap != nil || volume.GitRepo != nil {
  151. return true
  152. }
  153. return false
  154. }