Initial commit
This commit is contained in:
commit
612cd91430
10
.env.example
Normal file
10
.env.example
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
TARGETS=https://smt-develop2.ru/displays.php?r=GetDisplaysStates&p=15047
|
||||||
|
SCRAPE_INTERVAL=15s
|
||||||
|
GRAFANA_USER=admin
|
||||||
|
GRAFANA_PASSWORD=admin
|
||||||
|
ALERT_EMAIL_TO=example@example.com
|
||||||
|
ALERT_EMAIL_FROM=alert@example.com
|
||||||
|
ALERT_SMTP_HOST=smtp.example.com:25
|
||||||
|
ALERT_SMTP_USER=smtp_user
|
||||||
|
ALERT_SMTP_PASSWORD=smtp_password
|
||||||
|
ALERT_SLACK_WEBHOOK=https://slack.com/webhook
|
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.env
|
||||||
|
config/alertmanager.yml
|
||||||
|
config/prometheus.yml
|
13
README.md
Normal file
13
README.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
```
|
||||||
|
# После этого задать переменные в файле .env
|
||||||
|
cp .env.example .env
|
||||||
|
|
||||||
|
# Сгенерировать конфиги на основе установленных в файле .env переменных
|
||||||
|
docker-compose -f docker-compose.config.yml run gomplate
|
||||||
|
|
||||||
|
# Запустить весь стек
|
||||||
|
docker-compose up
|
||||||
|
|
||||||
|
# Открыть Grafana
|
||||||
|
open http://localhost:3000
|
||||||
|
```
|
10
config/alertrules.yml
Normal file
10
config/alertrules.yml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
groups:
|
||||||
|
- name: display
|
||||||
|
rules:
|
||||||
|
- alert: DisplayDown
|
||||||
|
expr: state_connected == 0
|
||||||
|
for: 1h
|
||||||
|
labels:
|
||||||
|
severity: critical
|
||||||
|
annotations:
|
||||||
|
summary: "Display {{ $labels.display_id }} down"
|
12
config/grafana/provisioning/dashboards/dashboard.yml
Normal file
12
config/grafana/provisioning/dashboards/dashboard.yml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
apiVersion: 1
|
||||||
|
|
||||||
|
providers:
|
||||||
|
- name: "Prometheus"
|
||||||
|
orgId: 1
|
||||||
|
folder: ""
|
||||||
|
type: file
|
||||||
|
disableDeletion: false
|
||||||
|
editable: true
|
||||||
|
allowUiUpdates: true
|
||||||
|
options:
|
||||||
|
path: /etc/grafana/provisioning/dashboards
|
175
config/grafana/provisioning/dashboards/display.json
Normal file
175
config/grafana/provisioning/dashboards/display.json
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
{
|
||||||
|
"annotations": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"builtIn": 1,
|
||||||
|
"datasource": "-- Grafana --",
|
||||||
|
"enable": true,
|
||||||
|
"hide": true,
|
||||||
|
"iconColor": "rgba(0, 211, 255, 1)",
|
||||||
|
"name": "Annotations & Alerts",
|
||||||
|
"type": "dashboard"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"editable": true,
|
||||||
|
"gnetId": null,
|
||||||
|
"graphTooltip": 0,
|
||||||
|
"id": 1,
|
||||||
|
"links": [],
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"aliasColors": {},
|
||||||
|
"bars": false,
|
||||||
|
"dashLength": 10,
|
||||||
|
"dashes": false,
|
||||||
|
"datasource": null,
|
||||||
|
"fill": 0,
|
||||||
|
"fillGradient": 0,
|
||||||
|
"gridPos": {
|
||||||
|
"h": 10,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"hiddenSeries": false,
|
||||||
|
"id": 2,
|
||||||
|
"legend": {
|
||||||
|
"alignAsTable": true,
|
||||||
|
"avg": false,
|
||||||
|
"current": false,
|
||||||
|
"hideEmpty": false,
|
||||||
|
"hideZero": false,
|
||||||
|
"max": false,
|
||||||
|
"min": false,
|
||||||
|
"rightSide": true,
|
||||||
|
"show": true,
|
||||||
|
"total": false,
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"lines": true,
|
||||||
|
"linewidth": 1,
|
||||||
|
"nullPointMode": "null",
|
||||||
|
"options": {
|
||||||
|
"dataLinks": [
|
||||||
|
{
|
||||||
|
"title": "",
|
||||||
|
"url": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"percentage": false,
|
||||||
|
"pointradius": 2,
|
||||||
|
"points": false,
|
||||||
|
"renderer": "flot",
|
||||||
|
"repeat": "targets",
|
||||||
|
"repeatDirection": "v",
|
||||||
|
"seriesOverrides": [],
|
||||||
|
"spaceLength": 10,
|
||||||
|
"stack": false,
|
||||||
|
"steppedLine": false,
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "state_last_forecasts_count{target=~\"$targets\"}",
|
||||||
|
"format": "time_series",
|
||||||
|
"hide": false,
|
||||||
|
"instant": false,
|
||||||
|
"intervalFactor": 1,
|
||||||
|
"legendFormat": "Display {{display_id}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"thresholds": [],
|
||||||
|
"timeFrom": null,
|
||||||
|
"timeRegions": [],
|
||||||
|
"timeShift": null,
|
||||||
|
"title": "Last Forecasts Count for $targets",
|
||||||
|
"tooltip": {
|
||||||
|
"shared": true,
|
||||||
|
"sort": 0,
|
||||||
|
"value_type": "individual"
|
||||||
|
},
|
||||||
|
"transparent": true,
|
||||||
|
"type": "graph",
|
||||||
|
"xaxis": {
|
||||||
|
"buckets": null,
|
||||||
|
"mode": "time",
|
||||||
|
"name": null,
|
||||||
|
"show": true,
|
||||||
|
"values": []
|
||||||
|
},
|
||||||
|
"yaxes": [
|
||||||
|
{
|
||||||
|
"format": "short",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": "short",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"yaxis": {
|
||||||
|
"align": false,
|
||||||
|
"alignLevel": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"schemaVersion": 21,
|
||||||
|
"style": "dark",
|
||||||
|
"tags": [],
|
||||||
|
"templating": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"allValue": null,
|
||||||
|
"datasource": "Prometheus",
|
||||||
|
"definition": "state_up",
|
||||||
|
"hide": 0,
|
||||||
|
"includeAll": false,
|
||||||
|
"label": null,
|
||||||
|
"multi": true,
|
||||||
|
"name": "targets",
|
||||||
|
"options": [],
|
||||||
|
"query": "state_up",
|
||||||
|
"refresh": 1,
|
||||||
|
"regex": "/target=\\\"(.*)\\\"/",
|
||||||
|
"skipUrlSync": false,
|
||||||
|
"sort": 1,
|
||||||
|
"tagValuesQuery": "",
|
||||||
|
"tags": [],
|
||||||
|
"tagsQuery": "",
|
||||||
|
"type": "query",
|
||||||
|
"useTags": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"from": "now-15m",
|
||||||
|
"to": "now"
|
||||||
|
},
|
||||||
|
"timepicker": {
|
||||||
|
"refresh_intervals": [
|
||||||
|
"5s",
|
||||||
|
"10s",
|
||||||
|
"30s",
|
||||||
|
"1m",
|
||||||
|
"5m",
|
||||||
|
"15m",
|
||||||
|
"30m",
|
||||||
|
"1h",
|
||||||
|
"2h",
|
||||||
|
"1d"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"timezone": "",
|
||||||
|
"title": "Display State",
|
||||||
|
"uid": "WYXKDQUWz",
|
||||||
|
"version": 6
|
||||||
|
}
|
11
config/grafana/provisioning/datasources/datasource.yml
Normal file
11
config/grafana/provisioning/datasources/datasource.yml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
apiVersion: 1
|
||||||
|
|
||||||
|
datasources:
|
||||||
|
- name: Prometheus
|
||||||
|
type: prometheus
|
||||||
|
access: proxy
|
||||||
|
orgId: 1
|
||||||
|
url: http://prometheus:9090
|
||||||
|
basicAuth: false
|
||||||
|
isDefault: true
|
||||||
|
editable: true
|
19
docker-compose.config.yml
Normal file
19
docker-compose.config.yml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
version: "3"
|
||||||
|
|
||||||
|
services:
|
||||||
|
gomplate:
|
||||||
|
image: hairyhenderson/gomplate
|
||||||
|
container_name: gomplate
|
||||||
|
command: "--input-dir /templates --output-dir /config"
|
||||||
|
volumes:
|
||||||
|
- ./templates:/templates
|
||||||
|
- ./config:/config
|
||||||
|
environment:
|
||||||
|
- TARGETS=${TARGETS}
|
||||||
|
- SCRAPE_INTERVAL=${SCRAPE_INTERVAL}
|
||||||
|
- ALERT_EMAIL_TO=${ALERT_EMAIL_TO}
|
||||||
|
- ALERT_EMAIL_FROM=${ALERT_EMAIL_FROM}
|
||||||
|
- ALERT_SMTP_HOST=${ALERT_SMTP_HOST}
|
||||||
|
- ALERT_SMTP_USER=${ALERT_SMTP_USER}
|
||||||
|
- ALERT_SMTP_PASSWORD=${ALERT_SMTP_PASSWORD}
|
||||||
|
- ALERT_SLACK_WEBHOOK=${ALERT_SLACK_WEBHOOK}
|
65
docker-compose.yml
Normal file
65
docker-compose.yml
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
version: "3"
|
||||||
|
|
||||||
|
services:
|
||||||
|
prometheus:
|
||||||
|
image: prom/prometheus:v2.15.2
|
||||||
|
container_name: prometheus
|
||||||
|
restart: unless-stopped
|
||||||
|
command:
|
||||||
|
- "--config.file=/etc/prometheus/prometheus.yml"
|
||||||
|
- "--web.console.libraries=/etc/prometheus/console_libraries"
|
||||||
|
- "--web.console.templates=/etc/prometheus/consoles"
|
||||||
|
- "--web.enable-lifecycle"
|
||||||
|
- "--storage.tsdb.path=/prometheus"
|
||||||
|
- "--storage.tsdb.retention=200h"
|
||||||
|
networks:
|
||||||
|
- monitoring
|
||||||
|
volumes:
|
||||||
|
- prometheus_data:/prometheus
|
||||||
|
- ./config/prometheus.yml:/etc/prometheus/prometheus.yml
|
||||||
|
- ./config/alertrules.yml:/etc/prometheus/alertrules.yml
|
||||||
|
ports:
|
||||||
|
- 9090:9090
|
||||||
|
|
||||||
|
display:
|
||||||
|
build:
|
||||||
|
context: ./exporter
|
||||||
|
container_name: display
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- monitoring
|
||||||
|
|
||||||
|
alertmanager:
|
||||||
|
image: prom/alertmanager:v0.20.0
|
||||||
|
container_name: alertmanager
|
||||||
|
restart: unless-stopped
|
||||||
|
command:
|
||||||
|
- "--config.file=/etc/alertmanager/alertmanager.yml"
|
||||||
|
- "--storage.path=/alertmanager"
|
||||||
|
networks:
|
||||||
|
- monitoring
|
||||||
|
volumes:
|
||||||
|
- ./config/alertmanager.yml:/etc/alertmanager/alertmanager.yml
|
||||||
|
|
||||||
|
grafana:
|
||||||
|
image: grafana/grafana:6.5.3
|
||||||
|
container_name: grafana
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- monitoring
|
||||||
|
volumes:
|
||||||
|
- grafana_data:/var/lib/grafana
|
||||||
|
- ./config/grafana/provisioning:/etc/grafana/provisioning
|
||||||
|
environment:
|
||||||
|
- GF_SECURITY_ADMIN_USER=${GRAFANA_USER}
|
||||||
|
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
|
||||||
|
- GF_USERS_ALLOW_SIGN_UP=false
|
||||||
|
ports:
|
||||||
|
- 3000:3000
|
||||||
|
|
||||||
|
networks:
|
||||||
|
monitoring: {}
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
prometheus_data: {}
|
||||||
|
grafana_data: {}
|
11
exporter/Dockerfile
Normal file
11
exporter/Dockerfile
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
FROM library/python:3.7-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY requirements.txt /app/
|
||||||
|
RUN pip3.7 install -r requirements.txt
|
||||||
|
|
||||||
|
COPY exporter.py /app/
|
||||||
|
EXPOSE 9115
|
||||||
|
|
||||||
|
CMD ["python3", "exporter.py"]
|
93
exporter/exporter.py
Normal file
93
exporter/exporter.py
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import aiohttp
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from collections import namedtuple
|
||||||
|
from functools import reduce
|
||||||
|
from aiohttp import web
|
||||||
|
|
||||||
|
PORT = os.getenv("PORT", 9115)
|
||||||
|
|
||||||
|
UP_HELP = "# HELP state_up State Up/Down status."
|
||||||
|
UP_TYPE = "# TYPE state_up gauge"
|
||||||
|
UP_FORMAT = "state_up %d"
|
||||||
|
|
||||||
|
CONNECTED_HELP = "# HELP state_connected Connected status."
|
||||||
|
CONNECTED_TYPE = "# TYPE state_connected gauge"
|
||||||
|
CONNECTED_FORMAT = "state_connected{display_id=\"%d\"} %d"
|
||||||
|
|
||||||
|
LAST_DURATION_HELP = "# HELP state_last_duration Last duration."
|
||||||
|
LAST_DURATION_TYPE = "# TYPE state_last_duration gauge"
|
||||||
|
LAST_DURATION_FORMAT = "state_last_duration{display_id=\"%d\"} %d"
|
||||||
|
|
||||||
|
LAST_FORECASTS_COUNT_HELP = "# HELP state_last_forecasts_count Last forecasts count."
|
||||||
|
LAST_FORECASTS_COUNT_TYPE = "# TYPE state_last_forecasts_count gauge"
|
||||||
|
LAST_FORECASTS_COUNT_FORMAT = "state_last_forecasts_count{display_id=\"%d\"} %d"
|
||||||
|
|
||||||
|
Metrics = namedtuple("Metrics", "connected duration forecasts")
|
||||||
|
|
||||||
|
def reducer(metrics, state):
|
||||||
|
display_id = int(state["DisplayId"])
|
||||||
|
|
||||||
|
forecasts = -1
|
||||||
|
if state["Connected"]:
|
||||||
|
forecasts = int(state["LastForecastsCount"])
|
||||||
|
|
||||||
|
metrics.connected.append(CONNECTED_FORMAT % (display_id, int(state["Connected"])))
|
||||||
|
metrics.duration.append(LAST_DURATION_FORMAT % (display_id, int(state["LastDuration"])))
|
||||||
|
metrics.forecasts.append(LAST_FORECASTS_COUNT_FORMAT % (display_id, forecasts))
|
||||||
|
|
||||||
|
return metrics
|
||||||
|
|
||||||
|
def format_metrics(up, metrics):
|
||||||
|
if up:
|
||||||
|
return "\n".join([
|
||||||
|
UP_HELP,
|
||||||
|
UP_TYPE,
|
||||||
|
UP_FORMAT % int(up),
|
||||||
|
CONNECTED_HELP,
|
||||||
|
CONNECTED_TYPE,
|
||||||
|
"\n".join(metrics.connected),
|
||||||
|
LAST_DURATION_HELP,
|
||||||
|
LAST_DURATION_TYPE,
|
||||||
|
"\n".join(metrics.duration),
|
||||||
|
LAST_FORECASTS_COUNT_HELP,
|
||||||
|
LAST_FORECASTS_COUNT_TYPE,
|
||||||
|
"\n".join(metrics.forecasts),
|
||||||
|
])
|
||||||
|
|
||||||
|
return "\n".join([UP_HELP, UP_TYPE, UP_FORMAT % int(up)])
|
||||||
|
|
||||||
|
|
||||||
|
async def persistent_session(app):
|
||||||
|
app["PERSISTENT_SESSION"] = aiohttp.ClientSession()
|
||||||
|
yield
|
||||||
|
await app["PERSISTENT_SESSION"].close()
|
||||||
|
|
||||||
|
async def fetch(session, url):
|
||||||
|
async with session.get(url) as response:
|
||||||
|
data = await response.read()
|
||||||
|
return json.loads(data)
|
||||||
|
|
||||||
|
async def handle(request):
|
||||||
|
try:
|
||||||
|
target = request.query["target"]
|
||||||
|
session = request.app["PERSISTENT_SESSION"]
|
||||||
|
data = await fetch(session, target)
|
||||||
|
states = data["GetDisplaysStatesResult"]["DisplayState"]
|
||||||
|
metrics = reduce(reducer, states, Metrics([], [], []))
|
||||||
|
|
||||||
|
return web.Response(text=format_metrics(True, metrics))
|
||||||
|
except:
|
||||||
|
return web.Response(text=format_metrics(False, None))
|
||||||
|
|
||||||
|
logging.basicConfig(stream=sys.stdout)
|
||||||
|
|
||||||
|
app = web.Application()
|
||||||
|
app.router.add_get('/probe', handle)
|
||||||
|
app.cleanup_ctx.append(persistent_session)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
web.run_app(app, port=PORT)
|
1
exporter/requirements.txt
Normal file
1
exporter/requirements.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
aiohttp==3.6.2
|
17
templates/alertmanager.yml
Normal file
17
templates/alertmanager.yml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
route:
|
||||||
|
receiver: "default"
|
||||||
|
|
||||||
|
receivers:
|
||||||
|
- name: "default"
|
||||||
|
email_configs:
|
||||||
|
- to: "{{ .Env.ALERT_EMAIL_TO }}"
|
||||||
|
from: "{{ .Env.ALERT_EMAIL_FROM }}"
|
||||||
|
smarthost: "{{ .Env.ALERT_SMTP_HOST }}"
|
||||||
|
auth_username: "{{ .Env.ALERT_SMTP_USER }}"
|
||||||
|
auth_password: "{{ .Env.ALERT_SMTP_PASSWORD }}"
|
||||||
|
send_resolved: true
|
||||||
|
|
||||||
|
slack_configs:
|
||||||
|
- api_url: "{{ .Env.ALERT_SLACK_WEBHOOK }}"
|
||||||
|
send_resolved: true
|
||||||
|
|
38
templates/prometheus.yml
Normal file
38
templates/prometheus.yml
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
global:
|
||||||
|
scrape_interval: 15s
|
||||||
|
evaluation_interval: 15s
|
||||||
|
external_labels:
|
||||||
|
monitor: "docker-host"
|
||||||
|
|
||||||
|
rule_files:
|
||||||
|
- alertrules.yml
|
||||||
|
|
||||||
|
scrape_configs:
|
||||||
|
- job_name: "prometheus"
|
||||||
|
static_configs:
|
||||||
|
- targets: ["localhost:9090"]
|
||||||
|
|
||||||
|
- job_name: "grafana"
|
||||||
|
static_configs:
|
||||||
|
- targets: ["grafana:3000"]
|
||||||
|
|
||||||
|
- job_name: "display"
|
||||||
|
metrics_path: /probe
|
||||||
|
scrape_interval: {{ .Env.SCRAPE_INTERVAL }}
|
||||||
|
static_configs:
|
||||||
|
- targets:
|
||||||
|
{{ range (split .Env.TARGETS ",") }} - "{{ . }}"
|
||||||
|
{{ end }}
|
||||||
|
relabel_configs:
|
||||||
|
- source_labels: [__address__]
|
||||||
|
target_label: __param_target
|
||||||
|
- source_labels: [__param_target]
|
||||||
|
target_label: target
|
||||||
|
- target_label: __address__
|
||||||
|
replacement: display:9115
|
||||||
|
|
||||||
|
alerting:
|
||||||
|
alertmanagers:
|
||||||
|
- scheme: http
|
||||||
|
static_configs:
|
||||||
|
- targets: ["alertmanager:9093"]
|
Loading…
Reference in New Issue
Block a user