summaryrefslogtreecommitdiffhomepage
path: root/pkg/sentry/control/pprof.go
blob: 91b8fb44f355c8d2babcf6c682754d215cec5628 (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
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
// Copyright 2019 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 (
	"errors"
	"runtime"
	"runtime/pprof"
	"runtime/trace"

	"gvisor.dev/gvisor/pkg/fd"
	"gvisor.dev/gvisor/pkg/sentry/kernel"
	"gvisor.dev/gvisor/pkg/sync"
	"gvisor.dev/gvisor/pkg/urpc"
)

var errNoOutput = errors.New("no output writer provided")

// ProfileOpts contains options for the StartCPUProfile/Goroutine RPC call.
type ProfileOpts struct {
	// File is the filesystem path for the profile.
	File string `json:"path"`

	// FilePayload is the destination for the profiling output.
	urpc.FilePayload
}

// Profile includes profile-related RPC stubs. It provides a way to
// control the built-in pprof facility in sentry via sentryctl.
//
// The following options to sentryctl are added:
//
// - collect CPU profile on-demand.
//   sentryctl -pid <pid> pprof-cpu-start
//   sentryctl -pid <pid> pprof-cpu-stop
//
// - dump out the stack trace of current go routines.
//   sentryctl -pid <pid> pprof-goroutine
type Profile struct {
	// Kernel is the kernel under profile. It's immutable.
	Kernel *kernel.Kernel

	// mu protects the fields below.
	mu sync.Mutex

	// cpuFile is the current CPU profile output file.
	cpuFile *fd.FD

	// traceFile is the current execution trace output file.
	traceFile *fd.FD
}

// StartCPUProfile is an RPC stub which starts recording the CPU profile in a
// file.
func (p *Profile) StartCPUProfile(o *ProfileOpts, _ *struct{}) error {
	if len(o.FilePayload.Files) < 1 {
		return errNoOutput
	}

	output, err := fd.NewFromFile(o.FilePayload.Files[0])
	if err != nil {
		return err
	}

	p.mu.Lock()
	defer p.mu.Unlock()

	// Returns an error if profiling is already started.
	if err := pprof.StartCPUProfile(output); err != nil {
		output.Close()
		return err
	}

	p.cpuFile = output
	return nil
}

// StopCPUProfile is an RPC stub which stops the CPU profiling and flush out the
// profile data. It takes no argument.
func (p *Profile) StopCPUProfile(_, _ *struct{}) error {
	p.mu.Lock()
	defer p.mu.Unlock()

	if p.cpuFile == nil {
		return errors.New("CPU profiling not started")
	}

	pprof.StopCPUProfile()
	p.cpuFile.Close()
	p.cpuFile = nil
	return nil
}

// HeapProfile generates a heap profile for the sentry.
func (p *Profile) HeapProfile(o *ProfileOpts, _ *struct{}) error {
	if len(o.FilePayload.Files) < 1 {
		return errNoOutput
	}
	output := o.FilePayload.Files[0]
	defer output.Close()
	runtime.GC() // Get up-to-date statistics.
	if err := pprof.WriteHeapProfile(output); err != nil {
		return err
	}
	return nil
}

// GoroutineProfile is an RPC stub which dumps out the stack trace for all
// running goroutines.
func (p *Profile) GoroutineProfile(o *ProfileOpts, _ *struct{}) error {
	if len(o.FilePayload.Files) < 1 {
		return errNoOutput
	}
	output := o.FilePayload.Files[0]
	defer output.Close()
	if err := pprof.Lookup("goroutine").WriteTo(output, 2); err != nil {
		return err
	}
	return nil
}

// BlockProfile is an RPC stub which dumps out the stack trace that led to
// blocking on synchronization primitives.
func (p *Profile) BlockProfile(o *ProfileOpts, _ *struct{}) error {
	if len(o.FilePayload.Files) < 1 {
		return errNoOutput
	}
	output := o.FilePayload.Files[0]
	defer output.Close()
	if err := pprof.Lookup("block").WriteTo(output, 0); err != nil {
		return err
	}
	return nil
}

// MutexProfile is an RPC stub which dumps out the stack trace of holders of
// contended mutexes.
func (p *Profile) MutexProfile(o *ProfileOpts, _ *struct{}) error {
	if len(o.FilePayload.Files) < 1 {
		return errNoOutput
	}
	output := o.FilePayload.Files[0]
	defer output.Close()
	if err := pprof.Lookup("mutex").WriteTo(output, 0); err != nil {
		return err
	}
	return nil
}

// StartTrace is an RPC stub which starts collection of an execution trace.
func (p *Profile) StartTrace(o *ProfileOpts, _ *struct{}) error {
	if len(o.FilePayload.Files) < 1 {
		return errNoOutput
	}

	output, err := fd.NewFromFile(o.FilePayload.Files[0])
	if err != nil {
		return err
	}

	p.mu.Lock()
	defer p.mu.Unlock()

	// Returns an error if profiling is already started.
	if err := trace.Start(output); err != nil {
		output.Close()
		return err
	}

	// Ensure all trace contexts are registered.
	p.Kernel.RebuildTraceContexts()

	p.traceFile = output
	return nil
}

// StopTrace is an RPC stub which stops collection of an ongoing execution
// trace and flushes the trace data. It takes no argument.
func (p *Profile) StopTrace(_, _ *struct{}) error {
	p.mu.Lock()
	defer p.mu.Unlock()

	if p.traceFile == nil {
		return errors.New("execution tracing not started")
	}

	// Similarly to the case above, if tasks have not ended traces, we will
	// lose information. Thus we need to rebuild the tasks in order to have
	// complete information. This will not lose information if multiple
	// traces are overlapping.
	p.Kernel.RebuildTraceContexts()

	trace.Stop()
	p.traceFile.Close()
	p.traceFile = nil
	return nil
}