Skip to content

Commit

Permalink
Merge pull request #376 from lichess-org/metrics/end-to-end-operation
Browse files Browse the repository at this point in the history
Add metrics for search service
  • Loading branch information
lenguyenthanh authored Nov 29, 2024
2 parents d5ad73e + 15180b3 commit eb7d497
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 18 deletions.
5 changes: 4 additions & 1 deletion modules/app/src/main/scala/app.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ object App extends IOApp.Simple:
.autoConfigured[IO](_.addMeterProviderCustomizer((b, _) => b.registerMetricReader(exporter.metricReader)))
.evalMap(_.meterProvider.get("lila-search"))

def mkServer(res: AppResources, config: AppConfig)(using MetricExporter.Pull[IO]): Resource[IO, Unit] =
def mkServer(res: AppResources, config: AppConfig)(using
Meter[IO],
MetricExporter.Pull[IO]
): Resource[IO, Unit] =
for
apiRoutes <- Routes(res, config.server)
httpRoutes = apiRoutes <+> mkPrometheusRoutes
Expand Down
8 changes: 5 additions & 3 deletions modules/app/src/main/scala/http.routes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,19 @@ import cats.syntax.all.*
import lila.search.spec.*
import org.http4s.HttpRoutes
import org.typelevel.log4cats.LoggerFactory
import org.typelevel.otel4s.metrics.Meter
import smithy4s.http4s.SimpleRestJsonBuilder

def Routes(resources: AppResources, config: HttpServerConfig)(using
LoggerFactory[IO]
LoggerFactory[IO],
Meter[IO]
): Resource[IO, HttpRoutes[IO]] =

val healthServiceImpl = HealthServiceImpl(resources.esClient)
val searchServiceImpl = SearchServiceImpl(resources.esClient)

val search: Resource[IO, HttpRoutes[IO]] =
SimpleRestJsonBuilder.routes(searchServiceImpl).resource
SearchServiceImpl(resources.esClient).toResource
.flatMap(SimpleRestJsonBuilder.routes(_).resource)

val health: Resource[IO, HttpRoutes[IO]] =
SimpleRestJsonBuilder.routes(healthServiceImpl).resource
Expand Down
79 changes: 65 additions & 14 deletions modules/app/src/main/scala/service.search.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,62 @@ import lila.search.spec.*
import lila.search.study.Study
import lila.search.team.Team
import org.typelevel.log4cats.{ Logger, LoggerFactory }
import org.typelevel.otel4s.metrics.{ Histogram, Meter }
import org.typelevel.otel4s.{ Attribute, AttributeKey, Attributes }
import smithy4s.Timestamp

import java.time.Instant
import java.util.concurrent.TimeUnit

class SearchServiceImpl(esClient: ESClient[IO])(using LoggerFactory[IO]) extends SearchService[IO]:
class SearchServiceImpl(esClient: ESClient[IO], metric: Histogram[IO, Double])(using
LoggerFactory[IO]
) extends SearchService[IO]:

import SearchServiceImpl.given
import SearchServiceImpl.{ *, given }

given logger: Logger[IO] = LoggerFactory[IO].getLogger

private val baseAttributes = Attributes(Attribute("http.request.method", "POST"))
private val countMetric =
metric
.recordDuration(
TimeUnit.MILLISECONDS,
withErrorType(
baseAttributes
.added(MetricKeys.httpRoute, s"/api/count/")
)
)

private val searchMetric =
metric
.recordDuration(
TimeUnit.MILLISECONDS,
withErrorType(
baseAttributes
.added(MetricKeys.httpRoute, s"/api/count/")
)
)

private def countRecord[A](f: IO[A]) = countMetric.surround(f)
private def searchRecord[A](f: IO[A]) = searchMetric.surround(f)

override def count(query: Query): IO[CountOutput] =
esClient
.count(query)
.map(CountOutput.apply)
.handleErrorWith: e =>
logger.error(e)(s"Error in count: query=$query") *>
IO.raiseError(InternalServerError("Internal server error"))
countRecord:
esClient
.count(query)
.map(CountOutput.apply)
.handleErrorWith: e =>
logger.error(e)(s"Error in count: query=$query") *>
IO.raiseError(InternalServerError("Internal server error"))

override def search(query: Query, from: From, size: Size): IO[SearchOutput] =
esClient
.search(query, from, size)
.map(SearchOutput.apply)
.handleErrorWith: e =>
logger.error(e)(s"Error in search: query=$query, from=$from, size=$size") *>
IO.raiseError(InternalServerError("Internal server error"))
searchRecord:
esClient
.search(query, from, size)
.map(SearchOutput.apply)
.handleErrorWith: e =>
logger.error(e)(s"Error in search: query=$query, from=$from, size=$size") *>
IO.raiseError(InternalServerError("Internal server error"))

object SearchServiceImpl:

Expand Down Expand Up @@ -66,3 +97,23 @@ object SearchServiceImpl:
case _: Query.Game => Index.Game
case _: Query.Study => Index.Study
case _: Query.Team => Index.Team

def apply(elastic: ESClient[IO])(using Meter[IO], LoggerFactory[IO]): IO[SearchService[IO]] =
Meter[IO]
.histogram[Double]("http.server.request.duration")
.withUnit("ms")
.create
.map(new SearchServiceImpl(elastic, _))

object MetricKeys:
val httpRoute = AttributeKey.string("http.route")
val errorType = AttributeKey.string("error.type")

import lila.search.ESClient.MetricKeys.*
def withErrorType(static: Attributes)(ec: Resource.ExitCase): Attributes = ec match
case Resource.ExitCase.Succeeded =>
static
case Resource.ExitCase.Errored(e) =>
static.added(errorType, e.getClass.getName)
case Resource.ExitCase.Canceled =>
static.added(errorType, "canceled")

0 comments on commit eb7d497

Please sign in to comment.