[java, compilation-charts] support compilation charts for JPS and Maven IDEA-346294

GitOrigin-RevId: 1a81a298541f02ae165dfb8327b5899614f1d195
This commit is contained in:
Aleksey Dobrynin
2024-05-07 13:45:07 +02:00
committed by intellij-monorepo-bot
parent 1d1c6f0f38
commit 2edba4709f
23 changed files with 1474 additions and 0 deletions

2
.idea/modules.xml generated
View File

@@ -449,6 +449,8 @@
<module fileurl="file://$PROJECT_DIR$/plugins/ByteCodeViewer/intellij.java.byteCodeViewer.iml" filepath="$PROJECT_DIR$/plugins/ByteCodeViewer/intellij.java.byteCodeViewer.iml" />
<module fileurl="file://$PROJECT_DIR$/java/compiler/openapi/intellij.java.compiler.iml" filepath="$PROJECT_DIR$/java/compiler/openapi/intellij.java.compiler.iml" />
<module fileurl="file://$PROJECT_DIR$/java/compiler/javac2/intellij.java.compiler.antTasks.iml" filepath="$PROJECT_DIR$/java/compiler/javac2/intellij.java.compiler.antTasks.iml" />
<module fileurl="file://$PROJECT_DIR$/java/compiler/charts/intellij.java.compiler.charts.iml" filepath="$PROJECT_DIR$/java/compiler/charts/intellij.java.compiler.charts.iml" />
<module fileurl="file://$PROJECT_DIR$/java/compiler/charts/jps-plugin/intellij.java.compiler.charts.jps.iml" filepath="$PROJECT_DIR$/java/compiler/charts/jps-plugin/intellij.java.compiler.charts.jps.iml" />
<module fileurl="file://$PROJECT_DIR$/java/compiler/impl/intellij.java.compiler.impl.iml" filepath="$PROJECT_DIR$/java/compiler/impl/intellij.java.compiler.impl.iml" />
<module fileurl="file://$PROJECT_DIR$/java/compiler/instrumentation-util/intellij.java.compiler.instrumentationUtil.iml" filepath="$PROJECT_DIR$/java/compiler/instrumentation-util/intellij.java.compiler.instrumentationUtil.iml" />
<module fileurl="file://$PROJECT_DIR$/java/compiler/instrumentation-util-8/intellij.java.compiler.instrumentationUtil.java8.iml" filepath="$PROJECT_DIR$/java/compiler/instrumentation-util-8/intellij.java.compiler.instrumentationUtil.java8.iml" />

View File

