From 1459755b4a752beaed9692444a3b325b747848cf Mon Sep 17 00:00:00 2001 From: GeorgianaElena Date: Mon, 2 May 2022 17:36:17 +0300 Subject: [PATCH 01/10] Add support for global dashboards, across multiple datasources --- deploy.py | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/deploy.py b/deploy.py index 2aa8a2c..975a60b 100755 --- a/deploy.py +++ b/deploy.py @@ -43,11 +43,25 @@ def ensure_folder(name, uid, api): -def build_dashboard(dashboard_path): - return json.loads(subprocess.check_output([ - 'jsonnet', '-J', 'vendor', - dashboard_path - ]).decode()) +def build_dashboard(dashboard_path, api): + cmd = ["jsonnet", "-J", "vendor", dashboard_path] + + # If we need to build a global dashboard across more datasource + # pass the list of datasources to be used in the queries + if "global" in dashboard_path: + datasources = api("/datasources") + if len(datasources) <= 1: + return + + datasources_names = [ds["name"] for ds in datasources] + cmd.extend(["--tla-code", f"datasources={datasources_names}"]) + + # We pass the list of all datasources because + # some of the dashboards use this to show info + # about all datasources in the same panel + return json.loads(subprocess.check_output( + cmd + ).decode()) def layout_dashboard(dashboard): @@ -81,7 +95,11 @@ def layout_dashboard(dashboard): return dashboard def deploy_dashboard(dashboard_path, folder_uid, api): - db = build_dashboard(dashboard_path) + db = build_dashboard(dashboard_path, api) + + if not db: + return + db = layout_dashboard(db) db = populate_template_variables(api, db) From fb6513bdafa9e4ff433b0d5b13268d9391c1078b Mon Sep 17 00:00:00 2001 From: GeorgianaElena Date: Mon, 2 May 2022 17:37:11 +0300 Subject: [PATCH 02/10] Add global usage stats --- dashboards/global-usage-stats.jsonnet | 54 +++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 dashboards/global-usage-stats.jsonnet diff --git a/dashboards/global-usage-stats.jsonnet b/dashboards/global-usage-stats.jsonnet new file mode 100644 index 0000000..60737ea --- /dev/null +++ b/dashboards/global-usage-stats.jsonnet @@ -0,0 +1,54 @@ +// Deploys one dashboard - "Global usage dashboard", +// with useful stats about usage across all datasources +local grafana = import '../vendor/grafonnet/grafana.libsonnet'; +local dashboard = grafana.dashboard; +local barGaugePanel = grafana.barGaugePanel; +local prometheus = grafana.prometheus; + +function(datasources) +local weeklyActiveUsers = barGaugePanel.new( + 'Active users (over 7 days)', + datasource='-- Mixed --', + thresholds=[ + { + "value": 0, + "color": "yellow" + }, + { + "value": 50, + "color": "green" + } + ], +).addTargets([ + prometheus.target( + // Removes any pods caused by stress testing + ||| + count( + sum( + min_over_time( + kube_pod_labels{ + label_app="jupyterhub", + label_component="singleuser-server", + label_hub_jupyter_org_username!~"(service|perf|hubtraf)-", + }[7d] + ) + ) by (pod) + ) + |||, + legendFormat=x, + interval='7d', + datasource=x + ), + // Create a target for each datasource + for x in datasources +]); + +dashboard.new( + 'Global Usage Dashboard', + uid='global-usage-dashboard', + tags=['jupyterhub', 'global'], + editable=true, + time_from='now-7d', +).addPanel( + weeklyActiveUsers, {}, +) \ No newline at end of file From 2bf55cf38dff0922e2dd1e1a10a9dab6fcc39278 Mon Sep 17 00:00:00 2001 From: GeorgianaElena Date: Mon, 2 May 2022 17:56:00 +0300 Subject: [PATCH 03/10] Make jsonnetfmt happy again --- dashboards/global-usage-stats.jsonnet | 90 +++++++++++++-------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/dashboards/global-usage-stats.jsonnet b/dashboards/global-usage-stats.jsonnet index 60737ea..b9222ec 100644 --- a/dashboards/global-usage-stats.jsonnet +++ b/dashboards/global-usage-stats.jsonnet @@ -6,49 +6,49 @@ local barGaugePanel = grafana.barGaugePanel; local prometheus = grafana.prometheus; function(datasources) -local weeklyActiveUsers = barGaugePanel.new( - 'Active users (over 7 days)', - datasource='-- Mixed --', - thresholds=[ - { - "value": 0, - "color": "yellow" - }, - { - "value": 50, - "color": "green" - } - ], -).addTargets([ - prometheus.target( - // Removes any pods caused by stress testing - ||| - count( - sum( - min_over_time( - kube_pod_labels{ - label_app="jupyterhub", - label_component="singleuser-server", - label_hub_jupyter_org_username!~"(service|perf|hubtraf)-", - }[7d] - ) - ) by (pod) - ) - |||, - legendFormat=x, - interval='7d', - datasource=x - ), - // Create a target for each datasource - for x in datasources -]); + local weeklyActiveUsers = barGaugePanel.new( + 'Active users (over 7 days)', + datasource='-- Mixed --', + thresholds=[ + { + value: 0, + color: 'yellow', + }, + { + value: 50, + color: 'green', + }, + ], + ).addTargets([ + prometheus.target( + // Removes any pods caused by stress testing + ||| + count( + sum( + min_over_time( + kube_pod_labels{ + label_app="jupyterhub", + label_component="singleuser-server", + label_hub_jupyter_org_username!~"(service|perf|hubtraf)-", + }[7d] + ) + ) by (pod) + ) + |||, + legendFormat=x, + interval='7d', + datasource=x + ) + // Create a target for each datasource + for x in datasources + ]); -dashboard.new( - 'Global Usage Dashboard', - uid='global-usage-dashboard', - tags=['jupyterhub', 'global'], - editable=true, - time_from='now-7d', -).addPanel( - weeklyActiveUsers, {}, -) \ No newline at end of file + dashboard.new( + 'Global Usage Dashboard', + uid='global-usage-dashboard', + tags=['jupyterhub', 'global'], + editable=true, + time_from='now-7d', + ).addPanel( + weeklyActiveUsers, {}, + ) From 241e01f3a57f88ab5cedab28e41f36e4a8ebd834 Mon Sep 17 00:00:00 2001 From: Georgiana Dolocan Date: Mon, 4 Jul 2022 17:41:34 +0300 Subject: [PATCH 04/10] Add support for a global dashboards directory --- deploy.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/deploy.py b/deploy.py index 975a60b..d95ef4a 100755 --- a/deploy.py +++ b/deploy.py @@ -43,12 +43,12 @@ def ensure_folder(name, uid, api): -def build_dashboard(dashboard_path, api): +def build_dashboard(dashboard_path, api, global_dash=False): cmd = ["jsonnet", "-J", "vendor", dashboard_path] # If we need to build a global dashboard across more datasource # pass the list of datasources to be used in the queries - if "global" in dashboard_path: + if global_dash: datasources = api("/datasources") if len(datasources) <= 1: return @@ -94,8 +94,8 @@ def layout_dashboard(dashboard): return dashboard -def deploy_dashboard(dashboard_path, folder_uid, api): - db = build_dashboard(dashboard_path, api) +def deploy_dashboard(dashboard_path, folder_uid, api, global_dash=False): + db = build_dashboard(dashboard_path, api, global_dash) if not db: return @@ -193,9 +193,18 @@ def main(): api = partial(grafana_request, args.grafana_url, grafana_token) folder = ensure_folder(args.folder_name, args.folder_uid, api) - for dashboard in glob(f'{args.dashboards_dir}/*.jsonnet'): - deploy_dashboard(dashboard, folder['id'], api) - print(f'Deployed {dashboard}') + dashboards_dir = args.dashboards_dir + def iterate_and_deploy(directory_name, global_dash=False): + # Small function to iterate a directory and deploy all jsonnet files in it + for dashboard in glob(f'{directory_name}/*.jsonnet'): + deploy_dashboard(dashboard, folder['id'], api, global_dash) + print(f'Deployed {dashboard}') + + if args.dashboards_dir == "dashboards": + # Deploy also the global dashboards in default directory + iterate_and_deploy(f"{dashboards_dir}/global-dashboards", global_dash=True) + + iterate_and_deploy(dashboards_dir) if __name__ == '__main__': main() From 3293b5b83016cc4fe0484ea15839d423323b5007 Mon Sep 17 00:00:00 2001 From: Georgiana Dolocan Date: Mon, 4 Jul 2022 17:42:21 +0300 Subject: [PATCH 05/10] Move global dashboards to own directory and make dashboard bigger --- .../global-usage-stats.jsonnet | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) rename dashboards/{ => global-dashboards}/global-usage-stats.jsonnet (93%) diff --git a/dashboards/global-usage-stats.jsonnet b/dashboards/global-dashboards/global-usage-stats.jsonnet similarity index 93% rename from dashboards/global-usage-stats.jsonnet rename to dashboards/global-dashboards/global-usage-stats.jsonnet index b9222ec..1794a62 100644 --- a/dashboards/global-usage-stats.jsonnet +++ b/dashboards/global-dashboards/global-usage-stats.jsonnet @@ -12,10 +12,6 @@ function(datasources) thresholds=[ { value: 0, - color: 'yellow', - }, - { - value: 50, color: 'green', }, ], @@ -50,5 +46,11 @@ function(datasources) editable=true, time_from='now-7d', ).addPanel( - weeklyActiveUsers, {}, + weeklyActiveUsers, + gridPos={ + x: 0, + y: 0, + w: 25, + h: 10, + }, ) From 24ff5adf7ee9929971e4ea629866df2d188d7027 Mon Sep 17 00:00:00 2001 From: Georgiana Dolocan Date: Mon, 4 Jul 2022 18:06:42 +0300 Subject: [PATCH 06/10] Add comment and message about grafanas with only one datasouce configured --- deploy.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/deploy.py b/deploy.py index d95ef4a..fc737bc 100755 --- a/deploy.py +++ b/deploy.py @@ -50,7 +50,10 @@ def build_dashboard(dashboard_path, api, global_dash=False): # pass the list of datasources to be used in the queries if global_dash: datasources = api("/datasources") + # If there is only one datasource, global dashboards don't make sense + # so don't bluid and deploy them if len(datasources) <= 1: + print("Only one datasource found. Global dashboards will not be deployed.") return datasources_names = [ds["name"] for ds in datasources] From ecf8abd2d94aefafb3f3605bbe2bbdba21804e73 Mon Sep 17 00:00:00 2001 From: Georgiana Dolocan Date: Mon, 4 Jul 2022 18:19:07 +0300 Subject: [PATCH 07/10] Add docs about global dashboards --- README.md | 5 ++++- dashboards/global-dashboards/README.md | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 dashboards/global-dashboards/README.md diff --git a/README.md b/README.md index 2e15065..221294c 100644 --- a/README.md +++ b/README.md @@ -11,11 +11,14 @@ issues on Kubernetes clusters running JupyterHub. However, everyone has to build own dashboards - there isn't an easy way to standardize them across many clusters run by many entities. +### The dashboards directory This project provides some standard [Grafana Dashboards as Code](https://grafana.com/blog/2020/02/26/how-to-configure-grafana-as-code/) to help with this. It uses [jsonnet](https://jsonnet.org/) and [grafonnet](https://github.com/grafana/grafonnet-lib) to generate dashboards completely via code. This can then be deployed on any Grafana instance! +If your Grafana deployment supports more than one datasource, then the dashboards in [`dashboards/global-dashboards` directory](https://github.com/jupyterhub/grafana-dashboards/tree/main/dashboards/global-dashboards) will also be deployed. The dashboards in this directory will be provided with the list of available dashboards in your Grafana and will build dashboards across all of them. + ## Pre-requisites 1. Locally, you need to have @@ -38,7 +41,7 @@ via code. This can then be deployed on any Grafana instance! ## Deployment -There's a helper `deploy.py` script that can deploy the dashboard to any grafana installation. +There's a helper `deploy.py` script that can deploy the dashboards to any grafana installation. ```bash export GRAFANA_TOKEN=" diff --git a/dashboards/global-dashboards/README.md b/dashboards/global-dashboards/README.md new file mode 100644 index 0000000..d318d65 --- /dev/null +++ b/dashboards/global-dashboards/README.md @@ -0,0 +1,3 @@ +# Dashboards across all datasources + +Contains "global" dashboards with useful stats computed across all datasources. \ No newline at end of file From 38fcc38695986e75265c0988417d585a366027fb Mon Sep 17 00:00:00 2001 From: Georgiana Dolocan Date: Wed, 6 Jul 2022 17:02:26 +0300 Subject: [PATCH 08/10] Remove any special casing around global dashboards when deploying --- deploy.py | 42 ++++++++++++------------------------------ 1 file changed, 12 insertions(+), 30 deletions(-) diff --git a/deploy.py b/deploy.py index fc737bc..f20a7ff 100755 --- a/deploy.py +++ b/deploy.py @@ -44,26 +44,17 @@ def ensure_folder(name, uid, api): def build_dashboard(dashboard_path, api, global_dash=False): - cmd = ["jsonnet", "-J", "vendor", dashboard_path] - # If we need to build a global dashboard across more datasource - # pass the list of datasources to be used in the queries - if global_dash: - datasources = api("/datasources") - # If there is only one datasource, global dashboards don't make sense - # so don't bluid and deploy them - if len(datasources) <= 1: - print("Only one datasource found. Global dashboards will not be deployed.") - return - - datasources_names = [ds["name"] for ds in datasources] - cmd.extend(["--tla-code", f"datasources={datasources_names}"]) - - # We pass the list of all datasources because - # some of the dashboards use this to show info - # about all datasources in the same panel + datasources = api("/datasources") + datasources_names = [ds["name"] for ds in datasources] + + # We pass the list of all datasources because the global dashboards + # use this information to show info about all datasources in the same panel return json.loads(subprocess.check_output( - cmd + [ + "jsonnet", "-J", "vendor", dashboard_path, + "--tla-code", f"datasources={datasources_names}" + ] ).decode()) @@ -196,18 +187,9 @@ def main(): api = partial(grafana_request, args.grafana_url, grafana_token) folder = ensure_folder(args.folder_name, args.folder_uid, api) - dashboards_dir = args.dashboards_dir - def iterate_and_deploy(directory_name, global_dash=False): - # Small function to iterate a directory and deploy all jsonnet files in it - for dashboard in glob(f'{directory_name}/*.jsonnet'): - deploy_dashboard(dashboard, folder['id'], api, global_dash) - print(f'Deployed {dashboard}') - - if args.dashboards_dir == "dashboards": - # Deploy also the global dashboards in default directory - iterate_and_deploy(f"{dashboards_dir}/global-dashboards", global_dash=True) - - iterate_and_deploy(dashboards_dir) + for dashboard in glob(f'{args.dashboards_dir}/*.jsonnet'): + deploy_dashboard(dashboard, folder['id'], api) + print(f'Deployed {dashboard}') if __name__ == '__main__': main() From 66a1fbaf93ef0e1c74b130ca0f09633a590f4ae6 Mon Sep 17 00:00:00 2001 From: Georgiana Dolocan Date: Wed, 6 Jul 2022 17:03:35 +0300 Subject: [PATCH 09/10] Move global-dashboards outside of the dashboards dir --- {dashboards/global-dashboards => global-dashboards}/README.md | 0 .../global-usage-stats.jsonnet | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {dashboards/global-dashboards => global-dashboards}/README.md (100%) rename {dashboards/global-dashboards => global-dashboards}/global-usage-stats.jsonnet (100%) diff --git a/dashboards/global-dashboards/README.md b/global-dashboards/README.md similarity index 100% rename from dashboards/global-dashboards/README.md rename to global-dashboards/README.md diff --git a/dashboards/global-dashboards/global-usage-stats.jsonnet b/global-dashboards/global-usage-stats.jsonnet similarity index 100% rename from dashboards/global-dashboards/global-usage-stats.jsonnet rename to global-dashboards/global-usage-stats.jsonnet From 9a27458e5bbcd371672311d31509671fba99013e Mon Sep 17 00:00:00 2001 From: Georgiana Dolocan Date: Wed, 6 Jul 2022 17:08:50 +0300 Subject: [PATCH 10/10] Update README to reflect latest changes --- README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 221294c..170c6ea 100644 --- a/README.md +++ b/README.md @@ -11,14 +11,11 @@ issues on Kubernetes clusters running JupyterHub. However, everyone has to build own dashboards - there isn't an easy way to standardize them across many clusters run by many entities. -### The dashboards directory This project provides some standard [Grafana Dashboards as Code](https://grafana.com/blog/2020/02/26/how-to-configure-grafana-as-code/) to help with this. It uses [jsonnet](https://jsonnet.org/) and [grafonnet](https://github.com/grafana/grafonnet-lib) to generate dashboards completely via code. This can then be deployed on any Grafana instance! -If your Grafana deployment supports more than one datasource, then the dashboards in [`dashboards/global-dashboards` directory](https://github.com/jupyterhub/grafana-dashboards/tree/main/dashboards/global-dashboards) will also be deployed. The dashboards in this directory will be provided with the list of available dashboards in your Grafana and will build dashboards across all of them. - ## Pre-requisites 1. Locally, you need to have @@ -51,6 +48,15 @@ export GRAFANA_TOKEN=" This creates a folder called 'JupyterHub Default Dashboards' in your grafana, and adds a couple of dashboards to it. +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). + +```bash +export GRAFANA_TOKEN=" +./deploy.py --dashboards-dir global-dashboards +``` + +The gloabal dashboards will use the list of available dashboards in your Grafana provided to them and will build dashboards across all of them. + **NOTE: ANY CHANGES YOU MAKE VIA THE GRAFANA UI WILL BE OVERWRITTEN NEXT TIME YOU RUN deploy.bash. TO MAKE CHANGES, EDIT THE JSONNET FILE AND DEPLOY AGAIN**