Skip to content

Commit 924e4de

Browse files
committed
Replace global dashboard with activity dashboard
The activity.jsonnet dashboard is extracting four panels from the jupyterhub.jsonnet dashboard. The running servers panel and daily/weekly/monhtly active users panels. By doing this, we get a dashboard with only those four panels, grouped into a row. This row is then made repeatable over the prometheus datasource variable. With a repeating row, we could end up with very many rows, making any panels below hard to get to. Due to that, it makes sense to use repeating rows in a dedicated dashboard. The global dashboard that is being deleted made use of hardcoded state in the grafana instance it was being deployed to. This then also required regular re-deploys of the dashboard just to update the hardcoded entries. Further, the global dashboard couldn't be pre-rendered and re-used across grafana instances - because it had grafance instance specific state within it. With the new Activity dashboard, we can avoid all this complexity and possibly publish pre-rendered dashboards that any grafana instance can install directly.
1 parent dfd56e4 commit 924e4de

File tree

9 files changed

+154
-184
lines changed

9 files changed

+154
-184
lines changed

.github/workflows/test.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,10 @@ jobs:
4343
- name: Render dashboards
4444
run: |
4545
mkdir rendered-dashboards
46-
dashboard_folders="dashboards global-dashboards"
46+
dashboard_folders="dashboards"
4747
for file in `find $dashboard_folders -name '*.jsonnet'`
4848
do
49-
jsonnet -J vendor --tla-code 'datasources=["prometheus-test"]' --output-file rendered-dashboards/`basename $file` $file
49+
jsonnet -J vendor --output-file rendered-dashboards/`basename $file` $file
5050
done
5151
5252
- name: Install dashboard-linter

.pre-commit-config.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ repos:
1616
- id: jsonnet-format
1717
# To workaround https://github.com/google/go-jsonnet/issues/591, we run
1818
# the jsonnet-lint hook once per .jsonnet / .libsonnet file.
19+
- id: jsonnet-lint
20+
pass_filenames: false
21+
name: jsonnet-lint cluster.jsonnet
22+
args: [-J, vendor, dashboards/activity.jsonnet]
1923
- id: jsonnet-lint
2024
pass_filenames: false
2125
name: jsonnet-lint cluster.jsonnet
@@ -44,10 +48,6 @@ repos:
4448
pass_filenames: false
4549
name: jsonnet-lint user.jsonnet
4650
args: [-J, vendor, dashboards/user.jsonnet]
47-
- id: jsonnet-lint
48-
pass_filenames: false
49-
name: jsonnet-lint global-usage-stats.jsonnet
50-
args: [-J, vendor, global-dashboards/global-usage-stats.jsonnet]
5151

5252
# Autoformat: Python code, syntax patterns are modernized
5353
- repo: https://github.com/asottile/pyupgrade

