Script monitoring
This commit is contained in:
@@ -18,7 +18,7 @@ val generatePartials = tasks.register<Exec>("generatePartials") {
|
||||
description = "Generate Partial classes (requests with all-nullable fields)"
|
||||
val scriptFile = project.file("partialize.main.kts")
|
||||
val targets = fileTree(requestsDirectory) {
|
||||
include("**/*.kt")
|
||||
include("Requests.kt")
|
||||
}
|
||||
val lst = targets.map { it.absolutePath }
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.jaytux.phoebench.common
|
||||
|
||||
import io.ktor.client.plugins.websocket.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.util.reflect.*
|
||||
import io.ktor.utils.io.*
|
||||
import io.ktor.websocket.*
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
sealed class CSERoute<TParams, TEvent: Any>(val path: String, val elevation: Elevation, private val _eventType: TypeInfo) {
|
||||
open val pattern = path
|
||||
|
||||
protected open fun buildUrl(params: TParams): String = path
|
||||
|
||||
abstract fun extractParams(reqParams: Parameters): TParams?
|
||||
|
||||
suspend fun call(client: IClient, params: TParams, body: suspend (sender: suspend (TEvent) -> Unit) -> Unit): Either<ErrorResponse, Unit> {
|
||||
val server = client.serverUrl.replace("https", "ws").replace("http", "ws")
|
||||
var error: ErrorResponse? = null
|
||||
try {
|
||||
client.client.webSocket("$server${buildUrl(params)}", {}) {
|
||||
try {
|
||||
body { sendSerialized(it, _eventType) }
|
||||
}
|
||||
catch(e: CancellationException) {
|
||||
closeReason.await()?.let {
|
||||
if(it.code != CloseReason.Codes.NORMAL.code) error = ErrorResponse(it.message)
|
||||
}
|
||||
throw e
|
||||
}
|
||||
}
|
||||
return Unit.value()
|
||||
}
|
||||
catch(e: CancellationException) {
|
||||
return (error ?: ErrorResponse(e.message ?: "Unknown websocket error")).error()
|
||||
}
|
||||
catch(e: WebSocketException) {
|
||||
return ErrorResponse("Could not set up websocket stream: ${e.message}").error()
|
||||
}
|
||||
catch(e: Exception) {
|
||||
return ErrorResponse("WebSocket connection failed: ${e.message}").error()
|
||||
}
|
||||
}
|
||||
|
||||
class CSERoute1<T1, TEvent: Any>(path: String, elevation: Elevation, eventType: TypeInfo, val urlEncode: (T1) -> String, val urlDecode: (String?) -> T1?)
|
||||
: CSERoute<T1, TEvent>(path, elevation, eventType)
|
||||
{
|
||||
override val pattern: String = "$path/{param}"
|
||||
override fun buildUrl(params: T1): String = "$path/${urlEncode(params)}"
|
||||
override fun extractParams(reqParams: Parameters): T1? = urlDecode(reqParams["param"])
|
||||
}
|
||||
|
||||
companion object {
|
||||
inline fun <T1, reified TEvent: Any> single(path: String, elevation: Elevation,
|
||||
noinline urlEncode: (T1) -> String = { it.toString() }, noinline urlDecode: (String?) -> T1?
|
||||
) = CSERoute1<T1, TEvent>(path, elevation, typeInfo<TEvent>(), urlEncode, urlDecode)
|
||||
|
||||
inline fun <reified TEvent: Any> uuid(path: String, elevation: Elevation) = single<Uuid, TEvent>(path, elevation) {
|
||||
it?.let { p -> Uuid.parseOrNull(p) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.jaytux.phoebench.common
|
||||
|
||||
enum class CloseReasons(val code: Short) {
|
||||
NOT_AUTHORIZED(4001),
|
||||
INVALID_REQUEST(4002),
|
||||
CONFLICT(4003),
|
||||
NOT_FOUND(4004)
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.jaytux.phoebench.common
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlin.collections.fold
|
||||
import kotlin.time.Instant
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
@Serializable
|
||||
@@ -65,4 +67,83 @@ sealed class ProjectEvent {
|
||||
|
||||
@Serializable
|
||||
data class EntryDeleted(val id: Uuid, val benchmarkId: Uuid) : ProjectEvent()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class Stream {
|
||||
STDOUT, STDERR
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class ANSI(val bitIdx: Int, val ansiCode: Int) {
|
||||
BOLD(0, 1), UNDERLINE(1, 4),
|
||||
|
||||
FG_BLACK(2, 30), FG_RED(3, 31), FG_GREEN(4, 32),
|
||||
FG_YELLOW(5, 33), FG_BLUE(6, 34), FG_MAGENTA(7, 35),
|
||||
FG_CYAN(8, 36),
|
||||
|
||||
BG_BLACK(9, 40), BG_RED(10, 41), BG_GREEN(11, 42),
|
||||
BG_YELLOW(12, 43), BG_BLUE(13, 44), BG_MAGENTA(14, 45),
|
||||
BG_CYAN(15, 46);
|
||||
|
||||
companion object {
|
||||
private val _mapping: Map<Int, ANSI>
|
||||
val ansiMapping: Map<Int, ANSI>
|
||||
init {
|
||||
if(entries.map { it.bitIdx }.toSet().size != entries.size)
|
||||
throw IllegalStateException("ANSI bit indices contain duplicates")
|
||||
if(entries.map { it.ansiCode }.toSet().size != entries.size)
|
||||
throw IllegalStateException("ANSI escape codes contain duplicates")
|
||||
|
||||
_mapping = entries.associateBy { ansi -> ansi.bitIdx }
|
||||
ansiMapping = entries.associateBy { ansi -> ansi.ansiCode }
|
||||
}
|
||||
|
||||
fun pack(vararg options: ANSI): UShort = options.fold(0u) { acc, ansi ->
|
||||
acc or (1u shl ansi.bitIdx).toUShort()
|
||||
}
|
||||
|
||||
fun unpack(packed: UShort): List<ANSI> {
|
||||
val res = mutableListOf<ANSI>()
|
||||
var remaining = packed
|
||||
for(i in 0..15) {
|
||||
if((remaining and 1u) != 0.toUShort()) {
|
||||
_mapping[i]?.let { res += it }
|
||||
}
|
||||
remaining = (remaining.toUInt() shr 1).toUShort()
|
||||
}
|
||||
return res
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed class ClientMonitorEvent {
|
||||
@Serializable
|
||||
data class Message(val msg: String, val stream: Stream, val options: UShort) : ClientMonitorEvent() {
|
||||
constructor(msg: String, stream: Stream, vararg options: ANSI) : this(msg, stream, ANSI.pack(*options))
|
||||
constructor(msg: String, stream: Stream, options: List<ANSI>) : this(msg, stream, ANSI.pack(*options.toTypedArray()))
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class ApplicationFinished(val exitCode: Int) : ClientMonitorEvent()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed class ServerMonitorEvent {
|
||||
sealed interface ITextEvent
|
||||
@Serializable
|
||||
object Cleared : ServerMonitorEvent()
|
||||
|
||||
@Serializable
|
||||
data class ApplicationStart(val time: Instant) : ServerMonitorEvent(), ITextEvent
|
||||
|
||||
@Serializable
|
||||
data class Message(val timeStamp: Instant, val message: String, val options: UShort, val stream: Stream) : ServerMonitorEvent(), ITextEvent
|
||||
|
||||
@Serializable
|
||||
data class Backlog(val start: ApplicationStart?, val messages: List<Message>, val end: ApplicationEnd?) : ServerMonitorEvent()
|
||||
|
||||
@Serializable
|
||||
data class ApplicationEnd(val time: Instant, val exitCode: Int) : ServerMonitorEvent(), ITextEvent
|
||||
}
|
||||
@@ -29,6 +29,7 @@ object Routes {
|
||||
val get = ApiRoute.getUuid<ProjectResponse>("/project", Elevation.AUTH)
|
||||
val update = ApiRoute.patchUuidNoRes<PartialProjectRequest>("/project", Elevation.AUTH)
|
||||
val delete = ApiRoute.deleteUuidNoRes("/project", Elevation.AUTH)
|
||||
val rmMonitor = ApiRoute.deleteUuidNoRes("/project/monitor", Elevation.AUTH)
|
||||
}
|
||||
|
||||
object Benchmark {
|
||||
@@ -52,5 +53,10 @@ object Routes {
|
||||
val home = SSERoute.noArgs<HomeEvent>("/rt/home", Elevation.AUTH)
|
||||
val admin = SSERoute.noArgs<AdminEvent>("/rt/admin", Elevation.ADMIN)
|
||||
val projectSpecific = SSERoute.uuid<ProjectEvent>("/rt/project", Elevation.AUTH)
|
||||
val monitor = SSERoute.uuid<ServerMonitorEvent>("/rt/monitor", Elevation.AUTH)
|
||||
}
|
||||
|
||||
object CSE {
|
||||
val monitor = CSERoute.uuid<ClientMonitorEvent>("/stream/monitor", Elevation.AUTH)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user