mirror of
https://github.com/Eyre-S/Coeur-Morny-Cono.git
synced 2024-11-22 11:14:55 +08:00
add event statistics, fix CronTask
- add for EventEnv a timeStartup field - cha EventListener and EventListenerManager - add for EventListener a method executeFilter used to manager if an event should be run. This replaced the condition statement inside the EventListenerManager - add for EventListener a method atEventPost, this will run at current event listener is on complete - add for MornyConfig a reportZone field - can be set by `--report-zone` - used for controlling Morny Report daemon uses the zoned time to send report. default is system default time zone. - add for MornyReport new EventStatistics and DailyReportTask - add for MornyInformation command new subcommand `event` to manually show MornyReport.EventStatistics info. - add WatchDog and MornyCoeur.watchDog, used for checking if the machine is in sleep mode and notify the MornyCoeur.tasks to avoid timing problem - fix CronTask frequency got initialize problem - add slf4j-nop for project
This commit is contained in:
parent
3d44972233
commit
2c30b5ec09
13
build.gradle
13
build.gradle
@ -83,20 +83,25 @@ dependencies {
|
|||||||
|
|
||||||
implementation group: 'cc.sukazyo', name: 'messiva', version: lib_messiva_v
|
implementation group: 'cc.sukazyo', name: 'messiva', version: lib_messiva_v
|
||||||
implementation group: 'cc.sukazyo', name: 'resource-tools', version: lib_resourcetools_v
|
implementation group: 'cc.sukazyo', name: 'resource-tools', version: lib_resourcetools_v
|
||||||
testImplementation group: 'cc.sukazyo', name: 'resource-tools', version: lib_resourcetools_v
|
|
||||||
|
|
||||||
implementation group: 'com.github.pengrad', name: 'java-telegram-bot-api', version: lib_javatelegramapi_v
|
implementation group: 'com.github.pengrad', name: 'java-telegram-bot-api', version: lib_javatelegramapi_v
|
||||||
implementation group: 'com.softwaremill.sttp.client3', name: scala('core'), version: lib_sttp_v
|
implementation group: 'com.softwaremill.sttp.client3', name: scala('core'), version: lib_sttp_v
|
||||||
implementation group: 'com.softwaremill.sttp.client3', name: scala('okhttp-backend'), version: lib_sttp_v
|
implementation group: 'com.softwaremill.sttp.client3', name: scala('okhttp-backend'), version: lib_sttp_v
|
||||||
implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: lib_okhttp_v
|
runtimeOnly group: 'com.squareup.okhttp3', name: 'okhttp', version: lib_okhttp_v
|
||||||
implementation group: 'com.google.code.gson', name: 'gson', version: lib_gson_v
|
implementation group: 'com.google.code.gson', name: 'gson', version: lib_gson_v
|
||||||
implementation group: 'com.cronutils', name: 'cron-utils', version: lib_cron_utils_v
|
implementation group: 'com.cronutils', name: 'cron-utils', version: lib_cron_utils_v
|
||||||
|
|
||||||
|
// used for disable slf4j
|
||||||
|
// due to the slf4j api have been used in the following libraries:
|
||||||
|
// - cron-utils
|
||||||
|
runtimeOnly group: 'org.slf4j', name: 'slf4j-nop', version: lib_slf4j_v
|
||||||
|
testRuntimeOnly group: 'org.slf4j', name: 'slf4j-nop', version: lib_slf4j_v
|
||||||
|
|
||||||
|
testImplementation group: 'cc.sukazyo', name: 'resource-tools', version: lib_resourcetools_v
|
||||||
testImplementation group: 'org.scalatest', name: scala('scalatest'), version: lib_scalatest_v
|
testImplementation group: 'org.scalatest', name: scala('scalatest'), version: lib_scalatest_v
|
||||||
testImplementation group: 'org.scalatest', name: scala('scalatest-freespec'), version: lib_scalatest_v
|
testImplementation group: 'org.scalatest', name: scala('scalatest-freespec'), version: lib_scalatest_v
|
||||||
testRuntimeOnly group: 'org.scala-lang.modules', name: scala('scala-xml'), version: lib_scalamodule_xml_v
|
testRuntimeOnly group: 'org.scala-lang.modules', name: scala('scala-xml'), version: lib_scalamodule_xml_v
|
||||||
|
// for generating HTML report: required by gradle-scalatest plugin
|
||||||
// for generating HTML report // required by gradle-scalatest plugin
|
|
||||||
testRuntimeOnly group: 'com.vladsch.flexmark', name: 'flexmark-all', version: '0.64.6'
|
testRuntimeOnly group: 'com.vladsch.flexmark', name: 'flexmark-all', version: '0.64.6'
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ MORNY_ARCHIVE_NAME = morny-coeur
|
|||||||
MORNY_CODE_STORE = https://github.com/Eyre-S/Coeur-Morny-Cono
|
MORNY_CODE_STORE = https://github.com/Eyre-S/Coeur-Morny-Cono
|
||||||
MORNY_COMMIT_PATH = https://github.com/Eyre-S/Coeur-Morny-Cono/commit/%s
|
MORNY_COMMIT_PATH = https://github.com/Eyre-S/Coeur-Morny-Cono/commit/%s
|
||||||
|
|
||||||
VERSION = 1.3.0-dev2
|
VERSION = 1.3.0-dev3
|
||||||
|
|
||||||
USE_DELTA = false
|
USE_DELTA = false
|
||||||
VERSION_DELTA =
|
VERSION_DELTA =
|
||||||
@ -19,6 +19,7 @@ lib_scalamodule_xml_v = 2.2.0
|
|||||||
|
|
||||||
lib_messiva_v = 0.2.0
|
lib_messiva_v = 0.2.0
|
||||||
lib_resourcetools_v = 0.2.2
|
lib_resourcetools_v = 0.2.2
|
||||||
|
lib_slf4j_v = 2.0.9
|
||||||
|
|
||||||
lib_javatelegramapi_v = 6.2.0
|
lib_javatelegramapi_v = 6.2.0
|
||||||
|
|
||||||
|
@ -9,9 +9,11 @@ import cc.sukazyo.cono.morny.bot.event.{MornyEventListeners, MornyOnInlineQuery,
|
|||||||
import cc.sukazyo.cono.morny.bot.query.MornyQueries
|
import cc.sukazyo.cono.morny.bot.query.MornyQueries
|
||||||
import cc.sukazyo.cono.morny.util.schedule.Scheduler
|
import cc.sukazyo.cono.morny.util.schedule.Scheduler
|
||||||
import cc.sukazyo.cono.morny.util.EpochDateTime.EpochMillis
|
import cc.sukazyo.cono.morny.util.EpochDateTime.EpochMillis
|
||||||
|
import cc.sukazyo.cono.morny.util.time.WatchDog
|
||||||
import com.pengrad.telegrambot.TelegramBot
|
import com.pengrad.telegrambot.TelegramBot
|
||||||
import com.pengrad.telegrambot.request.GetMe
|
import com.pengrad.telegrambot.request.GetMe
|
||||||
|
|
||||||
|
import scala.annotation.unused
|
||||||
import scala.util.boundary
|
import scala.util.boundary
|
||||||
import scala.util.boundary.break
|
import scala.util.boundary.break
|
||||||
|
|
||||||
@ -64,10 +66,10 @@ class MornyCoeur (using val config: MornyConfig) {
|
|||||||
/** [[account]]'s telegram user id */
|
/** [[account]]'s telegram user id */
|
||||||
val userid: Long = __loginResult.userid
|
val userid: Long = __loginResult.userid
|
||||||
|
|
||||||
/** current Morny's [[MornyTrusted]] instance */
|
|
||||||
val trusted: MornyTrusted = MornyTrusted()
|
|
||||||
/** Morny's task [[Scheduler]] */
|
/** Morny's task [[Scheduler]] */
|
||||||
val tasks: Scheduler = Scheduler()
|
val tasks: Scheduler = Scheduler()
|
||||||
|
/** current Morny's [[MornyTrusted]] instance */
|
||||||
|
val trusted: MornyTrusted = MornyTrusted()
|
||||||
|
|
||||||
val daemons: MornyDaemons = MornyDaemons()
|
val daemons: MornyDaemons = MornyDaemons()
|
||||||
//noinspection ScalaWeakerAccess
|
//noinspection ScalaWeakerAccess
|
||||||
@ -80,6 +82,15 @@ class MornyCoeur (using val config: MornyConfig) {
|
|||||||
eventManager register MornyOnInlineQuery(using queries)
|
eventManager register MornyOnInlineQuery(using queries)
|
||||||
//noinspection ScalaUnusedSymbol
|
//noinspection ScalaUnusedSymbol
|
||||||
val events: MornyEventListeners = MornyEventListeners(using eventManager)
|
val events: MornyEventListeners = MornyEventListeners(using eventManager)
|
||||||
|
eventManager register daemons.reporter.EventStatistics.EventInfoCatcher
|
||||||
|
@unused
|
||||||
|
val watchDog: WatchDog = WatchDog("watch-dog", 1000, 1500, { (consumed, _) =>
|
||||||
|
import cc.sukazyo.cono.morny.util.CommonFormat.formatDuration as f
|
||||||
|
logger warn
|
||||||
|
s"""Can't keep up! is the server overloaded or host machine fall asleep?
|
||||||
|
| current tick takes ${f(consumed)} to complete.""".stripMargin
|
||||||
|
tasks.notifyIt()
|
||||||
|
})
|
||||||
|
|
||||||
///>>> BLOCK START instance configure & startup stage 2
|
///>>> BLOCK START instance configure & startup stage 2
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import java.lang.annotation.*;
|
|||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
public class MornyConfig {
|
public class MornyConfig {
|
||||||
|
|
||||||
@ -109,6 +110,18 @@ public class MornyConfig {
|
|||||||
*/
|
*/
|
||||||
public final long reportToChat;
|
public final long reportToChat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 控制 Morny Coeur 系统的报告的基准时间.
|
||||||
|
* <p>
|
||||||
|
* 仅会用于 {@link cc.sukazyo.cono.morny.daemon.MornyReport} 内的时间敏感的报告,
|
||||||
|
* 不会用于 {@code /info} 命令等位置。
|
||||||
|
* <p>
|
||||||
|
* 默认使用 {@link TimeZone#getDefault()}.
|
||||||
|
*
|
||||||
|
* @since 1.3.0
|
||||||
|
*/
|
||||||
|
@Nonnull public final TimeZone reportZone;
|
||||||
|
|
||||||
/* ======================================= *
|
/* ======================================= *
|
||||||
* function: dinner query tool *
|
* function: dinner query tool *
|
||||||
* ======================================= */
|
* ======================================= */
|
||||||
@ -144,6 +157,7 @@ public class MornyConfig {
|
|||||||
this.dinnerTrustedReaders = prototype.dinnerTrustedReaders;
|
this.dinnerTrustedReaders = prototype.dinnerTrustedReaders;
|
||||||
this.dinnerChatId = prototype.dinnerChatId;
|
this.dinnerChatId = prototype.dinnerChatId;
|
||||||
this.reportToChat = prototype.reportToChat;
|
this.reportToChat = prototype.reportToChat;
|
||||||
|
this.reportZone = prototype.reportZone;
|
||||||
this.medicationNotifyToChat = prototype.medicationNotifyToChat;
|
this.medicationNotifyToChat = prototype.medicationNotifyToChat;
|
||||||
this.medicationTimerUseTimezone = prototype.medicationTimerUseTimezone;
|
this.medicationTimerUseTimezone = prototype.medicationTimerUseTimezone;
|
||||||
prototype.medicationNotifyAt.forEach(i -> { if (i < 0 || i > 23) throw new CheckFailure.UnavailableTimeInMedicationNotifyAt(); });
|
prototype.medicationNotifyAt.forEach(i -> { if (i < 0 || i > 23) throw new CheckFailure.UnavailableTimeInMedicationNotifyAt(); });
|
||||||
@ -173,6 +187,7 @@ public class MornyConfig {
|
|||||||
@Nonnull public final Set<Long> dinnerTrustedReaders = new HashSet<>();
|
@Nonnull public final Set<Long> dinnerTrustedReaders = new HashSet<>();
|
||||||
public long dinnerChatId = -1L;
|
public long dinnerChatId = -1L;
|
||||||
public long reportToChat = -1L;
|
public long reportToChat = -1L;
|
||||||
|
@Nonnull public TimeZone reportZone = TimeZone.getDefault();
|
||||||
public long medicationNotifyToChat = -1L;
|
public long medicationNotifyToChat = -1L;
|
||||||
@Nonnull public ZoneOffset medicationTimerUseTimezone = ZoneOffset.UTC;
|
@Nonnull public ZoneOffset medicationTimerUseTimezone = ZoneOffset.UTC;
|
||||||
@Nonnull public final Set<Integer> medicationNotifyAt = new HashSet<>();
|
@Nonnull public final Set<Integer> medicationNotifyAt = new HashSet<>();
|
||||||
|
@ -51,6 +51,7 @@ object ServerMain {
|
|||||||
case "--master" | "-mm" => i+=1 ; config.trustedMaster = args(i)toLong
|
case "--master" | "-mm" => i+=1 ; config.trustedMaster = args(i)toLong
|
||||||
case "--trusted-chat" | "-trs" => i+=1 ; config.trustedChat = args(i)toLong
|
case "--trusted-chat" | "-trs" => i+=1 ; config.trustedChat = args(i)toLong
|
||||||
case "--report-to" => i+=1; config.reportToChat = args(i)toLong
|
case "--report-to" => i+=1; config.reportToChat = args(i)toLong
|
||||||
|
case "--report-zone" => i+=1; config.reportZone = TimeZone.getTimeZone(args(i))
|
||||||
|
|
||||||
case "--trusted-reader-dinner" | "-trsd" => i+=1 ; config.dinnerTrustedReaders add (args(i)toLong)
|
case "--trusted-reader-dinner" | "-trsd" => i+=1 ; config.dinnerTrustedReaders add (args(i)toLong)
|
||||||
case "--dinner-chat" | "-chd" => i+=1 ; config.dinnerChatId = args(i)toLong
|
case "--dinner-chat" | "-chd" => i+=1 ; config.dinnerChatId = args(i)toLong
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package cc.sukazyo.cono.morny.bot.api
|
package cc.sukazyo.cono.morny.bot.api
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.util.EpochDateTime.EpochMillis
|
||||||
import com.pengrad.telegrambot.model.Update
|
import com.pengrad.telegrambot.model.Update
|
||||||
|
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
@ -12,6 +13,7 @@ class EventEnv (
|
|||||||
|
|
||||||
private var _isOk: Int = 0
|
private var _isOk: Int = 0
|
||||||
private val variables: mutable.HashMap[Class[?], Any] = mutable.HashMap.empty
|
private val variables: mutable.HashMap[Class[?], Any] = mutable.HashMap.empty
|
||||||
|
val timeStartup: EpochMillis = System.currentTimeMillis
|
||||||
|
|
||||||
def isEventOk: Boolean = _isOk > 0
|
def isEventOk: Boolean = _isOk > 0
|
||||||
|
|
||||||
|
@ -2,6 +2,25 @@ package cc.sukazyo.cono.morny.bot.api
|
|||||||
|
|
||||||
trait EventListener () {
|
trait EventListener () {
|
||||||
|
|
||||||
|
/** Determine if this event listener should be processed.
|
||||||
|
*
|
||||||
|
* Default implementation is it only be [[true]] when the event
|
||||||
|
* is not ok yet (when [[EventEnv.isEventOk]] is false).
|
||||||
|
*
|
||||||
|
* Notice that: You should not override this method to filter some
|
||||||
|
* affair level conditions (such as if this update contains a text
|
||||||
|
* message), you should write them to the listener function! This
|
||||||
|
* method is just for event low-level controls.
|
||||||
|
*
|
||||||
|
* @param env The [[EventEnv event variable]].
|
||||||
|
* @return [[true]] if this event listener should run; [[false]]
|
||||||
|
* if it should not run.
|
||||||
|
*/
|
||||||
|
def executeFilter (using env: EventEnv): Boolean =
|
||||||
|
if env.isEventOk then false else true
|
||||||
|
|
||||||
|
def atEventPost (using EventEnv): Unit = {}
|
||||||
|
|
||||||
def onMessage (using EventEnv): Unit = {}
|
def onMessage (using EventEnv): Unit = {}
|
||||||
def onEditedMessage (using EventEnv): Unit = {}
|
def onEditedMessage (using EventEnv): Unit = {}
|
||||||
def onChannelPost (using EventEnv): Unit = {}
|
def onChannelPost (using EventEnv): Unit = {}
|
||||||
|
@ -32,7 +32,9 @@ class EventListenerManager (using coeur: MornyCoeur) extends UpdatesListener {
|
|||||||
override def run (): Unit = {
|
override def run (): Unit = {
|
||||||
given env: EventEnv = EventEnv(update)
|
given env: EventEnv = EventEnv(update)
|
||||||
boundary { for (i <- listeners) {
|
boundary { for (i <- listeners) {
|
||||||
try {
|
|
||||||
|
if (i.executeFilter) try {
|
||||||
|
|
||||||
updateThreadName("message")
|
updateThreadName("message")
|
||||||
if update.message ne null then i.onMessage
|
if update.message ne null then i.onMessage
|
||||||
updateThreadName("edited-message")
|
updateThreadName("edited-message")
|
||||||
@ -61,6 +63,10 @@ class EventListenerManager (using coeur: MornyCoeur) extends UpdatesListener {
|
|||||||
if update.chatMember ne null then i.onChatMemberUpdated
|
if update.chatMember ne null then i.onChatMemberUpdated
|
||||||
updateThreadName("chat-join-request")
|
updateThreadName("chat-join-request")
|
||||||
if update.chatJoinRequest ne null then i.onChatJoinRequest
|
if update.chatJoinRequest ne null then i.onChatJoinRequest
|
||||||
|
|
||||||
|
updateThreadName("#post")
|
||||||
|
i.atEventPost
|
||||||
|
|
||||||
} catch case e => {
|
} catch case e => {
|
||||||
val errorMessage = StringBuilder()
|
val errorMessage = StringBuilder()
|
||||||
errorMessage ++= "Event throws unexpected exception:\n"
|
errorMessage ++= "Event throws unexpected exception:\n"
|
||||||
@ -75,7 +81,7 @@ class EventListenerManager (using coeur: MornyCoeur) extends UpdatesListener {
|
|||||||
logger error errorMessage.toString
|
logger error errorMessage.toString
|
||||||
coeur.daemons.reporter.exception(e, "on event running")
|
coeur.daemons.reporter.exception(e, "on event running")
|
||||||
}
|
}
|
||||||
if env.isEventOk then boundary.break()
|
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,11 +23,12 @@ class MornyInformation (using coeur: MornyCoeur) extends ITelegramCommand {
|
|||||||
val VERSION = "version"
|
val VERSION = "version"
|
||||||
val VERSION_2 = "v"
|
val VERSION_2 = "v"
|
||||||
val TASKS = "tasks"
|
val TASKS = "tasks"
|
||||||
|
val EVENTS = "event"
|
||||||
}
|
}
|
||||||
|
|
||||||
override val name: String = "info"
|
override val name: String = "info"
|
||||||
override val aliases: Array[ICommandAlias]|Null = null
|
override val aliases: Array[ICommandAlias]|Null = null
|
||||||
override val paramRule: String = "[(version|runtime|stickers[.IDs]|tasks)]"
|
override val paramRule: String = "[(version|runtime|stickers[.IDs]|tasks|event)]"
|
||||||
override val description: String = "输出当前 Morny 的各种信息"
|
override val description: String = "输出当前 Morny 的各种信息"
|
||||||
|
|
||||||
override def execute (using command: InputCommand, event: Update): Unit = {
|
override def execute (using command: InputCommand, event: Update): Unit = {
|
||||||
@ -44,6 +45,7 @@ class MornyInformation (using coeur: MornyCoeur) extends ITelegramCommand {
|
|||||||
case Subs.RUNTIME => echoRuntime
|
case Subs.RUNTIME => echoRuntime
|
||||||
case Subs.VERSION | Subs.VERSION_2 => echoVersion
|
case Subs.VERSION | Subs.VERSION_2 => echoVersion
|
||||||
case Subs.TASKS => echoTasksStatus
|
case Subs.TASKS => echoTasksStatus
|
||||||
|
case Subs.EVENTS => echoEventStatistics
|
||||||
case _ => echo404
|
case _ => echo404
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,6 +161,16 @@ class MornyInformation (using coeur: MornyCoeur) extends ITelegramCommand {
|
|||||||
).parseMode(ParseMode.HTML).replyToMessageId(update.message.messageId)
|
).parseMode(ParseMode.HTML).replyToMessageId(update.message.messageId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private def echoEventStatistics (using update: Update): Unit = {
|
||||||
|
coeur.account exec SendMessage(
|
||||||
|
update.message.chat.id,
|
||||||
|
// language=html
|
||||||
|
s"""<b>Event Statistics :</b>
|
||||||
|
|in today
|
||||||
|
|${coeur.daemons.reporter.EventStatistics.eventStatisticsHTML}""".stripMargin
|
||||||
|
).parseMode(ParseMode.HTML).replyToMessageId(update.message.messageId)
|
||||||
|
}
|
||||||
|
|
||||||
private def echo404 (using event: Update): Unit =
|
private def echo404 (using event: Update): Unit =
|
||||||
coeur.account exec new SendSticker(
|
coeur.account exec new SendSticker(
|
||||||
event.message.chat.id,
|
event.message.chat.id,
|
||||||
|
@ -13,8 +13,8 @@ class MornyDaemons (using val coeur: MornyCoeur) {
|
|||||||
|
|
||||||
logger notice "ALL Morny Daemons starting..."
|
logger notice "ALL Morny Daemons starting..."
|
||||||
|
|
||||||
// TrackerDataManager.init();
|
|
||||||
medicationTimer.start()
|
medicationTimer.start()
|
||||||
|
reporter.start()
|
||||||
|
|
||||||
logger notice "Morny Daemons started."
|
logger notice "Morny Daemons started."
|
||||||
|
|
||||||
@ -24,9 +24,8 @@ class MornyDaemons (using val coeur: MornyCoeur) {
|
|||||||
|
|
||||||
logger notice "stopping All Morny Daemons..."
|
logger notice "stopping All Morny Daemons..."
|
||||||
|
|
||||||
// TrackerDataManager.DAEMON.interrupt();
|
|
||||||
medicationTimer.stop()
|
medicationTimer.stop()
|
||||||
// TrackerDataManager.trackingLock.lock();
|
reporter.stop()
|
||||||
|
|
||||||
logger notice "stopped ALL Morny Daemons."
|
logger notice "stopped ALL Morny Daemons."
|
||||||
}
|
}
|
||||||
|
@ -2,17 +2,26 @@ package cc.sukazyo.cono.morny.daemon
|
|||||||
|
|
||||||
import cc.sukazyo.cono.morny.{MornyCoeur, MornyConfig}
|
import cc.sukazyo.cono.morny.{MornyCoeur, MornyConfig}
|
||||||
import cc.sukazyo.cono.morny.Log.{exceptionLog, logger}
|
import cc.sukazyo.cono.morny.Log.{exceptionLog, logger}
|
||||||
|
import cc.sukazyo.cono.morny.bot.api.{EventEnv, EventListener}
|
||||||
import cc.sukazyo.cono.morny.data.MornyInformation.getVersionAllFullTagHTML
|
import cc.sukazyo.cono.morny.data.MornyInformation.getVersionAllFullTagHTML
|
||||||
|
import cc.sukazyo.cono.morny.util.statistics.NumericStatistics
|
||||||
import cc.sukazyo.cono.morny.util.tgapi.event.EventRuntimeException
|
import cc.sukazyo.cono.morny.util.tgapi.event.EventRuntimeException
|
||||||
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramFormatter.*
|
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramFormatter.*
|
||||||
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramParseEscape.escapeHtml as h
|
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramParseEscape.escapeHtml as h
|
||||||
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec
|
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec
|
||||||
|
import cc.sukazyo.cono.morny.util.EpochDateTime.DurationMillis
|
||||||
|
import cc.sukazyo.cono.morny.util.schedule.CronTask
|
||||||
|
import com.cronutils.builder.CronBuilder
|
||||||
|
import com.cronutils.model.Cron
|
||||||
|
import com.cronutils.model.definition.CronDefinitionBuilder
|
||||||
import com.google.gson.GsonBuilder
|
import com.google.gson.GsonBuilder
|
||||||
import com.pengrad.telegrambot.model.request.ParseMode
|
import com.pengrad.telegrambot.model.request.ParseMode
|
||||||
import com.pengrad.telegrambot.model.User
|
import com.pengrad.telegrambot.model.User
|
||||||
import com.pengrad.telegrambot.request.{BaseRequest, SendMessage}
|
import com.pengrad.telegrambot.request.{BaseRequest, SendMessage}
|
||||||
import com.pengrad.telegrambot.response.BaseResponse
|
import com.pengrad.telegrambot.response.BaseResponse
|
||||||
|
|
||||||
|
import java.time.ZoneId
|
||||||
|
|
||||||
class MornyReport (using coeur: MornyCoeur) {
|
class MornyReport (using coeur: MornyCoeur) {
|
||||||
|
|
||||||
private val enabled = coeur.config.reportToChat != -1
|
private val enabled = coeur.config.reportToChat != -1
|
||||||
@ -67,10 +76,12 @@ class MornyReport (using coeur: MornyCoeur) {
|
|||||||
// language=html
|
// language=html
|
||||||
s"""<b>▌Morny Logged in</b>
|
s"""<b>▌Morny Logged in</b>
|
||||||
|-v $getVersionAllFullTagHTML
|
|-v $getVersionAllFullTagHTML
|
||||||
|as user @${coeur.username}
|
|Logged into user: @${coeur.username}
|
||||||
|
|
|
|
||||||
|as config fields:
|
|as config fields:
|
||||||
|${sectionConfigFields(coeur.config)}"""
|
|${sectionConfigFields(coeur.config)}
|
||||||
|
|
|
||||||
|
|Report Daemon will use TimeZone <code>${coeur.config.reportZone.getDisplayName}</code> for following report."""
|
||||||
.stripMargin
|
.stripMargin
|
||||||
).parseMode(ParseMode HTML))
|
).parseMode(ParseMode HTML))
|
||||||
}
|
}
|
||||||
@ -120,4 +131,91 @@ class MornyReport (using coeur: MornyCoeur) {
|
|||||||
).parseMode(ParseMode HTML))
|
).parseMode(ParseMode HTML))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object EventStatistics {
|
||||||
|
|
||||||
|
private var eventTotal = 0
|
||||||
|
private val runningTime: NumericStatistics[DurationMillis] = NumericStatistics()
|
||||||
|
|
||||||
|
def reset (): Unit = {
|
||||||
|
eventTotal = 0; runningTime.reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
private def runningTimeStatisticsHTML: String =
|
||||||
|
runningTime.value match
|
||||||
|
// language=html
|
||||||
|
case None => "<i><u><no-statistics></u></i>"
|
||||||
|
case Some(value) =>
|
||||||
|
import cc.sukazyo.cono.morny.util.CommonFormat.formatDuration as f
|
||||||
|
s""" - <i>average</i>: <code>${f(value.total / value.count)}</code>
|
||||||
|
| - <i>max time</i>: <code>${f(value.max)}</code>
|
||||||
|
| - <i>min time</i>: <code>${f(value.min)}</code>
|
||||||
|
| - <i>total</i>: <code>${f(value.total)}</code>""".stripMargin
|
||||||
|
|
||||||
|
def eventStatisticsHTML: String =
|
||||||
|
import cc.sukazyo.cono.morny.util.UseMath.percentageOf as p
|
||||||
|
val processed = runningTime.count
|
||||||
|
val ignored = eventTotal - processed
|
||||||
|
// language=html
|
||||||
|
s""" - <i>total event received</i>: <code>$eventTotal</code>
|
||||||
|
| - <i>event processed</i>: (<code>${eventTotal p processed}%</code>) <code>$processed</code>
|
||||||
|
| - <i>event ignored</i>: (<code>${eventTotal p ignored}%</code>) <code>$ignored</code>
|
||||||
|
| - <i>processed time usage</i>:
|
||||||
|
|${runningTimeStatisticsHTML.indent(3)}""".stripMargin
|
||||||
|
|
||||||
|
object EventInfoCatcher extends EventListener {
|
||||||
|
override def executeFilter (using EventEnv): Boolean = true
|
||||||
|
//noinspection ScalaWeakerAccess
|
||||||
|
case class EventTimeUsed (it: DurationMillis)
|
||||||
|
override def atEventPost (using event: EventEnv): Unit = {
|
||||||
|
eventTotal += 1
|
||||||
|
if event.isEventOk then {
|
||||||
|
val timeUsed = EventTimeUsed(System.currentTimeMillis - event.timeStartup)
|
||||||
|
event provide timeUsed
|
||||||
|
logger debug s"event consumed ${timeUsed.it}ms"
|
||||||
|
runningTime ++ timeUsed.it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private object DailyReportTask extends CronTask {
|
||||||
|
|
||||||
|
import com.cronutils.model.field.expression.FieldExpressionFactory.*
|
||||||
|
|
||||||
|
override val name: String = "reporter#event"
|
||||||
|
override val cron: Cron = CronBuilder.cron(
|
||||||
|
CronDefinitionBuilder.defineCron
|
||||||
|
.withHours.and
|
||||||
|
.instance
|
||||||
|
).withHour(on(0)).instance
|
||||||
|
override val zone: ZoneId = coeur.config.reportZone.toZoneId
|
||||||
|
|
||||||
|
//noinspection TypeAnnotation
|
||||||
|
override def main = {
|
||||||
|
|
||||||
|
executeReport(SendMessage(
|
||||||
|
coeur.config.reportToChat,
|
||||||
|
// language=html
|
||||||
|
s"""▌Morny Daily Report
|
||||||
|
|
|
||||||
|
|<b>Event Statistics :</b>
|
||||||
|
|${EventStatistics.eventStatisticsHTML}""".stripMargin
|
||||||
|
).parseMode(ParseMode.HTML))
|
||||||
|
|
||||||
|
// daily reset
|
||||||
|
EventStatistics.reset()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
def start (): Unit = {
|
||||||
|
coeur.tasks ++ DailyReportTask
|
||||||
|
}
|
||||||
|
|
||||||
|
def stop (): Unit = {
|
||||||
|
coeur.tasks % DailyReportTask
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -16,4 +16,9 @@ object UseMath {
|
|||||||
def ** (other: Int): Double = Math.pow(self, other)
|
def ** (other: Int): Double = Math.pow(self, other)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension (base: Int) {
|
||||||
|
def percentageOf (another: Int): Int =
|
||||||
|
Math.round((another.toDouble/base)*100).toInt
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import scala.jdk.OptionConverters.*
|
|||||||
|
|
||||||
trait CronTask extends RoutineTask {
|
trait CronTask extends RoutineTask {
|
||||||
|
|
||||||
private transparent inline def cronCalc = ExecutionTime.forCron(cron)
|
private lazy val cronCalc = ExecutionTime.forCron(cron)
|
||||||
|
|
||||||
def cron: Cron
|
def cron: Cron
|
||||||
|
|
||||||
|
@ -13,14 +13,20 @@ import cc.sukazyo.cono.morny.util.EpochDateTime.EpochMillis
|
|||||||
*/
|
*/
|
||||||
trait RoutineTask extends Task {
|
trait RoutineTask extends Task {
|
||||||
|
|
||||||
private[schedule] var currentScheduledTimeMillis: EpochMillis = firstRoutineTimeMillis
|
private[schedule] var currentScheduledTimeMillis: Option[EpochMillis] = None
|
||||||
|
|
||||||
/** Next running time of this task.
|
/** Next running time of this task.
|
||||||
*
|
*
|
||||||
* Should be auto generated from [[firstRoutineTimeMillis]] and
|
* Should be auto generated from [[firstRoutineTimeMillis]] when this method
|
||||||
* [[nextRoutineTimeMillis]].
|
* is called at first time, and then from [[nextRoutineTimeMillis]] for following
|
||||||
|
* routines controlled by [[Scheduler]].
|
||||||
*/
|
*/
|
||||||
override def scheduledTimeMillis: EpochMillis = currentScheduledTimeMillis
|
override def scheduledTimeMillis: EpochMillis =
|
||||||
|
currentScheduledTimeMillis match
|
||||||
|
case Some(time) => time
|
||||||
|
case None =>
|
||||||
|
currentScheduledTimeMillis = Some(firstRoutineTimeMillis)
|
||||||
|
currentScheduledTimeMillis.get
|
||||||
|
|
||||||
/** The task scheduled time at initial.
|
/** The task scheduled time at initial.
|
||||||
*
|
*
|
||||||
|
@ -63,8 +63,7 @@ class Scheduler {
|
|||||||
private val taskList: mutable.TreeSet[Task] = mutable.TreeSet.empty
|
private val taskList: mutable.TreeSet[Task] = mutable.TreeSet.empty
|
||||||
private var exitAtNextRoutine = false
|
private var exitAtNextRoutine = false
|
||||||
private var waitForDone = false
|
private var waitForDone = false
|
||||||
private var currentRunning: Task|Null = _
|
// private var currentRunning: Task|Null = _
|
||||||
private var currentRunning_isScheduledCancel = false
|
|
||||||
private var runtimeStatus = State.INIT
|
private var runtimeStatus = State.INIT
|
||||||
private val runtime: Thread = new Thread {
|
private val runtime: Thread = new Thread {
|
||||||
|
|
||||||
@ -76,20 +75,19 @@ class Scheduler {
|
|||||||
if taskList.isEmpty then true
|
if taskList.isEmpty then true
|
||||||
else false
|
else false
|
||||||
else false
|
else false
|
||||||
while (!willExit) {
|
taskList.synchronized { while (!willExit) {
|
||||||
|
|
||||||
runtimeStatus = State.PREPARE_RUN
|
runtimeStatus = State.PREPARE_RUN
|
||||||
|
|
||||||
val nextMove: Task|EpochMillis|"None" = taskList.synchronized {
|
val nextMove: Task|EpochMillis|"None" =
|
||||||
taskList.headOption match
|
taskList.headOption match
|
||||||
case Some(_readyToRun) if System.currentTimeMillis >= _readyToRun.scheduledTimeMillis =>
|
case Some(_readyToRun) if System.currentTimeMillis >= _readyToRun.scheduledTimeMillis =>
|
||||||
taskList -= _readyToRun
|
taskList -= _readyToRun
|
||||||
currentRunning = _readyToRun
|
// currentRunning = _readyToRun
|
||||||
_readyToRun
|
_readyToRun
|
||||||
case Some(_notReady) =>
|
case Some(_notReady) =>
|
||||||
_notReady.scheduledTimeMillis - System.currentTimeMillis
|
_notReady.scheduledTimeMillis - System.currentTimeMillis
|
||||||
case None => "None"
|
case None => "None"
|
||||||
}
|
|
||||||
|
|
||||||
nextMove match
|
nextMove match
|
||||||
case readyToRun: Task =>
|
case readyToRun: Task =>
|
||||||
@ -104,31 +102,33 @@ class Scheduler {
|
|||||||
runtimeStatus = State.RUNNING_POST
|
runtimeStatus = State.RUNNING_POST
|
||||||
this setName s"${readyToRun.name}#post"
|
this setName s"${readyToRun.name}#post"
|
||||||
|
|
||||||
if currentRunning_isScheduledCancel then {}
|
// this if is used for check if post effect need to be
|
||||||
|
// run. It is useless since the wait/notify changes.
|
||||||
|
if false then {}
|
||||||
else {
|
else {
|
||||||
currentRunning match
|
readyToRun match
|
||||||
case routine: RoutineTask =>
|
case routine: RoutineTask =>
|
||||||
routine.nextRoutineTimeMillis(routine.currentScheduledTimeMillis) match
|
routine.nextRoutineTimeMillis(routine.currentScheduledTimeMillis.get) match
|
||||||
case next: EpochMillis =>
|
case next: EpochMillis =>
|
||||||
routine.currentScheduledTimeMillis = next
|
routine.currentScheduledTimeMillis = Some(next)
|
||||||
if (!currentRunning_isScheduledCancel) schedule(routine)
|
schedule(routine)
|
||||||
case _ =>
|
case _ =>
|
||||||
case _ =>
|
case _ =>
|
||||||
}
|
}
|
||||||
|
|
||||||
currentRunning = null
|
// currentRunning = null
|
||||||
this setName runnerName
|
this setName runnerName
|
||||||
|
|
||||||
case needToWaitMillis: EpochMillis =>
|
case needToWaitMillis: EpochMillis =>
|
||||||
runtimeStatus = State.WAITING
|
runtimeStatus = State.WAITING
|
||||||
try Thread.sleep(needToWaitMillis)
|
try taskList.wait(needToWaitMillis)
|
||||||
catch case _: InterruptedException => {}
|
catch case _: (InterruptedException|IllegalArgumentException) => {}
|
||||||
case _: "None" =>
|
case _: "None" =>
|
||||||
runtimeStatus = State.WAITING_EMPTY
|
runtimeStatus = State.WAITING_EMPTY
|
||||||
try Thread.sleep(Long.MaxValue)
|
try taskList.wait()
|
||||||
catch case _: InterruptedException => {}
|
catch case _: InterruptedException => {}
|
||||||
|
|
||||||
}
|
}}
|
||||||
runtimeStatus = State.END
|
runtimeStatus = State.END
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,9 +154,9 @@ class Scheduler {
|
|||||||
* @return [[true]] if the task is added.
|
* @return [[true]] if the task is added.
|
||||||
*/
|
*/
|
||||||
def schedule (task: Task): Boolean =
|
def schedule (task: Task): Boolean =
|
||||||
try taskList.synchronized:
|
taskList.synchronized:
|
||||||
taskList add task
|
try taskList add task
|
||||||
finally runtime.interrupt()
|
finally taskList.notifyAll()
|
||||||
|
|
||||||
/** Remove the task from scheduler task queue.
|
/** Remove the task from scheduler task queue.
|
||||||
*
|
*
|
||||||
@ -172,23 +172,16 @@ class Scheduler {
|
|||||||
this
|
this
|
||||||
/** Remove the task from scheduler task queue.
|
/** Remove the task from scheduler task queue.
|
||||||
*
|
*
|
||||||
* If the removal task is running, the current run will be done, but will
|
* If the removal task is running, the method will wait for the current run
|
||||||
* not do the post effect of the task (like schedule the next routine
|
* complete (and current run post effect complete), then do remove.
|
||||||
* of [[RoutineTask]]).
|
|
||||||
*
|
*
|
||||||
* @return [[true]] if the task is in task queue or is running, and have been
|
* @return [[true]] if the task is in task queue or is running, and have been
|
||||||
* succeed removed from task queue.
|
* succeed removed from task queue.
|
||||||
*/
|
*/
|
||||||
def cancel (task: Task): Boolean =
|
def cancel (task: Task): Boolean =
|
||||||
try {
|
taskList synchronized:
|
||||||
val succeed = taskList.synchronized { taskList remove task }
|
try taskList remove task
|
||||||
if succeed then succeed
|
finally taskList.notifyAll()
|
||||||
else if task == currentRunning then
|
|
||||||
currentRunning_isScheduledCancel = true
|
|
||||||
true
|
|
||||||
else false
|
|
||||||
}
|
|
||||||
finally runtime.interrupt()
|
|
||||||
|
|
||||||
/** Count of tasks in the task queue.
|
/** Count of tasks in the task queue.
|
||||||
*
|
*
|
||||||
@ -205,6 +198,19 @@ class Scheduler {
|
|||||||
def runnerState: Thread.State =
|
def runnerState: Thread.State =
|
||||||
runtime.getState
|
runtime.getState
|
||||||
|
|
||||||
|
/** Manually update the task scheduler.
|
||||||
|
*
|
||||||
|
* If the inner state of the scheduler somehow changed and cannot automatically
|
||||||
|
* update schedule states to schedule the new state, you can call this method
|
||||||
|
* to manually let the task scheduler reschedule it.
|
||||||
|
*
|
||||||
|
* You can also use it with some tick-guard like [[cc.sukazyo.cono.morny.util.time.WatchDog]]
|
||||||
|
* to make the scheduler avoid fails when machine fall asleep or some else conditions.
|
||||||
|
*/
|
||||||
|
def notifyIt(): Unit =
|
||||||
|
taskList synchronized:
|
||||||
|
taskList.notifyAll()
|
||||||
|
|
||||||
/** Stop the scheduler's runner, no matter how much task is not run yet.
|
/** Stop the scheduler's runner, no matter how much task is not run yet.
|
||||||
*
|
*
|
||||||
* After call this, it will immediately give a signal to the runner for
|
* After call this, it will immediately give a signal to the runner for
|
||||||
@ -217,8 +223,9 @@ class Scheduler {
|
|||||||
* runner is stopped. If you want a sync version, see [[waitForStop]].
|
* runner is stopped. If you want a sync version, see [[waitForStop]].
|
||||||
*/
|
*/
|
||||||
def stop (): Unit =
|
def stop (): Unit =
|
||||||
exitAtNextRoutine = true
|
taskList synchronized:
|
||||||
runtime.interrupt()
|
exitAtNextRoutine = true
|
||||||
|
taskList.notifyAll()
|
||||||
|
|
||||||
/** Stop the scheduler's runner, no matter how much task is not run yet,
|
/** Stop the scheduler's runner, no matter how much task is not run yet,
|
||||||
* and wait for the runner stopped.
|
* and wait for the runner stopped.
|
||||||
@ -251,8 +258,9 @@ class Scheduler {
|
|||||||
*/
|
*/
|
||||||
//noinspection ScalaWeakerAccess
|
//noinspection ScalaWeakerAccess
|
||||||
def tagStopAtAllDone (): Unit =
|
def tagStopAtAllDone (): Unit =
|
||||||
waitForDone = true
|
taskList synchronized:
|
||||||
runtime.interrupt()
|
waitForDone = true
|
||||||
|
taskList.notifyAll()
|
||||||
|
|
||||||
/** Tag this scheduler runner stop when all of the scheduler's task in task
|
/** Tag this scheduler runner stop when all of the scheduler's task in task
|
||||||
* queue have been stopped, and wait for the runner stopped.
|
* queue have been stopped, and wait for the runner stopped.
|
||||||
@ -264,6 +272,7 @@ class Scheduler {
|
|||||||
* thread. The interrupted status of the current
|
* thread. The interrupted status of the current
|
||||||
* thread is cleared when this exception is thrown.
|
* thread is cleared when this exception is thrown.
|
||||||
*/
|
*/
|
||||||
|
@throws[InterruptedException]
|
||||||
def waitForStopAtAllDone(): Unit =
|
def waitForStopAtAllDone(): Unit =
|
||||||
tagStopAtAllDone()
|
tagStopAtAllDone()
|
||||||
runtime.join()
|
runtime.join()
|
||||||
|
@ -0,0 +1,101 @@
|
|||||||
|
package cc.sukazyo.cono.morny.util.statistics
|
||||||
|
|
||||||
|
import scala.annotation.targetName
|
||||||
|
|
||||||
|
/** Statistics for numbers.
|
||||||
|
*
|
||||||
|
* Gives a easy way to get amount of numbers min/max/sum value.
|
||||||
|
*
|
||||||
|
* Use [[++]] to collect a value to statistics, use [[value]] to
|
||||||
|
* get the statistic results.
|
||||||
|
*
|
||||||
|
* @param role The [[Numeric]] implementation of the given number type,
|
||||||
|
* required for numeric calculation.
|
||||||
|
* @tparam T The exactly number type
|
||||||
|
*/
|
||||||
|
class NumericStatistics [T] (using role: Numeric[T]) {
|
||||||
|
|
||||||
|
/** Statistic state values.
|
||||||
|
*
|
||||||
|
* This class instance should only be used in the statistics manager.
|
||||||
|
* You need to converted it to [[State.Immutable]] version when expose
|
||||||
|
* it (use its [[readonly]] method).
|
||||||
|
*
|
||||||
|
* @param total The sum of all data collected.
|
||||||
|
* @param min The minimal value in the collected data.
|
||||||
|
* @param max The maximize value in the collected data.
|
||||||
|
* @param count total collected data count.
|
||||||
|
*/
|
||||||
|
class State (
|
||||||
|
var min: T,
|
||||||
|
var max: T,
|
||||||
|
var total: T,
|
||||||
|
var count: Int
|
||||||
|
) {
|
||||||
|
/** Generate the [[State.Immutable]] readonly copy for this. */
|
||||||
|
def readonly: State.Immutable = State.Immutable(this)
|
||||||
|
}
|
||||||
|
object State:
|
||||||
|
/** The immutable (readonly) version [[State]]. */
|
||||||
|
class Immutable (source: State):
|
||||||
|
/** @see [[State.min]] */
|
||||||
|
val min: T = source.min
|
||||||
|
/** @see [[State.max]] */
|
||||||
|
val max: T = source.max
|
||||||
|
/** @see [[State.total]] */
|
||||||
|
val total: T = source.total
|
||||||
|
/** @see [[State.count]] */
|
||||||
|
val count: Int = source.count
|
||||||
|
|
||||||
|
private var state: Option[State] = None
|
||||||
|
|
||||||
|
/** Collect a new data to the statistic.
|
||||||
|
* @return The [[NumericStatistics]] itself for chained call.
|
||||||
|
*/
|
||||||
|
@targetName("collect")
|
||||||
|
def ++ (newOne: T): this.type =
|
||||||
|
state match
|
||||||
|
case Some(current) =>
|
||||||
|
if (role.lt(newOne, current.min)) current.min = newOne
|
||||||
|
if (role.gt(newOne, current.max)) current.max = newOne
|
||||||
|
current.total = role.plus(current.total, newOne)
|
||||||
|
current.count = current.count + 1
|
||||||
|
case None =>
|
||||||
|
state = Some(new State (
|
||||||
|
min = newOne,
|
||||||
|
max = newOne,
|
||||||
|
total = newOne,
|
||||||
|
count = 1
|
||||||
|
))
|
||||||
|
this
|
||||||
|
|
||||||
|
/** Reset the statistics to the initial state.
|
||||||
|
*
|
||||||
|
* All the collected data will be drop.
|
||||||
|
*/
|
||||||
|
def reset (): Unit =
|
||||||
|
state = None
|
||||||
|
|
||||||
|
/** Get the statistic values.
|
||||||
|
*
|
||||||
|
* @return An [[Option]] contains one [[State.Immutable]] object
|
||||||
|
* which refers the statistic state when call this method.
|
||||||
|
* If the statistic have no data recorded, then it will
|
||||||
|
* be [[None]]
|
||||||
|
*/
|
||||||
|
def value: Option[State.Immutable] =
|
||||||
|
state match
|
||||||
|
case Some(v) => Some(v.readonly)
|
||||||
|
case None => None
|
||||||
|
|
||||||
|
/** The number counts in the statistics.
|
||||||
|
*
|
||||||
|
* It will always returns a [[Int]] value, regardless if the
|
||||||
|
* statistic is collected some data.
|
||||||
|
*/
|
||||||
|
def count: Int =
|
||||||
|
state match
|
||||||
|
case Some(value) => value.count
|
||||||
|
case None => 0
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
package cc.sukazyo.cono.morny.util.time
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.util.EpochDateTime.{DurationMillis, EpochMillis}
|
||||||
|
|
||||||
|
trait WatchDog (val isDaemonIt: Boolean = true) extends Thread {
|
||||||
|
|
||||||
|
val threadName: String = "watch-dog"
|
||||||
|
val tickSpeedMillis: DurationMillis = 1000
|
||||||
|
val overloadMillis: DurationMillis = tickSpeedMillis + (tickSpeedMillis/2)
|
||||||
|
private var previousTickTimeMillis: Option[EpochMillis] = None
|
||||||
|
|
||||||
|
this setName threadName
|
||||||
|
this setDaemon isDaemonIt
|
||||||
|
|
||||||
|
this.start()
|
||||||
|
|
||||||
|
override def run(): Unit = {
|
||||||
|
while (!this.isInterrupted) {
|
||||||
|
val currentMillis = System.currentTimeMillis()
|
||||||
|
previousTickTimeMillis match
|
||||||
|
case Some(_previousMillis) =>
|
||||||
|
val consumedMillis = currentMillis - _previousMillis
|
||||||
|
if consumedMillis > overloadMillis then
|
||||||
|
this.overloaded(consumedMillis, consumedMillis - _previousMillis)
|
||||||
|
previousTickTimeMillis = Some(currentMillis)
|
||||||
|
case _ =>
|
||||||
|
previousTickTimeMillis = Some(currentMillis)
|
||||||
|
try Thread.sleep(tickSpeedMillis)
|
||||||
|
catch case _: InterruptedException =>
|
||||||
|
this.interrupt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def overloaded(consumed: DurationMillis, delayed: DurationMillis): Unit
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
object WatchDog {
|
||||||
|
|
||||||
|
def apply (
|
||||||
|
_threadName: String, _tickSpeedMillis: DurationMillis, _overloadMillis: DurationMillis,
|
||||||
|
overloadedCallback: (DurationMillis, DurationMillis) => Unit
|
||||||
|
): WatchDog =
|
||||||
|
new WatchDog:
|
||||||
|
override val threadName: String = _threadName
|
||||||
|
override val tickSpeedMillis: DurationMillis = _tickSpeedMillis
|
||||||
|
override val overloadMillis: DurationMillis = _overloadMillis
|
||||||
|
override def overloaded (consumed: DurationMillis, delayed: DurationMillis): Unit = overloadedCallback(consumed, delayed)
|
||||||
|
|
||||||
|
def apply (
|
||||||
|
_threadName: String, _tickSpeedMillis: DurationMillis,
|
||||||
|
overloadedCallback: (DurationMillis, DurationMillis) => Unit
|
||||||
|
): WatchDog =
|
||||||
|
new WatchDog:
|
||||||
|
override val threadName: String = _threadName
|
||||||
|
override val tickSpeedMillis: DurationMillis = _tickSpeedMillis
|
||||||
|
override def overloaded (consumed: DurationMillis, delayed: DurationMillis): Unit = overloadedCallback(consumed, delayed)
|
||||||
|
|
||||||
|
def apply (
|
||||||
|
_threadName: String,
|
||||||
|
overloadedCallback: (DurationMillis, DurationMillis) => Unit
|
||||||
|
): WatchDog =
|
||||||
|
new WatchDog:
|
||||||
|
override val threadName: String = _threadName
|
||||||
|
override def overloaded (consumed: DurationMillis, delayed: DurationMillis): Unit = overloadedCallback(consumed, delayed)
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user