sapling/eden/fs/telemetry/EdenStats.h
Xavier Deguillard 07df8faf5e win: when creating a file/directory, create the parents too
Summary:
As opposed to FUSE, ProjectedFS sends notifications for file/directory creation
after the fact, and for directory that means these will be visible on disk before
EdenFS may be aware of it. While EdenFS usually process it quickly, a heavily
multi-threaded application that tries to concurrently create a directory
hierarchy may end up sending notifications to EdenFS in a somewhat out of order
fashion.

Since this should be a very rare occurence, we make this a very slow path by
being optimistic and calling `getInode` first, and then only if that fails, we
aggressively create all the parent directories. During a buck build of ~1k
jobs, this happened only 3 times.

If we fully think this through, this change doesn't fully fix the race, as a
similar race can now happen when a create and remove/rename operations are
concurrent. However, a client performing these operations concurrently is
either aware that this is racy and should handle these properly, or is most
likely buggy. Both of these should significantly reduce the likelyhod of this
happening, thus, I'm leaving this unfixed for now.

To better understand how frequently this happens, I've added a stat counter.
For now, these aren't published to ODS, but this will be tackled later.

Reviewed By: wez

Differential Revision: D22783484

fbshipit-source-id: ea3aafc2f77b65d3967f697f68114921d5909137
2020-07-29 12:17:17 -07:00

239 lines
8.1 KiB
C++

