## Zadanie 1 [3p] ✅

### `Ło japidi` czyli początki bywają najtrudniejsze...
Potrzebujemy najpierw mieć grafanę zainstalowaną + prometheusza!!
```bash
# Debian/Ubuntu:
# https://grafana.com/docs/grafana/latest/setup-grafana/installation/debian/
# sudo systemctl start grafana-server.service
# sudo systemctl status grafana-server.service
sudo pacman -S grafana
sudo systemctl status grafana
sudo systemctl start grafana
```
jeśli wszystko dobrze pójdzie to pod http://localhost:3000 zobaczymy login page'a.
(Login i hasło to: `admin`)
potem po lewej stronie szukamy data source'y i dodajemy nowe connection.

Wybieramy opcję prometheus i wpisujemy odpowiedni adres

Klikamy na dole `save` i powinno być git.
Dodajemy sobie nowy dashboard i pamiętamy wybrać w source prometheusza (i mamy cacy setup)

Dobra teraz pora na gówno
Robimy wykresiki
a) Przygotuj w Grafanie tablicę rozdzielczą z podstawowymi informacjami o komputerze, na którym
pracujesz:
- Uśrednione zużycie procesora w oknie 1 min.
Wybieramy wykresik TimeSeries i wpisujemy jako query
```
avg(rate(node_cpu_seconds_total{mode!="idle"}[1m])) * 100
```

tak to powinno wyglądać, po lewej możemy też zaznaczyć by było to w procentach

- Wskaźnik aktualnego zużycia pamięci RAM.
Wybieramy Gauge
```
(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100
```
I tyle wsm

- Diagram kołowy zajętej i wolnej przestrzeni dyskowej.
Bierzemy Pie chart
i wklepujemy te Querki
```
node_filesystem_size_bytes{mountpoint="/"}
node_filesystem_free_bytes{mountpoint="/"}
```

Można się pobawić w legendę by zwracała to co chcemy
- Ruch przychodzący i wychodzący z karty sieciowej w postaci liczby wyświetlanej z jednostkami.
Bierzemy Stat
Zgodnie ze wskazówkę wklejamy
```
rate(node_network_receive_bytes_total[1m])
rate(node_network_transmit_bytes_total[1m])
```

Warto pamiętać by zmienić typ wartości na bytes/sec

Koniec końców tak wygląda mój dashboard :DD

:::spoiler
```json
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": 0,
"links": [],
"panels": [
{
"datasource": {
"type": "prometheus",
"uid": "bf9vgku47670ge"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "red",
"value": 80
}
]
},
"unit": "decbytes"
},
"overrides": []
},
"gridPos": {
"h": 9,
"w": 16,
"x": 0,
"y": 0
},
"id": 4,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.3.1",
"targets": [
{
"editorMode": "code",
"expr": "rate(node_network_receive_bytes_total[1m])",
"legendFormat": "__auto",
"range": true,
"refId": "A"
},
{
"datasource": {
"type": "prometheus",
"uid": "bf9vgku47670ge"
},
"editorMode": "code",
"expr": "rate(node_network_transmit_bytes_total[1m])",
"hide": false,
"instant": false,
"legendFormat": "__auto",
"range": true,
"refId": "B"
}
],
"title": "Network",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "bf9vgku47670ge"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "red",
"value": 80
}
]
},
"unit": "percent"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 5,
"x": 0,
"y": 9
},
"id": 1,
"options": {
"minVizHeight": 75,
"minVizWidth": 75,
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showThresholdLabels": false,
"showThresholdMarkers": true,
"sizing": "auto"
},
"pluginVersion": "12.3.1",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "bf9vgku47670ge"
},
"editorMode": "code",
"expr": "avg(rate(node_cpu_seconds_total{mode!=\"idle\"}[1m])) * 100",
"legendFormat": "__auto",
"range": true,
"refId": "A"
}
],
"title": "Avg. CPU usage",
"type": "gauge"
},
{
"datasource": {
"type": "prometheus",
"uid": "bf9vgku47670ge"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 6,
"x": 5,
"y": 9
},
"id": 2,
"options": {
"minVizHeight": 75,
"minVizWidth": 75,
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showThresholdLabels": false,
"showThresholdMarkers": true,
"sizing": "auto"
},
"pluginVersion": "12.3.1",
"targets": [
{
"editorMode": "code",
"expr": "(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100",
"legendFormat": "__auto",
"range": true,
"refId": "A"
}
],
"title": "RAM usage",
"type": "gauge"
},
{
"datasource": {
"type": "prometheus",
"uid": "bf9vgku47670ge"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
}
},
"mappings": []
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 5,
"x": 11,
"y": 9
},
"id": 3,
"options": {
"displayLabels": [],
"legend": {
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"pieType": "pie",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"sort": "desc",
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"pluginVersion": "12.3.1",
"targets": [
{
"editorMode": "code",
"expr": "node_filesystem_free_bytes{mountpoint=\"/\"}\n",
"legendFormat": "Free space",
"range": true,
"refId": "A"
},
{
"datasource": {
"type": "prometheus",
"uid": "bf9vgku47670ge"
},
"editorMode": "code",
"expr": "node_filesystem_size_bytes{mountpoint=\"/\"} ",
"hide": false,
"instant": false,
"legendFormat": "Used space",
"range": true,
"refId": "B"
}
],
"title": "Memory",
"type": "piechart"
}
],
"preload": false,
"schemaVersion": 42,
"tags": [],
"templating": {
"list": []
},
"time": {
"from": "now-5m",
"to": "now"
},
"timepicker": {},
"timezone": "browser",
"title": "Zadanie 1",
"uid": "ad4898h",
"version": 9
}
```
:::
b) Zademonstruj jak wyeksportować i następnie zaimportować tablicę nawigacyjną Grafany w postaci tekstowej.
EEEE nie wiem czy o to chodzi alee
wybierając dashboard -> settings -> JSON model mamy nasz dashboard

