From a8e9f3c0bcda6e4be2ecdf0b9f07e1b1a2a7904c Mon Sep 17 00:00:00 2001 From: rochala Date: Tue, 23 Sep 2025 21:56:19 +0200 Subject: [PATCH 1/6] Profiling experiments --- build.mill | 38 +- otel4s/docker-compose.yaml | 56 + otel4s/grafana/dashboards.yaml | 24 + otel4s/grafana/dashboards/cats-metrics.json | 2700 +++++++++++++++++ otel4s/grafana/dashboards/jvm-metrics.json | 0 otel4s/grafana/dashboards/tracing.json | 151 + otel4s/grafana/datasources.yaml | 39 + .../opentelemetry/otel-collector-config.yaml | 33 + otel4s/prometheus/prometheus.yaml | 28 + otel4s/tempo/tempo.yaml | 30 + .../abusers/profiling/runtime/LocalSpan.scala | 25 + .../runtime/ProfilingExecutionContext.scala | 34 + .../profiling/runtime/ProfilingIOApp.scala | 86 + .../runtime/ProfilingSpanProcessor.scala | 32 + .../scala/abusers/sls/ComputationQueue.scala | 3 +- .../org/scala/abusers/sls/ServerImpl.scala | 41 +- .../abusers/sls/SimpleLanguageServer.scala | 38 +- 17 files changed, 3327 insertions(+), 31 deletions(-) create mode 100644 otel4s/docker-compose.yaml create mode 100644 otel4s/grafana/dashboards.yaml create mode 100644 otel4s/grafana/dashboards/cats-metrics.json create mode 100644 otel4s/grafana/dashboards/jvm-metrics.json create mode 100644 otel4s/grafana/dashboards/tracing.json create mode 100644 otel4s/grafana/datasources.yaml create mode 100644 otel4s/opentelemetry/otel-collector-config.yaml create mode 100644 otel4s/prometheus/prometheus.yaml create mode 100644 otel4s/tempo/tempo.yaml create mode 100644 profilingRuntime/src/org/scala/abusers/profiling/runtime/LocalSpan.scala create mode 100644 profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingExecutionContext.scala create mode 100644 profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingIOApp.scala create mode 100644 profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingSpanProcessor.scala diff --git a/build.mill b/build.mill index a6d85b9..058ed9f 100644 --- a/build.mill +++ b/build.mill @@ -1,3 +1,7 @@ +import mill.scalalib.publish.Developer +import mill.scalalib.publish.VersionControl +import mill.scalalib.publish.License +import mill.scalalib.publish.PomSettings import mill.eval.Evaluator import smithy4s.codegen.LSP import com.goyeau.mill.scalafix.ScalafixModule @@ -37,14 +41,37 @@ object slsSmithy extends CommonScalaModule with Smithy4sModule { ) } +object profilingRuntime extends CommonScalaModule with PublishModule { + def ivyDeps = Agg( + ivy"io.pyroscope:agent:2.1.2", + ivy"org.typelevel::cats-effect:3.6.3", + ivy"org.typelevel::otel4s-sdk:0.13.1", + ivy"org.typelevel::otel4s-sdk-exporter:0.13.1", + ivy"org.typelevel::otel4s-experimental-metrics:0.7.0", + ) + + def pomSettings: T[PomSettings] = PomSettings( + description = "A runtime with pyroscope profiling and OpenTelemetry tracing", + url = "", + licenses = Seq(License.`MPL-2.0`), + organization = "org.scala.abusers", + versionControl = VersionControl.github("simple-scala-tooling", "simple-language-server"), + developers = Seq( + Developer("rochala", "Jędrzej Rochala", url = "https://github.com/rochala") + ) + ) + + def publishVersion: T[String] = "0.0.1-SNAPSHOT" +} + object sls extends CommonScalaModule { def mainClass = Some("org.scala.abusers.sls.SimpleScalaServer") - def moduleDeps: Seq[JavaModule] = Seq(slsSmithy) + def moduleDeps: Seq[JavaModule] = Seq(slsSmithy, profilingRuntime) def ivyDeps = Agg( - ivy"org.typelevel::cats-effect:3.6.2", + ivy"org.typelevel::cats-effect:3.6.3", ivy"co.fs2::fs2-io:3.13.0-M2", ivy"io.scalaland::chimney:1.8.1", ivy"io.scalaland::chimney-java-collections:1.8.1", @@ -55,7 +82,12 @@ object sls extends CommonScalaModule { ivy"org.scalameta:mtags-interfaces:1.5.1", ivy"com.evolution::scache:5.1.2", ivy"org.typelevel::cats-parse:1.1.0", - ivy"io.get-coursier:interface:1.0.28" + ivy"io.get-coursier:interface:1.0.28", + ) + + def javacOptions = Seq( + "-Dotel.service.name=simple-language-server", + "-Dcats.effect.trackFiberContext=true" ) object test extends ScalaTests { diff --git a/otel4s/docker-compose.yaml b/otel4s/docker-compose.yaml new file mode 100644 index 0000000..8e75da2 --- /dev/null +++ b/otel4s/docker-compose.yaml @@ -0,0 +1,56 @@ +services: + otel-collector: + image: otel/opentelemetry-collector-contrib + command: [--config=/etc/otel-collector-config.yaml] + volumes: + - "./opentelemetry/otel-collector-config.yaml:/etc/otel-collector-config.yaml" + ports: + - "8888:8888" # Prometheus metrics exposed by the collector + - "8889:8889" # Prometheus exporter metrics + - "4317:4317" # OTLP gRPC receiver + - "4318:4318" # OTLP http receiver + networks: + - static-network + + tempo: + image: grafana/tempo + command: [ "-config.file=/etc/tempo.yaml" ] + volumes: + - "./tempo/tempo.yaml:/etc/tempo.yaml" + ports: + - "3200:3200" + - "4317" + - "4318" + networks: + - static-network + + prometheus: + image: prom/prometheus:latest + volumes: + - "./prometheus/prometheus.yaml:/etc/prometheus/prometheus.yml" + ports: + - "9090:9090" + networks: + - static-network + + pyroscope: + image: grafana/pyroscope + ports: + - "4040:4040" + networks: + - static-network + + grafana: + image: grafana/grafana + restart: unless-stopped + volumes: + - "./grafana/datasources.yaml:/etc/grafana/provisioning/datasources/datasources.yaml" + - "./grafana/dashboards.yaml:/etc/grafana/provisioning/dashboards/dashboards.yaml" + - "./grafana/dashboards/:/var/lib/grafana/dashboards/" + ports: + - "3000:3000" + networks: + - static-network + +networks: + static-network: diff --git a/otel4s/grafana/dashboards.yaml b/otel4s/grafana/dashboards.yaml new file mode 100644 index 0000000..40289c1 --- /dev/null +++ b/otel4s/grafana/dashboards.yaml @@ -0,0 +1,24 @@ +apiVersion: 1 + +providers: + # an unique provider name. Required + - name: 'dashboards' + # Org id. Default to 1 + orgId: 1 + # name of the dashboard folder. + folder: '' + # folder UID. will be automatically generated if not specified + folderUid: '' + # provider type. Default to 'file' + type: file + # disable dashboard deletion + disableDeletion: false + # how often Grafana will scan for changed dashboards + updateIntervalSeconds: 10 + # allow updating provisioned dashboards from the UI + allowUiUpdates: true + options: + # path to dashboard files on disk. Required when using the 'file' type + path: /var/lib/grafana/dashboards + # use folder names from filesystem to create folders in Grafana + foldersFromFilesStructure: true diff --git a/otel4s/grafana/dashboards/cats-metrics.json b/otel4s/grafana/dashboards/cats-metrics.json new file mode 100644 index 0000000..66b4d79 --- /dev/null +++ b/otel4s/grafana/dashboards/cats-metrics.json @@ -0,0 +1,2700 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "A dashboard to visualize Cats Effect runtime metrics. ", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "id": 2, + "links": [], + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 8, + "panels": [], + "title": "CPU starvation", + "type": "row" + }, + { + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 7, + "x": 0, + "y": 1 + }, + "id": 13, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "The [starvation checker](https://typelevel.org/cats-effect/docs/core/starvation-and-tuning) is a runtime assertion that any latency higher than the threshold (by default, 100 milliseconds) is bad and deserves a warning. The common causes:\n- Blocking tasks\n- Compute-bound work\n- Not enough CPUs\n- Too many threads\n- Burst credit contention\n- Process contention\n\n", + "mode": "markdown" + }, + "pluginVersion": "12.1.1", + "title": "Overview", + "type": "text" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "description": "The current clock drift", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 0, + "fieldMinMax": false, + "mappings": [], + "max": 100, + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "light-orange", + "value": 10 + }, + { + "color": "red", + "value": 50 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 4, + "x": 7, + "y": 1 + }, + "id": 21, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^current$/", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "f13f44c8-69b1-48ae-a55c-90f49179283c" + }, + "editorMode": "code", + "exemplar": false, + "expr": "cats_effect_runtime_cpu_starvation_clock_drift_current_milliseconds{job=\"${job:value}\"}", + "hide": false, + "instant": true, + "legendFormat": "current", + "range": false, + "refId": "A" + } + ], + "title": "Clock drift [current]", + "transformations": [ + { + "id": "configFromData", + "options": { + "configRefId": "B", + "mappings": [ + { + "fieldName": "total", + "handlerKey": "max" + } + ] + } + } + ], + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "description": "The current number of time CPU starvation has occurred", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 11, + "y": 1 + }, + "id": 9, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "f13f44c8-69b1-48ae-a55c-90f49179283c" + }, + "editorMode": "code", + "expr": "increase(cats_effect_runtime_cpu_starvation_count_total{job=\"${job:value}\"}[$range])", + "instant": false, + "legendFormat": "Starvations", + "range": true, + "refId": "A" + } + ], + "title": "Starvations [$range]", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "description": "The current (last) and max (since the launch) observed clock drift in milliseconds", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 7, + "x": 17, + "y": 1 + }, + "id": 10, + "options": { + "legend": { + "calcs": [ + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "f13f44c8-69b1-48ae-a55c-90f49179283c" + }, + "editorMode": "code", + "exemplar": false, + "expr": "cats_effect_runtime_cpu_starvation_clock_drift_current_milliseconds{job=\"${job:value}\"}", + "instant": false, + "legendFormat": "Current", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "cats_effect_runtime_cpu_starvation_clock_drift_max_milliseconds{job=\"${job:value}\"}", + "hide": false, + "instant": false, + "legendFormat": "Max", + "range": true, + "refId": "B" + } + ], + "title": "Clock drift", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 9 + }, + "id": 2, + "panels": [], + "title": "Work-Stealing Thread Pool | Compute", + "type": "row" + }, + { + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 10 + }, + "id": 7, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "This dashboard evaluates the utilization of compute threads in the [Cats Effect](https://github.com/typelevel/cats-effect) runtime:\n- Thread pool utilization - monitor the utilization of the worker threads\n- Blocking and searching threads - identify bottlenecks by tracking blocked and searching threads\n- Fiber management - understand fiber enqueue trends\n", + "mode": "markdown" + }, + "pluginVersion": "12.1.1", + "title": "Overview", + "type": "text" + }, + { + "datasource": { + "type": "prometheus", + "uid": "f13f44c8-69b1-48ae-a55c-90f49179283c" + }, + "description": "The number of worker thread instances backing the work-stealing thread pool (WSTP)", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "light-blue", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 4, + "x": 12, + "y": 10 + }, + "id": 1, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "cats_effect_runtime_wstp_compute_thread_count{job=\"${job:value}\", pool_id=\"${wstp_pool:value}\"}", + "format": "time_series", + "instant": true, + "legendFormat": "__auto", + "range": false, + "refId": "A" + } + ], + "title": "Worker threads", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "f13f44c8-69b1-48ae-a55c-90f49179283c" + }, + "description": "The number of active worker thread instances currently executing fibers on the compute thread pool", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 4, + "x": 16, + "y": 10 + }, + "id": 19, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "cats_effect_runtime_wstp_compute_thread_active_count{job=\"${job:value}\", pool_id=\"${wstp_pool:value}\"}", + "format": "table", + "instant": true, + "legendFormat": "active", + "range": false, + "refId": "A" + } + ], + "title": "Active threads", + "transformations": [ + { + "id": "configFromData", + "options": { + "configRefId": "B", + "mappings": [ + { + "fieldName": "total", + "handlerKey": "max" + } + ] + } + } + ], + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "f13f44c8-69b1-48ae-a55c-90f49179283c" + }, + "description": "The number of worker thread instances that can run blocking actions on the compute thread pool", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 4, + "x": 20, + "y": 10 + }, + "id": 20, + "options": { + "colorMode": "none", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^blocked$/", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "cats_effect_runtime_wstp_compute_thread_blocked_count{job=\"${job:value}\", pool_id=\"${wstp_pool:value}\"}", + "format": "time_series", + "instant": true, + "legendFormat": "blocked", + "range": false, + "refId": "A" + } + ], + "title": "Blocking threads", + "transformations": [ + { + "id": "configFromData", + "options": { + "configRefId": "B", + "mappings": [ + { + "fieldName": "total", + "handlerKey": "max" + } + ] + } + } + ], + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "f13f44c8-69b1-48ae-a55c-90f49179283c" + }, + "description": "The number of active worker thread instances currently executing fibers on the compute thread pool", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 18 + }, + "id": 3, + "options": { + "legend": { + "calcs": [ + "min", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "editorMode": "code", + "expr": "sum(cats_effect_runtime_wstp_compute_thread_active_count{job=\"${job:value}\", pool_id=\"${wstp_pool:value}\"})", + "instant": false, + "legendFormat": "Active", + "range": true, + "refId": "A" + } + ], + "title": "Active", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "f13f44c8-69b1-48ae-a55c-90f49179283c" + }, + "description": "The number of worker thread instances that can run blocking actions on the compute thread pool", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 18 + }, + "id": 5, + "options": { + "legend": { + "calcs": [ + "min", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "editorMode": "code", + "expr": "sum(cats_effect_runtime_wstp_compute_thread_blocked_count{job=\"${job:value}\", pool_id=\"${wstp_pool:value}\"})", + "instant": false, + "legendFormat": "Blocking", + "range": true, + "refId": "A" + } + ], + "title": "Blocking", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "f13f44c8-69b1-48ae-a55c-90f49179283c" + }, + "description": "The number of worker thread instances currently searching for fibers to steal from other worker threads", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 26 + }, + "id": 4, + "options": { + "legend": { + "calcs": [ + "min", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "editorMode": "code", + "expr": "sum(cats_effect_runtime_wstp_compute_thread_searching_count{job=\"${job:value}\", pool_id=\"${wstp_pool:value}\"})", + "instant": false, + "legendFormat": "Searching", + "range": true, + "refId": "A" + } + ], + "title": "Searching", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "f13f44c8-69b1-48ae-a55c-90f49179283c" + }, + "description": "The total number of fibers enqueued on all local queues", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 26 + }, + "id": 6, + "options": { + "legend": { + "calcs": [ + "min", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "editorMode": "code", + "expr": "sum(increase(cats_effect_runtime_wstp_compute_fiber_enqueued_count{job=\"${job:value}\", pool_id=\"${wstp_pool:value}\"}[$range]))", + "instant": false, + "legendFormat": "Enqueued", + "range": true, + "refId": "A" + } + ], + "title": "Enqueued fibers [$range]", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "f13f44c8-69b1-48ae-a55c-90f49179283c" + }, + "description": "The number of fibers which are currently asynchronously suspended", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 34 + }, + "id": 31, + "options": { + "legend": { + "calcs": [ + "min", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "editorMode": "code", + "expr": "sum(increase(cats_effect_runtime_wstp_compute_fiber_suspended_count{job=\"${job:value}\", pool_id=\"${wstp_pool:value}\"}[$range]))", + "legendFormat": "Suspended", + "range": true, + "refId": "A" + } + ], + "title": "Suspended fibers [$range]", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 42 + }, + "id": 22, + "panels": [], + "title": "Work-Stealing Thread Pool | Worker Thread", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "f13f44c8-69b1-48ae-a55c-90f49179283c" + }, + "description": "The total number of events that happend to the worker thread", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 11, + "w": 12, + "x": 0, + "y": 43 + }, + "id": 23, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true, + "sortBy": "Last *", + "sortDesc": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "editorMode": "code", + "expr": "sum (increase(cats_effect_runtime_wstp_worker_thread_event_count_total{job=\"${job:value}\", pool_id=\"${wstp_pool:value}\", worker_index=~\"${wstp_worker_index:regex}\"}[$range])) by (worker_index, thread_event)", + "legendFormat": "Worker {{ worker_index }} - {{ thread_event }}", + "range": true, + "refId": "A" + } + ], + "title": "Events per state [$range]", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "f13f44c8-69b1-48ae-a55c-90f49179283c" + }, + "description": "The total amount of time in nanoseconds that this worker thread has been parked", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ns" + }, + "overrides": [] + }, + "gridPos": { + "h": 11, + "w": 12, + "x": 12, + "y": 43 + }, + "id": 24, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true, + "sortBy": "Last *", + "sortDesc": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "editorMode": "code", + "expr": "sum (increase(cats_effect_runtime_wstp_worker_thread_idle_duration_nanoseconds_total{job=\"${job:value}\", pool_id=\"${wstp_pool:value}\", worker_index=~\"${wstp_worker_index:regex}\"}[$range])) by (worker_index)", + "legendFormat": "Worker {{ worker_index }}", + "range": true, + "refId": "A" + } + ], + "title": "Idle (parked) duration [$range]", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 54 + }, + "id": 11, + "panels": [], + "title": "Work-Stealing Thread Pool | Local Queue", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "f13f44c8-69b1-48ae-a55c-90f49179283c" + }, + "description": "The number of currently enqueued fibers", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 55 + }, + "id": 14, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "editorMode": "code", + "expr": "sum(cats_effect_runtime_wstp_worker_localqueue_fiber_enqueued_count{job=\"${job:value}\", pool_id=\"${wstp_pool:value}\", worker_index=~\"${wstp_worker_index:regex}\"}) by (worker_index)", + "instant": false, + "legendFormat": "Queue {{ worker_index }}", + "range": true, + "refId": "A" + } + ], + "title": "Enqueued fibers", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "f13f44c8-69b1-48ae-a55c-90f49179283c" + }, + "description": "The total number of fibers enqueued during the lifetime of the local queue", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 55 + }, + "id": 15, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "editorMode": "code", + "expr": "sum(increase(cats_effect_runtime_wstp_worker_localqueue_fiber_count_total{job=\"${job:value}\", pool_id=\"${wstp_pool:value}\", worker_index=~\"${wstp_worker_index:regex}\"}[$range])) by (worker_index)", + "instant": false, + "legendFormat": "Queue {{ worker_index }}", + "range": true, + "refId": "A" + } + ], + "title": "Total fibers [$range]", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "f13f44c8-69b1-48ae-a55c-90f49179283c" + }, + "description": "The total number of stolen fibers by other worker threads", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 65 + }, + "id": 18, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "editorMode": "code", + "expr": "sum(increase(cats_effect_runtime_wstp_worker_localqueue_fiber_count_total{job=\"${job:value}\", pool_id=\"${wstp_pool:value}\", worker_index=~\"${wstp_worker_index:regex}\"}[$range])) by (worker_index)", + "instant": false, + "legendFormat": "Queue {{ worker_index }}", + "range": true, + "refId": "A" + } + ], + "title": "Stolen fibers [$range]", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "f13f44c8-69b1-48ae-a55c-90f49179283c" + }, + "description": "The total number of successful steal attempts by other worker threads", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 65 + }, + "id": 17, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "editorMode": "code", + "expr": "sum(increase(cats_effect_runtime_wstp_worker_localqueue_fiber_steal_attempt_count_total{job=\"${job:value}\", pool_id=\"${wstp_pool:value}\", worker_index=~\"${wstp_worker_index:regex}\"}[$range])) by (worker_index)", + "instant": false, + "interval": "", + "legendFormat": "Queue {{ worker_index }}", + "range": true, + "refId": "A" + } + ], + "title": "Successful steal attempts [$range]", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "f13f44c8-69b1-48ae-a55c-90f49179283c" + }, + "description": "The total number of fibers spilled over to the external queue", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 74 + }, + "id": 16, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "editorMode": "code", + "expr": "sum(increase(cats_effect_runtime_wstp_worker_localqueue_fiber_spillover_count_total{job=\"${job:value}\", pool_id=\"${wstp_pool:value}\", worker_index=~\"${wstp_worker_index:regex}\"}[$range])) by (worker_index)", + "instant": false, + "legendFormat": "Queue {{ worker_index }}", + "range": true, + "refId": "A" + } + ], + "title": "Spilled over fibers [$range]", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 84 + }, + "id": 26, + "panels": [], + "title": "Work-Stealing Thread Pool | Timer Heap", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "f13f44c8-69b1-48ae-a55c-90f49179283c" + }, + "description": "The time in nanoseconds till the next due to fire", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ns" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 85 + }, + "id": 27, + "options": { + "legend": { + "calcs": [ + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "editorMode": "code", + "expr": "sum (cats_effect_runtime_wstp_worker_timerheap_next_due_nanoseconds{job=\"${job:value}\", pool_id=\"${wstp_pool:value}\", worker_index=~\"${wstp_worker_index:regex}\"}) by (worker_index)", + "legendFormat": "Timer {{worker_index}}", + "range": true, + "refId": "A" + } + ], + "title": "Next due", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "f13f44c8-69b1-48ae-a55c-90f49179283c" + }, + "description": "The current number of the outstanding timers that remain to be executed", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 85 + }, + "id": 28, + "options": { + "legend": { + "calcs": [ + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "editorMode": "code", + "expr": "sum (cats_effect_runtime_wstp_worker_timerheap_outstanding_count{job=\"${job:value}\", pool_id=\"${wstp_pool:value}\", worker_index=~\"${wstp_worker_index:regex}\"}) by (worker_index)", + "legendFormat": "Timer {{worker_index}}", + "range": true, + "refId": "A" + } + ], + "title": "Outstanding timers", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "f13f44c8-69b1-48ae-a55c-90f49179283c" + }, + "description": "The total number of timers per state", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 94 + }, + "id": 30, + "options": { + "legend": { + "calcs": [ + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true, + "sortBy": "Last *", + "sortDesc": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "editorMode": "code", + "expr": "sum (increase(cats_effect_runtime_wstp_worker_timerheap_timer_count_total{job=\"${job:value}\", pool_id=\"${wstp_pool:value}\", worker_index=~\"${wstp_worker_index:regex}\"}[$range])) by (worker_index, timer_state)", + "legendFormat": "Timer {{worker_index}} - {{ timer_state }}", + "range": true, + "refId": "A" + } + ], + "title": "Timers [$range]", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "f13f44c8-69b1-48ae-a55c-90f49179283c" + }, + "description": "The total number of times the heap packed itself to remove canceled timers", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 94 + }, + "id": 29, + "options": { + "legend": { + "calcs": [ + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "editorMode": "code", + "expr": "sum (increase(cats_effect_runtime_wstp_worker_timerheap_packed_count_total{job=\"${job:value}\", pool_id=\"${wstp_pool:value}\", worker_index=~\"${wstp_worker_index:regex}\"}[$range])) by (worker_index)", + "legendFormat": "Timer {{worker_index}}", + "range": true, + "refId": "A" + } + ], + "title": "Packed [$range]", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 103 + }, + "id": 32, + "panels": [], + "title": "Work-Stealing Thread Pool | Poller", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "f13f44c8-69b1-48ae-a55c-90f49179283c" + }, + "description": "The current number of outstanding operations per category", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 104 + }, + "id": 33, + "options": { + "legend": { + "calcs": [ + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true, + "sortBy": "Last *", + "sortDesc": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "editorMode": "code", + "expr": "sum by (worker_index, poller_operation) (cats_effect_runtime_wstp_worker_poller_operation_outstanding_count{job=\"${job:value}\", pool_id=\"${wstp_pool:value}\", worker_index=~\"${wstp_worker_index:regex}\"})", + "instant": false, + "legendFormat": "Poller {{ worker_index }} - {{ poller_operation }}", + "range": true, + "refId": "A" + } + ], + "title": "Outstanding operations", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "f13f44c8-69b1-48ae-a55c-90f49179283c" + }, + "description": "The total number of operations per category and outcome", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 104 + }, + "id": 36, + "options": { + "legend": { + "calcs": [ + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "editorMode": "code", + "expr": "sum by (worker_index, poller_operation, poller_opration_status) (increase(cats_effect_runtime_wstp_worker_poller_operation_count_total{job=\"${job:value}\", pool_id=\"${wstp_pool:value}\", worker_index=~\"${wstp_worker_index:regex}\"}[$range]))", + "instant": false, + "legendFormat": "Poller {{ worker_index }} - {{ poller_operation }}", + "range": true, + "refId": "A" + } + ], + "title": "Total operations [$range]", + "type": "timeseries" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 41, + "tags": [ + "cats-effect" + ], + "templating": { + "list": [ + { + "current": { + "text": "prometheus", + "value": "f13f44c8-69b1-48ae-a55c-90f49179283c" + }, + "description": "The Prometheus instance to use", + "label": "Data Source", + "name": "prometheus", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "", + "type": "datasource" + }, + { + "current": { + "text": "otel-collector", + "value": "otel-collector" + }, + "datasource": { + "type": "prometheus", + "uid": "f13f44c8-69b1-48ae-a55c-90f49179283c" + }, + "definition": "label_values(cats_effect_runtime_wstp_compute_thread_active_count,job)", + "includeAll": false, + "label": "Job", + "name": "job", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(cats_effect_runtime_wstp_compute_thread_active_count,job)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 2, + "regex": "", + "type": "query" + }, + { + "current": { + "text": "0", + "value": "0" + }, + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "definition": "label_values(cats_effect_runtime_wstp_compute_thread_count,pool_id)", + "label": "WSTP pool", + "name": "wstp_pool", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(cats_effect_runtime_wstp_compute_thread_count,pool_id)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "type": "query" + }, + { + "allValue": ".*", + "current": { + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": { + "type": "prometheus", + "uid": "f13f44c8-69b1-48ae-a55c-90f49179283c" + }, + "definition": "label_values(cats_effect_runtime_wstp_worker_thread_event_count_total,worker_index)", + "description": "Which worker to show", + "includeAll": true, + "label": "WSTP worker", + "multi": true, + "name": "wstp_worker_index", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(cats_effect_runtime_wstp_worker_thread_event_count_total,worker_index)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "type": "query" + }, + { + "current": { + "text": "10m", + "value": "10m" + }, + "label": "Range", + "name": "range", + "options": [ + { + "selected": false, + "text": "1m", + "value": "1m" + }, + { + "selected": false, + "text": "2m", + "value": "2m" + }, + { + "selected": false, + "text": "5m", + "value": "5m" + }, + { + "selected": true, + "text": "10m", + "value": "10m" + }, + { + "selected": false, + "text": "30m", + "value": "30m" + } + ], + "query": "1m, 2m, 5m, 10m, 30m", + "type": "custom" + } + ] + }, + "time": { + "from": "now-1m", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Cats Effect Runtime Metrics", + "uid": "fe8ds2gwa0tmof", + "version": 1 +} diff --git a/otel4s/grafana/dashboards/jvm-metrics.json b/otel4s/grafana/dashboards/jvm-metrics.json new file mode 100644 index 0000000..e69de29 diff --git a/otel4s/grafana/dashboards/tracing.json b/otel4s/grafana/dashboards/tracing.json new file mode 100644 index 0000000..2de3465 --- /dev/null +++ b/otel4s/grafana/dashboards/tracing.json @@ -0,0 +1,151 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 6, + "links": [], + "panels": [ + { + "datasource": { + "type": "jaeger", + "uid": "d4cbeedf-98f0-4d71-accc-2eef0a37506a" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 50, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "pointShape": "circle", + "pointSize": { + "fixed": 10 + }, + "pointStrokeWidth": 2, + "scaleDistribution": { + "type": "linear" + }, + "show": "points" + }, + "links": [ + { + "oneClick": false, + "targetBlank": true, + "title": "Show trace", + "url": "${__data.fields[0]} " + } + ], + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "µs" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "mapping": "auto", + "series": [ + {} + ], + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "jaeger", + "uid": "d4cbeedf-98f0-4d71-accc-2eef0a37506a" + }, + "queryType": "search", + "refId": "A", + "service": "simple-language-server" + } + ], + "title": "New Panel", + "transformations": [ + { + "id": "partitionByValues", + "options": { + "fields": [ + "Trace name" + ], + "keepFields": false, + "naming": { + "asLabels": true + } + } + } + ], + "type": "xychart" + } + ], + "preload": false, + "schemaVersion": 41, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "2025-09-09T19:35:48.405Z", + "to": "2025-09-09T21:07:41.621Z" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Tracing", + "uid": "99e5a2eb-54b5-44d3-a53f-98aa85caf61c", + "version": 3 +} diff --git a/otel4s/grafana/datasources.yaml b/otel4s/grafana/datasources.yaml new file mode 100644 index 0000000..60f8733 --- /dev/null +++ b/otel4s/grafana/datasources.yaml @@ -0,0 +1,39 @@ +# Configuration file version +apiVersion: 1 + +# List of data sources to insert/update depending on what's +# available in the database. +datasources: + # Sets the name you use to refer to + # the data source in panels and queries. + - id: 1 + uid: f13f44c8-69b1-48ae-a55c-90f49179283c + orgId: 1 + name: prometheus + type: prometheus + typeName: Prometheus + typeLogoUrl: "/public/app/plugins/datasource/prometheus/img/prometheus_logo.svg" + access: proxy + url: http://prometheus:9090 + user: '' + database: '' + basicAuth: false + isDefault: false + jsonData: + httpMethod: POST + readOnly: false + - id: 2 + uid: d4cbeedf-98f0-4d71-accc-2eef0a37506a + orgId: 1 + type: jaeger + name: Jaeger + typeName: Jaeger + typeLogoUrl: "/public/app/plugins/datasource/jaeger/img/jaeger_logo.svg" + access: proxy + url: http://jaeger:16686 + user: '' + database: '' + basicAuth: false + isDefault: false + jsonData: {} + readOnly: false diff --git a/otel4s/opentelemetry/otel-collector-config.yaml b/otel4s/opentelemetry/otel-collector-config.yaml new file mode 100644 index 0000000..f1dfa5d --- /dev/null +++ b/otel4s/opentelemetry/otel-collector-config.yaml @@ -0,0 +1,33 @@ +receivers: + otlp: + protocols: # enable OpenTelemetry HTTP Protocol receiver + http: + endpoint: 0.0.0.0:4318 + grpc: + endpoint: 0.0.0.0:4317 + +exporters: + otlp: + endpoint: tempo:4317 + tls: + insecure: true + + prometheus: + endpoint: 0.0.0.0:8889 + send_timestamps: true + +processors: + batch: + timeout: 10s + + +service: + pipelines: + traces: + receivers: [otlp] + processors: [batch] + exporters: [otlp] + metrics: + receivers: [otlp] + processors: [batch] + exporters: [prometheus] diff --git a/otel4s/prometheus/prometheus.yaml b/otel4s/prometheus/prometheus.yaml new file mode 100644 index 0000000..76c9090 --- /dev/null +++ b/otel4s/prometheus/prometheus.yaml @@ -0,0 +1,28 @@ +global: + scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute. + evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute. + # scrape_timeout is set to the global default (10s). + +# Alertmanager configuration +alerting: + alertmanagers: + - static_configs: + - targets: + # - alertmanager:9093 + +# Load rules once and periodically evaluate them according to the global 'evaluation_interval'. +rule_files: +# - "first_rules.yml" +# - "second_rules.yml" + +# A scrape configuration containing exactly one endpoint to scrape: +# Here it's Prometheus itself. +scrape_configs: + # The job name is added as a label `job=` to any timeseries scraped from this config. + - job_name: "prometheus" + static_configs: + - targets: ["localhost:9090"] + + - job_name: "otel-collector" + static_configs: + - targets: ["otel-collector:8889"] diff --git a/otel4s/tempo/tempo.yaml b/otel4s/tempo/tempo.yaml new file mode 100644 index 0000000..e3a5ec9 --- /dev/null +++ b/otel4s/tempo/tempo.yaml @@ -0,0 +1,30 @@ +server: + http_listen_port: 3200 + +distributor: + receivers: # this configuration will listen on all ports and protocols that tempo is capable of. + otlp: + protocols: + http: + endpoint: 0.0.0.0:4318 + grpc: + endpoint: 0.0.0.0:4317 + +metrics_generator: + registry: + external_labels: + source: tempo + cluster: docker-compose + storage: + path: /tmp/tempo/generator/wal + remote_write: + - url: http://prometheus:9090/api/v1/write + send_exemplars: true + +storage: + trace: + backend: local # backend configuration to use + wal: + path: /tmp/tempo/wal # where to store the the wal locally + local: + path: /tmp/tempo/blocks diff --git a/profilingRuntime/src/org/scala/abusers/profiling/runtime/LocalSpan.scala b/profilingRuntime/src/org/scala/abusers/profiling/runtime/LocalSpan.scala new file mode 100644 index 0000000..a39a0c7 --- /dev/null +++ b/profilingRuntime/src/org/scala/abusers/profiling/runtime/LocalSpan.scala @@ -0,0 +1,25 @@ +package org.scala.abusers.profiling.runtime + +import cats.effect.IOLocal +import cats.effect.unsafe.IORuntime + +case class TraceSpan(val spanId: Long) + +class LocalSpan(private val _ioLocal: () => IOLocal[Option[TraceSpan]]) { + private lazy val ioLocal: IOLocal[Option[TraceSpan]] = _ioLocal() + private lazy val unsafeThreadLocal: ThreadLocal[Option[TraceSpan]] = { + val fiberLocal = ioLocal.unsafeThreadLocal() + + new ThreadLocal[Option[TraceSpan]] { + override def initialValue(): Option[TraceSpan] = None + + override def get(): Option[TraceSpan] = + if (IORuntime.isUnderFiberContext()) fiberLocal.get() else super.get() + + override def set(value: Option[TraceSpan]): Unit = + if (IORuntime.isUnderFiberContext()) fiberLocal.set(value) else super.set(value) + } + } + def get() = unsafeThreadLocal.get() + def set(value: Option[TraceSpan]) = unsafeThreadLocal.set(value) +} diff --git a/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingExecutionContext.scala b/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingExecutionContext.scala new file mode 100644 index 0000000..350a451 --- /dev/null +++ b/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingExecutionContext.scala @@ -0,0 +1,34 @@ +package org.scala.abusers.profiling.runtime + +import scala.concurrent.ExecutionContext +import io.pyroscope.vendor.one.profiler.AsyncProfiler + +object ProfilingExecutionContext { + + def wrapExecutionContext(ec: ExecutionContext, profiler: AsyncProfiler, localSpan: LocalSpan): ExecutionContext = { + new ExecutionContext { + def execute(runnable: Runnable): Unit = { + val spanOpt = localSpan.get() + + ec.execute(new Runnable { + def run(): Unit = { + + spanOpt.foreach { span => + profiler.setTracingContext(span.spanId, 0) + } + + try { + runnable.run() + } finally { + spanOpt.foreach { _ => profiler.setTracingContext(0, 0) } + } + } + }) + } + + def reportFailure(cause: Throwable): Unit = ec.reportFailure(cause) + } + } + + +} diff --git a/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingIOApp.scala b/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingIOApp.scala new file mode 100644 index 0000000..9e8710f --- /dev/null +++ b/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingIOApp.scala @@ -0,0 +1,86 @@ +package org.scala.abusers.profiling.runtime + +import cats.effect.IOLocal +import cats.effect.unsafe.IORuntime +import cats.effect.IOApp +import io.pyroscope.PyroscopeAsyncProfiler +import cats.effect.kernel.Resource +import cats.effect.IO +import io.pyroscope.javaagent.PyroscopeAgent +import cats.effect.SyncIO +import org.typelevel.otel4s.sdk.OpenTelemetrySdk +import io.pyroscope.javaagent.config.Config as PyroscopeConfig +import cats.syntax.all.* +import io.pyroscope.javaagent.EventType +import io.pyroscope.http.Format +import org.typelevel.otel4s.sdk.exporter.otlp.autoconfigure.OtlpExportersAutoConfigure +import cats.effect.ExitCode +import cats.effect.unsafe.IORuntimeBuilder +import org.typelevel.otel4s.experimental.metrics._ +import org.typelevel.otel4s.trace.Tracer +import org.typelevel.otel4s.metrics.Meter + + +trait ProfilingIOApp extends IOApp { + private lazy val profiler = PyroscopeAsyncProfiler.getAsyncProfiler() + private lazy val localSpan0: IOLocal[Option[TraceSpan]] = IOLocal[Option[TraceSpan]](None) + .syncStep(100) + .flatMap( + _.leftMap(_ => + new Error( + "Failed to initialize the local context of the IOLocalContextStorageProvider." + ) + ).liftTo[SyncIO] + ) + .unsafeRunSync() + + + private val localSpan = new LocalSpan(() => localSpan0) + private val _runtime = { + IORuntimeBuilder() + .transformCompute { ec => ProfilingExecutionContext.wrapExecutionContext(ec, profiler, localSpan) } + .transformBlocking { ec => ProfilingExecutionContext.wrapExecutionContext(ec, profiler, localSpan) } + .build() + } + + override protected def runtime: IORuntime = _runtime + + override def run(args: List[String]): IO[ExitCode] = + OpenTelemetrySdk + .autoConfigured[IO]( // register OTLP exporters configurer + _.addExportersConfigurer(OtlpExportersAutoConfigure[IO]) + .addTracerProviderCustomizer((builder, _) => + builder.addSpanProcessor(new ProfilingSpanProcessor(localSpan) + )) + ) + .flatTap(_ => registerPyroscope()) // register Pyroscope agent + .use { autoConfigured => + val sdk = autoConfigured.sdk + val app = for { + given Meter[IO] <- sdk.meterProvider.get(applicationName).toResource + given Tracer[IO] <- sdk.tracerProvider.get(applicationName).toResource + _ <- RuntimeMetrics.register[IO] + _ <- program + } yield () + + app.useForever + }.as(ExitCode.Success) + + def program(using meter: Meter[IO], tracer: Tracer[IO]): Resource[IO, Unit] + def applicationName: String + + private def registerPyroscope(): Resource[IO, Unit] = { + val acquire = IO.delay { + PyroscopeAgent.start( + PyroscopeConfig.Builder() + .setApplicationName(applicationName) + .setProfilingEvent(EventType.ITIMER) + .setFormat(Format.JFR) + .setServerAddress("http://localhost:4040") // Refactor in the future to be configurable + .build() + ) + } + + Resource.eval(acquire).void + } +} diff --git a/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingSpanProcessor.scala b/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingSpanProcessor.scala new file mode 100644 index 0000000..5684983 --- /dev/null +++ b/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingSpanProcessor.scala @@ -0,0 +1,32 @@ +package org.scala.abusers.profiling.runtime + +import cats.effect.IO +import org.typelevel.otel4s.sdk.trace.processor.SpanProcessor +import org.typelevel.otel4s.sdk.trace.processor.SpanProcessor.OnEnd +import org.typelevel.otel4s.sdk.trace.processor.SpanProcessor.OnStart +import org.typelevel.otel4s.trace.SpanContext +import org.typelevel.otel4s.sdk.trace.SpanRef +import org.typelevel.otel4s.sdk.trace.data.SpanData +import org.typelevel.otel4s.Attribute + +class ProfilingSpanProcessor(localSpan: LocalSpan) extends SpanProcessor[IO] { + + override def name: String = "ProfilingSpanProcessor" + + override def onStart: OnStart[IO] = new OnStart[IO] { + override def apply(parentContext: Option[SpanContext], span: SpanRef[IO]): IO[Unit] = { + val spanIdStr = span.context.spanIdHex + val spanId = java.lang.Long.parseUnsignedLong(spanIdStr, 16) + for { + _ <- span.addAttributes(Seq(Attribute("pyroscope.profile.id", spanIdStr))) + _ <- IO(localSpan.set(Some(TraceSpan(spanId)))) + } yield () + } + } + + override def onEnd: OnEnd[IO] = new OnEnd[IO] { + override def apply(span: SpanData): IO[Unit] = IO(localSpan.set(None)) + } + + override def forceFlush: IO[Unit] = IO.unit +} diff --git a/sls/src/org/scala/abusers/sls/ComputationQueue.scala b/sls/src/org/scala/abusers/sls/ComputationQueue.scala index b594529..d3900be 100644 --- a/sls/src/org/scala/abusers/sls/ComputationQueue.scala +++ b/sls/src/org/scala/abusers/sls/ComputationQueue.scala @@ -2,6 +2,7 @@ package org.scala.abusers.sls import cats.effect.IO import cats.effect.std.Semaphore +import org.typelevel.otel4s.trace.Tracer sealed trait SynchronizedState @@ -12,7 +13,7 @@ trait ComputationQueue { class ComputationQueueImpl(semaphore: Semaphore[IO]) extends ComputationQueue { def synchronously[A](computation: SynchronizedState ?=> IO[A]): IO[A] = { - semaphore.permit.use(_ => computation) + semaphore.permit.surround(computation) } } diff --git a/sls/src/org/scala/abusers/sls/ServerImpl.scala b/sls/src/org/scala/abusers/sls/ServerImpl.scala index bc0d6a0..cea2816 100644 --- a/sls/src/org/scala/abusers/sls/ServerImpl.scala +++ b/sls/src/org/scala/abusers/sls/ServerImpl.scala @@ -33,6 +33,9 @@ import scala.meta.pc.VirtualFileParams import LoggingUtils.* import ScalaBuildTargetInformation.* +import org.typelevel.otel4s.trace.Tracer +import org.typelevel.otel4s.metrics.Meter + class ServerImpl( pcProvider: PresentationCompilerProvider, @@ -44,7 +47,7 @@ class ServerImpl( computationQueue: ComputationQueue, textDocumentSyncManager: TextDocumentSyncManager, bspStateManager: BspStateManager, -) extends SlsLanguageServer[IO] { +)(using Tracer[IO], Meter[IO]) extends SlsLanguageServer[IO] { /* There can only be one client for one language-server */ @@ -78,19 +81,27 @@ class ServerImpl( )).guaranteeCase(s => lspClient.logMessage(s"closing initalize with $s")) } - def initialized(params: lsp.InitializedParams): IO[Unit] = computationQueue.synchronously { - bspStateManager.importBuild - } - - def textDocumentCompletionOp(params: lsp.CompletionParams): IO[lsp.TextDocumentCompletionOpOutput] = handleCompletion(params) - def textDocumentDefinitionOp(params: lsp.DefinitionParams): IO[lsp.TextDocumentDefinitionOpOutput] = handleDefinition(params) - def textDocumentDidChange(params: lsp.DidChangeTextDocumentParams): IO[Unit] = handleDidChange(params) - def textDocumentDidClose(params: lsp.DidCloseTextDocumentParams): IO[Unit] = handleDidClose(params) - def textDocumentDidOpen(params: lsp.DidOpenTextDocumentParams): IO[Unit] = handleDidOpen(params) - def textDocumentDidSave(params: lsp.DidSaveTextDocumentParams): IO[Unit] = handleDidSave(params) - def textDocumentHoverOp(params: lsp.HoverParams): IO[lsp.TextDocumentHoverOpOutput] = handleHover(params) - def textDocumentInlayHintOp(params: lsp.InlayHintParams): IO[lsp.TextDocumentInlayHintOpOutput] = handleInlayHints(params) - def textDocumentSignatureHelpOp(params: lsp.SignatureHelpParams): IO[lsp.TextDocumentSignatureHelpOpOutput] = handleSignatureHelp(params) + def initialized(params: lsp.InitializedParams): IO[Unit] = + computationQueue.synchronously { bspStateManager.importBuild } + + def textDocumentCompletionOp(params: lsp.CompletionParams): IO[lsp.TextDocumentCompletionOpOutput] = + Tracer[IO].span("completion").surround(handleCompletion(params)) + def textDocumentDefinitionOp(params: lsp.DefinitionParams): IO[lsp.TextDocumentDefinitionOpOutput] = + Tracer[IO].span("go-to-defintion").surround(handleDefinition(params)) + def textDocumentDidChange(params: lsp.DidChangeTextDocumentParams): IO[Unit] = + Tracer[IO].span("did-change").surround(handleDidChange(params)) + def textDocumentDidClose(params: lsp.DidCloseTextDocumentParams): IO[Unit] = + Tracer[IO].span("did-close").surround(handleDidClose(params)) + def textDocumentDidOpen(params: lsp.DidOpenTextDocumentParams): IO[Unit] = + Tracer[IO].span("did-open").surround(handleDidOpen(params)) + def textDocumentDidSave(params: lsp.DidSaveTextDocumentParams): IO[Unit] = + Tracer[IO].span("did-save").surround(handleDidSave(params)) + def textDocumentHoverOp(params: lsp.HoverParams): IO[lsp.TextDocumentHoverOpOutput] = + Tracer[IO].span("hover").surround(handleHover(params)) + def textDocumentInlayHintOp(params: lsp.InlayHintParams): IO[lsp.TextDocumentInlayHintOpOutput] = + Tracer[IO].span("inlay-hints").surround(handleInlayHints(params)) + def textDocumentSignatureHelpOp(params: lsp.SignatureHelpParams): IO[lsp.TextDocumentSignatureHelpOpOutput] = + Tracer[IO].span("signature-help").surround(handleSignatureHelp(params)) // // TODO: goto type definition with container types def handleCompletion(params: lsp.CompletionParams) = @@ -226,7 +237,7 @@ class ServerImpl( def isSupported(info: ScalaBuildTargetInformation): Boolean = { import scala.math.Ordered.orderingToOrdered - info.scalaVersion > ScalaVersion("3.7.2") + info.scalaVersion > ScalaVersion("3.7.9") } /** We want to debounce compiler diagnostics as they are expensive to compute and we can't really cancel them as diff --git a/sls/src/org/scala/abusers/sls/SimpleLanguageServer.scala b/sls/src/org/scala/abusers/sls/SimpleLanguageServer.scala index c56a486..ccc7a7b 100644 --- a/sls/src/org/scala/abusers/sls/SimpleLanguageServer.scala +++ b/sls/src/org/scala/abusers/sls/SimpleLanguageServer.scala @@ -7,6 +7,8 @@ import jsonrpclib.smithy4sinterop.ClientStub import jsonrpclib.CallId import org.scala.abusers.pc.IOCancelTokens import org.scala.abusers.pc.PresentationCompilerProvider +import org.typelevel.otel4s.trace.Tracer +import org.typelevel.otel4s.metrics.Meter case class BuildServer( generic: bsp.BuildServer[IO], @@ -24,21 +26,33 @@ object BuildServer { ) } -object SimpleScalaServer extends IOApp.Simple { - import jsonrpclib.smithy4sinterop.ServerEndpoints - val cancelEndpoint = CancelTemplate.make[CallId]("$/cancel", identity, identity) +private case class LSPCancelRequest(id: CallId) + +object LSPCancelRequest { + import io.circe.Codec + given Codec[LSPCancelRequest] = Codec.derived[LSPCancelRequest] + + val cancelTemplate: CancelTemplate = CancelTemplate + .make[LSPCancelRequest]( + "$/cancelRequest", + _.id, + LSPCancelRequest(_) + ) +} + +object SimpleScalaServer extends org.scala.abusers.profiling.runtime.ProfilingIOApp { + import jsonrpclib.smithy4sinterop.ServerEndpoints - def run: IO[Unit] = - runResource.useForever + override def applicationName: String = "simple-language-server" - private def runResource = + override def program(using meter: Meter[IO], tracer: Tracer[IO]) = for { - fs2Channel <- FS2Channel.resource[IO](cancelTemplate = cancelEndpoint.some) - client <- ClientStub(SlsLanguageClient, fs2Channel).liftTo[IO].toResource - serverImpl <- server(client) - serverEndpoints <- ServerEndpoints(serverImpl).liftTo[IO].toResource - channelWithEndpoints <- fs2Channel.withEndpoints(serverEndpoints) + fs2Channel <- FS2Channel.resource[IO](cancelTemplate = LSPCancelRequest.cancelTemplate.some) + client <- ClientStub(SlsLanguageClient, fs2Channel).liftTo[IO].toResource + serverImpl <- server(client) + serverEndpoints <- ServerEndpoints(serverImpl).liftTo[IO].toResource + channelWithEndpoints <- fs2Channel.withEndpoints(serverEndpoints) _ <- fs2.Stream // Refactor to be single threaded .never[IO] .concurrently( @@ -59,7 +73,7 @@ object SimpleScalaServer extends IOApp.Simple { .background } yield () - private def server(lspClient: SlsLanguageClient[IO]): Resource[IO, ServerImpl] = + private def server(lspClient: SlsLanguageClient[IO])(using Tracer[IO], Meter[IO]): Resource[IO, ServerImpl] = for { steward <- ResourceSupervisor[IO] pcProvider <- PresentationCompilerProvider.instance.toResource From 64c2ecd9a67ae26b554679376238c79f9fd7c8be Mon Sep 17 00:00:00 2001 From: rochala Date: Sun, 28 Sep 2025 15:13:40 +0200 Subject: [PATCH 2/6] Profiling fix --- build.mill | 7 ++-- .../abusers/profiling/runtime/LocalSpan.scala | 25 ------------- .../runtime/ProfilingExecutionContext.scala | 22 +++++++++--- .../profiling/runtime/ProfilingIOApp.scala | 35 +++++++++++-------- .../profiling/runtime/ProfilingOps.scala | 17 +++++++++ .../runtime/ProfilingSpanProcessor.scala | 10 ++---- .../scala/abusers/sls/ComputationQueue.scala | 7 ++-- .../org/scala/abusers/sls/ServerImpl.scala | 19 +++++----- .../abusers/sls/SimpleLanguageServer.scala | 7 ++-- 9 files changed, 79 insertions(+), 70 deletions(-) delete mode 100644 profilingRuntime/src/org/scala/abusers/profiling/runtime/LocalSpan.scala create mode 100644 profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingOps.scala diff --git a/build.mill b/build.mill index 058ed9f..0f5f91c 100644 --- a/build.mill +++ b/build.mill @@ -70,6 +70,10 @@ object sls extends CommonScalaModule { def moduleDeps: Seq[JavaModule] = Seq(slsSmithy, profilingRuntime) + override def forkArgs: T[Seq[String]] = Seq( + "-Dcats.effect.trackFiberContext=true" + ) + def ivyDeps = Agg( ivy"org.typelevel::cats-effect:3.6.3", ivy"co.fs2::fs2-io:3.13.0-M2", @@ -79,7 +83,7 @@ object sls extends CommonScalaModule { ivy"ch.qos.logback:logback-classic:1.4.14", ivy"com.lihaoyi::os-lib:0.11.4", ivy"org.polyvariant.smithy4s-bsp::bsp4s:0.5.0", - ivy"org.scalameta:mtags-interfaces:1.5.1", + ivy"org.scalameta:mtags-interfaces:1.6.1-SNAPSHOT", ivy"com.evolution::scache:5.1.2", ivy"org.typelevel::cats-parse:1.1.0", ivy"io.get-coursier:interface:1.0.28", @@ -87,7 +91,6 @@ object sls extends CommonScalaModule { def javacOptions = Seq( "-Dotel.service.name=simple-language-server", - "-Dcats.effect.trackFiberContext=true" ) object test extends ScalaTests { diff --git a/profilingRuntime/src/org/scala/abusers/profiling/runtime/LocalSpan.scala b/profilingRuntime/src/org/scala/abusers/profiling/runtime/LocalSpan.scala deleted file mode 100644 index a39a0c7..0000000 --- a/profilingRuntime/src/org/scala/abusers/profiling/runtime/LocalSpan.scala +++ /dev/null @@ -1,25 +0,0 @@ -package org.scala.abusers.profiling.runtime - -import cats.effect.IOLocal -import cats.effect.unsafe.IORuntime - -case class TraceSpan(val spanId: Long) - -class LocalSpan(private val _ioLocal: () => IOLocal[Option[TraceSpan]]) { - private lazy val ioLocal: IOLocal[Option[TraceSpan]] = _ioLocal() - private lazy val unsafeThreadLocal: ThreadLocal[Option[TraceSpan]] = { - val fiberLocal = ioLocal.unsafeThreadLocal() - - new ThreadLocal[Option[TraceSpan]] { - override def initialValue(): Option[TraceSpan] = None - - override def get(): Option[TraceSpan] = - if (IORuntime.isUnderFiberContext()) fiberLocal.get() else super.get() - - override def set(value: Option[TraceSpan]): Unit = - if (IORuntime.isUnderFiberContext()) fiberLocal.set(value) else super.set(value) - } - } - def get() = unsafeThreadLocal.get() - def set(value: Option[TraceSpan]) = unsafeThreadLocal.set(value) -} diff --git a/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingExecutionContext.scala b/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingExecutionContext.scala index 350a451..8d2111b 100644 --- a/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingExecutionContext.scala +++ b/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingExecutionContext.scala @@ -1,20 +1,34 @@ -package org.scala.abusers.profiling.runtime +package cats.effect.org.scala.abusers.profiling.runtime import scala.concurrent.ExecutionContext import io.pyroscope.vendor.one.profiler.AsyncProfiler +import org.typelevel.otel4s.sdk.context.Context +import org.typelevel.otel4s.sdk.trace.SdkContextKeys +import org.typelevel.otel4s.sdk.context.TraceContext object ProfilingExecutionContext { - def wrapExecutionContext(ec: ExecutionContext, profiler: AsyncProfiler, localSpan: LocalSpan): ExecutionContext = { + def wrapExecutionContext(ec: ExecutionContext, profiler: AsyncProfiler, localContext: ThreadLocal[Context]): ExecutionContext = { new ExecutionContext { def execute(runnable: Runnable): Unit = { - val spanOpt = localSpan.get() ec.execute(new Runnable { + val ctx = localContext.get() def run(): Unit = { + val spanOpt = ctx + .get(SdkContextKeys.SpanContextKey) + .filter(_.isValid) + .map { ctx => + TraceContext( + ctx.traceId, + ctx.spanId, + ctx.isSampled + ) + } + spanOpt.foreach { span => - profiler.setTracingContext(span.spanId, 0) + profiler.setTracingContext(span.spanId.toLong(signed = false), 0) } try { diff --git a/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingIOApp.scala b/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingIOApp.scala index 9e8710f..4e9feec 100644 --- a/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingIOApp.scala +++ b/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingIOApp.scala @@ -19,11 +19,18 @@ import cats.effect.unsafe.IORuntimeBuilder import org.typelevel.otel4s.experimental.metrics._ import org.typelevel.otel4s.trace.Tracer import org.typelevel.otel4s.metrics.Meter +import org.typelevel.otel4s.context.LocalProvider +import org.typelevel.otel4s.sdk.context.Context +import cats.effect.org.scala.abusers.profiling.runtime.ProfilingExecutionContext +object ProfilingIOAppSettings { + lazy val isEnabled: Boolean = sys.env.get("SLS_PROFILING").contains("true") || sys.props.get("sls.profiling").contains("true") +} trait ProfilingIOApp extends IOApp { private lazy val profiler = PyroscopeAsyncProfiler.getAsyncProfiler() - private lazy val localSpan0: IOLocal[Option[TraceSpan]] = IOLocal[Option[TraceSpan]](None) + + private given localCtx: IOLocal[Context] = IOLocal[Context](Context.root) .syncStep(100) .flatMap( _.leftMap(_ => @@ -34,28 +41,29 @@ trait ProfilingIOApp extends IOApp { ) .unsafeRunSync() + private val threadLocal = localCtx.unsafeThreadLocal() - private val localSpan = new LocalSpan(() => localSpan0) - private val _runtime = { + private lazy val _runtime = IORuntimeBuilder() - .transformCompute { ec => ProfilingExecutionContext.wrapExecutionContext(ec, profiler, localSpan) } - .transformBlocking { ec => ProfilingExecutionContext.wrapExecutionContext(ec, profiler, localSpan) } + .transformCompute { ec => ProfilingExecutionContext.wrapExecutionContext(ec, profiler, threadLocal) } + .transformBlocking { ec => ProfilingExecutionContext.wrapExecutionContext(ec, profiler, threadLocal) } .build() - } - override protected def runtime: IORuntime = _runtime + override protected def runtime: IORuntime = if ProfilingIOAppSettings.isEnabled then _runtime else null /* Null is the default value */ + + def program(using meter: Meter[IO], tracer: Tracer[IO]): Resource[IO, Unit] + def applicationName: String override def run(args: List[String]): IO[ExitCode] = OpenTelemetrySdk - .autoConfigured[IO]( // register OTLP exporters configurer + .autoConfigured[IO]( _.addExportersConfigurer(OtlpExportersAutoConfigure[IO]) - .addTracerProviderCustomizer((builder, _) => - builder.addSpanProcessor(new ProfilingSpanProcessor(localSpan) - )) + .addTracerProviderCustomizer((builder, _) => builder.addSpanProcessor(new ProfilingSpanProcessor)) ) - .flatTap(_ => registerPyroscope()) // register Pyroscope agent + .flatTap(_ => registerPyroscope()) .use { autoConfigured => val sdk = autoConfigured.sdk + val app = for { given Meter[IO] <- sdk.meterProvider.get(applicationName).toResource given Tracer[IO] <- sdk.tracerProvider.get(applicationName).toResource @@ -66,9 +74,6 @@ trait ProfilingIOApp extends IOApp { app.useForever }.as(ExitCode.Success) - def program(using meter: Meter[IO], tracer: Tracer[IO]): Resource[IO, Unit] - def applicationName: String - private def registerPyroscope(): Resource[IO, Unit] = { val acquire = IO.delay { PyroscopeAgent.start( diff --git a/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingOps.scala b/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingOps.scala new file mode 100644 index 0000000..8c8adf4 --- /dev/null +++ b/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingOps.scala @@ -0,0 +1,17 @@ +package org.scala.abusers.profiling.runtime + + +import org.typelevel.otel4s.trace.SpanOps +import org.typelevel.otel4s.trace.Span +import cats.effect.IO + +object ProfilingOps { + extension (ops: SpanOps[IO]) { + def profilingUse[A](f: Span[IO] => IO[A]): IO[A] = + if ProfilingIOAppSettings.isEnabled then + ops.use(span => (IO.cede *> f(span)).guarantee(IO.cede)) + else ops.use(f) + + inline def profilingSurround[A](fa: IO[A]): IO[A] = profilingUse(_ => fa) + } +} diff --git a/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingSpanProcessor.scala b/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingSpanProcessor.scala index 5684983..f41066d 100644 --- a/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingSpanProcessor.scala +++ b/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingSpanProcessor.scala @@ -9,23 +9,19 @@ import org.typelevel.otel4s.sdk.trace.SpanRef import org.typelevel.otel4s.sdk.trace.data.SpanData import org.typelevel.otel4s.Attribute -class ProfilingSpanProcessor(localSpan: LocalSpan) extends SpanProcessor[IO] { +class ProfilingSpanProcessor extends SpanProcessor[IO] { override def name: String = "ProfilingSpanProcessor" override def onStart: OnStart[IO] = new OnStart[IO] { override def apply(parentContext: Option[SpanContext], span: SpanRef[IO]): IO[Unit] = { val spanIdStr = span.context.spanIdHex - val spanId = java.lang.Long.parseUnsignedLong(spanIdStr, 16) - for { - _ <- span.addAttributes(Seq(Attribute("pyroscope.profile.id", spanIdStr))) - _ <- IO(localSpan.set(Some(TraceSpan(spanId)))) - } yield () + span.addAttributes(Seq(Attribute("pyroscope.profile.id", spanIdStr))) } } override def onEnd: OnEnd[IO] = new OnEnd[IO] { - override def apply(span: SpanData): IO[Unit] = IO(localSpan.set(None)) + override def apply(span: SpanData): IO[Unit] = IO.unit } override def forceFlush: IO[Unit] = IO.unit diff --git a/sls/src/org/scala/abusers/sls/ComputationQueue.scala b/sls/src/org/scala/abusers/sls/ComputationQueue.scala index d3900be..95383c3 100644 --- a/sls/src/org/scala/abusers/sls/ComputationQueue.scala +++ b/sls/src/org/scala/abusers/sls/ComputationQueue.scala @@ -2,7 +2,6 @@ package org.scala.abusers.sls import cats.effect.IO import cats.effect.std.Semaphore -import org.typelevel.otel4s.trace.Tracer sealed trait SynchronizedState @@ -12,12 +11,10 @@ trait ComputationQueue { } class ComputationQueueImpl(semaphore: Semaphore[IO]) extends ComputationQueue { - def synchronously[A](computation: SynchronizedState ?=> IO[A]): IO[A] = { + def synchronously[A](computation: SynchronizedState ?=> IO[A]): IO[A] = semaphore.permit.surround(computation) - } } object ComputationQueue { - def instance: IO[ComputationQueue] = - Semaphore[IO](1).map(ComputationQueueImpl.apply) + def instance: IO[ComputationQueue] = Semaphore[IO](1).map(ComputationQueueImpl.apply) } diff --git a/sls/src/org/scala/abusers/sls/ServerImpl.scala b/sls/src/org/scala/abusers/sls/ServerImpl.scala index cea2816..bf6933e 100644 --- a/sls/src/org/scala/abusers/sls/ServerImpl.scala +++ b/sls/src/org/scala/abusers/sls/ServerImpl.scala @@ -35,6 +35,7 @@ import LoggingUtils.* import ScalaBuildTargetInformation.* import org.typelevel.otel4s.trace.Tracer import org.typelevel.otel4s.metrics.Meter +import org.scala.abusers.profiling.runtime.ProfilingOps.* class ServerImpl( @@ -85,23 +86,23 @@ class ServerImpl( computationQueue.synchronously { bspStateManager.importBuild } def textDocumentCompletionOp(params: lsp.CompletionParams): IO[lsp.TextDocumentCompletionOpOutput] = - Tracer[IO].span("completion").surround(handleCompletion(params)) + Tracer[IO].span("completion").profilingSurround(handleCompletion(params)) def textDocumentDefinitionOp(params: lsp.DefinitionParams): IO[lsp.TextDocumentDefinitionOpOutput] = - Tracer[IO].span("go-to-defintion").surround(handleDefinition(params)) + Tracer[IO].span("go-to-defintion").profilingSurround(handleDefinition(params)) def textDocumentDidChange(params: lsp.DidChangeTextDocumentParams): IO[Unit] = - Tracer[IO].span("did-change").surround(handleDidChange(params)) + Tracer[IO].span("did-change").profilingSurround(handleDidChange(params)) def textDocumentDidClose(params: lsp.DidCloseTextDocumentParams): IO[Unit] = - Tracer[IO].span("did-close").surround(handleDidClose(params)) + Tracer[IO].span("did-close").profilingSurround(handleDidClose(params)) def textDocumentDidOpen(params: lsp.DidOpenTextDocumentParams): IO[Unit] = - Tracer[IO].span("did-open").surround(handleDidOpen(params)) + Tracer[IO].span("did-open").profilingSurround(handleDidOpen(params)) def textDocumentDidSave(params: lsp.DidSaveTextDocumentParams): IO[Unit] = - Tracer[IO].span("did-save").surround(handleDidSave(params)) + Tracer[IO].span("did-save").profilingSurround(handleDidSave(params)) def textDocumentHoverOp(params: lsp.HoverParams): IO[lsp.TextDocumentHoverOpOutput] = - Tracer[IO].span("hover").surround(handleHover(params)) + Tracer[IO].span("hover").profilingSurround(handleHover(params)) def textDocumentInlayHintOp(params: lsp.InlayHintParams): IO[lsp.TextDocumentInlayHintOpOutput] = - Tracer[IO].span("inlay-hints").surround(handleInlayHints(params)) + Tracer[IO].span("inlay-hints").profilingSurround(handleInlayHints(params)) def textDocumentSignatureHelpOp(params: lsp.SignatureHelpParams): IO[lsp.TextDocumentSignatureHelpOpOutput] = - Tracer[IO].span("signature-help").surround(handleSignatureHelp(params)) + Tracer[IO].span("signature-help").profilingSurround(handleSignatureHelp(params)) // // TODO: goto type definition with container types def handleCompletion(params: lsp.CompletionParams) = diff --git a/sls/src/org/scala/abusers/sls/SimpleLanguageServer.scala b/sls/src/org/scala/abusers/sls/SimpleLanguageServer.scala index ccc7a7b..6ec180e 100644 --- a/sls/src/org/scala/abusers/sls/SimpleLanguageServer.scala +++ b/sls/src/org/scala/abusers/sls/SimpleLanguageServer.scala @@ -1,6 +1,5 @@ package org.scala.abusers.sls -import cats.effect.* import cats.syntax.all.* import jsonrpclib.fs2.* import jsonrpclib.smithy4sinterop.ClientStub @@ -9,6 +8,8 @@ import org.scala.abusers.pc.IOCancelTokens import org.scala.abusers.pc.PresentationCompilerProvider import org.typelevel.otel4s.trace.Tracer import org.typelevel.otel4s.metrics.Meter +import org.scala.abusers.profiling.runtime.ProfilingIOApp +import cats.effect.* case class BuildServer( generic: bsp.BuildServer[IO], @@ -41,7 +42,7 @@ object LSPCancelRequest { ) } -object SimpleScalaServer extends org.scala.abusers.profiling.runtime.ProfilingIOApp { +object SimpleScalaServer extends ProfilingIOApp { import jsonrpclib.smithy4sinterop.ServerEndpoints override def applicationName: String = "simple-language-server" @@ -70,7 +71,7 @@ object SimpleScalaServer extends org.scala.abusers.profiling.runtime.ProfilingIO ) .compile .drain - .background + .toResource } yield () private def server(lspClient: SlsLanguageClient[IO])(using Tracer[IO], Meter[IO]): Resource[IO, ServerImpl] = From 7309c04a3a2657ccdd55999a7c07fd4460168699 Mon Sep 17 00:00:00 2001 From: rochala Date: Sun, 28 Sep 2025 15:17:56 +0200 Subject: [PATCH 3/6] Format --- .../runtime/ProfilingExecutionContext.scala | 29 ++++---- .../profiling/runtime/ProfilingIOApp.scala | 53 +++++++------- .../profiling/runtime/ProfilingOps.scala | 8 +-- .../runtime/ProfilingSpanProcessor.scala | 4 +- sls/src/org/scala/abusers/sls/BspClient.scala | 9 +-- .../scala/abusers/sls/BspStateManager.scala | 7 +- .../scala/abusers/sls/ComputationQueue.scala | 2 +- .../org/scala/abusers/sls/ServerImpl.scala | 70 +++++++++++-------- .../abusers/sls/SimpleLanguageServer.scala | 31 +++++--- .../sls/pc/PresentationCompilerProvider.scala | 2 +- .../scala/abusers/sls/DocumentSyncTests.scala | 4 +- 11 files changed, 121 insertions(+), 98 deletions(-) diff --git a/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingExecutionContext.scala b/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingExecutionContext.scala index 8d2111b..5f2033b 100644 --- a/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingExecutionContext.scala +++ b/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingExecutionContext.scala @@ -1,16 +1,21 @@ -package cats.effect.org.scala.abusers.profiling.runtime +package org.scala.abusers.profiling.runtime -import scala.concurrent.ExecutionContext import io.pyroscope.vendor.one.profiler.AsyncProfiler import org.typelevel.otel4s.sdk.context.Context -import org.typelevel.otel4s.sdk.trace.SdkContextKeys import org.typelevel.otel4s.sdk.context.TraceContext +import org.typelevel.otel4s.sdk.trace.SdkContextKeys + +import scala.concurrent.ExecutionContext object ProfilingExecutionContext { - def wrapExecutionContext(ec: ExecutionContext, profiler: AsyncProfiler, localContext: ThreadLocal[Context]): ExecutionContext = { + def wrapExecutionContext( + ec: ExecutionContext, + profiler: AsyncProfiler, + localContext: ThreadLocal[Context], + ): ExecutionContext = new ExecutionContext { - def execute(runnable: Runnable): Unit = { + def execute(runnable: Runnable): Unit = ec.execute(new Runnable { val ctx = localContext.get() @@ -23,26 +28,22 @@ object ProfilingExecutionContext { TraceContext( ctx.traceId, ctx.spanId, - ctx.isSampled + ctx.isSampled, ) } spanOpt.foreach { span => - profiler.setTracingContext(span.spanId.toLong(signed = false), 0) + profiler.setTracingContext(span.spanId.toLong(signed = false), 0) } - try { + try runnable.run() - } finally { - spanOpt.foreach { _ => profiler.setTracingContext(0, 0) } - } + finally + spanOpt.foreach(_ => profiler.setTracingContext(0, 0)) } }) - } def reportFailure(cause: Throwable): Unit = ec.reportFailure(cause) } - } - } diff --git a/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingIOApp.scala b/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingIOApp.scala index 4e9feec..b17967b 100644 --- a/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingIOApp.scala +++ b/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingIOApp.scala @@ -1,30 +1,30 @@ package org.scala.abusers.profiling.runtime -import cats.effect.IOLocal -import cats.effect.unsafe.IORuntime -import cats.effect.IOApp -import io.pyroscope.PyroscopeAsyncProfiler import cats.effect.kernel.Resource +import cats.effect.unsafe.IORuntime +import cats.effect.unsafe.IORuntimeBuilder +import cats.effect.ExitCode import cats.effect.IO -import io.pyroscope.javaagent.PyroscopeAgent +import cats.effect.IOApp +import cats.effect.IOLocal import cats.effect.SyncIO -import org.typelevel.otel4s.sdk.OpenTelemetrySdk -import io.pyroscope.javaagent.config.Config as PyroscopeConfig import cats.syntax.all.* -import io.pyroscope.javaagent.EventType import io.pyroscope.http.Format -import org.typelevel.otel4s.sdk.exporter.otlp.autoconfigure.OtlpExportersAutoConfigure -import cats.effect.ExitCode -import cats.effect.unsafe.IORuntimeBuilder +import io.pyroscope.javaagent.config.Config as PyroscopeConfig +import io.pyroscope.javaagent.EventType +import io.pyroscope.javaagent.PyroscopeAgent +import io.pyroscope.PyroscopeAsyncProfiler +import org.typelevel.otel4s.context.LocalProvider import org.typelevel.otel4s.experimental.metrics._ -import org.typelevel.otel4s.trace.Tracer import org.typelevel.otel4s.metrics.Meter -import org.typelevel.otel4s.context.LocalProvider import org.typelevel.otel4s.sdk.context.Context -import cats.effect.org.scala.abusers.profiling.runtime.ProfilingExecutionContext +import org.typelevel.otel4s.sdk.exporter.otlp.autoconfigure.OtlpExportersAutoConfigure +import org.typelevel.otel4s.sdk.OpenTelemetrySdk +import org.typelevel.otel4s.trace.Tracer object ProfilingIOAppSettings { - lazy val isEnabled: Boolean = sys.env.get("SLS_PROFILING").contains("true") || sys.props.get("sls.profiling").contains("true") + lazy val isEnabled: Boolean = + sys.env.get("SLS_PROFILING").contains("true") || sys.props.get("sls.profiling").contains("true") } trait ProfilingIOApp extends IOApp { @@ -45,11 +45,12 @@ trait ProfilingIOApp extends IOApp { private lazy val _runtime = IORuntimeBuilder() - .transformCompute { ec => ProfilingExecutionContext.wrapExecutionContext(ec, profiler, threadLocal) } - .transformBlocking { ec => ProfilingExecutionContext.wrapExecutionContext(ec, profiler, threadLocal) } + .transformCompute(ec => ProfilingExecutionContext.wrapExecutionContext(ec, profiler, threadLocal)) + .transformBlocking(ec => ProfilingExecutionContext.wrapExecutionContext(ec, profiler, threadLocal)) .build() - override protected def runtime: IORuntime = if ProfilingIOAppSettings.isEnabled then _runtime else null /* Null is the default value */ + override protected def runtime: IORuntime = + if ProfilingIOAppSettings.isEnabled then _runtime else null /* Null is the default value */ def program(using meter: Meter[IO], tracer: Tracer[IO]): Resource[IO, Unit] def applicationName: String @@ -58,32 +59,34 @@ trait ProfilingIOApp extends IOApp { OpenTelemetrySdk .autoConfigured[IO]( _.addExportersConfigurer(OtlpExportersAutoConfigure[IO]) - .addTracerProviderCustomizer((builder, _) => builder.addSpanProcessor(new ProfilingSpanProcessor)) + .addTracerProviderCustomizer((builder, _) => builder.addSpanProcessor(new ProfilingSpanProcessor)) ) .flatTap(_ => registerPyroscope()) .use { autoConfigured => val sdk = autoConfigured.sdk val app = for { - given Meter[IO] <- sdk.meterProvider.get(applicationName).toResource + given Meter[IO] <- sdk.meterProvider.get(applicationName).toResource given Tracer[IO] <- sdk.tracerProvider.get(applicationName).toResource - _ <- RuntimeMetrics.register[IO] - _ <- program + _ <- RuntimeMetrics.register[IO] + _ <- program } yield () app.useForever - }.as(ExitCode.Success) + } + .as(ExitCode.Success) private def registerPyroscope(): Resource[IO, Unit] = { val acquire = IO.delay { PyroscopeAgent.start( - PyroscopeConfig.Builder() + PyroscopeConfig + .Builder() .setApplicationName(applicationName) .setProfilingEvent(EventType.ITIMER) .setFormat(Format.JFR) .setServerAddress("http://localhost:4040") // Refactor in the future to be configurable .build() - ) + ) } Resource.eval(acquire).void diff --git a/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingOps.scala b/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingOps.scala index 8c8adf4..ede7777 100644 --- a/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingOps.scala +++ b/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingOps.scala @@ -1,15 +1,13 @@ package org.scala.abusers.profiling.runtime - -import org.typelevel.otel4s.trace.SpanOps -import org.typelevel.otel4s.trace.Span import cats.effect.IO +import org.typelevel.otel4s.trace.Span +import org.typelevel.otel4s.trace.SpanOps object ProfilingOps { extension (ops: SpanOps[IO]) { def profilingUse[A](f: Span[IO] => IO[A]): IO[A] = - if ProfilingIOAppSettings.isEnabled then - ops.use(span => (IO.cede *> f(span)).guarantee(IO.cede)) + if ProfilingIOAppSettings.isEnabled then ops.use(span => (IO.cede *> f(span)).guarantee(IO.cede)) else ops.use(f) inline def profilingSurround[A](fa: IO[A]): IO[A] = profilingUse(_ => fa) diff --git a/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingSpanProcessor.scala b/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingSpanProcessor.scala index f41066d..bb43fa8 100644 --- a/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingSpanProcessor.scala +++ b/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingSpanProcessor.scala @@ -1,12 +1,12 @@ package org.scala.abusers.profiling.runtime import cats.effect.IO +import org.typelevel.otel4s.sdk.trace.data.SpanData import org.typelevel.otel4s.sdk.trace.processor.SpanProcessor import org.typelevel.otel4s.sdk.trace.processor.SpanProcessor.OnEnd import org.typelevel.otel4s.sdk.trace.processor.SpanProcessor.OnStart -import org.typelevel.otel4s.trace.SpanContext import org.typelevel.otel4s.sdk.trace.SpanRef -import org.typelevel.otel4s.sdk.trace.data.SpanData +import org.typelevel.otel4s.trace.SpanContext import org.typelevel.otel4s.Attribute class ProfilingSpanProcessor extends SpanProcessor[IO] { diff --git a/sls/src/org/scala/abusers/sls/BspClient.scala b/sls/src/org/scala/abusers/sls/BspClient.scala index 6c6a0ce..1f5e9b8 100644 --- a/sls/src/org/scala/abusers/sls/BspClient.scala +++ b/sls/src/org/scala/abusers/sls/BspClient.scala @@ -14,8 +14,8 @@ import cats.syntax.all.* import com.comcast.ip4s.* import fs2.io.* import fs2.io.net.Network -import jsonrpclib.Endpoint import jsonrpclib.fs2.{lsp => jsonrpclibLsp, *} +import jsonrpclib.Endpoint import smithy4sbsp.bsp4s.BSPCodecs def makeBspClient(path: String, channel: FS2Channel[IO], report: String => IO[Unit]): Resource[IO, BuildServer] = @@ -25,7 +25,10 @@ def makeBspClient(path: String, channel: FS2Channel[IO], report: String => IO[Un fs2.Stream .eval(IO.never) .concurrently( - socket.reads.through(jsonrpclibLsp.decodeMessages).evalTap(m => report(m.toString)).through(channel.inputOrBounce) + socket.reads + .through(jsonrpclibLsp.decodeMessages) + .evalTap(m => report(m.toString)) + .through(channel.inputOrBounce) ) .concurrently(channel.output.through(jsonrpclibLsp.encodeMessages).through(socket.writes)) .compile @@ -47,8 +50,6 @@ def bspClientHandler(lspClient: SlsLanguageClient[IO], diagnosticManager: Diagno .serverEndpoints( new BuildClient[IO] { - - def onBuildLogMessage(input: LogMessageParams): IO[Unit] = IO.unit // we want some logging to file here def onBuildPublishDiagnostics(input: PublishDiagnosticsParams): IO[Unit] = diff --git a/sls/src/org/scala/abusers/sls/BspStateManager.scala b/sls/src/org/scala/abusers/sls/BspStateManager.scala index 6a2c030..1fba201 100644 --- a/sls/src/org/scala/abusers/sls/BspStateManager.scala +++ b/sls/src/org/scala/abusers/sls/BspStateManager.scala @@ -53,7 +53,7 @@ class BspStateManager( def importBuild = for { - _ <- lspClient.logMessage("Starting build import.") // in the future this should be a task with progress + _ <- lspClient.logMessage("Starting build import.") // in the future this should be a task with progress importedBuild <- getBuildInformation(bspServer) _ <- bspServer.generic.buildTargetCompile(CompileParams(targets = importedBuild.map(_.buildTarget.id).toList)) _ <- targets.set(importedBuild) @@ -113,7 +113,10 @@ class BspStateManager( state.getOrElse(uri, throw new IllegalStateException("Get should always be called after didOpen")) } - def didOpen(client: SlsLanguageClient[IO], params: lsp.DidOpenTextDocumentParams)(using SynchronizedState): IO[Unit] = { + def didOpen( + client: SlsLanguageClient[IO], + params: lsp.DidOpenTextDocumentParams, + )(using SynchronizedState): IO[Unit] = { val uri = URI.create(params.textDocument.uri) sourcesToTargets.evalUpdate(state => for { diff --git a/sls/src/org/scala/abusers/sls/ComputationQueue.scala b/sls/src/org/scala/abusers/sls/ComputationQueue.scala index 95383c3..9d0b0fe 100644 --- a/sls/src/org/scala/abusers/sls/ComputationQueue.scala +++ b/sls/src/org/scala/abusers/sls/ComputationQueue.scala @@ -1,7 +1,7 @@ package org.scala.abusers.sls -import cats.effect.IO import cats.effect.std.Semaphore +import cats.effect.IO sealed trait SynchronizedState diff --git a/sls/src/org/scala/abusers/sls/ServerImpl.scala b/sls/src/org/scala/abusers/sls/ServerImpl.scala index bf6933e..da65783 100644 --- a/sls/src/org/scala/abusers/sls/ServerImpl.scala +++ b/sls/src/org/scala/abusers/sls/ServerImpl.scala @@ -17,6 +17,9 @@ import org.scala.abusers.pc.IOCancelTokens import org.scala.abusers.pc.PresentationCompilerDTOInterop.* import org.scala.abusers.pc.PresentationCompilerProvider import org.scala.abusers.pc.ScalaVersion +import org.scala.abusers.profiling.runtime.ProfilingOps.* +import org.typelevel.otel4s.metrics.Meter +import org.typelevel.otel4s.trace.Tracer import smithy4s.schema.Schema import util.chaining.* @@ -33,10 +36,6 @@ import scala.meta.pc.VirtualFileParams import LoggingUtils.* import ScalaBuildTargetInformation.* -import org.typelevel.otel4s.trace.Tracer -import org.typelevel.otel4s.metrics.Meter -import org.scala.abusers.profiling.runtime.ProfilingOps.* - class ServerImpl( pcProvider: PresentationCompilerProvider, @@ -48,7 +47,8 @@ class ServerImpl( computationQueue: ComputationQueue, textDocumentSyncManager: TextDocumentSyncManager, bspStateManager: BspStateManager, -)(using Tracer[IO], Meter[IO]) extends SlsLanguageServer[IO] { +)(using Tracer[IO], Meter[IO]) + extends SlsLanguageServer[IO] { /* There can only be one client for one language-server */ @@ -83,19 +83,19 @@ class ServerImpl( } def initialized(params: lsp.InitializedParams): IO[Unit] = - computationQueue.synchronously { bspStateManager.importBuild } + computationQueue.synchronously(bspStateManager.importBuild) def textDocumentCompletionOp(params: lsp.CompletionParams): IO[lsp.TextDocumentCompletionOpOutput] = Tracer[IO].span("completion").profilingSurround(handleCompletion(params)) def textDocumentDefinitionOp(params: lsp.DefinitionParams): IO[lsp.TextDocumentDefinitionOpOutput] = Tracer[IO].span("go-to-defintion").profilingSurround(handleDefinition(params)) - def textDocumentDidChange(params: lsp.DidChangeTextDocumentParams): IO[Unit] = + def textDocumentDidChange(params: lsp.DidChangeTextDocumentParams): IO[Unit] = Tracer[IO].span("did-change").profilingSurround(handleDidChange(params)) - def textDocumentDidClose(params: lsp.DidCloseTextDocumentParams): IO[Unit] = + def textDocumentDidClose(params: lsp.DidCloseTextDocumentParams): IO[Unit] = Tracer[IO].span("did-close").profilingSurround(handleDidClose(params)) - def textDocumentDidOpen(params: lsp.DidOpenTextDocumentParams): IO[Unit] = + def textDocumentDidOpen(params: lsp.DidOpenTextDocumentParams): IO[Unit] = Tracer[IO].span("did-open").profilingSurround(handleDidOpen(params)) - def textDocumentDidSave(params: lsp.DidSaveTextDocumentParams): IO[Unit] = + def textDocumentDidSave(params: lsp.DidSaveTextDocumentParams): IO[Unit] = Tracer[IO].span("did-save").profilingSurround(handleDidSave(params)) def textDocumentHoverOp(params: lsp.HoverParams): IO[lsp.TextDocumentHoverOpOutput] = Tracer[IO].span("hover").profilingSurround(handleHover(params)) @@ -208,16 +208,21 @@ class ServerImpl( } def handleDidSave(params: lsp.DidSaveTextDocumentParams) = - computationQueue.synchronously { - for { - _ <- textDocumentSyncManager.didSave(params) - info <- bspStateManager.get(URI(params.textDocument.uri)) - } yield info - }.flatMap { info => - bspStateManager.bspServer.generic.buildTargetCompile(bsp.CompileParams(targets = List(info.buildTarget.id))) // we need to handle the case: - // User saves, compilation starts, we don't want to block it, completion starts, during completion compilation ends new classfiles emmited. We need to know which classfiles are new. - // I hardly believe that we should use straight to jar compilation for that - }.void + computationQueue + .synchronously { + for { + _ <- textDocumentSyncManager.didSave(params) + info <- bspStateManager.get(URI(params.textDocument.uri)) + } yield info + } + .flatMap { info => + bspStateManager.bspServer.generic.buildTargetCompile( + bsp.CompileParams(targets = List(info.buildTarget.id)) + ) // we need to handle the case: + // User saves, compilation starts, we don't want to block it, completion starts, during completion compilation ends new classfiles emmited. We need to know which classfiles are new. + // I hardly believe that we should use straight to jar compilation for that + } + .void def handleDidOpen(params: lsp.DidOpenTextDocumentParams) = computationQueue.synchronously { textDocumentSyncManager.didOpen(params) *> bspStateManager.didOpen(lspClient, params) @@ -259,21 +264,24 @@ class ServerImpl( .withFieldRenamed(_.everyItem.getMessage, _.everyItem.message) .enableOptionDefaultsToNone .transform - _ <- diagnosticManager.didChange(lspClient, uri.toString, lspDiags) + _ <- diagnosticManager.didChange(lspClient, uri.toString, lspDiags) } yield () } } - params => computationQueue.synchronously { - for { - _ <- textDocumentSyncManager.didChange(params) - _ <- lspClient.logDebug("Updated DocumentState") - uri = URI(params.textDocument.uri) - info <- bspStateManager.get(uri) - } yield (uri, info) - }.flatMap { (uri, info) => - if isSupported(info) then debounce.debounce(pcDiagnostics(info, uri)) else IO.unit - } + params => + computationQueue + .synchronously { + for { + _ <- textDocumentSyncManager.didChange(params) + _ <- lspClient.logDebug("Updated DocumentState") + uri = URI(params.textDocument.uri) + info <- bspStateManager.get(uri) + } yield (uri, info) + } + .flatMap { (uri, info) => + if isSupported(info) then debounce.debounce(pcDiagnostics(info, uri)) else IO.unit + } } private def serverCapabilities: lsp.ServerCapabilities = diff --git a/sls/src/org/scala/abusers/sls/SimpleLanguageServer.scala b/sls/src/org/scala/abusers/sls/SimpleLanguageServer.scala index 6ec180e..b4f962a 100644 --- a/sls/src/org/scala/abusers/sls/SimpleLanguageServer.scala +++ b/sls/src/org/scala/abusers/sls/SimpleLanguageServer.scala @@ -1,15 +1,15 @@ package org.scala.abusers.sls +import cats.effect.* import cats.syntax.all.* import jsonrpclib.fs2.* import jsonrpclib.smithy4sinterop.ClientStub import jsonrpclib.CallId import org.scala.abusers.pc.IOCancelTokens import org.scala.abusers.pc.PresentationCompilerProvider -import org.typelevel.otel4s.trace.Tracer -import org.typelevel.otel4s.metrics.Meter import org.scala.abusers.profiling.runtime.ProfilingIOApp -import cats.effect.* +import org.typelevel.otel4s.metrics.Meter +import org.typelevel.otel4s.trace.Tracer case class BuildServer( generic: bsp.BuildServer[IO], @@ -27,7 +27,6 @@ object BuildServer { ) } - private case class LSPCancelRequest(id: CallId) object LSPCancelRequest { @@ -38,7 +37,7 @@ object LSPCancelRequest { .make[LSPCancelRequest]( "$/cancelRequest", _.id, - LSPCancelRequest(_) + LSPCancelRequest(_), ) } @@ -49,11 +48,11 @@ object SimpleScalaServer extends ProfilingIOApp { override def program(using meter: Meter[IO], tracer: Tracer[IO]) = for { - fs2Channel <- FS2Channel.resource[IO](cancelTemplate = LSPCancelRequest.cancelTemplate.some) - client <- ClientStub(SlsLanguageClient, fs2Channel).liftTo[IO].toResource - serverImpl <- server(client) - serverEndpoints <- ServerEndpoints(serverImpl).liftTo[IO].toResource - channelWithEndpoints <- fs2Channel.withEndpoints(serverEndpoints) + fs2Channel <- FS2Channel.resource[IO](cancelTemplate = LSPCancelRequest.cancelTemplate.some) + client <- ClientStub(SlsLanguageClient, fs2Channel).liftTo[IO].toResource + serverImpl <- server(client) + serverEndpoints <- ServerEndpoints(serverImpl).liftTo[IO].toResource + channelWithEndpoints <- fs2Channel.withEndpoints(serverEndpoints) _ <- fs2.Stream // Refactor to be single threaded .never[IO] .concurrently( @@ -84,5 +83,15 @@ object SimpleScalaServer extends ProfilingIOApp { cancelTokens <- IOCancelTokens.instance diagnosticManager <- DiagnosticManager.instance.toResource computationQueue <- ComputationQueue.instance.toResource - } yield ServerImpl(pcProvider, cancelTokens, diagnosticManager, steward, bspClientDeferred, lspClient, computationQueue, textDocumentSync, bspStateManager) + } yield ServerImpl( + pcProvider, + cancelTokens, + diagnosticManager, + steward, + bspClientDeferred, + lspClient, + computationQueue, + textDocumentSync, + bspStateManager, + ) } diff --git a/sls/src/org/scala/abusers/sls/pc/PresentationCompilerProvider.scala b/sls/src/org/scala/abusers/sls/pc/PresentationCompilerProvider.scala index 3be198c..3df71b9 100644 --- a/sls/src/org/scala/abusers/sls/pc/PresentationCompilerProvider.scala +++ b/sls/src/org/scala/abusers/sls/pc/PresentationCompilerProvider.scala @@ -7,13 +7,13 @@ import com.evolution.scache.ExpiringCache import coursierapi.* import org.scala.abusers.sls.ScalaBuildTargetInformation import org.scala.abusers.sls.ScalaBuildTargetInformation.* +import org.scala.abusers.sls.SynchronizedState import os.Path import java.net.URLClassLoader import scala.concurrent.duration.* import scala.jdk.CollectionConverters.* import scala.meta.pc.PresentationCompiler -import org.scala.abusers.sls.SynchronizedState class PresentationCompilerProvider( serviceLoader: BlockingServiceLoader, diff --git a/sls/test/src/org/scala/abusers/sls/DocumentSyncTests.scala b/sls/test/src/org/scala/abusers/sls/DocumentSyncTests.scala index 447b9ed..c3dc0eb 100644 --- a/sls/test/src/org/scala/abusers/sls/DocumentSyncTests.scala +++ b/sls/test/src/org/scala/abusers/sls/DocumentSyncTests.scala @@ -1,14 +1,14 @@ package org.scala.abusers.sls +import cats.effect.IO import lsp.* import weaver.SimpleIOSuite -import cats.effect.IO object TextDocumentSyncSuite extends SimpleIOSuite { class TestComputationQueue extends ComputationQueue { override def synchronously[A](computation: (SynchronizedState) ?=> IO[A]): IO[A] = computation - def unsafeGetState: SynchronizedState = summon[SynchronizedState] + def unsafeGetState: SynchronizedState = summon[SynchronizedState] } given SynchronizedState = TestComputationQueue().unsafeGetState From fe3b3725fa7a276fd5badc93d3d436546f5768af Mon Sep 17 00:00:00 2001 From: rochala Date: Mon, 29 Sep 2025 22:29:27 +0200 Subject: [PATCH 4/6] Fix system property usage --- .../org/scala/abusers/profiling/runtime/ProfilingIOApp.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingIOApp.scala b/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingIOApp.scala index b17967b..6f2f66d 100644 --- a/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingIOApp.scala +++ b/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingIOApp.scala @@ -50,7 +50,7 @@ trait ProfilingIOApp extends IOApp { .build() override protected def runtime: IORuntime = - if ProfilingIOAppSettings.isEnabled then _runtime else null /* Null is the default value */ + if ProfilingIOAppSettings.isEnabled then _runtime else super.runtime def program(using meter: Meter[IO], tracer: Tracer[IO]): Resource[IO, Unit] def applicationName: String From a13bd0e2a8171def966d1ede9a613b4b06c6d2dc Mon Sep 17 00:00:00 2001 From: rochala Date: Thu, 9 Oct 2025 02:11:20 +0200 Subject: [PATCH 5/6] Add some sane dashboards, make server resistant to println logs, add a way to disable profiling --- build.mill | 1 + otel4s/docker-compose.yaml | 7 + .../{cats-metrics.json => application.json} | 1881 ++++++++++++++++- otel4s/grafana/dashboards/jvm-metrics.json | 0 otel4s/grafana/dashboards/tracing.json | 151 -- otel4s/grafana/datasources.yaml | 35 +- otel4s/tempo/tempo.yaml | 14 + .../profiling/runtime/ProfilingIOApp.scala | 28 +- .../org/scala/abusers/sls/ErrOutConsole.scala | 18 + .../abusers/sls/SimpleLanguageServer.scala | 3 + 10 files changed, 1927 insertions(+), 211 deletions(-) rename otel4s/grafana/dashboards/{cats-metrics.json => application.json} (61%) delete mode 100644 otel4s/grafana/dashboards/jvm-metrics.json delete mode 100644 otel4s/grafana/dashboards/tracing.json create mode 100644 sls/src/org/scala/abusers/sls/ErrOutConsole.scala diff --git a/build.mill b/build.mill index 0f5f91c..d3fae12 100644 --- a/build.mill +++ b/build.mill @@ -48,6 +48,7 @@ object profilingRuntime extends CommonScalaModule with PublishModule { ivy"org.typelevel::otel4s-sdk:0.13.1", ivy"org.typelevel::otel4s-sdk-exporter:0.13.1", ivy"org.typelevel::otel4s-experimental-metrics:0.7.0", + ivy"org.typelevel::otel4s-instrumentation-metrics:0.13.1" ) def pomSettings: T[PomSettings] = PomSettings( diff --git a/otel4s/docker-compose.yaml b/otel4s/docker-compose.yaml index 8e75da2..9528bcf 100644 --- a/otel4s/docker-compose.yaml +++ b/otel4s/docker-compose.yaml @@ -26,6 +26,9 @@ services: prometheus: image: prom/prometheus:latest + command: + - '--config.file=/etc/prometheus/prometheus.yml' + - '--web.enable-remote-write-receiver' volumes: - "./prometheus/prometheus.yaml:/etc/prometheus/prometheus.yml" ports: @@ -43,6 +46,10 @@ services: grafana: image: grafana/grafana restart: unless-stopped + environment: + - GF_AUTH_ANONYMOUS_ENABLED=true + - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin + - GF_AUTH_DISABLE_LOGIN_FORM=true volumes: - "./grafana/datasources.yaml:/etc/grafana/provisioning/datasources/datasources.yaml" - "./grafana/dashboards.yaml:/etc/grafana/provisioning/dashboards/dashboards.yaml" diff --git a/otel4s/grafana/dashboards/cats-metrics.json b/otel4s/grafana/dashboards/application.json similarity index 61% rename from otel4s/grafana/dashboards/cats-metrics.json rename to otel4s/grafana/dashboards/application.json index 66b4d79..2e25a73 100644 --- a/otel4s/grafana/dashboards/cats-metrics.json +++ b/otel4s/grafana/dashboards/application.json @@ -19,7 +19,7 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 1, - "id": 2, + "id": 1, "links": [], "panels": [ { @@ -30,6 +30,1788 @@ "x": 0, "y": 0 }, + "id": 59, + "panels": [], + "title": "Tracing", + "type": "row" + }, + { + "datasource": { + "type": "tempo", + "uid": "cezfsujm3zrb4d" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 50, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "pointShape": "circle", + "pointSize": { + "fixed": 10 + }, + "pointStrokeWidth": 1, + "scaleDistribution": { + "log": 2, + "type": "log" + }, + "show": "points" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 1 + }, + "id": 55, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "mapping": "auto", + "series": [ + {} + ], + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "tempo", + "uid": "cezfsujm3zrb4d" + }, + "filters": [ + { + "id": "f1a5cec4", + "operator": "=", + "scope": "span" + } + ], + "limit": 500, + "metricsQueryType": "range", + "queryType": "traceqlSearch", + "refId": "A", + "spss": 5, + "tableType": "traces" + } + ], + "title": "Traces", + "transformations": [ + { + "id": "partitionByValues", + "options": { + "fields": [ + "Name" + ], + "keepFields": false, + "naming": { + "asLabels": false + } + } + } + ], + "type": "xychart" + }, + { + "datasource": { + "type": "tempo", + "uid": "cezfsujm3zrb4d" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Trace Service" + }, + "properties": [ + { + "id": "custom.width", + "value": 159 + } + ] + } + ] + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 0, + "y": 11 + }, + "id": 56, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "Start time" + } + ] + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "tempo", + "uid": "cezfsujm3zrb4d" + }, + "filters": [ + { + "id": "9d3c8809", + "operator": "=", + "scope": "span" + } + ], + "limit": 500, + "metricsQueryType": "range", + "queryType": "traceqlSearch", + "refId": "A", + "spss": 5, + "tableType": "traces" + } + ], + "title": "Traces Table", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "Name": false, + "Service": true, + "Trace Service": true, + "traceIdHidden": true + }, + "includeByName": {}, + "indexByName": {}, + "orderByMode": "manual", + "renameByName": { + "traceIdHidden": "" + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "tempo", + "uid": "cezfsujm3zrb4d" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 12, + "y": 11 + }, + "id": 58, + "options": { + "spanFilters": { + "criticalPathOnly": false, + "matchesOnly": false, + "serviceNameOperator": "=", + "spanNameOperator": "=", + "tags": [ + { + "id": "b810943f-e7c", + "operator": "=" + } + ] + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "tempo", + "uid": "cezfsujm3zrb4d" + }, + "filters": [ + { + "id": "02551e08", + "operator": "=", + "scope": "span" + }, + { + "id": "span-name", + "isCustomValue": false, + "operator": "=", + "scope": "span", + "tag": "name", + "value": [], + "valueType": "string" + }, + { + "id": "status", + "isCustomValue": false, + "operator": "=", + "scope": "intrinsic", + "tag": "status", + "valueType": "keyword" + } + ], + "limit": 20, + "metricsQueryType": "range", + "query": "${traceId}", + "queryType": "traceql", + "refId": "A", + "tableType": "traces" + } + ], + "title": "Trace", + "type": "traces" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 23 + }, + "id": 44, + "panels": [], + "title": "JVM Metrics - Garbage Collection", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 7, + "x": 0, + "y": 24 + }, + "id": 43, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "rate(jvm_gc_duration_seconds_count[1m])", + "fullMetaSearch": false, + "includeNullMetadata": false, + "instant": false, + "interval": "", + "legendFormat": "{{jvm_gc_action}} - {{jvm_gc_name}}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Collections", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 8, + "x": 7, + "y": 24 + }, + "id": 45, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "editorMode": "code", + "expr": "rate(jvm_gc_duration_seconds_sum{}[1m])/rate(jvm_gc_duration_seconds_count{}[1m])", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Pause Durations", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 9, + "x": 15, + "y": 24 + }, + "id": 51, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "editorMode": "code", + "expr": "jvm_memory_used_after_last_gc_bytes", + "instant": false, + "interval": "", + "legendFormat": "{{jvm_memory_pool_name}} - {{jvm_memory_type}}", + "range": true, + "refId": "A" + } + ], + "title": "Memory Used After Last GC", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 34 + }, + "id": 40, + "panels": [], + "title": "JVM Metrics - Classloading", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 0, + "y": 35 + }, + "id": 46, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "editorMode": "builder", + "expr": "jvm_class_loaded_total", + "instant": false, + "interval": "", + "legendFormat": "loaded", + "range": true, + "refId": "A" + } + ], + "title": "Classes loaded", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 8, + "y": 35 + }, + "id": 47, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "delta(jvm_class_count[1m])", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "delta-1m", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Class delta", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 0, + "y": 44 + }, + "id": 53, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "jvm_class_unloaded_total", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "interval": "", + "legendFormat": "unloaded", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Classes unloaded", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 8, + "y": 44 + }, + "id": 54, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "jvm_class_count", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "delta-1m", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Currently Loaded Classes", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 53 + }, + "id": 39, + "panels": [], + "title": "JVM Metrics - CPU", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 4, + "x": 0, + "y": 54 + }, + "id": 41, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "jvm_cpu_count", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "CPU Count", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 1, + "mappings": [], + "max": 1, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 9, + "x": 4, + "y": 54 + }, + "id": 49, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "editorMode": "code", + "expr": "jvm_cpu_recent_utilization_ratio", + "instant": false, + "legendFormat": "process", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "avg_over_time(jvm_cpu_recent_utilization_ratio[15m])", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "process-15m", + "range": true, + "refId": "B", + "useBackend": false + } + ], + "title": "CPU Usage", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "description": "The percent of time spent on Garbage Collection over all CPUs assigned to the JVM process.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 1, + "mappings": [], + "max": 1, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 11, + "x": 13, + "y": 54 + }, + "id": 48, + "options": { + "legend": { + "calcs": [ + "lastNotNull", + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "sum by(job) (rate(jvm_gc_duration_seconds_count{job=\"$job\"}[1m])) / on(job) jvm_cpu_count", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "GC Pressure", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 62 + }, + "id": 38, + "panels": [], + "title": "JVM Metrics - Memory", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 0, + "y": 63 + }, + "id": 52, + "options": { + "legend": { + "calcs": [ + "lastNotNull", + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "jvm_memory_used_bytes{jvm_memory_type=\"non_heap\"}", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{jvm_memory_pool_name}} - used", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "jvm_memory_committed_bytes{jvm_memory_type=\"non_heap\"}", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{jvm_memory_pool_name}} - commited", + "range": true, + "refId": "D", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "jvm_memory_limit_bytes{jvm_memory_type=\"non_heap\"}", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{jvm_memory_pool_name}} - limit", + "range": true, + "refId": "B", + "useBackend": false + } + ], + "title": "JVM Memory Pool Non Heap", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 12, + "y": 63 + }, + "id": 50, + "options": { + "legend": { + "calcs": [ + "lastNotNull", + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "jvm_memory_used_bytes{jvm_memory_type=\"heap\"}", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{jvm_memory_pool_name}} - used", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "jvm_memory_committed_bytes{jvm_memory_type=\"heap\"}", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{jvm_memory_pool_name}} - commited", + "range": true, + "refId": "D", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "jvm_memory_limit_bytes{jvm_memory_type=\"heap\"}", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{jvm_memory_pool_name}} - limit", + "range": true, + "refId": "B", + "useBackend": false + } + ], + "title": "JVM Memory Pool Heap", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 75 + }, + "id": 37, + "panels": [], + "title": "JVM Metrics - Thread", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 25, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 7, + "x": 0, + "y": 76 + }, + "id": 42, + "options": { + "legend": { + "calcs": [], + "displayMode": "hidden", + "placement": "right", + "showLegend": false + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prometheus}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "jvm_thread_count", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{jvm_thread_state}}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Thread Count", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 85 + }, "id": 8, "panels": [], "title": "CPU starvation", @@ -44,7 +1826,7 @@ "h": 8, "w": 7, "x": 0, - "y": 1 + "y": 86 }, "id": 13, "options": { @@ -101,7 +1883,7 @@ "h": 8, "w": 4, "x": 7, - "y": 1 + "y": 86 }, "id": 21, "options": { @@ -218,7 +2000,7 @@ "h": 8, "w": 6, "x": 11, - "y": 1 + "y": 86 }, "id": 9, "options": { @@ -318,7 +2100,7 @@ "h": 8, "w": 7, "x": 17, - "y": 1 + "y": 86 }, "id": 10, "options": { @@ -376,7 +2158,7 @@ "h": 1, "w": 24, "x": 0, - "y": 9 + "y": 94 }, "id": 2, "panels": [], @@ -392,7 +2174,7 @@ "h": 8, "w": 12, "x": 0, - "y": 10 + "y": 95 }, "id": 7, "options": { @@ -438,7 +2220,7 @@ "h": 8, "w": 4, "x": 12, - "y": 10 + "y": 95 }, "id": 1, "options": { @@ -465,14 +2247,18 @@ "type": "prometheus", "uid": "${prometheus}" }, - "editorMode": "code", + "disableTextWrap": false, + "editorMode": "builder", "exemplar": false, - "expr": "cats_effect_runtime_wstp_compute_thread_count{job=\"${job:value}\", pool_id=\"${wstp_pool:value}\"}", + "expr": "cats_effect_runtime_wstp_compute_thread_count", "format": "time_series", + "fullMetaSearch": false, + "includeNullMetadata": true, "instant": true, "legendFormat": "__auto", "range": false, - "refId": "A" + "refId": "A", + "useBackend": false } ], "title": "Worker threads", @@ -506,7 +2292,7 @@ "h": 8, "w": 4, "x": 16, - "y": 10 + "y": 95 }, "id": 19, "options": { @@ -588,7 +2374,7 @@ "h": 8, "w": 4, "x": 20, - "y": 10 + "y": 95 }, "id": 20, "options": { @@ -707,7 +2493,7 @@ "h": 8, "w": 12, "x": 0, - "y": 18 + "y": 103 }, "id": 3, "options": { @@ -812,7 +2598,7 @@ "h": 8, "w": 12, "x": 12, - "y": 18 + "y": 103 }, "id": 5, "options": { @@ -914,7 +2700,7 @@ "h": 8, "w": 12, "x": 0, - "y": 26 + "y": 111 }, "id": 4, "options": { @@ -1019,7 +2805,7 @@ "h": 8, "w": 12, "x": 12, - "y": 26 + "y": 111 }, "id": 6, "options": { @@ -1121,7 +2907,7 @@ "h": 8, "w": 12, "x": 0, - "y": 34 + "y": 119 }, "id": 31, "options": { @@ -1163,7 +2949,7 @@ "h": 1, "w": 24, "x": 0, - "y": 42 + "y": 127 }, "id": 22, "panels": [], @@ -1235,7 +3021,7 @@ "h": 11, "w": 12, "x": 0, - "y": 43 + "y": 128 }, "id": 23, "options": { @@ -1338,7 +3124,7 @@ "h": 11, "w": 12, "x": 12, - "y": 43 + "y": 128 }, "id": 24, "options": { @@ -1381,7 +3167,7 @@ "h": 1, "w": 24, "x": 0, - "y": 54 + "y": 139 }, "id": 11, "panels": [], @@ -1456,7 +3242,7 @@ "h": 10, "w": 12, "x": 0, - "y": 55 + "y": 140 }, "id": 14, "options": { @@ -1560,7 +3346,7 @@ "h": 10, "w": 12, "x": 12, - "y": 55 + "y": 140 }, "id": 15, "options": { @@ -1664,7 +3450,7 @@ "h": 9, "w": 12, "x": 0, - "y": 65 + "y": 150 }, "id": 18, "options": { @@ -1768,7 +3554,7 @@ "h": 9, "w": 12, "x": 12, - "y": 65 + "y": 150 }, "id": 17, "options": { @@ -1873,7 +3659,7 @@ "h": 10, "w": 12, "x": 0, - "y": 74 + "y": 159 }, "id": 16, "options": { @@ -1915,7 +3701,7 @@ "h": 1, "w": 24, "x": 0, - "y": 84 + "y": 169 }, "id": 26, "panels": [], @@ -1988,7 +3774,7 @@ "h": 9, "w": 12, "x": 0, - "y": 85 + "y": 170 }, "id": 27, "options": { @@ -2089,7 +3875,7 @@ "h": 9, "w": 12, "x": 12, - "y": 85 + "y": 170 }, "id": 28, "options": { @@ -2193,7 +3979,7 @@ "h": 9, "w": 12, "x": 0, - "y": 94 + "y": 179 }, "id": 30, "options": { @@ -2296,7 +4082,7 @@ "h": 9, "w": 12, "x": 12, - "y": 94 + "y": 179 }, "id": 29, "options": { @@ -2338,7 +4124,7 @@ "h": 1, "w": 24, "x": 0, - "y": 103 + "y": 188 }, "id": 32, "panels": [], @@ -2410,7 +4196,7 @@ "h": 8, "w": 12, "x": 0, - "y": 104 + "y": 189 }, "id": 33, "options": { @@ -2514,7 +4300,7 @@ "h": 8, "w": 12, "x": 12, - "y": 104 + "y": 189 }, "id": 36, "options": { @@ -2583,14 +4369,14 @@ "type": "prometheus", "uid": "f13f44c8-69b1-48ae-a55c-90f49179283c" }, - "definition": "label_values(cats_effect_runtime_wstp_compute_thread_active_count,job)", + "definition": "label_values(cats_effect_runtime_cpu_starvation_clock_drift_max_milliseconds,job)", "includeAll": false, "label": "Job", "name": "job", "options": [], "query": { "qryType": 1, - "query": "label_values(cats_effect_runtime_wstp_compute_thread_active_count,job)", + "query": "label_values(cats_effect_runtime_cpu_starvation_clock_drift_max_milliseconds,job)", "refId": "PrometheusVariableQueryEditor-VariableQuery" }, "refresh": 2, @@ -2622,9 +4408,7 @@ { "allValue": ".*", "current": { - "text": [ - "All" - ], + "text": "All", "value": [ "$__all" ] @@ -2685,16 +4469,27 @@ ], "query": "1m, 2m, 5m, 10m, 30m", "type": "custom" + }, + { + "current": { + "text": "", + "value": "" + }, + "label": "Trace ID", + "name": "traceId", + "options": [], + "query": "", + "type": "custom" } ] }, "time": { - "from": "now-1m", + "from": "now-3h", "to": "now" }, "timepicker": {}, "timezone": "", - "title": "Cats Effect Runtime Metrics", + "title": "Application", "uid": "fe8ds2gwa0tmof", - "version": 1 + "version": 15 } diff --git a/otel4s/grafana/dashboards/jvm-metrics.json b/otel4s/grafana/dashboards/jvm-metrics.json deleted file mode 100644 index e69de29..0000000 diff --git a/otel4s/grafana/dashboards/tracing.json b/otel4s/grafana/dashboards/tracing.json deleted file mode 100644 index 2de3465..0000000 --- a/otel4s/grafana/dashboards/tracing.json +++ /dev/null @@ -1,151 +0,0 @@ -{ - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": { - "type": "grafana", - "uid": "-- Grafana --" - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] - }, - "editable": true, - "fiscalYearStartMonth": 0, - "graphTooltip": 0, - "id": 6, - "links": [], - "panels": [ - { - "datasource": { - "type": "jaeger", - "uid": "d4cbeedf-98f0-4d71-accc-2eef0a37506a" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "fillOpacity": 50, - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "pointShape": "circle", - "pointSize": { - "fixed": 10 - }, - "pointStrokeWidth": 2, - "scaleDistribution": { - "type": "linear" - }, - "show": "points" - }, - "links": [ - { - "oneClick": false, - "targetBlank": true, - "title": "Show trace", - "url": "${__data.fields[0]} " - } - ], - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": 0 - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "µs" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 0 - }, - "id": 1, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "mapping": "auto", - "series": [ - {} - ], - "tooltip": { - "hideZeros": false, - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "12.1.1", - "targets": [ - { - "datasource": { - "type": "jaeger", - "uid": "d4cbeedf-98f0-4d71-accc-2eef0a37506a" - }, - "queryType": "search", - "refId": "A", - "service": "simple-language-server" - } - ], - "title": "New Panel", - "transformations": [ - { - "id": "partitionByValues", - "options": { - "fields": [ - "Trace name" - ], - "keepFields": false, - "naming": { - "asLabels": true - } - } - } - ], - "type": "xychart" - } - ], - "preload": false, - "schemaVersion": 41, - "tags": [], - "templating": { - "list": [] - }, - "time": { - "from": "2025-09-09T19:35:48.405Z", - "to": "2025-09-09T21:07:41.621Z" - }, - "timepicker": {}, - "timezone": "browser", - "title": "Tracing", - "uid": "99e5a2eb-54b5-44d3-a53f-98aa85caf61c", - "version": 3 -} diff --git a/otel4s/grafana/datasources.yaml b/otel4s/grafana/datasources.yaml index 60f8733..84da070 100644 --- a/otel4s/grafana/datasources.yaml +++ b/otel4s/grafana/datasources.yaml @@ -18,22 +18,43 @@ datasources: user: '' database: '' basicAuth: false + basicAuthUser: '' isDefault: false jsonData: httpMethod: POST readOnly: false - id: 2 - uid: d4cbeedf-98f0-4d71-accc-2eef0a37506a + uid: dezft048pwcg0d orgId: 1 - type: jaeger - name: Jaeger - typeName: Jaeger - typeLogoUrl: "/public/app/plugins/datasource/jaeger/img/jaeger_logo.svg" + type: grafana-pyroscope-datasource + name: Pyroscope + typeLogoUrl: "/public/plugins/grafana-pyroscope-datasource/img/grafana_pyroscope_icon.svg" access: proxy - url: http://jaeger:16686 + url: http://pyroscope:4040 user: '' database: '' basicAuth: false + basicAuthUser: '' isDefault: false - jsonData: {} + jsonData: + pdcInjected: false + readOnly: false + - id: 3 + uid: cezfsujm3zrb4d + orgId: 1 + name: Tempo + type: tempo + typeLogoUrl: "/public/plugins/tempo/img/tempo_logo.svg" + access: proxy + url: http://tempo:3200 + user: '' + database: '' + basicAuth: false + basicAuthUser: '' + withCredentials: false + isDefault: false + jsonData: + pdcInjected: false + tracesToProfiles: + datasourceUid: dezft048pwcg0d readOnly: false diff --git a/otel4s/tempo/tempo.yaml b/otel4s/tempo/tempo.yaml index e3a5ec9..c17bd0f 100644 --- a/otel4s/tempo/tempo.yaml +++ b/otel4s/tempo/tempo.yaml @@ -11,6 +11,7 @@ distributor: endpoint: 0.0.0.0:4317 metrics_generator: + registry: external_labels: source: tempo @@ -21,6 +22,14 @@ metrics_generator: - url: http://prometheus:9090/api/v1/write send_exemplars: true + processor: + span_metrics: + dimensions: + - service.name + - span.name + - span.kind + - status.code + storage: trace: backend: local # backend configuration to use @@ -28,3 +37,8 @@ storage: path: /tmp/tempo/wal # where to store the the wal locally local: path: /tmp/tempo/blocks + +overrides: + defaults: + metrics_generator: + processors: [span-metrics] # enables metrics generator diff --git a/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingIOApp.scala b/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingIOApp.scala index 6f2f66d..290b14a 100644 --- a/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingIOApp.scala +++ b/profilingRuntime/src/org/scala/abusers/profiling/runtime/ProfilingIOApp.scala @@ -21,6 +21,9 @@ import org.typelevel.otel4s.sdk.context.Context import org.typelevel.otel4s.sdk.exporter.otlp.autoconfigure.OtlpExportersAutoConfigure import org.typelevel.otel4s.sdk.OpenTelemetrySdk import org.typelevel.otel4s.trace.Tracer +import cats.effect.std.Console +import org.typelevel.otel4s.instrumentation.ce.IORuntimeMetrics +import org.typelevel.otel4s.metrics.MeterProvider object ProfilingIOAppSettings { lazy val isEnabled: Boolean = @@ -30,6 +33,8 @@ object ProfilingIOAppSettings { trait ProfilingIOApp extends IOApp { private lazy val profiler = PyroscopeAsyncProfiler.getAsyncProfiler() + given console: Console[IO] + private given localCtx: IOLocal[Context] = IOLocal[Context](Context.root) .syncStep(100) .flatMap( @@ -43,11 +48,12 @@ trait ProfilingIOApp extends IOApp { private val threadLocal = localCtx.unsafeThreadLocal() - private lazy val _runtime = + private lazy val _runtime = { IORuntimeBuilder() .transformCompute(ec => ProfilingExecutionContext.wrapExecutionContext(ec, profiler, threadLocal)) .transformBlocking(ec => ProfilingExecutionContext.wrapExecutionContext(ec, profiler, threadLocal)) .build() + } override protected def runtime: IORuntime = if ProfilingIOAppSettings.isEnabled then _runtime else super.runtime @@ -57,19 +63,21 @@ trait ProfilingIOApp extends IOApp { override def run(args: List[String]): IO[ExitCode] = OpenTelemetrySdk - .autoConfigured[IO]( - _.addExportersConfigurer(OtlpExportersAutoConfigure[IO]) - .addTracerProviderCustomizer((builder, _) => builder.addSpanProcessor(new ProfilingSpanProcessor)) + .autoConfigured[IO](_ + .addExportersConfigurer(OtlpExportersAutoConfigure[IO]) + .addTracerProviderCustomizer((builder, _) => builder.addSpanProcessor(new ProfilingSpanProcessor)) ) .flatTap(_ => registerPyroscope()) .use { autoConfigured => val sdk = autoConfigured.sdk + given MeterProvider[IO] = sdk.meterProvider val app = for { - given Meter[IO] <- sdk.meterProvider.get(applicationName).toResource - given Tracer[IO] <- sdk.tracerProvider.get(applicationName).toResource - _ <- RuntimeMetrics.register[IO] - _ <- program + given Meter[IO] <- sdk.meterProvider.get(applicationName).toResource + given Tracer[IO] <- sdk.tracerProvider.get(applicationName).toResource + _ <- IORuntimeMetrics.register[IO](runtime.metrics, IORuntimeMetrics.Config.default) + _ <- RuntimeMetrics.register[IO] + _ <- program } yield () app.useForever @@ -77,7 +85,7 @@ trait ProfilingIOApp extends IOApp { .as(ExitCode.Success) private def registerPyroscope(): Resource[IO, Unit] = { - val acquire = IO.delay { + val acquire = IO.whenA(ProfilingIOAppSettings.isEnabled)( IO.delay { PyroscopeAgent.start( PyroscopeConfig .Builder() @@ -87,7 +95,7 @@ trait ProfilingIOApp extends IOApp { .setServerAddress("http://localhost:4040") // Refactor in the future to be configurable .build() ) - } + }) Resource.eval(acquire).void } diff --git a/sls/src/org/scala/abusers/sls/ErrOutConsole.scala b/sls/src/org/scala/abusers/sls/ErrOutConsole.scala new file mode 100644 index 0000000..0291838 --- /dev/null +++ b/sls/src/org/scala/abusers/sls/ErrOutConsole.scala @@ -0,0 +1,18 @@ +package org.scala.abusers.sls + +import cats.effect.* +import cats.Show +import cats.effect.std.Console +import java.nio.charset.Charset + +class ErrorOutConsole extends Console[IO] { + override def readLineWithCharset(charset: Charset): IO[String] = IO.raiseError(sys.error("Not implemented")) + + override def print[A](a: A)(implicit S: Show[A]): IO[Unit] = IO.consoleForIO.error(a) + + override def println[A](a: A)(implicit S: Show[A]): IO[Unit] = IO.consoleForIO.errorln(a) + + override def error[A](a: A)(implicit S: Show[A]): IO[Unit] = IO.consoleForIO.error(a) + + override def errorln[A](a: A)(implicit S: Show[A]): IO[Unit] = IO.consoleForIO.errorln(a) +} diff --git a/sls/src/org/scala/abusers/sls/SimpleLanguageServer.scala b/sls/src/org/scala/abusers/sls/SimpleLanguageServer.scala index b4f962a..be1003b 100644 --- a/sls/src/org/scala/abusers/sls/SimpleLanguageServer.scala +++ b/sls/src/org/scala/abusers/sls/SimpleLanguageServer.scala @@ -10,6 +10,7 @@ import org.scala.abusers.pc.PresentationCompilerProvider import org.scala.abusers.profiling.runtime.ProfilingIOApp import org.typelevel.otel4s.metrics.Meter import org.typelevel.otel4s.trace.Tracer +import cats.effect.std.Console case class BuildServer( generic: bsp.BuildServer[IO], @@ -46,6 +47,8 @@ object SimpleScalaServer extends ProfilingIOApp { override def applicationName: String = "simple-language-server" + override given console: Console[IO] = ErrorOutConsole() + override def program(using meter: Meter[IO], tracer: Tracer[IO]) = for { fs2Channel <- FS2Channel.resource[IO](cancelTemplate = LSPCancelRequest.cancelTemplate.some) From fedb0ae5c816d27465769602f7a3756897d7768d Mon Sep 17 00:00:00 2001 From: rochala Date: Fri, 17 Oct 2025 01:13:46 +0200 Subject: [PATCH 6/6] interfaces version --- build.mill | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.mill b/build.mill index d3fae12..d5abd59 100644 --- a/build.mill +++ b/build.mill @@ -84,7 +84,7 @@ object sls extends CommonScalaModule { ivy"ch.qos.logback:logback-classic:1.4.14", ivy"com.lihaoyi::os-lib:0.11.4", ivy"org.polyvariant.smithy4s-bsp::bsp4s:0.5.0", - ivy"org.scalameta:mtags-interfaces:1.6.1-SNAPSHOT", + ivy"org.scalameta:mtags-interfaces:1.6.3", ivy"com.evolution::scache:5.1.2", ivy"org.typelevel::cats-parse:1.1.0", ivy"io.get-coursier:interface:1.0.28",