Skip to content
This repository was archived by the owner on Jun 21, 2022. It is now read-only.
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ require (
github.com/minio/minio-go/v7 v7.0.24
github.com/percona-platform/dbaas-api v0.0.0-20220110092915-5aacd784d472
github.com/percona-platform/saas v0.0.0-20220427162947-f9d246ad0f16
github.com/percona/pmm v0.0.0-20220518171255-d446045c2d1c
github.com/percona/pmm v0.0.0-20220518214512-81558aadadae
github.com/percona/promconfig v0.2.4-0.20211110115058-98687f586f54
github.com/pkg/errors v0.9.1
github.com/pmezard/go-difflib v1.0.0
Expand Down
10 changes: 2 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,6 @@ github.com/go-openapi/loads v0.21.1/go.mod h1:/DtAMXXneXFjbQMGEtbamCZb+4x7eGwkvZ
github.com/go-openapi/runtime v0.23.1/go.mod h1:AKurw9fNre+h3ELZfk6ILsfvPN+bvvlaU/M9q/r9hpk=
github.com/go-openapi/runtime v0.24.0 h1:vTgDijpGLCgJOJTdAp5kG+O+nRsVCbH417YQ3O0iZo0=
github.com/go-openapi/runtime v0.24.0/go.mod h1:AKurw9fNre+h3ELZfk6ILsfvPN+bvvlaU/M9q/r9hpk=
github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
github.com/go-openapi/spec v0.20.5 h1:skHa8av4VnAtJU5zyAUXrrdK/NDiVX8lchbG+BfcdrE=
github.com/go-openapi/spec v0.20.5/go.mod h1:QbfOSIVt3/sac+a1wzmKbbcLXm5NdZnyBZYtCijp43o=
Expand Down Expand Up @@ -430,7 +429,6 @@ github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs=
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
Expand Down Expand Up @@ -470,10 +468,8 @@ github.com/percona-platform/dbaas-api v0.0.0-20220110092915-5aacd784d472 h1:Henk
github.com/percona-platform/dbaas-api v0.0.0-20220110092915-5aacd784d472/go.mod h1:WZZ3Hi+lAWCaGWmsrfkkvRQPkIa8n1OZ0s8Su+vbgus=
github.com/percona-platform/saas v0.0.0-20220427162947-f9d246ad0f16 h1:0fx16uGtl4MwrBwm9/VSoNEhjL0cXYxS0quEhLthGcc=
github.com/percona-platform/saas v0.0.0-20220427162947-f9d246ad0f16/go.mod h1:gFUwaFp6Ugu5qsBwiOVJYbDlzgZ77tmXdXGO7tG5xVI=
github.com/percona/pmm v0.0.0-20220517143005-ac6985a5a857 h1:9nw28TjRRFWOnPVYCoD8Rh/2g2gFRLVCjJCtb9shZGI=
github.com/percona/pmm v0.0.0-20220517143005-ac6985a5a857/go.mod h1:CyTSJFtA0+LPGLnC8rE3tr8nDbfP/QkJ9+HyUzimsR4=
github.com/percona/pmm v0.0.0-20220518171255-d446045c2d1c h1:H2/DJaY8k/1qdJfpHzRw5dRnpFgKRh/3NjO2yrPWt+E=
github.com/percona/pmm v0.0.0-20220518171255-d446045c2d1c/go.mod h1:gr+WLd8clEAe2xMFgsGhpw9ziZc2UCWcfy6d3M6Aq00=
github.com/percona/pmm v0.0.0-20220518214512-81558aadadae h1:f4JDAd7mTM8M8i5zhvHEWSYfdvfibUWdAw+XZ+ICN4E=
github.com/percona/pmm v0.0.0-20220518214512-81558aadadae/go.mod h1:gr+WLd8clEAe2xMFgsGhpw9ziZc2UCWcfy6d3M6Aq00=
github.com/percona/promconfig v0.2.4-0.20211110115058-98687f586f54 h1:aI1emmycDTGWKsBdxFPKZqohfBbK4y2ta9G4+RX7gVg=
github.com/percona/promconfig v0.2.4-0.20211110115058-98687f586f54/go.mod h1:Y2uXi5QNk71+ceJHuI9poank+0S1kjxd3K105fXKVkg=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
Expand Down Expand Up @@ -690,8 +686,6 @@ golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220412020605-290c469a71a5 h1:bRb386wvrE+oBNdF1d/Xh9mQrfQ4ecYhW5qJ5GvTGT4=
golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
Expand Down
13 changes: 11 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,17 @@ func addLogsHandler(mux *http.ServeMux, logs *supervisord.Logs) {
l := logrus.WithField("component", "logs.zip")

mux.HandleFunc("/logs.zip", func(rw http.ResponseWriter, req *http.Request) {
defaultContextTimeout := 10
// increase context timeout if pprof query parameter exist in request
pprofQueryParameter, _ := strconv.Atoi(req.FormValue("pprof"))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest using strconv.ParseBool instead of strconv.Atoi.
Do not ignore err, let's at least log the message.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed.

usePprof := pprofQueryParameter > 0
if usePprof {
// 60 seconds for profile, 10 seconds for trace, 1 sec for heap
defaultContextTimeout += 71
}

// fail-safe
ctx, cancel := context.WithTimeout(req.Context(), 10*time.Second)
ctx, cancel := context.WithTimeout(req.Context(), time.Duration(defaultContextTimeout)*time.Second)
defer cancel()

filename := fmt.Sprintf("pmm-server_%s.zip", time.Now().UTC().Format("2006-01-02_15-04"))
Expand All @@ -121,7 +130,7 @@ func addLogsHandler(mux *http.ServeMux, logs *supervisord.Logs) {
rw.Header().Set(`Content-Disposition`, `attachment; filename="`+filename+`"`)

ctx = logger.Set(ctx, "logs")
if err := logs.Zip(ctx, rw); err != nil {
if err := logs.Zip(ctx, rw, usePprof); err != nil {
l.Errorf("%+v", err)
}
})
Expand Down
38 changes: 34 additions & 4 deletions services/supervisord/logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import (
"golang.org/x/sys/unix"

"github.com/percona/pmm-managed/utils/logger"
pprofUtils "github.com/percona/pmm-managed/utils/pprof"
)

const (
Expand Down Expand Up @@ -69,7 +70,7 @@ func NewLogs(pmmVersion string, pmmUpdateChecker *PMMUpdateChecker) *Logs {
}

// Zip creates .zip archive with all logs.
func (l *Logs) Zip(ctx context.Context, w io.Writer) error {
func (l *Logs) Zip(ctx context.Context, w io.Writer, pprof bool) error {
start := time.Now()
log := logger.Get(ctx).WithField("component", "logs")
log.WithField("d", time.Since(start).Seconds()).Info("Starting...")
Expand All @@ -80,12 +81,13 @@ func (l *Logs) Zip(ctx context.Context, w io.Writer) error {
zw := zip.NewWriter(w)
now := time.Now().UTC()

files := l.files(ctx)
files := l.files(ctx, pprof)
log.WithField("d", time.Since(start).Seconds()).Infof("Collected %d files.", len(files))

for _, file := range files {
if ctx.Err() != nil {
log.WithField("d", time.Since(start).Seconds()).Warnf("%s; skipping the rest of the files", ctx.Err())
log.WithField("d", time.Since(start).Seconds()).Infof("%s; skipping the rest of the files", ctx.Err())
break
}

Expand Down Expand Up @@ -127,8 +129,8 @@ func (l *Logs) Zip(ctx context.Context, w io.Writer) error {
return nil
}

// files reads log/config files and returns content.
func (l *Logs) files(ctx context.Context) []fileContent {
// files reads log/config/pprof files and returns content.
func (l *Logs) files(ctx context.Context, pprof bool) []fileContent {
files := make([]fileContent, 0, 20)

// add logs
Expand Down Expand Up @@ -214,6 +216,34 @@ func (l *Logs) files(ctx context.Context) []fileContent {
Err: err,
})

// add pprof
// TODO: consider replacing writer with bytes[] in pprofUtils
if pprof {
var traceBuf bytes.Buffer
err = pprofUtils.Trace(&traceBuf, 10)
files = append(files, fileContent{
Name: "pprof/trace.out",
Data: traceBuf.Bytes(),
Err: err,
})

var profileBuf bytes.Buffer
err = pprofUtils.Profile(&profileBuf, 60)
files = append(files, fileContent{
Name: "pprof/profile.pb.gz",
Data: profileBuf.Bytes(),
Err: err,
})

var heapBuf bytes.Buffer
err = pprofUtils.Heap(&heapBuf, true)
files = append(files, fileContent{
Name: "pprof/heap.pb.gz",
Data: heapBuf.Bytes(),
Err: err,
})
}

sort.Slice(files, func(i, j int) bool { return files[i].Name < files[j].Name })
return files
}
Expand Down
4 changes: 2 additions & 2 deletions services/supervisord/logs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ func TestFiles(t *testing.T) {
l := NewLogs("2.4.5", checker)
ctx := logger.Set(context.Background(), t.Name())

files := l.files(ctx)
files := l.files(ctx, false)
actual := make([]string, 0, len(files))
for _, f := range files {
// present only after update
Expand Down Expand Up @@ -157,7 +157,7 @@ func TestZip(t *testing.T) {
ctx := logger.Set(context.Background(), t.Name())

var buf bytes.Buffer
require.NoError(t, l.Zip(ctx, &buf))
require.NoError(t, l.Zip(ctx, &buf, false))
reader := bytes.NewReader(buf.Bytes())
r, err := zip.NewReader(reader, reader.Size())
require.NoError(t, err)
Expand Down
71 changes: 71 additions & 0 deletions utils/pprof/pprof.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// pmm-managed
// Copyright (C) 2017 Percona LLC
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

package pprof

import (
"fmt"
"io"
"runtime"
"runtime/pprof"
"runtime/trace"
"time"
)

// Profile responds with the pprof-formatted cpu profile.
// Profiling lasts for duration specified in seconds.
func Profile(writer io.Writer, duration int64) error {
if err := pprof.StartCPUProfile(writer); err != nil {
return err
}

time.Sleep(time.Duration(duration) * time.Second)
pprof.StopCPUProfile()

return nil
}

// Trace responds with the execution trace in binary form.
// Tracing lasts for duration specified in seconds.
func Trace(writer io.Writer, duration int64) error {
if err := trace.Start(writer); err != nil {
return err
}

time.Sleep(time.Duration(duration) * time.Second)
trace.Stop()

return nil
}

// Heap responds with the pprof-formatted profile named "heap".
// listing the available profiles.
// You can specify the gc parameter to run gc before taking the heap sample.
func Heap(writer io.Writer, gc bool) error {
debug := 0
profile := "heap"

p := pprof.Lookup(profile)
if p == nil {
return fmt.Errorf("profile cannot be found: %s", profile)
}

if gc {
runtime.GC()
}

return p.WriteTo(writer, debug)
}
74 changes: 74 additions & 0 deletions utils/pprof/pprof_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// pmm-managed
// Copyright (C) 2017 Percona LLC
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

package pprof

import (
"bytes"
"compress/gzip"
"testing"

"github.com/stretchr/testify/assert"
)

func TestHeap(t *testing.T) {
t.Parallel()
t.Run("Heap test", func(t *testing.T) {
var heapBuf bytes.Buffer
err := Heap(&heapBuf, true)

// read gzip
reader, err := gzip.NewReader(&heapBuf)
assert.NoError(t, err)

var resB bytes.Buffer
_, err = resB.ReadFrom(reader)
assert.NoError(t, err)
assert.True(t, len(resB.Bytes()) > 0)
Comment thread
pkadej marked this conversation as resolved.
Outdated
})
}

func TestProfile(t *testing.T) {
t.Parallel()
t.Run("Profile test", func(t *testing.T) {
var profileBuf bytes.Buffer
err := Profile(&profileBuf, 1)

assert.NoError(t, err)
assert.True(t, len(profileBuf.Bytes()) > 0)
Comment thread
pkadej marked this conversation as resolved.
Outdated

// read gzip
reader, err := gzip.NewReader(&profileBuf)
assert.NoError(t, err)

var resB bytes.Buffer
_, err = resB.ReadFrom(reader)
assert.NoError(t, err)

assert.True(t, len(resB.Bytes()) > 0)
Comment thread
pkadej marked this conversation as resolved.
Outdated
})
}

func TestTrace(t *testing.T) {
t.Parallel()
t.Run("Trace test", func(t *testing.T) {
var traceBuf bytes.Buffer
err := Trace(&traceBuf, 1)

assert.NoError(t, err)
assert.True(t, len(traceBuf.Bytes()) > 0)
Comment thread
pkadej marked this conversation as resolved.
Outdated
})
}