/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This software may be used and distributed according to the terms of the
* GNU General Public License version 2.
*/
#pragma once
#include <fb303/ThreadLocalStats.h>
#include <folly/ThreadLocal.h>
#include <memory>
#include "eden/fs/eden-config.h"
namespace facebook {
namespace eden {
class FuseThreadStats;
class PrjFSThreadStats;
class ObjectStoreThreadStats;
class HgBackingStoreThreadStats;
class HgImporterThreadStats;
class JournalThreadStats;
class EdenStats {
public:
#ifndef _WIN32
using ChannelThreadStats = FuseThreadStats;
#else
using ChannelThreadStats = PrjFSThreadStats;
#endif
/**
* This function can be called on any thread.
*
* The returned object can be used only on the current thread.
*/
ChannelThreadStats& getChannelStatsForCurrentThread();
/**
* This function can be called on any thread.
*
* The returned object can be used only on the current thread.
*/
ObjectStoreThreadStats& getObjectStoreStatsForCurrentThread();
/**
* This function can be called on any thread.
*
* The returned object can be used only on the current thread.
*/
HgBackingStoreThreadStats& getHgBackingStoreStatsForCurrentThread();
/**
* This function can be called on any thread.
*
* The returned object can be used only on the current thread.
*/
HgImporterThreadStats& getHgImporterStatsForCurrentThread();
/**
* This function can be called on any thread.
*
* The returned object can be used only on the current thread.
*/
JournalThreadStats& getJournalStatsForCurrentThread();
/**
* This function can be called on any thread.
*/
void aggregate();
private:
class ThreadLocalTag {};
folly::ThreadLocal<ChannelThreadStats, ThreadLocalTag, void>
threadLocalChannelStats_;
folly::ThreadLocal<ObjectStoreThreadStats, ThreadLocalTag, void>
threadLocalObjectStoreStats_;
folly::ThreadLocal<HgBackingStoreThreadStats, ThreadLocalTag, void>
threadLocalHgBackingStoreStats_;
folly::ThreadLocal<HgImporterThreadStats, ThreadLocalTag, void>
threadLocalHgImporterStats_;
folly::ThreadLocal<JournalThreadStats, ThreadLocalTag, void>
threadLocalJournalStats_;
};
std::shared_ptr<HgImporterThreadStats> getSharedHgImporterStatsForCurrentThread(
std::shared_ptr<EdenStats>);
/**
* EdenThreadStatsBase is a base class for a group of thread-local stats
* structures.
*
* Each EdenThreadStatsBase object should only be used from a single thread. The
* EdenStats object should be used to maintain one EdenThreadStatsBase object
* for each thread that needs to access/update the stats.
*/
class EdenThreadStatsBase
: public fb303::ThreadLocalStatsT<fb303::TLStatsThreadSafe> {
public:
using Histogram = TLHistogram;
using Timeseries = TLTimeseries;
explicit EdenThreadStatsBase();
protected:
Histogram createHistogram(const std::string& name);
Timeseries createTimeseries(const std::string& name);
};
class FuseThreadStats : public EdenThreadStatsBase {
public:
// We track latency in units of microseconds, hence the _us suffix
// in the histogram names below.
Histogram lookup{createHistogram("fuse.lookup_us")};
Histogram forget{createHistogram("fuse.forget_us")};
Histogram getattr{createHistogram("fuse.getattr_us")};
Histogram setattr{createHistogram("fuse.setattr_us")};
Histogram readlink{createHistogram("fuse.readlink_us")};
Histogram mknod{createHistogram("fuse.mknod_us")};
Histogram mkdir{createHistogram("fuse.mkdir_us")};
Histogram unlink{createHistogram("fuse.unlink_us")};
Histogram rmdir{createHistogram("fuse.rmdir_us")};
Histogram symlink{createHistogram("fuse.symlink_us")};
Histogram rename{createHistogram("fuse.rename_us")};
Histogram link{createHistogram("fuse.link_us")};
Histogram open{createHistogram("fuse.open_us")};
Histogram read{createHistogram("fuse.read_us")};
Histogram write{createHistogram("fuse.write_us")};
Histogram flush{createHistogram("fuse.flush_us")};
Histogram release{createHistogram("fuse.release_us")};
Histogram fsync{createHistogram("fuse.fsync_us")};
Histogram opendir{createHistogram("fuse.opendir_us")};
Histogram readdir{createHistogram("fuse.readdir_us")};
Histogram releasedir{createHistogram("fuse.releasedir_us")};
Histogram fsyncdir{createHistogram("fuse.fsyncdir_us")};
Histogram statfs{createHistogram("fuse.statfs_us")};
Histogram setxattr{createHistogram("fuse.setxattr_us")};
Histogram getxattr{createHistogram("fuse.getxattr_us")};
Histogram listxattr{createHistogram("fuse.listxattr_us")};
Histogram removexattr{createHistogram("fuse.removexattr_us")};
Histogram access{createHistogram("fuse.access_us")};
Histogram create{createHistogram("fuse.create_us")};
Histogram bmap{createHistogram("fuse.bmap_us")};
Histogram ioctl{createHistogram("fuse.ioctl_us")};
Histogram poll{createHistogram("fuse.poll_us")};
Histogram forgetmulti{createHistogram("fuse.forgetmulti_us")};
// Since we can potentially finish a request in a different
// thread from the one used to initiate it, we use HistogramPtr
// as a helper for referencing the pointer-to-member that we
// want to update at the end of the request.
using HistogramPtr = Histogram FuseThreadStats::*;
/** Record a the latency for an operation.
* item is the pointer-to-member for one of the histograms defined
* above.
* elapsed is the duration of the operation, measured in microseconds.
* now is the current steady clock value in seconds.
* (Once we open source the common stats code we can eliminate the
* now parameter from this method). */
void recordLatency(
HistogramPtr item,
std::chrono::microseconds elapsed,
std::chrono::seconds now);
};
class PrjFSThreadStats : public EdenThreadStatsBase {
public:
Timeseries outOfOrderCreate{createTimeseries("prjfs.out_of_order_create")};
};
/**
* @see ObjectStore
*/
class ObjectStoreThreadStats : public EdenThreadStatsBase {
public:
Timeseries getBlobFromLocalStore{
createTimeseries("object_store.get_blob.local_store")};
Timeseries getBlobFromBackingStore{
createTimeseries("object_store.get_blob.backing_store")};
Timeseries getBlobMetadataFromMemory{
createTimeseries("object_store.get_blob_metadata.memory")};
Timeseries getBlobMetadataFromLocalStore{
createTimeseries("object_store.get_blob_metadata.local_store")};
Timeseries getBlobMetadataFromBackingStore{
createTimeseries("object_store.get_blob_metadata.backing_store")};
Timeseries getBlobSizeFromLocalStore{
createTimeseries("object_store.get_blob_size.local_store")};
Timeseries getBlobSizeFromBackingStore{
createTimeseries("object_store.get_blob_size.backing_store")};
};
/**
* @see HgBackingStore
*/
class HgBackingStoreThreadStats : public EdenThreadStatsBase {
public:
Histogram hgBackingStoreGetBlob{createHistogram("store.hg.get_blob")};
Histogram hgBackingStoreImportBlob{createHistogram("store.hg.import_blob")};
Histogram hgBackingStoreGetTree{createHistogram("store.hg.get_tree")};
Histogram hgBackingStoreImportTree{createHistogram("store.hg.import_tree")};
Histogram mononokeBackingStoreGetTree{
createHistogram("store.mononoke.get_tree")};
Histogram mononokeBackingStoreGetBlob{
createHistogram("store.mononoke.get_blob")};
};
/**
* @see HgImporter
* @see HgBackingStore
*/
class HgImporterThreadStats : public EdenThreadStatsBase {
public:
Timeseries catFile{createTimeseries("hg_importer.cat_file")};
Timeseries fetchTree{createTimeseries("hg_importer.fetch_tree")};
Timeseries manifest{createTimeseries("hg_importer.manifest")};
Timeseries manifestNodeForCommit{
createTimeseries("hg_importer.manifest_node_for_commit")};
Timeseries prefetchFiles{createTimeseries("hg_importer.prefetch_files")};
};
class JournalThreadStats : public EdenThreadStatsBase {
public:
Timeseries truncatedReads{this, "journal.truncated_reads", fb303::SUM};
Timeseries filesAccumulated{this,
"journal.files_accumulated",
fb303::COUNT,
fb303::SUM};
};
} // namespace eden
} // namespace facebook