diff --git a/app/Lila.scala b/app/Lila.scala index 35eedb6c912..48f3070fda0 100644 --- a/app/Lila.scala +++ b/app/Lila.scala @@ -81,7 +81,7 @@ final class LilaComponents( val env: lila.app.Env = lila.log("boot").info(s"Start loading lila modules") - val c = lila.common.Chronometer.sync(wire[lila.app.Env]) + val c = lila.mon.Chronometer.sync(wire[lila.app.Env]) lila.log("boot").info(s"Loaded lila modules in ${c.showDuration}") c.result diff --git a/app/controllers/Mod.scala b/app/controllers/Mod.scala index 9d9db475802..2edb9cdb6e2 100644 --- a/app/controllers/Mod.scala +++ b/app/controllers/Mod.scala @@ -1,11 +1,11 @@ package controllers +import scala.annotation.nowarn + import alleycats.Zero import play.api.libs.json.Json import play.api.mvc.* -import scala.annotation.nowarn - import lila.app.{ *, given } import lila.common.HTTPRequest import lila.core.id.ImageId @@ -15,6 +15,7 @@ import lila.core.security.FingerHash import lila.core.userId.ModId import lila.mod.{ Modlog, ModUserSearch } import lila.report.{ Mod as AsMod, Suspect } +import lila.mon.extensions.* final class Mod( env: Env, @@ -253,26 +254,28 @@ final class Mod( given lila.mod.IpRender.RenderIp = env.mod.ipRender.apply env.game.gameRepo .recentPovsByUserFromSecondary(user, 80) - .mon(_.mod.comm.segment("recentPovs")) + .mon(lila.mon.mod.comm.segment("recentPovs")) .flatMap: povs => ( - env.api.modTimeline.load(user, withPlayBans = false).mon(_.mod.comm.segment("modTimeline")), + env.api.modTimeline + .load(user, withPlayBans = false) + .mon(lila.mon.mod.comm.segment("modTimeline")), priv.so: env.chat.api.playerChat .optionsByOrderedIds(povs.map(_.gameId.into(ChatId))) - .mon(_.mod.comm.segment("playerChats")) + .mon(lila.mon.mod.comm.segment("playerChats")) , priv.so: env.msg.api .recentByForMod(user, 30) - .mon(_.mod.comm.segment("pms")) + .mon(lila.mon.mod.comm.segment("pms")) , env.shutup.api .getPublicLines(user.id) - .mon(_.mod.comm.segment("publicChats")), + .mon(lila.mon.mod.comm.segment("publicChats")), env.report.api.inquiries .ofModId(me.id) - .mon(_.mod.comm.segment("inquiries")), + .mon(lila.mon.mod.comm.segment("inquiries")), env.security.userLogins(user, 100).flatMap { userC.loginsTableData(user, _, 100) } diff --git a/app/controllers/Study.scala b/app/controllers/Study.scala index 6bec6c28b84..710123d55ab 100644 --- a/app/controllers/Study.scala +++ b/app/controllers/Study.scala @@ -18,8 +18,8 @@ import lila.study.PgnDump.WithFlags import lila.study.Study.WithChapter import lila.study.{ Who, Chapter, Orders, Settings, Study as StudyModel, StudyForm } import lila.tree.Node.partitionTreeWriter -import com.fasterxml.jackson.core.JsonParseException import lila.ui.Page +import lila.mon.extensions.* final class Study( env: Env, @@ -258,7 +258,7 @@ final class Study( }.optionFu: env.chat.api.userChat .findMine(study.id.into(ChatId)) - .mon(_.chat.fetch("study")) + .mon(lila.mon.chat.fetch("study")) def createAs = AuthBody { ctx ?=> me ?=> bindForm(StudyForm.importGame.form)( @@ -567,6 +567,7 @@ final class Study( bindForm(StudyForm.topicsForm)( _ => Redirect(routes.Study.topics), topics => + import com.fasterxml.jackson.core.JsonParseException try env.study.topicApi.userTopics(me, topics).inject(Redirect(routes.Study.topics)) catch case e: JsonParseException => BadRequest(e.getMessage) ) diff --git a/app/controllers/Tournament.scala b/app/controllers/Tournament.scala index 18e351b7bb0..f28961cc7f1 100644 --- a/app/controllers/Tournament.scala +++ b/app/controllers/Tournament.scala @@ -2,13 +2,14 @@ package controllers import play.api.libs.json.* import play.api.mvc.* +import scalalib.data.Preload import lila.app.{ *, given } import lila.common.HTTPRequest import lila.common.Json.given -import scalalib.data.Preload import lila.gathering.Condition.GetMyTeamIds import lila.tournament.{ MyInfo, Tournament as Tour, TournamentForm } +import lila.mon.extensions.* final class Tournament(env: Env, apiC: => Api)(using akka.stream.Materializer) extends LilaController(env): @@ -119,7 +120,7 @@ final class Tournament(env: Env, apiC: => Api)(using akka.stream.Materializer) e yield Ok(json.add("chat" -> jsChat)).noCache ) .monSuccess: - _.tournament.apiShowPartial(partial = getBool("partial"), HTTPRequest.clientName(ctx.req)) + lila.mon.tournament.apiShowPartial(partial = getBool("partial"), HTTPRequest.clientName(ctx.req)) def apiShow(id: TourId) = AnonOrScoped(): ctx ?=> WithVisibleTournament(id): tour => diff --git a/app/controllers/User.scala b/app/controllers/User.scala index 2f99f604b2f..aef8ee8b520 100644 --- a/app/controllers/User.scala +++ b/app/controllers/User.scala @@ -18,6 +18,7 @@ import lila.rating.PerfType import lila.rating.UserPerfsExt.best8Perfs import lila.security.UserLogins import lila.user.WithPerfsAndEmails +import lila.mon.extensions.* final class User( override val env: Env, @@ -88,7 +89,7 @@ final class User( _ <- env.userInfo.preloadTeams(info) social <- env.socialInfo(u) page <- renderPage: - lila.mon.chronoSync(_.user.segment("renderSync")): + lila.mon.chronoSync(lila.mon.user.segment("renderSync")): views.user.show.page.activity(as, info, social) yield status(page).withCanonical(routes.User.show(u.username)) else @@ -304,7 +305,7 @@ final class User( private def modZoneSegment(fu: Fu[Frag], name: String, user: UserModel): Source[Frag, ?] = Source.futureSource: - fu.monSuccess(_.mod.zoneSegment(name)) + fu.monSuccess(lila.mon.mod.zoneSegment(name)) .logFailure(lila.log("modZoneSegment").branch(s"$name ${user.id}")) .map(Source.single) diff --git a/app/http/KeyPages.scala b/app/http/KeyPages.scala index 2fc64ee4f8f..1d2f7dd35d6 100644 --- a/app/http/KeyPages.scala +++ b/app/http/KeyPages.scala @@ -2,8 +2,10 @@ package lila.app package http import play.api.mvc.* + import lila.app.{ *, given } import lila.memo.CacheApi.* +import lila.mon.extensions.* final class KeyPages(val env: Env)(using Executor) extends lila.web.ResponseWriter @@ -28,12 +30,12 @@ final class KeyPages(val env: Env)(using Executor) simuls = env.simul.allCreatedFeaturable.get {}.recoverDefault, streamerSpots = env.streamer.homepageMaxSetting.get() ) - .mon(_.lobby.segment("preloader.total")) + .mon(lila.mon.lobby.segment("preloader.total")) .flatMap: h => ctx.me.filter(_.hasTitle).foreach(env.msg.systemMsg.twoFactorReminder(_)) ctx.me.filterNot(_.hasEmail).foreach(env.msg.systemMsg.emailReminder(_)) renderPage: - lila.mon.chronoSync(_.lobby.segment("renderSync")): + lila.mon.chronoSync(lila.mon.lobby.segment("renderSync")): views.lobby.home(h) def notFound(msg: Option[String])(using Context): Fu[Result] = diff --git a/app/mashup/Preload.scala b/app/mashup/Preload.scala index 74b65bc9d64..997835fc168 100644 --- a/app/mashup/Preload.scala +++ b/app/mashup/Preload.scala @@ -14,6 +14,7 @@ import lila.timeline.Entry import lila.tournament.Tournament import lila.ublog.UblogPost import lila.user.{ LightUserApi, Me, User } +import lila.mon.extensions.* final class Preload( tv: lila.tv.Tv, @@ -63,19 +64,19 @@ final class Preload( ), lichessMsg ) <- lobbyApi.get - .mon(_.lobby.segment("lobbyApi")) - .zip(tours.mon(_.lobby.segment("tours"))) - .zip(events.mon(_.lobby.segment("events"))) - .zip(simuls.mon(_.lobby.segment("simuls"))) - .zip(tv.getBestGame.mon(_.lobby.segment("tvBestGame"))) - .zip((ctx.userId.so(timelineApi.userEntries)).mon(_.lobby.segment("timeline"))) - .zip((ctx.noBot.so(dailyPuzzle())).mon(_.lobby.segment("puzzle"))) + .mon(lila.mon.lobby.segment("lobbyApi")) + .zip(tours.mon(lila.mon.lobby.segment("tours"))) + .zip(events.mon(lila.mon.lobby.segment("events"))) + .zip(simuls.mon(lila.mon.lobby.segment("simuls"))) + .zip(tv.getBestGame.mon(lila.mon.lobby.segment("tvBestGame"))) + .zip((ctx.userId.so(timelineApi.userEntries)).mon(lila.mon.lobby.segment("timeline"))) + .zip((ctx.noBot.so(dailyPuzzle())).mon(lila.mon.lobby.segment("puzzle"))) .zip: ctx.kid.no.so: liveStreamApi.all .dmap(_.homepage(streamerSpots, ctx.acceptLanguages).withTitles(lightUserApi)) - .mon(_.lobby.segment("streams")) - .zip((ctx.userId.so(playbanApi.currentBan)).mon(_.lobby.segment("playban"))) + .mon(lila.mon.lobby.segment("streams")) + .zip((ctx.userId.so(playbanApi.currentBan)).mon(lila.mon.lobby.segment("playban"))) .zip(ctx.blind.so(ctx.me).so(roundProxy.urgentGames)) .zip(ublogApi.myCarousel) .zip: @@ -85,11 +86,11 @@ final class Preload( .so(unreadCount.hasLichessMsg) (currentGame, _) <- ctx.me .soUse(currentGameMyTurn(povs, lightUserApi.sync)) - .mon(_.lobby.segment("currentGame")) + .mon(lila.mon.lobby.segment("currentGame")) .zip: lightUserApi .preloadMany(entries.flatMap(_.userIds).toList) - .mon(_.lobby.segment("lightUsers")) + .mon(lila.mon.lobby.segment("lightUsers")) classes <- ctx.myId.so(me => clasApi.isStudent(me).so(clasApi.clas.ofStudent(me, 4))) yield Homepage( data, diff --git a/app/mashup/UserInfo.scala b/app/mashup/UserInfo.scala index 7d071585ee6..009620e2c0a 100644 --- a/app/mashup/UserInfo.scala +++ b/app/mashup/UserInfo.scala @@ -7,12 +7,13 @@ import lila.bookmark.BookmarkApi import lila.core.data.SafeJsonStr import lila.core.perf.UserWithPerfs import lila.core.user.User +import lila.core.security.IsProxy +import lila.core.perm.Granter import lila.forum.ForumPostApi import lila.game.Crosstable import lila.relation.RelationApi import lila.ublog.{ UblogApi, UblogPost } -import lila.core.security.IsProxy -import lila.core.perm.Granter +import lila.mon.extensions.* case class UserInfo( nbs: UserInfo.NbGames, @@ -56,10 +57,10 @@ object UserInfo: def apply(u: User)(using ctx: Context): Fu[Social] = given scala.concurrent.ExecutionContext = scala.concurrent.ExecutionContext.parasitic ( - ctx.userId.so(relationApi.fetchRelation(_, u.id).mon(_.user.segment("relation"))), - ctx.useMe(noteApi.getForMyPermissions(u).mon(_.user.segment("notes"))), - ctx.isAuth.so(prefApi.followable(u.id).mon(_.user.segment("followable"))), - ctx.userId.so(myId => relationApi.fetchBlocks(u.id, myId).mon(_.user.segment("blocks"))) + ctx.userId.so(relationApi.fetchRelation(_, u.id).mon(lila.mon.user.segment("relation"))), + ctx.useMe(noteApi.getForMyPermissions(u).mon(lila.mon.user.segment("notes"))), + ctx.isAuth.so(prefApi.followable(u.id).mon(lila.mon.user.segment("followable"))), + ctx.userId.so(myId => relationApi.fetchBlocks(u.id, myId).mon(lila.mon.user.segment("blocks"))) ).mapN(Social.apply) case class NbGames( @@ -81,11 +82,13 @@ object UserInfo: withCrosstable.so: me .filter(u.isnt(_)) - .traverse(me => crosstableApi.withMatchup(me.userId, u.id).mon(_.user.segment("crosstable"))) + .traverse(me => + crosstableApi.withMatchup(me.userId, u.id).mon(lila.mon.user.segment("crosstable")) + ) , - gameCached.nbPlaying(u.id).mon(_.user.segment("nbPlaying")), - gameCached.nbImportedBy(u.id).mon(_.user.segment("nbImported")), - bookmarkApi.countByUser(u).mon(_.user.segment("nbBookmarks")) + gameCached.nbPlaying(u.id).mon(lila.mon.user.segment("nbPlaying")), + gameCached.nbImportedBy(u.id).mon(lila.mon.user.segment("nbImported")), + bookmarkApi.countByUser(u).mon(lila.mon.user.segment("nbBookmarks")) ).mapN(NbGames.apply) final class UserInfoApi( @@ -112,19 +115,19 @@ object UserInfo: def showRatings = ctx.noBlind && ctx.pref.showRatings && isAuthOrNotProxied ( perfsRepo.withPerfs(user), - userApi.getTrophiesAndAwards(user).mon(_.user.segment("trophies")), - (nbs.playing > 0).so(simulApi.isSimulHost(user.id).mon(_.user.segment("simul"))), - showRatings.so(ratingChartApi(user)).mon(_.user.segment("ratingChart")), + userApi.getTrophiesAndAwards(user).mon(lila.mon.user.segment("trophies")), + (nbs.playing > 0).so(simulApi.isSimulHost(user.id).mon(lila.mon.user.segment("simul"))), + showRatings.so(ratingChartApi(user)).mon(lila.mon.user.segment("ratingChart")), (!user.is(UserId.lichess) && !user.isBot).so: - postApi.nbByUser(user.id).mon(_.user.segment("nbForumPosts")) + postApi.nbByUser(user.id).mon(lila.mon.user.segment("nbForumPosts")) , withUblog.so(ublogApi.userBlogPreviewFor(user, 3)), - studyRepo.countByOwner(user.id).recoverDefault.mon(_.user.segment("nbStudies")), - simulApi.countHostedByUser.get(user.id).mon(_.user.segment("nbSimuls")), - relayApi.countOwnedByUser.get(user.id).mon(_.user.segment("nbBroadcasts")), - ctx.useMe(teamApi.joinedTeamIdsOfUserAsSeenBy(user).mon(_.user.segment("teamIds"))), - streamerApi.isActualStreamer(user).mon(_.user.segment("streamer")), - coachApi.isListedCoach(user).mon(_.user.segment("coach")), + studyRepo.countByOwner(user.id).recoverDefault.mon(lila.mon.user.segment("nbStudies")), + simulApi.countHostedByUser.get(user.id).mon(lila.mon.user.segment("nbSimuls")), + relayApi.countOwnedByUser.get(user.id).mon(lila.mon.user.segment("nbBroadcasts")), + ctx.useMe(teamApi.joinedTeamIdsOfUserAsSeenBy(user).mon(lila.mon.user.segment("teamIds"))), + streamerApi.isActualStreamer(user).mon(lila.mon.user.segment("streamer")), + coachApi.isListedCoach(user).mon(lila.mon.user.segment("coach")), fideIdOf(user.light), fuccess(Granter.opt(_.SeeInsight)) >>| (user.count.rated >= 50).so(insightShare.grant(user)) ).mapN(UserInfo(nbs, _, _, _, _, _, _, _, _, _, _, _, _, _, _)) diff --git a/app/views/base/page.scala b/app/views/base/page.scala index b2b7db82eef..64f3fd3ced9 100644 --- a/app/views/base/page.scala +++ b/app/views/base/page.scala @@ -5,6 +5,7 @@ import scalalib.StringUtils.escapeHtmlRaw import lila.app.UiEnv.{ *, given } import lila.common.String.html.safeJsonValue import lila.ui.{ RenderedPage, PageFlags } +import lila.mon.extensions.* object page: diff --git a/build.sbt b/build.sbt index ce44634b4a3..f122737a1c7 100644 --- a/build.sbt +++ b/build.sbt @@ -98,6 +98,11 @@ lazy val coreI18n = module("coreI18n", Seq(scalatags) ++ scalalib.bundle ) +lazy val mon = module("mon", + Seq(core), + Seq(kamon.core, kamon.influxdb) +) + lazy val common = module("common", Seq(core), Seq( @@ -111,7 +116,7 @@ lazy val db = module("db", ) lazy val memo = module("memo", - Seq(db), + Seq(db, mon), Seq(scaffeine, bloomFilter) ++ playWs.bundle ) @@ -130,7 +135,7 @@ lazy val i18n = module("i18n", ) lazy val rating = module("rating", - Seq(db, ui), + Seq(db, ui, mon), tests.bundle ++ Seq(apacheMath) ).dependsOn(common % "test->test") @@ -346,7 +351,7 @@ lazy val security = module("security", ) lazy val shutup = module("shutup", - Seq(db), + Seq(db, mon), tests.bundle ) @@ -401,17 +406,17 @@ lazy val playban = module("playban", ) lazy val push = module("push", - Seq(db), + Seq(db, mon), playWs.bundle ++ Seq(googleOAuth) ) lazy val irc = module("irc", - Seq(common), + Seq(common, mon), playWs.bundle ) lazy val mailer = module("mailer", - Seq(memo, coreI18n, ui), + Seq(memo, ui), Seq(hasher, play.mailer) ) diff --git a/modules/activity/src/main/ActivityReadApi.scala b/modules/activity/src/main/ActivityReadApi.scala index 5554630f2bb..bc323a72e4d 100644 --- a/modules/activity/src/main/ActivityReadApi.scala +++ b/modules/activity/src/main/ActivityReadApi.scala @@ -2,13 +2,14 @@ package lila.activity import play.api.i18n.Lang import scalalib.HeapSort +import chess.Speed.Correspondence import lila.core.chess.Rank import lila.core.game.LightPov import lila.core.swiss.IdName as SwissIdName import lila.db.AsyncCollFailingSilently import lila.db.dsl.* -import chess.Speed.Correspondence +import lila.mon.extensions.* final class ActivityReadApi( coll: AsyncCollFailingSilently, @@ -37,12 +38,12 @@ final class ActivityReadApi( .cursor[Activity]() .list(Activity.recentNb) ).dmap(_.filterNot(_.isEmpty)) - .mon(_.user.segment("activity.raws")) + .mon(lila.mon.user.segment("activity.raws")) practiceStudies <- activities .exists(_.practice.isDefined) .optionFu(getPracticeStudies()) views <- activities.sequentially: a => - one(practiceStudies, a).mon(_.user.segment("activity.view")) + one(practiceStudies, a).mon(lila.mon.user.segment("activity.view")) _ <- preloadAll(views) yield addSignup(u.createdAt, views) @@ -56,7 +57,7 @@ final class ActivityReadApi( allForumPosts <- a.forumPosts.traverse: p => forumPostApi .miniViews(p.value) - .mon(_.user.segment("activity.posts")) + .mon(lila.mon.user.segment("activity.posts")) hiddenForumTeamIds <- teamApi.filterHideForum( (~allForumPosts).flatMap(_.topic.possibleTeamId).distinct ) @@ -67,7 +68,7 @@ final class ActivityReadApi( .traverse: p => ublogApi .liveLightsByIds(p.value) - .mon(_.user.segment("activity.ublogs")) + .mon(lila.mon.user.segment("activity.ublogs")) .dmap(_.filter(_.nonEmpty)) practice = for @@ -117,7 +118,7 @@ final class ActivityReadApi( ) ) ) - .mon(_.user.segment("activity.tours")) + .mon(lila.mon.user.segment("activity.tours")) swisses <- a.swisses.so: swisses => toSwissesView(swisses.value).dmap(_.nonEmptyOption) diff --git a/modules/api/src/main/LobbyApi.scala b/modules/api/src/main/LobbyApi.scala index b818df8c9a1..185e6701bca 100644 --- a/modules/api/src/main/LobbyApi.scala +++ b/modules/api/src/main/LobbyApi.scala @@ -6,6 +6,7 @@ import lila.common.Json.given import lila.core.perf.{ UserPerfs, UserWithPerfs } import lila.lobby.LobbySocket import lila.rating.UserPerfsExt.perfsList +import lila.mon.extensions.* final class LobbyApi( lightUserApi: lila.user.LightUserApi, @@ -16,7 +17,7 @@ final class LobbyApi( def get(using me: Option[UserWithPerfs]): Fu[(JsObject, List[Pov])] = me.so(gameProxyRepo.urgentGames) - .mon(_.lobby.segment("urgentGames")) + .mon(lila.mon.lobby.segment("urgentGames")) .flatMap: povs => val displayedPovs = povs.take(9) for _ <- lightUserApi.preloadMany(displayedPovs.flatMap(_.opponent.userId)) diff --git a/modules/api/src/main/RoundApi.scala b/modules/api/src/main/RoundApi.scala index 206a64d693d..ac1c4adb139 100644 --- a/modules/api/src/main/RoundApi.scala +++ b/modules/api/src/main/RoundApi.scala @@ -19,6 +19,7 @@ import lila.swiss.GameView as SwissView import lila.tournament.GameView as TourView import lila.tree.{ ExportOptions, Tree } import lila.game.GameExt.timeForFirstMove +import lila.mon.extensions.* final private[api] class RoundApi( jsonView: JsonView, @@ -66,7 +67,7 @@ final private[api] class RoundApi( .compose(withForecastCount(forecast.map(_.steps.size))) .compose(withOpponentSignal(pov)) )(json) - }.mon(_.round.api.player) + }.mon(lila.mon.round.api.player) def watcher( pov: Pov, @@ -94,7 +95,7 @@ final private[api] class RoundApi( .compose(withBookmark(bookmarked)) .compose(withSteps(pov, initialFen)) )(json) - }.mon(_.round.api.watcher) + }.mon(lila.mon.round.api.watcher) private def ctxFlags(using ctx: Context) = ExportOptions( @@ -144,7 +145,7 @@ final private[api] class RoundApi( .compose(withPuzzleOpening(puzzleOpening)) )(json) .flatMap(externalEngineApi.withExternalEngines) - .mon(_.round.api.watcher) + .mon(lila.mon.round.api.watcher) def userAnalysisJson( pov: Pov, diff --git a/modules/challenge/src/main/ChallengeApi.scala b/modules/challenge/src/main/ChallengeApi.scala index d4490d998ac..31a02a5f8d3 100644 --- a/modules/challenge/src/main/ChallengeApi.scala +++ b/modules/challenge/src/main/ChallengeApi.scala @@ -115,7 +115,7 @@ final class ChallengeApi( maxSize = Max(64), timeout = 5.seconds, "challengeAccept", - lila.log.asyncActorMonitor.full + lila.mon.asyncActorMonitor.full ) def accept( diff --git a/modules/challenge/src/main/ChallengeBulk.scala b/modules/challenge/src/main/ChallengeBulk.scala index 470799a7496..3aaa6031cf0 100644 --- a/modules/challenge/src/main/ChallengeBulk.scala +++ b/modules/challenge/src/main/ChallengeBulk.scala @@ -35,7 +35,7 @@ final class ChallengeBulkApi( expiration = 10.minutes, timeout = 10.seconds, name = "challenge.bulk", - lila.log.asyncActorMonitor.full + lila.mon.asyncActorMonitor.full ) def scheduledBy(me: User): Fu[List[ScheduledBulk]] = diff --git a/modules/clas/src/main/ClasForm.scala b/modules/clas/src/main/ClasForm.scala index 5723be392cf..07feefa1175 100644 --- a/modules/clas/src/main/ClasForm.scala +++ b/modules/clas/src/main/ClasForm.scala @@ -6,6 +6,7 @@ import play.api.i18n.Lang import lila.common.Form.{ cleanNonEmptyText, cleanText, into } import lila.clas.Student.RealName +import lila.mon.extensions.* final class ClasForm( lightUserAsync: lila.core.LightUser.Getter, diff --git a/modules/clas/src/main/ClasUserFilters.scala b/modules/clas/src/main/ClasUserFilters.scala index 25257f23d44..c6f0721092d 100644 --- a/modules/clas/src/main/ClasUserFilters.scala +++ b/modules/clas/src/main/ClasUserFilters.scala @@ -8,6 +8,7 @@ import reactivemongo.api.bson.BSONNull import play.api.Mode import lila.db.dsl.{ *, given } +import lila.mon.extensions.* final class ClasUserFilters(using Executor, Materializer, Scheduler)(colls: ClasColls)(using mode: Mode): @@ -84,7 +85,7 @@ private final class ClasUserCache(name: String)( lila.mon.clas.bloomFilter(name).count.update(nb) bloomFilter.dispose() bloomFilter = nextBloom - .monSuccess(_.clas.bloomFilter(name).fu) + .monSuccess(lila.mon.clas.bloomFilter(name).fu) scheduler.scheduleWithFixedDelay(initialDelay, 7.days): () => rebuildBloomFilter() diff --git a/modules/common/src/main/BatchProvider.scala b/modules/common/src/main/BatchProvider.scala index 90958150bdf..9339af104f9 100644 --- a/modules/common/src/main/BatchProvider.scala +++ b/modules/common/src/main/BatchProvider.scala @@ -1,8 +1,12 @@ package lila.common +import scalalib.actor.{ AsyncActorBounded, AsyncActorSequencer } + // provides values of A one by one // but generates them in batches -final class BatchProvider[A](name: String, timeout: FiniteDuration)(generateBatch: () => Fu[List[A]])(using +final class BatchProvider[A](name: String, timeout: FiniteDuration, monitor: AsyncActorBounded.Monitor)( + generateBatch: () => Fu[List[A]] +)(using Executor, Scheduler ): @@ -11,7 +15,7 @@ final class BatchProvider[A](name: String, timeout: FiniteDuration)(generateBatc maxSize = Max(4096), timeout = timeout, name = s"$name.batchProvider.workQueue", - lila.log.asyncActorMonitor.full + monitor = monitor ) private var reserve = List.empty[A] diff --git a/modules/common/src/main/Form.scala b/modules/common/src/main/Form.scala index 4e4c4606074..510ee654f79 100644 --- a/modules/common/src/main/Form.scala +++ b/modules/common/src/main/Form.scala @@ -51,7 +51,7 @@ object Form: def stringIn[A](choices: Seq[A])(key: A => String): Mapping[A] = stringIn(choices.map(key).toSet).transform[A](str => choices.find(c => str == key(c)).get, key) - def id[Id](size: Int, fixed: Option[Id])(exists: Id => Fu[Boolean])(using + def idWithSyncUniqueCheck[Id](size: Int, fixed: Option[Id])(exists: Id => Fu[Boolean])(using sr: StringRuntime[Id], rs: SameRuntime[String, Id] ): Mapping[Id] = @@ -59,9 +59,12 @@ object Form: .verifying("IDs must be made of ASCII letters and numbers", id => """(?i)^[a-z\d]+$""".r.matches(id)) .into[Id] fixed match - case Some(fixedId) => field.verifying("The ID cannot be changed now", id => id == fixedId) + case Some(fixedId) => field.verifying("The ID cannot be changed now", _ == fixedId) case None => - field.verifying("This ID is already in use", id => !exists(id).await(1.second, "unique ID")) + field.verifying( + "This ID is already in use", + id => !scala.concurrent.Await.result(exists(id), 1.second) + ) def empty[T]: FieldMapping[Option[T]] = given Formatter[Option[T]] = new: diff --git a/modules/common/src/main/Lilakka.scala b/modules/common/src/main/Lilakka.scala index a2898c1b51e..d404e2ef608 100644 --- a/modules/common/src/main/Lilakka.scala +++ b/modules/common/src/main/Lilakka.scala @@ -13,7 +13,6 @@ object Lilakka: val msg = s"$phase $name" cs.addTask(phase, name): () => shutdownLogger.info(msg) - Chronometer(f()) - .log(shutdownLogger)(_ => msg) - .result - .inject(akka.Done) + f().dmap: _ => + shutdownLogger.info(s"$msg done") + akka.Done diff --git a/modules/common/src/main/MarkdownRender.scala b/modules/common/src/main/MarkdownRender.scala index fe3b38223f3..a61ab8286de 100644 --- a/modules/common/src/main/MarkdownRender.scala +++ b/modules/common/src/main/MarkdownRender.scala @@ -104,19 +104,14 @@ final class MarkdownRender( Markdown(RawHtml.atUsernameRegex.replaceAllIn(markdown.value, "[@$1](/@/$1)")) def apply(key: MarkdownRender.Key)(text: Markdown): Html = Html: - Chronometer - .sync: - try - val saferText = MarkdownRender.preventStackOverflow(text) - val withMentions = if sourceMap then saferText else mentionsToLinks(saferText) - renderer.render(parser.parse(withMentions.value)) - catch - case e: StackOverflowError => - logger.branch(key).error("StackOverflowError", e) - text.value - .mon(_.markdown.time) - .logIfSlow(50, logger.branch(key))(_ => s"slow markdown size:${text.value.size}") - .result + try + val saferText = MarkdownRender.preventStackOverflow(text) + val withMentions = if sourceMap then saferText else mentionsToLinks(saferText) + renderer.render(parser.parse(withMentions.value)) + catch + case e: StackOverflowError => + logger.branch(key).error("StackOverflowError", e) + text.value object MarkdownRender: diff --git a/modules/common/src/main/log.scala b/modules/common/src/main/log.scala index 0ed96ea6eb7..a5647e35950 100644 --- a/modules/common/src/main/log.scala +++ b/modules/common/src/main/log.scala @@ -12,22 +12,3 @@ object log: def http(status: Int, body: String) = s"$status ${body.linesIterator.take(1).toList.headOption.getOrElse("-")}" - - object asyncActorMonitor: - lazy val full = scalalib.actor.AsyncActorBounded.Monitor( - overflow = name => - lila.mon.asyncActor.overflow(name).increment() - lila.log("asyncActor").warn(s"[$name] queue is full") - , - queueSize = (name, size) => lila.mon.asyncActor.queueSize(name).record(size), - unhandled = (name, msg) => lila.log("asyncActor").warn(s"[$name] unhandled msg: $msg") - ) - - lazy val highCardinality = full.copy( - queueSize = (_, _) => () - ) - - lazy val unhandled = full.copy( - overflow = _ => (), - queueSize = (_, _) => () - ) diff --git a/modules/common/src/main/mon.scala b/modules/common/src/main/mon.scala deleted file mode 100644 index 1f4f5459e0f..00000000000 --- a/modules/common/src/main/mon.scala +++ /dev/null @@ -1,759 +0,0 @@ -package lila - -import com.github.benmanes.caffeine.cache.Cache as CaffeineCache -import kamon.metric.{ Counter, Timer } -import kamon.tag.TagSet -import chess.variant.Variant - -import lila.core.id.* -import lila.core.net.* -import lila.core.userId.{ UserId, UserName } -import lila.core.perf.PerfKey - -object mon: - - import kamon.Kamon.{ timer, gauge, counter, histogram } - - // https://github.com/kamon-io/Kamon/issues/752 - extension (s: String) - def escape: String = - val builder = java.lang.StringBuilder(s.length) - for c <- s.toCharArray do - if c != '"' && c != '\n' && c != '\\' - then builder.append(c) - builder.toString - - private def tags(elems: (String, Any)*): Map[String, Any] = Map.from(elems) - - object http: - private val reqTime = timer("http.time") - private val reqCount = counter("http.count") - private val mobCount = counter("http.mobile.count") - - def time(action: String) = reqTime.withTag("action", action) - - def count(action: String, client: String, method: String, code: Int) = - reqCount.withTags: - tags("action" -> action, "client" -> client, "method" -> method, "code" -> code.toLong) - - def errorCount(action: String, client: String, method: String, code: Int) = - counter("http.error").withTags: - tags("action" -> action, "client" -> client, "method" -> method, "code" -> code.toLong) - - def mobileCount(action: String, version: String, auth: Boolean, os: String) = - mobCount.withTags: - tags( - "action" -> action, - "version" -> version, - "auth" -> (if auth then "auth" else "anon"), - "os" -> os - ) - - def path(p: String) = counter("http.path.count").withTag("path", p.escape) - val userGamesCost = counter("http.userGames.cost").withoutTags() - def csrfError(tpe: String, action: String, client: String) = - counter("http.csrf.error").withTags(tags("type" -> tpe, "action" -> action, "client" -> client)) - val fingerPrint = timer("http.fingerPrint.time").withoutTags() - object syncache: - def miss(name: String) = counter("syncache.miss").withTag("name", name) - def timeout(name: String) = counter("syncache.timeout").withTag("name", name) - def compute(name: String) = timer("syncache.compute").withTag("name", name) - def wait(name: String) = timer("syncache.wait").withTag("name", name) - def caffeineStats(cache: CaffeineCache[?, ?], name: String): Unit = - val stats = cache.stats - gauge("caffeine.request").withTags(tags("name" -> name, "hit" -> true)).update(stats.hitCount.toDouble) - gauge("caffeine.request").withTags(tags("name" -> name, "hit" -> false)).update(stats.missCount.toDouble) - histogram("caffeine.hit.rate").withTag("name", name).record((stats.hitRate * 100000).toLong) - if stats.totalLoadTime > 0 then - gauge("caffeine.load.count") - .withTags(tags("name" -> name, "success" -> "success")) - .update(stats.loadSuccessCount.toDouble) - gauge("caffeine.load.count") - .withTags(tags("name" -> name, "success" -> "failure")) - .update(stats.loadFailureCount.toDouble) - gauge("caffeine.loadTime.cumulated") - .withTag("name", name) - .update(stats.totalLoadTime / 1000000d) // in millis; too much nanos for Kamon to handle) - timer("caffeine.loadTime.penalty").withTag("name", name).record(stats.averageLoadPenalty.toLong) - gauge("caffeine.eviction.count").withTag("name", name).update(stats.evictionCount.toDouble) - gauge("caffeine.entry.count").withTag("name", name).update(cache.estimatedSize.toDouble) - object mongoCache: - def request(name: String, hit: Boolean) = - counter("mongocache.request").withTags: - tags( - "name" -> name, - "hit" -> hit - ) - def compute(name: String) = timer("mongocache.compute").withTag("name", name) - object evalCache: - private val r = counter("evalCache.request") - def request(ply: Int, isHit: Boolean) = - r.withTags(tags("ply" -> (if ply < 15 then ply.toString else "15+"), "hit" -> isHit)) - object upgrade: - val count = counter("evalCache.upgrade.count").withoutTags() - val members = gauge("evalCache.upgrade.members").withoutTags() - val evals = gauge("evalCache.upgrade.evals").withoutTags() - val expirable = gauge("evalCache.upgrade.expirable").withoutTags() - object lobby: - object hook: - val create = counter("lobby.hook.create").withoutTags() - val join = counter("lobby.hook.join").withoutTags() - val size = histogram("lobby.hook.size").withoutTags() - def apiCreate(client: String) = counter("lobby.hook.apiCreate").withTag("client", client) - object seek: - val create = counter("lobby.seek.create").withoutTags() - val join = counter("lobby.seek.join").withoutTags() - object socket: - val getSris = timer("lobby.socket.getSris").withoutTags() - val member = gauge("lobby.socket.member").withoutTags() - val idle = gauge("lobby.socket.idle").withoutTags() - val hookSubscribers = gauge("lobby.socket.hookSubscribers").withoutTags() - object pool: - object wave: - def scheduled(id: String) = counter("lobby.pool.wave.scheduled").withTag("pool", id) - def full(id: String) = counter("lobby.pool.wave.full").withTag("pool", id) - def candidates(id: String) = histogram("lobby.pool.wave.candidates").withTag("pool", id) - def paired(id: String) = histogram("lobby.pool.wave.paired").withTag("pool", id) - def missed(id: String) = histogram("lobby.pool.wave.missed").withTag("pool", id) - def ratingDiff(id: String) = histogram("lobby.pool.wave.ratingDiff").withTag("pool", id) - def withRange(id: String) = histogram("lobby.pool.wave.withRange").withTag("pool", id) - object thieve: - def stolen(id: String) = histogram("lobby.pool.thieve.stolen").withTag("pool", id) - private val lobbySegment = timer("lobby.segment") - def segment(seg: String) = lobbySegment.withTag("segment", seg) - object rating: - def distribution(perfKey: PerfKey, rating: Int) = - gauge("rating.distribution").withTags(tags("perf" -> perfKey, "rating" -> rating.toLong)) - object regulator: - def micropoints(perfKey: PerfKey) = histogram("rating.regulator").withTag("perf", perfKey.value) - object perfStat: - def indexTime = timer("perfStat.indexTime").withoutTags() - - object round: - object api: - val player = timer("round.api").withTag("endpoint", "player") - val watcher = timer("round.api").withTag("endpoint", "watcher") - object forecast: - val create = counter("round.forecast.create").withoutTags() - object move: - object lag: - val compDeviation = histogram("round.move.lag.comp_deviation").withoutTags() - def uncomped(key: String) = histogram("round.move.lag.uncomped_ms").withTag("key", key) - def uncompStdDev(key: String) = histogram("round.move.lag.uncomp_stdev_ms").withTag("key", key) - val stdDev = histogram("round.move.lag.stddev_ms").withoutTags() - val mean = histogram("round.move.lag.mean_ms").withoutTags() - val coefVar = histogram("round.move.lag.coef_var_1000").withoutTags() - val compEstStdErr = histogram("round.move.lag.comp_est_stderr_1000").withoutTags() - val compEstOverErr = histogram("round.move.lag.avg_over_error_ms").withoutTags() - val moveComp = timer("round.move.lag.comped").withoutTags() - val time = timer("round.move.time").withoutTags() - object error: - val client = counter("round.error").withTag("from", "client") - val fishnet = counter("round.error").withTag("from", "fishnet") - val glicko = counter("round.error").withTag("from", "glicko") - val other = counter("round.error").withTag("from", "other") - object titivate: - val time = future("round.titivate.time") - val game = histogram("round.titivate.game").withoutTags() // how many games were processed - val total = histogram("round.titivate.total").withoutTags() // how many games should have been processed - val old = histogram("round.titivate.old").withoutTags() // how many old games remain - def broken(error: String) = counter("round.titivate.broken").withTag("error", error) // broken game - object alarm: - val time = timer("round.alarm.time").withoutTags() - object expiration: - val count = counter("round.expiration.count").withoutTags() - val asyncActorCount = gauge("round.asyncActor.count").withoutTags() - object correspondenceEmail: - val emails = histogram("round.correspondenceEmail.emails").withoutTags() - val time = future("round.correspondenceEmail.time") - object farming: - val bot = counter("round.farming.bot").withoutTags() - val provisional = counter("round.farming.provisional").withoutTags() - object playban: - def outcome(out: String) = counter("playban.outcome").withTag("outcome", out) - object ban: - val count = counter("playban.ban.count").withoutTags() - val mins = histogram("playban.ban.mins").withoutTags() - object explorer: - object index: - def count(success: Boolean) = counter("explorer.index.count").withTag("success", successTag(success)) - val time = timer("explorer.index.time").withoutTags() - object timeline: - val notification = counter("timeline.notification").withoutTags() - object insight: - val user = future("insight.request.time", "user") - val peers = future("insight.request.time", "peer") - val index = future("insight.index.time") - object tutor: - def buildSegment(segment: String) = future("tutor.build.segment", segment) - val buildFull = future("tutor.build.full") - val askMine = askAs("mine") - val askPeer = askAs("peer") - val buildTimeout = counter("tutor.build.timeout").withoutTags() - def peerMatch(hit: Boolean, perf: PerfKey) = counter("tutor.peerMatch").withTags: - tags("hit" -> hitTag(hit), "perf" -> perf) - val parallelism = gauge("tutor.build.parallelism").withoutTags() - val fishnetMissing = histogram("tutor.fishnet.missing").withoutTags() - private def askAs(as: "mine" | "peer")(question: String, perf: PerfKey | "all") = - future("tutor.insight.ask", tags("question" -> question, "perf" -> perf, "as" -> as)) - object search: - def time(op: "search" | "count", index: String, success: Boolean) = - timer("search.client.time").withTags: - tags( - "op" -> op, - "index" -> index, - "success" -> successTag(success) - ) - object asyncActor: - def overflow(name: String) = counter("asyncActor.overflow").withTag("name", name) - def queueSize(name: String) = histogram("asyncActor.queueSize").withTag("name", name) - object irc: - object zulip: - def say(stream: String) = future("irc.zulip.say", tags("stream" -> stream.escape)) - object user: - val online = gauge("user.online").withoutTags() - object register: - def count( - confirm: String, - ipSusp: Boolean, - fp: Boolean, - proxy: Option[String], - country: String, - client: String - ) = - counter("user.register.count").withTags: - tags( - "confirm" -> confirm, - "ipSusp" -> ipSusp, - "fp" -> fp, - "proxy" -> proxy.getOrElse("no"), - "country" -> country.escape, - "client" -> client - ) - def result(client: String, result: String) = - counter("user.register.result").withTags: - tags("client" -> client, "result" -> result) - def mustConfirmEmail(v: String) = counter("user.register.mustConfirmEmail").withTag("type", v) - def confirmEmailResult(success: Boolean) = - counter("user.register.confirmEmail").withTag("success", successTag(success)) - def modConfirmEmail(by: "mod" | "worker", result: String) = - counter("user.register.modConfirmEmail").withTags: - tags("by" -> by, "result" -> result) - object auth: - val bcFullMigrate = counter("user.auth.bcFullMigrate").withoutTags() - val hashTime = timer("user.auth.hashTime").withoutTags() - def count(success: Boolean) = counter("user.auth.count").withTag("success", successTag(success)) - - def passwordResetRequest(s: String) = counter("user.auth.passwordResetRequest").withTag("type", s) - def passwordResetConfirm(s: String) = counter("user.auth.passwordResetConfirm").withTag("type", s) - - def reopenRequest(s: String) = counter("user.auth.reopenRequest").withTag("type", s) - def reopenConfirm(s: String) = counter("user.auth.reopenConfirm").withTag("type", s) - object oauth: - def request(success: Boolean) = counter("user.oauth.request").withTags: - tags("success" -> successTag(success)) - private val userSegment = timer("user.segment") - def segment(seg: String) = userSegment.withTag("segment", seg) - def leaderboardCompute = future("user.leaderboard.compute") - def weeklyStableRanking(perf: PerfKey) = future("user.weeklyStableRanking", perf.value) - object actor: - def queueSize(name: String) = gauge("trouper.queueSize").withTag("name", name) - object mod: - object report: - val highest = gauge("mod.report.highest").withoutTags() - val close = counter("mod.report.close").withoutTags() - def create(reason: String, score: Int) = - counter("mod.report.create").withTags: - tags("reason" -> reason, "score" -> score) - object automod: - val request = future("mod.report.automod.request") - def assessment(a: String) = counter("mod.report.automod.assessment").withTag("assessment", a) - val imageRequest = future("mod.report.automod.image.request") - def imageFlagged(v: Boolean) = counter("mod.report.automod.image.flagged").withTag("flagged", v) - object log: - val create = counter("mod.log.create").withoutTags() - object irwin: - val report = counter("mod.report.irwin.report").withoutTags() - val mark = counter("mod.report.irwin.mark").withoutTags() - def ownerReport(name: String) = counter("mod.irwin.ownerReport").withTag("name", name) - def streamEventType(name: String) = counter("mod.irwin.stream.eventType").withTag("name", name) - object kaladin: - def request(by: String) = counter("mod.kaladin.request").withTag("by", by) - def insufficientMoves(by: String) = counter("mod.kaladin.insufficientMoves").withTag("by", by) - def queue(priority: Int) = gauge("mod.kaladin.queue").withTag("priority", priority) - def error(errKind: String) = counter("mod.kaladin.error").withTag("error", errKind) - val activation = histogram("mod.report.kaladin.activation").withoutTags() - val report = counter("mod.report.kaladin.report").withoutTags() - val mark = counter("mod.report.kaladin.mark").withoutTags() - object comm: - def segment(seg: String) = timer("mod.comm.segmentLat").withTag("segment", seg) - def zoneSegment(name: String) = future("mod.zone.segment", name) - object relay: - private def by(official: Boolean) = if official then "official" else "user" - private def relay(official: Boolean, id: RelayTourId, slug: String) = - tags("by" -> by(official), "slug" -> s"$slug/$id") - def ongoing(official: Boolean) = gauge("relay.ongoing").withTag("by", by(official)) - val crowdMonitor = gauge("relay.crowdMonitor").withoutTags() - def moves(official: Boolean, id: RelayTourId, slug: String) = - counter("relay.moves").withTags(relay(official, id, slug)) - def fetchTime(official: Boolean, id: RelayTourId, slug: String) = - timer("relay.fetch.time").withTags(relay(official, id, slug)) - def syncTime(official: Boolean, id: RelayTourId, slug: String) = - timer("relay.sync.time").withTags(relay(official, id, slug)) - def httpGet(code: Int, host: String, etag: String, proxy: Option[String]) = - timer("relay.http.get").withTags: - tags( - "code" -> code.toLong, - "host" -> host.escape, - "etag" -> etag.escape, - "proxy" -> proxy.getOrElse("none") - ) - val dedup = counter("relay.fetch.dedup").withoutTags() - def push(name: String, user: UserName, client: String)(games: Int, moves: Int, errors: Int) = - val histogramTags = tags("name" -> name.escape, "user" -> user, "client" -> client.escape) - val counterTags = tags("name" -> name.escape, "user" -> user) - histogram("relay.push.games").withTags(histogramTags).record(games) - histogram("relay.push.moves").withTags(histogramTags).record(moves) - histogram("relay.push.errors").withTags(histogramTags).record(errors) - counter("relay.push.games.nb").withTags(counterTags).increment(games) - counter("relay.push.moves.nb").withTags(counterTags).increment(moves) - - object bot: - def moves(username: String) = counter("bot.moves").withTag("name", username) - def chats(username: String) = counter("bot.chats").withTag("name", username) - def gameStream(event: "start" | "stop") = counter("bot.gameStream").withTag("event", event) - object cheat: - def selfReport(wildName: String, auth: Boolean) = - val name = if wildName.startsWith("soc: ") then "soc" else wildName.takeWhile(' ' !=) - counter("cheat.selfReport").withTags(tags("name" -> name.escape, "auth" -> auth)) - val holdAlert = counter("cheat.holdAlert").withoutTags() - def autoAnalysis(reason: String) = counter("cheat.autoAnalysis").withTag("reason", reason) - val autoMark = counter("cheat.autoMark.count").withoutTags() - val autoReport = counter("cheat.autoReport.count").withoutTags() - object email: - object send: - private val c = counter("email.send") - val resetPassword = c.withTag("type", "resetPassword") - val magicLink = c.withTag("type", "magicLink") - val reopen = c.withTag("type", "reopen") - val fix = c.withTag("type", "fix") - val change = c.withTag("type", "change") - val confirmation = c.withTag("type", "confirmation") - val welcome = c.withTag("type", "welcome") - def time(mailer: String) = future("email.send.time", tags("mailer" -> mailer)) - val disposableDomain = gauge("email.disposableDomain").withoutTags() - object security: - val torNodes = gauge("security.tor.node").withoutTags() - object firewall: - val block = counter("security.firewall.block").withoutTags() - val ip = gauge("security.firewall.ip").withoutTags() - val prints = gauge("security.firewall.prints").withoutTags() - object proxy: - val request = future("security.proxy.time") - def result(r: Option[String]) = counter("security.proxy.result").withTag("result", r.getOrElse("none")) - def hit(prox: String, action: String) = - counter("security.proxy.hit").withTags(tags("proxy" -> prox, "action" -> action)) - def rateLimit(key: String) = counter("security.rateLimit.count").withTag("key", key) - def concurrencyLimit(key: String) = counter("security.concurrencyLimit.count").withTag("key", key) - object dnsApi: - val mx = future("security.dnsApi.mx.time") - object verifyMailApi: - def fetch(success: Boolean, ok: Boolean) = - timer("verifyMail.fetch").withTags(tags("success" -> successTag(success), "ok" -> ok)) - object mailcheckApi: - def fetch(success: Boolean, ok: Boolean) = - timer("mailcheck.fetch").withTags(tags("success" -> successTag(success), "ok" -> ok)) - object turnstile: - def hit(client: String, action: String, result: String) = - counter("turnstile.hit").withTags(tags("client" -> client, "action" -> action, "result" -> result)) - object pwned: - def get(res: Boolean) = timer("security.pwned.result").withTag("res", res) - object geoip: - val epoch = gauge("security.geoip.epoch").withoutTags() - val loadTime = gauge("security.geoip.loadTime").withoutTags() - object login: - def attempt(byEmail: Boolean, pwned: Boolean, result: Boolean) = - counter("security.login.attempt").withTags: - tags( - "by" -> (if byEmail then "email" else "name"), - "pwned" -> pwned, - "result" -> result - ) - def proxy(tpe: String) = counter("security.login.proxy").withTag("proxy", tpe) - def secretScanning(tokenType: String, source: String, hit: Boolean) = - counter("security.githubSecretScanning.hit").withTags( - tags("type" -> tokenType, "source" -> source.escape, "hit" -> hit) - ) - def userTrust(trust: Boolean, cause: String) = - counter("security.userTrust").withTags(tags("trust" -> trust, "cause" -> cause)).increment() - object shutup: - def analyzer = timer("shutup.analyzer.time").withoutTags() - object tv: - object selector: - def candidates(channel: String) = histogram("tv.selector.candidates").withTag("channel", channel) - def cheats(channel: String) = histogram("tv.selector.cheats").withTag("channel", channel) - def rating(channel: String) = histogram("tv.selector.rating").withTag("channel", channel) - object streamer: - def online = gauge("tv.streamer.count").withoutTags() - def present(n: String) = gauge("tv.streamer.present").withTag("name", n.escape) - object relation: - private val c = counter("relation.action") - val follow = c.withTag("type", "follow") - val unfollow = c.withTag("type", "unfollow") - val block = c.withTag("type", "block") - val unblock = c.withTag("type", "unblock") - object clas: - object student: - def create(teacher: UserId) = counter("clas.student.create").withTag("teacher", teacher) - def invite(teacher: UserId) = counter("clas.student.invite").withTag("teacher", teacher) - final class bloomFilter(name: String): - def count = gauge(s"clas.${name}.bloomFilter.count").withoutTags() - def fu = future(s"clas.${name}.bloomFilter.future") - object tournament: - object pairing: - val batchSize = histogram("tournament.pairing.batchSize").withoutTags() - val create = future("tournament.pairing.create") - val createRanking = timer("tournament.pairing.create.ranking").withoutTags() - val createPairings = timer("tournament.pairing.create.pairings").withoutTags() - val createPlayerMap = timer("tournament.pairing.create.playerMap").withoutTags() - val createInserts = timer("tournament.pairing.create.inserts").withoutTags() - val createFeature = timer("tournament.pairing.create.feature").withoutTags() - val createAutoPairing = timer("tournament.pairing.create.autoPairing").withoutTags() - val prep = future("tournament.pairing.prep") - val wmmatching = timer("tournament.pairing.wmmatching").withoutTags() - val created = gauge("tournament.count").withTag("type", "created") - val started = gauge("tournament.count").withTag("type", "started") - val waitingPlayers = histogram("tournament.waitingPlayers").withoutTags() - object startedOrganizer: - val tick = future("tournament.startedOrganizer.tick") - val waitingUsers = future("tournament.startedOrganizer.waitingUsers") - object createdOrganizer: - val tick = future("tournament.createdOrganizer.tick") - object lilaHttp: - val tick = future("tournament.lilaHttp.tick") - val fullSize = histogram("tournament.lilaHttp.fullSize").withoutTags() - val nbTours = gauge("tournament.lilaHttp.nbTours").withoutTags() - def apiShowPartial(partial: Boolean, client: String)(success: Boolean) = - timer("tournament.api.show").withTags: - tags( - "partial" -> partial, - "success" -> successTag(success), - "client" -> client - ) - def withdrawableIds(reason: String) = future("tournament.withdrawableIds", reason) - def action(tourId: String, action: String) = - timer("tournament.api.action").withTags(tags("tourId" -> tourId, "action" -> action)) - object notifier: - def tournaments = counter("tournament.notify.tournaments").withoutTags() - def players = counter("tournament.notify.players").withoutTags() - object featuring: - def forTeams(page: "index" | "homepage") = future("tournament.featuring.forTeams", page) - object swiss: - val tick = future("swiss.tick") - val bbpairing = timer("swiss.bbpairing").withoutTags() - val scoringGet = future("swiss.scoring.get") - val scoringRecompute = future("swiss.scoring.recompute") - val startRound = future("swiss.director.startRound") - def games(status: String) = histogram("swiss.ongoingGames").withTag("status", status) - val json = future("swiss.json") - object plan: - object paypalLegacy: - val amount = histogram("plan.amount").withTag("service", "paypal") - object paypalCheckout: - val amount = histogram("plan.amount").withTag("service", "paypalCheckout") - val fetchAccessToken = future("plan.paypal.accessToken") - val stripe = histogram("plan.amount").withTag("service", "stripe") - val goal = gauge("plan.goal").withoutTags() - val current = gauge("plan.current").withoutTags() - val percent = gauge("plan.percent").withoutTags() - def webhook(service: String, tpe: String) = - counter("plan.webhook").withTags(tags("service" -> service, "tpe" -> tpe)) - def intent(service: String, currency: java.util.Currency, coverFees: Boolean) = - counter("plan.intent").withTags: - tags("service" -> service, "currency" -> currency.getCurrencyCode, "coverFees" -> coverFees) - object charge: - def first(service: String) = counter("plan.charge.first").withTag("service", service) - def countryCents(country: String, currency: java.util.Currency, service: String, gift: Boolean) = - histogram("plan.charge.country.cents").withTags: - tags( - "country" -> country.escape, - "currency" -> currency.getCurrencyCode, - "service" -> service, - "gift" -> gift - ) - object forum: - object post: - val create = counter("forum.post.create").withoutTags() - object topic: - val view = counter("forum.topic.view").withoutTags() - def reaction(r: String) = counter("forum.reaction").withTag("reaction", r) - object msg: - def post(verdict: String, isNew: Boolean, multi: Boolean) = counter("msg.post").withTags( - tags("verdict" -> verdict, "isNew" -> isNew, "multi" -> multi) - ) - val teamBulk = histogram("msg.bulk.team").withoutTags() - def clasBulk(clasId: ClasId) = histogram("msg.bulk.clas").withTag("id", clasId.value) - object puzzle: - object selector: - object user: - def time(categ: String) = timer("puzzle.selector.user.puzzle").withTag("categ", categ) - def retries(categ: String) = histogram("puzzle.selector.user.retries").withTag("categ", categ) - val vote = histogram("puzzle.selector.user.vote").withoutTags() - def tier(t: String, categ: String, difficulty: String) = - counter("puzzle.selector.user.tier").withTags: - tags("tier" -> t, "categ" -> categ, "difficulty" -> difficulty) - def batch(nb: Int) = timer("puzzle.selector.user.batch").withTag("nb", nb) - object anon: - val time = timer("puzzle.selector.anon.puzzle").withoutTags() - def batch(nb: Int) = timer("puzzle.selector.anon.batch").withTag("nb", nb) - val vote = histogram("puzzle.selector.anon.vote").withoutTags() - def nextPuzzleResult(result: String) = - timer("puzzle.selector.user.puzzleResult").withTag("result", result) - def nextPathFor(categ: String, requester: String) = - timer("puzzle.path.nextFor").withTags(tags("categ" -> categ, "requester" -> requester)) - - object batch: - object selector: - val count = counter("puzzle.batch.selector.count").withoutTags() - val time = timer("puzzle.batch.selector").withoutTags() - val solve = counter("puzzle.batch.solve").withoutTags() - object round: - def attempt(user: Boolean, theme: String, rated: Boolean) = - counter("puzzle.attempt.count").withTags(tags("user" -> user, "theme" -> theme, "rated" -> rated)) - object vote: - def count(up: Boolean, win: Boolean) = - counter("puzzle.vote.count").withTags: - tags( - "up" -> up, - "win" -> win - ) - def theme(key: String, up: Option[Boolean], win: Boolean) = - counter("puzzle.vote.theme").withTags: - tags( - "up" -> up.fold("cancel")(_.toString), - "theme" -> key, - "win" -> win - ) - val future = mon.future("puzzle.vote.future") - val crazyGlicko = counter("puzzle.crazyGlicko").withoutTags() - object storm: - object selector: - val time = future("storm.selector.time") - val sets = histogram("storm.selector.sets").withoutTags() - val count = histogram("storm.selector.count").withoutTags() - val rating = histogram("storm.selector.rating").withoutTags() - def ratingSlice(index: Int) = histogram("storm.selector.ratingSlice").withTag("index", index) - object run: - def score(auth: Boolean) = histogram("storm.run.score").withTag("auth", auth) - def sign(cause: String) = counter("storm.run.sign").withTag("cause", cause) - object racer: - private def tpe(lobby: Boolean) = if lobby then "lobby" else "friend" - def race(lobby: Boolean) = counter("racer.lobby.race").withTag("tpe", tpe(lobby)) - def players(lobby: Boolean) = - histogram("racer.lobby.players").withTag("tpe", tpe(lobby)) - def score(lobby: Boolean, auth: Boolean) = - histogram("racer.player.score").withTags: - tags( - "tpe" -> tpe(lobby), - "auth" -> auth - ) - object streak: - object selector: - val time = timer("streak.selector.time").withoutTags() - val count = histogram("streak.selector.count").withoutTags() - val rating = histogram("streak.selector.rating").withoutTags() - def ratingSlice(index: Int) = histogram("streak.selector.ratingSlice").withTag("index", index) - object run: - def score(auth: String) = histogram("streak.run.score").withTag("auth", auth) - object game: - import chess.{ Speed, Rated, Status } - import lila.core.game.Source - def finish(variant: Variant, speed: Speed, source: Option[Source], mode: Rated, status: Status) = - counter("game.finish").withTags: - tags( - "variant" -> variant.key, - "speed" -> speed.key, - "source" -> source.fold("unknown")(_.name), - "mode" -> mode.name, - "status" -> status.name - ) - val fetch = counter("game.fetch.count").withoutTags() - val loadClockHistory = counter("game.loadClockHistory.count").withoutTags() - object pgn: - def encode(format: String) = timer("game.pgn.encode").withTag("format", format) - def decode(format: String) = timer("game.pgn.decode").withTag("format", format) - val idCollision = counter("game.idCollision").withoutTags() - def idGenerator(collisions: Int) = timer("game.idGenerator").withTags(tags("collisions" -> collisions)) - object streamByOauthOrigin: - def event(tpe: String) = counter("game.streamByOauthOrigin.event").withTag("type", tpe) - def users(sel: String) = gauge("game.streamByOauthOrigin.users").withTag("selector", sel) - def streams(ua: UserAgent) = gauge("game.streamByOauthOrigin.streams").withTag("ua", ua.value) - val bloomFP = counter("game.streamByOauthOrigin.bloomFP").withoutTags() - object chat: - private val msgCounter = counter("chat.message") - def message(parent: String, troll: Boolean) = - msgCounter.withTags(tags("parent" -> parent, "troll" -> troll)) - def fetch(parent: String) = timer("chat.fetch").withTag("parent", parent) - object push: - object register: - def in(platform: String) = counter("push.register").withTag("platform", platform) - val out = counter("push.register.out").withoutTags() - object web: - def post = future("push.web.post") - object send: - private def send(tpe: String)(platform: String, success: Boolean, count: Int): Unit = - counter("push.send") - .withTags: - tags( - "type" -> tpe, - "platform" -> platform, - "success" -> successTag(success) - ) - .increment(count) - () - val move = send("move") - val takeback = send("takeback") - val draw = send("draw") - val corresAlarm = send("corresAlarm") - val finish = send("finish") - val message = send("message") - val tourSoon = send("tourSoon") - val forumMention = send("forumMention") - val invitedStudy = send("invitedStudy") - val streamStart = send("streamStart") - val broadcastRound = send("broadcastRound") - - object challenge: - val create = send("challengeCreate") - val accept = send("challengeAccept") - val googleTokenTime = timer("push.send.googleToken").withoutTags() - def firebaseStatus(project: String, typ: String, status: Int) = - counter("push.firebase.status").withTags(tags("status" -> status, "project" -> project, "type" -> typ)) - object fishnet: - object client: - object result: - private val c = counter("fishnet.client.result") - private def apply(r: String)(client: UserId) = - c.withTags(tags("client" -> client, "result" -> r)) - val success = apply("success") - val failure = apply("failure") - val timeout = apply("timeout") - val notFound = apply("notFound") - val notAcquired = apply("notAcquired") - val abort = apply("abort") - def status(enabled: Boolean) = gauge("fishnet.client.status").withTag("enabled", enabled) - def version(v: String) = gauge("fishnet.client.version").withTag("version", v.escape) - def queueTime(sender: "system" | "user") = timer("fishnet.queue.db").withTag("sender", sender) - val acquire = future("fishnet.acquire") - def work(typ: String, as: "system" | "user") = - gauge("fishnet.work").withTags(tags("type" -> typ, "for" -> as)) - def oldest(as: "system" | "user") = gauge("fishnet.oldest").withTag("for", as) - object analysis: - object by: - def movetime(client: UserId) = histogram("fishnet.analysis.movetime").withTag("client", client) - def node(client: UserId) = histogram("fishnet.analysis.node").withTag("client", client) - def nps(client: UserId) = histogram("fishnet.analysis.nps").withTag("client", client) - def depth(client: UserId) = histogram("fishnet.analysis.depth").withTag("client", client) - def pvSize(client: UserId) = histogram("fishnet.analysis.pvSize").withTag("client", client) - def pv(client: UserId, isLong: Boolean) = - counter("fishnet.analysis.pvs").withTags(tags("client" -> client, "long" -> isLong)) - def totalMeganode(client: UserId) = - counter("fishnet.analysis.total.meganode").withTag("client", client) - def totalSecond(client: UserId) = - counter("fishnet.analysis.total.second").withTag("client", client) - def requestCount(tpe: "game" | "study") = counter("fishnet.analysis.request").withTag("type", tpe) - val evalCacheHits = histogram("fishnet.analysis.evalCacheHits").withoutTags() - val skipPositionsGame = future("fishnet.analysis.skipPositions.game") - val skipPositionsStudy = future("fishnet.analysis.skipPositions.study") - object http: - def request(hit: Boolean) = counter("fishnet.http.acquire").withTag("hit", hit) - def move(level: Int) = counter("fishnet.move.time").withTag("level", level) - def openingBook(variant: Variant, hit: Boolean) = - timer("fishnet.opening.hit").withTags: - tags("variant" -> variant.key, "hit" -> hitTag(hit)) - object opening: - def searchTime = timer("opening.search.time").withoutTags() - object explorer: - def stats = future("opening.explorer.stats") - object study: - object tree: - val read = timer("study.tree.read").withoutTags() - val write = timer("study.tree.write").withoutTags() - object sequencer: - val chapterTime = timer("study.sequencer.chapter.time").withoutTags() - object api: - val users = counter("api.cost").withTag("endpoint", "users") - val activity = counter("api.cost").withTag("endpoint", "activity") - object challenge: - object bulk: - def scheduleNb(by: UserId) = counter("api.challenge.bulk.schedule.nb").withTag("by", by) - def createNb(by: UserId) = counter("api.challenge.bulk.create.nb").withTag("by", by) - object `export`: - object png: - val game = counter("export.png").withTag("type", "game") - val puzzle = counter("export.png").withTag("type", "puzzle") - object bus: - val classifiers = gauge("bus.classifiers").withoutTags() - object blocking: - def time(name: String) = timer("blocking.time").withTag("name", name) - def timeout(name: String) = counter("blocking.timeout").withTag("name", name) - object workQueue: - def offerFail(name: String, result: String) = - counter("workQueue.offerFail").withTags: - tags("name" -> name, "result" -> result) - def timeout(name: String) = counter("workQueue.timeout").withTag("name", name) - class parallelQueue(name: String): - val parallelism = gauge("parallelQueue.parallelism").withTag("name", name) - val computeTimeout = counter("parallelQueue.buildTimeout").withTag("name", name) - object markdown: - val time = timer("markdown.time").withoutTags() - def pgnsFromText = future("markdown.pgnsFromText") - object ublog: - def create(user: UserId) = counter("ublog.create").withTag("user", user) - def view = counter("ublog.view").withoutTags() - object automod: - val request = future("ublog.automod.request") - def quality(q: String) = counter("ublog.automod.quality").withTag("quality", q) - def flagged(f: Boolean) = counter("ublog.automod.flagged").withTag("flagged", f) - object picfit: - def uploadTime(user: UserId) = future("picfit.upload.time", tags("user" -> user)) - def uploadSize(user: UserId) = histogram("picfit.upload.size").withTag("user", user) - object fideSync: - val time = future("fide.sync.time") - val players = gauge("fide.sync.players").withoutTags() - val updated = gauge("fide.sync.updated").withoutTags() - object recap: - val games = future("recap.build.games.time") - val puzzles = future("recap.build.puzzles.time") - - object jvm: - def threads() = - val perState = gauge("jvm.threads.group") - val total = gauge("jvm.threads.group.total") - for - group <- scalalib.Jvm.threadGroups() - _ = total.withTags(tags("name" -> group.name)).update(group.total) - (state, count) <- group.states - yield perState.withTags(tags("name" -> group.name, "state" -> state.toString)).update(count) - - object prometheus: - val lines = gauge("prometheus.lines").withoutTags() - - def chronoSync[A] = lila.common.Chronometer.syncMon[A] - - type TimerPath = lila.mon.type => Timer - type CounterPath = lila.mon.type => Counter - - private def future(name: String) = (success: Boolean) => timer(name).withTag("success", successTag(success)) - private def future(name: String, tags: Map[String, Any]) = (success: Boolean) => - timer(name).withTags(tags + ("success" -> successTag(success))) - private def future(name: String, segment: String)(success: Boolean) = - timer(name).withTags: - tags("success" -> successTag(success), "segment" -> segment) - - private def successTag(success: Boolean) = if success then "success" else "failure" - private def hitTag(hit: Boolean) = if hit then "hit" else "miss" - - import scala.language.implicitConversions - private given Conversion[UserId, String] = _.value - private given Conversion[Map[String, Any], TagSet] = TagSet.from diff --git a/modules/common/src/main/package.scala b/modules/common/src/main/package.scala index fcd1bbb2c54..0da6a343bd0 100644 --- a/modules/common/src/main/package.scala +++ b/modules/common/src/main/package.scala @@ -5,7 +5,6 @@ import scalalib.data.LazyFu export lila.core.lilaism.Lilaism.{ *, given } object extensions: - export Chronometer.futureExtension.* // replaces Product.unapply in play forms def unapply[P <: Product](p: P)(using m: scala.deriving.Mirror.ProductOf[P]): Option[m.MirroredElemTypes] = Some(Tuple.fromProductTyped(p)) diff --git a/modules/db/src/main/Db.scala b/modules/db/src/main/Db.scala index 3edf16ec92a..98a082e4ae0 100644 --- a/modules/db/src/main/Db.scala +++ b/modules/db/src/main/Db.scala @@ -2,7 +2,6 @@ package lila.db import reactivemongo.api.* -import lila.common.Chronometer import lila.core.config.CollName import lila.db.dsl.Coll @@ -37,16 +36,18 @@ final class Db( private val logger = lila.db.logger.branch(name) - private lazy val db: DB = Chronometer.syncEffect( - MongoConnection - .fromString(uri) - .flatMap: parsedUri => - driver - .connect(parsedUri, name.some) - .flatMap(_.database(parsedUri.db.getOrElse("lichess"))) - .await(5.seconds, s"db:$name") - ) { lap => - logger.info(s"MongoDB connected to $uri in ${lap.showDuration}") - } + private lazy val db: DB = + logger.info(s"MongoDB connecting to $uri") + val connected = scala.concurrent.Await.result( + MongoConnection + .fromString(uri) + .flatMap: parsedUri => + driver + .connect(parsedUri, name.some) + .flatMap(_.database(parsedUri.db.getOrElse("lichess"))), + 5.seconds + ) + logger.info(s"MongoDB connected to $uri") + connected def apply(name: CollName): Coll = db.collection(name.value) diff --git a/modules/fide/src/main/FidePlayerSync.scala b/modules/fide/src/main/FidePlayerSync.scala index d9b9dcaa4fb..afc148c3f2a 100644 --- a/modules/fide/src/main/FidePlayerSync.scala +++ b/modules/fide/src/main/FidePlayerSync.scala @@ -9,6 +9,7 @@ import reactivemongo.api.bson.* import java.util.zip.ZipInputStream import java.time.YearMonth +import lila.mon.extensions.* import lila.core.fide.Federation import lila.db.dsl.{ *, given } @@ -121,7 +122,7 @@ final private class FidePlayerSync(repo: FideRepo, ws: StandaloneWSClient)(using .map(_.toList) .mapAsync(1)(saveIfChanged) .runWith(lila.common.LilaStream.sinkSum) - .monSuccess(_.fideSync.time) + .monSuccess(lila.mon.fideSync.time) nbAll <- repo.player.countAll yield lila.mon.fideSync.updated.update(nbUpdated) diff --git a/modules/fishnet/src/main/Analyser.scala b/modules/fishnet/src/main/Analyser.scala index 7135fda8185..c0db4fc3705 100644 --- a/modules/fishnet/src/main/Analyser.scala +++ b/modules/fishnet/src/main/Analyser.scala @@ -3,6 +3,7 @@ package lila.fishnet import chess.Ply import scalalib.cache.OnceEvery +import lila.mon.extensions.* import lila.analyse.AnalysisRepo import lila.core.id import lila.fishnet.Work.{ Origin, Sender } @@ -68,7 +69,7 @@ final class Analyser( lila.mon.fishnet.analysis.requestCount("game").increment() evalCache .skipPositions(work.game) - .monSuccess(_.fishnet.analysis.skipPositionsGame) + .monSuccess(lila.mon.fishnet.analysis.skipPositionsGame) .flatMap: skipPositions => lila.mon.fishnet.analysis.evalCacheHits.record(skipPositions.size) repo.addAnalysis(work.copy(skipPositions = skipPositions)) @@ -119,7 +120,7 @@ final class Analyser( lila.mon.fishnet.analysis.requestCount("study").increment() evalCache .skipPositions(work.game) - .monSuccess(_.fishnet.analysis.skipPositionsStudy) + .monSuccess(lila.mon.fishnet.analysis.skipPositionsStudy) .withTimeout(2.seconds, s"study analysis skipPositions $work") .recoverDefault .flatMap: skipPositions => diff --git a/modules/fishnet/src/main/FishnetApi.scala b/modules/fishnet/src/main/FishnetApi.scala index adcd7d1dc2a..4fc44ab85d6 100644 --- a/modules/fishnet/src/main/FishnetApi.scala +++ b/modules/fishnet/src/main/FishnetApi.scala @@ -7,6 +7,7 @@ import scala.util.{ Failure, Success, Try } import lila.core.lilaism.LilaNoStackTrace import lila.core.net.IpAddress +import lila.mon.extensions.* import lila.db.dsl.{ *, given } import Client.Skill @@ -30,7 +31,7 @@ final class FishnetApi( maxSize = Max(256), timeout = 5.seconds, name = "fishnetApi", - lila.log.asyncActorMonitor.full + lila.mon.asyncActorMonitor.full ) def keyExists(key: Client.Key) = repo.getEnabledClient(key).map(_.isDefined) @@ -50,7 +51,7 @@ final class FishnetApi( .match case Skill.Move => fufail(s"Can't acquire a move directly on lichess! $client") case Skill.Analysis | Skill.All => acquireAnalysis(client, slow) - .monSuccess(_.fishnet.acquire) + .monSuccess(lila.mon.fishnet.acquire) .recover { case e: Exception => logger.error("Fishnet.acquire", e) none diff --git a/modules/fishnet/src/main/FishnetOpeningBook.scala b/modules/fishnet/src/main/FishnetOpeningBook.scala index f782d143edd..821234924f4 100644 --- a/modules/fishnet/src/main/FishnetOpeningBook.scala +++ b/modules/fishnet/src/main/FishnetOpeningBook.scala @@ -9,6 +9,7 @@ import play.api.libs.ws.StandaloneWSClient import scalalib.ThreadLocalRandom import lila.common.Json.given +import lila.mon.extensions.* import lila.memo.SettingStore final private class FishnetOpeningBook( @@ -51,7 +52,7 @@ final private class FishnetOpeningBook( none } .monTry: res => - _.fishnet.openingBook( + lila.mon.fishnet.openingBook( variant = game.variant, hit = res.toOption.exists(_.isDefined) ) diff --git a/modules/fishnet/src/test/AnnotatorTest.scala b/modules/fishnet/src/test/AnnotatorTest.scala index d2c6faf7905..f0c93262011 100644 --- a/modules/fishnet/src/test/AnnotatorTest.scala +++ b/modules/fishnet/src/test/AnnotatorTest.scala @@ -11,6 +11,7 @@ import lila.analyse.Annotator import lila.core.config.NetDomain import lila.core.game.Player import lila.core.id.GamePlayerId +import lila.mon.extensions.* import JsonApi.* import readers.given diff --git a/modules/game/src/main/CaptchaApi.scala b/modules/game/src/main/CaptchaApi.scala index a05e629c35c..e1e9bdcf64c 100644 --- a/modules/game/src/main/CaptchaApi.scala +++ b/modules/game/src/main/CaptchaApi.scala @@ -8,6 +8,7 @@ import scala.util.Success import lila.core.captcha.{ Captcha, CaptchaApi as ICaptchaApi, Solutions, WithCaptcha } import lila.core.game.Game +import lila.mon.extensions.* // only works with standard chess (not chess960) final private class CaptchaApi(gameRepo: GameRepo)(using Executor) extends ICaptchaApi: diff --git a/modules/game/src/main/IdGenerator.scala b/modules/game/src/main/IdGenerator.scala index 32dfa2d682d..2711a6a20ec 100644 --- a/modules/game/src/main/IdGenerator.scala +++ b/modules/game/src/main/IdGenerator.scala @@ -5,23 +5,25 @@ import scalalib.SecureRandom import lila.core.game.{ Game, NewGame } import lila.core.id.GameId import lila.common.BatchProvider +import lila.mon.extensions.* import lila.db.dsl.{ *, given } final class IdGenerator(gameRepo: GameRepo)(using Executor, Scheduler) extends lila.core.game.IdGenerator: import lila.core.game.IdGenerator.* - private val batchProvider = BatchProvider[GameId]("idGenerator", timeout = 3.seconds): () => - // must NOT use `games(nb)` for it would cause a deadlock - // due to `games` calling `game` which calls `batchProvider.one` - val ids = List.fill(256)(uncheckedGame).distinct - gameRepo.coll - .distinctEasy[GameId, List]("_id", $inIds(ids)) - .monValue: collisions => - _.game.idGenerator(collisions.size) - .map: - case Nil => ids - case collisions => ids.filterNot(collisions.contains) + private val batchProvider = + BatchProvider[GameId]("idGenerator", timeout = 3.seconds, lila.mon.asyncActorMonitor.full): () => + // must NOT use `games(nb)` for it would cause a deadlock + // due to `games` calling `game` which calls `batchProvider.one` + val ids = List.fill(256)(uncheckedGame).distinct + gameRepo.coll + .distinctEasy[GameId, List]("_id", $inIds(ids)) + .monValue: collisions => + lila.mon.game.idGenerator(collisions.size) + .map: + case Nil => ids + case collisions => ids.filterNot(collisions.contains) def game: Fu[GameId] = batchProvider.one @@ -34,7 +36,7 @@ final class IdGenerator(gameRepo: GameRepo)(using Executor, Scheduler) extends l gameRepo.coll .distinctEasy[GameId, Set]("_id", $inIds(ids)) .monValue: collisions => - _.game.idGenerator(collisions.size) + lila.mon.game.idGenerator(collisions.size) .flatMap: collisions => games(collisions.size).dmap { _ ++ (ids.diff(collisions)) } .map(_.toList) diff --git a/modules/game/src/main/PgnStorage.scala b/modules/game/src/main/PgnStorage.scala index 8a76fdfbf0c..7b26dfb5d05 100644 --- a/modules/game/src/main/PgnStorage.scala +++ b/modules/game/src/main/PgnStorage.scala @@ -8,15 +8,17 @@ import lila.db.ByteArray private object PgnStorage: + import lila.mon.Chronometer.syncMon as monitor + object OldBin: def encode(sans: Vector[SanStr]) = ByteArray: - monitor(_.game.pgn.encode("old")): + monitor(lila.mon.game.pgn.encode("old")): format.pgn.Binary.writeMoves(sans).get def decode(bytes: ByteArray, plies: Ply): Vector[SanStr] = - monitor(_.game.pgn.decode("old")): + monitor(lila.mon.game.pgn.decode("old")): format.pgn.Binary.readMoves(bytes.value.toList, plies.value).get.toVector object Huffman: @@ -25,11 +27,11 @@ private object PgnStorage: def encode(sans: Vector[SanStr]) = ByteArray: - monitor(_.game.pgn.encode("huffman")): + monitor(lila.mon.game.pgn.encode("huffman")): Encoder.encode(SanStr.raw(sans.toArray)) def decode(bytes: ByteArray, plies: Ply, id: GameId): Decoded = - monitor(_.game.pgn.decode("huffman")): + monitor(lila.mon.game.pgn.decode("huffman")): val decoded = try Encoder.decode(bytes.value, plies.value) catch @@ -72,6 +74,3 @@ private object PgnStorage: castles: Castles, // irrelevant after game ends halfMoveClock: HalfMoveClock // irrelevant after game ends ) - - private def monitor[A](mon: lila.mon.TimerPath)(f: => A): A = - lila.common.Chronometer.syncMon(mon)(f) diff --git a/modules/i18n/src/main/Registry.scala b/modules/i18n/src/main/Registry.scala index 50dc788aab4..262235f402b 100644 --- a/modules/i18n/src/main/Registry.scala +++ b/modules/i18n/src/main/Registry.scala @@ -7,7 +7,6 @@ import java.io.ObjectInputStream import java.util.Map as JMap import scala.jdk.CollectionConverters.* -import lila.common.Chronometer import lila.core.i18n.{ I18nKey, defaultLang } object Registry: @@ -26,11 +25,8 @@ object Registry: .zipWithIndex .foreach: (langs, i) => scheduler.scheduleOnce(i.seconds): - val lap = Chronometer.sync: - langs.foreach: lang => - register(lang, loadSerialized(lang)) - if i < 1 || mode.isProd - then logger.info(s"Loaded ${langs.size} languages in ${lap.showDuration}") + langs.foreach: lang => + register(lang, loadSerialized(lang)) // for tests private[i18n] def getAll(lang: Lang): Option[MessageMap] = all.get(lang) diff --git a/modules/insight/src/main/InsightApi.scala b/modules/insight/src/main/InsightApi.scala index 5bc5174ecac..6a89c8cc4c9 100644 --- a/modules/insight/src/main/InsightApi.scala +++ b/modules/insight/src/main/InsightApi.scala @@ -3,6 +3,7 @@ package lila.insight import scalalib.HeapSort.botN import lila.game.GameRepo +import lila.mon.extensions.* final class InsightApi( storage: InsightStorage, @@ -41,14 +42,14 @@ final class InsightApi( gameRepo.userPovsByGameIds(clusters.flatMap(_.gameIds).botN(4), user) .map { Answer(question, clusters, _) } } - .monSuccess(_.insight.user) + .monSuccess(lila.mon.insight.user) def askPeers[X](question: Question[X], rating: MeanRating, nbGames: Max): Fu[Answer[X]] = pipeline .aggregate(question, Right(PeersRatingRange.of(rating)), withPovs = false, nbGames = nbGames) .map: aggDocs => Answer(question, AggregationClusters(question, aggDocs), Nil) - .monSuccess(_.insight.peers) + .monSuccess(lila.mon.insight.peers) def userStatus(user: User): Fu[UserStatus] = indexer @@ -65,7 +66,7 @@ final class InsightApi( case _ => UserStatus.Fresh def indexAll(user: User, force: Boolean): Funit = - for _ <- indexer.all(user, force).monSuccess(_.insight.index) + for _ <- indexer.all(user, force).monSuccess(lila.mon.insight.index) yield userCache.put(user.id, computeUser(user.id)) def updateGame(g: Game) = diff --git a/modules/insight/src/main/InsightIndexer.scala b/modules/insight/src/main/InsightIndexer.scala index bff4a4d76bd..8afe3229f0f 100644 --- a/modules/insight/src/main/InsightIndexer.scala +++ b/modules/insight/src/main/InsightIndexer.scala @@ -19,7 +19,7 @@ final private class InsightIndexer( maxSize = Max(256), timeout = 1.minute, name = "insightIndexer", - lila.log.asyncActorMonitor.full + lila.mon.asyncActorMonitor.full ) def all(user: User, force: Boolean): Funit = diff --git a/modules/irc/src/main/ZulipClient.scala b/modules/irc/src/main/ZulipClient.scala index e591b894503..9f274abd319 100644 --- a/modules/irc/src/main/ZulipClient.scala +++ b/modules/irc/src/main/ZulipClient.scala @@ -7,6 +7,7 @@ import play.api.libs.ws.JsonBodyReadables.* import play.api.libs.ws.{ StandaloneWSClient, WSAuthScheme } import lila.common.String.urlencode +import lila.mon.extensions.* import lila.core.config.Secret final private class ZulipClient(ws: StandaloneWSClient, config: ZulipClient.Config)(using @@ -54,7 +55,7 @@ final private class ZulipClient(ws: StandaloneWSClient, config: ZulipClient.Conf case JsSuccess(result, _) => fuccess(result.some) case JsError(err) => fufail(s"[zulip]: $err, $msg ${res.status} ${res.body}") case res => fufail(s"[zulip] $msg ${res.status} ${res.body}") - .monSuccess(_.irc.zulip.say(msg.stream)) + .monSuccess(lila.mon.irc.zulip.say(msg.stream)) .logFailure(lila.log("zulip")) .recoverDefault diff --git a/modules/irwin/src/main/KaladinApi.scala b/modules/irwin/src/main/KaladinApi.scala index d5c74891227..d9c5363ca00 100644 --- a/modules/irwin/src/main/KaladinApi.scala +++ b/modules/irwin/src/main/KaladinApi.scala @@ -33,7 +33,7 @@ final class KaladinApi( maxSize = Max(512), timeout = 2.minutes, name = "kaladinApi", - lila.log.asyncActorMonitor.full + lila.mon.asyncActorMonitor.full ) private def sequence[A <: Matchable](user: Suspect)(f: Option[KaladinUser] => Fu[A]): Fu[A] = diff --git a/modules/lobby/src/main/LobbySyncActor.scala b/modules/lobby/src/main/LobbySyncActor.scala index cf3fe29f5d9..12a7094a096 100644 --- a/modules/lobby/src/main/LobbySyncActor.scala +++ b/modules/lobby/src/main/LobbySyncActor.scala @@ -5,6 +5,7 @@ import scalalib.actor.SyncActor import lila.common.Bus import lila.core.pool.{ HookThieve, IsClockCompatible } import lila.core.socket.{ Sri, Sris } +import lila.mon.extensions.* final private class LobbySyncActor( seekApi: SeekApi, @@ -98,7 +99,7 @@ final private class LobbySyncActor( .chronometer .logIfSlow(100, logger): r => s"GetSris size=${r.sris.size}" - .mon(_.lobby.socket.getSris) + .mon(lila.mon.lobby.socket.getSris) .result .logFailure(logger, err => s"broom cannot get sris from socket: $err") .foreach { this ! WithPromise(_, promise) } diff --git a/modules/mailer/src/main/Mailer.scala b/modules/mailer/src/main/Mailer.scala index 82b9b67a676..006c507ced9 100644 --- a/modules/mailer/src/main/Mailer.scala +++ b/modules/mailer/src/main/Mailer.scala @@ -1,5 +1,7 @@ package lila.mailer +import scala.concurrent.blocking + import akka.actor.ActorSystem import play.api.ConfigLoader import play.api.libs.mailer.{ Email, SMTPConfiguration, SMTPMailer } @@ -7,8 +9,7 @@ import scalatags.Text.all.{ html as htmlTag, * } import scalatags.Text.tags2.title as titleTag import org.apache.commons.mail.EmailException -import scala.concurrent.blocking - +import lila.mon.extensions.* import lila.common.String.html.nl2br import lila.common.autoconfig.* import lila.core.i18n.I18nKey.emails as trans @@ -71,7 +72,7 @@ final class Mailer( ) blocking: client.mailer.send(email) - .monSuccess(_.email.send.time(client.toString)) + .monSuccess(lila.mon.email.send.time(client.toString)) .recoverWith: case _: EmailException if msg.to.normalize.value != msg.to.value => logger.warn(s"Email ${msg.to} is invalid, trying ${msg.to.normalize}") diff --git a/modules/memo/src/main/MarkdownCache.scala b/modules/memo/src/main/MarkdownCache.scala index 92cab2555fd..41dac354f87 100644 --- a/modules/memo/src/main/MarkdownCache.scala +++ b/modules/memo/src/main/MarkdownCache.scala @@ -3,6 +3,7 @@ package lila.memo import scalalib.future.TimeoutException import lila.common.{ Bus, Markdown, MarkdownRender, MarkdownToastUi } +import lila.mon.extensions.* import lila.core.config import lila.core.misc.lpv.{ LpvEmbed, Lpv as LpvBus } @@ -66,7 +67,7 @@ final class MarkdownCache( .logIfSlow(300, logger): result => s"AllPgnsFromText for markdown $key - found ${result.size} embeds" .result - .monSuccess(_.markdown.pgnsFromText) + .monSuccess(lila.mon.markdown.pgnsFromText) .andThen: case scala.util.Success(pgns) => cache.putAll(pgns) .recoverWith: @@ -93,9 +94,14 @@ final class MarkdownCache( ) ) - private def bodyProcessor(key: RenderKey, opts: MarkdownOptions): Markdown => Html = - if opts.toastUi then toastUiProcessor(key, opts) - else getRenderer(opts)(key) + private def bodyProcessor(key: RenderKey, opts: MarkdownOptions)(text: Markdown): Html = + lila.mon.Chronometer + .sync: + if opts.toastUi then toastUiProcessor(key, opts)(text) + else getRenderer(opts)(key)(text) + .mon(lila.mon.markdown.time) + .logIfSlow(50, logger.branch(key))(_ => s"slow markdown size:${text.value.size}") + .result private def toastUiProcessor(key: RenderKey, opts: MarkdownOptions): Markdown => Html = MarkdownToastUi.unescapeAtUsername.apply diff --git a/modules/memo/src/main/MongoCache.scala b/modules/memo/src/main/MongoCache.scala index b9363f2dfb0..284fe2b5679 100644 --- a/modules/memo/src/main/MongoCache.scala +++ b/modules/memo/src/main/MongoCache.scala @@ -4,6 +4,7 @@ import com.github.blemale.scaffeine.AsyncLoadingCache import reactivemongo.api.bson.* import lila.db.dsl.{ *, given } +import lila.mon.extensions.* import CacheApi.* @@ -36,7 +37,7 @@ final class MongoCache[K, V: BSONHandler] private ( ) .inject(v) } - .mon(_.mongoCache.compute(name)) + .mon(lila.mon.mongoCache.compute(name)) case Some(entry) => lila.mon.mongoCache.request(name, hit = true).increment() fuccess(entry.v) diff --git a/modules/memo/src/main/MongoRateLimit.scala b/modules/memo/src/main/MongoRateLimit.scala index b498d7cb60e..b2e09f3e853 100644 --- a/modules/memo/src/main/MongoRateLimit.scala +++ b/modules/memo/src/main/MongoRateLimit.scala @@ -29,7 +29,7 @@ final class MongoRateLimit[K]( expiration = 1.minute, timeout = 10.seconds, name = s"$name.sequencer", - lila.log.asyncActorMonitor.highCardinality + lila.mon.asyncActorMonitor.highCardinality ) private def makeDbKey(k: K) = s"ratelimit:$name:${keyToString(k)}" diff --git a/modules/memo/src/main/ParallelMongoQueue.scala b/modules/memo/src/main/ParallelMongoQueue.scala index 8deabae7634..f955448346a 100644 --- a/modules/memo/src/main/ParallelMongoQueue.scala +++ b/modules/memo/src/main/ParallelMongoQueue.scala @@ -50,7 +50,7 @@ final class ParallelMongoQueue[A: BSONHandler]( maxSize = Max(256), timeout = 5.seconds, s"$name.workQueue", - lila.log.asyncActorMonitor.full + lila.mon.asyncActorMonitor.full ) /* Read the oldest entries from the queue diff --git a/modules/memo/src/main/Syncache.scala b/modules/memo/src/main/Syncache.scala index 4af3dcc3073..54925e771ac 100644 --- a/modules/memo/src/main/Syncache.scala +++ b/modules/memo/src/main/Syncache.scala @@ -6,6 +6,7 @@ import java.util.concurrent.TimeUnit import scala.util.Success import lila.common.Uptime +import lila.mon.extensions.* /** A synchronous cache from asynchronous computations. It will attempt to serve cached responses * synchronously. If none is available, it starts an async computation, and either waits for the result or @@ -37,7 +38,7 @@ final class Syncache[K, V]( new CacheLoader[K, Fu[V]]: def load(k: K) = compute(k) - .mon(_ => recCompute) // monitoring: record async time + .mon(recCompute) // monitoring: record async time .recover { case e: Exception => logger.branch(s"syncache $name").warn(s"key=$k", e) cache.invalidate(k) @@ -79,15 +80,15 @@ final class Syncache[K, V]( private def waitForResult(k: K, fu: Fu[V], duration: FiniteDuration): V = try - lila.common.Chronometer.syncMon(_ => recWait): + lila.mon.Chronometer.syncMon(recWait): fu.await(duration, s"syncache:$name") catch case _: java.util.concurrent.TimeoutException => incTimeout() default(k) - private val incMiss = (() => lila.mon.syncache.miss(name).increment()) - private val incTimeout = (() => lila.mon.syncache.timeout(name).increment()) + private val incMiss = () => lila.mon.syncache.miss(name).increment() + private val incTimeout = () => lila.mon.syncache.timeout(name).increment() private val recWait = lila.mon.syncache.wait(name) private val recCompute = lila.mon.syncache.compute(name) diff --git a/modules/memo/src/main/picfit/PicfitApi.scala b/modules/memo/src/main/picfit/PicfitApi.scala index a5b8be2f542..0ebcf0eafb3 100644 --- a/modules/memo/src/main/picfit/PicfitApi.scala +++ b/modules/memo/src/main/picfit/PicfitApi.scala @@ -11,6 +11,7 @@ import scala.util.matching.Regex.quote import scalalib.paginator.AdapterLike import lila.common.Bus +import lila.mon.extensions.* import lila.core.id.ImageId import lila.db.dsl.{ *, given } @@ -182,7 +183,7 @@ final class PicfitApi( if image.size > 0 then lila.mon.picfit.uploadSize(image.user).record(image.size) funit } - .monSuccess(_.picfit.uploadTime(image.user)) + .monSuccess(lila.mon.picfit.uploadTime(image.user)) def delete(image: PicfitImage): Funit = ws.url(s"${config.endpointPost}/${image.id}") diff --git a/modules/mon/src/main/AsyncActorMonitor.scala b/modules/mon/src/main/AsyncActorMonitor.scala new file mode 100644 index 00000000000..5f9b1f6fa4d --- /dev/null +++ b/modules/mon/src/main/AsyncActorMonitor.scala @@ -0,0 +1,25 @@ +package lila.mon + +import play.api.Logger + +object asyncActorMonitor: + + private lazy val logger = Logger("asyncActor") + + lazy val full = scalalib.actor.AsyncActorBounded.Monitor( + overflow = name => + lila.mon.asyncActor.overflow(name).increment() + logger.warn(s"[$name] queue is full") + , + queueSize = (name, size) => lila.mon.asyncActor.queueSize(name).record(size), + unhandled = (name, msg) => logger.warn(s"[$name] unhandled msg: $msg") + ) + + lazy val highCardinality = full.copy( + queueSize = (_, _) => () + ) + + lazy val unhandled = full.copy( + overflow = _ => (), + queueSize = (_, _) => () + ) diff --git a/modules/common/src/main/Chronometer.scala b/modules/mon/src/main/Chronometer.scala similarity index 74% rename from modules/common/src/main/Chronometer.scala rename to modules/mon/src/main/Chronometer.scala index 10e856c0708..da2266c9074 100644 --- a/modules/common/src/main/Chronometer.scala +++ b/modules/mon/src/main/Chronometer.scala @@ -1,4 +1,11 @@ -package lila.common +package lila.mon + +import scala.concurrent.Future +import scala.concurrent.duration.FiniteDuration +import kamon.metric.Timer +import play.api.LoggerLike + +import lila.core.lilaism.Lilaism.* object Chronometer: @@ -8,7 +15,7 @@ object Chronometer: extension [A](fua: Future[A]) def await(duration: FiniteDuration, name: String): A = - Chronometer.syncMon(_.blocking.time(name)): + Chronometer.syncMon(lila.mon.blocking.time(name)): try Await.result(fua, duration) catch case e: Exception => @@ -21,15 +28,15 @@ object Chronometer: def chronometer = Chronometer(fua) def chronometerTry = Chronometer.lapTry(fua) - def mon(path: lila.mon.TimerPath): Fu[A] = chronometer.mon(path).result - def monTry(path: scala.util.Try[A] => lila.mon.TimerPath): Fu[A] = - chronometerTry.mon(r => path(r)(lila.mon)).result - def monSuccess(path: lila.mon.type => Boolean => kamon.metric.Timer): Fu[A] = + def mon(path: Timer): Fu[A] = chronometer.mon(path).result + def monTry(path: scala.util.Try[A] => Timer): Fu[A] = + chronometerTry.mon(r => path(r)).result + def monSuccess(path: Boolean => kamon.metric.Timer): Fu[A] = chronometerTry .mon: r => - path(lila.mon)(r.isSuccess) + path(r.isSuccess) .result - def monValue(path: A => lila.mon.TimerPath): Fu[A] = chronometer.monValue(path).result + def monValue(path: A => Timer): Fu[A] = chronometer.monValue(path).result def logTime(name: String): Fu[A] = chronometer.pp(name) def logTimeIfGt(name: String, duration: FiniteDuration): Fu[A] = chronometer.ppIfGt(name, duration) @@ -41,19 +48,19 @@ object Chronometer: def micros = (nanos / 1000).toInt def seconds = (millis / 1000).toInt - def logIfSlow(threshold: Int, logger: lila.log.Logger)(msg: A => String) = + def logIfSlow(threshold: Int, logger: LoggerLike)(msg: A => String) = if millis >= threshold then log(logger)(msg) else this - def log(logger: lila.log.Logger)(msg: A => String) = + def log(logger: LoggerLike)(msg: A => String) = logger.info(s"<${millis}ms> ${msg(result)}") this - def mon(path: lila.mon.TimerPath) = - path(lila.mon).record(nanos) + def mon(path: Timer) = + path.record(nanos) this - def monValue(path: A => lila.mon.TimerPath) = - path(result)(lila.mon).record(nanos) + def monValue(path: A => Timer) = + path(result).record(nanos) this def pp: A = @@ -74,19 +81,19 @@ object Chronometer: case class FuLap[A](lap: Fu[Lap[A]]) extends AnyVal: - def logIfSlow(threshold: Int, logger: lila.log.Logger)(msg: A => String) = + def logIfSlow(threshold: Int, logger: LoggerLike)(msg: A => String) = lap.dforeach(_.logIfSlow(threshold, logger)(msg)) this - def mon(path: lila.mon.TimerPath) = + def mon(path: Timer) = lap.dforeach(_.mon(path)) this - def monValue(path: A => lila.mon.TimerPath) = + def monValue(path: A => Timer) = lap.dforeach(_.monValue(path)) this - def log(logger: lila.log.Logger)(msg: A => String) = + def log(logger: LoggerLike)(msg: A => String) = lap.dforeach(_.log(logger)(msg)) this @@ -133,8 +140,8 @@ object Chronometer: effect(lap) lap.result - def syncMon[A](path: lila.mon.TimerPath)(f: => A): A = - val timer = path(lila.mon).start() + def syncMon[A](path: Timer)(f: => A): A = + val timer = path.start() val res = f timer.stop() res diff --git a/modules/mon/src/main/mon.scala b/modules/mon/src/main/mon.scala new file mode 100644 index 00000000000..0479343e0c5 --- /dev/null +++ b/modules/mon/src/main/mon.scala @@ -0,0 +1,753 @@ +package lila.mon + +import com.github.benmanes.caffeine.cache.Cache as CaffeineCache +import kamon.metric.Timer +import kamon.tag.TagSet +import kamon.Kamon.{ timer, gauge, counter, histogram } +import chess.variant.Variant + +import lila.core.id.* +import lila.core.net.* +import lila.core.userId.{ UserId, UserName } +import lila.core.perf.PerfKey + +// https://github.com/kamon-io/Kamon/issues/752 +extension (s: String) + def escape: String = + val builder = java.lang.StringBuilder(s.length) + for c <- s.toCharArray do + if c != '"' && c != '\n' && c != '\\' + then builder.append(c) + builder.toString + +private def tags(elems: (String, Any)*): Map[String, Any] = Map.from(elems) + +object http: + private val reqTime = timer("http.time") + private val reqCount = counter("http.count") + private val mobCount = counter("http.mobile.count") + + def time(action: String) = reqTime.withTag("action", action) + + def count(action: String, client: String, method: String, code: Int) = + reqCount.withTags: + tags("action" -> action, "client" -> client, "method" -> method, "code" -> code.toLong) + + def errorCount(action: String, client: String, method: String, code: Int) = + counter("http.error").withTags: + tags("action" -> action, "client" -> client, "method" -> method, "code" -> code.toLong) + + def mobileCount(action: String, version: String, auth: Boolean, os: String) = + mobCount.withTags: + tags( + "action" -> action, + "version" -> version, + "auth" -> (if auth then "auth" else "anon"), + "os" -> os + ) + + def path(p: String) = counter("http.path.count").withTag("path", p.escape) + val userGamesCost = counter("http.userGames.cost").withoutTags() + def csrfError(tpe: String, action: String, client: String) = + counter("http.csrf.error").withTags(tags("type" -> tpe, "action" -> action, "client" -> client)) + val fingerPrint = timer("http.fingerPrint.time").withoutTags() +object syncache: + def miss(name: String) = counter("syncache.miss").withTag("name", name) + def timeout(name: String) = counter("syncache.timeout").withTag("name", name) + def compute(name: String) = timer("syncache.compute").withTag("name", name) + def wait(name: String) = timer("syncache.wait").withTag("name", name) +def caffeineStats(cache: CaffeineCache[?, ?], name: String): Unit = + val stats = cache.stats + gauge("caffeine.request").withTags(tags("name" -> name, "hit" -> true)).update(stats.hitCount.toDouble) + gauge("caffeine.request").withTags(tags("name" -> name, "hit" -> false)).update(stats.missCount.toDouble) + histogram("caffeine.hit.rate").withTag("name", name).record((stats.hitRate * 100000).toLong) + if stats.totalLoadTime > 0 then + gauge("caffeine.load.count") + .withTags(tags("name" -> name, "success" -> "success")) + .update(stats.loadSuccessCount.toDouble) + gauge("caffeine.load.count") + .withTags(tags("name" -> name, "success" -> "failure")) + .update(stats.loadFailureCount.toDouble) + gauge("caffeine.loadTime.cumulated") + .withTag("name", name) + .update(stats.totalLoadTime / 1000000d) // in millis; too much nanos for Kamon to handle) + timer("caffeine.loadTime.penalty").withTag("name", name).record(stats.averageLoadPenalty.toLong) + gauge("caffeine.eviction.count").withTag("name", name).update(stats.evictionCount.toDouble) + gauge("caffeine.entry.count").withTag("name", name).update(cache.estimatedSize.toDouble) +object mongoCache: + def request(name: String, hit: Boolean) = + counter("mongocache.request").withTags: + tags( + "name" -> name, + "hit" -> hit + ) + def compute(name: String) = timer("mongocache.compute").withTag("name", name) +object evalCache: + private val r = counter("evalCache.request") + def request(ply: Int, isHit: Boolean) = + r.withTags(tags("ply" -> (if ply < 15 then ply.toString else "15+"), "hit" -> isHit)) + object upgrade: + val count = counter("evalCache.upgrade.count").withoutTags() + val members = gauge("evalCache.upgrade.members").withoutTags() + val evals = gauge("evalCache.upgrade.evals").withoutTags() + val expirable = gauge("evalCache.upgrade.expirable").withoutTags() +object lobby: + object hook: + val create = counter("lobby.hook.create").withoutTags() + val join = counter("lobby.hook.join").withoutTags() + val size = histogram("lobby.hook.size").withoutTags() + def apiCreate(client: String) = counter("lobby.hook.apiCreate").withTag("client", client) + object seek: + val create = counter("lobby.seek.create").withoutTags() + val join = counter("lobby.seek.join").withoutTags() + object socket: + val getSris = timer("lobby.socket.getSris").withoutTags() + val member = gauge("lobby.socket.member").withoutTags() + val idle = gauge("lobby.socket.idle").withoutTags() + val hookSubscribers = gauge("lobby.socket.hookSubscribers").withoutTags() + object pool: + object wave: + def scheduled(id: String) = counter("lobby.pool.wave.scheduled").withTag("pool", id) + def full(id: String) = counter("lobby.pool.wave.full").withTag("pool", id) + def candidates(id: String) = histogram("lobby.pool.wave.candidates").withTag("pool", id) + def paired(id: String) = histogram("lobby.pool.wave.paired").withTag("pool", id) + def missed(id: String) = histogram("lobby.pool.wave.missed").withTag("pool", id) + def ratingDiff(id: String) = histogram("lobby.pool.wave.ratingDiff").withTag("pool", id) + def withRange(id: String) = histogram("lobby.pool.wave.withRange").withTag("pool", id) + object thieve: + def stolen(id: String) = histogram("lobby.pool.thieve.stolen").withTag("pool", id) + private val lobbySegment = timer("lobby.segment") + def segment(seg: String) = lobbySegment.withTag("segment", seg) +object rating: + def distribution(perfKey: PerfKey, rating: Int) = + gauge("rating.distribution").withTags(tags("perf" -> perfKey, "rating" -> rating.toLong)) + object regulator: + def micropoints(perfKey: PerfKey) = histogram("rating.regulator").withTag("perf", perfKey.value) +object perfStat: + def indexTime = timer("perfStat.indexTime").withoutTags() + +object round: + object api: + val player = timer("round.api").withTag("endpoint", "player") + val watcher = timer("round.api").withTag("endpoint", "watcher") + object forecast: + val create = counter("round.forecast.create").withoutTags() + object move: + object lag: + val compDeviation = histogram("round.move.lag.comp_deviation").withoutTags() + def uncomped(key: String) = histogram("round.move.lag.uncomped_ms").withTag("key", key) + def uncompStdDev(key: String) = histogram("round.move.lag.uncomp_stdev_ms").withTag("key", key) + val stdDev = histogram("round.move.lag.stddev_ms").withoutTags() + val mean = histogram("round.move.lag.mean_ms").withoutTags() + val coefVar = histogram("round.move.lag.coef_var_1000").withoutTags() + val compEstStdErr = histogram("round.move.lag.comp_est_stderr_1000").withoutTags() + val compEstOverErr = histogram("round.move.lag.avg_over_error_ms").withoutTags() + val moveComp = timer("round.move.lag.comped").withoutTags() + val time = timer("round.move.time").withoutTags() + object error: + val client = counter("round.error").withTag("from", "client") + val fishnet = counter("round.error").withTag("from", "fishnet") + val glicko = counter("round.error").withTag("from", "glicko") + val other = counter("round.error").withTag("from", "other") + object titivate: + val time = future("round.titivate.time") + val game = histogram("round.titivate.game").withoutTags() // how many games were processed + val total = histogram("round.titivate.total").withoutTags() // how many games should have been processed + val old = histogram("round.titivate.old").withoutTags() // how many old games remain + def broken(error: String) = counter("round.titivate.broken").withTag("error", error) // broken game + object alarm: + val time = timer("round.alarm.time").withoutTags() + object expiration: + val count = counter("round.expiration.count").withoutTags() + val asyncActorCount = gauge("round.asyncActor.count").withoutTags() + object correspondenceEmail: + val emails = histogram("round.correspondenceEmail.emails").withoutTags() + val time = future("round.correspondenceEmail.time") + object farming: + val bot = counter("round.farming.bot").withoutTags() + val provisional = counter("round.farming.provisional").withoutTags() +object playban: + def outcome(out: String) = counter("playban.outcome").withTag("outcome", out) + object ban: + val count = counter("playban.ban.count").withoutTags() + val mins = histogram("playban.ban.mins").withoutTags() +object explorer: + object index: + def count(success: Boolean) = counter("explorer.index.count").withTag("success", successTag(success)) + val time = timer("explorer.index.time").withoutTags() +object timeline: + val notification = counter("timeline.notification").withoutTags() +object insight: + val user = future("insight.request.time", "user") + val peers = future("insight.request.time", "peer") + val index = future("insight.index.time") +object tutor: + def buildSegment(segment: String) = future("tutor.build.segment", segment) + val buildFull = future("tutor.build.full") + val askMine = askAs("mine") + val askPeer = askAs("peer") + val buildTimeout = counter("tutor.build.timeout").withoutTags() + def peerMatch(hit: Boolean, perf: PerfKey) = counter("tutor.peerMatch").withTags: + tags("hit" -> hitTag(hit), "perf" -> perf) + val parallelism = gauge("tutor.build.parallelism").withoutTags() + val fishnetMissing = histogram("tutor.fishnet.missing").withoutTags() + private def askAs(as: "mine" | "peer")(question: String, perf: PerfKey | "all") = + future("tutor.insight.ask", tags("question" -> question, "perf" -> perf, "as" -> as)) +object search: + def time(op: "search" | "count", index: String, success: Boolean) = + timer("search.client.time").withTags: + tags( + "op" -> op, + "index" -> index, + "success" -> successTag(success) + ) +object asyncActor: + def overflow(name: String) = counter("asyncActor.overflow").withTag("name", name) + def queueSize(name: String) = histogram("asyncActor.queueSize").withTag("name", name) +object irc: + object zulip: + def say(stream: String) = future("irc.zulip.say", tags("stream" -> stream.escape)) +object user: + val online = gauge("user.online").withoutTags() + object register: + def count( + confirm: String, + ipSusp: Boolean, + fp: Boolean, + proxy: Option[String], + country: String, + client: String + ) = + counter("user.register.count").withTags: + tags( + "confirm" -> confirm, + "ipSusp" -> ipSusp, + "fp" -> fp, + "proxy" -> proxy.getOrElse("no"), + "country" -> country.escape, + "client" -> client + ) + def result(client: String, result: String) = + counter("user.register.result").withTags: + tags("client" -> client, "result" -> result) + def mustConfirmEmail(v: String) = counter("user.register.mustConfirmEmail").withTag("type", v) + def confirmEmailResult(success: Boolean) = + counter("user.register.confirmEmail").withTag("success", successTag(success)) + def modConfirmEmail(by: "mod" | "worker", result: String) = + counter("user.register.modConfirmEmail").withTags: + tags("by" -> by, "result" -> result) + object auth: + val bcFullMigrate = counter("user.auth.bcFullMigrate").withoutTags() + val hashTime = timer("user.auth.hashTime").withoutTags() + def count(success: Boolean) = counter("user.auth.count").withTag("success", successTag(success)) + + def passwordResetRequest(s: String) = counter("user.auth.passwordResetRequest").withTag("type", s) + def passwordResetConfirm(s: String) = counter("user.auth.passwordResetConfirm").withTag("type", s) + + def reopenRequest(s: String) = counter("user.auth.reopenRequest").withTag("type", s) + def reopenConfirm(s: String) = counter("user.auth.reopenConfirm").withTag("type", s) + object oauth: + def request(success: Boolean) = counter("user.oauth.request").withTags: + tags("success" -> successTag(success)) + private val userSegment = timer("user.segment") + def segment(seg: String) = userSegment.withTag("segment", seg) + def leaderboardCompute = future("user.leaderboard.compute") + def weeklyStableRanking(perf: PerfKey) = future("user.weeklyStableRanking", perf.value) +object actor: + def queueSize(name: String) = gauge("trouper.queueSize").withTag("name", name) +object mod: + object report: + val highest = gauge("mod.report.highest").withoutTags() + val close = counter("mod.report.close").withoutTags() + def create(reason: String, score: Int) = + counter("mod.report.create").withTags: + tags("reason" -> reason, "score" -> score) + object automod: + val request = future("mod.report.automod.request") + def assessment(a: String) = counter("mod.report.automod.assessment").withTag("assessment", a) + val imageRequest = future("mod.report.automod.image.request") + def imageFlagged(v: Boolean) = counter("mod.report.automod.image.flagged").withTag("flagged", v) + object log: + val create = counter("mod.log.create").withoutTags() + object irwin: + val report = counter("mod.report.irwin.report").withoutTags() + val mark = counter("mod.report.irwin.mark").withoutTags() + def ownerReport(name: String) = counter("mod.irwin.ownerReport").withTag("name", name) + def streamEventType(name: String) = counter("mod.irwin.stream.eventType").withTag("name", name) + object kaladin: + def request(by: String) = counter("mod.kaladin.request").withTag("by", by) + def insufficientMoves(by: String) = counter("mod.kaladin.insufficientMoves").withTag("by", by) + def queue(priority: Int) = gauge("mod.kaladin.queue").withTag("priority", priority) + def error(errKind: String) = counter("mod.kaladin.error").withTag("error", errKind) + val activation = histogram("mod.report.kaladin.activation").withoutTags() + val report = counter("mod.report.kaladin.report").withoutTags() + val mark = counter("mod.report.kaladin.mark").withoutTags() + object comm: + def segment(seg: String) = timer("mod.comm.segmentLat").withTag("segment", seg) + def zoneSegment(name: String) = future("mod.zone.segment", name) +object relay: + private def by(official: Boolean) = if official then "official" else "user" + private def relay(official: Boolean, id: RelayTourId, slug: String) = + tags("by" -> by(official), "slug" -> s"$slug/$id") + def ongoing(official: Boolean) = gauge("relay.ongoing").withTag("by", by(official)) + val crowdMonitor = gauge("relay.crowdMonitor").withoutTags() + def moves(official: Boolean, id: RelayTourId, slug: String) = + counter("relay.moves").withTags(relay(official, id, slug)) + def fetchTime(official: Boolean, id: RelayTourId, slug: String) = + timer("relay.fetch.time").withTags(relay(official, id, slug)) + def syncTime(official: Boolean, id: RelayTourId, slug: String) = + timer("relay.sync.time").withTags(relay(official, id, slug)) + def httpGet(code: Int, host: String, etag: String, proxy: Option[String]) = + timer("relay.http.get").withTags: + tags( + "code" -> code.toLong, + "host" -> host.escape, + "etag" -> etag.escape, + "proxy" -> proxy.getOrElse("none") + ) + val dedup = counter("relay.fetch.dedup").withoutTags() + def push(name: String, user: UserName, client: String)(games: Int, moves: Int, errors: Int) = + val histogramTags = tags("name" -> name.escape, "user" -> user, "client" -> client.escape) + val counterTags = tags("name" -> name.escape, "user" -> user) + histogram("relay.push.games").withTags(histogramTags).record(games) + histogram("relay.push.moves").withTags(histogramTags).record(moves) + histogram("relay.push.errors").withTags(histogramTags).record(errors) + counter("relay.push.games.nb").withTags(counterTags).increment(games) + counter("relay.push.moves.nb").withTags(counterTags).increment(moves) + +object bot: + def moves(username: String) = counter("bot.moves").withTag("name", username) + def chats(username: String) = counter("bot.chats").withTag("name", username) + def gameStream(event: "start" | "stop") = counter("bot.gameStream").withTag("event", event) +object cheat: + def selfReport(wildName: String, auth: Boolean) = + val name = if wildName.startsWith("soc: ") then "soc" else wildName.takeWhile(' ' !=) + counter("cheat.selfReport").withTags(tags("name" -> name.escape, "auth" -> auth)) + val holdAlert = counter("cheat.holdAlert").withoutTags() + def autoAnalysis(reason: String) = counter("cheat.autoAnalysis").withTag("reason", reason) + val autoMark = counter("cheat.autoMark.count").withoutTags() + val autoReport = counter("cheat.autoReport.count").withoutTags() +object email: + object send: + private val c = counter("email.send") + val resetPassword = c.withTag("type", "resetPassword") + val magicLink = c.withTag("type", "magicLink") + val reopen = c.withTag("type", "reopen") + val fix = c.withTag("type", "fix") + val change = c.withTag("type", "change") + val confirmation = c.withTag("type", "confirmation") + val welcome = c.withTag("type", "welcome") + def time(mailer: String) = future("email.send.time", tags("mailer" -> mailer)) + val disposableDomain = gauge("email.disposableDomain").withoutTags() +object security: + val torNodes = gauge("security.tor.node").withoutTags() + object firewall: + val block = counter("security.firewall.block").withoutTags() + val ip = gauge("security.firewall.ip").withoutTags() + val prints = gauge("security.firewall.prints").withoutTags() + object proxy: + val request = future("security.proxy.time") + def result(r: Option[String]) = counter("security.proxy.result").withTag("result", r.getOrElse("none")) + def hit(prox: String, action: String) = + counter("security.proxy.hit").withTags(tags("proxy" -> prox, "action" -> action)) + def rateLimit(key: String) = counter("security.rateLimit.count").withTag("key", key) + def concurrencyLimit(key: String) = counter("security.concurrencyLimit.count").withTag("key", key) + object dnsApi: + val mx = future("security.dnsApi.mx.time") + object verifyMailApi: + def fetch(success: Boolean, ok: Boolean) = + timer("verifyMail.fetch").withTags(tags("success" -> successTag(success), "ok" -> ok)) + object mailcheckApi: + def fetch(success: Boolean, ok: Boolean) = + timer("mailcheck.fetch").withTags(tags("success" -> successTag(success), "ok" -> ok)) + object turnstile: + def hit(client: String, action: String, result: String) = + counter("turnstile.hit").withTags(tags("client" -> client, "action" -> action, "result" -> result)) + object pwned: + def get(res: Boolean) = timer("security.pwned.result").withTag("res", res) + object geoip: + val epoch = gauge("security.geoip.epoch").withoutTags() + val loadTime = gauge("security.geoip.loadTime").withoutTags() + object login: + def attempt(byEmail: Boolean, pwned: Boolean, result: Boolean) = + counter("security.login.attempt").withTags: + tags( + "by" -> (if byEmail then "email" else "name"), + "pwned" -> pwned, + "result" -> result + ) + def proxy(tpe: String) = counter("security.login.proxy").withTag("proxy", tpe) + def secretScanning(tokenType: String, source: String, hit: Boolean) = + counter("security.githubSecretScanning.hit").withTags( + tags("type" -> tokenType, "source" -> source.escape, "hit" -> hit) + ) + def userTrust(trust: Boolean, cause: String) = + counter("security.userTrust").withTags(tags("trust" -> trust, "cause" -> cause)).increment() +object shutup: + def analyzer = timer("shutup.analyzer.time").withoutTags() +object tv: + object selector: + def candidates(channel: String) = histogram("tv.selector.candidates").withTag("channel", channel) + def cheats(channel: String) = histogram("tv.selector.cheats").withTag("channel", channel) + def rating(channel: String) = histogram("tv.selector.rating").withTag("channel", channel) +object streamer: + def online = gauge("tv.streamer.count").withoutTags() + def present(n: String) = gauge("tv.streamer.present").withTag("name", n.escape) +object relation: + private val c = counter("relation.action") + val follow = c.withTag("type", "follow") + val unfollow = c.withTag("type", "unfollow") + val block = c.withTag("type", "block") + val unblock = c.withTag("type", "unblock") +object clas: + object student: + def create(teacher: UserId) = counter("clas.student.create").withTag("teacher", teacher) + def invite(teacher: UserId) = counter("clas.student.invite").withTag("teacher", teacher) + final class bloomFilter(name: String): + def count = gauge(s"clas.${name}.bloomFilter.count").withoutTags() + def fu = future(s"clas.${name}.bloomFilter.future") +object tournament: + object pairing: + val batchSize = histogram("tournament.pairing.batchSize").withoutTags() + val create = future("tournament.pairing.create") + val createRanking = timer("tournament.pairing.create.ranking").withoutTags() + val createPairings = timer("tournament.pairing.create.pairings").withoutTags() + val createPlayerMap = timer("tournament.pairing.create.playerMap").withoutTags() + val createInserts = timer("tournament.pairing.create.inserts").withoutTags() + val createFeature = timer("tournament.pairing.create.feature").withoutTags() + val createAutoPairing = timer("tournament.pairing.create.autoPairing").withoutTags() + val prep = future("tournament.pairing.prep") + val wmmatching = timer("tournament.pairing.wmmatching").withoutTags() + val created = gauge("tournament.count").withTag("type", "created") + val started = gauge("tournament.count").withTag("type", "started") + val waitingPlayers = histogram("tournament.waitingPlayers").withoutTags() + object startedOrganizer: + val tick = future("tournament.startedOrganizer.tick") + val waitingUsers = future("tournament.startedOrganizer.waitingUsers") + object createdOrganizer: + val tick = future("tournament.createdOrganizer.tick") + object lilaHttp: + val tick = future("tournament.lilaHttp.tick") + val fullSize = histogram("tournament.lilaHttp.fullSize").withoutTags() + val nbTours = gauge("tournament.lilaHttp.nbTours").withoutTags() + def apiShowPartial(partial: Boolean, client: String)(success: Boolean) = + timer("tournament.api.show").withTags: + tags( + "partial" -> partial, + "success" -> successTag(success), + "client" -> client + ) + def withdrawableIds(reason: String) = future("tournament.withdrawableIds", reason) + def action(tourId: String, action: String) = + timer("tournament.api.action").withTags(tags("tourId" -> tourId, "action" -> action)) + object notifier: + def tournaments = counter("tournament.notify.tournaments").withoutTags() + def players = counter("tournament.notify.players").withoutTags() + object featuring: + def forTeams(page: "index" | "homepage") = future("tournament.featuring.forTeams", page) +object swiss: + val tick = future("swiss.tick") + val bbpairing = timer("swiss.bbpairing").withoutTags() + val scoringGet = future("swiss.scoring.get") + val scoringRecompute = future("swiss.scoring.recompute") + val startRound = future("swiss.director.startRound") + def games(status: String) = histogram("swiss.ongoingGames").withTag("status", status) + val json = future("swiss.json") +object plan: + object paypalLegacy: + val amount = histogram("plan.amount").withTag("service", "paypal") + object paypalCheckout: + val amount = histogram("plan.amount").withTag("service", "paypalCheckout") + val fetchAccessToken = future("plan.paypal.accessToken") + val stripe = histogram("plan.amount").withTag("service", "stripe") + val goal = gauge("plan.goal").withoutTags() + val current = gauge("plan.current").withoutTags() + val percent = gauge("plan.percent").withoutTags() + def webhook(service: String, tpe: String) = + counter("plan.webhook").withTags(tags("service" -> service, "tpe" -> tpe)) + def intent(service: String, currency: java.util.Currency, coverFees: Boolean) = + counter("plan.intent").withTags: + tags("service" -> service, "currency" -> currency.getCurrencyCode, "coverFees" -> coverFees) + object charge: + def first(service: String) = counter("plan.charge.first").withTag("service", service) + def countryCents(country: String, currency: java.util.Currency, service: String, gift: Boolean) = + histogram("plan.charge.country.cents").withTags: + tags( + "country" -> country.escape, + "currency" -> currency.getCurrencyCode, + "service" -> service, + "gift" -> gift + ) +object forum: + object post: + val create = counter("forum.post.create").withoutTags() + object topic: + val view = counter("forum.topic.view").withoutTags() + def reaction(r: String) = counter("forum.reaction").withTag("reaction", r) +object msg: + def post(verdict: String, isNew: Boolean, multi: Boolean) = counter("msg.post").withTags( + tags("verdict" -> verdict, "isNew" -> isNew, "multi" -> multi) + ) + val teamBulk = histogram("msg.bulk.team").withoutTags() + def clasBulk(clasId: ClasId) = histogram("msg.bulk.clas").withTag("id", clasId.value) +object puzzle: + object selector: + object user: + def time(categ: String) = timer("puzzle.selector.user.puzzle").withTag("categ", categ) + def retries(categ: String) = histogram("puzzle.selector.user.retries").withTag("categ", categ) + val vote = histogram("puzzle.selector.user.vote").withoutTags() + def tier(t: String, categ: String, difficulty: String) = + counter("puzzle.selector.user.tier").withTags: + tags("tier" -> t, "categ" -> categ, "difficulty" -> difficulty) + def batch(nb: Int) = timer("puzzle.selector.user.batch").withTag("nb", nb) + object anon: + val time = timer("puzzle.selector.anon.puzzle").withoutTags() + def batch(nb: Int) = timer("puzzle.selector.anon.batch").withTag("nb", nb) + val vote = histogram("puzzle.selector.anon.vote").withoutTags() + def nextPuzzleResult(result: String) = + timer("puzzle.selector.user.puzzleResult").withTag("result", result) + def nextPathFor(categ: String, requester: String) = + timer("puzzle.path.nextFor").withTags(tags("categ" -> categ, "requester" -> requester)) + + object batch: + object selector: + val count = counter("puzzle.batch.selector.count").withoutTags() + val time = timer("puzzle.batch.selector").withoutTags() + val solve = counter("puzzle.batch.solve").withoutTags() + object round: + def attempt(user: Boolean, theme: String, rated: Boolean) = + counter("puzzle.attempt.count").withTags(tags("user" -> user, "theme" -> theme, "rated" -> rated)) + object vote: + def count(up: Boolean, win: Boolean) = + counter("puzzle.vote.count").withTags: + tags( + "up" -> up, + "win" -> win + ) + def theme(key: String, up: Option[Boolean], win: Boolean) = + counter("puzzle.vote.theme").withTags: + tags( + "up" -> up.fold("cancel")(_.toString), + "theme" -> key, + "win" -> win + ) + val future = lila.mon.future("puzzle.vote.future") + val crazyGlicko = counter("puzzle.crazyGlicko").withoutTags() +object storm: + object selector: + val time = future("storm.selector.time") + val sets = histogram("storm.selector.sets").withoutTags() + val count = histogram("storm.selector.count").withoutTags() + val rating = histogram("storm.selector.rating").withoutTags() + def ratingSlice(index: Int) = histogram("storm.selector.ratingSlice").withTag("index", index) + object run: + def score(auth: Boolean) = histogram("storm.run.score").withTag("auth", auth) + def sign(cause: String) = counter("storm.run.sign").withTag("cause", cause) +object racer: + private def tpe(lobby: Boolean) = if lobby then "lobby" else "friend" + def race(lobby: Boolean) = counter("racer.lobby.race").withTag("tpe", tpe(lobby)) + def players(lobby: Boolean) = + histogram("racer.lobby.players").withTag("tpe", tpe(lobby)) + def score(lobby: Boolean, auth: Boolean) = + histogram("racer.player.score").withTags: + tags( + "tpe" -> tpe(lobby), + "auth" -> auth + ) +object streak: + object selector: + val time = timer("streak.selector.time").withoutTags() + val count = histogram("streak.selector.count").withoutTags() + val rating = histogram("streak.selector.rating").withoutTags() + def ratingSlice(index: Int) = histogram("streak.selector.ratingSlice").withTag("index", index) + object run: + def score(auth: String) = histogram("streak.run.score").withTag("auth", auth) +object game: + import chess.{ Speed, Rated, Status } + import lila.core.game.Source + def finish(variant: Variant, speed: Speed, source: Option[Source], mode: Rated, status: Status) = + counter("game.finish").withTags: + tags( + "variant" -> variant.key, + "speed" -> speed.key, + "source" -> source.fold("unknown")(_.name), + "mode" -> mode.name, + "status" -> status.name + ) + val fetch = counter("game.fetch.count").withoutTags() + val loadClockHistory = counter("game.loadClockHistory.count").withoutTags() + object pgn: + def encode(format: String) = timer("game.pgn.encode").withTag("format", format) + def decode(format: String) = timer("game.pgn.decode").withTag("format", format) + val idCollision = counter("game.idCollision").withoutTags() + def idGenerator(collisions: Int) = timer("game.idGenerator").withTags(tags("collisions" -> collisions)) + object streamByOauthOrigin: + def event(tpe: String) = counter("game.streamByOauthOrigin.event").withTag("type", tpe) + def users(sel: String) = gauge("game.streamByOauthOrigin.users").withTag("selector", sel) + def streams(ua: UserAgent) = gauge("game.streamByOauthOrigin.streams").withTag("ua", ua.value) + val bloomFP = counter("game.streamByOauthOrigin.bloomFP").withoutTags() +object chat: + private val msgCounter = counter("chat.message") + def message(parent: String, troll: Boolean) = + msgCounter.withTags(tags("parent" -> parent, "troll" -> troll)) + def fetch(parent: String) = timer("chat.fetch").withTag("parent", parent) +object push: + object register: + def in(platform: String) = counter("push.register").withTag("platform", platform) + val out = counter("push.register.out").withoutTags() + object web: + def post = future("push.web.post") + object send: + private def send(tpe: String)(platform: String, success: Boolean, count: Int): Unit = + counter("push.send") + .withTags: + tags( + "type" -> tpe, + "platform" -> platform, + "success" -> successTag(success) + ) + .increment(count) + () + val move = send("move") + val takeback = send("takeback") + val draw = send("draw") + val corresAlarm = send("corresAlarm") + val finish = send("finish") + val message = send("message") + val tourSoon = send("tourSoon") + val forumMention = send("forumMention") + val invitedStudy = send("invitedStudy") + val streamStart = send("streamStart") + val broadcastRound = send("broadcastRound") + + object challenge: + val create = send("challengeCreate") + val accept = send("challengeAccept") + val googleTokenTime = timer("push.send.googleToken").withoutTags() + def firebaseStatus(project: String, typ: String, status: Int) = + counter("push.firebase.status").withTags(tags("status" -> status, "project" -> project, "type" -> typ)) +object fishnet: + object client: + object result: + private val c = counter("fishnet.client.result") + private def apply(r: String)(client: UserId) = + c.withTags(tags("client" -> client, "result" -> r)) + val success = apply("success") + val failure = apply("failure") + val timeout = apply("timeout") + val notFound = apply("notFound") + val notAcquired = apply("notAcquired") + val abort = apply("abort") + def status(enabled: Boolean) = gauge("fishnet.client.status").withTag("enabled", enabled) + def version(v: String) = gauge("fishnet.client.version").withTag("version", v.escape) + def queueTime(sender: "system" | "user") = timer("fishnet.queue.db").withTag("sender", sender) + val acquire = future("fishnet.acquire") + def work(typ: String, as: "system" | "user") = + gauge("fishnet.work").withTags(tags("type" -> typ, "for" -> as)) + def oldest(as: "system" | "user") = gauge("fishnet.oldest").withTag("for", as) + object analysis: + object by: + def movetime(client: UserId) = histogram("fishnet.analysis.movetime").withTag("client", client) + def node(client: UserId) = histogram("fishnet.analysis.node").withTag("client", client) + def nps(client: UserId) = histogram("fishnet.analysis.nps").withTag("client", client) + def depth(client: UserId) = histogram("fishnet.analysis.depth").withTag("client", client) + def pvSize(client: UserId) = histogram("fishnet.analysis.pvSize").withTag("client", client) + def pv(client: UserId, isLong: Boolean) = + counter("fishnet.analysis.pvs").withTags(tags("client" -> client, "long" -> isLong)) + def totalMeganode(client: UserId) = + counter("fishnet.analysis.total.meganode").withTag("client", client) + def totalSecond(client: UserId) = + counter("fishnet.analysis.total.second").withTag("client", client) + def requestCount(tpe: "game" | "study") = counter("fishnet.analysis.request").withTag("type", tpe) + val evalCacheHits = histogram("fishnet.analysis.evalCacheHits").withoutTags() + val skipPositionsGame = future("fishnet.analysis.skipPositions.game") + val skipPositionsStudy = future("fishnet.analysis.skipPositions.study") + object http: + def request(hit: Boolean) = counter("fishnet.http.acquire").withTag("hit", hit) + def move(level: Int) = counter("fishnet.move.time").withTag("level", level) + def openingBook(variant: Variant, hit: Boolean) = + timer("fishnet.opening.hit").withTags: + tags("variant" -> variant.key, "hit" -> hitTag(hit)) +object opening: + def searchTime = timer("opening.search.time").withoutTags() + object explorer: + def stats = future("opening.explorer.stats") +object study: + object tree: + val read = timer("study.tree.read").withoutTags() + val write = timer("study.tree.write").withoutTags() + object sequencer: + val chapterTime = timer("study.sequencer.chapter.time").withoutTags() +object api: + val users = counter("api.cost").withTag("endpoint", "users") + val activity = counter("api.cost").withTag("endpoint", "activity") + object challenge: + object bulk: + def scheduleNb(by: UserId) = counter("api.challenge.bulk.schedule.nb").withTag("by", by) + def createNb(by: UserId) = counter("api.challenge.bulk.create.nb").withTag("by", by) +object `export`: + object png: + val game = counter("export.png").withTag("type", "game") + val puzzle = counter("export.png").withTag("type", "puzzle") +object bus: + val classifiers = gauge("bus.classifiers").withoutTags() +object blocking: + def time(name: String) = timer("blocking.time").withTag("name", name) + def timeout(name: String) = counter("blocking.timeout").withTag("name", name) +object workQueue: + def offerFail(name: String, result: String) = + counter("workQueue.offerFail").withTags: + tags("name" -> name, "result" -> result) + def timeout(name: String) = counter("workQueue.timeout").withTag("name", name) +class parallelQueue(name: String): + val parallelism = gauge("parallelQueue.parallelism").withTag("name", name) + val computeTimeout = counter("parallelQueue.buildTimeout").withTag("name", name) +object markdown: + val time = timer("markdown.time").withoutTags() + def pgnsFromText = future("markdown.pgnsFromText") +object ublog: + def create(user: UserId) = counter("ublog.create").withTag("user", user) + def view = counter("ublog.view").withoutTags() + object automod: + val request = future("ublog.automod.request") + def quality(q: String) = counter("ublog.automod.quality").withTag("quality", q) + def flagged(f: Boolean) = counter("ublog.automod.flagged").withTag("flagged", f) +object picfit: + def uploadTime(user: UserId) = future("picfit.upload.time", tags("user" -> user)) + def uploadSize(user: UserId) = histogram("picfit.upload.size").withTag("user", user) +object fideSync: + val time = future("fide.sync.time") + val players = gauge("fide.sync.players").withoutTags() + val updated = gauge("fide.sync.updated").withoutTags() +object recap: + val games = future("recap.build.games.time") + val puzzles = future("recap.build.puzzles.time") + +object jvm: + def threads() = + val perState = gauge("jvm.threads.group") + val total = gauge("jvm.threads.group.total") + for + group <- scalalib.Jvm.threadGroups() + _ = total.withTags(tags("name" -> group.name)).update(group.total) + (state, count) <- group.states + yield perState.withTags(tags("name" -> group.name, "state" -> state.toString)).update(count) + +object prometheus: + val lines = gauge("prometheus.lines").withoutTags() + +def chronoSync[A] = Chronometer.syncMon[A] + +private def future(name: String) = (success: Boolean) => timer(name).withTag("success", successTag(success)) +private def future(name: String, tags: Map[String, Any]) = (success: Boolean) => + timer(name).withTags(tags + ("success" -> successTag(success))) +private def future(name: String, segment: String)(success: Boolean) = + timer(name).withTags: + tags("success" -> successTag(success), "segment" -> segment) + +private def successTag(success: Boolean) = if success then "success" else "failure" +private def hitTag(hit: Boolean) = if hit then "hit" else "miss" + +import scala.language.implicitConversions +private given Conversion[UserId, String] = _.value +private given Conversion[Map[String, Any], TagSet] = TagSet.from diff --git a/modules/mon/src/main/package.scala b/modules/mon/src/main/package.scala new file mode 100644 index 00000000000..8b95a66bd58 --- /dev/null +++ b/modules/mon/src/main/package.scala @@ -0,0 +1,4 @@ +package lila.mon + +object extensions: + export Chronometer.futureExtension.* diff --git a/modules/opening/src/main/OpeningExplorer.scala b/modules/opening/src/main/OpeningExplorer.scala index bf1429e61c9..389357b754a 100644 --- a/modules/opening/src/main/OpeningExplorer.scala +++ b/modules/opening/src/main/OpeningExplorer.scala @@ -11,6 +11,7 @@ import scala.util.{ Failure, Success, Try } import lila.core.net.Crawler import lila.core.config.Secret +import lila.mon.extensions.* final private class OpeningExplorer( ws: StandaloneWSClient, @@ -47,7 +48,7 @@ final private class OpeningExplorer( err => fufail(s"Couldn't parse $err"), data => fuccess(data.some) ) - .monSuccess(_.opening.explorer.stats) + .monSuccess(lila.mon.opening.explorer.stats) .map(Success(_)) .recover: case e: Exception => diff --git a/modules/opening/src/main/OpeningSearch.scala b/modules/opening/src/main/OpeningSearch.scala index 08abd290fd5..0cb38e77530 100644 --- a/modules/opening/src/main/OpeningSearch.scala +++ b/modules/opening/src/main/OpeningSearch.scala @@ -6,7 +6,7 @@ import scalalib.HeapSort.topN import java.text.Normalizer -import lila.common.Chronometer +import lila.mon.Chronometer import lila.memo.CacheApi case class OpeningSearchResult(opening: Opening): @@ -114,7 +114,7 @@ private object OpeningSearch: private given Ordering[Match] = Ordering.by { case Match(_, score) => score } - def apply(str: String, max: Int): List[Opening] = Chronometer.syncMon(_.opening.searchTime): + def apply(str: String, max: Int): List[Opening] = Chronometer.syncMon(lila.mon.opening.searchTime): val query = makeQuery(str) index .flatMap: entry => diff --git a/modules/perfStat/src/main/PerfStatIndexer.scala b/modules/perfStat/src/main/PerfStatIndexer.scala index ba361ab61d1..71caec5d321 100644 --- a/modules/perfStat/src/main/PerfStatIndexer.scala +++ b/modules/perfStat/src/main/PerfStatIndexer.scala @@ -2,6 +2,7 @@ package lila.perfStat import lila.rating.PerfType import lila.rating.PerfType.GamePerf +import lila.mon.extensions.* final class PerfStatIndexer( gameRepo: lila.core.game.GameRepo, @@ -12,7 +13,7 @@ final class PerfStatIndexer( maxSize = Max(64), timeout = 10.seconds, name = "perfStatIndexer", - lila.log.asyncActorMonitor.full + lila.mon.asyncActorMonitor.full ) private[perfStat] def userPerf(user: UserId, perf: GamePerf): Fu[PerfStat] = @@ -30,7 +31,7 @@ final class PerfStatIndexer( else perfStat .flatMap: ps => storage.insert(ps).recover(lila.db.ignoreDuplicateKey).inject(ps) - .mon(_.perfStat.indexTime) + .mon(lila.mon.perfStat.indexTime) def addGame(game: Game): Funit = game.players.toList.sequentiallyVoid: player => diff --git a/modules/plan/src/main/PayPalClient.scala b/modules/plan/src/main/PayPalClient.scala index 9e8ebe498cc..0e5e395f1a9 100644 --- a/modules/plan/src/main/PayPalClient.scala +++ b/modules/plan/src/main/PayPalClient.scala @@ -1,5 +1,7 @@ package lila.plan +import java.util.Currency + import play.api.ConfigLoader import play.api.i18n.Lang import play.api.libs.json.* @@ -9,8 +11,7 @@ import play.api.libs.ws.JsonBodyReadables.* import play.api.libs.ws.JsonBodyWritables.* import play.api.libs.ws.{ StandaloneWSClient, StandaloneWSResponse, WSAuthScheme } -import java.util.Currency - +import lila.mon.extensions.* import lila.common.Json.given import lila.common.autoconfig.* import lila.common.config.given @@ -236,7 +237,7 @@ final private class PayPalClient( (res.body[JsValue] \ "access_token").validate[String] match case JsError(err) => fufail(s"PayPal access token ${err} ${res.body[String].take(200)}") case JsSuccess(token, _) => fuccess(AccessToken(token)) - .monSuccess(_.plan.paypalCheckout.fetchAccessToken) + .monSuccess(lila.mon.plan.paypalCheckout.fetchAccessToken) object PayPalClient: diff --git a/modules/plan/src/main/PlanCheckout.scala b/modules/plan/src/main/PlanCheckout.scala index d3d1b3e91e9..f9cdc66c594 100644 --- a/modules/plan/src/main/PlanCheckout.scala +++ b/modules/plan/src/main/PlanCheckout.scala @@ -1,10 +1,12 @@ package lila.plan +import java.util.Currency + import play.api.data.* import play.api.data.Forms.* import play.api.data.validation.Constraints -import java.util.Currency +import lila.mon.extensions.* case class PlanCheckout( email: Option[String], diff --git a/modules/pool/src/main/GameStarter.scala b/modules/pool/src/main/GameStarter.scala index e639ac8192e..0f52f36f3d6 100644 --- a/modules/pool/src/main/GameStarter.scala +++ b/modules/pool/src/main/GameStarter.scala @@ -18,7 +18,7 @@ final private class GameStarter( maxSize = Max(64), timeout = 10.seconds, name = "gameStarter", - lila.log.asyncActorMonitor.full + lila.mon.asyncActorMonitor.full ) def apply(pool: PoolConfig, couples: Vector[MatchMaking.Couple]): Funit = diff --git a/modules/push/src/main/FirebasePush.scala b/modules/push/src/main/FirebasePush.scala index ab32f3db6c6..dd18ece9aea 100644 --- a/modules/push/src/main/FirebasePush.scala +++ b/modules/push/src/main/FirebasePush.scala @@ -8,7 +8,7 @@ import play.api.libs.ws.StandaloneWSClient import scalalib.cache.FrequencyThreshold import scalalib.data.LazyFu -import lila.common.Chronometer +import lila.mon.extensions.* final private class FirebasePush( deviceApi: DeviceApi, @@ -26,7 +26,7 @@ final private class FirebasePush( maxSize = Max(512), timeout = 10.seconds, name = "firebasePush", - lila.log.asyncActorMonitor.full + lila.mon.asyncActorMonitor.full ) def apply(userId: UserId, data: LazyFu[PushApi.Data]): Funit = @@ -55,10 +55,10 @@ final private class FirebasePush( // access token has 1h lifetime and is requested only if expired token <- workQueue { Future: - Chronometer.syncMon(_.blocking.time("firebase")): + lila.mon.Chronometer.syncMon(lila.mon.blocking.time("firebase")): creds.refreshIfExpired() creds.getAccessToken() - }.chronometer.mon(_.push.googleTokenTime).result + }.chronometer.mon(lila.mon.push.googleTokenTime).result _ <- send(token, device, config, data) yield () yield () diff --git a/modules/push/src/main/WebPush.scala b/modules/push/src/main/WebPush.scala index 1edba840661..f1556c2d75d 100644 --- a/modules/push/src/main/WebPush.scala +++ b/modules/push/src/main/WebPush.scala @@ -9,6 +9,7 @@ import scalalib.data.LazyFu import lila.common.Json.given import lila.common.autoconfig.* +import lila.mon.extensions.* final private class WebPush( webSubscriptionApi: WebSubscriptionApi, @@ -77,7 +78,7 @@ final private class WebPush( .map: n => logger.info(s"[push] web: $n/${staleEndpoints.size} stale endpoints unsubscribed") case res => fufail(s"[push] web: ${res.status} ${res.body}") - .monSuccess(_.push.web.post) + .monSuccess(lila.mon.push.web.post) private object WebPush: diff --git a/modules/puzzle/src/main/PuzzleAnon.scala b/modules/puzzle/src/main/PuzzleAnon.scala index cb42590176f..287bc33fc4d 100644 --- a/modules/puzzle/src/main/PuzzleAnon.scala +++ b/modules/puzzle/src/main/PuzzleAnon.scala @@ -4,6 +4,7 @@ import scalalib.ThreadLocalRandom import lila.db.dsl.* import lila.memo.CacheApi +import lila.mon.extensions.* final class PuzzleAnon( colls: PuzzleColls, @@ -18,7 +19,7 @@ final class PuzzleAnon( pool .get(angle -> diff) .map(color.fold[Vector[Puzzle] => Option[Puzzle]](ThreadLocalRandom.oneOf)(selectWithColor)) - .mon(_.puzzle.selector.anon.time) + .mon(lila.mon.puzzle.selector.anon.time) .addEffect: _.foreach: puzzle => lila.mon.puzzle.selector.anon.vote.record(100 + math.round(puzzle.vote * 100)) @@ -31,7 +32,7 @@ final class PuzzleAnon( nextTry(1) def getBatchFor(angle: PuzzleAngle, diff: PuzzleDifficulty, nb: Int): Fu[Vector[Puzzle]] = - pool.get(angle -> diff).map(_.take(nb)).mon(_.puzzle.selector.anon.batch(nb)) + pool.get(angle -> diff).map(_.take(nb)).mon(lila.mon.puzzle.selector.anon.batch(nb)) private val poolSize = 150 diff --git a/modules/puzzle/src/main/PuzzleApi.scala b/modules/puzzle/src/main/PuzzleApi.scala index f607bf148d0..27ca4a811b9 100644 --- a/modules/puzzle/src/main/PuzzleApi.scala +++ b/modules/puzzle/src/main/PuzzleApi.scala @@ -6,6 +6,7 @@ import scalalib.paginator.Paginator import lila.core.i18n.I18nKey import lila.db.dsl.{ *, given } import lila.db.paginator.Adapter +import lila.mon.extensions.* final class PuzzleApi( colls: PuzzleColls, @@ -66,7 +67,7 @@ final class PuzzleApi( expiration = 1.minute, timeout = 3.seconds, name = "puzzle.vote", - monitor = lila.log.asyncActorMonitor.highCardinality + monitor = lila.mon.asyncActorMonitor.highCardinality ) def update(id: PuzzleId, user: User, vote: Boolean): Funit = @@ -86,7 +87,7 @@ final class PuzzleApi( }) .void } - .monSuccess(_.puzzle.vote.future) + .monSuccess(lila.mon.puzzle.vote.future) .recoverDefault private def updatePuzzle(puzzleId: PuzzleId, newVote: Int, prevVote: Option[Int]): Funit = diff --git a/modules/puzzle/src/main/PuzzleBatch.scala b/modules/puzzle/src/main/PuzzleBatch.scala index 32ed7c128c3..5d40a449657 100644 --- a/modules/puzzle/src/main/PuzzleBatch.scala +++ b/modules/puzzle/src/main/PuzzleBatch.scala @@ -1,6 +1,7 @@ package lila.puzzle import lila.db.dsl.* +import lila.mon.extensions.* // mobile app final class PuzzleBatch( @@ -55,4 +56,4 @@ final class PuzzleBatch( ) .map: _.view.flatMap(puzzleReader.readOpt).toVector - .mon(_.puzzle.selector.user.batch(nb = nb)) + .mon(lila.mon.puzzle.selector.user.batch(nb = nb)) diff --git a/modules/puzzle/src/main/PuzzleFinisher.scala b/modules/puzzle/src/main/PuzzleFinisher.scala index 4a0601a922f..fa8c30b7036 100644 --- a/modules/puzzle/src/main/PuzzleFinisher.scala +++ b/modules/puzzle/src/main/PuzzleFinisher.scala @@ -25,7 +25,7 @@ final private[puzzle] class PuzzleFinisher( expiration = 5.minutes, timeout = 5.seconds, name = "puzzle.finish", - lila.log.asyncActorMonitor.full + lila.mon.asyncActorMonitor.full ) private val calculator = GlickoCalculator() @@ -130,7 +130,7 @@ final private[puzzle] class PuzzleFinisher( date = now ) val userPerf = perf - .addOrReset(_.puzzle.crazyGlicko, s"puzzle ${puzzle.id}")(userGlicko, now) + .addOrReset(lila.mon.puzzle.crazyGlicko, s"puzzle ${puzzle.id}")(userGlicko, now) .pipe: p => p.copy(glicko = ponder.player(angle, win, perf.glicko -> p.glicko, puzzle.glicko)) (round, newPuzzleGlicko, userPerf) diff --git a/modules/puzzle/src/main/PuzzleOpening.scala b/modules/puzzle/src/main/PuzzleOpening.scala index f3b66c63d20..e2fc016c726 100644 --- a/modules/puzzle/src/main/PuzzleOpening.scala +++ b/modules/puzzle/src/main/PuzzleOpening.scala @@ -5,6 +5,7 @@ import reactivemongo.akkastream.cursorProducer import lila.common.{ LilaOpeningFamily, LilaStream, SimpleOpening } import lila.core.i18n.I18nKey +import lila.mon.extensions.* import lila.db.dsl.{ *, given } import lila.memo.{ CacheApi, MongoCache } import lila.memo.CacheApi.buildAsyncTimeout diff --git a/modules/puzzle/src/main/PuzzlePath.scala b/modules/puzzle/src/main/PuzzlePath.scala index 42a3bba9236..772c111d3a5 100644 --- a/modules/puzzle/src/main/PuzzlePath.scala +++ b/modules/puzzle/src/main/PuzzlePath.scala @@ -3,6 +3,7 @@ package lila.puzzle import scalalib.Iso import lila.db.dsl.{ *, given } +import lila.mon.extensions.* object PuzzlePath: @@ -65,7 +66,7 @@ h":"5B7ADA38","planCacheKey":"7FF0C349","queryFramework":"classic","reslen":286, nextFor(requester)(angle, actualTier, difficulty, previousPaths, compromise + 1) case _ => fuccess(none) }.mon: - _.puzzle.nextPathFor(angle.categ, requester) + lila.mon.puzzle.nextPathFor(angle.categ, requester) def select(angle: PuzzleAngle, tier: PuzzleTier, rating: Range) = $doc( "min".$lte(f"${angle.key}${sep}${tier}${sep}${rating.max}%04d"), diff --git a/modules/puzzle/src/main/PuzzleSelector.scala b/modules/puzzle/src/main/PuzzleSelector.scala index 8c9aea51818..80b174f71cb 100644 --- a/modules/puzzle/src/main/PuzzleSelector.scala +++ b/modules/puzzle/src/main/PuzzleSelector.scala @@ -1,6 +1,7 @@ package lila.puzzle import lila.db.dsl.{ *, given } +import lila.mon.extensions.* final class PuzzleSelector( colls: PuzzleColls, @@ -43,7 +44,7 @@ final class PuzzleSelector( , some ) - .mon(_.puzzle.selector.user.time(angle.categ)) + .mon(lila.mon.puzzle.selector.user.time(angle.categ)) private def findNextPuzzleFor(angle: PuzzleAngle, retries: Int)(using me: Me, perf: Perf): Fu[Puzzle] = sessionApi @@ -140,4 +141,4 @@ final class PuzzleSelector( PuzzleAlreadyPlayed(puzzle) else PuzzleFound(puzzle) .monValue: result => - _.puzzle.selector.nextPuzzleResult(result.name) + lila.mon.puzzle.selector.nextPuzzleResult(result.name) diff --git a/modules/puzzle/src/main/PuzzleStreak.scala b/modules/puzzle/src/main/PuzzleStreak.scala index e890d750792..533b4474a0c 100644 --- a/modules/puzzle/src/main/PuzzleStreak.scala +++ b/modules/puzzle/src/main/PuzzleStreak.scala @@ -3,6 +3,7 @@ package lila.puzzle import lila.db.dsl.{ *, given } import lila.memo.CacheApi import lila.memo.CacheApi.buildAsyncTimeout +import lila.mon.extensions.* case class PuzzleStreak(ids: String, first: Puzzle) @@ -75,7 +76,7 @@ final class PuzzleStreakApi(colls: PuzzleColls, cacheApi: CacheApi)(using Execut ) .map: _.flatMap(puzzleReader.readOpt) - .mon(_.streak.selector.time) + .mon(lila.mon.streak.selector.time) .addEffect(monitor) .map: puzzles => puzzles.headOption.map: diff --git a/modules/puzzle/src/main/PuzzleTagger.scala b/modules/puzzle/src/main/PuzzleTagger.scala index b1c337bee16..3620a94beab 100644 --- a/modules/puzzle/src/main/PuzzleTagger.scala +++ b/modules/puzzle/src/main/PuzzleTagger.scala @@ -5,6 +5,7 @@ import reactivemongo.akkastream.cursorProducer import lila.common.LilaStream import lila.db.dsl.{ *, given } +import lila.mon.extensions.* final private class PuzzleTagger(colls: PuzzleColls, openingApi: PuzzleOpeningApi)(using ec: Executor, diff --git a/modules/racer/src/main/RacerApi.scala b/modules/racer/src/main/RacerApi.scala index f2abacd0292..7bd81f31376 100644 --- a/modules/racer/src/main/RacerApi.scala +++ b/modules/racer/src/main/RacerApi.scala @@ -52,7 +52,7 @@ final class RacerApi( maxSize = Max(32), timeout = 20.seconds, name = "racer.rematch", - lila.log.asyncActorMonitor.full + lila.mon.asyncActorMonitor.full ) def rematch(race: RacerRace, player: RacerPlayer.Id): Fu[RacerRace.Id] = race.rematch.flatMap(get) match diff --git a/modules/racer/src/main/RacerLobby.scala b/modules/racer/src/main/RacerLobby.scala index 65b3524e4dc..89ce71a5f97 100644 --- a/modules/racer/src/main/RacerLobby.scala +++ b/modules/racer/src/main/RacerLobby.scala @@ -16,7 +16,7 @@ final class RacerLobby(api: RacerApi)(using Executor)(using scheduler: Scheduler maxSize = Max(128), timeout = 20.seconds, name = "racer.lobby", - lila.log.asyncActorMonitor.full + lila.mon.asyncActorMonitor.full ) private val fallbackRace = RacerRace.make(RacerPlayer.lichess, Nil, 10) diff --git a/modules/rating/src/main/Perf.scala b/modules/rating/src/main/Perf.scala index 95049f7f706..20ab3ed9d0d 100644 --- a/modules/rating/src/main/Perf.scala +++ b/modules/rating/src/main/Perf.scala @@ -13,7 +13,7 @@ object PerfExt: extension (p: Perf) - def addOrReset(monitor: lila.mon.CounterPath, msg: => String)(player: Glicko, date: Instant): Perf = + def addOrReset(counter: kamon.metric.Counter, msg: => String)(player: Glicko, date: Instant): Perf = val newGlicko = player.copy( rating = player.rating .atMost(p.glicko.rating + lila.rating.Glicko.maxRatingDelta) @@ -30,7 +30,7 @@ object PerfExt: if newGlicko.sanityCheck then append(newGlicko) else lila.log("rating").error(s"Crazy Glicko2 $msg") - monitor(lila.mon).increment() + counter.increment() append(lila.rating.Glicko.default) def refund(points: Int): Perf = diff --git a/modules/recap/src/main/RecapBuilder.scala b/modules/recap/src/main/RecapBuilder.scala index df41b33bba5..ef77f0181c3 100644 --- a/modules/recap/src/main/RecapBuilder.scala +++ b/modules/recap/src/main/RecapBuilder.scala @@ -10,6 +10,7 @@ import lila.common.SimpleOpening import lila.db.dsl.{ *, given } import lila.game.Query import lila.core.game.Source +import lila.mon.extensions.* private final class RecapBuilder( repo: RecapRepo, @@ -51,7 +52,7 @@ private final class RecapBuilder( nbs = NbWin(total = nb, win = wins - fixes), votes = PuzzleVotes(nb = votes, themes = themes) ) - .monSuccess(_.recap.puzzles) + .monSuccess(lila.mon.recap.puzzles) private def makeGameRecap(scan: GameScan): RecapGames = RecapGames( @@ -85,7 +86,7 @@ private final class RecapBuilder( .sortedCursor(query, Query.sortChronological) .documentSource() .runFold(GameScan())(_.addGame(userId)(_)) - .monSuccess(_.recap.games) + .monSuccess(lila.mon.recap.games) private case class GameScan( nbs: NbWin = NbWin(), diff --git a/modules/relay/src/main/HttpClient.scala b/modules/relay/src/main/HttpClient.scala index 72825c6c4b9..a5409c9ff35 100644 --- a/modules/relay/src/main/HttpClient.scala +++ b/modules/relay/src/main/HttpClient.scala @@ -6,6 +6,7 @@ import play.api.libs.ws.* import play.shaded.ahc.org.asynchttpclient.util.HttpUtils.extractContentTypeCharsetAttribute import lila.core.lilaism.LilaException +import lila.mon.extensions.* /* Extra generic features for play WS client, * without any knowledge of broadcast specifics. @@ -73,7 +74,7 @@ private final class HttpClient( req .get() .monValue: res => - _.relay.httpGet( + lila.mon.relay.httpGet( res.status, url.host.toString, etag = monitorEtagHit(req, res), diff --git a/modules/relay/src/main/RelayFetch.scala b/modules/relay/src/main/RelayFetch.scala index 65e95f1c299..2cf216cba8a 100644 --- a/modules/relay/src/main/RelayFetch.scala +++ b/modules/relay/src/main/RelayFetch.scala @@ -13,6 +13,7 @@ import lila.game.{ GameRepo, PgnDump } import lila.memo.CacheApi import lila.relay.RelayRound.Sync import lila.study.{ MultiPgn, StudyPgnImport } +import lila.mon.extensions.* final private class RelayFetch( sync: RelaySync, @@ -83,7 +84,7 @@ final private class RelayFetch( else val syncFu = for allGamesInSourceNoLimit <- fetchGames(rt).mon: - _.relay.fetchTime(rt.tour.official, rt.tour.id, rt.tour.slug) + lila.mon.relay.fetchTime(rt.tour.official, rt.tour.id, rt.tour.slug) allGamesInSource = allGamesInSourceNoLimit.take(maxGamesToRead(rt.tour.official).value) filtered = RelayGame.filter(rt.round.sync.onlyRound)(allGamesInSource) sliced = RelayGame.Slices.filterAndOrder(~rt.round.sync.slices)(filtered) @@ -98,7 +99,7 @@ final private class RelayFetch( res <- sync .updateStudyChapters(rt, reordered) .withTimeoutError(7.seconds, SyncResult.Timeout) - .mon(_.relay.syncTime(rt.tour.official, rt.tour.id, rt.tour.slug)) + .mon(lila.mon.relay.syncTime(rt.tour.official, rt.tour.id, rt.tour.slug)) games = res.plan.input.games _ <- notifyAdmin.orphanBoards.inspectPlan(rt, res.plan) nbGamesFinished = games.count(_.points.isDefined) diff --git a/modules/relay/src/main/RelayPush.scala b/modules/relay/src/main/RelayPush.scala index 40ecb0469de..a6ab64b5408 100644 --- a/modules/relay/src/main/RelayPush.scala +++ b/modules/relay/src/main/RelayPush.scala @@ -26,7 +26,7 @@ final class RelayPush( expiration = 1.minute, timeout = 10.seconds, name = "relay.push", - lila.log.asyncActorMonitor.full + lila.mon.asyncActorMonitor.full ) def apply(rt: RelayRound.WithTour, pgn: PgnStr)(using Me, UserAgent): Fu[Results] = diff --git a/modules/report/src/main/Automod.scala b/modules/report/src/main/Automod.scala index 95583d5ca1e..dd9604d8dff 100644 --- a/modules/report/src/main/Automod.scala +++ b/modules/report/src/main/Automod.scala @@ -12,6 +12,7 @@ import lila.common.Json.given import lila.core.config.Secret import lila.core.data.Text import lila.core.id.ImageId +import lila.mon.extensions.* import lila.memo.{ ImageAutomod, ImageAutomodRequest, Dimensions } import lila.memo.SettingStore.Text.given @@ -128,7 +129,7 @@ final class Automod( lila.mon.mod.report.automod.imageFlagged(flagged).increment() flagged.option: res.str("reason") | "No reason provided" - .monSuccess(_.mod.report.automod.imageRequest) + .monSuccess(lila.mon.mod.report.automod.imageRequest) .recover: case err => logger.error(err.getMessage, err) diff --git a/modules/report/src/main/ReportApi.scala b/modules/report/src/main/ReportApi.scala index 9a7b0ad56c1..debee90cb53 100644 --- a/modules/report/src/main/ReportApi.scala +++ b/modules/report/src/main/ReportApi.scala @@ -11,6 +11,7 @@ import lila.db.dsl.{ *, given } import lila.memo.CacheApi.* import lila.memo.SettingStore.Text.given import lila.report.Room.Scores +import lila.mon.extensions.* final class ReportApi( val coll: Coll, @@ -336,7 +337,7 @@ final class ReportApi( systemPrompt = commsPromptSetting.get(), model = commsModelSetting.get() ) - .monSuccess(_.mod.report.automod.request) + .monSuccess(lila.mon.mod.report.automod.request) val candidate = for (images, textResponse) <- automodApi.markdownImages(Markdown(userText)).zip(assessText) flaggedImages = images.flatMap(_.automod).flatMap(_.flagged) @@ -623,7 +624,7 @@ final class ReportApi( maxSize = Max(32), timeout = 20.seconds, name = "report.inquiries", - lila.log.asyncActorMonitor.full + lila.mon.asyncActorMonitor.full ) def allBySuspect: Fu[Map[UserId, Report.Inquiry]] = diff --git a/modules/report/src/main/ReportForm.scala b/modules/report/src/main/ReportForm.scala index bd3394d65a7..6dd6e0efb65 100644 --- a/modules/report/src/main/ReportForm.scala +++ b/modules/report/src/main/ReportForm.scala @@ -8,6 +8,7 @@ import lila.common.Form.cleanNonEmptyText import lila.core.LightUser import lila.core.config.NetDomain import lila.core.report.SuspectId +import lila.mon.extensions.* final private[report] class ReportForm(lightUserAsync: LightUser.Getter)(using domain: NetDomain): val cheatLinkConstraint: Constraint[ReportSetup] = Constraint("constraints.cheatgamelink"): setup => diff --git a/modules/round/src/main/CorresAlarm.scala b/modules/round/src/main/CorresAlarm.scala index 8d4e2812e3f..a5a9db94fdd 100644 --- a/modules/round/src/main/CorresAlarm.scala +++ b/modules/round/src/main/CorresAlarm.scala @@ -7,6 +7,7 @@ import reactivemongo.api.bson.* import lila.common.{ Bus, LilaScheduler, LilaStream } import lila.core.user.LightUserApi import lila.db.dsl.{ *, given } +import lila.mon.extensions.* final private class CorresAlarm( coll: Coll, @@ -71,5 +72,5 @@ final private class CorresAlarm( case (alarm, None) => deleteAlarm(alarm._id) .toMat(LilaStream.sinkCount)(Keep.right) .run() - .mon(_.round.alarm.time) + .mon(lila.mon.round.alarm.time) .void diff --git a/modules/round/src/main/CorrespondenceEmail.scala b/modules/round/src/main/CorrespondenceEmail.scala index 119f577e66a..2faebeaa5f3 100644 --- a/modules/round/src/main/CorrespondenceEmail.scala +++ b/modules/round/src/main/CorrespondenceEmail.scala @@ -9,6 +9,7 @@ import lila.core.misc.mailer.* import lila.core.notify.NotifyApi import lila.db.dsl.{ *, given } import lila.user.UserRepo +import lila.mon.extensions.* final private class CorrespondenceEmail(gameRepo: GameRepo, userRepo: UserRepo, notifyApi: NotifyApi)(using Executor, @@ -26,7 +27,7 @@ final private class CorrespondenceEmail(gameRepo: GameRepo, userRepo: UserRepo, .map { Bus.pub(_) } .runWith(LilaStream.sinkCount) .addEffect(lila.mon.round.correspondenceEmail.emails.record(_)) - .monSuccess(_.round.correspondenceEmail.time) + .monSuccess(lila.mon.round.correspondenceEmail.time) private def opponentStream = notifyApi.prefColl diff --git a/modules/round/src/main/PerfsUpdater.scala b/modules/round/src/main/PerfsUpdater.scala index 26e02f3fd25..b0467cbe77b 100644 --- a/modules/round/src/main/PerfsUpdater.scala +++ b/modules/round/src/main/PerfsUpdater.scala @@ -80,7 +80,7 @@ final class PerfsUpdater( val newPerfs = perfs .focusKey(perfKey) .modify: - _.addOrReset(_.round.error.glicko, s"game ${game.id}")(player, game.movedAt) + _.addOrReset(lila.mon.round.error.glicko, s"game ${game.id}")(player, game.movedAt) if game.ratingVariant.standard then updateStandard(newPerfs) else newPerfs diff --git a/modules/round/src/main/RoundAsyncActor.scala b/modules/round/src/main/RoundAsyncActor.scala index cd8014e6c3d..c7f456ef2be 100644 --- a/modules/round/src/main/RoundAsyncActor.scala +++ b/modules/round/src/main/RoundAsyncActor.scala @@ -11,6 +11,7 @@ import lila.game.GameExt.* import lila.game.{ Event, GameRepo, Player as GamePlayer, Progress } import lila.room.RoomSocket.{ Protocol as RP, * } import lila.round.RoundGame.* +import lila.mon.extensions.* final private class RoundAsyncActor( dependencies: RoundAsyncActor.Dependencies, @@ -195,7 +196,7 @@ final private class RoundAsyncActor( case RoundBus.FishnetPlay(uci, hash) => handle: game => player.fishnet(game, hash, uci) - .mon(_.round.move.time) + .mon(lila.mon.round.move.time) case RoundBus.Abort(playerId) => handle(playerId): pov => diff --git a/modules/round/src/main/RoundSocket.scala b/modules/round/src/main/RoundSocket.scala index 354e8a5b419..0abe9f4075a 100644 --- a/modules/round/src/main/RoundSocket.scala +++ b/modules/round/src/main/RoundSocket.scala @@ -15,6 +15,7 @@ import lila.core.net.IpAddress import lila.core.round.* import lila.core.socket.{ protocol as P, * } import lila.room.RoomSocket.{ Protocol as RP, * } +import lila.mon.extensions.* final class RoundSocket( socketKit: ParallelSocketKit, diff --git a/modules/round/src/main/Titivate.scala b/modules/round/src/main/Titivate.scala index 1b91e612671..ec4759d71a4 100644 --- a/modules/round/src/main/Titivate.scala +++ b/modules/round/src/main/Titivate.scala @@ -8,6 +8,7 @@ import lila.core.round.{ Abandon, RoundBus } import lila.db.dsl.* import lila.game.GameExt.abandoned import lila.game.{ GameRepo, Query } +import lila.mon.extensions.* /* * Cleans up unfinished games @@ -54,7 +55,7 @@ final private class Titivate( yield lila.mon.round.titivate.old.record(old) run - .monSuccess(_.round.titivate.time) + .monSuccess(lila.mon.round.titivate.time) .logFailure(logBranch) .addEffectAnyway(scheduleNext()) diff --git a/modules/search/src/main/LilaSearchClient.scala b/modules/search/src/main/LilaSearchClient.scala index 84fc6e3d0ef..b4a3722f431 100644 --- a/modules/search/src/main/LilaSearchClient.scala +++ b/modules/search/src/main/LilaSearchClient.scala @@ -2,6 +2,7 @@ package lila.search import lila.search.client.SearchClient import lila.search.spec.* +import lila.mon.extensions.* class LilaSearchClient(client: SearchClient, cacheApi: lila.memo.CacheApi)(using Executor) extends SearchClient: @@ -29,7 +30,7 @@ class LilaSearchClient(client: SearchClient, cacheApi: lila.memo.CacheApi)(using SearchOutput(Nil) private def monitor[A](op: "search" | "count", index: String)(f: Fu[A]) = - f.monTry(res => _.search.time(op, index, res.isSuccess)) + f.monTry(res => lila.mon.search.time(op, index, res.isSuccess)) extension (query: Query) def index: String = query match diff --git a/modules/security/src/main/DnsApi.scala b/modules/security/src/main/DnsApi.scala index 44ea60ef8e8..ae37c33630b 100644 --- a/modules/security/src/main/DnsApi.scala +++ b/modules/security/src/main/DnsApi.scala @@ -7,6 +7,7 @@ import play.api.libs.ws.StandaloneWSClient import lila.core.lilaism.LilaException import lila.core.net.Domain import lila.db.dsl.given +import lila.mon.extensions.* final private class DnsApi( ws: StandaloneWSClient, @@ -36,7 +37,7 @@ final private class DnsApi( } .flatten } - }.monSuccess(_.security.dnsApi.mx) + }.monSuccess(lila.mon.security.dnsApi.mx) } private def fetch[A](domain: Domain.Lower, tpe: String)(f: List[JsObject] => A): Fu[A] = diff --git a/modules/security/src/main/EmailAddressValidator.scala b/modules/security/src/main/EmailAddressValidator.scala index c992a55fa68..399d4b3b9c6 100644 --- a/modules/security/src/main/EmailAddressValidator.scala +++ b/modules/security/src/main/EmailAddressValidator.scala @@ -5,6 +5,7 @@ import play.api.data.validation.* import lila.core.net.Domain import lila.user.{ User, UserRepo } import lila.core.email.NormalizedEmailAddress +import lila.mon.extensions.* /** Validate and normalize emails */ diff --git a/modules/security/src/main/Env.scala b/modules/security/src/main/Env.scala index 489c0fb864d..37c19ca6896 100644 --- a/modules/security/src/main/Env.scala +++ b/modules/security/src/main/Env.scala @@ -58,7 +58,7 @@ final class Env( lazy val passwordHasher = PasswordHasher( secret = config.passwordBPassSecret, logRounds = 10, - hashTimer = lila.common.Chronometer.syncMon(_.user.auth.hashTime) + hashTimer = lila.mon.Chronometer.syncMon(lila.mon.user.auth.hashTime) ) lazy val authenticator = wire[Authenticator] diff --git a/modules/security/src/main/GeoIP.scala b/modules/security/src/main/GeoIP.scala index ca93f91d539..c0f7daf8414 100644 --- a/modules/security/src/main/GeoIP.scala +++ b/modules/security/src/main/GeoIP.scala @@ -19,7 +19,7 @@ final class GeoIP(config: GeoIP.Config, scheduler: Scheduler)(using Executor): private def loadFromFile(): Unit = if config.file.nonEmpty then try - val time = lila.common.Chronometer.sync: + val time = lila.mon.Chronometer.sync: reader = DatabaseReader.Builder(java.io.File(config.file)).fileMode(FileMode.MEMORY).build.some reader.foreach: r => val meta = r.getMetadata diff --git a/modules/security/src/main/Ip2Proxy.scala b/modules/security/src/main/Ip2Proxy.scala index a97aaee8eeb..918c9b06474 100644 --- a/modules/security/src/main/Ip2Proxy.scala +++ b/modules/security/src/main/Ip2Proxy.scala @@ -8,6 +8,7 @@ import play.api.libs.ws.StandaloneWSClient import lila.core.net.IpAddress import lila.core.security.{ Ip2ProxyApi, IsProxy } import lila.common.HTTPRequest +import lila.mon.extensions.* final class Ip2ProxySkip extends Ip2ProxyApi: def ofReq(req: RequestHeader): Fu[IsProxy] = fuccess(IsProxy.empty) @@ -89,7 +90,7 @@ final class Ip2ProxyServer( .withTimeout(200.millis, "Ip2Proxy.fetch") .dmap(_.body[JsValue]) .dmap(readProxyName) - .monSuccess(_.security.proxy.request) + .monSuccess(lila.mon.security.proxy.request) .addEffect: result => lila.mon.security.proxy.result(result.name).increment() .recoverDefault(IsProxy.empty) diff --git a/modules/security/src/main/PwnedApi.scala b/modules/security/src/main/PwnedApi.scala index 91b1a207b60..0738fba445f 100644 --- a/modules/security/src/main/PwnedApi.scala +++ b/modules/security/src/main/PwnedApi.scala @@ -4,6 +4,8 @@ import com.roundeights.hasher.Implicits.* import play.api.libs.ws.DefaultBodyReadables.* import play.api.libs.ws.StandaloneWSClient +import lila.mon.extensions.* + opaque type IsPwned = Boolean object IsPwned extends YesNo[IsPwned] @@ -24,5 +26,5 @@ final class PwnedApi(ws: StandaloneWSClient, rangeUrl: String)(using Executor): logger.warn(s"Pwnd ${url} ${res.status} ${res.body[String].take(200)}") IsPwned(false) .monValue: result => - _.security.pwned.get(result.yes) + lila.mon.security.pwned.get(result.yes) .recoverDefault(IsPwned(false)) diff --git a/modules/security/src/main/SecurityForm.scala b/modules/security/src/main/SecurityForm.scala index 8db33d2ff39..7ffffa63d27 100644 --- a/modules/security/src/main/SecurityForm.scala +++ b/modules/security/src/main/SecurityForm.scala @@ -11,6 +11,7 @@ import lila.core.security.ClearPassword import lila.user.TotpSecret.{ base32, verify } import lila.user.{ TotpSecret, TotpToken } import lila.oauth.OAuthSignedClient.SimpleSignup +import lila.mon.extensions.* final class SecurityForm( userRepo: lila.user.UserRepo, diff --git a/modules/security/src/main/VerifyMail.scala b/modules/security/src/main/VerifyMail.scala index 970bea6deb1..4680780f872 100644 --- a/modules/security/src/main/VerifyMail.scala +++ b/modules/security/src/main/VerifyMail.scala @@ -6,6 +6,7 @@ import play.api.libs.ws.JsonBodyReadables.* import play.api.libs.ws.StandaloneWSClient import lila.core.net.Domain +import lila.mon.extensions.* /* An expensive API detecting disposable email. * Only hit after trying everything else (DnsApi) @@ -77,7 +78,7 @@ final private class VerifyMail( ok ).getOrElse: throw lila.core.lilaism.LilaException(s"$url ${res.status} ${res.body[String].take(200)}") - .monTry(res => _.security.mailcheckApi.fetch(res.isSuccess, res.getOrElse(true))) + .monTry(res => lila.mon.security.mailcheckApi.fetch(res.isSuccess, res.getOrElse(true))) private def fetchPaid(domain: Domain.Lower): Fu[Boolean] = val url = s"https://verifymail.io/api/$domain" @@ -99,4 +100,4 @@ final private class VerifyMail( ok ).getOrElse: throw lila.core.lilaism.LilaException(s"$url ${res.status} ${res.body[String].take(200)}") - .monTry(res => _.security.verifyMailApi.fetch(res.isSuccess, res.getOrElse(true))) + .monTry(res => lila.mon.security.verifyMailApi.fetch(res.isSuccess, res.getOrElse(true))) diff --git a/modules/shutup/src/main/Analyser.scala b/modules/shutup/src/main/Analyser.scala index cd2262ec77b..9c5d70f24b4 100644 --- a/modules/shutup/src/main/Analyser.scala +++ b/modules/shutup/src/main/Analyser.scala @@ -4,14 +4,14 @@ import lila.common.constants.bannedYoutubeIds object Analyser extends lila.core.shutup.TextAnalyser: - def apply(raw: String): TextAnalysis = lila.common.Chronometer + def apply(raw: String): TextAnalysis = lila.mon.Chronometer .sync: val lower = raw.take(2000).toLowerCase val processable = removeDiacriticalCombination(removeSlash(lower)) val matches = latinBigRegex.findAllMatchIn(latinify(processable)).toList ::: ruBigRegex.findAllMatchIn(lower).toList TextAnalysis(lower, matches.map(_.toString)) - .mon(_.shutup.analyzer) + .mon(lila.mon.shutup.analyzer) .logIfSlow(100, logger)(_ => s"Slow shutup analyser ${raw.take(400)}") .result diff --git a/modules/simul/src/main/SimulApi.scala b/modules/simul/src/main/SimulApi.scala index 26a823a343c..0e861b57303 100644 --- a/modules/simul/src/main/SimulApi.scala +++ b/modules/simul/src/main/SimulApi.scala @@ -37,7 +37,7 @@ final class SimulApi( expiration = 10.minutes, timeout = 10.seconds, name = "simulApi", - lila.log.asyncActorMonitor.full + lila.mon.asyncActorMonitor.full ) export repo.{ find, byIds } diff --git a/modules/storm/src/main/StormSelector.scala b/modules/storm/src/main/StormSelector.scala index 33fbcb34666..78b89ed4feb 100644 --- a/modules/storm/src/main/StormSelector.scala +++ b/modules/storm/src/main/StormSelector.scala @@ -7,6 +7,7 @@ import lila.db.dsl.{ *, given } import lila.memo.CacheApi import lila.puzzle.PuzzleColls import lila.common.BatchProvider +import lila.mon.extensions.* /* The difficulty of storm should remain constant! * Be very careful when adjusting the selector. @@ -46,7 +47,7 @@ final class StormSelector(colls: PuzzleColls, cacheApi: CacheApi)(using Executor private val setSize = ratingBuckets._2F.sum // 137 private val batchProvider = - BatchProvider[PuzzleSet]("stormSelector", timeout = 15.seconds): () => + BatchProvider[PuzzleSet]("stormSelector", timeout = 15.seconds, lila.mon.asyncActorMonitor.full): () => aggregateMultipleSets: if lila.common.Uptime.startedSinceMinutes(2) then setsPerAggregation else 1 @@ -92,7 +93,7 @@ final class StormSelector(colls: PuzzleColls, cacheApi: CacheApi)(using Executor logger.warn: s"selector: $setSize x $nbSets. ${docs.size} docs, ${puzzles.size} puzzles, ${setsToMake} sets, ${groups.size} groups: $showGroups" .logTimeIfGt(s"storm selector x$nbSets", 8.seconds) - .monSuccess(_.storm.selector.time) + .monSuccess(lila.mon.storm.selector.time) .recoverWith: case e: IllegalArgumentException if nbSets > 1 => val retryNbSets = nbSets / 2 diff --git a/modules/study/src/main/StudyFlatTree.scala b/modules/study/src/main/StudyFlatTree.scala index a975dedc539..e8d0dff6f08 100644 --- a/modules/study/src/main/StudyFlatTree.scala +++ b/modules/study/src/main/StudyFlatTree.scala @@ -2,7 +2,7 @@ package lila.study import chess.format.UciPath -import lila.common.Chronometer +import lila.mon.Chronometer.syncMon import lila.db.dsl.* import lila.tree.{ Branch, Branches, NewBranch, NewRoot, NewTree, Root } @@ -23,7 +23,7 @@ private object StudyFlatTree: object reader: def rootChildren(flatTree: Bdoc): Branches = - Chronometer.syncMon(_.study.tree.read): + syncMon(lila.mon.study.tree.read): traverse: flatTree.elements.toList .collect: @@ -32,7 +32,7 @@ private object StudyFlatTree: .sortBy(-_.depth) def newRoot(flatTree: Bdoc): Option[NewTree] = - Chronometer.syncMon(_.study.tree.read): + syncMon(lila.mon.study.tree.read): traverseN: flatTree.elements.toList .collect: @@ -72,11 +72,11 @@ private object StudyFlatTree: object writer: def rootChildren(root: Root): List[(String, Bdoc)] = - Chronometer.syncMon(_.study.tree.write): + syncMon(lila.mon.study.tree.write): root.children.toList.flatMap { traverse(_, UciPath.root) } def newRootChildren(root: NewRoot): List[(String, Bdoc)] = - Chronometer.syncMon(_.study.tree.write): + syncMon(lila.mon.study.tree.write): root.tree.so: _.mapAccuml_(UciPath.root)((acc, branch) => val path = acc + branch.id diff --git a/modules/study/src/main/StudySequencer.scala b/modules/study/src/main/StudySequencer.scala index 3f2d27f229b..3dae33d62c4 100644 --- a/modules/study/src/main/StudySequencer.scala +++ b/modules/study/src/main/StudySequencer.scala @@ -13,7 +13,7 @@ final private class StudySequencer( expiration = 1.minute, timeout = 10.seconds, name = "study", - lila.log.asyncActorMonitor.highCardinality + lila.mon.asyncActorMonitor.highCardinality ) def sequenceStudy[A <: Matchable: Zero](studyId: StudyId)(f: Study => Fu[A]): Fu[A] = diff --git a/modules/study/src/main/StudyTopic.scala b/modules/study/src/main/StudyTopic.scala index 569265e3c09..140ca073923 100644 --- a/modules/study/src/main/StudyTopic.scala +++ b/modules/study/src/main/StudyTopic.scala @@ -117,7 +117,7 @@ final class StudyTopicApi(topicRepo: StudyTopicRepo, userTopicRepo: StudyUserTop maxSize = Max(1), timeout = 61.seconds, name = "studyTopicAggregation", - lila.log.asyncActorMonitor.unhandled + lila.mon.asyncActorMonitor.unhandled ) private[study] def recompute(): Unit = diff --git a/modules/swiss/src/main/PairingSystem.scala b/modules/swiss/src/main/PairingSystem.scala index 518116aa085..1c7f709adae 100644 --- a/modules/swiss/src/main/PairingSystem.scala +++ b/modules/swiss/src/main/PairingSystem.scala @@ -27,7 +27,7 @@ final private class PairingSystem(trf: SwissTrf, executable: String)(using val command = s"$executable --$flavour $file -p" val stdout = new collection.mutable.ListBuffer[String] val stderr = new StringBuilder - val status = lila.common.Chronometer.syncMon(_.swiss.bbpairing): + val status = lila.mon.Chronometer.syncMon(lila.mon.swiss.bbpairing): blocking: command ! ProcessLogger(stdout append _, stderr append _) if status != 0 then diff --git a/modules/swiss/src/main/SwissApi.scala b/modules/swiss/src/main/SwissApi.scala index 283ce6f92b3..2af860027a5 100644 --- a/modules/swiss/src/main/SwissApi.scala +++ b/modules/swiss/src/main/SwissApi.scala @@ -14,6 +14,7 @@ import lila.core.LightUser import lila.core.round.RoundBus import lila.core.swiss.{ IdName, SwissFinish } import lila.core.userId.UserSearch +import lila.mon.extensions.* import lila.db.dsl.{ *, given } import lila.gathering.Condition.WithVerdicts import lila.gathering.GreatPlayer @@ -41,7 +42,7 @@ final class SwissApi( expiration = 20.minutes, timeout = 10.seconds, name = "swiss.api", - lila.log.asyncActorMonitor.full + lila.mon.asyncActorMonitor.full ) import BsonHandlers.{ *, given } @@ -607,7 +608,7 @@ final class SwissApi( ) yield cache.swissCache.clear(swiss.id) } >> recomputeAndUpdateAll(id) - .monSuccess(_.swiss.tick) + .monSuccess(lila.mon.swiss.tick) private def countPresentPlayers(swiss: Swiss) = SwissPlayer.fields: f => mongo.player.countSel($doc(f.swissId -> swiss.id, f.absent.$ne(true))) diff --git a/modules/swiss/src/main/SwissDirector.scala b/modules/swiss/src/main/SwissDirector.scala index 1f0057ef2be..f2e705aeea6 100644 --- a/modules/swiss/src/main/SwissDirector.scala +++ b/modules/swiss/src/main/SwissDirector.scala @@ -4,6 +4,7 @@ import chess.ByColor import monocle.syntax.all.* import lila.db.dsl.{ *, given } +import lila.mon.extensions.* final private class SwissDirector( mongo: SwissMongo, @@ -75,7 +76,7 @@ final private class SwissDirector( logger.info(s"BBPairing ${from.id} $input") from.some } - .monSuccess(_.swiss.startRound) + .monSuccess(lila.mon.swiss.startRound) private def makeGame(swiss: Swiss, players: Map[UserId, SwissPlayer])(pairing: SwissPairing): Game = lila.core.game diff --git a/modules/swiss/src/main/SwissJson.scala b/modules/swiss/src/main/SwissJson.scala index 7859aa5ac9d..6c8bf438fa7 100644 --- a/modules/swiss/src/main/SwissJson.scala +++ b/modules/swiss/src/main/SwissJson.scala @@ -6,6 +6,7 @@ import play.api.libs.json.* import lila.common.Json.given import lila.core.LightUser import lila.core.socket.SocketVersion +import lila.mon.extensions.* import lila.db.dsl.{ *, given } import lila.gathering.Condition.WithVerdicts import lila.gathering.GreatPlayer @@ -72,7 +73,7 @@ final class SwissJson( .add("greatPlayer" -> GreatPlayer.wikiUrl(swiss.name).map { url => Json.obj("name" -> swiss.name, "url" -> url) }) - }.monSuccess(_.swiss.json) + }.monSuccess(lila.mon.swiss.json) def fetchMyInfo(swiss: Swiss, me: User): Fu[Option[MyInfo]] = mongo.player.byId[SwissPlayer](SwissPlayer.makeId(swiss.id, me.id).value).flatMapz { player => diff --git a/modules/swiss/src/main/SwissScoring.scala b/modules/swiss/src/main/SwissScoring.scala index 4a312614d9f..3c0e2459d63 100644 --- a/modules/swiss/src/main/SwissScoring.scala +++ b/modules/swiss/src/main/SwissScoring.scala @@ -3,12 +3,14 @@ package lila.swiss import reactivemongo.api.bson.* import lila.db.dsl.{ *, given } +import lila.mon.extensions.* final private class SwissScoring(mongo: SwissMongo)(using Scheduler, Executor): import BsonHandlers.given - def compute(id: SwissId): Fu[Option[SwissScoring.Result]] = sequencer(id).monSuccess(_.swiss.scoringGet) + def compute(id: SwissId): Fu[Option[SwissScoring.Result]] = + sequencer(id).monSuccess(lila.mon.swiss.scoringGet) private val sequencer = scalalib.actor.AskPipelines[SwissId, Option[SwissScoring.Result]]( compute = recompute, @@ -51,7 +53,7 @@ final private class SwissScoring(mongo: SwissMongo)(using Scheduler, Executor): pairingMap ) .some - .monSuccess(_.swiss.scoringRecompute) + .monSuccess(lila.mon.swiss.scoringRecompute) private def fetchPlayers(swiss: Swiss) = SwissPlayer.fields: f => diff --git a/modules/team/src/main/TeamApi.scala b/modules/team/src/main/TeamApi.scala index 83e2297dd9e..680e7ec4298 100644 --- a/modules/team/src/main/TeamApi.scala +++ b/modules/team/src/main/TeamApi.scala @@ -34,7 +34,7 @@ final class TeamApi( expiration = 30.seconds, timeout = 5.seconds, name = "team", - lila.log.asyncActorMonitor.highCardinality + lila.mon.asyncActorMonitor.highCardinality ) def team(id: TeamId) = teamRepo.byId(id) diff --git a/modules/team/src/main/TeamForm.scala b/modules/team/src/main/TeamForm.scala index d0f680e625d..3ce7b78b877 100644 --- a/modules/team/src/main/TeamForm.scala +++ b/modules/team/src/main/TeamForm.scala @@ -15,6 +15,7 @@ import lila.common.Form.{ import lila.core.captcha.CaptchaApi import lila.core.team.Access import lila.core.user.FlairApi +import lila.mon.extensions.* import lila.db.dsl.{ *, given } final private class TeamForm(teamRepo: TeamRepo, captcha: CaptchaApi, flairApi: FlairApi)(using diff --git a/modules/team/src/main/TeamSecurity.scala b/modules/team/src/main/TeamSecurity.scala index c3fb6dd8b32..458468152c9 100644 --- a/modules/team/src/main/TeamSecurity.scala +++ b/modules/team/src/main/TeamSecurity.scala @@ -5,6 +5,7 @@ import cats.derived.* import lila.core.perm.Granter import lila.memo.CacheApi.* +import lila.mon.extensions.* object TeamSecurity: enum Permission(val name: String, val desc: String) derives Eq: diff --git a/modules/tournament/src/main/CreatedOrganizer.scala b/modules/tournament/src/main/CreatedOrganizer.scala index f2540c19dc0..fdaa49230c7 100644 --- a/modules/tournament/src/main/CreatedOrganizer.scala +++ b/modules/tournament/src/main/CreatedOrganizer.scala @@ -1,6 +1,7 @@ package lila.tournament import lila.common.LilaScheduler +import lila.mon.extensions.* final private class CreatedOrganizer( api: TournamentApi, @@ -17,5 +18,5 @@ final private class CreatedOrganizer( .documentSource() .mapAsync(1)(api.start) .run() - .monSuccess(_.tournament.createdOrganizer.tick) + .monSuccess(lila.mon.tournament.createdOrganizer.tick) .void diff --git a/modules/tournament/src/main/PlayerRepo.scala b/modules/tournament/src/main/PlayerRepo.scala index 2111058bdd5..75bed2088d0 100644 --- a/modules/tournament/src/main/PlayerRepo.scala +++ b/modules/tournament/src/main/PlayerRepo.scala @@ -7,6 +7,7 @@ import reactivemongo.api.bson.* import lila.core.chess.Rank import lila.core.user.WithPerf import lila.core.userId.UserSearch +import lila.mon.extensions.* import lila.db.dsl.{ *, given } import lila.tournament.BSONHandlers.given diff --git a/modules/tournament/src/main/StartedOrganizer.scala b/modules/tournament/src/main/StartedOrganizer.scala index 2619fb34362..9daf74039b7 100644 --- a/modules/tournament/src/main/StartedOrganizer.scala +++ b/modules/tournament/src/main/StartedOrganizer.scala @@ -3,6 +3,7 @@ package lila.tournament import akka.stream.scaladsl.* import lila.common.{ LilaScheduler, LilaStream } +import lila.mon.extensions.* final private class StartedOrganizer( api: TournamentApi, @@ -35,7 +36,7 @@ final private class StartedOrganizer( .addEffect: nb => if doAllTournaments then lila.mon.tournament.started.update(nb) runCounter = runCounter + 1 - .monSuccess(_.tournament.startedOrganizer.tick) + .monSuccess(lila.mon.tournament.startedOrganizer.tick) .void private def processTour(tour: Tournament): Funit = @@ -55,7 +56,7 @@ final private class StartedOrganizer( for waitingBots <- fetchWaitingBots(tour) _ = if waitingBots.nonEmpty then waitingUsersApi.addApiUsers(tour, waitingBots) - waiting <- socket.getWaitingUsers(tour).monSuccess(_.tournament.startedOrganizer.waitingUsers) + waiting <- socket.getWaitingUsers(tour).monSuccess(lila.mon.tournament.startedOrganizer.waitingUsers) _ = waiting.hash _ = lila.mon.tournament.waitingPlayers.record(waiting.size) _ <- api.makePairings(tour, waiting, smallTourNbActivePlayers) diff --git a/modules/tournament/src/main/TournamentApi.scala b/modules/tournament/src/main/TournamentApi.scala index 406d3c952f4..bacbc5cb258 100644 --- a/modules/tournament/src/main/TournamentApi.scala +++ b/modules/tournament/src/main/TournamentApi.scala @@ -16,6 +16,7 @@ import lila.core.round.{ RoundBus, GoBerserk } import lila.core.team.LightTeam import lila.core.tournament.Status import lila.core.chess.Rank +import lila.mon.extensions.* import lila.gathering.Condition import lila.gathering.Condition.GetMyTeamIds @@ -147,11 +148,11 @@ final class TournamentApi( )).so(Parallel(forTour.id, "makePairings")(cached.tourCache.started): tour => cached .ranking(tour) - .mon(_.tournament.pairing.createRanking) + .mon(lila.mon.tournament.pairing.createRanking) .flatMap: ranking => pairingSystem .createPairings(tour, users, ranking, smallTourNbActivePlayers) - .mon(_.tournament.pairing.createPairings) + .mon(lila.mon.tournament.pairing.createPairings) .flatMap: case Nil => funit case pairings => @@ -159,9 +160,9 @@ final class TournamentApi( pairings .parallelVoid: pairing => autoPairing(tour, pairing, ranking.ranking) - .mon(_.tournament.pairing.createAutoPairing) + .mon(lila.mon.tournament.pairing.createAutoPairing) .map { socket.startGame(tour.id, _) } - .mon(_.tournament.pairing.createInserts) + .mon(lila.mon.tournament.pairing.createInserts) .andDo: lila.mon.tournament.pairing.batchSize.record(pairings.size) waitingUsers.registerPairedUsers( @@ -171,7 +172,7 @@ final class TournamentApi( socket.reload(tour.id) hadPairings.put(tour.id) featureOneOf(tour, pairings, ranking.ranking) // do outside of queue - .monSuccess(_.tournament.pairing.create) + .monSuccess(lila.mon.tournament.pairing.create) .chronometer .logIfSlow(100, logger)(_ => s"Pairings for https://lichess.org/tournament/${tour.id}") .result) @@ -788,7 +789,7 @@ final class TournamentApi( )(run: Tournament => Fu[A]): Fu[A] = fetch(tourId).flatMapz { tour => if tour.nbPlayers > 3000 - then run(tour).chronometer.mon(_.tournament.action(tourId.value, action)).result + then run(tour).chronometer.mon(lila.mon.tournament.action(tourId.value, action)).result else run(tour) } diff --git a/modules/tournament/src/main/TournamentFeaturing.scala b/modules/tournament/src/main/TournamentFeaturing.scala index 0bd1a0c44d7..99cad200f38 100644 --- a/modules/tournament/src/main/TournamentFeaturing.scala +++ b/modules/tournament/src/main/TournamentFeaturing.scala @@ -2,7 +2,9 @@ package lila package tournament import com.github.blemale.scaffeine.AsyncLoadingCache + import lila.memo.CacheApi.buildAsyncTimeout +import lila.mon.extensions.* final class TournamentFeaturing( api: TournamentApi, @@ -52,9 +54,4 @@ final class TournamentFeaturing( teamIds.nonEmpty.so: repo .visibleForTeams(teamIds, aheadMinutes) - // .map: - // _.foldLeft(List.empty[Tournament]): (dedup, tour) => - // if dedup.exists(_ sameNameAndTeam tour) then dedup - // else tour :: dedup - // .reverse - .monSuccess(_.tournament.featuring.forTeams(page)) + .monSuccess(lila.mon.tournament.featuring.forTeams(page)) diff --git a/modules/tournament/src/main/TournamentLilaHttp.scala b/modules/tournament/src/main/TournamentLilaHttp.scala index 0ba576ee542..7b628466b6e 100644 --- a/modules/tournament/src/main/TournamentLilaHttp.scala +++ b/modules/tournament/src/main/TournamentLilaHttp.scala @@ -1,6 +1,7 @@ package lila.tournament import akka.stream.scaladsl.* +import lila.mon.extensions.* import io.lettuce.core.RedisClient import play.api.libs.json.* import scalalib.cache.{ ExpireSetMemo, FrequencyThreshold } @@ -44,7 +45,7 @@ final class TournamentLilaHttp( lila.mon.tournament.lilaHttp.fullSize.record(str.size) conn.async.publish(channel, str) .runWith(LilaStream.sinkCount) - .monSuccess(_.tournament.lilaHttp.tick) + .monSuccess(lila.mon.tournament.lilaHttp.tick) .addEffect(lila.mon.tournament.lilaHttp.nbTours.update(_)) .void diff --git a/modules/tournament/src/main/TournamentRepo.scala b/modules/tournament/src/main/TournamentRepo.scala index 1770983e8ca..e86cf584f6a 100644 --- a/modules/tournament/src/main/TournamentRepo.scala +++ b/modules/tournament/src/main/TournamentRepo.scala @@ -6,6 +6,7 @@ import reactivemongo.akkastream.{ AkkaStreamCursor, cursorProducer } import lila.core.config.CollName import lila.core.tournament.Status import lila.db.dsl.{ *, given } +import lila.mon.extensions.* final class TournamentRepo(val coll: Coll, playerCollName: CollName)(using Executor): import BSONHandlers.given @@ -173,7 +174,7 @@ final class TournamentRepo(val coll: Coll, playerCollName: CollName)(using Execu Project($id(true)) ) .map(_.flatMap(_.getAsOpt[TourId]("_id"))) - .monSuccess(_.tournament.withdrawableIds(reason)) + .monSuccess(lila.mon.tournament.withdrawableIds(reason)) def setStatus(tourId: TourId, status: Status) = coll.updateField($id(tourId), "status", status.id).void diff --git a/modules/tournament/src/main/arena/AntmaPairing.scala b/modules/tournament/src/main/arena/AntmaPairing.scala index d6ec4283a17..f8a5c059815 100644 --- a/modules/tournament/src/main/arena/AntmaPairing.scala +++ b/modules/tournament/src/main/arena/AntmaPairing.scala @@ -3,7 +3,7 @@ package arena import scalalib.WMMatching -import lila.common.Chronometer +import lila.mon.Chronometer.syncMon import lila.tournament.arena.PairingSystem.Data private object AntmaPairing: @@ -35,7 +35,7 @@ private object AntmaPairing: def duelScore: (RPlayer, RPlayer) => Option[Int] = (_, _) => Some(1) - Chronometer.syncMon(_.tournament.pairing.wmmatching): + syncMon(lila.mon.tournament.pairing.wmmatching): WMMatching( players.toArray, if data.tour.isTeamBattle then battleScore diff --git a/modules/tournament/src/main/arena/PairingSystem.scala b/modules/tournament/src/main/arena/PairingSystem.scala index 856a2561257..825ac95d1ae 100644 --- a/modules/tournament/src/main/arena/PairingSystem.scala +++ b/modules/tournament/src/main/arena/PairingSystem.scala @@ -2,6 +2,7 @@ package lila.tournament package arena import lila.core.chess.Rank +import lila.mon.extensions.* final private[tournament] class PairingSystem( pairingRepo: PairingRepo, @@ -63,7 +64,7 @@ final private[tournament] class PairingSystem( // then up to 6 more groups of cheap pairing proximityPairings(idles.slice(groupSize * 2, groupSize * 8)) } - }.monSuccess(_.tournament.pairing.prep) + }.monSuccess(lila.mon.tournament.pairing.prep) .chronometer .logIfSlow(200, pairingLogger) { preps => s"makePreps ${tournamentUrl(data.tour.id)} ${users.size} users, ${preps.size} preps" diff --git a/modules/tournament/src/main/crud/CrudForm.scala b/modules/tournament/src/main/crud/CrudForm.scala index 48f49acd0e6..8e4d1dee963 100644 --- a/modules/tournament/src/main/crud/CrudForm.scala +++ b/modules/tournament/src/main/crud/CrudForm.scala @@ -12,7 +12,7 @@ final class CrudForm(repo: TournamentRepo, forms: TournamentForm): def apply(tour: Option[Tournament])(using me: Me) = Form( mapping( - "id" -> id[TourId](8, tour.map(_.id))(repo.exists), + "id" -> idWithSyncUniqueCheck[TourId](8, tour.map(_.id))(repo.exists), "homepageHours" -> number(min = 0, max = maxHomepageHours), "image" -> stringIn(imageChoices), "headline" -> text(minLength = 5, maxLength = 30), diff --git a/modules/tutor/src/main/TutorApi.scala b/modules/tutor/src/main/TutorApi.scala index 96a330d1f39..8a8f0fa104b 100644 --- a/modules/tutor/src/main/TutorApi.scala +++ b/modules/tutor/src/main/TutorApi.scala @@ -1,6 +1,6 @@ package lila.tutor -import lila.common.{ Chronometer, LilaScheduler, Uptime } +import lila.common.{ LilaScheduler, Uptime } import lila.core.perf.UserWithPerfs import lila.db.dsl.{ *, given } import lila.memo.CacheApi @@ -58,7 +58,7 @@ final class TutorApi( // we only wait for queue.start // NOT for builder private def buildThenRemoveFromQueue(config: TutorConfig) = - val chrono = Chronometer.start + val chrono = lila.mon.Chronometer.start logger.info(s"Start ${config.user}") for _ <- queue.start(config.user) yield builder(config).foreach: report => diff --git a/modules/tutor/src/main/TutorBuilder.scala b/modules/tutor/src/main/TutorBuilder.scala index 290e764607f..42d6bb2bec9 100644 --- a/modules/tutor/src/main/TutorBuilder.scala +++ b/modules/tutor/src/main/TutorBuilder.scala @@ -13,6 +13,7 @@ import lila.insight.{ } import lila.rating.PerfType import lila.common.LilaFuture +import lila.mon.extensions.* final private class TutorBuilder( colls: TutorColls, @@ -30,7 +31,7 @@ final private class TutorBuilder( def apply(config: TutorConfig): Fu[TutorFullReport] = for user <- userApi.withPerfs(config.user).orFail(s"No such user ${config.user}") - chrono = lila.common.Chronometer.lapTry(produce(user)(using config)) + chrono = lila.mon.Chronometer.lapTry(produce(user)(using config)) _ = chrono.mon { r => lila.mon.tutor.buildFull(r.isSuccess) } lap <- chrono.lap report <- lap.result.toFuture @@ -40,25 +41,25 @@ final private class TutorBuilder( yield report private def produce(user: UserWithPerfs)(using config: TutorConfig): Fu[TutorFullReport] = for - _ <- insightApi.indexAll(user, force = false).monSuccess(_.tutor.buildSegment("insight-index")) + _ <- insightApi.indexAll(user, force = false).monSuccess(lila.mon.tutor.buildSegment("insight-index")) perfStats <- perfStatsApi( user, config.period, eligiblePerfKeysOf(user).map(PerfType(_)), fishnet.maxGamesToConsider ) - .monSuccess(_.tutor.buildSegment("perf-stats")) + .monSuccess(lila.mon.tutor.buildSegment("perf-stats")) peerMatches <- findPeerMatches(perfStats.view.mapValues(_.stats.rating).toMap) - .monSuccess(_.tutor.buildSegment("peer-matches")) + .monSuccess(lila.mon.tutor.buildSegment("peer-matches")) tutorUsers = perfStats .map { (pt, stats) => TutorPlayer(user, pt, stats.stats, peerMatches.find(_.perf == pt)) } .toList .sortBy(-_.perfStats.totalNbGames) - _ <- fishnet.ensureSomeAnalysis(perfStats).monSuccess(_.tutor.buildSegment("fishnet-analysis")) + _ <- fishnet.ensureSomeAnalysis(perfStats).monSuccess(lila.mon.tutor.buildSegment("fishnet-analysis")) _ <- LilaFuture.sleep(1.second) // ensure fishnet analyses are indexed before asking questions perfs <- tutorUsers.toNel .so(TutorPerfReport.compute) - .monSuccess(_.tutor.buildSegment("perf-reports")) + .monSuccess(lila.mon.tutor.buildSegment("perf-reports")) yield TutorFullReport(config, nowInstant, perfs) private[tutor] def eligiblePerfKeysOf(user: UserWithPerfs): List[PerfKey] = @@ -126,7 +127,7 @@ private object TutorBuilder: ec: Executor ): Fu[AnswerMine[Dim]] = insightApi .ask(question.timeFilter(config).filter(Filter(user.perfType)), user.user, withPovs = false) - .monSuccess(_.tutor.askMine(question.monKey, user.perfType.key)) + .monSuccess(lila.mon.tutor.askMine(question.monKey, user.perfType.key)) .map(AnswerMine.apply) def answerPeer[Dim](question: Question[Dim], user: TutorPlayer, nbGames: Max = peerNbGames)(using @@ -134,7 +135,7 @@ private object TutorBuilder: ec: Executor ): Fu[AnswerPeer[Dim]] = insightApi .askPeers(question.filter(Filter(user.perfType)), user.perfStats.rating, nbGames = nbGames) - .monSuccess(_.tutor.askPeer(question.monKey, user.perfType.key)) + .monSuccess(lila.mon.tutor.askPeer(question.monKey, user.perfType.key)) .map(AnswerPeer.apply) def answerManyPerfs[Dim]( @@ -151,7 +152,7 @@ private object TutorBuilder: tutorUsers.head.user, withPovs = false ) - .monSuccess(_.tutor.askMine(question.monKey, "all")) + .monSuccess(lila.mon.tutor.askMine(question.monKey, "all")) .map(AnswerMine.apply) peerByPerf <- tutorUsers.toList.map(answerPeer(question, _)).parallel peer = AnswerPeer(InsightAnswer(question, peerByPerf.flatMap(_.answer.clusters), Nil)) diff --git a/modules/tutor/src/main/TutorCustomInsight.scala b/modules/tutor/src/main/TutorCustomInsight.scala index 6341731f7cb..505f3572567 100644 --- a/modules/tutor/src/main/TutorCustomInsight.scala +++ b/modules/tutor/src/main/TutorCustomInsight.scala @@ -4,6 +4,7 @@ import lila.db.AggregationPipeline import lila.db.dsl.* import lila.insight.* import lila.rating.PerfType +import lila.mon.extensions.* final private class TutorCustomInsight[A: TutorNumber]( users: NonEmptyList[TutorPlayer], @@ -23,7 +24,7 @@ final private class TutorCustomInsight[A: TutorNumber]( InsightStorage.selectUserId(users.head.user.id) ++ InsightStorage.gameMatcher(question.timeFilter(config).filters) .map { docs => TutorBuilder.AnswerMine(Answer(question, clusterParser(docs), Nil)) } - .monSuccess(_.tutor.askMine(monitoringKey, "all")) + .monSuccess(lila.mon.tutor.askMine(monitoringKey, "all")) peerDocs <- users.toList.map { u => u.peerMatch.flatMap(peerMatch).map(_.peer) match case Some(cached) => @@ -35,7 +36,7 @@ final private class TutorCustomInsight[A: TutorNumber]( insightColl .aggregateList(maxDocs = Int.MaxValue)(_ => aggregatePeer(peerSelect)) .map(clusterParser) - .monSuccess(_.tutor.askPeer(monitoringKey, u.perfType.key)) + .monSuccess(lila.mon.tutor.askPeer(monitoringKey, u.perfType.key)) }.parallel peer = TutorBuilder.AnswerPeer(Answer(question, peerDocs.flatten, Nil)) yield TutorBuilder.Answers(mine, peer) diff --git a/modules/tutor/src/main/TutorFlagging.scala b/modules/tutor/src/main/TutorFlagging.scala index b0c3074a1bd..ca1be7d3053 100644 --- a/modules/tutor/src/main/TutorFlagging.scala +++ b/modules/tutor/src/main/TutorFlagging.scala @@ -4,6 +4,7 @@ import alleycats.Zero import lila.insight.* import lila.rating.PerfType +import lila.mon.extensions.* private case class TutorFlagging( win: TutorBothOption[GoodPercent], @@ -28,10 +29,10 @@ private object TutorFlagging: for mine <- insightApi .ask(question.timeFilter(config), user.user, withPovs = false) - .monSuccess(_.tutor.askMine(question.monKey, user.perfType.key)) + .monSuccess(lila.mon.tutor.askMine(question.monKey, user.perfType.key)) peer <- insightApi .askPeers(question, user.perfStats.rating, nbGames = maxPeerGames) - .monSuccess(_.tutor.askPeer(question.monKey, user.perfType.key)) + .monSuccess(lila.mon.tutor.askPeer(question.monKey, user.perfType.key)) yield def valueCountOf(answer: Answer[Result], result: Result): Option[ValueCount[GoodPercent]] = answer.clusters.collectFirst: diff --git a/modules/tutor/src/main/TutorQueue.scala b/modules/tutor/src/main/TutorQueue.scala index 8261612ee0f..c3d0c681eba 100644 --- a/modules/tutor/src/main/TutorQueue.scala +++ b/modules/tutor/src/main/TutorQueue.scala @@ -24,7 +24,7 @@ final private class TutorQueue( maxSize = Max(64), timeout = 5.seconds, "tutorQueue", - lila.log.asyncActorMonitor.full + lila.mon.asyncActorMonitor.full ) private val durationCache = cacheApi.unit[FiniteDuration]: diff --git a/modules/ublog/src/main/UblogAutomod.scala b/modules/ublog/src/main/UblogAutomod.scala index 91b27f01cef..e9c309dd28e 100644 --- a/modules/ublog/src/main/UblogAutomod.scala +++ b/modules/ublog/src/main/UblogAutomod.scala @@ -8,6 +8,7 @@ import lila.core.ublog.Quality import lila.memo.SettingStore import lila.memo.SettingStore.Text.given import lila.report.Automod +import lila.mon.extensions.* // see also: // file://./../../../../bin/ublog-automod.mjs @@ -85,7 +86,7 @@ private final class UblogAutomod( lila.mon.ublog.automod.quality(res.quality.toString).increment() lila.mon.ublog.automod.flagged(res.flagged.isDefined).increment() res.copy(hash = Algo.sha256(userText).hex.take(12).some).some // match bin/ublog-automod.mjs hash - .monSuccess(_.ublog.automod.request) + .monSuccess(lila.mon.ublog.automod.request) assessImages .zip(assessText) .map: (imgs, text) => diff --git a/modules/user/src/main/Cached.scala b/modules/user/src/main/Cached.scala index 8c298fc878e..942789af37c 100644 --- a/modules/user/src/main/Cached.scala +++ b/modules/user/src/main/Cached.scala @@ -1,6 +1,7 @@ package lila.user import reactivemongo.api.bson.* +import scalalib.paginator.Paginator import lila.core.perf.UserWithPerfs import lila.core.user.LightPerf @@ -9,7 +10,7 @@ import lila.core.rating.UserRankMap import lila.db.dsl.* import lila.memo.CacheApi.* import lila.rating.{ PerfType, UserPerfs } -import scalalib.paginator.Paginator +import lila.mon.extensions.* final class Cached( userRepo: UserRepo, @@ -25,7 +26,7 @@ final class Cached( val top10 = cacheApi.unit[UserPerfs.Leaderboards]: _.refreshAfterWrite(2.minutes).buildAsyncTimeout(2.minutes): _ => - rankingApi.fetchLeaderboard(10).monSuccess(_.user.leaderboardCompute) + rankingApi.fetchLeaderboard(10).monSuccess(lila.mon.user.leaderboardCompute) private val topPerfFirstPage = mongoCache[PerfKey, Seq[LightPerf]]( PerfType.leaderboardable.size, diff --git a/modules/user/src/main/RankingApi.scala b/modules/user/src/main/RankingApi.scala index 3c7699c3000..81244a5ed70 100644 --- a/modules/user/src/main/RankingApi.scala +++ b/modules/user/src/main/RankingApi.scala @@ -4,6 +4,7 @@ import reactivemongo.api.bson.* import scala.util.Success import chess.{ IntRating, ByColor } import chess.rating.IntRatingDiff +import scalalib.paginator.Paginator import lila.core.perf.{ PerfId, UserWithPerfs } import lila.core.user.LightPerf @@ -11,7 +12,7 @@ import lila.db.dsl.{ *, given } import lila.memo.CacheApi.* import lila.rating.GlickoExt.rankable import lila.rating.PerfType -import scalalib.paginator.Paginator +import lila.mon.extensions.* final class RankingApi( c: lila.db.AsyncCollFailingSilently, @@ -128,7 +129,7 @@ final class RankingApi( computeAggregate(perf).chronometer .logIfSlow(500, logger.branch("ranking"))(_ => s"slow weeklyStableRanking for $perf") .result - .monSuccess(_.user.weeklyStableRanking(perf)) + .monSuccess(lila.mon.user.weeklyStableRanking(perf)) .dmap(perf -> _) .map(_.toMap) .chronometer