mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-15 02:59:33 +07:00
add tracing infrastructure
GitOrigin-RevId: 363b79b56e59bbc36d1abd59b929048d77fb6ada
This commit is contained in:
committed by
intellij-monorepo-bot
parent
434409225b
commit
45e2bab709
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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) {
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
17
platform/tracing-ide/intellij.platform.tracing.ide.iml
Normal file
17
platform/tracing-ide/intellij.platform.tracing.ide.iml
Normal 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>
|
||||
24
platform/tracing-ide/resources/META-INF/plugin.xml
Normal file
24
platform/tracing-ide/resources/META-INF/plugin.xml
Normal 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>
|
||||
@@ -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
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
11
platform/tracing/intellij.platform.tracing.iml
Normal file
11
platform/tracing/intellij.platform.tracing.iml
Normal 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>
|
||||
211
platform/tracing/src/com/intellij/tracing/Tracer.java
Normal file
211
platform/tracing/src/com/intellij/tracing/Tracer.java
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user