diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveManager.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveManager.java
index b666609579b..2f1505715a7 100644
--- a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveManager.java
+++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveManager.java
@@ -66,6 +66,7 @@
import org.eclipse.core.internal.localstore.SafeChunkyOutputStream;
import org.eclipse.core.internal.localstore.SafeFileInputStream;
import org.eclipse.core.internal.localstore.SafeFileOutputStream;
+import org.eclipse.core.internal.runtime.StartupTrace;
import org.eclipse.core.internal.utils.IStringPoolParticipant;
import org.eclipse.core.internal.utils.Messages;
import org.eclipse.core.internal.utils.Policy;
@@ -790,6 +791,7 @@ protected void resetSnapshots(IResource resource) throws CoreException {
* which were open when it was last saved.
*/
protected void restore(IProgressMonitor monitor) throws CoreException {
+ long tRestore = StartupTrace.begin();
if (Policy.DEBUG_RESTORE) {
Policy.debug("Restore workspace: starting..."); //$NON-NLS-1$
}
@@ -804,29 +806,44 @@ protected void restore(IProgressMonitor monitor) throws CoreException {
String msg = Messages.resources_startupProblems;
MultiStatus problems = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_READ_METADATA, msg, null);
+ long t;
+ t = StartupTrace.begin();
restoreMasterTable();
+ StartupTrace.record("SaveManager.restore/restoreMasterTable", t); //$NON-NLS-1$
// restore the saved tree and overlay the snapshots if any
+ t = StartupTrace.begin();
restoreTree(Policy.subMonitorFor(monitor, 10));
+ StartupTrace.record("SaveManager.restore/restoreTree", t); //$NON-NLS-1$
+ t = StartupTrace.begin();
restoreSnapshots(Policy.subMonitorFor(monitor, 10));
+ StartupTrace.record("SaveManager.restore/restoreSnapshots", t); //$NON-NLS-1$
// tolerate failure for non-critical information
// if startup fails, the entire workspace is shot
+ t = StartupTrace.begin();
try {
restoreMarkers(workspace.getRoot(), false, Policy.subMonitorFor(monitor, 10));
} catch (CoreException e) {
problems.merge(e.getStatus());
}
+ StartupTrace.record("SaveManager.restore/restoreMarkers", t); //$NON-NLS-1$
+ t = StartupTrace.begin();
try {
restoreSyncInfo(workspace.getRoot(), Policy.subMonitorFor(monitor, 10));
} catch (CoreException e) {
problems.merge(e.getStatus());
}
+ StartupTrace.record("SaveManager.restore/restoreSyncInfo", t); //$NON-NLS-1$
// restore meta info last because it might close a project if its description is not readable
+ t = StartupTrace.begin();
restoreMetaInfo(problems, Policy.subMonitorFor(monitor, 10));
+ StartupTrace.record("SaveManager.restore/restoreMetaInfo", t); //$NON-NLS-1$
+ t = StartupTrace.begin();
IProject[] roots = workspace.getRoot().getProjects(IContainer.INCLUDE_HIDDEN);
for (IProject root : roots) {
((Project) root).startup();
}
+ StartupTrace.record("SaveManager.restore/project.startup(all)", t); //$NON-NLS-1$
if (!problems.isOK()) {
Policy.log(problems);
}
@@ -835,6 +852,7 @@ protected void restore(IProgressMonitor monitor) throws CoreException {
}
} finally {
monitor.done();
+ StartupTrace.record("SaveManager.restore(total)", tRestore); //$NON-NLS-1$
}
if (Policy.DEBUG_RESTORE) {
Policy.debug("Restore workspace: " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
@@ -923,7 +941,9 @@ protected void restoreMarkers(IResource resource, boolean generateDeltas, IProgr
MarkerManager markerManager = workspace.getMarkerManager();
// when restoring a project, only load markers if it is open
if (resource.isAccessible()) {
+ long tRoot = StartupTrace.begin();
markerManager.restore(resource, generateDeltas, monitor);
+ StartupTrace.record("SaveManager.restore/restoreMarkers/readSnapshot", tRoot); //$NON-NLS-1$
}
// if we have the workspace root then restore markers for its projects
@@ -934,11 +954,13 @@ protected void restoreMarkers(IResource resource, boolean generateDeltas, IProgr
return;
}
IProject[] projects = ((IWorkspaceRoot) resource).getProjects(IContainer.INCLUDE_HIDDEN);
+ long tLoop = StartupTrace.begin();
for (IProject project : projects) {
if (project.isAccessible()) {
markerManager.restore(project, generateDeltas, monitor);
}
}
+ StartupTrace.record("SaveManager.restore/restoreMarkers/readDelta(count=" + projects.length + ")", tLoop); //$NON-NLS-1$ //$NON-NLS-2$
if (Policy.DEBUG_RESTORE_MARKERS) {
Policy.debug("Restore Markers for workspace: " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
}
@@ -977,6 +999,7 @@ protected void restoreMetaInfo(MultiStatus problems, IProgressMonitor monitor) {
}
long start = System.currentTimeMillis();
IProject[] roots = workspace.getRoot().getProjects(IContainer.INCLUDE_HIDDEN);
+ long tLoop = StartupTrace.begin();
for (IProject root : roots) {
//fatal to throw exceptions during startup
try {
@@ -986,6 +1009,7 @@ protected void restoreMetaInfo(MultiStatus problems, IProgressMonitor monitor) {
problems.merge(new ResourceStatus(IResourceStatus.FAILED_READ_METADATA, root.getFullPath(), message, e));
}
}
+ StartupTrace.record("SaveManager.restore/restoreMetaInfo/loadMetaInfo(count=" + roots.length + ")", tLoop); //$NON-NLS-1$ //$NON-NLS-2$
if (Policy.DEBUG_RESTORE_METAINFO) {
Policy.debug("Restore workspace metainfo: " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
}
@@ -1160,8 +1184,12 @@ protected void restoreTree(IProgressMonitor monitor) throws CoreException {
savedStates = Collections.synchronizedMap(new HashMap<>(10));
return;
}
+ long tOpen = StartupTrace.begin();
try (DataInputStream input = new DataInputStream(new SafeFileInputStream(treeLocation.toOSString(), tempLocation.toOSString(), TREE_BUFFER_SIZE))) {
+ StartupTrace.record("SaveManager.restore/restoreTree/open DataInputStream", tOpen); //$NON-NLS-1$
+ long tRead = StartupTrace.begin();
WorkspaceTreeReader.getReader(workspace, input.readInt()).readTree(input, monitor);
+ StartupTrace.record("SaveManager.restore/restoreTree/readTree (ElementTreeReader)", tRead); //$NON-NLS-1$
} catch (Exception e) { // "Unknown format" is passed as ResourceException
String msg = NLS.bind(Messages.resources_readMeta, treeLocation.toOSString());
throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, treeLocation, msg, e);
diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Workspace.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Workspace.java
index 690cb8fe72e..20ac7c5c686 100644
--- a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Workspace.java
+++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Workspace.java
@@ -65,6 +65,7 @@
import org.eclipse.core.internal.refresh.RefreshManager;
import org.eclipse.core.internal.resources.ComputeProjectOrder.Digraph;
import org.eclipse.core.internal.resources.ComputeProjectOrder.VertexOrder;
+import org.eclipse.core.internal.runtime.StartupTrace;
import org.eclipse.core.internal.utils.BitMask;
import org.eclipse.core.internal.utils.Messages;
import org.eclipse.core.internal.utils.Policy;
@@ -2342,6 +2343,7 @@ protected long nextNodeId() {
* @see ResourcesPlugin#getWorkspace()
*/
public IStatus open(IProgressMonitor monitor) throws CoreException {
+ long tOpen = StartupTrace.begin();
if (!localMetaArea.hasSavedWorkspace()) {
localMetaArea.createMetaArea();
}
@@ -2359,29 +2361,42 @@ public IStatus open(IProgressMonitor monitor) throws CoreException {
// Set explicit workspace encoding if no projects exist in the workspace
if (!localMetaArea.hasSavedProjects()) {
+ long tEnc = StartupTrace.begin();
setExplicitWorkspaceEncoding();
+ StartupTrace.record("Workspace.open/setExplicitWorkspaceEncoding", tEnc); //$NON-NLS-1$
}
+ long tPrefOrder = StartupTrace.begin();
initializePreferenceLookupOrder();
+ StartupTrace.record("Workspace.open/initializePreferenceLookupOrder", tPrefOrder); //$NON-NLS-1$
// create root location
localMetaArea.locationFor(getRoot()).toFile().mkdirs();
+ long tStartup = StartupTrace.begin();
startup(new NullProgressMonitor());
+ StartupTrace.record("Workspace.open/startup", tStartup); //$NON-NLS-1$
// restart the notification manager so it is initialized with the right tree
+ long tNotif = StartupTrace.begin();
notificationManager.startup(null);
+ StartupTrace.record("Workspace.open/notificationManager.startup(restart)", tNotif); //$NON-NLS-1$
openFlag = true;
if (crashed || refreshRequested()) {
+ long tRefresh = StartupTrace.begin();
try {
refreshManager.refresh(getRoot());
} catch (RuntimeException e) {
+ StartupTrace.record("Workspace.open/crashRecoveryRefresh", tRefresh); //$NON-NLS-1$
+ StartupTrace.record("Workspace.open", tOpen); //$NON-NLS-1$
//don't fail entire open if refresh failed, just report as warning
return new ResourceStatus(IResourceStatus.INTERNAL_ERROR, IPath.ROOT,
Messages.resources_errorMultiRefresh, e);
}
+ StartupTrace.record("Workspace.open/crashRecoveryRefresh", tRefresh); //$NON-NLS-1$
}
//finally register a string pool participant
stringPoolJob = new StringPoolJob();
stringPoolJob.addStringPoolParticipant(saveManager, getRoot());
+ StartupTrace.record("Workspace.open", tOpen); //$NON-NLS-1$
return Status.OK_STATUS;
}
@@ -2654,45 +2669,76 @@ public String[] sortNatureSet(String[] natureIds) {
* Starts all the workspace manager classes.
*/
protected void startup(IProgressMonitor monitor) throws CoreException {
+ long tAll = StartupTrace.begin();
// ensure the tree is locked during the startup notification
try {
+ long t;
+ t = StartupTrace.begin();
_workManager = new WorkManager(this);
_workManager.startup(null);
+ StartupTrace.record("Workspace.startup/WorkManager", t); //$NON-NLS-1$
+ t = StartupTrace.begin();
fileSystemManager = new FileSystemResourceManager(this);
fileSystemManager.startup(monitor);
+ StartupTrace.record("Workspace.startup/FileSystemResourceManager", t); //$NON-NLS-1$
+ t = StartupTrace.begin();
pathVariableManager = new PathVariableManager();
pathVariableManager.startup(null);
+ StartupTrace.record("Workspace.startup/PathVariableManager", t); //$NON-NLS-1$
+ t = StartupTrace.begin();
natureManager = new NatureManager(this);
natureManager.startup(null);
+ StartupTrace.record("Workspace.startup/NatureManager", t); //$NON-NLS-1$
+ t = StartupTrace.begin();
filterManager = new FilterTypeManager();
filterManager.startup(null);
+ StartupTrace.record("Workspace.startup/FilterTypeManager", t); //$NON-NLS-1$
+ t = StartupTrace.begin();
buildManager = new BuildManager(this, getWorkManager().getLock());
buildManager.startup(null);
+ StartupTrace.record("Workspace.startup/BuildManager", t); //$NON-NLS-1$
+ t = StartupTrace.begin();
notificationManager = new NotificationManager(this);
notificationManager.startup(null);
+ StartupTrace.record("Workspace.startup/NotificationManager", t); //$NON-NLS-1$
+ t = StartupTrace.begin();
markerManager = new MarkerManager(this);
markerManager.startup(null);
+ StartupTrace.record("Workspace.startup/MarkerManager", t); //$NON-NLS-1$
+ t = StartupTrace.begin();
synchronizer = new Synchronizer(this);
saveManager = new SaveManager(this);
saveManager.startup(null);
+ StartupTrace.record("Workspace.startup/SaveManager", t); //$NON-NLS-1$
+ t = StartupTrace.begin();
propertyManager = new PropertyManager2(this);
propertyManager.startup(monitor);
+ StartupTrace.record("Workspace.startup/PropertyManager2", t); //$NON-NLS-1$
+ t = StartupTrace.begin();
charsetManager = new CharsetManager(this);
charsetManager.startup(null);
+ StartupTrace.record("Workspace.startup/CharsetManager", t); //$NON-NLS-1$
+ t = StartupTrace.begin();
contentDescriptionManager = new ContentDescriptionManager(this);
contentDescriptionManager.startup(null);
+ StartupTrace.record("Workspace.startup/ContentDescriptionManager", t); //$NON-NLS-1$
//must start after save manager, because (read) access to tree is needed
//must start after other managers to avoid potential cyclic dependency on uninitialized managers (see bug 316182)
//must start before alias manager (see bug 94829)
+ t = StartupTrace.begin();
refreshManager = new RefreshManager(this);
refreshManager.startup(null);
+ StartupTrace.record("Workspace.startup/RefreshManager", t); //$NON-NLS-1$
//must start at the end to avoid potential cyclic dependency on other uninitialized managers (see bug 369177)
+ t = StartupTrace.begin();
aliasManager = new AliasManager(this);
aliasManager.startup(null);
+ StartupTrace.record("Workspace.startup/AliasManager", t); //$NON-NLS-1$
} finally {
//unlock tree even in case of failure, otherwise shutdown will also fail
treeLocked = null;
_workManager.postWorkspaceStartup();
+ StartupTrace.record("Workspace.startup(total)", tAll); //$NON-NLS-1$
}
}
diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceChangeEvent.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceChangeEvent.java
index b4d0d30fb93..2d4ad4be70f 100644
--- a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceChangeEvent.java
+++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceChangeEvent.java
@@ -18,7 +18,7 @@
/**
* Resource change events describe changes to resources.
*
- * There are currently five different types of resource change events:
+ * The following types of resource change events are reported:
*
* -
* Before-the-fact batch reports of arbitrary creations,
diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/ResourcesPlugin.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/ResourcesPlugin.java
index efc710e73d2..915ef4f6698 100644
--- a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/ResourcesPlugin.java
+++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/ResourcesPlugin.java
@@ -25,6 +25,7 @@
import java.util.Hashtable;
import java.util.List;
import org.eclipse.core.internal.resources.Workspace;
+import org.eclipse.core.internal.runtime.StartupTrace;
import org.eclipse.core.internal.utils.Messages;
import org.eclipse.core.internal.utils.Policy;
import org.eclipse.core.runtime.CoreException;
@@ -549,20 +550,28 @@ public void stop(BundleContext context) throws Exception {
*/
@Override
public void start(BundleContext context) throws Exception {
+ long tTotal = StartupTrace.begin();
+ long tSuper = StartupTrace.begin();
super.start(context);
+ StartupTrace.record("ResourcesPlugin.start/super.start", tSuper); //$NON-NLS-1$
workspaceInitCustomizer = new WorkspaceInitCustomizer(context);
// register debug options listener
+ long tDbg = StartupTrace.begin();
Hashtable properties = new Hashtable<>(2);
properties.put(DebugOptions.LISTENER_SYMBOLICNAME, PI_RESOURCES);
debugRegistration = context.registerService(DebugOptionsListener.class, Policy.RESOURCES_DEBUG_OPTIONS_LISTENER,
properties);
+ StartupTrace.record("ResourcesPlugin.start/registerDebugOptions", tDbg); //$NON-NLS-1$
instanceLocationTracker = new ServiceTracker<>(context,
context.createFilter(String.format("(&%s(%s=*))", Location.INSTANCE_FILTER, //$NON-NLS-1$
Location.SERVICE_PROPERTY_URL)),
workspaceInitCustomizer);
plugin = this; // must before open the tracker, as this can cause the registration of the
// workspace and this might trigger code that calls the static method then.
+ long tOpen = StartupTrace.begin();
instanceLocationTracker.open();
+ StartupTrace.record("ResourcesPlugin.start/openInstanceLocationTracker", tOpen); //$NON-NLS-1$
+ StartupTrace.record("ResourcesPlugin.start (total)", tTotal); //$NON-NLS-1$
}
private final class WorkspaceInitCustomizer implements ServiceTrackerCustomizer {
@@ -579,20 +588,26 @@ public Workspace addingService(ServiceReference reference) {
if (workspace != null) {
return null; // there can only be one workspace right now...
}
+ long tLoc = StartupTrace.begin();
Location location = context.getService(reference);
+ StartupTrace.record("ResourcesPlugin.start/openInstanceLocationTracker/open instance Location", tLoc); //$NON-NLS-1$
if (location == null) {
return null; // we can't use that service...
}
// the workspace is accessible from now on, this is because some plugins require
// access to it in the early startup phase
+ long tCtor = StartupTrace.begin();
workspace = new Workspace();
+ StartupTrace.record("ResourcesPlugin.start/openInstanceLocationTracker/new Workspace", tCtor); //$NON-NLS-1$
IStatus result;
try {
result = workspace.open(null);
if (!result.isOK()) {
getLog().log(result);
}
+ long tReg = StartupTrace.begin();
workspaceRegistration = context.registerService(IWorkspace.class, workspace, null);
+ StartupTrace.record("ResourcesPlugin.start/openInstanceLocationTracker/register workspace with platform", tReg); //$NON-NLS-1$
return workspace;
} catch (CoreException e) {
getLog().log(e.getStatus());
diff --git a/runtime/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/InternalPlatform.java b/runtime/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/InternalPlatform.java
index e4ba969794e..7e5884e1d38 100644
--- a/runtime/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/InternalPlatform.java
+++ b/runtime/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/InternalPlatform.java
@@ -705,17 +705,31 @@ public void setRuntimeInstance(Plugin runtime) {
* Note: the content type manager must be initialized only after the registry has been created
*/
public void start(BundleContext runtimeContext) {
+ long tStart = StartupTrace.begin();
this.context = runtimeContext;
this.fwkWiring = runtimeContext.getBundle(Constants.SYSTEM_BUNDLE_LOCATION).adapt(FrameworkWiring.class);
+ long tTrackers = StartupTrace.begin();
openOSGiTrackers();
+ StartupTrace.record("InternalPlatform.start/openOSGiTrackers", tTrackers); //$NON-NLS-1$
splashEnded = false;
+ long tCmdLine = StartupTrace.begin();
processCommandLine(getEnvironmentInfoService().getNonFrameworkArgs());
+ StartupTrace.record("InternalPlatform.start/processCommandLine", tCmdLine); //$NON-NLS-1$
+ long tDebug = StartupTrace.begin();
initializeDebugFlags();
+ StartupTrace.record("InternalPlatform.start/initializeDebugFlags", tDebug); //$NON-NLS-1$
initialized = true;
stopped = false;
+ long tAuth = StartupTrace.begin();
initializeAuthorizationHandler();
+ StartupTrace.record("InternalPlatform.start/initializeAuthorizationHandler", tAuth); //$NON-NLS-1$
+ long tSsl = StartupTrace.begin();
initializeSSLContext();
+ StartupTrace.record("InternalPlatform.start/initializeSSLContext", tSsl); //$NON-NLS-1$
+ long tSvc = StartupTrace.begin();
startServices();
+ StartupTrace.record("InternalPlatform.start/startServices", tSvc); //$NON-NLS-1$
+ StartupTrace.record("InternalPlatform.start", tStart); //$NON-NLS-1$
}
/**
@@ -733,19 +747,46 @@ public void stop(BundleContext bundleContext) {
}
private void openOSGiTrackers() {
+ long t;
+ t = StartupTrace.begin();
instanceLocation = createOpenTracker(Location.INSTANCE_FILTER);
+ StartupTrace.record("openOSGiTrackers/instanceLocation", t); //$NON-NLS-1$
+ t = StartupTrace.begin();
userLocation = createOpenTracker(Location.USER_FILTER);
+ StartupTrace.record("openOSGiTrackers/userLocation", t); //$NON-NLS-1$
+ t = StartupTrace.begin();
configurationLocation = createOpenTracker(Location.CONFIGURATION_FILTER);
+ StartupTrace.record("openOSGiTrackers/configurationLocation", t); //$NON-NLS-1$
+ t = StartupTrace.begin();
installLocation = createOpenTracker(Location.INSTALL_FILTER);
+ StartupTrace.record("openOSGiTrackers/installLocation", t); //$NON-NLS-1$
+ t = StartupTrace.begin();
logTracker = createOpenTracker(FrameworkLog.class);
+ StartupTrace.record("openOSGiTrackers/FrameworkLog", t); //$NON-NLS-1$
+ t = StartupTrace.begin();
platformTracker = createOpenTracker(PlatformAdmin.class);
+ StartupTrace.record("openOSGiTrackers/PlatformAdmin", t); //$NON-NLS-1$
+ t = StartupTrace.begin();
contentTracker = createOpenTracker(IContentTypeManager.class);
+ StartupTrace.record("openOSGiTrackers/IContentTypeManager", t); //$NON-NLS-1$
+ t = StartupTrace.begin();
preferencesTracker = createOpenTracker(IPreferencesService.class);
+ StartupTrace.record("openOSGiTrackers/IPreferencesService", t); //$NON-NLS-1$
+ t = StartupTrace.begin();
groupProviderTracker = createOpenTracker("(objectClass=" + IBundleGroupProvider.class.getName() + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+ StartupTrace.record("openOSGiTrackers/IBundleGroupProvider", t); //$NON-NLS-1$
+ t = StartupTrace.begin();
logReaderTracker = createOpenTracker(ExtendedLogReaderService.class);
+ StartupTrace.record("openOSGiTrackers/ExtendedLogReaderService", t); //$NON-NLS-1$
+ t = StartupTrace.begin();
extendedLogTracker = createOpenTracker(ExtendedLogService.class);
+ StartupTrace.record("openOSGiTrackers/ExtendedLogService", t); //$NON-NLS-1$
+ t = StartupTrace.begin();
environmentTracker = createOpenTracker(EnvironmentInfo.class);
+ StartupTrace.record("openOSGiTrackers/EnvironmentInfo", t); //$NON-NLS-1$
+ t = StartupTrace.begin();
debugTracker = createOpenTracker(DebugOptions.class);
+ StartupTrace.record("openOSGiTrackers/DebugOptions", t); //$NON-NLS-1$
}
private ServiceTracker createOpenTracker(String filterStr) {
diff --git a/runtime/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/KeyStoreUtil.java b/runtime/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/KeyStoreUtil.java
index 20299109d48..95f927f7e64 100644
--- a/runtime/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/KeyStoreUtil.java
+++ b/runtime/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/KeyStoreUtil.java
@@ -53,6 +53,7 @@ public void setUpSslContext() throws GeneralSecurityException, IOException {
return;
}
+ long tTrust = StartupTrace.begin();
List keyStores = new ArrayList<>();
// null will loads JVM cacerts OR store indicated by "javax.net.ssl.trustStore" properties
keyStores.add(new KeyStoreAndPassword(null, null));
@@ -68,16 +69,21 @@ public void setUpSslContext() throws GeneralSecurityException, IOException {
trustManagers.add(createX509TrustManager(storeAndPassword.keyStore()));
}
TrustManager[] tm = { new CollectionTrustManager(trustManagers) };
+ StartupTrace.record("InternalPlatform.start/initializeSSLContext/TrustManager init", tTrust);
+ long tKey = StartupTrace.begin();
KeyManager[] km = {};
KeyStoreAndPassword keyStore = createKeyStoreFromSystemProperties();
if (keyStore != null) {
km = new KeyManager[] { createX509KeyManager(keyStore.keyStore(), keyStore.password()) };
}
+ StartupTrace.record("InternalPlatform.start/initializeSSLContext/KeyManager init", tKey);
+ long tCtx = StartupTrace.begin();
SSLContext sslContext = SSLContext.getInstance("TLS");
initSSLContext(sslContext, tm, km, null);
SSLContext.setDefault(sslContext);
+ StartupTrace.record("InternalPlatform.start/initializeSSLContext/SSLContext.getDefault", tCtx);
}
private KeyStoreAndPassword createKeyStore(String type, String provider)
diff --git a/runtime/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/StartupTrace.java b/runtime/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/StartupTrace.java
new file mode 100644
index 00000000000..760d272d3a1
--- /dev/null
+++ b/runtime/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/StartupTrace.java
@@ -0,0 +1,378 @@
+/*******************************************************************************
+ * Local-only startup tracer for measuring Eclipse platform startup phases.
+ * NOT FOR UPSTREAM MERGE. Always-on; writes to ${user.home}/.eclipse/startup-trace.csv.
+ *
+ * Lives in equinox.common so it is visible to every downstream bundle that
+ * Require-Bundles org.eclipse.equinox.common (directly or via the split package
+ * contributed by org.eclipse.core.runtime).
+ *
+ * Flush strategy:
+ * - A daemon ScheduledExecutorService drains buffered entries to the CSV
+ * every few seconds so abnormal exits (kill -9, Runtime.halt, exec-restart,
+ * JVM crash) lose at most the last interval instead of the whole run.
+ * - A JVM shutdown hook does a final drain and prints the cumulative summary.
+ * - Callers may invoke {@link #flush()} explicitly from known quiesce points
+ * (e.g. Activator.stop, Workbench.close) for deterministic end-of-run data.
+ *******************************************************************************/
+package org.eclipse.core.internal.runtime;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Always-on lightweight startup tracer. Uses only {@code java.*} APIs so it is
+ * safe to call from the earliest startup phases before any Eclipse plug-in is
+ * activated. All call sites share one static queue and one {@code RUN_ID}, so
+ * contributions from every bundle end up in a single CSV under one run.
+ */
+public final class StartupTrace {
+
+ /**
+ * Hardcoded to {@code true} for this tracing build. Exposed so callers may
+ * guard expensive trace-building code, though in practice begin/record are
+ * cheap enough that gating is unnecessary.
+ */
+ public static final boolean ENABLED = true;
+
+ /** How often the background daemon drains the queue to disk. */
+ private static final long FLUSH_INTERVAL_SECONDS = 2L;
+
+ private static final Path OUTPUT_DIR = Path.of(System.getProperty("user.home"), ".eclipse"); //$NON-NLS-1$ //$NON-NLS-2$
+ private static final Path OUTPUT_CSV = OUTPUT_DIR.resolve("startup-trace.csv"); //$NON-NLS-1$
+
+ /**
+ * Prompt written once at the top of a freshly-created CSV. Lets the file be
+ * pasted directly into a large-context AI for filtering and summarization
+ * before downstream bottleneck analysis. Each instruction line is prefixed
+ * with {@code # } so the body is still grep/awk-filterable as
+ * {@code grep -v '^#' startup-trace.csv}.
+ */
+ private static final String ANALYSIS_PROMPT = """
+ # === AI ANALYSIS PROMPT — read this before processing the CSV below ===
+ #
+ # This file is a startup-time span trace from the Eclipse IDE platform
+ # runtime, produced by org.eclipse.core.internal.runtime.StartupTrace.
+ # Each line after the blank line is a CSV row with this schema:
+ # runId,seq,phase,startNs,endNs,durationUs,thread
+ #
+ # Field semantics
+ # - runId: short hex token identifying one JVM startup. The file
+ # accumulates across runs; multiple runIds may be present.
+ # - seq: per-run monotonically increasing entry sequence (not
+ # globally unique across runs).
+ # - phase: span name. '/' separates parent and child spans, e.g.
+ # "Workspace.open" parent
+ # "Workspace.open/setExplicitWorkspaceEncoding" child
+ # A phase may appear many times in one run (loop bodies).
+ # - startNs,
+ # endNs: System.nanoTime() values. Comparable ONLY within one
+ # runId. Not wall-clock; not comparable across runs.
+ # - durationUs: pre-computed (endNs - startNs) / 1000.
+ # - thread: thread that recorded the span.
+ #
+ # Zero-duration entries (startNs == endNs) are CHECKPOINTS emitted by
+ # StartupTrace.mark(name), not spans. Treat them as timestamps.
+ #
+ # The trace is frozen when the 'startup complete' checkpoint is
+ # recorded. So the last row in the file corresponds to end-of-startup;
+ # nothing after that is captured. The whole file is "startup data";
+ # you do not need to filter out a steady-state tail.
+ #
+ # === YOUR TASK ===
+ #
+ # Produce a compact per-run analysis summary that a downstream AI will
+ # consume as the input for bottleneck analysis. Do NOT echo raw CSV.
+ # Output strict Markdown only, using tables.
+ #
+ # For EACH runId, emit a "## Run " section containing:
+ #
+ # 1. Run overview: total observed wall time (max(endNs) - min(startNs)
+ # in ms), distinct phase count, total entry count, the set of
+ # threads that recorded spans, and a table of all mark()
+ # checkpoints with their offset-from-run-start in ms.
+ #
+ # 2. Top 20 phases by cumulative duration. Columns:
+ # phase, total_ms, calls, mean_ms, max_ms. Sort by total_ms desc.
+ #
+ # 3. Hot leaf phases: top 10 phases that are NOT a prefix of any other
+ # phase in the same run, by cumulative ms. Same columns as (2).
+ #
+ # 4. Parent-vs-children gaps. For each phase whose name is a prefix of
+ # at least one other phase, compute
+ # self_time = parent_total - sum(direct_child_totals)
+ # Report parents with self_time > 50 ms. Columns:
+ # parent, parent_ms, summed_children_ms, gap_ms. This identifies
+ # unaccounted time INSIDE a span that needs further sub-division.
+ #
+ # 5. Outliers: for any phase with call_count >= 3, list calls whose
+ # duration is > 2x the mean of the other calls of the same phase.
+ # Columns: phase, outlier_ms, mean_of_others_ms.
+ #
+ # Then, if the file contains >= 2 runIds, a final
+ # "## Cross-run comparison" section with the 10 phases having the
+ # highest p95/median ratio (most variable). Columns:
+ # phase, runs, median_ms, p95_ms, min_ms, max_ms.
+ #
+ # Do NOT speculate on root causes — that is the downstream AI's job.
+ # Stick to measurements. Do not normalize or deduplicate phase names.
+ # Do not include raw entry-level dumps in the summary.
+ #
+ # === END PROMPT — CSV data follows ===
+
+ """;
+
+ private static final ConcurrentLinkedQueue ENTRIES = new ConcurrentLinkedQueue<>();
+ private static final AtomicLong SEQ = new AtomicLong();
+ private static final AtomicLong TOTAL_WRITTEN = new AtomicLong();
+ private static final AtomicBoolean SUMMARY_PRINTED = new AtomicBoolean();
+ /** True until the first flush of this JVM; first flush truncates any prior file. */
+ private static final AtomicBoolean FIRST_FLUSH = new AtomicBoolean(true);
+ /**
+ * Sentinel mark name that, when passed to {@link #mark(String)}, freezes the
+ * trace: stops the periodic flush daemon, drains the queue one last time,
+ * and turns all subsequent {@link #begin()}, {@link #record(String, long)},
+ * and {@link #mark(String)} calls into no-ops. Lets the CSV stop at end of
+ * startup so runtime activity is not mixed in.
+ */
+ public static final String END_OF_STARTUP_MARK = "startup complete"; //$NON-NLS-1$
+ /** Flipped true by {@link #freeze()}; gates all further trace recording. */
+ private static final AtomicBoolean FROZEN = new AtomicBoolean(false);
+ private static final Object WRITE_LOCK = new Object();
+ private static final ConcurrentHashMap CUMULATIVE = new ConcurrentHashMap<>();
+ private static final String RUN_ID = Long.toHexString(System.currentTimeMillis()) + "-" //$NON-NLS-1$
+ + Long.toHexString(ThreadLocalRandom.current().nextLong() & 0xFFFFFFFFL);
+
+ private static final ScheduledExecutorService SCHEDULER = Executors.newSingleThreadScheduledExecutor(r -> {
+ Thread t = new Thread(r, "StartupTrace-periodic"); //$NON-NLS-1$
+ t.setDaemon(true);
+ return t;
+ });
+
+ static {
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+ try {
+ SCHEDULER.shutdownNow();
+ } catch (RuntimeException ignored) {
+ // best-effort
+ }
+ flushInternal("shutdown"); //$NON-NLS-1$
+ printSummaryOnce();
+ }, "StartupTrace-shutdown")); //$NON-NLS-1$
+
+ SCHEDULER.scheduleWithFixedDelay(() -> {
+ try {
+ flushInternal("periodic"); //$NON-NLS-1$
+ } catch (RuntimeException ignored) {
+ // never let a write failure kill the scheduler thread
+ }
+ }, FLUSH_INTERVAL_SECONDS, FLUSH_INTERVAL_SECONDS, TimeUnit.SECONDS);
+
+ System.out.println("[StartupTrace] enabled, runId=" + RUN_ID //$NON-NLS-1$
+ + ", periodicFlush=" + FLUSH_INTERVAL_SECONDS + "s"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ private StartupTrace() {
+ }
+
+ /** Returns a start timestamp (ns). */
+ public static long begin() {
+ return (ENABLED && !FROZEN.get()) ? System.nanoTime() : 0L;
+ }
+
+ /** Records a finished span. */
+ public static void record(String phase, long startNanos) {
+ if (!ENABLED || FROZEN.get()) {
+ return;
+ }
+ long end = System.nanoTime();
+ ENTRIES.add(new Entry(SEQ.getAndIncrement(), phase, startNanos, end, Thread.currentThread().getName()));
+ }
+
+ /**
+ * Records an instantaneous marker: startNs == endNs, durationUs == 0.
+ * Useful for checkpoints like "startup complete" where the timestamp is
+ * what matters, not a duration. Passing {@link #END_OF_STARTUP_MARK}
+ * freezes the trace after recording the marker.
+ */
+ public static void mark(String phase) {
+ if (!ENABLED || FROZEN.get()) {
+ return;
+ }
+ long now = System.nanoTime();
+ ENTRIES.add(new Entry(SEQ.getAndIncrement(), phase, now, now, Thread.currentThread().getName()));
+ if (END_OF_STARTUP_MARK.equals(phase)) {
+ freeze();
+ }
+ }
+
+ /** Convenience: time a Runnable. */
+ public static void time(String phase, Runnable r) {
+ if (!ENABLED) {
+ r.run();
+ return;
+ }
+ long t = System.nanoTime();
+ try {
+ r.run();
+ } finally {
+ record(phase, t);
+ }
+ }
+
+ /**
+ * Drains any buffered entries to the CSV immediately. Safe to call from any
+ * thread, any number of times. Does not print the cumulative summary
+ * (reserved for shutdown). Intended for known quiesce points such as
+ * {@code BundleActivator.stop} or {@code Workbench.close}.
+ */
+ public static void flush() {
+ flushInternal("flush"); //$NON-NLS-1$
+ }
+
+ /**
+ * Back-compat entry point used by the JVM shutdown hook. Drains, then prints
+ * the cumulative summary exactly once per JVM.
+ */
+ public static void dump() {
+ flushInternal("dump"); //$NON-NLS-1$
+ printSummaryOnce();
+ }
+
+ /**
+ * Stops the periodic daemon, drains the queue one last time, and gates all
+ * future {@link #begin()} / {@link #record(String, long)} / {@link #mark(String)}
+ * calls as no-ops. Idempotent and thread-safe; only the first invocation
+ * does the work.
+ */
+ private static void freeze() {
+ if (!FROZEN.compareAndSet(false, true)) {
+ return;
+ }
+ try {
+ SCHEDULER.shutdownNow();
+ } catch (RuntimeException ignored) {
+ // best-effort
+ }
+ flushInternal("freeze"); //$NON-NLS-1$
+ System.out.println("[StartupTrace] frozen at '" + END_OF_STARTUP_MARK //$NON-NLS-1$
+ + "' mark; further begin/record/mark calls are no-ops, runId=" + RUN_ID); //$NON-NLS-1$
+ }
+
+ private static void flushInternal(String reason) {
+ List drained = new ArrayList<>();
+ Entry e;
+ while ((e = ENTRIES.poll()) != null) {
+ drained.add(e);
+ }
+ if (drained.isEmpty()) {
+ return;
+ }
+ drained.sort(Comparator.comparingLong(en -> en.seq));
+
+ synchronized (WRITE_LOCK) {
+ try {
+ Files.createDirectories(OUTPUT_DIR);
+ boolean firstFlush = FIRST_FLUSH.getAndSet(false);
+ StandardOpenOption mode = firstFlush ? StandardOpenOption.TRUNCATE_EXISTING
+ : StandardOpenOption.APPEND;
+ try (BufferedWriter w = Files.newBufferedWriter(OUTPUT_CSV, StandardCharsets.UTF_8,
+ StandardOpenOption.CREATE, mode)) {
+ if (firstFlush) {
+ w.write(ANALYSIS_PROMPT);
+ w.write("runId,seq,phase,startNs,endNs,durationUs,thread\n"); //$NON-NLS-1$
+ }
+ for (Entry en : drained) {
+ long durNs = en.endNs - en.startNs;
+ long durUs = durNs / 1000L;
+ long[] agg = CUMULATIVE.computeIfAbsent(en.phase, k -> new long[2]);
+ agg[0] += durNs;
+ agg[1] += 1;
+ w.write(RUN_ID);
+ w.write(',');
+ w.write(Long.toString(en.seq));
+ w.write(',');
+ w.write(csvEscape(en.phase));
+ w.write(',');
+ w.write(Long.toString(en.startNs));
+ w.write(',');
+ w.write(Long.toString(en.endNs));
+ w.write(',');
+ w.write(Long.toString(durUs));
+ w.write(',');
+ w.write(csvEscape(en.thread));
+ w.write('\n');
+ }
+ }
+ long total = TOTAL_WRITTEN.addAndGet(drained.size());
+ System.out.println("[StartupTrace] wrote " + drained.size() //$NON-NLS-1$
+ + " entries for runId=" + RUN_ID //$NON-NLS-1$
+ + " (reason=" + reason //$NON-NLS-1$
+ + ", totalWritten=" + total + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+ } catch (IOException ex) {
+ System.err.println("[StartupTrace] failed to flush (reason=" + reason + "): " + ex); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ }
+ }
+
+ private static void printSummaryOnce() {
+ if (!SUMMARY_PRINTED.compareAndSet(false, true)) {
+ return;
+ }
+ List> sorted = new ArrayList<>(CUMULATIVE.entrySet());
+ sorted.sort((a, b) -> Long.compare(b.getValue()[0], a.getValue()[0]));
+ System.out.println("[StartupTrace] runId=" + RUN_ID //$NON-NLS-1$
+ + " totalEntries=" + TOTAL_WRITTEN.get() //$NON-NLS-1$
+ + " csv=" + OUTPUT_CSV); //$NON-NLS-1$
+ System.out.println("[StartupTrace] top phases by cumulative time:"); //$NON-NLS-1$
+ System.out.printf(" %10s %5s %s%n", "cum_ms", "count", "phase"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ int n = Math.min(40, sorted.size());
+ for (int i = 0; i < n; i++) {
+ Map.Entry m = sorted.get(i);
+ double ms = m.getValue()[0] / 1_000_000.0;
+ long count = m.getValue()[1];
+ System.out.printf(" %10.3f %5d %s%n", ms, count, m.getKey()); //$NON-NLS-1$
+ }
+ }
+
+ private static String csvEscape(String s) {
+ if (s == null) {
+ return ""; //$NON-NLS-1$
+ }
+ if (s.indexOf(',') < 0 && s.indexOf('"') < 0 && s.indexOf('\n') < 0) {
+ return s;
+ }
+ return "\"" + s.replace("\"", "\"\"") + "\""; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ }
+
+ private static final class Entry {
+ final long seq;
+ final String phase;
+ final long startNs;
+ final long endNs;
+ final String thread;
+
+ Entry(long seq, String phase, long startNs, long endNs, String thread) {
+ this.seq = seq;
+ this.phase = phase;
+ this.startNs = startNs;
+ this.endNs = endNs;
+ this.thread = thread;
+ }
+ }
+}