From d02b74a5dcfed4bfc8f2f8e545bca4d2afabb296 Mon Sep 17 00:00:00 2001 From: Googler Date: Fri, 27 Apr 2018 10:37:02 -0700 Subject: Check in gVisor. PiperOrigin-RevId: 194583126 Change-Id: Ica1d8821a90f74e7e745962d71801c598c652463 --- pkg/sentry/usage/BUILD | 38 +++++ pkg/sentry/usage/cpu.go | 44 ++++++ pkg/sentry/usage/io.go | 88 ++++++++++++ pkg/sentry/usage/memory.go | 282 ++++++++++++++++++++++++++++++++++++++ pkg/sentry/usage/memory_unsafe.go | 27 ++++ pkg/sentry/usage/usage.go | 16 +++ 6 files changed, 495 insertions(+) create mode 100644 pkg/sentry/usage/BUILD create mode 100644 pkg/sentry/usage/cpu.go create mode 100644 pkg/sentry/usage/io.go create mode 100644 pkg/sentry/usage/memory.go create mode 100644 pkg/sentry/usage/memory_unsafe.go create mode 100644 pkg/sentry/usage/usage.go (limited to 'pkg/sentry/usage') diff --git a/pkg/sentry/usage/BUILD b/pkg/sentry/usage/BUILD new file mode 100644 index 000000000..a0fe0aa07 --- /dev/null +++ b/pkg/sentry/usage/BUILD @@ -0,0 +1,38 @@ +package(licenses = ["notice"]) # Apache 2.0 + +load("@io_bazel_rules_go//go:def.bzl", "go_library") +load("//tools/go_stateify:defs.bzl", "go_stateify") + +go_stateify( + name = "usage_state", + srcs = [ + "cpu.go", + "io.go", + "memory.go", + ], + out = "usage_state.go", + package = "usage", +) + +go_library( + name = "usage", + srcs = [ + "cpu.go", + "io.go", + "memory.go", + "memory_unsafe.go", + "usage.go", + "usage_state.go", + ], + importpath = "gvisor.googlesource.com/gvisor/pkg/sentry/usage", + visibility = [ + "//pkg/sentry:internal", + ], + deps = [ + "//pkg/bits", + "//pkg/log", + "//pkg/sentry/memutil", + "//pkg/state", + "@org_golang_x_sys//unix:go_default_library", + ], +) diff --git a/pkg/sentry/usage/cpu.go b/pkg/sentry/usage/cpu.go new file mode 100644 index 000000000..1c2cc90e1 --- /dev/null +++ b/pkg/sentry/usage/cpu.go @@ -0,0 +1,44 @@ +// Copyright 2018 Google Inc. +// +// 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. +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..a05053c32 --- /dev/null +++ b/pkg/sentry/usage/io.go @@ -0,0 +1,88 @@ +// Copyright 2018 Google Inc. +// +// 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. +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..5d1b3a595 --- /dev/null +++ b/pkg/sentry/usage/memory.go @@ -0,0 +1,282 @@ +// Copyright 2018 Google Inc. +// +// 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. Noteably, 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 +} + +func newMemoryLocked() MemoryLocked { + name := "memory-usage" + fd, err := memutil.CreateMemFD(name, 0) + if err != nil { + panic("error creating usage file: " + err.Error()) + } + file := os.NewFile(uintptr(fd), name) + if err := file.Truncate(int64(RTMemoryStatsSize)); err != nil { + panic("error truncating usage file: " + err.Error()) + } + // 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 { + panic("error mapping usage file: " + err.Error()) + } + + return MemoryLocked{ + File: file, + RTMemoryStats: RTMemoryStatsPointer(mmap), + } +} + +// 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 = newMemoryLocked() + +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..f990a7750 --- /dev/null +++ b/pkg/sentry/usage/memory_unsafe.go @@ -0,0 +1,27 @@ +// Copyright 2018 Google Inc. +// +// 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..3b3118659 --- /dev/null +++ b/pkg/sentry/usage/usage.go @@ -0,0 +1,16 @@ +// Copyright 2018 Google Inc. +// +// 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 -- cgit v1.2.3