Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions .github/workflows/publish_garf_exporter.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Publish to PyPI - garf-exporter

on:
push:
branches:
- main
paths:
- 'libs/exporters/**'
- '!libs/exporters/**/*.md'

jobs:
publish:
runs-on: ubuntu-latest
environment:
name: pypi
permissions:
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install uv
uses: astral-sh/setup-uv@v4

- name: Get versions and check for change
id: version_check
run: |
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]+')
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]+')

if [[ "$CURRENT_VERSION" != "$PREV_VERSION" ]]; then
echo "Version change detected: $PREV_VERSION -> $CURRENT_VERSION"
echo "should_publish=true" >> $GITHUB_OUTPUT
echo "new_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
else
echo "No version change. Skipping publish."
echo "should_publish=false" >> $GITHUB_OUTPUT
fi

- name: Build the package
if: steps.version_check.outputs.should_publish == 'true'
run: uv build libs/exporters

- name: Publish to PyPI
if: steps.version_check.outputs.should_publish == 'true'
run: uv publish libs/exporters/dist/*
41 changes: 41 additions & 0 deletions .github/workflows/test_garf_exporter.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Run tests for garf-exporter

on:
workflow_dispatch:
pull_request:
branches: [main]
paths:
- 'libs/exporters/**'

env:
UV_SYSTEM_PYTHON: 1

jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Setup uv
uses: astral-sh/setup-uv@v5
with:
version: "0.5.4"
enable-cache: true
- name: Install dependencies
run: |
uv pip install pytest
uv pip install -e libs/core/.[all]
uv pip install -e libs/io/.[all]
uv pip install -e libs/executors/.[all]
- name: Test garf-exporters
run: |
cd libs/exporters/
uv pip install -e .
pytest tests/unit
5 changes: 5 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ repos:
entry: pytest -n auto libs/executors/tests/unit/
language: system
pass_filenames: false
- id: pytest-check-exporters
name: pytest-check-exporters
entry: pytest -n auto libs/exporters/tests/unit/
language: system
pass_filenames: false
- id: executors-e2e
stages: [pre-push]
name: e2e
Expand Down
55 changes: 55 additions & 0 deletions libs/exporters/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# garf exporter - Prometheus exporter for garf.

[![PyPI](https://img.shields.io/pypi/v/garf-exporter?logo=pypi&logoColor=white&style=flat-square)](https://pypi.org/project/garf-exporter)
[![Downloads PyPI](https://img.shields.io/pypi/dw/garf-exporter?logo=pypi)](https://pypi.org/project/garf-exporter/)


## Installation and usage

### Locally

1. Install `garf-exporter` from pip:

```
pip install garf-exporter
```
2. Run `garf-exporter`:

```
garf-exporter
```

### Docker

```
docker run --network=host \
-v `pwd`/garf_exporter.yaml:/app/garf_exporter.yaml \
garf_exporter
```

```
docker run --network=host garf_exporter \
--config gs://path/to/garf_config.yaml

```
By default it will start http_server on `localhost:8000` and will push some basic metrics to it.

### Customization

* `--config` - path to `garf_exporter.yaml`
> `config` can be taken from local storage or remote storage.
* `--expose-type` - type of exposition (`http` or `pushgateway`, `http` is used by default)
* `--host` - address of your http server (`localhost` by default)
* `--port` - port of your http server (`8000` by default)
* `--delay-minutes` - delay in minutes between scrapings (`15` by default)


### Customizing fetching dates

By default `garf-exporter` fetches performance data for TODAY; if you want to
customize it you can provide optional flags:
* `--macro.start_date=:YYYYMMDD-N`, where `N` is number of days starting from today
* `--macro.end_date=:YYYYMMDD-M`, where `N` is number of days starting from today

It will add an additional metric to be exposed to Prometheus `*_n_days` (i.e.
`googleads_clicks_n_days`).
21 changes: 21 additions & 0 deletions libs/exporters/garf_exporter/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from garf_exporter.exporter import GarfExporter

__all__ = [
'GarfExporter',
]

__version__ = '0.0.1'
65 changes: 65 additions & 0 deletions libs/exporters/garf_exporter/collector.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import os
import pathlib
from typing import Union

import pydantic
import smart_open
import yaml


class Collector(pydantic.BaseModel):
"""Stores API query with meta information.

Attributes:
query: Text of a query for an API.
title: Identifier of a query.
suffix: Optional element to be added to Prometheus metric.
"""

query: str
title: str
suffix: str = ''


def load_collector_data(
path_to_definitions: Union[str, os.PathLike[str], pathlib.Path],
) -> list[Collector]:
"""Loads collectors data from file or folder.

Args:
path_to_definitions: Local path to file / folder with collector definitions.

Returns:
Loaded collector definitions.
"""
if isinstance(path_to_definitions, str):
path_to_definitions = pathlib.Path(path_to_definitions)
results = []
if path_to_definitions.is_file():
with smart_open.open(path_to_definitions, 'r', encoding='utf-8') as f:
data = yaml.safe_load(f)
for entry in data:
results.append(Collector(**entry))
else:
for file in path_to_definitions.iterdir():
if file.suffix == '.yaml':
with smart_open.open(file, 'r', encoding='utf-8') as f:
data = yaml.safe_load(f)
for entry in data:
results.append(Collector(**entry))
return results
Loading