diff --git a/doc-snippets/prometheus-migration/build.gradle.kts b/doc-snippets/prometheus-migration/build.gradle.kts new file mode 100644 index 000000000..fa00fb831 --- /dev/null +++ b/doc-snippets/prometheus-migration/build.gradle.kts @@ -0,0 +1,18 @@ +plugins { + id("java") +} + +val moduleName by extra { "io.opentelemetry.examples.docs.prometheus.migration" } + +dependencies { + implementation(platform("io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom:2.22.0")) + + implementation("io.opentelemetry:opentelemetry-api") + implementation("io.opentelemetry:opentelemetry-sdk") + implementation("io.opentelemetry:opentelemetry-exporter-prometheus") + implementation("io.opentelemetry:opentelemetry-exporter-otlp") + + implementation("io.prometheus:prometheus-metrics-core:1.3.1") + implementation("io.prometheus:prometheus-metrics-exporter-httpserver:1.3.1") + implementation("io.prometheus:prometheus-metrics-exporter-opentelemetry:1.3.1") +} diff --git a/doc-snippets/prometheus-migration/src/main/java/otel/OtelCounter.java b/doc-snippets/prometheus-migration/src/main/java/otel/OtelCounter.java new file mode 100644 index 000000000..d2eb8bbb0 --- /dev/null +++ b/doc-snippets/prometheus-migration/src/main/java/otel/OtelCounter.java @@ -0,0 +1,30 @@ +package otel; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.DoubleCounter; +import io.opentelemetry.api.metrics.Meter; + +public class OtelCounter { + // Preallocate attribute keys and, when values are static, entire Attributes objects. + private static final AttributeKey ZONE = AttributeKey.stringKey("zone"); + private static final Attributes UPSTAIRS = Attributes.of(ZONE, "upstairs"); + private static final Attributes DOWNSTAIRS = Attributes.of(ZONE, "downstairs"); + + public static void counterUsage(OpenTelemetry openTelemetry) { + Meter meter = openTelemetry.getMeter("smart.home"); + // HVAC on-time is fractional — use ofDoubles() to get a DoubleCounter. + // No upfront label declaration: attributes are provided at record time. + DoubleCounter hvacOnTime = + meter + .counterBuilder("hvac.on") + .setDescription("Total time the HVAC system has been running") + .setUnit("s") + .ofDoubles() + .build(); + + hvacOnTime.add(127.5, UPSTAIRS); + hvacOnTime.add(3600.0, DOWNSTAIRS); + } +} diff --git a/doc-snippets/prometheus-migration/src/main/java/otel/OtelCounterCallback.java b/doc-snippets/prometheus-migration/src/main/java/otel/OtelCounterCallback.java new file mode 100644 index 000000000..cb36fcf7a --- /dev/null +++ b/doc-snippets/prometheus-migration/src/main/java/otel/OtelCounterCallback.java @@ -0,0 +1,19 @@ +package otel; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.metrics.Meter; + +public class OtelCounterCallback { + public static void counterCallbackUsage(OpenTelemetry openTelemetry) { + Meter meter = openTelemetry.getMeter("smart.home"); + // The smart energy meter maintains its own cumulative joule total in firmware. + // Use an asynchronous counter to report that value when a MetricReader + // collects metrics, without maintaining a separate counter in application code. + meter + .counterBuilder("energy.consumed") + .setDescription("Total energy consumed") + .setUnit("J") + .ofDoubles() + .buildWithCallback(measurement -> measurement.record(SmartHomeDevices.totalEnergyJoules())); + } +} diff --git a/doc-snippets/prometheus-migration/src/main/java/otel/OtelGauge.java b/doc-snippets/prometheus-migration/src/main/java/otel/OtelGauge.java new file mode 100644 index 000000000..ce3b130d3 --- /dev/null +++ b/doc-snippets/prometheus-migration/src/main/java/otel/OtelGauge.java @@ -0,0 +1,27 @@ +package otel; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.DoubleGauge; +import io.opentelemetry.api.metrics.Meter; + +public class OtelGauge { + // Preallocate attribute keys and, when values are static, entire Attributes objects. + private static final AttributeKey ZONE = AttributeKey.stringKey("zone"); + private static final Attributes UPSTAIRS = Attributes.of(ZONE, "upstairs"); + private static final Attributes DOWNSTAIRS = Attributes.of(ZONE, "downstairs"); + + public static void gaugeUsage(OpenTelemetry openTelemetry) { + Meter meter = openTelemetry.getMeter("smart.home"); + DoubleGauge thermostatSetpoint = + meter + .gaugeBuilder("thermostat.setpoint") + .setDescription("Target temperature set on the thermostat") + .setUnit("Cel") + .build(); + + thermostatSetpoint.set(22.5, UPSTAIRS); + thermostatSetpoint.set(20.0, DOWNSTAIRS); + } +} diff --git a/doc-snippets/prometheus-migration/src/main/java/otel/OtelGaugeCallback.java b/doc-snippets/prometheus-migration/src/main/java/otel/OtelGaugeCallback.java new file mode 100644 index 000000000..28c315bd9 --- /dev/null +++ b/doc-snippets/prometheus-migration/src/main/java/otel/OtelGaugeCallback.java @@ -0,0 +1,28 @@ +package otel; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.Meter; + +public class OtelGaugeCallback { + private static final AttributeKey ROOM = AttributeKey.stringKey("room"); + private static final Attributes LIVING_ROOM = Attributes.of(ROOM, "living_room"); + private static final Attributes BEDROOM = Attributes.of(ROOM, "bedroom"); + + public static void gaugeCallbackUsage(OpenTelemetry openTelemetry) { + Meter meter = openTelemetry.getMeter("smart.home"); + // Temperature sensors maintain their own readings in firmware. + // Use an asynchronous gauge to report those values when a MetricReader + // collects metrics, without maintaining separate gauges in application code. + meter + .gaugeBuilder("room.temperature") + .setDescription("Current temperature in the room") + .setUnit("Cel") + .buildWithCallback( + measurement -> { + measurement.record(SmartHomeDevices.livingRoomTemperatureCelsius(), LIVING_ROOM); + measurement.record(SmartHomeDevices.bedroomTemperatureCelsius(), BEDROOM); + }); + } +} diff --git a/doc-snippets/prometheus-migration/src/main/java/otel/OtelHistogram.java b/doc-snippets/prometheus-migration/src/main/java/otel/OtelHistogram.java new file mode 100644 index 000000000..ff6793182 --- /dev/null +++ b/doc-snippets/prometheus-migration/src/main/java/otel/OtelHistogram.java @@ -0,0 +1,31 @@ +package otel; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.DoubleHistogram; +import io.opentelemetry.api.metrics.Meter; +import java.util.List; + +public class OtelHistogram { + // Preallocate attribute keys and, when values are static, entire Attributes objects. + private static final AttributeKey DEVICE_TYPE = AttributeKey.stringKey("device_type"); + private static final Attributes THERMOSTAT = Attributes.of(DEVICE_TYPE, "thermostat"); + private static final Attributes LOCK = Attributes.of(DEVICE_TYPE, "lock"); + + public static void histogramUsage(OpenTelemetry openTelemetry) { + Meter meter = openTelemetry.getMeter("smart.home"); + // setExplicitBucketBoundariesAdvice() sets default boundaries as a hint to the SDK. + // Views configured at the SDK level take precedence over this advice. + DoubleHistogram deviceCommandDuration = + meter + .histogramBuilder("device.command.duration") + .setDescription("Time to receive acknowledgment from a smart home device") + .setUnit("s") + .setExplicitBucketBoundariesAdvice(List.of(0.1, 0.25, 0.5, 1.0, 2.5, 5.0)) + .build(); + + deviceCommandDuration.record(0.35, THERMOSTAT); + deviceCommandDuration.record(0.85, LOCK); + } +} diff --git a/doc-snippets/prometheus-migration/src/main/java/otel/OtelHistogramAsSummary.java b/doc-snippets/prometheus-migration/src/main/java/otel/OtelHistogramAsSummary.java new file mode 100644 index 000000000..487c27333 --- /dev/null +++ b/doc-snippets/prometheus-migration/src/main/java/otel/OtelHistogramAsSummary.java @@ -0,0 +1,30 @@ +package otel; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.DoubleHistogram; +import io.opentelemetry.api.metrics.Meter; +import java.util.List; + +public class OtelHistogramAsSummary { + private static final AttributeKey DEVICE_TYPE = AttributeKey.stringKey("device_type"); + private static final Attributes THERMOSTAT = Attributes.of(DEVICE_TYPE, "thermostat"); + private static final Attributes LOCK = Attributes.of(DEVICE_TYPE, "lock"); + + public static void summaryReplacement(OpenTelemetry openTelemetry) { + Meter meter = openTelemetry.getMeter("smart.home"); + // No explicit bucket boundaries: captures count and sum, a good stand-in for most + // Summary use cases. For quantile estimation, add boundaries that bracket your thresholds. + DoubleHistogram deviceCommandDuration = + meter + .histogramBuilder("device.command.duration") + .setDescription("Time to receive acknowledgment from a smart home device") + .setUnit("s") + .setExplicitBucketBoundariesAdvice(List.of()) + .build(); + + deviceCommandDuration.record(0.35, THERMOSTAT); + deviceCommandDuration.record(0.85, LOCK); + } +} diff --git a/doc-snippets/prometheus-migration/src/main/java/otel/OtelHistogramExplicitBucketView.java b/doc-snippets/prometheus-migration/src/main/java/otel/OtelHistogramExplicitBucketView.java new file mode 100644 index 000000000..409290e76 --- /dev/null +++ b/doc-snippets/prometheus-migration/src/main/java/otel/OtelHistogramExplicitBucketView.java @@ -0,0 +1,21 @@ +package otel; + +import io.opentelemetry.sdk.metrics.Aggregation; +import io.opentelemetry.sdk.metrics.InstrumentSelector; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.metrics.View; +import java.util.List; + +public class OtelHistogramExplicitBucketView { + static SdkMeterProvider createMeterProvider() { + // Override the bucket boundaries advised on the instrument for a specific histogram. + return SdkMeterProvider.builder() + .registerView( + InstrumentSelector.builder().setName("device.command.duration").build(), + View.builder() + .setAggregation( + Aggregation.explicitBucketHistogram(List.of(0.1, 0.25, 0.5, 1.0, 2.5, 5.0))) + .build()) + .build(); + } +} diff --git a/doc-snippets/prometheus-migration/src/main/java/otel/OtelHistogramExponentialExporter.java b/doc-snippets/prometheus-migration/src/main/java/otel/OtelHistogramExponentialExporter.java new file mode 100644 index 000000000..65c7cddd0 --- /dev/null +++ b/doc-snippets/prometheus-migration/src/main/java/otel/OtelHistogramExponentialExporter.java @@ -0,0 +1,19 @@ +package otel; + +import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter; +import io.opentelemetry.sdk.metrics.Aggregation; +import io.opentelemetry.sdk.metrics.InstrumentType; +import io.opentelemetry.sdk.metrics.export.DefaultAggregationSelector; + +public class OtelHistogramExponentialExporter { + static OtlpHttpMetricExporter createExporter() { + // Configure the exporter to use exponential histograms for all histogram instruments. + // This is the preferred approach — it applies globally without modifying instrumentation code. + return OtlpHttpMetricExporter.builder() + .setEndpoint("http://localhost:4318") + .setDefaultAggregationSelector( + DefaultAggregationSelector.getDefault() + .with(InstrumentType.HISTOGRAM, Aggregation.base2ExponentialBucketHistogram())) + .build(); + } +} diff --git a/doc-snippets/prometheus-migration/src/main/java/otel/OtelHistogramExponentialView.java b/doc-snippets/prometheus-migration/src/main/java/otel/OtelHistogramExponentialView.java new file mode 100644 index 000000000..9c65eedcb --- /dev/null +++ b/doc-snippets/prometheus-migration/src/main/java/otel/OtelHistogramExponentialView.java @@ -0,0 +1,18 @@ +package otel; + +import io.opentelemetry.sdk.metrics.Aggregation; +import io.opentelemetry.sdk.metrics.InstrumentSelector; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.metrics.View; + +public class OtelHistogramExponentialView { + static SdkMeterProvider createMeterProvider() { + // Use a view for per-instrument control — select a specific instrument by name + // to use exponential histograms while keeping explicit buckets for others. + return SdkMeterProvider.builder() + .registerView( + InstrumentSelector.builder().setName("device.command.duration").build(), + View.builder().setAggregation(Aggregation.base2ExponentialBucketHistogram()).build()) + .build(); + } +} diff --git a/doc-snippets/prometheus-migration/src/main/java/otel/OtelOtlpInit.java b/doc-snippets/prometheus-migration/src/main/java/otel/OtelOtlpInit.java new file mode 100644 index 000000000..c0115f1d1 --- /dev/null +++ b/doc-snippets/prometheus-migration/src/main/java/otel/OtelOtlpInit.java @@ -0,0 +1,44 @@ +package otel; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.metrics.LongCounter; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader; +import java.time.Duration; + +public class OtelOtlpInit { + public static void main(String[] args) throws InterruptedException { + // Configure the SDK: export metrics over OTLP/HTTP on a fixed interval. + OpenTelemetrySdk sdk = + OpenTelemetrySdk.builder() + .setMeterProvider( + SdkMeterProvider.builder() + .registerMetricReader( + PeriodicMetricReader.builder( + OtlpHttpMetricExporter.builder() + .setEndpoint("http://localhost:4318") + .build()) + .setInterval(Duration.ofSeconds(60)) + .build()) + .build()) + .build(); + Runtime.getRuntime().addShutdownHook(new Thread(sdk::close)); + + // Instrumentation code uses the OpenTelemetry API type, not the SDK type directly. + OpenTelemetry openTelemetry = sdk; + + Meter meter = openTelemetry.getMeter("smart.home"); + LongCounter doorOpens = + meter + .counterBuilder("door.opens") + .setDescription("Total number of times a door has been opened") + .build(); + + doorOpens.add(1); + + Thread.currentThread().join(); // sleep forever + } +} diff --git a/doc-snippets/prometheus-migration/src/main/java/otel/OtelScrapeInit.java b/doc-snippets/prometheus-migration/src/main/java/otel/OtelScrapeInit.java new file mode 100644 index 000000000..55f0b1bba --- /dev/null +++ b/doc-snippets/prometheus-migration/src/main/java/otel/OtelScrapeInit.java @@ -0,0 +1,43 @@ +package otel; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongCounter; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.exporter.prometheus.PrometheusHttpServer; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; + +public class OtelScrapeInit { + // Preallocate attribute keys and, when values are static, entire Attributes objects. + private static final AttributeKey DOOR = AttributeKey.stringKey("door"); + private static final Attributes FRONT_DOOR = Attributes.of(DOOR, "front"); + + public static void main(String[] args) throws InterruptedException { + // Configure the SDK: register a Prometheus reader that serves /metrics. + OpenTelemetrySdk sdk = + OpenTelemetrySdk.builder() + .setMeterProvider( + SdkMeterProvider.builder() + .registerMetricReader(PrometheusHttpServer.builder().setPort(9464).build()) + .build()) + .build(); + Runtime.getRuntime().addShutdownHook(new Thread(sdk::close)); + + // Instrumentation code uses the OpenTelemetry API type, not the SDK type directly. + OpenTelemetry openTelemetry = sdk; + + // Metrics are served at http://localhost:9464/metrics. + Meter meter = openTelemetry.getMeter("smart.home"); + LongCounter doorOpens = + meter + .counterBuilder("door.opens") + .setDescription("Total number of times a door has been opened") + .build(); + + doorOpens.add(1, FRONT_DOOR); + + Thread.currentThread().join(); // sleep forever + } +} diff --git a/doc-snippets/prometheus-migration/src/main/java/otel/OtelUpDownCounter.java b/doc-snippets/prometheus-migration/src/main/java/otel/OtelUpDownCounter.java new file mode 100644 index 000000000..fc7671f64 --- /dev/null +++ b/doc-snippets/prometheus-migration/src/main/java/otel/OtelUpDownCounter.java @@ -0,0 +1,29 @@ +package otel; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongUpDownCounter; +import io.opentelemetry.api.metrics.Meter; + +public class OtelUpDownCounter { + // Preallocate attribute keys and, when values are static, entire Attributes objects. + private static final AttributeKey DEVICE_TYPE = AttributeKey.stringKey("device_type"); + private static final Attributes THERMOSTAT = Attributes.of(DEVICE_TYPE, "thermostat"); + private static final Attributes LOCK = Attributes.of(DEVICE_TYPE, "lock"); + + public static void upDownCounterUsage(OpenTelemetry openTelemetry) { + Meter meter = openTelemetry.getMeter("smart.home"); + LongUpDownCounter devicesConnected = + meter + .upDownCounterBuilder("devices.connected") + .setDescription("Number of smart home devices currently connected") + .build(); + + // add() accepts positive and negative values. + devicesConnected.add(1, THERMOSTAT); + devicesConnected.add(1, THERMOSTAT); + devicesConnected.add(1, LOCK); + devicesConnected.add(-1, LOCK); + } +} diff --git a/doc-snippets/prometheus-migration/src/main/java/otel/OtelUpDownCounterCallback.java b/doc-snippets/prometheus-migration/src/main/java/otel/OtelUpDownCounterCallback.java new file mode 100644 index 000000000..85c1a8f85 --- /dev/null +++ b/doc-snippets/prometheus-migration/src/main/java/otel/OtelUpDownCounterCallback.java @@ -0,0 +1,27 @@ +package otel; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.Meter; + +public class OtelUpDownCounterCallback { + private static final AttributeKey DEVICE_TYPE = AttributeKey.stringKey("device_type"); + private static final Attributes THERMOSTAT = Attributes.of(DEVICE_TYPE, "thermostat"); + private static final Attributes LOCK = Attributes.of(DEVICE_TYPE, "lock"); + + public static void upDownCounterCallbackUsage(OpenTelemetry openTelemetry) { + Meter meter = openTelemetry.getMeter("smart.home"); + // The device manager maintains the count of connected devices. + // Use an asynchronous up-down counter to report that value when a MetricReader + // collects metrics. + meter + .upDownCounterBuilder("devices.connected") + .setDescription("Number of smart home devices currently connected") + .buildWithCallback( + measurement -> { + measurement.record(SmartHomeDevices.connectedDeviceCount("thermostat"), THERMOSTAT); + measurement.record(SmartHomeDevices.connectedDeviceCount("lock"), LOCK); + }); + } +} diff --git a/doc-snippets/prometheus-migration/src/main/java/otel/PrometheusCounter.java b/doc-snippets/prometheus-migration/src/main/java/otel/PrometheusCounter.java new file mode 100644 index 000000000..5ea2f2366 --- /dev/null +++ b/doc-snippets/prometheus-migration/src/main/java/otel/PrometheusCounter.java @@ -0,0 +1,25 @@ +package otel; + +import io.prometheus.metrics.core.metrics.Counter; + +public class PrometheusCounter { + public static void counterUsage() { + Counter hvacOnTime = + Counter.builder() + .name("hvac_on_seconds_total") + .help("Total time the HVAC system has been running, in seconds") + .labelNames("zone") + .register(); + + // Pre-bind to label value sets: subsequent calls go directly to the data point, + // skipping the internal series lookup. + var upstairs = hvacOnTime.labelValues("upstairs"); + var downstairs = hvacOnTime.labelValues("downstairs"); + + upstairs.inc(127.5); + downstairs.inc(3600.0); + + // Pre-initialize zones so they appear in /metrics with value 0 on startup. + hvacOnTime.initLabelValues("basement"); + } +} diff --git a/doc-snippets/prometheus-migration/src/main/java/otel/PrometheusCounterCallback.java b/doc-snippets/prometheus-migration/src/main/java/otel/PrometheusCounterCallback.java new file mode 100644 index 000000000..57d40ea5c --- /dev/null +++ b/doc-snippets/prometheus-migration/src/main/java/otel/PrometheusCounterCallback.java @@ -0,0 +1,16 @@ +package otel; + +import io.prometheus.metrics.core.metrics.CounterWithCallback; + +public class PrometheusCounterCallback { + public static void counterCallbackUsage() { + // The smart energy meter maintains its own cumulative joule total in firmware. + // Use a callback counter to report that value at scrape time without + // maintaining a separate counter in application code. + CounterWithCallback.builder() + .name("energy_consumed_joules_total") + .help("Total energy consumed in joules") + .callback(callback -> callback.call(SmartHomeDevices.totalEnergyJoules())) + .register(); + } +} diff --git a/doc-snippets/prometheus-migration/src/main/java/otel/PrometheusGauge.java b/doc-snippets/prometheus-migration/src/main/java/otel/PrometheusGauge.java new file mode 100644 index 000000000..264ce2c8f --- /dev/null +++ b/doc-snippets/prometheus-migration/src/main/java/otel/PrometheusGauge.java @@ -0,0 +1,17 @@ +package otel; + +import io.prometheus.metrics.core.metrics.Gauge; + +public class PrometheusGauge { + public static void gaugeUsage() { + Gauge thermostatSetpoint = + Gauge.builder() + .name("thermostat_setpoint_celsius") + .help("Target temperature set on the thermostat") + .labelNames("zone") + .register(); + + thermostatSetpoint.labelValues("upstairs").set(22.5); + thermostatSetpoint.labelValues("downstairs").set(20.0); + } +} diff --git a/doc-snippets/prometheus-migration/src/main/java/otel/PrometheusGaugeCallback.java b/doc-snippets/prometheus-migration/src/main/java/otel/PrometheusGaugeCallback.java new file mode 100644 index 000000000..f8207dd8d --- /dev/null +++ b/doc-snippets/prometheus-migration/src/main/java/otel/PrometheusGaugeCallback.java @@ -0,0 +1,21 @@ +package otel; + +import io.prometheus.metrics.core.metrics.GaugeWithCallback; + +public class PrometheusGaugeCallback { + public static void gaugeCallbackUsage() { + // Temperature sensors maintain their own readings in firmware. + // Use a callback gauge to report those values at scrape time without + // maintaining a separate gauge in application code. + GaugeWithCallback.builder() + .name("room_temperature_celsius") + .help("Current temperature in the room") + .labelNames("room") + .callback( + callback -> { + callback.call(SmartHomeDevices.livingRoomTemperatureCelsius(), "living_room"); + callback.call(SmartHomeDevices.bedroomTemperatureCelsius(), "bedroom"); + }) + .register(); + } +} diff --git a/doc-snippets/prometheus-migration/src/main/java/otel/PrometheusHistogram.java b/doc-snippets/prometheus-migration/src/main/java/otel/PrometheusHistogram.java new file mode 100644 index 000000000..e7b598c19 --- /dev/null +++ b/doc-snippets/prometheus-migration/src/main/java/otel/PrometheusHistogram.java @@ -0,0 +1,18 @@ +package otel; + +import io.prometheus.metrics.core.metrics.Histogram; + +public class PrometheusHistogram { + public static void histogramUsage() { + Histogram deviceCommandDuration = + Histogram.builder() + .name("device_command_duration_seconds") + .help("Time to receive acknowledgment from a smart home device") + .labelNames("device_type") + .classicUpperBounds(0.1, 0.25, 0.5, 1.0, 2.5, 5.0) + .register(); + + deviceCommandDuration.labelValues("thermostat").observe(0.35); + deviceCommandDuration.labelValues("lock").observe(0.85); + } +} diff --git a/doc-snippets/prometheus-migration/src/main/java/otel/PrometheusHistogramNative.java b/doc-snippets/prometheus-migration/src/main/java/otel/PrometheusHistogramNative.java new file mode 100644 index 000000000..e71bdff55 --- /dev/null +++ b/doc-snippets/prometheus-migration/src/main/java/otel/PrometheusHistogramNative.java @@ -0,0 +1,18 @@ +package otel; + +import io.prometheus.metrics.core.metrics.Histogram; + +public class PrometheusHistogramNative { + public static void nativeHistogramUsage() { + Histogram deviceCommandDuration = + Histogram.builder() + .name("device_command_duration_seconds") + .help("Time to receive acknowledgment from a smart home device") + .labelNames("device_type") + .nativeOnly() + .register(); + + deviceCommandDuration.labelValues("thermostat").observe(0.35); + deviceCommandDuration.labelValues("lock").observe(0.85); + } +} diff --git a/doc-snippets/prometheus-migration/src/main/java/otel/PrometheusOtlpInit.java b/doc-snippets/prometheus-migration/src/main/java/otel/PrometheusOtlpInit.java new file mode 100644 index 000000000..0fd1ab1f5 --- /dev/null +++ b/doc-snippets/prometheus-migration/src/main/java/otel/PrometheusOtlpInit.java @@ -0,0 +1,30 @@ +package otel; + +import io.prometheus.metrics.core.metrics.Counter; +import io.prometheus.metrics.exporter.opentelemetry.OpenTelemetryExporter; + +public class PrometheusOtlpInit { + public static void main(String[] args) throws Exception { + // Create a counter and register it with the default PrometheusRegistry. + Counter doorOpens = + Counter.builder() + .name("door_opens_total") + .help("Total number of times a door has been opened") + .labelNames("door") + .register(); + + // Start the OTLP exporter. It reads from the default PrometheusRegistry and + // pushes metrics to the configured endpoint on a fixed interval. + OpenTelemetryExporter exporter = + OpenTelemetryExporter.builder() + .protocol("http/protobuf") + .endpoint("http://localhost:4318") + .intervalSeconds(60) + .buildAndStart(); + Runtime.getRuntime().addShutdownHook(new Thread(exporter::close)); + + doorOpens.labelValues("front").inc(); + + Thread.currentThread().join(); // sleep forever + } +} diff --git a/doc-snippets/prometheus-migration/src/main/java/otel/PrometheusScrapeInit.java b/doc-snippets/prometheus-migration/src/main/java/otel/PrometheusScrapeInit.java new file mode 100644 index 000000000..29598f2ed --- /dev/null +++ b/doc-snippets/prometheus-migration/src/main/java/otel/PrometheusScrapeInit.java @@ -0,0 +1,25 @@ +package otel; + +import io.prometheus.metrics.core.metrics.Counter; +import io.prometheus.metrics.exporter.httpserver.HTTPServer; +import java.io.IOException; + +public class PrometheusScrapeInit { + public static void main(String[] args) throws IOException, InterruptedException { + // Create a counter and register it with the default PrometheusRegistry. + Counter doorOpens = + Counter.builder() + .name("door_opens_total") + .help("Total number of times a door has been opened") + .labelNames("door") + .register(); + + // Start the HTTP server; Prometheus scrapes http://localhost:9464/metrics. + HTTPServer server = HTTPServer.builder().port(9464).buildAndStart(); + Runtime.getRuntime().addShutdownHook(new Thread(server::close)); + + doorOpens.labelValues("front").inc(); + + Thread.currentThread().join(); // sleep forever + } +} diff --git a/doc-snippets/prometheus-migration/src/main/java/otel/PrometheusSummary.java b/doc-snippets/prometheus-migration/src/main/java/otel/PrometheusSummary.java new file mode 100644 index 000000000..91def8f39 --- /dev/null +++ b/doc-snippets/prometheus-migration/src/main/java/otel/PrometheusSummary.java @@ -0,0 +1,20 @@ +package otel; + +import io.prometheus.metrics.core.metrics.Summary; + +public class PrometheusSummary { + public static void summaryUsage() { + Summary deviceCommandDuration = + Summary.builder() + .name("device_command_duration_seconds") + .help("Time to receive acknowledgment from a smart home device") + .labelNames("device_type") + .quantile(0.5, 0.05) + .quantile(0.95, 0.01) + .quantile(0.99, 0.001) + .register(); + + deviceCommandDuration.labelValues("thermostat").observe(0.35); + deviceCommandDuration.labelValues("lock").observe(0.85); + } +} diff --git a/doc-snippets/prometheus-migration/src/main/java/otel/PrometheusUpDownCounter.java b/doc-snippets/prometheus-migration/src/main/java/otel/PrometheusUpDownCounter.java new file mode 100644 index 000000000..f0cc8d746 --- /dev/null +++ b/doc-snippets/prometheus-migration/src/main/java/otel/PrometheusUpDownCounter.java @@ -0,0 +1,21 @@ +package otel; + +import io.prometheus.metrics.core.metrics.Gauge; + +public class PrometheusUpDownCounter { + public static void upDownCounterUsage() { + // Prometheus uses Gauge for values that can increase or decrease. + Gauge devicesConnected = + Gauge.builder() + .name("devices_connected") + .help("Number of smart home devices currently connected") + .labelNames("device_type") + .register(); + + // Increment when a device connects, decrement when it disconnects. + devicesConnected.labelValues("thermostat").inc(); + devicesConnected.labelValues("thermostat").inc(); + devicesConnected.labelValues("lock").inc(); + devicesConnected.labelValues("lock").dec(); + } +} diff --git a/doc-snippets/prometheus-migration/src/main/java/otel/PrometheusUpDownCounterCallback.java b/doc-snippets/prometheus-migration/src/main/java/otel/PrometheusUpDownCounterCallback.java new file mode 100644 index 000000000..a00a2d6c2 --- /dev/null +++ b/doc-snippets/prometheus-migration/src/main/java/otel/PrometheusUpDownCounterCallback.java @@ -0,0 +1,20 @@ +package otel; + +import io.prometheus.metrics.core.metrics.GaugeWithCallback; + +public class PrometheusUpDownCounterCallback { + public static void upDownCounterCallbackUsage() { + // The device manager maintains the count of connected devices. + // Use a callback gauge to report that value at scrape time. + GaugeWithCallback.builder() + .name("devices_connected") + .help("Number of smart home devices currently connected") + .labelNames("device_type") + .callback( + callback -> { + callback.call(SmartHomeDevices.connectedDeviceCount("thermostat"), "thermostat"); + callback.call(SmartHomeDevices.connectedDeviceCount("lock"), "lock"); + }) + .register(); + } +} diff --git a/doc-snippets/prometheus-migration/src/main/java/otel/SmartHomeDevices.java b/doc-snippets/prometheus-migration/src/main/java/otel/SmartHomeDevices.java new file mode 100644 index 000000000..6ff7b322f --- /dev/null +++ b/doc-snippets/prometheus-migration/src/main/java/otel/SmartHomeDevices.java @@ -0,0 +1,32 @@ +package otel; + +/** Stubs representing smart home device APIs. */ +class SmartHomeDevices { + /** + * Returns cumulative energy consumed in joules since installation, as reported by the smart + * energy meter device. + */ + static double totalEnergyJoules() { + return 0.0; // In production, this would query the device API. + } + + /** Returns the current temperature in Celsius from the living room sensor. */ + static double livingRoomTemperatureCelsius() { + return 0.0; // In production, this would query the device API. + } + + /** Returns the current temperature in Celsius from the bedroom sensor. */ + static double bedroomTemperatureCelsius() { + return 0.0; // In production, this would query the device API. + } + + /** Returns the number of currently connected devices of the given type. */ + static long connectedDeviceCount(String deviceType) { + return 0L; // In production, this would query the device manager. + } + + /** Returns the number of device commands currently waiting to be processed. */ + static long pendingCommandCount() { + return 0L; // In production, this would query the command queue. + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index d5c5776c7..2346dd15e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -82,6 +82,7 @@ include( ":doc-snippets:spring-starter", ":doc-snippets:extensions-minimal", ":doc-snippets:extensions-testapp", + ":doc-snippets:prometheus-migration", ) rootProject.children.forEach {