This is an automated email from the ASF dual-hosted git repository.
asorokoumov pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hunter.git
The following commit(s) were added to refs/heads/master by this push:
new 251884d Introduce docs and reproducible examples (#28)
251884d is described below
commit 251884d95b1121348eb8a38b494db37a4f5686ae
Author: Alex Sorokoumov <[email protected]>
AuthorDate: Mon Jan 27 11:39:29 2025 -0800
Introduce docs and reproducible examples (#28)
* Add PostgreSQL example/docs
1. Add a reproducible docker-compose.
2. Move the docs from README to a separate docs page referencing the
reproducible example.
* Set content-type to application/json when creating an annotation in
Grafana
Without this change, current Grafana version responds with 400 Bad Request
* Move CONTRIBUTING.md to docs/
* Move BigQuery to docs
* Add example and docs for csv
* Add examples and docs for Graphite/Grafana
* Move installation instructions into a separate docs page
* Split Usage into individual docs pages
* Remove PostgreSQL instructions from README.md
* Add Getting Started
* Remove trailing spaces in README
* Pin poetry version to the same version we use in ci-tools, 1.1.13
The newest version, 2.0.1, fails Docker build. Since the goal of this PR
is to introduce docs with reproducible examples, I am not going to
upgrade Poetry version _here_, despite it being really, really old.
* Add table of contents
---
Dockerfile | 2 +-
README.md | 412 +--------------------
docs/BASICS.md | 177 +++++++++
examples/bigquery/README.md => docs/BIG_QUERY.md | 6 +-
CONTRIBUTING.md => docs/CONTRIBUTING.md | 0
docs/CSV.md | 45 +++
docs/GETTING_STARTED.md | 129 +++++++
docs/GRAFANA.md | 65 ++++
docs/GRAPHITE.md | 106 ++++++
docs/INSTALL.md | 19 +
docs/POSTGRESQL.md | 119 ++++++
docs/README.md | 16 +
examples/csv/data/local_sample.csv | 11 +
examples/csv/docker-compose.yaml | 12 +
examples/csv/hunter.yaml | 10 +
examples/graphite/datagen/datagen.sh | 72 ++++
examples/graphite/docker-compose.yaml | 62 ++++
.../graphite/grafana/dashboards/benchmarks.json | 246 ++++++++++++
.../graphite/grafana/dashboards/dashboards.yaml | 9 +
.../graphite/grafana/datasources/graphite.yaml | 12 +
examples/graphite/hunter.yaml | 31 ++
examples/postgresql/docker-compose.yaml | 38 ++
examples/{psql => postgresql}/hunter.yaml | 26 +-
examples/postgresql/init-db/schema.sql | 85 +++++
examples/psql/README.md | 22 --
examples/psql/schema.sql | 48 ---
hunter/grafana.py | 2 +-
27 files changed, 1313 insertions(+), 469 deletions(-)
diff --git a/Dockerfile b/Dockerfile
index b8ab8b0..67f2e39 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -25,7 +25,7 @@ RUN apt-get update --assume-yes && \
&& rm -rf /var/lib/apt/lists/*
# Get poetry package
-RUN curl -sSL https://install.python-poetry.org | python3 -
+RUN curl -sSL https://install.python-poetry.org | python3 - --version 1.1.13
# Adding poetry to PATH
ENV PATH="/root/.local/bin/:$PATH"
diff --git a/README.md b/README.md
index dbd99ee..bfa3db8 100644
--- a/README.md
+++ b/README.md
@@ -4,409 +4,35 @@ Hunter – Hunts Performance Regressions
_This is an unsupported open source project created by DataStax employees._
-Hunter performs statistical analysis of performance test results stored
-in CSV files or Graphite database. It finds change-points and notifies about
-possible performance regressions.
-
-A typical use-case of hunter is as follows:
+Hunter performs statistical analysis of performance test results stored
+in CSV files, PostgreSQL, BigQuery, or Graphite database. It finds
change-points and notifies about
+possible performance regressions.
+
+A typical use-case of hunter is as follows:
- A set of performance tests is scheduled repeatedly.
-- The resulting metrics of the test runs are stored in a time series database
(Graphite)
- or appended to CSV files.
-- Hunter is launched by a Jenkins/Cron job (or an operator) to analyze the
recorded
+- The resulting metrics of the test runs are stored in a time series database
(Graphite)
+ or appended to CSV files.
+- Hunter is launched by a Jenkins/Cron job (or an operator) to analyze the
recorded
metrics regularly.
- Hunter notifies about significant changes in recorded metrics by outputting
text reports or
sending Slack notifications.
-
-Hunter is capable of finding even small, but systematic shifts in metric
values,
-despite noise in data.
-It adapts automatically to the level of noise in data and tries not to notify
about changes that
-can happen by random. Unlike in threshold-based performance monitoring
systems,
-there is no need to setup fixed warning threshold levels manually for each
recorded metric.
-The level of accepted probability of false-positives, as well as the
-minimal accepted magnitude of changes are tunable. Hunter is also capable of
comparing
-the level of performance recorded in two different periods of time – which is
useful for
-e.g. validating the performance of the release candidate vs the previous
release of your product.
-
-This is still work-in-progress, unstable code.
-Features may be missing.
-Usability may be unsatisfactory.
-Documentation may be incomplete.
-Backward compatibility may be broken any time.
-
-See [CONTRIBUTING.md](CONTRIBUTING.md) for development instructions.
-
-## Installation
-
-Hunter requires Python 3.8. If you don't have python 3.8,
-use pyenv to install it.
-
-Use pipx to install hunter:
-
-```
-pipx install git+ssh://[email protected]/datastax-labs/hunter
-```
-
-## Setup
-Copy the main configuration file `resources/hunter.yaml` to
`~/.hunter/hunter.yaml` and adjust
-Graphite and Grafana addresses and credentials.
-
-Alternatively, it is possible to leave
-the config file as is, and provide credentials in the environment
-by setting appropriate environment variables.
-Environment variables are interpolated before interpreting the configuration
file.
-
-### Defining tests
-All test configurations are defined in the main configuration file.
-Hunter supports publishing results to a CSV file,
[Graphite](https://graphiteapp.org/), and
[PostgreSQL](https://www.postgresql.org/).
-
-Tests are defined in the `tests` section.
-
-#### Importing results from CSV
-The following definition will import results of the test from a local CSV
file:
-
-```yaml
-tests:
- local.sample:
- type: csv
- file: tests/resources/sample.csv
- time_column: time
- metrics: [metric1, metric2]
- attributes: [commit]
- csv_options:
- delimiter: ","
- quote_char: "'"
-```
-
-The `time_column` property points to the name of the column storing the
timestamp
-of each test-run. The data points will be ordered by that column.
-
-The `metrics` property selects the columns tha hold the values to be analyzed.
These values must
-be numbers convertible to floats. The `metrics` property can be not only a
simple list of column
-names, but it can also be a dictionary configuring other properties of each
metric,
-the column name or direction:
-
-```yaml
-metrics:
- resp_time_p99:
- direction: -1
- column: p99
-```
-
-Direction can be 1 or -1. If direction is set to 1, this means that the higher
the metric, the
-better the performance is. If it is set to -1, higher values mean worse
performance.
-
-The `attributes` property describes any other columns that should be attached
to the final
-report. Special attribute `version` and `commit` can be used to query for a
given time-range.
-
-
-#### Importing results from Graphite
-
-To import data from Graphite, the test configuration must inform Hunter how the
-data are published in your history server. This is done by specifying the
Graphite path prefix
-common for all the test's metrics and suffixes for each of the metrics
recorded by the test run.
-
-```yaml
-tests:
- my-product.test:
- type: graphite
- tags: [perf-test, daily, my-product]
- prefix: performance-tests.daily.my-product
- metrics:
- throughput:
- suffix: client.throughput
- response-time:
- suffix: client.p50
- direction: -1 # lower is better
- cpu-load:
- suffix: server.cpu
- direction: -1 # lower is better
-```
-
-The optional `tags` property contains the tags that are used to query for
Graphite events that store
-additional test run metadata such as run identifier, commit, branch and
product version information.
-
-The following command will post an event with the test run metadata:
-```shell
-$ curl -X POST "http://graphite_address/events/" \
- -d '{
- "what": "Performance Test",
- "tags": ["perf-test", "daily", "my-product"],
- "when": 1537884100,
- "data": {"commit": "fe6583ab", "branch": "new-feature", "version":
"0.0.1"}
- }'
-```
-
-Posting those events is not mandatory, but when they are available, Hunter is
able to
-filter data by commit or version using `--since-commit` or `--since-version`
selectors.
-
-#### Importing results from PostgreSQL
-
-To import data from PostgreSQL, Hunter configuration must contain the database
connection details:
-
-```yaml
-# External systems connectors configuration:
-postgres:
- hostname: ...
- port: ...
- username: ...
- password: ...
- database: ...
-```
-
-Test configurations must contain a query to select experiment data, a time
column, and a list of columns to analyze:
-
-```yaml
-tests:
- aggregate_mem:
- type: postgres
- time_column: commit_ts
- attributes: [experiment_id, config_id, commit]
- metrics:
- process_cumulative_rate_mean:
- direction: 1
- scale: 1
- process_cumulative_rate_stderr:
- direction: -1
- scale: 1
- process_cumulative_rate_diff:
- direction: -1
- scale: 1
- query: |
- SELECT e.commit,
- e.commit_ts,
- r.process_cumulative_rate_mean,
- r.process_cumulative_rate_stderr,
- r.process_cumulative_rate_diff,
- r.experiment_id,
- r.config_id
- FROM results r
- INNER JOIN configs c ON r.config_id = c.id
- INNER JOIN experiments e ON r.experiment_id = e.id
- WHERE e.exclude_from_analysis = false AND
- e.branch = 'trunk' AND
- e.username = 'ci' AND
- c.store = 'MEM' AND
- c.cache = true AND
- c.benchmark = 'aggregate' AND
- c.instance_type = 'ec2i3.large'
- ORDER BY e.commit_ts ASC;
-```
-For more details, see the examples in [examples/psql](examples/psql).
+Hunter is capable of finding even small, but persistent shifts in metric
values,
+despite noise in data. It adapts automatically to the level of noise in data
and
+tries to notify only about persistent, statistically significant changes, be
it in the system
+under test or in the environment.
-#### Avoiding test definition duplication
-You may find that your test definitions are very similar to each other,
-e.g. they all have the same metrics. Instead of copy-pasting the definitions
-you can use templating capability built-in hunter to define the common bits of
configs separately.
+Unlike in threshold-based performance monitoring systems, there is no need to
setup fixed warning
+threshold levels manually for each recorded metric. The level of accepted
probability of
+false-positives, as well as the minimal accepted magnitude of changes are
tunable. Hunter is
+also capable of comparingthe level of performance recorded in two different
periods of time – which
+is useful for e.g. validating the performance of the release candidate vs the
previous release of your product.
-First, extract the common pieces to the `templates` section:
-```yaml
-templates:
- common-metrics:
- throughput:
- suffix: client.throughput
- response-time:
- suffix: client.p50
- direction: -1 # lower is better
- cpu-load:
- suffix: server.cpu
- direction: -1 # lower is better
-```
-
-Next you can recall a template in the `inherit` property of the test:
-
-```yaml
-my-product.test-1:
- type: graphite
- tags: [perf-test, daily, my-product, test-1]
- prefix: performance-tests.daily.my-product.test-1
- inherit: common-metrics
-my-product.test-2:
- type: graphite
- tags: [perf-test, daily, my-product, test-2]
- prefix: performance-tests.daily.my-product.test-2
- inherit: common-metrics
-```
-
-You can inherit more than one template.
-
-## Usage
-### Listing Available Tests
-
-```
-hunter list-groups
-hunter list-tests [group name]
-```
-
-### Listing Available Metrics for Tests
-
-To list all available metrics defined for the test:
-```
-hunter list-metrics <test>
-```
-
-### Finding Change Points
-```
-hunter analyze <test>...
-hunter analyze <group>...
-```
-
-This command prints interesting results of all
-runs of the test and a list of change-points.
-A change-point is a moment when a metric value starts to differ significantly
-from the values of the earlier runs and when the difference
-is consistent enough that it is unlikely to happen by chance.
-Hunter calculates the probability (P-value) that the change point was caused
-by chance - the closer to zero, the more "sure" it is about the regression or
-performance improvement. The smaller is the actual magnitude of the change,
-the more data points are needed to confirm the change, therefore Hunter may
-not notice the regression after the first run that regressed.
-
-The `analyze` command accepts multiple tests or test groups.
-The results are simply concatenated.
-
-#### Example
-
-```
-$ hunter analyze local.sample
-INFO: Computing change points for test sample.csv...
-sample:
-time metric1 metric2
-------------------------- --------- ---------
-2021-01-01 02:00:00 +0000 154023 10.43
-2021-01-02 02:00:00 +0000 138455 10.23
-2021-01-03 02:00:00 +0000 143112 10.29
-2021-01-04 02:00:00 +0000 149190 10.91
-2021-01-05 02:00:00 +0000 132098 10.34
-2021-01-06 02:00:00 +0000 151344 10.69
- ·········
- -12.9%
- ·········
-2021-01-07 02:00:00 +0000 155145 9.23
-2021-01-08 02:00:00 +0000 148889 9.11
-2021-01-09 02:00:00 +0000 149466 9.13
-2021-01-10 02:00:00 +0000 148209 9.03
-```
-
-### Annotating Change Points in Grafana
-Change points found by `analyze` can be exported
-as Grafana annotations using the `--update-grafana` flag:
-
-```
-$ hunter analyze <test or group> --update-grafana
-```
-
-The annotations generated by Hunter get the following tags:
-- `hunter`
-- `change-point`
-- `test:<test name>`
-- `metric:<metric name>`
-- tags configured in the `tags` property of the test
-- tags configured in the `annotate` property of the test
-- tags configured in the `annotate` property of the metric
-
-Additionally, the `annotate` property supports variable tags:
-- `%{TEST_NAME}` - name of the test
-- `%{METRIC_NAME}` - name of the metric
-- `%{GRAPHITE_PATH}` - resolves to the path to the data in Graphite
-- `%{GRAPHITE_PATH_COMPONENTS}` - splits the path of the data in Graphite into
separate components
- and each path component is exported as a separate tag
-- `%{GRAPHITE_PREFIX}` - resolves to the prefix of the path to the data in
Graphite
- (the part of the path up to the metric suffix)
-- `%{GRAPHITE_PREFIX_COMPONENTS}` - similar as `%{GRAPHITE_PATH_COMPONENTS}`
but splits the prefix
-of the path instead of the path
-
-
-### Validating Performance of the Main Branch
-Often we want to know if the most recent product version
-performs at least as well as one of the previous releases. It is hard to tell
that by looking
-at the individual change points. Therefore, Hunter provides a separate command
for comparing
-the current performance with the baseline performance level denoted by
`--since-XXX` selector:
-
-```
-$ hunter regressions <test or group>
-$ hunter regressions <test or group> --since <date>
-$ hunter regressions <test or group> --since-version <version>
-$ hunter regressions <test or group> --since-commit <commit>
-```
-
-If there are no regressions found in any of the tests,
-Hunter prints `No regressions found` message.
-Otherwise, it gives a list of tests with metrics and
-magnitude of regressions.
-
-In this test, Hunter compares performance level around the baseline ("since")
point with
-the performance level at the end of the time series. If the baseline point is
not specified, the
-beginning of the time series is assumed. The "performance level at the point"
-is computed from all the data points between two nearest change points.
-Then two such selected fragments are compared using Student's T-test for
statistical differences.
-
-#### Examples
-```
-$ hunter regressions local.sample
-INFO: Computing change points for test local.sample...
-local.sample:
- metric2 : 10.5 --> 9.12 ( -12.9%)
-Regressions in 1 test found
-
-$ hunter regressions local.sample --since '2021-01-07 02:00:00'
-INFO: Computing change points for test local.sample...
-local.sample: OK
-No regressions found!
-```
-
-### Validating Performance of a Feature Branch
-The `hunter regressions` command can work with feature branches.
-
-First you need to tell Hunter how to fetch the data of the tests run against a
feature branch.
-The `prefix` property of the graphite test definition accepts `%{BRANCH}`
variable,
-which is substituted at the data import time by the branch name passed to
`--branch`
-command argument. Alternatively, if the prefix for the main branch of your
product is different
-from the prefix used for feature branches, you can define an additional
`branch_prefix` property.
-
-```yaml
-my-product.test-1:
- type: graphite
- tags: [perf-test, daily, my-product, test-1]
- prefix: performance-tests.daily.%{BRANCH}.my-product.test-1
- inherit: common-metrics
-
-my-product.test-2:
- type: graphite
- tags: [perf-test, daily, my-product, test-2]
- prefix: performance-tests.daily.master.my-product.test-2
- branch_prefix: performance-tests.feature.%{BRANCH}.my-product.test-2
- inherit: common-metrics
-```
-
-Now you can verify if correct data are imported by running
-`hunter analyze <test> --branch <branch>`.
-
-The `--branch` argument also works with `hunter regressions`. In this case a
comparison will be made
-between the tail of the specified branch and the tail of the main branch (or a
point of the
-main branch specified by one of the `--since` selectors).
-
-```
-$ hunter regressions <test or group> --branch <branch>
-$ hunter regressions <test or group> --branch <branch> --since <date>
-$ hunter regressions <test or group> --branch <branch> --since-version
<version>
-$ hunter regressions <test or group> --branch <branch> --since-commit <commit>
-```
-
-Sometimes when working on a feature branch, you may run the tests multiple
times,
-creating more than one data point. To ignore the previous test results, and
compare
-only the last few points on the branch with the tail of the main branch,
-use the `--last <n>` selector. E.g. to check regressions on the last run of
the tests
-on the feature branch:
+Backward compatibility may be broken any time.
-```
-$ hunter regressions <test or group> --branch <branch> --last 1
-```
+See the documentation in [docs/README.md](docs/README.md).
-Please beware that performance validation based on a single data point is
quite weak
-and Hunter might miss a regression if the point is not too much different from
-the baseline.
## License
diff --git a/docs/BASICS.md b/docs/BASICS.md
new file mode 100644
index 0000000..b2461f4
--- /dev/null
+++ b/docs/BASICS.md
@@ -0,0 +1,177 @@
+# Basics
+
+## Listing Available Tests
+
+```
+hunter list-groups
+```
+
+Lists all available test groups - high-level categories of tests.
+
+```
+hunter list-tests [group name]
+```
+
+Lists all tests or the tests within a given group, if the group name is
provided.
+
+## Listing Available Metrics for Tests
+
+To list all available metrics defined for the test:
+
+```
+hunter list-metrics <test>
+```
+
+### Example
+
+> [!TIP]
+> See [hunter.yaml](../examples/csv/hunter.yaml) for the full example
configuration.
+
+```
+$ hunter list-metrics local.sample
+metric1
+metric2
+```
+
+## Finding Change Points
+
+```
+hunter analyze <test>...
+hunter analyze <group>...
+```
+
+This command prints interesting results of all
+runs of the test and a list of change-points.
+A change-point is a moment when a metric value starts to differ significantly
+from the values of the earlier runs and when the difference
+is persistent and statistically significant that it is unlikely to happen by
chance.
+Hunter calculates the probability (P-value) that the change point was caused
+by chance - the closer to zero, the more "sure" it is about the regression or
+performance improvement. The smaller is the actual magnitude of the change,
+the more data points are needed to confirm the change, therefore Hunter may
+not notice the regression immediately after the first run that regressed.
+However, it will eventually identify the specific commit that caused the
regression,
+as it analyzes the history of changes rather than just the HEAD of a branch.
+
+The `analyze` command accepts multiple tests or test groups.
+The results are simply concatenated.
+
+### Example
+
+> [!TIP]
+> See [hunter.yaml](../examples/csv/hunter.yaml) for the full
+> example configuration and
[local_samples.csv](../examples/csv/data/local_samples)
+> for the data.
+
+```
+$ hunter analyze local.sample --since=2024-01-01
+INFO: Computing change points for test sample.csv...
+sample:
+time metric1 metric2
+------------------------- --------- ---------
+2021-01-01 02:00:00 +0000 154023 10.43
+2021-01-02 02:00:00 +0000 138455 10.23
+2021-01-03 02:00:00 +0000 143112 10.29
+2021-01-04 02:00:00 +0000 149190 10.91
+2021-01-05 02:00:00 +0000 132098 10.34
+2021-01-06 02:00:00 +0000 151344 10.69
+ ·········
+ -12.9%
+ ·········
+2021-01-07 02:00:00 +0000 155145 9.23
+2021-01-08 02:00:00 +0000 148889 9.11
+2021-01-09 02:00:00 +0000 149466 9.13
+2021-01-10 02:00:00 +0000 148209 9.03
+```
+
+## Avoiding test definition duplication
+
+You may find that your test definitions are very similar to each other, e.g.
they all have the same metrics. Instead
+of copy-pasting the definitions you can use templating capability built-in
hunter to define the common bits of configs
+separately.
+
+First, extract the common pieces to the `templates` section:
+```yaml
+templates:
+ common-metrics:
+ throughput:
+ suffix: client.throughput
+ response-time:
+ suffix: client.p50
+ direction: -1 # lower is better
+ cpu-load:
+ suffix: server.cpu
+ direction: -1 # lower is better
+```
+
+Next you can recall a template in the `inherit` property of the test:
+
+```yaml
+my-product.test-1:
+ type: graphite
+ tags: [perf-test, daily, my-product, test-1]
+ prefix: performance-tests.daily.my-product.test-1
+ inherit: common-metrics
+my-product.test-2:
+ type: graphite
+ tags: [perf-test, daily, my-product, test-2]
+ prefix: performance-tests.daily.my-product.test-2
+ inherit: common-metrics
+```
+
+You can inherit more than one template.
+
+## Validating Performance of a Feature Branch
+
+The `hunter regressions` command can work with feature branches.
+
+First you need to tell Hunter how to fetch the data of the tests run against a
feature branch.
+The `prefix` property of the graphite test definition accepts `%{BRANCH}`
variable,
+which is substituted at the data import time by the branch name passed to
`--branch`
+command argument. Alternatively, if the prefix for the main branch of your
product is different
+from the prefix used for feature branches, you can define an additional
`branch_prefix` property.
+
+```yaml
+my-product.test-1:
+ type: graphite
+ tags: [perf-test, daily, my-product, test-1]
+ prefix: performance-tests.daily.%{BRANCH}.my-product.test-1
+ inherit: common-metrics
+
+my-product.test-2:
+ type: graphite
+ tags: [perf-test, daily, my-product, test-2]
+ prefix: performance-tests.daily.master.my-product.test-2
+ branch_prefix: performance-tests.feature.%{BRANCH}.my-product.test-2
+ inherit: common-metrics
+```
+
+Now you can verify if correct data are imported by running
+`hunter analyze <test> --branch <branch>`.
+
+The `--branch` argument also works with `hunter regressions`. In this case a
comparison will be made
+between the tail of the specified branch and the tail of the main branch (or a
point of the
+main branch specified by one of the `--since` selectors).
+
+```
+$ hunter regressions <test or group> --branch <branch>
+$ hunter regressions <test or group> --branch <branch> --since <date>
+$ hunter regressions <test or group> --branch <branch> --since-version
<version>
+$ hunter regressions <test or group> --branch <branch> --since-commit <commit>
+```
+
+Sometimes when working on a feature branch, you may run the tests multiple
times,
+creating more than one data point. To ignore the previous test results, and
compare
+only the last few points on the branch with the tail of the main branch,
+use the `--last <n>` selector. E.g. to check regressions on the last run of
the tests
+on the feature branch:
+
+```
+$ hunter regressions <test or group> --branch <branch> --last 1
+```
+
+Please beware that performance validation based on a single data point is
quite weak
+and Hunter might miss a regression if the point is not too much different from
+the baseline. However, accuracy improves as more data points accumulate, and
it is
+a normal way of using Hunter to just merge a feature and then revert if it is
+flagged later.
diff --git a/examples/bigquery/README.md b/docs/BIG_QUERY.md
similarity index 68%
rename from examples/bigquery/README.md
rename to docs/BIG_QUERY.md
index 035d088..e4f9405 100644
--- a/examples/bigquery/README.md
+++ b/docs/BIG_QUERY.md
@@ -1,6 +1,8 @@
+# BigQuery
+
## Schema
-See [schema.sql](schema.sql) for the example schema.
+See [schema.sql](../examples/bigquery/schema.sql) for the example schema.
## Usage
@@ -13,7 +15,7 @@ export BIGQUERY_VAULT_SECRET=...
```
or in `hunter.yaml`.
-Also configure the credentials. See
[config_credentials.sh](config_credentials.sh) for an example.
+Also configure the credentials. See
[config_credentials.sh](../examples/bigquery/config_credentials.sh) for an
example.
The following command shows results for a single test `aggregate_mem` and
updates the database with newly found change points:
diff --git a/CONTRIBUTING.md b/docs/CONTRIBUTING.md
similarity index 100%
rename from CONTRIBUTING.md
rename to docs/CONTRIBUTING.md
diff --git a/docs/CSV.md b/docs/CSV.md
new file mode 100644
index 0000000..f342f7f
--- /dev/null
+++ b/docs/CSV.md
@@ -0,0 +1,45 @@
+# Importing results from CSV
+
+> [!TIP]
+> See [hunter.yaml](../examples/csv/hunter.yaml) for the full example
configuration.
+
+## Tests
+
+```yaml
+tests:
+ local.sample:
+ type: csv
+ file: tests/local_sample.csv
+ time_column: time
+ attributes: [commit]
+ metrics: [metric1, metric2]
+ csv_options:
+ delimiter: ','
+ quotechar: "'"
+```
+
+## Example
+
+```bash
+docker-compose -f examples/csv/docker-compose.yaml run --build hunter
bin/hunter analyze local.sample
+```
+
+Expected output:
+
+```bash
+time commit metric1 metric2
+------------------------- -------- --------- ---------
+2024-01-01 02:00:00 +0000 aaa0 154023 10.43
+2024-01-02 02:00:00 +0000 aaa1 138455 10.23
+2024-01-03 02:00:00 +0000 aaa2 143112 10.29
+2024-01-04 02:00:00 +0000 aaa3 149190 10.91
+2024-01-05 02:00:00 +0000 aaa4 132098 10.34
+2024-01-06 02:00:00 +0000 aaa5 151344 10.69
+ ·········
+ -12.9%
+ ·········
+2024-01-07 02:00:00 +0000 aaa6 155145 9.23
+2024-01-08 02:00:00 +0000 aaa7 148889 9.11
+2024-01-09 02:00:00 +0000 aaa8 149466 9.13
+2024-01-10 02:00:00 +0000 aaa9 148209 9.03
+```
diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md
new file mode 100644
index 0000000..7e94221
--- /dev/null
+++ b/docs/GETTING_STARTED.md
@@ -0,0 +1,129 @@
+# Getting Started
+
+## Installation
+
+Hunter requires Python 3.8. If you don't have python 3.8,
+use pyenv to install it.
+
+Use pipx to install hunter:
+
+```
+pipx install git+ssh://[email protected]/apache/hunter
+```
+
+## Setup
+
+Copy the main configuration file `resources/hunter.yaml` to
`~/.hunter/hunter.yaml` and adjust data source configuration.
+
+> [!TIP]
+> See docs on specific data sources to learn more about their configuration -
[CSV](CSV.md), [Graphite](GRAPHITE.md),
+[PostgreSQL](POSTGRESQL.md), or [BigQuery](BIGQUERY.md).
+
+Alternatively, it is possible to leave the config file as is, and provide
credentials in the environment
+by setting appropriate environment variables.
+Environment variables are interpolated before interpreting the configuration
file.
+
+## Defining tests
+
+All test configurations are defined in the main configuration file.
+Hunter supports reading data from and publishing results to a CSV file,
[Graphite](https://graphiteapp.org/),
+[PostgreSQL](https://www.postgresql.org/), and
[BigQuery](https://cloud.google.com/bigquery).
+
+Tests are defined in the `tests` section. For example, the following
definition will import results of the test from a CSV file:
+
+```yaml
+tests:
+ local.sample:
+ type: csv
+ file: tests/resources/sample.csv
+ time_column: time
+ metrics: [metric1, metric2]
+ attributes: [commit]
+ csv_options:
+ delimiter: ","
+ quote_char: "'"
+```
+
+The `time_column` property points to the name of the column storing the
timestamp
+of each test-run. The data points will be ordered by that column.
+
+The `metrics` property selects the columns tha hold the values to be analyzed.
These values must
+be numbers convertible to floats. The `metrics` property can be not only a
simple list of column
+names, but it can also be a dictionary configuring other properties of each
metric,
+the column name or direction:
+
+```yaml
+metrics:
+ resp_time_p99:
+ direction: -1
+ column: p99
+```
+
+Direction can be 1 or -1. If direction is set to 1, this means that the higher
the metric, the
+better the performance is. If it is set to -1, higher values mean worse
performance.
+
+The `attributes` property describes any other columns that should be attached
to the final
+report. Special attribute `version` and `commit` can be used to query for a
given time-range.
+
+> [!TIP] To learn how to avoid repeating the same configuration in multiple
tests, see [Avoiding test definition duplication](TEMPLATES.md).
+
+## Listing Available Tests
+
+```
+hunter list-groups
+hunter list-tests [group name]
+```
+
+## Listing Available Metrics for Tests
+
+To list all available metrics defined for the test:
+```
+hunter list-metrics <test>
+```
+
+## Finding Change Points
+
+> [!TIP]
+> For more details, see [Finding Change
Points](BASICS.md#finding-change-points) and
+> [Validating Performance of a Feature
Branch](FEATURE_BRANCH.md#validating-performance-of-a-feature-branch).
+
+```
+hunter analyze <test>...
+hunter analyze <group>...
+```
+
+This command prints interesting results of all runs of the test and a list of
change-points.
+
+A change-point is a moment when a metric value starts to differ significantly
from the values of the earlier runs and
+when the difference is statistically significant.
+
+Hunter calculates the probability (P-value) that the change point was caused
by chance - the closer to zero, the more
+"sure" it is about the regression or performance improvement. The smaller is
the actual magnitude of the change, the
+more data points are needed to confirm the change, therefore Hunter may not
notice the regression immediately after the first run
+that regressed.
+
+The `analyze` command accepts multiple tests or test groups.
+The results are simply concatenated.
+
+## Example
+
+```
+$ hunter analyze local.sample
+INFO: Computing change points for test sample.csv...
+sample:
+time metric1 metric2
+------------------------- --------- ---------
+2021-01-01 02:00:00 +0000 154023 10.43
+2021-01-02 02:00:00 +0000 138455 10.23
+2021-01-03 02:00:00 +0000 143112 10.29
+2021-01-04 02:00:00 +0000 149190 10.91
+2021-01-05 02:00:00 +0000 132098 10.34
+2021-01-06 02:00:00 +0000 151344 10.69
+ ·········
+ -12.9%
+ ·········
+2021-01-07 02:00:00 +0000 155145 9.23
+2021-01-08 02:00:00 +0000 148889 9.11
+2021-01-09 02:00:00 +0000 149466 9.13
+2021-01-10 02:00:00 +0000 148209 9.03
+```
diff --git a/docs/GRAFANA.md b/docs/GRAFANA.md
new file mode 100644
index 0000000..da3ad39
--- /dev/null
+++ b/docs/GRAFANA.md
@@ -0,0 +1,65 @@
+# Annotating Change Points in Grafana
+
+Change points found by `analyze` can be exported
+as Grafana annotations using the `--update-grafana` flag:
+
+```
+$ hunter analyze <test or group> --update-grafana
+```
+
+The annotations generated by Hunter get the following tags:
+- `hunter`
+- `change-point`
+- `test:<test name>`
+- `metric:<metric name>`
+- tags configured in the `tags` property of the test
+- tags configured in the `annotate` property of the test
+- tags configured in the `annotate` property of the metric
+
+Additionally, the `annotate` property supports variable tags:
+- `%{TEST_NAME}` - name of the test
+- `%{METRIC_NAME}` - name of the metric
+- `%{GRAPHITE_PATH}` - resolves to the path to the data in Graphite
+- `%{GRAPHITE_PATH_COMPONENTS}` - splits the path of the data in Graphite into
separate components
+ and each path component is exported as a separate tag
+- `%{GRAPHITE_PREFIX}` - resolves to the prefix of the path to the data in
Graphite
+ (the part of the path up to the metric suffix)
+- `%{GRAPHITE_PREFIX_COMPONENTS}` - similar as `%{GRAPHITE_PATH_COMPONENTS}`
but splits the prefix
+of the path instead of the path
+
+## Example
+
+> [!TIP]
+> See [hunter.yaml](../examples/graphite/hunter.yaml) for the full Graphite &
Grafana example.
+
+Start docker-compose with Graphite in one tab:
+
+```bash
+docker-compose -f examples/graphite/docker-compose.yaml up --force-recreate
--always-recreate-deps --renew-anon-volumes --build
+````
+
+
+Run hunter in another tab:
+
+```bash
+docker-compose -f examples/graphite/docker-compose.yaml run hunter hunter
analyze my-product.test --since=-10m --update-grafana
+```
+
+Expected output:
+
+```bash
+time run branch version commit throughput
response_time cpu_usage
+------------------------- ----- -------- --------- -------- ------------
--------------- -----------
+2024-12-14 22:45:10 +0000 61160
87 0.2
+2024-12-14 22:46:10 +0000 60160
85 0.3
+2024-12-14 22:47:10 +0000 60960
89 0.1
+ ············
···········
+ -5.6%
+300.0%
+ ············
···········
+2024-12-14 22:48:10 +0000 57123
88 0.8
+2024-12-14 22:49:10 +0000 57980
87 0.9
+2024-12-14 22:50:10 +0000 56950
85 0.7
+```
+
+Open local [Grafana](http://localhost:3000) in your browser (use default
`admin/admin` credentials), open
+`Benchmarks` dashboard, and see your data points and annotations on both
charts.:
diff --git a/docs/GRAPHITE.md b/docs/GRAPHITE.md
new file mode 100644
index 0000000..9035632
--- /dev/null
+++ b/docs/GRAPHITE.md
@@ -0,0 +1,106 @@
+# Importing results from Graphite
+
+> [!TIP]
+> See [hunter.yaml](../examples/graphite/hunter.yaml) for the full example
configuration.
+
+## Graphite and Grafana Connection
+
+The following block contains Graphite and Grafana connection details:
+
+```yaml
+graphite:
+ url: ...
+
+grafana:
+ url: ...
+ user: ...
+ password: ...
+```
+
+These variables can be specified directly in `hunter.yaml` or passed as
environment variables:
+
+```yaml
+graphite:
+ url: ${GRAPHITE_ADDRESS}
+
+grafana:
+ url: ${GRAFANA_ADDRESS}
+ user: ${GRAFANA_USER}
+ password: ${GRAFANA_PASSWORD}
+```
+
+
+## Tests
+
+### Importing results from Graphite
+
+Test configuration contains queries selecting experiment data from Graphite.
This is done by specifying the Graphite
+path prefix common for all the test's metrics and suffixes for each of the
metrics recorded by the test run.
+
+```yaml
+tests:
+ my-product.test:
+ type: graphite
+ prefix: performance-tests.daily.my-product
+ metrics:
+ throughput:
+ suffix: client.throughput
+ response-time:
+ suffix: client.p50
+ direction: -1 # lower is better
+ cpu-load:
+ suffix: server.cpu
+ direction: -1 # lower is better
+```
+
+### Tags
+
+> [!WARNING]
+> Tags do not work as expected in the current version. See
https://github.com/datastax-labs/hunter/issues/24 for more details
+
+The optional `tags` property contains the tags that are used to query for
Graphite events that store
+additional test run metadata such as run identifier, commit, branch and
product version information.
+
+The following command will post an event with the test run metadata:
+```shell
+$ curl -X POST "http://graphite_address/events/" \
+ -d '{
+ "what": "Performance Test",
+ "tags": ["perf-test", "daily", "my-product"],
+ "when": 1537884100,
+ "data": {"commit": "fe6583ab", "branch": "new-feature", "version":
"0.0.1"}
+ }'
+```
+
+Posting those events is not mandatory, but when they are available, Hunter is
able to
+filter data by commit or version using `--since-commit` or `--since-version`
selectors.
+
+## Example
+
+Start docker-compose with Graphite in one tab:
+
+```bash
+docker-compose -f examples/graphite/docker-compose.yaml up --force-recreate
--always-recreate-deps --renew-anon-volumes --build
+````
+
+Run hunter in another tab:
+
+```bash
+docker-compose -f examples/graphite/docker-compose.yaml run hunter hunter
analyze my-product.test --since=-10m
+```
+
+Expected output:
+
+```bash
+time run branch version commit throughput
response_time cpu_usage
+------------------------- ----- -------- --------- -------- ------------
--------------- -----------
+2024-12-14 22:45:10 +0000 61160
87 0.2
+2024-12-14 22:46:10 +0000 60160
85 0.3
+2024-12-14 22:47:10 +0000 60960
89 0.1
+ ············
···········
+ -5.6%
+300.0%
+ ············
···········
+2024-12-14 22:48:10 +0000 57123
88 0.8
+2024-12-14 22:49:10 +0000 57980
87 0.9
+2024-12-14 22:50:10 +0000 56950
85 0.7
+```
diff --git a/docs/INSTALL.md b/docs/INSTALL.md
new file mode 100644
index 0000000..3d284e7
--- /dev/null
+++ b/docs/INSTALL.md
@@ -0,0 +1,19 @@
+# Installation
+
+## Install using pipx
+
+Hunter requires Python 3.8. If you don't have python 3.8, use pyenv to
install it.
+
+Use pipx to install hunter:
+
+```
+pipx install git+ssh://[email protected]/datastax-labs/hunter
+```
+
+## Build Docker container
+
+To build the Docker container, run the following command:
+
+```bash
+docker build -t hunter .
+```
diff --git a/docs/POSTGRESQL.md b/docs/POSTGRESQL.md
new file mode 100644
index 0000000..107f691
--- /dev/null
+++ b/docs/POSTGRESQL.md
@@ -0,0 +1,119 @@
+# Importing results from PostgreSQL
+
+> [!TIP]
+> See [hunter.yaml](../examples/postgresql/hunter.yaml) for the full example
configuration.
+
+## PostgreSQL Connection
+The following block contains PostgreSQL connection details:
+
+```yaml
+postgres:
+ hostname: ...
+ port: ...
+ username: ...
+ password: ...
+ database: ...
+```
+
+These variables can be specified directly in `hunter.yaml` or passed as
environment variables:
+
+```yaml
+postgres:
+ hostname: ${POSTGRES_HOSTNAME}
+ port: ${POSTGRES_PORT}
+ username: ${POSTGRES_USERNAME}
+ password: ${POSTGRES_PASSWORD}
+ database: ${POSTGRES_DATABASE}
+```
+
+## Tests
+
+Test configuration contains queries selecting experiment data, a time column,
and a list of columns to analyze:
+
+```yaml
+tests:
+ aggregate_mem:
+ type: postgres
+ time_column: commit_ts
+ attributes: [experiment_id, config_id, commit]
+ metrics:
+ process_cumulative_rate_mean:
+ direction: 1
+ scale: 1
+ process_cumulative_rate_stderr:
+ direction: -1
+ scale: 1
+ process_cumulative_rate_diff:
+ direction: -1
+ scale: 1
+ query: |
+ SELECT e.commit,
+ e.commit_ts,
+ r.process_cumulative_rate_mean,
+ r.process_cumulative_rate_stderr,
+ r.process_cumulative_rate_diff,
+ r.experiment_id,
+ r.config_id
+ FROM results r
+ INNER JOIN configs c ON r.config_id = c.id
+ INNER JOIN experiments e ON r.experiment_id = e.id
+ WHERE e.exclude_from_analysis = false AND
+ e.branch = 'trunk' AND
+ e.username = 'ci' AND
+ c.store = 'MEM' AND
+ c.cache = true AND
+ c.benchmark = 'aggregate' AND
+ c.instance_type = 'ec2i3.large'
+ ORDER BY e.commit_ts ASC;
+```
+
+## Example
+
+### Usage
+
+Start docker-compose with PostgreSQL in one tab:
+
+```bash
+docker-compose -f examples/postgresql/docker-compose.yaml up --force-recreate
--always-recreate-deps --renew-anon-volumes
+````
+
+Run Hunter in the other tab to show results for a single test `aggregate_mem`
and update the database with newly found change points:
+
+```bash
+docker-compose -f examples/postgresql/docker-compose.yaml run --build hunter
bin/hunter analyze aggregate_mem --update-postgres
+```
+
+Expected output:
+
+```bash
0.0s
+time experiment_id commit
process_cumulative_rate_mean process_cumulative_rate_stderr
process_cumulative_rate_diff
+------------------------- ------------------ --------
------------------------------ --------------------------------
------------------------------
+2024-03-13 10:03:02 +0000 aggregate-36e5ccd2 36e5ccd2
61160 2052 13558
+2024-03-25 10:03:02 +0000 aggregate-d5460f38 d5460f38
60160 2142 13454
+2024-04-02 10:03:02 +0000 aggregate-bc9425cb bc9425cb
60960 2052 13053
+
······························
+
-5.6%
+
······························
+2024-04-06 10:03:02 +0000 aggregate-14df1b11 14df1b11
57123 2052 14052
+2024-04-13 10:03:02 +0000 aggregate-ac40c0d8 ac40c0d8
57980 2052 13521
+2024-04-27 10:03:02 +0000 aggregate-0af4ccbc 0af4ccbc
56950 2052 13532
+```
+
+### Configuration
+
+See [hunter.yaml](../examples/postgresql/hunter.yaml) for the example
configuration:
+* Block `postgres` contains connection details to the PostgreSQL database.
+* Block `templates` contains common pieces of configuration used by all tests
- time column and a list of attributes and metrics.
+* Block `tests` contains configuration for the individual tests, specifically
a query that fetches analyzed columns sorted by commit timestamp.
+
+[schema.sql](../examples/postgresql/init-db/schema.sql) contains the schema
used in this example.
+
+[docker-compose.yaml](../examples/postgresql/docker-compose.yaml) contains
example config required to connect to PosgreSQL:
+1. `POSTGRES_*` environment variables are used to pass connection details to
the container.
+2. `HUNTER_CONFIG` is the path to the configuration file described above.
+3. `BRANCH` variable is used within `HUNTER_CONFIG` to analyze experiment
results only for a specific branch.
+
+
+### CLI arguments
+
+* `--update-postgres` - updates the database with newly found change points.
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000..3899795
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,16 @@
+# Table of Contents
+
+## Getting Started
+- [Installation](docs/INSTALL.md)
+- [Getting Started](docs/GETTING_STARTED.md)
+- [Contributing](docs/CONTRIBUTING.md)
+
+## Basics
+- [Basics](docs/BASICS.md)
+
+## Data Sources
+- [Graphite](docs/GRAPHITE.md)
+- [PostgreSQL][docs/POSTGRESQL.md)
+- [BigQuery](docs/BIG_QUERY.md)
+- [CSV](docs/CSV.md)
+- [Annotating Change Points in Grafana](docs/GRAFANA.md)
diff --git a/examples/csv/data/local_sample.csv
b/examples/csv/data/local_sample.csv
new file mode 100644
index 0000000..4fd9b90
--- /dev/null
+++ b/examples/csv/data/local_sample.csv
@@ -0,0 +1,11 @@
+time,commit,metric1,metric2
+2025.01.01 3:00:00 +0100,aaa0,154023,10.43
+2025.01.02 3:00:00 +0100,aaa1,138455,10.23
+2025.01.03 3:00:00 +0100,aaa2,143112,10.29
+2025.01.04 3:00:00 +0100,aaa3,149190,10.91
+2025.01.05 3:00:00 +0100,aaa4,132098,10.34
+2025.01.06 3:00:00 +0100,aaa5,151344,10.69
+2025.01.07 3:00:00 +0100,aaa6,155145,9.23
+2025.01.08 3:00:00 +0100,aaa7,148889,9.11
+2025.01.09 3:00:00 +0100,aaa8,149466,9.13
+2025.01.10 3:00:00 +0100,aaa9,148209,9.03
diff --git a/examples/csv/docker-compose.yaml b/examples/csv/docker-compose.yaml
new file mode 100644
index 0000000..d2b58be
--- /dev/null
+++ b/examples/csv/docker-compose.yaml
@@ -0,0 +1,12 @@
+services:
+ hunter:
+ build:
+ context: ../..
+ dockerfile: Dockerfile
+ container_name: hunter
+ environment:
+ HUNTER_CONFIG: examples/csv/hunter.yaml
+ volumes:
+ - ./tests:/tests
+
+
diff --git a/examples/csv/hunter.yaml b/examples/csv/hunter.yaml
new file mode 100644
index 0000000..5e43024
--- /dev/null
+++ b/examples/csv/hunter.yaml
@@ -0,0 +1,10 @@
+tests:
+ local.sample:
+ type: csv
+ file: /tests/local_sample.csv
+ time_column: time
+ attributes: [commit]
+ metrics: [metric1, metric2]
+ csv_options:
+ delimiter: ','
+ quotechar: "'"
\ No newline at end of file
diff --git a/examples/graphite/datagen/datagen.sh
b/examples/graphite/datagen/datagen.sh
new file mode 100755
index 0000000..426abaf
--- /dev/null
+++ b/examples/graphite/datagen/datagen.sh
@@ -0,0 +1,72 @@
+#!/bin/bash
+
+GRAPHITE_SERVER="graphite"
+GRAPHITE_PORT=2003
+
+commits=("a1b2c3" "d4e5f6" "g7h8i9" "j1k2l3" "m4n5o6" "p7q8r9")
+num_commits=${#commits[@]}
+
+throughput_path="performance-tests.daily.my-product.client.throughput"
+throughput_values=(56950 57980 57123 60960 60160 61160)
+
+p50_path="performance-tests.daily.my-product.client.p50"
+p50_values=(85 87 88 89 85 87)
+
+cpu_path="performance-tests.daily.my-product.server.cpu"
+cpu_values=(0.7 0.9 0.8 0.1 0.3 0.2)
+
+
+# Function to send throughput to Graphite
+send_to_graphite() {
+ local throughput_path=$1
+ local value=$2
+ local timestamp=$3
+ local commit=$4
+ # send the metric
+ echo "${throughput_path} ${value} ${timestamp}" | nc ${GRAPHITE_SERVER}
${GRAPHITE_PORT}
+ # annotate the metric
+ # Commented out, waiting for
https://github.com/datastax-labs/hunter/issues/24 to be fixed
+ # curl -X POST "http://${GRAPHITE_SERVER}/events/" \
+ # -d "{
+ # \"what\": \"Performance Test\",
+ # \"tags\": [\"perf-test\", \"daily\", \"my-product\"],
+ # \"when\": ${timestamp},
+ # \"data\": {\"commit\": \"${commit}\", \"branch\":
\"new-feature\", \"version\": \"0.0.1\"}
+ # }"
+}
+
+
+
+sleep 5 # Wait for Graphite to start
+
+start_timestamp=$(date +%s)
+timestamp=$start_timestamp
+
+# Send metrics for each commit
+for ((i=0; i<${num_commits}; i++)); do
+ send_to_graphite ${throughput_path} ${throughput_values[$i]} ${timestamp}
${commits[$i]}
+ send_to_graphite ${p50_path} ${p50_values[$i]} ${timestamp} ${commits[$i]}
+ send_to_graphite ${cpu_path} ${cpu_values[$i]} ${timestamp} ${commits[$i]}
+ timestamp=$((timestamp - 60))
+done
+
+## Send each throughput value
+#timestamp=$start_timestamp
+#for value in "${throughput_values[@]}"; do
+# send_to_graphite ${throughput_path} ${value} ${timestamp}
+# timestamp=$((timestamp - 60))
+#done
+#
+## Send each p50 value
+#timestamp=$start_timestamp
+#for value in "${p50_values[@]}"; do
+# send_to_graphite ${p50_path} ${value} ${timestamp}
+# timestamp=$((timestamp - 60))
+#done
+#
+## Send each CPU value
+#timestamp=$start_timestamp
+#for value in "${cpu_values[@]}"; do
+# send_to_graphite ${cpu_path} ${value} ${timestamp}
+# timestamp=$((timestamp - 60))
+#done
\ No newline at end of file
diff --git a/examples/graphite/docker-compose.yaml
b/examples/graphite/docker-compose.yaml
new file mode 100644
index 0000000..c4a56c3
--- /dev/null
+++ b/examples/graphite/docker-compose.yaml
@@ -0,0 +1,62 @@
+services:
+ graphite:
+ image: graphiteapp/graphite-statsd
+ container_name: graphite
+ ports:
+ - "80:80"
+ - "2003-2004:2003-2004"
+ - "2023-2024:2023-2024"
+ - "8125:8125/udp"
+ - "8126:8126"
+ networks:
+ - hunter-graphite
+
+ grafana:
+ image: grafana/grafana
+ container_name: grafana
+ environment:
+ - GF_SECURITY_ADMIN_PASSWORD=admin
+ depends_on:
+ - graphite
+ ports:
+ - "3000:3000"
+ volumes:
+ - ./grafana:/etc/grafana/provisioning
+ networks:
+ - hunter-graphite
+
+ data-sender:
+ image: bash
+ container_name: data-sender
+ depends_on:
+ - graphite
+ volumes:
+ - ./datagen:/datagen
+ entrypoint: ["bash", "/datagen/datagen.sh"]
+ networks:
+ - hunter-graphite
+
+ hunter:
+ build:
+ context: ../..
+ dockerfile: Dockerfile
+ container_name: hunter
+ depends_on:
+ - graphite
+ environment:
+ GRAPHITE_ADDRESS: http://graphite/
+ GRAFANA_ADDRESS: http://grafana:3000/
+ GRAFANA_USER: admin
+ GRAFANA_PASSWORD: admin
+ HUNTER_CONFIG: examples/graphite/hunter.yaml
+ networks:
+ - hunter-graphite
+
+networks:
+ hunter-graphite:
+ driver: bridge
+
+
+# TODO:
+# 3. make sure Hunter can connect to graphite and query the data
+# 4. make sure it annotates the dashboard correctly
\ No newline at end of file
diff --git a/examples/graphite/grafana/dashboards/benchmarks.json
b/examples/graphite/grafana/dashboards/benchmarks.json
new file mode 100644
index 0000000..612551d
--- /dev/null
+++ b/examples/graphite/grafana/dashboards/benchmarks.json
@@ -0,0 +1,246 @@
+{
+ "annotations": {
+ "list": [
+ {
+ "builtIn": 1,
+ "datasource": {
+ "type": "grafana",
+ "uid": "-- Grafana --"
+ },
+ "enable": true,
+ "hide": true,
+ "iconColor": "rgba(0, 211, 255, 1)",
+ "name": "Annotations & Alerts",
+ "target": {
+ "limit": 100,
+ "matchAny": false,
+ "tags": [],
+ "type": "dashboard"
+ },
+ "type": "dashboard"
+ },
+ {
+ "datasource": {
+ "type": "datasource",
+ "uid": "grafana"
+ },
+ "enable": true,
+ "hide": false,
+ "iconColor": "red",
+ "name": "New annotation",
+ "target": {
+ "fromAnnotations": true,
+ "queryType": "annotations",
+ "tags": [""]
+ }
+ }
+ ]
+ },
+ "editable": true,
+ "fiscalYearStartMonth": 0,
+ "graphTooltip": 0,
+ "id": 1,
+ "links": [],
+ "panels": [
+ {
+ "datasource": {
+ "type": "graphite",
+ "uid": "P1D261A8554D2DA69"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "barWidthFactor": 0.6,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 0
+ },
+ "id": 2,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "11.4.0",
+ "targets": [
+ {
+ "refCount": 0,
+ "refId": "A",
+ "target": "performance-tests.daily.my-product.server.cpu"
+ }
+ ],
+ "title": "Server CPU Usage",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "graphite",
+ "uid": "Graphite"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "barWidthFactor": 0.6,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 0
+ },
+ "id": 1,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "maxHeight": 600,
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "11.4.0",
+ "targets": [
+ {
+ "datasource": {
+ "type": "graphite",
+ "uid": "${DS_GRAPHITE}"
+ },
+ "refId": "A",
+ "target": "performance-tests.daily.my-product.client.throughput"
+ }
+ ],
+ "title": "Throughput",
+ "type": "timeseries"
+ }
+ ],
+ "preload": false,
+ "schemaVersion": 40,
+ "tags": [],
+ "templating": {
+ "list": []
+ },
+ "time": {
+ "from": "now-5m",
+ "to": "now"
+ },
+ "timepicker": {},
+ "timezone": "browser",
+ "title": "Benchmarks",
+ "uid": "adr2geu68gutcd",
+ "version": 1,
+ "weekStart": ""
+}
diff --git a/examples/graphite/grafana/dashboards/dashboards.yaml
b/examples/graphite/grafana/dashboards/dashboards.yaml
new file mode 100644
index 0000000..4ae98b0
--- /dev/null
+++ b/examples/graphite/grafana/dashboards/dashboards.yaml
@@ -0,0 +1,9 @@
+apiVersion: 1
+
+providers:
+ - name: 'default'
+ orgId: 1
+ folder: ''
+ type: file
+ options:
+ path: /etc/grafana/provisioning/dashboards
\ No newline at end of file
diff --git a/examples/graphite/grafana/datasources/graphite.yaml
b/examples/graphite/grafana/datasources/graphite.yaml
new file mode 100644
index 0000000..f4fb287
--- /dev/null
+++ b/examples/graphite/grafana/datasources/graphite.yaml
@@ -0,0 +1,12 @@
+apiVersion: 1
+
+datasources:
+ - name: Graphite
+ type: graphite
+ access: proxy
+ url: http://graphite:80
+ isDefault: true
+ jsonData:
+ graphiteVersion: "1.1"
+ tlsAuth: false
+ tlsAuthWithCACert: false
\ No newline at end of file
diff --git a/examples/graphite/hunter.yaml b/examples/graphite/hunter.yaml
new file mode 100644
index 0000000..bf791f1
--- /dev/null
+++ b/examples/graphite/hunter.yaml
@@ -0,0 +1,31 @@
+# External systems connectors configuration:
+graphite:
+ url: ${GRAPHITE_ADDRESS}
+
+grafana:
+ url: ${GRAFANA_ADDRESS}
+ user: ${GRAFANA_USER}
+ password: ${GRAFANA_PASSWORD}
+
+# Define your tests here:
+tests:
+ my-product.test:
+ type: graphite
+ prefix: performance-tests.daily.my-product
+ tags: [perf-test, daily, my-product]
+ metrics:
+ throughput:
+ suffix: client.throughput
+ direction: 1 # higher is better
+ scale: 1
+ response_time:
+ suffix: client.p50
+ direction: -1 # lower is better
+ scale: 1
+ cpu_usage:
+ suffix: server.cpu
+ direction: -1 # lower is better
+ scale: 1
+
+
+
diff --git a/examples/postgresql/docker-compose.yaml
b/examples/postgresql/docker-compose.yaml
new file mode 100644
index 0000000..569a8cf
--- /dev/null
+++ b/examples/postgresql/docker-compose.yaml
@@ -0,0 +1,38 @@
+version: "3.8"
+
+services:
+ postgres:
+ image: postgres:latest
+ container_name: postgres
+ environment:
+ POSTGRES_USER: exampleuser
+ POSTGRES_PASSWORD: examplepassword
+ POSTGRES_DB: benchmark_results
+ ports:
+ - "5432:5432"
+ volumes:
+ - ./init-db:/docker-entrypoint-initdb.d
+ networks:
+ - hunter-postgres
+
+ hunter:
+ build:
+ context: ../..
+ dockerfile: Dockerfile
+ container_name: hunter
+ depends_on:
+ - postgres
+ environment:
+ POSTGRES_HOSTNAME: postgres
+ POSTGRES_PORT: 5432
+ POSTGRES_USERNAME: exampleuser
+ POSTGRES_PASSWORD: examplepassword
+ POSTGRES_DATABASE: benchmark_results
+ HUNTER_CONFIG: examples/postgresql/hunter.yaml
+ BRANCH: trunk
+ networks:
+ - hunter-postgres
+
+networks:
+ hunter-postgres:
+ driver: bridge
diff --git a/examples/psql/hunter.yaml b/examples/postgresql/hunter.yaml
similarity index 65%
rename from examples/psql/hunter.yaml
rename to examples/postgresql/hunter.yaml
index 664dca2..36b14ac 100644
--- a/examples/psql/hunter.yaml
+++ b/examples/postgresql/hunter.yaml
@@ -11,7 +11,7 @@ templates:
common:
type: postgres
time_column: commit_ts
- attributes: [experiment_id, config_id, commit]
+ attributes: [experiment_id, commit, config_id]
# required for --update-postgres to work
update_statement: |
UPDATE results
@@ -40,7 +40,7 @@ tests:
r.process_cumulative_rate_mean,
r.process_cumulative_rate_stderr,
r.process_cumulative_rate_diff,
- r.experiment_id,
+ r.experiment_id,
r.config_id
FROM results r
INNER JOIN configs c ON r.config_id = c.id
@@ -52,4 +52,26 @@ tests:
c.cache = true AND
c.benchmark = 'aggregate' AND
c.instance_type = 'ec2i3.large'
+ ORDER BY e.commit_ts ASC;
+
+ aggregate_time_rocks:
+ inherit: [ common ]
+ query: |
+ SELECT e.commit,
+ e.commit_ts,
+ r.process_cumulative_rate_mean,
+ r.process_cumulative_rate_stderr,
+ r.process_cumulative_rate_diff,
+ r.experiment_id,
+ r.config_id
+ FROM results r
+ INNER JOIN configs c ON r.config_id = c.id
+ INNER JOIN experiments e ON r.experiment_id = e.id
+ WHERE e.exclude_from_analysis = false AND
+ e.branch = '${BRANCH}' AND
+ e.username = 'ci' AND
+ c.store = 'TIME_ROCKS' AND
+ c.cache = true AND
+ c.benchmark = 'aggregate' AND
+ c.instance_type = 'ec2i3.large'
ORDER BY e.commit_ts ASC;
\ No newline at end of file
diff --git a/examples/postgresql/init-db/schema.sql
b/examples/postgresql/init-db/schema.sql
new file mode 100644
index 0000000..99ff9cf
--- /dev/null
+++ b/examples/postgresql/init-db/schema.sql
@@ -0,0 +1,85 @@
+\c benchmark_results;
+
+CREATE TABLE IF NOT EXISTS configs (
+ id SERIAL PRIMARY KEY,
+ benchmark TEXT NOT NULL,
+ store TEXT NOT NULL,
+ instance_type TEXT NOT NULL,
+ cache BOOLEAN NOT NULL,
+ UNIQUE(benchmark,
+ store,
+ cache,
+ instance_type)
+);
+
+CREATE TABLE IF NOT EXISTS experiments (
+ id TEXT PRIMARY KEY,
+ ts TIMESTAMPTZ NOT NULL,
+ branch TEXT NOT NULL,
+ commit TEXT NOT NULL,
+ commit_ts TIMESTAMPTZ NOT NULL,
+ username TEXT NOT NULL,
+ details_url TEXT NOT NULL,
+ exclude_from_analysis BOOLEAN DEFAULT false NOT NULL,
+ exclude_reason TEXT
+);
+
+CREATE TABLE IF NOT EXISTS results (
+ experiment_id TEXT NOT NULL REFERENCES experiments(id),
+ config_id INTEGER NOT NULL REFERENCES configs(id),
+
+ process_cumulative_rate_mean BIGINT NOT NULL,
+ process_cumulative_rate_stderr BIGINT NOT NULL,
+ process_cumulative_rate_diff BIGINT NOT NULL,
+
+ process_cumulative_rate_mean_rel_forward_change DOUBLE PRECISION,
+ process_cumulative_rate_mean_rel_backward_change DOUBLE PRECISION,
+ process_cumulative_rate_mean_p_value DECIMAL,
+
+ process_cumulative_rate_stderr_rel_forward_change DOUBLE PRECISION,
+ process_cumulative_rate_stderr_rel_backward_change DOUBLE PRECISION,
+ process_cumulative_rate_stderr_p_value DECIMAL,
+
+ process_cumulative_rate_diff_rel_forward_change DOUBLE PRECISION,
+ process_cumulative_rate_diff_rel_backward_change DOUBLE PRECISION,
+ process_cumulative_rate_diff_p_value DECIMAL,
+
+ PRIMARY KEY (experiment_id, config_id)
+);
+
+-- configurations --
+INSERT INTO configs (id, benchmark, store, instance_type, cache) VALUES
+ (1, 'aggregate', 'MEM', 'ec2i3.large', true),
+ (2, 'aggregate', 'TIME_ROCKS', 'ec2i3.large', true);
+
+-- experiments --
+INSERT INTO experiments
+ (id, ts, branch, commit, commit_ts, username, details_url)
+VALUES
+ ('aggregate-36e5ccd2', '2024-03-14 12:03:02+00', 'trunk', '36e5ccd2',
'2024-03-13 10:03:02+00', 'ci',
'https://example.com/experiments/aggregate-36e5ccd2'),
+ ('aggregate-d5460f38', '2024-03-27 12:03:02+00', 'trunk', 'd5460f38',
'2024-03-25 10:03:02+00', 'ci',
'https://example.com/experiments/aggregate-d5460f38'),
+ ('aggregate-bc9425cb', '2024-04-01 12:03:02+00', 'trunk', 'bc9425cb',
'2024-04-02 10:03:02+00', 'ci',
'https://example.com/experiments/aggregate-bc9425cb'),
+ ('aggregate-14df1b11', '2024-04-07 12:03:02+00', 'trunk', '14df1b11',
'2024-04-06 10:03:02+00', 'ci',
'https://example.com/experiments/aggregate-14df1b11'),
+ ('aggregate-ac40c0d8', '2024-04-14 12:03:02+00', 'trunk', 'ac40c0d8',
'2024-04-13 10:03:02+00', 'ci',
'https://example.com/experiments/aggregate-ac40c0d8'),
+ ('aggregate-0af4ccbc', '2024-04-28 12:03:02+00', 'trunk', '0af4ccbc',
'2024-04-27 10:03:02+00', 'ci',
'https://example.com/experiments/aggregate-0af4ccbc');
+
+
+INSERT INTO results (experiment_id, config_id, process_cumulative_rate_mean,
process_cumulative_rate_stderr, process_cumulative_rate_diff)
+VALUES
+ ('aggregate-36e5ccd2', 1, 61160, 2052, 13558),
+ ('aggregate-36e5ccd2', 2, 59250, 2599, 15557),
+
+ ('aggregate-d5460f38', 1, 60160, 2142, 13454),
+ ('aggregate-d5460f38', 2, 58316, 2573, 16028),
+
+ ('aggregate-bc9425cb', 1, 60960, 2052, 13053),
+ ('aggregate-bc9425cb', 2, 59021, 2459, 15259),
+
+ ('aggregate-14df1b11', 1, 57123, 2052, 14052),
+ ('aggregate-14df1b11', 2, 54725, 2291, 15558),
+
+ ('aggregate-ac40c0d8', 1, 57980, 2052, 13521),
+ ('aggregate-ac40c0d8', 2, 54250, 2584, 15558),
+
+ ('aggregate-0af4ccbc', 1, 56950, 2052, 13532),
+ ('aggregate-0af4ccbc', 2, 54992, 2311, 15585);
diff --git a/examples/psql/README.md b/examples/psql/README.md
deleted file mode 100644
index f1333db..0000000
--- a/examples/psql/README.md
+++ /dev/null
@@ -1,22 +0,0 @@
-## Schema
-
-See [schema.sql](schema.sql) for the example schema.
-
-## Usage
-
-Define PostgreSQL connection details via environment variables:
-
-```bash
-export POSTGRES_HOSTNAME=...
-export POSTGRES_USERNAME=...
-export POSTGRES_PASSWORD=...
-export POSTGRES_DATABASE=...
-```
-
-or in `hunter.yaml`.
-
-The following command shows results for a single test `aggregate_mem` and
updates the database with newly found change points:
-
-```bash
-$ BRANCH=trunk HUNTER_CONFIG=hunter.yaml hunter analyze aggregate_mem
--update-postgres
-```
diff --git a/examples/psql/schema.sql b/examples/psql/schema.sql
deleted file mode 100644
index de825a9..0000000
--- a/examples/psql/schema.sql
+++ /dev/null
@@ -1,48 +0,0 @@
-CREATE TABLE IF NOT EXISTS configs (
- id SERIAL PRIMARY KEY,
- benchmark TEXT NOT NULL,
- scenario TEXT NOT NULL,
- store TEXT NOT NULL,
- instance_type TEXT NOT NULL,
- cache BOOLEAN NOT NULL,
- UNIQUE(benchmark,
- scenario,
- store,
- cache,
- instance_type)
-);
-
-CREATE TABLE IF NOT EXISTS experiments (
- id TEXT PRIMARY KEY,
- ts TIMESTAMPTZ NOT NULL,
- branch TEXT NOT NULL,
- commit TEXT NOT NULL,
- commit_ts TIMESTAMPTZ NOT NULL,
- username TEXT NOT NULL,
- details_url TEXT NOT NULL,
- exclude_from_analysis BOOLEAN DEFAULT false NOT NULL,
- exclude_reason TEXT
-);
-
-CREATE TABLE IF NOT EXISTS results (
- experiment_id TEXT NOT NULL REFERENCES experiments(id),
- config_id INTEGER NOT NULL REFERENCES configs(id),
-
- process_cumulative_rate_mean BIGINT NOT NULL,
- process_cumulative_rate_stderr BIGINT NOT NULL,
- process_cumulative_rate_diff BIGINT NOT NULL,
-
- process_cumulative_rate_mean_rel_forward_change DOUBLE PRECISION,
- process_cumulative_rate_mean_rel_backward_change DOUBLE PRECISION,
- process_cumulative_rate_mean_p_value DECIMAL,
-
- process_cumulative_rate_stderr_rel_forward_change DOUBLE PRECISION,
- process_cumulative_rate_stderr_rel_backward_change DOUBLE PRECISION,
- process_cumulative_rate_stderr_p_value DECIMAL,
-
- process_cumulative_rate_diff_rel_forward_change DOUBLE PRECISION,
- process_cumulative_rate_diff_rel_backward_change DOUBLE PRECISION,
- process_cumulative_rate_diff_p_value DECIMAL,
-
- PRIMARY KEY (experiment_id, config_id)
-);
\ No newline at end of file
diff --git a/hunter/grafana.py b/hunter/grafana.py
index a643c9a..7cc02eb 100644
--- a/hunter/grafana.py
+++ b/hunter/grafana.py
@@ -100,7 +100,7 @@ class Grafana:
data = asdict(annotation)
data["time"] = int(annotation.time.timestamp() * 1000)
del data["id"]
- response = requests.post(url=url, data=data,
auth=(self.__user, self.__password))
+ response = requests.post(url=url, json=data,
auth=(self.__user, self.__password))
response.raise_for_status()
except HTTPError as err:
raise GrafanaError(str(err))