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,29 +124,8 @@ 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)
@ -147,21 +133,35 @@ class Launcher(logger: Logger) {
val notSupportedError = s"$osName $osArch not supported"
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
if osArch.toLowerCase.contains("aarch64") then
Right("aarch64")
Right("macosx")
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,17 +7,17 @@ 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):

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"))
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