github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/metrics/cgroups/v1/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 v1
    20  
    21  import (
    22  	"context"
    23  	"fmt"
    24  	"sync"
    25  
    26  	"github.com/containerd/cgroups"
    27  	"github.com/containerd/containerd/log"
    28  	v1 "github.com/containerd/containerd/metrics/types/v1"
    29  	"github.com/containerd/containerd/namespaces"
    30  	"github.com/containerd/typeurl"
    31  	metrics "github.com/docker/go-metrics"
    32  	"github.com/gogo/protobuf/types"
    33  	"github.com/prometheus/client_golang/prometheus"
    34  )
    35  
    36  // Statable type that returns cgroup metrics
    37  type Statable interface {
    38  	ID() string
    39  	Namespace() string
    40  	Stats(context.Context) (*types.Any, error)
    41  }
    42  
    43  // Trigger will be called when an event happens and provides the cgroup
    44  // where the event originated from
    45  type Trigger func(string, string, cgroups.Cgroup)
    46  
    47  // NewCollector registers the collector with the provided namespace and returns it so
    48  // that cgroups can be added for collection
    49  func NewCollector(ns *metrics.Namespace) *Collector {
    50  	if ns == nil {
    51  		return &Collector{}
    52  	}
    53  	// add machine cpus and memory info
    54  	c := &Collector{
    55  		ns:    ns,
    56  		tasks: make(map[string]Statable),
    57  	}
    58  	c.metrics = append(c.metrics, pidMetrics...)
    59  	c.metrics = append(c.metrics, cpuMetrics...)
    60  	c.metrics = append(c.metrics, memoryMetrics...)
    61  	c.metrics = append(c.metrics, hugetlbMetrics...)
    62  	c.metrics = append(c.metrics, blkioMetrics...)
    63  	c.storedMetrics = make(chan prometheus.Metric, 100*len(c.metrics))
    64  	ns.Add(c)
    65  	return c
    66  }
    67  
    68  func taskID(id, namespace string) string {
    69  	return fmt.Sprintf("%s-%s", id, namespace)
    70  }
    71  
    72  // Collector provides the ability to collect container stats and export
    73  // them in the prometheus format
    74  type Collector struct {
    75  	mu sync.RWMutex
    76  
    77  	tasks         map[string]Statable
    78  	ns            *metrics.Namespace
    79  	metrics       []*metric
    80  	storedMetrics chan prometheus.Metric
    81  }
    82  
    83  // Describe prometheus metrics
    84  func (c *Collector) Describe(ch chan<- *prometheus.Desc) {
    85  	for _, m := range c.metrics {
    86  		ch <- m.desc(c.ns)
    87  	}
    88  }
    89  
    90  // Collect prometheus metrics
    91  func (c *Collector) Collect(ch chan<- prometheus.Metric) {
    92  	c.mu.RLock()
    93  	wg := &sync.WaitGroup{}
    94  	for _, t := range c.tasks {
    95  		wg.Add(1)
    96  		go c.collect(t, ch, true, wg)
    97  	}
    98  storedLoop:
    99  	for {
   100  		// read stored metrics until the channel is flushed
   101  		select {
   102  		case m := <-c.storedMetrics:
   103  			ch <- m
   104  		default:
   105  			break storedLoop
   106  		}
   107  	}
   108  	c.mu.RUnlock()
   109  	wg.Wait()
   110  }
   111  
   112  func (c *Collector) collect(t Statable, ch chan<- prometheus.Metric, block bool, wg *sync.WaitGroup) {
   113  	if wg != nil {
   114  		defer wg.Done()
   115  	}
   116  	ctx := namespaces.WithNamespace(context.Background(), t.Namespace())
   117  	stats, err := t.Stats(ctx)
   118  	if err != nil {
   119  		log.L.WithError(err).Errorf("stat task %s", t.ID())
   120  		return
   121  	}
   122  	data, err := typeurl.UnmarshalAny(stats)
   123  	if err != nil {
   124  		log.L.WithError(err).Errorf("unmarshal stats for %s", t.ID())
   125  		return
   126  	}
   127  	s, ok := data.(*v1.Metrics)
   128  	if !ok {
   129  		log.L.WithError(err).Errorf("invalid metric type for %s", t.ID())
   130  		return
   131  	}
   132  	for _, m := range c.metrics {
   133  		m.collect(t.ID(), t.Namespace(), s, c.ns, ch, block)
   134  	}
   135  }
   136  
   137  // Add adds the provided cgroup and id so that metrics are collected and exported
   138  func (c *Collector) Add(t Statable) error {
   139  	if c.ns == nil {
   140  		return nil
   141  	}
   142  	c.mu.Lock()
   143  	defer c.mu.Unlock()
   144  	id := taskID(t.ID(), t.Namespace())
   145  	if _, ok := c.tasks[id]; ok {
   146  		return nil // requests to collect metrics should be idempotent
   147  	}
   148  	c.tasks[id] = t
   149  	return nil
   150  }
   151  
   152  // Remove removes the provided cgroup by id from the collector
   153  func (c *Collector) Remove(t Statable) {
   154  	if c.ns == nil {
   155  		return
   156  	}
   157  	c.mu.Lock()
   158  	delete(c.tasks, taskID(t.ID(), t.Namespace()))
   159  	c.mu.Unlock()
   160  }
   161  
   162  // RemoveAll statable items from the collector
   163  func (c *Collector) RemoveAll() {
   164  	if c.ns == nil {
   165  		return
   166  	}
   167  	c.mu.Lock()
   168  	c.tasks = make(map[string]Statable)
   169  	c.mu.Unlock()
   170  }