sweet: add sweet benchmarks

These aren't yet fully functional because the assets aren't yet public.
Later CLs will enable use of assets and add tests. This is basically
just an export of the internal version, with import paths updated so
that it builds.

It also makes a very tiny change to the way the help string is printed
to avoid issues with go vet, and places all the gVisor-related code
behind a Linux build tag so that the subdirectory at least builds on
other platforms.

Change-Id: Ia0d733accfba73fa64dac3b0bd31ee82cad6d92f
Reviewed-on: https://go-review.googlesource.com/c/benchmarks/+/368195
Trust: Michael Knyszek <mknyszek@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Michael Pratt <mpratt@google.com>
diff --git a/go.mod b/go.mod
index 2e6dd58..f4b739b 100644
--- a/go.mod
+++ b/go.mod
@@ -3,6 +3,24 @@
 go 1.16
 
 require (
+	cloud.google.com/go/storage v1.18.2
 	github.com/BurntSushi/toml v0.3.1
-	golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0
+	github.com/biogo/biogo v1.0.4
+	github.com/biogo/graph v0.0.0-20150317020928-057c1989faed
+	github.com/biogo/store v0.0.0-20201120204734-aad293a2328f
+	github.com/blevesearch/bleve v1.0.14
+	github.com/dustin/go-wikiparse v0.0.0-20211018054215-c01ec186f20c
+	github.com/fogleman/fauxgl v0.0.0-20200818143847-27cddc103802
+	github.com/fogleman/pt v0.0.0-20170619012416-6fa0015c2178
+	github.com/fogleman/simplify v0.0.0-20170216171241-d32f302d5046 // indirect
+	github.com/gomodule/redigo v1.8.5
+	github.com/google/pprof v0.0.0-20211122183932-1daafda22083
+	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
+	github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
+	github.com/opencontainers/runtime-spec v1.0.2
+	github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9
+	gitlab.com/golang-commonmark/markdown v0.0.0-20211110145824-bf3e522c626a
+	golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8
+	golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359
+	google.golang.org/api v0.60.0
 )
diff --git a/go.sum b/go.sum
index e33e1b2..1f2ea2c 100644
--- a/go.sum
+++ b/go.sum
@@ -1,4 +1,749 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
+cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
+cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
+cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
+cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
+cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
+cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
+cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
+cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
+cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
+cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
+cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
+cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
+cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
+cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
+cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
+cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
+cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
+cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
+cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
+cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
+cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
+cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
+cloud.google.com/go v0.97.0 h1:3DXvAyifywvq64LfkKaMOmkWPS1CikIQdMe2lY9vxU8=
+cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
+cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
+cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
+cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
+cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
+cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
+cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
+cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
+cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
+cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
+cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
+cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
+cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
+cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
+cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
+cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
+cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
+cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
+cloud.google.com/go/storage v1.18.2 h1:5NQw6tOn3eMm0oE8vTkfjau18kjL79FlMjy/CHTpmoY=
+cloud.google.com/go/storage v1.18.2/go.mod h1:AiIj7BWXyhO5gGVmYJ+S8tbkCx3yb0IMjua8Aw4naVM=
+dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0 h1:xrCZDmdtoloIiooiA9q0OQb9r8HejIHYoHGhGCe1pGg=
-golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
+github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
+github.com/RoaringBitmap/roaring v0.4.23 h1:gpyfd12QohbqhFO4NVDUdoPOCXsyahYRQhINmlHxKeo=
+github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
+github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
+github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
+github.com/biogo/biogo v1.0.4 h1:I+FV8WHty5o6pk1VWZxwFETJDcd25GKcGsghMTeQgCY=
+github.com/biogo/biogo v1.0.4/go.mod h1:WlqzR+oIOt6UKRqDbDsbLm7zHe4+FLLDd9iFTrnfloc=
+github.com/biogo/boom v0.0.0-20150317015657-28119bc1ffc1/go.mod h1:fwtxkutinkQcME9Zlywh66T0jZLLjgrwSLY2WxH2N3U=
+github.com/biogo/graph v0.0.0-20150317020928-057c1989faed h1:g46/nB/R498zw0GxBjqY6tES+V6VmktZGZZEvSo6bw4=
+github.com/biogo/graph v0.0.0-20150317020928-057c1989faed/go.mod h1:UuyD2swDzTz1ChZTQld42mP5pyePLSDccmGycTpxRew=
+github.com/biogo/hts v1.1.0/go.mod h1:6C9MdMt9ALD5PsluK5n0B0svHOpmVse3UjQQx/cTgOw=
+github.com/biogo/store v0.0.0-20200104231603-2c6ad937eb83/go.mod h1:wdbXg77soR6ESRprAMEwAQDFtLT6EAGF5o1GRy0cB5k=
+github.com/biogo/store v0.0.0-20201120204734-aad293a2328f h1:+6okTAeUsUrdQr/qN7fIODzowrjjCrnJDg/gkYqcSXY=
+github.com/biogo/store v0.0.0-20201120204734-aad293a2328f/go.mod h1:z52shMwD6SGwRg2iYFjjDwX5Ene4ENTw6HfXraUy/08=
+github.com/blevesearch/bleve v1.0.14 h1:Q8r+fHTt35jtGXJUM0ULwM3Tzg+MRfyai4ZkWDy2xO4=
+github.com/blevesearch/bleve v1.0.14/go.mod h1:e/LJTr+E7EaoVdkQZTfoz7dt4KoDNvDbLb8MSKuNTLQ=
+github.com/blevesearch/blevex v1.0.0 h1:pnilj2Qi3YSEGdWgLj1Pn9Io7ukfXPoQcpAI1Bv8n/o=
+github.com/blevesearch/blevex v1.0.0/go.mod h1:2rNVqoG2BZI8t1/P1awgTKnGlx5MP9ZbtEciQaNhswc=
+github.com/blevesearch/cld2 v0.0.0-20200327141045-8b5f551d37f5/go.mod h1:PN0QNTLs9+j1bKy3d/GB/59wsNBFC4sWLWG3k69lWbc=
+github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo=
+github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M=
+github.com/blevesearch/mmap-go v1.0.2 h1:JtMHb+FgQCTTYIhtMvimw15dJwu1Y5lrZDMOFXVWPk0=
+github.com/blevesearch/mmap-go v1.0.2/go.mod h1:ol2qBqYaOUsGdm7aRMRrYGgPvnwLe6Y+7LMvAB5IbSA=
+github.com/blevesearch/segment v0.9.0 h1:5lG7yBCx98or7gK2cHMKPukPZ/31Kag7nONpoBt22Ac=
+github.com/blevesearch/segment v0.9.0/go.mod h1:9PfHYUdQCgHktBgvtUOF4x+pc4/l8rdH0u5spnW85UQ=
+github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s=
+github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs=
+github.com/blevesearch/zap/v11 v11.0.14 h1:IrDAvtlzDylh6H2QCmS0OGcN9Hpf6mISJlfKjcwJs7k=
+github.com/blevesearch/zap/v11 v11.0.14/go.mod h1:MUEZh6VHGXv1PKx3WnCbdP404LGG2IZVa/L66pyFwnY=
+github.com/blevesearch/zap/v12 v12.0.14 h1:2o9iRtl1xaRjsJ1xcqTyLX414qPAwykHNV7wNVmbp3w=
+github.com/blevesearch/zap/v12 v12.0.14/go.mod h1:rOnuZOiMKPQj18AEKEHJxuI14236tTQ1ZJz4PAnWlUg=
+github.com/blevesearch/zap/v13 v13.0.6 h1:r+VNSVImi9cBhTNNR+Kfl5uiGy8kIbb0JMz/h8r6+O4=
+github.com/blevesearch/zap/v13 v13.0.6/go.mod h1:L89gsjdRKGyGrRN6nCpIScCvvkyxvmeDCwZRcjjPCrw=
+github.com/blevesearch/zap/v14 v14.0.5 h1:NdcT+81Nvmp2zL+NhwSvGSLh7xNgGL8QRVZ67njR0NU=
+github.com/blevesearch/zap/v14 v14.0.5/go.mod h1:bWe8S7tRrSBTIaZ6cLRbgNH4TUDaC9LZSpRGs85AsGY=
+github.com/blevesearch/zap/v15 v15.0.3 h1:Ylj8Oe+mo0P25tr9iLPp33lN6d4qcztGjaIsP51UxaY=
+github.com/blevesearch/zap/v15 v15.0.3/go.mod h1:iuwQrImsh1WjWJ0Ue2kBqY83a0rFtJTqfa9fp1rbVVU=
+github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
+github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403 h1:cqQfy1jclcSy/FwLjemeg3SR1yaINm74aQyupQ0Bl8M=
+github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed h1:OZmjad4L3H8ncOIR8rnb5MREYqG8ixi5+WbeUsquF0c=
+github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
+github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
+github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/couchbase/ghistogram v0.1.0/go.mod h1:s1Jhy76zqfEecpNWJfWUiKZookAFaiGOEoyzgHt9i7k=
+github.com/couchbase/go-couchbase v0.1.0/go.mod h1:+/bddYDxXsf9qt0xpDUtRR47A2GjaXmGGAqQ/k3GJ8A=
+github.com/couchbase/gomemcached v0.1.3/go.mod h1:mxliKQxOv84gQ0bJWbI+w9Wxdpt9HjDvgW9MjCym5Vo=
+github.com/couchbase/goutils v0.1.1/go.mod h1:h89Ek/tiOxxqjz30nPPlwZdQbdB8BwgnuBxeoUe/ViE=
+github.com/couchbase/moss v0.1.0/go.mod h1:9MaHIaRuy9pvLPUJxB8sh8OrLfyDczECVL37grCIubs=
+github.com/couchbase/vellum v1.0.2 h1:BrbP0NKiyDdndMPec8Jjhy0U47CZ0Lgx3xUC2r9rZqw=
+github.com/couchbase/vellum v1.0.2/go.mod h1:FcwrEivFpNi24R3jLOs3n+fs5RnuQnQqCLBJ1uAg1W4=
+github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
+github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d h1:SwD98825d6bdB+pEuTxWOXiSjBrHdOl/UVp75eI7JT8=
+github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8=
+github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM=
+github.com/cznic/strutil v0.0.0-20181122101858-275e90344537/go.mod h1:AHHPPPXTw0h6pVabbcbyGRK1DckRn7r/STdZEeIDzZc=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dustin/go-couch v0.0.0-20160816170231-8251128dab73/go.mod h1:WG/TWzFd/MRvOZ4jjna3FQ+K8AKhb2jOw4S2JMw9VKI=
+github.com/dustin/go-elasticsearch v0.0.0-20120326184656-90a3246b811e/go.mod h1:9FysOb0psgV2Iwm6ocgmg8nc3EsVLfoPc0Sj1HxnVrE=
+github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/dustin/go-wikiparse v0.0.0-20211018054215-c01ec186f20c h1:2LZhS221ttVN6g2xi9u5iTSHs9kZ+B0qkAvpsHlifMc=
+github.com/dustin/go-wikiparse v0.0.0-20211018054215-c01ec186f20c/go.mod h1:BWPhJUMpzRU9dWxw1V78ZKsZgOGSIz8e0bybAoSNoPY=
+github.com/dustin/httputil v0.0.0-20170305193905-c47743f54f89/go.mod h1:ZoDWdnxro8Kesk3zrCNOHNFWtajFPSnDMjVEjGjQu/0=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0 h1:dulLQAYQFYtG5MTplgNGHWuV2D+OBD+Z8lmDBmbLg+s=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
+github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64=
+github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
+github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0=
+github.com/fogleman/fauxgl v0.0.0-20200818143847-27cddc103802 h1:5vdq0jOnV15v1NdZbAcU+dIJ22rFgwaieiFewPvnKCA=
+github.com/fogleman/fauxgl v0.0.0-20200818143847-27cddc103802/go.mod h1:7f7F8EvO8MWvDx9sIoloOfZBCKzlWuZV/h3TjpXOO3k=
+github.com/fogleman/pt v0.0.0-20170619012416-6fa0015c2178 h1:4TnvdTCFXrGh+yjYmzMrrc9V0o29xB/J2pldpxDfUrg=
+github.com/fogleman/pt v0.0.0-20170619012416-6fa0015c2178/go.mod h1:7L0Ag5f6vgYY8ILH9R2Jbn0I4rShliILuSP0kXpxOFQ=
+github.com/fogleman/simplify v0.0.0-20170216171241-d32f302d5046 h1:n3RPbpwXSFT0G8FYslzMUBDO09Ix8/dlqzvUkcJm4Jk=
+github.com/fogleman/simplify v0.0.0-20170216171241-d32f302d5046/go.mod h1:KDwyDqFmVUxUmo7tmqXtyaaJMdGon06y8BD2jmh84CQ=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2 h1:Ujru1hufTHVb++eG6OuNDKMxZnGIvF6o/u8q/8h2+I4=
+github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
+github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31 h1:gclg6gY70GLy3PbkQ1AERPfmLMMagS60DKF78eWwLn8=
+github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
+github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
+github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
+github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
+github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
+github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
+github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
+github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/gomodule/redigo v1.8.5 h1:nRAxCa+SVsyjSBrtZmG/cqb6VbTmuRzpg/PoTFlpumc=
+github.com/gomodule/redigo v1.8.5/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
+github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
+github.com/google/martian/v3 v3.2.1 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1VLQ=
+github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20211122183932-1daafda22083 h1:c8EUapQFi+kjzedr4c6WqbwMdmB95+oDBWZ5XFHFYxY=
+github.com/google/pprof v0.0.0-20211122183932-1daafda22083/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
+github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
+github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
+github.com/googleapis/gax-go/v2 v2.1.1 h1:dp3bWCh+PPO1zjRRiCSczJav13sBvG4UhNyVTa1KqdU=
+github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
+github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99 h1:twflg0XRTjwKpxb/jFExr4HGq6on2dEOmnL6FV+fgPw=
+github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
+github.com/ikawaha/kagome.ipadic v1.1.2/go.mod h1:DPSBbU0czaJhAb/5uKQZHMc9MTVRpDugJfX+HddPHHg=
+github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U=
+github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ=
+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
+github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
+github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
+github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/kljensen/snowball v0.6.0/go.mod h1:27N7E8fVU5H68RlUmnWwZCfxgt4POBJfENGMvNRhldw=
+github.com/kortschak/utter v0.0.0-20190412033250-50fe362e6560/go.mod h1:oDr41C7kH9wvAikWyFhr6UFr8R7nelpmCF5XR5rL7I8=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
+github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
+github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
+github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
+github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
+github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/opencontainers/runtime-spec v1.0.2 h1:UfAcuLBJB9Coz72x1hgl8O5RVzTdNiaglX6v2DM6FI0=
+github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
+github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ=
+github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
+github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
+github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
+github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
+github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
+github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
+github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
+github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
+github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
+github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
+github.com/steveyen/gtreap v0.1.0 h1:CjhzTa274PyJLJuMZwIzCO1PfC00oRa8d1Kc78bFXJM=
+github.com/steveyen/gtreap v0.1.0/go.mod h1:kl/5J7XbrOmlIbYIXdRHDDE5QxHqpk0cmkT7Z4dM9/Y=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
+github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
+github.com/tebeka/snowball v0.4.2/go.mod h1:4IfL14h1lvwZcp1sfXuuc7/7yCsvVffTWxWxCLfFpYg=
+github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c h1:g+WoO5jjkqGAzHWCjJB1zZfXPIAaDpzXIEJ0eS6B5Ok=
+github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8=
+github.com/tinylib/msgp v1.1.0 h1:9fQd+ICuRIu/ue4vxJZu6/LzxN0HwMds2nq/0cFvxHU=
+github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
+github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
+github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
+github.com/willf/bitset v1.1.10 h1:NotGKqX0KwQ72NUzqrjZq5ipPNDQex9lo3WpaS8L2sc=
+github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
+github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
+github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 h1:k/gmLsJDWwWqbLCur2yWnJzwQEKRcAHXo6seXGuSwWw=
+github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA=
+gitlab.com/golang-commonmark/html v0.0.0-20191124015941-a22733972181 h1:K+bMSIx9A7mLES1rtG+qKduLIXq40DAzYHtb0XuCukA=
+gitlab.com/golang-commonmark/html v0.0.0-20191124015941-a22733972181/go.mod h1:dzYhVIwWCtzPAa4QP98wfB9+mzt33MSmM8wsKiMi2ow=
+gitlab.com/golang-commonmark/linkify v0.0.0-20191026162114-a0c2df6c8f82 h1:oYrL81N608MLZhma3ruL8qTM4xcpYECGut8KSxRY59g=
+gitlab.com/golang-commonmark/linkify v0.0.0-20191026162114-a0c2df6c8f82/go.mod h1:Gn+LZmCrhPECMD3SOKlE+BOHwhOYD9j7WT9NUtkCrC8=
+gitlab.com/golang-commonmark/markdown v0.0.0-20211110145824-bf3e522c626a h1:O85GKETcmnCNAfv4Aym9tepU8OE0NmcZNqPlXcsBKBs=
+gitlab.com/golang-commonmark/markdown v0.0.0-20211110145824-bf3e522c626a/go.mod h1:LaSIs30YPGs1H5jwGgPhLzc8vkNc/k0rDX/fEZqiU/M=
+gitlab.com/golang-commonmark/mdurl v0.0.0-20191124015652-932350d1cb84 h1:qqjvoVXdWIcZCLPMlzgA7P9FZWdPGPvP/l3ef8GzV6o=
+gitlab.com/golang-commonmark/mdurl v0.0.0-20191124015652-932350d1cb84/go.mod h1:IJZ+fdMvbW2qW6htJx7sLJ04FEs4Ldl/MDsJtMKywfw=
+gitlab.com/golang-commonmark/puny v0.0.0-20191124015043-9f83538fa04f h1:Wku8eEdeJqIOFHtrfkYUByc4bCaTeA6fL0UJgfEiFMI=
+gitlab.com/golang-commonmark/puny v0.0.0-20191124015043-9f83538fa04f/go.mod h1:Tiuhl+njh/JIg0uS/sOJVYi0x2HEa5rc1OAaVsb5tAs=
+gitlab.com/opennota/wd v0.0.0-20180912061657-c5d65f63c638 h1:uPZaMiz6Sz0PZs3IZJWpU5qHKGNy///1pacZC9txiUI=
+gitlab.com/opennota/wd v0.0.0-20180912061657-c5d65f63c638/go.mod h1:EGRJaqe2eO9XGmFtQCvV3Lm9NLico3UhFwUpCG/+mVU=
+go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
+go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
+go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
+go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
+go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
+go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
+go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
+go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
+golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
+golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
+golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
+golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
+golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
+golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
+golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
+golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420 h1:a8jGStKg0XqKDlKqjLrXn0ioF5MH36pT7Z0BRTqLhbk=
+golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg=
+golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359 h1:2B5p2L5IfGiD7+b9BOoRMC6DgObAVZV+Fsp050NqXik=
+golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
+golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
+golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
+golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
+google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
+google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
+google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
+google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
+google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
+google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
+google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
+google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
+google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
+google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
+google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
+google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
+google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
+google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
+google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
+google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
+google.golang.org/api v0.58.0/go.mod h1:cAbP2FsxoGVNwtgNAmmn3y5G1TWAiVYRmg4yku3lv+E=
+google.golang.org/api v0.60.0 h1:eq/zs5WPH4J9undYM9IP1O7dSr7Yh8Y0GtSCpzGzIUk=
+google.golang.org/api v0.60.0/go.mod h1:d7rl65NZAkEQ90JFzqBjcRq1TVeG5ZoGV3sSpEnnVb4=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
+google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
+google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
+google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
+google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
+google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
+google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
+google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
+google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
+google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
+google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
+google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
+google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
+google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211016002631-37fc39342514/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211021150943-2b146023228c h1:FqrtZMB5Wr+/RecOM3uPJNPfWR8Upb5hAPnt7PU6i4k=
+google.golang.org/genproto v0.0.0-20211021150943-2b146023228c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
+google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
+google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
+google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
+google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
+google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
+google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
+google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q=
+google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
+google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
+google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
+rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
diff --git a/sweet/README.md b/sweet/README.md
new file mode 100644
index 0000000..6545c9b
--- /dev/null
+++ b/sweet/README.md
@@ -0,0 +1,171 @@
+# Sweet: Benchmarking Suite for Go Implementations
+
+Sweet is a set of benchmarks derived from the Go community which are intended
+to represent a breadth of real-world applications. The primary use-case of this
+suite is to perform an evaluation of the difference in CPU and memory
+performance between two Go implementations.
+
+If you use this benchmarking suite for any measurements, please ensure you use
+a versioned release and note the version in the release.
+
+## Quickstart
+
+### Supported Platforms
+
+* Linux
+* TODO(mknyszek): Support more.
+
+### Dependencies
+
+The `sweet` tool only depends on having a stable version of Go and `git`.
+If you're testing out a development version of Go with Sweet, don't build sweet
+itself with it! Use what's installed on your system instead, or some known-stable
+version.
+
+Some benchmarks, however, have various requirements for building. Notably
+they are:
+
+* `make` (tile38)
+* `bash` (tile38)
+* `binutils` (tile38)
+
+Please ensure your system has these tools installed and available in your
+system's PATH.
+
+Furthermore, some benchmarks are able to produce additional information
+on some platforms. For instance, running on platforms where systemd is available
+adds an average RSS measurement for the go-build benchmark.
+
+#### gVisor
+
+The gVisor benchmark has additional requirements:
+* The target platform must be `linux/amd64`. Nothing else is supported or ever
+  will be.
+* The `ptrace` API must be enabled on your system. Set
+  `/proc/sys/kernel/yama/ptrace_scope` appropriately (0 and 1 work, 2 might,
+  3 will not).
+
+### Build
+
+```sh
+$ go build ./cmd/sweet
+```
+
+### Getting Assets
+
+```sh
+$ ./sweet get -auth=app-default
+```
+
+Note: make sure you run `gcloud auth application-default login` before you run
+the above command! 
+
+TODO(mknyszek): Make it so authentication isn't necessary in the future.
+
+### Running the benchmarks
+
+Create a configuration file called `config.toml` with the following contents:
+
+```toml
+[[config]]
+  name = "myconfig"
+  goroot = "<insert some GOROOT here>"
+```
+
+Run the benchmarks by running:
+
+```sh
+$ ./sweet run -shell config.toml
+```
+
+Benchmark results will appear in the `results` directory.
+
+`-shell` will cause the tool to print each action it performs as a shell
+command. Note that while the shell commands are valid for many systems, they
+may depend on tools being available on your system that `sweet` does not
+require (e.g. `git`).
+
+## Tips and Rules of Thumb
+
+* You can expect the benchmarks to take a few hours to run with the default
+  settings on a somewhat sizable Linux box.
+* If a benchmark fails to build, run with `-shell` and copy and re-run the
+  command to get output.
+  TODO(mknyszek): Dump the output to the terminal.
+* If a benchmark fails to run, the output should have been captured in the
+  corresponding results file (e.g. if biogo-igor failed, check
+  `/path/to/results/biogo-igor/myconfig.results`) which is really just the
+  stderr (and usually stdout too) of the benchmark. You can also try to re-run
+  it yourself with the output of `-shell`.
+
+## Memory Requirements
+
+These benchmarks generally try to stress the Go runtime in interesting ways, and
+some may end up with very large heaps. Therefore, it's recommended to run the
+suite on a system with at least 30 GiB of RAM available to minimize the chance
+that results are lost due to an out-of-memory error.
+
+## Configuration Format
+
+The configuration is TOML-based and a more detailed description of fields may
+be found in the help docs for the `run` subcommand:
+
+```sh
+$ ./sweet help run
+```
+
+## Results Format
+
+Results are produced into a single directory containing each benchmark as a
+sub-directory. Within each sub-directory is one file per configuration
+containing the stderr (and usually combined stdout) of the benchmark run,
+which also doubles as the benchmark output format.
+
+All results are reported in the standard Go testing package format, such that
+results may be compared using the
+[benchstat](https://godoc.org/golang.org/x/perf/cmd/benchstat) tool.
+
+Results then may also be composed together for easy viewing. For example, if
+one runs sweet with two configurations named `config1` and `config2`, then to
+quickly compare all results, do:
+
+```sh
+$ cat results/*/config1.results > config1.results
+$ cat results/*/config2.results > config2.results
+$ benchstat config1.results config2.results
+```
+
+## Noise
+
+This benchmark suite tries to keep noise low in measurements where possible.
+* Each measurement is taken against a fresh OS process.
+* Benchmarks have been modified to reduce noise from the input.
+  * All inputs are deterministic, including implicit inputs, such as querying an
+	existing database.
+  * Inputs are loaded into memory when possible instead of streamed from disk.
+* The suite mitigates external effects we can control (e.g. the suite is aware
+  of its co-tenancy with the benchmark and throttles itself when the benchmarks
+  are running).
+
+### Tips for Reducing Noise
+
+* Sweet should be run on a system where a [perflock
+  daemon](https://github.com/aclements/perflock) is running (to avoid noise due
+  to CPU throttling).
+* Avoid running these benchmarks in cloud environments if possible. Generally
+  the noise inherent to those environments can skew A/B tests and hide small
+  changes in performance. See [this paper](https://peerj.com/preprints/3507.pdf)
+  for more details. Try to use dedicated hardware instead.
+
+*Do not* compare results produced by separate invocations of the `sweet` tool.
+
+### Caveats
+
+With the current release there are a few notable caveats when it comes to the
+results and the noisiness of the benchmarks that will addressed with future
+releases. Notably:
+
+* Time for the tile38 benchmarks is quite noisy, but at least consistent.
+* RSS numbers for the gopher-lua and biogo-igor benchmarks are quite noisy.
+* Peak RSS is currently not very reliable.
+* The gVisor startup benchmarks are very noisy.
diff --git a/sweet/assets.hash b/sweet/assets.hash
new file mode 100644
index 0000000..1fa2683
--- /dev/null
+++ b/sweet/assets.hash
@@ -0,0 +1 @@
+{"v0.2.1":"9d1fb576fbf174cfa3e23b6bdba3ca1474e54bf43d5a2645e597bfd4b7a922ce"}
diff --git a/sweet/benchmarks/biogo-igor/README.md b/sweet/benchmarks/biogo-igor/README.md
new file mode 100644
index 0000000..4667ce3
--- /dev/null
+++ b/sweet/benchmarks/biogo-igor/README.md
@@ -0,0 +1,10 @@
+# biogo-igor Benchmark
+
+This directory contains a benchmark that takes pairwise alignment data as
+produced by PALS or krishna, and reports repeat feature family groupings in
+JSON format. Powered by the biogo bioinformatics library.
+
+The benchmark is a modified form of the Igor example which may be found in
+the [biogo examples repository](https://github.com/biogo/examples) and thus
+retains the [BSD-3-clause license](../../third_party/biogo-examples/LICENSE)
+associated.
diff --git a/sweet/benchmarks/biogo-igor/igor.go b/sweet/benchmarks/biogo-igor/igor.go
new file mode 100644
index 0000000..1e787f1
--- /dev/null
+++ b/sweet/benchmarks/biogo-igor/igor.go
@@ -0,0 +1,82 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// igor is a tool that takes pairwise alignment data as produced by PALS or krishna
+// and reports repeat feature family groupings in JSON format.
+package main
+
+import (
+	"bytes"
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"runtime"
+
+	"golang.org/x/benchmarks/sweet/benchmarks/internal/driver"
+	"golang.org/x/benchmarks/third_party/biogo-examples/igor/igor"
+
+	"github.com/biogo/biogo/align/pals"
+	"github.com/biogo/biogo/io/featio/gff"
+)
+
+const (
+	band          = 0.05
+	mergeOverlap  = 0
+	removeOverlap = 0.95
+	requiredCover = 0.95
+	strictness    = 0
+
+	pileDiff  = 0.05
+	imageDiff = 0.05
+)
+
+func main() {
+	driver.SetFlags(flag.CommandLine)
+	flag.Parse()
+	log.SetFlags(0)
+
+	if flag.NArg() != 1 {
+		log.Fatal("error: input GFF file required")
+	}
+
+	data, err := ioutil.ReadFile(flag.Arg(0))
+	if err != nil {
+		log.Fatalf("error: %v", err)
+	}
+	err = driver.RunBenchmark("BiogoIgor", func(_ *driver.B) error {
+		r := bytes.NewReader(data)
+		in := gff.NewReader(r)
+
+		out := bytes.Buffer{}
+		out.Grow(1024 * 1024)
+
+		var pf pals.PairFilter
+		piles, err := igor.Piles(in, mergeOverlap, pf)
+		if err != nil {
+			return fmt.Errorf("piling: %v", err)
+		}
+
+		_, clusters := igor.Cluster(piles, igor.ClusterConfig{
+			BandWidth:         band,
+			RequiredCover:     requiredCover,
+			OverlapStrictness: strictness,
+			OverlapThresh:     removeOverlap,
+			Procs:             runtime.GOMAXPROCS(0),
+		})
+		cc := igor.Group(clusters, igor.GroupConfig{
+			pileDiff,
+			imageDiff,
+			false,
+		})
+		err = igor.WriteJSON(cc, &out)
+		if err != nil {
+			return err
+		}
+		return nil
+	}, driver.InProcessMeasurementOptions...)
+	if err != nil {
+		log.Fatalf("error: %v", err)
+	}
+}
diff --git a/sweet/benchmarks/biogo-krishna/README.md b/sweet/benchmarks/biogo-krishna/README.md
new file mode 100644
index 0000000..79c9a09
--- /dev/null
+++ b/sweet/benchmarks/biogo-krishna/README.md
@@ -0,0 +1,4 @@
+# biogo-krishna Benchmark
+
+This directory contains a benchmark which runs a Go implementation of PALS,
+powered by the biogo bioinformatics library.
diff --git a/sweet/benchmarks/biogo-krishna/krishna.go b/sweet/benchmarks/biogo-krishna/krishna.go
new file mode 100644
index 0000000..1617937
--- /dev/null
+++ b/sweet/benchmarks/biogo-krishna/krishna.go
@@ -0,0 +1,75 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// krishna is a pure Go implementation of Edgar and Myers PALS tool.
+// This version of krishna is modified from its original form and only
+// computes alignment for a sequence against itself.
+package main
+
+import (
+	"bytes"
+	"flag"
+	"log"
+	"runtime"
+
+	"golang.org/x/benchmarks/sweet/benchmarks/internal/driver"
+	"golang.org/x/benchmarks/third_party/biogo-examples/krishna"
+
+	"github.com/biogo/biogo/align/pals"
+)
+
+const (
+	minHitLen  = 400
+	minId      = 0.94
+	tubeOffset = 0
+	tmpChunk   = 1e6
+)
+
+var (
+	alignconc     bool
+	tmpDir        string
+	tmpConcurrent bool
+)
+
+func init() {
+	driver.SetFlags(flag.CommandLine)
+	flag.BoolVar(&alignconc, "alignconc", false, "whether to perform alignment concurrently (2 threads)")
+	flag.StringVar(&tmpDir, "tmp", "", "directory to store temporary files")
+	flag.BoolVar(&tmpConcurrent, "tmpconc", false, "whether to process morass concurrently")
+}
+
+func main() {
+	flag.Parse()
+	log.SetFlags(0)
+
+	if flag.NArg() != 1 {
+		log.Fatal("error: input FASTA target sequence required")
+	}
+	k, err := krishna.New(flag.Arg(0), tmpDir, krishna.Params{
+		TmpChunkSize: 1e6,
+		MinHitLen:    400,
+		MinHitId:     0.94,
+		TubeOffset:   0,
+		AlignConc:    alignconc,
+		TmpConc:      tmpConcurrent,
+	})
+	if err != nil {
+		log.Fatalf("error: %v", err)
+	}
+	defer k.CleanUp()
+	err = driver.RunBenchmark("BiogoKrishna", func(d *driver.B) error {
+		runtime.GC()
+
+		// Make initial buffer size 1 MiB.
+		b := bytes.Buffer{}
+		b.Grow(1024 * 1024)
+		writer := pals.NewWriter(&b, 2, 60, false)
+		d.ResetTimer()
+
+		return k.Run(writer)
+	}, driver.InProcessMeasurementOptions...)
+	if err != nil {
+		log.Fatalf("error: %v", err)
+	}
+}
diff --git a/sweet/benchmarks/bleve-index/README.md b/sweet/benchmarks/bleve-index/README.md
new file mode 100644
index 0000000..2d4c82e
--- /dev/null
+++ b/sweet/benchmarks/bleve-index/README.md
@@ -0,0 +1,9 @@
+# bleve-index Benchmark
+
+This directory contains a benchmark that indexes a small subset of Wikipedia
+into a Bleve search index.
+
+The benchmark is a based loosely on the benchmark contained within the
+[bleve-bench](https://github.com/blevesearch/bleve-bench) repository.
+The parts that were derived from bleve-bench may be found
+[here](../../third_party/bleve-bench/README.md).
diff --git a/sweet/benchmarks/bleve-index/main.go b/sweet/benchmarks/bleve-index/main.go
new file mode 100644
index 0000000..994271c
--- /dev/null
+++ b/sweet/benchmarks/bleve-index/main.go
@@ -0,0 +1,108 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"compress/bzip2"
+	"flag"
+	"fmt"
+	"io"
+	"os"
+
+	"golang.org/x/benchmarks/sweet/benchmarks/internal/driver"
+	blevebench "golang.org/x/benchmarks/third_party/bleve-bench"
+
+	"github.com/blevesearch/bleve"
+	_ "github.com/blevesearch/bleve/analysis/analyzer/keyword"
+	wikiparse "github.com/dustin/go-wikiparse"
+)
+
+var (
+	batchSize int
+	documents int
+)
+
+func init() {
+	driver.SetFlags(flag.CommandLine)
+	flag.IntVar(&batchSize, "batch-size", 256, "number of index requests to batch together")
+	flag.IntVar(&documents, "documents", 1000, "number of documents to index")
+}
+
+func parseFlags() error {
+	flag.Parse()
+	if flag.NArg() != 1 {
+		return fmt.Errorf("expected wiki dump as input")
+	}
+	return nil
+}
+
+func run(wikidump string) error {
+	f, err := os.Open(wikidump)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+
+	z := bzip2.NewReader(f)
+
+	parser, err := wikiparse.NewParser(z)
+	if err != nil {
+		return err
+	}
+
+	articles := make([]blevebench.Article, 0, documents)
+	for d := 0; d < documents; d++ {
+		p, err := parser.Next()
+		if err == io.EOF {
+			break
+		} else if err != nil {
+			return err
+		}
+		if len(p.Revisions) == 0 {
+			continue
+		}
+		articles = append(articles, blevebench.Article{
+			Title: p.Title,
+			Text:  p.Revisions[0].Text,
+		})
+	}
+
+	mapping := blevebench.ArticleMapping()
+	name := fmt.Sprintf("BleveIndexBatch%d", batchSize)
+	return driver.RunBenchmark(name, func(d *driver.B) error {
+		index, err := bleve.NewMemOnly(mapping)
+		if err != nil {
+			return err
+		}
+		b := index.NewBatch()
+		for _, a := range articles {
+			b.Index(a.Title, a)
+			if b.Size() >= batchSize {
+				if err := index.Batch(b); err != nil {
+					return err
+				}
+				b = index.NewBatch()
+			}
+		}
+		if b.Size() != 0 {
+			if err := index.Batch(b); err != nil {
+				return err
+			}
+		}
+		d.StopTimer()
+		return index.Close()
+	}, driver.InProcessMeasurementOptions...)
+}
+
+func main() {
+	if err := parseFlags(); err != nil {
+		fmt.Fprintln(os.Stderr, err)
+		os.Exit(1)
+	}
+	if err := run(flag.Arg(0)); err != nil {
+		fmt.Fprintln(os.Stderr, err)
+		os.Exit(1)
+	}
+}
diff --git a/sweet/benchmarks/bleve-query/README.md b/sweet/benchmarks/bleve-query/README.md
new file mode 100644
index 0000000..6776a92
--- /dev/null
+++ b/sweet/benchmarks/bleve-query/README.md
@@ -0,0 +1,8 @@
+# bleve-query Benchmark
+
+This directory contains a benchmark that queries a pre-built Bleve search index
+containing a small subset of Wikipedia.
+
+The benchmark is a heavily modified derivative of the
+[bleve-bench](https://github.com/blevesearch/bleve-bench) repository and thus
+partially retains its copyright as well as its Apache 2.0 license.
diff --git a/sweet/benchmarks/bleve-query/main.go b/sweet/benchmarks/bleve-query/main.go
new file mode 100644
index 0000000..73dad95
--- /dev/null
+++ b/sweet/benchmarks/bleve-query/main.go
@@ -0,0 +1,56 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"flag"
+	"fmt"
+	"os"
+
+	"golang.org/x/benchmarks/sweet/benchmarks/internal/driver"
+
+	"github.com/blevesearch/bleve"
+	_ "github.com/blevesearch/bleve/analysis/analyzer/keyword"
+)
+
+func parseFlags() error {
+	flag.Parse()
+	if flag.NArg() != 1 {
+		return fmt.Errorf("expected bleve index directory as input")
+	}
+	return nil
+}
+
+func run(idxdir string) error {
+	index, err := bleve.Open(idxdir)
+	if err != nil {
+		return err
+	}
+	return driver.RunBenchmark("BleveQuery", func(_ *driver.B) error {
+		for j := 0; j < 50; j++ {
+			for _, term := range terms {
+				query := bleve.NewTermQuery(term)
+				query.SetField("Text")
+				_, err := index.Search(bleve.NewSearchRequest(query))
+				if err != nil {
+					return err
+				}
+			}
+		}
+		return nil
+	}, driver.InProcessMeasurementOptions...)
+}
+
+func main() {
+	driver.SetFlags(flag.CommandLine)
+	if err := parseFlags(); err != nil {
+		fmt.Fprintln(os.Stderr, err)
+		os.Exit(1)
+	}
+	if err := run(flag.Arg(0)); err != nil {
+		fmt.Fprintln(os.Stderr, err)
+		os.Exit(1)
+	}
+}
diff --git a/sweet/benchmarks/bleve-query/terms.go b/sweet/benchmarks/bleve-query/terms.go
new file mode 100644
index 0000000..a4b0443
--- /dev/null
+++ b/sweet/benchmarks/bleve-query/terms.go
@@ -0,0 +1,299 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+// TODO(mknyszek): Add more terms, approximately 50x more if possible.
+var terms = []string{
+	"water",
+	"education",
+	"time",
+	"pillar",
+	"genetics",
+	"philosophy",
+	"architecture",
+	"chemistry",
+	"literature",
+	"flower",
+	"oil",
+	"airplane",
+	"knowledge",
+	"technology",
+	"web",
+	"programming",
+	"physics",
+	"economics",
+	"garbage",
+	"television",
+	"history",
+	"war",
+	"earth",
+	"postage",
+	"moon",
+	"1962",
+	"basketball",
+	"soccer",
+	"waves",
+	"ocean",
+	"route",
+	"infrastructure",
+	"benchmark",
+	"helmet",
+	"bone",
+	"corn",
+	"taxi",
+	"corner",
+	"coroner",
+	"1981",
+	"1555",
+	"converse",
+	"organization",
+	"linear",
+	"correspondance",
+	"growth",
+	"capitalism",
+	"communism",
+	"show",
+	"passion",
+	"banana",
+	"monday",
+	"march",
+	"robot",
+	"work",
+	"email",
+	"collection",
+	"stamps",
+	"1886",
+	"providence",
+	"intelligence",
+	"alien",
+	"survival",
+	"fear",
+	"joy",
+	"accordion",
+	"tambourine",
+	"whale",
+	"dance",
+	"organism",
+	"beautiful",
+	"grace",
+	"winter",
+	"path",
+	"strawberry",
+	"scarf",
+	"head",
+	"snow",
+	"fall",
+	"rss",
+	"atomic",
+	"hiss",
+	"snake",
+	"ornery",
+	"tedious",
+	"interface",
+	"application",
+	"wikipedia",
+	"dictionary",
+	"thesaurus",
+	"dinosaur",
+	"meteor",
+	"island",
+	"climate",
+	"nuclear",
+	"fallout",
+	"orange",
+	"disagreement",
+	"shock",
+	"lightning",
+	"thunder",
+	"elemental",
+	"substantial",
+	"disagreement",
+	"agreement",
+	"dreams",
+	"muscle",
+	"apple",
+	"trouble",
+	"clash",
+	"quarrel",
+	"quiver",
+	"appearance",
+	"scarecrow",
+	"different",
+	"jar",
+	"form",
+	"style",
+	"tin",
+	"awkward",
+	"sound",
+	"june",
+	"rubbish",
+	"canine",
+	"teeth",
+	"aquatic",
+	"fish",
+	"riddance",
+	"good",
+	"bad",
+	"morality",
+	"quality",
+	"nonsense",
+	"dump",
+	"superlative",
+	"comparison",
+	"horrendous",
+	"fantastic",
+	"excellent",
+	"go",
+	"return",
+	"string",
+	"qubit",
+	"bit",
+	"nibble",
+	"standard",
+	"colloquial",
+	"critic",
+	"disparage",
+	"denounce",
+	"origin",
+	"exceed",
+	"expect",
+	"brain",
+	"context",
+	"abstract",
+	"syntax",
+	"tree",
+	"arbor",
+	"ogre",
+	"monster",
+	"sweater",
+	"clothing",
+	"apparel",
+	"shoe",
+	"foot",
+	"meter",
+	"metric",
+	"ordinary",
+	"artificer",
+	"stone",
+	"granite",
+	"composite",
+	"gopher",
+	"gobble",
+	"turkey",
+	"cranberry",
+	"egg",
+	"red",
+	"green",
+	"yellow",
+	"blue",
+	"purple",
+	"spectrum",
+	"light",
+	"luigi",
+	"space",
+	"deep",
+	"extravagant",
+	"order",
+	"prime",
+	"optimal",
+	"transformer",
+	"vehicle",
+	"halo",
+	"sword",
+	"shield",
+	"border",
+	"land",
+	"lord",
+	"rent",
+	"restore",
+	"curative",
+	"herb",
+	"horror",
+	"love",
+	"first",
+	"dirt",
+	"rescue",
+	"volcano",
+	"avalanche",
+	"tornado",
+	"disaster",
+	"relief",
+	"terror",
+	"rehabilitation",
+	"globe",
+	"second",
+	"third",
+	"direction",
+	"honey",
+	"bee",
+	"thing",
+	"escape",
+	"hive",
+	"horde",
+	"heavy",
+	"soft",
+	"star",
+	"sun",
+	"gas",
+	"astronomy",
+	"astrology",
+	"distant",
+	"speed",
+	"velocity",
+	"acceleration",
+	"mass",
+	"delta",
+	"alpha",
+	"beta",
+	"gamma",
+	"omega",
+	"ray",
+	"resistance",
+	"revolution",
+	"volt",
+	"electron",
+	"proton",
+	"quark",
+	"ignite",
+	"collide",
+	"colloid",
+	"suspension",
+	"mechanical",
+	"force",
+	"torsion",
+	"centripetal",
+	"centrifugal",
+	"orbit",
+	"jupiter",
+	"quantum",
+	"quantitative",
+	"state",
+	"vector",
+	"matrix",
+	"tensor",
+	"calculus",
+	"lambda",
+	"machine",
+	"revolve",
+	"spin",
+	"superposition",
+	"impose",
+	"crater",
+	"trench",
+	"ice",
+	"iceberg",
+	"arctic",
+	"cold",
+	"hot",
+	"degree",
+	"extent",
+	"span",
+	"molten",
+	"core",
+	"surge",
+	"tectonic",
+	"plate",
+	"fascinate",
+}
diff --git a/sweet/benchmarks/fogleman-fauxgl/README.md b/sweet/benchmarks/fogleman-fauxgl/README.md
new file mode 100644
index 0000000..152172a
--- /dev/null
+++ b/sweet/benchmarks/fogleman-fauxgl/README.md
@@ -0,0 +1,5 @@
+# fogleman-fauxgl Benchmark
+
+This directory contains a benchmark of Michael Fogleman's FauxGL library which
+is an OpenGL-esque software rendering pipeline implemented in pure Go. It
+renders a given input mesh 72 times, rotating it by 5 degrees each time.
diff --git a/sweet/benchmarks/fogleman-fauxgl/main.go b/sweet/benchmarks/fogleman-fauxgl/main.go
new file mode 100644
index 0000000..4b969e5
--- /dev/null
+++ b/sweet/benchmarks/fogleman-fauxgl/main.go
@@ -0,0 +1,47 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"flag"
+	"fmt"
+	"image"
+	"os"
+	"runtime"
+
+	"golang.org/x/benchmarks/sweet/benchmarks/internal/driver"
+	animatebench "golang.org/x/benchmarks/third_party/fogleman-fauxgl"
+)
+
+var im image.Image
+
+func main() {
+	driver.SetFlags(flag.CommandLine)
+	flag.Parse()
+
+	if flag.NArg() != 1 {
+		fmt.Fprintln(os.Stderr, "expected input STL file")
+		os.Exit(1)
+	}
+
+	// Load mesh into animation structure.
+	anim, err := animatebench.Load(flag.Arg(0))
+	if err != nil {
+		fmt.Fprintln(os.Stderr, err)
+		os.Exit(1)
+	}
+	err = driver.RunBenchmark("FoglemanFauxGLRenderRotateBoat", func(b *driver.B) error {
+		runtime.GC()
+		b.ResetTimer()
+		for i := 0; i < 360; i += 5 {
+			im = anim.RenderNext()
+		}
+		return nil
+	}, driver.InProcessMeasurementOptions...)
+	if err != nil {
+		fmt.Fprintln(os.Stderr, err)
+		os.Exit(1)
+	}
+}
diff --git a/sweet/benchmarks/fogleman-pt/README.md b/sweet/benchmarks/fogleman-pt/README.md
new file mode 100644
index 0000000..e9e8662
--- /dev/null
+++ b/sweet/benchmarks/fogleman-pt/README.md
@@ -0,0 +1,5 @@
+# fogleman-fauxgl Benchmark
+
+This directory contains a benchmark of Michael Fogleman's `pt` library which
+is a pure-Go path tracing rendering library. It performs a given number of
+rendering iterations on the input mesh (which is expected to be a Go Gopher).
diff --git a/sweet/benchmarks/fogleman-pt/main.go b/sweet/benchmarks/fogleman-pt/main.go
new file mode 100644
index 0000000..ec11d76
--- /dev/null
+++ b/sweet/benchmarks/fogleman-pt/main.go
@@ -0,0 +1,58 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"flag"
+	"fmt"
+	"image"
+	"os"
+	"runtime"
+
+	"golang.org/x/benchmarks/sweet/benchmarks/internal/driver"
+	ptbench "golang.org/x/benchmarks/third_party/fogleman-pt"
+)
+
+var (
+	iter int
+	im   image.Image
+)
+
+func init() {
+	flag.Usage = func() {
+		fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [flags] <gopher.obj>\n", os.Args[0])
+		flag.PrintDefaults()
+	}
+	driver.SetFlags(flag.CommandLine)
+	flag.IntVar(&iter, "iter", 2, "number of iterations to run renderer for")
+}
+
+func main() {
+	flag.Parse()
+
+	if flag.NArg() != 1 {
+		fmt.Fprintln(os.Stderr, "expected one argument: gopher.obj")
+		os.Exit(1)
+	}
+
+	// load and transform gopher mesh
+	gopher, err := ptbench.Load(flag.Arg(0))
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "%v\n", err)
+		os.Exit(1)
+	}
+
+	name := fmt.Sprintf("FoglemanPathTraceRenderGopherIter%d", iter)
+	err = driver.RunBenchmark(name, func(b *driver.B) error {
+		runtime.GC()
+		b.ResetTimer()
+		im = gopher.Render(iter)
+		return nil
+	}, driver.InProcessMeasurementOptions...)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "%v\n", err)
+		os.Exit(1)
+	}
+}
diff --git a/sweet/benchmarks/go-build/main.go b/sweet/benchmarks/go-build/main.go
new file mode 100644
index 0000000..4f652cb
--- /dev/null
+++ b/sweet/benchmarks/go-build/main.go
@@ -0,0 +1,268 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"flag"
+	"fmt"
+	"io"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"strings"
+
+	"github.com/google/pprof/profile"
+	"golang.org/x/benchmarks/sweet/benchmarks/internal/cgroups"
+	"golang.org/x/benchmarks/sweet/benchmarks/internal/driver"
+)
+
+var (
+	goTool    string
+	tmpDir    string
+	toolexec  bool
+	benchName string
+)
+
+func init() {
+	driver.SetFlags(flag.CommandLine)
+	flag.StringVar(&goTool, "go", "", "path to cmd/go binary")
+	flag.StringVar(&tmpDir, "tmp", "", "work directory (cleared before use)")
+	flag.BoolVar(&toolexec, "toolexec", false, "run as a toolexec binary")
+	flag.StringVar(&benchName, "bench-name", "", "for -toolexec")
+}
+
+func tmpResultsDir() string {
+	return filepath.Join(tmpDir, "results")
+}
+
+var benchOpts = []driver.RunOption{
+	driver.DoTime(true),
+}
+
+func run(pkgPath string) error {
+	// Clear any stale results from previous runs and recreate
+	// the directory.
+	if err := os.RemoveAll(tmpResultsDir()); err != nil {
+		return err
+	}
+	if err := os.MkdirAll(tmpResultsDir(), 0777); err != nil {
+		return err
+	}
+
+	name := "GoBuild" + strings.Title(filepath.Base(pkgPath))
+
+	cmdArgs := []string{"build", "-a"}
+
+	// Build a command comprised of this binary to pass to -toolexec.
+	selfPath, err := filepath.Abs(os.Args[0])
+	if err != nil {
+		return err
+	}
+	selfCmd := []string{
+		selfPath, "-toolexec",
+		"-bench-name", name,
+	}
+	flag.CommandLine.Visit(func(f *flag.Flag) {
+		if f.Name == "go" || f.Name == "bench-name" {
+			// No need to pass this along.
+			return
+		}
+		selfCmd = append(selfCmd, "-"+f.Name, f.Value.String())
+	})
+
+	cmdArgs = append(cmdArgs, "-toolexec", strings.Join(selfCmd, " "))
+	var baseCmd *exec.Cmd
+	if driver.ProfilingEnabled(driver.ProfilePerf) {
+		baseCmd = exec.Command("perf", append([]string{"record", "-o", filepath.Join(tmpDir, "perf.data"), goTool}, cmdArgs...)...)
+	} else {
+		baseCmd = exec.Command(goTool, cmdArgs...)
+	}
+	baseCmd.Dir = pkgPath
+	cmd, err := cgroups.WrapCommand(baseCmd, "test.scope")
+	if err != nil {
+		return err
+	}
+	err = driver.RunBenchmark(name, func(d *driver.B) error {
+		return cmd.Run()
+	}, append(benchOpts, driver.DoAvgRSS(cmd.RSSFunc()))...)
+	if err != nil {
+		return err
+	}
+
+	// Handle any CPU profiles produced, and merge them.
+	// Then, write them out to the canonical profiles above.
+	if driver.ProfilingEnabled(driver.ProfileCPU) {
+		compileProfile, err := mergeProfiles(tmpDir, profilePrefix("compile", driver.ProfileCPU))
+		if err != nil {
+			return err
+		}
+		if err := driver.WriteProfile(compileProfile, driver.ProfileCPU, name+"Compile"); err != nil {
+			return err
+		}
+
+		linkProfile, err := mergeProfiles(tmpDir, profilePrefix("link", driver.ProfileCPU))
+		if err != nil {
+			return err
+		}
+		if err := driver.WriteProfile(linkProfile, driver.ProfileCPU, name+"Link"); err != nil {
+			return err
+		}
+	}
+	if driver.ProfilingEnabled(driver.ProfileMem) {
+		if err := copyProfiles(tmpDir, "compile", driver.ProfileMem, name+"Compile"); err != nil {
+			return err
+		}
+		if err := copyProfiles(tmpDir, "link", driver.ProfileMem, name+"Link"); err != nil {
+			return err
+		}
+	}
+	if driver.ProfilingEnabled(driver.ProfilePerf) {
+		if err := driver.CopyProfile(filepath.Join(tmpDir, "perf.data"), driver.ProfilePerf, name); err != nil {
+			return err
+		}
+	}
+	return printOtherResults(tmpResultsDir())
+}
+
+func mergeProfiles(dir, prefix string) (*profile.Profile, error) {
+	profiles, err := collectProfiles(dir, prefix)
+	if err != nil {
+		return nil, err
+	}
+	return profile.Merge(profiles)
+}
+
+func copyProfiles(dir, bin string, typ driver.ProfileType, finalPrefix string) error {
+	profiles, err := collectProfiles(dir, profilePrefix(bin, typ))
+	if err != nil {
+		return err
+	}
+	for _, profile := range profiles {
+		if err := driver.WriteProfile(profile, typ, finalPrefix); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func collectProfiles(dir, prefix string) ([]*profile.Profile, error) {
+	entries, err := os.ReadDir(dir)
+	if err != nil {
+		return nil, err
+	}
+	var profiles []*profile.Profile
+	for _, entry := range entries {
+		name := entry.Name()
+		path := filepath.Join(tmpDir, name)
+		if info, err := entry.Info(); err != nil {
+			return nil, err
+		} else if info.Size() == 0 {
+			// Skip zero-sized files, otherwise the pprof package
+			// will call it a parsing error.
+			continue
+		}
+		if strings.HasPrefix(name, prefix) {
+			p, err := driver.ReadProfile(path)
+			if err != nil {
+				return nil, err
+			}
+			profiles = append(profiles, p)
+			continue
+		}
+	}
+	return profiles, nil
+}
+
+func profilePrefix(bin string, typ driver.ProfileType) string {
+	return bin + "-prof." + string(typ)
+}
+
+func printOtherResults(dir string) error {
+	entries, err := os.ReadDir(dir)
+	if err != nil {
+		return err
+	}
+	for _, entry := range entries {
+		name := entry.Name()
+		path := filepath.Join(dir, name)
+		if strings.HasSuffix(name, ".results") {
+			f, err := os.Open(path)
+			if err != nil {
+				return err
+			}
+			if _, err := io.Copy(os.Stderr, f); err != nil {
+				f.Close()
+				return err
+			}
+			f.Close()
+		}
+	}
+	return nil
+}
+
+func runToolexec() error {
+	var benchSuffix string
+	benchmark := false
+	bin := filepath.Base(flag.Arg(0))
+	switch bin {
+	case "compile":
+	case "link":
+		benchSuffix = "Link"
+		benchmark = true
+	default:
+		cmd := exec.Command(flag.Args()[0], flag.Args()[1:]...)
+		cmd.Stdout = os.Stdout
+		cmd.Stderr = os.Stderr
+		return cmd.Run()
+	}
+	var extraFlags []string
+	for _, typ := range []driver.ProfileType{driver.ProfileCPU, driver.ProfileMem} {
+		if driver.ProfilingEnabled(typ) {
+			// Stake a claim for a filename.
+			f, err := os.CreateTemp(tmpDir, profilePrefix(bin, typ))
+			if err != nil {
+				return err
+			}
+			f.Close()
+			extraFlags = append(extraFlags, "-"+string(typ)+"profile", f.Name())
+		}
+	}
+	cmd := exec.Command(flag.Args()[0], append(extraFlags, flag.Args()[1:]...)...)
+	cmd.Stdout = os.Stdout
+	cmd.Stderr = os.Stderr
+	if benchmark {
+		name := benchName + benchSuffix
+		f, err := os.Create(filepath.Join(tmpResultsDir(), name+".results"))
+		if err != nil {
+			return err
+		}
+		defer f.Close()
+		return driver.RunBenchmark(name, func(d *driver.B) error {
+			return cmd.Run()
+		}, append(benchOpts, driver.WriteResultsTo(f))...)
+	}
+	return cmd.Run()
+}
+
+func main() {
+	flag.Parse()
+
+	if toolexec {
+		if err := runToolexec(); err != nil {
+			fmt.Fprintf(os.Stderr, "error: %v\n", err)
+			os.Exit(1)
+		}
+		return
+	}
+
+	if flag.NArg() != 1 {
+		fmt.Fprintf(os.Stderr, "error: expected one argument\n")
+		os.Exit(1)
+	}
+	if err := run(flag.Arg(0)); err != nil {
+		fmt.Fprintf(os.Stderr, "error: %v\n", err)
+		os.Exit(1)
+	}
+}
diff --git a/sweet/benchmarks/gopher-lua/main.go b/sweet/benchmarks/gopher-lua/main.go
new file mode 100644
index 0000000..d2db860
--- /dev/null
+++ b/sweet/benchmarks/gopher-lua/main.go
@@ -0,0 +1,98 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"bufio"
+	"flag"
+	"fmt"
+	"os"
+	"strings"
+
+	"golang.org/x/benchmarks/sweet/benchmarks/internal/driver"
+
+	lua "github.com/yuin/gopher-lua"
+)
+
+func parseFlags() error {
+	flag.Parse()
+	if flag.NArg() != 2 {
+		return fmt.Errorf("expected lua program and input for it")
+	}
+	return nil
+}
+
+func parseInput(inputfile string) (string, error) {
+	f, err := os.Open(inputfile)
+	if err != nil {
+		return "", err
+	}
+	defer f.Close()
+	var builder strings.Builder
+	scanner := bufio.NewScanner(f)
+	for scanner.Scan() {
+		_, err := builder.Write(scanner.Bytes())
+		if err != nil {
+			return "", err
+		}
+	}
+	return builder.String(), nil
+}
+
+func doBenchmark(s *lua.LState, input lua.LString) error {
+	freq := lua.P{
+		Fn:      s.GetGlobal("frequency"),
+		NRet:    0,
+		Protect: true,
+	}
+	count := lua.P{
+		Fn:      s.GetGlobal("count"),
+		NRet:    0,
+		Protect: true,
+	}
+	if err := s.CallByParam(freq, input, lua.LNumber(1)); err != nil {
+		return err
+	}
+	if err := s.CallByParam(freq, input, lua.LNumber(2)); err != nil {
+		return err
+	}
+	if err := s.CallByParam(count, input, lua.LString("GGT")); err != nil {
+		return err
+	}
+	if err := s.CallByParam(count, input, lua.LString("GGTA")); err != nil {
+		return err
+	}
+	if err := s.CallByParam(count, input, lua.LString("GGTATT")); err != nil {
+		return err
+	}
+	return nil
+}
+
+func run(luafile, inputfile string) error {
+	s := lua.NewState()
+	defer s.Close()
+	if err := s.DoFile(luafile); err != nil {
+		return err
+	}
+	input, err := parseInput(inputfile)
+	if err != nil {
+		return err
+	}
+	return driver.RunBenchmark("GopherLuaKNucleotide", func(_ *driver.B) error {
+		return doBenchmark(s, lua.LString(input))
+	}, driver.InProcessMeasurementOptions...)
+}
+
+func main() {
+	driver.SetFlags(flag.CommandLine)
+	if err := parseFlags(); err != nil {
+		fmt.Fprintln(os.Stderr, err)
+		os.Exit(1)
+	}
+	if err := run(flag.Arg(0), flag.Arg(1)); err != nil {
+		fmt.Fprintln(os.Stderr, err)
+		os.Exit(1)
+	}
+}
diff --git a/sweet/benchmarks/gvisor/common.go b/sweet/benchmarks/gvisor/common.go
new file mode 100644
index 0000000..aeb2879
--- /dev/null
+++ b/sweet/benchmarks/gvisor/common.go
@@ -0,0 +1,62 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build linux
+// +build linux
+
+package main
+
+import (
+	"fmt"
+	"os/exec"
+	"path/filepath"
+	"syscall"
+
+	"golang.org/x/benchmarks/sweet/benchmarks/internal/driver"
+	"golang.org/x/benchmarks/sweet/common"
+)
+
+func workloadsPath(assetsDir, subBenchmark string) string {
+	p := common.CurrentPlatform()
+	platformDir := fmt.Sprintf("%s-%s", p.GOOS, p.GOARCH)
+	return filepath.Join(assetsDir, subBenchmark, "bin", platformDir, "workload")
+}
+
+func (c *config) profilePath(typ driver.ProfileType) string {
+	return filepath.Join(c.tmpDir, string(typ)+".prof")
+}
+
+func (cfg *config) runscCmd(arg ...string) *exec.Cmd {
+	var cmd *exec.Cmd
+	goProfiling := false
+	for _, typ := range []driver.ProfileType{driver.ProfileCPU, driver.ProfileMem} {
+		if driver.ProfilingEnabled(typ) {
+			goProfiling = true
+			break
+		}
+	}
+	if goProfiling {
+		arg = append([]string{"-profile"}, arg...)
+	}
+	if driver.ProfilingEnabled(driver.ProfileCPU) {
+		arg = append([]string{"-profile-cpu", cfg.profilePath(driver.ProfileCPU)}, arg...)
+	}
+	if driver.ProfilingEnabled(driver.ProfileMem) {
+		arg = append([]string{"-profile-heap", cfg.profilePath(driver.ProfileMem)}, arg...)
+	}
+	if driver.ProfilingEnabled(driver.ProfilePerf) {
+		cmd = exec.Command("perf", append([]string{"record", "-o", cfg.profilePath(driver.ProfilePerf), cfg.runscPath}, arg...)...)
+	} else {
+		cmd = exec.Command(cfg.runscPath, arg...)
+	}
+	cmd.SysProcAttr = &syscall.SysProcAttr{
+		// Try to bring down the sandbox if we unexpectedly exit.
+		Pdeathsig: syscall.SIGKILL,
+
+		// New process group, so we can kill the entire sub-process
+		// tree at once.
+		Setpgid: true,
+	}
+	return cmd
+}
diff --git a/sweet/benchmarks/gvisor/http_server.go b/sweet/benchmarks/gvisor/http_server.go
new file mode 100644
index 0000000..1c9b91e
--- /dev/null
+++ b/sweet/benchmarks/gvisor/http_server.go
@@ -0,0 +1,206 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build linux
+// +build linux
+
+package main
+
+import (
+	"bufio"
+	"context"
+	"fmt"
+	"io"
+	"net/http"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"runtime"
+	"sort"
+	"strconv"
+	"syscall"
+	"time"
+
+	"golang.org/x/benchmarks/sweet/benchmarks/internal/cgroups"
+	"golang.org/x/benchmarks/sweet/benchmarks/internal/driver"
+	"golang.org/x/benchmarks/sweet/benchmarks/internal/pool"
+)
+
+const (
+	ip   = "127.0.0.1"
+	port = "8081"
+	host = "http://" + ip + ":" + port
+)
+
+func httpGet(ctx context.Context, url string) (*http.Response, error) {
+	r, err := http.NewRequestWithContext(ctx, "GET", url, nil)
+	if err != nil {
+		return nil, err
+	}
+	return http.DefaultClient.Do(r)
+}
+
+type httpServer struct {
+	duration time.Duration
+}
+
+func (h httpServer) name() string {
+	return "GVisorHTTP"
+}
+
+type worker struct {
+	lat []time.Duration
+}
+
+func newWorker() *worker {
+	return &worker{
+		lat: make([]time.Duration, 0, 100000),
+	}
+}
+
+func (w *worker) Run(_ context.Context) error {
+	start := time.Now()
+	resp, err := http.Get(host)
+	if err != nil {
+		return err
+	}
+	defer resp.Body.Close()
+
+	scanner := bufio.NewScanner(resp.Body)
+	for scanner.Scan() {
+		resp, err := http.Get(host + "/" + scanner.Text())
+		if err != nil {
+			return err
+		}
+		resp.Body.Close()
+	}
+	if err := scanner.Err(); err != nil {
+		return err
+	}
+	dur := time.Now().Sub(start)
+	w.lat = append(w.lat, dur)
+	return nil
+}
+
+func (w *worker) Close() error {
+	return nil
+}
+
+func (b httpServer) run(cfg *config, out io.Writer) (err error) {
+	procs := runtime.GOMAXPROCS(-1)
+	clientProcs := procs / 4
+	if clientProcs == 0 {
+		clientProcs = 1
+	}
+	serverProcs := procs - clientProcs
+	if serverProcs == 0 {
+		serverProcs = 1
+	}
+	runtime.GOMAXPROCS(clientProcs)
+	defer runtime.GOMAXPROCS(procs)
+	clients := clientProcs
+
+	baseSrvCmd := cfg.runscCmd(
+		"-rootless", "do", "-ip", ip,
+		workloadsPath(cfg.assetsDir, "http"),
+		"-host", ip,
+		"-port", port,
+		"-assets", filepath.Join(cfg.assetsDir, "http", "assets"),
+		"-procs", strconv.Itoa(serverProcs),
+	)
+	baseSrvCmd.Stdout = out
+	baseSrvCmd.Stderr = out
+	srvCmd, err := cgroups.WrapCommand(baseSrvCmd, "test-http-server.scope")
+	if err != nil {
+		return err
+	}
+	ctx := context.Background()
+	defer func() {
+		if r := srvCmd.Process.Signal(os.Interrupt); r != nil {
+			if err != nil {
+				fmt.Fprintf(os.Stderr, "failed to force shut down server: %v\n", r)
+			} else {
+				err = r
+			}
+		}
+		if r := srvCmd.Wait(); r != nil {
+			ee, ok := r.(*exec.ExitError)
+			if ok {
+				status := ee.ProcessState.Sys().(syscall.WaitStatus)
+				if status.Signaled() && status.Signal() == os.Interrupt {
+					return
+				}
+			}
+			if err != nil {
+				fmt.Fprintf(os.Stderr, "warning: failed to wait for server: %v\n", r)
+				return
+			}
+			err = r
+			return
+		}
+	}()
+
+	err = driver.RunBenchmark(b.name()+"Startup", func(d *driver.B) error {
+		if err := srvCmd.Start(); err != nil {
+			return err
+		}
+		// Poll until the server is ready to serve, up to a maximum in case of a bug.
+		const timeout = 30 * time.Second
+		start := time.Now()
+		for time.Now().Sub(start) < timeout {
+			resp, err := httpGet(ctx, host)
+			if err == nil {
+				resp.Body.Close()
+				break
+			}
+		}
+		if time.Now().Sub(start) >= timeout {
+			return fmt.Errorf("server startup timed out")
+		}
+		return nil
+	}, driver.DoTime(true))
+
+	workers := make([]pool.Worker, 0, clients)
+	for i := 0; i < clients; i++ {
+		workers = append(workers, newWorker())
+	}
+
+	// Run the benchmark for b.duration.
+	ctx, cancel := context.WithTimeout(ctx, b.duration)
+	defer cancel()
+	p := pool.New(ctx, workers)
+	return driver.RunBenchmark(b.name(), func(d *driver.B) error {
+		if err := p.Run(); err != nil {
+			return err
+		}
+		d.StopTimer()
+
+		// Test is done, bring all latency measurements together.
+		latencies := make([]time.Duration, 0, len(workers)*100000)
+		for _, w := range workers {
+			latencies = append(latencies, w.(*worker).lat...)
+		}
+		sort.Slice(latencies, func(i, j int) bool {
+			return latencies[i] < latencies[j]
+		})
+
+		// Sort and report percentiles.
+		p50 := latencies[len(latencies)*50/100]
+		p90 := latencies[len(latencies)*90/100]
+		p99 := latencies[len(latencies)*99/100]
+		d.Report("p50-latency-ns", uint64(p50))
+		d.Report("p90-latency-ns", uint64(p90))
+		d.Report("p99-latency-ns", uint64(p99))
+
+		// Report throughput.
+		lengthS := float64(b.duration) / float64(time.Second)
+		reqsPerSec := float64(len(latencies)) / lengthS
+		d.Report("ops/s", uint64(reqsPerSec))
+
+		// Report the average request latency.
+		d.Ops(len(latencies))
+		d.Report(driver.StatTime, uint64((int(b.duration)*clients)/len(latencies)))
+		return nil
+	}, driver.DoTime(true), driver.DoAvgRSS(srvCmd.RSSFunc()))
+}
diff --git a/sweet/benchmarks/gvisor/main.go b/sweet/benchmarks/gvisor/main.go
new file mode 100644
index 0000000..0a21508
--- /dev/null
+++ b/sweet/benchmarks/gvisor/main.go
@@ -0,0 +1,86 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build linux
+// +build linux
+
+package main
+
+import (
+	"bytes"
+	"flag"
+	"fmt"
+	"io"
+	"os"
+	"runtime/debug"
+	"time"
+
+	"golang.org/x/benchmarks/sweet/benchmarks/internal/driver"
+)
+
+type config struct {
+	runscPath string
+	assetsDir string
+	tmpDir    string
+}
+
+var cliCfg config
+
+func init() {
+	driver.SetFlags(flag.CommandLine)
+	flag.StringVar(&cliCfg.runscPath, "runsc", "", "path to the runsc binary")
+	flag.StringVar(&cliCfg.assetsDir, "assets-dir", "", "path to the directory containing benchmark root filesystems")
+	flag.StringVar(&cliCfg.tmpDir, "tmp", "", "path to a temporary working directory")
+}
+
+type benchmark interface {
+	name() string
+	run(*config, io.Writer) error
+}
+
+// List of all benchmarks.
+var benchmarks = []benchmark{
+	startup{},
+	systemCall{500000},
+	httpServer{20 * time.Second},
+}
+
+func main1() error {
+	// Run each benchmark once.
+	for _, bench := range benchmarks {
+		// Run the benchmark command under runsc.
+		var buf bytes.Buffer
+		if err := bench.run(&cliCfg, &buf); err != nil {
+			if buf.Len() != 0 {
+				fmt.Fprintln(os.Stderr, "=== Benchmark stdout+stderr ===")
+				fmt.Fprintln(os.Stderr, buf.String())
+			}
+			return err
+		}
+		for _, typ := range driver.ProfileTypes {
+			if !driver.ProfilingEnabled(typ) {
+				continue
+			}
+			// runscCmd ensures these are created if necessary.
+			if err := driver.CopyProfile(cliCfg.profilePath(typ), typ, bench.name()); err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
+
+func main() {
+	debug.SetTraceback("all")
+
+	flag.Parse()
+	if flag.NArg() != 0 {
+		fmt.Fprintf(os.Stderr, "error: unexpected args\n")
+		os.Exit(1)
+	}
+	if err := main1(); err != nil {
+		fmt.Fprintf(os.Stderr, "error: %v\n", err)
+		os.Exit(1)
+	}
+}
diff --git a/sweet/benchmarks/gvisor/startup.go b/sweet/benchmarks/gvisor/startup.go
new file mode 100644
index 0000000..0439b47
--- /dev/null
+++ b/sweet/benchmarks/gvisor/startup.go
@@ -0,0 +1,31 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build linux
+// +build linux
+
+package main
+
+import (
+	"io"
+	"path/filepath"
+
+	"golang.org/x/benchmarks/sweet/benchmarks/internal/driver"
+)
+
+type startup struct{}
+
+func (b startup) name() string {
+	return "GVisorStartup"
+}
+
+func (b startup) run(cfg *config, out io.Writer) error {
+	cmd := cfg.runscCmd("-rootless", "-network=none", "run", "bench")
+	cmd.Stdout = out
+	cmd.Stderr = out
+	cmd.Dir = filepath.Join(cfg.assetsDir, "startup")
+	return driver.RunBenchmark(b.name(), func(d *driver.B) error {
+		return cmd.Run()
+	}, driver.DoTime(true))
+}
diff --git a/sweet/benchmarks/gvisor/syscall.go b/sweet/benchmarks/gvisor/syscall.go
new file mode 100644
index 0000000..e9b5a91
--- /dev/null
+++ b/sweet/benchmarks/gvisor/syscall.go
@@ -0,0 +1,38 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build linux
+// +build linux
+
+package main
+
+import (
+	"io"
+
+	"golang.org/x/benchmarks/sweet/benchmarks/internal/cgroups"
+	"golang.org/x/benchmarks/sweet/benchmarks/internal/driver"
+)
+
+type systemCall struct {
+	ops int
+}
+
+func (b systemCall) name() string {
+	return "GVisorSyscall"
+}
+
+func (b systemCall) run(cfg *config, out io.Writer) error {
+	baseCmd := cfg.runscCmd("-rootless", "do", workloadsPath(cfg.assetsDir, "syscall"))
+	baseCmd.Stdout = out
+	baseCmd.Stderr = out
+	cmd, err := cgroups.WrapCommand(baseCmd, "test-syscall.scope")
+	if err != nil {
+		return err
+	}
+	return driver.RunBenchmark(b.name(), func(d *driver.B) error {
+		d.Ops(b.ops)
+		d.ResetTimer()
+		return cmd.Run()
+	}, driver.DoTime(true), driver.DoAvgRSS(cmd.RSSFunc()))
+}
diff --git a/sweet/benchmarks/internal/cgroups/cgroups.go b/sweet/benchmarks/internal/cgroups/cgroups.go
new file mode 100644
index 0000000..878e344
--- /dev/null
+++ b/sweet/benchmarks/internal/cgroups/cgroups.go
@@ -0,0 +1,64 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package cgroups
+
+import (
+	"errors"
+	"fmt"
+	"os"
+	"os/exec"
+	"os/user"
+	"path/filepath"
+	"strconv"
+	"strings"
+)
+
+type Cmd struct {
+	exec.Cmd
+	modified bool
+	path     string
+	scope    string
+}
+
+func WrapCommand(cmd *exec.Cmd, scope string) (*Cmd, error) {
+	wrapped := Cmd{Cmd: *cmd}
+
+	// TODO(mknyszek): Maybe make a more stringent check?
+	systemdRunPath, err := exec.LookPath("systemd-run")
+	if errors.Is(err, exec.ErrNotFound) {
+		fmt.Fprintln(os.Stderr, "# warning: systemd-run not available, skipping...")
+		return &wrapped, nil
+	} else if err != nil {
+		return nil, err
+	}
+
+	u, err := user.Current()
+	if err != nil {
+		return nil, err
+	}
+	wrapped.Cmd.Args = append([]string{
+		systemdRunPath, "--user", "--scope", "--unit=" + scope,
+	}, wrapped.Cmd.Args...)
+	wrapped.Cmd.Path = systemdRunPath
+	wrapped.modified = true
+	wrapped.path = fmt.Sprintf("user-%s.slice/user@%s.service/app.slice", u.Uid, u.Uid)
+	wrapped.scope = scope
+
+	return &wrapped, nil
+}
+
+func (c *Cmd) RSSFunc() func() (uint64, error) {
+	if !c.modified {
+		return nil
+	}
+	memPath := filepath.Join("/sys/fs/cgroup/user.slice", c.path, c.scope, "memory.current")
+	return func() (uint64, error) {
+		data, err := os.ReadFile(memPath)
+		if err != nil {
+			return 0, err
+		}
+		return strconv.ParseUint(strings.TrimSpace(string(data)), 10, 64)
+	}
+}
diff --git a/sweet/benchmarks/internal/driver/driver.go b/sweet/benchmarks/internal/driver/driver.go
new file mode 100644
index 0000000..c72093b
--- /dev/null
+++ b/sweet/benchmarks/internal/driver/driver.go
@@ -0,0 +1,602 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package driver
+
+import (
+	"context"
+	"flag"
+	"fmt"
+	"io"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"runtime/pprof"
+	"sort"
+	"strconv"
+	"strings"
+	"sync"
+	"time"
+
+	"github.com/google/pprof/profile"
+)
+
+var (
+	coreDumpDir   string
+	cpuProfileDir string
+	memProfileDir string
+	perfDir       string
+	perfFlags     string
+)
+
+func SetFlags(f *flag.FlagSet) {
+	f.StringVar(&coreDumpDir, "dump-cores", "", "dump a core file to the given directory after every benchmark run")
+	f.StringVar(&cpuProfileDir, "cpuprofile", "", "write a CPU profile to the given directory after every benchmark run")
+	f.StringVar(&memProfileDir, "memprofile", "", "write a memory profile to the given directory after every benchmark run")
+	f.StringVar(&perfDir, "perf", "", "write a Linux perf data file to the given directory after every benchmark run")
+	f.StringVar(&perfFlags, "perf-flags", "", "pass the following additional flags to Linux perf")
+}
+
+const (
+	StatPeakRSS = "peak-RSS-bytes"
+	StatPeakVM  = "peak-VM-bytes"
+	StatAvgRSS  = "average-RSS-bytes"
+	StatTime    = "ns/op"
+)
+
+type RunOption func(*B)
+
+func DoDefaultAvgRSS() RunOption {
+	return func(b *B) {
+		b.rssFunc = func() (uint64, error) {
+			return ReadRSS(b.pid)
+		}
+	}
+}
+
+func DoAvgRSS(f func() (uint64, error)) RunOption {
+	return func(b *B) {
+		b.rssFunc = f
+	}
+}
+
+func DoTime(v bool) RunOption {
+	return func(b *B) {
+		b.doTime = v
+	}
+}
+
+func DoPeakRSS(v bool) RunOption {
+	return func(b *B) {
+		b.doPeakRSS = v
+	}
+}
+
+func DoPeakVM(v bool) RunOption {
+	return func(b *B) {
+		b.doPeakVM = v
+	}
+}
+
+func DoCoreDump(v bool) RunOption {
+	return func(b *B) {
+		b.doCoreDump = v
+	}
+}
+
+func DoCPUProfile(v bool) RunOption {
+	return func(b *B) {
+		b.doProfile[ProfileCPU] = v
+	}
+}
+
+func DoMemProfile(v bool) RunOption {
+	return func(b *B) {
+		b.doProfile[ProfileMem] = v
+	}
+}
+
+func DoPerf(v bool) RunOption {
+	return func(b *B) {
+		b.doProfile[ProfilePerf] = v
+	}
+}
+
+func BenchmarkPID(pid int) RunOption {
+	return func(b *B) {
+		b.pid = pid
+		if pid != os.Getpid() {
+			b.doProfile[ProfileCPU] = false
+			b.doProfile[ProfileMem] = false
+			b.doProfile[ProfilePerf] = false
+		}
+	}
+}
+
+func WithContext(ctx context.Context) RunOption {
+	return func(b *B) {
+		b.ctx = ctx
+	}
+}
+
+func WriteResultsTo(wr io.Writer) RunOption {
+	return func(b *B) {
+		b.resultsWriter = wr
+	}
+}
+
+var InProcessMeasurementOptions = []RunOption{
+	DoTime(true),
+	DoPeakRSS(true),
+	DoDefaultAvgRSS(),
+	DoPeakVM(true),
+	DoCoreDump(true),
+	DoCPUProfile(true),
+	DoMemProfile(true),
+	DoPerf(true),
+}
+
+type B struct {
+	ctx           context.Context
+	pid           int
+	name          string
+	start         time.Time
+	dur           time.Duration
+	doTime        bool
+	doPeakRSS     bool
+	doPeakVM      bool
+	doCoreDump    bool
+	doProfile     map[ProfileType]bool
+	rssFunc       func() (uint64, error)
+	statsMu       sync.Mutex
+	stats         map[string]uint64
+	ops           int
+	wg            sync.WaitGroup
+	profiles      map[ProfileType]*os.File
+	resultsWriter io.Writer
+	perfProcess   *os.Process
+}
+
+func newB(name string) *B {
+	b := &B{
+		pid:  os.Getpid(),
+		name: name,
+		doProfile: map[ProfileType]bool{
+			ProfileCPU: false,
+			ProfileMem: false,
+		},
+		stats:    make(map[string]uint64),
+		ops:      1,
+		profiles: make(map[ProfileType]*os.File),
+	}
+	return b
+}
+
+func (b *B) setStat(name string, value uint64) {
+	b.statsMu.Lock()
+	defer b.statsMu.Unlock()
+	b.stats[name] = value
+}
+
+func (b *B) shouldProfile(typ ProfileType) bool {
+	return b.doProfile[typ] && ProfilingEnabled(typ)
+}
+
+func (b *B) StartTimer() {
+	if b.shouldProfile(ProfileCPU) {
+		pprof.StartCPUProfile(b.profiles[ProfileCPU])
+	}
+	if b.shouldProfile(ProfilePerf) {
+		if err := b.startPerf(); err != nil {
+			warningf("failed to start perf: %v", err)
+		}
+	}
+	b.start = time.Now()
+}
+
+func (b *B) ResetTimer() {
+	if b.shouldProfile(ProfileCPU) {
+		pprof.StopCPUProfile()
+		if err := b.truncateProfile(ProfileCPU); err != nil {
+			warningf("failed to truncate CPU profile: %v", err)
+		}
+		pprof.StartCPUProfile(b.profiles[ProfileCPU])
+	}
+	if b.shouldProfile(ProfilePerf) {
+		if err := b.stopPerf(); err != nil {
+			warningf("failed to stop perf: %v", err)
+		}
+		if err := b.truncateProfile(ProfilePerf); err != nil {
+			warningf("failed to truncate perf data file: %v", err)
+		}
+		if err := b.startPerf(); err != nil {
+			warningf("failed to start perf: %v", err)
+		}
+	}
+	if !b.start.IsZero() {
+		b.start = time.Now()
+	}
+	b.dur = 0
+}
+
+func (b *B) truncateProfile(typ ProfileType) error {
+	f := b.profiles[typ]
+	_, err := f.Seek(0, 0)
+	if err != nil {
+		return err
+	}
+	return f.Truncate(0)
+}
+
+func (b *B) StopTimer() {
+	end := time.Now()
+	if b.start.IsZero() {
+		panic("stopping unstarted timer")
+	}
+	b.dur += end.Sub(b.start)
+	b.start = time.Time{}
+
+	if b.shouldProfile(ProfileCPU) {
+		pprof.StopCPUProfile()
+	}
+	if b.shouldProfile(ProfilePerf) {
+		if err := b.stopPerf(); err != nil {
+			warningf("failed to stop perf: %v", err)
+		}
+	}
+}
+
+func (b *B) TimerRunning() bool {
+	return !b.start.IsZero()
+}
+
+func (b *B) Elapsed() time.Duration {
+	return b.dur
+}
+
+func (b *B) Report(name string, value uint64) {
+	b.stats[name] = value
+}
+
+func (b *B) Ops(ops int) {
+	b.ops = ops
+}
+
+func (b *B) Context() context.Context {
+	if b.ctx != nil {
+		return b.ctx
+	}
+	return context.Background()
+}
+
+func (b *B) startRSSSampler() chan<- struct{} {
+	if b.rssFunc == nil {
+		return nil
+	}
+	stop := make(chan struct{})
+	b.wg.Add(1)
+	go func() {
+		defer b.wg.Done()
+
+		rssSamples := make([]uint64, 0, 1024)
+		for {
+			select {
+			case <-stop:
+				b.setStat(StatAvgRSS, avg(rssSamples))
+				return
+			case <-time.After(100 * time.Millisecond):
+				r, err := b.rssFunc()
+				if err != nil {
+					warningf("failed to read RSS: %v", err)
+					continue
+				}
+				if r == 0 {
+					continue
+				}
+				rssSamples = append(rssSamples, r)
+			}
+		}
+	}()
+	return stop
+}
+
+func splitName(s string) []string {
+	var comps []string
+	last := 0
+	for i, r := range s {
+		if r == '-' || r == '*' || r == '/' {
+			comps = append(comps, s[last:i])
+			last = i + 1
+		}
+	}
+	if len(comps) == 0 {
+		comps = []string{s}
+	}
+	return comps
+}
+
+func (b *B) report() {
+	b.statsMu.Lock()
+	defer b.statsMu.Unlock()
+
+	// Collect all names of non-zero stats.
+	names := make([]string, 0, len(b.stats))
+	for name, value := range b.stats {
+		if value != 0 {
+			names = append(names, name)
+		}
+	}
+	if len(names) == 0 {
+		fmt.Fprintln(os.Stderr, "# No benchmark results found for this run.")
+		return
+	}
+	namesToComps := make(map[string][]string)
+	for _, n := range names {
+		namesToComps[n] = splitName(n)
+	}
+	sort.Slice(names, func(i, j int) bool {
+		// Let's make sure StatTime always ends up first.
+		if names[i] == StatTime {
+			return true
+		} else if names[j] == StatTime {
+			return false
+		}
+		ci := namesToComps[names[i]]
+		cj := namesToComps[names[j]]
+		min := len(ci)
+		if len(ci) > len(cj) {
+			min = len(cj)
+		}
+		for i := 0; i < min; i++ {
+			k := strings.Compare(ci[len(ci)-1-i], cj[len(cj)-1-i])
+			if k < 0 {
+				return true
+			} else if k > 0 {
+				return false
+			}
+		}
+		return len(ci) < len(cj)
+	})
+
+	// Write out stats.
+	var out io.Writer = os.Stderr
+	if b.resultsWriter != nil {
+		out = b.resultsWriter
+	}
+	fmt.Fprintf(out, "Benchmark%s %d", b.name, b.ops)
+	for _, name := range names {
+		value := b.stats[name]
+		if value != 0 {
+			fmt.Fprintf(out, " %d %s", value, name)
+		}
+	}
+	fmt.Fprintln(out)
+}
+
+func warningf(format string, args ...interface{}) {
+	s := fmt.Sprintf(format, args...)
+	s = strings.Join(strings.Split(s, "\n"), "\n# ")
+	fmt.Fprintf(os.Stderr, "# warning: %s\n", s)
+}
+
+func avg(s []uint64) uint64 {
+	avg := uint64(0)
+	lo := uint64(0)
+	l := uint64(len(s))
+	for i := 0; i < len(s); i++ {
+		avg += s[i] / l
+		mod := s[i] % l
+		if lo >= l-mod {
+			avg += 1
+			lo -= l - mod
+		} else {
+			lo += mod
+		}
+	}
+	return avg
+}
+
+func (b *B) startPerf() error {
+	if b.perfProcess != nil {
+		panic("perf process already started")
+	}
+	args := []string{"record", "-o", b.profiles[ProfilePerf].Name(), "-p", strconv.Itoa(b.pid)}
+	if perfFlags != "" {
+		args = append(args, strings.Split(perfFlags, " ")...)
+	}
+	cmd := exec.Command("perf", args...)
+	if err := cmd.Start(); err != nil {
+		return err
+	}
+	b.perfProcess = cmd.Process
+	return nil
+}
+
+func (b *B) stopPerf() error {
+	if b.perfProcess == nil {
+		panic("perf process not started")
+	}
+	proc := b.perfProcess
+	b.perfProcess = nil
+
+	if err := proc.Signal(os.Interrupt); err != nil {
+		return err
+	}
+	_, err := proc.Wait()
+	return err
+}
+
+func RunBenchmark(name string, f func(*B) error, opts ...RunOption) error {
+	// Create a B and populate it with options.
+	b := newB(name)
+	for _, opt := range opts {
+		opt(b)
+	}
+
+	// Start the RSS sampler and start the timer.
+	stop := b.startRSSSampler()
+
+	// Make sure profile file(s) are created if necessary.
+	for _, typ := range ProfileTypes {
+		if b.shouldProfile(typ) {
+			f, err := newProfileFile(typ, b.name)
+			if err != nil {
+				return err
+			}
+			b.profiles[typ] = f
+		}
+	}
+
+	b.StartTimer()
+
+	// Run the benchmark itself.
+	if err := f(b); err != nil {
+		return err
+	}
+	if b.TimerRunning() {
+		b.StopTimer()
+	}
+
+	// Stop the RSS sampler.
+	if stop != nil {
+		stop <- struct{}{}
+	}
+
+	if b.doPeakRSS {
+		v, err := ReadPeakRSS(b.pid)
+		if err != nil {
+			warningf("failed to read RSS peak: %v", err)
+		} else if v != 0 {
+			b.setStat(StatPeakRSS, v)
+		}
+	}
+	if b.doPeakVM {
+		v, err := ReadPeakVM(b.pid)
+		if err != nil {
+			warningf("failed to read VM peak: %v", err)
+		} else if v != 0 {
+			b.setStat(StatPeakVM, v)
+		}
+	}
+	if b.doTime {
+		if b.dur == 0 {
+			panic("timer never stopped")
+		} else if b.dur < 0 {
+			panic("negative duration encountered")
+		}
+		if b.ops == 0 {
+			panic("zero ops reported")
+		} else if b.ops < 0 {
+			panic("negative ops encountered")
+		}
+		b.setStat(StatTime, uint64(b.dur.Nanoseconds())/uint64(b.ops))
+	}
+	if b.doCoreDump && coreDumpDir != "" {
+		// Use gcore to dump the core of the benchmark process.
+		cmd := exec.Command(
+			"gcore", "-o", filepath.Join(coreDumpDir, name), strconv.Itoa(b.pid),
+		)
+		if out, err := cmd.CombinedOutput(); err != nil {
+			// Just print a warning; this isn't a fatal error.
+			warningf("failed to dump core: %v\n%s", err, string(out))
+		}
+	}
+
+	b.wg.Wait()
+
+	// Finalize all the profile files we're handling ourselves.
+	for typ, f := range b.profiles {
+		if typ == ProfileMem {
+			if err := pprof.Lookup("heap").WriteTo(f, 0); err != nil {
+				return err
+			}
+		}
+		f.Close()
+	}
+
+	// Report the results.
+	b.report()
+	return nil
+}
+
+type ProfileType string
+
+const (
+	ProfileCPU  ProfileType = "cpu"
+	ProfileMem  ProfileType = "mem"
+	ProfilePerf ProfileType = "perf"
+)
+
+var ProfileTypes = []ProfileType{
+	ProfileCPU,
+	ProfileMem,
+	ProfilePerf,
+}
+
+func ProfilingEnabled(typ ProfileType) bool {
+	switch typ {
+	case ProfileCPU:
+		return cpuProfileDir != ""
+	case ProfileMem:
+		return memProfileDir != ""
+	case ProfilePerf:
+		return perfDir != ""
+	}
+	panic("bad profile type")
+}
+
+func ReadProfile(filename string) (*profile.Profile, error) {
+	f, err := os.Open(filename)
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+	return profile.Parse(f)
+}
+
+func WriteProfile(prof *profile.Profile, typ ProfileType, pattern string) error {
+	if !ProfilingEnabled(typ) {
+		return fmt.Errorf("this type of profile is not currently enabled")
+	}
+	f, err := newProfileFile(typ, pattern)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+	return prof.Write(f)
+}
+
+func CopyProfile(profilePath string, typ ProfileType, pattern string) error {
+	inF, err := os.Open(profilePath)
+	if err != nil {
+		return err
+	}
+	defer inF.Close()
+	outF, err := newProfileFile(typ, pattern)
+	if err != nil {
+		return err
+	}
+	defer outF.Close()
+	_, err = io.Copy(outF, inF)
+	return err
+}
+
+func newProfileFile(typ ProfileType, pattern string) (*os.File, error) {
+	if !ProfilingEnabled(typ) {
+		return nil, fmt.Errorf("this type of profile is not currently enabled")
+	}
+	var outDir, patternSuffix string
+	switch typ {
+	case ProfileCPU:
+		outDir = cpuProfileDir
+		patternSuffix = ".cpu"
+	case ProfileMem:
+		outDir = memProfileDir
+		patternSuffix = ".mem"
+	case ProfilePerf:
+		outDir = perfDir
+		patternSuffix = ".perf"
+	}
+	return os.CreateTemp(outDir, pattern+patternSuffix)
+}
diff --git a/sweet/benchmarks/internal/driver/mem.go b/sweet/benchmarks/internal/driver/mem.go
new file mode 100644
index 0000000..6063ebe
--- /dev/null
+++ b/sweet/benchmarks/internal/driver/mem.go
@@ -0,0 +1,25 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !linux
+
+package driver
+
+import "os"
+
+func ReadRSS(pid int) (uint64, error) {
+	return 0, nil
+}
+
+func ReadPeakRSS(pid int) (uint64, error) {
+	return 0, nil
+}
+
+func ReadPeakVM(pid int) (uint64, error) {
+	return 0, nil
+}
+
+func ProcessPeakRSS(s *os.ProcessState) uint64 {
+	return 0
+}
diff --git a/sweet/benchmarks/internal/driver/mem_linux.go b/sweet/benchmarks/internal/driver/mem_linux.go
new file mode 100644
index 0000000..9243aa0
--- /dev/null
+++ b/sweet/benchmarks/internal/driver/mem_linux.go
@@ -0,0 +1,49 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package driver
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"regexp"
+	"strconv"
+	"syscall"
+)
+
+var (
+	reVmPeak = regexp.MustCompile(`VmPeak:\s*(\d+) kB`)
+	reVmRSS  = regexp.MustCompile(`VmRSS:\s*(\d+) kB`)
+	reVmHWM  = regexp.MustCompile(`VmHWM:\s*(\d+) kB`)
+)
+
+func readStat(pid int, r *regexp.Regexp) (uint64, error) {
+	b, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/status", pid))
+	if err != nil {
+		return 0, err
+	}
+	m := r.FindSubmatch(b)
+	if len(m) < 2 {
+		return 0, nil
+	}
+	val, err := strconv.ParseUint(string(m[1]), 10, 64)
+	return val * 1024, err
+}
+
+func ReadRSS(pid int) (uint64, error) {
+	return readStat(pid, reVmRSS)
+}
+
+func ReadPeakRSS(pid int) (uint64, error) {
+	return readStat(pid, reVmHWM)
+}
+
+func ReadPeakVM(pid int) (uint64, error) {
+	return readStat(pid, reVmPeak)
+}
+
+func ProcessPeakRSS(s *os.ProcessState) uint64 {
+	return uint64(s.SysUsage().(*syscall.Rusage).Maxrss) * 1024
+}
diff --git a/sweet/benchmarks/internal/pool/pool.go b/sweet/benchmarks/internal/pool/pool.go
new file mode 100644
index 0000000..e043c63
--- /dev/null
+++ b/sweet/benchmarks/internal/pool/pool.go
@@ -0,0 +1,137 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+/*
+	This package provides a facility to run a heterogeneous pool of workers.
+
+	Each worker is defined by an interface, and the pool execute's each worker's
+	Run method repeatedly concurrently. The worker may exit early by returning
+	the Done error. Each worker's Run method accepts a context.Context which is
+	passed to it through the pool. If this context is cancelled, it may cancel
+	workers and will always cancel the pool.
+
+	Each worker is guaranteed to start immediately when the pool's Run method is
+	called and not any sooner.
+*/
+package pool
+
+import (
+	"context"
+	"errors"
+	"sync"
+)
+
+// Done is used to signal to the pool that the worker has no more useful work
+// to do.
+var Done error = errors.New("pool worker is done")
+
+// Worker represents a stateful task which is executed repeatedly by calling
+// its Run method. Any resources associated with the Worker may be freed with
+// Close.
+type Worker interface {
+	// Run executes a task once, returning an error on failure.
+	Run(context.Context) error
+
+	// Close releases any resources associated with the Worker.
+	Close() error
+}
+
+// P implements a heterogeneous pool of Workers.
+type P struct {
+	workers           []Worker
+	gun, cancel, done chan struct{}
+	errs              chan error
+}
+
+// New creates a new pool of the given workers.
+//
+// The provided context will be passed to all workers' run methods.
+func New(ctx context.Context, workers []Worker) *P {
+	gun := make(chan struct{})
+	cancel := make(chan struct{})
+	errs := make(chan error, len(workers))
+	ready := make(chan struct{}, len(workers))
+
+	// Spin up workers.
+	wg := sync.WaitGroup{}
+	for _, w := range workers {
+		go func(w Worker) {
+			wg.Add(1)
+			defer wg.Done()
+			ready <- struct{}{}
+			<-gun // wait for starting gun to close
+		loop:
+			for {
+				err := w.Run(ctx)
+				if err == Done {
+					break
+				} else if err != nil {
+					errs <- err
+					break
+				}
+				select {
+				case <-ctx.Done():
+					break loop
+				case <-cancel:
+					break loop
+				default:
+				}
+			}
+		}(w)
+	}
+
+	// Wait for all workers to be ready.
+	for range workers {
+		<-ready
+	}
+
+	// Spin up waiter.
+	done := make(chan struct{})
+	go func() {
+		wg.Wait()
+		done <- struct{}{}
+	}()
+
+	return &P{
+		workers: workers,
+		gun:     gun,
+		cancel:  cancel,
+		done:    done,
+		errs:    errs,
+	}
+}
+
+// Run signals all the workers to begin and waits for all of them to complete.
+//
+// Each Worker's Run method is called in a loop until the worker returns an
+// error or the context passed to New is cancelled. If the error is Done, then
+// it does not propagate to Run and instead the worker stops looping.
+//
+// If the context is cancelled for any reason no error is returned. Check the
+// context for any errors in that case.
+//
+// Always cleans up the pool's workers by calling Close before returning.
+//
+// Returns the first error encountered from any worker that failed and cancels
+// the rest immediately.
+func (p *P) Run() error {
+	close(p.gun) // fire starting gun
+	defer func() {
+		// Clean up on exit.
+		for _, w := range p.workers {
+			w.Close()
+		}
+	}()
+	// Wait for completion.
+	select {
+	case <-p.done:
+	case err := <-p.errs:
+		// Broadcast cancel to all workers
+		// and wait until they're done.
+		close(p.cancel)
+		<-p.done
+		return err
+	}
+	return nil
+}
diff --git a/sweet/benchmarks/internal/pool/pool_test.go b/sweet/benchmarks/internal/pool/pool_test.go
new file mode 100644
index 0000000..6647183
--- /dev/null
+++ b/sweet/benchmarks/internal/pool/pool_test.go
@@ -0,0 +1,112 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package pool
+
+import (
+	"context"
+	"io"
+	"testing"
+	"time"
+)
+
+func TestEmptyPool(t *testing.T) {
+	p := New(context.Background(), []Worker{})
+	if err := p.Run(); err != nil {
+		t.Fatalf("got error from empty pool: %v", err)
+	}
+}
+
+type countCloser struct {
+	c int
+}
+
+func (c *countCloser) Close() error {
+	c.c++
+	return nil
+}
+
+type boolWorker struct {
+	countCloser
+	exec bool
+}
+
+func (b *boolWorker) Run(_ context.Context) error {
+	b.exec = true
+	return Done
+}
+
+func TestPool(t *testing.T) {
+	workers := []Worker{
+		&boolWorker{},
+		&boolWorker{},
+		&boolWorker{},
+		&boolWorker{},
+	}
+	p := New(context.Background(), workers)
+	if err := p.Run(); err != nil {
+		t.Fatalf("got error from good pool: %v", err)
+	}
+	for _, w := range workers {
+		if !w.(*boolWorker).exec {
+			t.Fatal("found worker that was never executed")
+		}
+	}
+}
+
+type badWorker struct {
+	countCloser
+}
+
+func (b *badWorker) Run(_ context.Context) error {
+	return io.EOF
+}
+
+func TestPoolError(t *testing.T) {
+	workers := []Worker{
+		&boolWorker{},
+		&boolWorker{},
+		&boolWorker{},
+		&badWorker{},
+	}
+	p := New(context.Background(), workers)
+	if err := p.Run(); err == nil {
+		t.Fatal("expected error from bad worker")
+	} else if err != io.EOF {
+		t.Fatalf("unexpected error from pool: %v", err)
+	}
+}
+
+type foreverWorker struct {
+	countCloser
+}
+
+func (f *foreverWorker) Run(ctx context.Context) error {
+	return nil
+}
+
+func TestPoolCancel(t *testing.T) {
+	workers := []Worker{
+		&foreverWorker{},
+		&foreverWorker{},
+		&foreverWorker{},
+		&foreverWorker{},
+	}
+	ctx := context.Background()
+	ctx, cancel := context.WithCancel(ctx)
+	p := New(ctx, workers)
+	sig := make(chan struct{})
+	go func() {
+		if err := p.Run(); err != nil {
+			t.Fatalf("got error from good pool: %v", err)
+		}
+		sig <- struct{}{}
+	}()
+	cancel()
+	select {
+	case <-sig:
+	case <-time.After(3 * time.Second):
+		t.Fatal("test timed out after 3 seconds")
+	}
+}
diff --git a/sweet/benchmarks/markdown/main.go b/sweet/benchmarks/markdown/main.go
new file mode 100644
index 0000000..67bfc73
--- /dev/null
+++ b/sweet/benchmarks/markdown/main.go
@@ -0,0 +1,68 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"bytes"
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+
+	"golang.org/x/benchmarks/sweet/benchmarks/internal/driver"
+
+	"gitlab.com/golang-commonmark/markdown"
+)
+
+func run(mddir string) error {
+	files, err := ioutil.ReadDir(mddir)
+	if err != nil {
+		return err
+	}
+
+	contents := make([][]byte, 0, len(files))
+	for _, file := range files {
+		if !file.IsDir() && filepath.Ext(file.Name()) == ".md" {
+			content, err := ioutil.ReadFile(filepath.Join(mddir, file.Name()))
+			if err != nil {
+				return err
+			}
+			contents = append(contents, content)
+		}
+	}
+
+	out := bytes.Buffer{}
+	out.Grow(1024 * 1024)
+
+	md := markdown.New(
+		markdown.XHTMLOutput(true),
+		markdown.Tables(true),
+		markdown.MaxNesting(8),
+		markdown.Typographer(true),
+		markdown.Linkify(true),
+	)
+
+	return driver.RunBenchmark("MarkdownRenderXHTML", func(_ *driver.B) error {
+		for _, c := range contents {
+			md.Render(&out, c)
+			out.Reset()
+		}
+		return nil
+	}, driver.InProcessMeasurementOptions...)
+}
+
+func main() {
+	driver.SetFlags(flag.CommandLine)
+	flag.Parse()
+	if flag.NArg() != 1 {
+		fmt.Fprintln(os.Stderr, "expected asset directory as input")
+		os.Exit(1)
+	}
+	if err := run(flag.Arg(0)); err != nil {
+		fmt.Fprintln(os.Stderr, err)
+		os.Exit(1)
+	}
+}
diff --git a/sweet/benchmarks/tile38/README.md b/sweet/benchmarks/tile38/README.md
new file mode 100644
index 0000000..45bff2d
--- /dev/null
+++ b/sweet/benchmarks/tile38/README.md
@@ -0,0 +1,20 @@
+# Tile38 Benchmark
+
+The benchmark consists of stress-testing the [Tile38](https://tile38.com) server
+with three different kinds of commands:
+* `WITHIN` -- Each request looks for points or regions in the database which
+  lie wholly within 100km of a given input point.
+* `INTERSECTS` -- Same as `WITHIN`, but bounds may simply overlap with the
+  search space, rather than be wholly enclosed by it.
+* `NEARBY` -- Each request looks for the 100-nearest-neighbors to a given input
+  point.
+
+Each command is run for the amount of time specified in the CLI (default 20
+seconds).
+
+Much of the idea for the benchmarks is derived from the `tile38-benchmark`
+program built as part of building tile38 from the [upstream
+repository](https://github.com/tidwall/tile38/tree/master/cmd/tile38-benchmark).
+
+This implementation is custom and not derived via source modification from the
+`tile38-benchmark` tool.
diff --git a/sweet/benchmarks/tile38/main.go b/sweet/benchmarks/tile38/main.go
new file mode 100644
index 0000000..dc92162
--- /dev/null
+++ b/sweet/benchmarks/tile38/main.go
@@ -0,0 +1,336 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"bytes"
+	"context"
+	"flag"
+	"fmt"
+	"io"
+	"math/rand"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"runtime"
+	"sort"
+	"strconv"
+	"sync/atomic"
+	"time"
+
+	"golang.org/x/benchmarks/sweet/benchmarks/internal/driver"
+	"golang.org/x/benchmarks/sweet/benchmarks/internal/pool"
+
+	"github.com/gomodule/redigo/redis"
+)
+
+type config struct {
+	host        string
+	port        int
+	seed        int64
+	iter        int
+	serverBin   string
+	dataPath    string
+	tmpDir      string
+	serverProcs int
+	isProfiling bool
+}
+
+func (c *config) profilePath(typ driver.ProfileType) string {
+	var fname string
+	switch typ {
+	case driver.ProfileCPU:
+		fname = "cpu.prof"
+	case driver.ProfileMem:
+		fname = "mem.prof"
+	case driver.ProfilePerf:
+		fname = "perf.data"
+	default:
+		panic("unsupported profile type " + string(typ))
+	}
+	return filepath.Join(c.tmpDir, fname)
+}
+
+var cliCfg config
+
+func init() {
+	driver.SetFlags(flag.CommandLine)
+	flag.StringVar(&cliCfg.host, "host", "", "hostname of tile38 server")
+	flag.IntVar(&cliCfg.port, "port", 9851, "port for tile38 server")
+	flag.Int64Var(&cliCfg.seed, "seed", 0, "seed for PRNG")
+	flag.IntVar(&cliCfg.iter, "iter", 60*50000, "how many iterations to run (for profiling)")
+	flag.StringVar(&cliCfg.serverBin, "server", "", "path to tile38 server binary")
+	flag.StringVar(&cliCfg.dataPath, "data", "", "path to tile38 server data")
+	flag.StringVar(&cliCfg.tmpDir, "tmp", "", "path to temporary directory")
+
+	// Grab the number of procs we have and give ourselves only 1/4 of those.
+	procs := runtime.GOMAXPROCS(-1)
+	clientProcs := procs / 4
+	if clientProcs == 0 {
+		clientProcs = 1
+	}
+	serverProcs := procs - clientProcs
+	if serverProcs == 0 {
+		serverProcs = 1
+	}
+	runtime.GOMAXPROCS(clientProcs)
+	cliCfg.serverProcs = serverProcs
+}
+
+type requestFunc func(redis.Conn, float64, float64) error
+
+func doWithinCircle(c redis.Conn, lat, lon float64) error {
+	_, err := c.Do("WITHIN", "key:bench", "COUNT", "CIRCLE",
+		strconv.FormatFloat(lat, 'f', 5, 64),
+		strconv.FormatFloat(lon, 'f', 5, 64),
+		"100000",
+	)
+	return err
+}
+
+func doIntersectsCircle(c redis.Conn, lat, lon float64) error {
+	_, err := c.Do("INTERSECTS", "key:bench", "COUNT", "CIRCLE",
+		strconv.FormatFloat(lat, 'f', 5, 64),
+		strconv.FormatFloat(lon, 'f', 5, 64),
+		"100000",
+	)
+	return err
+}
+
+func doNearby(c redis.Conn, lat, lon float64) error {
+	_, err := c.Do("NEARBY", "key:bench", "LIMIT", "100", "COUNT", "POINT",
+		strconv.FormatFloat(lat, 'f', 5, 64),
+		strconv.FormatFloat(lon, 'f', 5, 64),
+	)
+	return err
+}
+
+func randPoint() (float64, float64) {
+	return rand.Float64()*180 - 90, rand.Float64()*360 - 180
+}
+
+type worker struct {
+	redis.Conn
+	runner    requestFunc
+	iterCount *int64 // Accessed atomically.
+	lat       []time.Duration
+}
+
+func newWorker(host string, port int, req requestFunc, iterCount *int64) (*worker, error) {
+	conn, err := redis.Dial("tcp", fmt.Sprintf("%s:%d", host, port))
+	if err != nil {
+		return nil, err
+	}
+	return &worker{
+		Conn:      conn,
+		runner:    req,
+		iterCount: iterCount,
+		lat:       make([]time.Duration, 0, 100000),
+	}, nil
+}
+
+func (w *worker) Run(_ context.Context) error {
+	lat, lon := randPoint()
+	start := time.Now()
+	if err := w.runner(w.Conn, lat, lon); err != nil {
+		return err
+	}
+	dur := time.Now().Sub(start)
+	w.lat = append(w.lat, dur)
+	if atomic.AddInt64(w.iterCount, -1) < 0 {
+		return pool.Done
+	}
+	return nil
+}
+
+func (w *worker) Close() error {
+	return w.Conn.Close()
+}
+
+type durSlice []time.Duration
+
+func (d durSlice) Len() int           { return len(d) }
+func (d durSlice) Less(i, j int) bool { return d[i] < d[j] }
+func (d durSlice) Swap(i, j int)      { d[i], d[j] = d[j], d[i] }
+
+type benchmark struct {
+	sname  string
+	runner requestFunc
+}
+
+func (b *benchmark) name() string {
+	return fmt.Sprintf("Tile38%sRequest", b.sname)
+}
+
+func (b *benchmark) run(d *driver.B, host string, port, clients int, iters int) error {
+	workers := make([]pool.Worker, 0, clients)
+	iterCount := int64(iters) // Shared atomic variable.
+	for i := 0; i < clients; i++ {
+		w, err := newWorker(host, port, b.runner, &iterCount)
+		if err != nil {
+			return err
+		}
+		workers = append(workers, w)
+	}
+	p := pool.New(context.Background(), workers)
+
+	d.ResetTimer()
+	if err := p.Run(); err != nil {
+		return err
+	}
+	d.StopTimer()
+
+	// Test is done, bring all latency measurements together.
+	latencies := make([]time.Duration, 0, len(workers)*100000)
+	for _, w := range workers {
+		latencies = append(latencies, w.(*worker).lat...)
+	}
+	sort.Sort(durSlice(latencies))
+
+	// Sort and report percentiles.
+	p50 := latencies[len(latencies)*50/100]
+	p90 := latencies[len(latencies)*90/100]
+	p99 := latencies[len(latencies)*99/100]
+	d.Report("p50-latency-ns", uint64(p50))
+	d.Report("p90-latency-ns", uint64(p90))
+	d.Report("p99-latency-ns", uint64(p99))
+
+	// Report throughput.
+	lengthS := float64(d.Elapsed()) / float64(time.Second)
+	reqsPerSec := float64(len(latencies)) / lengthS
+	d.Report("ops/s", uint64(reqsPerSec))
+
+	// Report the average request latency.
+	d.Ops(len(latencies))
+	d.Report(driver.StatTime, uint64((int(d.Elapsed())*clients)/len(latencies)))
+	return nil
+}
+
+var benchmarks = []benchmark{
+	{"WithinCircle100km", doWithinCircle},
+	{"IntersectsCircle100km", doIntersectsCircle},
+	{"KNearestLimit100", doNearby},
+}
+
+func launchServer(cfg *config, out io.Writer) (*exec.Cmd, error) {
+	// Set up arguments.
+	srvArgs := []string{
+		"-d", cfg.dataPath,
+		"-h", "127.0.0.1",
+		"-p", "9851",
+		"-threads", strconv.Itoa(cfg.serverProcs),
+	}
+	for _, typ := range []driver.ProfileType{driver.ProfileCPU, driver.ProfileMem} {
+		if driver.ProfilingEnabled(typ) {
+			srvArgs = append(srvArgs, "-"+string(typ)+"profile", cfg.profilePath(typ))
+		}
+	}
+
+	// Start up the server.
+	srvCmd := exec.Command(cfg.serverBin, srvArgs...)
+	srvCmd.Env = append(os.Environ(),
+		fmt.Sprintf("GOMAXPROCS=%d", cfg.serverProcs),
+	)
+	srvCmd.Stdout = out
+	srvCmd.Stderr = out
+	if err := srvCmd.Start(); err != nil {
+		return nil, fmt.Errorf("failed to start server: %v", err)
+	}
+
+	// Poll until the server is ready to serve, up to 120 seconds.
+	var err error
+	start := time.Now()
+	for time.Now().Sub(start) < 120*time.Second {
+		var c redis.Conn
+		c, err = redis.Dial("tcp", fmt.Sprintf("%s:%d", cfg.host, cfg.port))
+		if err == nil {
+			c.Close()
+			return srvCmd, nil
+		}
+		time.Sleep(2 * time.Second)
+	}
+	return nil, fmt.Errorf("timeout trying to connect to server: %v", err)
+}
+
+func runOne(bench benchmark, cfg *config) (err error) {
+	var buf bytes.Buffer
+
+	// Launch the server.
+	srvCmd, err := launchServer(cfg, &buf)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "starting server: %v\n%s\n", err, &buf)
+		os.Exit(1)
+	}
+
+	// Clean up the server process after we're done.
+	defer func() {
+		if r := srvCmd.Process.Signal(os.Interrupt); r != nil {
+			if err == nil {
+				err = r
+			} else {
+				fmt.Fprintf(os.Stderr, "failed to shut down server: %v\n", r)
+			}
+			return
+		}
+		if _, r := srvCmd.Process.Wait(); r != nil {
+			if err == nil {
+				err = r
+			} else if r != nil {
+				fmt.Fprintf(os.Stderr, "failed to wait for server to exit: %v\n", r)
+			}
+			return
+		}
+		if buf.Len() != 0 {
+			fmt.Fprintln(os.Stderr, "=== Server stdout+stderr ===")
+			fmt.Fprintln(os.Stderr, buf.String())
+		}
+
+		// Now that the server is done, the profile should be complete and flushed.
+		// Copy it over.
+		for _, typ := range []driver.ProfileType{driver.ProfileCPU, driver.ProfileMem} {
+			if driver.ProfilingEnabled(typ) {
+				p, r := driver.ReadProfile(cfg.profilePath(typ))
+				if r != nil {
+					err = r
+					return
+				}
+				if r := driver.WriteProfile(p, typ, bench.name()); r != nil {
+					err = r
+					return
+				}
+			}
+		}
+	}()
+
+	rand.Seed(cfg.seed)
+	opts := []driver.RunOption{
+		driver.DoPeakRSS(true),
+		driver.DoPeakVM(true),
+		driver.DoDefaultAvgRSS(),
+		driver.DoCoreDump(true),
+		driver.BenchmarkPID(srvCmd.Process.Pid),
+		driver.DoPerf(true),
+	}
+	return driver.RunBenchmark(bench.name(), func(d *driver.B) error {
+		return bench.run(d, cfg.host, cfg.port, cfg.serverProcs, cfg.iter)
+	}, opts...)
+}
+
+func main() {
+	flag.Parse()
+	if flag.NArg() != 0 {
+		fmt.Fprintf(os.Stderr, "error: unexpected args\n")
+		os.Exit(1)
+	}
+	for _, typ := range driver.ProfileTypes {
+		cliCfg.isProfiling = cliCfg.isProfiling || driver.ProfilingEnabled(typ)
+	}
+	for _, bench := range benchmarks {
+		if err := runOne(bench, &cliCfg); err != nil {
+			fmt.Fprintf(os.Stderr, "error: %v\n", err)
+			os.Exit(1)
+		}
+	}
+}
diff --git a/sweet/cli/bootstrap/cache.go b/sweet/cli/bootstrap/cache.go
new file mode 100644
index 0000000..15221e9
--- /dev/null
+++ b/sweet/cli/bootstrap/cache.go
@@ -0,0 +1,36 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package bootstrap
+
+import (
+	"errors"
+	"fmt"
+	"os"
+	"path/filepath"
+)
+
+var ErrNotInCache = errors.New("not found in cache")
+
+func CachedAssets(cache, version string) (string, error) {
+	name := VersionDirName(version)
+	if err := os.MkdirAll(cache, os.ModePerm); err != nil {
+		return "", fmt.Errorf("failed to create cache directory: %v", err)
+	}
+	cacheloc := filepath.Join(cache, name)
+	if _, err := os.Lstat(cacheloc); os.IsNotExist(err) {
+		return cacheloc, ErrNotInCache
+	} else if err != nil {
+		return "", fmt.Errorf("failed to check cache: %v", err)
+	}
+	return cacheloc, nil
+}
+
+func CacheDefault() string {
+	cache, err := os.UserCacheDir()
+	if err == nil {
+		cache = filepath.Join(cache, "go-sweet")
+	}
+	return cache
+}
diff --git a/sweet/cli/bootstrap/gcs.go b/sweet/cli/bootstrap/gcs.go
new file mode 100644
index 0000000..9eab0bb
--- /dev/null
+++ b/sweet/cli/bootstrap/gcs.go
@@ -0,0 +1,116 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package bootstrap
+
+import (
+	"context"
+	"fmt"
+	"io"
+
+	"cloud.google.com/go/storage"
+
+	"golang.org/x/oauth2/google"
+
+	"google.golang.org/api/option"
+)
+
+type AuthOption int
+
+const (
+	AuthNone AuthOption = iota
+	AuthAppDefault
+	NumAuthOptions
+)
+
+var authOptString = [NumAuthOptions]string{
+	"none",
+	"app-default",
+}
+
+func (a *AuthOption) String() string {
+	return authOptString[*a]
+}
+
+func (a *AuthOption) Set(input string) error {
+	for i := range authOptString {
+		if authOptString[i] == input {
+			*a = AuthOption(i)
+			return nil
+		}
+	}
+	return fmt.Errorf("unrecognized authentication option: %s", input)
+}
+
+func UploadArchive(r io.Reader, bucket, version string, auth AuthOption, force, public bool) error {
+	ctx := context.Background()
+	var opts []option.ClientOption
+	switch auth {
+	case AuthAppDefault:
+		creds, err := google.FindDefaultCredentials(ctx, storage.ScopeReadWrite)
+		if err != nil {
+			return err
+		}
+		opts = append(opts, option.WithCredentials(creds))
+	case AuthNone:
+		return fmt.Errorf("authentication required for upload")
+	default:
+		return fmt.Errorf("unknown authentication method")
+	}
+	client, err := storage.NewClient(ctx, opts...)
+	if err != nil {
+		return err
+	}
+	o := client.Bucket(bucket).Object(VersionArchiveName(version))
+	if _, err := o.Attrs(ctx); err != nil && err != storage.ErrObjectNotExist {
+		return fmt.Errorf("checking if object exists: %v", err)
+	} else if err == nil && !force {
+		return fmt.Errorf("assets object already exists for version %s", version)
+	}
+
+	// Write the archive to GCS.
+	wc := o.NewWriter(ctx)
+	if _, err = io.Copy(wc, r); err != nil {
+		return err
+	}
+	if err := wc.Close(); err != nil {
+		return err
+	}
+
+	if public {
+		// Make the archive public.
+		acl := o.ACL()
+		return acl.Set(ctx, storage.AllUsers, storage.RoleReader)
+	}
+	return nil
+}
+
+func DownloadArchive(w io.Writer, bucket, version string, auth AuthOption) error {
+	ctx := context.Background()
+	opts := []option.ClientOption{option.WithScopes(storage.ScopeReadOnly)}
+	switch auth {
+	case AuthAppDefault:
+		creds, err := google.FindDefaultCredentials(ctx, storage.ScopeReadOnly)
+		if err != nil {
+			return err
+		}
+		opts = append(opts, option.WithCredentials(creds))
+	case AuthNone:
+		opts = append(opts, option.WithoutAuthentication())
+	default:
+		return fmt.Errorf("unknown authentication method")
+	}
+	client, err := storage.NewClient(ctx, opts...)
+	if err != nil {
+		return err
+	}
+	rc, err := client.Bucket(bucket).Object(VersionArchiveName(version)).NewReader(ctx)
+	if err != nil {
+		return err
+	}
+	if _, err = io.Copy(w, rc); err != nil {
+		return err
+	}
+	return rc.Close()
+}
diff --git a/sweet/cli/bootstrap/hash.go b/sweet/cli/bootstrap/hash.go
new file mode 100644
index 0000000..980b1d3
--- /dev/null
+++ b/sweet/cli/bootstrap/hash.go
@@ -0,0 +1,63 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package bootstrap
+
+import (
+	"crypto/sha256"
+	"encoding/json"
+	"fmt"
+	"hash"
+	"io"
+	"os"
+)
+
+type Hashes map[string]string
+
+func (h Hashes) Get(version string) (string, bool) {
+	v, b := h[version]
+	return v, b
+}
+
+func (h Hashes) Put(version string, hash string, force bool) bool {
+	if _, ok := h[version]; ok && !force {
+		return false
+	}
+	h[version] = hash
+	return true
+}
+
+func ReadHashesFile(hashfile string) (Hashes, error) {
+	f, err := os.Open(hashfile)
+	if os.IsNotExist(err) {
+		return make(Hashes), nil
+	} else if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+	var vals map[string]string
+	err = json.NewDecoder(f).Decode(&vals)
+	return Hashes(vals), err
+}
+
+func (h Hashes) WriteToFile(hashfile string) error {
+	f, err := os.Create(hashfile)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+	return json.NewEncoder(f).Encode(&h)
+}
+
+func canonicalizeHash(h hash.Hash) string {
+	return fmt.Sprintf("%x", h.Sum(nil))
+}
+
+func HashStream(r io.Reader) (string, error) {
+	hash := sha256.New()
+	if _, err := io.Copy(hash, r); err != nil {
+		return "", err
+	}
+	return canonicalizeHash(hash), nil
+}
diff --git a/sweet/cli/bootstrap/version.go b/sweet/cli/bootstrap/version.go
new file mode 100644
index 0000000..219ea45
--- /dev/null
+++ b/sweet/cli/bootstrap/version.go
@@ -0,0 +1,27 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package bootstrap
+
+import (
+	"fmt"
+	"regexp"
+)
+
+var versionRegexp = regexp.MustCompile(`v\d+\.\d+\.\d+`)
+
+func ValidateVersion(version string) error {
+	if !versionRegexp.MatchString(version) {
+		return fmt.Errorf("version must be of the form 'v1.2.3'")
+	}
+	return nil
+}
+
+func VersionArchiveName(version string) string {
+	return fmt.Sprintf("%s.tar.gz", VersionDirName(version))
+}
+
+func VersionDirName(version string) string {
+	return fmt.Sprintf("assets-%s", version)
+}
diff --git a/sweet/cli/subcommands/sc.go b/sweet/cli/subcommands/sc.go
new file mode 100644
index 0000000..6b3bba5
--- /dev/null
+++ b/sweet/cli/subcommands/sc.go
@@ -0,0 +1,135 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package subcommands
+
+import (
+	"flag"
+	"fmt"
+	"io"
+	"os"
+	"path/filepath"
+	"unicode/utf8"
+
+	"golang.org/x/benchmarks/sweet/common"
+)
+
+const (
+	usageHeader = `Sweet %s: Go Benchmarking Suite
+
+`
+	usageTop = `Sweet is a set of benchmarks derived from the Go community which are intended
+to represent a breadth of real-world applications. The primary use-case of this
+suite is to perform an evaluation of the difference in CPU and memory
+performance between two Go implementations.
+
+If you use this benchmarking suite for any measurements, please ensure you use
+a versioned release and note the version in the release.
+
+All results are reported in the standard Go testing package format, such that
+results may be compared using the benchstat tool.
+
+Usage: %s <subcommand> [subcommand flags] [subcommand args]
+
+Subcommands:
+`
+)
+
+var (
+	base string
+	cmds []*command
+	out  io.Writer
+)
+
+func init() {
+	base = filepath.Base(os.Args[0])
+	out = os.Stderr
+}
+
+type command struct {
+	Command
+	flags *flag.FlagSet
+}
+
+func (c *command) usage() {
+	fmt.Fprintf(out, usageHeader, common.Version)
+	c.PrintUsage(out, base)
+	c.flags.PrintDefaults()
+}
+
+type Command interface {
+	Name() string
+	Synopsis() string
+	PrintUsage(w io.Writer, base string)
+	SetFlags(f *flag.FlagSet)
+	Run(args []string) error
+}
+
+func Register(cmd Command) {
+	f := flag.NewFlagSet(cmd.Name(), flag.ExitOnError)
+	cmd.SetFlags(f)
+	c := &command{
+		Command: cmd,
+		flags:   f,
+	}
+	f.Usage = func() {
+		c.usage()
+	}
+	cmds = append(cmds, c)
+}
+
+func usage() {
+	fmt.Fprintf(out, usageHeader, common.Version)
+	fmt.Fprintf(out, usageTop, base)
+	maxnamelen := 10
+	for _, c := range cmds {
+		l := utf8.RuneCountInString(c.Name())
+		if l > maxnamelen {
+			maxnamelen = l
+		}
+	}
+	for _, c := range cmds {
+		fmt.Fprintf(out, fmt.Sprintf("  %%%ds: %%s\n", maxnamelen), c.Name(), c.Synopsis())
+	}
+}
+
+func Run() int {
+	if len(os.Args) < 2 {
+		usage()
+		os.Exit(1)
+	}
+	subcmd := os.Args[1]
+	if subcmd == "help" {
+		if len(os.Args) >= 3 {
+			subhelp := os.Args[2]
+			for _, cmd := range cmds {
+				if cmd.Name() == subhelp {
+					cmd.usage()
+					return 0
+				}
+			}
+		}
+		usage()
+		return 0
+	}
+	var chosen *command
+	for _, cmd := range cmds {
+		if cmd.Name() == subcmd {
+			chosen = cmd
+			break
+		}
+	}
+	if chosen == nil {
+		fmt.Fprintf(out, "unknown subcommand: %q\n", subcmd)
+		fmt.Fprintln(out)
+		usage()
+		return 1
+	}
+	chosen.flags.Parse(os.Args[2:])
+	if err := chosen.Run(chosen.flags.Args()); err != nil {
+		fmt.Fprintf(out, "error: %v\n", err)
+		return 1
+	}
+	return 0
+}
diff --git a/sweet/cmd/sweet/benchmark.go b/sweet/cmd/sweet/benchmark.go
new file mode 100644
index 0000000..1779dd2
--- /dev/null
+++ b/sweet/cmd/sweet/benchmark.go
@@ -0,0 +1,304 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"runtime"
+	"runtime/debug"
+
+	"golang.org/x/benchmarks/sweet/common"
+	"golang.org/x/benchmarks/sweet/common/fileutil"
+	"golang.org/x/benchmarks/sweet/common/log"
+	"golang.org/x/benchmarks/sweet/generators"
+	"golang.org/x/benchmarks/sweet/harnesses"
+)
+
+var allBenchmarks = []benchmark{
+	{
+		name:        "biogo-igor",
+		description: "Reports feature family groupings in pairwise alignment data",
+		harness:     harnesses.BiogoIgor(),
+		generator:   generators.BiogoIgor(),
+	},
+	{
+		name:        "biogo-krishna",
+		description: "Performs pairwise alignment of a target sequence against itself",
+		harness:     harnesses.BiogoKrishna(),
+		generator:   generators.BiogoKrishna(),
+	},
+	{
+		name:        "bleve-index",
+		description: "Indexes a subset of Wikipedia into a search index",
+		harness:     harnesses.BleveIndex(),
+		generator:   generators.BleveIndex(),
+	},
+	{
+		name:        "bleve-query",
+		description: "Queries a pre-built search index with keywords",
+		harness:     harnesses.BleveQuery(),
+		generator:   generators.BleveQuery{},
+	},
+	{
+		name:        "fogleman-fauxgl",
+		description: "Renders a rotating boat via an OpenGL-like software rendering pipeline",
+		harness:     harnesses.FoglemanFauxGL(),
+		generator:   generators.FoglemanFauxGL(),
+	},
+	{
+		name:        "fogleman-pt",
+		description: "Renders a Go gopher via path tracing",
+		harness:     harnesses.FoglemanPT(),
+		generator:   generators.FoglemanPT(),
+	},
+	{
+		name:        "go-build",
+		description: "Go build command",
+		harness:     harnesses.GoBuild{},
+		generator:   generators.None{},
+	},
+	{
+		name:        "gopher-lua",
+		description: "Runs a k-nucleotide benchmark written in Lua on a Go-based Lua VM",
+		harness:     harnesses.GopherLua(),
+		generator:   generators.GopherLua(),
+	},
+	{
+		name:        "gvisor",
+		description: "Container runtime sandbox for Linux (requires root)",
+		harness:     harnesses.GVisor{},
+		generator:   generators.GVisor{},
+	},
+	{
+		name:        "markdown",
+		description: "Renders a corpus of markdown documents to XHTML",
+		harness:     harnesses.Markdown(),
+		generator:   generators.Markdown(),
+	},
+	{
+		name:        "tile38",
+		description: "Redis-like geospatial database and geofencing server",
+		harness:     harnesses.Tile38{},
+		generator:   generators.Tile38{},
+	},
+}
+
+var allBenchmarksMap = func() map[string]*benchmark {
+	m := make(map[string]*benchmark)
+	for i := range allBenchmarks {
+		m[allBenchmarks[i].name] = &allBenchmarks[i]
+	}
+	return m
+}()
+
+var benchmarkGroups = map[string][]*benchmark{
+	"default": {
+		allBenchmarksMap["biogo-igor"],
+		allBenchmarksMap["biogo-krishna"],
+		allBenchmarksMap["bleve-index"],
+		allBenchmarksMap["bleve-query"],
+		allBenchmarksMap["fogleman-fauxgl"],
+		allBenchmarksMap["fogleman-pt"],
+		allBenchmarksMap["go-build"],
+		allBenchmarksMap["gopher-lua"],
+		allBenchmarksMap["gvisor"],
+		allBenchmarksMap["markdown"],
+		allBenchmarksMap["tile38"],
+	},
+	"all": func() (b []*benchmark) {
+		for i := range allBenchmarks {
+			b = append(b, &allBenchmarks[i])
+		}
+		return
+	}(),
+}
+
+func benchmarkNames(b []*benchmark) (s []string) {
+	for _, bench := range b {
+		s = append(s, bench.name)
+	}
+	return
+}
+
+func mkdirAll(path string) error {
+	log.CommandPrintf("mkdir -p %s", path)
+	return os.MkdirAll(path, os.ModePerm)
+}
+
+func copyDirContents(dst, src string) error {
+	log.CommandPrintf("cp -r %s/* %s", src, dst)
+	return fileutil.CopyDir(dst, src)
+}
+
+func rmDirContents(dir string) error {
+	log.CommandPrintf("rm -rf %s/*", dir)
+	fs, err := ioutil.ReadDir(dir)
+	if err != nil {
+		return err
+	}
+	for _, fi := range fs {
+		if err := os.RemoveAll(filepath.Join(dir, fi.Name())); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+type benchmark struct {
+	name        string
+	description string
+	harness     common.Harness
+	generator   common.Generator
+}
+
+func (b *benchmark) execute(cfgs []*common.Config, r *runCfg) error {
+	log.Printf("Setting up benchmark: %s", b.name)
+
+	// Compute top-level directories for this benchmark to work in.
+	topAssetsDir := filepath.Join(r.assetsDir, b.name)
+	benchDir := filepath.Join(r.benchDir, b.name)
+	topDir := filepath.Join(r.workDir, b.name)
+	srcDir := filepath.Join(topDir, "src")
+
+	hasAssets, err := fileutil.FileExists(topAssetsDir)
+	if err != nil {
+		return err
+	}
+
+	// Retrieve the benchmark's source.
+	if err := b.harness.Get(srcDir); err != nil {
+		return fmt.Errorf("retrieving source for %s: %v", b.name, err)
+	}
+
+	// Create the results directory for the benchmark.
+	resultsDir := filepath.Join(r.resultsDir, b.name)
+	if err := mkdirAll(resultsDir); err != nil {
+		return fmt.Errorf("creating results directory for %s: %v", b.name, err)
+	}
+
+	// Perform a setup step for each config for the benchmark.
+	setups := make([]common.RunConfig, 0, len(cfgs))
+	for _, cfg := range cfgs {
+		// Create directory heirarchy for benchmarks.
+		workDir := filepath.Join(topDir, cfg.Name)
+		binDir := filepath.Join(workDir, "bin")
+		tmpDir := filepath.Join(workDir, "tmp")
+		assetsDir := filepath.Join(workDir, "assets")
+		if err := mkdirAll(binDir); err != nil {
+			return fmt.Errorf("create %s bin for %s: %v", b.name, cfg.Name, err)
+		}
+		if err := mkdirAll(srcDir); err != nil {
+			return fmt.Errorf("create %s src for %s: %v", b.name, cfg.Name, err)
+		}
+		if err := mkdirAll(tmpDir); err != nil {
+			return fmt.Errorf("create %s tmp for %s: %v", b.name, cfg.Name, err)
+		}
+		if hasAssets {
+			if err := mkdirAll(assetsDir); err != nil {
+				return fmt.Errorf("create %s assets dir for %s: %v", b.name, cfg.Name, err)
+			}
+		}
+
+		// Build the benchmark (application and any other necessary components).
+		bcfg := common.BuildConfig{
+			BinDir:   binDir,
+			SrcDir:   srcDir,
+			BenchDir: benchDir,
+		}
+		if err := b.harness.Build(cfg, &bcfg); err != nil {
+			return fmt.Errorf("build %s for %s: %v", b.name, cfg.Name, err)
+		}
+
+		// Generate any args to funnel through to benchmarks.
+		args := []string{}
+		if r.dumpCore {
+			// Create a directory for the core files to live in.
+			resultsCoresDir := filepath.Join(resultsDir, "core")
+			mkdirAll(resultsCoresDir)
+			// We need to pass an argument to the benchmark binary to generate
+			// a core file. See benchmarks/internal/driver for details.
+			args = append(args, "-dump-cores", resultsCoresDir)
+			// Copy the bin directory so that the binaries may be used to analyze
+			// the core dump.
+			resultsBinDir := filepath.Join(resultsDir, "bin")
+			mkdirAll(resultsBinDir)
+			copyDirContents(resultsBinDir, binDir)
+		}
+		if r.cpuProfile || r.memProfile || r.perf {
+			// Create a directory for any profile files to live in.
+			resultsProfilesDir := filepath.Join(resultsDir, fmt.Sprintf("%s.debug", cfg.Name))
+			mkdirAll(resultsProfilesDir)
+
+			// We need to pass arguments to the benchmark binary to generate
+			// profiles. See benchmarks/internal/driver for details.
+			if r.cpuProfile {
+				args = append(args, "-cpuprofile", resultsProfilesDir)
+			}
+			if r.memProfile {
+				args = append(args, "-memprofile", resultsProfilesDir)
+			}
+			if r.perf {
+				args = append(args, "-perf", resultsProfilesDir)
+				if r.perfFlags != "" {
+					args = append(args, "-perf-flags", r.perfFlags)
+				}
+			}
+		}
+
+		results, err := os.Create(filepath.Join(resultsDir, fmt.Sprintf("%s.results", cfg.Name)))
+		if err != nil {
+			return fmt.Errorf("create %s results file for %s: %v", b.name, cfg.Name, err)
+		}
+		defer results.Close()
+		setups = append(setups, common.RunConfig{
+			BinDir:    binDir,
+			TmpDir:    tmpDir,
+			AssetsDir: assetsDir,
+			Args:      args,
+			Results:   results,
+		})
+	}
+
+	for j := 0; j < r.count; j++ {
+		// Execute the benchmark for each configuration.
+		for i, setup := range setups {
+			if hasAssets {
+				// Set up assets directory for test run.
+				if err := copyDirContents(setup.AssetsDir, topAssetsDir); err != nil {
+					return err
+				}
+			}
+
+			log.Printf("Running benchmark %s for %s: run %d", b.name, cfgs[i].Name, j+1)
+			// Force a GC now because we're about to turn it off.
+			runtime.GC()
+			// Hold your breath: we're turning off GC for the duration of the
+			// run so that the suite's GC doesn't start blasting on all Ps,
+			// introducing undue noise into the experiments.
+			gogc := debug.SetGCPercent(-1)
+			if err := b.harness.Run(cfgs[i], &setup); err != nil {
+				debug.SetGCPercent(gogc)
+				setup.Results.Close()
+				return fmt.Errorf("run benchmark %s for config %s: %v", b.name, cfgs[i].Name, err)
+			}
+			debug.SetGCPercent(gogc)
+
+			// Clean up tmp directory so benchmarks may assume it's empty.
+			if err := rmDirContents(setup.TmpDir); err != nil {
+				return err
+			}
+			if hasAssets {
+				// Clean up assets directory just in case any of the files were written to.
+				if err := rmDirContents(setup.AssetsDir); err != nil {
+					return err
+				}
+			}
+		}
+	}
+	return nil
+}
diff --git a/sweet/cmd/sweet/gen.go b/sweet/cmd/sweet/gen.go
new file mode 100644
index 0000000..247f9d6
--- /dev/null
+++ b/sweet/cmd/sweet/gen.go
@@ -0,0 +1,179 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"flag"
+	"fmt"
+	"io"
+	"os"
+	"path/filepath"
+	"sort"
+	"strings"
+	"unicode/utf8"
+
+	"golang.org/x/benchmarks/sweet/common"
+	"golang.org/x/benchmarks/sweet/common/log"
+)
+
+const (
+	genLongDesc = `Generate dynamic binary assets.`
+	genUsage    = `Usage: %s gen [flags]
+`
+)
+
+type genCfg struct {
+	assetsDir       string
+	sourceAssetsDir string
+	outputDir       string
+}
+
+type genCmd struct {
+	genCfg
+	quiet bool
+	toGen csvFlag
+}
+
+func (*genCmd) Name() string     { return "gen" }
+func (*genCmd) Synopsis() string { return "Generate dynamic binary assets." }
+func (*genCmd) PrintUsage(w io.Writer, base string) {
+	// Print header.
+	fmt.Fprintln(w, genLongDesc)
+
+	// Print supported benchmarks.
+	fmt.Fprintln(w, "\nSupported benchmarks:")
+	maxBenchNameLen := 0
+	for _, b := range allBenchmarks {
+		l := utf8.RuneCountInString(b.name)
+		if l > maxBenchNameLen {
+			maxBenchNameLen = l
+		}
+	}
+	for _, b := range allBenchmarks {
+		fmt.Fprintf(w, fmt.Sprintf("  %%%ds: %%s\n", maxBenchNameLen), b.name, b.description)
+	}
+
+	// Print benchmark groups.
+	fmt.Fprintln(w, "\nBenchmark groups:")
+	maxGroupNameLen := 0
+	var groups []string
+	for groupName := range benchmarkGroups {
+		l := utf8.RuneCountInString(groupName)
+		if l > maxGroupNameLen {
+			maxGroupNameLen = l
+		}
+		groups = append(groups, groupName)
+	}
+	sort.Strings(groups)
+	for _, group := range groups {
+		var groupBenchNames []string
+		if group == "all" {
+			groupBenchNames = []string{"all supported benchmarks"}
+		} else {
+			groupBenchNames = benchmarkNames(benchmarkGroups[group])
+		}
+		fmt.Fprintf(w, fmt.Sprintf("  %%%ds: %%s\n", maxGroupNameLen), group, strings.Join(groupBenchNames, " "))
+	}
+
+	// Print usage line. Flags will automatically be added after.
+	fmt.Fprintf(w, genUsage, base)
+}
+
+func (c *genCmd) SetFlags(f *flag.FlagSet) {
+	f.StringVar(&c.genCfg.assetsDir, "assets-dir", "./assets", "the directory containing existing assets for sweet benchmarks")
+	f.StringVar(&c.genCfg.sourceAssetsDir, "source-assets-dir", "./source-assets", "the directory containing source assets for some of the generators")
+	f.StringVar(&c.genCfg.outputDir, "output-dir", "./assets", "the directory into which new assets should be generated")
+
+	f.BoolVar(&c.quiet, "quiet", false, "whether to suppress activity output on stderr (no effect on -shell)")
+	f.Var(&c.toGen, "gen", "benchmark group or comma-separated list of benchmarks to gen (default: all)")
+}
+
+func (c *genCmd) Run(args []string) error {
+	if len(args) != 0 {
+		return fmt.Errorf("unexpected arguments")
+	}
+
+	log.SetActivityLog(!c.quiet)
+
+	// Ensure all provided directories are absolute paths. This avoids problems with
+	// benchmarks potentially changing their current working directory.
+	var err error
+	c.assetsDir, err = filepath.Abs(c.assetsDir)
+	if err != nil {
+		return fmt.Errorf("creating absolute path from assets path: %v", err)
+	}
+	c.sourceAssetsDir, err = filepath.Abs(c.sourceAssetsDir)
+	if err != nil {
+		return fmt.Errorf("creating absolute path from source assets path: %v", err)
+	}
+	c.outputDir, err = filepath.Abs(c.outputDir)
+	if err != nil {
+		return fmt.Errorf("creating absolute path from output path: %v", err)
+	}
+
+	// Make sure the assets directory is there.
+	if info, err := os.Stat(c.assetsDir); os.IsNotExist(err) {
+		return fmt.Errorf("assets not found at %q: forgot to run `sweet get (-copy)`?", c.assetsDir)
+	} else if err != nil {
+		return fmt.Errorf("stat assets %q: %v", c.assetsDir, err)
+	} else if !info.IsDir() {
+		return fmt.Errorf("%q is not a directory", c.assetsDir)
+	}
+
+	// Decide which benchmarks to gen assets for, based on the -gen flag.
+	var benchmarks []*benchmark
+	var unknown []string
+	switch len(c.toGen) {
+	case 0:
+		benchmarks = benchmarkGroups["all"]
+	case 1:
+		if grp, ok := benchmarkGroups[c.toGen[0]]; ok {
+			benchmarks = grp
+			break
+		}
+		fallthrough
+	default:
+		for _, name := range c.toGen {
+			if benchmark, ok := allBenchmarksMap[name]; ok {
+				benchmarks = append(benchmarks, benchmark)
+			} else {
+				unknown = append(unknown, name)
+			}
+		}
+	}
+	if len(unknown) != 0 {
+		return fmt.Errorf("unknown benchmarks: %s", strings.Join(unknown, ", "))
+	}
+
+	// Find the go tool.
+	goTool, err := common.SystemGoTool()
+	if err != nil {
+		return err
+	}
+	log.Printf("Using Go: %s", goTool.Tool)
+
+	// Execute each generator.
+	for _, b := range benchmarks {
+		log.Printf("Generating assets: %s", b.name)
+		outputDir := filepath.Join(c.outputDir, b.name)
+		if err := mkdirAll(outputDir); err != nil {
+			return err
+		}
+		// Make a copy of the Go tool so that the generator
+		// is free to manipulate it without it leaking between
+		// generators.
+		gt := *goTool
+		cfg := common.GenConfig{
+			AssetsDir:       filepath.Join(c.assetsDir, b.name),
+			SourceAssetsDir: filepath.Join(c.sourceAssetsDir, b.name),
+			OutputDir:       outputDir,
+			GoTool:          &gt,
+		}
+		if err := b.generator.Generate(&cfg); err != nil {
+			return err
+		}
+	}
+	return nil
+}
diff --git a/sweet/cmd/sweet/get.go b/sweet/cmd/sweet/get.go
new file mode 100644
index 0000000..2ab645a
--- /dev/null
+++ b/sweet/cmd/sweet/get.go
@@ -0,0 +1,213 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"archive/tar"
+	"compress/gzip"
+	"flag"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"golang.org/x/benchmarks/sweet/cli/bootstrap"
+	"golang.org/x/benchmarks/sweet/common"
+	"golang.org/x/benchmarks/sweet/common/fileutil"
+	"golang.org/x/benchmarks/sweet/common/log"
+)
+
+const (
+	getUsage = `Retrieves assets for benchmarks from GCS.
+
+Usage: %s get [flags]
+`
+)
+
+func authOpts(includeNone bool) string {
+	i := bootstrap.AuthOption(0)
+	if !includeNone {
+		i = 1
+	}
+	s := make([]string, 0, bootstrap.NumAuthOptions)
+	for ; i < bootstrap.NumAuthOptions; i++ {
+		s = append(s, i.String())
+	}
+	return strings.Join(s, ", ")
+}
+
+type getCmd struct {
+	auth           bootstrap.AuthOption
+	force          bool
+	copyAssets     bool
+	cache          string
+	bucket         string
+	assetsDir      string
+	assetsHashFile string
+	version        string
+}
+
+func (*getCmd) Name() string     { return "get" }
+func (*getCmd) Synopsis() string { return "Retrieves assets for benchmarks." }
+func (*getCmd) PrintUsage(w io.Writer, base string) {
+	fmt.Fprintf(w, getUsage, base)
+}
+
+func (c *getCmd) SetFlags(f *flag.FlagSet) {
+	f.Var(&c.auth, "auth", fmt.Sprintf("authentication method (options: %s)", authOpts(true)))
+	f.BoolVar(&c.force, "force", false, "force download even if assets for this version exist in the cache")
+	f.BoolVar(&c.copyAssets, "copy", false, "copy assets to assets-dir instead of symlinking")
+	f.StringVar(&c.cache, "cache", bootstrap.CacheDefault(), "cache location for tar'd and compressed assets, if set to \"\" will ignore cache")
+	f.StringVar(&c.version, "version", common.Version, "the version to download assets for")
+	f.StringVar(&c.bucket, "bucket", "go-sweet-assets", "GCS bucket to download assets from")
+	f.StringVar(&c.assetsDir, "assets-dir", "./assets", "location to extract assets into")
+	f.StringVar(&c.assetsHashFile, "assets-hash-file", "./assets.hash", "file to check SHA256 hash of the downloaded artifact against")
+}
+
+func (c *getCmd) Run(_ []string) error {
+	log.SetActivityLog(true)
+	if err := bootstrap.ValidateVersion(c.version); err != nil {
+		return err
+	}
+	installAssets := func(todir string, readonly bool) error {
+		return downloadAndExtract(todir, c.bucket, c.assetsHashFile, c.version, c.auth, readonly)
+	}
+	if c.cache == "" {
+		log.Printf("Skipping cache...")
+		return installAssets(c.assetsDir, false)
+	}
+	log.Printf("Checking cache: %s", c.cache)
+	t, err := bootstrap.CachedAssets(c.cache, c.version)
+	if err == bootstrap.ErrNotInCache || (err == nil && c.force) {
+		if err := installAssets(t, true); err != nil {
+			return err
+		}
+	} else if err != nil {
+		return err
+	}
+	if !c.copyAssets {
+		log.Printf("Creating symlink to %s", c.assetsDir)
+		if info, err := os.Lstat(c.assetsDir); err == nil {
+			if info.Mode()&os.ModeSymlink != 0 {
+				// We have a symlink, so just delete it so we can replace it.
+				if err := os.Remove(c.assetsDir); err != nil {
+					return fmt.Errorf("installing assets: removing %s: %v", c.assetsDir, err)
+				}
+			} else {
+				return fmt.Errorf("installing assets: %s is not a symlink; to install assets here, remove it and re-run this command", c.assetsDir)
+			}
+		} else if !os.IsNotExist(err) {
+			return fmt.Errorf("stat %s: %v", c.assetsDir, err)
+		}
+		return os.Symlink(t, c.assetsDir)
+	}
+	if _, err := os.Stat(c.assetsDir); err == nil {
+		return fmt.Errorf("installing assets: %s exists; to copy assets here, remove it and re-run this command", c.assetsDir)
+	} else if !os.IsNotExist(err) {
+		return fmt.Errorf("stat %s: %v", c.assetsDir, err)
+	}
+	log.Printf("Copying assets %s", c.assetsDir)
+	return fileutil.CopyDir(c.assetsDir, t)
+}
+
+func downloadAndExtract(todir, bucket, hashfile, version string, auth bootstrap.AuthOption, readonly bool) error {
+	tf, err := ioutil.TempFile("", "go-sweet-assets")
+	if err != nil {
+		return err
+	}
+	defer tf.Close()
+	log.Printf("Downloading assets archive for version %s", version)
+	if err := bootstrap.DownloadArchive(tf, bucket, version, auth); err != nil {
+		tf.Close()
+		return err
+	}
+	if _, err := tf.Seek(0, 0); err != nil {
+		return err
+	}
+	log.Printf("Verifying archive checksum...")
+	if err := checkAssetsHash(tf, hashfile, version); err != nil {
+		return err
+	}
+	if _, err := tf.Seek(0, 0); err != nil {
+		return err
+	}
+	log.Printf("Installing assets to %s", todir)
+	return extractAssets(tf, todir, readonly)
+}
+
+func checkAssetsHash(tf io.Reader, hashfile, version string) error {
+	vals, err := bootstrap.ReadHashesFile(hashfile)
+	if err != nil {
+		return err
+	}
+	hash, err := bootstrap.HashStream(tf)
+	if err != nil {
+		return err
+	}
+	check, ok := vals.Get(version)
+	if !ok {
+		return fmt.Errorf("hash for version %s not found", version)
+	}
+	if hash != check {
+		return fmt.Errorf("downloaded artifact has unexpected hash: expected %s, got %s", hash, check)
+	}
+	return nil
+}
+
+func extractAssets(tf io.Reader, outdir string, readonly bool) error {
+	if err := os.MkdirAll(outdir, os.ModePerm); err != nil {
+		return fmt.Errorf("create assets directory: %v", err)
+	}
+	gr, err := gzip.NewReader(tf)
+	if err != nil {
+		return err
+	}
+	defer gr.Close()
+
+	tr := tar.NewReader(gr)
+	for {
+		hdr, err := tr.Next()
+		if err == io.EOF {
+			break
+		} else if err != nil {
+			return err
+		}
+		fullpath := filepath.Join(outdir, hdr.Name)
+		if err := os.MkdirAll(filepath.Dir(fullpath), os.ModePerm); err != nil {
+			return err
+		}
+		f, err := os.Create(fullpath)
+		if err != nil {
+			return err
+		}
+		if _, err := io.Copy(f, tr); err != nil {
+			f.Close()
+			return err
+		}
+		fperm := os.FileMode(uint32(hdr.Mode))
+		if readonly {
+			fperm = 0444 | (fperm & 0555)
+		}
+		if err := f.Chmod(fperm); err != nil {
+			f.Close()
+			return err
+		}
+		f.Close()
+	}
+	if readonly {
+		return filepath.Walk(outdir, func(path string, info os.FileInfo, err error) error {
+			if err != nil {
+				return err
+			}
+			if info.IsDir() {
+				return os.Chmod(path, 0555)
+			}
+			return nil
+		})
+	}
+	return nil
+}
diff --git a/sweet/cmd/sweet/main.go b/sweet/cmd/sweet/main.go
new file mode 100644
index 0000000..cf09968
--- /dev/null
+++ b/sweet/cmd/sweet/main.go
@@ -0,0 +1,19 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"os"
+
+	"golang.org/x/benchmarks/sweet/cli/subcommands"
+)
+
+func main() {
+	subcommands.Register(&getCmd{})
+	subcommands.Register(&putCmd{})
+	subcommands.Register(&runCmd{})
+	subcommands.Register(&genCmd{})
+	os.Exit(subcommands.Run())
+}
diff --git a/sweet/cmd/sweet/put.go b/sweet/cmd/sweet/put.go
new file mode 100644
index 0000000..5b75910
--- /dev/null
+++ b/sweet/cmd/sweet/put.go
@@ -0,0 +1,150 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"archive/tar"
+	"compress/gzip"
+	"flag"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+
+	"golang.org/x/benchmarks/sweet/cli/bootstrap"
+	"golang.org/x/benchmarks/sweet/common"
+	"golang.org/x/benchmarks/sweet/common/log"
+)
+
+const (
+	putUsage = `Uploads a new version of the benchmark assets to GCS.
+
+Usage: %s put [flags]
+`
+)
+
+type putCmd struct {
+	auth           bootstrap.AuthOption
+	force          bool
+	public         bool
+	cache          string
+	bucket         string
+	assetsDir      string
+	assetsHashFile string
+	version        string
+}
+
+func (*putCmd) Name() string { return "put" }
+func (*putCmd) Synopsis() string {
+	return "Uploads a new version of the benchmark assets."
+}
+func (*putCmd) PrintUsage(w io.Writer, base string) {
+	fmt.Fprintf(w, putUsage, base)
+}
+
+func (c *putCmd) SetFlags(f *flag.FlagSet) {
+	c.auth = bootstrap.AuthAppDefault
+	f.Var(&c.auth, "auth", fmt.Sprintf("authentication method (options: %s)", authOpts(false)))
+	f.BoolVar(&c.force, "force", false, "force upload even if assets for this version exist")
+	f.BoolVar(&c.public, "public", false, "make the new assets archive public")
+	f.StringVar(&c.version, "version", common.Version, "the version to upload assets for")
+	f.StringVar(&c.bucket, "bucket", "go-sweet-assets", "GCS bucket to upload assets to")
+	f.StringVar(&c.assetsDir, "assets-dir", "./assets", "assets directory to tar, compress, and upload")
+	f.StringVar(&c.assetsHashFile, "assets-hash-file", "./assets.hash", "file containing assets SHA256 hashes")
+}
+
+func (c *putCmd) Run(_ []string) error {
+	log.SetActivityLog(true)
+
+	if err := bootstrap.ValidateVersion(c.version); err != nil {
+		return err
+	}
+	tf, err := ioutil.TempFile("", "go-sweet-assets")
+	if err != nil {
+		return err
+	}
+	defer tf.Close()
+
+	log.Printf("Archiving and compressing: %s", c.assetsDir)
+	if err := createAssetsArchive(tf, c.assetsDir, c.version); err != nil {
+		return err
+	}
+	if _, err := tf.Seek(0, 0); err != nil {
+		return err
+	}
+	log.Printf("Uploading archive to %s", c.bucket)
+	if err := bootstrap.UploadArchive(tf, c.bucket, c.version, c.auth, c.force, c.public); err != nil {
+		return err
+	}
+	if _, err := tf.Seek(0, 0); err != nil {
+		return err
+	}
+	log.Printf("Updating hash file...")
+	return hashAssetsArchive(tf, c.assetsHashFile, c.version, c.force)
+}
+
+func createAssetsArchive(w io.Writer, assetsDir, version string) error {
+	gw := gzip.NewWriter(w)
+	defer gw.Close()
+
+	tw := tar.NewWriter(gw)
+	defer tw.Close()
+
+	return filepath.Walk(assetsDir, func(fpath string, info os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+		if info.IsDir() {
+			return nil
+		}
+		isSymlink := info.Mode()&os.ModeSymlink != 0
+		link := ""
+		if isSymlink {
+			l, err := os.Readlink(fpath)
+			if err != nil {
+				return err
+			}
+			link = l
+		}
+		f, err := os.Open(fpath)
+		if err != nil {
+			return err
+		}
+		defer f.Close()
+		header, err := tar.FileInfoHeader(info, link)
+		if err != nil {
+			return err
+		}
+		header.Name, err = filepath.Rel(assetsDir, fpath)
+		if err != nil {
+			panic(err)
+		}
+		if err := tw.WriteHeader(header); err != nil {
+			return err
+		}
+		if isSymlink {
+			// We don't need to copy any data for the symlink.
+			return nil
+		}
+		_, err = io.Copy(tw, f)
+		return err
+	})
+}
+
+func hashAssetsArchive(tf io.Reader, hashfile, version string, force bool) error {
+	vals, err := bootstrap.ReadHashesFile(hashfile)
+	if err != nil {
+		return err
+	}
+	hash, err := bootstrap.HashStream(tf)
+	if err != nil {
+		return err
+	}
+	if ok := vals.Put(version, hash, force); !ok {
+		return fmt.Errorf("hash for this version already exists")
+	}
+	return vals.WriteToFile(hashfile)
+}
diff --git a/sweet/cmd/sweet/run.go b/sweet/cmd/sweet/run.go
new file mode 100644
index 0000000..c03b3cc
--- /dev/null
+++ b/sweet/cmd/sweet/run.go
@@ -0,0 +1,292 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"flag"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"sort"
+	"strings"
+	"unicode/utf8"
+
+	"golang.org/x/benchmarks/sweet/common"
+	"golang.org/x/benchmarks/sweet/common/log"
+
+	"github.com/BurntSushi/toml"
+)
+
+type csvFlag []string
+
+func (c *csvFlag) String() string {
+	return strings.Join([]string(*c), ",")
+}
+
+func (c *csvFlag) Set(input string) error {
+	*c = strings.Split(input, ",")
+	return nil
+}
+
+const (
+	runLongDesc = `Execute benchmarks in the suite against GOROOTs provided in TOML configuration
+files.`
+	runUsage = `Usage: %s run [flags] <config> [configs...]
+`
+)
+
+type runCfg struct {
+	count      int
+	resultsDir string
+	benchDir   string
+	assetsDir  string
+	workDir    string
+	dumpCore   bool
+	cpuProfile bool
+	memProfile bool
+	perf       bool
+	perfFlags  string
+}
+
+type runCmd struct {
+	runCfg
+	quiet       bool
+	printCmd    bool
+	stopOnError bool
+	toRun       csvFlag
+}
+
+func (*runCmd) Name() string     { return "run" }
+func (*runCmd) Synopsis() string { return "Executes benchmarks in the suite." }
+func (*runCmd) PrintUsage(w io.Writer, base string) {
+	// Print header.
+	fmt.Fprintln(w, runLongDesc)
+
+	// Print supported benchmarks.
+	fmt.Fprintln(w, "\nSupported benchmarks:")
+	maxBenchNameLen := 0
+	for _, b := range allBenchmarks {
+		l := utf8.RuneCountInString(b.name)
+		if l > maxBenchNameLen {
+			maxBenchNameLen = l
+		}
+	}
+	for _, b := range allBenchmarks {
+		fmt.Fprintf(w, fmt.Sprintf("  %%%ds: %%s\n", maxBenchNameLen), b.name, b.description)
+	}
+
+	// Print benchmark groups.
+	fmt.Fprintln(w, "\nBenchmark groups:")
+	maxGroupNameLen := 0
+	var groups []string
+	for groupName := range benchmarkGroups {
+		l := utf8.RuneCountInString(groupName)
+		if l > maxGroupNameLen {
+			maxGroupNameLen = l
+		}
+		groups = append(groups, groupName)
+	}
+	sort.Strings(groups)
+	for _, group := range groups {
+		var groupBenchNames []string
+		if group == "all" {
+			groupBenchNames = []string{"all supported benchmarks"}
+		} else {
+			groupBenchNames = benchmarkNames(benchmarkGroups[group])
+		}
+		fmt.Fprintf(w, fmt.Sprintf("  %%%ds: %%s\n", maxGroupNameLen), group, strings.Join(groupBenchNames, " "))
+	}
+
+	// Print configuration format information.
+	fmt.Fprintf(w, common.ConfigHelp)
+	fmt.Fprintln(w)
+
+	// Print usage line. Flags will automatically be added after.
+	fmt.Fprintf(w, runUsage, base)
+}
+
+func (c *runCmd) SetFlags(f *flag.FlagSet) {
+	f.StringVar(&c.runCfg.resultsDir, "results", "./results", "location to write benchmark results to")
+	f.StringVar(&c.runCfg.benchDir, "bench-dir", "./benchmarks", "the benchmarks directory in the sweet source")
+	f.StringVar(&c.runCfg.assetsDir, "assets-dir", "./assets", "the directory containing assets for sweet benchmarks")
+	f.StringVar(&c.runCfg.workDir, "work-dir", "", "work directory for benchmarks (default: temporary directory)")
+	f.BoolVar(&c.runCfg.dumpCore, "dump-core", false, "whether to dump core files for each benchmark process when it completes a benchmark")
+	f.BoolVar(&c.runCfg.cpuProfile, "cpuprofile", false, "whether to dump a CPU profile for each benchmark (ensures all benchmarks do the same amount of work)")
+	f.BoolVar(&c.runCfg.memProfile, "memprofile", false, "whether to dump a memory profile for each benchmark (ensures all executions do the same amount of work")
+	f.BoolVar(&c.runCfg.perf, "perf", false, "whether to run each benchmark under Linux perf and dump the results")
+	f.StringVar(&c.runCfg.perfFlags, "perf-flags", "", "the flags to pass to Linux perf if -perf is set")
+	f.IntVar(&c.runCfg.count, "count", 25, "the number of times to run each benchmark")
+
+	f.BoolVar(&c.quiet, "quiet", false, "whether to suppress activity output on stderr (no effect on -shell)")
+	f.BoolVar(&c.printCmd, "shell", false, "whether to print the commands being executed to stdout")
+	f.BoolVar(&c.stopOnError, "stop-on-error", false, "whether to stop running benchmarks if an error occurs or a benchmark fails")
+	f.Var(&c.toRun, "run", "benchmark group or comma-separated list of benchmarks to run")
+}
+
+func (c *runCmd) Run(args []string) error {
+	if len(args) == 0 {
+		return fmt.Errorf("at least one configuration is required")
+	}
+	checkPlatform()
+
+	log.SetCommandTrace(c.printCmd)
+	log.SetActivityLog(!c.quiet)
+
+	var err error
+	if c.workDir == "" {
+		// Create a temporary work tree for running the benchmarks.
+		c.workDir, err = ioutil.TempDir("", "gosweet")
+		if err != nil {
+			return fmt.Errorf("creating work root: %v", err)
+		}
+	}
+	// Ensure all provided directories are absolute paths. This avoids problems with
+	// benchmarks potentially changing their current working directory.
+	c.workDir, err = filepath.Abs(c.workDir)
+	if err != nil {
+		return fmt.Errorf("creating absolute path from provided work root: %v", err)
+	}
+	c.assetsDir, err = filepath.Abs(c.assetsDir)
+	if err != nil {
+		return fmt.Errorf("creating absolute path from assets path: %v", err)
+	}
+	c.benchDir, err = filepath.Abs(c.benchDir)
+	if err != nil {
+		return fmt.Errorf("creating absolute path from benchmarks path: %v", err)
+	}
+	c.resultsDir, err = filepath.Abs(c.resultsDir)
+	if err != nil {
+		return fmt.Errorf("creating absolute path from results path: %v", err)
+	}
+
+	// Make sure the assets directory is there.
+	if info, err := os.Stat(c.assetsDir); os.IsNotExist(err) {
+		return fmt.Errorf("assets not found at %q: forgot to run `sweet get`?", c.assetsDir)
+	} else if err != nil {
+		return fmt.Errorf("stat assets %q: %v", c.assetsDir, err)
+	} else if info.Mode()&os.ModeDir == 0 {
+		return fmt.Errorf("%q is not a directory", c.assetsDir)
+	}
+	log.Printf("Work directory: %s", c.workDir)
+
+	// Parse and validate all input TOML configs.
+	configs := make([]*common.Config, 0, len(args))
+	names := make(map[string]struct{})
+	for _, configFile := range args {
+		// Make the configuration file path absolute relative to the CWD.
+		configFile, err := filepath.Abs(configFile)
+		if err != nil {
+			return fmt.Errorf("failed to absolutize %q: %v", configFile, err)
+		}
+		configDir := filepath.Dir(configFile)
+
+		// Read and parse the configuration file.
+		b, err := ioutil.ReadFile(configFile)
+		if err != nil {
+			return fmt.Errorf("failed to read %q: %v", configFile, err)
+		}
+		var fconfigs common.ConfigFile
+		if err := toml.Unmarshal(b, &fconfigs); err != nil {
+			return fmt.Errorf("failed to parse %q: %v", configFile, err)
+		}
+		// Validate each config and append to central list.
+		for _, config := range fconfigs.Configs {
+			if config.Name == "" {
+				return fmt.Errorf("config in %q is missing a name", configFile)
+			}
+			if _, ok := names[config.Name]; ok {
+				return fmt.Errorf("name of config in %q is not unique: %s", configFile, config.Name)
+			}
+			names[config.Name] = struct{}{}
+			if config.GoRoot == "" {
+				return fmt.Errorf("config %q in %q is missing a goroot", config.Name, configFile)
+			}
+			if strings.Contains(config.GoRoot, "~") {
+				return fmt.Errorf("path containing ~ found in config %q; feature not supported since v0.1.0", config.Name)
+			}
+			config.GoRoot = canonicalizePath(config.GoRoot, configDir)
+			if config.BuildEnv.Env == nil {
+				config.BuildEnv.Env = common.NewEnvFromEnviron()
+			}
+			if config.ExecEnv.Env == nil {
+				config.ExecEnv.Env = common.NewEnvFromEnviron()
+			}
+			configs = append(configs, config)
+		}
+	}
+
+	// Decide which benchmarks to run, based on the -run flag.
+	var benchmarks []*benchmark
+	var unknown []string
+	switch len(c.toRun) {
+	case 0:
+		benchmarks = benchmarkGroups["default"]
+	case 1:
+		if grp, ok := benchmarkGroups[c.toRun[0]]; ok {
+			benchmarks = grp
+			break
+		}
+		fallthrough
+	default:
+		for _, name := range c.toRun {
+			if benchmark, ok := allBenchmarksMap[name]; ok {
+				benchmarks = append(benchmarks, benchmark)
+			} else {
+				unknown = append(unknown, name)
+			}
+		}
+	}
+	if len(unknown) != 0 {
+		return fmt.Errorf("unknown benchmarks: %s", strings.Join(unknown, ", "))
+	}
+	log.Printf("Benchmarks: %s", strings.Join(benchmarkNames(benchmarks), " "))
+
+	// Check prerequisites for each benchmark.
+	for _, b := range benchmarks {
+		if err := b.harness.CheckPrerequisites(); err != nil {
+			return fmt.Errorf("failed to meet prerequisites for %s: %v", b.name, err)
+		}
+	}
+
+	// Execute each benchmark for all configs.
+	var errEncountered bool
+	for _, b := range benchmarks {
+		if err := b.execute(configs, &c.runCfg); err != nil {
+			if c.stopOnError {
+				return err
+			}
+			errEncountered = true
+			log.Printf("error: %v\n", err)
+		}
+	}
+	if errEncountered {
+		return fmt.Errorf("failed to execute benchmarks, see log for details")
+	}
+	return nil
+}
+
+func canonicalizePath(path, base string) string {
+	if filepath.IsAbs(path) {
+		return path
+	}
+	path = filepath.Join(base, path)
+	return filepath.Clean(path)
+}
+
+func checkPlatform() {
+	currentPlatform := common.CurrentPlatform()
+	platformOK := false
+	for _, platform := range common.SupportedPlatforms {
+		if currentPlatform == platform {
+			platformOK = true
+			break
+		}
+	}
+	if !platformOK {
+		log.Printf("warning: %s is an unsupported platform, use at your own risk!", currentPlatform)
+	}
+}
diff --git a/sweet/common/config.go b/sweet/common/config.go
new file mode 100644
index 0000000..63c88c8
--- /dev/null
+++ b/sweet/common/config.go
@@ -0,0 +1,77 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package common
+
+import (
+	"fmt"
+	"path/filepath"
+)
+
+const ConfigHelp = `
+The input configuration format is TOML consisting of a single array field
+called 'config'. Each element of the array consists of the following fields:
+      name: a unique name for the configuration (required)
+    goroot: path to a GOROOT representing the toolchain to run (required)
+  envbuild: additional environment variables that should be used for compilation
+            each variable should take the form "X=Y" (optional)
+   envexec: additional environment variables that should be used for execution
+            each variable should take the form "X=Y" (optional)
+
+A simple example configuration might look like:
+
+[[config]]
+  name = "original"
+  goroot = "~/work/go"
+  envexec = ["GODEBUG=gctrace=1"]
+
+[[config]]
+  name = "improved"
+  goroot = "~/work/go-but-better"
+  envexec = ["GODEBUG=gctrace=1"]
+
+Note that because 'config' is an array field, one may have multiple
+configurations present in a single file.
+`
+
+type ConfigFile struct {
+	Configs []*Config `toml:"config"`
+}
+
+type Config struct {
+	Name     string    `toml:"name"`
+	GoRoot   string    `toml:"goroot"`
+	BuildEnv ConfigEnv `toml:"envbuild"`
+	ExecEnv  ConfigEnv `toml:"envexec"`
+}
+
+func (c *Config) GoTool() *Go {
+	return &Go{
+		Tool: filepath.Join(c.GoRoot, "bin", "go"),
+		Env:  c.BuildEnv.Env,
+	}
+}
+
+type ConfigEnv struct {
+	*Env
+}
+
+func (c *ConfigEnv) UnmarshalTOML(data interface{}) error {
+	ldata, ok := data.([]interface{})
+	if !ok {
+		return fmt.Errorf("expected data for env to be a list")
+	}
+	vars := make([]string, 0, len(ldata))
+	for _, d := range ldata {
+		s, ok := d.(string)
+		if !ok {
+			return fmt.Errorf("expected data for env to contain strings")
+		}
+		vars = append(vars, s)
+	}
+	var err error
+	c.Env = NewEnvFromEnviron()
+	c.Env, err = c.Env.Set(vars...)
+	return err
+}
diff --git a/sweet/common/env.go b/sweet/common/env.go
new file mode 100644
index 0000000..7717a5e
--- /dev/null
+++ b/sweet/common/env.go
@@ -0,0 +1,110 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package common
+
+import (
+	"fmt"
+	"os"
+	"strings"
+)
+
+type Env struct {
+	parent *Env
+	data   map[string]string
+}
+
+func varsToMap(vars ...string) (map[string]string, error) {
+	env := make(map[string]string)
+	for _, v := range vars {
+		s := strings.SplitN(v, "=", 2)
+		if len(s) != 2 {
+			return nil, fmt.Errorf("%q is not a valid environment variable", v)
+		}
+		env[s[0]] = s[1]
+	}
+	return env, nil
+}
+
+func NewEnvFromEnviron() *Env {
+	env, err := NewEnv(os.Environ()...)
+	if err != nil {
+		panic(err)
+	}
+	return env
+}
+
+func NewEnv(vars ...string) (*Env, error) {
+	m, err := varsToMap(vars...)
+	if err != nil {
+		return nil, err
+	}
+	return &Env{data: m}, nil
+}
+
+func (e *Env) Set(vars ...string) (*Env, error) {
+	m, err := varsToMap(vars...)
+	if err != nil {
+		return nil, err
+	}
+	return &Env{
+		data:   m,
+		parent: e,
+	}, nil
+}
+
+func (e *Env) MustSet(vars ...string) *Env {
+	env, err := e.Set(vars...)
+	if err != nil {
+		panic(err)
+	}
+	return env
+}
+
+func (e *Env) Lookup(name string) (string, bool) {
+	t := e
+	for t != nil {
+		if v, ok := t.data[name]; ok {
+			return v, true
+		}
+		t = t.parent
+	}
+	return "", false
+}
+
+func (e *Env) Prefix(name, prefix string) *Env {
+	var (
+		n   *Env
+		err error
+	)
+	if v, ok := e.Lookup(name); ok {
+		n, err = e.Set(fmt.Sprintf("%s=%s%s", name, prefix, v))
+	} else {
+		n, err = e.Set(fmt.Sprintf("%s=%s", name, prefix))
+	}
+	// If we actually get an error out of Set here, then
+	// something went very wrong. Panic.
+	if err != nil {
+		panic(err.Error())
+	}
+	return n
+}
+
+func (e *Env) Collapse() []string {
+	t := e
+	c := make(map[string]string)
+	for t != nil {
+		for k, v := range t.data {
+			if _, ok := c[k]; !ok {
+				c[k] = v
+			}
+		}
+		t = t.parent
+	}
+	env := make([]string, 0, len(c))
+	for k, v := range c {
+		env = append(env, fmt.Sprintf("%s=%s", k, v))
+	}
+	return env
+}
diff --git a/sweet/common/env_test.go b/sweet/common/env_test.go
new file mode 100644
index 0000000..d5955b7
--- /dev/null
+++ b/sweet/common/env_test.go
@@ -0,0 +1,104 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package common_test
+
+import (
+	"reflect"
+	"testing"
+
+	"golang.org/x/benchmarks/sweet/common"
+)
+
+func stringSliceToSet(sl []string) map[string]struct{} {
+	ss := make(map[string]struct{})
+	for _, s := range sl {
+		ss[s] = struct{}{}
+	}
+	return ss
+}
+
+func TestEnv(t *testing.T) {
+	tryLookup := func(t *testing.T, env *common.Env, try, expect string) {
+		if v, ok := env.Lookup(try); !ok {
+			t.Fatalf("expected to find variable %q", try)
+		} else if v != expect {
+			t.Fatalf("expected to find value %q for %q, instead got %q", v, try, expect)
+		}
+	}
+	tryBadLookup := func(t *testing.T, env *common.Env, try string) {
+		if v, ok := env.Lookup(try); ok {
+			t.Fatalf("expected to not find variable %q, got %q", try, v)
+		}
+	}
+	tryCreate := func(t *testing.T, args ...string) *common.Env {
+		env, err := common.NewEnv(args...)
+		if err != nil {
+			t.Fatalf("unexpected err: %v", err)
+		}
+		return env
+	}
+	trySet := func(t *testing.T, env *common.Env, args ...string) *common.Env {
+		env2, err := env.Set(args...)
+		if err != nil {
+			t.Fatalf("unexpected err: %v", err)
+		}
+		return env2
+	}
+
+	env := tryCreate(t, "MYVAR=2", "MYVAR2=100")
+
+	t.Run("BadCreate", func(t *testing.T) {
+		_, err := common.NewEnv("MYVAR", "MYVAR2=100")
+		if err == nil {
+			t.Fatal("expected error due to bad input")
+		}
+	})
+	t.Run("Lookup", func(t *testing.T) {
+		tryLookup(t, env, "MYVAR", "2")
+	})
+	t.Run("EmptyLookup", func(t *testing.T) {
+		tryBadLookup(t, env, "NOVAR")
+	})
+	t.Run("BadSet", func(t *testing.T) {
+		_, err := env.Set("BADVAR")
+		if err == nil {
+			t.Fatal("expected error due to bad input")
+		}
+	})
+	exp := stringSliceToSet([]string{"MYVAR=3", "OTHERVAR=6", "MYVAR2=100"})
+	t.Run("Set", func(t *testing.T) {
+		env2 := trySet(t, env, "MYVAR=3", "OTHERVAR=6")
+		tryLookup(t, env2, "MYVAR", "3")
+		tryLookup(t, env2, "OTHERVAR", "6")
+		tryLookup(t, env2, "MYVAR2", "100")
+		tryLookup(t, env, "MYVAR", "2")
+		tryBadLookup(t, env, "OTHERVAR")
+		l := stringSliceToSet(env2.Collapse())
+		if !reflect.DeepEqual(l, exp) {
+			t.Fatalf("on collapse got %v, expected %v", l, exp)
+		}
+	})
+	t.Run("DeepSet", func(t *testing.T) {
+		env2 := trySet(t, env, "OTHERVAR=6")
+		env3 := trySet(t, env2, "MYVAR=3")
+		tryLookup(t, env3, "MYVAR", "3")
+		tryLookup(t, env3, "MYVAR2", "100")
+		tryLookup(t, env3, "OTHERVAR", "6")
+		tryLookup(t, env2, "OTHERVAR", "6")
+		tryLookup(t, env2, "MYVAR2", "100")
+		tryLookup(t, env2, "MYVAR", "2")
+		tryLookup(t, env, "MYVAR", "2")
+		tryBadLookup(t, env, "OTHERVAR")
+		l := stringSliceToSet(env3.Collapse())
+		if !reflect.DeepEqual(l, exp) {
+			t.Fatalf("on collapse got %v, expected %v", l, exp)
+		}
+	})
+	t.Run("Prefix", func(t *testing.T) {
+		env2 := env.Prefix("MYVAR", "3")
+		tryLookup(t, env2, "MYVAR", "32")
+		tryLookup(t, env, "MYVAR", "2")
+	})
+}
diff --git a/sweet/common/fileutil/copy.go b/sweet/common/fileutil/copy.go
new file mode 100644
index 0000000..288e8f5
--- /dev/null
+++ b/sweet/common/fileutil/copy.go
@@ -0,0 +1,136 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fileutil
+
+import (
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+)
+
+// FileExists returns true if a file or directory exists at the
+// specified path, otherwise it returns false. If an error is
+// encountered while checking, an error is returned.
+func FileExists(path string) (bool, error) {
+	_, err := os.Stat(path)
+	if os.IsNotExist(err) {
+		return false, nil
+	} else if err != nil {
+		return false, err
+	}
+	return true, nil
+}
+
+// CopyFile copies a file at path src to dst. sfinfo
+// is the os.FileInfo associated with the file at path src
+// and must be derived from it. sfinfo may be nil, in which
+// case the file at src is queried for its os.FileInfo,
+// and symbolic links are followed.
+//
+// In effect, sfinfo is just an optimization to avoid
+// querying the path for the os.FileInfo more than necessary.
+//
+// Thus, CopyFile always copies the bytes of the file at
+// src to a new file created at dst with the same file mode
+// as the old one.
+//
+// Returns a non-nil error if copying or acquiring the
+// os.FileInfo for the file fails.
+func CopyFile(dst, src string, sfinfo os.FileInfo) error {
+	sf, err := os.Open(src)
+	if err != nil {
+		return err
+	}
+	defer sf.Close()
+	if sfinfo == nil || sfinfo.Mode()&os.ModeSymlink != 0 {
+		sfinfo, err = sf.Stat()
+		if err != nil {
+			return err
+		}
+	}
+	df, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, sfinfo.Mode())
+	if err != nil {
+		return err
+	}
+	defer df.Close()
+	_, err = io.Copy(df, sf)
+	return err
+}
+
+// CopySymlink takes the symlink at path src and installs a new
+// symlink at path dst which contains the same link path. As a result,
+// relative symlinks point to a new location, relative to dst.
+//
+// sfinfo should be the result of an Lstat on src, and should always
+// indicate a symlink. If not, or if sfinfo is nil, then the os.FileInfo
+// for the symlink at src is regenerated.
+//
+// In effect, sfinfo is just an optimization to avoid
+// querying the path for the os.FileInfo more than necessary.
+//
+// Returns a non-nil error if the path src doesn't point to a symlink
+// or if an error is encountered in reading the link or installing
+// a new link.
+func CopySymlink(dst, src string, sfinfo os.FileInfo) error {
+	if sfinfo == nil || sfinfo.Mode()&os.ModeSymlink == 0 {
+		var err error
+		sfinfo, err = os.Lstat(src)
+		if err != nil {
+			return err
+		}
+	}
+	if sfinfo.Mode()&os.ModeSymlink == 0 {
+		return fmt.Errorf("source file is not a symlink")
+	}
+	// Handle a symlink by copying the
+	// link verbatim.
+	link, err := os.Readlink(src)
+	if err != nil {
+		return err
+	}
+	return os.Symlink(link, dst)
+}
+
+// CopyDir recursively copies the directory at path src to
+// a new directory at path dst. If a symlink is encountered
+// along the way, its link is copied verbatim and installed
+// in the destination directory heirarchy, as in CopySymlink.
+//
+// dst and directories under dst may not retain the permissions
+// of src or the corresponding directories under src. Instead,
+// we always set the permissions of the new directories to 0755.
+func CopyDir(dst, src string) error {
+	// Ignore the permissions of src, since if dst
+	// isn't writable we can't actually copy files into it.
+	// Pick a safe default that allows us to modify the
+	// directory and files within however we want, but let
+	// others only inspect it.
+	if err := os.MkdirAll(dst, 0755); err != nil {
+		return err
+	}
+	fs, err := ioutil.ReadDir(src)
+	if err != nil {
+		return err
+	}
+	for _, fi := range fs {
+		d, s := filepath.Join(dst, fi.Name()), filepath.Join(src, fi.Name())
+		if fi.IsDir() {
+			if err := CopyDir(d, s); err != nil {
+				return err
+			}
+		} else if fi.Mode()&os.ModeSymlink != 0 {
+			if err := CopySymlink(d, s, fi); err != nil {
+				return err
+			}
+		} else {
+			if err := CopyFile(d, s, fi); err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
diff --git a/sweet/common/generator.go b/sweet/common/generator.go
new file mode 100644
index 0000000..ac6f312
--- /dev/null
+++ b/sweet/common/generator.go
@@ -0,0 +1,18 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package common
+
+type GenConfig struct {
+	AssetsDir       string
+	SourceAssetsDir string
+	OutputDir       string
+	GoTool          *Go
+}
+
+type Generator interface {
+	// Generate generates a new assets directory
+	// from an old assets directory, given a configuration.
+	Generate(*GenConfig) error
+}
diff --git a/sweet/common/gotool.go b/sweet/common/gotool.go
new file mode 100644
index 0000000..7caaabc
--- /dev/null
+++ b/sweet/common/gotool.go
@@ -0,0 +1,67 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package common
+
+import (
+	"fmt"
+	"os"
+	"os/exec"
+
+	"golang.org/x/benchmarks/sweet/common/log"
+)
+
+type Go struct {
+	Tool string
+	Env  *Env
+}
+
+func SystemGoTool() (*Go, error) {
+	tool, err := exec.LookPath("go")
+	if err != nil {
+		return nil, fmt.Errorf("looking for system Go: %v", err)
+	}
+	return &Go{
+		Tool: tool,
+		Env:  NewEnvFromEnviron(),
+	}, nil
+}
+
+func (g *Go) run(args ...string) error {
+	cmd := exec.Command(g.Tool, args...)
+	cmd.Env = g.Env.Collapse()
+	log.TraceCommand(cmd, false)
+	return cmd.Run()
+}
+
+func (g *Go) BuildPackage(pkg, out string) error {
+	if pkg[0] == '/' || pkg[0] == '.' {
+		return fmt.Errorf("path used as package in go build")
+	}
+	return g.build(pkg, out)
+}
+
+func (g *Go) BuildPath(path, out string) error {
+	if path[0] != '/' && path[0] != '.' {
+		path = "./" + path
+	}
+	return g.build(path, out)
+}
+
+func (g *Go) build(pkgOrPath, out string) (err error) {
+	cwd, err := os.Getwd()
+	if err != nil {
+		return fmt.Errorf("failed to get current working directory: %v", err)
+	}
+	defer chdir(cwd)
+	if err := chdir(pkgOrPath); err != nil {
+		return fmt.Errorf("failed to enter build directory: %v", err)
+	}
+	return g.run("build", "-o", out)
+}
+
+func chdir(path string) error {
+	log.CommandPrintf("cd %s", path)
+	return os.Chdir(path)
+}
diff --git a/sweet/common/harness.go b/sweet/common/harness.go
new file mode 100644
index 0000000..d5afb2a
--- /dev/null
+++ b/sweet/common/harness.go
@@ -0,0 +1,71 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package common
+
+import "os"
+
+type BuildConfig struct {
+	// BinDir is the path to the directory where all built binaries should be
+	// placed.
+	BinDir string
+
+	// SrcDir is the path to the directory containing the benchmark's source
+	// code, excluding benchmark code that is part of the Sweet repository.
+	//
+	// For instance, this directory would contain a pulled source repository.
+	SrcDir string
+
+	// BenchDir is the path to the benchmark's source directory in the Sweet
+	// repository.
+	BenchDir string
+}
+
+type RunConfig struct {
+	// BinDir is the path to the directory containing the benchmark
+	// binaries.
+	BinDir string
+
+	// TmpDir is the path to a dedicated scratch directory.
+	//
+	// This directory is empty at the beginning of each run.
+	TmpDir string
+
+	// AssetsDir is the path to the directory containing runtime assets
+	// for the benchmark.
+	//
+	// AssistsDir is reconstructed for each run, so files within are safe
+	// to mutate.
+	AssetsDir string
+
+	// Args is a set of additional command-line arguments to pass to the
+	// primary benchmark binary (e.g. -dump-cores).
+	//
+	// The purpose of this field is to plumb through flags that all
+	// benchmarks support, such as flags for generating CPU profiles and
+	// such.
+	Args []string
+
+	// Results is the file to which benchmark results should be appended
+	// in the Go benchmark format.
+	Results *os.File
+}
+
+type Harness interface {
+	// CheckPrerequisites checks benchmark-specific environment prerequisites
+	// such as whether we're running as root or on a specific platform.
+	//
+	// Returns an error if any prerequisites are not met, nil otherwise.
+	CheckPrerequisites() error
+
+	// Get retrieves the source code for a benchmark and places it in srcDir.
+	Get(srcDir string) error
+
+	// Build builds a benchmark and places the binaries in binDir.
+	Build(cfg *Config, b *BuildConfig) error
+
+	// Run runs the given benchmark and writes Go `testing` formatted benchmark
+	// output to `results`.
+	Run(cfg *Config, r *RunConfig) error
+}
diff --git a/sweet/common/log/log.go b/sweet/common/log/log.go
new file mode 100644
index 0000000..1b0d28d
--- /dev/null
+++ b/sweet/common/log/log.go
@@ -0,0 +1,121 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package log
+
+import (
+	"log"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"strings"
+
+	shellquote "github.com/kballard/go-shellquote"
+)
+
+var (
+	cmdLog, actLog *log.Logger
+	cmdOn, actOn   = false, false
+	envMap         map[string]string
+)
+
+func init() {
+	cmdLog = log.New(os.Stdout, "[shell] ", 0)
+	actLog = log.New(os.Stderr, "[sweet] ", 0)
+	envMap = makeEnvironMap()
+}
+
+func makeEnvironMap() map[string]string {
+	env := os.Environ()
+	envmap := make(map[string]string)
+	for _, e := range env {
+		s := strings.SplitN(e, "=", 2)
+		if len(s) != 2 {
+			continue
+		}
+		envmap[s[0]] = s[1]
+	}
+	return envmap
+}
+
+func SetCommandTrace(on bool) {
+	cmdOn = on
+}
+
+func SetActivityLog(on bool) {
+	actOn = on
+}
+
+func filterEnviron(env []string) []string {
+	fenv := make([]string, 0, len(env))
+	for _, e := range env {
+		s := strings.SplitN(e, "=", 2)
+		if len(s) != 2 {
+			continue
+		}
+		if v, ok := envMap[s[0]]; ok && v == s[1] {
+			continue
+		}
+		fenv = append(fenv, e)
+	}
+	return fenv
+}
+
+func TraceCommand(cmd *exec.Cmd, background bool) {
+	if !cmdOn {
+		return
+	}
+	senv := ""
+	if len(cmd.Env) != 0 {
+		senv = strings.Join(filterEnviron(cmd.Env), " ")
+	}
+	if cmd.Dir != "" {
+		cmdLog.Printf("pushd %s", cmd.Dir)
+	}
+	sbg := ""
+	if background {
+		sbg = " &"
+	}
+	sarg := shellquote.Join(cmd.Args...)
+	if senv != "" {
+		cmdLog.Printf("%s %s%s", senv, sarg, sbg)
+	} else {
+		cmdLog.Printf("%s%s", sarg, sbg)
+	}
+	if cmd.Dir != "" {
+		cmdLog.Printf("popd")
+	}
+}
+
+func TraceKill(cmd *exec.Cmd) {
+	if !cmdOn {
+		return
+	}
+	cmdLog.Printf("killall -SIGINT %s", filepath.Base(cmd.Path))
+}
+
+func CommandPrintf(format string, args ...interface{}) {
+	if !cmdOn {
+		return
+	}
+	cmdLog.Printf(format, args...)
+}
+
+func Printf(format string, args ...interface{}) {
+	if !actOn {
+		return
+	}
+	actLog.Printf(format, args...)
+}
+
+func Print(args ...interface{}) {
+	if !actOn {
+		return
+	}
+	actLog.Print(args...)
+}
+
+func Error(err error) {
+	actLog.Printf("error: %v", err)
+}
diff --git a/sweet/common/platforms.go b/sweet/common/platforms.go
new file mode 100644
index 0000000..df7373a
--- /dev/null
+++ b/sweet/common/platforms.go
@@ -0,0 +1,36 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package common
+
+import (
+	"fmt"
+	"runtime"
+)
+
+type Platform struct {
+	GOOS, GOARCH string
+}
+
+func (p Platform) String() string {
+	return fmt.Sprintf("%s/%s", p.GOOS, p.GOARCH)
+}
+
+func (p Platform) BuildEnv(e *Env) *Env {
+	return e.MustSet(
+		fmt.Sprintf("GOOS=%s", p.GOOS),
+		fmt.Sprintf("GOARCH=%s", p.GOARCH),
+	)
+}
+
+var SupportedPlatforms = []Platform{
+	{"linux", "amd64"},
+}
+
+func CurrentPlatform() Platform {
+	return Platform{
+		GOOS:   runtime.GOOS,
+		GOARCH: runtime.GOARCH,
+	}
+}
diff --git a/sweet/common/version.go b/sweet/common/version.go
new file mode 100644
index 0000000..5814435
--- /dev/null
+++ b/sweet/common/version.go
@@ -0,0 +1,7 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package common
+
+const Version = "v0.2.1"
diff --git a/sweet/generators/bleve-query.go b/sweet/generators/bleve-query.go
new file mode 100644
index 0000000..2429699
--- /dev/null
+++ b/sweet/generators/bleve-query.go
@@ -0,0 +1,107 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package generators
+
+import (
+	"compress/bzip2"
+	"io"
+	"os"
+	"path/filepath"
+
+	"github.com/blevesearch/bleve"
+	_ "github.com/blevesearch/bleve/analysis/analyzer/keyword"
+	wikiparse "github.com/dustin/go-wikiparse"
+
+	"golang.org/x/benchmarks/sweet/common"
+	blevebench "golang.org/x/benchmarks/third_party/bleve-bench"
+)
+
+// documents is the number of documents to index.
+const documents = 100000
+
+// wikiDumpPath is a path to the static asset from
+// which we'll build our index.
+var wikiDumpPath = filepath.Join("..", "bleve-index", wikiDumpName)
+
+// BleveQuery is a dynamic assets Generator for the bleve-query benchmark.
+type BleveQuery struct{}
+
+// Generate creates a persistent index for the Bleve search engine for
+// the bleve-query benchmark. It generates this index from a subset of
+// the static assets for the bleve-index benchmark, a dump of wikipedia
+// from 2008.
+func (_ BleveQuery) Generate(cfg *common.GenConfig) (err error) {
+	// Copy README.md over.
+	if err := copyFiles(cfg.OutputDir, cfg.AssetsDir, []string{"README.md"}); err != nil {
+		return err
+	}
+
+	f, err := os.Open(filepath.Join(cfg.AssetsDir, wikiDumpPath))
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+
+	z := bzip2.NewReader(f)
+
+	parser, err := wikiparse.NewParser(z)
+	if err != nil {
+		return err
+	}
+
+	// Create a new Bleve index with on-disk
+	// storage in the output directory.
+	mapping := blevebench.ArticleMapping()
+	outputDir := filepath.Join(cfg.OutputDir, "index")
+	index, err := bleve.New(outputDir, mapping)
+	if err != nil {
+		return err
+	}
+	defer func() {
+		// Make sure we close the index so the data
+		// persists to disk.
+		cerr := index.Close()
+		if err == nil {
+			err = cerr
+		}
+	}()
+
+	todo := ^uint64(0)
+	if documents >= 0 {
+		todo = uint64(documents)
+	}
+
+	// Create batches of wikipedia articles
+	// and index them.
+	const batchSize = 256
+	b := index.NewBatch()
+	for i := uint64(0); i < todo; i++ {
+		p, err := parser.Next()
+		if err == io.EOF {
+			break
+		} else if err != nil {
+			return err
+		}
+		if len(p.Revisions) == 0 {
+			continue
+		}
+		b.Index(p.Title, blevebench.Article{
+			Title: p.Title,
+			Text:  p.Revisions[0].Text,
+		})
+		if b.Size() >= batchSize {
+			if err := index.Batch(b); err != nil {
+				return err
+			}
+			b = index.NewBatch()
+		}
+	}
+	if b.Size() != 0 {
+		if err := index.Batch(b); err != nil {
+			return err
+		}
+	}
+	return nil
+}
diff --git a/sweet/generators/copy.go b/sweet/generators/copy.go
new file mode 100644
index 0000000..2b051c2
--- /dev/null
+++ b/sweet/generators/copy.go
@@ -0,0 +1,291 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package generators
+
+import (
+	"path/filepath"
+
+	"golang.org/x/benchmarks/sweet/common"
+	"golang.org/x/benchmarks/sweet/common/fileutil"
+)
+
+// copyStatic is a generic Generator that copies a list of static assets over
+// to the new assets directory.
+type copyStatic struct {
+	assets  []string
+	sources []string
+}
+
+// Generate moves a static list of static assets from the assets
+// directory to the output directory. If the assets directory
+// and the output directory are identical, it does nothing.
+func (c *copyStatic) Generate(cfg *common.GenConfig) error {
+	if cfg.AssetsDir != cfg.OutputDir {
+		if err := copyFiles(cfg.OutputDir, cfg.AssetsDir, c.assets); err != nil {
+			return err
+		}
+	}
+	return copyFiles(cfg.OutputDir, cfg.SourceAssetsDir, c.sources)
+}
+
+func copyFiles(dstPath, srcPath string, relPaths []string) error {
+	for _, relPath := range relPaths {
+		outputPath := filepath.Join(dstPath, relPath)
+		inputPath := filepath.Join(srcPath, relPath)
+		err := fileutil.CopyFile(outputPath, inputPath, nil)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func BiogoIgor() common.Generator {
+	return &copyStatic{assets: []string{
+		"Homo_sapiens.GRCh38.dna.chromosome.22.gff",
+		"README.md",
+		"LICENSE",
+	}}
+}
+
+func BiogoKrishna() common.Generator {
+	return &copyStatic{assets: []string{
+		"Mus_musculus.GRCm38.dna.nonchromosomal.fa",
+		"README.md",
+		"LICENSE",
+	}}
+}
+
+const wikiDumpName = "enwiki-20080103-pages-articles.xml.bz2"
+
+func BleveIndex() common.Generator {
+	return &copyStatic{assets: []string{
+		wikiDumpName,
+		"README.md",
+		"LICENSE",
+	}}
+}
+
+func FoglemanFauxGL() common.Generator {
+	return &copyStatic{assets: []string{
+		"3dbenchy.stl",
+		"README.md",
+		"LICENSE",
+	}}
+}
+
+func FoglemanPT() common.Generator {
+	return &copyStatic{assets: []string{
+		"gopher.mtl",
+		"gopher.obj",
+		"README.md",
+		"LICENSE",
+	}}
+}
+
+func GopherLua() common.Generator {
+	return &copyStatic{
+		assets: []string{
+			"input.txt",
+			"README.md",
+			"LICENSE",
+			"k-nucleotide.lua",
+		},
+	}
+}
+
+func Markdown() common.Generator {
+	return &copyStatic{assets: []string{
+		"AchoArnold_discount-for-student-dev_README.md",
+		"agarrharr_awesome-cli-apps_README.md",
+		"agarrharr_awesome-macos-screensavers_README.md",
+		"agarrharr_awesome-static-website-services_README.md",
+		"alferov_awesome-gulp_README.md",
+		"analyticalmonk_awesome-neuroscience_README.md",
+		"angrykoala_awesome-esolangs_README.md",
+		"avajs_awesome-ava_README.md",
+		"aviaryan_awesome-no-login-web-apps_README.md",
+		"beaconinside_awesome-beacon_README.md",
+		"benoitjadinon_awesome-xamarin_README.md",
+		"bfred-it_Awesome-WebExtensions_README.md",
+		"brabadu_awesome-fonts_README.md",
+		"briatte_awesome-network-analysis_README.md",
+		"browserify_awesome-browserify_README.md",
+		"brunocvcunha_awesome-userscripts_README.md",
+		"BubuAnabelas_awesome-markdown_README.md",
+		"burningtree_awesome-json_README.md",
+		"candelibas_awesome-ionic_README.md",
+		"chentsulin_awesome-graphql_README.md",
+		"choojs_awesome-choo_README.md",
+		"christian-bromann_awesome-selenium_README.md",
+		"ChristosChristofidis_awesome-deep-learning_README.md",
+		"ciconia_awesome-music_README.md",
+		"Codepoints_awesome-codepoints_README.md",
+		"cristianoliveira_awesome4girls_README.md",
+		"CUTR-at-USF_awesome-transit_README.md",
+		"cyberglot_awesome-answers_README.md",
+		"cyclejs-community_awesome-cyclejs_README.md",
+		"d3viant0ne_awesome-rethinkdb_README.md",
+		"danielecook_Awesome-Bioinformatics_README.md",
+		"dav009_awesome-spanish-nlp_README.md",
+		"DavidLambauer_awesome-magento2_README.md",
+		"deanhume_typography_README.md",
+		"diessica_awesome-sketch_README.md",
+		"dok_awesome-text-editing_README.md",
+		"domenicosolazzo_awesome-okr_README.md",
+		"drewrwilson_toolsforactivism_README.md",
+		"dustinspecker_awesome-eslint_README.md",
+		"dylanrees_citizen-science_README.md",
+		"eleventigers_awesome-rxjava_README.md",
+		"enaqx_awesome-react_README.md",
+		"exAspArk_awesome-chatops_README.md",
+		"Famolus_awesome-sass_README.md",
+		"fasouto_awesome-dataviz_README.md",
+		"fcambus_nginx-resources_README.md",
+		"felipebueno_awesome-PICO-8_README.md",
+		"feross_awesome-mad-science_README.md",
+		"filipelinhares_awesome-slack_README.md",
+		"fliptheweb_motion-ui-design_README.md",
+		"Fr0sT-Brutal_awesome-delphi_README.md",
+		"gamontal_awesome-katas_README.md",
+		"gdi2290_awesome-angular_README.md",
+		"Granze_awesome-polymer_README.md",
+		"guillaume-chevalier_awesome-deep-learning-resources_README.md",
+		"hackerkid_bots_README.md",
+		"hackerkid_Mind-Expanding-Books_README.md",
+		"hantuzun_awesome-clojurescript_README.md",
+		"harpribot_awesome-information-retrieval_README.md",
+		"hbokh_awesome-saltstack_README.md",
+		"heynickc_awesome-ddd_README.md",
+		"hobbyquaker_awesome-mqtt_README.md",
+		"HQarroum_awesome-iot_README.md",
+		"igorbarinov_awesome-bitcoin_README.md",
+		"igorbarinov_awesome-data-engineering_README.md",
+		"iJackUA_awesome-vagrant_README.md",
+		"inspectit-labs_awesome-inspectit_README.md",
+		"ipfs_awesome-ipfs_README.md",
+		"isRuslan_awesome-elm_README.md",
+		"jagracey_Awesome-Unicode_README.md",
+		"jakoch_awesome-composer_README.md",
+		"JanVanRyswyck_awesome-talks_README.md",
+		"jbhuang0604_awesome-computer-vision_README.md",
+		"jbmoelker_progressive-enhancement-resources_README.md",
+		"jdorfman_awesome-json-datasets_README.md",
+		"jdrgomes_awesome-postcss_README.md",
+		"JesseTG_awesome-qt_README.md",
+		"joaomilho_awesome-idris_README.md",
+		"jonathandion_awesome-emails_README.md",
+		"jwaterfaucett_awesome-foss-apps_README.md",
+		"karlhorky_learn-to-program_README.md",
+		"kdeldycke_awesome-falsehood_README.md",
+		"KotlinBy_awesome-kotlin_README.md",
+		"krispo_awesome-haskell_README.md",
+		"LappleApple_awesome-leading-and-managing_README.md",
+		"LewisJEllis_awesome-lua_README.md",
+		"LucasBassetti_awesome-less_README.md",
+		"lucasviola_awesome-functional-programming_README.md",
+		"lucasviola_awesome-tech-videos_README.md",
+		"lukasz-madon_awesome-remote-job_README.md",
+		"machinomy_awesome-non-financial-blockchain_README.md",
+		"mailtoharshit_awesome-salesforce_README.md",
+		"mark-rushakoff_awesome-influxdb_README.md",
+		"matiassingers_awesome-readme_README.md",
+		"matiassingers_awesome-slack_README.md",
+		"MaximAbramchuck_awesome-interview-questions_README.md",
+		"melvin0008_awesome-projects-boilerplates_README.md",
+		"mfornos_awesome-microservices_README.md",
+		"micromata_awesome-javascript-learning_README.md",
+		"mmccaff_PlacesToPostYourStartup_README.md",
+		"mohataher_awesome-tinkerpop_README.md",
+		"motion-open-source_awesome-rubymotion_README.md",
+		"moul_awesome-ssh_README.md",
+		"mre_awesome-static-analysis_README.md",
+		"MunGell_awesome-for-beginners_README.md",
+		"neueda_awesome-neo4j_README.md",
+		"neutraltone_awesome-stock-resources_README.md",
+		"nicolesaidy_awesome-web-design_README.md",
+		"nikgraf_awesome-draft-js_README.md",
+		"nirgn975_awesome-drupal_README.md",
+		"nmec_awesome-ember_README.md",
+		"NoahBuscher_Inspire_README.md",
+		"notthetup_awesome-webaudio_README.md",
+		"ooade_awesome-preact_README.md",
+		"owainlewis_awesome-artificial-intelligence_README.md",
+		"parro-it_awesome-micro-npm-packages_README.md",
+		"passy_awesome-purescript_README.md",
+		"pazguille_offline-first_README.md",
+		"pehapkari_awesome-symfony-education_README.md",
+		"PerfectCarl_awesome-play1_README.md",
+		"petk_awesome-dojo_README.md",
+		"petk_awesome-jquery_README.md",
+		"PhantomYdn_awesome-wicket_README.md",
+		"podo_awesome-framer_README.md",
+		"qazbnm456_awesome-web-security_README.md",
+		"quozd_awesome-dotnet_README.md",
+		"ramnes_awesome-mongodb_README.md",
+		"refinerycms-contrib_awesome-refinerycms_README.md",
+		"RichardLitt_awesome-conferences_README.md",
+		"RichardLitt_awesome-fantasy_README.md",
+		"RichardLitt_awesome-styleguides_README.md",
+		"roaldnefs_awesome-prometheus_README.md",
+		"rossant_awesome-math_README.md",
+		"rust-unofficial_awesome-rust_README.md",
+		"RyanZim_awesome-npm-scripts_README.md",
+		"scholtzm_awesome-steam_README.md",
+		"seancoyne_awesome-coldfusion_README.md",
+		"sfischer13_awesome-eta_README.md",
+		"sfischer13_awesome-frege_README.md",
+		"sfischer13_awesome-ledger_README.md",
+		"shuaibiyy_awesome-terraform_README.md",
+		"siboehm_awesome-learn-datascience_README.md",
+		"Siddharth11_Colorful_README.md",
+		"sindresorhus_amas_README.md",
+		"sindresorhus_awesome-electron_README.md",
+		"sindresorhus_awesome-nodejs_README.md",
+		"sindresorhus_awesome-npm_README.md",
+		"sindresorhus_awesome-observables_README.md",
+		"sindresorhus_awesome_README.md",
+		"sindresorhus_awesome-scifi_README.md",
+		"sindresorhus_awesome-tap_README.md",
+		"sindresorhus_quick-look-plugins_README.md",
+		"sitepoint-editors_awesome-symfony_README.md",
+		"sjfricke_awesome-webgl_README.md",
+		"sorrycc_awesome-javascript_README.md",
+		"springload_awesome-wagtail_README.md",
+		"SrinivasanTarget_awesome-appium_README.md",
+		"standard_awesome-standard_README.md",
+		"stetso_awesome-gideros_README.md",
+		"stevemao_awesome-git-addons_README.md",
+		"stoeffel_awesome-ama-answers_README.md",
+		"stoeffel_awesome-fp-js_README.md",
+		"stve_awesome-dropwizard_README.md",
+		"sublimino_awesome-funny-markov_README.md",
+		"terkelg_awesome-creative-coding_README.md",
+		"terryum_awesome-deep-learning-papers_README.md",
+		"thangchung_awesome-dotnet-core_README.md",
+		"TheJambo_awesome-testing_README.md",
+		"thibmaek_awesome-raspberry-pi_README.md",
+		"tmcw_awesome-geojson_README.md",
+		"tobiasbueschel_awesome-pokemon_README.md",
+		"unicodeveloper_awesome-lumen_README.md",
+		"unicodeveloper_awesome-nextjs_README.md",
+		"uralbash_awesome-pyramid_README.md",
+		"vhpoet_awesome-ripple_README.md",
+		"viatsko_awesome-vscode_README.md",
+		"vinkla_awesome-fuse_README.md",
+		"vinkla_shareable-links_README.md",
+		"vitalets_awesome-smart-tv_README.md",
+		"vorpaljs_awesome-vorpal_README.md",
+		"vredniy_awesome-newsletters_README.md",
+		"vuejs_awesome-vue_README.md",
+		"watson_awesome-computer-history_README.md",
+		"webpro_awesome-dotfiles_README.md",
+		"yenchenlin_awesome-watchos_README.md",
+		"yissachar_awesome-dart_README.md",
+		"yrgo_awesome-eg_README.md",
+		"README.md",
+		"LICENSE",
+	}}
+}
diff --git a/sweet/generators/gvisor.go b/sweet/generators/gvisor.go
new file mode 100644
index 0000000..483f2f1
--- /dev/null
+++ b/sweet/generators/gvisor.go
@@ -0,0 +1,169 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package generators
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path/filepath"
+
+	"golang.org/x/benchmarks/sweet/common"
+	"golang.org/x/benchmarks/sweet/common/fileutil"
+	"golang.org/x/benchmarks/sweet/harnesses"
+
+	osi "github.com/opencontainers/runtime-spec/specs-go"
+)
+
+// GVisor is a dynamic assets Generator for the gvisor benchmark.
+type GVisor struct{}
+
+// Generate builds binaries for workloads that will run under gVisor
+// as part of the benchmark. The sources for these workloads live in
+// the source assets directory and are relatively short Go programs.
+//
+// It also copies over static assets which are necessary to run the
+// benchmarks.
+func (_ GVisor) Generate(cfg *common.GenConfig) error {
+	goTool := *cfg.GoTool
+	goTool.Env = goTool.Env.MustSet("CGO_ENABLED=0") // Disable CGO for workloads.
+
+	// Build workload sources into binaries in the output directory,
+	// with one binary for each supported platform.
+	workloads := []string{
+		"http",
+		"syscall",
+	}
+	for _, workload := range workloads {
+		workloadSrcDir := filepath.Join(cfg.SourceAssetsDir, workload)
+		workloadOutDir := filepath.Join(cfg.OutputDir, workload)
+		if err := os.MkdirAll(workloadOutDir, 0755); err != nil {
+			return err
+		}
+		for _, p := range common.SupportedPlatforms {
+			// Generate the output directory.
+			platformDirName := fmt.Sprintf("%s-%s", p.GOOS, p.GOARCH)
+			workloadBinOutDir := filepath.Join(workloadOutDir, "bin", platformDirName)
+			if err := os.MkdirAll(workloadBinOutDir, 0755); err != nil {
+				return err
+			}
+			goTool := common.Go{Tool: goTool.Tool, Env: p.BuildEnv(goTool.Env)}
+
+			// Build the workload.
+			err := goTool.BuildPath(workloadSrcDir, filepath.Join(workloadBinOutDir, "workload"))
+			if err != nil {
+				return fmt.Errorf("building workload %s for %s: %v", workload, p, err)
+			}
+		}
+	}
+
+	// In order to regenerate startup/config.json, we require a working
+	// copy of runsc. Get and build it from the harness.
+	//
+	// Create a temporary directory where we can put the gVisor source.
+	tmpDir, err := ioutil.TempDir("", "gvisor-gen")
+	if err != nil {
+		return err
+	}
+	srcDir := filepath.Join(tmpDir, "src")
+	if err := os.MkdirAll(srcDir, os.ModePerm); err != nil {
+		return err
+	}
+	if err := (harnesses.GVisor{}).Get(srcDir); err != nil {
+		return err
+	}
+
+	// Ensure the startup subdirectory exists.
+	if err := os.MkdirAll(filepath.Join(cfg.OutputDir, "startup"), 0755); err != nil {
+		return err
+	}
+
+	// Build the runsc package in the repository. CGO_ENABLED must be 0.
+	// See https://github.com/google/gvisor#using-go-get.
+	cfg.GoTool.Env = cfg.GoTool.Env.MustSet("CGO_ENABLED=0")
+	runscBin := filepath.Join(tmpDir, "runsc")
+	if err := cfg.GoTool.BuildPath(filepath.Join(srcDir, "runsc"), runscBin); err != nil {
+		return err
+	}
+
+	// Delete config.json if it already exists, because runsc
+	// will fail otherwise.
+	specFile := filepath.Join(cfg.OutputDir, "startup", "config.json")
+	if err := os.Remove(specFile); err != nil && !errors.Is(err, os.ErrNotExist) {
+		return err
+	}
+
+	// Generate config.json.
+	cmd := exec.Command(runscBin, "spec")
+	cmd.Dir = filepath.Join(cfg.OutputDir, "startup")
+	if err := cmd.Run(); err != nil {
+		return err
+	}
+
+	// Mutate the config.json slightly for our purposes and write it back out.
+	specBytes, err := os.ReadFile(specFile)
+	if err != nil {
+		return err
+	}
+	var spec osi.Spec
+	if err := json.Unmarshal(specBytes, &spec); err != nil {
+		return err
+	}
+	spec.Process.Terminal = false
+	spec.Process.Args = []string{"/hello"}
+	var buf bytes.Buffer
+	enc := json.NewEncoder(&buf)
+	enc.SetIndent("", "    ")
+	if err := enc.Encode(&spec); err != nil {
+		return err
+	}
+	if err := os.WriteFile(specFile, buf.Bytes(), 0666); err != nil {
+		return err
+	}
+
+	// Everything below this point is static assets. If we're in the
+	// same directory, just stop here.
+	if cfg.AssetsDir == cfg.OutputDir {
+		return nil
+	}
+
+	// Generate additional directory structure for static assets
+	// that isn't already generated by the build process above.
+	if err := os.MkdirAll(filepath.Join(cfg.OutputDir, "http", "assets"), 0755); err != nil {
+		return err
+	}
+
+	// Copy static assets over.
+	staticAssets := []string{
+		filepath.Join("http", "assets", "gopherhat.jpg"),
+		filepath.Join("http", "assets", "gophermega.jpg"),
+		filepath.Join("http", "assets", "gopherswim.jpg"),
+		filepath.Join("http", "assets", "gopherhelmet.jpg"),
+		filepath.Join("http", "assets", "gopherrunning.jpg"),
+		filepath.Join("http", "assets", "gopherswrench.jpg"),
+		filepath.Join("http", "README.md"),
+		filepath.Join("startup", "README.md"),
+		filepath.Join("syscall", "README.md"),
+	}
+	if err := copyFiles(cfg.OutputDir, cfg.AssetsDir, staticAssets); err != nil {
+		return err
+	}
+
+	// As a special case, copy everything under startup/rootfs.
+	// It's a rootfs, so enumerating everything here would be tedious
+	// and not really useful.
+	//
+	// TODO(mknyszek): Generate this directory from a container image.
+	// There's some complications to this, because Cloud Build runs
+	// inside docker, and this is generated from a docker image.
+	return fileutil.CopyDir(
+		filepath.Join(cfg.OutputDir, "startup", "rootfs"),
+		filepath.Join(cfg.AssetsDir, "startup", "rootfs"),
+	)
+}
diff --git a/sweet/generators/none.go b/sweet/generators/none.go
new file mode 100644
index 0000000..20674e1
--- /dev/null
+++ b/sweet/generators/none.go
@@ -0,0 +1,15 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package generators
+
+import "golang.org/x/benchmarks/sweet/common"
+
+// None is a Generator that does nothing.
+type None struct{}
+
+// Generate does nothing.
+func (_ None) Generate(_ *common.GenConfig) error {
+	return nil
+}
diff --git a/sweet/generators/tile38.go b/sweet/generators/tile38.go
new file mode 100644
index 0000000..945bf98
--- /dev/null
+++ b/sweet/generators/tile38.go
@@ -0,0 +1,300 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package generators
+
+import (
+	"bufio"
+	"bytes"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"strconv"
+	"strings"
+	"time"
+	"unicode/utf8"
+
+	"github.com/gomodule/redigo/redis"
+
+	"golang.org/x/benchmarks/sweet/common"
+	"golang.org/x/benchmarks/sweet/common/fileutil"
+	"golang.org/x/benchmarks/sweet/common/log"
+	"golang.org/x/benchmarks/sweet/harnesses"
+)
+
+// Tile38 is a dynamic assets Generator for the tile38 benchmark.
+type Tile38 struct{}
+
+// Generate starts from static assets to generate a persistent store
+// for Tile38 that will be passed to the server for benchmarking.
+//
+// The persistent store is created from gen-data/allCountries.txt,
+// which is a TSV file listing points of interest around the globe,
+// and gen-data/countries.geojson, which is a GeoJSON file that
+// describe countries' borders. Both of these are used to populate
+// a running Tile38 server which is downloaded and built on the
+// fly, using the same version of Tile38 that will be benchmarked.
+//
+// The resulting persistent store is placed in the data directory
+// in the output directory.
+//
+// This generator also copies over the static assets used to generate
+// the dynamic assets.
+func (_ Tile38) Generate(cfg *common.GenConfig) error {
+	if cfg.AssetsDir != cfg.OutputDir {
+		// Copy over the datasets which are used to generate
+		// the server's persistent data.
+		if err := os.MkdirAll(filepath.Join(cfg.OutputDir, "gen-data", "geonames"), 0755); err != nil {
+			return err
+		}
+		if err := os.MkdirAll(filepath.Join(cfg.OutputDir, "gen-data", "datahub"), 0755); err != nil {
+			return err
+		}
+		err := copyFiles(cfg.OutputDir, cfg.AssetsDir, []string{
+			"gen-data/geonames/allCountries.txt",
+			"gen-data/geonames/LICENSE",
+			"gen-data/datahub/countries.geojson",
+			"gen-data/datahub/LICENSE",
+			"gen-data/README.md",
+		})
+		if err != nil {
+			return err
+		}
+	}
+
+	// Create a temporary directory where we can put the Tile38
+	// source and build it.
+	tmpDir, err := ioutil.TempDir("", "tile38-gen")
+	if err != nil {
+		return err
+	}
+
+	// In order to generate the assets, we need a working Tile38
+	// server. Use the harness code to get the source.
+	srcDir := filepath.Join(tmpDir, "src")
+	if err := os.MkdirAll(srcDir, os.ModePerm); err != nil {
+		return err
+	}
+	if err := (harnesses.Tile38{}).Get(srcDir); err != nil {
+		return err
+	}
+
+	// Add the Go tool to PATH, since tile38's Makefile doesn't provide enough
+	// visibility into how tile38 is built to allow us to pass this information
+	// directly.
+	env := cfg.GoTool.Env.Prefix("PATH", filepath.Join(filepath.Dir(cfg.GoTool.Tool))+":")
+
+	// Build Tile38.
+	cmd := exec.Command("make", "-C", srcDir)
+	cmd.Env = env.Collapse()
+	if err := cmd.Run(); err != nil {
+		return err
+	}
+
+	// Launch the server.
+	//
+	// Generate the datastore in the tmp directory and copy it
+	// over later, otherwise if cfg.OutputDir == cfg.AssetsDir, then
+	// we might launch the server with an old database.
+	serverPath := filepath.Join(srcDir, "tile38-server")
+	tmpDataPath := filepath.Join(srcDir, "tile38-data")
+	var buf bytes.Buffer
+	srvCmd, err := launchServer(serverPath, tmpDataPath, &buf)
+	if err != nil {
+		log.Printf("=== Server stdout+stderr ===")
+		for _, line := range strings.Split(buf.String(), "\n") {
+			log.Printf(line)
+		}
+		return fmt.Errorf("error: starting server: %v", err)
+	}
+
+	// Clean up the server process after we're done.
+	defer func() {
+		if r := srvCmd.Process.Signal(os.Interrupt); r != nil {
+			if err == nil {
+				err = r
+			} else {
+				fmt.Fprintf(os.Stderr, "failed to shut down server: %v\n", r)
+			}
+			return
+		}
+		if _, r := srvCmd.Process.Wait(); r != nil {
+			if err == nil {
+				err = r
+			} else if r != nil {
+				fmt.Fprintf(os.Stderr, "failed to wait for server to exit: %v\n", r)
+			}
+			return
+		}
+		if err != nil && buf.Len() != 0 {
+			log.Printf("=== Server stdout+stderr ===")
+			for _, line := range strings.Split(buf.String(), "\n") {
+				log.Printf(line)
+			}
+		}
+		if err == nil {
+			// Copy database to the output directory.
+			// We cannot do this until we've stopped the
+			// server because the data might not have been
+			// written back yet. An interrupt should have
+			// the server shut down gracefully.
+			err = fileutil.CopyDir(
+				filepath.Join(cfg.OutputDir, "data"),
+				tmpDataPath,
+			)
+		}
+	}()
+
+	// Connect to the server and feed it data.
+	c, err := redis.Dial("tcp", ":9851")
+	if err != nil {
+		return err
+	}
+	defer c.Close()
+
+	// Store GeoJSON of countries.
+	genDataDir := filepath.Join(cfg.AssetsDir, "gen-data")
+	if err := storeGeoJSON(c, filepath.Join(genDataDir, "datahub", "countries.geojson")); err != nil {
+		return err
+	}
+
+	// Feed the server points-of-interest.
+	f, err := os.Open(filepath.Join(genDataDir, "geonames", "allCountries.txt"))
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+
+	// allCountries.txt is a TSV file with a fixed number ofcolumns per row
+	// (line). What we need to pull out of it is a unique ID, and the
+	// coordinates for the point-of-interest.
+	const (
+		columnsPerLine = 19
+		idColumn       = 0
+		latColumn      = 4
+		lonColumn      = 5
+	)
+	s := tsvScanner(f)
+
+	var item int
+	var obj geoObj
+	for s.Scan() {
+		// Each iteration of this loop is another cell in the
+		// TSV file.
+		switch item % columnsPerLine {
+		case idColumn:
+			i, err := strconv.ParseInt(s.Text(), 10, 64)
+			if err != nil {
+				return err
+			}
+			obj.id = i
+		case latColumn:
+			f, err := strconv.ParseFloat(s.Text(), 64)
+			if err != nil {
+				return err
+			}
+			obj.lat = f
+		case lonColumn:
+			f, err := strconv.ParseFloat(s.Text(), 64)
+			if err != nil {
+				return err
+			}
+			obj.lon = f
+		}
+		item++
+
+		// We finished off another row, which means obj
+		// should be correctly populated.
+		if item%columnsPerLine == 0 {
+			if err := storeGeoObj(c, &obj); err != nil {
+				return err
+			}
+		}
+	}
+	return s.Err()
+}
+
+func launchServer(serverBin, dataPath string, out io.Writer) (*exec.Cmd, error) {
+	// Start up the server.
+	srvCmd := exec.Command(serverBin,
+		"-d", dataPath,
+		"-h", "127.0.0.1",
+		"-p", "9851",
+	)
+	srvCmd.Stdout = out
+	srvCmd.Stderr = out
+	if err := srvCmd.Start(); err != nil {
+		return nil, fmt.Errorf("failed to start server: %v", err)
+	}
+
+	// Poll until the server is ready to serve, up to 120 seconds.
+	var err error
+	start := time.Now()
+	for time.Now().Sub(start) < 120*time.Second {
+		var c redis.Conn
+		c, err = redis.Dial("tcp", ":9851")
+		if err == nil {
+			c.Close()
+			return srvCmd, nil
+		}
+		time.Sleep(2 * time.Second)
+	}
+	return nil, fmt.Errorf("timeout trying to connect to server: %v", err)
+}
+
+// tsvScanner returns a bufio.Scanner that emits a cell in
+// a TSV stream for each call to Scan.
+func tsvScanner(f io.Reader) *bufio.Scanner {
+	s := bufio.NewScanner(f)
+	s.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
+		// Skip leading tab or newline (1).
+		start := 0
+
+		// Scan until tab or newline, marking end of value.
+		for width, i := 0, start; i < len(data); i += width {
+			var r rune
+			r, width = utf8.DecodeRune(data[i:])
+			if r == '\t' || r == '\n' {
+				return i + width, data[start:i], nil
+			}
+		}
+		// If we're at EOF, we have a final, non-empty, non-terminated word. Return it.
+		if atEOF && len(data) > start {
+			return len(data), data[start:], nil
+		}
+		// Request more data.
+		return start, nil, nil
+	})
+	return s
+}
+
+// geoObj represents a single point on a globe with a unique ID
+// indicating it as a point-of-interest.
+type geoObj struct {
+	id       int64
+	lat, lon float64
+}
+
+// storeGeoObj writes a new point to a Tile38 database.
+func storeGeoObj(c redis.Conn, g *geoObj) error {
+	_, err := c.Do("SET", "key:bench", "id:"+strconv.FormatInt(g.id, 10), "POINT",
+		strconv.FormatFloat(g.lat, 'f', 5, 64),
+		strconv.FormatFloat(g.lon, 'f', 5, 64),
+	)
+	return err
+}
+
+// storeGeoJSON writes an entire GeoJSON object (which may contain many polygons)
+// to a Tile38 database.
+func storeGeoJSON(c redis.Conn, jsonFile string) error {
+	b, err := ioutil.ReadFile(jsonFile)
+	if err != nil {
+		return err
+	}
+	_, err = c.Do("SET", "key:bench", "id:countries", "OBJECT", string(b))
+	return err
+}
diff --git a/sweet/harnesses/common.go b/sweet/harnesses/common.go
new file mode 100644
index 0000000..0ace974
--- /dev/null
+++ b/sweet/harnesses/common.go
@@ -0,0 +1,59 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package harnesses
+
+import (
+	"os"
+	"os/exec"
+	"path/filepath"
+
+	"golang.org/x/benchmarks/sweet/common/fileutil"
+	"golang.org/x/benchmarks/sweet/common/log"
+)
+
+func gitShallowClone(dir, url, ref string) error {
+	cmd := exec.Command("git", "clone", "--depth", "1", "-b", ref, url, dir)
+	log.TraceCommand(cmd, false)
+	return cmd.Run()
+}
+
+func gitCloneToCommit(dir, url, branch, hash string) error {
+	cloneCmd := exec.Command("git", "clone", "-b", branch, url, dir)
+	log.TraceCommand(cloneCmd, false)
+	if err := cloneCmd.Run(); err != nil {
+		return err
+	}
+	checkoutCmd := exec.Command("git", "-C", dir, "checkout", hash)
+	log.TraceCommand(checkoutCmd, false)
+	return checkoutCmd.Run()
+}
+
+func copyFile(dst, src string) error {
+	log.CommandPrintf("cp %s %s", src, dst)
+	return fileutil.CopyFile(dst, src, nil)
+}
+
+func makeWriteable(dir string) error {
+	log.CommandPrintf("chmod -R a+w %s", dir)
+	return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+		if info.Mode()&0222 == 0222 {
+			return nil
+		}
+		return os.Chmod(path, info.Mode()|0222)
+	})
+}
+
+func symlink(dst, src string) error {
+	log.CommandPrintf("ln -s %s %s", src, dst)
+	return os.Symlink(src, dst)
+}
+
+func copySymlink(dst, src string) error {
+	log.CommandPrintf("cp %s %s", src, dst)
+	return fileutil.CopySymlink(dst, src, nil)
+}
diff --git a/sweet/harnesses/go-build.go b/sweet/harnesses/go-build.go
new file mode 100644
index 0000000..16eb7ca
--- /dev/null
+++ b/sweet/harnesses/go-build.go
@@ -0,0 +1,115 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package harnesses
+
+import (
+	"os/exec"
+	"path/filepath"
+
+	"golang.org/x/benchmarks/sweet/common"
+	"golang.org/x/benchmarks/sweet/common/log"
+)
+
+type buildBenchmark struct {
+	name  string
+	pkg   string
+	clone func(outDir string) error
+}
+
+var buildBenchmarks = []*buildBenchmark{
+	{
+		name: "kubernetes",
+		pkg:  "cmd/kubelet",
+		clone: func(outDir string) error {
+			return gitShallowClone(
+				outDir,
+				"https://github.com/kubernetes/kubernetes",
+				"v1.22.1",
+			)
+		},
+	},
+	{
+		name: "istio",
+		pkg:  "istioctl/cmd/istioctl",
+		clone: func(outDir string) error {
+			return gitShallowClone(
+				outDir,
+				"https://github.com/istio/istio",
+				"1.11.1",
+			)
+		},
+	},
+	{
+		name: "pkgsite",
+		pkg:  "cmd/frontend",
+		clone: func(outDir string) error {
+			return gitCloneToCommit(
+				outDir,
+				"https://go.googlesource.com/pkgsite",
+				"master",
+				"0a8194a898a1ceff6a0b29e3419650daf43d8567",
+			)
+		},
+	},
+}
+
+type GoBuild struct{}
+
+func (h GoBuild) CheckPrerequisites() error {
+	return nil
+}
+
+func (h GoBuild) Get(srcDir string) error {
+	// Clone the sources that we're going to build.
+	for _, bench := range buildBenchmarks {
+		if err := bench.clone(filepath.Join(srcDir, bench.name)); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (h GoBuild) Build(cfg *common.Config, bcfg *common.BuildConfig) error {
+	for _, bench := range buildBenchmarks {
+		// Generate a symlink to the repository and put it in bin.
+		// It's not a binary, but it's the only place we can put it
+		// and still access it in Run.
+		link := filepath.Join(bcfg.BinDir, bench.name)
+		err := symlink(link, filepath.Join(bcfg.SrcDir, bench.name))
+		if err != nil {
+			return err
+		}
+
+		// Build the benchmark once, pulling in any requisite packages.
+		cmd := exec.Command(cfg.GoTool().Tool, "build")
+		cmd.Dir = filepath.Join(bcfg.BinDir, bench.name, bench.pkg)
+		log.TraceCommand(cmd, false)
+		if err := cmd.Run(); err != nil {
+			return err
+		}
+	}
+	return cfg.GoTool().BuildPath(bcfg.BenchDir, filepath.Join(bcfg.BinDir, "go-build-bench"))
+}
+
+func (h GoBuild) Run(cfg *common.Config, rcfg *common.RunConfig) error {
+	for _, bench := range buildBenchmarks {
+		cmd := exec.Command(
+			filepath.Join(rcfg.BinDir, "go-build-bench"),
+			append(rcfg.Args, []string{
+				"-go", cfg.GoTool().Tool,
+				"-tmp", rcfg.TmpDir,
+				filepath.Join(rcfg.BinDir, bench.name, bench.pkg),
+			}...)...,
+		)
+		cmd.Env = cfg.ExecEnv.Collapse()
+		cmd.Stdout = rcfg.Results
+		cmd.Stderr = rcfg.Results
+		log.TraceCommand(cmd, false)
+		if err := cmd.Run(); err != nil {
+			return err
+		}
+	}
+	return nil
+}
diff --git a/sweet/harnesses/gvisor.go b/sweet/harnesses/gvisor.go
new file mode 100644
index 0000000..2b10b6f
--- /dev/null
+++ b/sweet/harnesses/gvisor.go
@@ -0,0 +1,73 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package harnesses
+
+import (
+	"fmt"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"runtime"
+
+	"golang.org/x/benchmarks/sweet/common"
+	"golang.org/x/benchmarks/sweet/common/log"
+)
+
+type GVisor struct{}
+
+func (h GVisor) CheckPrerequisites() error {
+	if runtime.GOOS != "linux" {
+		return fmt.Errorf("requires Linux")
+	}
+	return nil
+}
+
+func (h GVisor) Get(srcDir string) error {
+	return gitCloneToCommit(
+		srcDir,
+		"https://github.com/google/gvisor",
+		"go",
+		"6392b0f3bea052af0de9d95677233dd9e442dbd5", // release-20210906.0-38-gb28bd31bb
+	)
+}
+
+func (h GVisor) Build(cfg *common.Config, bcfg *common.BuildConfig) error {
+	// Build benchmarking client which will handle a bunch of coordination.
+	if err := cfg.GoTool().BuildPath(filepath.Join(bcfg.BenchDir), filepath.Join(bcfg.BinDir, "gvisor-bench")); err != nil {
+		return err
+	}
+
+	// Build the runsc package in the repository. CGO_ENABLED must be 0.
+	// See https://github.com/google/gvisor#using-go-get.
+	cfg.BuildEnv.Env = cfg.BuildEnv.MustSet("CGO_ENABLED=0")
+	bin := filepath.Join(bcfg.BinDir, "runsc")
+	if err := cfg.GoTool().BuildPath(filepath.Join(bcfg.SrcDir, "runsc"), bin); err != nil {
+		return err
+	}
+
+	// Make sure the binary has all the right permissions set.
+	// See https://gvisor.dev/docs/user_guide/install/#install-directly
+	log.CommandPrintf("chmod 755 %s", bin)
+	if err := os.Chmod(bin, 0755); err != nil {
+		return fmt.Errorf("failed to set permissions on runsc: %v", err)
+	}
+	return nil
+}
+
+func (h GVisor) Run(cfg *common.Config, rcfg *common.RunConfig) error {
+	cmd := exec.Command(
+		filepath.Join(rcfg.BinDir, "gvisor-bench"),
+		append(rcfg.Args, []string{
+			"-runsc", filepath.Join(rcfg.BinDir, "runsc"),
+			"-assets-dir", rcfg.AssetsDir,
+			"-tmp", rcfg.TmpDir,
+		}...)...,
+	)
+	cmd.Env = cfg.ExecEnv.Collapse()
+	cmd.Stdout = rcfg.Results
+	cmd.Stderr = rcfg.Results
+	log.TraceCommand(cmd, false)
+	return cmd.Run()
+}
diff --git a/sweet/harnesses/local.go b/sweet/harnesses/local.go
new file mode 100644
index 0000000..c5aa29f
--- /dev/null
+++ b/sweet/harnesses/local.go
@@ -0,0 +1,153 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package harnesses
+
+import (
+	"os/exec"
+	"path/filepath"
+
+	"golang.org/x/benchmarks/sweet/common"
+	"golang.org/x/benchmarks/sweet/common/log"
+)
+
+type localBenchHarness struct {
+	binName   string
+	genArgs   func(cfg *common.Config, rcfg *common.RunConfig) []string
+	beforeRun func(cfg *common.Config, rcfg *common.RunConfig) error
+	noStdout  bool
+}
+
+func (h *localBenchHarness) CheckPrerequisites() error {
+	return nil
+}
+
+func (h *localBenchHarness) Get(_ string) error {
+	return nil
+}
+
+func (h *localBenchHarness) Build(cfg *common.Config, bcfg *common.BuildConfig) error {
+	return cfg.GoTool().BuildPath(bcfg.BenchDir, filepath.Join(bcfg.BinDir, h.binName))
+}
+
+func (h *localBenchHarness) Run(cfg *common.Config, rcfg *common.RunConfig) error {
+	if h.beforeRun != nil {
+		if err := h.beforeRun(cfg, rcfg); err != nil {
+			return err
+		}
+	}
+	cmd := exec.Command(
+		filepath.Join(rcfg.BinDir, h.binName),
+		append(rcfg.Args, h.genArgs(cfg, rcfg)...)...,
+	)
+	cmd.Env = cfg.ExecEnv.Collapse()
+	if !h.noStdout {
+		cmd.Stdout = rcfg.Results
+	}
+	cmd.Stderr = rcfg.Results
+	log.TraceCommand(cmd, false)
+	return cmd.Run()
+}
+
+func BiogoIgor() common.Harness {
+	return &localBenchHarness{
+		binName: "biogo-igor-bench",
+		genArgs: func(cfg *common.Config, rcfg *common.RunConfig) []string {
+			return []string{
+				filepath.Join(rcfg.AssetsDir, "Homo_sapiens.GRCh38.dna.chromosome.22.gff"),
+			}
+		},
+	}
+}
+
+func BiogoKrishna() common.Harness {
+	return &localBenchHarness{
+		binName: "biogo-krishna-bench",
+		genArgs: func(cfg *common.Config, rcfg *common.RunConfig) []string {
+			return []string{
+				"-alignconc",
+				"-tmp", rcfg.TmpDir,
+				"-tmpconc",
+				filepath.Join(rcfg.AssetsDir, "Mus_musculus.GRCm38.dna.nonchromosomal.fa"),
+			}
+		},
+	}
+}
+
+func BleveIndex() common.Harness {
+	return &localBenchHarness{
+		binName: "bleve-index-bench",
+		genArgs: func(cfg *common.Config, rcfg *common.RunConfig) []string {
+			return []string{
+				"-batch-size", "100",
+				"-documents", "1000",
+				filepath.Join(rcfg.AssetsDir, "enwiki-20080103-pages-articles.xml.bz2"),
+			}
+		},
+	}
+}
+
+func BleveQuery() common.Harness {
+	return &localBenchHarness{
+		binName: "bleve-query-bench",
+		genArgs: func(cfg *common.Config, rcfg *common.RunConfig) []string {
+			return []string{
+				filepath.Join(rcfg.AssetsDir, "index"),
+			}
+		},
+		beforeRun: func(cfg *common.Config, rcfg *common.RunConfig) error {
+			// Make sure all the index passed to the benchmark is writeable.
+			indexPath := filepath.Join(rcfg.AssetsDir, "index")
+			return makeWriteable(indexPath)
+		},
+	}
+}
+
+func FoglemanFauxGL() common.Harness {
+	return &localBenchHarness{
+		binName: "fogleman-fauxgl-bench",
+		genArgs: func(cfg *common.Config, rcfg *common.RunConfig) []string {
+			return []string{
+				filepath.Join(rcfg.AssetsDir, "3dbenchy.stl"),
+			}
+		},
+		noStdout: true,
+	}
+}
+
+func FoglemanPT() common.Harness {
+	return &localBenchHarness{
+		binName: "fogleman-pt-bench",
+		genArgs: func(cfg *common.Config, rcfg *common.RunConfig) []string {
+			return []string{
+				"-iter", "1",
+				filepath.Join(rcfg.AssetsDir, "gopher.obj"),
+			}
+		},
+		noStdout: true,
+	}
+}
+
+func GopherLua() common.Harness {
+	return &localBenchHarness{
+		binName: "gopher-lua-bench",
+		genArgs: func(cfg *common.Config, rcfg *common.RunConfig) []string {
+			return []string{
+				filepath.Join(rcfg.AssetsDir, "k-nucleotide.lua"),
+				filepath.Join(rcfg.AssetsDir, "input.txt"),
+			}
+		},
+	}
+}
+
+func Markdown() common.Harness {
+	return &localBenchHarness{
+		binName: "markdown-bench",
+		genArgs: func(cfg *common.Config, rcfg *common.RunConfig) []string {
+			return []string{
+				rcfg.AssetsDir,
+			}
+		},
+	}
+}
diff --git a/sweet/harnesses/tile38.go b/sweet/harnesses/tile38.go
new file mode 100644
index 0000000..ef1692a
--- /dev/null
+++ b/sweet/harnesses/tile38.go
@@ -0,0 +1,78 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package harnesses
+
+import (
+	"os/exec"
+	"path/filepath"
+
+	"golang.org/x/benchmarks/sweet/common"
+	"golang.org/x/benchmarks/sweet/common/log"
+)
+
+const (
+	server = "tile38-server"
+)
+
+type Tile38 struct{}
+
+func (h Tile38) CheckPrerequisites() error {
+	return nil
+}
+
+func (h Tile38) Get(srcDir string) error {
+	return gitShallowClone(
+		srcDir,
+		"https://github.com/tidwall/tile38",
+		"1.25.3",
+	)
+}
+
+func (h Tile38) Build(cfg *common.Config, bcfg *common.BuildConfig) error {
+	env := cfg.BuildEnv.Env
+
+	// Add the Go tool to PATH, since tile38's Makefile doesn't provide enough
+	// visibility into how tile38 is built to allow us to pass this information
+	// directly.
+	env = env.Prefix("PATH", filepath.Join(cfg.GoRoot, "bin")+":")
+
+	cmd := exec.Command("make", "-C", bcfg.SrcDir)
+	cmd.Env = env.Collapse()
+	log.TraceCommand(cmd, false)
+	if err := cmd.Run(); err != nil {
+		return err
+	}
+	// Note that no matter what we do, the build script insists on putting the
+	// binaries into the source directory, so copy the one we care about into
+	// BinDir.
+	if err := copyFile(filepath.Join(bcfg.BinDir, server), filepath.Join(bcfg.SrcDir, server)); err != nil {
+		return err
+	}
+	return cfg.GoTool().BuildPath(bcfg.BenchDir, filepath.Join(bcfg.BinDir, "tile38-bench"))
+}
+
+func (h Tile38) Run(cfg *common.Config, rcfg *common.RunConfig) error {
+	// Make sure all the data passed to the server is writable.
+	// The server needs to be able to open its persistent storage as read-write.
+	dataPath := filepath.Join(rcfg.AssetsDir, "data")
+	if err := makeWriteable(dataPath); err != nil {
+		return err
+	}
+	cmd := exec.Command(
+		filepath.Join(rcfg.BinDir, "tile38-bench"),
+		append(rcfg.Args, []string{
+			"-host", "127.0.0.1",
+			"-port", "9851",
+			"-server", filepath.Join(rcfg.BinDir, server),
+			"-data", dataPath,
+			"-tmp", rcfg.TmpDir,
+		}...)...,
+	)
+	cmd.Env = cfg.ExecEnv.Collapse()
+	cmd.Stdout = rcfg.Results
+	cmd.Stderr = rcfg.Results
+	log.TraceCommand(cmd, false)
+	return cmd.Run()
+}
diff --git a/sweet/source-assets/gvisor/http/README.md b/sweet/source-assets/gvisor/http/README.md
new file mode 100644
index 0000000..807e15b
--- /dev/null
+++ b/sweet/source-assets/gvisor/http/README.md
@@ -0,0 +1,6 @@
+# gVisor HTTP Server Benchmark
+
+The purpose of this benchmark is to measure the performance of gVisor's
+networking stack and VFS layer. It is loosely based on the HTTP
+benchmark found in the [gVisor
+repository](https://github.com/google/gvisor/blob/3ad6d3056371b031fb0c16c4e365d5c7e60bdaf0/benchmarks/suites/http.py#L115).
diff --git a/sweet/source-assets/gvisor/http/server.go b/sweet/source-assets/gvisor/http/server.go
new file mode 100644
index 0000000..163d8a0
--- /dev/null
+++ b/sweet/source-assets/gvisor/http/server.go
@@ -0,0 +1,105 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"context"
+	"flag"
+	"fmt"
+	"log"
+	"net/http"
+	"os"
+	"os/signal"
+	"path/filepath"
+	"runtime"
+	"text/template"
+	"time"
+)
+
+var (
+	host       string
+	port       int
+	procs      int
+	assetsRoot string
+)
+
+func init() {
+	flag.StringVar(&host, "host", "localhost", "host to serve on")
+	flag.IntVar(&port, "port", 8081, "port to serve on")
+	flag.IntVar(&procs, "procs", runtime.GOMAXPROCS(-1), "how many processors to use")
+	flag.StringVar(&assetsRoot, "assets", "./assets", "directory to serve assets from")
+}
+
+var frontPage = template.Must(template.New("front").Parse(`
+{{- range . -}}
+  {{.}}
+{{end -}}
+`))
+
+func main() {
+	flag.Parse()
+	runtime.GOMAXPROCS(procs)
+
+	var images []string
+	filepath.Walk(assetsRoot, func(path string, info os.FileInfo, err error) error {
+		if err != nil {
+			log.Printf("warning: failed to walk %s: %v", path, err)
+			return nil
+		}
+		if info.IsDir() {
+			// Ignore directories.
+			return nil
+		}
+		relPath, _ := filepath.Rel(assetsRoot, path)
+		switch filepath.Ext(path) {
+		case ".jpg", ".png", ".gif":
+			images = append(images, relPath)
+		default:
+		}
+		return nil
+	})
+	for i, img := range images {
+		images[i] = filepath.Join("/static", img)
+		log.Printf("Image: %s", images[i])
+	}
+
+	ctx := context.Background()
+	ctx, cancel := signal.NotifyContext(ctx, os.Interrupt)
+	defer cancel()
+
+	// Set up a done channel.
+	http.Handle("/static/", http.FileServer(http.Dir(assetsRoot)))
+	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+		if r.URL.Path != "/" {
+			http.NotFound(w, r)
+			return
+		}
+		if err := frontPage.Execute(w, images); err != nil {
+			http.Error(w, fmt.Sprintf("Internal server error: %s", err.Error()), 500)
+			return
+		}
+	})
+
+	server := &http.Server{Addr: fmt.Sprintf("%s:%d", host, port), Handler: http.DefaultServeMux}
+	go func() {
+		err := server.ListenAndServe()
+		if err != nil && err != http.ErrServerClosed {
+			log.Fatalf("Server error listening/serving: %s", err)
+		}
+	}()
+
+	// Wait for a signal to stop.
+	<-ctx.Done()
+
+	// Shut down the server cleanly.
+	exitCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+	defer cancel()
+
+	if err := server.Shutdown(exitCtx); err != nil {
+		log.Printf("Clean shutdown failed: %v", err)
+		return
+	}
+	log.Print("Shut down successfully. Goodbye!")
+}
diff --git a/sweet/source-assets/gvisor/syscall/README.md b/sweet/source-assets/gvisor/syscall/README.md
new file mode 100644
index 0000000..602e42a
--- /dev/null
+++ b/sweet/source-assets/gvisor/syscall/README.md
@@ -0,0 +1,11 @@
+# gVisor Syscall Benchmark
+
+This directory contains a linux/amd64 Go binary which measures raw
+syscall performance by running getppid (which is usually not cached
+in userspace) a fixed number of iterations. It would be ideal to run
+for a specified amount of time instead, but plumbing results back
+is a pain, and this is good enough. The benchmark source is in the
+`src` directory.
+
+The benchmark is loosely based off the syscall benchmark in [gVisor's
+repository](https://github.com/google/gvisor/tree/3ad6d30/benchmarks/workloads/syscall).
diff --git a/sweet/source-assets/gvisor/syscall/syscall-bench.go b/sweet/source-assets/gvisor/syscall/syscall-bench.go
new file mode 100644
index 0000000..d4d709f
--- /dev/null
+++ b/sweet/source-assets/gvisor/syscall/syscall-bench.go
@@ -0,0 +1,15 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import "os"
+
+var ppid int
+
+func main() {
+	for i := 0; i < 500000; i++ {
+		ppid = os.Getppid()
+	}
+}
diff --git a/third_party/biogo-examples/LICENSE b/third_party/biogo-examples/LICENSE
new file mode 100644
index 0000000..75cc58b
--- /dev/null
+++ b/third_party/biogo-examples/LICENSE
@@ -0,0 +1,23 @@
+Copyright ©2012 The bíogo Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of the bíogo project nor the names of its authors and
+      contributors may be used to endorse or promote products derived from this
+      software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/third_party/biogo-examples/README.md b/third_party/biogo-examples/README.md
new file mode 100644
index 0000000..f7f193c
--- /dev/null
+++ b/third_party/biogo-examples/README.md
@@ -0,0 +1,5 @@
+# biogo-examples
+
+This directory contains modified examples from the [biogo examples
+repository](https://github.com/biogo/examples). It retains the [BSD-3-clause
+license](./LICENSE) associated.
diff --git a/third_party/biogo-examples/igor/igor/cluster.go b/third_party/biogo-examples/igor/igor/cluster.go
new file mode 100644
index 0000000..26f202d
--- /dev/null
+++ b/third_party/biogo-examples/igor/igor/cluster.go
@@ -0,0 +1,211 @@
+// Copyright ©2014 The bíogo Authors. All rights reserved.
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package igor
+
+import (
+	"sort"
+	"sync"
+
+	"golang.org/x/benchmarks/third_party/biogo-examples/igor/turner"
+
+	"github.com/biogo/biogo/align/pals"
+	"github.com/biogo/store/interval"
+	"github.com/biogo/store/step"
+)
+
+func min(a, b int) int {
+	if a < b {
+		return a
+	}
+	return b
+}
+
+func max(a, b int) int {
+	if a > b {
+		return a
+	}
+	return b
+}
+
+func within(alpha float64, short, long int) bool {
+	return float64(short) >= float64(long)*(1-alpha)
+}
+
+func overlap(a, b interval.IntRange) int {
+	return max(0, max(a.End-b.Start, b.End-a.Start))
+}
+
+type pileInterval struct {
+	p  *pals.Pile
+	id uintptr
+}
+
+func (pi pileInterval) ID() uintptr { return pi.id }
+func (pi pileInterval) Overlap(b interval.IntRange) bool {
+	return pi.p.Start() < b.End && pi.p.End() > b.Start
+}
+func (pi pileInterval) Range() interval.IntRange {
+	return interval.IntRange{pi.p.Start(), pi.p.End()}
+}
+
+// ClusterConfig specifies Cluster behaviour.
+type ClusterConfig struct {
+	// BandWidth specifies the maximum fractional distance between
+	// endpoints of images being clustered into sub-piles. See
+	// turner.Cluster for details.
+	BandWidth float64
+
+	// RequiredCover specifies the target coverage fraction for
+	// each input pile. If RequiredCover is greater than 1, all
+	// all sub-piles are retained depending on the values of
+	// KeepOverlaps and OverlapThresh.
+	RequiredCover float64
+
+	// OverlapStrictness specifies the clustering behaviour.
+	// If OverlapStrictness is zero, all clusters are passed
+	// returned. If set to one, clusters containing clusters
+	// with greater depth are rejected. If set to two, contained
+	// features overlapping by more than OverlapThresh fraction
+	// of the smaller pile are are discarded.
+	OverlapStrictness byte
+	OverlapThresh     float64
+
+	// Procs specifies the number of independent clustering
+	// instances to run in parallel. If zero, only single threaded
+	// operation is performed.
+	Procs int
+}
+
+// Cluster performs sub-pile clustering according to the config provided.
+// The number of sub-piles and a collection of piles broken into sub-piles is
+// returned.
+func Cluster(piles []*pals.Pile, cfg ClusterConfig) (int, [][]*pals.Pile) {
+	procs := cfg.Procs
+	if procs < 1 {
+		procs = 1
+	}
+	type workItem struct {
+		i int
+		p *pals.Pile
+	}
+	var wg sync.WaitGroup
+	clust := make([][]*pals.Pile, len(piles))
+	work := make([]chan workItem, 0, procs)
+	ready := make(chan int, procs)
+	// skipLock protect writes/reads to p.Loc which is abused as a flag to
+	// allow Group to know which piles to ignore in the grouping phase.
+	var skipLock sync.Mutex
+	for i := 0; i < procs; i++ {
+		wg.Add(1)
+		work = append(work, make(chan workItem))
+		go func(id int) {
+			for {
+				ready <- id
+				w := <-work[id]
+				i, p := w.i, w.p
+				if p == nil {
+					wg.Done()
+					return
+				}
+
+				skipLock.Lock()
+				loc := p.Loc
+				skipLock.Unlock()
+				if loc == nil {
+					return
+				}
+
+				tc := turner.Cluster(p, cfg.BandWidth)
+
+				sv, err := step.New(p.Start(), p.End(), step.Int(0))
+				if err != nil {
+					panic(err)
+				}
+				sort.Sort(turner.ByDepth(tc))
+				var (
+					t        interval.IntTree
+					accepted int
+				)
+				for j, c := range tc {
+					if cfg.OverlapStrictness > 0 {
+						pi := pileInterval{c, uintptr(j)}
+						for _, iv := range t.Get(pi) {
+							r := iv.Range()
+							pir := pi.Range()
+							discard := func() {
+								c = nil
+								pile := iv.(pileInterval).p
+								skipLock.Lock()
+								pile.Loc = nil
+								for _, im := range pile.Images {
+									im.Location().(*pals.Pile).Loc = nil
+								}
+								skipLock.Unlock()
+							}
+							switch cfg.OverlapStrictness {
+							case 1:
+								if (pir.Start <= r.Start && pir.End > r.End) || (pir.Start < r.Start && pir.End >= r.End) {
+									discard()
+								}
+							case 2:
+								if within(cfg.OverlapThresh, overlap(r, pir), min(pi.p.Len(), r.End-r.Start)) {
+									discard()
+								}
+							default:
+								panic("illegal strictness value")
+							}
+						}
+						if c == nil {
+							tc[j] = nil
+							continue
+						}
+						t.Insert(pi, false)
+					}
+
+					accepted++
+					sv.ApplyRange(c.Start(), c.End(), func(e step.Equaler) step.Equaler {
+						return e.(step.Int) + step.Int(len(c.Images))
+					})
+					var cov int
+					sv.Do(func(start, end int, e step.Equaler) {
+						if e.(step.Int) > 1 {
+							cov += end - start
+						}
+					})
+					if !within(cfg.RequiredCover, cov, p.Len()) {
+						skipLock.Lock()
+						for _, dc := range tc[j+1:] {
+							dc.Loc = nil
+							for _, im := range dc.Images {
+								im.Location().(*pals.Pile).Loc = nil
+							}
+						}
+						skipLock.Unlock()
+						tc = tc[:j+1]
+						break
+					}
+				}
+				clust[i] = tc
+			}
+		}(i)
+	}
+	for i, p := range piles {
+		id := <-ready
+		work[id] <- workItem{i, p}
+	}
+	// Send nil to all to signal completion, and wait for all
+	// workers to quit gracefully.
+	for i := 0; i < procs; i++ {
+		work[i] <- workItem{}
+	}
+	wg.Wait()
+
+	var n int
+	for _, c := range clust {
+		n += len(c)
+	}
+	return n, clust
+}
diff --git a/third_party/biogo-examples/igor/igor/group.go b/third_party/biogo-examples/igor/igor/group.go
new file mode 100644
index 0000000..10cbee9
--- /dev/null
+++ b/third_party/biogo-examples/igor/igor/group.go
@@ -0,0 +1,209 @@
+// Copyright ©2014 The bíogo Authors. All rights reserved.
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package igor
+
+import (
+	"log"
+
+	"github.com/biogo/biogo/align/pals"
+	"github.com/biogo/biogo/seq"
+	"github.com/biogo/graph"
+)
+
+// GroupConfig specifies Group behaviour
+type GroupConfig struct {
+	// PileDiff specifies the acceptable fractional difference between
+	// piles for assignment to the same group.
+	PileDiff float64
+	// ImageDiff specifies the acceptable fractional difference between
+	// an image and its containing pile when considering a pile for
+	// assignment based on that image.
+	ImageDiff float64
+
+	// When Classic is true, Group will run a reasonable approximation
+	// of the original C PILER family grouping.
+	Classic bool
+}
+
+// Group clusters the input pile collection based on the existence of satisfactory
+// image alignments according to the provided config. Piles with nil locations are
+// ignored. Checks are performed to ensure that the produced clusters can be
+// unambiguously assigned to a DNA strand.
+func Group(clust [][]*pals.Pile, cfg GroupConfig) []graph.Nodes {
+	g := newPileGraph()
+
+	for _, c := range clust {
+		for _, pile := range c {
+			if pile == nil || pile.Loc == nil || len(pile.Images) == 0 {
+				continue
+			}
+			g.insert(pile)
+
+			for _, im := range pile.Images {
+				partner := im.Mate().Location().(*pals.Pile)
+				if partner.Loc == nil {
+					continue
+				}
+				if !cfg.Classic { // We already know these are true when cfg.Classic is true.
+					// Confirm that piles are within cfg.PileDiff in length.
+					if !within(cfg.PileDiff, min(pile.Len(), partner.Len()), max(pile.Len(), partner.Len())) {
+						continue
+					}
+					// Confirm that images are within cfg.ImageDiff of their piles in length.
+					if !within(cfg.ImageDiff, im.Len(), pile.Len()) || !within(cfg.ImageDiff, im.Mate().Len(), partner.Len()) {
+						continue
+					}
+				}
+				g.insert(partner)
+
+				fPile := feature{pile.Name(), pile.Start(), pile.End()}
+				fPartner := feature{partner.Name(), partner.Start(), partner.End()}
+				if pile.Node != partner.Node && fPile != fPartner {
+					err := g.connect(pile, partner, im.Pair.Strand)
+					if err != nil {
+						log.Fatalf("igor: internal error: %v", err)
+					}
+				}
+			}
+		}
+	}
+
+	for p, n := range g.poisoned {
+		if n > 1 {
+			// Removing poisioned node.
+			g.delete(p)
+		}
+		// May still have a poisoned node.
+	}
+
+	cc := g.connectedComponents(func(e graph.Edge) bool {
+		te := e.(*twistEdge)
+		if te.twist == seq.None {
+			return false
+		}
+		h := e.Head().(*pals.Pile)
+		t := e.Tail().(*pals.Pile)
+		switch {
+		case h.Strand == 0 && t.Strand == 0:
+			h.Strand = 1
+			t.Strand = te.twist
+		case t.Strand == 0:
+			t.Strand = h.Strand * te.twist
+		case h.Strand == 0:
+			h.Strand = t.Strand * te.twist
+		default:
+			if h.Strand != t.Strand*te.twist {
+				te.conflict = true
+			}
+		}
+		return true
+	})
+
+	for i := 0; i < len(cc); i++ {
+	loop:
+		for _, n := range cc[i] {
+			for _, e := range n.Edges() {
+				if e.(*twistEdge).conflict {
+					cc[i] = cc[len(cc)-1]
+					cc = cc[:len(cc)-1]
+					i--
+					break loop
+				}
+			}
+		}
+	}
+
+	for _, c := range cc {
+		for _, n := range c {
+			n.(*pals.Pile).Node = nil
+		}
+	}
+
+	return cc
+}
+
+type feature struct {
+	name     string
+	from, to int
+}
+
+type twistEdge struct {
+	graph.Edge
+	twist    seq.Strand
+	conflict bool
+}
+
+type pileGraph struct {
+	g     *graph.Undirected
+	nodes map[feature]graph.Node
+	edges map[[2]*pals.Pile]*twistEdge
+
+	poisoned map[*pals.Pile]int
+}
+
+func newPileGraph() pileGraph {
+	return pileGraph{
+		g: graph.NewUndirected(),
+
+		nodes: make(map[feature]graph.Node),
+		edges: make(map[[2]*pals.Pile]*twistEdge),
+
+		poisoned: make(map[*pals.Pile]int),
+	}
+}
+
+func (g pileGraph) insert(p *pals.Pile) {
+	if p.Node != nil {
+		return
+	}
+	f := feature{p.Loc.Name(), p.Start(), p.End()}
+	if n, exists := g.nodes[f]; exists {
+		p.Node = g.g.NewNode()
+		g.g.Add(p)
+		e := &twistEdge{
+			Edge:  graph.NewEdge(),
+			twist: seq.Plus,
+		}
+		g.g.ConnectWith(n, p, e)
+		return
+	}
+	p.Node = g.g.NewNode()
+	g.g.Add(p)
+	g.nodes[f] = p
+}
+
+func (g pileGraph) connect(pile, partner *pals.Pile, twist seq.Strand) error {
+	var (
+		e  *twistEdge
+		ok bool
+	)
+	if e, ok = g.edges[[2]*pals.Pile{pile, partner}]; !ok {
+		e, ok = g.edges[[2]*pals.Pile{partner, pile}]
+	}
+	if ok && e.twist != twist {
+		if e.twist != seq.None {
+			g.poisoned[pile]++
+			g.poisoned[partner]++
+		}
+		e.twist = seq.None
+		return nil
+	}
+
+	e = &twistEdge{
+		Edge:  graph.NewEdge(),
+		twist: twist,
+	}
+	g.edges[[2]*pals.Pile{pile, partner}] = e
+	return g.g.ConnectWith(pile, partner, e)
+}
+
+func (g pileGraph) delete(p *pals.Pile) error {
+	return g.g.Delete(p)
+}
+
+func (g pileGraph) connectedComponents(fn func(e graph.Edge) bool) []graph.Nodes {
+	return graph.ConnectedComponents(g.g, fn)
+}
diff --git a/third_party/biogo-examples/igor/igor/pile.go b/third_party/biogo-examples/igor/igor/pile.go
new file mode 100644
index 0000000..e9c2070
--- /dev/null
+++ b/third_party/biogo-examples/igor/igor/pile.go
@@ -0,0 +1,57 @@
+// Copyright ©2014 The bíogo Authors. All rights reserved.
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package igor
+
+import (
+	"github.com/biogo/biogo/align/pals"
+	"github.com/biogo/biogo/io/featio"
+	"github.com/biogo/biogo/io/featio/gff"
+)
+
+// store is a pals.Contig internment implementation.
+type store map[pals.Contig]pals.Contig
+
+// intern returns an interned copy of the parameter.
+func (is store) intern(c pals.Contig) pals.Contig {
+	if c == "" {
+		return ""
+	}
+	t, ok := is[c]
+	if ok {
+		return t
+	}
+	is[c] = c
+	return c
+}
+
+// Piles reads the features in the input gff.Reader and applies pals.Piler analysis
+// using the specified overlap and pair filter function. The features in the input
+// must satisfy pals.ExpandFeature restrictions.
+func Piles(in *gff.Reader, overlap int, pf pals.PairFilter) ([]*pals.Pile, error) {
+	piler := pals.NewPiler(overlap)
+	contigs := make(store)
+
+	var n int
+
+	sc := featio.NewScanner(in)
+	for sc.Next() {
+		rep := sc.Feat().(*gff.Feature)
+
+		p, err := pals.ExpandFeature(rep)
+		if err != nil {
+			return nil, err
+		}
+		p.A.Loc = contigs.intern(p.A.Loc.(pals.Contig))
+		p.B.Loc = contigs.intern(p.B.Loc.(pals.Contig))
+
+		piler.Add(p)
+		n++
+	}
+	if err := sc.Error(); err != nil {
+		return nil, err
+	}
+	return piler.Piles(pf), nil
+}
diff --git a/third_party/biogo-examples/igor/igor/write.go b/third_party/biogo-examples/igor/igor/write.go
new file mode 100644
index 0000000..145afff
--- /dev/null
+++ b/third_party/biogo-examples/igor/igor/write.go
@@ -0,0 +1,68 @@
+// Copyright ©2014 The bíogo Authors. All rights reserved.
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package igor
+
+import (
+	"encoding/json"
+	"io"
+
+	"github.com/biogo/biogo/align/pals"
+	"github.com/biogo/biogo/seq"
+	"github.com/biogo/graph"
+)
+
+func WriteJSON(cc []graph.Nodes, w io.Writer) error {
+	type feat struct {
+		C string
+		S int
+		E int
+		O seq.Strand
+	}
+	var (
+		a feat
+		f []feat
+		j = json.NewEncoder(w)
+	)
+
+	seen := make(map[feat]struct{})
+	var fi int
+	for _, fam := range cc {
+		for _, p := range fam {
+			pile := p.(*pals.Pile)
+
+			a.C = pile.Location().Name()
+			a.S = pile.Start()
+			a.E = pile.End()
+			a.O = pile.Strand
+			if _, ok := seen[a]; !ok && pile.Loc != nil {
+				seen[a] = struct{}{}
+				f = append(f, a)
+			}
+		}
+		switch len(f) {
+		case 0, 1:
+			continue
+		default:
+			for i := 0; i < len(f); {
+				if f[i].O == seq.None {
+					f[i], f = f[len(f)-1], f[:len(f)-1]
+				} else {
+					i++
+				}
+			}
+		}
+		if len(f) < 2 {
+			continue
+		}
+		err := j.Encode(f)
+		if err != nil {
+			return err
+		}
+		f = f[:0]
+		fi++
+	}
+	return nil
+}
diff --git a/third_party/biogo-examples/igor/turner/cluster.go b/third_party/biogo-examples/igor/turner/cluster.go
new file mode 100644
index 0000000..4ee73b9
--- /dev/null
+++ b/third_party/biogo-examples/igor/turner/cluster.go
@@ -0,0 +1,175 @@
+// Copyright ©2014 The bíogo Authors. All rights reserved.
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package turner
+
+import (
+	"sort"
+
+	"github.com/biogo/biogo/align/pals"
+	"github.com/biogo/biogo/feat"
+	"github.com/biogo/store/interval"
+)
+
+type pairings struct {
+	interval interval.IntRange
+	id       uintptr
+
+	loc
+	data []*pals.Feature
+}
+
+type loc struct{ feat.Feature }
+
+func (p *pairings) Start() int { return p.interval.Start }
+func (p *pairings) End() int   { return p.interval.End }
+func (p *pairings) Len() int   { return p.interval.End - p.interval.Start }
+
+func (p *pairings) ID() uintptr              { return p.id }
+func (p *pairings) Range() interval.IntRange { return p.interval }
+func (p *pairings) Overlap(b interval.IntRange) bool {
+	return b.End >= p.Start() && b.Start <= p.End()
+}
+
+type byDepth []*pairings
+
+func (p byDepth) Len() int { return len(p) }
+func (p byDepth) Less(i, j int) bool {
+	return len(p[i].data) > len(p[j].data)
+}
+func (p byDepth) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
+
+type simple interval.IntRange
+
+func (i simple) Range() interval.IntRange { return interval.IntRange(i) }
+func (i simple) ID() uintptr              { return 0 }
+func (i simple) Overlap(b interval.IntRange) bool {
+	return b.End >= i.Start && b.Start <= i.End
+}
+
+// Cluster performs a clustering of images in the pile p to create a collection
+// of piles where image ends are within h times the images length of the ends of
+// the generated sub-pile. Clustering is seeded by the set of unique image intervals
+// in order of decreasing depth.
+func Cluster(p *pals.Pile, h float64) []*pals.Pile {
+	pm := make(map[interval.IntRange][]*pals.Feature)
+	for _, fv := range p.Images {
+		i := interval.IntRange{Start: fv.Start(), End: fv.End()}
+		c := pm[i]
+		c = append(c, fv)
+		pm[i] = c
+	}
+
+	pl := make([]*pairings, 0, len(pm))
+	for iv, data := range pm {
+		pl = append(pl, &pairings{interval: iv, loc: loc{p}, data: data})
+	}
+
+	var t interval.IntTree
+	for i, pe := range pl {
+		pe.id = uintptr(i)
+		t.Insert(pe, true)
+	}
+	t.AdjustRanges()
+
+	var cl []*pals.Pile
+
+	sort.Sort(byDepth(pl))
+	for _, pe := range pl {
+		if _, ok := pm[interval.IntRange{Start: pe.Start(), End: pe.End()}]; !ok {
+			continue
+		}
+		thr := int(h * float64(pe.interval.End-pe.interval.Start))
+		from := pe.End()
+		to := pe.Start()
+		var (
+			mf []*pals.Feature
+			dm []interval.IntInterface
+		)
+		t.DoMatching(func(iv interval.IntInterface) (done bool) {
+			r := iv.Range()
+			if abs(pe.Start()-r.Start) <= thr && abs(pe.End()-r.End) <= thr {
+				ivp := iv.(*pairings)
+				dm = append(dm, iv)
+				mf = append(mf, ivp.data...)
+
+				if r.Start < from {
+					from = r.Start
+				}
+				if r.End > to {
+					to = r.End
+				}
+			}
+			return
+		}, simple{pe.Start() - thr, pe.End() + thr})
+		if len(mf) == 0 {
+			continue
+		}
+		for _, de := range dm {
+			t.Delete(de, true)
+			delete(pm, de.Range())
+		}
+		t.AdjustRanges()
+		cl = append(cl, &pals.Pile{
+			From:   from,
+			To:     to,
+			Loc:    pe.Location(),
+			Images: mf,
+		})
+	}
+
+	return cl
+}
+
+func abs(a int) int {
+	if a != 0 && a == -a {
+		panic("weird number")
+	}
+	if a < 0 {
+		return -a
+	}
+	return a
+}
+
+// Range returns the start and end positions for all the members of p.
+func Range(p []*pals.Pile) (start, end int) {
+	if len(p) == 0 {
+		return
+	}
+	start, end = p[0].Start(), p[0].End()
+	for _, pi := range p[1:] {
+		if s := pi.Start(); s < start {
+			start = s
+		}
+		if e := pi.End(); e > end {
+			end = e
+		}
+	}
+	return start, end
+}
+
+// Volume returns the number of bases contributing to the pile p.
+func Volume(p *pals.Pile) int {
+	var m int
+	for _, im := range p.Images {
+		m += im.Len()
+	}
+	return m
+}
+
+// ByVolume allows a collection of pals.Pile to be sorted by volume.
+type ByVolume []*pals.Pile
+
+func (p ByVolume) Len() int           { return len(p) }
+func (p ByVolume) Less(i, j int) bool { return Volume(p[i]) > Volume(p[j]) }
+func (p ByVolume) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
+
+// ByVolume allows a collection of pals.Pile to be sorted by the number
+// images in each pile.
+type ByDepth []*pals.Pile
+
+func (p ByDepth) Len() int           { return len(p) }
+func (p ByDepth) Less(i, j int) bool { return len(p[i].Images) > len(p[j].Images) }
+func (p ByDepth) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
diff --git a/third_party/biogo-examples/krishna/krishna.go b/third_party/biogo-examples/krishna/krishna.go
new file mode 100644
index 0000000..01b5bc9
--- /dev/null
+++ b/third_party/biogo-examples/krishna/krishna.go
@@ -0,0 +1,109 @@
+// Copyright ©2014 The bíogo Authors. All rights reserved.
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package krishna
+
+import (
+	"fmt"
+	"sync"
+
+	"github.com/biogo/biogo/align/pals"
+	"github.com/biogo/biogo/align/pals/filter"
+	"github.com/biogo/biogo/morass"
+)
+
+type Params struct {
+	TmpChunkSize int
+	MinHitLen    int
+	MinHitId     float64
+	TubeOffset   int
+	AlignConc    bool
+	TmpConc      bool
+}
+
+// Krishna is a pure Go implementation of Edgar and Myers PALS tool.
+// This version of krishna is modified from its original form and only
+// computes alignment for a sequence against itself.
+type Krishna struct {
+	params Params
+	target *pals.Packed
+	pa     [2]*pals.PALS
+}
+
+func New(seqPath, tmpDir string, params Params) (*Krishna, error) {
+	target, err := packSequence(seqPath)
+	if err != nil {
+		return nil, err
+	}
+	if target.Len() == 0 {
+		return nil, fmt.Errorf("target sequence is zero length")
+	}
+	// Allocate morass before since these can be somewhat large
+	// and single large allocations tend to be noisy.
+	m1, err := morass.New(filter.Hit{}, "krishna_", tmpDir, params.TmpChunkSize, params.TmpConc)
+	if err != nil {
+		return nil, err
+	}
+	m2, err := morass.New(filter.Hit{}, "krishna_", tmpDir, params.TmpChunkSize, params.TmpConc)
+	if err != nil {
+		return nil, err
+	}
+	pa := [2]*pals.PALS{
+		pals.New(target.Seq, target.Seq, true, m1, params.TubeOffset, nil, nil),
+		pals.New(target.Seq, target.Seq, true, m2, params.TubeOffset, nil, nil),
+	}
+	return &Krishna{params, target, pa}, nil
+}
+
+//
+// Returns a cleanup function, and an error. The cleanup function should be
+// called before program exit, if not nil.
+func (k *Krishna) Run(writer *pals.Writer) error {
+	if err := k.pa[0].Optimise(k.params.MinHitLen, k.params.MinHitId); err != nil {
+		return err
+	}
+	if err := k.pa[0].BuildIndex(); err != nil {
+		return err
+	}
+	k.pa[1].Share(k.pa[0])
+
+	var wg sync.WaitGroup
+	errs := make([]error, 2)
+	for i, comp := range [...]bool{false, true} {
+		wg.Add(1)
+		go func(i int, p *pals.PALS, comp bool) {
+			defer wg.Done()
+			hits, err := p.Align(comp)
+			if err != nil {
+				errs[i] = err
+				return
+			}
+			_, err = writeDPHits(writer, k.target, k.target, hits, comp)
+			if err != nil {
+				errs[i] = err
+				return
+			}
+		}(i, k.pa[i], comp)
+		if !k.params.AlignConc {
+			// Block until it's done if we don't want to run
+			// the alignment processing concurrently.
+			wg.Wait()
+		}
+	}
+	wg.Wait()
+
+	for _, err := range errs {
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (k *Krishna) CleanUp() {
+	for _, p := range k.pa {
+		p.CleanUp()
+	}
+}
diff --git a/third_party/biogo-examples/krishna/packseqs.go b/third_party/biogo-examples/krishna/packseqs.go
new file mode 100644
index 0000000..d49627b
--- /dev/null
+++ b/third_party/biogo-examples/krishna/packseqs.go
@@ -0,0 +1,43 @@
+// Copyright ©2014 The bíogo Authors. All rights reserved.
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package krishna
+
+import (
+	"os"
+	"path/filepath"
+
+	"github.com/biogo/biogo/align/pals"
+	"github.com/biogo/biogo/alphabet"
+	"github.com/biogo/biogo/io/seqio/fasta"
+	"github.com/biogo/biogo/seq"
+	"github.com/biogo/biogo/seq/linear"
+)
+
+func packSequence(fileName string) (*pals.Packed, error) {
+	_, name := filepath.Split(fileName)
+	packer := pals.NewPacker(name)
+
+	file, err := os.Open(fileName)
+	if err != nil {
+		return nil, err
+	}
+	defer file.Close()
+	template := &linear.Seq{Annotation: seq.Annotation{Alpha: alphabet.DNA}}
+	seqFile := fasta.NewReader(file, template)
+
+	var seq seq.Sequence
+	for {
+		seq, err = seqFile.Read()
+		if err != nil {
+			break
+		}
+		_, err = packer.Pack(seq.(*linear.Seq))
+		if err != nil {
+			return nil, err
+		}
+	}
+	return packer.FinalisePack(), nil
+}
diff --git a/third_party/biogo-examples/krishna/write.go b/third_party/biogo-examples/krishna/write.go
new file mode 100644
index 0000000..a0c287c
--- /dev/null
+++ b/third_party/biogo-examples/krishna/write.go
@@ -0,0 +1,35 @@
+// Copyright ©2014 The bíogo Authors. All rights reserved.
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package krishna
+
+import (
+	"sync"
+
+	"github.com/biogo/biogo/align/pals"
+	"github.com/biogo/biogo/align/pals/dp"
+)
+
+var wlock = &sync.Mutex{}
+
+func writeDPHits(w *pals.Writer, target, query *pals.Packed, hits []dp.Hit, comp bool) (n int, err error) {
+	wlock.Lock()
+	defer wlock.Unlock()
+
+	for _, hit := range hits {
+		pair, err := pals.NewPair(target, query, hit, comp)
+		if err != nil {
+			return n, err
+		} else {
+			ln, err := w.Write(pair)
+			n += ln
+			if err != nil {
+				return n, err
+			}
+		}
+	}
+
+	return
+}
diff --git a/third_party/bleve-bench/LICENSE b/third_party/bleve-bench/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/third_party/bleve-bench/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/third_party/bleve-bench/README.md b/third_party/bleve-bench/README.md
new file mode 100644
index 0000000..0653e61
--- /dev/null
+++ b/third_party/bleve-bench/README.md
@@ -0,0 +1,4 @@
+# bleve-bench
+
+This directory contains code derived from the
+[bleve-bench](https://github.com/blevesearch/bleve-bench) repository.
diff --git a/third_party/bleve-bench/mapping.go b/third_party/bleve-bench/mapping.go
new file mode 100644
index 0000000..adb2867
--- /dev/null
+++ b/third_party/bleve-bench/mapping.go
@@ -0,0 +1,59 @@
+//  Copyright (c) 2018 Couchbase, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 		http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package blevebench
+
+import (
+	"github.com/blevesearch/bleve"
+	"github.com/blevesearch/bleve/mapping"
+)
+
+// articleMapping returns a mapping for indexing wikipedia articles
+// in a manner similar to that done by the Apache Lucene nightly
+// benchmarks.
+func ArticleMapping() mapping.IndexMapping {
+	standard := bleve.NewTextFieldMapping()
+	standard.Store = false
+	standard.IncludeInAll = false
+	standard.IncludeTermVectors = false
+	standard.Analyzer = "standard"
+
+	keyword := bleve.NewTextFieldMapping()
+	keyword.Store = false
+	keyword.IncludeInAll = false
+	keyword.IncludeTermVectors = false
+	keyword.Analyzer = "keyword"
+
+	article := bleve.NewDocumentMapping()
+	article.AddFieldMappingsAt("Title", keyword)
+	article.AddFieldMappingsAt("Text", standard)
+
+	disabled := bleve.NewDocumentDisabledMapping()
+	article.AddSubDocumentMapping("Other", disabled)
+
+	mapping := bleve.NewIndexMapping()
+	mapping.DefaultMapping = article
+	mapping.DefaultField = "Other"
+	mapping.DefaultAnalyzer = "standard"
+
+	return mapping
+}
+
+type Article struct {
+	Title, Text string
+}
diff --git a/third_party/fogleman-fauxgl/LICENSE b/third_party/fogleman-fauxgl/LICENSE
new file mode 100644
index 0000000..e0d6be6
--- /dev/null
+++ b/third_party/fogleman-fauxgl/LICENSE
@@ -0,0 +1,19 @@
+Copyright (C) 2017 Michael Fogleman
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/third_party/fogleman-fauxgl/README.md b/third_party/fogleman-fauxgl/README.md
new file mode 100644
index 0000000..46e5991
--- /dev/null
+++ b/third_party/fogleman-fauxgl/README.md
@@ -0,0 +1,5 @@
+# fogleman-fauxgl
+
+The code is a modified version of the `animate.go` example included in the
+[FauxGL upstream repository](https://github.com/fogleman/fauxgl). Thus, it
+retains the MIT license originally associated with that code.
diff --git a/third_party/fogleman-fauxgl/animate.go b/third_party/fogleman-fauxgl/animate.go
new file mode 100644
index 0000000..534079b
--- /dev/null
+++ b/third_party/fogleman-fauxgl/animate.go
@@ -0,0 +1,82 @@
+// Copyright (C) 2017 Michael Fogleman.
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package animatebench
+
+import (
+	"flag"
+	"image"
+
+	. "github.com/fogleman/fauxgl"
+	"github.com/nfnt/resize"
+)
+
+const (
+	scale  = 4   // optional supersampling
+	width  = 800 // output width in pixels
+	height = 800 // output height in pixels
+	fovy   = 30  // vertical field of view in degrees
+	near   = 1   // near clipping plane
+	far    = 10  // far clipping plane
+)
+
+var (
+	eye        = V(4, 4, 2)                  // camera position
+	center     = V(0, 0, 0)                  // view center position
+	up         = V(0, 0, 1)                  // up vector
+	light      = V(0.25, 0.5, 1).Normalize() // light direction
+	color      = HexColor("#FEB41C")         // object color
+	background = HexColor("#24221F")         // background color
+)
+
+type RotateAnimation struct {
+	mesh    *Mesh
+	matrix  Matrix
+	context *Context
+}
+
+func Load(meshPath string) (*RotateAnimation, error) {
+	// load a mesh
+	mesh, err := LoadSTL(flag.Arg(0))
+	if err != nil {
+		return nil, err
+	}
+
+	// fit mesh in a bi-unit cube centered at the origin
+	mesh.BiUnitCube()
+
+	// create transformation matrix and light direction
+	aspect := float64(width) / float64(height)
+	matrix := LookAt(eye, center, up).Perspective(fovy, aspect, near, far)
+
+	return &RotateAnimation{
+		mesh:    mesh,
+		matrix:  matrix,
+		context: NewContext(width*scale, height*scale),
+	}, nil
+}
+
+// RenderNext renders the next step in the animation, returning the resulting
+// image. Each step rotates the input mesh by 5 degrees.
+func (a *RotateAnimation) RenderNext() image.Image {
+	// render
+	a.context.ClearDepthBuffer()
+	a.context.ClearColorBufferWith(background)
+	shader := NewPhongShader(a.matrix, light, eye)
+	shader.ObjectColor = color
+	shader.DiffuseColor = Gray(0.9)
+	shader.SpecularColor = Gray(0.25)
+	shader.SpecularPower = 100
+	a.context.Shader = shader
+	a.context.DrawMesh(a.mesh)
+
+	// resize image for anti-aliasing
+	image := a.context.Image()
+	image = resize.Resize(width, height, image, resize.Bilinear)
+
+	a.mesh.Transform(Rotate(up, Radians(5)))
+
+	return image
+}
diff --git a/third_party/fogleman-pt/LICENSE b/third_party/fogleman-pt/LICENSE
new file mode 100644
index 0000000..c8fe382
--- /dev/null
+++ b/third_party/fogleman-pt/LICENSE
@@ -0,0 +1,19 @@
+Copyright (C) 2015 Michael Fogleman
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/third_party/fogleman-pt/README.md b/third_party/fogleman-pt/README.md
new file mode 100644
index 0000000..34c7ff3
--- /dev/null
+++ b/third_party/fogleman-pt/README.md
@@ -0,0 +1,5 @@
+# fogleman-fauxgl
+
+The code is a modified version of the `gopher.go` example included in the
+[pt upstream repository](https://github.com/fogleman/pt). Thus, it retains the
+MIT license originally associated with that code.
diff --git a/third_party/fogleman-pt/gopher.go b/third_party/fogleman-pt/gopher.go
new file mode 100644
index 0000000..8412dff
--- /dev/null
+++ b/third_party/fogleman-pt/gopher.go
@@ -0,0 +1,59 @@
+// Copyright (C) 2015 Michael Fogleman.
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ptbench
+
+import (
+	"image"
+
+	"github.com/fogleman/pt/pt"
+)
+
+type Gopher struct {
+	mesh *pt.Mesh
+}
+
+func Load(meshPath string) (*Gopher, error) {
+	gopher := pt.GlossyMaterial(pt.Black, 1.2, pt.Radians(30))
+	mesh, err := pt.LoadOBJ(meshPath, gopher)
+	if err != nil {
+		return nil, err
+	}
+	return &Gopher{
+		mesh: mesh,
+	}, nil
+}
+
+func (g *Gopher) Render(iter int) image.Image {
+	scene := pt.Scene{}
+
+	// create materials
+	wall := pt.GlossyMaterial(pt.HexColor(0xFCFAE1), 1.5, pt.Radians(10))
+	light := pt.LightMaterial(pt.White, 80)
+
+	// add walls and lights
+	scene.Add(pt.NewCube(pt.V(-10, -1, -10), pt.V(-2, 10, 10), wall))
+	scene.Add(pt.NewCube(pt.V(-10, -1, -10), pt.V(10, 0, 10), wall))
+	scene.Add(pt.NewSphere(pt.V(4, 10, 1), 1, light))
+
+	g.mesh.Transform(pt.Rotate(pt.V(0, 1, 0), pt.Radians(-10)))
+	g.mesh.SmoothNormals()
+	g.mesh.FitInside(pt.Box{pt.V(-1, 0, -1), pt.V(1, 2, 1)}, pt.V(0.5, 0, 0.5))
+	scene.Add(g.mesh)
+
+	// position camera
+	camera := pt.LookAt(pt.V(4, 1, 0), pt.V(0, 0.9, 0), pt.V(0, 1, 0), 40)
+
+	// render the scene
+	sampler := pt.NewSampler(16, 16)
+	renderer := pt.NewRenderer(&scene, &camera, sampler, 1024, 1024)
+	renderer.Verbose = false
+
+	// perform iter iterations of rendering
+	for j := 0; j < iter; j++ {
+		renderer.Render()
+	}
+	return renderer.Buffer.Image(pt.ColorChannel)
+}