-
Notifications
You must be signed in to change notification settings - Fork 39
PMM-5492 Add Pprof data to logs.zip #1117
base: main
Are you sure you want to change the base?
Changes from 11 commits
e92bca5
882f9aa
366d963
a3c438d
80e5d2d
683b4db
214a656
b8ddc92
ea5c617
2f36941
f8aa75a
b0e5163
a28413e
28e4406
291869e
22c5b95
27856c8
d988223
3d5d295
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -93,6 +93,7 @@ import ( | |
| "github.com/percona/pmm-managed/utils/clean" | ||
| "github.com/percona/pmm-managed/utils/interceptors" | ||
| "github.com/percona/pmm-managed/utils/logger" | ||
| "github.com/percona/pmm-managed/utils/pprof" | ||
| ) | ||
|
|
||
| const ( | ||
|
|
@@ -110,9 +111,23 @@ const ( | |
| func addLogsHandler(mux *http.ServeMux, logs *supervisord.Logs) { | ||
| l := logrus.WithField("component", "logs.zip") | ||
|
|
||
| cfg := config.NewService() | ||
| if err := cfg.Load(); err != nil { | ||
| l.Panicf("Failed to load config: %+v", err) | ||
| } | ||
|
|
||
| mux.HandleFunc("/logs.zip", func(rw http.ResponseWriter, req *http.Request) { | ||
| contextTimeout := 10 * time.Second | ||
| // increase context timeout if pprof query parameter exist in request | ||
| pprofQueryParameter, _ := strconv.Atoi(req.FormValue("pprof")) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suggest using
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed. |
||
| var pprofSettings *pprof.Config | ||
| if pprofQueryParameter > 0 { | ||
| contextTimeout += cfg.Config.Services.Pprof.ProfileDuration + cfg.Config.Services.Pprof.TraceDuration | ||
| pprofSettings = &cfg.Config.Services.Pprof | ||
| } | ||
|
|
||
| // fail-safe | ||
| ctx, cancel := context.WithTimeout(req.Context(), 10*time.Second) | ||
| ctx, cancel := context.WithTimeout(req.Context(), contextTimeout) | ||
| defer cancel() | ||
|
|
||
| filename := fmt.Sprintf("pmm-server_%s.zip", time.Now().UTC().Format("2006-01-02_15-04")) | ||
|
|
@@ -121,7 +136,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, pprofSettings); err != nil { | ||
| l.Errorf("%+v", err) | ||
| } | ||
| }) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,6 +28,7 @@ import ( | |
|
|
||
| "github.com/percona/pmm-managed/services/platform" | ||
| "github.com/percona/pmm-managed/services/telemetry" | ||
| "github.com/percona/pmm-managed/utils/pprof" | ||
| ) | ||
|
|
||
| const ( | ||
|
|
@@ -49,6 +50,7 @@ type Config struct { | |
| Services struct { | ||
| Platform platform.Config `yaml:"platform"` | ||
| Telemetry telemetry.ServiceConfig `yaml:"telemetry"` | ||
| Pprof pprof.Config `yaml:"pprof"` | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please remove it from the config, it will simplify the code a lot.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
| } `yaml:"services"` | ||
| } | ||
|
|
||
|
|
@@ -94,7 +96,7 @@ func (s *Service) Load() error { | |
| if err := cfg.Services.Telemetry.Init(s.l); err != nil { | ||
| return err | ||
| } | ||
|
|
||
| cfg.Services.Pprof.Init() | ||
| s.Config = cfg | ||
|
|
||
| return nil | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,3 +32,6 @@ services: | |
| retry_backoff_env: "PERCONA_TEST_TELEMETRY_RETRY_BACKOFF" | ||
| retry_count: 20 | ||
| send_timeout: 5s | ||
| pprof: | ||
| profile_duration: 30s | ||
| trace_duration: 10s | ||
|
artemgavrilov marked this conversation as resolved.
Outdated
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. making timeouts configurable doesn't bring any value to customers, it's so rare case when we might need to update these values in the future.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. moved to constants |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| // 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" | ||
| "fmt" | ||
| "runtime" | ||
| "runtime/pprof" | ||
| "runtime/trace" | ||
| "time" | ||
| ) | ||
|
|
||
| // Profile responds with the pprof-formatted cpu profile. | ||
| // Profiling lasts for duration specified in seconds. | ||
| func Profile(duration time.Duration) ([]byte, error) { | ||
| var profileBuf bytes.Buffer | ||
| if err := pprof.StartCPUProfile(&profileBuf); err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| time.Sleep(duration) | ||
| pprof.StopCPUProfile() | ||
|
|
||
| return profileBuf.Bytes(), nil | ||
| } | ||
|
|
||
| // Trace responds with the execution trace in binary form. | ||
| // Tracing lasts for duration specified in seconds. | ||
| func Trace(duration time.Duration) ([]byte, error) { | ||
| var traceBuf bytes.Buffer | ||
| if err := trace.Start(&traceBuf); err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| time.Sleep(duration) | ||
| trace.Stop() | ||
|
|
||
| return traceBuf.Bytes(), 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(gc bool) ([]byte, error) { | ||
| var heapBuf bytes.Buffer | ||
| debug := 0 | ||
| profile := "heap" | ||
|
|
||
| p := pprof.Lookup(profile) | ||
| if p == nil { | ||
| return nil, fmt.Errorf("profile cannot be found: %s", profile) | ||
| } | ||
|
|
||
| if gc { | ||
| runtime.GC() | ||
| } | ||
|
|
||
| err := p.WriteTo(&heapBuf, debug) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| return heapBuf.Bytes(), nil | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| // 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 ( | ||
| "time" | ||
| ) | ||
|
|
||
| // Config pprof settings. | ||
| type Config struct { | ||
| ProfileDuration time.Duration `yaml:"profile_duration"` //nolint:tagliatelle | ||
| TraceDuration time.Duration `yaml:"trace_duration"` //nolint:tagliatelle | ||
| } | ||
|
|
||
| // Init pprof config init. | ||
| func (c *Config) Init() { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| // 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" | ||
| "time" | ||
|
|
||
| "github.com/stretchr/testify/assert" | ||
| ) | ||
|
|
||
| func TestHeap(t *testing.T) { | ||
| t.Parallel() | ||
| t.Run("Heap test", func(t *testing.T) { | ||
| heapBytes, err := Heap(true) | ||
|
|
||
| // read gzip | ||
| reader, err := gzip.NewReader(bytes.NewBuffer(heapBytes)) | ||
| assert.NoError(t, err) | ||
|
|
||
| var resB bytes.Buffer | ||
| _, err = resB.ReadFrom(reader) | ||
| assert.NoError(t, err) | ||
| assert.True(t, len(resB.Bytes()) != 0) | ||
|
artemgavrilov marked this conversation as resolved.
Outdated
|
||
| }) | ||
| } | ||
|
|
||
| func TestProfile(t *testing.T) { | ||
| t.Parallel() | ||
| t.Run("Profile test", func(t *testing.T) { | ||
| profileBytes, err := Profile(1 * time.Second) | ||
|
|
||
| assert.NoError(t, err) | ||
| assert.True(t, len(profileBytes) != 0) | ||
|
artemgavrilov marked this conversation as resolved.
Outdated
|
||
|
|
||
| // read gzip | ||
| reader, err := gzip.NewReader(bytes.NewBuffer(profileBytes)) | ||
| assert.NoError(t, err) | ||
|
|
||
| var resB bytes.Buffer | ||
| _, err = resB.ReadFrom(reader) | ||
| assert.NoError(t, err) | ||
|
|
||
| assert.True(t, len(resB.Bytes()) != 0) | ||
|
artemgavrilov marked this conversation as resolved.
Outdated
|
||
| }) | ||
| } | ||
|
|
||
| func TestTrace(t *testing.T) { | ||
| t.Parallel() | ||
| t.Run("Trace test", func(t *testing.T) { | ||
| traceBytes, err := Trace(1 * time.Second) | ||
|
|
||
| assert.NoError(t, err) | ||
| assert.True(t, len(traceBytes) != 0) | ||
|
artemgavrilov marked this conversation as resolved.
Outdated
|
||
| }) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can it be moved to config
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I propose to use just a constant for that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree with @artemgavrilov, no reason to artificially increase the number of items in configs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved to const defaultContextTimeout.