scmstore: collect API usage metrics

Summary:
Instrument all the fetching-related legacy API implementations (ie, not `get_logged_fetches` and `get_shared_mutable`) to track the number of calls, keys, and single-key calls.

This introduces an additional lock acquisition to each of these implementations (and it'd be awkward to merge it with the one in `fetch`), but I think that's probably fine.

For APIs which do not support a variable number of keys, I use `.call(0)` so we simply track the total number of API calls.

Reviewed By: DurhamG

Differential Revision: D30003444

fbshipit-source-id: 8756d2669ca038b3f6a08e211e44e8ccb9251312
This commit is contained in:
Meyer Jacobs 2021-08-12 19:33:46 -07:00 committed by Facebook GitHub Bot
parent d9a885a6df
commit d4e6c15939
3 changed files with 130 additions and 3 deletions

View File

@ -70,6 +70,11 @@ impl FetchMetrics {
}
}
// TODO(meyer): I don't think this is in any critical paths, but it'd be nicer to rewrite this
// to use `Item = (Vec<&'static str>, usize)` instead of `Item = (String, usize)`, since all
// the fields are indeed statically named right now, or, better, just tree of some sort instead of a
// list of metrics. Probably appropriate for a `SmallVec` too, since the namespace depth is
// limited.
fn namespaced(
namespace: &'static str,
metrics: impl Iterator<Item = (impl AsRef<str>, usize)>,
@ -240,10 +245,111 @@ impl FileStoreWriteMetrics {
}
}
#[derive(Clone, Debug, Default)]
pub struct ApiMetrics {
/// Number of calls to this API
calls: usize,
/// Total number of entities requested across all calls
keys: usize,
/// Number of calls for only a single entity
singles: usize,
}
impl AddAssign for ApiMetrics {
fn add_assign(&mut self, rhs: Self) {
self.calls += rhs.calls;
self.keys += rhs.keys;
self.singles += rhs.singles;
}
}
impl ApiMetrics {
pub(crate) fn call(&mut self, keys: usize) {
self.calls += 1;
self.keys += keys;
if keys == 1 {
self.singles += 1;
}
}
fn metrics(&self) -> impl Iterator<Item = (&'static str, usize)> {
std::array::IntoIter::new([
("calls", self.calls),
("keys", self.keys),
("singles", self.singles),
])
.filter(|&(_, v)| v != 0)
}
}
#[derive(Clone, Debug, Default)]
pub struct FileStoreApiMetrics {
pub(crate) hg_getfilecontent: ApiMetrics,
pub(crate) hg_addpending: ApiMetrics,
pub(crate) hg_commitpending: ApiMetrics,
pub(crate) hg_get: ApiMetrics,
pub(crate) hg_getmeta: ApiMetrics,
pub(crate) hg_refresh: ApiMetrics,
pub(crate) hg_prefetch: ApiMetrics,
pub(crate) hg_upload: ApiMetrics,
pub(crate) hg_getmissing: ApiMetrics,
pub(crate) hg_add: ApiMetrics,
pub(crate) hg_flush: ApiMetrics,
pub(crate) contentdatastore_blob: ApiMetrics,
pub(crate) contentdatastore_metadata: ApiMetrics,
}
impl AddAssign for FileStoreApiMetrics {
fn add_assign(&mut self, rhs: Self) {
self.hg_getfilecontent += rhs.hg_getfilecontent;
self.hg_addpending += rhs.hg_addpending;
self.hg_commitpending += rhs.hg_commitpending;
self.hg_get += rhs.hg_get;
self.hg_getmeta += rhs.hg_getmeta;
self.hg_refresh += rhs.hg_refresh;
self.hg_prefetch += rhs.hg_prefetch;
self.hg_upload += rhs.hg_upload;
self.hg_getmissing += rhs.hg_getmissing;
self.hg_add += rhs.hg_add;
self.hg_flush += rhs.hg_flush;
self.contentdatastore_blob += rhs.contentdatastore_blob;
self.contentdatastore_metadata += rhs.contentdatastore_metadata;
}
}
impl FileStoreApiMetrics {
fn metrics(&self) -> impl Iterator<Item = (String, usize)> {
namespaced("hg_getfilecontent", self.hg_getfilecontent.metrics())
.chain(namespaced("hg_addpending", self.hg_addpending.metrics()))
.chain(namespaced(
"hg_commitpending",
self.hg_commitpending.metrics(),
))
.chain(namespaced("hg_get", self.hg_get.metrics()))
.chain(namespaced("hg_getmeta", self.hg_getmeta.metrics()))
.chain(namespaced("hg_refresh", self.hg_refresh.metrics()))
.chain(namespaced("hg_prefetch", self.hg_prefetch.metrics()))
.chain(namespaced("hg_upload", self.hg_upload.metrics()))
.chain(namespaced("hg_getmissing", self.hg_getmissing.metrics()))
.chain(namespaced("hg_add", self.hg_add.metrics()))
.chain(namespaced(
"contentdatastore_blob",
self.contentdatastore_blob.metrics(),
))
.chain(namespaced(
"contentdatastore_metadata",
self.contentdatastore_metadata.metrics(),
))
}
}
#[derive(Debug, Default, Clone)]
pub struct FileStoreMetrics {
pub(crate) fetch: FileStoreFetchMetrics,
pub(crate) write: FileStoreWriteMetrics,
pub(crate) api: FileStoreApiMetrics,
}
impl FileStoreMetrics {
@ -255,7 +361,8 @@ impl FileStoreMetrics {
namespaced(
"scmstore.file",
namespaced("fetch", self.fetch.metrics())
.chain(namespaced("write", self.write.metrics())),
.chain(namespaced("write", self.write.metrics()))
.chain(namespaced("api", self.api.metrics())),
)
}
}

