Log PNG/SVG icons load time statistics

This commit is contained in:
Anton Tarasov
2018-01-12 17:37:57 +03:00
parent 8d6644415e
commit c7e9cdd5d4
5 changed files with 129 additions and 11 deletions
@@ -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<Integer> 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<Long, String> 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);
}
}
}
@@ -286,6 +286,8 @@
<welcomeFrameProvider implementation="com.intellij.openapi.wm.impl.welcomeScreen.FlatWelcomeFrameProvider"/>
<postStartupActivity implementation="com.intellij.internal.IconsLoadTime$StartupLoadTime"/>
<!-- General -->
<applicationConfigurable groupId="appearance" groupWeight="120" key="title.general" bundle="messages.IdeBundle" id="preferences.general"
instance="com.intellij.ide.GeneralSettingsConfigurable"/>
@@ -260,6 +260,8 @@
<action id="CloseEditor" class="com.intellij.ide.actions.CloseEditorAction"/>
<action id="PinActiveTab" class="com.intellij.ide.actions.PinActiveTabAction"/>
<action id="IconsLoadTime" internal="true" class="com.intellij.internal.IconsLoadTime" text="Log icons load time"/>
<group id="MainMenu">
<group id="FileMenu" popup="true">
<group id="FileOpenGroup">
@@ -55,7 +55,7 @@ public class FieldAccessor<E, T> {
}
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<E, T> {
}
public void set(@Nullable E object, @Nullable T value) {
if (!isAvailable() || object == null) return;
if (!isAvailable()) return;
try {
myFieldRef.get().set(object, value);
}
@@ -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<String, Image> 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));
}