Jak go skopiujemy i wkleimy jako nowy dashboard to będziemy mieli to samo :DD
Można też po wyjściu z trybu edycji kliknąć jebitny przycisk eksport ale to by było za łatwe...
I potem się klika dashboard import i podaje plik/kod.
c) Pokaż, w jaki sposób bez kopiuj-wklej, w łatwy sposób, zmieniać okno uśredniania zużycia procesora z 1min na 5min i 10min
Tworzymy nowe Variable w settings (najlepiej typ custom)

i w naszym wykresie podmieniamy (albo najlepiej dodać) nowy source (korzystanie ze zmiennych wsm jak w bashu)

I teraz w lewym górnym rogu możemy sterować jak duże ma być okienko :DD

## Zadanie 2 [1p] ✅

Okej to zacznę od `dashboard link`. W skrócie pozwala on przechowywać przedział czasowy oraz zmienne jak skaczemy między dashboardami (da się również ustawić jakbyśmy korzystali z linku). Tworzymy go prosto, w dashboardzie klikamy settings a potem links


Możemy ustawiać tagi itp jakie dashboardy mamy widzieć ale głównie obchodzą nas opcje:
- `Include current time range`
- `Include current template variable values`
Jak to zapiszemy to tak wygląda nasz dashboard

łatwo przetestować, że zmieniając range'a albo zmienną po kliknięciu mamy te same wartości :DD
Teraz pora na `panel link`.
W panelu po lewej mamy opcje dodania linku


Wsm podajemy nazwę, link i to ma pozwolić np użytkownikom zostawić jakieś linki jak sobie poradzić z poszczególnymi problemami.

Tutaj można je kliknąć
Różnice to ee nawigacja między dashboardami vs helper dla użytkownika w jednym z wykresów (tutaj można dać linki do dashboardów innych ale still)
## Zadanie 3 [1p] ✅

Skrypcioor
```python
import os
import time
import random
from prometheus_client import start_http_server, Histogram
WRITE_TIME = Histogram(
"file_write_duration_seconds",
"Czas zapisu pliku na dysk",
buckets=(0.001, 0.005, 0.01, 0.05, 0.1, 0.2, 0.5, 1, 2, 5)
)
DATA_DIR = "/tmp"
os.makedirs(DATA_DIR, exist_ok=True)
if __name__ == "__main__":
start_http_server(9101)
print("Exporter działa na http://localhost:9101/metrics")
while True:
size = random.randint(1024, 10 * 1024 * 1024) # 1KB – 10MB
payload = os.urandom(size)
filename = f"{DATA_DIR}/blob_{int(time.time()*1000)}.bin"
start = time.perf_counter()
with open(filename, "wb") as f:
f.write(payload)
f.flush()
os.fsync(f.fileno())
duration = time.perf_counter() - start
WRITE_TIME.observe(duration)
time.sleep(0.3)
```
Nie ma co tłumaczyć bo już to robiliśmy.
Jedyne co to pamiętajcie dodać do `prometheus.yml`
```
- job_name: "fileWriter"
static_configs:
- targets: ['localhost:9101']
```
W grafanie wybieramy heatmape

I chyba trzeba zaznaczyć Calculate from data (wtedy lepiej wygląda imo)
Ja bym taką komendę dał, bo ktoś uciął....
`sum(rate(file_write_duration_seconds_bucket[1m])) by (le)`
## Zadanie 4 [2p] ✅

