From 658f772c16da05807c2c079449e8ac71fd6f55c5 Mon Sep 17 00:00:00 2001 From: Kunal Kushwaha Date: Fri, 26 Oct 2018 14:47:52 +0900 Subject: perf test a stress test to profile CPU load of podman Signed-off-by: Kunal Kushwaha --- Makefile | 6 + contrib/perftest/README.md | 51 ++++++ contrib/perftest/main.go | 283 +++++++++++++++++++++++++++++++ vendor.conf | 1 + vendor/github.com/pkg/profile/LICENSE | 24 +++ vendor/github.com/pkg/profile/README.md | 54 ++++++ vendor/github.com/pkg/profile/mutex.go | 13 ++ vendor/github.com/pkg/profile/mutex17.go | 9 + vendor/github.com/pkg/profile/profile.go | 244 ++++++++++++++++++++++++++ vendor/github.com/pkg/profile/trace.go | 8 + vendor/github.com/pkg/profile/trace16.go | 10 ++ 11 files changed, 703 insertions(+) create mode 100644 contrib/perftest/README.md create mode 100644 contrib/perftest/main.go create mode 100644 vendor/github.com/pkg/profile/LICENSE create mode 100644 vendor/github.com/pkg/profile/README.md create mode 100644 vendor/github.com/pkg/profile/mutex.go create mode 100644 vendor/github.com/pkg/profile/mutex17.go create mode 100644 vendor/github.com/pkg/profile/profile.go create mode 100644 vendor/github.com/pkg/profile/trace.go create mode 100644 vendor/github.com/pkg/profile/trace16.go diff --git a/Makefile b/Makefile index 250226cf0..b8f1eff93 100644 --- a/Makefile +++ b/Makefile @@ -189,6 +189,12 @@ clientintegration: $(MAKE) -C contrib/python/podman integration $(MAKE) -C contrib/python/pypodman integration +perftest: + $ cd contrib/perftest;go build + +run-perftest: perftest + $ contrib/perftest/perftest + vagrant-check: BOX=$(BOX) sh ./vagrant.sh diff --git a/contrib/perftest/README.md b/contrib/perftest/README.md new file mode 100644 index 000000000..bd0ef08f5 --- /dev/null +++ b/contrib/perftest/README.md @@ -0,0 +1,51 @@ +## 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 \ No newline at end of file diff --git a/contrib/perftest/main.go b/contrib/perftest/main.go new file mode 100644 index 000000000..0d0947890 --- /dev/null +++ b/contrib/perftest/main.go @@ -0,0 +1,283 @@ +package main + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "text/tabwriter" + "time" + + "github.com/containers/image/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() { + + 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() + + if reexec.Init() { + return + } + + 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{}, false) + 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) + 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) + 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() +} diff --git a/vendor.conf b/vendor.conf index f2d7fa414..b272b537a 100644 --- a/vendor.conf +++ b/vendor.conf @@ -100,3 +100,4 @@ github.com/ulikunitz/xz v0.5.4 github.com/mailru/easyjson 03f2033d19d5860aef995fe360ac7d395cd8ce65 github.com/coreos/go-iptables 25d087f3cffd9aedc0c2b7eff25f23cbf3c20fe1 github.com/google/shlex c34317bd91bf98fab745d77b03933cf8769299fe +github.com/pkg/profile v1.2.1 \ No newline at end of file diff --git a/vendor/github.com/pkg/profile/LICENSE b/vendor/github.com/pkg/profile/LICENSE new file mode 100644 index 000000000..f747a8411 --- /dev/null +++ b/vendor/github.com/pkg/profile/LICENSE @@ -0,0 +1,24 @@ +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. diff --git a/vendor/github.com/pkg/profile/README.md b/vendor/github.com/pkg/profile/README.md new file mode 100644 index 000000000..37bfa58c5 --- /dev/null +++ b/vendor/github.com/pkg/profile/README.md @@ -0,0 +1,54 @@ +profile +======= + +Simple profiling support package for Go + +[![Build Status](https://travis-ci.org/pkg/profile.svg?branch=master)](https://travis-ci.org/pkg/profile) [![GoDoc](http://godoc.org/github.com/pkg/profile?status.svg)](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. diff --git a/vendor/github.com/pkg/profile/mutex.go b/vendor/github.com/pkg/profile/mutex.go new file mode 100644 index 000000000..e69c5b44d --- /dev/null +++ b/vendor/github.com/pkg/profile/mutex.go @@ -0,0 +1,13 @@ +// +build go1.8 + +package profile + +import "runtime" + +func enableMutexProfile() { + runtime.SetMutexProfileFraction(1) +} + +func disableMutexProfile() { + runtime.SetMutexProfileFraction(0) +} diff --git a/vendor/github.com/pkg/profile/mutex17.go b/vendor/github.com/pkg/profile/mutex17.go new file mode 100644 index 000000000..b004c21d5 --- /dev/null +++ b/vendor/github.com/pkg/profile/mutex17.go @@ -0,0 +1,9 @@ +// +build !go1.8 + +package profile + +// mock mutex support for Go 1.7 and earlier. + +func enableMutexProfile() {} + +func disableMutexProfile() {} diff --git a/vendor/github.com/pkg/profile/profile.go b/vendor/github.com/pkg/profile/profile.go new file mode 100644 index 000000000..c44913a4c --- /dev/null +++ b/vendor/github.com/pkg/profile/profile.go @@ -0,0 +1,244 @@ +// 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" + "sync/atomic" +) + +const ( + cpuMode = iota + memMode + mutexMode + blockMode + traceMode +) + +// 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. +// +// Mutex profiling is a no-op before go1.8. +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 controls if execution tracing will be enabled. It disables any previous profiling settings. +func TraceProfile(p *Profile) { p.mode = traceMode } + +// 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) + } + enableMutexProfile() + logf("profile: mutex profiling enabled, %s", fn) + prof.closer = func() { + if mp := pprof.Lookup("mutex"); mp != nil { + mp.WriteTo(f, 0) + } + f.Close() + disableMutexProfile() + 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 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 := startTrace(f); err != nil { + log.Fatalf("profile: could not start trace: %v", err) + } + logf("profile: trace enabled, %s", fn) + prof.closer = func() { + stopTrace() + logf("profile: trace 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 +} diff --git a/vendor/github.com/pkg/profile/trace.go b/vendor/github.com/pkg/profile/trace.go new file mode 100644 index 000000000..b349ed8b2 --- /dev/null +++ b/vendor/github.com/pkg/profile/trace.go @@ -0,0 +1,8 @@ +// +build go1.7 + +package profile + +import "runtime/trace" + +var startTrace = trace.Start +var stopTrace = trace.Stop diff --git a/vendor/github.com/pkg/profile/trace16.go b/vendor/github.com/pkg/profile/trace16.go new file mode 100644 index 000000000..6aa6566ef --- /dev/null +++ b/vendor/github.com/pkg/profile/trace16.go @@ -0,0 +1,10 @@ +// +build !go1.7 + +package profile + +import "io" + +// mock trace support for Go 1.6 and earlier. + +func startTrace(w io.Writer) error { return nil } +func stopTrace() {} -- cgit v1.2.3-54-g00ecf