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:
Thomas Orozco 2019-05-10 08:16:40 -07:00 committed by Facebook Github Bot
parent 2c814d935c
commit a70681f359
4 changed files with 151 additions and 58 deletions

View File

@ -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");
}

View File

@ -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 {

View File

@ -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)
}
}
}
}

View 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
}
);
}
}