@@ -63,6 +63,7 @@ suspend fun buildCommunityStandaloneJpsBuilder(targetDir: Path,
layout.withModule("intellij.maven.jps", "maven-jps.jar")
layout.withModule("intellij.java.compiler.charts.jps", "java-compiler-charts-jps.jar")
layout.withModule("intellij.java.aetherDependencyResolver", "aether-dependency-resolver.jar")
layout.withModule("intellij.gradle.jps", "gradle-jps.jar")

View File

@@ -218,5 +218,6 @@
<orderEntry type="module" module-name="intellij.platform.testFramework.junit5Jimfs" scope="TEST" />
<orderEntry type="module" module-name="intellij.yaml.editing" scope="RUNTIME" />
<orderEntry type="module" module-name="intellij.platform.whatsNew" scope="TEST" />
<orderEntry type="module" module-name="intellij.java.compiler.charts" scope="RUNTIME" />
</component>
</module>

View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<module relativePaths="true" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="kotlin-language" name="Kotlin">
<configuration version="5" platform="JVM 11" allPlatforms="JVM [11]" useProjectSettings="false">
<compilerSettings>
<option name="additionalArguments" value="-Xjvm-default=all -opt-in=com.intellij.openapi.util.IntellijInternalApi" />
</compilerSettings>
<compilerArguments>
<stringArguments>
<stringArg name="jvmTarget" arg="11" />
<stringArg name="apiVersion" arg="1.9" />
<stringArg name="languageVersion" arg="1.9" />
</stringArguments>
</compilerArguments>
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_11" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="intellij.java.compiler.impl" />
<orderEntry type="module" module-name="intellij.platform.jps.build" />
<orderEntry type="module" module-name="intellij.platform.extensions" />
<orderEntry type="module" module-name="intellij.platform.core" />
<orderEntry type="module" module-name="intellij.platform.lang" />
<orderEntry type="module" module-name="intellij.platform.lang.impl" />
<orderEntry type="library" name="jackson" level="project" />
<orderEntry type="library" name="jackson-databind" level="project" />
<orderEntry type="library" name="rd-core" level="project" />
<orderEntry type="library" name="rd-swing" level="project" />
<orderEntry type="library" name="kotlinx-coroutines-core" level="project" />
<orderEntry type="module" module-name="intellij.platform.rd.community" />
<orderEntry type="library" name="kotlinx-collections-immutable" level="project" />
<orderEntry type="module" module-name="intellij.java.compiler.charts.jps" />
<orderEntry type="module" module-name="intellij.platform.core.ui" />
</component>
</module>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<module relativePaths="true" type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_11" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="intellij.platform.jps.build" />
<orderEntry type="library" name="gson" level="project" />
<orderEntry type="module" module-name="intellij.platform.jps.model" />
</component>
</module>

View File

@@ -0,0 +1,2 @@
# Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
com.intellij.compiler.charts.jps.ChartsBuilderService

View File

@@ -0,0 +1,95 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.compiler.charts.jps;
import com.intellij.openapi.util.NlsSafe;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.concurrency.AppExecutorUtil;
import com.sun.management.OperatingSystemMXBean;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.ModuleChunk;
import org.jetbrains.jps.builders.DirtyFilesHolder;
import org.jetbrains.jps.builders.java.JavaSourceRootDescriptor;
import org.jetbrains.jps.incremental.*;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public class ChartsBuilderService extends BuilderService {
public static String COMPILATION_STATISTIC_BUILDER_ID = "jps.compile.statistic";
@Override
public @NotNull List<? extends ModuleLevelBuilder> createModuleLevelBuilders() {
return List.of(new ChartsModuleLevelBuilder());
}
private static class ChartsModuleLevelBuilder extends ModuleLevelBuilder {
private ScheduledFuture<?> myStatisticsReporter = null;
private Runnable myStatisticsRunnable = null;
protected ChartsModuleLevelBuilder() {
super(BuilderCategory.TRANSLATOR);
}
@Override
public ExitCode build(@NotNull CompileContext context,
@NotNull ModuleChunk chunk,
@NotNull DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget> dirtyFilesHolder,
@NotNull OutputConsumer outputConsumer) throws ProjectBuildException, IOException {
return ExitCode.NOTHING_DONE;
}
@Override
public @NotNull List<String> getCompilableFileExtensions() {
return List.of();
}
@Nls(capitalization = Nls.Capitalization.Sentence)
@Override
public @NotNull String getPresentableName() {
return StringUtil.capitalize(getBuilderName());
}
public static @NotNull @NlsSafe String getBuilderName() {
return "charts";
}
@Override
public void buildStarted(@NotNull CompileContext context) {
final MemoryMXBean memory = ManagementFactory.getMemoryMXBean();
final OperatingSystemMXBean os = ManagementFactory.getPlatformMXBean(OperatingSystemMXBean.class);
myStatisticsRunnable = () -> context.processMessage(CompileStatisticBuilderMessage.create(memory, os));
myStatisticsReporter = AppExecutorUtil.createBoundedScheduledExecutorService("IncProjectBuilder metrics reporter", 1)
.scheduleWithFixedDelay(myStatisticsRunnable, 0, 100, TimeUnit.MILLISECONDS);
}
@Override
public void buildFinished(@NotNull CompileContext context) {
if (myStatisticsRunnable != null) {
myStatisticsRunnable.run();
myStatisticsRunnable = null;
}
if (myStatisticsReporter != null) {
myStatisticsReporter.cancel(true);
myStatisticsReporter = null;
}
}
@Override
public void chunkBuildStarted(@NotNull CompileContext context, @NotNull ModuleChunk chunk) {
context.processMessage(CompileStatisticBuilderMessage.create(chunk.getTargets(), "STARTED"));
if (myStatisticsRunnable != null) myStatisticsRunnable.run();
}
@Override
public void chunkBuildFinished(@NotNull CompileContext context, @NotNull ModuleChunk chunk) {
context.processMessage(CompileStatisticBuilderMessage.create(chunk.getTargets(), "FINISHED"));
if (myStatisticsRunnable != null) myStatisticsRunnable.run();
}
}
}

View File

@@ -0,0 +1,87 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.compiler.charts.jps;
import com.google.gson.Gson;
import com.intellij.util.containers.ContainerUtil;
import com.sun.management.OperatingSystemMXBean;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.builders.BuildTarget;
import org.jetbrains.jps.builders.ModuleBasedTarget;
import org.jetbrains.jps.builders.java.JavaModuleBuildTargetType;
import org.jetbrains.jps.incremental.messages.BuildMessage;
import org.jetbrains.jps.incremental.messages.CustomBuilderMessage;
import java.lang.management.MemoryMXBean;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
import static com.intellij.compiler.charts.jps.ChartsBuilderService.COMPILATION_STATISTIC_BUILDER_ID;
public class CompileStatisticBuilderMessage extends CustomBuilderMessage {
private static final Gson JSON = new Gson();
private CompileStatisticBuilderMessage(@NotNull String messageType, @NotNull String data) {
super(COMPILATION_STATISTIC_BUILDER_ID, messageType, data);
}
@NotNull
public static CompileStatisticBuilderMessage create(@NotNull Set<? extends BuildTarget<?>> targets,
@NotNull String event) {
List<CompileStatisticBuilderMessage.TargetEvent>
events = ContainerUtil.map(targets, target -> map(target, event.equals("STARTED")
? CompileStatisticBuilderMessage.StartTarget::new
: CompileStatisticBuilderMessage.FinishTarget::new));
return new CompileStatisticBuilderMessage(event, JSON.toJson(events));
}
@NotNull
private static <T extends CompileStatisticBuilderMessage.TargetEvent> T map(@NotNull BuildTarget<?> target,
@NotNull Supplier<T> event) {
T data = event.get();
data.name = target instanceof ModuleBasedTarget
? ((ModuleBasedTarget<?>)target).getModule().getName() :
target.getId();
data.type = target.getTargetType().getTypeId();
data.isFileBased = target.getTargetType().isFileBased();
data.isTest = target.getTargetType() instanceof JavaModuleBuildTargetType &&
((JavaModuleBuildTargetType)target.getTargetType()).isTests();
return data;
}
@NotNull
public static BuildMessage create(@NotNull MemoryMXBean memory, @NotNull OperatingSystemMXBean os) {
CompileStatisticBuilderMessage.CpuMemoryStatistics
statistics = new CompileStatisticBuilderMessage.CpuMemoryStatistics();
statistics.heapUsed = Math.max(memory.getHeapMemoryUsage().getUsed(), 0);
statistics.heapMax = Math.max(memory.getHeapMemoryUsage().getMax(), 0);
statistics.nonHeapUsed = Math.max(memory.getNonHeapMemoryUsage().getUsed(), 0);
statistics.nonHeapMax = Math.max(memory.getNonHeapMemoryUsage().getMax(), 0);
statistics.cpu = Math.max(os.getProcessCpuLoad() * 100, 0);
return new CompileStatisticBuilderMessage("STATISTIC", JSON.toJson(statistics));
}
public static abstract class TargetEvent {
public String name;
public String type;
public boolean isTest;
public boolean isFileBased;
public long time = System.nanoTime();
public long thread = Thread.currentThread().getId();
}
public static class StartTarget extends CompileStatisticBuilderMessage.TargetEvent {
}
public static class FinishTarget extends CompileStatisticBuilderMessage.TargetEvent {
}
public static class CpuMemoryStatistics {
public long heapUsed;
public long heapMax;
public long nonHeapUsed;
public long nonHeapMax;
public double cpu;
public long time = System.nanoTime();
}
}

View File

@@ -0,0 +1,7 @@
<idea-plugin package="com.intellij.compiler.charts">
<module value="intellij.java.compiler.charts" />
<extensions defaultExtensionNs="com.intellij">
<postStartupActivity implementation="com.intellij.compiler.charts.CompilationChartsProjectActivity"/>
<compileServer.plugin classpath="jps/java-compiler-charts-jps.jar"/>
</extensions>
</idea-plugin>

View File

@@ -0,0 +1,8 @@
charts.module.info=<html> <body style='padding: 5px;'> <b>{0}</b> <br /> <b>duration</b> {1} </body> </html>
charts.tab.name=Chart\u2026
charts.module=Module
charts.reset=Reset
charts.production.type=Production
charts.test.type=Test
charts.memory.type=Memory
charts.cpu.type=CPU

View File

@@ -0,0 +1,19 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.compiler.charts;
import com.intellij.DynamicBundle;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.PropertyKey;
public final class CompilationChartsBundle {
public static final @NonNls String BUNDLE = "messages.CompilationChartsBundle";
private static final DynamicBundle INSTANCE = new DynamicBundle(CompilationChartsBundle.class, BUNDLE);
private CompilationChartsBundle() {}
public static @NotNull @Nls String message(@NotNull @PropertyKey(resourceBundle = BUNDLE) String key, Object @NotNull ... params) {
return INSTANCE.getMessage(key, params);
}
}

View File

@@ -0,0 +1,89 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.compiler.charts
import com.fasterxml.jackson.core.JsonFactory
import com.fasterxml.jackson.core.JsonProcessingException
import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.databind.ObjectMapper
import com.intellij.build.BuildProgressListener
import com.intellij.build.BuildViewManager
import com.intellij.build.events.FinishBuildEvent
import com.intellij.build.events.StartBuildEvent
import com.intellij.compiler.charts.jps.ChartsBuilderService.COMPILATION_STATISTIC_BUILDER_ID
import com.intellij.compiler.charts.jps.CompileStatisticBuilderMessage.*
import com.intellij.compiler.charts.ui.CompilationChartsBuildEvent
import com.intellij.compiler.server.CustomBuilderMessageHandler
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project
import com.intellij.openapi.startup.ProjectActivity
import com.intellij.openapi.util.Disposer
import com.intellij.util.messages.MessageBusConnection
import java.util.*
class CompilationChartsProjectActivity : ProjectActivity {
companion object {
private val LOG: Logger = Logger.getInstance(CompilationChartsProjectActivity::class.java)
}
override suspend fun execute(project: Project) {
val view = project.getService(BuildViewManager::class.java)
val disposable = Disposer.newDisposable(view, "Compilation charts event listener disposable")
val connection: MessageBusConnection = project.messageBus.connect()
val handler = CompilationChartsMessageHandler()
connection.subscribe(CustomBuilderMessageHandler.TOPIC, handler)
view.addListener(BuildProgressListener { buildId, event ->
when (event) {
is StartBuildEvent -> {
val title = event.buildDescriptor.title.lowercase()
if (title.contains("up-to-date") || title.startsWith("worksheet")) return@BuildProgressListener
val chartEvent = CompilationChartsBuildEvent(buildId)
view.onEvent(buildId, chartEvent)
handler.addState(chartEvent.vm());
}
is FinishBuildEvent -> handler.removeState()
}
}, disposable)
}
private class CompilationChartsMessageHandler : CustomBuilderMessageHandler {
private val json = ObjectMapper(JsonFactory())
private val states: Queue<CompilationChartsViewModel> = ArrayDeque()
private var currentState: CompilationChartsViewModel? = null
fun addState(vm: CompilationChartsViewModel) {
states.add(vm)
if (currentState == null) removeState()
}
fun removeState() {
currentState = states.poll()
}
override fun messageReceived(builderId: String?, messageType: String?, messageText: String?) {
if (builderId != COMPILATION_STATISTIC_BUILDER_ID) return
try {
when (messageType) {
"STARTED" -> {
val values = json.readValue(messageText, object : TypeReference<List<StartTarget>>() {})
currentState?.started(values)
}
"FINISHED" -> {
val values = json.readValue(messageText, object : TypeReference<List<FinishTarget>>() {})
currentState?.finished(values)
}
"STATISTIC" -> {
val value = json.readValue(messageText, CpuMemoryStatistics::class.java)
currentState?.statistic(value)
}
}
}
catch (e: JsonProcessingException) {
LOG.warn("Failed to parse message: $messageText", e)
}
}
}
}

View File

@@ -0,0 +1,123 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.compiler.charts
import com.intellij.compiler.charts.CompilationChartsViewModel.Modules.EventKey
import com.intellij.compiler.charts.jps.CompileStatisticBuilderMessage.*
import com.jetbrains.rd.framework.impl.RdList
import com.jetbrains.rd.framework.impl.RdMap
import com.jetbrains.rd.framework.impl.RdProperty
import com.jetbrains.rd.util.lifetime.Lifetime
import com.jetbrains.rd.util.reactive.IViewableMap
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
import java.util.concurrent.ConcurrentHashMap
import java.util.function.Predicate
class CompilationChartsViewModel(val lifetime: Lifetime) {
val modules: Modules = Modules(Long.MAX_VALUE, 0, 0, RdMap())
val statistics: Statistics = Statistics()
val cpuMemory: RdProperty<CpuMemoryStatisticsType> = RdProperty(CpuMemoryStatisticsType.MEMORY)
val filter: RdProperty<Filter> = RdProperty(Filter())
private val threadIndexes: MutableMap<Long, Int> = ConcurrentHashMap()
fun started(values: List<StartTarget>) {
values.forEach { value ->
modules.add(Modules.StartEvent(value, index(value.thread)))
}
}
fun finished(values: List<FinishTarget>) {
values.forEach { value ->
modules.add(Modules.FinishEvent(value, index(value.thread)))
}
}
private fun index(threadId: Long): Int {
var index: Int? = threadIndexes[threadId]
if (index != null) return index
synchronized(threadIndexes) {
index = threadIndexes[threadId]
if (index != null) return index!!
index = threadIndexes.size
threadIndexes[threadId] = index!!
return index!!
}
}
fun statistic(value: CpuMemoryStatistics) {
if (statistics.maxMemory < value.heapUsed) statistics.maxMemory = value.heapUsed
if (statistics.start > value.time) statistics.start = value.time
if (statistics.end < value.time) statistics.end = value.time
statistics.cpu.add(StatisticData(value.time, value.cpu.toLong()))
statistics.memoryMax.add(StatisticData(value.time, value.heapMax))
statistics.memoryUsed.add(StatisticData(value.time, value.heapUsed))
}
data class Modules(var start: Long, var end: Long, var threadCount: Int, private val events: RdMap<EventKey, PersistentList<Event>>) {
fun add(event: Event) {
if (start > event.target.time) start = event.target.time
if (end < event.target.time) end = event.target.time
if (threadCount <= event.threadNumber) threadCount = event.threadNumber + 1
events.compute(event.key) { _, list ->
list?.add(event) ?: persistentListOf(event)
}
}
fun get(): IViewableMap<EventKey, List<Event>> = events
interface Event {
val target: TargetEvent
val threadNumber: Int
val key: EventKey
get() = EventKey(target.name, target.type, target.isTest)
}
data class EventKey(val name: String, val type: String, val test: Boolean)
data class StartEvent(override val target: StartTarget, override val threadNumber: Int) : Event
data class FinishEvent(override val target: FinishTarget, override val threadNumber: Int) : Event
}
data class StatisticData(val time: Long, val data: Long) : Comparable<StatisticData> {
override fun compareTo(other: StatisticData): Int = time.compareTo(other.time)
}
data class Statistics(val memoryUsed: RdList<StatisticData> = RdList(),
val memoryMax: RdList<StatisticData> = RdList(),
val cpu: RdList<StatisticData> = RdList(),
var maxMemory: Long = 0,
var start: Long = Long.MAX_VALUE,
var end: Long = 0)
data class ViewModules(var filter: Predicate<EventKey> = Predicate<EventKey> { _ -> true },
val data: MutableMap<EventKey, List<Modules.Event>> = ConcurrentHashMap()) {
fun data(): Map<EventKey, List<Modules.Event>> = data.filter { filter.test(it.key) }
}
data class Filter(val text: List<String> = listOf(), val production: Boolean = true, val test: Boolean = true) : Predicate<EventKey> {
fun setText(text: List<String>): Filter = Filter(text, production, test)
fun setProduction(production: Boolean): Filter = Filter(text, production, test)
fun setTest(test: Boolean): Filter = Filter(text, production, test)
override fun test(key: EventKey): Boolean {
if (text.isNotEmpty()) {
if (!text.all { key.name.contains(it) }) return false
}
if (key.test) {
if (!test) return false
}
else {
if (!production) return false
}
return true
}
}
enum class CpuMemoryStatisticsType {
CPU, MEMORY
}
}

View File

@@ -0,0 +1,175 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.compiler.charts.ui
import com.intellij.compiler.charts.CompilationChartsBundle
import com.intellij.compiler.charts.CompilationChartsViewModel
import com.intellij.icons.AllIcons
import com.intellij.openapi.ui.popup.IconButton
import com.intellij.ui.components.JBLabel
import com.intellij.ui.components.JBPanel
import com.intellij.ui.components.JBTextField
import com.intellij.ui.components.fields.ExtendableTextComponent
import com.intellij.ui.components.fields.ExtendableTextField
import com.intellij.ui.scale.JBUIScale.scale
import com.intellij.util.ui.JBUI
import com.intellij.util.ui.components.BorderLayoutPanel
import java.awt.BorderLayout
import java.awt.Dimension
import java.awt.FlowLayout
import java.awt.event.KeyAdapter
import java.awt.event.KeyEvent
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import javax.swing.BorderFactory
import javax.swing.BoxLayout
import javax.swing.JPanel
class ActionPanel(private val vm: CompilationChartsViewModel) : BorderLayoutPanel() {
private val searchField: JBTextField = object : ExtendableTextField() {
val reset = Runnable {
vm.filter.set(vm.filter.value.setText(listOf()))
text = ""
}
init {
setExtensions(object : ExtendableTextComponent.Extension {
override fun getIcon(hovered: Boolean) = AllIcons.Actions.Search
override fun isIconBeforeText() = true
override fun getIconGap() = scale(6)
}, object : ExtendableTextComponent.Extension {
override fun getIcon(hovered: Boolean) = IconButton(
CompilationChartsBundle.message("charts.reset"),
AllIcons.Actions.Close,
AllIcons.Actions.CloseHovered
)
override fun isIconBeforeText() = false
override fun getIconGap() = scale(6)
override fun getActionOnClick() = reset
})
addActionListener { _ ->
val words = this.text.split(" ").filter { it.isNotBlank() }.map { it.trim() }
if (words.isEmpty()) {
vm.filter.set(vm.filter.value.setText(listOf()))
}
else {
vm.filter.set(vm.filter.value.setText(words))
}
}
addKeyListener(object : KeyAdapter() {
override fun keyPressed(e: KeyEvent) {
if (e.keyCode == KeyEvent.VK_ESCAPE) reset.run()
}
})
border = JBUI.Borders.empty(1)
BoxLayout(this, BoxLayout.LINE_AXIS)
}
}
init {
border = JBUI.Borders.emptyBottom(2)
layout = BorderLayout()
// module name
addToLeft(JPanel().apply {
layout = BoxLayout(this, BoxLayout.LINE_AXIS)
border = JBUI.Borders.empty(2)
add(JBLabel(CompilationChartsBundle.message("charts.module")))
add(searchField)
})
// legend
addToRight(JPanel().apply {
layout = BoxLayout(this, BoxLayout.LINE_AXIS)
border = JBUI.Borders.empty(2)
add(JBPanel<JBPanel<*>>(FlowLayout(FlowLayout.LEFT)).apply {
val block = JBLabel().apply {
preferredSize = Dimension(10, 10)
isOpaque = true
background = COLOR_PRODUCTION_BLOCK
border = BorderFactory.createLineBorder(COLOR_PRODUCTION_BORDER, 1)
}
add(block)
add(JBLabel(CompilationChartsBundle.message("charts.production.type")))
addMouseListener(object : MouseAdapter() {
override fun mouseEntered(e: MouseEvent) {
block.border = BorderFactory.createLineBorder(COLOR_PRODUCTION_BORDER_SELECTED, 1)
}
override fun mouseExited(e: MouseEvent) {
block.border = BorderFactory.createLineBorder(COLOR_PRODUCTION_BORDER, 1)
}
override fun mouseClicked(e: MouseEvent) {
vm.filter.set(vm.filter.value.setProduction(!vm.filter.value.production))
if (vm.filter.value.production)
block.background = COLOR_PRODUCTION_BLOCK
else
block.background = COLOR_PRODUCTION_BLOCK_DISABLED
}
})
})
add(JBPanel<JBPanel<*>>(FlowLayout(FlowLayout.LEFT)).apply {
val block = JBLabel().apply {
preferredSize = Dimension(10, 10)
isOpaque = true
background = COLOR_TEST_BLOCK
border = BorderFactory.createLineBorder(COLOR_TEST_BORDER)
}
add(block)
add(JBLabel(CompilationChartsBundle.message("charts.test.type")))
addMouseListener(object : MouseAdapter() {
override fun mouseEntered(e: MouseEvent) {
block.border = BorderFactory.createLineBorder(COLOR_TEST_BORDER_SELECTED, 1)
}
override fun mouseExited(e: MouseEvent) {
block.border = BorderFactory.createLineBorder(COLOR_TEST_BORDER, 1)
}
override fun mouseClicked(e: MouseEvent) {
vm.filter.set(vm.filter.value.setTest(!vm.filter.value.test))
if (vm.filter.value.test)
block.background = COLOR_TEST_BLOCK
else
block.background = COLOR_TEST_BLOCK_DISABLED
}
})
})
add(JBPanel<JBPanel<*>>(FlowLayout(FlowLayout.LEFT)).apply {
val block = JBLabel().apply {
preferredSize = Dimension(10, 2)
isOpaque = true
background = COLOR_MEMORY_BORDER
}
val label = JBLabel(CompilationChartsBundle.message("charts.memory.type"))
add(block)
add(label)
addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
when(vm.cpuMemory.value) {
CompilationChartsViewModel.CpuMemoryStatisticsType.MEMORY -> {
label.text = CompilationChartsBundle.message("charts.cpu.type")
block.background = COLOR_CPU_BORDER
vm.cpuMemory.set(CompilationChartsViewModel.CpuMemoryStatisticsType.CPU)
}
CompilationChartsViewModel.CpuMemoryStatisticsType.CPU -> {
label.text = CompilationChartsBundle.message("charts.memory.type")
block.background = COLOR_MEMORY_BORDER
vm.cpuMemory.set(CompilationChartsViewModel.CpuMemoryStatisticsType.MEMORY)
}
}
}
})
})
})
}
}

View File

@@ -0,0 +1,46 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.compiler.charts.ui
import com.intellij.build.events.BuildEventPresentationData
import com.intellij.build.events.PresentableBuildEvent
import com.intellij.build.events.impl.AbstractBuildEvent
import com.intellij.compiler.charts.CompilationChartsBundle
import com.intellij.compiler.charts.CompilationChartsViewModel
import com.intellij.execution.ui.ExecutionConsole
import com.intellij.icons.AllIcons
import com.intellij.openapi.actionSystem.ActionGroup
import com.intellij.openapi.rd.createLifetime
import javax.swing.Icon
import javax.swing.JComponent
class CompilationChartsBuildEvent(buildId: Any) :
AbstractBuildEvent(Any(), buildId, System.currentTimeMillis(), CompilationChartsBundle.message("charts.tab.name")),
PresentableBuildEvent {
private val console: CompilationChartsExecutionConsole by lazy { CompilationChartsExecutionConsole() }
override fun getPresentationData(): BuildEventPresentationData = CompilationChartsPresentationData(console)
fun vm(): CompilationChartsViewModel = console.vm
private class CompilationChartsPresentationData(private val component: ExecutionConsole) : BuildEventPresentationData {
override fun getNodeIcon(): Icon = AllIcons.Actions.Profile
override fun getExecutionConsole(): ExecutionConsole = component
override fun consoleToolbarActions(): ActionGroup? = null
}
private class CompilationChartsExecutionConsole : ExecutionConsole {
val vm: CompilationChartsViewModel = CompilationChartsViewModel(this.createLifetime())
private val _component: JComponent by lazy {
CompilationChartsView(vm)
}
override fun dispose() {
}
override fun getComponent(): JComponent = _component
override fun getPreferredFocusableComponent(): JComponent = _component
}
}

View File

@@ -0,0 +1,120 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.compiler.charts.ui
import com.intellij.compiler.charts.CompilationChartsViewModel
import com.intellij.compiler.charts.CompilationChartsViewModel.CpuMemoryStatisticsType
import com.intellij.ide.ui.UISettings.Companion.setupAntialiasing
import com.intellij.ui.components.JBPanelWithEmptyText
import com.intellij.ui.table.JBTable
import java.awt.Dimension
import java.awt.Graphics
import java.awt.Graphics2D
import java.util.concurrent.ConcurrentSkipListSet
import javax.swing.JViewport
import kotlin.math.exp
class CompilationChartsDiagramsComponent(private val vm: CompilationChartsViewModel,
private val getViewport: () -> JViewport) : JBPanelWithEmptyText() {
companion object {
val ROW_HEIGHT = JBTable().rowHeight * 1.5
}
var modules: CompilationChartsViewModel.ViewModules = CompilationChartsViewModel.ViewModules()
var cpu: MutableSet<CompilationChartsViewModel.StatisticData> = ConcurrentSkipListSet()
var memory: MutableSet<CompilationChartsViewModel.StatisticData> = ConcurrentSkipListSet()
var statistic: Statistic = Statistic()
var cpuMemory: CpuMemoryStatisticsType = CpuMemoryStatisticsType.MEMORY
private val mouseAdapter: CompilationChartsMouseAdapter
private var zoom: Zoom = Zoom()
init {
addMouseWheelListener { e ->
if (e.isControlDown) {
zoom.adjustUser(getViewport(), e.x, exp(e.preciseWheelRotation * -0.05))
this@CompilationChartsDiagramsComponent.repaint()
}
else {
e.component.parent.dispatchEvent(e)
}
}
mouseAdapter = CompilationChartsMouseAdapter(vm, this)
addMouseListener(mouseAdapter)
}
override fun paintComponent(g2d: Graphics) {
if (g2d !is Graphics2D) return
charts(vm, zoom, getViewport()) {
progress {
model = modules.data()
height = ROW_HEIGHT
threads = statistic.threadCount
block {
border = MODULE_BLOCK_BORDER
padding = MODULE_BLOCK_PADDING
color = { m -> if (m.target.isTest) COLOR_TEST_BLOCK else COLOR_PRODUCTION_BLOCK }
outline = { m -> if (m.target.isTest) COLOR_TEST_BORDER else COLOR_PRODUCTION_BORDER }
selected = { m -> if (m.target.isTest) COLOR_TEST_BORDER_SELECTED else COLOR_PRODUCTION_BORDER_SELECTED }
}
background {
color = { row -> if (row % 2 == 0) COLOR_BACKGROUND_EVEN else COLOR_BACKGROUND_ODD }
}
}
usage {
when (cpuMemory) {
CpuMemoryStatisticsType.MEMORY -> {
model = memory
maximum = statistic.maxMemory
unit = "MB"
color {
background = COLOR_MEMORY
border = COLOR_MEMORY_BORDER
}
}
CpuMemoryStatisticsType.CPU -> {
model = cpu
maximum = statistic.maxCpu
unit = "%"
color {
background = COLOR_CPU
border = COLOR_CPU_BORDER
}
}
}
}
axis {
stroke = floatArrayOf(5f, 5f)
distance = AXIS_DISTANCE_PX
count = AXIS_MARKERS_COUNT
height = ROW_HEIGHT
padding = AXIS_TEXT_PADDING
}
settings {
font {
size = FONT_SIZE
color = COLOR_TEXT
}
duration {
from = statistic.start
to = statistic.end
}
background = COLOR_BACKGROUND
line {
color = COLOR_LINE
}
mouse = mouseAdapter
}
}.draw(g2d) {
setupAntialiasing(g2d) // ??
val size = Dimension(width().toInt(), height().toInt())
if (size != this@CompilationChartsDiagramsComponent.preferredSize) {
this@CompilationChartsDiagramsComponent.preferredSize = size
this@CompilationChartsDiagramsComponent.revalidate()
}
}
}
}

View File

@@ -0,0 +1,108 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.compiler.charts.ui
import com.intellij.compiler.charts.CompilationChartsViewModel
import com.intellij.ui.components.JBScrollPane
import com.intellij.util.ui.JBUI
import com.intellij.util.ui.components.BorderLayoutPanel
import com.jetbrains.rd.util.reactive.IViewableList
import java.util.concurrent.atomic.AtomicReference
import javax.swing.JViewport
import javax.swing.ScrollPaneConstants
class CompilationChartsView(private val vm: CompilationChartsViewModel) : BorderLayoutPanel() {
init {
val init = Initialization()
val diagrams = CompilationChartsDiagramsComponent(vm, init.init())
val scroll = JBScrollPane(diagrams).apply {
horizontalScrollBarPolicy = ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED
verticalScrollBarPolicy = ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED
border = JBUI.Borders.empty()
viewport.scrollMode = JViewport.SIMPLE_SCROLL_MODE
name = "compilation-charts-scroll-pane"
init.set(this)
}
val actionPanel = ActionPanel(vm)
addToTop(actionPanel)
addToCenter(scroll)
vm.modules.get().advise(vm.lifetime) { module ->
module.newValueOpt?.let { data ->
diagrams.modules.data[module.key] = data
} ?: diagrams.modules.data.remove(module.key)
diagrams.statistic.time(vm.modules.start)
diagrams.statistic.time(vm.modules.end)
diagrams.statistic.thread(vm.modules.threadCount)
}
vm.statistics.cpu.advise(vm.lifetime) { statistics ->
if (statistics !is IViewableList.Event.Add) return@advise
diagrams.cpu.add(statistics.newValue)
diagrams.statistic.time(statistics.newValueOpt?.time)
diagrams.statistic.cpu(statistics.newValueOpt?.data)
if (vm.cpuMemory.value == CompilationChartsViewModel.CpuMemoryStatisticsType.CPU) {
scroll.revalidate()
scroll.repaint()
}
}
vm.statistics.memoryUsed.advise(vm.lifetime) { statistics ->
if (statistics !is IViewableList.Event.Add) return@advise
diagrams.memory.add(statistics.newValue)
diagrams.statistic.memory(statistics.newValueOpt?.data)
diagrams.statistic.time(statistics.newValueOpt?.time)
if (vm.cpuMemory.value == CompilationChartsViewModel.CpuMemoryStatisticsType.MEMORY) {
scroll.revalidate()
scroll.repaint()
}
}
vm.filter.advise(vm.lifetime) { filter ->
diagrams.modules.filter = filter
scroll.revalidate()
scroll.repaint()
}
vm.cpuMemory.advise(vm.lifetime) { filter ->
diagrams.cpuMemory = filter
scroll.revalidate()
scroll.repaint()
}
}
private class Initialization {
val scroll: AtomicReference<JBScrollPane> = AtomicReference<JBScrollPane>()
fun init(): () -> JViewport = { scroll.get().viewport }
fun set(scroll: JBScrollPane) = this.scroll.set(scroll)
}
}
data class Statistic(var start: Long, var end: Long, var maxMemory: Long, var threadCount: Int, var maxCpu: Long) {
constructor() : this(Long.MAX_VALUE, 0, 0, 0, 0)
fun time(time: Long?) {
if (time == null) return
if (start > time) start = time
if (end < time) end = time
}
fun memory(memory: Long?) {
if (memory == null) return
if (maxMemory < memory) maxMemory = memory
}
fun cpu(cpu: Long?) {
if (cpu == null) return
if (maxCpu < cpu) maxCpu = cpu
}
fun thread(count: Int) {
if (threadCount < count) threadCount = count
}
}

