diff --git a/platform/platform-impl/src/com/intellij/internal/IconsLoadTime.java b/platform/platform-impl/src/com/intellij/internal/IconsLoadTime.java new file mode 100644 index 000000000000..a8b52461fdcf --- /dev/null +++ b/platform/platform-impl/src/com/intellij/internal/IconsLoadTime.java @@ -0,0 +1,86 @@ +/* + * Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. + */ +package com.intellij.internal; + +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.DumbAwareAction; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.startup.StartupActivity; +import com.intellij.openapi.util.registry.Registry; +import com.intellij.util.FieldAccessor; +import com.intellij.util.ImageLoader; +import com.intellij.util.ImageLoader.LoadFunction; +import org.jetbrains.annotations.NotNull; + +import java.awt.*; +import java.io.IOException; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; + +/** + * Logs load time statistics for PNG/SVG images, such as: average, median, ide startup total, first N icons total. + * + * @author tav + */ +public class IconsLoadTime extends DumbAwareAction { + private static final Logger LOG = Logger.getInstance("#com.intellij.internal.IconsLoadTime"); + + private static final boolean IS_INTERNAL_MODE = Boolean.valueOf(System.getProperty("idea.is.internal")).booleanValue(); + private static final int STATS_LIMIT = 10000; + private static final int FIXED_SCOPE = 100; // log stats for a first fixed number of icons + + private static final List stats = IS_INTERNAL_MODE ? Collections.synchronizedList(new LinkedList<>()) : null; // load time per icon + + static { + if (IS_INTERNAL_MODE) new FieldAccessor<>(ImageLoader.class, "measureLoad").set(null, (LoadFunction)func -> measure(Objects.requireNonNull(func))); + } + + @Override + public void actionPerformed(AnActionEvent e) { + log(false); + } + + private static void log(boolean measureStartupLoad) { + if (stats == null || stats.isEmpty()) return; + + int size = stats.size(); + long sum = stats.stream().mapToInt(Integer::intValue).sum(); + long average = sum / size; + long median = (size % 2 == 0) ? stats.get(size / 2 - 1) + stats.get(size / 2) : stats.get(size / 2); + + Function ms = (nano) -> String.format("%.02fms", nano / 1000000f); + + LOG.info((Registry.is("ide.svg.icon") ? "SVG" : "PNG") + + " load time: " + + (measureStartupLoad ? "ide_startup=" : "total=") + ms.apply(sum) + + ", average=" + ms.apply(average) + + ", median=" + ms.apply(median) + + "; number of icons: " + size); + } + + private static Image measure(LoadFunction func) throws IOException { + boolean measure = stats.size() < STATS_LIMIT; + long t = measure ? System.nanoTime() : 0; + + Image img = func.load(null); + + if (measure) { + stats.add((int)(System.nanoTime() - t)); + if (stats.size() == FIXED_SCOPE) log(false); + } + return img; + } + + public static class StartupLoadTime implements StartupActivity, DumbAware { + @Override + public void runActivity(@NotNull Project project) { + if (IS_INTERNAL_MODE) log(true); + } + } +} diff --git a/platform/platform-resources/src/META-INF/PlatformExtensions.xml b/platform/platform-resources/src/META-INF/PlatformExtensions.xml index ae273649247b..2534f2bbeb9c 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensions.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensions.xml @@ -286,6 +286,8 @@ + + diff --git a/platform/platform-resources/src/idea/PlatformActions.xml b/platform/platform-resources/src/idea/PlatformActions.xml index 532cac031020..3ea8629aabb3 100644 --- a/platform/platform-resources/src/idea/PlatformActions.xml +++ b/platform/platform-resources/src/idea/PlatformActions.xml @@ -260,6 +260,8 @@ + + diff --git a/platform/util/src/com/intellij/util/FieldAccessor.java b/platform/util/src/com/intellij/util/FieldAccessor.java index 6fc0504b71ad..cfe8af35c357 100644 --- a/platform/util/src/com/intellij/util/FieldAccessor.java +++ b/platform/util/src/com/intellij/util/FieldAccessor.java @@ -55,7 +55,7 @@ public class FieldAccessor { } public T get(@Nullable E object) { - if (!isAvailable() || object == null) return null; + if (!isAvailable()) return null; try { @SuppressWarnings("unchecked") T value = (T)myFieldRef.get().get(object); @@ -68,7 +68,7 @@ public class FieldAccessor { } public void set(@Nullable E object, @Nullable T value) { - if (!isAvailable() || object == null) return; + if (!isAvailable()) return; try { myFieldRef.get().set(object, value); } diff --git a/platform/util/src/com/intellij/util/ImageLoader.java b/platform/util/src/com/intellij/util/ImageLoader.java index cd93f76c74cd..75a363422205 100644 --- a/platform/util/src/com/intellij/util/ImageLoader.java +++ b/platform/util/src/com/intellij/util/ImageLoader.java @@ -54,21 +54,51 @@ public class ImageLoader implements Serializable { public static final int CACHED_IMAGE_MAX_SIZE = (int)Math.round(Registry.doubleValue("ide.cached.image.max.size") * 1024 * 1024); private static final ConcurrentMap ourCache = ContainerUtil.createConcurrentSoftValueMap(); + private static final boolean SVG_ENABLED = Registry.is("ide.svg.icon"); + + @SuppressWarnings({"UnusedDeclaration"}) // set from com.intellij.internal.IconsLoadTime + private static LoadFunction measureLoad; + + /** + * For internal usage. + */ + public interface LoadFunction { + Image load(@Nullable LoadFunction delegate) throws IOException; + } + private static class ImageDesc { public enum Type { PNG, SVG { @Override - public Image load(URL url, InputStream is, double scale) throws IOException { - return SVGLoader.load(url, is, scale); + public Image load(final URL url, final InputStream is, final double scale) throws IOException { + LoadFunction f = new LoadFunction() { + @Override + public Image load(LoadFunction delegate) throws IOException { + return SVGLoader.load(url, is, scale); + } + }; + if (measureLoad != null && SVG_ENABLED) { + return measureLoad.load(f); + } + return f.load(null); } }, UNDEFINED; - public Image load(URL url, InputStream stream, double scale) throws IOException { - return ImageLoader.load(stream, scale); + public Image load(final URL url, final InputStream is, final double scale) throws IOException { + LoadFunction f = new LoadFunction() { + @Override + public Image load(LoadFunction delegate) { + return ImageLoader.load(is, scale); + } + }; + if (measureLoad != null && !SVG_ENABLED) { + return measureLoad.load(f); + } + return f.load(null); } } @@ -170,23 +200,21 @@ public class ImageLoader implements Serializable { { ImageDescList vars = new ImageDescList(); - boolean ideSvgIconSupport = Registry.is("ide.svg.icon"); - // Prefer retina images for HiDPI scale, because downscaling // retina images provides a better result than upscaling non-retina images. boolean retina = JBUI.isHiDPI(ctx.getScale(PIX_SCALE)); - if (retina || dark || ideSvgIconSupport) { + if (retina || dark || SVG_ENABLED) { final String name = FileUtil.getNameWithoutExtension(file); final String ext = FileUtilRt.getExtension(file); double scale = adjustScaleFactor(allowFloatScaling, ctx.getScale(PIX_SCALE)); - if (ideSvgIconSupport && dark) { + if (SVG_ENABLED && dark) { vars.add(new ImageDesc(name + "_dark.svg", cls, scale, ImageDesc.Type.SVG)); } - if (ideSvgIconSupport) { + if (SVG_ENABLED) { vars.add(new ImageDesc(name + ".svg", cls, scale, ImageDesc.Type.SVG)); }