mirror of
https://github.com/containers/podman.git
synced 2025-06-29 15:08:09 +08:00
rm contrib/perftest
Perftest was intended to be used for testing CPU intensive tasks of Podman. However, it does not compile for a long while and is not integrated in the CI which clearly indicates that it has not been used for a considerable amount of time. Remove contrib/perftest entirely. If the desire arises to revive it, all code is still reachable in the git history. Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
This commit is contained in:
6
Makefile
6
Makefile
@ -312,12 +312,6 @@ remotesystem:
|
|||||||
system.test-binary: .install.ginkgo
|
system.test-binary: .install.ginkgo
|
||||||
$(GO) test -c ./test/system
|
$(GO) test -c ./test/system
|
||||||
|
|
||||||
perftest: ## Build perf tests
|
|
||||||
$ cd contrib/perftest;go build
|
|
||||||
|
|
||||||
run-perftest: perftest ## Build and run perf tests
|
|
||||||
$ contrib/perftest/perftest
|
|
||||||
|
|
||||||
vagrant-check:
|
vagrant-check:
|
||||||
BOX=$(BOX) sh ./vagrant.sh
|
BOX=$(BOX) sh ./vagrant.sh
|
||||||
|
|
||||||
|
@ -1,51 +0,0 @@
|
|||||||
## perftest : tool for benchmarking and profiling libpod library
|
|
||||||
perftest uses libpod as golang library and perform stress test and profile for CPU usage.
|
|
||||||
|
|
||||||
Build:
|
|
||||||
|
|
||||||
```
|
|
||||||
# cd $GOPATH/src/github.com/containers/libpod/contrib/perftest
|
|
||||||
# go build
|
|
||||||
# go install
|
|
||||||
```
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
|
|
||||||
```
|
|
||||||
# perftest -h
|
|
||||||
Usage of perftest:
|
|
||||||
|
|
||||||
-count int
|
|
||||||
count of loop counter for test (default 50)
|
|
||||||
-image string
|
|
||||||
image-name to be used for test (default "docker.io/library/alpine:latest")
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
e.g.
|
|
||||||
|
|
||||||
```
|
|
||||||
# perftest
|
|
||||||
runc version spec: 1.0.1-dev
|
|
||||||
conmon version 1.12.0-dev, commit: b6c5cafeffa9b3cde89812207b29ccedd3102712
|
|
||||||
|
|
||||||
preparing test environment...
|
|
||||||
2018/11/05 16:52:14 profile: cpu profiling enabled, /tmp/profile626959338/cpu.pprof
|
|
||||||
Test Round: 0 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
|
|
||||||
Profile data
|
|
||||||
|
|
||||||
Create Start Stop Delete
|
|
||||||
Min 0.23s 0.34s 2.12s 0.51s
|
|
||||||
Avg 0.25s 0.38s 2.13s 0.54s
|
|
||||||
Max 0.27s 0.48s 2.13s 0.70s
|
|
||||||
Total 12.33s 18.82s 106.47s 26.91s
|
|
||||||
2018/11/05 16:54:59 profile: cpu profiling disabled, /tmp/profile626959338/cpu.pprof
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
Analyse CPU profile.
|
|
||||||
|
|
||||||
```
|
|
||||||
# go tool pprof -http=":8081" $GOPATH/src/github.com/containers/libpod/contrib/perftest/perftest /tmp/profile626959338/cpu.pprof
|
|
||||||
```
|
|
||||||
- Open http://localhost:8081 in webbrowser
|
|
@ -1,282 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"text/tabwriter"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/containers/image/v5/types"
|
|
||||||
"github.com/containers/libpod/libpod"
|
|
||||||
image2 "github.com/containers/libpod/libpod/image"
|
|
||||||
cc "github.com/containers/libpod/pkg/spec"
|
|
||||||
"github.com/containers/libpod/pkg/util"
|
|
||||||
"github.com/containers/storage/pkg/reexec"
|
|
||||||
"github.com/cri-o/ocicni/pkg/ocicni"
|
|
||||||
"github.com/pkg/profile"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
defaultTestImage = "docker.io/library/alpine:latest"
|
|
||||||
defaultRunCount = 50
|
|
||||||
)
|
|
||||||
|
|
||||||
var helpMessage = `
|
|
||||||
-count int
|
|
||||||
count of loop counter for test (default 50)
|
|
||||||
-image string
|
|
||||||
image-name to be used for test (default "docker.io/library/alpine:latest")
|
|
||||||
-log string
|
|
||||||
log level (info|debug|warn|error) (default "error")
|
|
||||||
|
|
||||||
`
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if reexec.Init() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
imageName := ""
|
|
||||||
|
|
||||||
testImageName := flag.String("image", defaultTestImage, "image-name to be used for test")
|
|
||||||
testRunCount := flag.Int("count", defaultRunCount, "count of loop counter for test")
|
|
||||||
logLevel := flag.String("log", "error", "log level (info|debug|warn|error)")
|
|
||||||
|
|
||||||
flag.Usage = func() {
|
|
||||||
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
|
|
||||||
fmt.Fprintf(os.Stderr, "%s \n", helpMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
switch strings.ToLower(*logLevel) {
|
|
||||||
case "error":
|
|
||||||
logrus.SetLevel(logrus.ErrorLevel)
|
|
||||||
case "warn":
|
|
||||||
logrus.SetLevel(logrus.WarnLevel)
|
|
||||||
case "info":
|
|
||||||
logrus.SetLevel(logrus.InfoLevel)
|
|
||||||
case "debug":
|
|
||||||
logrus.SetLevel(logrus.DebugLevel)
|
|
||||||
default:
|
|
||||||
logrus.Fatalf("invalid option : %s ", *logLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := defaultRuntimeOptions()
|
|
||||||
client, err := libpod.NewRuntime(opts...)
|
|
||||||
if err != nil {
|
|
||||||
logrus.Fatal(err)
|
|
||||||
}
|
|
||||||
defer client.Shutdown(false)
|
|
||||||
|
|
||||||
// Print Runtime & System Information.
|
|
||||||
err = printSystemInfo(client)
|
|
||||||
if err != nil {
|
|
||||||
logrus.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
imageRuntime := client.ImageRuntime()
|
|
||||||
if imageRuntime == nil {
|
|
||||||
logrus.Fatal("ImageRuntime is null")
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("preparing test environment...\n")
|
|
||||||
//Prepare for test.
|
|
||||||
testImage, err := imageRuntime.NewFromLocal(*testImageName)
|
|
||||||
if err != nil {
|
|
||||||
// Download the image from remote registry.
|
|
||||||
writer := os.Stderr
|
|
||||||
registryCreds := &types.DockerAuthConfig{
|
|
||||||
Username: "",
|
|
||||||
Password: "",
|
|
||||||
}
|
|
||||||
dockerRegistryOptions := image2.DockerRegistryOptions{
|
|
||||||
DockerRegistryCreds: registryCreds,
|
|
||||||
DockerCertPath: "",
|
|
||||||
DockerInsecureSkipTLSVerify: types.OptionalBoolFalse,
|
|
||||||
}
|
|
||||||
fmt.Printf("image %s not found locally, fetching from remote registry..\n", *testImageName)
|
|
||||||
|
|
||||||
testImage, err = client.ImageRuntime().New(ctx, *testImageName, "", "", writer, &dockerRegistryOptions, image2.SigningOptions{}, nil, util.PullImageMissing)
|
|
||||||
if err != nil {
|
|
||||||
logrus.Fatal(err)
|
|
||||||
}
|
|
||||||
fmt.Printf("image downloaded successfully\n\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
names := testImage.Names()
|
|
||||||
if len(names) > 0 {
|
|
||||||
imageName = names[0]
|
|
||||||
} else {
|
|
||||||
imageName = testImage.ID()
|
|
||||||
}
|
|
||||||
|
|
||||||
idmappings, err := util.ParseIDMapping(nil, nil, "", "")
|
|
||||||
if err != nil {
|
|
||||||
logrus.Fatal(err)
|
|
||||||
}
|
|
||||||
config := &cc.CreateConfig{
|
|
||||||
Tty: true,
|
|
||||||
Image: imageName,
|
|
||||||
ImageID: testImage.ID(),
|
|
||||||
IDMappings: idmappings,
|
|
||||||
Command: []string{"/bin/sh"},
|
|
||||||
WorkDir: "/",
|
|
||||||
NetMode: "bridge",
|
|
||||||
Network: "bridge",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enable CPU Profile
|
|
||||||
defer profile.Start().Stop()
|
|
||||||
|
|
||||||
data, err := runSingleThreadedStressTest(ctx, client, imageName, testImage.ID(), config, *testRunCount)
|
|
||||||
if err != nil {
|
|
||||||
logrus.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
data.printProfiledData((float64)(*testRunCount))
|
|
||||||
}
|
|
||||||
|
|
||||||
func defaultRuntimeOptions() []libpod.RuntimeOption {
|
|
||||||
options := []libpod.RuntimeOption{}
|
|
||||||
return options
|
|
||||||
/*
|
|
||||||
//TODO: Shall we test in clean environment?
|
|
||||||
sOpts := storage.StoreOptions{
|
|
||||||
GraphDriverName: "overlay",
|
|
||||||
RunRoot: "/var/run/containers/storage",
|
|
||||||
GraphRoot: "/var/lib/containers/storage",
|
|
||||||
}
|
|
||||||
|
|
||||||
storageOpts := libpod.WithStorageConfig(sOpts)
|
|
||||||
options = append(options, storageOpts)
|
|
||||||
return options
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
func printSystemInfo(client *libpod.Runtime) error {
|
|
||||||
OCIRuntimeInfo, err := client.GetOCIRuntimeVersion()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
connmanInfo, err := client.GetConmonVersion()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Printf("%s\n%s\n\n", OCIRuntimeInfo, connmanInfo)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func runSingleThreadedStressTest(ctx context.Context, client *libpod.Runtime, imageName string, imageID string, config *cc.CreateConfig, testCount int) (*profileData, error) {
|
|
||||||
data := new(profileData)
|
|
||||||
fmt.Printf("Test Round: ")
|
|
||||||
for i := 0; i < testCount; i++ {
|
|
||||||
fmt.Printf("%d ", i)
|
|
||||||
|
|
||||||
runtimeSpec, err := cc.CreateConfigToOCISpec(config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
//Create Container
|
|
||||||
networks := make([]string, 0)
|
|
||||||
netmode := "bridge"
|
|
||||||
createStartTime := time.Now()
|
|
||||||
ctr, err := client.NewContainer(ctx,
|
|
||||||
runtimeSpec,
|
|
||||||
libpod.WithRootFSFromImage(imageID, imageName, false),
|
|
||||||
libpod.WithNetNS([]ocicni.PortMapping{}, false, netmode, networks),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
createTotalTime := time.Now().Sub(createStartTime)
|
|
||||||
|
|
||||||
// Start container
|
|
||||||
startStartTime := time.Now()
|
|
||||||
err = ctr.Start(ctx, false)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
startTotalTime := time.Now().Sub(startStartTime)
|
|
||||||
|
|
||||||
//Stop Container
|
|
||||||
stopStartTime := time.Now()
|
|
||||||
err = ctr.StopWithTimeout(2)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
stopTotalTime := time.Now().Sub(stopStartTime)
|
|
||||||
|
|
||||||
//Delete Container
|
|
||||||
deleteStartTime := time.Now()
|
|
||||||
|
|
||||||
err = client.RemoveContainer(ctx, ctr, true, false)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteTotalTime := time.Now().Sub(deleteStartTime)
|
|
||||||
|
|
||||||
data.updateProfileData(createTotalTime, startTotalTime, stopTotalTime, deleteTotalTime)
|
|
||||||
}
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type profileData struct {
|
|
||||||
minCreate, minStart, minStop, minDel time.Duration
|
|
||||||
avgCreate, avgStart, avgStop, avgDel time.Duration
|
|
||||||
maxCreate, maxStart, maxStop, maxDel time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (data *profileData) updateProfileData(create, start, stop, delete time.Duration) {
|
|
||||||
if create < data.minCreate || data.minCreate == 0 {
|
|
||||||
data.minCreate = create
|
|
||||||
}
|
|
||||||
if create > data.maxCreate || data.maxCreate == 0 {
|
|
||||||
data.maxCreate = create
|
|
||||||
}
|
|
||||||
if start < data.minStart || data.minStart == 0 {
|
|
||||||
data.minStart = start
|
|
||||||
}
|
|
||||||
if start > data.maxStart || data.maxStart == 0 {
|
|
||||||
data.maxStart = start
|
|
||||||
}
|
|
||||||
if stop < data.minStop || data.minStop == 0 {
|
|
||||||
data.minStop = stop
|
|
||||||
}
|
|
||||||
if stop > data.maxStop || data.maxStop == 0 {
|
|
||||||
data.maxStop = stop
|
|
||||||
}
|
|
||||||
if delete < data.minDel || data.minDel == 0 {
|
|
||||||
data.minDel = delete
|
|
||||||
}
|
|
||||||
if delete > data.maxDel || data.maxDel == 0 {
|
|
||||||
data.maxDel = delete
|
|
||||||
}
|
|
||||||
|
|
||||||
data.avgCreate = data.avgCreate + create
|
|
||||||
data.avgStart = data.avgStart + start
|
|
||||||
data.avgStop = data.avgStop + stop
|
|
||||||
data.avgDel = data.avgDel + delete
|
|
||||||
}
|
|
||||||
|
|
||||||
func (data *profileData) printProfiledData(testCount float64) {
|
|
||||||
|
|
||||||
fmt.Printf("\nProfile data\n\n")
|
|
||||||
w := new(tabwriter.Writer)
|
|
||||||
w.Init(os.Stdout, 0, 8, 0, '\t', 0)
|
|
||||||
fmt.Fprintln(w, "\tCreate\tStart\tStop\tDelete")
|
|
||||||
fmt.Fprintf(w, "Min\t%.2fs\t%.2fs\t%.2fs\t%.2fs\n", data.minCreate.Seconds(), data.minStart.Seconds(), data.minStop.Seconds(), data.minDel.Seconds())
|
|
||||||
fmt.Fprintf(w, "Avg\t%.2fs\t%.2fs\t%.2fs\t%.2fs\n", data.avgCreate.Seconds()/testCount, data.avgStart.Seconds()/testCount, data.avgStop.Seconds()/testCount, data.avgDel.Seconds()/testCount)
|
|
||||||
fmt.Fprintf(w, "Max\t%.2fs\t%.2fs\t%.2fs\t%.2fs\n", data.maxCreate.Seconds(), data.maxStart.Seconds(), data.maxStop.Seconds(), data.maxDel.Seconds())
|
|
||||||
fmt.Fprintf(w, "Total\t%.2fs\t%.2fs\t%.2fs\t%.2fs\n", data.avgCreate.Seconds(), data.avgStart.Seconds(), data.avgStop.Seconds(), data.avgDel.Seconds())
|
|
||||||
fmt.Fprintln(w)
|
|
||||||
w.Flush()
|
|
||||||
}
|
|
2
go.mod
2
go.mod
@ -51,7 +51,7 @@ require (
|
|||||||
github.com/opencontainers/selinux v1.3.0
|
github.com/opencontainers/selinux v1.3.0
|
||||||
github.com/opentracing/opentracing-go v1.1.0
|
github.com/opentracing/opentracing-go v1.1.0
|
||||||
github.com/pkg/errors v0.8.1
|
github.com/pkg/errors v0.8.1
|
||||||
github.com/pkg/profile v1.4.0
|
github.com/pkg/profile v1.4.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0
|
github.com/pmezard/go-difflib v1.0.0
|
||||||
github.com/rootless-containers/rootlesskit v0.7.1
|
github.com/rootless-containers/rootlesskit v0.7.1
|
||||||
github.com/seccomp/containers-golang v0.0.0-20190312124753-8ca8945ccf5f
|
github.com/seccomp/containers-golang v0.0.0-20190312124753-8ca8945ccf5f
|
||||||
|
10
vendor/github.com/pkg/profile/.travis.yml
generated
vendored
10
vendor/github.com/pkg/profile/.travis.yml
generated
vendored
@ -1,10 +0,0 @@
|
|||||||
language: go
|
|
||||||
go_import_path: github.com/pkg/profile
|
|
||||||
go:
|
|
||||||
- 1.12.x
|
|
||||||
- 1.13.x
|
|
||||||
- tip
|
|
||||||
|
|
||||||
script:
|
|
||||||
- go test github.com/pkg/profile
|
|
||||||
- go test -race github.com/pkg/profile
|
|
1
vendor/github.com/pkg/profile/AUTHORS
generated
vendored
1
vendor/github.com/pkg/profile/AUTHORS
generated
vendored
@ -1 +0,0 @@
|
|||||||
Dave Cheney <dave@cheney.net>
|
|
24
vendor/github.com/pkg/profile/LICENSE
generated
vendored
24
vendor/github.com/pkg/profile/LICENSE
generated
vendored
@ -1,24 +0,0 @@
|
|||||||
Copyright (c) 2013 Dave Cheney. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above
|
|
||||||
copyright notice, this list of conditions and the following disclaimer
|
|
||||||
in the documentation and/or other materials provided with the
|
|
||||||
distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
54
vendor/github.com/pkg/profile/README.md
generated
vendored
54
vendor/github.com/pkg/profile/README.md
generated
vendored
@ -1,54 +0,0 @@
|
|||||||
profile
|
|
||||||
=======
|
|
||||||
|
|
||||||
Simple profiling support package for Go
|
|
||||||
|
|
||||||
[](https://travis-ci.org/pkg/profile) [](http://godoc.org/github.com/pkg/profile)
|
|
||||||
|
|
||||||
|
|
||||||
installation
|
|
||||||
------------
|
|
||||||
|
|
||||||
go get github.com/pkg/profile
|
|
||||||
|
|
||||||
usage
|
|
||||||
-----
|
|
||||||
|
|
||||||
Enabling profiling in your application is as simple as one line at the top of your main function
|
|
||||||
|
|
||||||
```go
|
|
||||||
import "github.com/pkg/profile"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
defer profile.Start().Stop()
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
options
|
|
||||||
-------
|
|
||||||
|
|
||||||
What to profile is controlled by config value passed to profile.Start.
|
|
||||||
By default CPU profiling is enabled.
|
|
||||||
|
|
||||||
```go
|
|
||||||
import "github.com/pkg/profile"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// p.Stop() must be called before the program exits to
|
|
||||||
// ensure profiling information is written to disk.
|
|
||||||
p := profile.Start(profile.MemProfile, profile.ProfilePath("."), profile.NoShutdownHook)
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Several convenience package level values are provided for cpu, memory, and block (contention) profiling.
|
|
||||||
|
|
||||||
For more complex options, consult the [documentation](http://godoc.org/github.com/pkg/profile).
|
|
||||||
|
|
||||||
contributing
|
|
||||||
------------
|
|
||||||
|
|
||||||
We welcome pull requests, bug fixes and issue reports.
|
|
||||||
|
|
||||||
Before proposing a change, please discuss it first by raising an issue.
|
|
3
vendor/github.com/pkg/profile/go.mod
generated
vendored
3
vendor/github.com/pkg/profile/go.mod
generated
vendored
@ -1,3 +0,0 @@
|
|||||||
module github.com/pkg/profile
|
|
||||||
|
|
||||||
go 1.12
|
|
284
vendor/github.com/pkg/profile/profile.go
generated
vendored
284
vendor/github.com/pkg/profile/profile.go
generated
vendored
@ -1,284 +0,0 @@
|
|||||||
// Package profile provides a simple way to manage runtime/pprof
|
|
||||||
// profiling of your Go application.
|
|
||||||
package profile
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"runtime/pprof"
|
|
||||||
"runtime/trace"
|
|
||||||
"sync/atomic"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
cpuMode = iota
|
|
||||||
memMode
|
|
||||||
mutexMode
|
|
||||||
blockMode
|
|
||||||
traceMode
|
|
||||||
threadCreateMode
|
|
||||||
goroutineMode
|
|
||||||
)
|
|
||||||
|
|
||||||
// Profile represents an active profiling session.
|
|
||||||
type Profile struct {
|
|
||||||
// quiet suppresses informational messages during profiling.
|
|
||||||
quiet bool
|
|
||||||
|
|
||||||
// noShutdownHook controls whether the profiling package should
|
|
||||||
// hook SIGINT to write profiles cleanly.
|
|
||||||
noShutdownHook bool
|
|
||||||
|
|
||||||
// mode holds the type of profiling that will be made
|
|
||||||
mode int
|
|
||||||
|
|
||||||
// path holds the base path where various profiling files are written.
|
|
||||||
// If blank, the base path will be generated by ioutil.TempDir.
|
|
||||||
path string
|
|
||||||
|
|
||||||
// memProfileRate holds the rate for the memory profile.
|
|
||||||
memProfileRate int
|
|
||||||
|
|
||||||
// closer holds a cleanup function that run after each profile
|
|
||||||
closer func()
|
|
||||||
|
|
||||||
// stopped records if a call to profile.Stop has been made
|
|
||||||
stopped uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// NoShutdownHook controls whether the profiling package should
|
|
||||||
// hook SIGINT to write profiles cleanly.
|
|
||||||
// Programs with more sophisticated signal handling should set
|
|
||||||
// this to true and ensure the Stop() function returned from Start()
|
|
||||||
// is called during shutdown.
|
|
||||||
func NoShutdownHook(p *Profile) { p.noShutdownHook = true }
|
|
||||||
|
|
||||||
// Quiet suppresses informational messages during profiling.
|
|
||||||
func Quiet(p *Profile) { p.quiet = true }
|
|
||||||
|
|
||||||
// CPUProfile enables cpu profiling.
|
|
||||||
// It disables any previous profiling settings.
|
|
||||||
func CPUProfile(p *Profile) { p.mode = cpuMode }
|
|
||||||
|
|
||||||
// DefaultMemProfileRate is the default memory profiling rate.
|
|
||||||
// See also http://golang.org/pkg/runtime/#pkg-variables
|
|
||||||
const DefaultMemProfileRate = 4096
|
|
||||||
|
|
||||||
// MemProfile enables memory profiling.
|
|
||||||
// It disables any previous profiling settings.
|
|
||||||
func MemProfile(p *Profile) {
|
|
||||||
p.memProfileRate = DefaultMemProfileRate
|
|
||||||
p.mode = memMode
|
|
||||||
}
|
|
||||||
|
|
||||||
// MemProfileRate enables memory profiling at the preferred rate.
|
|
||||||
// It disables any previous profiling settings.
|
|
||||||
func MemProfileRate(rate int) func(*Profile) {
|
|
||||||
return func(p *Profile) {
|
|
||||||
p.memProfileRate = rate
|
|
||||||
p.mode = memMode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MutexProfile enables mutex profiling.
|
|
||||||
// It disables any previous profiling settings.
|
|
||||||
func MutexProfile(p *Profile) { p.mode = mutexMode }
|
|
||||||
|
|
||||||
// BlockProfile enables block (contention) profiling.
|
|
||||||
// It disables any previous profiling settings.
|
|
||||||
func BlockProfile(p *Profile) { p.mode = blockMode }
|
|
||||||
|
|
||||||
// Trace profile enables execution tracing.
|
|
||||||
// It disables any previous profiling settings.
|
|
||||||
func TraceProfile(p *Profile) { p.mode = traceMode }
|
|
||||||
|
|
||||||
// ThreadcreationProfile enables thread creation profiling..
|
|
||||||
// It disables any previous profiling settings.
|
|
||||||
func ThreadcreationProfile(p *Profile) { p.mode = threadCreateMode }
|
|
||||||
|
|
||||||
// GoroutineProfile enables goroutine profiling.
|
|
||||||
// It disables any previous profiling settings.
|
|
||||||
func GoroutineProfile(p *Profile) { p.mode = goroutineMode }
|
|
||||||
|
|
||||||
// ProfilePath controls the base path where various profiling
|
|
||||||
// files are written. If blank, the base path will be generated
|
|
||||||
// by ioutil.TempDir.
|
|
||||||
func ProfilePath(path string) func(*Profile) {
|
|
||||||
return func(p *Profile) {
|
|
||||||
p.path = path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop stops the profile and flushes any unwritten data.
|
|
||||||
func (p *Profile) Stop() {
|
|
||||||
if !atomic.CompareAndSwapUint32(&p.stopped, 0, 1) {
|
|
||||||
// someone has already called close
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.closer()
|
|
||||||
atomic.StoreUint32(&started, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// started is non zero if a profile is running.
|
|
||||||
var started uint32
|
|
||||||
|
|
||||||
// Start starts a new profiling session.
|
|
||||||
// The caller should call the Stop method on the value returned
|
|
||||||
// to cleanly stop profiling.
|
|
||||||
func Start(options ...func(*Profile)) interface {
|
|
||||||
Stop()
|
|
||||||
} {
|
|
||||||
if !atomic.CompareAndSwapUint32(&started, 0, 1) {
|
|
||||||
log.Fatal("profile: Start() already called")
|
|
||||||
}
|
|
||||||
|
|
||||||
var prof Profile
|
|
||||||
for _, option := range options {
|
|
||||||
option(&prof)
|
|
||||||
}
|
|
||||||
|
|
||||||
path, err := func() (string, error) {
|
|
||||||
if p := prof.path; p != "" {
|
|
||||||
return p, os.MkdirAll(p, 0777)
|
|
||||||
}
|
|
||||||
return ioutil.TempDir("", "profile")
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("profile: could not create initial output directory: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
logf := func(format string, args ...interface{}) {
|
|
||||||
if !prof.quiet {
|
|
||||||
log.Printf(format, args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch prof.mode {
|
|
||||||
case cpuMode:
|
|
||||||
fn := filepath.Join(path, "cpu.pprof")
|
|
||||||
f, err := os.Create(fn)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("profile: could not create cpu profile %q: %v", fn, err)
|
|
||||||
}
|
|
||||||
logf("profile: cpu profiling enabled, %s", fn)
|
|
||||||
pprof.StartCPUProfile(f)
|
|
||||||
prof.closer = func() {
|
|
||||||
pprof.StopCPUProfile()
|
|
||||||
f.Close()
|
|
||||||
logf("profile: cpu profiling disabled, %s", fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
case memMode:
|
|
||||||
fn := filepath.Join(path, "mem.pprof")
|
|
||||||
f, err := os.Create(fn)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("profile: could not create memory profile %q: %v", fn, err)
|
|
||||||
}
|
|
||||||
old := runtime.MemProfileRate
|
|
||||||
runtime.MemProfileRate = prof.memProfileRate
|
|
||||||
logf("profile: memory profiling enabled (rate %d), %s", runtime.MemProfileRate, fn)
|
|
||||||
prof.closer = func() {
|
|
||||||
pprof.Lookup("heap").WriteTo(f, 0)
|
|
||||||
f.Close()
|
|
||||||
runtime.MemProfileRate = old
|
|
||||||
logf("profile: memory profiling disabled, %s", fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
case mutexMode:
|
|
||||||
fn := filepath.Join(path, "mutex.pprof")
|
|
||||||
f, err := os.Create(fn)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("profile: could not create mutex profile %q: %v", fn, err)
|
|
||||||
}
|
|
||||||
runtime.SetMutexProfileFraction(1)
|
|
||||||
logf("profile: mutex profiling enabled, %s", fn)
|
|
||||||
prof.closer = func() {
|
|
||||||
if mp := pprof.Lookup("mutex"); mp != nil {
|
|
||||||
mp.WriteTo(f, 0)
|
|
||||||
}
|
|
||||||
f.Close()
|
|
||||||
runtime.SetMutexProfileFraction(0)
|
|
||||||
logf("profile: mutex profiling disabled, %s", fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
case blockMode:
|
|
||||||
fn := filepath.Join(path, "block.pprof")
|
|
||||||
f, err := os.Create(fn)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("profile: could not create block profile %q: %v", fn, err)
|
|
||||||
}
|
|
||||||
runtime.SetBlockProfileRate(1)
|
|
||||||
logf("profile: block profiling enabled, %s", fn)
|
|
||||||
prof.closer = func() {
|
|
||||||
pprof.Lookup("block").WriteTo(f, 0)
|
|
||||||
f.Close()
|
|
||||||
runtime.SetBlockProfileRate(0)
|
|
||||||
logf("profile: block profiling disabled, %s", fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
case threadCreateMode:
|
|
||||||
fn := filepath.Join(path, "threadcreation.pprof")
|
|
||||||
f, err := os.Create(fn)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("profile: could not create thread creation profile %q: %v", fn, err)
|
|
||||||
}
|
|
||||||
logf("profile: thread creation profiling enabled, %s", fn)
|
|
||||||
prof.closer = func() {
|
|
||||||
if mp := pprof.Lookup("threadcreate"); mp != nil {
|
|
||||||
mp.WriteTo(f, 0)
|
|
||||||
}
|
|
||||||
f.Close()
|
|
||||||
logf("profile: thread creation profiling disabled, %s", fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
case traceMode:
|
|
||||||
fn := filepath.Join(path, "trace.out")
|
|
||||||
f, err := os.Create(fn)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("profile: could not create trace output file %q: %v", fn, err)
|
|
||||||
}
|
|
||||||
if err := trace.Start(f); err != nil {
|
|
||||||
log.Fatalf("profile: could not start trace: %v", err)
|
|
||||||
}
|
|
||||||
logf("profile: trace enabled, %s", fn)
|
|
||||||
prof.closer = func() {
|
|
||||||
trace.Stop()
|
|
||||||
logf("profile: trace disabled, %s", fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
case goroutineMode:
|
|
||||||
fn := filepath.Join(path, "goroutine.pprof")
|
|
||||||
f, err := os.Create(fn)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("profile: could not create goroutine profile %q: %v", fn, err)
|
|
||||||
}
|
|
||||||
logf("profile: goroutine profiling enabled, %s", fn)
|
|
||||||
prof.closer = func() {
|
|
||||||
if mp := pprof.Lookup("goroutine"); mp != nil {
|
|
||||||
mp.WriteTo(f, 0)
|
|
||||||
}
|
|
||||||
f.Close()
|
|
||||||
logf("profile: goroutine profiling disabled, %s", fn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !prof.noShutdownHook {
|
|
||||||
go func() {
|
|
||||||
c := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(c, os.Interrupt)
|
|
||||||
<-c
|
|
||||||
|
|
||||||
log.Println("profile: caught interrupt, stopping profiles")
|
|
||||||
prof.Stop()
|
|
||||||
|
|
||||||
os.Exit(0)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
return &prof
|
|
||||||
}
|
|
2
vendor/modules.txt
vendored
2
vendor/modules.txt
vendored
@ -423,8 +423,6 @@ github.com/ostreedev/ostree-go/pkg/glibobject
|
|||||||
github.com/ostreedev/ostree-go/pkg/otbuiltin
|
github.com/ostreedev/ostree-go/pkg/otbuiltin
|
||||||
# github.com/pkg/errors v0.8.1
|
# github.com/pkg/errors v0.8.1
|
||||||
github.com/pkg/errors
|
github.com/pkg/errors
|
||||||
# github.com/pkg/profile v1.4.0
|
|
||||||
github.com/pkg/profile
|
|
||||||
# github.com/pmezard/go-difflib v1.0.0
|
# github.com/pmezard/go-difflib v1.0.0
|
||||||
github.com/pmezard/go-difflib/difflib
|
github.com/pmezard/go-difflib/difflib
|
||||||
# github.com/pquerna/ffjson v0.0.0-20190813045741-dac163c6c0a9
|
# github.com/pquerna/ffjson v0.0.0-20190813045741-dac163c6c0a9
|
||||||
|
Reference in New Issue
Block a user