This project contains Golang code, but it does not have the file hierarchy of
regular Golang projects (this is due to the use of Bazel as a build system).
The cmd/gofuse
utility enables to re-create the file hierarchy expected by Go
tools:
# Make sure to build to have all compile-time generated files
cd <path-to-agi-source>
bazel build pkg
# Prepare a agi-gofuse directory **outside of the AGI checkout directory**
mkdir <path-outside-agi-source>/agi-gofuse
# Run gofuse with the previous directory as a target
bazel run //cmd/gofuse -- -dir <path-to-agi-gofuse>
# If you build with bazel build -c dbg pkg, the <path-to-bazelout> is `k8-dbg` on Linux.
bazel run //cmd/gofuse -- -dir <path-to-agi-gofuse> -bazelout <path-to-bazelout>
# Add agi-gofuse directory to your GOPATH environment variable.
# On Linux, with a bash shell, you can add the following to your ~/.bashrc file:
export GOPATH="${GOPATH:+${GOPATH}:}<path-to-agi-gofuse>"
# On other configurations, please search online how to add/edit environment variables.
If you encounter a symlink error on Windows like 'a required privilege is not held by the client', you have to use a command prompt with administrator privileges or enable Developer Mode as described here.
After adding the gofuse directory to your GOPATH, Go tools should work as expected. You can edit files under the newly populated gofuse directory. You should still compile under the original AGI checkout directory.
Despite its name, the gofuse command does NOT use FUSE (filesystem in userspace). It just creates directories and links to source files, including generated files. It is a good idea to re-run gofuse from time to time, to re-sync links to potential new files.
In terms of editor, VsCode has good Go support
thanks to its
Go extension.
With the GOPATH setup to gofuse and opening the <path-to-agi-gofuse>
directory,
as the root of your workspace, you should get some jump-to-definition and autocomplete
features working. Make sure to edit the files through their link found under the gofuse directory.
The recommended Golang debugger is
delve. You can start a debug build of
gapis or a client under this debugger. To build in debug mode, use the -c dbg
Bazel flag, e.g.:
bazel build -c dbg pkg
To debug gapis, you can do:
dlv exec ./bazel-bin/pkg/gapis -- -enable-local-files -persist -rpc localhost:8888
You can then use dlv commands to add breakpoints, and actually start GAPIS, e.g.:
(dlv) break gapis/server/server.go:228
(dlv) continue # this actually starts gapis
See delve documentation on how to specify a breakpoint location, there are more convenient alternatives than
path/to/file:line
Once gapis is started, you can run a client to interact with it and hit somes breakpoints:
# in another terminal
./bazel-bin/pkg/gapit <verb> -gapis-port 8888 <verb args>
If you want to debug a client like gapit, just start it under dlv:
dlv exec ./bazel-bin/pkg/gapit <verb> <verb args>
To automate a delve startup sequence, you can edit a script of delve commands to be executed when delve starts. The script looks like:
# This is a comment.
break gapis/server/server.go:228
# add a second breakpoint, with a condition for it to trigger
break gapis/foo/bar.go:123
condition 2 some_variable == 42
# launch program
continue
And you can pass this script to delve using the --init
flag:
dlv exec --init my-delve-init-script.txt <program to debug...>
If you want to interact with the debugger via your editor or IDE, be aware that delve will think file paths start from the AGI top directory, and not your root directory. This is very likely due to Bazel compilation. You may have to find workarounds if you call delve from an editor/IDE which consider the file paths to start from another directory, typically your root directory. There may be a way to adjust using GOPATH to tell to your IDE a possible root for filename lookups.
See the workaround for VSCode below, any help to fix it for other IDEs is very welcome!
Follow these steps to use the delve debugger for Go with VSCode to debug gapis
.
-
Make sure to complete the Golang setup above for AGI.
-
Settings file: There are two settings file(
settings.json
) that can be written.-
Global one that applies to all projects that can be opened with
Ctrl + Shift + P
andPreferences: Open Settings (JSON)
. Add this line to ensure that you have a stable tools directory:"go.toolsGopath": "<path-to-go-plugin-tools-folder>",
-
Local one is under
.vscode
folder in your project folder. Create one if it does not already exist and add this line to your local settings to be able to search source code in AGI:"go.gopath": "<path-to-agi-gofuse>"
,
-
-
Launch file: Create a
launch.json
file under the workspace directory withCtrl + Shift + P
andDebug: Open launch.json
-
Paste the following as one of the launch configurations. This will ensure that there is a launch configuration for attaching to Delve.
{
...
"configurations": [
...,
{
"name": "Attach to Delve",
"type": "go",
"request": "attach",
"mode": "remote",
"apiVersion": 2,
"remotePath": "",
"cwd": "`<path-to-agi-gofuse>`",
"dlvLoadConfig": {
"followPointers": true,
"maxVariableRecurse": 1,
"maxStringLen": 120,
"maxArrayValues": 120,
"maxStructFields": -1
},
"host": <host>,
"port": <port>,
},
],
...
}
As an example, <host>
could be 127.0.0.1
and <port>
could be 1234
.
- Start delve in headless mode in the AGI root folder.
dlv exec --headless --listen=<host>:<port> --api-version 2 ./bazel-bin/pkg/gapis -- <gapis-arguments>
The command below will allow using port 1234
(or any other preferred port) to connect to delve from VSCode.
dlv exec --headless --listen=127.0.0.1:1234 --api-version 2 ./bazel-bin/pkg/gapis -- -persist -rpc localhost:8888
-
Start debugging with
Debug->Start Debugging
(on Linux withF5
) and make sureAttach to Delve
is selected as the launch configuration. -
Now VSCode can interact with Delve and can be used for debugging
gapis
in VSCode UI instead of the command line. Enjoy your debugging :)
This allows you to put breakpoint at any line in AGI Go source code regardless if they are handwritten, generated or in Go Standard Library. For the generated file, you can put the breakpoints under the bazel-bin/
or bazel-out/
folder and the debugger will still find it under the fuse directory during debugging and open it. The only downside is you will have two versions of the same file but this is the only working workaround until Go Plugin supports bazel-generated files.
You can use the built-in logging functions to place debug prints.
In Golang:
import (
// ...
"github.com/google/gapid/core/log"
)
// ...
log.E(ctx, "Here debug print, myVar: %v", myVar)
In C++:
#include "core/cc/log.h"
// ...
GAPID_ERROR("Here debug print, myStr: %s", myStr)
The usual logging levels are available, listed for instance with gapit -fullhelp
:
$ ./bazel-bin/pkg/gapit -fullhelp
...
-log-level value
The severity to enable logs at [one of: "Verbose", "Debug", "Info", "Warning", "Error", "Fatal"] (default Info)
The Error level is recommended when adding debug print, to make sure it is not filtered away.
The server has instrumentation to output profiling information:
$ ./agi/gapis --fullhelp
[...]
-profile-pprof
enable pprof profiling
-profile-trace string
write a trace to file
[...]
The -profile-trace
option generates a trace that can be open in Chrome via
chrome://tracing
, or using Perfetto web UI.
The gapit
and agi
(UI starter) clients can pass these arguments to gapis via
-gapis-args
, e.g.:
# GAPIT
./agi/gapit <verb> -gapis-args '-profile-trace my-profile-trace.out' <verb arguments>
# GAPIC
./agi/agi -gapis-args '-profile-trace my-profile-trace.out' foobar.gfxtrace
To profile the interceptor GAPII and the replayer GAPIR on Android devices, you can resort to classic profiling solutions. Check the Android system tracing overview doc. Beside systrace, perfetto and the Android studio profile, also note that Inferno makes it easy to get flamegraphs.
To profile the replayer using AGI itself, you can export a given replay into a
standalone APK (using gapit export_replay -apk
), and profile that replay-APK
using AGI.
Unit testing is achieved with separate frameworks depending on the programming language, but they are all accessible with Bazel.
# List all tests
bazel query 'tests(//...)'
# List all Go tests
bazel query 'kind(go_.*, tests(//...))'
# List all C++ tests
bazel query 'kind(cc_.*, tests(//...))'
# Run all the tests
bazel test tests
# Run a given test
bazel test //core/log:go_default_test
Following the regular Go test setup,
tests are written as func TestXXX(t *testing.T)
functions in *_test.go
files.
Adding a Go test file into Bazel is done by invoking Gazelle. The
kokoro/presubmit/presubmit.sh
script does that for you: if you create or
remove *_test.go
files, running the presubmit script automatically edit the
BUILD.bazel files to reflect your changes.
A few useful homemade packages:
-
github.com/google/gapid/core/assert
defines an assertion framework. -
github.com/google/gapid/core/log
lets you create contexts for tests withctx := log.Testing(t)
TODO
TODO
A large amount of code is generated at compile time. Code is typically generated
via APIC (API Compiler, cmd/apic/
). APIC takes as input .api
and .tmpl
files, and it generates code in another language.
+----------+
<.api files> --->| |
| APIC |---> <generated files>
<.tmpl files> --->| |
+----------+
.api
files are GAPIL ("graphics API language") sources. GAPIL is a
domain-specific language to specify a graphics API, it is documented in
gapil/README.md
. AGI currently supports only Vulkan, but its ancestor GAPID
was designed to support arbitrary graphics APIs. Vulkan is described by API files
located under gapis/api/vulkan/
, the top-level file is
gapis/api/vulkan/vulkan.api
.
.tmpl
files are template files. The templating language is documented in
gapis/api/templates/README.md
. There are various template files in the project
to generate code for the interceptor, server, replayer, Vulkan layers, etc.
APIC is a GAPIL compiler, its entry-point is defined in cmd/apic/
and the
actual compiler logic is defined in gapil/
. In a nutshell, APIC parses the
.api
files to gather information about the graphics API, and then instantiates
the templates in the .tmpl
files to generate code.
The generated code is not checked under version control. It is generated at
compilation time by Bazel rules calling APIC, resulting in files that can be
seen under e.g. bazel-bin/
.
For instance, generated C++ code for the interceptor can be found in:
user@machine:~/work/agi$ find -L bazel-bin -name '*_spy_*.cpp'
bazel-bin/gapii/cc/vulkan_spy_0.cpp
bazel-bin/gapii/cc/vulkan_spy_subroutines_1.cpp
bazel-bin/gapii/cc/vulkan_spy_subroutines_0.cpp
bazel-bin/gapii/cc/vulkan_spy_2.cpp
bazel-bin/gapii/cc/vulkan_spy_3.cpp
bazel-bin/gapii/cc/vulkan_spy_helpers.cpp
bazel-bin/gapii/cc/vulkan_spy_1.cpp
To get a unified file tree view, interleaving source files with generated files,
you can use the cmd/gofuse
setup. cmd/gofuse
links the generated source
files in the same directories as their siblings in the same packages/namespaces.