/*
* Copyright (c) 2005-2016 Substance Kirill Grouchnikov. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* o Neither the name of Substance Kirill Grouchnikov nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.pushingpixels.substance.internal.utils;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Insets;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import javax.swing.SwingUtilities;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import org.pushingpixels.lafwidget.utils.TrackableThread;
/**
* Tracer for memory usage patterns of <b>Substance</b> look-and-feel. The
* tracer is started when VM has <code>-Dsubstancelaf.traceFile</code> flag. The
* value of this flag specifies the location of trace log file. When activated,
* the tracer runs a thread that collects information on memory usage and
* appends it to the trace log file every <code>X</code> seconds. The
* <code>X</code> (delay) is specified in the constructor. This class is <b>for
* internal use only</b>.
*
* @author Kirill Grouchnikov
*/
public class MemoryAnalyzer extends TrackableThread {
/**
* Sleep delay between trace log iterations.
*/
private long delay;
/**
* Trace logfile name.
*/
private String filename;
/**
* Singleton instance.
*/
private static MemoryAnalyzer instance;
/**
* If <code>true</code>, <code>this</code> tracer has received a request to
* stop.
*/
private static boolean isStopRequest = false;
/**
* Usage strings collected during the sleep time.
*/
private static ArrayList<String> usages;
/**
* Formatting object.
*/
private static SimpleDateFormat sdf;
/**
* Simple constructor.
*
* @param delay
* Sleep delay between trace log iterations.
* @param filename
* Trace logfile name.
*/
private MemoryAnalyzer(long delay, String filename) {
super();
this.delay = delay;
this.filename = filename;
this.setName("Substance memory analyzer");
}
/**
* Starts the memory tracing.
*
* @param delay
* Sleep delay between trace log iterations.
* @param filename
* Trace logfile name.
*/
public static synchronized void commence(long delay, String filename) {
if (instance == null) {
instance = new MemoryAnalyzer(delay, filename);
usages = new ArrayList<String>();
// yeah, yeah, it's not multi-thread safe.
sdf = new SimpleDateFormat("HH:mm:ss.SSS");
instance.start();
}
}
/**
* Issues request to stop tracing.
*/
@Override
public synchronized void requestStop() {
isStopRequest = true;
}
/**
* Checks whether a request to stop tracing has been issued.
*
* @return <code>true</code> if a request to stop tracing has been issued,
* <code>false</code> otherwise.
*/
private static synchronized boolean hasStopRequest() {
return isStopRequest;
}
/**
* Checks whether tracer is running.
*
* @return <code>true</code> if tracer is running, <code>false</code>
* otherwise.
*/
public static boolean isRunning() {
return (instance != null);
}
/**
* Adds usage string.
*
* @param usage
* Usage string. Will be output to the trace file at next
* iteration of the tracer.
*/
public static synchronized void enqueueUsage(String usage) {
if (instance != null) {
usages.add(sdf.format(new Date()) + ": " + usage);
}
}
/**
* Returns all queued usages.
*
* @return All queued usages.
*/
public static synchronized ArrayList<String> getUsages() {
ArrayList<String> copy = new ArrayList<String>();
for (String usage : usages)
copy.add(usage);
usages.clear();
return copy;
}
/*
* (non-Javadoc)
*
* @see java.lang.Runnable#run()
*/
@Override
public void run() {
// output all settings from UIManager
// Need to run on EDT - issue 392
try {
SwingUtilities.invokeAndWait(() -> {
try (BufferedWriter bw = new BufferedWriter(new FileWriter(new File(filename), true))) {
bw.write(sdf.format(new Date()) + "\n");
UIDefaults uidefs = UIManager.getLookAndFeel().getDefaults();
// Retrieve the keys. Can't use an iterator since the
// map may be modified during the iteration. So retrieve
// all at once.
Set<Object> keySet = uidefs.keySet();
List<String> keyList = new LinkedList<String>();
for (Object key : keySet) {
keyList.add((String) key);
}
Collections.sort(keyList);
for (String key : keyList) {
Object v = uidefs.get(key);
if (v instanceof Integer) {
int intVal = uidefs.getInt(key);
bw.write(key + " (int) : " + intVal);
} else if (v instanceof Boolean) {
boolean boolVal = uidefs.getBoolean(key);
bw.write(key + " (bool) : " + boolVal);
} else if (v instanceof String) {
String strVal = uidefs.getString(key);
bw.write(key + " (string) : " + strVal);
} else if (v instanceof Dimension) {
Dimension dimVal = uidefs.getDimension(key);
bw.write(key + " (Dimension) : " + dimVal.width + "*" + dimVal.height);
} else if (v instanceof Insets) {
Insets insetsVal = uidefs.getInsets(key);
bw.write(key + " (Insets) : " + insetsVal.top + "*" + insetsVal.left + "*"
+ insetsVal.bottom + "*" + insetsVal.right);
} else if (v instanceof Color) {
Color colorVal = uidefs.getColor(key);
bw.write(key + " (int) : " + colorVal.getRed() + "," + colorVal.getGreen() + ","
+ colorVal.getBlue());
} else if (v instanceof Font) {
Font fontVal = uidefs.getFont(key);
bw.write(key + " (Font) : " + fontVal.getFontName() + "*" + fontVal.getSize());
} else {
bw.write(key + " (Object) : " + uidefs.get(key));
}
bw.write("\n");
}
} catch (IOException ioe) {
requestStop();
}
});
} catch (Exception exc) {
requestStop();
}
while (!hasStopRequest()) {
// gather statistics and print them to file
try (BufferedWriter bw = new BufferedWriter(new FileWriter(new File(this.filename), true))) {
bw.write(sdf.format(new Date()) + "\n");
java.util.List<String> stats = LazyResettableHashMap.getStats();
if (stats != null) {
for (String stat : stats) {
bw.write(stat + "\n");
}
}
ArrayList<String> usages = getUsages();
for (String usage : usages) {
bw.write(usage + "\n");
}
bw.write("UIManager has " + UIManager.getDefaults().size() + " entries\n");
long heapSize = Runtime.getRuntime().totalMemory();
long heapFreeSize = Runtime.getRuntime().freeMemory();
int heapSizeKB = (int) (heapSize / 1024);
int takenHeapSizeKB = (int) ((heapSize - heapFreeSize) / 1024);
bw.write("Heap : " + takenHeapSizeKB + " / " + heapSizeKB);
bw.write("\n");
} catch (IOException ioe) {
this.requestStop();
}
// sleep
try {
sleep(this.delay);
} catch (InterruptedException ie) {
}
}
}
}