👋 We wrote this to figure out why our CI had slowed down a bit and found something was accidentally compiling in much more Kubernetes than an offline CLI needed. If you enjoy writing performant Go too, please do considering joining us in the fight against fraud: ravelin.com/careers.
actiongraph
is a CLI for investigating where go build
is spending its time
compiling. It consumes the file written using the compile.json output by go build -debug-actiongraph=compile.json
, which includes information about compile
steps and dependencies between packages.
actiongraph
can help you identify which packages take a lot of compilation
time with top
, summarise where that time is spent with tree
, and identify
where the dependencies come from with graph --why
.
Alongside the -debug-actiongraph
flag is the -debug-trace
flag which this
program does not use, but is a similarly instructive tool which can help you
identify parallelism and build order. For completeness, it is also shown below.
ℹ Both the
-debug-actiongraph
and-debug-trace
flags are not supported and deliberately not documented by the Go team: https://github.com/golang/go/commit/8fce59eab5cb2facfafca89e047b4b43ba44785f.
go install github.com/icio/actiongraph@latest
# Optionally clear your go build cache, if you're wanting to understanding why
# a build in CI is taking a while (depending on its cache reuse):
go clean -cache
# Compile your program using the undocumented -debug-actiongraph flag:
go build -debug-actiongraph=compile.json ./my-prog
# Show the slowest individual packages:
actiongraph top -f compile.json
# Show aggregate time spent compiling nested packages:
actiongraph tree -f compile.json
# Show aggregate time spent compiling github packages:
actiongraph tree -f compile.json -L 2 github.com
# Render dependency diagrams of packages, focusing on why PKG was compiled in:
actiongraph graph --why PKG -f compile.json > compile-pkg.dot
dot -Tsvg -Grankdir=LR < compile-pkg.dot > compile-pkg.svg
In this example, we're going to look inside one of @icio's favourite CLIs, https://k9scli.io/.
First, let's check that we're all set up:
go version
actiongraph -h
then create our demo space:
mkdir demo
cd demo
We want to investigate a fresh build, so let's clear the build cache:
go clean -cache
Now we compile. We're going to specify body -debug-actiongraph
and -debug-trace
here so we can compare the results. (And we're going to send the compiled binary into a temporary directory.)
GOBIN=$(mktemp -d) go install -debug-actiongraph=k9s.json -debug-trace=k9s-trace.json github.com/derailed/k9s@v0.27.4
We should have a new file called k9s.json
describing the actiongraph. Let's take a look at the first few entries jq '.[:5] | .[]' -c < k9s.json
:
{"ID":0,"Mode":"go install","Package":"","Deps":[1],"Priority":1190,"TimeReady":"2023-05-12T09:24:39.647607494+01:00","TimeStart":"2023-05-12T09:24:39.647610965+01:00","TimeDone":"2023-05-12T09:24:39.647611018+01:00","Cmd":null}
{"ID":1,"Mode":"link-install","Package":"github.com/derailed/k9s","Deps":[2],"Objdir":"/tmp/go-build3966251150/b001/","Target":"/tmp/tmp.WIZyR7yX3Y/k9s","Priority":1189,"Built":"/tmp/tmp.WIZyR7yX3Y/k9s","BuildID":"LZKyYv0RT8N-_YjcRQ1R/PsuartwNDnqAz2PYjtcW/lvWIs0IgoEnXVRitK8yx/ClYoHVGOEFtTZ-Gnzq2h","TimeReady":"2023-05-12T09:24:39.647340894+01:00","TimeStart":"2023-05-12T09:24:39.647348289+01:00","TimeDone":"2023-05-12T09:24:39.647607252+01:00","Cmd":null}
{"ID":2,"Mode":"link","Package":"github.com/derailed/k9s","Deps":[3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,503,504,505,506,507,508,509,510,511,512,513,514,515,516,517,518,519,520,521,522,523,524,525,526,527,528,529,530,531,532,533,534,535,536,537,538,539,540,541,542,543,544,545,546,547,548,549,550,551,552,553,554,555,556,557,558,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602,603,604,605,606,607,608,609,610,611,612,613,614,615,616,617,618,619,620,621,622,623,624,625,626,627,628,629,630,631,632,633,634,635,636,637,638,639,640,641,642,643,644,645,646,647,648,649,650,651,652,653,654,655,656,657,658,659,660,661,662,663,664,665,666,667,668,669,670,671,672,673,674,675,676,677,678,679,680,681,682,683,684,685,686,687,688,689,690,691,692,693,694,695,696,697,698,699,700,701,702,703,704,705,706,707,708,709,710,711,712,713,714,715,716,717,718,719,720,721,722,723,724,725,726,727,728,729,730,731,732,733,734,735,736,737,738,739,740,741,742,743,744,745,746,747,748,749,750,751,752,753,754,755,756,757,758,759,760,761,762,763,764,765,766,767,768,769,770,771,772,773,774,775,776,777,778,779,780,781,782,783,784,785,786,787,788,789,790,791,792,793,794,795,796,797,798,799,800,801,802,803,804,805,806,807,808,809,810,811,812,813,814,815,816,817,818,819,820,821,822,823,824,825,826,827,828,829,830,831,832,833,834,835,836,837,838,839,840,841,842,843,844,845,846,847,848,849,850,851,852,853,854,855,856,857,858,859,860,861,862,863,864,865,866,867,868,869,870,871,872,873,874,875,876,877,878,879,880,881,882,883,884,885,886,887,888,889,890,891,892,893,894,895,896,897,898,899,900,901,902,903,904,905,906,907,908,909,910,911,912,913,914,915,916,917,918,919,920,921,922,923,924,925,926,927,928,929,930,931,932,933,934,935,936,937,938,939,940,941,942,943,944,945,946,947,948,949,950,951,952,953,954,955,956,957,958,959,960,961,962,963,964,965,966,967,968,969,970,971,972,973,974,975,976,977,978,979,980,981,982,983,984,985,986,987,988,989,990,991,992,993,994,995,996,997,998,999,1000,1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1011,1012,1013,1014,1015,1016,1017,1018,1019,1020,1021,1022,1023,1024,1025,1026,1027,1028,1029,1030,1031,1032,1033,1034,1035,1036,1037,1038,1039,1040,1041,1042,1043,1044,1045,1046,1047,1048,1049,1050,1051,1052,1053,1054,1055,1056,1057,1058,1059,1060,1061,1062,1063,1064,1065,1066,1067,1068,1069,1070,1071,1072,1073,1074,1075,1076,1077,1078,1079,1080,1081,1082,1083,1084,1085,1086,1087,1088,1089,1090,1091,1092,1093,1094,1095,1096,1097,1098,1099,1100,1101,1102,1103,1104,1105,1106,1107,1108,1109,1110,1111,1112,1113,1114,1115,1116,1117,1118,1119,1120,1121,1122,1123,1124,1125,1126,1127,1128,1129,1130,1131,1132,1133,1134,1135,1136,1137,1138,1139,1140,1141,1142,1143,1144,1145,1146,1147,1148,1149,1150,1151,1152,1153,1154,1155,1156,1157,1158,1159,1160,1161,1162,1163,1164,1165,1166,1167,1168,1169,1170,1171,1172,1173,1174,1175,1176,1177,1178,1179,1180,1181,1182,1183,1184,1185,1186,1187,1188],"Objdir":"/tmp/go-build3966251150/b001/","Target":"/tmp/go-build3966251150/b001/exe/a.out","Priority":1188,"Built":"/tmp/go-build3966251150/b001/exe/a.out","ActionID":"LZKyYv0RT8N-_YjcRQ1R","BuildID":"LZKyYv0RT8N-_YjcRQ1R/PsuartwNDnqAz2PYjtcW/lvWIs0IgoEnXVRitK8yx/ClYoHVGOEFtTZ-Gnzq2h","TimeReady":"2023-05-12T09:24:33.62117428+01:00","TimeStart":"2023-05-12T09:24:33.621181576+01:00","TimeDone":"2023-05-12T09:24:39.647340491+01:00","Cmd":["/home/icio/sdk/go/pkg/tool/linux_amd64/link -o /tmp/go-build3966251150/b001/exe/a.out -importcfg /tmp/go-build3966251150/b001/importcfg.link -X=runtime.godebugDefault=panicnil=1 -buildmode=exe -buildid=LZKyYv0RT8N-_YjcRQ1R/PsuartwNDnqAz2PYjtcW/lvWIs0IgoEnXVRitK8yx/LZKyYv0RT8N-_YjcRQ1R -extld=gcc /tmp/go-build3966251150/b001/_pkg_.a"],"CmdReal":5757072143,"CmdUser":6457235000,"CmdSys":853740000}
{"ID":3,"Mode":"build","Package":"github.com/derailed/k9s","Deps":[4,5,6,7,8,1189],"Objdir":"/tmp/go-build3966251150/b001/","Priority":1187,"NeedBuild":true,"ActionID":"PsuartwNDnqAz2PYjtcW","BuildID":"PsuartwNDnqAz2PYjtcW/lvWIs0IgoEnXVRitK8yx","TimeReady":"2023-05-12T09:24:33.604116889+01:00","TimeStart":"2023-05-12T09:24:33.604117711+01:00","TimeDone":"2023-05-12T09:24:33.621174013+01:00","Cmd":["/home/icio/sdk/go/pkg/tool/linux_amd64/compile -o /tmp/go-build3966251150/b001/_pkg_.a -trimpath \"/tmp/go-build3966251150/b001=>\" -p main -lang=go1.20 -complete -buildid PsuartwNDnqAz2PYjtcW/PsuartwNDnqAz2PYjtcW -c=4 -nolocalimports -importcfg /tmp/go-build3966251150/b001/importcfg -pack /home/icio/go/pkg/mod/github.com/derailed/k9s@v0.27.4/main.go"],"CmdReal":15611237,"CmdUser":16273000}
{"ID":4,"Mode":"build","Package":"flag","Deps":[9,10,11,12,13,14,8,15,16,17,18],"Objdir":"/tmp/go-build3966251150/b002/","Priority":44,"NeedBuild":true,"ActionID":"fhRdV7jbdbpj3WwhqK7M","BuildID":"fhRdV7jbdbpj3WwhqK7M/fpr490Yv8t_PFttE-9YV","TimeReady":"2023-05-12T09:23:48.146874388+01:00","TimeStart":"2023-05-12T09:23:48.146904614+01:00","TimeDone":"2023-05-12T09:23:48.29620602+01:00","Cmd":["/home/icio/sdk/go/pkg/tool/linux_amd64/compile -o /tmp/go-build3966251150/b002/_pkg_.a -trimpath \"/tmp/go-build3966251150/b002=>\" -p flag -std -complete -buildid fhRdV7jbdbpj3WwhqK7M/fhRdV7jbdbpj3WwhqK7M -c=4 -nolocalimports -importcfg /tmp/go-build3966251150/b002/importcfg -pack /home/icio/sdk/go/src/flag/flag.go"],"CmdReal":140280033,"CmdUser":144616000,"CmdSys":22834000}
So let's find the compile steps that took the longest:
$ actiongraph -f k9s.json top
9.016s 2.75% build k8s.io/api/core/v1
6.026s 4.59% link github.com/derailed/k9s
5.071s 6.13% build github.com/aws/aws-sdk-go/service/s3
3.620s 7.23% build github.com/aws/aws-sdk-go/aws/endpoints
3.474s 8.29% build net/http
3.215s 9.27% build net
2.869s 10.15% build github.com/google/gnostic/openapiv2
2.846s 11.02% build github.com/google/gnostic/openapiv3
2.581s 11.80% build k8s.io/apimachinery/pkg/apis/meta/v1
2.476s 12.56% build google.golang.org/protobuf/internal/impl
2.186s 13.22% build github.com/gogo/protobuf/proto
1.974s 13.83% build k8s.io/api/extensions/v1beta1
1.919s 14.41% build github.com/derailed/tview
1.898s 14.99% build k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1
1.840s 15.55% build github.com/klauspost/compress/zstd
1.796s 16.10% build github.com/derailed/k9s/internal/view
1.671s 16.61% build k8s.io/kubectl/pkg/describe
1.658s 17.11% build github.com/prometheus/procfs
1.624s 17.61% build k8s.io/api/apps/v1
1.596s 18.09% build runtime
By default, actiongraph top
will show the 20 slowest steps. This may be
overridden using the -n0
flag. For example, to get the fastest steps we could
check:
$ actiongraph -f k9s.json top -n0 | tail -5
0.006s 100.00% build google.golang.org/protobuf/internal/flags
0.000s 100.00% link-install github.com/derailed/k9s
0.000s 100.00% nop
0.000s 100.00% built-in package unsafe
0.000s 100.00% go install
Here we can see that the second column is showing the cumulative percentage of time spent up to that package.
We can summarise the time spent within package domain/directories using the
tree
subcommand:
$ actiongraph -f k9s.json tree -L 1
322.013s (root)
154.673s k8s.io
83.357s github.com
40.294s std
12.338s sigs.k8s.io
10.886s golang.org
7.178s google.golang.org
5.413s helm.sh
2.428s gopkg.in
2.344s go.starlark.net
1.702s go.opentelemetry.io
1.400s oras.land
We can see that we spent 322 seconds compiling k9s, though some of that compilation would have happened in parallel, and so the
We've rolled up all standard libraries under std
:
$ actiongraph -f k9s.json tree encoding
322.013s (root)
40.294s std
1.942s 0.017s std/encoding
0.572s 0.572s std/encoding/json
0.564s 0.564s std/encoding/xml
0.207s 0.207s std/encoding/asn1
0.192s 0.192s std/encoding/binary
0.096s 0.096s std/encoding/csv
0.080s 0.080s std/encoding/hex
0.074s 0.074s std/encoding/base64
0.073s 0.073s std/encoding/pem
0.067s 0.067s std/encoding/base32
Let's look at which github repos are taking the longest to compile:
$ actiongraph -f k9s.json tree github.com -L 2 | head -15
322.013s (root)
83.357s github.com
17.307s github.com/aws
17.307s github.com/aws/aws-sdk-go
16.360s github.com/derailed
7.674s 0.017s github.com/derailed/k9s
4.517s github.com/derailed/popeye
2.250s github.com/derailed/tcell
1.919s 1.919s github.com/derailed/tview
8.206s github.com/google
6.524s github.com/google/gnostic
1.014s github.com/google/go-cmp
0.277s 0.277s github.com/google/btree
0.163s 0.115s github.com/google/gofuzz
0.150s 0.150s github.com/google/uuid
We saw from top
that package github.com/aws/aws-sdk-go/service/s3 was one of the slowest to compile. To understand why, we're going to use the graph
subcommand which can filter down the dependency list to highlight all import paths leading from our build target to the package indicated by --why PKG
:
actiongraph -f k9s.json graph --why github.com/aws/aws-sdk-go/service/s3 > k9s-s3.dot
and then render it using Graphviz's dot
:
dot -Tsvg < k9s-s3.dot > k9s-s3.svg
which looks something like this:
Lastly we can open our k9s-trace.json in https://ui.perfetto.dev/ or Google Chrome/Chromium's chrome://tracing. This shows us how successfully the scheduler utilises the multiple cores on our machine when compiling each package:
At the time of writing, -debug-trace
is under development in
golang/go#38714.