/* * MIT License * * Copyright (c) 2024 Nicolas JUHEL * * 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. * * */ package pprof import ( "context" "errors" "fmt" "os" "path/filepath" "runtime" "runtime/debug" "runtime/pprof" "time" srvtck "github.com/nabbar/golib/server/runner/ticker" ) var ( c *os.File m string ctx, cnl = context.WithCancel(context.Background()) s = srvtck.New(5*time.Minute, ProfilingMemRun) ) func StartProfiling() { ProfilingCPUStart() ProfilingMemStart() } func StopProfiling() { ProfilingMemDefer() ProfilingCPUDefer() } func getPath(basename string) (*os.File, error) { var ( h *os.File p string e error ) p, e = os.Executable() if e != nil { return nil, e } p = filepath.Join(filepath.Dir(p), basename) if _, e = os.Stat(p); e != nil && !errors.Is(e, os.ErrNotExist) { return nil, e } else if e != nil { h, e = os.Create(p) } else { h, e = os.Open(p) } if e != nil { return nil, e } if e = h.Truncate(0); e != nil { _ = h.Close() return nil, e } return h, nil } func ProfilingCPUStart() { var e error if c, e = getPath("cpu.prof"); e != nil { panic(e) } else if e = pprof.StartCPUProfile(c); e != nil { panic(e) } _, _ = fmt.Fprintf(os.Stdout, "Starting pprof for CPU to file '%s'\n", c.Name()) } func ProfilingCPUDefer() { _, _ = fmt.Fprintf(os.Stdout, "Stopping pprof for CPU to file '%s'\n", c.Name()) pprof.StopCPUProfile() _ = c.Close() } func ProfilingMemStart() { if h, e := getPath("mem.prof"); e != nil { panic(e) } else { m = h.Name() } if e := s.Start(ctx); e != nil { panic(e) } } func ProfilingMemRun(ctx context.Context, tck *time.Ticker) error { if ctx.Err() != nil { return nil } else if len(m) < 1 { return nil } else if h, e := os.OpenFile(m, os.O_RDWR|os.O_EXCL|os.O_SYNC, 0644); e != nil { return e } else { defer func() { _ = h.Close() }() runtime.GC() debug.FreeOSMemory() if e = pprof.WriteHeapProfile(h); e != nil { return e } return nil } } func ProfilingMemDefer() { if cnl != nil { cnl() } x, l := context.WithTimeout(context.Background(), 15*time.Second) defer l() _ = s.Stop(x) }