View File

@ -456,6 +456,7 @@ impl LegacyStore for FileStore {
#[instrument(skip(self))]
fn get_file_content(&self, key: &Key) -> Result<Option<Bytes>> {
self.metrics.write().api.hg_getfilecontent.call(0);
self.fetch(std::iter::once(key.clone()), FileAttributes::CONTENT)
.single()?
.map(|entry| entry.content.unwrap().file_content())
@ -471,6 +472,7 @@ impl LegacyStore for FileStore {
meta: Metadata,
location: RepackLocation,
) -> Result<()> {
self.metrics.write().api.hg_addpending.call(0);
if let Some(contentstore) = self.contentstore.as_ref() {
contentstore.add_pending(key, data, meta, location)
} else {
@ -488,6 +490,7 @@ impl LegacyStore for FileStore {
}
fn commit_pending(&self, location: RepackLocation) -> Result<Option<Vec<PathBuf>>> {
self.metrics.write().api.hg_commitpending.call(0);
if let Some(contentstore) = self.contentstore.as_ref() {
contentstore.commit_pending(location)
} else {
@ -500,6 +503,7 @@ impl LegacyStore for FileStore {
impl HgIdDataStore for FileStore {
// Fetch the raw content of a single TreeManifest blob
fn get(&self, key: StoreKey) -> Result<StoreResult<Vec<u8>>> {
self.metrics.write().api.hg_get.call(0);
Ok(
match self
.fetch(
@ -515,6 +519,7 @@ impl HgIdDataStore for FileStore {
}
fn get_meta(&self, key: StoreKey) -> Result<StoreResult<Metadata>> {
self.metrics.write().api.hg_getmeta.call(0);
Ok(
match self
.fetch(
@ -530,6 +535,7 @@ impl HgIdDataStore for FileStore {
}
fn refresh(&self) -> Result<()> {
self.metrics.write().api.hg_refresh.call(0);
// AFAIK refresh only matters for DataPack / PackStore
Ok(())
}
@ -537,6 +543,7 @@ impl HgIdDataStore for FileStore {
impl RemoteDataStore for FileStore {
fn prefetch(&self, keys: &[StoreKey]) -> Result<Vec<StoreKey>> {
self.metrics.write().api.hg_prefetch.call(keys.len());
Ok(self
.fetch(
keys.iter().cloned().filter_map(|sk| sk.maybe_into_key()),
@ -549,6 +556,7 @@ impl RemoteDataStore for FileStore {
}
fn upload(&self, keys: &[StoreKey]) -> Result<Vec<StoreKey>> {
self.metrics.write().api.hg_upload.call(keys.len());
// TODO(meyer): Eliminate usage of legacy API, or at least minimize it (do we really need memcache + multiplex, etc)
if let Some(ref lfs_remote) = self.lfs_remote {
let mut multiplex = MultiplexDeltaStore::new();
@ -570,6 +578,7 @@ impl RemoteDataStore for FileStore {
impl LocalStore for FileStore {
fn get_missing(&self, keys: &[StoreKey]) -> Result<Vec<StoreKey>> {
self.metrics.write().api.hg_getmissing.call(keys.len());
Ok(self
.local()
.fetch(
@ -585,6 +594,7 @@ impl LocalStore for FileStore {
impl HgIdMutableDeltaStore for FileStore {
fn add(&self, delta: &Delta, metadata: &Metadata) -> Result<()> {
self.metrics.write().api.hg_add.call(0);
if let Delta {
data,
base: None,
@ -598,6 +608,7 @@ impl HgIdMutableDeltaStore for FileStore {
}
fn flush(&self) -> Result<Option<Vec<PathBuf>>> {
self.metrics.write().api.hg_flush.call(0);
self.flush()?;
Ok(None)
}
@ -607,6 +618,7 @@ impl HgIdMutableDeltaStore for FileStore {
// that if available, but I feel like there's probably something wrong if this is called for trees.
impl ContentDataStore for FileStore {
fn blob(&self, key: StoreKey) -> Result<StoreResult<Bytes>> {
self.metrics.write().api.contentdatastore_blob.call(0);
Ok(
match self
.fetch(
@ -622,6 +634,7 @@ impl ContentDataStore for FileStore {
}
fn metadata(&self, key: StoreKey) -> Result<StoreResult<ContentMetadata>> {
self.metrics.write().api.contentdatastore_metadata.call(0);
Ok(
match self
.fetch(

View File

@ -24,7 +24,13 @@ Clone it
fetching tree '' a539ce0c1a22b0ecf34498f9f5ce8ea56df9ecb7
1 trees fetched over * (glob)
2 files fetched over 1 fetches - (2 misses, 0.00% hit ratio) over * (glob) (?)
{ metrics : { scmstore : { file : { fetch : { contentstore : { hits : 2,
{ metrics : { scmstore : { file : { api : { hg : { getfilecontent : { calls : 2},
getmeta : { calls : 2},
getmissing : { calls : 1,
keys : 2},
prefetch : { calls : 3,
keys : 4}}},
fetch : { contentstore : { hits : 2,
keys : 2,
requests : 1},
indexedlog : { cache : { hits : 8,
@ -86,7 +92,8 @@ Pull exactly up to d into the client
adding manifests
adding file changes
added 2 changesets with 0 changes to 0 files
{ metrics : { ssh : { connections : 1,
{ metrics : { scmstore : { file : { api : { hg : { prefetch : { calls : 1}}}}},
ssh : { connections : 1,
read : { bytes : 1086},
write : { bytes : 680}}}}