Determine total system memory

This commit is contained in:
Jarek Sacha 2023-05-19 22:16:57 -04:00
parent ab28e2daf7
commit 4d793fcc27
No known key found for this signature in database
GPG key ID: F29625CE62288163
8 changed files with 130 additions and 127 deletions

View file

@ -12,3 +12,5 @@ importSelectors = singleLine
newlines.source = keep
rewrite.scala3.convertToNewSyntax = yes
rewrite.scala3.removeOptionalBraces = yes
rewrite.scala3.insertEndMarkerMinLines = 42

View file

@ -13,7 +13,7 @@ libraryDependencies ++= Seq(
Compile / run / mainClass := Some("ij_plugins.imagej_launcher.Main")
// import to add Scala Native options
import scala.scalanative.build.*
import scala.scalanative.build._
// defaults set with common options shown
nativeConfig ~= { c =>

View file

@ -0,0 +1,19 @@
#if defined(_WIN32)
#include <windows.h>
#else
#include <unistd.h>
#endif
unsigned long long determineTotalSystemMemory()
{
#if defined(_WIN32)
MEMORYSTATUSEX status;
status.dwLength = sizeof(status);
GlobalMemoryStatusEx(&status);
return status.ullTotalPhys;
#else
long pages = sysconf(_SC_PHYS_PAGES);
long page_size = sysconf(_SC_PAGE_SIZE);
return pages * page_size;
#endif
}

View file

@ -1,17 +0,0 @@
/*
* Copyright (c) 2000-2023 Jarek Sacha. All Rights Reserved.
* Author's e-mail: jpsacha at gmail.com
*/
package ij_plugins.imagej_launcher
import scala.collection.immutable
enum ErrorCode(val value: Int, val message: String):
case OK extends ErrorCode(0, "OK")
case InvalidCommandLineArguments extends ErrorCode(-10, "Invalid command line arguments")
case GeneralError extends ErrorCode(-100, "General error")
case UnhandledNonFatalError extends ErrorCode(-200, "Unhandled non-fatal error")
case UnhandledFatalError extends ErrorCode(-300, "Unhandled fatal error")
case NotImplemented extends ErrorCode(-400, "Functionality not implemented")
override def toString: String = s"$message [exit code: $value]"

View file

@ -9,37 +9,34 @@ import ij_plugins.imagej_launcher.Launcher.javaExeFileName
import ij_plugins.imagej_launcher.Main.Config
import os.Path
import java.io.{BufferedReader, File, InputStreamReader}
import java.io.File
import java.lang.ProcessBuilder.Redirect
import java.util.Locale
class Launcher(logger: Logger) {
class Launcher(logger: Logger):
private val jarsDirName = "jars"
def run(config: Config): ErrorCode = {
def run(config: Config): Unit =
createCommandLine(config) match
case Right(commandLine) =>
if config.dryRun then
logger.debug("dry-run")
println(commandLine.mkString(" "))
ErrorCode.OK
else
launch(commandLine)
case Left(errorMessage) =>
logger.error(errorMessage)
ErrorCode.GeneralError
}
private def createCommandLine(config: Config): Either[String, Seq[String]] =
for {
for
ijDir <- locateIJDir(config)
launcherJar <- findImageJLauncherJar(ijDir)
javaExe <- locateJavaExecutable(config, ijDir)
systemType <- determineSystemType()
} yield {
buildCommandLine(ijDir, javaExe, launcherJar, systemType)
}
yield
val maxMemoryMB = determineMaxMemoryMB()
logger.info(s"Max memory to use: ${maxMemoryMB}MB")
buildCommandLine(ijDir, javaExe, launcherJar, systemType, maxMemoryMB)
private def locateIJDir(config: Config): Either[String, File] =
logger.debug("Looking for ImageJ directory")
@ -54,22 +51,32 @@ class Launcher(logger: Logger) {
dir.listFiles().find(f => f.getName == jarsDirName && f.isDirectory) match
case Some(_) =>
logger.debug(s" ImageJ directory set to: $dir")
logger.info(s" ImageJ directory set to: '$dir'")
Right(dir)
case None =>
Left(s"Cannot locate ImageJ directory. No subdirectory '$jarsDirName' in '$dir''")
private def launch(command: Seq[String]): ErrorCode =
logger.debug("launchImageJ ...")
logger.debug(s"Executing command:\n" + command.mkString(" "))
val builder = new ProcessBuilder(command*)
builder.redirectOutput(ProcessBuilder.Redirect.INHERIT)
builder.redirectError(ProcessBuilder.Redirect.INHERIT)
val process = builder.start()
ErrorCode.OK
private def findImageJLauncherJar(ijDir: File): Either[String, File] =
logger.debug("Looking for 'imagej-launcher*.jar'")
val jarsDir = new File(ijDir, jarsDirName)
if jarsDir.exists() then
if jarsDir.isDirectory then
// Locate launcher
jarsDir
.listFiles()
.find { f =>
val name = f.getName
name.startsWith("imagej-launcher") && name.endsWith(".jar")
} match
case Some(f) =>
logger.info(s" Found launcher jar: '$f'")
Right(f)
case None =>
Left(s"Cannot find 'imagej-launcher*.jar' in '$jarsDir'")
else
Left(s"'$jarsDir' is not a directory'")
else
Left(s"Cannot find subdirectory '$jarsDirName' [$jarsDir]")
private def locateJavaExecutable(config: Config, ijDir: File): Either[String, File] =
logger.debug("Looking for Java executable")
@ -92,11 +99,11 @@ class Launcher(logger: Logger) {
Left("Unable to determine Java home")
javaHomeE.flatMap(javaHome =>
logger.debug(s" Java home set as: $javaHome")
logger.info(s" Java home set to: '$javaHome'")
logger.debug(s" Looking for java executable bin/$javaExeFileName")
val javaExe = new File(javaHome, s"bin/$javaExeFileName")
if javaExe.exists() then
logger.debug(s" Found '$javaExeFileName' in '$javaHome/bin'")
logger.info(s" Found '$javaExeFileName' in: '$javaHome/bin'")
Right(javaExe)
else
Left("cannot find 'java.exe' in 'path'")
@ -117,51 +124,44 @@ class Launcher(logger: Logger) {
else
None
private def findImageJLauncherJar(ijDir: File): Either[String, File] =
logger.debug("Looking for 'imagej-launcher*.jar'")
val jarsDir = new File(ijDir, jarsDirName)
if jarsDir.exists() then
if jarsDir.isDirectory then
// Locate launcher
jarsDir
.listFiles()
.find { f =>
val name = f.getName
name.startsWith("imagej-launcher") && name.endsWith(".jar")
} match
case Some(f) =>
logger.debug(s"Found: '$f'")
Right(f)
case None =>
Left(s"Cannot find 'imagej-launcher*.jar' in '$jarsDir'")
else
Left(s"'$jarsDir' is not a directory'")
else
Left(s"Cannot find subdirectory '$jarsDirName' [$jarsDir]")
private def determineSystemType(): Either[String, String] = {
private def determineSystemType(): Either[String, String] =
logger.debug("Determining system type")
val osName = System.getProperty("os.name")
val osArch = System.getProperty("os.arch")
logger.debug("os.name: " + osName)
logger.debug("os.arch: " + osArch)
logger.debug(" os.name: " + osName)
logger.debug(" os.arch: " + osArch)
val notSupportedError = s"$osName $osArch not supported"
if osName.toLowerCase.startsWith("windows") then
if osArch.toLowerCase.contains("64") then
Right("win64")
val r =
if osName.toLowerCase.startsWith("windows") then
if osArch.toLowerCase.contains("64") then
Right("win64")
else
Left(notSupportedError)
else if osName.toLowerCase.contains("mac os x") then
Right("macosx")
else
Left(notSupportedError)
else if osName.toLowerCase.contains("mac os x") then
if osArch.toLowerCase.contains("aarch64") then
Right("aarch64")
else
Left(notSupportedError)
else
Left(notSupportedError)
}
private def buildCommandLine(ijDir: File, javaExe: File, launcherJar: File, systemType: String): Seq[String] = {
r.foreach(s => logger.info(s" System type set to: '$s'"))
r
private def determineMaxMemoryMB(): Long =
logger.debug("Determine memory to use")
val sysMax = Native.mem.determineTotalSystemMemory()
logger.debug(s" Available RAM: ${sysMax / 1024 / 1024}MB")
val ijMaxMemMB = Math.round(sysMax * 0.75 / 1024 / 1024)
logger.debug(s" Using 3/4 of that: ${ijMaxMemMB}MB")
ijMaxMemMB
private def buildCommandLine(
ijDir: File,
javaExe: File,
launcherJar: File,
systemType: String,
maxMemoryMB: Long
): Seq[String] =
val ijDirPath = ijDir.getAbsolutePath
Seq(
@ -172,9 +172,11 @@ class Launcher(logger: Logger) {
"java.base/java.util=ALL-UNNAMED",
"--add-opens",
"java.desktop/sun.awt=ALL-UNNAMED",
"--add-opens",
"java.desktop/com.apple.eawt=ALL-UNNAMED",
"-Dpython.cachedir.skip=true",
s"-Dplugins.dir=$ijDirPath",
"-Xmx2048m",
s"-Xmx${maxMemoryMB}m",
"-Dimagej.splash=true",
s"-Djava.class.path=${launcherJar.getAbsolutePath}",
s"-Dimagej.dir=$ijDirPath",
@ -192,8 +194,17 @@ class Launcher(logger: Logger) {
"plugins",
"net.imagej.Main"
)
}
}
private def launch(command: Seq[String]): Unit =
logger.debug("launchImageJ ...")
logger.debug(s" Executing command:\n" + command.mkString(" "))
val builder = new ProcessBuilder(command*)
builder.redirectOutput(ProcessBuilder.Redirect.INHERIT)
builder.redirectError(ProcessBuilder.Redirect.INHERIT)
builder.start()
end Launcher
object Launcher:
lazy val javaExeFileName: String = if Utils.isWindows then "java.exe" else "java"

View file

@ -7,22 +7,22 @@ package ij_plugins.imagej_launcher
import ij_plugins.imagej_launcher.Logger.Level
class Logger {
class Logger:
var level: Level = Level.Info
def debug(msg: String): Unit = pprint(Level.Debug, msg)
def info(msg: String): Unit = pprint(Level.Info, msg)
def error(msg: String): Unit = pprint(Level.Error, msg)
private def pprint(l: Level, message: String): Unit =
if l.level <= level.level then println(s"${l.name}: $message")
}
def error(msg: String): Unit = pprint(Level.Error, msg)
object Logger:
enum Level(val level: Int, val name: String):
case Off extends Level(0, "OFF")
case Off extends Level(0, "OFF")
case Error extends Level(200, "ERROR")
case Info extends Level(400, "INFO")
case Info extends Level(400, "INFO")
case Debug extends Level(500, "DEBUG")
case All extends Level(Int.MaxValue, "DEBUG")
case All extends Level(Int.MaxValue, "DEBUG")

View file

@ -5,12 +5,11 @@
package ij_plugins.imagej_launcher
import ij_plugins.imagej_launcher.ErrorCode
import scopt.{DefaultOEffectSetup, OParser}
import java.io.File
object Main {
object Main:
private val logger = new Logger()
private val AppName = "ijp-imagej-launcher"
// private val AppVersion = s"${Version.version} [${Version.buildTimeStr}]"
@ -20,44 +19,18 @@ object Main {
"""Native launcher for ImageJ2
|""".stripMargin
case class Config(
logLevel: Logger.Level = Logger.Level.Error,
dryRun: Boolean = false,
javaHome: Option[File] = None,
ijDir: Option[File] = None
)
def main(args: Array[String]): Unit =
setupLogger(Logger.Level.All)
printInfo()
val ret: ErrorCode =
parseCommandLine(args) match
case Some(config) =>
setupLogger(config.logLevel)
runLauncher(config)
case None =>
// arguments are bad
ErrorCode.InvalidCommandLineArguments
val msg = s"${ret.message} [exit code: ${ret.value}]"
if ret == ErrorCode.OK then
logger.info(msg)
else
logger.error(msg)
// System.exit(ret.value)
end main
private def printInfo(): Unit =
logger.debug("os.arch: " + System.getProperty("os.arch"))
logger.debug("os.name: " + System.getProperty("os.name"))
// logger.debug("os.version: " + System.getProperty("os.version"))
parseCommandLine(args) match
case Some(config) =>
setupLogger(config.logLevel)
runLauncher(config)
case None =>
private def parseCommandLine(args: Array[String]): Option[Config] =
val builder = OParser.builder[Config]
val parser1 = {
val parser1 =
import builder.*
OParser.sequence(
programName(AppName),
@ -94,12 +67,18 @@ object Main {
.action((path, c) => c.copy(ijDir = Option(path)))
.text("set the ImageJ directory to <path> (used to find jars/, plugins/ and macros/)")
)
}
OParser.parse(parser1, args, Config())
end parseCommandLine
private def setupLogger(logLevel: Logger.Level): Unit = logger.level = logLevel
private def runLauncher(config: Config): ErrorCode = new Launcher(logger).run(config)
}
private def runLauncher(config: Config): Unit = new Launcher(logger).run(config)
case class Config(
logLevel: Logger.Level = Logger.Level.Error,
dryRun: Boolean = false,
javaHome: Option[File] = None,
ijDir: Option[File] = None
)
end Main

View file

@ -0,0 +1,9 @@
package ij_plugins.imagej_launcher
import scala.scalanative.unsafe.extern
object Native:
@extern
object mem:
def determineTotalSystemMemory(): Long = extern