Tricium Analyzer Contribution

This doc is intended for readers who are interested in building and deploying a new Tricium Analyzer. A Tricium Analyzer can automatically produce conveniently timed warnings and suggestions for changes under review in your project, in the form of code review comments.

Analyzer recipes

Analyzers are run by triggering LUCI builders; each builder is configured to run some recipe. Tricium Analyzer recipes will call api.tricium.emit_results and the Tricium service will post comments to the CL when the build is done.

See the tricium recipe module.

Analyzer recipes can run third-party tools, or read files directly, or run any number of pre-made common legacy analyzers.

Legacy Analyzers

A legacy analyzer, also called a simple analyzer, takes some input (usually FILES) and produces Tricium RESULTS. Results of Analyzers can be visible as comments in code review. Such analyzers read input (files and metadata) from the -input flag value, and write output to “tricium/data/results.json”, relative to the -output flag value. Each program is packaged and deployed using CIPD.

Example Analyzers implemented in Go are located in tricium/functions/.

If you add a legacy analyzer, it must be run inside a recipe.

Data types

The input and output types that Tricium Analyzers can take are defined in data.proto; there are three main important types:

  • GIT_FILE_DETAILS: Just the information required to fetch the repository, but without any file contents. This doesn't include the actual contents of the file, but it does include the git repository URL and ref string as well as a list of files. Example.
  • FILES: The list of files and file metadata, along with the actual contents of the files. The file metadata includes the relative path, how the file was changed, and whether it is binary or text. Many simple Analyzers that depend on just the text of the files changed in the CL can use this type. This input type does not include repository URL or git ref. Example.
  • RESULTS: The result of an Analyzer, which is a list of comments to post, as well as (optionally) the platform that the results apply to. Results are generated Example in a unit test; an example output can be generated by running make test in one of the analyzer directories like [../functions/spellchecker].

All legacy analyzers take FILES input, and all recipe analyzers take GIT_FILE_DETAILS. All analyzers produce RESULTS.


Third-party language-specific “linter” programs exist for many languages; to adapt them for Tricium, we need to make a wrapper that will run the program and produce Tricium RESULTS, such as Pylint.

If third-party dependencies are required, they should be fetched and included in the CIPD package, but generally shouldn't be checked into source. The dependencies may be fetched using a set-up script that is run at package-build time. For example, the ESLint Analyzer uses a script called that is run before building and uploading a CIPD package.

Simple regex-based checks

Some checks may also just read the input files and check for certain text patterns. Examples include Copyright.


To build the Go Analyzers examples, you should first set up the infra Go environment; see The infra Go README.. After the environment is set up, running go build in the directory should produce a usable binary.

Local testing

Besides writing unit tests, you can also test run your Analyzer locally, because the Analyzer is just a program that reads from local files and writes to local files. You can make an example test input directory, for example tricium/functions/spacey/test/.

Then, you can build your program (if necessary) and run it. For Go programs, this may be done by running go build and then invoking the resulting binary with the arguments -input and -output.

“End-to-End” testing

Analyzers are run in recipes, and changes to recipes can be tested before committing using led. In order to test, you‘ll need (1) an example build of the analyzer builder that you’ll be changing and (2) an example change to run on.

You should be able to test by making a uncommitted change to an analyzer recipe, and then running something like:

