Skip to content

Commit b0c4097

Browse files
[exporters] feat: initial commit for garf-exporter
garf-exporter exposes data fetched from an API into prometheus format thus making monitoring of API changes easily available.
1 parent 3925934 commit b0c4097

File tree

12 files changed

+1057
-0
lines changed

12 files changed

+1057
-0
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: Publish to PyPI - garf-exporter
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
paths:
8+
- 'libs/exporters/**'
9+
- '!libs/exporters/**/*.md'
10+
11+
jobs:
12+
publish:
13+
runs-on: ubuntu-latest
14+
environment:
15+
name: pypi
16+
permissions:
17+
id-token: write
18+
steps:
19+
- name: Checkout repository
20+
uses: actions/checkout@v4
21+
with:
22+
fetch-depth: 0
23+
- name: Install uv
24+
uses: astral-sh/setup-uv@v4
25+
26+
- name: Get versions and check for change
27+
id: version_check
28+
run: |
29+
PREV_VERSION=$(git show HEAD~1:libs/exporters/garf_exporter/__init__.py | grep -oE "__version__ = '([0-9]+\.[0-9]+\.[0-9]+)'" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
30+
CURRENT_VERSION=$(grep -oE "__version__ = '([0-9]+\.[0-9]+\.[0-9]+)'" libs/exporters/garf_exporter/__init__.py | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
31+
32+
if [[ "$CURRENT_VERSION" != "$PREV_VERSION" ]]; then
33+
echo "Version change detected: $PREV_VERSION -> $CURRENT_VERSION"
34+
echo "should_publish=true" >> $GITHUB_OUTPUT
35+
echo "new_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
36+
else
37+
echo "No version change. Skipping publish."
38+
echo "should_publish=false" >> $GITHUB_OUTPUT
39+
fi
40+
41+
- name: Build the package
42+
if: steps.version_check.outputs.should_publish == 'true'
43+
run: uv build libs/exporters
44+
45+
- name: Publish to PyPI
46+
if: steps.version_check.outputs.should_publish == 'true'
47+
run: uv publish libs/exporters/dist/*
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: Run tests for garf-exporter
2+
3+
on:
4+
workflow_dispatch:
5+
pull_request:
6+
branches: [main]
7+
paths:
8+
- 'libs/exporters/**'
9+
10+
env:
11+
UV_SYSTEM_PYTHON: 1
12+
13+
jobs:
14+
test:
15+
runs-on: ubuntu-latest
16+
strategy:
17+
fail-fast: false
18+
matrix:
19+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
20+
steps:
21+
- uses: actions/checkout@v3
22+
- name: Set up Python
23+
uses: actions/setup-python@v5
24+
with:
25+
python-version: ${{ matrix.python-version }}
26+
- name: Setup uv
27+
uses: astral-sh/setup-uv@v5
28+
with:
29+
version: "0.5.4"
30+
enable-cache: true
31+
- name: Install dependencies
32+
run: |
33+
uv pip install pytest
34+
- name: Test garf-exporters
35+
run: |
36+
cd libs/exporters/
37+
uv pip install -e .
38+
pytest tests/unit

libs/exporters/README.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# garf exporter - Prometheus exporter for garf.
2+
3+
[![PyPI](https://img.shields.io/pypi/v/garf-exporter?logo=pypi&logoColor=white&style=flat-square)](https://pypi.org/project/garf-exporter)
4+
[![Downloads PyPI](https://img.shields.io/pypi/dw/garf-exporter?logo=pypi)](https://pypi.org/project/garf-exporter/)
5+
6+
7+
## Installation and usage
8+
9+
### Locally
10+
11+
1. Install `garf-exporter` from pip:
12+
13+
```
14+
pip install garf-exporter
15+
```
16+
2. Run `garf-exporter`:
17+
18+
```
19+
garf-exporter
20+
```
21+
22+
### Docker
23+
24+
```
25+
docker run --network=host \
26+
-v `pwd`/garf_exporter.yaml:/app/garf_exporter.yaml \
27+
garf_exporter
28+
```
29+
30+
```
31+
docker run --network=host garf_exporter \
32+
--config gs://path/to/garf_config.yaml
33+
34+
```
35+
By default it will start http_server on `localhost:8000` and will push some basic metrics to it.
36+
37+
### Customization
38+
39+
* `--config` - path to `garf_exporter.yaml`
40+
> `config` can be taken from local storage or remote storage.
41+
* `--expose-type` - type of exposition (`http` or `pushgateway`, `http` is used by default)
42+
* `--host` - address of your http server (`localhost` by default)
43+
* `--port` - port of your http server (`8000` by default)
44+
* `--delay-minutes` - delay in minutes between scrapings (`15` by default)
45+
46+
47+
### Customizing fetching dates
48+
49+
By default `garf-exporter` fetches performance data for TODAY; if you want to
50+
customize it you can provide optional flags:
51+
* `--macro.start_date=:YYYYMMDD-N`, where `N` is number of days starting from today
52+
* `--macro.end_date=:YYYYMMDD-M`, where `N` is number of days starting from today
53+
54+
It will add an additional metric to be exposed to Prometheus `*_n_days` (i.e.
55+
`googleads_clicks_n_days`).
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from garf_exporter.exporter import GarfExporter
16+
17+
__all__ = [
18+
'GarfExporter',
19+
]
20+
21+
__version__ = '0.0.1'
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
16+
import os
17+
import pathlib
18+
19+
import pydantic
20+
import smart_open
21+
import yaml
22+
23+
24+
class Collector(pydantic.BaseModel):
25+
query: str
26+
title: str
27+
suffix: str = ''
28+
29+
30+
def load_collector_data(
31+
path_to_definitions: str | os.PathLike[str],
32+
) -> list[Collector]:
33+
"""Loads collectors data from file or folder.
34+
35+
Args:
36+
path_to_definitions: Local path to file / folder with collector definitions.
37+
38+
Returns:
39+
Loaded collector definitions.
40+
"""
41+
if isinstance(path_to_definitions, str):
42+
path_to_definitions = pathlib.Path(path_to_definitions)
43+
results = []
44+
if path_to_definitions.is_file():
45+
with smart_open.open(path_to_definitions, 'r', encoding='utf-8') as f:
46+
data = yaml.safe_load(f)
47+
for entry in data:
48+
results.append(Collector(**entry))
49+
else:
50+
for file in path_to_definitions.iterdir():
51+
if file.suffix == '.yaml':
52+
with smart_open.open(file, 'r', encoding='utf-8') as f:
53+
data = yaml.safe_load(f)
54+
for entry in data:
55+
results.append(Collector(**entry))
56+
return results

0 commit comments

Comments
 (0)