From 2eec5d639e82bb573aa4452797ecaf386d2092be Mon Sep 17 00:00:00 2001 From: Apostolis Anastasiou Date: Sat, 20 Jan 2024 23:22:29 +0200 Subject: [PATCH] add virtual mem --- .idea/.gitignore | 8 ++++++++ .idea/modules.xml | 8 ++++++++ .idea/peekprof.iml | 9 +++++++++ .idea/vcs.xml | 6 ++++++ app.go | 28 ++++++++++++++++++++++----- internal/extractors/chartExtractor.go | 11 +++++++++++ internal/extractors/csvExtractor.go | 9 +++++---- internal/extractors/extractors.go | 1 + internal/process/linuxProcess.go | 27 ++++++++++++++++++++++++++ internal/process/process.go | 1 + main.go | 2 ++ 11 files changed, 101 insertions(+), 9 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/modules.xml create mode 100644 .idea/peekprof.iml create mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..ba5e8c8 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/peekprof.iml b/.idea/peekprof.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/.idea/peekprof.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app.go b/app.go index 45989c6..b59efe4 100644 --- a/app.go +++ b/app.go @@ -34,6 +34,7 @@ type App struct { server *http.Server noProfilerOutput bool pretty bool + showConsole bool } type AppOptions struct { @@ -47,6 +48,7 @@ type AppOptions struct { ChartLiveUpdates bool NoProfilerOutput bool Pretty bool + ShowConsole bool } func NewApp(opts *AppOptions) *App { @@ -66,7 +68,7 @@ func NewApp(opts *AppOptions) *App { opts.Host = "localhost:8089" } - exts := []interface{}{} + var exts []interface{} if opts.CsvFilename != "" { csvExtractorOpts := extractors.NewCsvExtractorOptions(opts.CsvFilename) exts = append(exts, csvExtractorOpts) @@ -107,6 +109,7 @@ func NewApp(opts *AppOptions) *App { server: server, noProfilerOutput: opts.NoProfilerOutput, pretty: opts.Pretty, + showConsole: opts.ShowConsole, } } @@ -155,8 +158,8 @@ func (a *App) watchMemoryUsage(wg *sync.WaitGroup) { go func() { defer wg.Done() defer a.cancel() - if !a.pretty { - fmt.Printf("timestamp, rss kb, %%cpu\n") + if a.showConsole && !a.pretty { + fmt.Printf("timestamp, rss kb, virtual kb, %%cpu\n") } ch := a.process.WatchStats(a.ctx, a.refreshInterval) LOOP: @@ -167,25 +170,40 @@ func (a *App) watchMemoryUsage(wg *sync.WaitGroup) { break LOOP } + // TODO make console an extractor instead to skip this goto ~~logic~~ atrocity + if !a.showConsole { + goto skipConsole + } + if !a.noProfilerOutput { if !a.pretty { - fmt.Printf("%s,%d,%.1f\n", pstats.Timestamp, pstats.MemoryUsage.Rss, pstats.CpuUsage.Percentage) + fmt.Printf( + "%s,%d,%d,%.1f\n", + pstats.Timestamp, + pstats.MemoryUsage.Rss, + pstats.MemoryUsage.Virtual, + pstats.CpuUsage.Percentage, + ) } else { fmt.Printf( - "%02d:%02d:%02d\tmemory usage: %d mb\tcpu usage: %.1f%%\n", + "%02d:%02d:%02d\tmemory usage: %d mb\tvirtual: %d mb\tcpu usage: %.1f%%\n", pstats.Timestamp.Hour(), pstats.Timestamp.Minute(), pstats.Timestamp.Second(), pstats.MemoryUsage.Rss/1024, + pstats.MemoryUsage.Virtual/1024, pstats.CpuUsage.Percentage, ) } } + skipConsole: + err := a.extractor.Add(extractors.ProcessStatsData{ MemoryUsage: extractors.MemoryUsageData{ Rss: pstats.MemoryUsage.Rss, RssSwap: pstats.MemoryUsage.RssSwap, + Virtual: pstats.MemoryUsage.Virtual, }, CpuUsage: extractors.CpuUsageData{ Percentage: pstats.CpuUsage.Percentage, diff --git a/internal/extractors/chartExtractor.go b/internal/extractors/chartExtractor.go index b33dc38..b4dcea6 100644 --- a/internal/extractors/chartExtractor.go +++ b/internal/extractors/chartExtractor.go @@ -198,6 +198,9 @@ func (m *ChartExtractor) generateMemoryUsageChart(withLiveUpdatesListener bool) line.AddSeries("RSS+Swap", rssSwapLine, charts.WithLabelOpts(opts.Label{Show: true, Position: "top"})) } + virtualMemLine := m.getVirtualMemLineData() + line.AddSeries("Virtual", virtualMemLine, charts.WithLabelOpts(opts.Label{Show: true, Position: "top"})) + if m.UpdateLiveListenWSHost != "" && withLiveUpdatesListener { m.AddMemoryLineLiveUpdateJSFuncs(line) } @@ -322,6 +325,14 @@ func (m *ChartExtractor) getRssSwapLineData() []opts.LineData { return items } +func (m *ChartExtractor) getVirtualMemLineData() []opts.LineData { + items := make([]opts.LineData, len(m.Data)) + for i := 0; i < len(m.Data); i++ { + items[i] = opts.LineData{Value: m.Data[i].MemoryUsage.Virtual / 1024} + } + return items +} + // DivideTimeIntoParts returns a string formatted time that is divided into parts func (m *ChartExtractor) DivideTimeIntoParts(parts int) []string { if parts == 0 { diff --git a/internal/extractors/csvExtractor.go b/internal/extractors/csvExtractor.go index 87471bf..503543b 100644 --- a/internal/extractors/csvExtractor.go +++ b/internal/extractors/csvExtractor.go @@ -49,12 +49,13 @@ func (c *CsvMemoryUsage) dataToCsvRecord(data ProcessStatsData) []string { timestamp := data.Timestamp.Local().Format(time.RFC3339) rss := fmt.Sprintf("%d", data.MemoryUsage.Rss) rssSwap := fmt.Sprintf("%d", data.MemoryUsage.RssSwap) + virt := fmt.Sprintf("%d", data.MemoryUsage.Virtual) cpuPercent := fmt.Sprintf("%.1f", data.CpuUsage.Percentage) if runtime.GOOS != "darwin" { - r = []string{timestamp, rss, rssSwap, cpuPercent} + r = []string{timestamp, rss, rssSwap, virt, cpuPercent} } else { - r = []string{timestamp, rss, cpuPercent} + r = []string{timestamp, rss, virt, cpuPercent} } return r @@ -63,9 +64,9 @@ func (c *CsvMemoryUsage) dataToCsvRecord(data ProcessStatsData) []string { func (c *CsvMemoryUsage) headers() []string { var headers []string if runtime.GOOS != "darwin" { - headers = []string{"timestamp", "rss kb", "rss+swap kb", "cpu%"} + headers = []string{"timestamp", "rss kb", "rss+swap kb", "virtual kb", "cpu%"} } else { - headers = []string{"timestamp", "rss kb", "cpu%"} + headers = []string{"timestamp", "rss kb", "virtual kb", "cpu%"} } return headers } diff --git a/internal/extractors/extractors.go b/internal/extractors/extractors.go index 87db194..d1cf045 100644 --- a/internal/extractors/extractors.go +++ b/internal/extractors/extractors.go @@ -8,6 +8,7 @@ import ( type MemoryUsageData struct { Rss int64 RssSwap int64 + Virtual int64 } type CpuUsageData struct { diff --git a/internal/process/linuxProcess.go b/internal/process/linuxProcess.go index cd79971..e65f26b 100644 --- a/internal/process/linuxProcess.go +++ b/internal/process/linuxProcess.go @@ -207,6 +207,28 @@ func (p *LinuxProcess) GetCpuUsage() (CpuUsage, error) { }, nil } +func (p *LinuxProcess) GetVirtualMem() (int64, error) { + cmd := fmt.Sprintf(`grep VmSize /proc/%d/status | awk '{print $2}'`, p.Pid) + output, err := exec.Command("bash", "-c", cmd).Output() + if err != nil { + if err.Error() != "signal: interrupt" { + return 0, fmt.Errorf("failed executing command %s: %s", cmd, err) + } + } + + outputStr := strings.Trim(string(output), "\n ") + if len(output) == 0 { + return 0, nil + } + + memUsage, err := strconv.ParseInt(outputStr, 10, 64) + if err != nil { + return 0, fmt.Errorf("failed to convert output %q to int: %w", outputStr, err) + } + + return memUsage, nil +} + func (p *LinuxProcess) GetMemoryUsage() (MemoryUsage, error) { emptymu := MemoryUsage{} @@ -219,10 +241,15 @@ func (p *LinuxProcess) GetMemoryUsage() (MemoryUsage, error) { return emptymu, fmt.Errorf("failed getting process rss with swap: %w", err) } rssSwap := rss + swap + virtMem, err := p.GetVirtualMem() + if err != nil { + return emptymu, fmt.Errorf("failed getting process virtual memory: %w", err) + } return MemoryUsage{ Rss: rss, RssSwap: rssSwap, + Virtual: virtMem, }, nil } diff --git a/internal/process/process.go b/internal/process/process.go index 955ebf9..d7d3425 100644 --- a/internal/process/process.go +++ b/internal/process/process.go @@ -13,6 +13,7 @@ import ( type MemoryUsage struct { Rss int64 `json:"rss"` RssSwap int64 `json:"rssSwap"` + Virtual int64 `json:"virtual"` } type CpuUsage struct { diff --git a/main.go b/main.go index ae8d53f..0f99e40 100644 --- a/main.go +++ b/main.go @@ -84,6 +84,7 @@ Flags This is used with -live and -html. The profiler automatically opens the file in your browser. `) pretty := flag.Bool("pretty", false, "Print in a more human-friendly - non-csv format, and print the pid of the running process.") + showConsole := flag.Bool("console", true, "Show the console output of the process") flag.Parse() @@ -153,6 +154,7 @@ Flags ChartLiveUpdates: *live, NoProfilerOutput: *noOutput, Pretty: *pretty, + ShowConsole: *showConsole, }) a.Start() }