diff --git a/README.md b/README.md
index 8d131ff2b..5f5fa6532 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@ This microservices branch was initially derived from [AngularJS version](https:/
## Starting services locally without Docker
Every microservice is a Spring Boot application and can be started locally using IDE or `../mvnw spring-boot:run` command. Please note that supporting services (Config and Discovery Server) must be started before any other application (Customers, Vets, Visits and API).
-Tracing server and Admin server startup is optional.
+Startup of Tracing server, Admin server, Grafana and Prometheus is optional.
If everything goes well, you can access the following services at given location:
* Discovery Server - http://localhost:8761
* Config Server - http://localhost:8888
@@ -14,6 +14,8 @@ If everything goes well, you can access the following services at given location
* Customers, Vets and Visits Services - random port, check Eureka Dashboard
* Tracing Server (Zipkin) - http://localhost:9411/zipkin/ (we use [openzipkin](https://github.com/openzipkin/zipkin/tree/master/zipkin-server))
* Admin Server (Spring Boot Admin) - http://localhost:9090
+* Grafana Dashboards - http://localhost:3000
+* Prometheus - http://localhost:9091
* Hystrix Dashboard for Circuit Breaker pattern - http://localhost:7979 - On the home page is a form where you can enter
the URL for an event stream to monitor, for example the `api-gateway` service running locally: `http://localhost:8080/actuator/hystrix.stream`
or running into docker: `http://api-gateway:8080/actuator/hystrix.stream`
@@ -87,29 +89,36 @@ the host and port of your MySQL JDBC connection string.
## Custom metrics monitoring
-@todo Add default custom dashboards to grafana
+Grafana and Prometheus are included in the `docker-compose.yml` configuration, and the public facing applications
+have been instrumented with [MicroMeter](https://micrometer.io) to collect JVM and custom business metrics.
-Grafana and Prometheus are included in the `docker-compose.yml` configuration, and the public facing applications have been instrumented with [MicroMeter](https://micrometer.io) to collect JVM and custom business metrics.
+A JMeter load testing script is available to stress the application and generate metrics: [petclinic_test_plan.jmx](spring-petclinic-api-gateway/src/test/jmeter/petclinic_test_plan.jmx)
+
+
### Using Prometheus
-* Prometheus can be accessed from your local machine at http://localhost:9091
+* Prometheus can be accessed from your local machine at [http://localhost:9091]()
### Using Grafana with Prometheus
-* Login to Grafana at http://localhost:3000, the default user/pass is `admin:admin`, you will be prompted to change your password.
-* Setup a prometheus datasource and point the URL to `http://prometheus-server:9090`, leave all the other options set to their default.
-* Add the [Micrometer/SpringBoot dashboard](https://grafana.com/dashboards/4701) via the Import Dashboard menu item. The id for the dashboard is `4701`
+* An anonymous access and a Prometheus datasource are setup.
+* A `Spring Petclinic Metrics` Dashboard is available at the URL [http://localhost:3000/d/69JXeR0iw/spring-petclinic-metrics]().
+You will find the JSON configuration file here: [docker/grafana/dashboards/grafana-petclinic-dashboard.json]().
+* You may create your own dashboard or import the [Micrometer/SpringBoot dashboard](https://grafana.com/dashboards/4701) via the Import Dashboard menu item.
+The id for this dashboard is `4701`.
+
+### Custom metrics
-### Custom metrics implementation
+Spring Boot registers a lot number of core metrics: JVM, CPU, Tomcat, Logback...
+The Spring Boot auto-configuration enables the instrumentation of requests handled by Spring MVC.
+All those three REST controllers `OwnerResource`, `PetResource` and `VisitResource` have been instrumented by the `@Timed` Micrometer annotation at class level.
* `customers-service` application has the following custom metrics enabled:
- * counter: `create.owner`
- * counter: `update.owner`
- * counter: `create.pet`
- * counter: `update.pet`
+ * @Timed: `petclinic.owner`
+ * @Timed: `petclinic.pet`
* `visits-service` application has the following custom metrics enabled:
- * counter: `create.visit`
+ * @Timed: `petclinic.visit`
## Looking for something in particular?
@@ -119,8 +128,8 @@ Grafana and Prometheus are included in the `docker-compose.yml` configuration, a
| Service Discovery | [Eureka server](spring-petclinic-discovery-server) and [Service discovery client](spring-petclinic-vets-service/src/main/java/org/springframework/samples/petclinic/vets/VetsServiceApplication.java) |
| API Gateway | [Zuul reverse proxy](spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/ApiGatewayApplication.java) and [Routing configuration](https://github.com/spring-petclinic/spring-petclinic-microservices-config/blob/master/api-gateway.yml) |
| Docker Compose | [Spring Boot with Docker guide](https://spring.io/guides/gs/spring-boot-docker/) and [docker-compose file](docker-compose.yml) |
-| Circuit Breaker | TBD |
-| Grafana / Prometheus Monitoring | [Micrometer implementation](https://micrometer.io/) |
+| Circuit Breaker | [Hystrix fallback method](spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/VisitsServiceClient.java) |
+| Grafana / Prometheus Monitoring | [Micrometer implementation](https://micrometer.io/), [Spring Boot Actuator Production Ready Metrics] |
Front-end module | Files |
|-------------------|-------|
@@ -149,3 +158,4 @@ For pull requests, editor preferences are available in the [editor config](.edit
[Configuration repository]: https://github.com/spring-petclinic/spring-petclinic-microservices-config
+[Spring Boot Actuator Production Ready Metrics]: https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-metrics.html
diff --git a/docker-compose.yml b/docker-compose.yml
index 1209bd7b4..2fffc3ebe 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,8 +1,5 @@
version: '2'
-volumes:
- graf-data:
-
services:
config-server:
image: mszarlinski/spring-petclinic-config-server
@@ -99,17 +96,14 @@ services:
## Grafana / Prometheus
grafana-server:
- image: grafana/grafana:5.2.4
+ build: ./docker/grafana
container_name: grafana-server
mem_limit: 256M
ports:
- 3000:3000
- volumes:
- - graf-data:/var/lib/grafana
prometheus-server:
build: ./docker/prometheus
- image: prometheus-local:v2.4.2
container_name: prometheus-server
mem_limit: 256M
ports:
diff --git a/docker/grafana/Dockerfile b/docker/grafana/Dockerfile
new file mode 100644
index 000000000..c11b3d911
--- /dev/null
+++ b/docker/grafana/Dockerfile
@@ -0,0 +1,4 @@
+FROM grafana/grafana:5.2.4
+ADD ./provisioning /etc/grafana/provisioning
+ADD ./grafana.ini /etc/grafana/grafana.ini
+ADD ./dashboards /var/lib/grafana/dashboards
diff --git a/docker/grafana/dashboards/grafana-petclinic-dashboard.json b/docker/grafana/dashboards/grafana-petclinic-dashboard.json
new file mode 100644
index 000000000..6d79a8d5f
--- /dev/null
+++ b/docker/grafana/dashboards/grafana-petclinic-dashboard.json
@@ -0,0 +1,772 @@
+{
+ "__requires": [
+ {
+ "type": "grafana",
+ "id": "grafana",
+ "name": "Grafana",
+ "version": "5.0.0"
+ },
+ {
+ "type": "panel",
+ "id": "graph",
+ "name": "Graph",
+ "version": "5.0.0"
+ },
+ {
+ "type": "datasource",
+ "id": "prometheus",
+ "name": "Prometheus",
+ "version": "5.0.0"
+ },
+ {
+ "type": "panel",
+ "id": "singlestat",
+ "name": "Singlestat",
+ "version": "5.0.0"
+ }
+ ],
+ "annotations": {
+ "list": [
+ {
+ "builtIn": 1,
+ "datasource": "-- Grafana --",
+ "enable": true,
+ "hide": true,
+ "iconColor": "rgba(0, 211, 255, 1)",
+ "name": "Annotations & Alerts",
+ "type": "dashboard"
+ }
+ ]
+ },
+ "editable": true,
+ "gnetId": null,
+ "graphTooltip": 0,
+ "id": null,
+ "iteration": 1539967676482,
+ "links": [],
+ "panels": [
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "Prometheus",
+ "fill": 1,
+ "gridPos": {
+ "h": 9,
+ "w": 12,
+ "x": 0,
+ "y": 0
+ },
+ "id": 11,
+ "legend": {
+ "avg": false,
+ "current": true,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": false,
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "expr": "sum(rate(http_server_requests_seconds_sum{status!~\"5..\"}[1m]))/sum(rate(http_server_requests_seconds_count{ status!~\"5..\"}[1m]))",
+ "format": "time_series",
+ "intervalFactor": 1,
+ "legendFormat": "HTTP - AVG",
+ "refId": "A"
+ },
+ {
+ "expr": "max(http_server_requests_seconds_max{status!~\"5..\"})",
+ "format": "time_series",
+ "intervalFactor": 1,
+ "legendFormat": "HTTP - MAX",
+ "refId": "B"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "HTTP Request Latency",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "decimals": null,
+ "format": "s",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "Prometheus",
+ "fill": 1,
+ "gridPos": {
+ "h": 9,
+ "w": 12,
+ "x": 12,
+ "y": 0
+ },
+ "id": 9,
+ "legend": {
+ "avg": false,
+ "current": true,
+ "max": true,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": false,
+ "pointradius": 0.5,
+ "points": true,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "expr": "sum(rate(http_server_requests_seconds_count[1m]))",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "request - ok",
+ "refId": "A"
+ },
+ {
+ "expr": "sum(rate(http_server_requests_seconds_count{status=~\"5..\"}[1m]))",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "request - err",
+ "refId": "B"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "HTTP Request Activity",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "ops",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "cacheTimeout": null,
+ "colorBackground": false,
+ "colorValue": false,
+ "colors": [
+ "#299c46",
+ "rgba(237, 129, 40, 0.89)",
+ "#d44a3a"
+ ],
+ "datasource": "Prometheus",
+ "format": "none",
+ "gauge": {
+ "maxValue": 100,
+ "minValue": 0,
+ "show": false,
+ "thresholdLabels": false,
+ "thresholdMarkers": true
+ },
+ "gridPos": {
+ "h": 5,
+ "w": 6,
+ "x": 0,
+ "y": 9
+ },
+ "id": 2,
+ "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": "sum(petclinic_owner_seconds_count{method=\"PUT\", status=\"200\"})",
+ "format": "time_series",
+ "instant": true,
+ "intervalFactor": 1,
+ "refId": "A"
+ }
+ ],
+ "thresholds": "",
+ "title": "Owners Updated",
+ "type": "singlestat",
+ "valueFontSize": "80%",
+ "valueMaps": [
+ {
+ "op": "=",
+ "text": "N/A",
+ "value": "null"
+ }
+ ],
+ "valueName": "avg"
+ },
+ {
+ "cacheTimeout": null,
+ "colorBackground": false,
+ "colorValue": false,
+ "colors": [
+ "#299c46",
+ "rgba(237, 129, 40, 0.89)",
+ "#d44a3a"
+ ],
+ "datasource": "Prometheus",
+ "format": "none",
+ "gauge": {
+ "maxValue": 100,
+ "minValue": 0,
+ "show": false,
+ "thresholdLabels": false,
+ "thresholdMarkers": true
+ },
+ "gridPos": {
+ "h": 5,
+ "w": 6,
+ "x": 6,
+ "y": 9
+ },
+ "id": 3,
+ "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": "sum(petclinic_owner_seconds_count{method=\"POST\", status=\"201\"})",
+ "format": "time_series",
+ "instant": true,
+ "intervalFactor": 1,
+ "legendFormat": "",
+ "refId": "A"
+ }
+ ],
+ "thresholds": "",
+ "title": "Owners Created",
+ "type": "singlestat",
+ "valueFontSize": "80%",
+ "valueMaps": [
+ {
+ "op": "=",
+ "text": "N/A",
+ "value": "null"
+ }
+ ],
+ "valueName": "avg"
+ },
+ {
+ "cacheTimeout": null,
+ "colorBackground": false,
+ "colorValue": false,
+ "colors": [
+ "#299c46",
+ "rgba(237, 129, 40, 0.89)",
+ "#d44a3a"
+ ],
+ "datasource": "Prometheus",
+ "format": "none",
+ "gauge": {
+ "maxValue": 100,
+ "minValue": 0,
+ "show": false,
+ "thresholdLabels": false,
+ "thresholdMarkers": true
+ },
+ "gridPos": {
+ "h": 5,
+ "w": 6,
+ "x": 12,
+ "y": 9
+ },
+ "id": 4,
+ "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": "sum(petclinic_pet_seconds_count{method=\"POST\", status=\"204\"})",
+ "format": "time_series",
+ "instant": true,
+ "intervalFactor": 1,
+ "legendFormat": "",
+ "refId": "A"
+ }
+ ],
+ "thresholds": "",
+ "title": "Pets Created",
+ "type": "singlestat",
+ "valueFontSize": "80%",
+ "valueMaps": [
+ {
+ "op": "=",
+ "text": "N/A",
+ "value": "null"
+ }
+ ],
+ "valueName": "avg"
+ },
+ {
+ "cacheTimeout": null,
+ "colorBackground": false,
+ "colorValue": false,
+ "colors": [
+ "#299c46",
+ "rgba(237, 129, 40, 0.89)",
+ "#d44a3a"
+ ],
+ "datasource": "Prometheus",
+ "format": "none",
+ "gauge": {
+ "maxValue": 100,
+ "minValue": 0,
+ "show": false,
+ "thresholdLabels": false,
+ "thresholdMarkers": true
+ },
+ "gridPos": {
+ "h": 5,
+ "w": 6,
+ "x": 18,
+ "y": 9
+ },
+ "id": 5,
+ "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": "Value",
+ "targets": [
+ {
+ "expr": "sum(petclinic_visit_seconds_count{method=\"POST\", status=\"204\"})",
+ "format": "time_series",
+ "instant": true,
+ "intervalFactor": 1,
+ "legendFormat": "",
+ "refId": "A"
+ }
+ ],
+ "thresholds": "",
+ "title": "Visit Created",
+ "type": "singlestat",
+ "valueFontSize": "80%",
+ "valueMaps": [
+ {
+ "op": "=",
+ "text": "N/A",
+ "value": "null"
+ }
+ ],
+ "valueName": "avg"
+ },
+ {
+ "aliasColors": {},
+ "bars": true,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "Prometheus",
+ "fill": 1,
+ "gridPos": {
+ "h": 8,
+ "w": 24,
+ "x": 0,
+ "y": 14
+ },
+ "id": 7,
+ "legend": {
+ "alignAsTable": false,
+ "avg": false,
+ "current": false,
+ "hideEmpty": false,
+ "hideZero": true,
+ "max": true,
+ "min": false,
+ "rightSide": false,
+ "show": true,
+ "total": false,
+ "values": true
+ },
+ "lines": false,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": false,
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": true,
+ "steppedLine": false,
+ "targets": [
+ {
+ "expr": "sum(petclinic_owner_seconds_count{method=\"POST\", status=\"201\"})",
+ "format": "time_series",
+ "instant": false,
+ "intervalFactor": 1,
+ "legendFormat": "owner create",
+ "refId": "A"
+ },
+ {
+ "expr": "sum(petclinic_pet_seconds_count{method=\"POST\", status=\"204\"})",
+ "format": "time_series",
+ "intervalFactor": 1,
+ "legendFormat": "pet create",
+ "refId": "B"
+ },
+ {
+ "expr": "sum(petclinic_visit_seconds_count{method=\"POST\", status=\"204\"})",
+ "format": "time_series",
+ "intervalFactor": 1,
+ "legendFormat": "visit create",
+ "refId": "C"
+ },
+ {
+ "expr": "sum(petclinic_owner_seconds_count{method=\"PUT\", status=\"200\"})",
+ "format": "time_series",
+ "intervalFactor": 1,
+ "legendFormat": "owner update",
+ "refId": "D"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "SPC Business Histogram",
+ "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
+ }
+ ]
+ }
+ ],
+ "refresh": "30s",
+ "schemaVersion": 16,
+ "style": "dark",
+ "tags": [],
+ "templating": {
+ "list": [
+ {
+ "auto": true,
+ "auto_count": 1,
+ "auto_min": "10s",
+ "current": {
+ "text": "auto",
+ "value": "$__auto_interval_timeRange"
+ },
+ "hide": 0,
+ "label": null,
+ "name": "timeRange",
+ "options": [
+ {
+ "selected": true,
+ "text": "auto",
+ "value": "$__auto_interval_timeRange"
+ },
+ {
+ "selected": false,
+ "text": "1m",
+ "value": "1m"
+ },
+ {
+ "selected": false,
+ "text": "10m",
+ "value": "10m"
+ },
+ {
+ "selected": false,
+ "text": "30m",
+ "value": "30m"
+ },
+ {
+ "selected": false,
+ "text": "1h",
+ "value": "1h"
+ },
+ {
+ "selected": false,
+ "text": "6h",
+ "value": "6h"
+ },
+ {
+ "selected": false,
+ "text": "12h",
+ "value": "12h"
+ },
+ {
+ "selected": false,
+ "text": "1d",
+ "value": "1d"
+ },
+ {
+ "selected": false,
+ "text": "7d",
+ "value": "7d"
+ },
+ {
+ "selected": false,
+ "text": "14d",
+ "value": "14d"
+ },
+ {
+ "selected": false,
+ "text": "30d",
+ "value": "30d"
+ }
+ ],
+ "query": "1m,10m,30m,1h,6h,12h,1d,7d,14d,30d",
+ "refresh": 2,
+ "type": "interval"
+ }
+ ]
+ },
+ "time": {
+ "from": "now-1h",
+ "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": "",
+ "title": "Spring Petclinic Metrics",
+ "uid": "69JXeR0iw",
+ "version": 1
+}
diff --git a/docker/grafana/grafana.ini b/docker/grafana/grafana.ini
new file mode 100644
index 000000000..2919aa463
--- /dev/null
+++ b/docker/grafana/grafana.ini
@@ -0,0 +1,27 @@
+##################### Spring Petclinic Microservices Grafana Configuration #####################
+
+# possible values : production, development
+app_mode = development
+
+#################################### Paths ####################################
+[paths]
+# folder that contains provisioning config files that grafana will apply on startup and while running.
+provisioning = /etc/grafana/provisioning
+
+#################################### Server ####################################
+[server]
+# enable gzip
+enable_gzip = true
+
+#################################### Anonymous Auth ##########################
+# Anonymous authentication has been enabled in the Petclinic sample with Admin role
+# Do not do that in Production environment
+[auth.anonymous]
+# enable anonymous access
+enabled = true
+
+# specify organization name that should be used for unauthenticated users
+org_name = Main Org.
+
+# specify role for unauthenticated users
+org_role = Admin
diff --git a/docker/grafana/provisioning/dashboards/all.yml b/docker/grafana/provisioning/dashboards/all.yml
new file mode 100644
index 000000000..3b978e625
--- /dev/null
+++ b/docker/grafana/provisioning/dashboards/all.yml
@@ -0,0 +1,11 @@
+apiVersion: 1
+
+providers:
+- name: 'default'
+ orgId: 1
+ folder: ''
+ type: file
+ disableDeletion: false
+ updateIntervalSeconds: 10 #how often Grafana will scan for changed dashboards
+ options:
+ path: /var/lib/grafana/dashboards
diff --git a/docker/grafana/provisioning/datasources/all.yml b/docker/grafana/provisioning/datasources/all.yml
new file mode 100644
index 000000000..9c88fce5a
--- /dev/null
+++ b/docker/grafana/provisioning/datasources/all.yml
@@ -0,0 +1,13 @@
+# config file version
+apiVersion: 1
+
+# list of datasources to insert/update depending what's available in the database
+datasources:
+- name: Prometheus
+ type: prometheus
+ access: proxy
+ org_id: 1
+ url: http://prometheus-server:9090
+ is_default: true
+ version: 1
+ editable: true
diff --git a/docs/grafana-custom-metrics-dashboard.png b/docs/grafana-custom-metrics-dashboard.png
new file mode 100644
index 000000000..56e87b47e
Binary files /dev/null and b/docs/grafana-custom-metrics-dashboard.png differ
diff --git a/pom.xml b/pom.xml
index 1958e2487..3627c00de 100644
--- a/pom.xml
+++ b/pom.xml
@@ -33,7 +33,6 @@
2.0.4.RELEASE
Finchley.SR2
2.0.0.RC2
- 1.0.5
2.22.0
@@ -79,18 +78,6 @@
test
-
-
- io.micrometer
- micrometer-core
- ${micrometer.version}
-
-
-
- io.micrometer
- micrometer-registry-prometheus
- ${micrometer.version}
-
diff --git a/spring-petclinic-api-gateway/pom.xml b/spring-petclinic-api-gateway/pom.xml
index ae40e34eb..790845120 100644
--- a/spring-petclinic-api-gateway/pom.xml
+++ b/spring-petclinic-api-gateway/pom.xml
@@ -87,10 +87,6 @@
org.projectlombok
lombok
-
- io.micrometer
- micrometer-core
-
io.micrometer
micrometer-registry-prometheus
diff --git a/spring-petclinic-api-gateway/src/test/jmeter/petclinic_test_plan.jmx b/spring-petclinic-api-gateway/src/test/jmeter/petclinic_test_plan.jmx
new file mode 100644
index 000000000..9e88f1a8e
--- /dev/null
+++ b/spring-petclinic-api-gateway/src/test/jmeter/petclinic_test_plan.jmx
@@ -0,0 +1,568 @@
+
+
+
+
+
+ false
+ true
+ false
+
+
+
+ PETCLINC_HOST
+ localhost
+ =
+
+
+ PETCLINIC_PORT
+ 8080
+ =
+
+
+
+
+
+
+
+
+
+
+ ${PETCLINC_HOST}
+ ${PETCLINIC_PORT}
+
+
+
+ 6
+
+
+
+
+
+
+
+ Accept
+ application/json, text/plain, */*
+
+
+ Content-Type
+ application/json;charset=UTF-8
+
+
+ Accept-Encoding
+ gzip, deflate, br
+
+
+
+
+
+
+
+ 1
+ 0
+ 0
+ 300
+ 30
+
+
+
+ false
+ -1
+
+ continue
+
+
+
+ PET_TYPE
+
+ 1
+ 6
+
+ false
+
+
+
+ 300
+ 100.0
+
+
+
+
+
+
+
+
+
+
+ /api/customer/owners
+ GET
+ true
+ false
+ true
+ false
+
+
+
+
+
+
+
+ 200
+
+
+ Assertion.response_code
+ false
+ 8
+
+
+
+
+ true
+
+
+
+ false
+ {
+ "firstName":"Firstname",
+ "lastName":"Lastname",
+ "address":"Adress",
+ "city":"City",
+ "telephone":"0000000000"
+}
+ =
+
+
+
+
+
+
+
+ /api/customer/owners
+ POST
+ true
+ false
+ true
+ false
+
+
+
+
+
+
+
+ 201
+
+
+ Assertion.response_code
+ false
+ 8
+
+
+
+ OWNER_ID
+ $.id
+
+
+
+
+
+
+
+
+
+
+
+
+ /api/gateway/owners/${OWNER_ID}
+ GET
+ true
+ false
+ true
+ false
+
+
+
+
+
+
+
+ 200
+
+
+ Assertion.response_code
+ false
+ 8
+
+
+
+
+ true
+
+
+
+ false
+ {
+ "id":"${OWNER_ID}",
+ "firstName":"Firstname",
+ "lastName":"Lastname${OWNER_ID}",
+ "address":"Adress${OWNER_ID}",
+ "city":"City${OWNER_ID}",
+ "telephone":"1111111111"
+}
+ =
+
+
+
+
+
+
+
+ /api/customer/owners/${OWNER_ID}
+ PUT
+ true
+ false
+ true
+ false
+
+
+
+
+
+
+
+ 204
+
+
+ Assertion.response_code
+ false
+ 8
+
+
+
+
+ true
+
+
+
+ false
+ {
+ "name":"Pet",
+ "birthDate":"2018-12-31T23:00:00.000Z",
+ "typeId":"${PET_TYPE}"
+}
+ =
+
+
+
+
+
+
+
+ /api/customer/owners/${OWNER_ID}/pets
+ POST
+ true
+ false
+ true
+ false
+
+
+
+
+
+
+
+ 201
+
+
+ Assertion.response_code
+ false
+ 8
+
+
+
+ PET_ID
+ $.id
+
+
+
+
+
+ 1
+
+
+
+ true
+
+
+
+ false
+ {
+ "name":"Pet",
+ "birthDate":"2018-12-31T23:00:00.000Z",
+ "typeId":"${PET_TYPE}"
+}
+ =
+
+
+
+
+
+
+
+ /api/customer/owners/${OWNER_ID}/pets
+ POST
+ true
+ false
+ true
+ false
+
+
+
+
+
+
+
+ 201
+
+
+ Assertion.response_code
+ false
+ 8
+
+
+
+
+
+ true
+
+
+
+ false
+ {
+ "id": ${PET_ID},
+ "name":"Pet${OWNER_ID}",
+ "birthDate":"2018-12-31T23:00:00.000Z",
+ "typeId":"${PET_TYPE}"
+}
+ =
+
+
+
+
+
+
+
+ /api/customer/owners/${OWNER_ID}/pets/${PET_ID}
+ PUT
+ true
+ false
+ true
+ false
+
+
+
+
+
+
+
+ 204
+
+
+ Assertion.response_code
+ false
+ 8
+
+
+
+
+ true
+
+
+
+ false
+ {
+ "date":"2019-03-15",
+ "description":"Visit"
+}
+ =
+
+
+
+
+
+
+
+ /api/visit/owners/${OWNER_ID}/pets/${PET_ID}/visits
+ POST
+ true
+ false
+ true
+ false
+
+
+
+
+
+
+
+ 201
+
+
+ Assertion.response_code
+ false
+ 8
+
+
+
+
+ 1
+
+
+
+ true
+
+
+
+ false
+ {
+ "date":"2019-03-15",
+ "description":"Visit"
+}
+ =
+
+
+
+
+
+
+
+ /api/visit/owners/${OWNER_ID}/pets/${PET_ID}/visits
+ POST
+ true
+ false
+ true
+ false
+
+
+
+
+
+
+
+ 201
+
+
+ Assertion.response_code
+ false
+ 8
+
+
+
+
+
+
+
+
+
+
+
+
+ /api/vet/vets
+ GET
+ true
+ false
+ true
+ false
+
+
+
+
+
+
+
+ 200
+
+
+ Assertion.response_code
+ false
+ 8
+
+
+
+
+ false
+
+ saveConfig
+
+
+ true
+ true
+ true
+
+ true
+ true
+ true
+ true
+ false
+ true
+ true
+ false
+ false
+ false
+ true
+ false
+ false
+ false
+ true
+ 0
+ true
+ true
+ true
+ true
+ true
+ true
+
+
+
+
+
+
+ false
+
+ saveConfig
+
+
+ true
+ true
+ true
+
+ true
+ true
+ true
+ true
+ false
+ true
+ true
+ false
+ false
+ false
+ true
+ false
+ false
+ false
+ true
+ 0
+ true
+ true
+ true
+ true
+ true
+ true
+
+
+
+
+
+
+
+
+
diff --git a/spring-petclinic-customers-service/pom.xml b/spring-petclinic-customers-service/pom.xml
index dc5928e47..9c411deb6 100644
--- a/spring-petclinic-customers-service/pom.xml
+++ b/spring-petclinic-customers-service/pom.xml
@@ -77,10 +77,6 @@
org.projectlombok
lombok
-
- io.micrometer
- micrometer-core
-
io.micrometer
micrometer-registry-prometheus
diff --git a/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/web/OwnerResource.java b/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/web/OwnerResource.java
index 80916f3ef..c9ea4b929 100644
--- a/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/web/OwnerResource.java
+++ b/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/web/OwnerResource.java
@@ -15,7 +15,7 @@
*/
package org.springframework.samples.petclinic.customers.web;
-import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.annotation.Timed;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
@@ -36,21 +36,20 @@
*/
@RequestMapping("/owners")
@RestController
+@Timed("petclinic.owner")
@RequiredArgsConstructor
@Slf4j
class OwnerResource {
private final OwnerRepository ownerRepository;
- private final MeterRegistry registry;
/**
* Create Owner
*/
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
- public void createOwner(@Valid @RequestBody Owner owner) {
- registry.counter("create.owner").increment();
- ownerRepository.save(owner);
+ public Owner createOwner(@Valid @RequestBody Owner owner) {
+ return ownerRepository.save(owner);
}
/**
@@ -73,7 +72,8 @@ public List findAll() {
* Update Owner
*/
@PutMapping(value = "/{ownerId}")
- public Owner updateOwner(@PathVariable("ownerId") int ownerId, @Valid @RequestBody Owner ownerRequest) {
+ @ResponseStatus(HttpStatus.NO_CONTENT)
+ public void updateOwner(@PathVariable("ownerId") int ownerId, @Valid @RequestBody Owner ownerRequest) {
final Optional owner = ownerRepository.findById(ownerId);
final Owner ownerModel = owner.orElseThrow(() -> new ResourceNotFoundException("Owner "+ownerId+" not found"));
@@ -84,7 +84,6 @@ public Owner updateOwner(@PathVariable("ownerId") int ownerId, @Valid @RequestBo
ownerModel.setAddress(ownerRequest.getAddress());
ownerModel.setTelephone(ownerRequest.getTelephone());
log.info("Saving owner {}", ownerModel);
- registry.counter("update.owner").increment();
- return ownerRepository.save(ownerModel);
+ ownerRepository.save(ownerModel);
}
}
diff --git a/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/web/PetResource.java b/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/web/PetResource.java
index 34c8c70d6..3b8bdd887 100644
--- a/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/web/PetResource.java
+++ b/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/web/PetResource.java
@@ -15,7 +15,7 @@
*/
package org.springframework.samples.petclinic.customers.web;
-import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.annotation.Timed;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
@@ -32,13 +32,13 @@
* @author Maciej Szarlinski
*/
@RestController
+@Timed("petclinic.pet")
@RequiredArgsConstructor
@Slf4j
class PetResource {
private final PetRepository petRepository;
private final OwnerRepository ownerRepository;
- private final MeterRegistry registry;
@GetMapping("/petTypes")
@@ -47,8 +47,8 @@ public List getPetTypes() {
}
@PostMapping("/owners/{ownerId}/pets")
- @ResponseStatus(HttpStatus.NO_CONTENT)
- public void processCreationForm(
+ @ResponseStatus(HttpStatus.CREATED)
+ public Pet processCreationForm(
@RequestBody PetRequest petRequest,
@PathVariable("ownerId") int ownerId) {
@@ -57,8 +57,7 @@ public void processCreationForm(
Owner owner = optionalOwner.orElseThrow(() -> new ResourceNotFoundException("Owner "+ownerId+" not found"));
owner.addPet(pet);
- registry.counter("create.pet").increment();
- save(pet, petRequest);
+ return save(pet, petRequest);
}
@PutMapping("/owners/*/pets/{petId}")
@@ -66,11 +65,10 @@ public void processCreationForm(
public void processUpdateForm(@RequestBody PetRequest petRequest) {
int petId = petRequest.getId();
Pet pet = findPetById(petId);
- registry.counter("update.pet").increment();
save(pet, petRequest);
}
- private void save(final Pet pet, final PetRequest petRequest) {
+ private Pet save(final Pet pet, final PetRequest petRequest) {
pet.setName(petRequest.getName());
pet.setBirthDate(petRequest.getBirthDate());
@@ -79,7 +77,7 @@ private void save(final Pet pet, final PetRequest petRequest) {
.ifPresent(pet::setType);
log.info("Saving pet {}", pet);
- petRepository.save(pet);
+ return petRepository.save(pet);
}
@GetMapping("owners/*/pets/{petId}")
diff --git a/spring-petclinic-customers-service/src/test/java/org/springframework/samples/petclinic/customers/web/PetResourceTest.java b/spring-petclinic-customers-service/src/test/java/org/springframework/samples/petclinic/customers/web/PetResourceTest.java
index 5b2b3303c..04a6e0079 100644
--- a/spring-petclinic-customers-service/src/test/java/org/springframework/samples/petclinic/customers/web/PetResourceTest.java
+++ b/spring-petclinic-customers-service/src/test/java/org/springframework/samples/petclinic/customers/web/PetResourceTest.java
@@ -2,7 +2,6 @@
import java.util.Optional;
-import io.micrometer.core.instrument.MeterRegistry;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
@@ -42,9 +41,6 @@ class PetResourceTest {
@MockBean
OwnerRepository ownerRepository;
- @MockBean
- MeterRegistry registry;
-
@Test
void shouldGetAPetInJSonFormat() throws Exception {
diff --git a/spring-petclinic-vets-service/pom.xml b/spring-petclinic-vets-service/pom.xml
index 99b957703..9f3c58cd7 100644
--- a/spring-petclinic-vets-service/pom.xml
+++ b/spring-petclinic-vets-service/pom.xml
@@ -93,10 +93,6 @@
mysql-connector-java
runtime
-
- io.micrometer
- micrometer-core
-
io.micrometer
micrometer-registry-prometheus
diff --git a/spring-petclinic-visits-service/pom.xml b/spring-petclinic-visits-service/pom.xml
index c60737406..2bf6cc5d0 100644
--- a/spring-petclinic-visits-service/pom.xml
+++ b/spring-petclinic-visits-service/pom.xml
@@ -76,10 +76,6 @@
mysql-connector-java
runtime
-
- io.micrometer
- micrometer-core
-
io.micrometer
micrometer-registry-prometheus
diff --git a/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/web/VisitResource.java b/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/web/VisitResource.java
index 5905f3715..3bcf7f76d 100644
--- a/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/web/VisitResource.java
+++ b/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/web/VisitResource.java
@@ -18,7 +18,7 @@
import java.util.List;
import javax.validation.Valid;
-import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.annotation.Timed;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
@@ -43,21 +43,20 @@
@RestController
@RequiredArgsConstructor
@Slf4j
+@Timed("petclinic.visit")
class VisitResource {
private final VisitRepository visitRepository;
- private final MeterRegistry registry;
@PostMapping("owners/*/pets/{petId}/visits")
- @ResponseStatus(HttpStatus.NO_CONTENT)
- void create(
+ @ResponseStatus(HttpStatus.CREATED)
+ Visit create(
@Valid @RequestBody Visit visit,
@PathVariable("petId") int petId) {
visit.setPetId(petId);
log.info("Saving visit {}", visit);
- registry.counter("create.visit").increment();
- visitRepository.save(visit);
+ return visitRepository.save(visit);
}
@GetMapping("owners/*/pets/{petId}/visits")
diff --git a/spring-petclinic-visits-service/src/test/java/org/springframework/samples/petclinic/visits/web/VisitResourceTest.java b/spring-petclinic-visits-service/src/test/java/org/springframework/samples/petclinic/visits/web/VisitResourceTest.java
index 3409b30ab..25d33f24b 100644
--- a/spring-petclinic-visits-service/src/test/java/org/springframework/samples/petclinic/visits/web/VisitResourceTest.java
+++ b/spring-petclinic-visits-service/src/test/java/org/springframework/samples/petclinic/visits/web/VisitResourceTest.java
@@ -1,6 +1,5 @@
package org.springframework.samples.petclinic.visits.web;
-import io.micrometer.core.instrument.MeterRegistry;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
@@ -30,9 +29,6 @@ class VisitResourceTest {
@MockBean
VisitRepository visitRepository;
- @MockBean
- MeterRegistry registry;
-
@Test
void shouldFetchVisits() throws Exception {
given(visitRepository.findByPetIdIn(asList(111, 222)))