001package org.cache2k.impl;
002
003/*
004 * #%L
005 * cache2k core package
006 * %%
007 * Copyright (C) 2000 - 2015 headissue GmbH, Munich
008 * %%
009 * This program is free software: you can redistribute it and/or modify
010 * it under the terms of the GNU General Public License as
011 * published by the Free Software Foundation, either version 3 of the 
012 * License, or (at your option) any later version.
013 * 
014 * This program is distributed in the hope that it will be useful,
015 * but WITHOUT ANY WARRANTY; without even the implied warranty of
016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
017 * GNU General Public License for more details.
018 * 
019 * You should have received a copy of the GNU General Public 
020 * License along with this program.  If not, see
021 * <http://www.gnu.org/licenses/gpl-3.0.html>.
022 * #L%
023 */
024
025import java.lang.reflect.Array;
026import java.lang.reflect.Field;
027import java.lang.reflect.Modifier;
028import java.util.ArrayList;
029import java.util.HashMap;
030import java.util.HashSet;
031import java.util.Iterator;
032import java.util.List;
033import java.util.Set;
034
035/**
036 * Helper to estimate the size of object within a cache. Usage: Insert all objects to
037 * analyze with insert(), than call descend() to descend one level of the object graph.
038 * The end is reached when hasNext() yields false. The The result can be obtained via
039 * getBytes().
040 *
041 * <p>The basic problem when we want to estimate the size is that it is never known
042 * whether an object is exclusive for our cache structure, or it is also referenced by
043 * some other classes. To do a "perfect" size measurement the whole java heap must be
044 * analyzed and it must be decided which object should be counted where. So, anyway,
045 * let's try a good guess, and see whether it is of some use.
046 * </p>
047 *
048 * <p>Heuristics: Top-Level objects are always counted at least once by instance. An identical
049 * object that is found via different ways when descending the graph is not counted at all.
050 * Object reached via a non-counted object are not counted.</p>
051 *
052 * <p>Caveats: If only one object contains a reference to a bigger static data structure
053 * that belongs to the system, the size could be totally wrong. Countermeasures:
054 * we do measure the size only if more then x objects are in the cache and we limit the
055 * depth. If objects have a reference e.g. to some system objects, or maybe the cache, these
056 * objects will not be counted, if the reference is appearing more than once. So an
057 * problem may still arise if there is one object within the cache that holds such
058 * a reference and the others not. Current countermeasures for this: Estimate size only
059 * when sufficient objects are in the cache; limit the descending depth.</p>
060 *
061 * @author Jens Wilke; created: 2013-06-25
062 */
063@SuppressWarnings("unused")
064public class DepthSearchAndSizeCounter {
065
066  int bytes;
067  int objectCount;
068  int counter;
069
070  HashMap<Integer, SeenEntry> seen = new HashMap<Integer, SeenEntry>();
071  Set<SeenEntry> next = new HashSet<SeenEntry>();
072
073  /**
074   * Array of hash entries seen twice or more times. Top level objects will always be
075   * counted once. Secondary entries, will never be counted, since we cannot know
076   * if they are exclusive to the hash or not.
077   * */
078  Set<SeenEntry> eleminate = new HashSet<SeenEntry>();
079
080  boolean commonObjects = false;
081  boolean circles = false;
082
083  public void insert(Object o) {
084    counter++;
085    nextLevel(null, o);
086  }
087
088  public int getNextCount() {
089    return next.size();
090  }
091
092  public boolean hasNext() {
093    return !next.isEmpty();
094  }
095
096  public void descend() throws EstimationException {
097    SeenEntry e = null;
098    try {
099      Iterator<SeenEntry> it = next.iterator();
100      next = new HashSet<SeenEntry>();
101      eleminate = new HashSet<SeenEntry>();
102      while (it.hasNext()) {
103        descend(e = it.next());
104      }
105    } catch (Exception ex) {
106      List<Class<?>> _path = new ArrayList<Class<?>>();
107      while (e != null) {
108        _path.add(0, e.object.getClass());
109        e = e.via;
110      }
111      throw new EstimationException(ex, _path);
112    }
113    eleminateSeenEntries();
114  }
115
116  /**
117   * Reset our counters but keep the lookup set of the objects seen so far.
118   */
119  public void resetCounter() {
120    objectCount = 0;
121    bytes = 0;
122    counter = 0;
123  }
124
125  /** go down the subtree and make sure that we don't descend it further */
126  void eleminateDescendantsFromNext(SeenEntry e) {
127    next.remove(e);
128    for (SeenEntry e2 : e.transitive) {
129      eleminateDescendantsFromNext(e2);
130    }
131  }
132
133  void eleminateSeenEntries() {
134    for (SeenEntry e : eleminate) {
135      eleminateDescendantsFromNext(e);
136      bytes -= e.getTotalBytes();
137      objectCount -= e.getObjectCount();
138    }
139  }
140
141  void descend(SeenEntry e) throws IllegalAccessException {
142    Object o = e.object;
143
144    Class<?> c = o.getClass();
145    if (c.isArray()) {
146      recurseArray(e, c, o);
147    } else {
148      recurseFields(e, c, o);
149    }
150  }
151
152  void recurseArray(SeenEntry e, Class<?> c, Object o) throws IllegalAccessException {
153    Class<?> t = c.getComponentType();
154    if (t.isPrimitive()) {
155      if (t == long.class || t == double.class) {
156        e.bytes += 8 * Array.getLength(o);
157      } else {
158        e.bytes += 4 * Array.getLength(o);
159      }
160    } else {
161      Object[] oa = (Object[]) o;
162      e.bytes += 8 * Array.getLength(o);
163      for (Object oi : oa) {
164        nextLevel(e, oi);
165      }
166    }
167    e.bytes += 24; // array overhead
168    bytes += e.bytes;
169    objectCount++;
170  }
171
172  void recurseFields(SeenEntry e, Class<?> c, Object o) throws IllegalAccessException {
173    if (c == null) { return; }
174    recurseFields(e, c.getSuperclass(), o);
175    Field[] fa = c.getDeclaredFields();
176    for (Field f : fa) {
177      if (Modifier.isStatic(f.getModifiers())) {
178        continue;
179      }
180      Class<?> t = f.getType();
181      if (t.isPrimitive()) {
182        if (t == long.class || t == double.class) {
183          e.bytes += 8;
184        } else {
185          e.bytes += 4;
186        }
187        continue;
188      }
189      e.bytes += 8;
190      f.setAccessible(true);
191      Object o2 = f.get(o);
192      nextLevel(e, o2);
193    }
194    e.bytes += 20; // object overhead
195    objectCount++;
196    bytes += e.bytes;
197  }
198
199  void nextLevel(SeenEntry _parent, Object o) {
200    if (o == null) {
201      return;
202    }
203    SeenEntry e = seen.get(System.identityHashCode(o));
204    if (e != null) {
205      if (_parent != null) { checkObjectNavigationPath(_parent, e); }
206      e.referenceCount++;
207      if (!e.eleminated) {
208        eleminate.add(e);
209        e.eleminated = true;
210      }
211    } else {
212      e = new SeenEntry(o);
213      if (_parent != null) {
214        _parent.transitive.add(e);
215        e.via = _parent;
216      }
217      seen.put(System.identityHashCode(o), e);
218      next.add(e);
219    }
220  }
221
222  /**
223   * to know the accuracy we check the navigation path that has lead us to an object.
224   */
225  final void checkObjectNavigationPath(SeenEntry _parent, SeenEntry e) {
226    if (!circles || !commonObjects) {
227      SeenEntry r0 = _parent.getRoot();
228      SeenEntry r1 = e.getRoot();
229      if (r0 == r1) {
230        circles = true;
231      } else {
232        commonObjects = true;
233      }
234    }
235  }
236
237  public int getByteCount() {
238    return bytes;
239  }
240
241  public int getObjectCount() {
242    return objectCount;
243  }
244
245  public int getCounter() {
246    return counter;
247  }
248
249  public boolean hasCommonObjects() {
250    return commonObjects;
251  }
252
253  public boolean hasCircles() {
254    return circles;
255  }
256
257  static class SeenEntry {
258
259    SeenEntry via;
260    Set<SeenEntry> transitive = new HashSet<SeenEntry>();
261    Object object;
262    int referenceCount = 1;
263    int bytes;
264    boolean eleminated;
265
266    SeenEntry(Object object) {
267      this.object = object;
268    }
269
270    public final SeenEntry getRoot() {
271      return via == null ? this : via.getRoot();
272    }
273
274    public int getTotalBytes() {
275      int v = bytes;
276      for (SeenEntry e : transitive) {
277        if (!e.eleminated) {
278          v += e.getTotalBytes();
279        }
280      }
281      return v;
282    }
283
284    /** Count of object seen below this one, including itself */
285    public int getObjectCount() {
286      int v = 1;
287      for (SeenEntry e : transitive) {
288        if (!e.eleminated && e.bytes > 0) {
289          v++;
290          v += e.getObjectCount();
291        }
292      }
293      return v;
294    }
295
296  }
297
298  public static class EstimationException extends Exception {
299
300    List<Class<?>> path;
301
302    EstimationException(Throwable cause, List<Class<?>> path) {
303      super(cause);
304      this.path = path;
305    }
306
307    public List<Class<?>> getPath() {
308      return path;
309    }
310  }
311
312}