fc_rpc/cache/
lru_cache.rs

1// This file is part of Frontier.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19use scale_codec::Encode;
20use schnellru::{LruMap, Unlimited};
21
22pub struct LRUCacheByteLimited<K, V> {
23	cache: LruMap<K, V, Unlimited>,
24	max_size: u64,
25	metrics: Option<LRUCacheByteLimitedMetrics>,
26	size: u64,
27}
28
29impl<K: Eq + core::hash::Hash, V: Encode> LRUCacheByteLimited<K, V> {
30	pub fn new(
31		cache_name: &'static str,
32		max_size: u64,
33		prometheus_registry: Option<prometheus_endpoint::Registry>,
34	) -> Self {
35		let metrics = match prometheus_registry {
36			Some(registry) => match LRUCacheByteLimitedMetrics::register(cache_name, &registry) {
37				Ok(metrics) => Some(metrics),
38				Err(e) => {
39					log::error!(target: "eth-cache", "Failed to register metrics: {e:?}");
40					None
41				}
42			},
43			None => None,
44		};
45
46		Self {
47			cache: LruMap::new(Unlimited),
48			max_size,
49			metrics,
50			size: 0,
51		}
52	}
53	pub fn get(&mut self, k: &K) -> Option<&V> {
54		if let Some(v) = self.cache.get(k) {
55			// Update metrics
56			if let Some(metrics) = &self.metrics {
57				metrics.hits.inc();
58			}
59			Some(v)
60		} else {
61			// Update metrics
62			if let Some(metrics) = &self.metrics {
63				metrics.miss.inc();
64			}
65			None
66		}
67	}
68	pub fn put(&mut self, k: K, v: V) {
69		// Handle size limit
70		self.size += v.encoded_size() as u64;
71
72		while self.size > self.max_size {
73			if let Some((_, v)) = self.cache.pop_oldest() {
74				let v_size = v.encoded_size() as u64;
75				self.size -= v_size;
76			} else {
77				break;
78			}
79		}
80
81		// Add entry in cache
82		self.cache.insert(k, v);
83		// Update metrics
84		if let Some(metrics) = &self.metrics {
85			metrics.size.set(self.size);
86		}
87	}
88}
89
90struct LRUCacheByteLimitedMetrics {
91	hits: prometheus::IntCounter,
92	miss: prometheus::IntCounter,
93	size: prometheus_endpoint::Gauge<prometheus_endpoint::U64>,
94}
95
96impl LRUCacheByteLimitedMetrics {
97	pub(crate) fn register(
98		cache_name: &'static str,
99		registry: &prometheus_endpoint::Registry,
100	) -> Result<Self, prometheus_endpoint::PrometheusError> {
101		Ok(Self {
102			hits: prometheus_endpoint::register(
103				prometheus::IntCounter::new(
104					format!("frontier_eth_{cache_name}_hits"),
105					format!("Hits of eth {cache_name} cache."),
106				)?,
107				registry,
108			)?,
109			miss: prometheus_endpoint::register(
110				prometheus::IntCounter::new(
111					format!("frontier_eth_{cache_name}_miss"),
112					format!("Misses of eth {cache_name} cache."),
113				)?,
114				registry,
115			)?,
116			size: prometheus_endpoint::register(
117				prometheus_endpoint::Gauge::new(
118					format!("frontier_eth_{cache_name}_size"),
119					format!("Size of eth {cache_name} data cache."),
120				)?,
121				registry,
122			)?,
123		})
124	}
125}
126
127#[cfg(test)]
128mod tests {
129	use super::*;
130
131	#[test]
132	fn test_size_limit() {
133		let mut cache = LRUCacheByteLimited::new("name", 10, None);
134		cache.put(0, "abcd");
135		assert!(cache.get(&0).is_some());
136		cache.put(1, "efghij");
137		assert!(cache.get(&1).is_some());
138		cache.put(2, "k");
139		assert!(cache.get(&2).is_some());
140		// Entry (0,  "abcd") should be deleted
141		assert!(cache.get(&0).is_none());
142		// Size should be 7 now, so we should be able to add a value of size 3
143		cache.put(3, "lmn");
144		assert!(cache.get(&3).is_some());
145	}
146}