Facebook Linkedin Twitter
Posted Sat Dec 18, 2021 •  Reading time: 3 minutes

Analyzing Application Metrics in Golang

The analysis of memory consumption, CPU usage, and others are becoming more and more relevant to ensure the application is healthy over the time.

Metrics tell us a history on how the services have been behaving in a period of time, allowing for example the configuration of some alarms that are triggered when something is not going well with the system.

Today’s article brings some ways developers can get information from a golang application.

Memory analysis

The memory consumption analysis may be performed using the Golnag native library runtime.

var m runtime.MemStats
runtime.ReadMemStats(&m)

ReadMemStats function used to store execution time statistics in the variable m runtime.MemStats, which contains some of the following fields:

  • HeapAlloc: total of heap memory allocated. It may contain non-freed memory by the garbage collector.
  • HeapInuse: total of heap still in use.
  • TotalAlloc: cumulative quantity of allocated heap over the process execution.
  • Sys: total of memory allocated from the OS.
  • NumGC: gargabe collector completed cycles.
  • and more…

The following code shows an example of how to show memory information during the code execution:

package main

import (
	"fmt"
	"os"
	"os/signal"
	"runtime"
	"time"
)

type runner struct {
	closeSignal chan os.Signal
	ticker      *time.Ticker
	data        [][]int
}

func main() {
	showInfo()

	signals := make(chan os.Signal, 1)
	signal.Notify(signals, os.Interrupt)

	run := runner{
		ticker:      time.NewTicker(1 * time.Second),
		data:        [][]int{},
		closeSignal: signals,
	}

	go func() {
		for {
			<-run.closeSignal

			run.data = nil
			showInfo()

			runtime.GC()
			showInfo()
			os.Exit(0)
		}
	}()

	for {
		showInfo()
		select {
		case _ = <-run.ticker.C:
			a := make([]int, 0, 999999)
			run.data = append(run.data, a)
		}
	}
}

func showInfo() {
	var m runtime.MemStats
	runtime.ReadMemStats(&m)

	fmt.Printf("HeapAlloc = %v mb", toMb(m.HeapAlloc))
	fmt.Printf("\tHeapInuse = %v mb", toMb(m.HeapInuse))
	fmt.Printf("\tTotalAlloc = %v mb", toMb(m.TotalAlloc))
	fmt.Printf("\tSys = %v mb", toMb(m.Sys))
	fmt.Printf("\tNumGC = %v\n", m.NumGC)
}

func toMb(b uint64) uint64 {
	return b / 1024 / 1024
}

Let’s dig into the showInfo method. It is responsible to take a memory information snapshot in realtime. As mentioned before, some data about heap usage (lines 56-58) and garbage collector (line 60) are presented to the user.

On the line 47, there is a simulation of memory allocation over the time. The increasing usage may be visualized in the image below:

Memory usage observation using runtime library

The last line in the image presents a drastic variation of HeapAlloc and HeapInuse informations. It happens that the last but one action is to force the garbage collector to run (runtime.GC), cleaning all the non-used heap and freeing memory to the OS. Memory is not used anymore when on the line 33 the map is disassociated to the memory location it was pointing to. In that way the garbage collector realize some memory won’t be utilized and frees it to the OS.

CPU analysis

For the CPU analysis, the best choice so far is to make use of the library gopsutil. The developer can use gopsutil to retrieve some CPU metrics as CPU model, family, core number, and son on.

The library is pretty straightfoward to use. All the massive code to get CPU information from the OS are already in place, so the developer don’t need to waste too much time coding anything.

package main

import (
	"fmt"

	"github.com/shirou/gopsutil/cpu"
)

func main() {
	cpuStat, _ := cpu.Times(true)

	for _, ce := range cpuStat {
		fmt.Println(ce)
	}

	fmt.Println("")
	fmt.Println("")

	infoStat, _ := cpu.Info()
	for _, ce := range infoStat {
		fmt.Println(ce)
	}

	fmt.Println("")
	fmt.Println("")
}

The usage is as simple as it looks like above. On the line 10, the method Times returns informations like CPU iddle time, CPU usage by user processes, CPU usage by OS processes.

On the other hand, the method Info retrieves the chip supplier, core number, processor name and model.

CPU informations using gopsutil

Conclusions

The system information analysis is an essential task to understand how the services are behaving, and if the have enough resources or they are making good use of them.

Golang language has in-place functionalities to memory analysis, through the runtime library. In the CPU analysis case, there is a third-party library called gopsutil which brings all-in-place tools to do the hard work.