Welcome! We're excited that you're interested in contributing. Below are some basic guidelines.
Grafana Mimir follows a standard GitHub pull request workflow. If you're unfamiliar with this workflow, read the very helpful Understanding the GitHub flow guide from GitHub.
You are welcome to create draft PRs at any stage of readiness.
Doing so can be a helpful way of asking for assistance or to develop an idea.
When you open a PR as a draft, add a short description of what you’re still working on, what you are seeking assistance with, or both.
There is an automated GitHub action which closes PRs after 180 days of inactivity to keep the PR list clean. 30 days before closing, the GitHub action will add stale label to the PR. If you need more time, please remove the stale label.
Before a piece of work is finished:
- Organize it into one or more commits, and include a commit message for each that describes all of the changes that you made in that commit. It is more helpful to explain why more than what, which are available via
git diff. - Each commit should build towards the whole - don't leave in back-tracks and mistakes that you later corrected.
- Have unit and/or integration tests for new functionality or tests that would have caught the bug being fixed.
- Include a CHANGELOG message if users of Grafana Mimir need to hear about what you did.
- If you have made any changes to flags or config, run
make reference-help docand commit the changed files to update the config file documentation. - Follow the pull request template when creating PRs.
By default, new experimental features should be disabled and gated behind a per-tenant limit or configuration flag, so that they can be tested and introduced gradually. Document the feature in docs/sources/mimir/configure/about-versioning.md.
Please see the dedicated "Contributing to Grafana Mimir helm chart" page.
Grafana Mimir uses goimports tool (go get golang.org/x/tools/cmd/goimports to install) to format the Go files, and sort imports.
We use goimports with -local github.com/grafana/mimir parameter, to put Grafana Mimir internal imports into a separate group.
We try to keep imports sorted into three groups:
- imports from standard library
- imports of 3rd party packages and
- internal Grafana Mimir imports.
Goimports will fix the order, but will keep existing newlines between imports in the groups. We try to avoid extra newlines like that.
You're using an IDE you may find useful the following settings for the Grafana Mimir project:
Always run make format before creating commits, or configure your IDE to run goimports on save. This prevents formatting-only commits and keeps the git history clean.
When making changes to jsonnet/libsonnet files, always format before creating commits:
- Mixin files (
operations/mimir-mixin/): Runmake format-mixinthenmake build-mixinto render compiled YAML outputs - Other jsonnet files (
operations/mimir,operations/mimir-tests,development/): Runmake format-jsonnet-manifests
This prevents formatting-only commits and ensures your jsonnet changes compile correctly.
- Makefiles: Run
make format-makefilesfor any Makefile changes - Protobuf files: Run
make format-protobuffor.protofile changes - PromQL test files: Run
make format-promql-testsfor PromQL test changes
To build:
make
You can use make help to see the available targets.
(By default, the build runs in a Docker container, using an image built
with all the tools required. The source code is mounted from where you
run make into the build container as a Docker volume.
The mount options can be adjusted with CONTAINER_MOUNT_OPTIONS.)
To run the unit tests suite:
make test
To run the integration tests suite please see "How integration tests work".
If using macOS, make sure you have gnu-sed installed; otherwise, some make targets will not work properly.
Depending on how Docker is installed and configured and also the hardening applied to your workstation, using the Docker mount options might not work properly.
This is also true if you are using an alternative to Docker like Podman. In such case, you can use CONTAINER_MOUNT_OPTIONS to adjust the mount option.
Example:
make CONTAINER_MOUNT_OPTIONS=delegated
To compile Protobuf files (.proto) to Go code (.pb.go), run:
make protos
To easily run Grafana Mimir locally during development, use the docker-compose setup at development/<deployment-mode>/:
development/mimir-read-write-mode/compose-up.sh -dDoing so builds the local version of the code and starts the Mimir components that are part of the chosen deployment mode as a set of containers. Use the compose-down.sh script to tear down the containers when you no longer need them.
We uses Go modules to manage dependencies on external packages. This requires a working Go environment with version 1.11 or greater, git and bzr installed.
To add or update a new dependency, use the go get command:
# Pick the latest tagged release.
go get example.com/some/module/pkg
# Pick a specific version.
go get example.com/some/module/pkg@vX.Y.ZTidy up the go.mod and go.sum files:
go mod tidy
go mod vendor
git add go.mod go.sum vendor
git commitYou have to commit the changes to go.mod and go.sum before submitting the pull request.
Please see the dedicated "Design patterns and Code conventions" page.
For performance, buffers used for decompressing gRPC requests into are kept in a pool shared between requests.
Also, when unmarshaling those byte buffers into structured Go values, we construct strings that aren't actually references to immutable memory, but to those same shared buffers. The codebase refers to this trick as yoloString, and in some places (but not exhaustively), such strings are denoted as type unsafeMutableString, which is an alias of string.
This means that innocent-looking string values that outlive the window between taking a request buffer from the pool and releasing it back may actually refer to another request's buffer. Given Mimir is multi-tenant, it's likely to result in cross-tenant data leakage.
Take into account that copying strings around actually moves references around. Make sure strings that must outlive the handling window (e. g. for error messages) are deep-copied, e. g. with strings.Clone or strings.Builder. Note that, if those strings are within a slice, it is not enough to clone the slice with slices.Clone or similar, as only the references will be cloned, not the underlying bytes in memory. The same applies if there are inside a struct or array.
There are currently no guardrails around this, beyond tests.
Some (but maybe not all) specific critical areas are:
- In the distributor, those strings must not outlive the
PushFuncpassed toHandler.- The
PushFuncdecompresses an incoming request into a reused buffer, then unmarshals the buffer into aPreallocWriteRequestthat holdsunsafeMutableStrings (indirectly throughunsafeMutableLabel) into that same buffer, and then, before returning, returns the buffer to the pool to be reused. If any strings from thePreallocWriteRequestare still alive at that point, they will be referring to a buffer that could be reused for a new request at any point.
- The
The Grafana Mimir documentation and the Helm chart documentation for Mimir and GEM are compiled and published to https://grafana.com/docs/mimir/latest/ and https://grafana.com/docs/helm-charts/mimir-distributed/latest/. Run make docs to build and serve the documentation locally.
For more detail on style and organisation of the documentation, refer to the dedicated page "How to write documentation".
Note: if you attempt to view pages on GitHub, it's likely that you might find broken links or pages. That is expected and should not be addressed unless it is causing issues with the site that occur as part of the build.
Please see dedicated instructions for documentation authoring for more information on the Docs toolkit.
We document the common user-visible errors so it is easy for the user to search for how to address those errors when they see them.
To add a new error:
- Under
pkg/util/globalerror/user.go, create a new unique ID string as a constant. After your changes make it into a public release, do not change this string. - When returning the error, use one of the functions in
globalerrorto generate the message. If you return the same error from multiple places, create a new function to return that error so that its message string is defined in only one place. Then, add a simple test for that function to compare its actual output with the expected message which is defined as a hard-coded string. - Update the runbook in
docs/sources/mimir/manage/mimir-runbooks/_index.mdwith details about why the error happens, and if possible how to address it.
When appending to the changelog, the changes must be listed with a corresponding scope. A scope denotes the type of change that has occurred.
The ordering of entries in the changelog should be [CHANGE], [FEATURE], [ENHANCEMENT], [BUGFIX].
The CHANGE scope denotes a change that changes the expected behavior of the project while not adding new functionality or fixing an underling issue. This commonly occurs when renaming things to make them more consistent or to accommodate updated versions of vendored dependencies.
The FEATURE scope denotes a change that adds new functionality to the project/service.
The ENHANCEMENT scope denotes a change that improves upon the current functionality of the project/service. Generally, an enhancement is something that improves upon something that is already present. Either by making it simpler, more powerful, or more performant. For example:
- An optimization on a particular process in a service that makes it more performant
- Simpler syntax for setting a configuration value, like allowing
1minstead of 60 for a duration setting.
The BUGFIX scope denotes a change that fixes an issue with the project in question. A BUGFIX should align the behaviour of the service with the current expected behaviour of the service. If a BUGFIX introduces new unexpected behaviour to ameliorate the issue, a corresponding FEATURE or ENHANCEMENT scope should also be added to the changelog.
When adding an entry to the changelog, the first line of the entry must end with #<PR> where <PR> is the GitHub pull request ID. You get the next <PR> ID right before opening the PR by executing the script ./tools/github-next-pr-number.sh.