Add super shell options

This commit adds a few options to supershell:
1. Max items -- sets the max number of tasks to display in the progress
   reports. It is pretty hard to read more than a few items in the
   progress reports so I set the default limit to 8 and made that
   configurable via the superShellMaxTasks parameter. If there are more
   than the limit, there is an additional line telling how many additional
   tasks are running
2. sleep -- sets how long to sleep between reports. The default is 500ms
   to ensure that it updates at least once per second but the previous
   value of 100ms is more frequent than necessary
3. threshold -- sets the minimum duration a task has to run before being
   printed in the progress reports. The default threshold is increased
   from 10ms to 100ms. This introduces a delay of threshold milliseconds
   before any progress lines appear and also means that if no tasks ever
   exceed the threshold, then no progress is ever displayed.
This commit is contained in:
Ethan Atkins 2020-08-09 17:39:15 -07:00
parent 102e3d1969
commit d58aab5d84
10 changed files with 67 additions and 21 deletions

View File

@ -26,14 +26,16 @@ private[sbt] final class ProgressState(
val padding: AtomicInteger,
val blankZone: Int,
val currentLineBytes: AtomicReference[ArrayBuffer[Byte]],
val maxItems: Int,
) {
def this(blankZone: Int) =
this(
new AtomicReference(Nil),
new AtomicInteger(0),
blankZone,
new AtomicReference(new ArrayBuffer[Byte]),
)
def this(blankZone: Int, maxItems: Int) = this(
new AtomicReference(Nil),
new AtomicInteger(0),
blankZone,
new AtomicReference(new ArrayBuffer[Byte]),
maxItems,
)
def this(blankZone: Int) = this(blankZone, 8)
def currentLine: Option[String] =
new String(currentLineBytes.get.toArray, "UTF-8").linesIterator.toSeq.lastOption
.map(EscHelpers.stripColorsAndMoves)
@ -162,14 +164,18 @@ private[sbt] object ProgressState {
terminal.withPrintStream { ps =>
val commandFromThisTerminal = pe.channelName.fold(true)(_ == terminal.name)
val info = if (commandFromThisTerminal) {
pe.items.map { item =>
val base = pe.items.map { item =>
val elapsed = item.elapsedMicros / 1000000L
s" | => ${item.name} ${elapsed}s"
}
val limit = state.maxItems
if (base.size > limit)
s" | ... (${base.size - limit} other tasks)" +: base.takeRight(limit)
else base
} else {
pe.command.toSeq.flatMap { cmd =>
val width = terminal.getWidth
val sanitized = if ((cmd.length + SERVER_IS_RUNNING_LENGTH) < width) {
val sanitized = if ((cmd.length + SERVER_IS_RUNNING_LENGTH) > width) {
if (SERVER_IS_RUNNING_LENGTH + cmd.length < width) cmd
else cmd.take(MIN_COMMAND_WIDTH) + "..."
} else cmd

View File

@ -141,7 +141,7 @@ trait Terminal extends AutoCloseable {
private[sbt] def withPrintStream[T](f: PrintStream => T): T
private[sbt] def withRawOutput[R](f: => R): R
private[sbt] def restore(): Unit = {}
private[sbt] val progressState = new ProgressState(1)
private[sbt] def progressState: ProgressState
private[this] val promptHolder: AtomicReference[Prompt] = new AtomicReference(Prompt.Pending)
private[sbt] final def prompt: Prompt = promptHolder.get
private[sbt] final def setPrompt(newPrompt: Prompt): Unit =
@ -315,6 +315,7 @@ object Terminal {
private[this] object ProxyTerminal extends Terminal {
private def t: Terminal = activeTerminal.get
override private[sbt] def progressState: ProgressState = t.progressState
override def getWidth: Int = t.getWidth
override def getHeight: Int = t.getHeight
override def getLineHeightAndWidth(line: String): (Int, Int) = t.getLineHeightAndWidth(line)
@ -755,6 +756,9 @@ object Terminal {
private val capabilityMap =
org.jline.utils.InfoCmp.Capability.values().map(c => c.toString -> c).toMap
private val consoleProgressState = new AtomicReference[ProgressState](new ProgressState(1))
private[sbt] def setConsoleProgressState(progressState: ProgressState): Unit =
consoleProgressState.set(progressState)
@deprecated("For compatibility only", "1.4.0")
private[sbt] def deprecatedTeminal: jline.Terminal = console.toJLine
@ -770,6 +774,7 @@ object Terminal {
}
private[this] val isCI = sys.env.contains("BUILD_NUMBER") || sys.env.contains("CI")
override lazy val isAnsiSupported: Boolean = term.isAnsiSupported && !isCI
override private[sbt] def progressState: ProgressState = consoleProgressState.get
override def isEchoEnabled: Boolean = system.echo()
override def isSuccessEnabled: Boolean = true
override def getBooleanCapability(capability: String, jline3: Boolean): Boolean =
@ -912,6 +917,7 @@ object Terminal {
new WriteableInputStream(nullInputStream, "null-writeable-input-stream")
private[sbt] val NullTerminal = new Terminal {
override def close(): Unit = {}
override private[sbt] def progressState: ProgressState = new ProgressState(1)
override def getBooleanCapability(capability: String, jline3: Boolean): Boolean = false
override def getHeight: Int = 0
override def getLastLine: Option[String] = None

View File

@ -97,7 +97,7 @@ import sjsonnew._
import sjsonnew.support.scalajson.unsafe.Converter
import scala.collection.immutable.ListMap
import scala.concurrent.duration.FiniteDuration
import scala.concurrent.duration._
import scala.util.control.NonFatal
import scala.xml.NodeSeq
@ -393,11 +393,15 @@ object Defaults extends BuildCommon {
else appConfiguration.value.provider.scalaProvider.launcher.topLoader.getParent
},
useSuperShell := { if (insideCI.value) false else Terminal.console.isSupershellEnabled },
superShellThreshold :== SysProp.supershellThreshold,
superShellMaxTasks :== SysProp.supershellMaxTasks,
superShellSleep :== SysProp.supershellSleep.millis,
progressReports := {
val rs = EvaluateTask.taskTimingProgress.toVector ++ EvaluateTask.taskTraceEvent.toVector
rs map { Keys.TaskProgress(_) }
},
progressState := Some(new ProgressState(SysProp.supershellBlankZone)),
// progressState is deprecated
SettingKey[Option[ProgressState]]("progressState") := None,
Previous.cache := new Previous(
Def.streamsManagerKey.value,
Previous.references.value.getReferences

View File

@ -557,9 +557,13 @@ object Keys {
private[sbt] val currentTaskProgress = AttributeKey[TaskProgress]("current-task-progress")
private[sbt] val taskProgress = AttributeKey[sbt.internal.TaskProgress]("active-task-progress")
val useSuperShell = settingKey[Boolean]("Enables (true) or disables the super shell.")
val superShellMaxTasks = settingKey[Int]("The max number of tasks to display in the supershell progress report")
val superShellSleep = settingKey[FiniteDuration]("The minimum duration to sleep between progress reports")
val superShellThreshold = settingKey[FiniteDuration]("The minimum amount of time a task must be running to appear in the supershell progress report")
val turbo = settingKey[Boolean]("Enables (true) or disables optional performance features.")
// This key can be used to add custom ExecuteProgress instances
val progressReports = settingKey[Seq[TaskProgress]]("A function that returns a list of progress reporters.").withRank(DTask)
@deprecated("unused", "1.4.0")
private[sbt] val progressState = settingKey[Option[ProgressState]]("The optional progress state if supershell is enabled.").withRank(Invisible)
private[sbt] val postProgressReports = settingKey[Unit]("Internally used to modify logger.").withRank(DTask)
@deprecated("No longer used", "1.3.0")

View File

@ -935,13 +935,25 @@ object BuiltinCommands {
// This is a workaround for the console task in dotty which uses the classloader cache.
// We need to override the top loader in that case so that it gets the forked jline.
s5.extendedClassLoaderCache.setParent(Project.extract(s5).get(Keys.scalaInstanceTopLoader))
CheckBuildSources.init(LintUnused.lintUnusedFunc(s5))
addSuperShellParams(CheckBuildSources.init(LintUnused.lintUnusedFunc(s5)))
}
private val setupGlobalFileTreeRepository: State => State = { state =>
state.get(sbt.nio.Keys.globalFileTreeRepository).foreach(_.close())
state.put(sbt.nio.Keys.globalFileTreeRepository, FileTreeRepository.default)
}
private val addSuperShellParams: State => State = (s: State) => {
val extracted = Project.extract(s)
import scala.concurrent.duration._
val sleep = extracted.getOpt(Keys.superShellSleep).getOrElse(SysProp.supershellSleep.millis)
val threshold =
extracted.getOpt(Keys.superShellThreshold).getOrElse(SysProp.supershellThreshold)
val maxItems = extracted.getOpt(Keys.superShellMaxTasks).getOrElse(SysProp.supershellMaxTasks)
Terminal.setConsoleProgressState(new ProgressState(1, maxItems))
s.put(Keys.superShellSleep.key, sleep)
.put(Keys.superShellThreshold.key, threshold)
.put(Keys.superShellMaxTasks.key, maxItems)
}
private val addCacheStoreFactoryFactory: State => State = (s: State) => {
val size = Project
.extract(s)

View File

@ -22,8 +22,10 @@ import sbt.protocol._
import sbt.util.{ Logger, LoggerContext }
import scala.annotation.tailrec
import scala.concurrent.duration._
import scala.util.control.NonFatal
import sbt.internal.FastTrackCommands
import sbt.internal.SysProp
object MainLoop {
@ -150,7 +152,11 @@ object MainLoop {
def next(state: State): State = {
val context = LoggerContext(useLog4J = state.get(Keys.useLog4J.key).getOrElse(false))
val taskProgress = new TaskProgress
val superShellSleep =
state.get(Keys.superShellSleep.key).getOrElse(SysProp.supershellSleep.millis)
val superShellThreshold =
state.get(Keys.superShellThreshold.key).getOrElse(SysProp.supershellThreshold)
val taskProgress = new TaskProgress(superShellSleep, superShellThreshold)
try {
ErrorHandling.wideConvert {
state

View File

@ -201,7 +201,8 @@ private[sbt] final class CommandExchange {
instance,
handlers,
s.log,
mkAskUser(name)
mkAskUser(name),
Option(lastState.get),
)
subscribe(channel)
}

View File

@ -104,7 +104,9 @@ object SysProp {
def dumbTerm: Boolean = sys.env.get("TERM").contains("dumb")
def supershell: Boolean = booleanOpt("sbt.supershell").getOrElse(!dumbTerm && color)
def supershellSleep: Long = long("sbt.supershell.sleep", 100L)
def supershellMaxTasks: Int = int("sbt.supershell.maxitems", 8)
def supershellSleep: Long = long("sbt.supershell.sleep", 500.millis.toMillis)
def supershellThreshold: FiniteDuration = long("sbt.supershell.threshold", 100L).millis
def supershellBlankZone: Int = int("sbt.supershell.blankzone", 1)
def defaultUseCoursier: Boolean = {

View File

@ -20,13 +20,11 @@ import java.util.concurrent.{ ConcurrentHashMap, Executors, TimeoutException }
/**
* implements task progress display on the shell.
*/
private[sbt] class TaskProgress
private[sbt] class TaskProgress(sleepDuration: FiniteDuration, threshold: FiniteDuration)
extends AbstractTaskExecuteProgress
with ExecuteProgress[Task]
with AutoCloseable {
private[this] val lastTaskCount = new AtomicInteger(0)
private[this] val sleepDuration = SysProp.supershellSleep.millis
private[this] val threshold = 10.millis
private[this] val reportLoop = new AtomicReference[AutoCloseable]
private[this] val active = new ConcurrentHashMap[Task[_], AutoCloseable]
private[this] val nextReport = new AtomicReference(Deadline.now)

View File

@ -49,6 +49,7 @@ import sjsonnew.support.scalajson.unsafe.{ CompactPrinter, Converter }
import BasicJsonProtocol._
import Serialization.{ attach, promptChannel }
import sbt.internal.util.ProgressState
final class NetworkChannel(
val name: String,
@ -58,7 +59,8 @@ final class NetworkChannel(
instance: ServerInstance,
handlers: Seq[ServerHandler],
val log: Logger,
mkUIThreadImpl: (State, CommandChannel) => UITask
mkUIThreadImpl: (State, CommandChannel) => UITask,
state: Option[State],
) extends CommandChannel { self =>
def this(
name: String,
@ -77,7 +79,8 @@ final class NetworkChannel(
instance,
handlers,
log,
new UITask.AskUserTask(_, _)
new UITask.AskUserTask(_, _),
None
)
private val running = new AtomicBoolean(true)
@ -787,6 +790,10 @@ final class NetworkChannel(
)
}
private[this] val blockedThreads = ConcurrentHashMap.newKeySet[Thread]
override private[sbt] val progressState: ProgressState = new ProgressState(
1,
state.flatMap(_.get(Keys.superShellMaxTasks.key)).getOrElse(SysProp.supershellMaxTasks)
)
override def getWidth: Int = getProperty(_.width, 0).getOrElse(0)
override def getHeight: Int = getProperty(_.height, 0).getOrElse(0)
override def isAnsiSupported: Boolean = getProperty(_.isAnsiSupported, false).getOrElse(false)