Commit 9c2a7955 authored by guus's avatar guus

OF-497: Properly determine the size of Collections to be cached. Patch by Marcin Cieślak.

git-svn-id: http://svn.igniterealtime.org/svn/repos/openfire/trunk@12942 b35dd754-fafc-0310-a699-88a17e54d16e
parent 03192ac7
......@@ -22,6 +22,7 @@ package org.jivesoftware.openfire.entitycaps;
import org.jivesoftware.util.cache.CacheSizes;
import org.jivesoftware.util.cache.Cacheable;
import org.jivesoftware.util.cache.CannotCalculateSizeException;
import org.jivesoftware.util.cache.ExternalizableUtil;
import java.io.Externalizable;
......@@ -136,7 +137,7 @@ public class EntityCapabilities implements Cacheable, Externalizable {
ExternalizableUtil.getInstance().writeSafeUTF(out, verAttribute);
}
public int getCachedSize() {
public int getCachedSize() throws CannotCalculateSizeException {
int size = CacheSizes.sizeOfCollection(identities);
size += CacheSizes.sizeOfCollection(features);
size += CacheSizes.sizeOfString(verAttribute);
......
......@@ -44,6 +44,7 @@ import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.event.GroupEventDispatcher;
import org.jivesoftware.util.cache.CacheSizes;
import org.jivesoftware.util.cache.Cacheable;
import org.jivesoftware.util.cache.CannotCalculateSizeException;
import org.jivesoftware.util.cache.ExternalizableUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -347,7 +348,8 @@ public class Group implements Cacheable, Externalizable {
}
}
public int getCachedSize() {
public int getCachedSize()
throws CannotCalculateSizeException {
// Approximate the size of the object in bytes by calculating the size
// of each field.
int size = 0;
......@@ -717,4 +719,4 @@ public class Group implements Cacheable, Externalizable {
ExternalizableUtil.getInstance().readSerializableCollection(in, members, getClass().getClassLoader());
ExternalizableUtil.getInstance().readSerializableCollection(in, administrators, getClass().getClassLoader());
}
}
\ No newline at end of file
}
......@@ -38,6 +38,7 @@ import org.jivesoftware.openfire.roster.Roster;
import org.jivesoftware.openfire.user.UserNotFoundException;
import org.jivesoftware.util.cache.CacheSizes;
import org.jivesoftware.util.cache.Cacheable;
import org.jivesoftware.util.cache.CannotCalculateSizeException;
import org.jivesoftware.util.cache.ExternalizableUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -247,7 +248,7 @@ public class PrivacyList implements Cacheable, Externalizable {
return null;
}
public int getCachedSize() {
public int getCachedSize() throws CannotCalculateSizeException {
// Approximate the size of the object in bytes by calculating the size
// of each field.
int size = 0;
......
......@@ -51,6 +51,7 @@ import org.jivesoftware.openfire.user.UserNotFoundException;
import org.jivesoftware.util.JiveConstants;
import org.jivesoftware.util.cache.CacheSizes;
import org.jivesoftware.util.cache.Cacheable;
import org.jivesoftware.util.cache.CannotCalculateSizeException;
import org.jivesoftware.util.cache.ExternalizableUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -732,7 +733,7 @@ public class Roster implements Cacheable, Externalizable {
}
}
public int getCachedSize() {
public int getCachedSize() throws CannotCalculateSizeException {
// Approximate the size of the object in bytes by calculating the size
// of the content of each field, if that content is likely to be eligable for
// garbage collection if the Roster instance is dereferenced.
......@@ -743,8 +744,8 @@ public class Roster implements Cacheable, Externalizable {
// implicitFrom
for(Map.Entry<String, Set<String>> entry : implicitFrom.entrySet()) {
size += CacheSizes.sizeOfString(entry.getKey());
size += CacheSizes.sizeOfCollection(entry.getValue());
size += CacheSizes.sizeOfString(entry.getKey());
size += CacheSizes.sizeOfCollection(entry.getValue());
}
return size;
......
......@@ -29,6 +29,7 @@ import org.jivesoftware.openfire.user.UserNotFoundException;
import org.jivesoftware.util.IntEnum;
import org.jivesoftware.util.cache.CacheSizes;
import org.jivesoftware.util.cache.Cacheable;
import org.jivesoftware.util.cache.CannotCalculateSizeException;
import org.jivesoftware.util.cache.ExternalizableUtil;
import org.xmpp.packet.JID;
......@@ -540,7 +541,7 @@ public class RosterItem implements Cacheable, Externalizable {
*
* @see org.jivesoftware.util.cache.Cacheable#getCachedSize()
*/
public int getCachedSize() {
public int getCachedSize() throws CannotCalculateSizeException {
int size = jid.toBareJID().length();
size += CacheSizes.sizeOfString(nickname);
size += CacheSizes.sizeOfCollection(groups);
......@@ -582,4 +583,4 @@ public class RosterItem implements Cacheable, Externalizable {
askStatus = AskType.getTypeFromInt(ExternalizableUtil.getInstance().readInt(in));
rosterID = ExternalizableUtil.getInstance().readLong(in);
}
}
\ No newline at end of file
}
......@@ -45,6 +45,7 @@ import org.jivesoftware.openfire.roster.Roster;
import org.jivesoftware.util.StringUtils;
import org.jivesoftware.util.cache.CacheSizes;
import org.jivesoftware.util.cache.Cacheable;
import org.jivesoftware.util.cache.CannotCalculateSizeException;
import org.jivesoftware.util.cache.ExternalizableUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -387,7 +388,8 @@ public class User implements Cacheable, Externalizable, Result {
}
}
public int getCachedSize() {
public int getCachedSize()
throws CannotCalculateSizeException {
// Approximate the size of the object in bytes by calculating the size
// of each field.
int size = 0;
......@@ -614,4 +616,4 @@ public class User implements Cacheable, Externalizable, Result {
{
return username;
}
}
\ No newline at end of file
}
......@@ -22,8 +22,12 @@ package org.jivesoftware.util.cache;
import org.jivesoftware.util.cache.Cacheable;
import java.util.Map;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
/**
* Utility class for determining the sizes in bytes of commonly used objects.
......@@ -112,23 +116,24 @@ public class CacheSizes {
}
/**
* Returns the size in bytes of a Map object. All keys and
* values <b>must be Strings</b>.
* Returns the size in bytes of a Map object.
*
* @param map the Map object to determine the size of.
* @return the size of the Map object.
*/
public static int sizeOfMap(Map<String, String> map) {
public static int sizeOfMap(Map map)
throws CannotCalculateSizeException {
if (map == null) {
return 0;
}
// Base map object -- should be something around this size.
int size = 36;
Set<? extends Map.Entry> set = map.entrySet();
// Add in size of each value
for (Map.Entry<String, String> entry : map.entrySet()) {
size += sizeOfString(entry.getKey());
size += sizeOfString(entry.getValue());
for (Map.Entry<Object, Object> entry : set) {
size += sizeOfAnything(entry.getKey());
size += sizeOfAnything(entry.getValue());
}
return size;
}
......@@ -140,7 +145,8 @@ public class CacheSizes {
* @param list the Collection object to determine the size of.
* @return the size of the Collection object.
*/
public static int sizeOfCollection(Collection list) {
public static int sizeOfCollection(Collection list)
throws CannotCalculateSizeException {
if (list == null) {
return 0;
}
......@@ -149,17 +155,99 @@ public class CacheSizes {
// Add in size of each value
Object[] values = list.toArray();
for (int i = 0; i < values.length; i++) {
Object obj = values[i];
if (obj instanceof String) {
size += sizeOfString((String)obj);
}
else if (obj instanceof Long) {
size += sizeOfLong() + sizeOfObject();
size += sizeOfAnything(values[i]);
}
return size;
}
/**
* Returns the size of an object in bytes. Determining size by serialization
* is only used as a last resort.
*
* @return the size of an object in bytes.
*/
public static int sizeOfAnything(Object object)
throws CannotCalculateSizeException {
// If the object is Cacheable, ask it its size.
if (object == null) {
return 0;
}
if (object instanceof Cacheable) {
return ((Cacheable)object).getCachedSize();
}
// Check for other common types of objects put into cache.
else if (object instanceof String) {
return sizeOfString((String)object);
}
else if (object instanceof Long) {
return sizeOfLong();
}
else if (object instanceof Integer) {
return sizeOfObject() + sizeOfInt();
}
else if (object instanceof Double) {
return sizeOfObject() + sizeOfDouble();
}
else if (object instanceof Boolean) {
return sizeOfObject() + sizeOfBoolean();
}
else if (object instanceof Map) {
return sizeOfMap((Map)object);
}
else if (object instanceof long[]) {
long[] array = (long[])object;
return sizeOfObject() + array.length * sizeOfLong();
}
else if (object instanceof Collection) {
return sizeOfCollection((Collection)object);
}
else if (object instanceof byte[]) {
byte [] array = (byte[])object;
return sizeOfObject() + array.length;
}
// Default behavior -- serialize the object to determine its size.
else {
int size = 1;
try {
// Default to serializing the object out to determine size.
CacheSizes.NullOutputStream out = new NullOutputStream();
ObjectOutputStream outObj = new ObjectOutputStream(out);
outObj.writeObject(object);
size = out.size();
}
else {
size += ((Cacheable)obj).getCachedSize();
catch (IOException ioe) {
throw new CannotCalculateSizeException(object);
}
return size;
}
}
private static class NullOutputStream extends OutputStream {
int size = 0;
@Override
public void write(int b) throws IOException {
size++;
}
@Override
public void write(byte[] b) throws IOException {
size += b.length;
}
@Override
public void write(byte[] b, int off, int len) {
size += len;
}
/**
* Returns the number of bytes written out through the stream.
*
* @return the number of bytes written to the stream.
*/
public int size() {
return size;
}
return size;
}
}
\ No newline at end of file
}
......@@ -42,5 +42,5 @@ public interface Cacheable extends java.io.Serializable {
*
* @return the size of the Object in bytes.
*/
public int getCachedSize();
public int getCachedSize() throws CannotCalculateSizeException;
}
/**
* $RCSfile$
* $Revision: 11291 $
* $Date: 2009-09-30 10:17:14 +0000 (śro) $
*
* Copyright (C) 2004-2008 Jive Software. All rights reserved.
*
* 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 org.jivesoftware.util.cache;
/**
* <p>Flags an exception when we cannot determine size of the object to be cached.</p>
*
* @author Marcin Cieslak
*/
public class CannotCalculateSizeException extends Exception {
public static final long serialVersionUID = 1754096832201752388L;
public CannotCalculateSizeException() {
}
public CannotCalculateSizeException(Object obj) {
super("Unable to determine size of " + obj.getClass() + " instance");
}
}
......@@ -19,9 +19,6 @@
*/
package org.jivesoftware.util.cache;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
......@@ -138,7 +135,13 @@ public class DefaultCache<K, V> implements Cache<K, V> {
// Delete an old entry if it exists.
V answer = remove(key);
int objectSize = calculateSize(value);
int objectSize = 1;
try {
objectSize = CacheSizes.sizeOfAnything(value);
}
catch (CannotCalculateSizeException e) {
Log.warn(e.getMessage(), e);
}
// If the object is bigger than the entire cache, simply don't add it.
if (maxCacheSize > 0 && objectSize > maxCacheSize * .90) {
......@@ -541,55 +544,6 @@ public class DefaultCache<K, V> implements Cache<K, V> {
CacheFactory.setMaxLifetimeProperty(name, maxLifetime);
}
/**
* Returns the size of an object in bytes. Determining size by serialization
* is only used as a last resort.
*
* @return the size of an object in bytes.
*/
private int calculateSize(Object object) {
// If the object is Cacheable, ask it its size.
if (object instanceof Cacheable) {
return ((Cacheable)object).getCachedSize();
}
// Check for other common types of objects put into cache.
else if (object instanceof String) {
return CacheSizes.sizeOfString((String)object);
}
else if (object instanceof Long) {
return CacheSizes.sizeOfLong();
}
else if (object instanceof Integer) {
return CacheSizes.sizeOfObject() + CacheSizes.sizeOfInt();
}
else if (object instanceof Boolean) {
return CacheSizes.sizeOfObject() + CacheSizes.sizeOfBoolean();
}
else if (object instanceof long[]) {
long[] array = (long[])object;
return CacheSizes.sizeOfObject() + array.length * CacheSizes.sizeOfLong();
}
else if (object instanceof byte[]) {
byte [] array = (byte[])object;
return CacheSizes.sizeOfObject() + array.length;
}
// Default behavior -- serialize the object to determine its size.
else {
int size = 1;
try {
// Default to serializing the object out to determine size.
DefaultCache.NullOutputStream out = new DefaultCache.NullOutputStream();
ObjectOutputStream outObj = new ObjectOutputStream(out);
outObj.writeObject(object);
size = out.size();
}
catch (IOException ioe) {
Log.error(ioe.getMessage(), ioe);
}
return size;
}
}
/**
* Clears all entries out of cache where the entries are older than the
* maximum defined age.
......@@ -712,37 +666,4 @@ public class DefaultCache<K, V> implements Cache<K, V> {
this.size = size;
}
}
/**
* An extension of OutputStream that does nothing but calculate the number
* of bytes written through it.
*/
private static class NullOutputStream extends OutputStream {
int size = 0;
@Override
public void write(int b) throws IOException {
size++;
}
@Override
public void write(byte[] b) throws IOException {
size += b.length;
}
@Override
public void write(byte[] b, int off, int len) {
size += len;
}
/**
* Returns the number of bytes written out through the stream.
*
* @return the number of bytes written to the stream.
*/
public int size() {
return size;
}
}
}
......@@ -44,11 +44,16 @@
Clustering Plugin Changelog
</h1>
<p><b>1.2.1</b> -- Dec 16, 2011</p>
<ul>
<li>CoherenceCache is now compatible with a changed API of Openfire caches.</li>
</ul>
<p><b>1.2.0</b> -- Nov 10, 2009</p>
<ul>
<li>Changed license to Apache 2.0. Oracle Coherence should now be obtained separately.</li>
<li>ClustserListener is now compatible with distributed caches.</li>
<li>ClusterListener is now compatible with distributed caches.</li>
</ul>
<p><b>1.1.1</b> -- June 4, 2008</p>
......
......@@ -5,8 +5,8 @@
<name>${plugin.name}</name>
<description>${plugin.description}</description>
<author>Jive Software</author>
<version>1.2.0</version>
<date>11/10/2009</date>
<minServerVersion>3.5.2</minServerVersion>
<version>1.2.1</version>
<date>12/17/2011</date>
<minServerVersion>3.7.2</minServerVersion>
</plugin>
......@@ -27,6 +27,7 @@ import org.jivesoftware.util.Log;
import org.jivesoftware.util.cache.Cache;
import org.jivesoftware.util.cache.CacheSizes;
import org.jivesoftware.util.cache.Cacheable;
import org.jivesoftware.util.cache.CannotCalculateSizeException;
/**
* Implementation of the Cache interface that uses the Coherence LocalCache class.
......@@ -153,34 +154,19 @@ public class CoherenceCache extends LocalCache implements Cache {
public class Entry extends OldCache.Entry {
public int calculateUnits(Object object) {
// If the object is Cacheable, ask for its size.
if (object instanceof Cacheable) {
return ((Cacheable) object).getCachedSize();
}
// Coherence puts com.tangosol.util.Binary objects in cache.
else if (object instanceof com.tangosol.util.Binary) {
return ((com.tangosol.util.Binary) object).length();
}
// Check for other common types of objects put into cache.
else if (object instanceof Long) {
return CacheSizes.sizeOfLong();
}
else if (object instanceof Integer) {
return CacheSizes.sizeOfObject() + CacheSizes.sizeOfInt();
}
else if (object instanceof Boolean) {
return CacheSizes.sizeOfObject() + CacheSizes.sizeOfBoolean();
}
else if (object instanceof long[]) {
long[] array = (long[]) object;
return CacheSizes.sizeOfObject() + array.length * CacheSizes.sizeOfLong();
}
else if (object instanceof String) {
return CacheSizes.sizeOfString((String)object);
}
else {
try {
// Coherence puts com.tangosol.util.Binary objects in cache.
if (object instanceof com.tangosol.util.Binary) {
return ((com.tangosol.util.Binary) object).length();
}
// Check for other common types of objects put into cache.
else {
return CacheSizes.sizeOfAnything(object);
}
} catch (CannotCalculateSizeException e) {
Log.warn(e.getMessage(), e);
return 1;
}
}
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment