summaryrefslogtreecommitdiffstats
path: root/container-search/src/main/java/com/yahoo/prelude/fastsearch/PacketCache.java
blob: dca23d13ba611e68c079b4d785a8e9fd451e442c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.prelude.fastsearch;


import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.logging.Logger;

import com.yahoo.log.LogLevel;


/**
 * An LRU cache using number of hits cached inside the results as
 * size limiting factor. Directly modelled after com.yahoo.collections.Cache.
 *
 * @author  <a href="mailto:steinar@yahoo-inc.com">Steinar Knutsen</a>
 * @author  bratseth
 */
public class PacketCache extends LinkedHashMap<CacheKey, PacketWrapper> {

    private static final long serialVersionUID = -7403077211906108356L;

    /** The <i>current</i> number of bytes of packets in this cache */
    private int totalSize;

    /** The maximum number of bytes of packets in this cache */
    private final int capacity;

    /** The max size of a cached item compared to the total size */
    private int maxCacheItemPercentage = 1;

    /** The max age for a valid cache entry, 0 mean infinite */
    private final long maxAge;

    private static final Logger log = Logger.getLogger(PacketCache.class.getName());

    public void clear() {
        super.clear();
        totalSize = 0;
    }

    /**
     * Sets the max size of a cached item compared to the total size
     * Cache requests for larger objects will be ignored
     */
    public void setMaxCacheItemPercentage(int maxCapacityPercentage) {
        maxCacheItemPercentage = maxCapacityPercentage;
    }

    /**
     * Creates a cache with a size given by
     * cachesizemegabytes*2^20+cachesizebytes
     *
     * @param capacityMegaBytes the cache size, measured in megabytes
     * @param capacityBytes additional number of bytes to add to the cache size
     * @param maxAge seconds a cache entry is valid, 0 or less are illegal arguments
     */
    public PacketCache(int capacityMegaBytes,int capacityBytes,double maxAge) {
        // hardcoded inital entry capacity, won't matter much anyway
        // after a while
        super(12500, 1.0f, true);
        if (maxAge <= 0.0d) {
            throw new IllegalArgumentException("maxAge <= 0 not legal on 5.1, use some very large number for no timeout.");
        }
        if (capacityMegaBytes > (Integer.MAX_VALUE >> 20)) {
            log.log(LogLevel.INFO, "Packet cache of more than 2 GB requested. Reverting to 2 GB packet cache.");
            this.capacity = Integer.MAX_VALUE;
        } else {
            this.capacity = (capacityMegaBytes << 20) + capacityBytes;
        }
        if (this.capacity <= 0) {
            throw new IllegalArgumentException("Total cache size set to 0 or less bytes. If no caching is desired, avoid creating this object instead.");
        }
        this.maxAge = (long) (maxAge * 1000.0d);
    }

    /**
     * Overrides LinkedHashMap.removeEldestEntry as suggested to implement LRU cache.
     */
    protected boolean removeEldestEntry(Map.Entry<CacheKey, PacketWrapper> eldest)
    {
        if (totalSize > capacity) {
            totalSize -= eldest.getValue().getPacketsSize();
            return true;
        }
        return false;
    }

    private void removeOverflow() {
        if (totalSize < capacity) return;

        for (Iterator<PacketWrapper> i = values().iterator(); i.hasNext();) {
            PacketWrapper eldestEntry = i.next();
            totalSize -= eldestEntry.getPacketsSize();

            i.remove();
            if (totalSize < capacity) {
                break;
            }
        }
    }

    public int getCapacity() {
        return capacity >> 20;
    }

    public int getByteCapacity() {
        return capacity;
    }

    /**
     * Adds a PacketWrapper object to this cache,
     * unless the size is more than maxCacheItemPercentage of the total size
     */
    public PacketWrapper put(CacheKey key, PacketWrapper value) {
        return put(key, value, System.currentTimeMillis());
    }

    /**
     * Adds a BasicPacket array to this cache,
     * unless the size is more than maxCacheItemPercentage of the total size
     *
     * @param timestamp the timestamp for the first packet in the array,
     * unit milliseconds
     */
    public PacketWrapper put(CacheKey key, PacketWrapper result, long timestamp) {
        int size = result.getPacketsSize();

        if (size > 0) {
            result.setTimestamp(timestamp);
        }

        // don't insert if it is too big
        if (size * 100 > capacity * maxCacheItemPercentage) {
            // removeField the old one since that is now stale.
            return remove(key);
        }

        totalSize += size;
        PacketWrapper previous = super.put(key, result);
        if (previous != null) {
            totalSize -= previous.getPacketsSize();
        }
        if (totalSize > (capacity * 1.1)) {
            removeOverflow();
        }

        return previous;
    }

    public PacketWrapper get(CacheKey key) {
        return get(key, System.currentTimeMillis());
    }

    public PacketWrapper get(CacheKey key, long now) {
        PacketWrapper result = super.get(key);

        if (result == null) {
            return result;
        }

        long timestamp = result.getTimestamp();

        if ((now - timestamp) > maxAge) {
            remove(key);
            return null;
        } else {
            return result;
        }
    }

    public PacketWrapper remove(CacheKey key) {
        PacketWrapper removed = super.remove(key);

        if (removed != null) {
            totalSize -= removed.getPacketsSize();
        }
        return removed;
    }

    public int totalPacketSize() {
        return totalSize;
    }

}