ℹ️ This article is based on Go 1.13.
go test command provides many great features like code coverage, CPU and memory profiling. Providing those stats will require Go to have a way to track the CPU usage or when a function is being used for the code coverage.
Go uses many ways to produce those stats:
- It dynamically inserts instrumentation statements that allow it to track when the code enters in a function or a condition. This strategy is used through code coverage.
- Records a sample of the programs about multiple times per second. This strategy is used in CPU profiling.
- Using static hooks in the code in order to call, during the execution, the functions it needs. This strategy is used in memory profiling.
Let’s write a simple program and review all of them. Here is the code we will use in the next sections:
The SSA code generation, via the command
GOSSAFUNC=run go test -cover, will allow us to see how the program is modified by Go:
GoCover_0_313837343662366134383538 is an array of flags where each key is a section of the code and the flag is set to
1 when the code actually enters in this section.
You can find more information about SSA in my article “Go: Compiler Phases.”
This generated code will be used later in the function that manages the report of the code coverage. We can verify it by disassembling the object file generated during the code coverage thanks to the command
go test -cover -o main.o && go tool objdump main.go will disassemble the code and show the missing pieces. It first initializes and registers the coverage in an auto-generated init function:
Then, the tests will collect the coverage during the execution, as seen previously, and will trigger a method to actually write and display the coverage:
The strategy to track the usage of the CPU is a bit different. Go will stop the program and collect samples of the running program. Here are the traces of the code without CPU profiling:
Here is the same tracing with CPU profiling:
The additional traces are related to
pprof and profiling. Here is a zoom on one of them:
profileWriter will loop and collect CPU data every 100 milliseconds to finally dump the report when the profiling will be turned off.
The memory profiling is included in the source code and already integrated into the allocation system. The memory allocator, located in malloc.go, will profile the allocated memory if the profiling has been turned on thanks to the flag
-memprofile. Here again, it can also be confirmed with the disassembly code. Here is the memory allocator usage:
You can find more information about the test package in my article “Go: Unknown Parts of the Test Package.”