Grafana¶
Grafana is the main visualization tool of PerfShop. It aggregates the four telemetry sinks (Prometheus, Loki, Tempo, Pyroscope), exposes 10 shipped dashboards (5 Students + 5 Instructors) with a differentiated access policy, and allows interactive exploration in Explore mode.
Source of truth
This page is taken from the grafana block in the compose files, the provisioning files grafana/provisioning/datasources/*.yml, grafana/provisioning/dashboards/dashboards.yml, and the initialization script grafana-seed/seed.py.
Pinned version¶
PerfShop pins Grafana to version 12.0.0 (image grafana/grafana:12.0.0). The other observability components use latest; Grafana is the exception, because:
- The 10 shipped dashboards rely on panel features (flamegraph, traces table, TraceQL editor, auto-units format) introduced in Grafana 11+ and stabilized in 12.
- The Python seed uses the legacy API
POST /api/folders/{uid}/permissionswhich could evolve in a future major version. - The Pyroscope and Tempo datasources depend on native plugins whose configuration format may change.
Upgrading to a later version will require manual validation against the 10 dashboards.
Configuration via environment variables¶
environment:
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=${GF_SECURITY_ADMIN_PASSWORD:-perfshop}
- GF_USERS_ALLOW_SIGN_UP=false
- GF_SERVER_ROOT_URL=${PUBLIC_GRAFANA_URL:-http://localhost:3002}
- GF_PANELS_DISABLE_SANITIZE_HTML=true
- GF_AUTH_ANONYMOUS_ENABLED=true
- GF_AUTH_ANONYMOUS_ORG_NAME=Main Org.
- GF_AUTH_ANONYMOUS_ORG_ROLE=Viewer
- GF_USERS_DEFAULT_LANGUAGE=${PERFSHOP_UI_LOCALE:-en-US}
| Variable | Effect |
|---|---|
GF_SECURITY_ADMIN_USER / _PASSWORD |
Grafana superadmin account, used by the seed and by the instructor |
GF_USERS_ALLOW_SIGN_UP=false |
Disables self-signup from the UI |
GF_SERVER_ROOT_URL |
Public URL used for absolute links (emails, sharing, OAuth) |
GF_PANELS_DISABLE_SANITIZE_HTML=true |
Allows Text panels to use raw HTML (used by the "Guide" panels of the APM dashboards) |
GF_AUTH_ANONYMOUS_ENABLED=true |
Anonymous access enabled — the key of the pedagogical strategy |
GF_AUTH_ANONYMOUS_ORG_ROLE=Viewer |
Anonymous users only get the Viewer role (read-only) |
GF_USERS_DEFAULT_LANGUAGE |
Grafana UI locale (fr-FR or en-US), driven by PERFSHOP_UI_LOCALE |
The internal management port of Grafana is 3000, exposed on host port 3002 by default (GRAFANA_HTTP_PORT variable).
The Docker healthcheck queries /api/health every 10 seconds with start_period: 30s:
healthcheck:
test: ["CMD-SHELL", "wget -q --spider http://localhost:3000/api/health || exit 1"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
Provisioned datasources¶
Grafana automatically reads the grafana/provisioning/datasources/ folder at startup and declares four datasources, each with a stable UID used by the JSON dashboards.
flowchart LR
G["Grafana 12.0.0"]
G --> P["uid: prometheus<br/>http://prometheus:9090<br/>(default)"]
G --> L["uid: loki<br/>http://perfshop-loki:3100"]
G --> T["uid: tempo<br/>http://perfshop-tempo:3200"]
G --> PY["uid: pyroscope<br/>http://perfshop-pyroscope:4040"]
Prometheus datasource¶
- name: Prometheus
type: prometheus
uid: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
editable: true
jsonData:
timeInterval: "5s"
isDefault: true: this is the datasource selected by default in the panel editor and in Explore.timeInterval: "5s": tells Grafana about the actualscrape_intervalso thatrate()andirate()functions are properly calibrated when the user does not specify the window.
Loki datasource¶
- name: Loki
type: loki
uid: loki
access: proxy
url: http://perfshop-loki:3100
isDefault: false
editable: true
jsonData:
maxLines: 1000
timeout: 60
maxLines: 1000: limits the number of lines returned by default in log panels (can be overridden per panel).timeout: 60: query timeout on the Grafana side, in seconds.
Tempo datasource¶
- name: Tempo
type: tempo
uid: tempo
access: proxy
url: http://perfshop-tempo:3200
isDefault: false
editable: true
jsonData:
httpMethod: GET
tracesToLogsV2:
datasourceUid: loki
spanStartTimeShift: "-1m"
spanEndTimeShift: "1m"
tags:
- key: service.name
value: app
filterByTraceID: true
filterBySpanID: false
customQuery: false
tracesToMetrics:
datasourceUid: prometheus
spanStartTimeShift: "-1m"
spanEndTimeShift: "1m"
tags:
- key: service.name
value: app
serviceMap:
datasourceUid: prometheus
search:
hide: false
nodeGraph:
enabled: true
lokiSearch:
datasourceUid: loki
This is the most richly configured datasource. Three cross-correlations are enabled:
tracesToLogsV2→ Loki: from any span in the trace view, you can click to open Loki with a±1 mintime filter around the span and an automatictraceIDfilter.tracesToMetrics→ Prometheus: from a span, you can display the Prometheus metrics for the operation over the same time window.serviceMap: uses thetraces_service_graph_*metrics produced by Tempo'smetrics_generatorand stored in Prometheus to generate the service map.nodeGraph: enabled: true: enables the graph visualization for traces.lokiSearch: enables searching for correlated logs directly from the Tempo editor.
Pyroscope datasource¶
- name: Pyroscope
type: grafana-pyroscope-datasource
uid: pyroscope
access: proxy
url: http://perfshop-pyroscope:4040
isDefault: false
editable: true
Minimal configuration — Pyroscope has no special correlations to configure; the native Grafana plugin handles everything.
Folders and dashboard provisioning¶
Dashboards are organized into two distinct folders in Grafana, with two separate provisioning providers.
# grafana/provisioning/dashboards/dashboards.yml
apiVersion: 1
providers:
- name: 'PerfShop Dashboards — Élèves'
orgId: 1
folder: 'Élèves'
folderUid: 'perfshop-eleves'
type: file
disableDeletion: false
updateIntervalSeconds: 10
allowUiUpdates: true
options:
path: /etc/grafana/dashboards/eleves
- name: 'PerfShop Dashboards — Formateurs'
orgId: 1
folder: 'Formateurs'
folderUid: 'perfshop-formateurs'
type: file
disableDeletion: false
updateIntervalSeconds: 10
allowUiUpdates: true
options:
path: /etc/grafana/dashboards/formateurs
| Aspect | Students folder | Instructors folder |
|---|---|---|
folderUid |
perfshop-eleves |
perfshop-formateurs |
| Source path | ./grafana/dashboards/eleves/ |
./grafana/dashboards/formateurs/ |
| Number of dashboards | 5 | 5 |
| ACL after seed | Inherits the Viewer role (visible without login) | Admin only (set by the seed) |
updateIntervalSeconds |
10 (Grafana re-reads files every 10 s) | 10 |
allowUiUpdates |
true | true |
disableDeletion |
false | false |
The bind mounts in compose:
volumes:
- grafana-data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning
- ./grafana/dashboards:/etc/grafana/dashboards
Access strategy — the key element¶
PerfShop cleanly separates two user populations on Grafana:
flowchart TB
V["Anonymous user<br/>(no login)"]
A["Logged-in admin<br/>(login admin/perfshop)"]
subgraph G["Grafana"]
direction TB
FE["Students folder<br/>(Viewer visible)"]
FF["Instructors folder<br/>(Admin-only ACL)"]
end
V -->|GF_AUTH_ANONYMOUS_ENABLED=true<br/>Role=Viewer| FE
V -.-|❌ HTTP 403| FF
A -->|✅| FE
A -->|✅| FF
How it works in practice¶
GF_AUTH_ANONYMOUS_ENABLED=true+GF_AUTH_ANONYMOUS_ORG_ROLE=Viewerdeclared as environment variables → any visitor reaching the Grafana URL is automatically authenticated as an anonymous user with the Viewer role in the "Main Org." organization.- By default, Grafana applies organizational inheritance: a Viewer sees all folders in their org.
- The
perfshop-grafana-seedseed applies an explicit ACL on the Instructors folder:Admin only. This ACL replaces the Viewer inheritance for this folder — as a result, anonymous users see the Students folder but not the Instructors folder.
The grafana-seed/seed.py seed¶
The perfshop-grafana-seed service is a python:3.11-slim container that:
- Installs
requests==2.31.0at startup. - Waits for Grafana to respond on
/api/health(retry loop, timeout 7m30). - Checks that the Instructors folder is properly provisioned (retry loop on
GET /api/folders/{uid}). - Sets the Admin-only ACL via
POST /api/folders/perfshop-formateurs/permissions:permission: 4= Admin (1=View, 2=Edit, 4=Admin). A single item is enough: the API replaces the entire existing ACL. - Verifies that the ACL is effective by making a call without authentication on
/api/folders/perfshop-formateurs— must return 401 or 403. - Sets the home dashboard to
perfshop-general-v1("General Containers View") viaPATCH /api/org/preferenceswith{"homeDashboardUID": "perfshop-general-v1"}.
The seed is marked restart: "no" (one-shot) and depends_on: grafana: condition: service_healthy.
sequenceDiagram
autonumber
participant S as perfshop-grafana-seed
participant G as Grafana
S->>G: GET /api/health<br/>(loop 5s × 90)
G-->>S: 200 OK
S->>G: GET /api/folders/perfshop-formateurs<br/>(admin auth)<br/>(loop 5s × 24)
G-->>S: 200 (folder provisioned)
S->>G: POST /api/folders/perfshop-formateurs/permissions<br/>{"items":[{"role":"Admin","permission":4}]}
G-->>S: 200 OK
S->>G: GET /api/folders/perfshop-formateurs<br/>(WITHOUT auth)
G-->>S: 401 or 403
Note over S: ✓ ACL effective
S->>G: PATCH /api/org/preferences<br/>{"homeDashboardUID":"perfshop-general-v1"}
G-->>S: 200 OK
Note over S: Exit code 0
Seed idempotency
The seed can be re-run manually at any time (docker compose up perfshop-grafana-seed) without side effects: POST /api/folders/{uid}/permissions replaces the entire ACL, and PATCH /api/org/preferences is just an update.
Volumes and persistent data¶
| Volume | Mount | Content |
|---|---|---|
grafana-data (named volume) |
/var/lib/grafana |
Grafana SQLite database, user sessions, installed plugins, manually created dashboards, custom ACLs |
./grafana/provisioning (bind mount) |
/etc/grafana/provisioning |
Provisioning files for datasources and dashboards (read-only in practice) |
./grafana/dashboards (bind mount) |
/etc/grafana/dashboards |
10 shipped JSON files (Students + Instructors) |
Modifying a shipped dashboard
The shipped JSON dashboards are reloaded every 10 s by Grafana (updateIntervalSeconds: 10). A change made in the UI is kept for 10 s then overwritten by the JSON file content. To durably modify a shipped dashboard, edit the corresponding JSON file in grafana/dashboards/{eleves|formateurs}/ and repackage it in the deployment.
To go further¶
- Overview — global observability flow
- Shipped dashboards — panel-by-panel detail of the 10 dashboards
- Prometheus — main datasource
- Loki, Tempo, Pyroscope — other datasources