dashboards/activity.jsonnet

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
#!/usr/bin/env -S jsonnet -J ../vendor
2+
local grafonnet = import 'grafonnet/main.libsonnet';
3+
local dashboard = grafonnet.dashboard;
4+
local ts = grafonnet.panel.timeSeries;
5+
local prometheus = grafonnet.query.prometheus;
6+
local row = grafonnet.panel.row;
7+
local var = grafonnet.dashboard.variable;
8+
9+
local common = import './common.libsonnet';
10+
11+
local activeUserTsOptions =
12+
common.tsOptions
13+
+ ts.standardOptions.withDecimals(0)
14+
// stacking is used here as the total number of users is as relevant as the
15+
// number of users per hub
16+
+ ts.fieldConfig.defaults.custom.stacking.withMode('normal')
17+
// stepAfter is used here as these metrics indicate what has happened the time
18+
// before the metric is read
19+
+ ts.fieldConfig.defaults.custom.withLineInterpolation('stepAfter')
20+
+ ts.panelOptions.withDescription(
21+
|||
22+
Number of unique users who were active within the preceding period.
23+
|||
24+
)
25+
;
26+
27+
local runningServers =
28+
common.tsOptions
29+
+ ts.new('Running Servers')
30+
+ ts.standardOptions.withDecimals(0)
31+
+ ts.fieldConfig.defaults.custom.stacking.withMode('normal')
32+
+ ts.fieldConfig.defaults.custom.withLineInterpolation('stepBefore')
33+
+ ts.panelOptions.withDescription(
34+
|||
35+
Number of running user servers at any given time.
36+
37+
Note that a single user could have multiple servers running if the
38+
JupyterHub is configured with `c.JupyterHub.allow_named_servers = True`.
39+
|||
40+
)
41+
+ ts.queryOptions.withTargets([
42+
prometheus.new(
43+
'$PROMETHEUS_DS',
44+
|||
45+
max(
46+
jupyterhub_running_servers{namespace=~"$hub"}
47+
) by (namespace)
48+
|||
49+
)
50+
+ prometheus.withLegendFormat('{{ namespace }}'),
51+
]);
52+
53+
local dailyActiveUsers =
54+
activeUserTsOptions
55+
+ ts.new('Daily Active Users')
56+
+ ts.queryOptions.withTargets([
57+
prometheus.new(
58+
'$PROMETHEUS_DS',
59+
|||
60+
max(
61+
jupyterhub_active_users{period="24h", namespace=~"$hub"}
62+
) by (namespace)
63+
|||
64+
)
65+
+ prometheus.withLegendFormat('{{ namespace }}'),
66+
]);
67+
68+
local weeklyActiveUsers =
69+
activeUserTsOptions
70+
+ ts.new('Weekly Active Users')
71+
+ ts.queryOptions.withTargets([
72+
prometheus.new(
73+
'$PROMETHEUS_DS',
74+
|||
75+
max(
76+
jupyterhub_active_users{period="7d", namespace=~"$hub"}
77+
) by (namespace)
78+
|||
79+
)
80+
+ prometheus.withLegendFormat('{{ namespace }}'),
81+
]);
82+
83+
local monthlyActiveUsers =
84+
activeUserTsOptions
85+
+ ts.new('Monthly Active Users')
86+
+ ts.queryOptions.withTargets([
87+
prometheus.new(
88+
'$PROMETHEUS_DS',
89+
|||
90+
max(
91+
jupyterhub_active_users{period="30d", namespace=~"$hub"}
92+
) by (namespace)
93+
|||,
94+
)
95+
+ prometheus.withLegendFormat('{{ namespace }}'),
96+
]);
97+
98+
99+
dashboard.new('Activity')
100+
+ dashboard.withTags(['jupyterhub'])
101+
+ dashboard.withUid('jhgd-activity')
102+
+ dashboard.withEditable(true)
103+
+ dashboard.time.withFrom('now-90d')
104+
+ dashboard.withVariables([
105+
/*
106+
* This dashboard repeats the single row it defines once per datasource, due
107+
* to that we allow multiple or all datasources to be selected in this
108+
* dashboard but not in others. This repeating is only usable for repeating
109+
* panels or rows, as individual panels can't repeat queries based on the
110+
* available datasources.
111+
*/
112+
common.variables.prometheus
113+
+ var.query.selectionOptions.withMulti()
114+
+ var.query.selectionOptions.withIncludeAll(),
115+
/*
116+
* The hub variable will behave weirdly when multiple datasources are selected,
117+
* only showing hubs from one datasource. This is currently an accepted issue.
118+
* Many deployments of these dashboard will only be in a Grafana instance with
119+
* a single prometheus datasource.
120+
*/
121+
common.variables.hub,
122+
])
123+
+ dashboard.withPanels(
124+
grafonnet.util.grid.makeGrid(
125+
[
126+
row.new('Activity ($PROMETHEUS_DS)')
127+
+ row.withPanels([
128+
runningServers,
129+
dailyActiveUsers,
130+
weeklyActiveUsers,
131+
monthlyActiveUsers,
132+
])
133+
+ row.withRepeat('PROMETHEUS_DS'),
134+
],
135+
panelWidth=6,
136+
panelHeight=8,
137+
)
138+
)

