-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
1,683 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
.venv | ||
.git | ||
.gitignore |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
name: Build docker image | ||
on: | ||
create: | ||
tags: | ||
- 'v*' | ||
push: | ||
branches: | ||
- main | ||
|
||
jobs: | ||
docker: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Checkout | ||
uses: actions/checkout@v4 | ||
|
||
- name: Set up QEMU | ||
uses: docker/setup-qemu-action@v3 | ||
|
||
- name: Set up Docker Buildx | ||
uses: docker/setup-buildx-action@v3 | ||
|
||
- name: Login to GitHub Container Registry | ||
uses: docker/login-action@v3 | ||
with: | ||
registry: ghcr.io | ||
username: ${{ github.repository_owner }} | ||
password: ${{ secrets.GITHUB_TOKEN }} | ||
|
||
- name: Build and push | ||
uses: docker/build-push-action@v6 | ||
with: | ||
platforms: linux/amd64,linux/arm64 | ||
push: true | ||
tags: | | ||
ghcr.io/dysnix/block_lag_exporter:latest | ||
ghcr.io/user/block_lag_exporter:${{ github.ref_name }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
.venv | ||
.idea |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# Use an official Python runtime as a parent image | ||
FROM python:3.12-slim-bookworm | ||
|
||
# Set the working directory in the container to /app | ||
WORKDIR /app | ||
|
||
# Add Pipfile and Pipfile.lock to the WORKDIR | ||
ADD Pipfile Pipfile.lock /app/ | ||
|
||
RUN apt-get update && apt-get -y install gcc && \ | ||
pip install pipenv && pipenv install --system --deploy && \ | ||
apt-get -y remove gcc && apt-get -y autoremove | ||
|
||
# Add the rest of the code | ||
ADD ./exporter/ /app/ | ||
|
||
CMD [ "python3", "main.py" ] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
[[source]] | ||
url = "https://pypi.org/simple" | ||
verify_ssl = true | ||
name = "pypi" | ||
|
||
[packages] | ||
prometheus-client = "*" | ||
web3 = "*" | ||
websockets = "*" | ||
aiohttp = "*" | ||
prometheus_async = "*" | ||
|
||
[dev-packages] | ||
|
||
[requires] | ||
python_version = "3.12" | ||
python_full_version = "3.12.4" |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,15 @@ | ||
# block_lag_exporter | ||
Metrics exporter to export latest block lag for evm-based chains | ||
Simple metrics exporter to export latest block lag for evm-based chains. It exports 2 metrics: | ||
* `head_lag_seconds` - histogram with values from uptime | ||
* `head_lag_seconds_last` - gauge with latest value of block lag | ||
|
||
and 2 endpoints on `http://0.0.0.0:${LISTENER_PORT}`: | ||
* `/metrics` - to scrape prometheus metrics | ||
* `/health` - to check liveness | ||
|
||
Default values are optimized to use it as a k8s geth/geth-like sidecar | ||
Use following environment variables to override defaults: | ||
* `LISTENER_PORT=8000` - port to listen | ||
* `WS_URL=ws://localhost:8545` - websocket URL to connect and subscribe to new blocks | ||
* `HIST_BUCKETS=0.05,0.08,0.1,0.15,0.2,0.3,0.4,0.6,0.8,1.0,1.2,1.6,2.0,2.5,3.0,4.0,8.0,+Inf` - override prometheus | ||
histogram buckets for histogram metric |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import asyncio | ||
import signal | ||
import json | ||
import sys | ||
import os | ||
import time | ||
|
||
from web3 import Web3 | ||
from websockets import connect | ||
from aiohttp import web | ||
from prometheus_async import aio | ||
from prometheus_client import Histogram, Gauge | ||
|
||
|
||
def process_block(block): | ||
timestamp = int(block['timestamp'], 16) | ||
block_number = int(block['number'], 16) | ||
ts = time.time() | ||
lag = ts - timestamp | ||
hist.observe(lag) | ||
gauge.set("{:+.4f}".format(lag)) | ||
print("ts=%d block=%d lag=%2.4f" % (timestamp, block_number, lag)) | ||
return | ||
|
||
|
||
async def get_event(): | ||
try: | ||
async with connect(ws_url) as ws: | ||
await ws.send('{"jsonrpc": "2.0", "id": 1, "method": "eth_subscribe", "params": ["newHeads"]}') | ||
subscription_response = await ws.recv() | ||
print("ws subscription: %s" % subscription_response) | ||
while True: | ||
message = await asyncio.wait_for(ws.recv(), timeout=5) | ||
response = json.loads(message) | ||
block = response['params']['result'] | ||
process_block(block) | ||
except Exception as e: | ||
print(e) | ||
return | ||
|
||
|
||
async def event_wrapper(): | ||
# await asyncio.sleep(0) | ||
counter = 0 | ||
while True: | ||
print("Starting wrapper tasks #%d" % counter) | ||
await get_event() | ||
print("await app done") | ||
await asyncio.sleep(2) | ||
counter += 1 | ||
# yield | ||
|
||
|
||
async def background_tasks(app): | ||
app[ws_listener] = asyncio.create_task(event_wrapper()) | ||
yield | ||
app[ws_listener].cancel() | ||
await app[ws_listener] | ||
|
||
|
||
async def on_shutdown(app: web.Application) -> None: | ||
print("Shutting down ...") | ||
# there is no sense to wait, metrics server is stopped anyway | ||
# await asyncio.sleep(30) | ||
sys.exit(0) | ||
|
||
|
||
async def health(self): | ||
return web.Response(text="OK") | ||
|
||
|
||
if __name__ == "__main__": | ||
metrics_port = int(os.environ.get("LISTENER_PORT", 8000)) | ||
ws_url = os.environ.get("WS_URL", "ws://localhost:8545") | ||
buckets = os.environ.get( | ||
"HIST_BUCKETS", "0.05,0.08,0.1,0.15,0.2,0.3,0.4,0.6,0.8,1.0,1.2,1.6,2.0,2.5,3.0,4.0,8.0,+Inf") | ||
|
||
hist = Histogram('head_lag_seconds', 'Last block lag', | ||
buckets=buckets.split(',')) | ||
gauge = Gauge('head_lag_seconds_last', 'Last block lag') | ||
|
||
loop = asyncio.new_event_loop() | ||
asyncio.set_event_loop(loop) | ||
|
||
app = web.Application() | ||
ws_listener = web.AppKey("ws_listener", asyncio.Task[None]) | ||
app.cleanup_ctx.append(background_tasks) | ||
app.on_shutdown.append(on_shutdown) | ||
app.router.add_get("/metrics", aio.web.server_stats) | ||
app.router.add_get("/health", health) | ||
|
||
web.run_app(app, port=metrics_port, loop=loop, handle_signals=True) |