summaryrefslogtreecommitdiffhomepage
path: root/pkg/sentry/usage
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/sentry/usage')
-rw-r--r--pkg/sentry/usage/cpu.go46
-rw-r--r--pkg/sentry/usage/io.go90
-rw-r--r--pkg/sentry/usage/memory.go284
-rw-r--r--pkg/sentry/usage/memory_unsafe.go27
-rw-r--r--pkg/sentry/usage/usage.go16
-rwxr-xr-xpkg/sentry/usage/usage_state_autogen.go50
6 files changed, 513 insertions, 0 deletions
diff --git a/pkg/sentry/usage/cpu.go b/pkg/sentry/usage/cpu.go
new file mode 100644
index 000000000..bfc282d69
--- /dev/null
+++ b/pkg/sentry/usage/cpu.go
@@ -0,0 +1,46 @@
+// Copyright 2018 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package usage
+
+import (
+ "time"
+)
+
+// CPUStats contains the subset of struct rusage fields that relate to CPU
+// scheduling.
+//
+// +stateify savable
+type CPUStats struct {
+ // UserTime is the amount of time spent executing application code.
+ UserTime time.Duration
+
+ // SysTime is the amount of time spent executing sentry code.
+ SysTime time.Duration
+
+ // VoluntarySwitches is the number of times control has been voluntarily
+ // ceded due to blocking, etc.
+ VoluntarySwitches uint64
+
+ // InvoluntarySwitches (struct rusage::ru_nivcsw) is unsupported, since
+ // "preemptive" scheduling is managed by the Go runtime, which doesn't
+ // provide this information.
+}
+
+// Accumulate adds s2 to s.
+func (s *CPUStats) Accumulate(s2 CPUStats) {
+ s.UserTime += s2.UserTime
+ s.SysTime += s2.SysTime
+ s.VoluntarySwitches += s2.VoluntarySwitches
+}
diff --git a/pkg/sentry/usage/io.go b/pkg/sentry/usage/io.go
new file mode 100644
index 000000000..dfcd3a49d
--- /dev/null
+++ b/pkg/sentry/usage/io.go
@@ -0,0 +1,90 @@
+// Copyright 2018 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package usage
+
+import (
+ "sync/atomic"
+)
+
+// IO contains I/O-related statistics.
+//
+// +stateify savable
+type IO struct {
+ // CharsRead is the number of bytes read by read syscalls.
+ CharsRead uint64
+
+ // CharsWritten is the number of bytes written by write syscalls.
+ CharsWritten uint64
+
+ // ReadSyscalls is the number of read syscalls.
+ ReadSyscalls uint64
+
+ // WriteSyscalls is the number of write syscalls.
+ WriteSyscalls uint64
+
+ // The following counter is only meaningful when Sentry has internal
+ // pagecache.
+
+ // BytesRead is the number of bytes actually read into pagecache.
+ BytesRead uint64
+
+ // BytesWritten is the number of bytes actually written from pagecache.
+ BytesWritten uint64
+
+ // BytesWriteCancelled is the number of bytes not written out due to
+ // truncation.
+ BytesWriteCancelled uint64
+}
+
+// AccountReadSyscall does the accounting for a read syscall.
+func (i *IO) AccountReadSyscall(bytes int64) {
+ atomic.AddUint64(&i.ReadSyscalls, 1)
+ if bytes > 0 {
+ atomic.AddUint64(&i.CharsRead, uint64(bytes))
+ }
+}
+
+// AccountWriteSyscall does the accounting for a write syscall.
+func (i *IO) AccountWriteSyscall(bytes int64) {
+ atomic.AddUint64(&i.WriteSyscalls, 1)
+ if bytes > 0 {
+ atomic.AddUint64(&i.CharsWritten, uint64(bytes))
+ }
+}
+
+// AccountReadIO does the accounting for a read IO into the file system.
+func (i *IO) AccountReadIO(bytes int64) {
+ if bytes > 0 {
+ atomic.AddUint64(&i.BytesRead, uint64(bytes))
+ }
+}
+
+// AccountWriteIO does the accounting for a write IO into the file system.
+func (i *IO) AccountWriteIO(bytes int64) {
+ if bytes > 0 {
+ atomic.AddUint64(&i.BytesWritten, uint64(bytes))
+ }
+}
+
+// Accumulate adds up io usages.
+func (i *IO) Accumulate(io *IO) {
+ atomic.AddUint64(&i.CharsRead, atomic.LoadUint64(&io.CharsRead))
+ atomic.AddUint64(&i.CharsWritten, atomic.LoadUint64(&io.CharsWritten))
+ atomic.AddUint64(&i.ReadSyscalls, atomic.LoadUint64(&io.ReadSyscalls))
+ atomic.AddUint64(&i.WriteSyscalls, atomic.LoadUint64(&io.WriteSyscalls))
+ atomic.AddUint64(&i.BytesRead, atomic.LoadUint64(&io.BytesRead))
+ atomic.AddUint64(&i.BytesWritten, atomic.LoadUint64(&io.BytesWritten))
+ atomic.AddUint64(&i.BytesWriteCancelled, atomic.LoadUint64(&io.BytesWriteCancelled))
+}
diff --git a/pkg/sentry/usage/memory.go b/pkg/sentry/usage/memory.go
new file mode 100644
index 000000000..c316f1597
--- /dev/null
+++ b/pkg/sentry/usage/memory.go
@@ -0,0 +1,284 @@
+// Copyright 2018 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package usage
+
+import (
+ "fmt"
+ "os"
+ "sync"
+ "sync/atomic"
+ "syscall"
+
+ "gvisor.googlesource.com/gvisor/pkg/bits"
+ "gvisor.googlesource.com/gvisor/pkg/sentry/memutil"
+)
+
+// MemoryKind represents a type of memory used by the application.
+//
+// For efficiency reasons, it is assumed that the Memory implementation is
+// responsible for specific stats (documented below), and those may be reported
+// in aggregate independently. See the platform.Memory interface as well as the
+// control.Usage.Collect method for more information.
+type MemoryKind int
+
+const (
+ // System represents miscellaneous system memory. This may include
+ // memory that is in the process of being reclaimed, system caches,
+ // page tables, swap, etc.
+ //
+ // This memory kind is backed by platform memory.
+ System MemoryKind = iota
+
+ // Anonymous represents anonymous application memory.
+ //
+ // This memory kind is backed by platform memory.
+ Anonymous
+
+ // PageCache represents memory allocated to back sandbox-visible files that
+ // do not have a local fd. The contents of these files are buffered in
+ // memory to support application mmaps.
+ //
+ // This memory kind is backed by platform memory.
+ PageCache
+
+ // Tmpfs represents memory used by the sandbox-visible tmpfs.
+ //
+ // This memory kind is backed by platform memory.
+ Tmpfs
+
+ // Ramdiskfs represents memory used by the ramdiskfs.
+ //
+ // This memory kind is backed by platform memory.
+ Ramdiskfs
+
+ // Mapped represents memory related to files which have a local fd on the
+ // host, and thus can be directly mapped. Typically these are files backed
+ // by gofers with donated-fd support. Note that this value may not track the
+ // exact amount of memory used by mapping on the host, because we don't have
+ // any visibility into the host kernel memory management. In particular,
+ // once we map some part of a host file, the host kernel is free to
+ // abitrarily populate/decommit the pages, which it may do for various
+ // reasons (ex. host memory reclaim, NUMA balancing).
+ //
+ // This memory kind is backed by the host pagecache, via host mmaps.
+ Mapped
+)
+
+// MemoryStats tracks application memory usage in bytes. All fields correspond to the
+// memory category with the same name. This object is thread-safe if accessed
+// through the provided methods. The public fields may be safely accessed
+// directly on a copy of the object obtained from Memory.Copy().
+type MemoryStats struct {
+ System uint64
+ Anonymous uint64
+ PageCache uint64
+ Tmpfs uint64
+ // Lazily updated based on the value in RTMapped.
+ Mapped uint64
+ Ramdiskfs uint64
+}
+
+// RTMemoryStats contains the memory usage values that need to be directly
+// exposed through a shared memory file for real-time access. These are
+// categories not backed by platform memory. For details about how this works,
+// see the memory accounting docs.
+//
+// N.B. Please keep the struct in sync with the API. Notably, changes to this
+// struct requires a version bump and addition of compatibility logic in the
+// control server. As a special-case, adding fields without re-ordering existing
+// ones do not require a version bump because the mapped page we use is
+// initially zeroed. Any added field will be ignored by an older API and will be
+// zero if read by a newer API.
+type RTMemoryStats struct {
+ RTMapped uint64
+}
+
+// MemoryLocked is Memory with access methods.
+type MemoryLocked struct {
+ mu sync.RWMutex
+ // MemoryStats records the memory stats.
+ MemoryStats
+ // RTMemoryStats records the memory stats that need to be exposed through
+ // shared page.
+ *RTMemoryStats
+ // File is the backing file storing the memory stats.
+ File *os.File
+}
+
+// Init initializes global 'MemoryAccounting'.
+func Init() error {
+ const name = "memory-usage"
+ fd, err := memutil.CreateMemFD(name, 0)
+ if err != nil {
+ return fmt.Errorf("error creating usage file: %v", err)
+ }
+ file := os.NewFile(uintptr(fd), name)
+ if err := file.Truncate(int64(RTMemoryStatsSize)); err != nil {
+ return fmt.Errorf("error truncating usage file: %v", err)
+ }
+ // Note: We rely on the returned page being initially zeroed. This will
+ // always be the case for a newly mapped page from /dev/shm. If we obtain
+ // the shared memory through some other means in the future, we may have to
+ // explicitly zero the page.
+ mmap, err := syscall.Mmap(int(file.Fd()), 0, int(RTMemoryStatsSize), syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED)
+ if err != nil {
+ return fmt.Errorf("error mapping usage file: %v", err)
+ }
+
+ MemoryAccounting = &MemoryLocked{
+ File: file,
+ RTMemoryStats: RTMemoryStatsPointer(mmap),
+ }
+ return nil
+}
+
+// MemoryAccounting is the global memory stats.
+//
+// There is no need to save or restore the global memory accounting object,
+// because individual frame kinds are saved and charged only when they become
+// resident.
+var MemoryAccounting *MemoryLocked
+
+func (m *MemoryLocked) incLocked(val uint64, kind MemoryKind) {
+ switch kind {
+ case System:
+ atomic.AddUint64(&m.System, val)
+ case Anonymous:
+ atomic.AddUint64(&m.Anonymous, val)
+ case PageCache:
+ atomic.AddUint64(&m.PageCache, val)
+ case Mapped:
+ atomic.AddUint64(&m.RTMapped, val)
+ case Tmpfs:
+ atomic.AddUint64(&m.Tmpfs, val)
+ case Ramdiskfs:
+ atomic.AddUint64(&m.Ramdiskfs, val)
+ default:
+ panic(fmt.Sprintf("invalid memory kind: %v", kind))
+ }
+}
+
+// Inc adds an additional usage of 'val' bytes to memory category 'kind'.
+//
+// This method is thread-safe.
+func (m *MemoryLocked) Inc(val uint64, kind MemoryKind) {
+ m.mu.RLock()
+ m.incLocked(val, kind)
+ m.mu.RUnlock()
+}
+
+func (m *MemoryLocked) decLocked(val uint64, kind MemoryKind) {
+ switch kind {
+ case System:
+ atomic.AddUint64(&m.System, ^(val - 1))
+ case Anonymous:
+ atomic.AddUint64(&m.Anonymous, ^(val - 1))
+ case PageCache:
+ atomic.AddUint64(&m.PageCache, ^(val - 1))
+ case Mapped:
+ atomic.AddUint64(&m.RTMapped, ^(val - 1))
+ case Tmpfs:
+ atomic.AddUint64(&m.Tmpfs, ^(val - 1))
+ case Ramdiskfs:
+ atomic.AddUint64(&m.Ramdiskfs, ^(val - 1))
+ default:
+ panic(fmt.Sprintf("invalid memory kind: %v", kind))
+ }
+}
+
+// Dec remove a usage of 'val' bytes from memory category 'kind'.
+//
+// This method is thread-safe.
+func (m *MemoryLocked) Dec(val uint64, kind MemoryKind) {
+ m.mu.RLock()
+ m.decLocked(val, kind)
+ m.mu.RUnlock()
+}
+
+// Move moves a usage of 'val' bytes from 'from' to 'to'.
+//
+// This method is thread-safe.
+func (m *MemoryLocked) Move(val uint64, to MemoryKind, from MemoryKind) {
+ m.mu.RLock()
+ // Just call decLocked and incLocked directly. We held the RLock to
+ // protect against concurrent callers to Total().
+ m.decLocked(val, from)
+ m.incLocked(val, to)
+ m.mu.RUnlock()
+}
+
+// totalLocked returns a total usage.
+//
+// Precondition: must be called when locked.
+func (m *MemoryLocked) totalLocked() (total uint64) {
+ total += atomic.LoadUint64(&m.System)
+ total += atomic.LoadUint64(&m.Anonymous)
+ total += atomic.LoadUint64(&m.PageCache)
+ total += atomic.LoadUint64(&m.RTMapped)
+ total += atomic.LoadUint64(&m.Tmpfs)
+ total += atomic.LoadUint64(&m.Ramdiskfs)
+ return
+}
+
+// Total returns a total memory usage.
+//
+// This method is thread-safe.
+func (m *MemoryLocked) Total() uint64 {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ return m.totalLocked()
+}
+
+// Copy returns a copy of the structure with a total.
+//
+// This method is thread-safe.
+func (m *MemoryLocked) Copy() (MemoryStats, uint64) {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ ms := m.MemoryStats
+ ms.Mapped = m.RTMapped
+ return ms, m.totalLocked()
+}
+
+// MinimumTotalMemoryBytes is the minimum reported total system memory.
+var MinimumTotalMemoryBytes uint64 = 2 << 30 // 2 GB
+
+// TotalMemory returns the "total usable memory" available.
+//
+// This number doesn't really have a true value so it's based on the following
+// inputs and further bounded to be above some minimum guaranteed value (2GB),
+// additionally ensuring that total memory reported is always less than used.
+//
+// memSize should be the platform.Memory size reported by platform.Memory.TotalSize()
+// used is the total memory reported by MemoryLocked.Total()
+func TotalMemory(memSize, used uint64) uint64 {
+ if memSize < MinimumTotalMemoryBytes {
+ memSize = MinimumTotalMemoryBytes
+ }
+ if memSize < used {
+ memSize = used
+ // Bump totalSize to the next largest power of 2, if one exists, so
+ // that MemFree isn't 0.
+ if msb := bits.MostSignificantOne64(memSize); msb < 63 {
+ memSize = uint64(1) << (uint(msb) + 1)
+ }
+ }
+ return memSize
+}
+
+// IncrementalMappedAccounting controls whether host mapped memory is accounted
+// incrementally during map translation. This may be modified during early
+// initialization, and is read-only afterward.
+var IncrementalMappedAccounting = false
diff --git a/pkg/sentry/usage/memory_unsafe.go b/pkg/sentry/usage/memory_unsafe.go
new file mode 100644
index 000000000..9e0014ca0
--- /dev/null
+++ b/pkg/sentry/usage/memory_unsafe.go
@@ -0,0 +1,27 @@
+// Copyright 2018 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package usage
+
+import (
+ "unsafe"
+)
+
+// RTMemoryStatsSize is the size of the RTMemoryStats struct.
+var RTMemoryStatsSize = unsafe.Sizeof(RTMemoryStats{})
+
+// RTMemoryStatsPointer casts the address of the byte slice into a RTMemoryStats pointer.
+func RTMemoryStatsPointer(b []byte) *RTMemoryStats {
+ return (*RTMemoryStats)(unsafe.Pointer(&b[0]))
+}
diff --git a/pkg/sentry/usage/usage.go b/pkg/sentry/usage/usage.go
new file mode 100644
index 000000000..e3d33a965
--- /dev/null
+++ b/pkg/sentry/usage/usage.go
@@ -0,0 +1,16 @@
+// Copyright 2018 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package usage provides representations of resource usage.
+package usage
diff --git a/pkg/sentry/usage/usage_state_autogen.go b/pkg/sentry/usage/usage_state_autogen.go
new file mode 100755
index 000000000..38411db2e
--- /dev/null
+++ b/pkg/sentry/usage/usage_state_autogen.go
@@ -0,0 +1,50 @@
+// automatically generated by stateify.
+
+package usage
+
+import (
+ "gvisor.googlesource.com/gvisor/pkg/state"
+)
+
+func (x *CPUStats) beforeSave() {}
+func (x *CPUStats) save(m state.Map) {
+ x.beforeSave()
+ m.Save("UserTime", &x.UserTime)
+ m.Save("SysTime", &x.SysTime)
+ m.Save("VoluntarySwitches", &x.VoluntarySwitches)
+}
+
+func (x *CPUStats) afterLoad() {}
+func (x *CPUStats) load(m state.Map) {
+ m.Load("UserTime", &x.UserTime)
+ m.Load("SysTime", &x.SysTime)
+ m.Load("VoluntarySwitches", &x.VoluntarySwitches)
+}
+
+func (x *IO) beforeSave() {}
+func (x *IO) save(m state.Map) {
+ x.beforeSave()
+ m.Save("CharsRead", &x.CharsRead)
+ m.Save("CharsWritten", &x.CharsWritten)
+ m.Save("ReadSyscalls", &x.ReadSyscalls)
+ m.Save("WriteSyscalls", &x.WriteSyscalls)
+ m.Save("BytesRead", &x.BytesRead)
+ m.Save("BytesWritten", &x.BytesWritten)
+ m.Save("BytesWriteCancelled", &x.BytesWriteCancelled)
+}
+
+func (x *IO) afterLoad() {}
+func (x *IO) load(m state.Map) {
+ m.Load("CharsRead", &x.CharsRead)
+ m.Load("CharsWritten", &x.CharsWritten)
+ m.Load("ReadSyscalls", &x.ReadSyscalls)
+ m.Load("WriteSyscalls", &x.WriteSyscalls)
+ m.Load("BytesRead", &x.BytesRead)
+ m.Load("BytesWritten", &x.BytesWritten)
+ m.Load("BytesWriteCancelled", &x.BytesWriteCancelled)
+}
+
+func init() {
+ state.Register("usage.CPUStats", (*CPUStats)(nil), state.Fns{Save: (*CPUStats).save, Load: (*CPUStats).load})
+ state.Register("usage.IO", (*IO)(nil), state.Fns{Save: (*IO).save, Load: (*IO).load})
+}