Initial commit
This commit is contained in:
commit
ab33b83bf4
19
LICENSE
Normal file
19
LICENSE
Normal file
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2022 WeebDataHoarder, pgo-collector Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
116
collector.go
Normal file
116
collector.go
Normal file
|
@ -0,0 +1,116 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
pprofile "github.com/google/pprof/profile"
|
||||
"golang.org/x/exp/slices"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type stringList []string
|
||||
|
||||
func (i *stringList) String() string {
|
||||
return strings.Join(*i, ", ")
|
||||
}
|
||||
|
||||
func (i *stringList) Set(value string) error {
|
||||
*i = append(*i, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
profileInterval := flag.Duration("profile-interval", time.Hour, "Profile every given duration")
|
||||
profileDuration := flag.Duration("profile-duration", time.Second*30, "Profile each time for this given duration")
|
||||
var endpoints stringList
|
||||
flag.Var(&endpoints, "endpoint", "Endpoint where a pprof HTTP API is exposed at, for example, http://127.0.0.1:6060. Can specify multiple")
|
||||
profileDirectory := flag.String("profile-directory", "/tmp", "Place where to dump generated merged profiles")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
log.Printf("Profiling for %s every %s", *profileDuration, *profileInterval)
|
||||
|
||||
for range time.Tick(*profileInterval) {
|
||||
var wg sync.WaitGroup
|
||||
profiles := make([]*pprofile.Profile, len(endpoints))
|
||||
for i, endpoint := range endpoints {
|
||||
wg.Add(1)
|
||||
go func(i int, endpoint string) {
|
||||
defer wg.Done()
|
||||
log.Printf("attempting profile collection on %s", endpoint)
|
||||
if response, err := http.Get(fmt.Sprintf("%s/debug/pprof/profile?seconds=%d", endpoint, uint64(profileDuration.Seconds()))); err != nil {
|
||||
log.Printf("error collecting profile on %s: %s", endpoint, err)
|
||||
return
|
||||
} else {
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode != http.StatusOK {
|
||||
if response.Header.Get("X-Go-Pprof") != "" && strings.Contains(response.Header.Get("Content-Type"), "text/plain") {
|
||||
// error is from pprof endpoint
|
||||
if body, err := io.ReadAll(response.Body); err == nil {
|
||||
log.Printf("error collecting profile on %s: got %d %s - %s", endpoint, response.StatusCode, response.Status, string(body))
|
||||
return
|
||||
}
|
||||
}
|
||||
log.Printf("error collecting profile on %s: expected status %d, got %d %s", endpoint, http.StatusOK, response.StatusCode, response.Status)
|
||||
return
|
||||
} else if profile, err := pprofile.Parse(response.Body); err != nil {
|
||||
log.Printf("error collecting profile on %s while reading: %s", endpoint, err)
|
||||
return
|
||||
} else {
|
||||
profiles[i] = profile
|
||||
}
|
||||
}
|
||||
}(i, endpoint)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
//remove any errored ones
|
||||
for i := len(profiles) - 1; i >= 0; i-- {
|
||||
if profiles[i] == nil {
|
||||
profiles = slices.Delete(profiles, i, i+1)
|
||||
}
|
||||
}
|
||||
if mergedProfile, err := pprofile.Merge(profiles); err != nil {
|
||||
log.Printf("could not merge profiles: %s", err)
|
||||
} else {
|
||||
//maybe redundant
|
||||
compactProfile := mergedProfile.Compact()
|
||||
profilePath := path.Join(*profileDirectory, fmt.Sprintf("pgo-profile-%d.pprof", time.Now().UTC().Unix()))
|
||||
if f, err := os.Create(profilePath); err != nil {
|
||||
log.Printf("error opening output profile %s: %s", profilePath, err)
|
||||
} else {
|
||||
func() {
|
||||
defer f.Close()
|
||||
if err = compactProfile.Write(f); err != nil {
|
||||
log.Printf("error writing output profile %s: %s", profilePath, err)
|
||||
} else {
|
||||
log.Printf("wrote profile %s", profilePath)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
//copies to latest atomically
|
||||
func() {
|
||||
r, err := os.Open(profilePath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer r.Close()
|
||||
w, err := os.Create(path.Join(*profileDirectory, "pgo-profile-latest-temp.pprof"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer w.Close()
|
||||
}()
|
||||
_ = os.Rename(path.Join(*profileDirectory, "pgo-profile-latest-temp.pprof"), path.Join(*profileDirectory, "pgo-profile-latest.pprof"))
|
||||
|
||||
}
|
||||
}
|
||||
}
|
8
go.mod
Normal file
8
go.mod
Normal file
|
@ -0,0 +1,8 @@
|
|||
module git.gammaspectra.live/P2Pool/pgo-collector
|
||||
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/google/pprof v0.0.0-20230510103437-eeec1cb781c3
|
||||
golang.org/x/exp v0.0.0-20230519143937-03e91628a987
|
||||
)
|
10
go.sum
Normal file
10
go.sum
Normal file
|
@ -0,0 +1,10 @@
|
|||
github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/pprof v0.0.0-20230510103437-eeec1cb781c3 h1:2XF1Vzq06X+inNqgJ9tRnGuw+ZVCB3FazXODD6JE1R8=
|
||||
github.com/google/pprof v0.0.0-20230510103437-eeec1cb781c3/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20220517205856-0058ec4f073c h1:rwmN+hgiyp8QyBqzdEX43lTjKAxaqCrYHaU5op5P9J8=
|
||||
golang.org/x/exp v0.0.0-20230519143937-03e91628a987 h1:3xJIFvzUFbu4ls0BTBYcgbCGhA63eAOEMxIHugyXJqA=
|
||||
golang.org/x/exp v0.0.0-20230519143937-03e91628a987/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||
golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I=
|
||||
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
|
||||
golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE=
|
Loading…
Reference in a new issue