github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/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  }