diff --git a/app/Env.scala b/app/Env.scala index 2a30a4fd3b..8043be4927 100644 --- a/app/Env.scala +++ b/app/Env.scala @@ -20,7 +20,7 @@ final class Env(config: Config, system: ActorSystem, isServer: Boolean) { )), name = RouterName) loginfo("[boot] Preloading modules") - (Env.site, Env.game, Env.setup, Env.game, Env.gameSearch, Env.team, + (Env.site, Env.game, Env.ai, Env.setup, Env.round, Env.gameSearch, Env.team, Env.teamSearch, Env.forumSearch, Env.message, Env.socket) if (Env.ai.isServer) println("Running as AI server") diff --git a/app/controllers/Monitor.scala b/app/controllers/Monitor.scala index 6ac6de33fd..a6bcb69ef6 100644 --- a/app/controllers/Monitor.scala +++ b/app/controllers/Monitor.scala @@ -1,44 +1,36 @@ package controllers import play.api.mvc._ -import play.api.libs.Comet import play.api.libs.json._ import akka.pattern.ask import lila.app._ +import lila.monitor.actorApi._ import lila.socket.actorApi.GetNbMembers -// import lila.monitor._ import makeTimeout.short object Monitor extends LilaController { - // private def reporting = env.monitor.reporting - // private def usernameMemo = env.user.usernameMemo - // private def userRepo = env.user.userRepo - // private def gameRepo = env.game.gameRepo + private def env = Env.monitor - def index = TODO - // Action { - // Ok(views.html.monitor.monitor()) - // } + def index = Action { + Ok(views.html.monitor.monitor()) + } - // def websocket = WebSocket.async[JsValue] { implicit req ⇒ - // env.monitor.socket.join(uidOption = get("sri", req)) - // } + def websocket = WebSocket.async[JsValue] { implicit req ⇒ + get("sri", req) zmap env.socketHandler.join + } - // def status = Open { implicit ctx ⇒ - // Async { - // import lila.common.Futuristic.ioToFuture - // (~get("key") match { - // case "elo" ⇒ - // userRepo.idsAverageElo(usernameMemo.keys).toFuture zip - // gameRepo.recentAverageElo(5).toFuture map { - // case (users, (rated, casual)) ⇒ List(users, rated, casual) mkString " " - // } - // case "moves" ⇒ (reporting ? GetNbMoves).mapTo[Int] - // case "players" ⇒ (reporting ? GetNbMembers).mapTo[Int] map { "%d %d".format(_, usernameMemo.preciseCount) } - // case _ ⇒ (reporting ? GetStatus).mapTo[String] - // }) map { x ⇒ Ok(x.toString) } - // } - // } + def status = Open { implicit ctx ⇒ + (~get("key") match { + case "elo" ⇒ + lila.user.UserRepo.idsAverageElo(Env.user.usernameMemo.keys) zip + lila.game.GameRepo.recentAverageElo(5) map { + case (users, (rated, casual)) ⇒ List(users, rated, casual) mkString " " + } + case "moves" ⇒ (env.reporting ? GetNbMoves).mapTo[Int] + case "players" ⇒ (env.reporting ? GetNbMembers).mapTo[Int] map { "%d %d".format(_, Env.user.usernameMemo.count) } + case _ ⇒ (env.reporting ? GetStatus).mapTo[String] + }) map { x ⇒ Ok(x.toString) } + } } diff --git a/old/views/monitor/layout.scala.html b/app/views/monitor/layout.scala.html similarity index 100% rename from old/views/monitor/layout.scala.html rename to app/views/monitor/layout.scala.html diff --git a/old/views/monitor/monitor.scala.html b/app/views/monitor/monitor.scala.html similarity index 100% rename from old/views/monitor/monitor.scala.html rename to app/views/monitor/monitor.scala.html diff --git a/conf/routes b/conf/routes index 930a189d2d..6d2b5440f1 100644 --- a/conf/routes +++ b/conf/routes @@ -177,10 +177,8 @@ POST /import controllers.Importer.sendGame # Monitor GET /monitor controllers.Monitor.index -# GET /monitor/socket controllers.Monitor.websocket -# GET /monitor/players controllers.Monitor.nbPlayers -# GET /monitor/mps controllers.Monitor.nbMoves -# GET /monitor/status controllers.Monitor.status +GET /monitor/socket controllers.Monitor.websocket +GET /monitor/status controllers.Monitor.status # Misc POST /cli controllers.Cli.command diff --git a/modules/ai/src/main/Env.scala b/modules/ai/src/main/Env.scala index 36aa9e8414..1b75e8ce37 100644 --- a/modules/ai/src/main/Env.scala +++ b/modules/ai/src/main/Env.scala @@ -2,7 +2,7 @@ package lila.ai import lila.common.PimpedConfig._ -import akka.actor.{ ActorRef, ActorSystem } +import akka.actor._ import com.typesafe.config.Config final class Env( @@ -17,6 +17,7 @@ final class Env( val StockfishExecPath = config getString "stockfish.exec_path" val StockfishPlayUrl = config getString "stockfish.play.url" val StockfishAnalyseUrl = config getString "stockfish.analyse.url" + val ActorName = config getString "actor.name" } import settings._ @@ -32,6 +33,13 @@ final class Env( def isServer = IsServer + // api actor + system.actorOf(Props(new Actor { + def receive = { + case lila.hub.actorApi.ai.Ping ⇒ sender ! clientPing + } + }), name = ActorName) + { import scala.concurrent.duration._ @@ -39,7 +47,7 @@ final class Env( clientDiagnose } - scheduler.once(5 millis) { clientDiagnose } + scheduler.once(10 millis) { clientDiagnose } } private lazy val stockfishAi = new stockfish.Ai( diff --git a/modules/game/src/main/Env.scala b/modules/game/src/main/Env.scala index 5f071ee888..6c85399d6e 100644 --- a/modules/game/src/main/Env.scala +++ b/modules/game/src/main/Env.scala @@ -3,6 +3,7 @@ package lila.game import com.typesafe.config.Config import lila.common.PimpedConfig._ import akka.actor._ +import akka.pattern.pipe final class Env( config: Config, @@ -21,6 +22,7 @@ final class Env( val CollectionPgn = config getString "collection.pgn" val JsPathRaw = config getString "js_path.raw" val JsPathCompiled = config getString "js_path.compiled" + val ActorName = config getString "actor.name" } import settings._ @@ -50,6 +52,13 @@ final class Env( // load captcher actor system.actorOf(Props(new Captcher), name = CaptcherName) + // api actor + system.actorOf(Props(new Actor { + def receive = { + case lila.hub.actorApi.game.Count ⇒ cached.nbGames pipeTo sender + } + }), name = ActorName) + { import scala.concurrent.duration._ diff --git a/modules/game/src/main/GameRepo.scala b/modules/game/src/main/GameRepo.scala index 397cb2b2c8..12736cfc86 100644 --- a/modules/game/src/main/GameRepo.scala +++ b/modules/game/src/main/GameRepo.scala @@ -7,8 +7,11 @@ import lila.user.User import lila.db.api._ import lila.db.Implicits._ import tube.gameTube +import lila.common.PimpedJson._ import play.api.libs.json._ +import play.modules.reactivemongo.json.ImplicitBSONHandlers.JsObjectWriter +import play.modules.reactivemongo.json.BSONFormats.toJSON import org.joda.time.DateTime import org.scala_tools.time.Imports._ @@ -19,8 +22,7 @@ object GameRepo { type ID = String - import Game._ - import Game.ShortFields._ + import Game._, ShortFields._ def game(gameId: ID): Fu[Option[Game]] = $find byId gameId @@ -140,35 +142,39 @@ object GameRepo { $count(Json.obj(createdAt -> ($gte(from) ++ $lt(to)))) } - // def recentAverageElo(minutes: Int): Fu[(Int, Int)] = io { - // val result = collection.mapReduce( - // mapFunction = """function() { - // emit(!!this.ra, this.p); - // }""", - // reduceFunction = """function(rated, values) { - // var sum = 0, nb = 0; - // values.forEach(function(game) { - // if(typeof game[0] != "undefined") { - // game.forEach(function(player) { - // if(player.elo) { - // sum += player.elo; - // ++nb; - // } - // }); - // } - // }); - // return nb == 0 ? nb : Math.round(sum / nb); - // }""", - // output = MapReduceInlineOutput, - // query = Some { - // (createdAt $gte (DateTime.now - minutes.minutes)) ++ ("p.elo" $exists true) - // } - // ) - // (for { - // ratedRow ← result.hasNext option result.next - // rated ← ratedRow.getAs[Double]("value") - // casualRow ← result.hasNext option result.next - // casual ← casualRow.getAs[Double]("value") - // } yield rated.toInt -> casual.toInt) | (0, 0) - // } + def recentAverageElo(minutes: Int): Fu[(Int, Int)] = { + val command = MapReduce( + collectionName = gameTube.coll.name, + mapFunction = """function() { + emit(!!this.ra, this.p); + }""", + reduceFunction = """function(rated, values) { + var sum = 0, nb = 0; + values.forEach(function(game) { + if(typeof game[0] != "undefined") { + game.forEach(function(player) { + if(player.elo) { + sum += player.elo; + ++nb; + } + }); + } + }); + return nb == 0 ? nb : Math.round(sum / nb); + }""", + query = Some(JsObjectWriter write Json.obj( + createdAt -> $gte(DateTime.now - minutes.minutes), + "p.elo" -> $exists(true) + )) + ) + gameTube.coll.db.command(command) map { res ⇒ + toJSON(res).pp.arr("results").pp.flatMap(_.apply(0) int "value") + } map (~_) inject (0, 0) + // (for { + // ratedRow ← result.hasNext option result.next + // rated ← ratedRow.getAs[Double]("value") + // casualRow ← result.hasNext option result.next + // casual ← casualRow.getAs[Double]("value") + // } yield rated.toInt -> casual.toInt) | (0, 0) + } } diff --git a/modules/hub/src/main/Env.scala b/modules/hub/src/main/Env.scala index 373dc485e1..459d8e0e9c 100644 --- a/modules/hub/src/main/Env.scala +++ b/modules/hub/src/main/Env.scala @@ -10,6 +10,7 @@ final class Env(config: Config, system: ActorSystem) { private val SocketHubTimeout = config duration "socket.hub.timeout" object actor { + val game = actorFor("game.actor") val gameIndexer = actorFor("game.indexer") val renderer = actorFor("renderer") val captcher = actorFor("captcher") @@ -29,6 +30,7 @@ final class Env(config: Config, system: ActorSystem) { val lobby = socketFor("lobby") val monitor = socketFor("monitor") val site = socketFor("site") + val round = socketFor("round") val hub = system.actorOf(Props(new Broadcast(List( socket.lobby, socket.site ))(makeTimeout(SocketHubTimeout))), name = SocketHubName) diff --git a/modules/hub/src/main/actorApi.scala b/modules/hub/src/main/actorApi.scala index cb30526b32..7066043481 100644 --- a/modules/hub/src/main/actorApi.scala +++ b/modules/hub/src/main/actorApi.scala @@ -28,6 +28,10 @@ package lobby { case class Censor(username: String) } +package game { + case object Count +} + package message { case class LichessThread(to: String, subject: String, message: String) } @@ -44,6 +48,7 @@ package forum { } package ai { + case object Ping case class Analyse(pgn: String, initialFen: Option[String]) } diff --git a/modules/monitor/src/main/Env.scala b/modules/monitor/src/main/Env.scala index e4faf5edc2..994abaa767 100644 --- a/modules/monitor/src/main/Env.scala +++ b/modules/monitor/src/main/Env.scala @@ -22,10 +22,11 @@ final class Env( lazy val socketHandler = new SocketHandler(socket) - lazy val reporting = system.actorOf( + val reporting = system.actorOf( Props(new Reporting( rpsProvider = rpsProvider, mpsProvider = mpsProvider, + socket = socket, db = db, hub = hub )), name = ActorName) @@ -33,8 +34,8 @@ final class Env( { import scala.concurrent.duration._ - scheduler.message(5 seconds) { - reporting -> actorApi.Update + scheduler.message(1 seconds) { + reporting -> lila.hub.actorApi.monitor.Update } } diff --git a/modules/monitor/src/main/Reporting.scala b/modules/monitor/src/main/Reporting.scala index 894990ea49..b68e140761 100644 --- a/modules/monitor/src/main/Reporting.scala +++ b/modules/monitor/src/main/Reporting.scala @@ -1,9 +1,8 @@ package lila.monitor import actorApi._ -import lila.socket.actorApi.GetNbMembers +import lila.socket.actorApi.{ GetNbMembers, GetNbHubs } import lila.hub.actorApi.monitor._ -// import round.GetNbHubs import akka.actor._ import akka.pattern.{ ask, pipe } @@ -16,6 +15,7 @@ import java.lang.management.ManagementFactory private[monitor] final class Reporting( rpsProvider: RpsProvider, mpsProvider: RpsProvider, + socket: ActorRef, db: lila.db.Env, hub: lila.hub.Env) extends Actor { @@ -61,37 +61,35 @@ private[monitor] final class Reporting( case GetMonitorData ⇒ sender ! monitorData - // TODO - // case Update ⇒ { - // val before = nowMillis - // List( - // (env.site.hub ? GetNbMembers).mapTo[Int], - // (hub.actor.lobby ? GetNbMembers).mapTo[Int], - // (env.round.hubMaster ? GetNbHubs).mapTo[Int], - // (env.round.hubMaster ? GetNbMembers).mapTo[Int] - // ).sequence onComplete { - // case Failure(e) ⇒ logwarn("[reporting] " + e.getMessage) - // case Success(List( - // siteMembers, - // lobbyMembers, - // gameHubs, - // gameMembers)) ⇒ { - // latency = (nowMillis - before).toInt - // site = SiteSocket(siteMembers) - // lobby = LobbySocket(lobbyMembers) - // game = GameSocket(gameHubs, gameMembers) - // mongoStatus = MongoStatus(mongodb)(mongoStatus) - // nbGames = env.game.cached.nbGames - // loadAvg = osStats.getSystemLoadAverage.toFloat - // nbThreads = threadStats.getThreadCount - // memory = memoryStats.getHeapMemoryUsage.getUsed / 1024 / 1024 - // rps = rpsProvider.rps - // mps = mpsProvider.rps - // cpu = ((cpuStats.getCpuUsage() * 1000).round / 10.0).toInt - // clientAi = env.ai.clientPing - // hub ! MonitorData(monitorData) - // } - // } + case Update ⇒ { + val before = nowMillis + MongoStatus(db.db)(mongoStatus) zip + (hub.actor.ai ? lila.hub.actorApi.ai.Ping).mapTo[Option[Int]] zip + List((hub.socket.site ? GetNbMembers), + (hub.socket.lobby ? GetNbMembers), + (hub.socket.round ? GetNbHubs), + (hub.socket.round ? GetNbMembers), + (hub.actor.game ? lila.hub.actorApi.game.Count) + ).map(_.mapTo[Int]).sequence onComplete { + case Failure(e) ⇒ logwarn("[reporting] " + e.getMessage) + case Success(((mongoS, aiPing), List(siteMembers, lobbyMembers, gameHubs, gameMembers, games))) ⇒ { + latency = (nowMillis - before).toInt + site = SiteSocket(siteMembers) + lobby = LobbySocket(lobbyMembers) + game = GameSocket(gameHubs, gameMembers) + mongoStatus = mongoS + nbGames = games + loadAvg = osStats.getSystemLoadAverage.toFloat + nbThreads = threadStats.getThreadCount + memory = memoryStats.getHeapMemoryUsage.getUsed / 1024 / 1024 + rps = rpsProvider.rps + mps = mpsProvider.rps + cpu = ((cpuStats.getCpuUsage() * 1000).round / 10.0).toInt + clientAi = aiPing + socket ! MonitorData(monitorData) + } + } + } } private def display { diff --git a/modules/monitor/src/main/SocketHandler.scala b/modules/monitor/src/main/SocketHandler.scala index fd70d45c6d..10e4e6c4d6 100644 --- a/modules/monitor/src/main/SocketHandler.scala +++ b/modules/monitor/src/main/SocketHandler.scala @@ -11,7 +11,7 @@ private[monitor] final class SocketHandler(socket: ActorRef) { def join(uid: String): Fu[JsSocketHandler] = { def controller: Handler.Controller = { - case _ => + case _ ⇒ } Handler(socket, uid, Join(uid)) { diff --git a/modules/monitor/src/main/actorApi.scala b/modules/monitor/src/main/actorApi.scala index b45cb9caf1..19d7ca2656 100644 --- a/modules/monitor/src/main/actorApi.scala +++ b/modules/monitor/src/main/actorApi.scala @@ -12,7 +12,5 @@ case object GetNbMoves case object GetStatus case object GetMonitorData -case object Update - case class Join(uid: String) case class MonitorData(data: List[String]) diff --git a/modules/round/src/main/Env.scala b/modules/round/src/main/Env.scala index 573f27f013..5b9e3d3d71 100644 --- a/modules/round/src/main/Env.scala +++ b/modules/round/src/main/Env.scala @@ -35,7 +35,7 @@ final class Env( lazy val history = () ⇒ new History(ttl = MessageTtl) - lazy val socketHub = system.actorOf(Props(new SocketHub( + val socketHub = system.actorOf(Props(new SocketHub( makeHistory = history, uidTimeout = UidTimeout, socketTimeout = SocketTimeout, diff --git a/modules/round/src/main/SocketHub.scala b/modules/round/src/main/SocketHub.scala index 9375c58111..7b9fd58ecb 100644 --- a/modules/round/src/main/SocketHub.scala +++ b/modules/round/src/main/SocketHub.scala @@ -30,6 +30,8 @@ private[round] final class SocketHub( case msg @ GameEvents(gameId, events) ⇒ sockets get gameId foreach (_ forward msg) + case GetNbHubs ⇒ sender ! sockets.size + case GetSocket(id) ⇒ sender ! { (sockets get id) | { mkSocket(id) ~ { h ⇒ sockets = sockets + (id -> h) } diff --git a/modules/socket/src/main/actorApi.scala b/modules/socket/src/main/actorApi.scala index dd364016b1..3f1b468237 100644 --- a/modules/socket/src/main/actorApi.scala +++ b/modules/socket/src/main/actorApi.scala @@ -9,6 +9,7 @@ case class Connected[M <: SocketMember]( case object Close case object GetNbMembers case class NbMembers(nb: Int) +case object GetNbHubs case class Ping(uid: String) case class PingVersion(uid: String, version: Int) case object Broom diff --git a/modules/user/src/main/UserRepo.scala b/modules/user/src/main/UserRepo.scala index 56dad4ab26..1053aa3f30 100644 --- a/modules/user/src/main/UserRepo.scala +++ b/modules/user/src/main/UserRepo.scala @@ -128,7 +128,7 @@ object UserRepo { def idsAverageElo(ids: Iterable[String]): Fu[Int] = { val command = MapReduce( - collectionName = tube.userTube.coll.name, + collectionName = userTube.coll.name, mapFunction = """function() { emit("e", this.elo); }""", reduceFunction = """function(key, values) { var sum = 0; @@ -139,14 +139,14 @@ object UserRepo { JsObjectWriter write Json.obj("_id" -> $in(ids map normalize)) } ) - tube.userTube.coll.db.command(command) map { res ⇒ + userTube.coll.db.command(command) map { res ⇒ toJSON(res).arr("results").flatMap(_.apply(0) int "value") } map (~_) } def idsSumToints(ids: Iterable[String]): Fu[Int] = { val command = MapReduce( - collectionName = tube.userTube.coll.name, + collectionName = userTube.coll.name, mapFunction = """function() { emit("e", this.toints); }""", reduceFunction = """function(key, values) { var sum = 0; @@ -157,7 +157,7 @@ object UserRepo { JsObjectWriter write Json.obj("_id" -> $in(ids map normalize)) } ) - tube.userTube.coll.db.command(command) map { res ⇒ + userTube.coll.db.command(command) map { res ⇒ toJSON(res).arr("results").flatMap(_.apply(0) int "value") } map (~_) } diff --git a/old/tournament/actorApi.scala b/old/tournament/actorApi.scala index 55079c0124..f721977a21 100644 --- a/old/tournament/actorApi.scala +++ b/old/tournament/actorApi.scala @@ -37,7 +37,6 @@ case object Start case object Reload case object ReloadPage case object HubTimeout -case object GetNbHubs case class StartGame(game: DbGame) case class Joining(userId: String)