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 org.apache.commons.logging.Log;
026import org.apache.commons.logging.LogFactory;
027
028import java.util.Random;
029
030/**
031 * @author Jens Wilke; created: 2013-07-18
032 */
033public class CacheSizeEstimator {
034
035  final static Log log = LogFactory.getLog(CacheSizeEstimator.class);
036
037  final int MIN_ENTRY_COUNT = 3;
038  final int ADDED_ENTRY_COUNT = 27;
039  final int DEPTH_COUNT = 12;
040
041  int accuracy;
042  Random random;
043  Entry lastEntry;
044  Entry[] hash1;
045  Entry[] hash2;
046
047  final void switchHash() {
048    Entry[] tmp = hash1;
049    hash1 = hash2;
050    hash2 = tmp;
051  }
052
053  final Entry nextEntry() {
054    if (lastEntry.another != null) {
055      return lastEntry = lastEntry.another;
056    }
057    int idx = Hash.index(hash1, lastEntry.hashCode);
058    Entry e;
059    do {
060      idx++;
061      if (idx >= hash1.length) {
062        idx = 0;
063
064      }
065      e = hash1[idx];
066    } while (e == null);
067    return lastEntry = e;
068  }
069
070  final void findAnyEntry() {
071    int idx = 0;
072    while (hash1[idx] != null) {
073      idx++;
074      if (idx >= hash1.length) {
075        idx = 0;
076        switchHash();
077      }
078    }
079    lastEntry = hash1[idx];
080  }
081
082  final void randomizeStartEntry() {
083    findAnyEntry();
084    int _forward = random.nextInt(hash1.length + hash2.length);
085    for (;_forward != 0; _forward--) {
086      nextEntry();
087    }
088  }
089
090  final void addEntries(DepthSearchAndSizeCounter _counter, int _count) {
091    for (int i = 0; i < _count; i++) {
092      _counter.insert(nextEntry().value);
093      _counter.insert(nextEntry().key);
094    }
095  }
096
097  final int getSizeEstimationForAnEntry() {
098    try {
099      DepthSearchAndSizeCounter cse = new DepthSearchAndSizeCounter();
100      addEntries(cse, ADDED_ENTRY_COUNT);
101      for (int i = 0; (i < DEPTH_COUNT) && cse.hasNext(); i++) {
102        cse.descend();
103        if (log.isDebugEnabled()) {
104          log.debug("CSE: depth=" + i + ", counter=" + cse.getCounter() + ", objectCount=" + cse.getObjectCount() + ", memUsage=" + cse.getByteCount() + ", nextCnt=" + cse.next.size());
105        }
106      }
107      accuracy =
108        (cse.hasCircles() ? 1 : 0) +
109        (cse.hasCommonObjects() ? 2 : 0) + // this may apply quite often, e.g. for strings, locale etc.
110        (cse.hasNext() ? 4 : 0);
111      return cse.getByteCount() / ADDED_ENTRY_COUNT;
112
113    } catch (DepthSearchAndSizeCounter.EstimationException ex) {
114      StringBuilder sb = new StringBuilder();
115      sb.append("Problems descending object tree for size estimation, path: ");
116      for (Class<?> c : ex.getPath()) {
117        sb.append(" -> ");
118        sb.append(c.getSimpleName());
119      }
120      log.warn(sb.toString(), ex.getCause());
121      accuracy = 8;
122      return 0;
123    }
124  }
125
126  final int getAccuracy() {
127    return accuracy;
128  }
129
130}