From 22b9b89f2b138b594cced165090c967fd8b7db12 Mon Sep 17 00:00:00 2001 From: Alexey Kudravtsev Date: Wed, 13 Jul 2016 13:16:09 +0300 Subject: [PATCH] less memory for action update/write action statistics --- .../openapi/actionSystem/ex/ActionUtil.java | 8 +- .../application/impl/ApplicationImpl.java | 2 +- .../testFramework/src/_LastInSuiteTest.java | 2 +- .../src/com/intellij/util/PausesStat.java | 55 +++-- .../containers/UnsignedShortArrayList.java | 226 ++++++++++++++++++ 5 files changed, 262 insertions(+), 31 deletions(-) create mode 100644 platform/util/src/com/intellij/util/containers/UnsignedShortArrayList.java diff --git a/platform/platform-api/src/com/intellij/openapi/actionSystem/ex/ActionUtil.java b/platform/platform-api/src/com/intellij/openapi/actionSystem/ex/ActionUtil.java index c2029184e88e..b9c6d959b481 100644 --- a/platform/platform-api/src/com/intellij/openapi/actionSystem/ex/ActionUtil.java +++ b/platform/platform-api/src/com/intellij/openapi/actionSystem/ex/ActionUtil.java @@ -88,7 +88,6 @@ public class ActionUtil { + " not available while " + ApplicationNamesInfo.getInstance().getProductName() + " is updating indices"; } - public static final PausesStat ACTION_UPDATE_PAUSES = new PausesStat("AnAction.update()"); private static int insidePerformDumbAwareUpdate; /** * @param action action @@ -113,7 +112,7 @@ public class ActionUtil { final boolean notAllowed = dumbMode && !action.isDumbAware(); if (insidePerformDumbAwareUpdate++ == 0) { - ACTION_UPDATE_PAUSES.started(); + ActionPauses.STAT.started(); } try { if (beforeActionPerformed) { @@ -133,7 +132,7 @@ public class ActionUtil { } finally { if (--insidePerformDumbAwareUpdate == 0) { - ACTION_UPDATE_PAUSES.finished(presentation.getText()+" action update ("+action.getClass()+")"); + ActionPauses.STAT.finished(presentation.getText() + " action update (" + action.getClass() + ")"); } if (notAllowed) { if (wasEnabledBefore == null) { @@ -145,6 +144,9 @@ public class ActionUtil { return false; } + public static class ActionPauses { + public static final PausesStat STAT = new PausesStat("AnAction.update()"); + } /** * @return whether a dumb mode is in progress for the passed project or, if the argument is null, for any open project. diff --git a/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java b/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java index 2080e83bd4e6..fd5dde976fce 100644 --- a/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java @@ -488,7 +488,7 @@ public class ApplicationImpl extends PlatformComponentManagerImpl implements App if (gatherStatistics) { //noinspection TestOnlyProblems LOG.info(writeActionStatistics()); - LOG.info(ActionUtil.ACTION_UPDATE_PAUSES.statistics()); + LOG.info(ActionUtil.ActionPauses.STAT.statistics()); //noinspection TestOnlyProblems LOG.info(((AppScheduledExecutorService)AppExecutorUtil.getAppScheduledExecutorService()).statistics() + "; ProcessIOExecutorService threads: "+((ProcessIOExecutorService)ProcessIOExecutorService.INSTANCE).getThreadCounter() diff --git a/platform/testFramework/src/_LastInSuiteTest.java b/platform/testFramework/src/_LastInSuiteTest.java index 91c3726d5f8e..989bc325ccd4 100644 --- a/platform/testFramework/src/_LastInSuiteTest.java +++ b/platform/testFramework/src/_LastInSuiteTest.java @@ -54,7 +54,7 @@ public class _LastInSuiteTest extends TestCase { PlatformTestUtil.cleanupAllProjects(); ApplicationImpl application = (ApplicationImpl)ApplicationManager.getApplication(); System.out.println(application.writeActionStatistics()); - System.out.println(ActionUtil.ACTION_UPDATE_PAUSES.statistics()); + System.out.println(ActionUtil.ActionPauses.STAT.statistics()); System.out.println(((AppScheduledExecutorService)AppExecutorUtil.getAppScheduledExecutorService()).statistics()); System.out.println("ProcessIOExecutorService threads created: "+((ProcessIOExecutorService)ProcessIOExecutorService.INSTANCE).getThreadCounter()); diff --git a/platform/util/src/com/intellij/util/PausesStat.java b/platform/util/src/com/intellij/util/PausesStat.java index 20ab5bfd86db..fbd15c980320 100644 --- a/platform/util/src/com/intellij/util/PausesStat.java +++ b/platform/util/src/com/intellij/util/PausesStat.java @@ -15,16 +15,19 @@ */ package com.intellij.util; -import gnu.trove.TIntArrayList; +import com.intellij.util.containers.UnsignedShortArrayList; import org.jetbrains.annotations.NotNull; +import java.awt.*; + public class PausesStat { - private static final int N_MAX = 200000; - // stores pairs of (timestamp of the event start), (timestamp of the event end). Timestamps are stored as diffs between System.currentTimeMillis() and epochStart. - private final TIntArrayList pauses = new TIntArrayList(); - private final long epochStart; + private static final int N_MAX = 100000; + // stores durations of the event: (timestamp of the event end) - (timestamp of the event start) in milliseconds. + private final UnsignedShortArrayList durations = new UnsignedShortArrayList(); @NotNull private final String myName; - private volatile boolean started; + private final Thread myEdtThread; + private boolean started; + private long startTimeStamp; private int maxDuration; private Object maxDurationDescription; private int totalNumberRecorded; @@ -32,51 +35,51 @@ public class PausesStat { public PausesStat(@NotNull String name) { myName = name; - epochStart = System.currentTimeMillis(); + assert EventQueue.isDispatchThread() : Thread.currentThread(); + myEdtThread = Thread.currentThread(); } - private int register() { - int stamp = (int)(System.currentTimeMillis() - epochStart); - if (pauses.size()/2 == N_MAX) { - pauses.set(indexToOverwrite, stamp); + private int register(int duration) { + if (durations.size() == N_MAX) { + durations.set(indexToOverwrite, duration); indexToOverwrite = (indexToOverwrite + 1) % N_MAX; } else { - pauses.add(stamp); + durations.add(duration); } - return stamp; + return duration; } public void started() { + assertEdt(); assert !started; - register(); started = true; + startTimeStamp = System.currentTimeMillis(); + } + + private void assertEdt() { + assert Thread.currentThread() == myEdtThread : Thread.currentThread(); } public void finished(@NotNull String description) { + assertEdt(); assert started; - int startStamp = pauses.get(pauses.size()/2 == N_MAX ? indexToOverwrite-1 : pauses.size() - 1); - int finishStamp = register(); - int duration = finishStamp - startStamp; + long finishStamp = System.currentTimeMillis(); + int duration = (int)(finishStamp - startTimeStamp); started = false; + duration = Math.min(duration, (1 << 16) - 1); if (duration > maxDuration) { maxDuration = duration; maxDurationDescription = description; } totalNumberRecorded++; + register(duration); } public String statistics() { int total = 0; - int number = pauses.size() / 2; - int[] duration = new int[number]; - for (int i = 0; i < number*2; i+=2) { - int start = pauses.get(i); - int finish = pauses.get(i+1); - int thisDuration = finish - start; - total += thisDuration; - duration[i / 2] = thisDuration; - } + int number = durations.size(); + int[] duration = durations.toArray(); return myName + " Statistics" + (totalNumberRecorded == number ? "" : " ("+totalNumberRecorded+" events was recorded in total, but only last "+number+" are reported here)")+":"+ "\nEvent number: " + number + diff --git a/platform/util/src/com/intellij/util/containers/UnsignedShortArrayList.java b/platform/util/src/com/intellij/util/containers/UnsignedShortArrayList.java new file mode 100644 index 000000000000..df67fb86d92c --- /dev/null +++ b/platform/util/src/com/intellij/util/containers/UnsignedShortArrayList.java @@ -0,0 +1,226 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.util.containers; + +import com.intellij.util.ArrayUtil; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; + +public class UnsignedShortArrayList implements Cloneable { + private char[] myData; // use char as an unsigned short + private int mySize; + + public UnsignedShortArrayList(int initialCapacity) { + myData = new char[initialCapacity]; + } + + public UnsignedShortArrayList() { + this(10); + } + + public void trimToSize() { + if (mySize < myData.length){ + myData = ArrayUtil.realloc(myData, mySize); + } + } + + public void ensureCapacity(int minCapacity) { + int oldCapacity = myData.length; + if (minCapacity > oldCapacity){ + char[] oldData = myData; + int newCapacity = oldCapacity * 3 / 2 + 1; + if (newCapacity < minCapacity){ + newCapacity = minCapacity; + } + myData = new char[newCapacity]; + System.arraycopy(oldData, 0, myData, 0, mySize); + } + } + + public void fill(int fromIndex, int toIndex, int value) { + assertShort(value); + if (toIndex > mySize) { + ensureCapacity(toIndex); + mySize = toIndex; + } + Arrays.fill(myData, fromIndex, toIndex, (char)value); + } + + public int size() { + return mySize; + } + + public boolean isEmpty() { + return mySize == 0; + } + + public boolean contains(int element) { + assertShort(element); + return indexOf(element) >= 0; + } + + public int indexOf(int element) { + assertShort(element); + return indexOf(element, 0, mySize); + } + + public int indexOf(int element, int startIndex, int endIndex) { + assertShort(element); + + if (startIndex < 0 || endIndex < startIndex || endIndex > mySize) { + throw new IndexOutOfBoundsException("startIndex: "+startIndex+"; endIndex: "+endIndex+"; mySize: "+mySize); + } + for(int i = startIndex; i < endIndex; i++){ + if (element == myData[i]) return i; + } + return -1; + } + + public int lastIndexOf(int element) { + assertShort(element); + for(int i = mySize - 1; i >= 0; i--){ + if (element == myData[i]) return i; + } + return -1; + } + + @Override + public Object clone() { + try{ + UnsignedShortArrayList v = (UnsignedShortArrayList)super.clone(); + v.myData = myData.clone(); + return v; + } + catch(CloneNotSupportedException e){ + // this shouldn't happen, since we are Cloneable + throw new InternalError(); + } + } + + @NotNull + public int[] toArray() { + return toArray(0,mySize); + } + + @NotNull + public int[] toArray(@NotNull int[] a) { + if (a.length < mySize){ + a = new int[mySize]; + } + for (int i = 0; i < mySize; i++) { + char c = myData[i]; + a[i] = c; + } + + return a; + } + + @NotNull + public int[] toArray(int startIndex, int length) { + int[] result = new int[length]; + for (int i = startIndex; i < length; i++) { + char c = myData[i]; + result[i-startIndex] = c; + } + return result; + } + + public int get(int index) { + checkRange(index); + return myData[index]; + } + + public int getQuick(int index) { + return myData[index]; + } + + public int set(int index, int element) { + checkRange(index); + + int oldValue = myData[index]; + setQuick(index, element); + return oldValue; + } + public void setQuick(int index, int element) { + assertShort(element); + + myData[index] = (char)element; + } + + private static void assertShort(int element) { + assert element >= 0 && element < 1<<16 : element; + } + + public void add(int element) { + ensureCapacity(mySize + 1); + setQuick(mySize++, element); + } + + public void add(int index, int element) { + if (index > mySize || index < 0){ + throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + mySize); + } + + ensureCapacity(mySize + 1); + System.arraycopy(myData, index, myData, index + 1, mySize - index); + setQuick(index, element); + mySize++; + } + + public int remove(int index) { + checkRange(index); + + int oldValue = myData[index]; + + int numMoved = mySize - index - 1; + if (numMoved > 0){ + System.arraycopy(myData, index + 1, myData, index,numMoved); + } + mySize--; + + return oldValue; + } + + public void clear() { + mySize = 0; + } + + public void removeRange(int fromIndex, int toIndex) { + int numMoved = mySize - toIndex; + System.arraycopy(myData, toIndex, myData, fromIndex, numMoved); + mySize -= toIndex - fromIndex; + } + + public void copyRange(int fromIndex, int length, int toIndex) { + if (length < 0 || fromIndex < 0 || fromIndex + length > mySize || toIndex < 0 || toIndex + length > mySize) { + throw new IndexOutOfBoundsException("fromIndex: "+fromIndex+"; length: "+length+"; toIndex: "+toIndex+"; mySize: "+mySize); + } + System.arraycopy(myData, fromIndex, myData, toIndex, length); + } + + private void checkRange(int index) { + if (index >= mySize || index < 0){ + //noinspection HardCodedStringLiteral + throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + mySize); + } + } + + @Override + public String toString() { + return Arrays.toString(toArray()); + } +}