Skip to content

Commit

Permalink
initial
Browse files Browse the repository at this point in the history
  • Loading branch information
voron committed Jul 10, 2024
1 parent f2a78f2 commit b831b56
Show file tree
Hide file tree
Showing 8 changed files with 1,683 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.venv
.git
.gitignore
37 changes: 37 additions & 0 deletions .github/workflows/docker-build-push.yml
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 }}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.venv
.idea
17 changes: 17 additions & 0 deletions Dockerfile
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" ]
17 changes: 17 additions & 0 deletions Pipfile
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"
1,501 changes: 1,501 additions & 0 deletions Pipfile.lock

Large diffs are not rendered by default.

15 changes: 14 additions & 1 deletion README.md
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
92 changes: 92 additions & 0 deletions exporter/main.py
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)

0 comments on commit b831b56

Please sign in to comment.