Add Grafana to master-slave monitoring stack

Feature: Issue 12619
Change-Id: Ia1e64e39d192402ec329b0497591f3be34503d5b
diff --git a/master-slave/Makefile b/master-slave/Makefile
index 90b60e4..aa87fc3 100644
--- a/master-slave/Makefile
+++ b/master-slave/Makefile
@@ -22,7 +22,9 @@
 		--stack-name $(CLUSTER_STACK_NAME) \
 		--capabilities CAPABILITY_IAM  \
 		--template-body file://`pwd`/$(CLUSTER_TEMPLATE) \
-		--region $(AWS_REGION)
+		--region $(AWS_REGION) \
+		--parameters \
+		ParameterKey=DesiredCapacity,ParameterValue=$(CLUSTER_DESIRED_CAPACITY)
 
 service-master:
 	$(AWS_FC_COMMAND) create-stack \
@@ -154,5 +156,8 @@
 prometheus-publish:
 	$(MAKE) -C ../monitoring/prometheus prometheus-publish RECIPE=master-slave
 
+grafana-publish:
+	$(MAKE) -C ../monitoring/grafana grafana-publish RECIPE=master-slave
+
 service-monitoring:
 	$(MAKE) -C ../monitoring service-monitoring RECIPE=master-slave
diff --git a/master-slave/README.md b/master-slave/README.md
index 4dcff58..6b5baa5 100644
--- a/master-slave/README.md
+++ b/master-slave/README.md
@@ -73,6 +73,11 @@
 * `HOSTED_ZONE_NAME`: Optional. Name of the hosted zone. `mycompany.com` by default.
 * `MASTER_SUBDOMAIN`: Optional. Name of the master sub domain. `gerrit-master-demo` by default.
 * `SLAVE_SUBDOMAIN`: Optional. Name of the slave sub domain. `gerrit-slave-demo` by default.
+* `CLUSTER_DESIRED_CAPACITY`: Optional.  Number of EC2 instances composing the cluster. `1` by default.
+
+*NOTE: if you are planning to run the monitoring stack, set the
+`CLUSTER_DESIRED_CAPACITY` value to at least 2. The resources provided by
+a single EC2 instance won't be enough for all the services that will be ran*
 
 ### Prerequisites
 
diff --git a/master-slave/setup.env.template b/master-slave/setup.env.template
index 30fee20..eb20cf5 100644
--- a/master-slave/setup.env.template
+++ b/master-slave/setup.env.template
@@ -1,13 +1,16 @@
 CLUSTER_STACK_NAME:=gerrit-cluster
+CLUSTER_DESIRED_CAPACITY:=1
 SERVICE_MASTER_STACK_NAME:=gerrit-service-master
 SERVICE_SLAVE_STACK_NAME:=gerrit-service-slave
 SERVICE_PROMETHEUS_STACK_NAME:=gerrit-prometheus
+SERVICE_GRAFANA_STACK_NAME:=gerrit-grafana
 DNS_ROUTING_STACK_NAME:=gerrit-dns-routing
 DNS_ROUTING_MONITORING_STACK_NAME:=gerrit-monitoring-dns-routing
 HOSTED_ZONE_NAME:=yourcompany.com
 MASTER_SUBDOMAIN:=gerrit-master.gerrit-demo
 SLAVE_SUBDOMAIN:=gerrit-slave.gerrit-demo
 PROMETHEUS_SUBDOMAIN:=gerrit-prometheus.gerrit-demo
+GRAFANA_SUBDOMAIN:=gerrit-grafana.gerrit-demo
 AWS_REGION:=us-east-1
 DOCKER_REGISTRY_URI:=<yourAccountId>.dkr.ecr.us-east-1.amazonaws.com
 SSL_CERTIFICATE_ARN=arn:aws:acm:us-east-1:<yourAccountId>:certificate/33e2c235-a4d1-42b7-b866-18d8d744975c
diff --git a/monitoring/Makefile b/monitoring/Makefile
index 3a77cc9..f703011 100644
--- a/monitoring/Makefile
+++ b/monitoring/Makefile
@@ -2,9 +2,11 @@
 
 AWS_FC_COMMAND=export AWS_PAGER=;aws cloudformation
 SERVICE_PROMETHEUS_TEMPLATE:=cf-service-prometheus.yml
+SERVICE_GRAFANA_TEMPLATE:=cf-service-grafana.yml
 DNS_ROUTING_MONITORING_TEMPLATE:=cf-dns-route.yml
 
 service-monitoring: service-prometheus wait-for-service-prometheus-creation \
+										service-grafana wait-for-service-grafana-creation \
 										dns-monitoring-routing wait-for-dns-routing-creation
 
 service-prometheus:
@@ -22,6 +24,23 @@
 		ParameterKey=CertificateArn,ParameterValue=$(SSL_CERTIFICATE_ARN) \
 		ParameterKey=TokenVersion,ParameterValue=$(TOKEN_VERSION)
 
+service-grafana:
+	@echo "Create Grafana stack"
+	$(AWS_FC_COMMAND) create-stack \
+		--stack-name $(SERVICE_GRAFANA_STACK_NAME) \
+		--capabilities CAPABILITY_IAM  \
+		--template-body file://`pwd`/$(SERVICE_GRAFANA_TEMPLATE) \
+		--region $(AWS_REGION) \
+		--parameters \
+		ParameterKey=ClusterStackName,ParameterValue=$(CLUSTER_STACK_NAME) \
+		ParameterKey=PrometheusStackName,ParameterValue=$(SERVICE_PROMETHEUS_STACK_NAME) \
+		ParameterKey=MasterStackName,ParameterValue=$(SERVICE_MASTER_STACK_NAME) \
+		ParameterKey=SlaveStackName,ParameterValue=$(SERVICE_SLAVE_STACK_NAME) \
+		ParameterKey=HostedZoneName,ParameterValue=$(HOSTED_ZONE_NAME) \
+		ParameterKey=Subdomain,ParameterValue=$(GRAFANA_SUBDOMAIN) \
+		ParameterKey=DockerRegistryUrl,ParameterValue=$(DOCKER_REGISTRY_URI) \
+		ParameterKey=CertificateArn,ParameterValue=$(SSL_CERTIFICATE_ARN)
+
 dns-monitoring-routing:
 	$(AWS_FC_COMMAND) create-stack \
 		--stack-name $(DNS_ROUTING_MONITORING_STACK_NAME) \
@@ -29,7 +48,8 @@
 		--template-body file://`pwd`/$(DNS_ROUTING_MONITORING_TEMPLATE) \
 		--region $(AWS_REGION) \
 		--parameters \
-		ParameterKey=PrometheusServiceStackName,ParameterValue=$(SERVICE_PROMETHEUS_STACK_NAME)
+		ParameterKey=PrometheusServiceStackName,ParameterValue=$(SERVICE_PROMETHEUS_STACK_NAME) \
+		ParameterKey=GrafanaServiceStackName,ParameterValue=$(SERVICE_GRAFANA_STACK_NAME)
 
 wait-for-service-prometheus-creation:
 	@echo "*** Wait for Serice Prometheus stack '$(SERVICE_PROMETHEUS_STACK_NAME)' creation"
@@ -38,6 +58,13 @@
 	--region $(AWS_REGION)
 	@echo "*** Serice Prometheus stack '$(SERVICE_PROMETHEUS_STACK_NAME)' created"
 
+wait-for-service-grafana-creation:
+	@echo "*** Wait for Serice Grafana stack '$(SERVICE_GRAFANA_STACK_NAME)' creation"
+	$(AWS_FC_COMMAND) wait stack-create-complete \
+	--stack-name $(SERVICE_GRAFANA_STACK_NAME) \
+	--region $(AWS_REGION)
+	@echo "*** Serice Prometheus stack '$(SERVICE_GRAFANA_STACK_NAME)' created"
+
 wait-for-dns-routing-creation:
 	@echo "*** Wait for DNS routing stack '$(DNS_ROUTING_MONITORING_STACK_NAME)' creation"
 	$(AWS_FC_COMMAND) wait stack-create-complete \
diff --git a/monitoring/README.md b/monitoring/README.md
index a5f3051..e9e098c 100644
--- a/monitoring/README.md
+++ b/monitoring/README.md
@@ -15,6 +15,13 @@
 
 * From the main cookbook, publish the image: `make prometheus-publish`
 
+### Publish custom Grafana Docker image
+
+* Create the repository in the Docker registry:
+  `aws ecr create-repository --repository-name aws-gerrit/grafana`
+
+* From the main cookbook, publish the image: `make grafana-publish`
+
 ### Import a Prometheus Bearer Token
 
 * [Generate](https://www.uuidgenerator.net/) a Token
@@ -46,3 +53,19 @@
   | grep OutputValue \
   | cut -d'"' -f 4
 ```
+
+### Access your Grafana instance
+
+Get the URL of your Prometheus instance this way:
+
+```
+aws cloudformation describe-stacks \
+  --stack-name <SERVICE_PROMETHEUS_STACK_NAME> \
+  | grep -A1 '"OutputKey": "CanonicalWebUrl"' \
+  | grep OutputValue \
+  | cut -d'"' -f 4
+```
+
+The default credentials are:
+* user `admin`
+* password `admin`
diff --git a/monitoring/cf-dns-route.yml b/monitoring/cf-dns-route.yml
index 2292e0e..36dc0ed 100644
--- a/monitoring/cf-dns-route.yml
+++ b/monitoring/cf-dns-route.yml
@@ -5,6 +5,10 @@
       Description: Stack name of the ECS Prometheus service
       Type: String
       Default: gerrit-prometheus
+  GrafanaServiceStackName:
+      Description: Stack name of the ECS Grafana service
+      Type: String
+      Default: gerrit-grafana
 
 Resources:
   PrometheusDnsRecord:
@@ -20,7 +24,7 @@
             - ''
             - - Fn::ImportValue: !Join [':', [!Ref 'PrometheusServiceStackName', 'HostedZoneName']]
               - '.'
-        Comment: DNS name for Gerrit Master.
+        Comment: DNS name for Prometheus.
         Type: A
         AliasTarget:
           DNSName:
@@ -30,3 +34,26 @@
             Fn::ImportValue:
               !Join [':', [!Ref 'PrometheusServiceStackName', 'CanonicalHostedZoneID']]
           EvaluateTargetHealth: False
+  GrafanaDnsRecord:
+      Type: AWS::Route53::RecordSet
+      Properties:
+        Name:
+          !Join
+            - '.'
+            - - Fn::ImportValue: !Join [':', [!Ref 'GrafanaServiceStackName', 'Subdomain']]
+              - Fn::ImportValue: !Join [':', [!Ref 'GrafanaServiceStackName', 'HostedZoneName']]
+        HostedZoneName:
+          !Join
+            - ''
+            - - Fn::ImportValue: !Join [':', [!Ref 'GrafanaServiceStackName', 'HostedZoneName']]
+              - '.'
+        Comment: DNS name for Grafana.
+        Type: A
+        AliasTarget:
+          DNSName:
+            Fn::ImportValue:
+              !Join [':', [!Ref 'GrafanaServiceStackName', 'PublicLoadBalancerDNSName']]
+          HostedZoneId:
+            Fn::ImportValue:
+              !Join [':', [!Ref 'GrafanaServiceStackName', 'CanonicalHostedZoneID']]
+          EvaluateTargetHealth: False
diff --git a/monitoring/cf-service-grafana.yml b/monitoring/cf-service-grafana.yml
new file mode 100644
index 0000000..a159f6f
--- /dev/null
+++ b/monitoring/cf-service-grafana.yml
@@ -0,0 +1,215 @@
+  AWSTemplateFormatVersion: '2010-09-09'
+  Description: Deploy a Grafana service into an ECS cluster behind a public load balancer.
+  Parameters:
+    GrafanaServiceName:
+        Type: String
+        Default: gerrit-grafana
+    PrometheusStackName:
+        Type: String
+        Default: gerrit-prometheus
+    MasterStackName:
+        Type: String
+        Default: gerrit-master
+    SlaveStackName:
+        Type: String
+        Default: gerrit-slave
+    ClusterStackName:
+        Description: Stack name of the ECS cluster to deploy the serivces
+        Type: String
+        Default: gerrit-cluster
+    EnvironmentName:
+        Description: An environment name that will be prefixed to the resource names
+        Type: String
+        Default: test
+    DockerImage:
+          Description: Grafana official Docker image
+          Type: String
+          Default: aws-gerrit/grafana:latest
+    DockerRegistryUrl:
+          Description: Docker registry URL
+          Type: String
+    DesiredCount:
+          Description: How many instances of this task should we run across our cluster?
+          Type: Number
+          Default: 1
+    HTTPPort:
+          Description: Grafana HTTP port
+          Type: Number
+          Default: 3000
+    HTTPSPort:
+          Description: Grafana HTTPS port
+          Type: Number
+          Default: 443
+    CertificateArn:
+          Description: SSL Certificates ARN
+          Type: String
+    HostedZoneName:
+          Description: The route53 HostedZoneName.
+          Type: String
+    Subdomain:
+          Description: The subdomain of the Monitoring service
+          Type: String
+          Default: gerrit-grafana
+
+  Resources:
+      Service:
+          Type: AWS::ECS::Service
+          DependsOn:
+            - HTTPListener
+          Properties:
+              Cluster:
+                Fn::ImportValue:
+                    !Join [':', [!Ref 'ClusterStackName', 'ClusterName']]
+              DesiredCount: !Ref DesiredCount
+              TaskDefinition: !Ref TaskDefinition
+              LoadBalancers:
+                  - ContainerName: !Ref GrafanaServiceName
+                    ContainerPort: !Ref HTTPPort
+                    TargetGroupArn: !Ref HTTPTargetGroup
+
+      TaskDefinition:
+          Type: AWS::ECS::TaskDefinition
+          Properties:
+              Family: !Join ['', [!Ref GrafanaServiceName, TaskDefinition]]
+              TaskRoleArn: !Ref ECSTaskExecutionRole
+              ExecutionRoleArn: !Ref ECSTaskExecutionRole
+              NetworkMode: bridge
+              ContainerDefinitions:
+                  - Name: !Ref GrafanaServiceName
+                    Essential: true
+                    Image: !Sub '${DockerRegistryUrl}/${DockerImage}'
+                    Environment:
+                      - Name: AWS_REGION
+                        Value: !Ref AWS::Region
+                      - Name: GRAFANA_DOMAIN
+                        Value: !Sub '${Subdomain}.${HostedZoneName}'
+                      - Name: MASTER_URL
+                        Value:
+                          !Join
+                            - '.'
+                            - - Fn::ImportValue: !Join [':', [!Ref 'MasterStackName', 'Subdomain']]
+                              - Fn::ImportValue: !Join [':', [!Ref 'MasterStackName', 'HostedZoneName']]
+                      - Name: SLAVE_URL
+                        Value:
+                          !Join
+                            - '.'
+                            - - Fn::ImportValue: !Join [':', [!Ref 'SlaveStackName', 'Subdomain']]
+                              - Fn::ImportValue: !Join [':', [!Ref 'SlaveStackName', 'HostedZoneName']]
+                      - Name: PROMETHEUS_URL
+                        Value:
+                          Fn::ImportValue:
+                              !Join [':', [!Ref 'PrometheusStackName', 'CanonicalWebUrl']]
+                    Cpu: 1024
+                    Memory: 2048
+                    PortMappings:
+                      - ContainerPort: !Ref HTTPPort
+                        HostPort: !Ref HTTPPort
+                        Protocol: tcp
+                    LogConfiguration:
+                      LogDriver: awslogs
+                      Options:
+                          awslogs-group: !Ref ClusterStackName
+                          awslogs-region: !Ref AWS::Region
+                          awslogs-stream-prefix: !Ref EnvironmentName
+
+      LoadBalancer:
+          Type: AWS::ElasticLoadBalancingV2::LoadBalancer
+          Properties:
+              Type: network
+              Scheme: internet-facing
+              Subnets:
+                - Fn::ImportValue:
+                    !Join [':', [!Ref 'ClusterStackName', 'PublicSubnetOne']]
+              Tags:
+                  - Key: Name
+                    Value: !Join ['-', [!Ref 'EnvironmentName', !Ref 'GrafanaServiceName', 'monitoring']]
+
+      HTTPTargetGroup:
+          Type: AWS::ElasticLoadBalancingV2::TargetGroup
+          DependsOn: LoadBalancer
+          Properties:
+              VpcId:
+                Fn::ImportValue:
+                    !Join [':', [!Ref 'ClusterStackName', 'VPCId']]
+              Port: !Ref HTTPPort
+              Protocol: TCP
+
+      HTTPListener:
+          Type: AWS::ElasticLoadBalancingV2::Listener
+          DependsOn: LoadBalancer
+          Properties:
+              Certificates:
+                - CertificateArn: !Ref CertificateArn
+              DefaultActions:
+              - Type: forward
+                TargetGroupArn: !Ref HTTPTargetGroup
+              LoadBalancerArn: !Ref LoadBalancer
+              Port: !Ref HTTPSPort
+              Protocol: TLS
+
+      # This is a role which is used by the ECS tasks themselves.
+      ECSTaskExecutionRole:
+        Type: AWS::IAM::Role
+        Properties:
+          AssumeRolePolicyDocument:
+            Statement:
+            - Effect: Allow
+              Principal:
+                Service: [ecs-tasks.amazonaws.com]
+              Action: ['sts:AssumeRole']
+          Path: /
+          Policies:
+            - PolicyName: AmazonECSTaskExecutionRolePolicy
+              PolicyDocument:
+                Statement:
+                - Effect: Allow
+                  Action:
+                    # Allow the ECS Tasks to download images from ECR
+                    - 'ecr:GetAuthorizationToken'
+                    - 'ecr:BatchCheckLayerAvailability'
+                    - 'ecr:GetDownloadUrlForLayer'
+                    - 'ecr:BatchGetImage'
+                    # Allow the ECS tasks to upload logs to CloudWatch
+                    - 'logs:CreateLogStream'
+                    - 'logs:PutLogEvents'
+                  Resource: '*'
+            - PolicyName: AmazonECSTaskSecretManagerRolePolicy
+              PolicyDocument:
+                Statement:
+                - Effect: Allow
+                  Action:
+                    # Allow the ECS Tasks to get SSH Keys
+                    - 'secretsmanager:GetSecretValue'
+                    - 'kms:Decrypt'
+                  Resource: '*'
+  Outputs:
+    PublicLoadBalancerDNSName:
+      Description: The DNS name of the external load balancer
+      Value: !GetAtt 'LoadBalancer.DNSName'
+      Export:
+        Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicLoadBalancerDNSName' ] ]
+    CanonicalHostedZoneID:
+      Description: Canonical Hosted Zone ID
+      Value: !GetAtt 'LoadBalancer.CanonicalHostedZoneID'
+      Export:
+        Name: !Join [ ':', [ !Ref 'AWS::StackName', 'CanonicalHostedZoneID' ] ]
+    PublicLoadBalancerUrl:
+      Description: The url of the external load balancer
+      Value: !Join ['', ['http://', !GetAtt 'LoadBalancer.DNSName']]
+      Export:
+        Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicLoadBalancerUrl' ] ]
+    HostedZoneName:
+      Description: Route53 Hosted Zone name
+      Value: !Ref HostedZoneName
+      Export:
+        Name: !Join [ ':', [ !Ref 'AWS::StackName', 'HostedZoneName' ] ]
+    Subdomain:
+      Description: Service DNS subdomain
+      Value: !Ref Subdomain
+      Export:
+        Name: !Join [ ':', [ !Ref 'AWS::StackName', 'Subdomain' ] ]
+    CanonicalWebUrl:
+      Description: Canonical Web URL
+      Value: !Sub 'https://${Subdomain}.${HostedZoneName}'
+      Export:
+        Name: !Join [ ':', [ !Ref 'AWS::StackName', 'CanonicalWebUrl' ] ]
diff --git a/monitoring/grafana/Dockerfile b/monitoring/grafana/Dockerfile
new file mode 100644
index 0000000..cae1df8
--- /dev/null
+++ b/monitoring/grafana/Dockerfile
@@ -0,0 +1,13 @@
+FROM grafana/grafana:6.7.0
+
+USER root
+ADD ./provisioning /etc/grafana/provisioning
+ADD ./config.ini /etc/grafana/config.ini
+ADD ./dashboards /var/lib/grafana/dashboards
+COPY --chown=grafana:grafana entrypoint.sh /entrypoint.sh
+RUN chown -R grafana:grafana /etc/grafana && \
+    chown -R grafana:grafana /var/lib/grafana && \
+    chmod +x /entrypoint.sh
+
+USER grafana
+ENTRYPOINT /entrypoint.sh
diff --git a/monitoring/grafana/Makefile b/monitoring/grafana/Makefile
new file mode 100644
index 0000000..e613e81
--- /dev/null
+++ b/monitoring/grafana/Makefile
@@ -0,0 +1,12 @@
+include ../../$(RECIPE)/setup.env
+
+docker-registry-login:
+	aws ecr get-login-password --region $(AWS_REGION) \
+		| docker login --username AWS --password-stdin $(DOCKER_REGISTRY_URI)/aws-gerrit/grafana
+
+grafana-build:
+	docker build -t aws-gerrit/grafana .
+	docker tag aws-gerrit/grafana:latest $(DOCKER_REGISTRY_URI)/aws-gerrit/grafana:latest
+
+grafana-publish: docker-registry-login grafana-build
+	docker push $(DOCKER_REGISTRY_URI)/aws-gerrit/grafana:latest
diff --git a/monitoring/grafana/config.ini b/monitoring/grafana/config.ini
new file mode 100644
index 0000000..9ee0efe
--- /dev/null
+++ b/monitoring/grafana/config.ini
@@ -0,0 +1,9 @@
+[paths]
+provisioning = /etc/grafana/provisioning
+
+[server]
+enable_gzip = true
+domain = {{DOMAIN}}
+
+[users]
+default_theme = light
diff --git a/monitoring/grafana/dashboards/Gerrit.json b/monitoring/grafana/dashboards/Gerrit.json
new file mode 100644
index 0000000..45d41e4
--- /dev/null
+++ b/monitoring/grafana/dashboards/Gerrit.json
@@ -0,0 +1,2047 @@
+{
+  "annotations": {
+    "list": [
+      {
+        "builtIn": 1,
+        "datasource": "-- Grafana --",
+        "enable": true,
+        "hide": true,
+        "iconColor": "rgba(0, 211, 255, 1)",
+        "name": "Annotations & Alerts",
+        "type": "dashboard"
+      }
+    ]
+  },
+  "description": "Metrics dashboard for Gerrit multi-site",
+  "editable": true,
+  "gnetId": 4496,
+  "graphTooltip": 0,
+  "id": 1,
+  "iteration": 1587574303643,
+  "links": [],
+  "panels": [
+    {
+      "cacheTimeout": null,
+      "colorBackground": false,
+      "colorValue": false,
+      "colors": [
+        "#299c46",
+        "rgba(237, 129, 40, 0.89)",
+        "#d44a3a"
+      ],
+      "datasource": "Prometheus",
+      "decimals": 0,
+      "format": "none",
+      "gauge": {
+        "maxValue": 100,
+        "minValue": 0,
+        "show": true,
+        "thresholdLabels": false,
+        "thresholdMarkers": false
+      },
+      "gridPos": {
+        "h": 8,
+        "w": 6,
+        "x": 0,
+        "y": 0
+      },
+      "id": 92,
+      "interval": null,
+      "links": [],
+      "mappingType": 1,
+      "mappingTypes": [
+        {
+          "name": "value to text",
+          "value": 1
+        },
+        {
+          "name": "range to text",
+          "value": 2
+        }
+      ],
+      "maxDataPoints": 100,
+      "nullPointMode": "connected",
+      "nullText": null,
+      "postfix": "",
+      "postfixFontSize": "50%",
+      "prefix": "",
+      "prefixFontSize": "50%",
+      "rangeMaps": [
+        {
+          "from": "null",
+          "text": "N/A",
+          "to": "null"
+        }
+      ],
+      "sparkline": {
+        "fillColor": "rgba(31, 118, 189, 0.18)",
+        "full": false,
+        "lineColor": "rgb(31, 120, 193)",
+        "show": false
+      },
+      "tableColumn": "",
+      "targets": [
+        {
+          "expr": "max_over_time(sshd_sessions_connected{instance=\"{{MASTER_URL}}:443\"}[$Window])",
+          "format": "time_series",
+          "instant": true,
+          "interval": "",
+          "intervalFactor": 1,
+          "legendFormat": "",
+          "refId": "A"
+        }
+      ],
+      "thresholds": "30,60",
+      "title": "SSH Conn master",
+      "type": "singlestat",
+      "valueFontSize": "80%",
+      "valueMaps": [
+        {
+          "op": "=",
+          "text": "0",
+          "value": "null"
+        }
+      ],
+      "valueName": "avg"
+    },
+    {
+      "cacheTimeout": null,
+      "colorBackground": false,
+      "colorValue": false,
+      "colors": [
+        "#299c46",
+        "rgba(237, 129, 40, 0.89)",
+        "#d44a3a"
+      ],
+      "datasource": "Prometheus",
+      "decimals": 0,
+      "format": "none",
+      "gauge": {
+        "maxValue": 100,
+        "minValue": 0,
+        "show": true,
+        "thresholdLabels": false,
+        "thresholdMarkers": false
+      },
+      "gridPos": {
+        "h": 8,
+        "w": 6,
+        "x": 6,
+        "y": 0
+      },
+      "id": 94,
+      "interval": null,
+      "links": [],
+      "mappingType": 1,
+      "mappingTypes": [
+        {
+          "name": "value to text",
+          "value": 1
+        },
+        {
+          "name": "range to text",
+          "value": 2
+        }
+      ],
+      "maxDataPoints": 100,
+      "nullPointMode": "connected",
+      "nullText": null,
+      "postfix": "",
+      "postfixFontSize": "50%",
+      "prefix": "",
+      "prefixFontSize": "50%",
+      "rangeMaps": [
+        {
+          "from": "null",
+          "text": "N/A",
+          "to": "null"
+        }
+      ],
+      "sparkline": {
+        "fillColor": "rgba(31, 118, 189, 0.18)",
+        "full": false,
+        "lineColor": "rgb(31, 120, 193)",
+        "show": false
+      },
+      "tableColumn": "env",
+      "targets": [
+        {
+          "expr": "max_over_time(sshd_sessions_connected{instance=\"{{SLAVE_URL}}:443\"}[$Window])",
+          "format": "time_series",
+          "instant": true,
+          "intervalFactor": 1,
+          "refId": "A"
+        }
+      ],
+      "thresholds": "30,60",
+      "title": "SSH Conn slave",
+      "type": "singlestat",
+      "valueFontSize": "80%",
+      "valueMaps": [
+        {
+          "op": "=",
+          "text": "0",
+          "value": "null"
+        }
+      ],
+      "valueName": "avg"
+    },
+    {
+      "collapsed": false,
+      "datasource": null,
+      "gridPos": {
+        "h": 1,
+        "w": 24,
+        "x": 0,
+        "y": 8
+      },
+      "id": 100,
+      "panels": [],
+      "title": "Live Status",
+      "type": "row"
+    },
+    {
+      "aliasColors": {
+        "review-1-javamelody": "#5195ce",
+        "review-2-javamelody": "#7eb26d"
+      },
+      "bars": false,
+      "dashLength": 10,
+      "dashes": false,
+      "datasource": "Prometheus",
+      "description": "system cpu load",
+      "fill": 1,
+      "fillGradient": 0,
+      "gridPos": {
+        "h": 10,
+        "w": 8,
+        "x": 0,
+        "y": 9
+      },
+      "hiddenSeries": false,
+      "id": 3,
+      "legend": {
+        "alignAsTable": true,
+        "avg": true,
+        "current": true,
+        "max": true,
+        "min": true,
+        "show": true,
+        "total": false,
+        "values": true
+      },
+      "lines": true,
+      "linewidth": 1,
+      "links": [],
+      "nullPointMode": "null",
+      "options": {
+        "dataLinks": []
+      },
+      "percentage": false,
+      "pointradius": 5,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "application": {
+            "filter": ""
+          },
+          "expr": "avg_over_time(javamelody_system_cpu_load_pct[$Window])",
+          "format": "time_series",
+          "functions": [],
+          "group": {
+            "filter": ""
+          },
+          "host": {
+            "filter": ""
+          },
+          "instant": false,
+          "interval": "30s",
+          "intervalFactor": 2,
+          "item": {
+            "filter": ""
+          },
+          "legendFormat": "{{instance}}",
+          "mode": 0,
+          "options": {
+            "showDisabledItems": false
+          },
+          "refId": "A",
+          "triggers": {
+            "acknowledged": 2,
+            "count": true,
+            "minSeverity": 3
+          }
+        }
+      ],
+      "thresholds": [],
+      "timeFrom": null,
+      "timeRegions": [],
+      "timeShift": null,
+      "title": "% Cpu Load",
+      "tooltip": {
+        "shared": true,
+        "sort": 0,
+        "value_type": "individual"
+      },
+      "type": "graph",
+      "xaxis": {
+        "buckets": null,
+        "mode": "time",
+        "name": null,
+        "show": true,
+        "values": []
+      },
+      "yaxes": [
+        {
+          "decimals": null,
+          "format": "percent",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": "0",
+          "show": true
+        },
+        {
+          "format": "short",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": null,
+          "show": true
+        }
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
+    },
+    {
+      "aliasColors": {
+        "review-1-javamelody": "#5195ce",
+        "review-2-javamelody": "#7eb26d"
+      },
+      "bars": false,
+      "dashLength": 10,
+      "dashes": false,
+      "datasource": "Prometheus",
+      "description": "used memory for JGit cache in bytes",
+      "fill": 1,
+      "fillGradient": 0,
+      "gridPos": {
+        "h": 10,
+        "w": 8,
+        "x": 8,
+        "y": 9
+      },
+      "hiddenSeries": false,
+      "id": 146,
+      "interval": "1s",
+      "legend": {
+        "alignAsTable": true,
+        "avg": true,
+        "current": true,
+        "max": true,
+        "min": true,
+        "show": true,
+        "total": false,
+        "values": true
+      },
+      "lines": true,
+      "linewidth": 1,
+      "links": [],
+      "nullPointMode": "null",
+      "options": {
+        "dataLinks": []
+      },
+      "percentage": false,
+      "pointradius": 5,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [
+        {
+          "alias": "review-1-javamelody",
+          "yaxis": 1
+        }
+      ],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "application": {
+            "filter": ""
+          },
+          "expr": "avg_over_time(jgit_block_cache_cache_used[$Window])",
+          "format": "time_series",
+          "functions": [],
+          "group": {
+            "filter": ""
+          },
+          "host": {
+            "filter": ""
+          },
+          "instant": false,
+          "interval": "10s",
+          "intervalFactor": 1,
+          "item": {
+            "filter": ""
+          },
+          "legendFormat": "{{instance}}",
+          "mode": 0,
+          "options": {
+            "showDisabledItems": false
+          },
+          "refId": "A",
+          "triggers": {
+            "acknowledged": 2,
+            "count": true,
+            "minSeverity": 3
+          }
+        }
+      ],
+      "thresholds": [],
+      "timeFrom": null,
+      "timeRegions": [],
+      "timeShift": null,
+      "title": "JGit Cache",
+      "tooltip": {
+        "shared": true,
+        "sort": 0,
+        "value_type": "individual"
+      },
+      "type": "graph",
+      "xaxis": {
+        "buckets": null,
+        "mode": "time",
+        "name": null,
+        "show": true,
+        "values": []
+      },
+      "yaxes": [
+        {
+          "format": "decbytes",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": "0",
+          "show": true
+        },
+        {
+          "format": "short",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": "0",
+          "show": false
+        }
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
+    },
+    {
+      "aliasColors": {
+        "review-1-javamelody": "#5195ce",
+        "review-2-javamelody": "#7eb26d"
+      },
+      "bars": false,
+      "dashLength": 10,
+      "dashes": false,
+      "datasource": "Prometheus",
+      "description": "used memory in bytes",
+      "fill": 1,
+      "fillGradient": 0,
+      "gridPos": {
+        "h": 10,
+        "w": 8,
+        "x": 16,
+        "y": 9
+      },
+      "hiddenSeries": false,
+      "id": 12,
+      "interval": "1s",
+      "legend": {
+        "alignAsTable": true,
+        "avg": true,
+        "current": true,
+        "max": true,
+        "min": true,
+        "show": true,
+        "total": false,
+        "values": true
+      },
+      "lines": true,
+      "linewidth": 1,
+      "links": [],
+      "nullPointMode": "null",
+      "options": {
+        "dataLinks": []
+      },
+      "percentage": false,
+      "pointradius": 5,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [
+        {
+          "alias": "review-1-javamelody",
+          "yaxis": 1
+        }
+      ],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "application": {
+            "filter": ""
+          },
+          "expr": "avg_over_time(javamelody_memory_used_bytes[$Window])",
+          "format": "time_series",
+          "functions": [],
+          "group": {
+            "filter": ""
+          },
+          "host": {
+            "filter": ""
+          },
+          "instant": false,
+          "interval": "10s",
+          "intervalFactor": 1,
+          "item": {
+            "filter": ""
+          },
+          "legendFormat": "{{instance}}",
+          "mode": 0,
+          "options": {
+            "showDisabledItems": false
+          },
+          "refId": "A",
+          "triggers": {
+            "acknowledged": 2,
+            "count": true,
+            "minSeverity": 3
+          }
+        }
+      ],
+      "thresholds": [],
+      "timeFrom": null,
+      "timeRegions": [],
+      "timeShift": null,
+      "title": "Used Memory",
+      "tooltip": {
+        "shared": true,
+        "sort": 0,
+        "value_type": "individual"
+      },
+      "type": "graph",
+      "xaxis": {
+        "buckets": null,
+        "mode": "time",
+        "name": null,
+        "show": true,
+        "values": []
+      },
+      "yaxes": [
+        {
+          "format": "decbytes",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": "0",
+          "show": true
+        },
+        {
+          "format": "short",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": "0",
+          "show": false
+        }
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
+    },
+    {
+      "aliasColors": {},
+      "bars": false,
+      "dashLength": 10,
+      "dashes": false,
+      "datasource": "Prometheus",
+      "fill": 1,
+      "fillGradient": 0,
+      "gridPos": {
+        "h": 10,
+        "w": 8,
+        "x": 0,
+        "y": 19
+      },
+      "hiddenSeries": false,
+      "id": 118,
+      "legend": {
+        "alignAsTable": true,
+        "avg": true,
+        "current": false,
+        "max": true,
+        "min": true,
+        "show": true,
+        "total": false,
+        "values": true
+      },
+      "lines": true,
+      "linewidth": 1,
+      "links": [],
+      "nullPointMode": "null",
+      "options": {
+        "dataLinks": []
+      },
+      "percentage": false,
+      "pointradius": 5,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "expr": "delta(http_server_error_count_500_total[$Window])",
+          "format": "time_series",
+          "interval": "",
+          "intervalFactor": 1,
+          "legendFormat": "{{instance}}",
+          "refId": "A"
+        }
+      ],
+      "thresholds": [],
+      "timeFrom": null,
+      "timeRegions": [],
+      "timeShift": null,
+      "title": "HTTP 500",
+      "tooltip": {
+        "shared": true,
+        "sort": 0,
+        "value_type": "individual"
+      },
+      "type": "graph",
+      "xaxis": {
+        "buckets": null,
+        "mode": "time",
+        "name": null,
+        "show": true,
+        "values": []
+      },
+      "yaxes": [
+        {
+          "decimals": null,
+          "format": "short",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": "0",
+          "show": true
+        },
+        {
+          "format": "short",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": "0",
+          "show": true
+        }
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
+    },
+    {
+      "aliasColors": {},
+      "bars": false,
+      "dashLength": 10,
+      "dashes": false,
+      "datasource": "Prometheus",
+      "fill": 1,
+      "fillGradient": 0,
+      "gridPos": {
+        "h": 10,
+        "w": 8,
+        "x": 8,
+        "y": 19
+      },
+      "hiddenSeries": false,
+      "id": 116,
+      "legend": {
+        "alignAsTable": true,
+        "avg": true,
+        "current": false,
+        "max": true,
+        "min": true,
+        "show": true,
+        "total": false,
+        "values": true
+      },
+      "lines": true,
+      "linewidth": 1,
+      "links": [],
+      "nullPointMode": "null",
+      "options": {
+        "dataLinks": []
+      },
+      "percentage": false,
+      "pointradius": 5,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "expr": "delta(sshd_sessions_authentication_failures_total[$Window])",
+          "format": "time_series",
+          "intervalFactor": 1,
+          "legendFormat": "{{instance}}",
+          "refId": "A"
+        }
+      ],
+      "thresholds": [],
+      "timeFrom": null,
+      "timeRegions": [],
+      "timeShift": null,
+      "title": "SSH Authentication Failures",
+      "tooltip": {
+        "shared": true,
+        "sort": 0,
+        "value_type": "individual"
+      },
+      "type": "graph",
+      "xaxis": {
+        "buckets": null,
+        "mode": "time",
+        "name": null,
+        "show": true,
+        "values": []
+      },
+      "yaxes": [
+        {
+          "format": "short",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": "0",
+          "show": true
+        },
+        {
+          "format": "short",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": "0",
+          "show": true
+        }
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
+    },
+    {
+      "collapsed": false,
+      "datasource": null,
+      "gridPos": {
+        "h": 1,
+        "w": 24,
+        "x": 0,
+        "y": 29
+      },
+      "id": 72,
+      "panels": [],
+      "repeat": null,
+      "title": "System Resources",
+      "type": "row"
+    },
+    {
+      "aliasColors": {
+        "review-1-javamelody": "#5195ce",
+        "review-2-javamelody": "#7eb26d"
+      },
+      "bars": false,
+      "dashLength": 10,
+      "dashes": false,
+      "datasource": "Prometheus",
+      "description": "system load average",
+      "fill": 1,
+      "fillGradient": 0,
+      "gridPos": {
+        "h": 9,
+        "w": 12,
+        "x": 0,
+        "y": 30
+      },
+      "hiddenSeries": false,
+      "id": 1,
+      "legend": {
+        "alignAsTable": true,
+        "avg": true,
+        "current": true,
+        "max": true,
+        "min": true,
+        "show": true,
+        "sortDesc": true,
+        "total": false,
+        "values": true
+      },
+      "lines": true,
+      "linewidth": 1,
+      "links": [],
+      "nullPointMode": "null",
+      "options": {
+        "dataLinks": []
+      },
+      "percentage": false,
+      "pointradius": 5,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "application": {
+            "filter": ""
+          },
+          "expr": "avg_over_time(javamelody_system_load_avg[$Window])",
+          "format": "time_series",
+          "functions": [],
+          "group": {
+            "filter": ""
+          },
+          "host": {
+            "filter": ""
+          },
+          "instant": false,
+          "interval": "1m",
+          "intervalFactor": 1,
+          "item": {
+            "filter": ""
+          },
+          "legendFormat": "{{instance}}",
+          "mode": 0,
+          "options": {
+            "showDisabledItems": false
+          },
+          "refId": "A",
+          "triggers": {
+            "acknowledged": 2,
+            "count": true,
+            "minSeverity": 3
+          }
+        }
+      ],
+      "thresholds": [],
+      "timeFrom": null,
+      "timeRegions": [],
+      "timeShift": null,
+      "title": "System Load",
+      "tooltip": {
+        "shared": true,
+        "sort": 0,
+        "value_type": "individual"
+      },
+      "type": "graph",
+      "xaxis": {
+        "buckets": null,
+        "mode": "time",
+        "name": null,
+        "show": true,
+        "values": []
+      },
+      "yaxes": [
+        {
+          "format": "short",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": null,
+          "show": true
+        },
+        {
+          "format": "short",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": null,
+          "show": true
+        }
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
+    },
+    {
+      "aliasColors": {
+        "review-1-javamelody": "#5195ce",
+        "review-2-javamelody": "#7eb26d"
+      },
+      "bars": false,
+      "dashLength": 10,
+      "dashes": false,
+      "datasource": "Prometheus",
+      "description": "gc time",
+      "fill": 1,
+      "fillGradient": 0,
+      "gridPos": {
+        "h": 9,
+        "w": 12,
+        "x": 12,
+        "y": 30
+      },
+      "hiddenSeries": false,
+      "id": 14,
+      "legend": {
+        "alignAsTable": true,
+        "avg": true,
+        "current": true,
+        "max": true,
+        "min": true,
+        "show": true,
+        "total": false,
+        "values": true
+      },
+      "lines": true,
+      "linewidth": 1,
+      "links": [],
+      "nullPointMode": "null",
+      "options": {
+        "dataLinks": []
+      },
+      "percentage": false,
+      "pointradius": 5,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "application": {
+            "filter": ""
+          },
+          "expr": "rate(javamelody_memory_gc_millis[$Window])",
+          "format": "time_series",
+          "functions": [],
+          "group": {
+            "filter": ""
+          },
+          "host": {
+            "filter": ""
+          },
+          "instant": false,
+          "interval": "10s",
+          "intervalFactor": 1,
+          "item": {
+            "filter": ""
+          },
+          "legendFormat": "{{instance}}",
+          "mode": 0,
+          "options": {
+            "showDisabledItems": false
+          },
+          "refId": "A",
+          "triggers": {
+            "acknowledged": 2,
+            "count": true,
+            "minSeverity": 3
+          }
+        }
+      ],
+      "thresholds": [],
+      "timeFrom": null,
+      "timeRegions": [],
+      "timeShift": null,
+      "title": "GC Time",
+      "tooltip": {
+        "shared": true,
+        "sort": 0,
+        "value_type": "individual"
+      },
+      "type": "graph",
+      "xaxis": {
+        "buckets": null,
+        "mode": "time",
+        "name": null,
+        "show": true,
+        "values": []
+      },
+      "yaxes": [
+        {
+          "format": "ms",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": null,
+          "show": true
+        },
+        {
+          "format": "short",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": null,
+          "show": true
+        }
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
+    },
+    {
+      "aliasColors": {
+        "review-1-javamelody": "#5195ce",
+        "review-2-javamelody": "#7eb26d"
+      },
+      "bars": false,
+      "dashLength": 10,
+      "dashes": false,
+      "datasource": "Prometheus",
+      "description": "active thread count",
+      "fill": 1,
+      "fillGradient": 0,
+      "gridPos": {
+        "h": 9,
+        "w": 12,
+        "x": 0,
+        "y": 39
+      },
+      "hiddenSeries": false,
+      "id": 16,
+      "legend": {
+        "alignAsTable": true,
+        "avg": true,
+        "current": true,
+        "max": true,
+        "min": true,
+        "show": true,
+        "total": false,
+        "values": true
+      },
+      "lines": true,
+      "linewidth": 1,
+      "links": [],
+      "nullPointMode": "null",
+      "options": {
+        "dataLinks": []
+      },
+      "percentage": false,
+      "pointradius": 5,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "application": {
+            "filter": ""
+          },
+          "expr": "avg_over_time(javamelody_threads_active_count[$Window])",
+          "format": "time_series",
+          "functions": [],
+          "group": {
+            "filter": ""
+          },
+          "host": {
+            "filter": ""
+          },
+          "instant": false,
+          "interval": "",
+          "intervalFactor": 1,
+          "item": {
+            "filter": ""
+          },
+          "legendFormat": "{{instance}}",
+          "mode": 0,
+          "options": {
+            "showDisabledItems": false
+          },
+          "refId": "A",
+          "triggers": {
+            "acknowledged": 2,
+            "count": true,
+            "minSeverity": 3
+          }
+        }
+      ],
+      "thresholds": [],
+      "timeFrom": null,
+      "timeRegions": [],
+      "timeShift": null,
+      "title": "Active Threads",
+      "tooltip": {
+        "shared": true,
+        "sort": 0,
+        "value_type": "individual"
+      },
+      "type": "graph",
+      "xaxis": {
+        "buckets": null,
+        "mode": "time",
+        "name": null,
+        "show": true,
+        "values": []
+      },
+      "yaxes": [
+        {
+          "format": "short",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": null,
+          "show": true
+        },
+        {
+          "format": "short",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": null,
+          "show": true
+        }
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
+    },
+    {
+      "aliasColors": {
+        "review-1-javamelody": "#5195ce",
+        "review-2-javamelody": "#7eb26d"
+      },
+      "bars": false,
+      "dashLength": 10,
+      "dashes": false,
+      "datasource": "Prometheus",
+      "description": "unix open file descriptors count",
+      "fill": 1,
+      "fillGradient": 0,
+      "gridPos": {
+        "h": 9,
+        "w": 12,
+        "x": 12,
+        "y": 39
+      },
+      "hiddenSeries": false,
+      "id": 6,
+      "legend": {
+        "alignAsTable": true,
+        "avg": true,
+        "current": true,
+        "max": true,
+        "min": true,
+        "show": true,
+        "total": false,
+        "values": true
+      },
+      "lines": true,
+      "linewidth": 1,
+      "links": [],
+      "nullPointMode": "null",
+      "options": {
+        "dataLinks": []
+      },
+      "percentage": false,
+      "pointradius": 5,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "application": {
+            "filter": ""
+          },
+          "expr": "avg_over_time(javamelody_system_unix_file_descriptors_open_count[$Window])",
+          "format": "time_series",
+          "functions": [],
+          "group": {
+            "filter": ""
+          },
+          "host": {
+            "filter": ""
+          },
+          "instant": false,
+          "interval": "30s",
+          "intervalFactor": 2,
+          "item": {
+            "filter": ""
+          },
+          "legendFormat": "{{instance}}",
+          "mode": 0,
+          "options": {
+            "showDisabledItems": false
+          },
+          "refId": "A",
+          "triggers": {
+            "acknowledged": 2,
+            "count": true,
+            "minSeverity": 3
+          }
+        }
+      ],
+      "thresholds": [],
+      "timeFrom": null,
+      "timeRegions": [],
+      "timeShift": null,
+      "title": "Unix File Descriptors Open Count",
+      "tooltip": {
+        "shared": true,
+        "sort": 0,
+        "value_type": "individual"
+      },
+      "type": "graph",
+      "xaxis": {
+        "buckets": null,
+        "mode": "time",
+        "name": null,
+        "show": true,
+        "values": []
+      },
+      "yaxes": [
+        {
+          "format": "short",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": "0",
+          "show": true
+        },
+        {
+          "format": "short",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": null,
+          "show": false
+        }
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
+    },
+    {
+      "collapsed": false,
+      "datasource": null,
+      "gridPos": {
+        "h": 1,
+        "w": 24,
+        "x": 0,
+        "y": 48
+      },
+      "id": 76,
+      "panels": [],
+      "repeat": null,
+      "title": "HTTP",
+      "type": "row"
+    },
+    {
+      "aliasColors": {},
+      "bars": false,
+      "dashLength": 10,
+      "dashes": false,
+      "datasource": "Prometheus",
+      "fill": 1,
+      "fillGradient": 0,
+      "gridPos": {
+        "h": 9,
+        "w": 8,
+        "x": 0,
+        "y": 49
+      },
+      "hiddenSeries": false,
+      "id": 96,
+      "legend": {
+        "alignAsTable": true,
+        "avg": true,
+        "current": true,
+        "max": true,
+        "min": true,
+        "rightSide": false,
+        "show": true,
+        "total": false,
+        "values": true
+      },
+      "lines": true,
+      "linewidth": 1,
+      "links": [],
+      "nullPointMode": "null",
+      "options": {
+        "dataLinks": []
+      },
+      "percentage": false,
+      "pointradius": 5,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "expr": "delta(javamelody_http_duration_millis[$Window])/delta(javamelody_http_hits_count[$Window])",
+          "format": "time_series",
+          "intervalFactor": 1,
+          "legendFormat": "{{instance}}",
+          "refId": "A"
+        }
+      ],
+      "thresholds": [],
+      "timeFrom": null,
+      "timeRegions": [],
+      "timeShift": null,
+      "title": "HTTP requests mean time",
+      "tooltip": {
+        "shared": true,
+        "sort": 0,
+        "value_type": "individual"
+      },
+      "type": "graph",
+      "xaxis": {
+        "buckets": null,
+        "mode": "time",
+        "name": null,
+        "show": true,
+        "values": []
+      },
+      "yaxes": [
+        {
+          "format": "ms",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": "0",
+          "show": true
+        },
+        {
+          "format": "short",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": "0",
+          "show": true
+        }
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
+    },
+    {
+      "aliasColors": {
+        "review-1-javamelody": "#5195ce",
+        "review-2-javamelody": "#7eb26d"
+      },
+      "bars": false,
+      "dashLength": 10,
+      "dashes": false,
+      "datasource": "Prometheus",
+      "description": "",
+      "fill": 1,
+      "fillGradient": 0,
+      "gridPos": {
+        "h": 9,
+        "w": 8,
+        "x": 8,
+        "y": 49
+      },
+      "hiddenSeries": false,
+      "id": 51,
+      "legend": {
+        "alignAsTable": true,
+        "avg": true,
+        "current": true,
+        "max": true,
+        "min": true,
+        "show": true,
+        "total": false,
+        "values": true
+      },
+      "lines": true,
+      "linewidth": 1,
+      "links": [],
+      "nullPointMode": "null",
+      "options": {
+        "dataLinks": []
+      },
+      "percentage": false,
+      "pointradius": 5,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "application": {
+            "filter": ""
+          },
+          "expr": "delta(javamelody_http_hits_count[$Window])",
+          "format": "time_series",
+          "functions": [],
+          "group": {
+            "filter": ""
+          },
+          "host": {
+            "filter": ""
+          },
+          "instant": false,
+          "interval": "30s",
+          "intervalFactor": 2,
+          "item": {
+            "filter": ""
+          },
+          "legendFormat": "{{instance}}",
+          "mode": 0,
+          "options": {
+            "showDisabledItems": false
+          },
+          "refId": "A",
+          "triggers": {
+            "acknowledged": 2,
+            "count": true,
+            "minSeverity": 3
+          }
+        }
+      ],
+      "thresholds": [],
+      "timeFrom": null,
+      "timeRegions": [],
+      "timeShift": null,
+      "title": "HTTP hits per minute",
+      "tooltip": {
+        "shared": true,
+        "sort": 0,
+        "value_type": "individual"
+      },
+      "type": "graph",
+      "xaxis": {
+        "buckets": null,
+        "mode": "time",
+        "name": null,
+        "show": true,
+        "values": []
+      },
+      "yaxes": [
+        {
+          "format": "opm",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": "0",
+          "show": true
+        },
+        {
+          "format": "short",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": "0",
+          "show": false
+        }
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
+    },
+    {
+      "aliasColors": {
+        "review-1-javamelody": "#5195ce",
+        "review-2-javamelody": "#7eb26d"
+      },
+      "bars": false,
+      "dashLength": 10,
+      "dashes": false,
+      "datasource": "Prometheus",
+      "description": "",
+      "fill": 1,
+      "fillGradient": 0,
+      "gridPos": {
+        "h": 9,
+        "w": 8,
+        "x": 16,
+        "y": 49
+      },
+      "hiddenSeries": false,
+      "id": 52,
+      "legend": {
+        "alignAsTable": true,
+        "avg": true,
+        "current": true,
+        "max": true,
+        "min": true,
+        "show": true,
+        "total": false,
+        "values": true
+      },
+      "lines": true,
+      "linewidth": 1,
+      "links": [],
+      "nullPointMode": "null",
+      "options": {
+        "dataLinks": []
+      },
+      "percentage": false,
+      "pointradius": 5,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "application": {
+            "filter": ""
+          },
+          "expr": "delta(javamelody_http_errors_count[$Window])/delta(javamelody_http_hits_count[$Window])*100",
+          "format": "time_series",
+          "functions": [],
+          "group": {
+            "filter": ""
+          },
+          "host": {
+            "filter": ""
+          },
+          "instant": false,
+          "interval": "30s",
+          "intervalFactor": 2,
+          "item": {
+            "filter": ""
+          },
+          "legendFormat": "{{instance}}",
+          "mode": 0,
+          "options": {
+            "showDisabledItems": false
+          },
+          "refId": "A",
+          "triggers": {
+            "acknowledged": 2,
+            "count": true,
+            "minSeverity": 3
+          }
+        }
+      ],
+      "thresholds": [],
+      "timeFrom": null,
+      "timeRegions": [],
+      "timeShift": null,
+      "title": "% of HTTP Errors",
+      "tooltip": {
+        "shared": true,
+        "sort": 0,
+        "value_type": "individual"
+      },
+      "type": "graph",
+      "xaxis": {
+        "buckets": null,
+        "mode": "time",
+        "name": null,
+        "show": true,
+        "values": []
+      },
+      "yaxes": [
+        {
+          "decimals": null,
+          "format": "percent",
+          "label": null,
+          "logBase": 1,
+          "max": "100",
+          "min": "0",
+          "show": true
+        },
+        {
+          "format": "short",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": null,
+          "show": false
+        }
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
+    },
+    {
+      "collapsed": false,
+      "datasource": null,
+      "gridPos": {
+        "h": 1,
+        "w": 24,
+        "x": 0,
+        "y": 58
+      },
+      "id": 126,
+      "panels": [],
+      "title": "Git",
+      "type": "row"
+    },
+    {
+      "aliasColors": {},
+      "bars": false,
+      "dashLength": 10,
+      "dashes": false,
+      "datasource": "Prometheus",
+      "fill": 1,
+      "fillGradient": 0,
+      "gridPos": {
+        "h": 9,
+        "w": 12,
+        "x": 0,
+        "y": 59
+      },
+      "hiddenSeries": false,
+      "id": 124,
+      "legend": {
+        "alignAsTable": true,
+        "avg": true,
+        "current": false,
+        "max": true,
+        "min": true,
+        "rightSide": false,
+        "show": true,
+        "total": true,
+        "values": true
+      },
+      "lines": true,
+      "linewidth": 1,
+      "links": [],
+      "nullPointMode": "null",
+      "options": {
+        "dataLinks": []
+      },
+      "percentage": false,
+      "pointradius": 5,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "expr": "increase(git_upload_pack_request_count_total_total[$Window])",
+          "format": "time_series",
+          "interval": "1s",
+          "intervalFactor": 1,
+          "legendFormat": "{{instance}} Tot git upload pack",
+          "refId": "A"
+        }
+      ],
+      "thresholds": [],
+      "timeFrom": null,
+      "timeRegions": [],
+      "timeShift": null,
+      "title": "Git upload pack - count",
+      "tooltip": {
+        "shared": true,
+        "sort": 0,
+        "value_type": "individual"
+      },
+      "type": "graph",
+      "xaxis": {
+        "buckets": null,
+        "mode": "time",
+        "name": null,
+        "show": true,
+        "values": []
+      },
+      "yaxes": [
+        {
+          "format": "short",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": null,
+          "show": true
+        },
+        {
+          "format": "short",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": null,
+          "show": true
+        }
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
+    },
+    {
+      "aliasColors": {},
+      "bars": false,
+      "dashLength": 10,
+      "dashes": false,
+      "datasource": "Prometheus",
+      "fill": 1,
+      "fillGradient": 0,
+      "gridPos": {
+        "h": 9,
+        "w": 12,
+        "x": 12,
+        "y": 59
+      },
+      "hiddenSeries": false,
+      "id": 128,
+      "legend": {
+        "alignAsTable": true,
+        "avg": true,
+        "current": false,
+        "max": true,
+        "min": true,
+        "rightSide": false,
+        "show": true,
+        "total": true,
+        "values": true
+      },
+      "lines": true,
+      "linewidth": 1,
+      "links": [],
+      "nullPointMode": "null",
+      "options": {
+        "dataLinks": []
+      },
+      "percentage": false,
+      "pointradius": 5,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "expr": "increase(git_upload_pack_request_count_CLONE_total[$Window])",
+          "format": "time_series",
+          "interval": "1s",
+          "intervalFactor": 1,
+          "legendFormat": "{{instance}} Tot Clone",
+          "refId": "B"
+        },
+        {
+          "expr": "increase(git_upload_pack_request_count_FETCH_total[$Window])",
+          "format": "time_series",
+          "interval": "1s",
+          "intervalFactor": 1,
+          "legendFormat": "{{instance}} Tot Fetch",
+          "refId": "C"
+        }
+      ],
+      "thresholds": [],
+      "timeFrom": null,
+      "timeRegions": [],
+      "timeShift": null,
+      "title": "Git clone and fetch count",
+      "tooltip": {
+        "shared": true,
+        "sort": 0,
+        "value_type": "individual"
+      },
+      "type": "graph",
+      "xaxis": {
+        "buckets": null,
+        "mode": "time",
+        "name": null,
+        "show": true,
+        "values": []
+      },
+      "yaxes": [
+        {
+          "format": "short",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": null,
+          "show": true
+        },
+        {
+          "format": "short",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": null,
+          "show": true
+        }
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
+    },
+    {
+      "collapsed": false,
+      "datasource": null,
+      "gridPos": {
+        "h": 1,
+        "w": 24,
+        "x": 0,
+        "y": 68
+      },
+      "id": 114,
+      "panels": [],
+      "title": "Queues",
+      "type": "row"
+    },
+    {
+      "aliasColors": {},
+      "bars": false,
+      "dashLength": 10,
+      "dashes": false,
+      "datasource": "Prometheus",
+      "fill": 1,
+      "fillGradient": 0,
+      "gridPos": {
+        "h": 8,
+        "w": 24,
+        "x": 0,
+        "y": 69
+      },
+      "hiddenSeries": false,
+      "id": 112,
+      "legend": {
+        "avg": false,
+        "current": false,
+        "max": false,
+        "min": false,
+        "show": true,
+        "total": false,
+        "values": false
+      },
+      "lines": true,
+      "linewidth": 1,
+      "links": [],
+      "nullPointMode": "null",
+      "options": {
+        "dataLinks": []
+      },
+      "percentage": false,
+      "pointradius": 5,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "expr": "delta(queue_index_batch_total_scheduled_tasks_count[$Window])",
+          "format": "time_series",
+          "intervalFactor": 1,
+          "legendFormat": "{{instance}} Index",
+          "refId": "A"
+        },
+        {
+          "expr": "delta(queue_receive_commits_total_scheduled_tasks_count[$Window])",
+          "format": "time_series",
+          "intervalFactor": 1,
+          "legendFormat": "{{instance}} Receive Commits",
+          "refId": "B"
+        },
+        {
+          "expr": "delta(queue_work_queue_total_scheduled_tasks_count[$Window])",
+          "format": "time_series",
+          "intervalFactor": 1,
+          "legendFormat": "{{instance}} Tasks",
+          "refId": "C"
+        },
+        {
+          "expr": "delta(queue_ssh_command_start_total_scheduled_tasks_count[$Window])",
+          "format": "time_series",
+          "intervalFactor": 1,
+          "legendFormat": "{{instance}} SSH Command start",
+          "refId": "D"
+        }
+      ],
+      "thresholds": [],
+      "timeFrom": null,
+      "timeRegions": [],
+      "timeShift": null,
+      "title": "Scheduled Tasks Queues",
+      "tooltip": {
+        "shared": true,
+        "sort": 0,
+        "value_type": "individual"
+      },
+      "type": "graph",
+      "xaxis": {
+        "buckets": null,
+        "mode": "time",
+        "name": null,
+        "show": true,
+        "values": []
+      },
+      "yaxes": [
+        {
+          "format": "short",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": null,
+          "show": true
+        },
+        {
+          "format": "short",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": null,
+          "show": true
+        }
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
+    }
+  ],
+  "refresh": false,
+  "schemaVersion": 22,
+  "style": "dark",
+  "tags": [
+    "prometheus"
+  ],
+  "templating": {
+    "list": [
+      {
+        "allValue": null,
+        "current": {
+          "isNone": true,
+          "selected": false,
+          "text": "None",
+          "value": ""
+        },
+        "datasource": "Prometheus",
+        "definition": "",
+        "hide": 0,
+        "includeAll": false,
+        "index": -1,
+        "label": "Application",
+        "multi": false,
+        "name": "Application",
+        "options": [],
+        "query": "javamelody_system_load_avg",
+        "refresh": 2,
+        "regex": "/{alias=\"(.*)\",instance/",
+        "skipUrlSync": false,
+        "sort": 0,
+        "tagValuesQuery": "",
+        "tags": [],
+        "tagsQuery": "",
+        "type": "query",
+        "useTags": false
+      },
+      {
+        "auto": false,
+        "auto_count": 30,
+        "auto_min": "10s",
+        "current": {
+          "selected": false,
+          "text": "1m",
+          "value": "1m"
+        },
+        "hide": 0,
+        "label": "Aggregation window",
+        "name": "Window",
+        "options": [
+          {
+            "selected": true,
+            "text": "1m",
+            "value": "1m"
+          },
+          {
+            "selected": false,
+            "text": "5m",
+            "value": "5m"
+          },
+          {
+            "selected": false,
+            "text": "10m",
+            "value": "10m"
+          },
+          {
+            "selected": false,
+            "text": "30m",
+            "value": "30m"
+          },
+          {
+            "selected": false,
+            "text": "1h",
+            "value": "1h"
+          }
+        ],
+        "query": "1m,5m,10m,30m,1h",
+        "refresh": 2,
+        "skipUrlSync": false,
+        "type": "interval"
+      }
+    ]
+  },
+  "time": {
+    "from": "2020-04-21T05:35:35.287Z",
+    "to": "2020-04-21T12:11:11.551Z"
+  },
+  "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": "Gerrit",
+  "uid": "VfddQL5mk",
+  "variables": {
+    "list": []
+  },
+  "version": 1
+}
diff --git a/monitoring/grafana/entrypoint.sh b/monitoring/grafana/entrypoint.sh
new file mode 100644
index 0000000..a474d2a
--- /dev/null
+++ b/monitoring/grafana/entrypoint.sh
@@ -0,0 +1,9 @@
+#!/bin/bash -e
+
+echo "Updating Grafana templates..."
+sed -i -e "s|{{DOMAIN}}|$GRAFANA_DOMAIN|g" /etc/grafana/config.ini
+sed -i -e "s|{{PROMETHEUS_URL}}|$PROMETHEUS_URL|g" /etc/grafana/provisioning/datasources/prometheus.yml
+sed -i -e "s|{{MASTER_URL}}|$MASTER_URL|g" /var/lib/grafana/dashboards/Gerrit.json
+sed -i -e "s|{{SLAVE_URL}}|$SLAVE_URL|g" /var/lib/grafana/dashboards/Gerrit.json
+
+/run.sh
diff --git a/monitoring/grafana/provisioning/dashboards/gerrit.yml b/monitoring/grafana/provisioning/dashboards/gerrit.yml
new file mode 100644
index 0000000..abc1adc
--- /dev/null
+++ b/monitoring/grafana/provisioning/dashboards/gerrit.yml
@@ -0,0 +1,11 @@
+# # config file version
+apiVersion: 1
+
+providers:
+- name: 'Gerrit'
+  orgId: 1
+  folder: ''
+  folderUid: ''
+  type: file
+  options:
+    path: /var/lib/grafana/dashboards
diff --git a/monitoring/grafana/provisioning/datasources/prometheus.yml b/monitoring/grafana/provisioning/datasources/prometheus.yml
new file mode 100644
index 0000000..f4c6037
--- /dev/null
+++ b/monitoring/grafana/provisioning/datasources/prometheus.yml
@@ -0,0 +1,50 @@
+# config file version
+apiVersion: 1
+
+# list of datasources that should be deleted from the database
+deleteDatasources:
+  - name: Prometheus
+    orgId: 1
+
+# list of datasources to insert/update depending
+# whats available in the database
+datasources:
+  # <string, required> name of the datasource. Required
+- name: Prometheus
+  # <string, required> datasource type. Required
+  type: prometheus
+  # <string, required> access mode. direct or proxy. Required
+  access: proxy
+  # <int> org id. will default to orgId 1 if not specified
+  orgId: 1
+  # <string> url
+  url: {{PROMETHEUS_URL}}
+  # <string> database password, if used
+  password:
+  # <string> database user, if used
+  user:
+  # <string> database name, if used
+  database:
+  # <bool> enable/disable basic auth
+  basicAuth: false
+  # <string> basic auth username, if used
+  basicAuthUser:
+  # <string> basic auth password, if used
+  basicAuthPassword:
+  # <bool> enable/disable with credentials headers
+  withCredentials:
+  # <bool> mark as default datasource. Max one per org
+  isDefault: true
+  # <map> fields that will be converted to json and stored in json_data
+  jsonData:
+     graphiteVersion: "1.1"
+     tlsAuth: false
+     tlsAuthWithCACert: false
+  # <string> json object of data that will be encrypted.
+  secureJsonData:
+    tlsCACert: "..."
+    tlsClientCert: "..."
+    tlsClientKey: "..."
+  version: 1
+  # <bool> allow users to edit datasources from the UI.
+  editable: true