diff --git a/README.md b/README.md index 8d131ff2be2f455da1a0b88ee639e8057a34a78d..5f5fa65329e4be8b3d9a1403f6fbf40cb8fc947c 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 1209bd7b4d87be0ae1cdc76d3e077193e021aa7e..2fffc3ebe37fab17dbf658824e14f71805ee9a82 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 0000000000000000000000000000000000000000..c11b3d911e8ef1424cea7c5d704e7e3efc82de9d --- /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 0000000000000000000000000000000000000000..6d79a8d5f45cbab728b11a49e5a9f0d43b725619 --- /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 0000000000000000000000000000000000000000..2919aa46350ba482fe098fb7e34f1fed306a4d8e --- /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 0000000000000000000000000000000000000000..3b978e625482bba3edbde48587da97f154676120 --- /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 0000000000000000000000000000000000000000..9c88fce5ac1923edb0207ecf34090a3c3642e760 --- /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 0000000000000000000000000000000000000000..56e87b47ed9cbb1052ea782814a81e421c68220f Binary files /dev/null and b/docs/grafana-custom-metrics-dashboard.png differ diff --git a/pom.xml b/pom.xml index 1958e248768cf502a2b84bb34f7e1a554e1672f9..3627c00deff6697f2bed2f7505e74270df667a9d 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,6 @@ <spring-boot.version>2.0.4.RELEASE</spring-boot.version> <spring-cloud.version>Finchley.SR2</spring-cloud.version> <sleuth.version>2.0.0.RC2</sleuth.version> - <micrometer.version>1.0.5</micrometer.version> <maven-surefire-plugin.version>2.22.0</maven-surefire-plugin.version> @@ -79,18 +78,6 @@ <scope>test</scope> </dependency> - <!-- Micrometer core dependency --> - <dependency> - <groupId>io.micrometer</groupId> - <artifactId>micrometer-core</artifactId> - <version>${micrometer.version}</version> - </dependency> - <!-- Micrometer Prometheus registry --> - <dependency> - <groupId>io.micrometer</groupId> - <artifactId>micrometer-registry-prometheus</artifactId> - <version>${micrometer.version}</version> - </dependency> </dependencies> </dependencyManagement> diff --git a/spring-petclinic-api-gateway/pom.xml b/spring-petclinic-api-gateway/pom.xml index ae40e34eb04dc67f455d2d70230d02ab627cf1ea..790845120eb49db6ac2ab97087eab572d478338c 100644 --- a/spring-petclinic-api-gateway/pom.xml +++ b/spring-petclinic-api-gateway/pom.xml @@ -87,10 +87,6 @@ <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> - <dependency> - <groupId>io.micrometer</groupId> - <artifactId>micrometer-core</artifactId> - </dependency> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> 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 0000000000000000000000000000000000000000..9e88f1a8e5c61b88bc2ef804a99568a8b77b5278 --- /dev/null +++ b/spring-petclinic-api-gateway/src/test/jmeter/petclinic_test_plan.jmx @@ -0,0 +1,568 @@ +<?xml version="1.0" encoding="UTF-8"?> +<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.0 r1840935"> + <hashTree> + <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Spring Petclinic Microservices" enabled="true"> + <stringProp name="TestPlan.comments"></stringProp> + <boolProp name="TestPlan.functional_mode">false</boolProp> + <boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp> + <boolProp name="TestPlan.serialize_threadgroups">false</boolProp> + <elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"> + <elementProp name="PETCLINC_HOST" elementType="Argument"> + <stringProp name="Argument.name">PETCLINC_HOST</stringProp> + <stringProp name="Argument.value">localhost</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="PETCLINIC_PORT" elementType="Argument"> + <stringProp name="Argument.name">PETCLINIC_PORT</stringProp> + <stringProp name="Argument.value">8080</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="TestPlan.user_define_classpath"></stringProp> + </TestPlan> + <hashTree> + <ConfigTestElement guiclass="HttpDefaultsGui" testclass="ConfigTestElement" testname="HTTP Request Defaults" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="Variables pr�-d�finies" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">${PETCLINC_HOST}</stringProp> + <stringProp name="HTTPSampler.port">${PETCLINIC_PORT}</stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.concurrentPool">6</stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </ConfigTestElement> + <hashTree/> + <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> + <collectionProp name="HeaderManager.headers"> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Accept</stringProp> + <stringProp name="Header.value">application/json, text/plain, */*</stringProp> + </elementProp> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Content-Type</stringProp> + <stringProp name="Header.value">application/json;charset=UTF-8</stringProp> + </elementProp> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Accept-Encoding</stringProp> + <stringProp name="Header.value">gzip, deflate, br</stringProp> + </elementProp> + </collectionProp> + </HeaderManager> + <hashTree/> + <kg.apc.jmeter.threads.UltimateThreadGroup guiclass="kg.apc.jmeter.threads.UltimateThreadGroupGui" testclass="kg.apc.jmeter.threads.UltimateThreadGroup" testname="jp@gc - Ultimate Thread Group" enabled="true"> + <collectionProp name="ultimatethreadgroupdata"> + <collectionProp name="-111815413"> + <stringProp name="49">1</stringProp> + <stringProp name="0">0</stringProp> + <stringProp name="48">0</stringProp> + <stringProp name="50547">300</stringProp> + <stringProp name="1629">30</stringProp> + </collectionProp> + </collectionProp> + <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true"> + <boolProp name="LoopController.continue_forever">false</boolProp> + <intProp name="LoopController.loops">-1</intProp> + </elementProp> + <stringProp name="ThreadGroup.on_sample_error">continue</stringProp> + </kg.apc.jmeter.threads.UltimateThreadGroup> + <hashTree> + <RandomVariableConfig guiclass="TestBeanGUI" testclass="RandomVariableConfig" testname="Pet Type" enabled="true"> + <stringProp name="variableName">PET_TYPE</stringProp> + <stringProp name="outputFormat"></stringProp> + <stringProp name="minimumValue">1</stringProp> + <stringProp name="maximumValue">6</stringProp> + <stringProp name="randomSeed"></stringProp> + <boolProp name="perThread">false</boolProp> + </RandomVariableConfig> + <hashTree/> + <GaussianRandomTimer guiclass="GaussianRandomTimerGui" testclass="GaussianRandomTimer" testname="Gaussian Random Timer" enabled="true"> + <stringProp name="ConstantTimer.delay">300</stringProp> + <stringProp name="RandomTimer.range">100.0</stringProp> + </GaussianRandomTimer> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="All Owners" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="Variables pr�-d�finies" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain"></stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path">/api/customer/owners</stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assertion HTTP 200" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="49586">200</stringProp> + </collectionProp> + <stringProp name="Assertion.custom_message"></stringProp> + <stringProp name="Assertion.test_field">Assertion.response_code</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Add Owner" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{ 
 + "firstName":"Firstname",
 + "lastName":"Lastname",
 + "address":"Adress",
 + "city":"City",
 + "telephone":"0000000000"
 +}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"></stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path">/api/customer/owners</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assertion HTTP 201" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="49587">201</stringProp> + </collectionProp> + <stringProp name="Assertion.custom_message"></stringProp> + <stringProp name="Assertion.test_field">Assertion.response_code</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> + <hashTree/> + <JSONPostProcessor guiclass="JSONPostProcessorGui" testclass="JSONPostProcessor" testname="Owner ID Extractor" enabled="true"> + <stringProp name="JSONPostProcessor.referenceNames">OWNER_ID</stringProp> + <stringProp name="JSONPostProcessor.jsonPathExprs">$.id</stringProp> + <stringProp name="JSONPostProcessor.match_numbers"></stringProp> + </JSONPostProcessor> + <hashTree/> + </hashTree> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Owner Details" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="Variables pr�-d�finies" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain"></stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path">/api/gateway/owners/${OWNER_ID}</stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assertion HTTP 200" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="49586">200</stringProp> + </collectionProp> + <stringProp name="Assertion.custom_message"></stringProp> + <stringProp name="Assertion.test_field">Assertion.response_code</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Update Owner" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{ 
 + "id":"${OWNER_ID}",
 + "firstName":"Firstname",
 + "lastName":"Lastname${OWNER_ID}",
 + "address":"Adress${OWNER_ID}",
 + "city":"City${OWNER_ID}",
 + "telephone":"1111111111"
 +}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"></stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path">/api/customer/owners/${OWNER_ID}</stringProp> + <stringProp name="HTTPSampler.method">PUT</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assertion HTTP 204" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="49590">204</stringProp> + </collectionProp> + <stringProp name="Assertion.custom_message"></stringProp> + <stringProp name="Assertion.test_field">Assertion.response_code</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Add Pet" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{ 
 + "name":"Pet",
 + "birthDate":"2018-12-31T23:00:00.000Z",
 + "typeId":"${PET_TYPE}"
 +}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"></stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path">/api/customer/owners/${OWNER_ID}/pets</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assertion HTTP 201" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="49587">201</stringProp> + </collectionProp> + <stringProp name="Assertion.custom_message"></stringProp> + <stringProp name="Assertion.test_field">Assertion.response_code</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> + <hashTree/> + <JSONPostProcessor guiclass="JSONPostProcessorGui" testclass="JSONPostProcessor" testname="Pet ID Extractor" enabled="true"> + <stringProp name="JSONPostProcessor.referenceNames">PET_ID</stringProp> + <stringProp name="JSONPostProcessor.jsonPathExprs">$.id</stringProp> + <stringProp name="JSONPostProcessor.match_numbers"></stringProp> + </JSONPostProcessor> + <hashTree/> + </hashTree> + <RandomController guiclass="RandomControlGui" testclass="RandomController" testname="Add Random second Pet" enabled="true"> + <intProp name="InterleaveControl.style">1</intProp> + </RandomController> + <hashTree> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Add Pet" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{ 
 + "name":"Pet",
 + "birthDate":"2018-12-31T23:00:00.000Z",
 + "typeId":"${PET_TYPE}"
 +}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"></stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path">/api/customer/owners/${OWNER_ID}/pets</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assertion HTTP 201" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="49587">201</stringProp> + </collectionProp> + <stringProp name="Assertion.custom_message"></stringProp> + <stringProp name="Assertion.test_field">Assertion.response_code</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + </hashTree> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Update Pet" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{ 
 + "id": ${PET_ID},
 + "name":"Pet${OWNER_ID}",
 + "birthDate":"2018-12-31T23:00:00.000Z",
 + "typeId":"${PET_TYPE}"
 +}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"></stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path">/api/customer/owners/${OWNER_ID}/pets/${PET_ID}</stringProp> + <stringProp name="HTTPSampler.method">PUT</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assertion HTTP 204" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="49590">204</stringProp> + </collectionProp> + <stringProp name="Assertion.custom_message"></stringProp> + <stringProp name="Assertion.test_field">Assertion.response_code</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Add Visit" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{ 
 + "date":"2019-03-15",
 + "description":"Visit"
 +}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"></stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path">/api/visit/owners/${OWNER_ID}/pets/${PET_ID}/visits</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assertion HTTP 201" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="49587">201</stringProp> + </collectionProp> + <stringProp name="Assertion.custom_message"></stringProp> + <stringProp name="Assertion.test_field">Assertion.response_code</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + <RandomController guiclass="RandomControlGui" testclass="RandomController" testname="Add Random second visit" enabled="true"> + <intProp name="InterleaveControl.style">1</intProp> + </RandomController> + <hashTree> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Add Visit" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{ 
 + "date":"2019-03-15",
 + "description":"Visit"
 +}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"></stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path">/api/visit/owners/${OWNER_ID}/pets/${PET_ID}/visits</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assertion HTTP 201" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="49587">201</stringProp> + </collectionProp> + <stringProp name="Assertion.custom_message"></stringProp> + <stringProp name="Assertion.test_field">Assertion.response_code</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + </hashTree> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="All Vets" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="Variables pr�-d�finies" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain"></stringProp> + <stringProp name="HTTPSampler.port"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path">/api/vet/vets</stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assertion HTTP 200" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="49586">200</stringProp> + </collectionProp> + <stringProp name="Assertion.custom_message"></stringProp> + <stringProp name="Assertion.test_field">Assertion.response_code</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + <ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>false</xml> + <fieldNames>true</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + <sentBytes>true</sentBytes> + <url>true</url> + <threadCounts>true</threadCounts> + <idleTime>true</idleTime> + <connectTime>true</connectTime> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + <ResultCollector guiclass="SummaryReport" testclass="ResultCollector" testname="Summary Report" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>false</xml> + <fieldNames>true</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + <sentBytes>true</sentBytes> + <url>true</url> + <threadCounts>true</threadCounts> + <idleTime>true</idleTime> + <connectTime>true</connectTime> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + </hashTree> + </hashTree> + </hashTree> +</jmeterTestPlan> diff --git a/spring-petclinic-customers-service/pom.xml b/spring-petclinic-customers-service/pom.xml index dc5928e477bcc4cda5d72006c66700d5d07c1ac7..9c411deb62087ea1fee872ed9515896b7c5e025d 100644 --- a/spring-petclinic-customers-service/pom.xml +++ b/spring-petclinic-customers-service/pom.xml @@ -77,10 +77,6 @@ <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> - <dependency> - <groupId>io.micrometer</groupId> - <artifactId>micrometer-core</artifactId> - </dependency> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> 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 80916f3efd0e60341bacff931fe56be06edfa759..c9ea4b929fbfe6fa951259d5d972f3d9d4c607b5 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 @@ import java.util.Optional; */ @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 @@ class OwnerResource { * 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> owner = ownerRepository.findById(ownerId); final Owner ownerModel = owner.orElseThrow(() -> new ResourceNotFoundException("Owner "+ownerId+" not found")); @@ -84,7 +84,6 @@ class OwnerResource { 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 34c8c70d6da7487791803f8fc8b6638ce6c3fdc7..3b8bdd88702744e3bdcaba892b82dd7e1530991a 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 @@ import java.util.Optional; * @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 @@ class PetResource { } @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 @@ class PetResource { 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 @@ class PetResource { 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 @@ class PetResource { .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 5b2b3303c556b0efd3f1e1fd17a9d50a56cc3379..04a6e0079df23370789c31ad55d65ffd092ee994 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 @@ package org.springframework.samples.petclinic.customers.web; 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 99b95770388e3001f753f5c1a1349d137fb523e7..9f3c58cd7f1bbeb78151a69dae1d2ae98396761f 100644 --- a/spring-petclinic-vets-service/pom.xml +++ b/spring-petclinic-vets-service/pom.xml @@ -93,10 +93,6 @@ <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> - <dependency> - <groupId>io.micrometer</groupId> - <artifactId>micrometer-core</artifactId> - </dependency> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> diff --git a/spring-petclinic-visits-service/pom.xml b/spring-petclinic-visits-service/pom.xml index c60737406048892caf24887750a0a576bc6bbc50..2bf6cc5d07af1dc9d18e0913f6ee2d8a42e1440a 100644 --- a/spring-petclinic-visits-service/pom.xml +++ b/spring-petclinic-visits-service/pom.xml @@ -76,10 +76,6 @@ <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> - <dependency> - <groupId>io.micrometer</groupId> - <artifactId>micrometer-core</artifactId> - </dependency> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> 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 5905f3715392fac0d3d7b59047ad84817caffd34..3bcf7f76d69df3b32593efb0860bd826219a8900 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 @@ package org.springframework.samples.petclinic.visits.web; 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 @@ import org.springframework.web.bind.annotation.RestController; @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 3409b30abd3b848b888586e422a7cb84bc294481..25d33f24ba7bba4bbd962752b6f05a98acd786d3 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)))