/* * 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. */ using System; using System.Collections.Generic; namespace Lucene.Net.Util { /// Estimates the size of a given Object using a given MemoryModel for primitive /// size information. /// /// Resource Usage: /// /// Internally uses a Map to temporally hold a reference to every /// object seen. /// /// If checkIntered, all Strings checked will be interned, but those /// that were not already interned will be released for GC when the /// estimate is complete. /// public sealed class RamUsageEstimator { private MemoryModel memoryModel; private IDictionary seen; private int refSize; private int arraySize; private int classSize; private bool checkInterned; /// Constructs this object with an AverageGuessMemoryModel and /// checkInterned = true. /// public RamUsageEstimator():this(new AverageGuessMemoryModel()) { } /// check if Strings are interned and don't add to size /// if they are. Defaults to true but if you know the objects you are checking /// won't likely contain many interned Strings, it will be faster to turn off /// intern checking. /// public RamUsageEstimator(bool checkInterned):this(new AverageGuessMemoryModel(), checkInterned) { } /// MemoryModel to use for primitive object sizes. /// public RamUsageEstimator(MemoryModel memoryModel):this(memoryModel, true) { } /// MemoryModel to use for primitive object sizes. /// /// check if Strings are interned and don't add to size /// if they are. Defaults to true but if you know the objects you are checking /// won't likely contain many interned Strings, it will be faster to turn off /// intern checking. /// public RamUsageEstimator(MemoryModel memoryModel, bool checkInterned) { this.memoryModel = memoryModel; this.checkInterned = checkInterned; // Use Map rather than Set so that we can use an IdentityHashMap - not // seeing an IdentityHashSet seen = new IdentityDictionary(64); this.refSize = memoryModel.ReferenceSize; this.arraySize = memoryModel.ArraySize; this.classSize = memoryModel.ClassSize; } public long EstimateRamUsage(System.Object obj) { long size = Size(obj); seen.Clear(); return size; } private long Size(System.Object obj) { if (obj == null) { return 0; } // interned not part of this object if (checkInterned && obj is System.String && obj == (System.Object) String.Intern(((System.String) obj))) { // interned string will be eligible // for GC on // estimateRamUsage(Object) return return 0; } // skip if we have seen before if (seen.ContainsKey(obj)) { return 0; } // add to seen seen[obj] = null; System.Type clazz = obj.GetType(); if (clazz.IsArray) { return SizeOfArray(obj); } long size = 0; // walk type hierarchy while (clazz != null) { System.Reflection.FieldInfo[] fields = clazz.GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.DeclaredOnly | System.Reflection.BindingFlags.Static); for (int i = 0; i < fields.Length; i++) { if (fields[i].IsStatic) { continue; } if (fields[i].FieldType.IsPrimitive) { size += memoryModel.GetPrimitiveSize(fields[i].FieldType); } else { size += refSize; fields[i].GetType(); try { System.Object value_Renamed = fields[i].GetValue(obj); if (value_Renamed != null) { size += Size(value_Renamed); } } catch (System.UnauthorizedAccessException) { // ignore for now? } } } clazz = clazz.BaseType; } size += classSize; return size; } private long SizeOfArray(System.Object obj) { int len = ((System.Array) obj).Length; if (len == 0) { return 0; } long size = arraySize; System.Type arrayElementClazz = obj.GetType().GetElementType(); if (arrayElementClazz.IsPrimitive) { size += len * memoryModel.GetPrimitiveSize(arrayElementClazz); } else { for (int i = 0; i < len; i++) { size += refSize + Size(((System.Array) obj).GetValue(i)); } } return size; } private const long ONE_KB = 1024; private static readonly long ONE_MB = ONE_KB * ONE_KB; private static readonly long ONE_GB = ONE_KB * ONE_MB; /// Return good default units based on byte size. public static System.String HumanReadableUnits(long bytes, System.IFormatProvider df) { System.String newSizeAndUnits; if (bytes / ONE_GB > 0) { newSizeAndUnits = System.Convert.ToString(((float) bytes / ONE_GB), df) + " GB"; } else if (bytes / ONE_MB > 0) { newSizeAndUnits = System.Convert.ToString((float) bytes / ONE_MB, df) + " MB"; } else if (bytes / ONE_KB > 0) { newSizeAndUnits = System.Convert.ToString((float) bytes / ONE_KB, df) + " KB"; } else { newSizeAndUnits = System.Convert.ToString(bytes) + " bytes"; } return newSizeAndUnits; } } }