mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-22 10:28:27 +00:00
Add (inofficial) routes to get system information
This commit is contained in:
69
modules/common/src/main/scala/docspell/common/ByteSize.scala
Normal file
69
modules/common/src/main/scala/docspell/common/ByteSize.scala
Normal file
@ -0,0 +1,69 @@
|
||||
package docspell.common
|
||||
|
||||
import io.circe.Decoder
|
||||
import io.circe.Encoder
|
||||
|
||||
final case class ByteSize(bytes: Long) {
|
||||
|
||||
def toHuman: String =
|
||||
ByteSize.bytesToHuman(bytes)
|
||||
|
||||
def <=(other: ByteSize) =
|
||||
bytes <= other.bytes
|
||||
|
||||
def >=(other: ByteSize) =
|
||||
bytes >= other.bytes
|
||||
|
||||
def >(other: ByteSize) =
|
||||
bytes > other.bytes
|
||||
|
||||
def -(other: ByteSize) =
|
||||
ByteSize(bytes - other.bytes)
|
||||
|
||||
def +(other: ByteSize) =
|
||||
ByteSize(bytes + other.bytes)
|
||||
}
|
||||
|
||||
object ByteSize {
|
||||
|
||||
val zero = ByteSize(0L)
|
||||
|
||||
def bytesToHuman(bytes: Long): String =
|
||||
if (math.abs(bytes) < 1024 && bytes != Long.MinValue) s"${bytes}B"
|
||||
else {
|
||||
val k = bytes / 1024.0
|
||||
if (math.abs(k) < 1024) f"$k%.02fK"
|
||||
else {
|
||||
val m = k / 1024.0
|
||||
if (math.abs(m) < 1024) f"$m%.02fM"
|
||||
else f"${m / 1024.0}%.02fG"
|
||||
}
|
||||
}
|
||||
|
||||
def parse(str: String): Either[String, ByteSize] =
|
||||
str.toLongOption
|
||||
.map(ByteSize.apply)
|
||||
.toRight(s"Not a valid size string: $str")
|
||||
.orElse(span(str.toLowerCase) match {
|
||||
case (num, "k") =>
|
||||
Right(ByteSize(math.round(num.toDouble * 1024)))
|
||||
case (num, "m") =>
|
||||
Right(ByteSize(math.round(num.toDouble * 1024 * 1024)))
|
||||
case (num, "g") =>
|
||||
Right(ByteSize(math.round(num.toDouble * 1024 * 1024 * 1024)))
|
||||
case _ =>
|
||||
Left(s"Invalid byte string: $str")
|
||||
})
|
||||
|
||||
private def span(str: String): (String, String) =
|
||||
if (str.isEmpty) ("", "")
|
||||
else (str.init, str.last.toString)
|
||||
|
||||
def unsafe(str: String): ByteSize =
|
||||
parse(str).fold(sys.error, identity)
|
||||
|
||||
implicit val jsonDecoder: Decoder[ByteSize] =
|
||||
Decoder.decodeLong.map(ByteSize.apply)
|
||||
implicit val jsonEncoder: Encoder[ByteSize] =
|
||||
Encoder.encodeLong.contramap(_.bytes)
|
||||
}
|
103
modules/common/src/main/scala/docspell/common/JvmInfo.scala
Normal file
103
modules/common/src/main/scala/docspell/common/JvmInfo.scala
Normal file
@ -0,0 +1,103 @@
|
||||
package docspell.common
|
||||
|
||||
import java.time.Instant
|
||||
|
||||
import scala.jdk.CollectionConverters._
|
||||
|
||||
import cats.effect._
|
||||
import cats.implicits._
|
||||
|
||||
import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}
|
||||
import io.circe.{Decoder, Encoder}
|
||||
|
||||
case class JvmInfo(
|
||||
id: Ident,
|
||||
pidHost: String,
|
||||
ncpu: Int,
|
||||
inputArgs: List[String],
|
||||
libraryPath: String,
|
||||
specVendor: String,
|
||||
specVersion: String,
|
||||
startTime: Timestamp,
|
||||
uptime: Duration,
|
||||
vmName: String,
|
||||
vmVendor: String,
|
||||
vmVersion: String,
|
||||
heapUsage: JvmInfo.MemoryUsage,
|
||||
props: Map[String, String]
|
||||
)
|
||||
|
||||
object JvmInfo {
|
||||
|
||||
def create[F[_]: Sync](id: Ident): F[JvmInfo] =
|
||||
MemoryUsage.createHeap[F].flatMap { mu =>
|
||||
Sync[F].delay {
|
||||
val rmb = management.ManagementFactory.getRuntimeMXBean()
|
||||
val rt = Runtime.getRuntime()
|
||||
JvmInfo(
|
||||
id,
|
||||
pidHost = rmb.getName(),
|
||||
ncpu = rt.availableProcessors(),
|
||||
inputArgs = rmb.getInputArguments().asScala.toList,
|
||||
libraryPath = rmb.getLibraryPath(),
|
||||
specVendor = rmb.getSpecVendor(),
|
||||
specVersion = rmb.getSpecVersion(),
|
||||
startTime = Timestamp(Instant.ofEpochMilli(rmb.getStartTime())),
|
||||
uptime = Duration.millis(rmb.getUptime()),
|
||||
vmName = rmb.getVmName(),
|
||||
vmVendor = rmb.getVmVendor(),
|
||||
vmVersion = rmb.getVmVersion(),
|
||||
heapUsage = mu,
|
||||
props = rmb.getSystemProperties().asScala.toMap
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
case class MemoryUsage(
|
||||
init: Long,
|
||||
used: Long,
|
||||
comitted: Long,
|
||||
max: Long,
|
||||
free: Long,
|
||||
description: String
|
||||
)
|
||||
|
||||
object MemoryUsage {
|
||||
|
||||
def apply(init: Long, used: Long, comitted: Long, max: Long): MemoryUsage = {
|
||||
def str(n: Long) = ByteSize(n).toHuman
|
||||
|
||||
val free = max - used
|
||||
|
||||
val descr =
|
||||
s"init=${str(init)}, used=${str(used)}, comitted=${str(comitted)}, max=${str(max)}, free=${str(free)}"
|
||||
MemoryUsage(init, used, comitted, max, free, descr)
|
||||
}
|
||||
|
||||
val empty = MemoryUsage(0, 0, 0, 0)
|
||||
|
||||
def createHeap[F[_]: Sync]: F[MemoryUsage] =
|
||||
Sync[F].delay {
|
||||
val mxb = management.ManagementFactory.getMemoryMXBean()
|
||||
val heap = mxb.getHeapMemoryUsage()
|
||||
MemoryUsage(
|
||||
init = math.max(0, heap.getInit()),
|
||||
used = math.max(0, heap.getUsed()),
|
||||
comitted = math.max(0, heap.getCommitted()),
|
||||
max = math.max(0, heap.getMax())
|
||||
)
|
||||
}
|
||||
|
||||
implicit val jsonEncoder: Encoder[MemoryUsage] =
|
||||
deriveEncoder[MemoryUsage]
|
||||
|
||||
implicit val jsonDecoder: Decoder[MemoryUsage] =
|
||||
deriveDecoder[MemoryUsage]
|
||||
}
|
||||
|
||||
implicit val jsonEncoder: Encoder[JvmInfo] =
|
||||
deriveEncoder[JvmInfo]
|
||||
|
||||
implicit val jsonDecoder: Decoder[JvmInfo] =
|
||||
deriveDecoder[JvmInfo]
|
||||
}
|
Reference in New Issue
Block a user