Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions core/src/main/java/tech/ydb/core/grpc/GrpcTransport.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ <ReqT, RespT> GrpcReadWriteStream<RespT, ReqT> readWriteStreamCall(

String getDatabase();

/**
* Returns the discovery endpoint as {@code host:port}, or an empty string when unknown.
*
* @return discovery endpoint or empty string
*/
default String getEndpoint() {
return "";
}

ScheduledExecutorService getScheduler();

default Tracer getTracer() {
Expand Down
5 changes: 5 additions & 0 deletions core/src/main/java/tech/ydb/core/impl/BaseGrpcTransport.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ protected BaseGrpcTransport(EndpointRecord serverEndpoint) {
this.serverEndpoint = serverEndpoint;
}

@Override
public String getEndpoint() {
return serverEndpoint == null ? "" : serverEndpoint.getHost() + ":" + serverEndpoint.getPort();
}

protected abstract AuthCallOptions getAuthCallOptions();

protected abstract GrpcChannel getChannel(GrpcRequestSettings settings);
Expand Down
19 changes: 19 additions & 0 deletions core/src/main/java/tech/ydb/core/metrics/DoubleHistogram.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package tech.ydb.core.metrics;

import io.grpc.ExperimentalApi;

/**
* Histogram of {@code double} values (typically used for durations in seconds).
*/
@ExperimentalApi("YDB Meter is experimental and API may change without notice")
public interface DoubleHistogram {
DoubleHistogram NOOP = (value, keyValues) -> { };

/**
* Records the given value with optional pairs of attribute key/value.
*
* @param value value to record
* @param keyValues alternating {@code key, value} pairs; must have even length
*/
void record(double value, String... keyValues);
}
19 changes: 19 additions & 0 deletions core/src/main/java/tech/ydb/core/metrics/LongCounter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package tech.ydb.core.metrics;

import io.grpc.ExperimentalApi;

/**
* A monotonic counter that records non-negative {@code long} deltas.
*/
@ExperimentalApi("YDB Meter is experimental and API may change without notice")
public interface LongCounter {
LongCounter NOOP = (value, keyValues) -> { };

/**
* Adds the given value with optional pairs of attribute key/value.
*
* @param value non-negative delta
* @param keyValues alternating {@code key, value} pairs; must have even length
*/
void add(long value, String... keyValues);
}
20 changes: 20 additions & 0 deletions core/src/main/java/tech/ydb/core/metrics/LongMeasurement.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package tech.ydb.core.metrics;

import io.grpc.ExperimentalApi;

/**
* Per-observation handle passed to {@link Meter#createLongGauge} callbacks.
*
* <p>A single callback invocation may call {@link #record} multiple times with different
* attributes to emit several measurements per collection cycle.
*/
@ExperimentalApi("YDB Meter is experimental and API may change without notice")
public interface LongMeasurement {
/**
* Records the current value of the gauge for the given attribute set.
*
* @param value observed value
* @param keyValues alternating {@code key, value} pairs; must have even length
*/
void record(long value, String... keyValues);
}
58 changes: 58 additions & 0 deletions core/src/main/java/tech/ydb/core/metrics/Meter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package tech.ydb.core.metrics;

import java.util.function.Consumer;

import io.grpc.ExperimentalApi;

/**
* Entry point to create metric instruments (counters, histograms, gauges).
*
* <p>The interface is dependency-free, so the SDK core does not require an OpenTelemetry runtime
* to compile or run. Each call site (session pool, RPC service, etc.) is expected to create and
* own its private instruments via this {@code Meter} in its constructor and call them on the
* hot path.
*
* <p>Implementations must be thread-safe.
*/
@ExperimentalApi("YDB Meter is experimental and API may change without notice")
public interface Meter {
Meter NOOP = new Meter() { };

/**
* Creates a monotonic {@code long} counter.
*
* @param name metric name
* @param unit metric unit (for example, {@code {operation}})
* @param description human-readable metric description
* @return created counter
*/
default LongCounter createCounter(String name, String unit, String description) {
return LongCounter.NOOP;
}

/**
* Creates a {@code double} histogram.
*
* @param name metric name
* @param unit metric unit (for example, {@code s})
* @param description human-readable metric description
* @return created histogram
*/
default DoubleHistogram createHistogram(String name, String unit, String description) {
return DoubleHistogram.NOOP;
}

/**
* Registers an asynchronous {@code long} gauge. The {@code callback} is invoked by the metrics
* backend on every collection cycle; the supplied {@link LongMeasurement} may emit any number
* of measurements with different attributes.
*
* @param name metric name
* @param unit metric unit (for example, {@code {session}})
* @param description human-readable metric description
* @param callback callback invoked by the metrics backend to collect gauge values
*/
default void createLongGauge(String name, String unit, String description, Consumer<LongMeasurement> callback) {
// noop: the backend never queries the callback
}
}
17 changes: 17 additions & 0 deletions core/src/main/java/tech/ydb/core/metrics/MetricAttributes.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package tech.ydb.core.metrics;

import io.grpc.ExperimentalApi;

/**
* Attribute keys shared by SDK metrics across modules (query, topic, ...).
*/
@ExperimentalApi("YDB Meter is experimental and API may change without notice")
public final class MetricAttributes {
public static final String DATABASE = "database";
public static final String ENDPOINT = "endpoint";
public static final String OPERATION_NAME = "operation.name";
public static final String STATUS_CODE = "status_code";

private MetricAttributes() {
}
}
95 changes: 95 additions & 0 deletions core/src/main/java/tech/ydb/core/metrics/OpenTelemetryMeter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package tech.ydb.core.metrics;

import java.util.Objects;
import java.util.function.Consumer;

import io.grpc.ExperimentalApi;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.api.metrics.DoubleHistogramBuilder;
import io.opentelemetry.api.metrics.LongCounterBuilder;
import io.opentelemetry.api.metrics.LongGaugeBuilder;

/**
* OpenTelemetry-backed implementation of {@link Meter}.
*
* <p>This adapter is a thin facade over an OpenTelemetry {@code Meter}: it owns no state besides
* the underlying meter. Callers attach attributes (including any resource-style attributes such as
* {@code database}) per recorded measurement.
*/
@ExperimentalApi("YDB Meter is experimental and API may change without notice")
public final class OpenTelemetryMeter implements Meter {
private static final String DEFAULT_SCOPE = "tech.ydb.sdk";

private final io.opentelemetry.api.metrics.Meter meter;

private OpenTelemetryMeter(io.opentelemetry.api.metrics.Meter meter) {
this.meter = Objects.requireNonNull(meter, "meter is null");
}

public static OpenTelemetryMeter fromOpenTelemetry(OpenTelemetry openTelemetry) {
Objects.requireNonNull(openTelemetry, "openTelemetry is null");
return new OpenTelemetryMeter(openTelemetry.getMeter(DEFAULT_SCOPE));
}

public static OpenTelemetryMeter createGlobal() {
return fromOpenTelemetry(GlobalOpenTelemetry.get());
}

@Override
public LongCounter createCounter(String name, String unit, String description) {
LongCounterBuilder builder = meter.counterBuilder(name);
if (unit != null) {
builder.setUnit(unit);
}
if (description != null) {
builder.setDescription(description);
}
io.opentelemetry.api.metrics.LongCounter counter = builder.build();
return (value, kv) -> counter.add(value, attributesOf(kv));
}

@Override
public DoubleHistogram createHistogram(String name, String unit, String description) {
DoubleHistogramBuilder builder = meter.histogramBuilder(name);
if (unit != null) {
builder.setUnit(unit);
}
if (description != null) {
builder.setDescription(description);
}
io.opentelemetry.api.metrics.DoubleHistogram histogram = builder.build();
return (value, kv) -> histogram.record(value, attributesOf(kv));
}

@Override
public void createLongGauge(String name, String unit, String description, Consumer<LongMeasurement> callback) {
LongGaugeBuilder builder = meter.gaugeBuilder(name).ofLongs();
if (unit != null) {
builder.setUnit(unit);
}
if (description != null) {
builder.setDescription(description);
}
builder.buildWithCallback(otelMeasurement ->
callback.accept((value, kv) -> otelMeasurement.record(value, attributesOf(kv))));
}

private static Attributes attributesOf(String[] keyValues) {
if (keyValues == null || keyValues.length == 0) {
return Attributes.empty();
}
if ((keyValues.length & 1) == 1) {
throw new IllegalArgumentException(
"Meter attribute keyValues must contain an even number of entries (got "
+ keyValues.length + ")");
}
AttributesBuilder builder = Attributes.builder();
for (int i = 0; i < keyValues.length; i += 2) {
builder.put(keyValues[i], keyValues[i + 1]);
}
return builder.build();
}
}
35 changes: 0 additions & 35 deletions core/src/main/java/tech/ydb/core/tracing/Span.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
package tech.ydb.core.tracing;

import java.util.concurrent.CompletableFuture;

import javax.annotation.Nullable;

import io.grpc.ExperimentalApi;

import tech.ydb.core.Result;
import tech.ydb.core.Status;
import tech.ydb.core.utils.FutureTools;

/**
* A span represents a timed operation.
Expand Down Expand Up @@ -93,35 +89,4 @@ default Scope restoreContext() {
*/
default void end() {
}

/**
* Subscribes to a {@link Status} future: when it completes, sets status/error and ends the span.
* For non-valid spans returns the future as-is without subscribing.
*
* @param span the span to finalize
* @param future the future to observe
* @return the same future (for chaining)
*/
static CompletableFuture<Status> endOnStatus(Span span, CompletableFuture<Status> future) {
return span.isValid() ? future.whenComplete((status, th) -> {
span.setStatus(status, FutureTools.unwrapCompletionException(th));
span.end();
}) : future;
}

/**
* Subscribes to a {@link Result} future: when it completes, sets status/error and ends the span.
* For non-valid spans returns the future as-is without subscribing.
*
* @param <T> result value type
* @param span the span to finalize
* @param future the future to observe
* @return the same future (for chaining)
*/
static <T> CompletableFuture<Result<T>> endOnResult(Span span, CompletableFuture<Result<T>> future) {
return span.isValid() ? future.whenComplete((result, th) -> {
span.setStatus(result != null ? result.getStatus() : null, FutureTools.unwrapCompletionException(th));
span.end();
}) : future;
}
}
Loading
Loading