All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ecwid.apiclient.v3.metric.RequestSizeMetric.kt Maven / Gradle / Ivy

package com.ecwid.apiclient.v3.metric

import com.ecwid.apiclient.v3.dto.ApiRequest
import com.ecwid.apiclient.v3.httptransport.HttpRequest
import com.ecwid.apiclient.v3.httptransport.HttpResponse
import com.ecwid.apiclient.v3.httptransport.TransportHttpBody
import com.ecwid.apiclient.v3.impl.RequestInfo
import io.prometheus.metrics.core.metrics.Histogram
import io.prometheus.metrics.model.snapshots.Unit
import java.util.concurrent.atomic.AtomicLong
import java.util.logging.Level
import java.util.logging.Logger

private val log = Logger.getLogger(RequestSizeMetric::class.qualifiedName)

object RequestSizeMetric {
	private val metric = Histogram.builder()
		.name("ecwid_api_client_request_size_bytes")
		.help("Ecwid API client request size of parameters & body in bytes")
		.classicOnly()
		.classicUpperBounds(
			100.0,
			500.0,
			1_000.0,
			5_000.0,
			10_000.0,
			50_000.0,
			100_000.0,
			500_000.0,
			1_000_000.0,
			5_000_000.0,
			10_000_000.0,
			25_000_000.0,
			50_000_000.0,
			100_000_000.0,
		)
		.labelNames("request_type", "path", "method", "status")
		.unit(Unit.BYTES)
		.register()

	fun observeRequest(
		apiRequest: ApiRequest,
		requestInfo: RequestInfo,
		size: AtomicLong,
		httpResponse: HttpResponse,
	) {
		metric
			.labelValues(
				apiRequest.javaClass.simpleName,
				requestInfo.getFirstPathSegment(),
				requestInfo.method.name,
				extractStatusFromHttpResponse(httpResponse),
			)
			.observe(size.toDouble())
	}

	fun makeHttpRequestCountable(httpRequest: HttpRequest): HttpRequestAndCounter {
		val parametersSizeInBytes = httpRequest.params.countSizeInBytes()
		val headersSizeInBytes = httpRequest.headers.countSizeInBytes()

		val counter = AtomicLong(parametersSizeInBytes + headersSizeInBytes)

		return HttpRequestAndCounter(
			httpRequest = wrapHttpRequestToCountable(httpRequest, counter),
			counter = counter,
		)
	}

	private fun Map.countSizeInBytes() = entries
		.sumOf { it.key.length + it.value.length }
		.toLong()

	private fun wrapHttpRequestToCountable(httpRequest: HttpRequest, counter: AtomicLong): HttpRequest {
		return when (httpRequest) {
			is HttpRequest.HttpDeleteRequest -> httpRequest

			is HttpRequest.HttpGetRequest -> httpRequest

			is HttpRequest.HttpPostRequest -> httpRequest.copy(
				transportHttpBody = wrapTransportHttpBodyToCountable(
					httpBody = httpRequest.transportHttpBody,
					counter = counter,
				)
			)

			is HttpRequest.HttpPutRequest -> httpRequest.copy(
				transportHttpBody = wrapTransportHttpBodyToCountable(
					httpBody = httpRequest.transportHttpBody,
					counter = counter,
				)
			)
		}
	}

	private fun wrapTransportHttpBodyToCountable(
		httpBody: TransportHttpBody,
		counter: AtomicLong
	): TransportHttpBody {
		return when (httpBody) {
			is TransportHttpBody.ByteArrayBody -> httpBody.also {
				counter.addAndGet(httpBody.byteArray.size.toLong())
			}

			TransportHttpBody.EmptyBody -> httpBody

			is TransportHttpBody.InputStreamBody -> TransportHttpBody.InputStreamBody(
				stream = CountingInputStream(httpBody.stream, counter),
				mimeType = httpBody.mimeType,
			)

			is TransportHttpBody.LocalFileBody -> httpBody.also {
				try {
					counter.addAndGet(httpBody.file.length())
				} catch (e: Exception) {
					log.log(Level.WARNING, "Unable to get file [${httpBody.file.absolutePath}] size", e)
				}
			}
		}
	}

	data class HttpRequestAndCounter(
		val httpRequest: HttpRequest,
		val counter: AtomicLong,
	)
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy