add tracing infrastructure

GitOrigin-RevId: 363b79b56e59bbc36d1abd59b929048d77fb6ada
This commit is contained in:
Roman Ivanov
2021-09-23 16:56:51 +02:00
committed by intellij-monorepo-bot
parent 434409225b
commit 45e2bab709
21 changed files with 565 additions and 0 deletions

View File

@@ -88,6 +88,7 @@ abstract class BaseIdeaProperties extends JetBrainsProductProperties {
"intellij.vcs.git.featuresTrainer",
"intellij.lombok",
"intellij.searchEverywhereMl",
"intellij.platform.tracing.ide",
KotlinPluginBuilder.MAIN_KOTLIN_PLUGIN_MODULE,
)

View File

@@ -31,6 +31,7 @@ final class CommunityStandaloneJpsBuilder {
module("intellij.platform.util.text.matching")
module("intellij.platform.util.base")
module("intellij.platform.util.xmlDom")
module("intellij.platform.tracing")
}
jar("jps-launcher.jar") {

View File

@@ -160,5 +160,6 @@
<orderEntry type="module" module-name="intellij.laf.win10" scope="RUNTIME" />
<orderEntry type="module" module-name="intellij.searchEverywhereMl" scope="RUNTIME" />
<orderEntry type="module" module-name="intellij.toml" scope="RUNTIME" />
<orderEntry type="module" module-name="intellij.platform.tracing.ide" scope="RUNTIME" />
</component>
</module>

View File

@@ -41,6 +41,7 @@
<orderEntry type="module" module-name="intellij.platform.ide.util.io.impl" />
<orderEntry type="module" module-name="intellij.platform.ide.util.netty" />
<orderEntry type="module" module-name="intellij.platform.serviceContainer" />
<orderEntry type="module" module-name="intellij.platform.tracing" />
</component>
<component name="copyright">
<Base>

View File

@@ -53,5 +53,6 @@
<orderEntry type="library" name="jps-javac-extension" level="project" />
<orderEntry type="library" name="netty-buffer" level="project" />
<orderEntry type="module" module-name="intellij.platform.jps.build.javac.rt" exported="" />
<orderEntry type="module" module-name="intellij.platform.tracing" exported="" />
</component>
</module>

View File

@@ -9,6 +9,7 @@ import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.tracing.Tracer;
import com.intellij.util.concurrency.Semaphore;
import com.intellij.util.concurrency.SequentialTaskExecutor;
import com.intellij.util.io.DataOutputStream;
@@ -34,6 +35,8 @@ import org.jetbrains.jps.service.JpsServiceManager;
import org.jetbrains.jps.service.SharedThreadPool;
import java.io.*;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.Executor;
@@ -120,6 +123,19 @@ final class BuildSession implements Runnable, CanceledStatus {
LOG.debug(" preloadedData = " + myPreloadedData);
LOG.debug(" buildType = " + myBuildType);
}
try {
String tracingFile = System.getProperty("tracingFile");
if (tracingFile != null) {
LOG.debug("Tracing enabled, file: " + tracingFile);
Path tracingFilePath = Paths.get(tracingFile);
Tracer.runTracer(1, tracingFilePath, 1, e -> {
LOG.warn(e);
});
}
} catch (IOException e) {
LOG.warn(e);
}
}
private static @NonNls String showFirstItemIfAny(List<String> list) {

View File

@@ -8,6 +8,7 @@ import com.intellij.openapi.application.ClassPathUtil;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.tracing.Tracer;
import com.intellij.uiDesigner.compiler.AlienFormFileException;
import com.intellij.uiDesigner.core.GridConstraints;
import com.intellij.util.SystemProperties;
@@ -128,6 +129,7 @@ public final class ClasspathBootstrap {
addToClassPath(Gson.class, cp); // gson
addToClassPath(cp, ArtifactRepositoryManager.getClassesFromDependencies());
addToClassPath(Tracer.class, cp); // tracing infrastructure
try {
Class<?> cmdLineWrapper = Class.forName("com.intellij.rt.execution.CommandLineWrapper");

View File

@@ -15,6 +15,7 @@ final class JavaPluginLayout {
withModule("intellij.platform.jps.build.launcher", "jps-launcher.jar")
withModule("intellij.platform.jps.build", "jps-builders.jar")
withModule("intellij.platform.jps.build.javac.rt", "jps-builders-6.jar")
withModule("intellij.platform.tracing")
withModule("intellij.java.aetherDependencyResolver", "aether-dependency-resolver.jar")
withModule("intellij.java.jshell.protocol", "jshell-protocol.jar")
withModule("intellij.java.resources", mainJarName)

View File

@@ -103,6 +103,7 @@ final class PlatformModules {
"intellij.platform.credentialStore.ui",
"intellij.platform.rd.community",
"intellij.platform.ml.impl",
"intellij.platform.tracing",
)
private static final String UTIL_JAR = "util.jar"

View File

@@ -66,5 +66,6 @@
</orderEntry>
<orderEntry type="module" module-name="intellij.platform.bootstrap" />
<orderEntry type="module" module-name="intellij.platform.ml.impl" />
<orderEntry type="module" module-name="intellij.platform.tracing" />
</component>
</module>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" 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="library" name="kotlin-stdlib-jdk8" level="project" />
<orderEntry type="module" module-name="intellij.platform.ide" />
<orderEntry type="module" module-name="intellij.platform.tracing" />
<orderEntry type="module" module-name="intellij.platform.lang" />
<orderEntry type="module" module-name="intellij.java.compiler.impl" />
</component>
</module>

View File

@@ -0,0 +1,24 @@
<idea-plugin package="com.intellij.tracing.ide"
implementation-detail="true">
<id>com.intellij.tracing.ide</id>
<vendor>JetBrains</vendor>
<depends>com.intellij.modules.java</depends>
<actions>
<action class="com.intellij.tracing.ide.ToggleBuildTracingAction" internal="true" id="toggle.tracing.action"/>
</actions>
<extensions defaultExtensionNs="com.intellij">
<registryKey defaultValue="" description="Path to tracing snapshots" key="tracing.snapshots.path" />
<notificationGroup displayType="BALLOON" id="BuildTracing" bundle="messages.TracingBundle" key="build.tracing.group"/>
<applicationService serviceImplementation="com.intellij.tracing.ide.TracingPersistentStateComponent"/>
<buildProcess.parametersProvider implementation="com.intellij.tracing.ide.TracingBuildProcessParameterProvider"/>
</extensions>
<projectListeners>
<listener class="com.intellij.tracing.ide.TracingProjectTaskListener" topic="com.intellij.task.ProjectTaskListener" />
</projectListeners>
<resource-bundle>messages.TracingBundle</resource-bundle>
</idea-plugin>

View File

@@ -0,0 +1,5 @@
action.open.trace.directory.in.file.manager.text=Open Directory in File Manager
action.toggle.build.tracing.text=Toggle Build Tracing
action.toggle.tracing.action.description=Toggle tracing
build.tracing.group=Build tracing
notification.content.tracing.file.was.created=Tracing file was created

View File

@@ -0,0 +1,15 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.tracing.ide
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.ToggleAction
class ToggleBuildTracingAction : ToggleAction(TracingBundle.message("action.toggle.build.tracing.text")) {
override fun isSelected(e: AnActionEvent): Boolean {
return TracingService.getInstance().isTracingEnabled()
}
override fun setSelected(e: AnActionEvent, state: Boolean) {
TracingService.getInstance().setTracingEnabled(state)
}
}

View File

@@ -0,0 +1,15 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.tracing.ide
import com.intellij.compiler.server.BuildProcessParametersProvider
internal class TracingBuildProcessParameterProvider : BuildProcessParametersProvider() {
override fun getVMArguments(): List<String> {
if (TracingService.getInstance().isTracingEnabled()) {
val path = TracingService.createPath(TracingService.TraceKind.Jps)
TracingService.getInstance().registerJpsTrace(path)
return listOf("-DtracingFile=$path")
}
return emptyList()
}
}

View File

@@ -0,0 +1,23 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.tracing.ide
import com.intellij.DynamicBundle
import org.jetbrains.annotations.Nls
import org.jetbrains.annotations.NonNls
import org.jetbrains.annotations.PropertyKey
internal class TracingBundle : DynamicBundle(BUNDLE) {
companion object {
@NonNls
private const val BUNDLE = "messages.TracingBundle"
@JvmStatic
private val INSTANCE: TracingBundle = TracingBundle()
@JvmStatic
@Nls
fun message(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any): String {
return INSTANCE.getMessage(key, *params)
}
}
}

View File

@@ -0,0 +1,16 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.tracing.ide
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.*
@State(name = "tracing", storages = [(Storage(value = "tracing.xml"))])
internal class TracingPersistentStateComponent : SimplePersistentStateComponent<TracingPersistentStateComponent.State>(State()) {
companion object {
fun getInstance(): TracingPersistentStateComponent = ApplicationManager.getApplication().getService(TracingPersistentStateComponent::class.java)
}
class State : BaseState() {
var isEnabled by property(false)
}
}

View File

@@ -0,0 +1,99 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.tracing.ide
import com.intellij.ide.util.PsiNavigationSupport
import com.intellij.notification.Notification
import com.intellij.notification.NotificationType
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.diagnostic.logger
import com.intellij.task.ProjectTaskContext
import com.intellij.task.ProjectTaskListener
import com.intellij.task.ProjectTaskManager
import com.intellij.tracing.Tracer
import com.intellij.util.concurrency.AppExecutorUtil
import com.intellij.util.io.exists
import java.io.IOException
import java.nio.file.Files
import java.nio.file.Path
import kotlin.io.path.writeText
import kotlin.jvm.Throws
internal class TracingProjectTaskListener : ProjectTaskListener {
companion object {
private val log = logger<TracingProjectTaskListener>()
}
@Volatile
private var span: Tracer.Span? = null
override fun started(context: ProjectTaskContext) {
val tracingService = TracingService.getInstance()
if (!tracingService.isTracingEnabled()) return
try {
val filePath = TracingService.createPath(TracingService.TraceKind.Ide)
tracingService.registerIdeTrace(filePath)
tracingService.bindJpsTraceIfExistsToCurrentSession()
Tracer.runTracer(0, filePath, 1) { exception ->
handleException(tracingService, exception)
}
span = Tracer.start("Build")
} catch (e: IOException) {
handleException(tracingService, e)
}
}
override fun finished(result: ProjectTaskManager.Result) {
span?.complete()
val tracingService = TracingService.getInstance()
Tracer.finishTracer { exception ->
handleException(tracingService, exception)
}
val filesToMerge = tracingService.drainFilesToMerge()
AppExecutorUtil.getAppExecutorService().execute {
try {
val mergedText = mergeFiles(filesToMerge)
val mergedFilePath = TracingService.createPath(TracingService.TraceKind.Merged)
Files.createDirectories(mergedFilePath.parent)
mergedFilePath.writeText(mergedText)
showNotificationNotification(mergedFilePath.parent)
}
catch (e: IOException) {
handleException(tracingService, e)
}
}
}
private fun handleException(tracingService: TracingService, e: Exception) {
tracingService.clearPathsToMerge()
log.warn(e)
}
private fun showNotificationNotification(mergedFile: Path) {
val notification = Notification("BuildTracing", TracingBundle.message("notification.content.tracing.file.was.created"), NotificationType.INFORMATION)
notification.addAction(object : AnAction(TracingBundle.message("action.open.trace.directory.in.file.manager.text")) {
override fun actionPerformed(e: AnActionEvent) {
PsiNavigationSupport.getInstance().openDirectoryInSystemFileManager(mergedFile.parent.toFile())
}
})
notification.notify(null)
}
@Throws(IOException::class)
fun mergeFiles(files: List<Path>) : String {
return buildString {
appendLine("[\n")
for (filePath in files) {
if (filePath.exists()) {
val entries = readEntries(filePath)
for (entry in entries) {
appendLine(entry)
}
}
}
appendLine("]\n")
}
}
private fun readEntries(trace: Path) = trace.toFile().bufferedReader().lineSequence().drop(1).toList().dropLast(1)
}

View File

@@ -0,0 +1,103 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.tracing.ide
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.PathManager
import com.intellij.openapi.components.Service
import java.nio.file.Path
import java.nio.file.Paths
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.*
@Service(Service.Level.APP)
internal class TracingService {
companion object {
fun getInstance() : TracingService {
return ApplicationManager.getApplication().getService(TracingService::class.java)
}
fun createPath(kind: TraceKind): Path {
val tracesDirPath = getTracesDirPath()
val subDir = when (kind) {
TraceKind.Jps -> JPS_TRACE_DIR_NAME
TraceKind.Ide -> IDE_TRACE_DIR_NAME
TraceKind.Merged -> MERGED_TRACE_DIR_NAME
}
return tracesDirPath.resolve(subDir).resolve(getNewTraceFileName())
}
private fun getNewTraceFileName() = "trace_" + LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + ".json"
private fun getTracesDirPath() : Path {
return Paths.get(PathManager.getHomePath()).resolve(COMMON_TRACING_DIR_NAME)
}
private const val COMMON_TRACING_DIR_NAME: String = "tracing"
private const val IDE_TRACE_DIR_NAME: String = "ide"
private const val JPS_TRACE_DIR_NAME: String = "jps"
private const val MERGED_TRACE_DIR_NAME: String = "merged"
}
private val lock = Any()
private var traces = ArrayList<Path>()
private var jpsTrace: Path? = null
fun isTracingEnabled() : Boolean {
return synchronized(lock) {
TracingPersistentStateComponent.getInstance().state.isEnabled
}
}
fun setTracingEnabled(enabled: Boolean) {
synchronized(lock) {
TracingPersistentStateComponent.getInstance().state.isEnabled = enabled
if (!enabled) {
traces.clear()
}
}
}
fun registerIdeTrace(filePath: Path) {
synchronized(lock) {
traces.add(filePath)
}
}
fun registerJpsTrace(filePath: Path) {
synchronized(lock) {
bindJpsTraceIfExistsToCurrentSession()
jpsTrace = filePath
}
}
fun bindJpsTraceIfExistsToCurrentSession() {
synchronized(lock) {
val jpsTrace = jpsTrace
if (jpsTrace != null) {
traces.add(jpsTrace)
this.jpsTrace = null
}
}
}
fun drainFilesToMerge() : List<Path> {
synchronized(lock) {
val tracesToMerge = traces
traces = ArrayList()
return tracesToMerge
}
}
fun clearPathsToMerge() {
synchronized(lock) {
traces.clear()
}
}
enum class TraceKind {
Jps,
Ide,
Merged,
}
}

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -0,0 +1,211 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.tracing;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class Tracer {
private static long tracingStartNs;
private static long tracingStartMs;
private static final AtomicLong eventId = new AtomicLong();
private static final ConcurrentLinkedDeque<Span> spans = new ConcurrentLinkedDeque<>();
private static volatile int pid;
private static volatile long durationThreshold;
private static volatile FileState fileState = null;
private static volatile boolean running = false;
private static ScheduledExecutorService executor = null;
private static Thread shutdownHook;
private Tracer() { }
public static DelayedSpan start(Supplier<String> nameSupplier) {
long eventId = Tracer.eventId.getAndIncrement();
long threadId = Thread.currentThread().getId();
long startNs = System.nanoTime();
return new DelayedSpan(eventId, threadId, nameSupplier, startNs);
}
public static Span start(String name) {
long eventId = Tracer.eventId.getAndIncrement();
long threadId = Thread.currentThread().getId();
long startNs = System.nanoTime();
return new Span(eventId, threadId, name, startNs);
}
public static void runTracer(int pid, Path filePath, long threshold, Consumer<Exception> exceptionHandler) throws IOException {
if (running) throw new IllegalStateException("Tracer already started");
tracingStartMs = System.currentTimeMillis();
tracingStartNs = System.nanoTime();
Files.createDirectories(filePath.getParent());
FileOutputStream fileOutputStream = new FileOutputStream(filePath.toFile());
OutputStreamWriter writer = new OutputStreamWriter(new BufferedOutputStream(fileOutputStream), StandardCharsets.UTF_8);
fileState = new FileState(writer);
durationThreshold = threshold;
Tracer.pid = pid;
executor = createExecutor();
FlushingTask flushingTask = new FlushingTask(fileState, false, exceptionHandler);
executor.scheduleAtFixedRate(flushingTask, 5, 5, TimeUnit.SECONDS);
shutdownHook = new Thread(new FlushingTask(fileState, true, exceptionHandler), "Shutdown hook trace flusher");
Runtime.getRuntime().addShutdownHook(shutdownHook);
running = true;
}
public static void finishTracer(Consumer<Exception> exceptionHandler) {
if (!running) throw new IllegalStateException("Tracer already finished");
new FlushingTask(fileState, true, exceptionHandler).run();
fileState = null;
executor.shutdown();
executor = null;
Runtime.getRuntime().removeShutdownHook(shutdownHook);
running = false;
}
public static boolean isRunning() {
return running;
}
private static ScheduledExecutorService createExecutor() {
return Executors.newScheduledThreadPool(1, r -> {
Thread thread = new Thread(r, "Trace flusher");
thread.setDaemon(true);
return thread;
});
}
public static class DelayedSpan {
final long eventId;
final long threadId;
final Supplier<String> nameSupplier;
final long startTimeNs;
public DelayedSpan(long eventId, long threadId, Supplier<String> nameSupplier, long startTimeNs) {
this.eventId = eventId;
this.threadId = threadId;
this.nameSupplier = nameSupplier;
this.startTimeNs = startTimeNs;
}
public void complete() {
if (running) {
Span span = new Span(eventId, threadId, nameSupplier.get(), startTimeNs);
span.complete();
}
}
}
public static class Span {
final long eventId;
final long threadId;
final String name;
final long startTimeNs;
long finishTimeNs;
public Span(long eventId, long threadId, String name, long startTimeNs) {
this.eventId = eventId;
this.threadId = threadId;
this.name = name;
this.startTimeNs = startTimeNs;
}
public void complete() {
if (running) {
finishTimeNs = System.nanoTime();
if (getDuration() > durationThreshold) {
spans.offerLast(this);
}
}
}
/**
* If event has been started on one thread and finished on the other it is not guaranteed to have non-negative duration
*/
long getDuration() {
return finishTimeNs - startTimeNs;
}
}
private static class FileState {
final Writer writer;
boolean openBracketWritten = false;
boolean finished = false;
private FileState(Writer writer) {
this.writer = writer;
}
}
private static class FlushingTask implements Runnable {
private final FileState fileState;
private final boolean shouldFinish;
private final Consumer<Exception> myExceptionHandler;
private FlushingTask(FileState fileState, boolean shouldFinish, Consumer<Exception> exceptionHandler) {
this.fileState = fileState;
this.shouldFinish = shouldFinish;
myExceptionHandler = exceptionHandler;
}
@Override
public void run() {
synchronized (fileState) {
if (fileState.finished) return;
try {
if (!fileState.openBracketWritten) {
fileState.writer.write("[\n");
fileState.openBracketWritten = true;
}
while (true) {
Span span = spans.pollLast();
if (span == null) break;
fileState.writer.write(serialize(span, true));
fileState.writer.write(serialize(span, false));
}
if (shouldFinish) {
fileState.writer.write("]");
}
fileState.writer.flush();
}
catch (IOException e) {
myExceptionHandler.accept(e);
}
}
}
private static String serialize(Span span, boolean isStart) {
StringBuilder sb = new StringBuilder();
sb.append("{\"name\": \"")
.append(span.name)
.append("\", \"cat\": \"PERF\", \"ph\": ");
if (isStart) {
sb.append("\"B\"");
}
else {
sb.append("\"E\"");
}
sb.append(", \"pid\": ").append(pid)
.append(", \"tid\": ").append(span.threadId)
.append(", \"ts\": ");
if (isStart) {
sb.append(getTimeUs(span.startTimeNs));
}
else {
sb.append(getTimeUs(span.finishTimeNs));
}
return sb.append("},\n").toString();
}
}
static long getTimeUs(long timeNs) {
return (tracingStartMs * 1000_000 - tracingStartNs + timeNs) / 1000;
}
}