Ponoć można wymusić na dockerze wystawianie metryk (sic!)
dodajemy do `/etc/docker/daemon.json`
```
{
"metrics-addr": "localhost:9323"
}
```
Szybki restart
`sudo systemctl restart docker`
i powinniśmy widzieć metryki na
`curl http://localhost:9323/metrics`
Pamiętajcie dodać do `prometheus.yml`
```
- job_name: "docker_engine"
static_configs:
- targets: ["localhost:9323"]
```
Jaki dashboard zrobić?
Zapytajmy chata!!

Takie coś razem z nim zrobiłem
mega proste rzeczy
:pepopray: że nie będzie trzeba tego pokazywać
tutaj wszystkie querki jakie mam
- `up{job="docker_engine", instance="localhost:9323"}`
- `increase(builder_builds_triggered_total{instance="localhost:9323"}[15m])`
- `sum by (action) (rate(engine_daemon_container_actions_seconds_count[1m]))`
- `sum by (action) (rate(engine_daemon_container_actions_seconds_sum[5m])) / sum by (action) (rate(engine_daemon_container_actions_seconds_count[5m]))`
Tutaj jakieś op rzeczy do generowania sztucznie danych
`for i in {1..50}; do docker run --rm alpine true; done`
## Zadanie 5 [2p]


Holy shit logi działają!!!
Dobra japidi one last time
potrzebujemy takie rzeczy jak: loki, promtail, grafana
```bash!
sudo pacman -S loki
# promtail powininen przyjść z prometheuszem
```
Teraz trzeba zrobić setup lokiego oraz promtaila
```yaml
#/etc/loki/loki.yaml
auth_enabled: false
limits_config:
allow_structured_metadata: false
reject_old_samples: false
server:
http_listen_port: 3100
log_level: info
common:
instance_addr: 127.0.0.1
path_prefix: /tmp/loki
replication_factor: 1
ring:
kvstore:
store: inmemory
storage:
filesystem:
chunks_directory: /tmp/loki/chunks
rules_directory: /tmp/loki/rules
schema_config:
configs:
- from: 1995-01-01
store: boltdb-shipper
object_store: filesystem
schema: v11
index:
prefix: index_
period: 24h
```
```yaml
#/etc/loki/promtail.yaml
server:
http_listen_port: 9080
grpc_listen_port: 0
positions:
filename: /tmp/promtail_positions.yaml
clients:
- url: http://localhost:3100/loki/api/v1/push
scrape_configs:
- job_name: nasa_http
static_configs:
- targets: [localhost]
labels:
job: nasa_http
__path__: /var/log/http_logs/*.log
pipeline_stages:
- regex:
expression: '^(?P<ip>\S+) \S+ \S+ \[(?P<ts>[^\]]+)\] "(?P<method>\S+) (?P<path>\S+) \S+" (?P<status>\d{3}) (?P<bytes>\S+)'
- timestamp:
source: ts
format: '02/Jan/2006:15:04:05 -0700'
- labels:
ip:
status:
method:
```
Teraz wsm trzeba pobrać logi i wrzucić je do `/var/log/http_logs/*.log`
Teraz standardowy setup
```bash!
sudo systemctl start loki
sudo systemctl start promtail
sudo systemctl start grafana
```
Dodajemy jako source loki w grafanie i sanity check to w zakładce explore sprawdźmy czy działa

Teraz j*bane dashboardy
Pamiętajcie zmienić datę na 1995!! (polecam przedział 01/08/1995 -> 15/08/1995)
a)
```
sum(rate({job="nasa_http"}[1m]))
```
b)
```
sum by (status) (
rate({job="nasa_http"}[5m])
)
```
## Zadanie 6 [2p]