View File

@@ -0,0 +1,83 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.compiler.charts.ui
import java.awt.Point
import javax.swing.JViewport
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
class Zoom {
private var userScale = -1.0
private var dynamicScale = 0.0
private var correctionDuration: Long = 0
private var lastCorrectionTime: Long = System.currentTimeMillis()
constructor() {
this.dynamicScale = 5.5e7 / NANOS
}
constructor(seconds: Double) {
this.userScale = 5.5e7 / (seconds * NANOS)
}
fun toPixels(duration: Long): Double = toPixels(duration.toDouble(), scale())
fun toPixels(duration: Double): Double = toPixels(duration, scale())
fun toPixels(duration: Double, scale: Double): Double = (duration / NANOS) * scale
fun toDuration(pixels: Int): Long = toDuration(pixels.toDouble(), scale())
private fun toDuration(pixels: Double, scale: Double): Long = Math.round(pixels / scale * NANOS)
fun adjustUser(viewport: JViewport, xPosition: Int, delta: Double) {
if (lastCorrectionTime + 50 < System.currentTimeMillis()) correctionDuration = 0
val localX = xPosition - viewport.viewPosition.x
val currentTimeUnderCursor = toDuration(xPosition) + correctionDuration
userScale = scale() * delta
val newViewPositionX = toPixels(currentTimeUnderCursor) - localX
val correctedViewPositionX = correctedViewPosition(newViewPositionX, localX, currentTimeUnderCursor)
correctionDuration = currentTimeUnderCursor - toDuration(correctedViewPositionX + localX)
lastCorrectionTime = System.currentTimeMillis()
viewport.viewPosition = Point(correctedViewPositionX, viewport.viewPosition.y)
}
private fun correctedViewPosition(newPosition: Double, x: Int, duration: Long): Int {
val correctedX = Math.round(max(0.0, newPosition)).toInt()
val index = listOf(abs(duration - toDuration(correctedX + x)),
abs(duration - toDuration(correctedX + x + 1)),
abs(duration - toDuration(correctedX + x - 1)),
abs(duration - toDuration(correctedX + x + 2)),
abs(duration - toDuration(correctedX + x - 2)))
.withIndex().minBy { it.value }.index
return when (index) {
0 -> return correctedX
1 -> return correctedX + 1
2 -> return correctedX - 1
3 -> return correctedX + 2
4 -> return correctedX - 2
else -> correctedX
}
}
private fun scale(): Double = if (userScale == -1.0) dynamicScale else userScale
fun adjustDynamic(totalDuration: Int, window: Int) = adjustDynamic(totalDuration.toDouble(), window.toDouble())
fun adjustDynamic(totalDuration: Double, window: Double) {
dynamicScale = max(normalize(secondsToScale(totalDuration / NANOS, Math.round(window - 10).toInt())),
secondsToScale(60.0, AXIS_DISTANCE_PX))
}
private fun secondsToScale(seconds: Double, size: Int): Double = size / seconds
private fun normalize(scale: Double): Double = max(secondsToScale(MAX_ZOOM_SECONDS, AXIS_DISTANCE_PX),
min(scale, secondsToScale(MIN_ZOOM_SECONDS, AXIS_DISTANCE_PX)))
companion object {
private const val NANOS: Long = 1_000_000_000
}
}

View File

