github.com/containerd/Containerd@v1.4.13/metrics/cgroups/v2/metrics.go (about) 1 // +build linux 2 3 /* 4 Copyright The containerd Authors. 5 6 Licensed under the Apache License, Version 2.0 (the "License"); 7 you may not use this file except in compliance with the License. 8 You may obtain a copy of the License at 9 10 http://www.apache.org/licenses/LICENSE-2.0 11 12 Unless required by applicable law or agreed to in writing, software 13 distributed under the License is distributed on an "AS IS" BASIS, 14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 See the License for the specific language governing permissions and 16 limitations under the License. 17 */ 18 19 package v2 20 21 import ( 22 "context" 23 "fmt" 24 "sync" 25 26 "github.com/containerd/containerd/log" 27 v2 "github.com/containerd/containerd/metrics/types/v2" 28 "github.com/containerd/containerd/namespaces" 29 "github.com/containerd/typeurl" 30 metrics "github.com/docker/go-metrics" 31 "github.com/gogo/protobuf/types" 32 "github.com/prometheus/client_golang/prometheus" 33 ) 34 35 // Statable type that returns cgroup metrics 36 type Statable interface { 37 ID() string 38 Namespace() string 39 Stats(context.Context) (*types.Any, error) 40 } 41 42 // NewCollector registers the collector with the provided namespace and returns it so 43 // that cgroups can be added for collection 44 func NewCollector(ns *metrics.Namespace) *Collector { 45 if ns == nil { 46 return &Collector{} 47 } 48 c := &Collector{ 49 ns: ns, 50 tasks: make(map[string]Statable), 51 } 52 c.metrics = append(c.metrics, pidMetrics...) 53 c.metrics = append(c.metrics, cpuMetrics...) 54 c.metrics = append(c.metrics, memoryMetrics...) 55 c.metrics = append(c.metrics, ioMetrics...) 56 c.storedMetrics = make(chan prometheus.Metric, 100*len(c.metrics)) 57 ns.Add(c) 58 return c 59 } 60 61 func taskID(id, namespace string) string { 62 return fmt.Sprintf("%s-%s", id, namespace) 63 } 64 65 // Collector provides the ability to collect container stats and export 66 // them in the prometheus format 67 type Collector struct { 68 mu sync.RWMutex 69 70 tasks map[string]Statable 71 ns *metrics.Namespace 72 metrics []*metric 73 storedMetrics chan prometheus.Metric 74 } 75 76 // Describe prometheus metrics 77 func (c *Collector) Describe(ch chan<- *prometheus.Desc) { 78 for _, m := range c.metrics { 79 ch <- m.desc(c.ns) 80 } 81 } 82 83 // Collect prometheus metrics 84 func (c *Collector) Collect(ch chan<- prometheus.Metric) { 85 c.mu.RLock() 86 wg := &sync.WaitGroup{} 87 for _, t := range c.tasks { 88 wg.Add(1) 89 go c.collect(t, ch, true, wg) 90 } 91 storedLoop: 92 for { 93 // read stored metrics until the channel is flushed 94 select { 95 case m := <-c.storedMetrics: 96 ch <- m 97 default: 98 break storedLoop 99 } 100 } 101 c.mu.RUnlock() 102 wg.Wait() 103 } 104 105 func (c *Collector) collect(t Statable, ch chan<- prometheus.Metric, block bool, wg *sync.WaitGroup) { 106 if wg != nil { 107 defer wg.Done() 108 } 109 ctx := namespaces.WithNamespace(context.Background(), t.Namespace()) 110 stats, err := t.Stats(ctx) 111 if err != nil { 112 log.L.WithError(err).Errorf("stat task %s", t.ID()) 113 return 114 } 115 data, err := typeurl.UnmarshalAny(stats) 116 if err != nil { 117 log.L.WithError(err).Errorf("unmarshal stats for %s", t.ID()) 118 return 119 } 120 s, ok := data.(*v2.Metrics) 121 if !ok { 122 log.L.WithError(err).Errorf("invalid metric type for %s", t.ID()) 123 return 124 } 125 for _, m := range c.metrics { 126 m.collect(t.ID(), t.Namespace(), s, c.ns, ch, block) 127 } 128 } 129 130 // Add adds the provided cgroup and id so that metrics are collected and exported 131 func (c *Collector) Add(t Statable) error { 132 if c.ns == nil { 133 return nil 134 } 135 c.mu.Lock() 136 defer c.mu.Unlock() 137 id := taskID(t.ID(), t.Namespace()) 138 if _, ok := c.tasks[id]; ok { 139 return nil // requests to collect metrics should be idempotent 140 } 141 c.tasks[id] = t 142 return nil 143 } 144 145 // Remove removes the provided cgroup by id from the collector 146 func (c *Collector) Remove(t Statable) { 147 if c.ns == nil { 148 return 149 } 150 c.mu.Lock() 151 defer c.mu.Unlock() 152 delete(c.tasks, taskID(t.ID(), t.Namespace())) 153 } 154 155 // RemoveAll statable items from the collector 156 func (c *Collector) RemoveAll() { 157 if c.ns == nil { 158 return 159 } 160 c.mu.Lock() 161 c.tasks = make(map[string]Statable) 162 c.mu.Unlock() 163 }