Commit b80fd8a3 authored by Syed Bilal Raees's avatar Syed Bilal Raees

migrating

parents
{
"__inputs": [ ],
"__requires": [ ],
"annotations": {
"list": [ ]
},
"editable": true,
"gnetId": null,
"graphTooltip": 0,
"hideControls": false,
"id": null,
"links": [ ],
"panels": [
{
"datasource": "Prometheus",
"fieldConfig": {
"defaults": {
"thresholds": {
"mode": "absolute",
"steps": [ ]
},
"unit": null
}
},
"gridPos": {
"h": 8,
"w": 8,
"x": 0,
"y": 0
},
"id": 2,
"targets": [
{
"expr": "promhttp_metric_handler_requests_total{}",
"format": "time_series",
"intervalFactor": 2,
"legendFormat": "{{code}}",
"refId": "A"
}
],
"title": "promhttp_metric_handler_requests",
"type": "bargauge"
}
],
"refresh": "",
"rows": [ ],
"schemaVersion": 14,
"style": "dark",
"tags": [ ],
"templating": {
"list": [
{
"current": {
"text": "Prometheus",
"value": "Prometheus"
},
"hide": 1,
"label": null,
"name": "PROMETHEUS_DS",
"options": [ ],
"query": "prometheus",
"refresh": 1,
"regex": "",
"type": "datasource"
},
{
"allValue": null,
"current": { },
"datasource": "$PROMETHEUS_DS",
"hide": 0,
"includeAll": false,
"label": "Application",
"multi": false,
"name": "application",
"options": [ ],
"query": "label_values(application)",
"refresh": 2,
"regex": "",
"sort": 0,
"tagValuesQuery": "",
"tags": [ ],
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"allValue": null,
"current": { },
"datasource": "$PROMETHEUS_DS",
"hide": 0,
"includeAll": false,
"label": "Instance",
"multi": false,
"name": "instance",
"options": [ ],
"query": "label_values(jvm_memory_used_bytes{application=\"$application\"}, instance)",
"refresh": 2,
"regex": "",
"sort": 0,
"tagValuesQuery": "",
"tags": [ ],
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"allValue": null,
"current": { },
"datasource": "$PROMETHEUS_DS",
"hide": 0,
"includeAll": false,
"label": "test",
"multi": false,
"name": "test",
"options": [ ],
"query": "label_values(test)",
"refresh": 2,
"regex": "",
"sort": 0,
"tagValuesQuery": "",
"tags": [ ],
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"allValue": null,
"current": { },
"datasource": "$PROMETHEUS_DS",
"hide": 0,
"includeAll": false,
"label": "test",
"multi": false,
"name": "test2",
"options": [ ],
"query": "label_values(test)",
"refresh": 2,
"regex": "",
"sort": 0,
"tagValuesQuery": "",
"tags": [ ],
"tagsQuery": "",
"type": "query",
"useTags": false
}
]
},
"time": {
"from": "now-30m",
"to": "now"
},
"timepicker": {
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
]
},
"timezone": "browser",
"title": "MySQL-reusabilityy-test",
"uid": "55555",
"version": 0
}
local grafana = import 'grafonnet/grafana.libsonnet';
local dashboard = grafana.dashboard;
local target_common = import 'targets_common.jsonnet';
local row = grafana.row;
local template = grafana.template;
local gaugePanel = grafana.gaugePanel;
local bargaugePanel = grafana.bargaugePanel;
local singlestat = grafana.singlestat;
local prometheus = grafana.prometheus;
grafana.dashboard.new(
title='MySQL-reusabilityy-test',
uid='55555',
editable=true,
# Display metrics over the last 30 minutes
time_from='now-30m',
).addPanel(
grafana.barGaugePanel.new(
title='promhttp_metric_handler_requests',
# Direct queries to the `Prometheus` data source
datasource='Prometheus',
#unit='number',
).addTarget(
grafana.prometheus.target(
expr='promhttp_metric_handler_requests_total{}',
legendFormat='{{code}}',
)
),
gridPos = { h: 8, w: 8, x: 0, y: 0 }
).addTemplate(target_common.prometheusDs)
.addTemplate(target_common.applicationTemplate)
.addTemplate(target_common.instanceTemplate)
.addTemplate(target_common.testTemplate)
.addTemplate(target_common.testTemplate1)
\ No newline at end of file
# grafana-as-code
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
local grafana = import 'grafonnet/grafana.libsonnet';
local dashboard = grafana.dashboard;
local row = grafana.row;
local template = grafana.template;
local graphPanel = grafana.graphPanel;
local singlestat = grafana.singlestat;
local prometheus = grafana.prometheus;
grafana.dashboard.new(
'Elastic dashboard Via Graffonet',
refresh='1m',
editable=true,
time_from='now-12h',
uid='KquscW-Vz'
)
.addTemplate(
grafana.template.datasource(
'server',
'prometheus',
'Prometheus',
label='Server'
)
)
.addTemplate(
template.new(
'cluster',
'$server',
'label_values(elasticsearch_cluster_health_status, cluster)',
label='cluster',
refresh='time',
)
)
.addRow(
row.new(
title='Clusters',
height='125px',
)
.addPanel(
singlestat.new(
'Running Nodes',
span=4,
valueName='current',
)
.addTarget(
prometheus.target(
'sum(elasticsearch_cluster_health_number_of_nodes{cluster=~"$cluster"})/count(elasticsearch_cluster_health_number_of_nodes{cluster=~"$cluster"})',
datasource='$server',
)
)
)
.addPanel(
singlestat.new(
'Active Data Nodes',
span=4,
valueName='current',
)
.addTarget(
prometheus.target(
'elasticsearch_cluster_health_number_of_data_nodes{cluster=~"$cluster"}',
datasource='$server',
)
)
)
.addPanel(
singlestat.new(
'Pending task',
span=4,
valueName='current'
)
.addTarget(
prometheus.target(
'elasticsearch_cluster_health_number_of_pending_tasks',
datasource='$server',
)
)
)
)
.addRow(
row.new(
title='Shards',
height='125px',
)
.addPanel(
singlestat.new(
'Active Shards',
span=4,
valueName='current',
)
.addTarget(
prometheus.target(
'elasticsearch_cluster_health_active_shards{cluster=~"$cluster"}',
datasource='$server',
)
)
)
.addPanel(
singlestat.new(
'Active Primary Shards',
span=4,
valueName='current',
)
.addTarget(
prometheus.target(
'elasticsearch_cluster_health_active_primary_shards{cluster=~"$cluster"}',
datasource='$server',
)
)
)
.addPanel(
singlestat.new(
'Unassigned Shards',
span=4,
valueName='current',
)
.addTarget(
prometheus.target(
'elasticsearch_cluster_health_unassigned_shards{cluster=~"$cluster"}',
datasource='$server',
)
)
)
)
.addRow(
row.new(
title='Documents',
height='250px',
)
.addPanel(
graphPanel.new(
'Documents indexed',
span=6,
fill=1,
min=0,
legend_values=true,
legend_min=true,
legend_max=true,
legend_current=true,
legend_total=false,
legend_avg=true,
legend_alignAsTable=true,
legend_sideWidth=200,
)
.addTarget(
prometheus.target(
'sum(elasticsearch_indices_docs{cluster=~"$cluster"})',
legendFormat='Documents',
datasource='$server',
)
)
)
.addPanel(
graphPanel.new(
'Index Size',
span=6,
fill=1,
min=0,
format='bytes',
decimals=2,
legend_values=true,
legend_min=true,
legend_max=true,
legend_current=true,
legend_total=false,
legend_avg=true,
legend_alignAsTable=true,
)
.addTarget(
prometheus.target(
'sum(elasticsearch_indices_store_size_bytes{cluster=~"$cluster"})',
legendFormat='Index Size',
datasource='$server',
)
),
gridPos = { h: 9, w: 12, x: 0, y: 18}
)
.addPanel(
graphPanel.new(
'Documents Indexed Rate',
span=6,
fill=1,
min=0,
format='Mib',
decimals=2,
legend_values=true,
legend_min=true,
legend_max=true,
legend_current=true,
legend_total=false,
legend_avg=true,
legend_alignAsTable=true,
legend_sideWidth=200,
)
.addTarget(
prometheus.target(
'rate(elasticsearch_indices_indexing_index_total{cluster=~"$cluster"}[1h])',
legendFormat='Documents Indexed Rate',
datasource='$server',
)
)
)
.addPanel(
graphPanel.new(
'Query Rate',
span=6,
fill=1,
min=0,
decimals=3,
legend_values=true,
legend_min=true,
legend_max=true,
legend_current=true,
legend_total=false,
legend_avg=true,
legend_alignAsTable=true,
legend_sideWidth=200,
)
.addTarget(
prometheus.target(
'rate(elasticsearch_indices_search_fetch_total{cluster=~"$cluster"}[1h])',
legendFormat='Query Rate',
datasource='$server',
)
)
)
)
.addRequired('grafana', 'grafana', 'Grafana', '5.0.0')
.addRequired('panel', 'graph', 'Graph', '5.0.0')
.addRequired('datasource', 'prometheus', 'Prometheus', '5.0.0')
.addRequired('panel', 'singlestat', 'Singlestat', '5.0.0')
\ No newline at end of file
site
docs/api-docs.md
*_test_output.json
e2e/node_modules
.DS_Store
# Changelog
## 0.1.0 (2020-07-29)
Includes a breaking change for `gauge`. It has been renamed to `gaugePanel` to
be consistent with the project's panel naming convention.
### Features
* **panel**: Create Stat Panel, Update Gauge Panel
([#242](https://github.com/grafana/grafonnet-lib/pull/242)).
* **template**: Add adhoc template variable
([#245](https://github.com/grafana/grafonnet-lib/pull/245)).
### Enhancements
* **panel**: Add support for repeat in pie chart panel
([#246](https://github.com/grafana/grafonnet-lib/pull/246)).
## 0.0.1 (2020-07-06)
The project is almost 3 years old and this is the first official release.
Historically, users have targeted a specific commit for their personal and
organization's usage. Today, we're creating the first "official" release for
folks to optionally target. This marks a small step in carrying out plans for
making Grafonnet more accessible to our community and improving compatibility.
# Code of Conduct
Grafonnet is developed within the Grafana community.
We are following the same Code of Conduct as Grafana (Contributor Covenant Code
of Conduct). Please find it [here][coc].
[coc]:https://github.com/grafana/grafana/blob/master/CODE_OF_CONDUCT.md
# Contributing
Thank you for your interest in contributing to Grafonnet! We welcome all people
who want to contribute in a healthy and constructive manner within our
community. Grafonnet is developed within the Grafana community. Therefore we
are following the same [Code of Conduct as
Grafana](https://github.com/grafana/grafana/blob/master/CODE_OF_CONDUCT.md). To
help us create a safe and positive community experience for all, we require all
participants to adhere to it.
## Version Support
We strive to only support the latest and last major Grafana versions - currently
6 and 7.
## Feature Support
**Grafonnet only aims to support core features and plugins.**
In terms of plugins, this means only those that are pre-installed:
* Alert List
* Azure Monitor
* Bar gauge
* CloudWatch
* Dashboard list
* Elasticsearch
* Gauge
* Graph
* Graphite
* Heatmap
* InfluxDB
* Jaeger
* Logs
* Loki
* Microsoft SQL Server
* MySQL
* News
* OpenTSDB
* Plugin list
* PostgreSQL
* Prometheus
* Singlestat
* Stackdriver
* Stat
* Table
* Table (old)
* TestData DB
* Text
* Zipkin
While Grafonnet itself only supports core Grafana functionality, we strongly
encourage development and use of community Grafonnet extensions. See the
[Community Plugins](https://grafana.github.io/grafonnet-lib/community-plugins/)
page for more info on this.
This diff is collapsed.
UID = $(shell id -u $(USER))
GID = $(shell id -g $(USER))
help: # Show this message.
@echo "\nAvailable Targets:\n"
@sed -ne '/@sed/!s/# //p' $(MAKEFILE_LIST)
test: # Run all unit tests.
@docker run --rm \
-w $$PWD \
-v $$PWD:$$PWD \
-u $(UID):$(GID) \
--entrypoint bash \
bitnami/jsonnet:0.16.0 \
tests.sh
test-update: # Run all unit tests while copying test_output.json to compiled.json file.
@docker run --rm \
-w $$PWD \
-v $$PWD:$$PWD \
-u $(UID):$(GID) \
--entrypoint bash \
bitnami/jsonnet:0.16.0 \
tests.sh update
E2E_GRAFANA_VERSION ?= 7.1.1
e2e: # Run all end-to-end tests.
GRAFANA_VERSION=${E2E_GRAFANA_VERSION} \
docker-compose -f e2e/docker-compose.yml up \
--abort-on-container-exit \
--exit-code-from e2e
e2e-dev: # Run e2e tests in Cypress test runner.
GRAFANA_VERSION=${E2E_GRAFANA_VERSION} \
DISPLAY=$$(ipconfig getifaddr en0):0 \
docker-compose -f e2e/docker-compose.dev.yml up \
--abort-on-container-exit \
--exit-code-from e2e
gen-api-docs: # Generate api-docs.md from source code comments.
@docker run --rm \
-w $$PWD \
-v $$PWD:$$PWD \
trotttrotttrott/jsonnetdoc:ece56aa \
grafonnet --markdown \
> docs/api-docs.md
spec-import: # Import generated libraries from https://github.com/grafana/dashboard-spec.
svn export https://github.com/grafana/dashboard-spec/branches/_gen/_gen/7.0/jsonnet grafonnet-7.0 --force
.PHONY: help test test-update e2e gen-api-docs spec-import
# Grafonnet
Jsonnet libraries for writing Grafana dashboards as code.
[![CircleCI](https://circleci.com/gh/grafana/grafonnet-lib.svg?style=svg)](https://circleci.com/gh/grafana/grafonnet-lib)
_**Attention:** We're in the process of introducing generated code that can be
used instead of the manually maintained Jsonnet code in the
[grafonnet](./grafonnet) directory. The generated code lives in
[grafonnet-7.0](./grafonnet-7.0). It's generated from a new project,
[grafana/dashboard-spec](https://github.com/grafana/dashboard-spec). The
generated code is still incomplete, however, the components present are useable.
We very much appreciate contributions in
[grafana/dashboard-spec](https://github.com/grafana/dashboard-spec) for
components yet to be added._
See the full docs here: https://grafana.github.io/grafonnet-lib/
## Contributing
Please contribute! If you're interested, please start by reading the
[contributing guide](CONTRIBUTING.md). Before you begin work please take note of
our code of conduct and ensure that what you'd like to contribute is within the
scope of what Grafonnet attempts to support.
// layout.libsonnet - a little mixin for the Grafana dashboard type that
// automatically arranges panels on a dashboard, so you don't have to manually
// manage the positions anymore.
//
// We track a cursor in the dashboard and add panels at the cursor, moving
// the cursor to the right after each new panel. We also expose a function
// to move the cursor to the next row, and track the height of the panels added
// to make this possible.
//
// Usage
// local grafana = import 'grafonnet-7.0/grafana.libsonnet';
// local layout = import "grafonnet-7.0/layout.libsonnet";
//
// local dashboard = grafana.dashboard.new('Empty Dashboard') + layout;
//
// dashboard
// .addPanel(...)
// .addPanel(...)
// .nextRow()
// .addPanel(...)
// .addPanel(...)
{
// Track where the next panel should go using these private variables, aka "the cursor".
_cursor:: {
x: 0,
y: 0,
h: 0,
},
// addPanels add a panel at the cursor and moves the cursor to the right by the panel width.
addPanel(panel)::
local cursor = self._cursor;
local panelWithPos = panel {
gridPos+: {
x: cursor.x,
y: cursor.y,
},
};
super.addPanel(panelWithPos) + {
_cursor+:: {
x+: panel.gridPos.w,
h:
if cursor.h > panel.gridPos.h
then cursor.h
else panel.gridPos.h,
},
},
// Start a new row beneath the current cursor.
nextRow():: self + {
_cursor+:: {
x: 0,
y+: super.h,
h: 0,
},
},
}
# Community Plugins
Jsonnet makes it easy to patch an existing library. Although Grafonnet only
supports core Grafana features and plugins, it is easy to extend. For example:
```jsonnet
local grafonnet = (import 'grafonnet-lib/grafana.libsonnet')
+ (import 'my-plugin-lib/my-plugin.libsonnet');
{
...
}
```
## Plugin List
If you've developed a Grafonnet extension for supporting a community plugin,
please submit a pull request to get it added to this list.
* [Status panel (by Vonage)](https://grafana.com/grafana/plugins/vonage-status-panel) template plugin: [link](https://github.com/DifferentialOrange/grafonnet-status-panel).
* [Statusmap panel (by Flant)](https://grafana.com/grafana/plugins/flant-statusmap-panel) template plugin: [link](https://github.com/blablacar/grafonnet-lib-plugins).
* [Polystat panel (by Grafana Labs)](https://grafana.com/grafana/plugins/grafana-polystat-panel) template plugin: [link](https://github.com/thelastpickle/grafonnet-polystat-panel).
* [Bigquery panel (by DoiT International)](https://grafana.com/grafana/plugins/doitintl-bigquery-datasource) template plugin: [link](https://github.com/gojekfarm/grafonnet-bigquery-panel).
# Examples
Simple Grafana 5.x dashboard:
Please note that the layout has changed, no `row` objects and new possible
nesting of `panel` objects. You need to set `schemaVersion` parameter on
dashboard object to at least 16.
```jsonnet
local grafana = import 'grafonnet/grafana.libsonnet';
local dashboard = grafana.dashboard;
local row = grafana.row;
local singlestat = grafana.singlestat;
local prometheus = grafana.prometheus;
local template = grafana.template;
dashboard.new(
'JVM',
schemaVersion=16,
tags=['java'],
)
.addTemplate(
grafana.template.datasource(
'PROMETHEUS_DS',
'prometheus',
'Prometheus',
hide='label',
)
)
.addTemplate(
template.new(
'env',
'$PROMETHEUS_DS',
'label_values(jvm_threads_current, env)',
label='Environment',
refresh='time',
)
)
.addTemplate(
template.new(
'job',
'$PROMETHEUS_DS',
'label_values(jvm_threads_current{env="$env"}, job)',
label='Job',
refresh='time',
)
)
.addTemplate(
template.new(
'instance',
'$PROMETHEUS_DS',
'label_values(jvm_threads_current{env="$env",job="$job"}, instance)',
label='Instance',
refresh='time',
)
)
.addPanel(
singlestat.new(
'uptime',
format='s',
datasource='Prometheus',
span=2,
valueName='current',
)
.addTarget(
prometheus.target(
'time() - process_start_time_seconds{env="$env", job="$job", instance="$instance"}',
)
), gridPos={
x: 0,
y: 0,
w: 24,
h: 3,
}
)
```
Simple Grafana 4.x dashboard:
```jsonnet
local grafana = import 'grafonnet/grafana.libsonnet';
local dashboard = grafana.dashboard;
local row = grafana.row;
local singlestat = grafana.singlestat;
local prometheus = grafana.prometheus;
local template = grafana.template;
dashboard.new(
'JVM',
tags=['java'],
)
.addTemplate(
grafana.template.datasource(
'PROMETHEUS_DS',
'prometheus',
'Prometheus',
hide='label',
)
)
.addTemplate(
template.new(
'env',
'$PROMETHEUS_DS',
'label_values(jvm_threads_current, env)',
label='Environment',
refresh='time',
)
)
.addTemplate(
template.new(
'job',
'$PROMETHEUS_DS',
'label_values(jvm_threads_current{env="$env"}, job)',
label='Job',
refresh='time',
)
)
.addTemplate(
template.new(
'instance',
'$PROMETHEUS_DS',
'label_values(jvm_threads_current{env="$env",job="$job"}, instance)',
label='Instance',
refresh='time',
)
)
.addRow(
row.new()
.addPanel(
singlestat.new(
'uptime',
format='s',
datasource='Prometheus',
span=2,
valueName='current',
)
.addTarget(
prometheus.target(
'time() - process_start_time_seconds{env="$env", job="$job", instance="$instance"}',
)
)
)
)
```
Find more examples in the repo [examples](https://github.com/grafana/grafonnet-lib/tree/master/examples) directory.
# Getting Started
## Prerequisites
You must first install Jsonnet.
See the instructions on its GitHub page:
[https://github.com/google/jsonnet#packages](https://github.com/google/jsonnet#packages)
There is also an implementation in Go if you'd prefer:
[https://github.com/google/go-jsonnet#installation-instructions](https://github.com/google/go-jsonnet#installation-instructions)
## Install Grafonnet
Simplest way to install Grafonnet is to clone the repository:
```
git clone https://github.com/grafana/grafonnet-lib.git
```
A slightly more advanced approach is to use
[jsonnet-bundler](https://github.com/jsonnet-bundler/jsonnet-bundler).
```
jb init
jb install https://github.com/grafana/grafonnet-lib/grafonnet
```
See the [Usage](../usage) page for next steps.
# ![Grafonnet logo](images/grafonnet.png)
Grafonnet provides an easy and maintainable way of writing
[Grafana](https://grafana.org) dashboards. Instead of generating JSON files and
maintaining them, you can easily create your own dashboards using the many
helpers grafonnet-lib offers you, thanks to the data templating language
[Jsonnet](http://jsonnet.org/).
!!! warning
We're in the process of introducing generated code that can be used instead
of the manually maintained Jsonnet code in the `grafonnet` directory. The
generated code lives in `grafonnet-7.0`. It's generated from a new project,
[grafana/dashboard-spec](https://github.com/grafana/dashboard-spec). The
generated code is still incomplete, however, the components present are
useable. We very much appreciate contributions in
[grafana/dashboard-spec](https://github.com/grafana/dashboard-spec) for
components yet to be added._
| Grafana Version | Grafonnet Library | API Docs |
| --- | --- | --- |
| 7.x | [grafonnet-7.0](https://github.com/grafana/grafonnet-lib/tree/master/grafonnet-7.0) | [DOCS.md](https://github.com/grafana/grafonnet-lib/tree/master/grafonnet-7.0/DOCS.md) |
## Grafana dashboard
[A dashboard in Grafana is represented by a JSON
object](https://grafana.com/docs/grafana/latest/reference/dashboard/). While
this choice makes sense from a technical point of view, people who want to keep
those dashboards under version control end up putting large, independent JSON
files under source control.
When doing so, it is hard to maintain the same links, templates, or even
annotation between graphs. It usually requires a lot of custom tooling to
change and keep those Json files aligned. There are alternatives, like
[grafanalib](https://github.com/weaveworks/grafanalib), that makes thing easier.
However, as Grafonnet is using [Jsonnet](http://jsonnet.org/), a superset of
JSON, it gives you out of the box a very easy way to use any feature of grafana
that would not be covered by Grafonnet already.
## Scope
Grafonnet aims to support any basic feature of dashboards (annotations,
templates, rows, panels...) as well as a number of datasources and plugins. That
is, core Grafana features and plugins only.
We do, however, encourage development and use of community Grafonnet extensions.
See the [Community Plugins](community-plugins) page for more info on this.
## Code of Conduct
Grafonnet is developed within the Grafana community. Therefore we are following
the same [Code of Conduct as
Grafana](https://github.com/grafana/grafana/blob/master/CODE_OF_CONDUCT.md). You
need to agree and follow the code of conduct when you contribute to Grafonnet.
## License
We use the same
[license](https://github.com/grafana/grafonnet-lib/blob/master/LICENSE) as
[Grafana](https://github.com/grafana/grafana/blob/master/LICENSE) (ASL 2.0).
# Style Guide
## Code Style
Grafonnet carries no specific code style opinions. Only to use and enforce those
of [Jsonnet tooling](https://jsonnet.org/learning/tools.html). Particularly
`jsonnetfmt`.
Running `make test-update` will run the repository's unit tests while updating
files with `jsonnetfmt`. This is a good way to ensure your contributions are
formatted correctly.
## Component Composition
Grafonnet is, however, opinionated about component composition. This is to
ensure consistency among components and hopefully keep the codebase simple.
### Interfaces
All panels should implement the same one function interface, `panel.new`.
All datasource targets should implement the same one function interface,
`datasource.target`.
Most components are either panels or datasource targets so these two interfaces
cover the bases for the most part. An exception is `template` which implements a
function for each template type.
### Mutator Functions and Chaining
Being that components are to implement a single function, they are expected to
have mutator functions to build upon them. Each function should return the
component itself so mutator functions can be chained. A common example is
`panel.addTarget` for appending a datasource target.
```libsonnet
graphPanel.new(
'My Graph'
).addTarget(
prometheus.target('up'),
)
```
# Usage
## The Simplest Approach
Your current directory would look something like this:
```
▸ tree -L 1 .
.
├── dashboard.jsonnet
└── grafonnet-lib
1 directory, 1 file
```
You've cloned Grafonnet and you've create a file called, `dashboard.jsonnet`.
That file might look something like this:
```
local grafana = import 'grafonnet/grafana.libsonnet';
grafana.dashboard.new('Empty Dashboard')
```
From here, you can run the following command to generate your dashboard:
```
jsonnet -J grafonnet-lib dashboard.jsonnet
```
<details>
<summary>show output</summary>
```
{
"__inputs": [ ],
"__requires": [ ],
"annotations": {
"list": [ ]
},
"editable": false,
"gnetId": null,
"graphTooltip": 0,
"hideControls": false,
"id": null,
"links": [ ],
"refresh": "",
"rows": [ ],
"schemaVersion": 14,
"style": "dark",
"tags": [ ],
"templating": {
"list": [ ]
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
]
},
"timezone": "browser",
"title": "Empty Dashboard",
"version": 0
}
```
</details>
Next you need to actually create the dashboard on Grafana. One option is to
paste the dashboard JSON on the Grafana UI.
A less tedious approach would be to use Grafana's [dashboard
API](https://grafana.com/docs/grafana/latest/http_api/dashboard/). For example,
you could create and execute this script in our example directory:
```
#!/usr/bin/env bash
JSONNET_PATH=grafonnet-lib \
jsonnet dashboard.jsonnet > dashboard.json
payload="{\"dashboard\": $(jq . dashboard.json), \"overwrite\": true}"
curl -X POST $BASIC_AUTH \
-H 'Content-Type: application/json' \
-d "${payload}" \
"http://admin:admin@localhost:3000/api/dashboards/db"
```
The above URL assumes you're running a Grafana instance locally. You can do that
by running a Grafana container on your local Docker engine:
```
docker run --rm -d -p 3000:3000 grafana/grafana
```
## Grizzly
Another way you could manage your Grafonnet code is by using
[Grizzly](https://github.com/malcolmholmes/grizzly). Grizzly is a command line
tool for managing Grafana dashboards as code written in Jsonnet.
In this section, we'll assume you've used
[jsonnet-bundler](https://github.com/jsonnet-bundler/jsonnet-bundler) to install
Grafonnet. In which case your current directory would look like this:
```
▸ tree -L 2 .
.
├── dashboards.jsonnet
├── jsonnetfile.json
├── jsonnetfile.lock.json
└── vendor
├── github.com
└── grafonnet -> github.com/grafana/grafonnet-lib/grafonnet
3 directories, 3 files
```
`dashboards.jsonnet` (now plural) will look slightly different than before:
```
local grafana = import 'grafonnet/grafana.libsonnet';
{
grafanaDashboards:: {
empty_dashboard: grafana.dashboard.new('Empty Dashboard'),
},
}
```
First you need to set the `GRAFANA_URL` environment variable:
```
export GRAFANA_URL=http://admin:admin@localhost:3000
```
Next create the dashboard with on your Grafana instance with:
```
grr apply dashboards.jsonnet
```
Check [Grizzly's GitHub page](https://github.com/malcolmholmes/grizzly) for
other commands and documentation.
ARG CYPRESS_IMAGE
FROM $CYPRESS_IMAGE
WORKDIR /e2e
# dependencies will be installed only if the package files change
COPY package.json .
COPY package-lock.json .
# by setting CI environment variable we switch the Cypress install messages
# to small "started / finished" and avoid 1000s of lines of progress messages
# https://github.com/cypress-io/cypress/issues/1243
ENV CI=1
RUN npm ci
# verify that Cypress has been installed correctly.
# running this command separately from "cypress run" will also cache its result
# to avoid verifying again when running the tests
RUN npx cypress verify
# Grafonnet End to End Testing
Grafonnet uses [Cypress](https://www.cypress.io).
## Purpose
These tests attempt to assert truth to the following:
* Does this library generate valid dashboard JSON?
* Are dashboard elements displayed as expected?
* Do elements get configured as intended?
* Do the configured elements do what they're expected to do?
Some of this is automated here. However, the visual aspects are difficult for
machines to cover. Even some behavioral aspects are as well because they incur
an impractical amount of complexity, time, or cost. For those aspects, these
tests provide a way to quickly generate dashboards consistently so we can use
our human abilities to assert truth.
## Usage
`docker-compose` is used to run Cypress and Grafana. There are two targets in
[Makefile](../Makefile) to help run it.
`make e2e`: runs tests headless and exits.
`make e2e-dev`: opens the [the test
runner](https://docs.cypress.io/guides/core-concepts/test-runner.html#Overview)
and exposes Grafana to the host machine - http://localhost:3030. This requires
an X11 server to work. [This
post](https://www.cypress.io/blog/2019/05/02/run-cypress-with-a-single-docker-command/#Interactive-mode)
describes how to set this up with [XQuartz](https://www.xquartz.org/).
## Notes
Tests depend on compiled artifacts in [tests](../tests) for generating
dashboards.
describe('Gauge Panel', function() {
let panelTitles = []
before(function() {
let testDir = './tests/gauge_panel/test_compiled.json'
let uid = 'gauge-panel'
panelTitles = cy.createDashboardFromUnitTests(testDir, uid)
})
it('renders all gauge panels', function() {
cy.visit('/d/gauge-panel/gauge-panel')
for (const title of panelTitles) {
cy.contains(title)
}
})
})
describe('Graph Panel', function() {
let panelTitles = []
before(function() {
let testDir = './tests/graph_panel/test_compiled.json'
let uid = 'graph-panel'
// Exclude panels with alerts. They are incompatible with the test
// datasource. They will cause dashboard creation to fail.
let excludePanels = ["alerts", "alertsWithMultipleConditions"]
panelTitles = cy.createDashboardFromUnitTests(testDir, uid, excludePanels)
})
it('renders all graph panels', function() {
cy.visit('/d/graph-panel/graph-panel')
for (const title of panelTitles) {
cy.contains(title)
}
})
})
const fs = require('fs')
describe('Stat Panel', function() {
let panelTitles = []
before(function() {
let testDir = './tests/stat_panel/test_compiled.json'
let uid = 'stat-panel'
panelTitles = cy.createDashboardFromUnitTests(testDir, uid)
})
it('renders all stat panels', function() {
cy.visit('/d/stat-panel/stat-panel')
for (const title of panelTitles) {
cy.contains(title)
}
})
})
/// <reference types="cypress" />
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
/**
* @type {Cypress.PluginConfig}
*/
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
}
const http = require("http")
Cypress.Commands.overwrite('visit', (orig, url, options) => {
options = options || {}
options.auth = {
username: 'admin',
password: 'admin',
}
return orig(url, options)
})
Cypress.Commands.add('createDashboard', function(dashboardJSON) {
const payload = JSON.stringify({
dashboard: dashboardJSON,
overwrite: true
})
const options = {
auth: 'admin:admin',
hostname: 'grafana',
port: 3000,
path: '/api/dashboards/db',
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
}
const req = http.request(options)
req.write(payload)
req.end()
})
require('./commands')
const fs = require('fs')
// This does not use the usual Cypress.Commands.add registration so that it's
// performed synchronously and we're able to return the panelTitles variable.
cy.createDashboardFromUnitTests = function(testDir, uid, excludePanels=[]) {
let panelTitles = []
cy.readFile(testDir).then(function(str) {
let panels = []
for (let [i, [name, panel]] of Object.entries(Object.entries(str))) {
if (excludePanels.includes(name)) {
continue
}
panel['id'] = parseInt(i)
panel['gridPos'] = {'w': 6, 'h': 4, 'x': i * 6 % 24 }
panelTitles.push(panel.title)
panels.push(panel)
}
let dashboardJSON = {
'uid': uid,
'title': uid,
'panels': panels
}
cy.createDashboard(dashboardJSON)
})
return panelTitles
}
version: '3'
services:
grafana:
image: grafana/grafana:${GRAFANA_VERSION?err}
ports:
- "3030:3000"
e2e:
build:
context: .
args:
CYPRESS_IMAGE: cypress/included:4.7.0
image: grafonnet-e2e-dev
entrypoint: cypress open --project .
depends_on:
- grafana
environment:
- CYPRESS_baseUrl=http://grafana:3000
- CYPRESS_video=false
- DISPLAY=${DISPLAY?err}
volumes:
- ./cypress:/e2e/cypress
- ./cypress.json:/e2e/cypress.json
- ../tests:/e2e/tests
- /tmp/.X11-unix:/tmp/.X11-unix
version: '3'
services:
grafana:
image: grafana/grafana:${GRAFANA_VERSION?err}
e2e:
build:
context: .
args:
CYPRESS_IMAGE: cypress/base:12
image: grafonnet-e2e
command: npx cypress run
depends_on:
- grafana
environment:
- CYPRESS_baseUrl=http://grafana:3000
- CYPRESS_video=false
volumes:
- ./cypress:/e2e/cypress
- ./cypress.json:/e2e/cypress.json
- ../tests:/e2e/tests
This diff is collapsed.
{
"license": "Apache-2.0",
"devDependencies": {
"cypress": "^4.11.0"
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
local grafana = import 'grafonnet/grafana.libsonnet';
local dashboard = grafana.dashboard;
local template = grafana.template;
local singlestat = grafana.singlestat;
local graphPanel = grafana.graphPanel;
local prometheus = grafana.prometheus;
local buildInfo =
singlestat.new(
title='Version',
datasource='Prometheus',
format='none',
valueName='name',
).addTarget(
prometheus.target(
'prometheus_build_info{instance="$instance"}',
legendFormat='{{ version }}',
)
);
local systemLoad =
singlestat.new(
title='5m system load',
datasource='Prometheus',
format='none',
valueName='current',
decimals=2,
sparklineShow=true,
colorValue=true,
thresholds='4,6',
).addTarget(
prometheus.target(
'node_load5{instance="$instance"}',
)
);
local networkTraffic =
graphPanel.new(
title='Network traffic on eth0',
datasource='Prometheus',
linewidth=2,
format='Bps',
aliasColors={
Rx: 'light-green',
Tx: 'light-red',
},
).addTarget(
prometheus.target(
'rate(node_network_receive_bytes_total{instance="$instance",device="eth0"}[1m])',
legendFormat='Rx',
)
).addTarget(
prometheus.target(
'irate(node_network_transmit_bytes_total{instance="$instance",device="eth0"}[1m]) * (-1)',
legendFormat='Tx',
)
);
dashboard.new(
'Prometheus test',
tags=['prometheus'],
schemaVersion=18,
editable=true,
time_from='now-1h',
refresh='1m',
)
.addTemplate(
template.datasource(
'PROMETHEUS_DS',
'prometheus',
'Prometheus',
hide='label',
)
)
.addTemplate(
template.new(
'instance',
'$PROMETHEUS_DS',
'label_values(prometheus_build_info, instance)',
label='Instance',
refresh='time',
)
)
.addPanels(
[
buildInfo { gridPos: { h: 4, w: 3, x: 0, y: 0 } },
systemLoad { gridPos: { h: 4, w: 4, x: 3, y: 0 } },
networkTraffic { gridPos: { h: 8, w: 7, x: 0, y: 4 } },
]
)
This diff is collapsed.
This diff is collapsed.
// This file was generated by https://github.com/grafana/dashboard-spec
{
new(
description=null,
editable=true,
graphTooltip=0,
refresh=null,
schemaVersion=25,
style='dark',
tags=[],
timezone=null,
title=null,
uid=null,
):: {
[if description != null then 'description']: description,
[if editable != null then 'editable']: editable,
[if graphTooltip != null then 'graphTooltip']: graphTooltip,
[if refresh != null then 'refresh']: refresh,
[if schemaVersion != null then 'schemaVersion']: schemaVersion,
[if style != null then 'style']: style,
[if tags != null then 'tags']: tags,
[if timezone != null then 'timezone']: timezone,
[if title != null then 'title']: title,
[if uid != null then 'uid']: uid,
setTime(
from='now-6h',
to='now',
):: self {}
+ { time+: { [if from != null then 'from']: from } }
+ { time+: { [if to != null then 'to']: to } }
,
setTimepicker(
hidden=false,
refreshIntervals=['5s', '10s', '30s', '1m', '5m', '15m', '30m', '1h', '2h', '1d'],
):: self {}
+ { timepicker+: { [if hidden != null then 'hidden']: hidden } }
+ { timepicker+: { [if refreshIntervals != null then 'refresh_intervals']: refreshIntervals } }
,
addTemplate(
template
):: self {}
+ { templating+: { list+: [
template,
] } },
addAnnotation(
builtIn=0,
datasource='default',
enable=true,
hide=false,
iconColor=null,
name=null,
rawQuery=null,
showIn=0,
):: self {}
+ { annotations+: { list+: [
{
[if builtIn != null then 'builtIn']: builtIn,
[if datasource != null then 'datasource']: datasource,
[if enable != null then 'enable']: enable,
[if hide != null then 'hide']: hide,
[if iconColor != null then 'iconColor']: iconColor,
[if name != null then 'name']: name,
[if rawQuery != null then 'rawQuery']: rawQuery,
[if showIn != null then 'showIn']: showIn,
},
] } },
panels: [],
_nextPanelID:: 2,
addPanel(panel):: self {
local nextPanelID = super._nextPanelID,
panels+: [
panel { id: nextPanelID } +
if 'panels' in panel then { panels: std.mapWithIndex(function(i, p) p { id: nextPanelID + i + 1 }, panel.panels) } else {},
],
_nextPanelID:: nextPanelID + 1 + (if 'panels' in panel then std.length(panel.panels) else 0),
},
addPanels(panels):: std.foldl(function(d, p) d.addPanel(p), panels, self),
},
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment