blob: 2dd943e4ad699bc200d547c7199832a912b9f0dd [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.netbeans.modules.turbo;
import java.io.*;
import java.util.Date;
import java.util.Set;
import java.util.HashSet;
import java.util.Iterator;
/**
* Statistics support for {@link Turbo}.
*
* <p>Results analysis allows to determine if cache
* have any effect.
*
* @author Petr Kuzel
*/
class Statistics {
private static final Statistics NOP_INSTANCE = new NOP();
private static final int REMOVED_BATCH_INTERVAL = 1000 *10; // 10s
private long requests = 0;
private long memoryHits = 0;
private long diskHits = 0;
// background loading
private long threads = 0;
private long duplicates = 0;
private long maxQueueSize = 0;
// Memory.liveentitiesMap utilization
private long maxMemoryEntries = 0;
private long gcCounter = 0;
private long newCounter = 0;
private int removedInvocaionCounter = 0;
private long removedInvocationTime = System.currentTimeMillis() - REMOVED_BATCH_INTERVAL;
/** Holds keys string reprentation to avoid memory leaks. */
private Set recentKeys;
// cache instance identification fields
private static volatile int idPool = 1;
private final int id;
private final Exception origin;
private PrintWriter out;
/** Creates new statistics instance according to
* <tt>netbeans.experimental.vcsTurboStatistics</tt> system
* property:
* <ul>
* <li><tt>none</tt> (default) no-op implementaion
* <li><tt>mini</tt> logs fast events
* <li><tt>performance</tt> logs also heavy events slowing down Turbo and increasing it's memory requirements.
* </ul>
*/
public static Statistics createInstance() {
if ("none".equalsIgnoreCase(System.getProperty("netbeans.experimental.vcsTurboStatistics", "none"))) { // NOI18N
return NOP_INSTANCE;
} else {
return new Statistics();
}
}
private Statistics() {
origin = new RuntimeException();
id = idPool++;
}
/**
* Checks if additional logging required for detailed performance evaluation is required.
*/
private static boolean logPerformance() {
return System.getProperty("netbeans.experimental.vcsTurboStatistics", "mini").equalsIgnoreCase("performance"); // NOI18N
}
/** Key created adding permision to store it in memory layer. */
public void keyAdded(Object key) {
if (key != null) println("EK+ " + key); // NOi18N
newCounter++;
long allocated = newCounter - gcCounter;
if (allocated > maxMemoryEntries) {
maxMemoryEntries = allocated;
}
if (recentKeys != null) {
assert recentKeys.add(key.toString()) : "Key added for the second time: " + key;
}
}
/** Key was removed from memory. */
public void keyRemoved(Object key) {
if (key != null) println("EK- " + key); // NOi18N
gcCounter++;
if (recentKeys != null) {
recentKeys.remove(key.toString());
}
}
/**
* Detect reclaimed keys. It lods first results on the second call.
*
* <p>It's heavy event.
*/
public void computeRemoved(Set keys) {
if (logPerformance() == false) return;
if (System.currentTimeMillis() - removedInvocationTime > REMOVED_BATCH_INTERVAL) return;
removedInvocaionCounter++;
Iterator it = keys.iterator();
Set currentKeys = new HashSet(keys.size());
while (it.hasNext()) {
String stringKey = it.next().toString();
currentKeys.add(stringKey);
}
if (recentKeys != null) {
recentKeys.removeAll(currentKeys);
int reclaimed = recentKeys.size();
gcCounter += reclaimed;
if (reclaimed > 0) {
println("MSG [" + new Date().toString() + "] reclaimed keys:" ); // NOI18N
Iterator itr = recentKeys.iterator();
while (itr.hasNext()) {
String next = itr.next().toString();
println("EK- " + next); // NOI18N
}
println("MSG EOL reclaimed keys" ); // NOI18N
}
}
removedInvocationTime = System.currentTimeMillis();
recentKeys = currentKeys;
}
/** Turbo request arrived */
public void attributeRequest() {
requests++;
if (requests % 1000 == 0) {
printCacheStatistics();
}
}
/** The client request was resolved by memory layer */
public void memoryHit() {
memoryHits++;
}
/** new background thread spawned */
public void backgroundThread() {
threads++;
}
/** Duplicate request eliminated from queue. */
public void duplicate() {
duplicates++;
}
public void queueSize(int size) {
if (size > maxQueueSize) {
maxQueueSize = size;
}
}
/** The client request was resolved at providers layer */
public void providerHit() {
diskHits++;
}
public void shutdown() {
printCacheStatistics();
out.flush();
out.close();
// System.out.println(" Statistics goes to " + Statistics.logPath()); // NOI18N
}
private String logPath() {
return System.getProperty("java.io.tmpdir") + File.separator + "netbeans-versioning-turbo-" + id + ".log"; // NOI18N
}
private void printCacheStatistics() {
println("CS turbo.requests=" + requests); // NOI18N
println("CS memory.hits=" + memoryHits + " " + (((float)memoryHits/(float)requests) * 100) + "%"); // NOI18N
println("CS provider.hits=" + diskHits + " " + (((float)diskHits/(float)requests) * 100) + "%"); // NOI18N
if (removedInvocaionCounter >= 2) {
println("CS memory.max=" + maxMemoryEntries); // NOI18N
println("CS memory.entries=" + (newCounter - gcCounter)); // NOI18N
println("CS memory.entiresReclaimingRatio=" + (((float)gcCounter/(float)newCounter) * 100) + "%"); // NOI18N
} else {
println("MSG No memory utilization data known, use -J-Dnetbeans.experimental.vcsTurboStatistics=performance.");
}
println("CS queue.threads=" + threads + " queue.duplicates=" + duplicates + " queue.maxSize=" + maxQueueSize); // NOI18N
println("MSG --"); // NOI18N
println("MSG turbo.log.Statistics on " + new Date().toString()); // NOI18N
}
private synchronized void println(String s) {
if (out == null) {
String filePath = logPath();
try {
out = new PrintWriter(new BufferedWriter(new FileWriter(filePath), 512));
} catch (IOException e) {
out = new PrintWriter(new OutputStreamWriter(System.out), true);
}
out.println("MSG EK followed by +/- denotes new memory cache entry/releasing it"); // NOI18N
out.println("MSG CS describes summary statistics of memory and disk caches"); // NOI18N
out.println("MSG the MSG prefix denotes free form messages"); // NOI18N
out.println("MSG turbo.Statistics instance serving:\n"); // NOI18N
StackTraceElement elements[] = origin.getStackTrace();
for (int i = 0; i < elements.length; i++) {
StackTraceElement element = elements[i];
out.println("MSG " + element.toString() ); // NOI18N
}
out.println();
}
out.println(s);
}
/** Logs nothing. */
private static final class NOP extends Statistics {
public void keyAdded(Object key) {
}
public void computeRemoved(Set keys) {
}
public void attributeRequest() {
}
public void memoryHit() {
}
public void backgroundThread() {
}
public void duplicate() {
}
public void queueSize(int size) {
}
public void providerHit() {
}
public void shutdown() {
}
}
}