@@ -0,0 +1,366 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.compiler.charts.ui
import com.intellij.compiler.charts.CompilationChartsBundle
import com.intellij.compiler.charts.CompilationChartsViewModel
import com.intellij.compiler.charts.CompilationChartsViewModel.Modules
import com.intellij.compiler.charts.CompilationChartsViewModel.StatisticData
import com.intellij.openapi.util.text.Formats
import com.intellij.ui.JBColor
import com.intellij.util.ui.UIUtil
import com.intellij.util.ui.UIUtil.FontSize
import java.awt.*
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.awt.geom.Line2D
import java.awt.geom.Path2D
import java.awt.geom.Rectangle2D
import java.util.concurrent.TimeUnit
import javax.swing.JLabel
import javax.swing.JPopupMenu
import javax.swing.JViewport
import kotlin.math.max
fun charts(vm: CompilationChartsViewModel, zoom: Zoom, viewport: JViewport, init: Charts.() -> Unit): Charts {
return Charts(vm, zoom, viewport).apply(init).also { charts ->
val size = MaxSize(charts.progress, charts.settings)
zoom.adjustDynamic(size.width, charts.area.width)
charts.progress.clip = Rectangle2D.Double(0.0,
0.0,
max(zoom.toPixels(size.width), charts.area.width),
size.height)
charts.usage.clip = Rectangle2D.Double(0.0,
size.height,
charts.progress.clip.width,
max(charts.progress.height * 3, charts.area.height - charts.progress.clip.height - charts.axis.height))
charts.axis.clip = Rectangle2D.Double(0.0,
charts.progress.clip.height + charts.usage.clip.height,
charts.progress.clip.width,
charts.axis.height)
}
}
class Charts(private val vm: CompilationChartsViewModel, private val zoom: Zoom, viewport: JViewport) {
internal val area: Area = Area(viewport.x.toDouble(), viewport.y.toDouble(), viewport.width.toDouble(), viewport.height.toDouble())
internal lateinit var progress: ChartProgress
internal lateinit var usage: ChartUsage
internal lateinit var axis: ChartAxis
internal var settings: ChartSettings = ChartSettings()
fun settings(init: ChartSettings.() -> Unit) {
settings = ChartSettings().apply(init)
}
fun progress(init: ChartProgress.() -> Unit) {
progress = ChartProgress(zoom).apply(init)
}
fun usage(init: ChartUsage.() -> Unit) {
usage = ChartUsage(zoom).apply(init)
}
fun axis(init: ChartAxis.() -> Unit) {
axis = ChartAxis(zoom).apply(init)
}
fun draw(g2d: Graphics2D, init: Charts.() -> Unit) {
init()
val components = listOf(progress, usage, axis)
components.forEach { it.background(g2d, settings) }
components.forEach { it.component(g2d, settings) }
}
fun width(): Double = axis.clip.width
fun height(): Double = axis.clip.run { y + height }
}
interface ChartComponent {
fun background(g2d: Graphics2D, settings: ChartSettings)
fun component(g2d: Graphics2D, settings: ChartSettings)
}
class ChartProgress(private val zoom: Zoom) : ChartComponent {
lateinit var model: Map<Modules.EventKey, List<Modules.Event>>
var selected: Modules.EventKey? = null
var height: Double = 25.5
var threads: Int = 0
private lateinit var block: ModuleBlock
private lateinit var background: ModuleBackground
internal lateinit var clip: Rectangle2D
fun block(init: ModuleBlock.() -> Unit) {
block = ModuleBlock().apply(init)
}
class ModuleBlock {
var border: Double = 2.0
var padding: Double = 1.0
lateinit var color: (Modules.Event) -> Color
lateinit var outline: (Modules.Event) -> Color
lateinit var selected: (Modules.Event) -> Color
}
fun background(init: ModuleBackground.() -> Unit) {
background = ModuleBackground().apply(init)
}
class ModuleBackground {
lateinit var color: (Int) -> Color
}
override fun background(g2d: Graphics2D, settings: ChartSettings) {
g2d.withColor(settings.background) {
fill(this@ChartProgress.clip)
}
for (row in 0 until threads) {
val cell = Rectangle2D.Double(clip.x, height * row + clip.y, clip.width, height)
g2d.withColor(background.color(row)) {
fill(cell)
}
}
}
override fun component(g2d: Graphics2D, settings: ChartSettings) {
settings.mouse.clear()
g2d.withAntialiasing {
for ((key, events) in model) {
val start = events.filterIsInstance<Modules.StartEvent>().firstOrNull() ?: continue
val end = events.filterIsInstance<Modules.FinishEvent>().firstOrNull()
val rect = getRectangle(start, end, settings)
settings.mouse.module(rect, key, mutableMapOf(
"duration" to Formats.formatDuration(((end?.target?.time ?: System.nanoTime()) - start.target.time) / 1_000_000),
"name" to start.target.name,
"type" to start.target.type,
"test" to start.target.isTest.toString(),
"fileBased" to start.target.isFileBased.toString(),
))
withColor(block.color(start)) { // module
fill(rect)
}
withColor(if(selected == key) block.selected(start) else block.outline(start)) { // module border
draw(rect)
}
(g2d.create() as Graphics2D).withColor(settings.font.color) {
withFont(UIUtil.getLabelFont(settings.font.size)) { // name
clip(rect)
drawString(" ${start.target.name}", rect.x.toFloat(), (rect.y + (this@ChartProgress.height - block.padding * 2) / 2 + fontMetrics.ascent / 2).toFloat())
}
}
}
}
}
private fun getRectangle(start: Modules.StartEvent, end: Modules.FinishEvent?, settings: ChartSettings): Rectangle2D {
val x0 = zoom.toPixels(start.target.time - settings.duration.from) + block.border
val x1 = zoom.toPixels((end?.target?.time ?: System.nanoTime()) - settings.duration.from)
val width = max(x1 - x0 - block.padding, block.padding) - block.border * 2
return Rectangle2D.Double(x0, (start.threadNumber * height + block.padding + block.border),
width, height - block.padding - block.border * 2
)
}
}
class ChartUsage(private val zoom: Zoom) : ChartComponent {
lateinit var model: Collection<StatisticData>
lateinit var unit: String
lateinit var color: UsageColor
var maximum: Long = 0
internal lateinit var clip: Rectangle2D
fun color(init: UsageColor.() -> Unit) {
color = UsageColor().apply(init)
}
class UsageColor {
lateinit var background: JBColor
lateinit var border: JBColor
}
override fun background(g2d: Graphics2D, settings: ChartSettings) {
g2d.withColor(settings.background) {
fill(this@ChartUsage.clip)
}
g2d.withColor(settings.line.color) {
draw(Line2D.Double(0.0, this@ChartUsage.clip.y, this@ChartUsage.clip.width, this@ChartUsage.clip.y))
}
}
override fun component(g2d: Graphics2D, settings: ChartSettings) {
if (model.isEmpty()) return
val path = path(settings)
g2d.withStroke(BasicStroke(USAGE_BORDER)) {
withColor(this@ChartUsage.color.border) {
draw(path)
}
withColor(this@ChartUsage.color.background) {
fill(path)
}
}
}
private fun path(settings: ChartSettings): Path2D {
val y0 = clip.y + clip.height
val path = Path2D.Double()
path.moveTo(0.0, y0)
model.forEach { statistic ->
path.lineTo(zoom.toPixels(statistic.time - settings.duration.from),
y0 - (statistic.data.toDouble() / maximum * clip.height))
}
path.lineTo(zoom.toPixels(model.last().time - settings.duration.from), y0)
path.closePath()
return path
}
}
class ChartAxis(private val zoom: Zoom) : ChartComponent {
var stroke: FloatArray = floatArrayOf(5f, 5f)
var distance: Int = 250
var count: Int = 10
var height: Double = 0.0
var padding: Int = 2
internal lateinit var clip: Rectangle2D
override fun background(g2d: Graphics2D, settings: ChartSettings) {
g2d.withColor(settings.background) {
fill(this@ChartAxis.clip)
}
g2d.withColor(settings.line.color) {
draw(Line2D.Double(0.0, this@ChartAxis.clip.y, this@ChartAxis.clip.width, this@ChartAxis.clip.y))
}
}
override fun component(g2d: Graphics2D, settings: ChartSettings) {
g2d.withAntialiasing {
val size = UIUtil.getFontSize(settings.font.size) + padding
val from = 0
val to = this@ChartAxis.clip.width.toInt()
withColor(COLOR_LINE) {
for (x in from..to step distance) {
// big axis
withStroke(BasicStroke(1.5F, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0f, this@ChartAxis.stroke, 0.0f)) {
draw(Line2D.Double(x.toDouble(), 0.0, x.toDouble(), this@ChartAxis.clip.y))
}
for (x1 in x..x + distance step distance / count) {
// additional axis
draw(Line2D.Double(x1.toDouble(), this@ChartAxis.clip.y, x1.toDouble(), this@ChartAxis.clip.y + (size / 2)))
}
}
}
withColor(settings.font.color) {
val step = zoom.toDuration(distance)
val trim = if (TimeUnit.NANOSECONDS.toMinutes(step) > 2) 60_000
else if (TimeUnit.NANOSECONDS.toMinutes(step) >= 1) 1_000
else if (TimeUnit.NANOSECONDS.toSeconds(step) > 2) 1_000
else 1
withFont(UIUtil.getLabelFont(settings.font.size)) {
for (x in from..to step distance) {
val time = Formats.formatDuration((TimeUnit.NANOSECONDS.toMillis(zoom.toDuration(x)) / trim) * trim)
drawString(time, x + padding, (this@ChartAxis.clip.y + size).toInt())
}
}
}
}
}
}
class ChartSettings {
internal lateinit var font: ChartFont
internal lateinit var mouse: CompilationChartsMouseAdapter
var background: Color = JBColor.WHITE
internal lateinit var duration: ChartDuration
internal var line: ChartLine = ChartLine()
fun font(init: ChartFont.() -> Unit) {
font = ChartFont().apply(init)
}
fun duration(init: ChartDuration.() -> Unit) {
duration = ChartDuration().apply(init)
}
fun line(init: ChartLine.() -> Unit) {
line = ChartLine().apply(init)
}
class ChartFont {
var size: FontSize = FontSize.NORMAL
var color: Color = JBColor.DARK_GRAY
}
class ChartDuration {
var from: Long = Long.MAX_VALUE
var to: Long = 0
}
}
class ChartLine {
var color: Color = JBColor.LIGHT_GRAY
var size: Int = 1
}
internal data class Area(val x: Double, val y: Double, val width: Double, val height: Double)
internal data class MaxSize(val width: Double, val height: Double) {
constructor(width: Long, height: Double) : this(width.toDouble(), height)
constructor(progress: ChartProgress, settings: ChartSettings) : this(with(settings.duration) { to - from }, (progress.threads + 1) * progress.height)
}
class CompilationChartsMouseAdapter(private val vm: CompilationChartsViewModel, private val component: Component) : MouseAdapter() {
private val components: MutableList<Index> = mutableListOf()
private var currentPopup: JPopupMenu? = null
override fun mouseClicked(e: MouseEvent) {
val info = search(e.point)?.info ?: return
val popupMenu = JPopupMenu().apply {
add(JLabel(CompilationChartsBundle.message("charts.module.info", info["name"], info["duration"]))) // TODO
show(this@CompilationChartsMouseAdapter.component, e.point.x, e.point.y)
}
currentPopup = popupMenu
}
override fun mouseExited(e: MouseEvent) {
val popup = currentPopup ?: return
if (popup.isShowing && !popup.contains(e.point)) {
popup.isVisible = false // todo animation
}
}
fun clear() {
components.clear()
}
fun module(rect: Rectangle2D, key: Modules.EventKey, info: Map<String, String>) {
components.add(Index(rect, key, info))
}
private fun search(point: Point): Index? {
components.forEach { index ->
if (index.x0 <= point.x && index.x1 >= point.x && index.y0 <= point.y && index.y1 >= point.y) return index
}
return null
}
private data class Index(val x0: Double, val x1: Double,
val y0: Double, val y1: Double,
val key: Modules.EventKey,
val info: Map<String, String>) {
constructor(rect: Rectangle2D, key: Modules.EventKey, info: Map<String, String>) : this(rect.x, rect.x + rect.width,
rect.y, rect.y + rect.height,
key, info)
}
}

