mirror of
https://github.com/facebook/sapling.git
synced 2024-10-10 08:47:12 +03:00
caching_ext: expose cache hits / misses
Summary: This updates caching_ext to record cache hit and miss stats. This makes it easier to write tests that exercise this caching. As part of this, I refactored the CachelibHandler and MemcacheHandler mocks to use a shared MockStore implementation. Reviewed By: StanislavGlebik Differential Revision: D15220647 fbshipit-source-id: b0f70b9780f577226664ebf6760b5fc93d733cd3
This commit is contained in:
parent
2c814d935c
commit
a70681f359
@ -6,17 +6,19 @@
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::hash::Hash;
|
||||
use std::sync::{Arc, Mutex, atomic::{AtomicUsize, Ordering}};
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use cachelib::{get_cached, set_cached, Abomonation, LruCachePool};
|
||||
use failure::prelude::*;
|
||||
use mock_store::MockStore;
|
||||
|
||||
use CachelibKey;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum CachelibHandler<T> {
|
||||
Real(LruCachePool),
|
||||
#[allow(dead_code)] Mock(MockCachelib<T>),
|
||||
#[allow(dead_code)]
|
||||
Mock(MockStore<T>),
|
||||
}
|
||||
|
||||
impl<T> From<LruCachePool> for CachelibHandler<T> {
|
||||
@ -25,21 +27,6 @@ impl<T> From<LruCachePool> for CachelibHandler<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MockCachelib<T> {
|
||||
cache: Arc<Mutex<HashMap<String, T>>>,
|
||||
get_count: Arc<AtomicUsize>,
|
||||
}
|
||||
|
||||
impl<T> MockCachelib<T> {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
cache: Arc::new(Mutex::new(HashMap::new())),
|
||||
get_count: Arc::new(AtomicUsize::new(0)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Abomonation + Clone + Send + 'static> CachelibHandler<T> {
|
||||
pub(crate) fn get_multiple_from_cachelib<Key: Eq + Hash>(
|
||||
&self,
|
||||
@ -78,25 +65,15 @@ impl<T: Abomonation + Clone + Send + 'static> CachelibHandler<T> {
|
||||
fn get_cached(&self, key: &String) -> Result<Option<T>> {
|
||||
match self {
|
||||
CachelibHandler::Real(ref cache) => get_cached(cache, key),
|
||||
CachelibHandler::Mock(MockCachelib {
|
||||
ref cache,
|
||||
ref get_count,
|
||||
..
|
||||
}) => {
|
||||
get_count.fetch_add(1, Ordering::SeqCst);
|
||||
Ok(cache.lock().expect("poisoned lock").get(key).cloned())
|
||||
}
|
||||
CachelibHandler::Mock(store) => Ok(store.get(key)),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_cached(&self, key: &String, value: &T) -> Result<bool> {
|
||||
match self {
|
||||
CachelibHandler::Real(ref cache) => set_cached(cache, key, value),
|
||||
CachelibHandler::Mock(MockCachelib { ref cache, .. }) => {
|
||||
cache
|
||||
.lock()
|
||||
.expect("poisoned lock")
|
||||
.insert(key.clone(), value.clone());
|
||||
CachelibHandler::Mock(store) => {
|
||||
store.set(key, value);
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
@ -104,14 +81,14 @@ impl<T: Abomonation + Clone + Send + 'static> CachelibHandler<T> {
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn create_mock() -> Self {
|
||||
CachelibHandler::Mock(MockCachelib::new())
|
||||
CachelibHandler::Mock(MockStore::new())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn gets_count(&self) -> usize {
|
||||
match self {
|
||||
CachelibHandler::Real(_) => unimplemented!(),
|
||||
CachelibHandler::Mock(MockCachelib { ref get_count, .. }) => {
|
||||
CachelibHandler::Mock(MockStore { ref get_count, .. }) => {
|
||||
get_count.load(Ordering::SeqCst)
|
||||
}
|
||||
}
|
||||
@ -134,12 +111,12 @@ mod tests {
|
||||
let fill_query = initial_keys.clone().into_iter().map(|(key, val)| (key.clone(), (val, CachelibKey(key)))).collect();
|
||||
let get_query = keys_to_query.clone().into_iter().map(|key| (key.clone(), CachelibKey(key))).collect();
|
||||
|
||||
let mock_cachelib = MockCachelib::new();
|
||||
let mock_cachelib = MockStore::new();
|
||||
let cachelib_handler = CachelibHandler::Mock(mock_cachelib.clone());
|
||||
|
||||
cachelib_handler.fill_multiple_cachelib(fill_query);
|
||||
|
||||
if *mock_cachelib.cache.lock().expect("poisoned lock") != initial_keys {
|
||||
if mock_cachelib.data() != initial_keys {
|
||||
return TestResult::error("After fill_multiple_cachelib the content of cache is incorrect");
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,7 @@ extern crate quickcheck;
|
||||
|
||||
mod cachelib_utils;
|
||||
mod memcache_utils;
|
||||
mod mock_store;
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::hash::Hash;
|
||||
@ -47,6 +48,7 @@ use mononoke_types::RepositoryId;
|
||||
|
||||
pub use cachelib_utils::CachelibHandler;
|
||||
pub use memcache_utils::MemcacheHandler;
|
||||
pub use mock_store::MockStoreStats;
|
||||
|
||||
/// Error type to help with proper reporting of memcache errors
|
||||
pub enum McErrorKind {
|
||||
|
@ -5,21 +5,18 @@
|
||||
// GNU General Public License version 2 or any later version.
|
||||
|
||||
use bytes::Bytes;
|
||||
use futures::{Future, future::ok};
|
||||
use futures::{future::ok, Future};
|
||||
use futures_ext::FutureExt;
|
||||
use iobuf::IOBuf;
|
||||
use memcache::MemcacheClient;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex, atomic::{AtomicUsize, Ordering}};
|
||||
use mock_store::MockStore;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum MemcacheHandler {
|
||||
Real(MemcacheClient),
|
||||
#[allow(dead_code)]
|
||||
Mock {
|
||||
data: Arc<Mutex<HashMap<String, Bytes>>>,
|
||||
get_count: Arc<AtomicUsize>,
|
||||
},
|
||||
Mock(MockStore<Bytes>),
|
||||
}
|
||||
|
||||
impl From<MemcacheClient> for MemcacheHandler {
|
||||
@ -32,14 +29,8 @@ impl MemcacheHandler {
|
||||
pub fn get(&self, key: String) -> impl Future<Item = Option<IOBuf>, Error = ()> {
|
||||
match self {
|
||||
MemcacheHandler::Real(ref client) => client.get(key).left_future(),
|
||||
MemcacheHandler::Mock {
|
||||
ref data,
|
||||
ref get_count,
|
||||
..
|
||||
} => {
|
||||
get_count.fetch_add(1, Ordering::SeqCst);
|
||||
let data = data.lock().expect("poisoned lock");
|
||||
ok(data.get(&key).map(|value| value.clone().into())).right_future()
|
||||
MemcacheHandler::Mock(store) => {
|
||||
ok(store.get(&key).map(|value| value.clone().into())).right_future()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -47,10 +38,8 @@ impl MemcacheHandler {
|
||||
pub fn set(&self, key: String, value: Bytes) -> impl Future<Item = (), Error = ()> {
|
||||
match self {
|
||||
MemcacheHandler::Real(ref client) => client.set(key, value).left_future(),
|
||||
MemcacheHandler::Mock { ref data, .. } => {
|
||||
data.lock()
|
||||
.expect("poisoned lock")
|
||||
.insert(key.clone(), value.clone());
|
||||
MemcacheHandler::Mock(store) => {
|
||||
store.set(&key, &value);
|
||||
ok(()).right_future()
|
||||
}
|
||||
}
|
||||
@ -58,17 +47,16 @@ impl MemcacheHandler {
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn create_mock() -> Self {
|
||||
MemcacheHandler::Mock {
|
||||
data: Arc::new(Mutex::new(HashMap::new())),
|
||||
get_count: Arc::new(AtomicUsize::new(0)),
|
||||
}
|
||||
MemcacheHandler::Mock(MockStore::new())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn gets_count(&self) -> usize {
|
||||
match self {
|
||||
MemcacheHandler::Real(_) => unimplemented!(),
|
||||
MemcacheHandler::Mock { ref get_count, .. } => get_count.load(Ordering::SeqCst),
|
||||
MemcacheHandler::Mock(MockStore { ref get_count, .. }) => {
|
||||
get_count.load(Ordering::SeqCst)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
126
common/rust/caching_ext/src/mock_store.rs
Normal file
126
common/rust/caching_ext/src/mock_store.rs
Normal file
@ -0,0 +1,126 @@
|
||||
// Copyright (c) 2018-present, Facebook, Inc.
|
||||
// All Rights Reserved.
|
||||
//
|
||||
// This software may be used and distributed according to the terms of the
|
||||
// GNU General Public License version 2 or any later version.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc, Mutex,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct MockStoreStats {
|
||||
pub sets: usize,
|
||||
pub gets: usize,
|
||||
pub hits: usize,
|
||||
pub misses: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MockStore<T> {
|
||||
data: Arc<Mutex<HashMap<String, T>>>,
|
||||
pub(crate) set_count: Arc<AtomicUsize>,
|
||||
pub(crate) get_count: Arc<AtomicUsize>,
|
||||
pub(crate) hit_count: Arc<AtomicUsize>,
|
||||
pub(crate) miss_count: Arc<AtomicUsize>,
|
||||
}
|
||||
|
||||
impl<T> MockStore<T> {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
data: Arc::new(Mutex::new(HashMap::new())),
|
||||
set_count: Arc::new(AtomicUsize::new(0)),
|
||||
get_count: Arc::new(AtomicUsize::new(0)),
|
||||
hit_count: Arc::new(AtomicUsize::new(0)),
|
||||
miss_count: Arc::new(AtomicUsize::new(0)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stats(&self) -> MockStoreStats {
|
||||
MockStoreStats {
|
||||
sets: self.set_count.load(Ordering::SeqCst),
|
||||
gets: self.get_count.load(Ordering::SeqCst),
|
||||
misses: self.miss_count.load(Ordering::SeqCst),
|
||||
hits: self.hit_count.load(Ordering::SeqCst),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> MockStore<T> {
|
||||
pub fn get(&self, key: &String) -> Option<T> {
|
||||
self.get_count.fetch_add(1, Ordering::SeqCst);
|
||||
let value = self.data.lock().expect("poisoned lock").get(key).cloned();
|
||||
match &value {
|
||||
Some(..) => self.hit_count.fetch_add(1, Ordering::SeqCst),
|
||||
None => self.miss_count.fetch_add(1, Ordering::SeqCst),
|
||||
};
|
||||
value
|
||||
}
|
||||
|
||||
pub fn set(&self, key: &String, value: &T) {
|
||||
self.set_count.fetch_add(1, Ordering::SeqCst);
|
||||
self.data
|
||||
.lock()
|
||||
.expect("poisoned lock")
|
||||
.insert(key.clone(), value.clone());
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn data(&self) -> HashMap<String, T> {
|
||||
self.data.lock().expect("poisoned lock").clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_counts() {
|
||||
let store = MockStore::new();
|
||||
assert_eq!(
|
||||
store.stats(),
|
||||
MockStoreStats {
|
||||
sets: 0,
|
||||
gets: 0,
|
||||
misses: 0,
|
||||
hits: 0
|
||||
}
|
||||
);
|
||||
|
||||
store.set(&"foo".to_string(), &());
|
||||
assert_eq!(
|
||||
store.stats(),
|
||||
MockStoreStats {
|
||||
sets: 1,
|
||||
gets: 0,
|
||||
misses: 0,
|
||||
hits: 0
|
||||
}
|
||||
);
|
||||
|
||||
store.get(&"foo".to_string());
|
||||
assert_eq!(
|
||||
store.stats(),
|
||||
MockStoreStats {
|
||||
sets: 1,
|
||||
gets: 1,
|
||||
misses: 0,
|
||||
hits: 1
|
||||
}
|
||||
);
|
||||
|
||||
store.get(&"bar".to_string());
|
||||
assert_eq!(
|
||||
store.stats(),
|
||||
MockStoreStats {
|
||||
sets: 1,
|
||||
gets: 2,
|
||||
misses: 1,
|
||||
hits: 1
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user