---
# System prepended metadata

title: DevOps 11

---

## Zadanie 1 [3p] ✅
![image](https://hackmd.io/_uploads/H1CFP7Wr-l.png)

### `Ł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.
![image](https://hackmd.io/_uploads/S1WSWDbHbe.png)

Wybieramy opcję prometheus i wpisujemy odpowiedni adres
![image](https://hackmd.io/_uploads/S1qPZP-rWx.png)

Klikamy na dole `save` i powinno być git.
Dodajemy sobie nowy dashboard i pamiętamy wybrać w source prometheusza (i mamy cacy setup)
![image](https://hackmd.io/_uploads/Bke9WDZH-l.png)

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
```
![image](https://hackmd.io/_uploads/HkVGKvbBbl.png)
tak to powinno wyglądać, po lewej możemy też zaznaczyć by było to w procentach
![image](https://hackmd.io/_uploads/BJ9mFD-BWg.png)

- Wskaźnik aktualnego zużycia pamięci RAM.
Wybieramy Gauge
```
(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100
```
I tyle wsm
![image](https://hackmd.io/_uploads/SyIPFD-S-x.png)

- 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="/"}
```
![image](https://hackmd.io/_uploads/HyeiKD-rWe.png)

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])
```
![image](https://hackmd.io/_uploads/rJwM5w-SZg.png)

Warto pamiętać by zmienić typ wartości na bytes/sec
![image](https://hackmd.io/_uploads/HJIXcPWrbe.png)

Koniec końców tak wygląda mój dashboard :DD 
![image](https://hackmd.io/_uploads/SJKYcPWHbg.png)

:::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
![image](https://hackmd.io/_uploads/ryKQoD-rbx.png)

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)
![image](https://hackmd.io/_uploads/rk8znPbrbe.png)
i w naszym wykresie podmieniamy (albo najlepiej dodać) nowy source (korzystanie ze zmiennych wsm jak w bashu)
![image](https://hackmd.io/_uploads/S1CnnDZB-g.png)

I teraz w lewym górnym rogu możemy sterować jak duże ma być okienko :DD 
![image](https://hackmd.io/_uploads/rJ3yavWHZe.png)




## Zadanie 2 [1p] ✅
![image](https://hackmd.io/_uploads/HkCsDmWS-g.png)
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
![image](https://hackmd.io/_uploads/rkanADbBZx.png)
![image](https://hackmd.io/_uploads/r1JJ1dWr-l.png)
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
![image](https://hackmd.io/_uploads/S1wX1uZSbe.png)
ł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
![image](https://hackmd.io/_uploads/SkHJl_WSZl.png)
![image](https://hackmd.io/_uploads/rkyxgdWr-x.png)
Wsm podajemy nazwę, link i to ma pozwolić np użytkownikom zostawić jakieś linki jak sobie poradzić z poszczególnymi problemami. 
![image](https://hackmd.io/_uploads/HyRWxObSWg.png)
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] ✅
![image](https://hackmd.io/_uploads/rJU2DXWH-l.png)
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
![image](https://hackmd.io/_uploads/rk5DQdbrZe.png)
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] ✅
![image](https://hackmd.io/_uploads/SJAhwXZBWx.png)

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!!
![image](https://hackmd.io/_uploads/rJ8ApOZSbl.png)
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]
![image](https://hackmd.io/_uploads/rk56PQbrZl.png)
![image](https://hackmd.io/_uploads/B1zAwQWrZg.png)



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

![image](https://hackmd.io/_uploads/HynIOk4HWx.png)

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]
![image](https://hackmd.io/_uploads/SJ0Aw7ZS-l.png)

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]✅
![image](https://hackmd.io/_uploads/H1Lydm-H-l.png)
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