View File

@@ -0,0 +1,44 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.compiler.charts.ui
import com.intellij.ui.JBColor
import com.intellij.util.ui.UIUtil.FontSize
import java.awt.Color
val COLOR_TEST_BLOCK: JBColor = JBColor(Color(0xc5e5cc), Color(0x375239))
val COLOR_TEST_BLOCK_DISABLED: JBColor = JBColor(Color(0xb6eedd), Color(0x26392b))
val COLOR_TEST_BORDER: JBColor = JBColor(Color(0x3b9954), Color(0x549159))
val COLOR_TEST_BORDER_SELECTED: JBColor = JBColor.GREEN
val COLOR_PRODUCTION_BLOCK: JBColor = JBColor(Color(0xc2d6fc), Color(0x2e436e))
val COLOR_PRODUCTION_BLOCK_DISABLED: JBColor = JBColor(Color(0xd7e4fd), Color(0x202e4d))
val COLOR_PRODUCTION_BORDER: JBColor = JBColor(Color(0x4781fa), Color(0x4978d6))
val COLOR_PRODUCTION_BORDER_SELECTED: JBColor = JBColor.BLUE
val COLOR_BACKGROUND: JBColor = JBColor.WHITE
val COLOR_BACKGROUND_ODD: JBColor = JBColor.WHITE
val COLOR_BACKGROUND_EVEN: JBColor = JBColor(Color(0xfefefe), Color(0x3e4042))
val COLOR_TEXT: JBColor = JBColor(Color(0x1d1d1d), Color(0xbfbfbf))
val COLOR_LINE: JBColor = JBColor.LIGHT_GRAY
val COLOR_MEMORY: JBColor = JBColor.namedColor("Profiler.MemoryChart.inactiveBorderColor")
val COLOR_MEMORY_BORDER: JBColor = JBColor.namedColor("Profiler.MemoryChart.borderColor")
val COLOR_CPU: JBColor = JBColor.namedColor("Profiler.CpuChart.inactiveBorderColor")
val COLOR_CPU_BORDER: JBColor = JBColor.namedColor("Profiler.CpuChart.borderColor")
const val MODULE_BLOCK_PADDING: Double = 1.0
const val MODULE_BLOCK_BORDER: Double = 2.0
const val USAGE_BORDER: Float = 2.0f
val FONT_SIZE: FontSize = FontSize.NORMAL
const val AXIS_DISTANCE_PX: Int = 250
const val AXIS_MARKERS_COUNT: Int = 10
const val AXIS_TEXT_PADDING: Int = 2
const val MIN_ZOOM_SECONDS = 0.1
const val MAX_ZOOM_SECONDS = 100.0

View File

@@ -0,0 +1,38 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.compiler.charts.ui
import java.awt.*
fun Graphics2D.withColor(color: Color, block: Graphics2D.() -> Unit): Graphics2D {
val oldColor = this.color
this.color = color
block()
this.color = oldColor
return this
}
fun Graphics2D.withFont(font: Font, block: Graphics2D.() -> Unit): Graphics2D {
val oldFont = this.font
this.font = font
block()
this.font = oldFont
return this
}
fun Graphics2D.withAntialiasing(block: Graphics2D.() -> Unit): Graphics2D {
val old = getRenderingHint(RenderingHints.KEY_ANTIALIASING)
setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
block()
setRenderingHint(RenderingHints.KEY_ANTIALIASING, old)
return this
}
fun Graphics2D.withStroke(stroke: Stroke, block: Graphics2D.() -> Unit): Graphics2D {
val oldStroke = this.stroke
this.stroke = stroke
block()
this.stroke = oldStroke
return this
}

View File

@@ -23,6 +23,7 @@
<xi:include href="intellij.java.frontback.psi.impl.xml" xpointer="xpointer(/idea-plugin/*)"/>
<xi:include href="intellij.java.frontback.impl.xml" xpointer="xpointer(/idea-plugin/*)"/>
<xi:include href="intellij.java.compiler.charts.xml" xpointer="xpointer(/idea-plugin/*)"/>
<xi:include href="/META-INF/JvmAnalysisPlugin.xml" xpointer="xpointer(/idea-plugin/*)"/>
<xi:include href="/META-INF/JavaIndexingPlugin.xml" xpointer="xpointer(/idea-plugin/*)"/>
<xi:include href="/META-INF/JavaPsiPlugin.xml" xpointer="xpointer(/idea-plugin/*)"/>

View File

@@ -238,5 +238,6 @@
<orderEntry type="library" name="ion" level="project" />
<orderEntry type="module" module-name="intellij.platform.boot" />
<orderEntry type="library" name="ktor-client-cio" level="project" />
<orderEntry type="module" module-name="intellij.java.compiler.charts.jps" scope="RUNTIME" />
</component>
</module>