dashboards/jupyterhub.jsonnet

Lines changed: 0 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -12,100 +12,6 @@ local row = grafonnet.panel.row;
1212
local common = import './common.libsonnet';
1313
local jupyterhub = import 'jupyterhub.libsonnet';
1414

15-
// Hub usage stats
16-
local currentActiveUsers =
17-
common.tsOptions
18-
+ ts.new('Currently Active Users')
19-
+ ts.standardOptions.withDecimals(0)
20-
+ ts.fieldConfig.defaults.custom.stacking.withMode('normal')
21-
+ ts.queryOptions.withTargets([
22-
prometheus.new(
23-
'$PROMETHEUS_DS',
24-
|||
25-
sum(
26-
group(
27-
kube_pod_status_phase{phase="Running"}
28-
) by (label_component, pod, namespace)
29-
%s
30-
) by (namespace)
31-
|||
32-
% jupyterhub.onComponentLabel('singleuser-server', group_right=''),
33-
)
34-
+ prometheus.withLegendFormat('{{ namespace }}'),
35-
]);
36-
37-
local dailyActiveUsers =
38-
common.tsOptions
39-
+ ts.new('Daily Active Users')
40-
+ ts.panelOptions.withDescription(
41-
|||
42-
Number of unique users who were active within the preceding 24h period.
43-
44-
Requires JupyterHub 3.1.
45-
|||,
46-
)
47-
+ ts.standardOptions.withDecimals(0)
48-
+ ts.fieldConfig.defaults.custom.stacking.withMode('normal')
49-
+ ts.queryOptions.withTargets([
50-
prometheus.new(
51-
'$PROMETHEUS_DS',
52-
|||
53-
max(
54-
jupyterhub_active_users{period="24h", namespace=~"$hub"}
55-
) by (namespace)
56-
|||
57-
)
58-
+ prometheus.withLegendFormat('{{ namespace }}'),
59-
]);
60-
61-
local weeklyActiveUsers =
62-
common.tsOptions
63-
+ ts.new('Weekly Active Users')
64-
+ ts.panelOptions.withDescription(
65-
|||
66-
Number of unique users who were active within the preceeding 7d period.
67-
68-
Requires JupyterHub 3.1.
69-
|||
70-
)
71-
+ ts.standardOptions.withDecimals(0)
72-
+ ts.fieldConfig.defaults.custom.stacking.withMode('normal')
73-
+ ts.queryOptions.withTargets([
74-
prometheus.new(
75-
'$PROMETHEUS_DS',
76-
|||
77-
max(
78-
jupyterhub_active_users{period="7d", namespace=~"$hub"}
79-
) by (namespace)
80-
|||
81-
)
82-
+ prometheus.withLegendFormat('{{ namespace }}'),
83-
]);
84-
85-
local monthlyActiveUsers =
86-
common.tsOptions
87-
+ ts.new('Monthly Active Users')
88-
+ ts.panelOptions.withDescription(
89-
|||
90-
Number of unique users who were active within the preceeding 7d period.
91-
92-
Requires JupyterHub 3.1.
93-
|||
94-
)
95-
+ ts.standardOptions.withDecimals(0)
96-
+ ts.fieldConfig.defaults.custom.stacking.withMode('normal')
97-
+ ts.queryOptions.withTargets([
98-
prometheus.new(
99-
'$PROMETHEUS_DS',
100-
|||
101-
max(
102-
jupyterhub_active_users{period="30d", namespace=~"$hub"}
103-
) by (namespace)
104-
|||,
105-
)
106-
+ prometheus.withLegendFormat('{{ namespace }}'),
107-
]);
108-
10915
local userMemoryDistribution =
11016
common.heatmapOptions
11117
+ heatmap.new('User memory usage distribution')
@@ -597,13 +503,6 @@ dashboard.new('JupyterHub Dashboard')
597503
+ dashboard.withPanels(
598504
grafonnet.util.grid.makeGrid(
599505
[
600-
row.new('Hub usage stats')
601-
+ row.withPanels([
602-
currentActiveUsers,
603-
dailyActiveUsers,
604-
weeklyActiveUsers,
605-
monthlyActiveUsers,
606-
]),
607506
row.new('Container Images')
608507
+ row.withPanels([
609508
notebookImagesUsed,

deploy.py

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -65,25 +65,16 @@ def build_dashboard(dashboard_path, api):
6565
Returns JSON representing a Grafana dashboard by rendering an individual
6666
`.jsonnet` dashboard template with `jsonnet`.
6767
"""
68-
# global-dashboards/global-usage-stats.json needs to be rendered with
69-
# information about the grafana instance's datasources in order to show info
70-
# about all datasources in a single panel. Due to that, we also ask the
71-
# Grafana instance we are to deploy to about its datasources and then pass
72-
# them to `jsonnet` when rendering via the `--tla-code` flag.
73-
datasources = api("/datasources")
74-
datasources_names = [ds["name"] for ds in datasources]
75-
7668
dashboard = json.loads(
7769
subprocess.check_output(
7870
[
7971
"jsonnet",
8072
"-J",
8173
"vendor",
8274
dashboard_path,
83-
"--tla-code",
84-
f"datasources={datasources_names}",
85-
]
86-
).decode()
75+
],
76+
text=True,
77+
)
8778
)
8879
if not dashboard:
8980
raise ValueError(f"jsonnet render of {dashboard_path} led to an empty object")

docs/contributing.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,7 @@ Dashboards are `.json` files generated from `.jsonnet` files using `jsonnet`
3232
like this:
3333

3434
```shell
35-
# --tla-code flag is currently only relevant for global-dashboards
36-
jsonnet -J vendor --tla-code 'datasources=["prometheus-test"]' dashboards/cluster.json
35+
jsonnet -J vendor dashboards/cluster.json
3736
```
3837

3938
To tweak dashboard settings in the `.jsonnet` files can be tricky. One way to do

docs/tutorials/deploy.md

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -226,21 +226,18 @@ You will likely only need to adjust the `claimName` above to use this example.
226226
There's a helper `deploy.py` script that can deploy the dashboards to any grafana installation.
227227

228228
```bash
229-
export GRAFANA_TOKEN="<API-TOKEN-FOR-YOUR-GRAFANA>
229+
# note the leading space in the command below, it makes the
230+
# sensitive command not be stored in your shell history
231+
export GRAFANA_TOKEN="<API-TOKEN-FOR-YOUR-GRAFANA>
232+
230233
./deploy.py <your-grafana-url>
231234
```
232235

233236
This creates a folder called 'JupyterHub Default Dashboards' in your grafana, and adds
234237
a couple of dashboards to it.
235238

236-
If your Grafana deployment supports more than one datasource, then apart from the default dashboards in the [`dashboards` directory](https://github.com/jupyterhub/grafana-dashboards/tree/main/dashboards), you should also consider deploying apart the dashboards in [`global-dashboards` directory](https://github.com/jupyterhub/grafana-dashboards/tree/main/global-dashboards).
237-
238-
```bash
239-
export GRAFANA_TOKEN="<API-TOKEN-FOR-YOUR-GRAFANA>
240-
./deploy.py <your-grafana-url> --dashboards-dir global-dashboards
241-
```
242-
243-
The global dashboards will use the list of available dashboards in your Grafana provided to them and will build dashboards across all of them.
239+
The Activity dashboard is unique because it repeats rows of panels for every
240+
prometheus datasource accessible by Grafana.
244241

245242
If your Grafana instance uses a self-signed certificate, use the `--no-tls-verify` flag when executing the `deploy.py` script. For example:
246243

global-dashboards/README.md

Lines changed: 0 additions & 3 deletions
This file was deleted.

0 commit comments

Comments
 (0)