content: /*
   * Copyright The OpenTelemetry Authors
   * SPDX-License-Identifier: Apache-2.0
   */
  
  package io.opentelemetry.instrumentation.api.semconv.http;
  
  import static java.util.logging.Level.FINE;
  
  import com.google.auto.value.AutoValue;
  import io.opentelemetry.api.common.Attributes;
  import io.opentelemetry.api.metrics.DoubleHistogram;
  import io.opentelemetry.api.metrics.DoubleHistogramBuilder;
  import io.opentelemetry.api.metrics.Meter;
  import io.opentelemetry.context.Context;
  import io.opentelemetry.context.ContextKey;
  import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder;
  import io.opentelemetry.instrumentation.api.instrumenter.OperationListener;
  import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics;
  import io.opentelemetry.instrumentation.api.internal.OperationMetricsUtil;
  import java.util.concurrent.TimeUnit;
  import java.util.logging.Logger;
  
  /**
   * {@link OperationListener} which keeps track of <a
   * href="https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/http/http-metrics.md#http-client">HTTP
   * client metrics</a>.
   *
   * @since 2.0.0
   */
  public final class HttpClientMetrics implements OperationListener {
  
    private static final double NANOS_PER_S = TimeUnit.SECONDS.toNanos(1);
  
    private static final ContextKey<State> HTTP_CLIENT_REQUEST_METRICS_STATE =
        ContextKey.named("http-client-metrics-state");
  
    private static final Logger logger = Logger.getLogger(HttpClientMetrics.class.getName());
  
    /**
     * Returns an {@link OperationMetrics} instance which can be used to enable recording of {@link
     * HttpClientMetrics}.
     *
     * @see InstrumenterBuilder#addOperationMetrics(OperationMetrics)
     */
    public static OperationMetrics get() {
      return OperationMetricsUtil.create("http client", HttpClientMetrics::new);
    }
  
    private final DoubleHistogram duration;
  
    private HttpClientMetrics(Meter meter) {
      DoubleHistogramBuilder stableDurationBuilder =
          meter
              .histogramBuilder("http.client.request.duration")
              .setUnit("s")
              .setDescription("Duration of HTTP client requests.")
              .setExplicitBucketBoundariesAdvice(HttpMetricsAdvice.DURATION_SECONDS_BUCKETS);
      HttpMetricsAdvice.applyClientDurationAdvice(stableDurationBuilder);
      duration = stableDurationBuilder.build();
    }
  
    @Override
    public Context onStart(Context context, Attributes startAttributes, long startNanos) {
      return context.with(
          HTTP_CLIENT_REQUEST_METRICS_STATE,
          new AutoValue_HttpClientMetrics_State(startAttributes, startNanos));
    }
  
    @Override
    public void onEnd(Context context, Attributes endAttributes, long endNanos) {
      State state = context.get(HTTP_CLIENT_REQUEST_METRICS_STATE);
      if (state == null) {
        logger.log(
            FINE,
            "No state present when ending context {0}. Cannot record HTTP request metrics.",
            context);
        return;
      }
  
      Attributes attributes = state.startAttributes().toBuilder().putAll(endAttributes).build();
  
      duration.record((endNanos - state.startTimeNanos()) / NANOS_PER_S, attributes, context);
    }
  
    @AutoValue
    abstract static class State {
  
      abstract Attributes startAttributes();
  
      abstract long startTimeNanos();
    }
  }
  