summaryrefslogtreecommitdiffhomepage
path: root/pkg/sentry/control/usage.go
blob: cc78d3f452dee474389a2642aaf9bf5678c40bf2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
// Copyright 2021 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 control

import (
	"fmt"
	"os"
	"runtime"

	"golang.org/x/sys/unix"
	"gvisor.dev/gvisor/pkg/sentry/kernel"
	"gvisor.dev/gvisor/pkg/sentry/usage"
	"gvisor.dev/gvisor/pkg/urpc"
)

// Usage includes usage-related RPC stubs.
type Usage struct {
	Kernel *kernel.Kernel
}

// MemoryUsageOpts contains usage options.
type MemoryUsageOpts struct {
	// Full indicates that a full accounting should be done. If Full is not
	// specified, then a partial accounting will be done, and Unknown will
	// contain a majority of memory. See Collect for more information.
	Full bool `json:"Full"`
}

// MemoryUsage is a memory usage structure.
type MemoryUsage struct {
	Unknown   uint64 `json:"Unknown"`
	System    uint64 `json:"System"`
	Anonymous uint64 `json:"Anonymous"`
	PageCache uint64 `json:"PageCache"`
	Mapped    uint64 `json:"Mapped"`
	Tmpfs     uint64 `json:"Tmpfs"`
	Ramdiskfs uint64 `json:"Ramdiskfs"`
	Total     uint64 `json:"Total"`
}

// MemoryUsageFileOpts contains usage file options.
type MemoryUsageFileOpts struct {
	// Version is used to ensure both sides agree on the format of the
	// shared memory buffer.
	Version uint64 `json:"Version"`
}

// MemoryUsageFile contains the file handle to the usage file.
type MemoryUsageFile struct {
	urpc.FilePayload
}

// UsageFD returns the file that tracks the memory usage of the application.
func (u *Usage) UsageFD(opts *MemoryUsageFileOpts, out *MemoryUsageFile) error {
	// Only support version 1 for now.
	if opts.Version != 1 {
		return fmt.Errorf("unsupported version requested: %d", opts.Version)
	}

	mf := u.Kernel.MemoryFile()
	*out = MemoryUsageFile{
		FilePayload: urpc.FilePayload{
			Files: []*os.File{
				usage.MemoryAccounting.File,
				mf.File(),
			},
		},
	}

	return nil
}

// Collect returns memory used by the sandboxed application.
func (u *Usage) Collect(opts *MemoryUsageOpts, out *MemoryUsage) error {
	if opts.Full {
		// Ensure everything is up to date.
		if err := u.Kernel.MemoryFile().UpdateUsage(); err != nil {
			return err
		}

		// Copy out a snapshot.
		snapshot, total := usage.MemoryAccounting.Copy()
		*out = MemoryUsage{
			System:    snapshot.System,
			Anonymous: snapshot.Anonymous,
			PageCache: snapshot.PageCache,
			Mapped:    snapshot.Mapped,
			Tmpfs:     snapshot.Tmpfs,
			Ramdiskfs: snapshot.Ramdiskfs,
			Total:     total,
		}
	} else {
		// Get total usage from the MemoryFile implementation.
		total, err := u.Kernel.MemoryFile().TotalUsage()
		if err != nil {
			return err
		}

		// The memory accounting is guaranteed to be accurate only when
		// UpdateUsage is called. If UpdateUsage is not called, then only Mapped
		// will be up-to-date.
		snapshot, _ := usage.MemoryAccounting.Copy()
		*out = MemoryUsage{
			Unknown: total,
			Mapped:  snapshot.Mapped,
			Total:   total + snapshot.Mapped,
		}

	}

	return nil
}

// UsageReduceOpts contains options to Usage.Reduce().
type UsageReduceOpts struct {
	// If Wait is true, Reduce blocks until all activity initiated by
	// Usage.Reduce() has completed.
	Wait bool `json:"wait"`
}

// UsageReduceOutput contains output from Usage.Reduce().
type UsageReduceOutput struct{}

// Reduce requests that the sentry attempt to reduce its memory usage.
func (u *Usage) Reduce(opts *UsageReduceOpts, out *UsageReduceOutput) error {
	mf := u.Kernel.MemoryFile()
	mf.StartEvictions()
	if opts.Wait {
		mf.WaitForEvictions()
	}
	return nil
}

// MemoryUsageRecord contains the mapping and platform memory file.
type MemoryUsageRecord struct {
	mmap  uintptr
	stats *usage.RTMemoryStats
	mf    os.File
}

// NewMemoryUsageRecord creates a new MemoryUsageRecord from usageFile and
// platformFile.
func NewMemoryUsageRecord(usageFile, platformFile os.File) (*MemoryUsageRecord, error) {
	mmap, _, e := unix.RawSyscall6(unix.SYS_MMAP, 0, usage.RTMemoryStatsSize, unix.PROT_READ, unix.MAP_SHARED, usageFile.Fd(), 0)
	if e != 0 {
		return nil, fmt.Errorf("mmap returned %d, want 0", e)
	}

	m := MemoryUsageRecord{
		mmap:  mmap,
		stats: usage.RTMemoryStatsPointer(mmap),
		mf:    platformFile,
	}

	runtime.SetFinalizer(&m, finalizer)
	return &m, nil
}

func finalizer(m *MemoryUsageRecord) {
	unix.RawSyscall(unix.SYS_MUNMAP, m.mmap, usage.RTMemoryStatsSize, 0)
}

// Fetch fetches the usage info from a MemoryUsageRecord.
func (m *MemoryUsageRecord) Fetch() (mapped, unknown, total uint64, err error) {
	var stat unix.Stat_t
	if err := unix.Fstat(int(m.mf.Fd()), &stat); err != nil {
		return 0, 0, 0, err
	}
	fmem := uint64(stat.Blocks) * 512
	return m.stats.RTMapped, fmem, m.stats.RTMapped + fmem, nil
}