Skip to content

Commit

Permalink
fix(agent): 🐛 enable more profiling options
Browse files Browse the repository at this point in the history
  • Loading branch information
joshuar committed May 10, 2024
1 parent e5c7265 commit 0717f92
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 22 deletions.
16 changes: 0 additions & 16 deletions internal/logging/logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
package logging

import (
"fmt"
"net/http"
_ "net/http/pprof"
"os"
"path/filepath"
Expand All @@ -23,20 +21,6 @@ func init() {
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
}

func SetProfiling() {
go func() {
for i := 6060; i < 6070; i++ {
log.Debug().
Msgf("Starting profiler web interface on localhost:" + fmt.Sprint(i))
err := http.ListenAndServe("localhost:"+fmt.Sprint(i), nil)
if err != nil {
log.Debug().Err(err).
Msg("Trouble starting profiler, trying again.")
}
}
}()
}

// SetLoggingLevel sets an appropriate log level and enables profiling if requested.
func SetLoggingLevel(level string) {
switch level {
Expand Down
124 changes: 124 additions & 0 deletions internal/logging/profiling.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// Copyright (c) 2024 Joshua Rich <[email protected]>
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT

package logging

import (
"errors"
"fmt"
"net/http"
"os"
"runtime"
"runtime/pprof"
"runtime/trace"
"strconv"

"github.com/rs/zerolog/log"
)

type ProfileFlags map[string]string

func StartProfiling(flags ProfileFlags) error {
for k, v := range flags {
switch k {
case "webui":
webui, err := strconv.ParseBool(v)
if err != nil {
return errors.Join(errors.New("could not interpret webui value"), err)
}
if webui {
go func() {
for i := 6060; i < 6070; i++ {
log.Debug().
Msgf("Starting profiler web interface on localhost:" + fmt.Sprint(i))
err := http.ListenAndServe("localhost:"+fmt.Sprint(i), nil)
if err != nil {
log.Debug().Err(err).
Msg("Trouble starting profiler, trying again.")
}
}
}()
}
case "heapprofile":
log.Debug().Msg("Heap profiling enabled.")
case "cpuprofile":
f, err := os.Create(v)
if err != nil {
log.Fatal().Err(err).Msg("Cannot create CPU profile.")
}
if err := pprof.StartCPUProfile(f); err != nil {
log.Fatal().Err(err).Msg("Could not start CPU profiling.")
}
log.Debug().Msg("CPU profiling enabled.")
case "traceprofile":
f, err := os.Create(v)
if err != nil {
log.Fatal().Err(err).Msg("Cannot create trace profile.")
}
if err = trace.Start(f); err != nil {
log.Fatal().Err(err).Msg("Could not start trace profiling.")
}
log.Debug().Msg("Trace profiling enabled.")
default:
return fmt.Errorf("unknown argument for profiling: %s=%s", k, v)
}
}
return nil
}

func StopProfiling(flags ProfileFlags) error {
for k, v := range flags {
switch k {
case "heapprofile":
f, err := os.Create(v)
if err != nil {
return errors.Join(errors.New("cannot create heap profile file"), err)
}
var ms runtime.MemStats
runtime.ReadMemStats(&ms)
printMemStats(&ms)
if err := pprof.WriteHeapProfile(f); err != nil {
return errors.Join(errors.New("cannot write to heap profile file"), err)
}
_ = f.Close()
log.Debug().Msgf("Heap profile written to %s", v)
case "cpuprofile":
pprof.StopCPUProfile()
case "traceprofile":
trace.Stop()
}
}
return nil
}

// printMemStats and formatMemory functions are taken from golang-ci source

func printMemStats(ms *runtime.MemStats) {
log.Debug().Msgf("Mem stats: alloc=%s total_alloc=%s sys=%s "+
"heap_alloc=%s heap_sys=%s heap_idle=%s heap_released=%s heap_in_use=%s "+
"stack_in_use=%s stack_sys=%s "+
"mspan_sys=%s mcache_sys=%s buck_hash_sys=%s gc_sys=%s other_sys=%s "+
"mallocs_n=%d frees_n=%d heap_objects_n=%d gc_cpu_fraction=%.2f",
formatMemory(ms.Alloc), formatMemory(ms.TotalAlloc), formatMemory(ms.Sys),
formatMemory(ms.HeapAlloc), formatMemory(ms.HeapSys),
formatMemory(ms.HeapIdle), formatMemory(ms.HeapReleased), formatMemory(ms.HeapInuse),
formatMemory(ms.StackInuse), formatMemory(ms.StackSys),
formatMemory(ms.MSpanSys), formatMemory(ms.MCacheSys), formatMemory(ms.BuckHashSys),
formatMemory(ms.GCSys), formatMemory(ms.OtherSys),
ms.Mallocs, ms.Frees, ms.HeapObjects, ms.GCCPUFraction)
}

func formatMemory(memBytes uint64) string {
const Kb = 1024
const Mb = Kb * 1024

if memBytes < Kb {
return fmt.Sprintf("%db", memBytes)
}
if memBytes < Mb {
return fmt.Sprintf("%dkb", memBytes/Kb)
}
return fmt.Sprintf("%dmb", memBytes/Mb)
}
15 changes: 9 additions & 6 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
package main

import (
"errors"
"path/filepath"
"syscall"

Expand All @@ -30,11 +31,10 @@ func (d logLevel) AfterApply() error {
return nil
}

type profileFlag bool
type profileFlags logging.ProfileFlags

func (d profileFlag) AfterApply() error {
logging.SetProfiling()
return nil
func (d profileFlags) AfterApply() error {
return logging.StartProfiling(logging.ProfileFlags(d))
}

type noLogFileFlag bool
Expand All @@ -47,8 +47,8 @@ func (d noLogFileFlag) AfterApply() error {
}

type Context struct {
Profile profileFlags
AppID string
Profile profileFlag
Headless bool
}

Expand Down Expand Up @@ -172,10 +172,10 @@ var CLI struct {
Run RunCmd `cmd:"" help:"Run Go Hass Agent."`
Reset ResetCmd `cmd:"" help:"Reset Go Hass Agent."`
Version VersionCmd `cmd:"" help:"Show the Go Hass Agent version."`
Profile profileFlags `help:"Enable profiling."`
AppID string `name:"appid" default:"${defaultAppID}" help:"Specify a custom app id (for debugging)."`
LogLevel logLevel `name:"log-level" help:"Set logging level."`
Register RegisterCmd `cmd:"" help:"Register with Home Assistant."`
Profile profileFlag `help:"Enable profiling."`
NoLog noLogFileFlag `help:"Don't write to a log file."`
Headless bool `name:"terminal" help:"Run without a GUI."`
}
Expand All @@ -198,5 +198,8 @@ func main() {
kong.Description(preferences.AppDescription)
ctx := kong.Parse(&CLI, kong.Bind(), kong.Vars{"defaultAppID": preferences.AppID})
err := ctx.Run(&Context{Headless: CLI.Headless, Profile: CLI.Profile, AppID: CLI.AppID})
if CLI.Profile != nil {
errors.Join(logging.StopProfiling(logging.ProfileFlags(CLI.Profile)), err)
}
ctx.FatalIfErrorf(err)
}

0 comments on commit 0717f92

Please sign in to comment.