led get-build <build-number> | \
    led edit-recipe-bundle | \
    led edit-cr-cl <example change patchset>` |
    led launch

See go/luci-how-to-led or led help for more information.

Packaging with CIPD

Make sure you have the CIPD client installed; see installation instructions. Add a cipd.yaml file to the root of your Analyzer's directory tree, e.g. hello/cipd.yaml:

package: infra/tricium/function/hello
install_mode: symlink
        - file: hello

Note that package should be infra/tricium/functions/ANALYZER, replacing CamelCase (e.g. ClangTidy) with words separated with dashes, e.g. (clang-tidy). The data section should list files to be included in the CIPD package; see cipd/client/cipd/local/pkgdef.go for details.

Deploying with CIPD

Run the CIPD command line tool to create and tag the new release. If the CIPD client complains about a lack of authentication, contact to get added to the “tricium-contributors” group.

Uploading a new version of an Analyzer usually has two steps: uploading a new instance and setting the ref “live” to that instance.

There is a convenience helper script which may be used to update Analyzers.

$ cipd create -pkg-def cipd.yaml
infra/tricium/function/hello:5b010cd78bc78252dda3e791cd6510c56111a990 was successfully registered
$ cipd set-ref infra/tricium/functions/hello -ref live -version 5b010cd78bc78252dda3e791cd6510c56111a990
[P26551 20:36:06.432 client.go:1004 I] cipd: setting ref of "infra/tricium/function/hello": "live" => "5b010cd78bc78252dda3e791cd6510c56111a990"

NB! If your Analyzer contains compiled binaries, make sure that the target platform for your built binaries are the same as the platform that will run the Analyzer; for example, if it will run on Windows, you must build for Windows. The place where the runtime platform is configured is in the Analyzer definition; see the example config below, which specifies “UBUNTU”.

About CIPD packages

CIPD packages are basically zip files with a manifest file. They have an instance ID which is a digest of the zip package, which may look like ZEBe-8SEx5Z3TQ_bby6Ok82WbLc71YPdLEAnGWjmHKsC.

CIPD package instances have a package name and version. Versions can be the instance ID, or a ref, such as “live”. It's conventional for Tricium Analyzers to use the “live” ref, which is set in the Analyzer definition in the config file, as in the example below.

Configure your Analyzer

To enable a legacy analyzer, you must change the relevant recipe.

To enable a new recipe analyzer or analyzer wrapper recipe, you must add an entry to the project config, which will look like this:

# Analyzer definition.
functions {
  type: ANALYZER
  name: "Wrapper"
  provides: RESULTS
  owner: ""
  monorail_component: "Infra>LUCI>BuildService>PreSubmit>Tricium"
  impls {
    runtime_platform: LINUX
    provides_for_platform: LINUX
    # The recipe determines the actual behavior, including what is run.
    recipe {
      project: "my-project"
      bucket: "try"
      builder: "tricium-analysis"
    deadline: 900
selections {
  function: "Wrapper"
  platform: LINUX  # Must match platform in definition.
repos {
  gerrit_project {
    host: ""
    project: "my/project"
    git_url: ""
service_account: ""

Staging and Release

Deploying to a limited environment:

  • In the project config for a test project connected to tricium-dev (e.g. playground/gerrit-tricium), add the Analyzer definition and selection to the project config. The Analyzer definition and selection can both be put in the project config file for the playground repository.
  • Upload an example CL that should have comments.
  • If everything is configured correctly, the Gerrit page should show that there was a run, and clicking on the run link should show you that your Analyzer ran. For help with debugging, if you have questions, reach out to


  • If the Analyzer is applicable to multiple projects, the definition can go in the tricium-prod service config (internal). A tricium-prod.cfg config file for your project must be created or updated to include a selection for your Analyzer. The example for chromium/src is at //infra/config/tricium-prod.cfg.

What makes a good Analyzer?

  • An Analyzer should not be too noisy; for example, you should aim for false positive rate of less than 10%. That is, messages should be “useful” at least 90% of the time. Categories of comments that have been found to be too noisy should be disabled or improved.
  • An Analyzer should be fast enough so that results can generally appear before the reviewer reviews the CL; for example, you should aim for results in under 10 minutes.
  • An Analyzer should produce clear and actionable comments.

A Tricium analyzer can take more time and include more checks than presubmit. It should find some results when run on the whole codebase, but not too many so that it's overwhelming.

How to release a new legacy Analyzer version

If you have an Analyzer that's already running and configured, and you want to release a new version, the following is a general process for release:

First, test locally; try both unit tests and a test-invocation on sample input. Note, you can run Go unit tests by running go test ./.... Make sure you run this on a machine that is binary-compatible with the production execution environment.

Optionally, you could try with a local devserver instance of Tricium (documentation pending; for now, Googlers can see the current doc at go/tricium-playbook). The advantage of testing it out with a devserver instance would be that you can change the configs without making any actual commits, by changing tricium/appengine/devcfg.

After you're ready, upload it as described above in the “Deploy CIPD Package” section, by running the cipd commands yourself or possibly by running my-analyzer from tricium/functions.

Then, again optionally, upload a new patchset or a new CL to confirm that it works as expected.