diff --git a/analysis-service/build.gradle b/analysis-service/build.gradle index 751ec05a..f5f26b08 100644 --- a/analysis-service/build.gradle +++ b/analysis-service/build.gradle @@ -19,11 +19,21 @@ dependencies { implementation project(':kiekeradapter') // Teetime Pipe and Filter Framework - implementation group: 'net.sourceforge.teetime', name: 'teetime', version: '3.0-SNAPSHOT' + implementation group: 'net.sourceforge.teetime', name: 'teetime', version: '3.1-SNAPSHOT' implementation group: 'net.sourceforge.teetime-stages', name: 'teetime-stages', version: '3.0-SNAPSHOT' // Kieker Monitoring Framework implementation group: 'net.kieker-monitoring', name: 'kieker', version: '1.14-SNAPSHOT' + + // Prometheus + // Base + implementation group: 'io.prometheus', name: 'simpleclient', version: '0.8.1' + // Exposition server + implementation group: 'io.prometheus', name: 'simpleclient_httpserver', version: '0.8.1' + // JVM Metrics + implementation group: 'io.prometheus', name: 'simpleclient_hotspot', version: '0.8.1' + + } // disable integration tests, since there are none @@ -76,4 +86,4 @@ task spotbugs { dependsOn 'spotbugsMain' dependsOn 'spotbugsTest' -} \ No newline at end of file +} diff --git a/analysis-service/src/main/java/net/explorviz/analysis/Main.java b/analysis-service/src/main/java/net/explorviz/analysis/Main.java index 9871f9fb..63578706 100644 --- a/analysis-service/src/main/java/net/explorviz/analysis/Main.java +++ b/analysis-service/src/main/java/net/explorviz/analysis/Main.java @@ -1,10 +1,33 @@ package net.explorviz.analysis; +import io.prometheus.client.exporter.HTTPServer; +import io.prometheus.client.hotspot.DefaultExports; import net.explorviz.kiekeradapter.main.KiekerAdapter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; public class Main { + private static final Logger LOGGER = LoggerFactory.getLogger(Main.class); + + private static final int PROM_PORT = 1234; + public static void main(final String[] args) { + + + try { + // Starts the server + new HTTPServer(PROM_PORT); + // JVM Metrics + DefaultExports.initialize(); + LOGGER.info("Started prometheus server on port " + PROM_PORT); + } catch (IOException e) { + LOGGER.warn("Failed to start metrics HTTP server", e); + } + + KiekerAdapter.getInstance().startReader(); } diff --git a/broadcast-service/build.gradle b/broadcast-service/build.gradle index 768ad654..785538f9 100644 --- a/broadcast-service/build.gradle +++ b/broadcast-service/build.gradle @@ -41,6 +41,17 @@ dependencies { implementation group: 'org.glassfish.jersey.media', name: 'jersey-media-sse', version: '2.27' implementation group: 'org.apache.kafka', name: 'kafka_2.12', version: '2.2.0' + + // Prometheus + // Base + implementation group: 'io.prometheus', name: 'simpleclient', version: '0.8.1' + //implementation group: 'io.prometheus', name: 'simpleclient_httpserver', version: '0.8.1' + // Expose via Jetty + implementation group: 'io.prometheus', name: 'simpleclient_servlet', version: '0.8.1' + // Collect Jetty stats + implementation group: 'io.prometheus', name: 'simpleclient_jetty', version: '0.8.1' + // JVM Metrics + implementation group: 'io.prometheus', name: 'simpleclient_hotspot', version: '0.8.1' } // disable integration tests, since there are none diff --git a/broadcast-service/src/main/java/net/explorviz/broadcast/server/main/Main.java b/broadcast-service/src/main/java/net/explorviz/broadcast/server/main/Main.java index c0dc49dc..d1c3177e 100644 --- a/broadcast-service/src/main/java/net/explorviz/broadcast/server/main/Main.java +++ b/broadcast-service/src/main/java/net/explorviz/broadcast/server/main/Main.java @@ -1,7 +1,13 @@ package net.explorviz.broadcast.server.main; +import io.prometheus.client.exporter.MetricsServlet; +import io.prometheus.client.filter.MetricsFilter; +import io.prometheus.client.hotspot.DefaultExports; +import io.prometheus.client.jetty.JettyStatisticsCollector; import net.explorviz.shared.config.helper.PropertyHelper; import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.StatisticsHandler; +import org.eclipse.jetty.servlet.FilterHolder; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.glassfish.jersey.server.ResourceConfig; @@ -9,6 +15,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.servlet.DispatcherType; +import java.util.EnumSet; + /** * Main entry point for this Java application. * @@ -34,6 +43,24 @@ public static void main(final String[] args) { final ServletContextHandler context = new ServletContextHandler(server, getContextPath()); context.addServlet(jerseyServlet, "/*"); + // Prometheus + StatisticsHandler stats = new StatisticsHandler(); + stats.setHandler(server.getHandler()); + server.setHandler(stats); + + new JettyStatisticsCollector(stats).register(); + + context.addServlet(new ServletHolder(new MetricsServlet()), "/metrics"); + + MetricsFilter metricsFilter = + new MetricsFilter("explorviz_request_time", + "Filter to measure and forward request times", 2, null); + FilterHolder filterHolder = new FilterHolder(metricsFilter); + context.addFilter(filterHolder, "/v1/*", EnumSet.of(DispatcherType.REQUEST)); + + // Collect JVM metrics + DefaultExports.initialize(); + try { server.start(); } catch (final Exception e) { // NOPMD diff --git a/discovery-service/build.gradle b/discovery-service/build.gradle index ddfe99bf..54cf16a5 100644 --- a/discovery-service/build.gradle +++ b/discovery-service/build.gradle @@ -33,7 +33,19 @@ dependencies { } // SSE context for injection - implementation group: 'org.glassfish.jersey.media', name: 'jersey-media-sse', version: '2.27' + implementation group: 'org.glassfish.jersey.media', name: 'jersey-media-sse', version: '2.27' + + // Prometheus + // Base + implementation group: 'io.prometheus', name: 'simpleclient', version: '0.8.1' + //implementation group: 'io.prometheus', name: 'simpleclient_httpserver', version: '0.8.1' + // Expose via Jetty + implementation group: 'io.prometheus', name: 'simpleclient_servlet', version: '0.8.1' + // Collect Jetty stats + implementation group: 'io.prometheus', name: 'simpleclient_jetty', version: '0.8.1' + // JVM Metrics + implementation group: 'io.prometheus', name: 'simpleclient_hotspot', version: '0.8.1' + } // disable integration tests, since there are none @@ -118,4 +130,4 @@ task updateEnvPropsInBuildDir { run.dependsOn updateEnvPropsInBuildDir updateEnvPropsInBuildDir.shouldRunAfter classes -// END environment task \ No newline at end of file +// END environment task diff --git a/discovery-service/src/main/java/net/explorviz/discovery/server/main/Main.java b/discovery-service/src/main/java/net/explorviz/discovery/server/main/Main.java index 42b054cd..e9a55262 100644 --- a/discovery-service/src/main/java/net/explorviz/discovery/server/main/Main.java +++ b/discovery-service/src/main/java/net/explorviz/discovery/server/main/Main.java @@ -1,7 +1,13 @@ package net.explorviz.discovery.server.main; +import io.prometheus.client.exporter.MetricsServlet; +import io.prometheus.client.filter.MetricsFilter; +import io.prometheus.client.hotspot.DefaultExports; +import io.prometheus.client.jetty.JettyStatisticsCollector; import net.explorviz.shared.config.helper.PropertyHelper; import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.StatisticsHandler; +import org.eclipse.jetty.servlet.FilterHolder; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.glassfish.jersey.server.ResourceConfig; @@ -9,6 +15,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.servlet.DispatcherType; +import java.util.EnumSet; + /** * Entry point for the web service. This main method will start a web server based on the * configuration properties inside of the explorviz.properties file. @@ -36,6 +45,25 @@ public static void main(final String[] args) { final ServletContextHandler context = new ServletContextHandler(server, getContextPath()); context.addServlet(jerseyServlet, "/*"); + + // Prometheus + StatisticsHandler stats = new StatisticsHandler(); + stats.setHandler(server.getHandler()); + server.setHandler(stats); + + new JettyStatisticsCollector(stats).register(); + + context.addServlet(new ServletHolder(new MetricsServlet()), "/metrics"); + + MetricsFilter metricsFilter = + new MetricsFilter("explorviz_request_time", + "Filter to measure and forward request times", 20, null); + FilterHolder filterHolder = new FilterHolder(metricsFilter); + context.addFilter(filterHolder, "/v1/*", EnumSet.of(DispatcherType.REQUEST)); + + // JVM Metrics + DefaultExports.initialize(); + try { server.start(); } catch (final Exception e) { // NOPMD diff --git a/docker-compose/docker-compose-linux.yml b/docker-compose/docker-compose-linux.yml index cb1287f4..7f771277 100644 --- a/docker-compose/docker-compose-linux.yml +++ b/docker-compose/docker-compose-linux.yml @@ -4,8 +4,6 @@ services: reverse-proxy: image: "traefik:v2.1" container_name: "explorviz-reverse-proxy" - ports: - - "8080:8080" volumes: - "./traefik/traefik-static.toml:/etc/traefik/traefik.toml" - "./traefik/traefik-dynamic-linux.toml:/etc/traefik/traefik-dynamic.toml" @@ -69,6 +67,33 @@ services: env_file: - apis.env + prometheus: + image: prom/prometheus + ports: + - 9090:9090 + volumes: + - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml + command: + - '--config.file=/etc/prometheus/prometheus.yml' + network_mode: "host" + + prom-node-exporter: + image: prom/node-exporter + ports: + - 9100:9100 + network_mode: "host" + + grafana: + image: grafana/grafana + ports: + - 3000:3000 + volumes: + - ./monitoring/grafana/config.ini:/etc/grafana/grafana.ini + - ./monitoring/grafana/provisioning:/etc/grafana/provisioning + - ./monitoring/grafana/dashboards:/var/lib/grafana/dashboards + network_mode: "host" + + volumes: explorviz-auth-mongo-data: explorviz-auth-mongo-configdb: diff --git a/docker-compose/docker-compose-windows-mac.yml b/docker-compose/docker-compose-windows-mac.yml index dfbeeee5..ef49ccc3 100644 --- a/docker-compose/docker-compose-windows-mac.yml +++ b/docker-compose/docker-compose-windows-mac.yml @@ -66,6 +66,29 @@ services: env_file: - apis.env + prometheus: + image: prom/prometheus + ports: + - 9090:9090 + volumes: + - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml + command: + - '--config.file=/etc/prometheus/prometheus.yml' + + prom-node-exporter: + image: prom/node-exporter + ports: + - 9100:9100 + + grafana: + image: grafana/grafana + ports: + - 3000:3000 + volumes: + - ./monitoring/grafana/config.ini:/etc/grafana/grafana.ini + - ./monitoring/grafana/provisioning:/etc/grafana/provisioning + - ./monitoring/grafana/dashboards:/var/lib/grafana/dashboards + volumes: explorviz-auth-mongo-data: explorviz-auth-mongo-configdb: diff --git a/docker-compose/monitoring/grafana/config.ini b/docker-compose/monitoring/grafana/config.ini new file mode 100644 index 00000000..353cbd58 --- /dev/null +++ b/docker-compose/monitoring/grafana/config.ini @@ -0,0 +1,9 @@ +[paths] +provisioning = /etc/grafana/provisioning + +[server] +enable_gzip = true + +[security] +admin_user = admin +admin_password = password diff --git a/docker-compose/monitoring/grafana/dashboards/http-apis.json b/docker-compose/monitoring/grafana/dashboards/http-apis.json new file mode 100644 index 00000000..3121f48a --- /dev/null +++ b/docker-compose/monitoring/grafana/dashboards/http-apis.json @@ -0,0 +1,374 @@ +{ + "annotations": { + "list": [ + { + "$$hashKey": "object:1587", + "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, + "iteration": 1585316576289, + "links": [], + "panels": [ + { + "aliasColors": { + "1xx Response": "semi-dark-purple", + "2xx Response": "green", + "3xx Response": "purple", + "5xx Response": "red", + "Answered": "green", + "Inbound Requests": "blue" + }, + "bars": true, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 4, + "fillGradient": 6, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "hiddenSeries": false, + "hideTimeOverride": false, + "id": 2, + "interval": "", + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "targets": [ + { + "expr": "floor(increase(jetty_requests_total{job=\"$service\"}[$__interval] offset $__interval))", + "format": "time_series", + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "Inbound Requests", + "refId": "A" + }, + { + "expr": "floor(increase(jetty_responses_total{job=\"$service\"}[$__interval] offset $__interval))", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{code}} Response", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "HTTP Requests/Responses", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:1767", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:1768", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": false + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 0, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "hiddenSeries": false, + "id": 8, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "connected", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pluginVersion": "6.7.1", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(explorviz_request_time_sum{job=\"$service\"}[$__interval])/rate(explorviz_request_time_count{job=\"$service\"}[$__interval])", + "interval": "", + "legendFormat": "{{method}} {{path}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Average Request Duration", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:2341", + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:2342", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "cards": { + "cardPadding": null, + "cardRound": 5 + }, + "color": { + "cardColor": "#b4ff00", + "colorScale": "sqrt", + "colorScheme": "interpolateRdYlGn", + "exponent": 0.5, + "min": 0, + "mode": "spectrum" + }, + "dataFormat": "tsbuckets", + "datasource": "Prometheus", + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 8 + }, + "heatmap": {}, + "hideZeroBuckets": false, + "highlightCards": true, + "id": 6, + "interval": "", + "legend": { + "show": true + }, + "pluginVersion": "6.7.1", + "reverseYBuckets": false, + "targets": [ + { + "expr": "increase(explorviz_request_time_count{job=\"$service\"}[$__interval])", + "interval": "", + "legendFormat": "{{method}} {{path}}", + "refId": "A" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "API Calls per Minute", + "tooltip": { + "show": true, + "showHistogram": false + }, + "tooltipDecimals": null, + "type": "heatmap", + "xAxis": { + "show": true + }, + "xBucketNumber": null, + "xBucketSize": null, + "yAxis": { + "decimals": null, + "format": "short", + "logBase": 1, + "max": null, + "min": null, + "show": true, + "splitFactor": null + }, + "yBucketBound": "middle", + "yBucketNumber": null, + "yBucketSize": null + } + ], + "refresh": "", + "schemaVersion": 22, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "allValue": null, + "current": { + "$$hashKey": "object:3976", + "selected": true, + "text": "user-service", + "value": "user-service" + }, + "hide": 0, + "includeAll": false, + "label": null, + "multi": false, + "name": "service", + "options": [ + { + "$$hashKey": "object:3972", + "selected": false, + "text": "broadcast-service", + "value": "broadcast-service" + }, + { + "$$hashKey": "object:3973", + "selected": false, + "text": "discovery-service", + "value": "discovery-service" + }, + { + "$$hashKey": "object:3974", + "selected": false, + "text": "history-service", + "value": "history-service" + }, + { + "$$hashKey": "object:3975", + "selected": false, + "text": "settings-service", + "value": "settings-service" + }, + { + "$$hashKey": "object:3976", + "selected": true, + "text": "user-service", + "value": "user-service" + } + ], + "query": "broadcast-service, discovery-service, history-service, settings-service, user-service,", + "skipUrlSync": false, + "type": "custom" + } + ] + }, + "time": { + "from": "now-5m", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "HTTP APIs", + "uid": "yKqsrprWk", + "variables": { + "list": [] + }, + "version": 1 +} \ No newline at end of file diff --git a/docker-compose/monitoring/grafana/dashboards/landscape.json b/docker-compose/monitoring/grafana/dashboards/landscape.json new file mode 100644 index 00000000..3382c2d7 --- /dev/null +++ b/docker-compose/monitoring/grafana/dashboards/landscape.json @@ -0,0 +1,162 @@ +{ + "annotations": { + "list": [ + { + "$$hashKey": "object:36", + "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": 3, + "links": [], + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "description": "Amount of calls and duration of `InsertionRepositoryPart.insertIntoModel(...)`", + "fill": 2, + "fillGradient": 6, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 0 + }, + "hiddenSeries": false, + "id": 2, + "interval": "", + "legend": { + "alignAsTable": false, + "avg": true, + "current": false, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null as zero", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:85", + "alias": "Total Processed Records", + "yaxis": 2 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "idelta(explorviz_landscape_insertion_time_sum[$__interval])", + "interval": "", + "legendFormat": "Insertion Time", + "refId": "B" + }, + { + "expr": "idelta(explorviz_landscape_insertion_time_count[$__interval])", + "interval": "", + "legendFormat": "Total Processed Records", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Record Insertion", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:98", + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:99", + "format": "short", + "label": "", + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "schemaVersion": 22, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-5m", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "Landscape Processing", + "uid": "OW0-2l9Wz", + "variables": { + "list": [] + }, + "version": 2 +} \ No newline at end of file diff --git a/docker-compose/monitoring/grafana/dashboards/overview.json b/docker-compose/monitoring/grafana/dashboards/overview.json new file mode 100644 index 00000000..764c20d9 --- /dev/null +++ b/docker-compose/monitoring/grafana/dashboards/overview.json @@ -0,0 +1,431 @@ +{ + "annotations": { + "list": [ + { + "$$hashKey": "object:7", + "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, + "links": [], + "panels": [ + { + "cacheTimeout": null, + "columns": [ + { + "$$hashKey": "object:453", + "text": "Current", + "value": "current" + } + ], + "datasource": "Prometheus", + "description": "Displays which services are currently up and running", + "fontSize": "100%", + "gridPos": { + "h": 9, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 8, + "links": [], + "pageSize": null, + "pluginVersion": "6.7.1", + "repeat": null, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "$$hashKey": "object:86", + "alias": "Service", + "align": "right", + "colorMode": null, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "link": false, + "pattern": "Metric", + "preserveFormat": false, + "sanitize": false, + "thresholds": [ + "0", + "1" + ], + "type": "string", + "unit": "short" + }, + { + "$$hashKey": "object:1889", + "alias": "", + "align": "auto", + "colorMode": "value", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Current", + "thresholds": [ + "0.1", + "1" + ], + "type": "string", + "unit": "short", + "valueMaps": [ + { + "$$hashKey": "object:1973", + "text": "Up", + "value": "1" + }, + { + "$$hashKey": "object:1996", + "text": "Down", + "value": "0" + } + ] + } + ], + "targets": [ + { + "expr": "up{job=~\"broadcast-service|user-service|settings-service|analysis-service|history-service|landscape-service|discovery-service\"}", + "interval": "", + "legendFormat": "{{job}} ({{instance}})", + "refId": "A" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Up/Down", + "transform": "timeseries_aggregations", + "type": "table" + }, + { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "description": "Average amount of Requests inbound at a service per minute. ", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 18, + "x": 6, + "y": 0 + }, + "hiddenSeries": false, + "id": 2, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "hideEmpty": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 1, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(jetty_requests_total[$__interval]))", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "Total Inbound Requests", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Average HTTP-Requests per Minute", + "tooltip": { + "shared": true, + "sort": 1, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:62", + "decimals": null, + "format": "short", + "label": "Requests", + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "$$hashKey": "object:63", + "format": "short", + "label": "", + "logBase": 1, + "max": null, + "min": null, + "show": false + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 9 + }, + "hiddenSeries": false, + "id": 4, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pluginVersion": "6.7.1", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "100 - (avg by (instance) (irate(node_cpu_seconds_total{mode=\"idle\"}[$__interval])) * 100)", + "interval": "", + "legendFormat": "{{instance}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Host CPU Usage", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:342", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:343", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 9 + }, + "hiddenSeries": false, + "id": 6, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "node_memory_MemTotal_bytes - node_memory_MemFree_bytes", + "interval": "", + "legendFormat": "{{instance}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Host Memory Usage", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:526", + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:527", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": "", + "schemaVersion": 22, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-15m", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "Overview", + "uid": "wuUJ4FrZk", + "variables": { + "list": [] + }, + "version": 1 +} \ No newline at end of file diff --git a/docker-compose/monitoring/grafana/dashboards/resource-usage.json b/docker-compose/monitoring/grafana/dashboards/resource-usage.json new file mode 100644 index 00000000..fce3c097 --- /dev/null +++ b/docker-compose/monitoring/grafana/dashboards/resource-usage.json @@ -0,0 +1,229 @@ +{ + "annotations": { + "list": [ + { + "$$hashKey": "object:229", + "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, + "links": [], + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "description": "Amount of time (in %) of each service's process spent in \"user\" or \"system\". Relative to the CPU of the host a service runs on", + "fill": 2, + "fillGradient": 2, + "gridPos": { + "h": 11, + "w": 24, + "x": 0, + "y": 0 + }, + "hiddenSeries": false, + "id": 4, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": false, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null as zero", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "(irate(process_cpu_seconds_total{job=~\".*-service\"}[10s])) * 100", + "interval": "", + "legendFormat": "{{job}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "CPU Time per Service in %", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:224", + "format": "percent", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:225", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": false + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 2, + "gridPos": { + "h": 11, + "w": 24, + "x": 0, + "y": 11 + }, + "hiddenSeries": false, + "id": 2, + "interval": "", + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "sort": null, + "sortDesc": null, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null as zero", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "jvm_memory_bytes_used{area=\"heap\"}", + "interval": "", + "legendFormat": "{{job}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Heap Memory Used", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:294", + "format": "decbytes", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:295", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": false + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "schemaVersion": 22, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-5m", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Resource Usage", + "uid": "uqeFT_9Zk", + "variables": { + "list": [] + }, + "version": 2 +} \ No newline at end of file diff --git a/docker-compose/monitoring/grafana/provisioning/dashboards/default.yaml b/docker-compose/monitoring/grafana/provisioning/dashboards/default.yaml new file mode 100644 index 00000000..0090a285 --- /dev/null +++ b/docker-compose/monitoring/grafana/provisioning/dashboards/default.yaml @@ -0,0 +1,8 @@ +apiVersion: 1 + +providers: +- name: 'default' + folder: '' + type: file + options: + path: '/var/lib/grafana/dashboards' \ No newline at end of file diff --git a/docker-compose/monitoring/grafana/provisioning/datasources/prom.yaml b/docker-compose/monitoring/grafana/provisioning/datasources/prom.yaml new file mode 100644 index 00000000..df2b2e7d --- /dev/null +++ b/docker-compose/monitoring/grafana/provisioning/datasources/prom.yaml @@ -0,0 +1,10 @@ +apiVersion: 1 + +datasources: + - name: Prometheus + type: prometheus + access: proxy + url: http://localhost:9090 + is_default: true + version: 1 + diff --git a/docker-compose/monitoring/prometheus.yml b/docker-compose/monitoring/prometheus.yml new file mode 100644 index 00000000..246355ad --- /dev/null +++ b/docker-compose/monitoring/prometheus.yml @@ -0,0 +1,44 @@ +scrape_configs: + + # Scrape the Node Exporter every 5 seconds. + - job_name: 'node' + scrape_interval: 5s + static_configs: + - targets: ['localhost:9100'] + + # Scrape all ExplorViz services every 5 seconds. + - job_name: 'user-service' + scrape_interval: 5s + static_configs: + - targets: ['localhost:8082'] + + - job_name: 'analysis-service' + scrape_interval: 5s + static_configs: + - targets: ['localhost:1234'] + + - job_name: 'landscape-service' + scrape_interval: 5s + static_configs: + - targets: ['localhost:1235'] + + - job_name: 'settings-service' + scrape_interval: 5s + static_configs: + - targets: ['localhost:8087'] + + - job_name: 'history-service' + scrape_interval: 5s + static_configs: + - targets: ['localhost:8086'] + + - job_name: 'discovery-service' + scrape_interval: 5s + static_configs: + - targets: ['localhost:8083'] + + - job_name: 'broadcast-service' + scrape_interval: 5s + static_configs: + - targets: ['localhost:8081'] + \ No newline at end of file diff --git a/docker-compose/traefik/traefik-static.toml b/docker-compose/traefik/traefik-static.toml index 0218fc17..f34b980b 100644 --- a/docker-compose/traefik/traefik-static.toml +++ b/docker-compose/traefik/traefik-static.toml @@ -7,4 +7,11 @@ [api] insecure = true - dashboard = true \ No newline at end of file + dashboard = true + +[entryPoints] + [entryPoints.web] + address = ":8080" + + [entryPoints.traefik] + address=":8181" \ No newline at end of file diff --git a/history-service/build.gradle b/history-service/build.gradle index f8ee8c6c..20c9d52f 100644 --- a/history-service/build.gradle +++ b/history-service/build.gradle @@ -1,13 +1,13 @@ plugins { - id "application" - id "com.github.johnrengelman.shadow" version "5.1.0" - id "com.github.spotbugs" version "1.6.10" - id "maven" + id "application" + id "com.github.johnrengelman.shadow" version "5.1.0" + id "com.github.spotbugs" version "1.6.10" + id "maven" } // Force new Dependencies configurations.all { - resolutionStrategy.cacheChangingModulesFor 0, 'seconds' + resolutionStrategy.cacheChangingModulesFor 0, 'seconds' } repositories { @@ -27,34 +27,44 @@ dockerCompose { dependencies { - // Swagger - implementation group: 'io.swagger.core.v3', name: 'swagger-core', version: '2.0.8' - implementation group: 'io.swagger.core.v3', name: 'swagger-jaxrs2', version: '2.0.8' - implementation project(':landscape-service:landscape-model') - implementation project(':user-service:user-model') + // Swagger + implementation group: 'io.swagger.core.v3', name: 'swagger-core', version: '2.0.8' + implementation group: 'io.swagger.core.v3', name: 'swagger-jaxrs2', version: '2.0.8' + implementation project(':landscape-service:landscape-model') + implementation project(':user-service:user-model') // ExplorViz Shared Dependencies - if(useRemoteSharedProject.toBoolean()) { - implementation group: 'net.explorviz', name: 'config-injection', version: "${sharedProjectVersion}" - implementation group: 'net.explorviz', name: 'security', version: "${sharedProjectVersion}" - implementation group: 'net.explorviz', name: 'exception-handling', version: "${sharedProjectVersion}" - implementation group: 'net.explorviz', name: 'common-concerns', version: "${sharedProjectVersion}" - implementation group: 'net.explorviz', name: 'query', version: "${sharedProjectVersion}" - } - else { + if (useRemoteSharedProject.toBoolean()) { + implementation group: 'net.explorviz', name: 'config-injection', version: "${sharedProjectVersion}" + implementation group: 'net.explorviz', name: 'security', version: "${sharedProjectVersion}" + implementation group: 'net.explorviz', name: 'exception-handling', version: "${sharedProjectVersion}" + implementation group: 'net.explorviz', name: 'common-concerns', version: "${sharedProjectVersion}" + implementation group: 'net.explorviz', name: 'query', version: "${sharedProjectVersion}" + } else { implementation project(':config-injection') implementation project(':security') implementation project(':exception-handling') implementation project(':common-concerns') implementation project(':query') - } - - // multipart/form-data type - implementation group: 'org.glassfish.jersey.media', name:'jersey-media-multipart', version:'2.26' - - implementation group: 'commons-io', name: 'commons-io', version: '2.6' - - implementation group: 'org.apache.kafka', name: 'kafka_2.12', version: '2.2.0' + } + + + // Prometheus + // Base + implementation group: 'io.prometheus', name: 'simpleclient', version: '0.8.1' + // Expose via Jetty + implementation group: 'io.prometheus', name: 'simpleclient_servlet', version: '0.8.1' + // Collect Jetty stats + implementation group: 'io.prometheus', name: 'simpleclient_jetty', version: '0.8.1' + // JVM Metrics + implementation group: 'io.prometheus', name: 'simpleclient_hotspot', version: '0.8.1' + + // multipart/form-data type + implementation group: 'org.glassfish.jersey.media', name: 'jersey-media-multipart', version: '2.26' + + implementation group: 'commons-io', name: 'commons-io', version: '2.6' + + implementation group: 'org.apache.kafka', name: 'kafka_2.12', version: '2.2.0' } assemble.dependsOn shadowJar @@ -63,13 +73,13 @@ jar.enabled = false jar { manifest { attributes( - 'Main-Class': 'net.explorviz.history.server.main.Main' + 'Main-Class': 'net.explorviz.history.server.main.Main' ) } } application { - mainClassName = 'net.explorviz.history.server.main.Main' + mainClassName = 'net.explorviz.history.server.main.Main' } shadowJar { @@ -98,7 +108,7 @@ tasks.withType(com.github.spotbugs.SpotBugsTask) { task spotbugs { group 'Quality Assurance' description 'Run SpotBugs' - + dependsOn 'spotbugsMain' dependsOn 'spotbugsTest' } @@ -106,29 +116,29 @@ task spotbugs { // START environment task task updateEnvPropsInBuildDir { - // run with: - // .././gradlew run -Penvironment=production - - doLast { - if (project.hasProperty("environment")) { - def environment = project.findProperty("environment") - - if(environment.equals("production")) { - println("Using the production properties file") - delete("${buildDir}/resources/main/explorviz.properties") - - copy { - from "${buildDir}/resources/main" - into "${buildDir}/resources/main" - rename "explorviz.production.properties", "explorviz.properties" - } - - delete("${buildDir}/resources/main/explorviz.production.properties") - } - } else { - println("Using default properties file") - } - } + // run with: + // .././gradlew run -Penvironment=production + + doLast { + if (project.hasProperty("environment")) { + def environment = project.findProperty("environment") + + if (environment.equals("production")) { + println("Using the production properties file") + delete("${buildDir}/resources/main/explorviz.properties") + + copy { + from "${buildDir}/resources/main" + into "${buildDir}/resources/main" + rename "explorviz.production.properties", "explorviz.properties" + } + + delete("${buildDir}/resources/main/explorviz.production.properties") + } + } else { + println("Using default properties file") + } + } } run.dependsOn updateEnvPropsInBuildDir diff --git a/history-service/src/main/java/net/explorviz/history/server/main/Main.java b/history-service/src/main/java/net/explorviz/history/server/main/Main.java index d828362d..d8b562ea 100644 --- a/history-service/src/main/java/net/explorviz/history/server/main/Main.java +++ b/history-service/src/main/java/net/explorviz/history/server/main/Main.java @@ -1,7 +1,13 @@ package net.explorviz.history.server.main; +import io.prometheus.client.exporter.MetricsServlet; +import io.prometheus.client.filter.MetricsFilter; +import io.prometheus.client.hotspot.DefaultExports; +import io.prometheus.client.jetty.JettyStatisticsCollector; import net.explorviz.shared.config.helper.PropertyHelper; import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.StatisticsHandler; +import org.eclipse.jetty.servlet.FilterHolder; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.glassfish.jersey.server.ResourceConfig; @@ -9,6 +15,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.servlet.DispatcherType; +import java.util.EnumSet; + /** * Entry point for the web service. This main method will start a web server based on the * configuration properties inside of the explorviz.properties file. @@ -36,6 +45,25 @@ public static void main(final String[] args) { final ServletContextHandler context = new ServletContextHandler(server, getContextPath()); context.addServlet(jerseyServlet, "/*"); + // Prometheus + StatisticsHandler stats = new StatisticsHandler(); + stats.setHandler(server.getHandler()); + server.setHandler(stats); + + new JettyStatisticsCollector(stats).register(); + + context.addServlet(new ServletHolder(new MetricsServlet()), "/metrics"); + + MetricsFilter metricsFilter = + new MetricsFilter("explorviz_request_time", + "Filter to measure and forward request times", 2, null); + FilterHolder filterHolder = new FilterHolder(metricsFilter); + + context.addFilter(filterHolder, "/v1/*", EnumSet.of(DispatcherType.REQUEST)); + + // JVM Metrics + DefaultExports.initialize(); + try { server.start(); } catch (final Exception e) { // NOPMD diff --git a/kiekeradapter/build.gradle b/kiekeradapter/build.gradle index a806d5c5..f24b412f 100644 --- a/kiekeradapter/build.gradle +++ b/kiekeradapter/build.gradle @@ -18,6 +18,8 @@ dependencies { // Kieker Monitoring Framework implementation group: 'net.kieker-monitoring', name: 'kieker', version: '1.14-SNAPSHOT' + + } // disable integration tests, since there are none @@ -76,4 +78,4 @@ task spotbugs { dependsOn 'spotbugsMain' dependsOn 'spotbugsTest' -} \ No newline at end of file +} diff --git a/landscape-service/build.gradle b/landscape-service/build.gradle index 497868fb..37e16863 100644 --- a/landscape-service/build.gradle +++ b/landscape-service/build.gradle @@ -51,6 +51,15 @@ dependencies { implementation project(':landscape-service:landscape-model') implementation group: 'com.github.jasminb', name: 'jsonapi-converter', version:'0.8' + + // Prometheus + // Base + implementation group: 'io.prometheus', name: 'simpleclient', version: '0.8.1' + // Exposition server + implementation group: 'io.prometheus', name: 'simpleclient_httpserver', version: '0.8.1' + // JVM Metrics + implementation group: 'io.prometheus', name: 'simpleclient_hotspot', version: '0.8.1' + } // disable api tests, since there are none diff --git a/landscape-service/src/main/java/net/explorviz/landscape/repository/InsertionRepositoryPart.java b/landscape-service/src/main/java/net/explorviz/landscape/repository/InsertionRepositoryPart.java index 624587a6..960e3355 100644 --- a/landscape-service/src/main/java/net/explorviz/landscape/repository/InsertionRepositoryPart.java +++ b/landscape-service/src/main/java/net/explorviz/landscape/repository/InsertionRepositoryPart.java @@ -18,6 +18,9 @@ import java.util.List; import java.util.Map; import java.util.Stack; + +import io.prometheus.client.Gauge; +import io.prometheus.client.Summary; import net.explorviz.landscape.model.application.Application; import net.explorviz.landscape.model.application.Clazz; import net.explorviz.landscape.model.application.Component; @@ -38,8 +41,17 @@ */ public class InsertionRepositoryPart { + + private static final Summary TIMER = Summary.build() + .name("explorviz_landscape_insertion_time") + .help("Time it took to insert a new record") + .register(); + private static final String DEFAULT_COMPONENT_NAME = "(default)"; + + private static long c = 0; + private final Map nodeCache = new HashMap<>(); private final Map applicationCache = new HashMap<>(); private final Map> clazzCache = new HashMap<>(); @@ -61,12 +73,15 @@ public InsertionRepositoryPart(final IdGenerator idGen) { public void insertIntoModel(final IRecord inputIRecord, final Landscape landscape, final RemoteCallRepositoryPart remoteCallRepositoryPart) { + Summary.Timer insertionTimer = TIMER.startTimer(); + if (inputIRecord instanceof Trace) { final Trace trace = (Trace) inputIRecord; final List hostApplicationMetadataList = trace.getTraceEvents().get(0).getHostApplicationMetadataList(); + synchronized (landscape) { for (int i = 0; i < hostApplicationMetadataList.size(); i++) { final HostApplicationMetaDataRecord hostApplicationRecord = @@ -128,7 +143,6 @@ public void insertIntoModel(final IRecord inputIRecord, final Landscape landscap } } else if (inputIRecord instanceof SystemMonitoringRecord) { final SystemMonitoringRecord systemMonitoringRecord = (SystemMonitoringRecord) inputIRecord; - for (final Node node : this.nodeCache.values()) { if (node.getName() .equalsIgnoreCase(systemMonitoringRecord.getHostApplicationMetadata().getHostname()) @@ -142,8 +156,11 @@ public void insertIntoModel(final IRecord inputIRecord, final Landscape landscap node.setUsedRAM(systemMonitoringRecord.getUsedRAM()); } } + } + insertionTimer.observeDuration(); + insertionTimer.close(); } /** diff --git a/landscape-service/src/main/java/net/explorviz/landscape/server/main/Main.java b/landscape-service/src/main/java/net/explorviz/landscape/server/main/Main.java index 22839d89..b9d9ced4 100644 --- a/landscape-service/src/main/java/net/explorviz/landscape/server/main/Main.java +++ b/landscape-service/src/main/java/net/explorviz/landscape/server/main/Main.java @@ -1,15 +1,24 @@ package net.explorviz.landscape.server.main; +import io.prometheus.client.exporter.HTTPServer; +import io.prometheus.client.hotspot.DefaultExports; import net.explorviz.landscape.model.helper.TypeProvider; import net.explorviz.shared.common.provider.GenericTypeFinder; import org.glassfish.hk2.api.ServiceLocator; import org.glassfish.hk2.utilities.ServiceLocatorUtilities; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; /** * Starts the Java application. */ public final class Main { + private static final Logger LOGGER = LoggerFactory.getLogger(Main.class); + private static final int PROM_PORT = 1235; + private Main() { // no instantiation } @@ -20,6 +29,17 @@ private Main() { * */ public static void main(final String[] args) { + + try { + // Starts the server + new HTTPServer(PROM_PORT); + // JVM Metrics + DefaultExports.initialize(); + LOGGER.info("Started prometheus server on port " + PROM_PORT); + } catch (IOException e) { + LOGGER.warn("Failed to start prometheus HTTP server", e); + } + // register landscape model classes // this must happen before initializing the ServiceLocator TypeProvider.getExplorVizCoreTypesAsMap().forEach((classname, classRef) -> { diff --git a/settings-service/build.gradle b/settings-service/build.gradle index cc516388..f5a82b47 100644 --- a/settings-service/build.gradle +++ b/settings-service/build.gradle @@ -79,6 +79,15 @@ dependencies { implementation project(':query') } + // Prometheus + // Base + implementation group: 'io.prometheus', name: 'simpleclient', version: '0.8.1' + // Expose via Jetty + implementation group: 'io.prometheus', name: 'simpleclient_servlet', version: '0.8.1' + // Collect Jetty stats + implementation group: 'io.prometheus', name: 'simpleclient_jetty', version: '0.8.1' + // JVM Metrics + implementation group: 'io.prometheus', name: 'simpleclient_hotspot', version: '0.8.1' apiTestImplementation project(':user-service') // For login apiTestImplementation 'io.rest-assured:rest-assured:4.0.0' diff --git a/settings-service/src/main/java/net/explorviz/settings/server/main/Main.java b/settings-service/src/main/java/net/explorviz/settings/server/main/Main.java index 0b44163d..cbabe993 100644 --- a/settings-service/src/main/java/net/explorviz/settings/server/main/Main.java +++ b/settings-service/src/main/java/net/explorviz/settings/server/main/Main.java @@ -1,7 +1,13 @@ package net.explorviz.settings.server.main; +import io.prometheus.client.exporter.MetricsServlet; +import io.prometheus.client.filter.MetricsFilter; +import io.prometheus.client.hotspot.DefaultExports; +import io.prometheus.client.jetty.JettyStatisticsCollector; import net.explorviz.shared.config.helper.PropertyHelper; import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.StatisticsHandler; +import org.eclipse.jetty.servlet.FilterHolder; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.glassfish.jersey.server.ResourceConfig; @@ -9,6 +15,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.servlet.DispatcherType; +import java.util.EnumSet; + public class Main { private static final Logger LOGGER = LoggerFactory.getLogger(Main.class); @@ -32,6 +41,23 @@ public static void main(final String[] args) { final ServletContextHandler context = new ServletContextHandler(server, getContextPath()); context.addServlet(jerseyServlet, "/*"); + // Prometheus + StatisticsHandler stats = new StatisticsHandler(); + stats.setHandler(server.getHandler()); + server.setHandler(stats); + + new JettyStatisticsCollector(stats).register(); + + context.addServlet(new ServletHolder(new MetricsServlet()), "/metrics"); + + MetricsFilter metricsFilter = + new MetricsFilter("explorviz_request_time", + "Filter to measure and forward request times", 2, null); + FilterHolder filterHolder = new FilterHolder(metricsFilter); + context.addFilter(filterHolder, "/v1/*", EnumSet.of(DispatcherType.REQUEST)); + + DefaultExports.initialize(); + try { server.start(); } catch (final Exception e) { // NOPMD diff --git a/user-service/build.gradle b/user-service/build.gradle index 5046c889..b6290111 100644 --- a/user-service/build.gradle +++ b/user-service/build.gradle @@ -80,6 +80,20 @@ dependencies { implementation group: 'io.swagger.core.v3', name: 'swagger-core', version: '2.0.8' implementation group: 'io.swagger.core.v3', name: 'swagger-jaxrs2', version: '2.0.8' + + // Prometheus + // Base + implementation group: 'io.prometheus', name: 'simpleclient', version: '0.8.1' + //implementation group: 'io.prometheus', name: 'simpleclient_httpserver', version: '0.8.1' + // Expose via Jetty + implementation group: 'io.prometheus', name: 'simpleclient_servlet', version: '0.8.1' + // Collect Jetty stats + implementation group: 'io.prometheus', name: 'simpleclient_jetty', version: '0.8.1' + // JVM Metrics + implementation group: 'io.prometheus', name: 'simpleclient_hotspot', version: '0.8.1' + + + apiTestImplementation 'io.rest-assured:rest-assured:4.0.0' } diff --git a/user-service/src/main/java/net/explorviz/security/server/main/Main.java b/user-service/src/main/java/net/explorviz/security/server/main/Main.java index 3da3c7f9..a76d9610 100644 --- a/user-service/src/main/java/net/explorviz/security/server/main/Main.java +++ b/user-service/src/main/java/net/explorviz/security/server/main/Main.java @@ -1,7 +1,13 @@ package net.explorviz.security.server.main; +import io.prometheus.client.exporter.MetricsServlet; +import io.prometheus.client.filter.MetricsFilter; +import io.prometheus.client.hotspot.DefaultExports; +import io.prometheus.client.jetty.JettyStatisticsCollector; import net.explorviz.shared.config.helper.PropertyHelper; import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.StatisticsHandler; +import org.eclipse.jetty.servlet.FilterHolder; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.glassfish.jersey.server.ResourceConfig; @@ -9,6 +15,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.servlet.DispatcherType; +import java.util.EnumSet; + /** * Entry point for the web service. This main method will start a web server based on the * configuration properties inside of the explorviz.properties file @@ -36,6 +45,27 @@ public static void main(final String[] args) { final ServletContextHandler context = new ServletContextHandler(server, getContextPath()); context.addServlet(jerseyServlet, "/*"); + + + // Prometheus + StatisticsHandler stats = new StatisticsHandler(); + stats.setHandler(server.getHandler()); + server.setHandler(stats); + + new JettyStatisticsCollector(stats).register(); + + context.addServlet(new ServletHolder(new MetricsServlet()), "/metrics"); + + // JVM Metrics + DefaultExports.initialize(); + + MetricsFilter metricsFilter = + new MetricsFilter("explorviz_request_time", + "Filter to measure and forward request times", 2, null); + FilterHolder filterHolder = new FilterHolder(metricsFilter); + context.addFilter(filterHolder, "/v1/*", EnumSet.of(DispatcherType.REQUEST)); + + try { server.start(); } catch (final Exception e) { // NOPMD @@ -79,4 +109,6 @@ private static ResourceConfig createJaxRsApp() { return new ResourceConfig(new Application()); } + + }