ehhh nie chce mi się już :C
Kodzik w pythonie robiący regresję
```py
import time
import numpy as np
import requests
from prometheus_client import start_http_server, Gauge
PROM_URL = "http://localhost:9090"
STEP_SECONDS = 15 # co ile próbkujemy dane z query_range
HISTORY_MINUTES = 30 # ile historii bierzemy do modelu
HORIZON_MINUTES = 5 # ile minut do przodu prognozujemy
UPDATE_EVERY_SECONDS = 30 # co ile odświeżamy predykcję
# Metryka wynikowa (predykcja)
pred_cpu = Gauge(
"predicted_cpu_percent",
"Predykcja zuzycia CPU w procentach (regresja liniowa)",
["horizon"]
)
# Ten query ma zwracać JEDNĄ serię (jak masz wiele instancji, wybierz konkretną etykietą instance)
CPU_QUERY = '100 * (1 - avg(rate(node_cpu_seconds_total{mode="idle"}[1m])))'
def query_range(promql: str, start_ts: float, end_ts: float, step_seconds: int):
r = requests.get(
f"{PROM_URL}/api/v1/query_range",
params={
"query": promql,
"start": start_ts,
"end": end_ts,
"step": step_seconds
},
timeout=10
)
r.raise_for_status()
data = r.json()
if data.get("status") != "success":
raise RuntimeError(f"Prometheus status != success: {data}")
result = data["data"]["result"]
if not result:
raise RuntimeError("Brak danych z query_range (result pusty).")
# bierzemy pierwszą serię
values = result[0]["values"] # [[ts, "value"], ...]
ts = np.array([float(v[0]) for v in values], dtype=float)
y = np.array([float(v[1]) for v in values], dtype=float)
return ts, y
def linear_forecast(ts: np.ndarray, y: np.ndarray, horizon_minutes: int):
# regresja po czasie; stabilniej centrować czas
t0 = ts[0]
x = ts - t0 # sekundy od startu okna
# dopasuj linię y = a*x + b
a, b = np.polyfit(x, y, 1)
future_x = (ts[-1] - t0) + horizon_minutes * 60.0
y_hat = a * future_x + b
return float(y_hat)
if __name__ == "__main__":
start_http_server(9102)
print("Predictor-exporter działa na http://localhost:9102/metrics")
while True:
try:
end_ts = time.time()
start_ts = end_ts - HISTORY_MINUTES * 60.0
ts, y = query_range(CPU_QUERY, start_ts, end_ts, STEP_SECONDS)
print(f"ts: {ts}, y:{y}")
# jak masz dużo szumu: można uśrednić krocząco (opcjonalnie)
# y = np.convolve(y, np.ones(5)/5, mode="same")
y_hat = linear_forecast(ts, y, HORIZON_MINUTES)
# przytnij do sensownych granic 0..100 (CPU%)
y_hat = max(0.0, min(100.0, y_hat))
print(f"y_hat:{y_hat}")
pred_cpu.labels(horizon=f"{HORIZON_MINUTES}m").set(y_hat)
except Exception as e:
# w razie błędu nie wywalaj procesu – tylko log
print("Błąd predykcji:", e)
time.sleep(UPDATE_EVERY_SECONDS)
```
Nic nie ma ciekawego wsm
Dodajemy takie fiku miku do prometheusza
```
- job_name: "predictor"
static_configs:
- targets: ["localhost:9102"]
```
`100 * (1 - avg(rate(node_cpu_seconds_total{mode="idle"}[1m])))`
`predicted_cpu_percent{horizon="5m"} offset 5m`
Nowy keyword! `offset x` pozwala nam cofnąć czas o `x` jednostek!!
## Zadanie 7 [3p]✅

japidi
Zacznijmy od tego, że warto pobrać i zainstalować toole:
1. https://github.com/MacroPower/prometheus_video_renderer
2. https://github.com/MacroPower/grafana-image-renderer-sdk-go
Oraz pobrać jakiś filmik np. https://www.youtube.com/watch?v=fC7oUOUEEi4
Teraz w `prometheus_video_renderer`
```bash!
mkdir -p frames/stick
ffmpeg -i 'stick.mp4' -vf 'scale=180:100' -vsync 0 'frames/stick/out%06d.png'
mkdir -p metrics/stick
export DATA=$(date +%s000)
go run ./cmd/prometheus_video_renderer --mode=rgb --project=stick --frames-location=frames --metrics-location=metrics --frames-per-file=120 --frame-duration=5m --scrape-interval=1 --start-time=$DATA
bash scripts/load.sh stick
sudo chmod 777 data
docker compose up -d
```
Teraz w `grafana_image_renderer_sdk_go`
```bash!
export FRAME_COUNT=$(ls ../prometheus_video_renderer/frames/stick | wc -l)
export OUT_DIR=../prometheus_video_renderer/frames_capture/stick
mkdir -p "$OUT_DIR"
go run ./cmd/grafana-image-renderer-cli sequence \
--api-url=http://localhost:3000 \
--api-key-or-basic-auth=admin:admin \
--dashboard=pvr-dash-8-rgb \
--panel=2 \
--start-time=$DATA \
--frame-interval=5m \
--frames=1-$FRAME_COUNT \
--width=1800 --height=1000 \
--out-directory="$OUT_DIR"
ffmpeg -framerate 30 -i "$OUT_DIR/%06d.png" -c:v libx264 -pix_fmt yuv420p stick-render.mp4
```
I w ten sposób powstaje nam plik `stick-render.mp4`, który ma nagranie naszego filmiku z grafany:D
Jano niby mówił, że można animować w grafanie, ja nic takiego nie znalazłem poza tym wyżej (przynajmniej dla danych). Można niby zrobić tak, że po tym exporcie pokazać, że te dane faktycznie są lokalnie na grafanie w stylu od `2026-01-11 22:46:00` do jakiegoś czasu co 5min, ale kurde ja nie widzę łatwej metody na pokazanie tego filmiku inaczej:c