1/* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15*/ 16 17package server 18 19import ( 20 "context" 21 "time" 22 23 "github.com/containerd/containerd/errdefs" 24 snapshot "github.com/containerd/containerd/snapshots" 25 "github.com/pkg/errors" 26 "github.com/sirupsen/logrus" 27 28 snapshotstore "github.com/containerd/containerd/pkg/cri/store/snapshot" 29 ctrdutil "github.com/containerd/containerd/pkg/cri/util" 30) 31 32// snapshotsSyncer syncs snapshot stats periodically. imagefs info and container stats 33// should both use cached result here. 34// TODO(random-liu): Benchmark with high workload. We may need a statsSyncer instead if 35// benchmark result shows that container cpu/memory stats also need to be cached. 36type snapshotsSyncer struct { 37 store *snapshotstore.Store 38 snapshotter snapshot.Snapshotter 39 syncPeriod time.Duration 40} 41 42// newSnapshotsSyncer creates a snapshot syncer. 43func newSnapshotsSyncer(store *snapshotstore.Store, snapshotter snapshot.Snapshotter, 44 period time.Duration) *snapshotsSyncer { 45 return &snapshotsSyncer{ 46 store: store, 47 snapshotter: snapshotter, 48 syncPeriod: period, 49 } 50} 51 52// start starts the snapshots syncer. No stop function is needed because 53// the syncer doesn't update any persistent states, it's fine to let it 54// exit with the process. 55func (s *snapshotsSyncer) start() { 56 tick := time.NewTicker(s.syncPeriod) 57 go func() { 58 defer tick.Stop() 59 // TODO(random-liu): This is expensive. We should do benchmark to 60 // check the resource usage and optimize this. 61 for { 62 if err := s.sync(); err != nil { 63 logrus.WithError(err).Error("Failed to sync snapshot stats") 64 } 65 <-tick.C 66 } 67 }() 68} 69 70// sync updates all snapshots stats. 71func (s *snapshotsSyncer) sync() error { 72 ctx := ctrdutil.NamespacedContext() 73 start := time.Now().UnixNano() 74 var snapshots []snapshot.Info 75 // Do not call `Usage` directly in collect function, because 76 // `Usage` takes time, we don't want `Walk` to hold read lock 77 // of snapshot metadata store for too long time. 78 // TODO(random-liu): Set timeout for the following 2 contexts. 79 if err := s.snapshotter.Walk(ctx, func(ctx context.Context, info snapshot.Info) error { 80 snapshots = append(snapshots, info) 81 return nil 82 }); err != nil { 83 return errors.Wrap(err, "walk all snapshots failed") 84 } 85 for _, info := range snapshots { 86 sn, err := s.store.Get(info.Name) 87 if err == nil { 88 // Only update timestamp for non-active snapshot. 89 if sn.Kind == info.Kind && sn.Kind != snapshot.KindActive { 90 sn.Timestamp = time.Now().UnixNano() 91 s.store.Add(sn) 92 continue 93 } 94 } 95 // Get newest stats if the snapshot is new or active. 96 sn = snapshotstore.Snapshot{ 97 Key: info.Name, 98 Kind: info.Kind, 99 Timestamp: time.Now().UnixNano(), 100 } 101 usage, err := s.snapshotter.Usage(ctx, info.Name) 102 if err != nil { 103 if !errdefs.IsNotFound(err) { 104 logrus.WithError(err).Errorf("Failed to get usage for snapshot %q", info.Name) 105 } 106 continue 107 } 108 sn.Size = uint64(usage.Size) 109 sn.Inodes = uint64(usage.Inodes) 110 s.store.Add(sn) 111 } 112 for _, sn := range s.store.List() { 113 if sn.Timestamp >= start { 114 continue 115 } 116 // Delete the snapshot stats if it's not updated this time. 117 s.store.Delete(sn.Key) 118 } 119 return nil 120} 121