-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
usage.go
222 lines (194 loc) · 6.5 KB
/
usage.go
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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
// 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 (
"encoding/json"
"fmt"
"os"
"runtime"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/sentry/fsmetric"
"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(nil); 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.
// If Wait is `false`, Go garbage collection is still performed and may
// still block for some time, unless `DoNotGC` is `true`.
Wait bool `json:"wait"`
// If DoNotGC is true, Reduce does not explicitly run Go garbage collection.
// Garbage collection may block for an indeterminate amount of time.
// Note that the runtime Go may still perform routine garbage collection at
// any time during program execution, so a routine GC is still possible even
// when this option set to `true`.
DoNotGC bool `json:"do_not_gc"`
}
// 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()
}
if !opts.DoNotGC {
runtime.GC()
}
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
}
// GetFileIoStats writes the read times in nanoseconds to out.
func (*Usage) GetFileIoStats(_ *struct{}, out *string) error {
fileIoStats := struct {
// The total amount of time spent reading. The map maps gopher prefixes
// to the total time spent reading. Times not included in a known prefix
// are placed in the "/" prefix.
ReadWait map[string]uint64 `json:"ReadWait"`
// The total amount of time spent reading. The map maps gopher prefixes
// to the total time spent reading. Times not included in a known prefix
// are placed in the "/" prefix.
ReadWait9P map[string]uint64 `json:"ReadWait9P"`
}{
ReadWait: map[string]uint64{"/": fsmetric.ReadWait.Value()},
ReadWait9P: map[string]uint64{"/": fsmetric.GoferReadWait9P.Value()},
}
m, err := json.Marshal(fileIoStats)
if err != nil {
return err
}
*out = string(m)
return 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
rtmapped := m.stats.RTMapped.Load()
return rtmapped, fmem, rtmapped + fmem, nil
}