Implementing callback functionality for exception routines
authorDmitry Pleshkov <bort@fb.com>
Mon, 1 Feb 2016 23:35:18 +0000 (15:35 -0800)
committerfacebook-github-bot-4 <folly-bot@fb.com>
Tue, 2 Feb 2016 00:20:32 +0000 (16:20 -0800)
Summary: Depends on D2865911

Reviewed By: luciang

Differential Revision: D2742158

fb-gh-sync-id: 3e7866a742575ee4f7501cff0abbd5c21e26a46e

folly/experimental/exception_tracer/ExceptionCounterLib.cpp [new file with mode: 0644]
folly/experimental/exception_tracer/ExceptionCounterLib.h [new file with mode: 0644]
folly/experimental/exception_tracer/ExceptionStackTraceLib.cpp [new file with mode: 0644]
folly/experimental/exception_tracer/ExceptionTracer.cpp
folly/experimental/exception_tracer/ExceptionTracerLib.cpp
folly/experimental/exception_tracer/ExceptionTracerLib.h [new file with mode: 0644]
folly/experimental/exception_tracer/test/ExceptionCounterTest.cpp [new file with mode: 0644]

diff --git a/folly/experimental/exception_tracer/ExceptionCounterLib.cpp b/folly/experimental/exception_tracer/ExceptionCounterLib.cpp
new file mode 100644 (file)
index 0000000..d08971b
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2016 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/experimental/exception_tracer/ExceptionCounterLib.h>
+
+#include <iosfwd>
+#include <unordered_map>
+
+#include <folly/RWSpinLock.h>
+#include <folly/Synchronized.h>
+#include <folly/ThreadLocal.h>
+
+#include <folly/experimental/exception_tracer/ExceptionTracerLib.h>
+#include <folly/experimental/exception_tracer/StackTrace.h>
+#include <folly/experimental/symbolizer/Symbolizer.h>
+
+using namespace folly::exception_tracer;
+
+namespace {
+
+// We are using hash of the stack trace to uniquely identify the exception
+using ExceptionId = uintptr_t;
+
+using ExceptionStatsHolderType =
+    std::unordered_map<ExceptionId, ExceptionStats>;
+
+struct ExceptionStatsStorage {
+  void appendTo(ExceptionStatsHolderType& data) {
+    ExceptionStatsHolderType tempHolder;
+    SYNCHRONIZED(statsHolder) {
+      using std::swap;
+      swap(statsHolder, tempHolder);
+    }
+
+    for (const auto& myData : tempHolder) {
+      const auto& myStat = myData.second;
+
+      auto it = data.find(myData.first);
+      if (it != data.end()) {
+        it->second.count += myStat.count;
+      } else {
+        data.insert(myData);
+      }
+    }
+  }
+
+  folly::Synchronized<ExceptionStatsHolderType, folly::RWSpinLock> statsHolder;
+};
+
+class Tag {};
+
+folly::ThreadLocal<ExceptionStatsStorage, Tag> gExceptionStats;
+
+} // namespace
+
+namespace folly {
+namespace exception_tracer {
+
+std::vector<ExceptionStats> getExceptionStatistics() {
+  ExceptionStatsHolderType accumulator;
+  for (auto& threadStats : gExceptionStats.accessAllThreads()) {
+    threadStats.appendTo(accumulator);
+  }
+
+  std::vector<ExceptionStats> result;
+  result.reserve(accumulator.size());
+  for (const auto& item : accumulator) {
+    result.push_back(item.second);
+  }
+
+  std::sort(result.begin(),
+            result.end(),
+            [](const ExceptionStats& lhs, const ExceptionStats& rhs) {
+              return (lhs.count > rhs.count);
+            });
+
+  return result;
+}
+
+std::ostream& operator<<(std::ostream& out, const ExceptionStats& stats) {
+  out << "Exception report: " << std::endl;
+  out << "Exception count: " << stats.count << std::endl;
+  out << stats.info;
+
+  return out;
+}
+
+} // namespace exception_tracer
+} // namespace folly
+
+namespace {
+
+/*
+ * This handler gathers statistics on all exceptions thrown by the program
+ * Information is being stored in thread local storage.
+ */
+void throwHandler(void*, std::type_info* exType, void (*)(void*)) noexcept {
+  ExceptionInfo info;
+  info.type = exType;
+  auto& frames = info.frames;
+
+  frames.resize(kMaxFrames);
+  auto n = folly::symbolizer::getStackTrace(frames.data(), kMaxFrames);
+
+  if (n == -1) {
+    LOG(ERROR) << "Invalid stack frame";
+    return;
+  }
+
+  frames.resize(n);
+  auto exceptionId = folly::hash::hash_range(frames.begin(), frames.end());
+
+  SYNCHRONIZED(holder, gExceptionStats->statsHolder) {
+    auto it = holder.find(exceptionId);
+    if (it != holder.end()) {
+      ++it->second.count;
+    } else {
+      holder.emplace(exceptionId, ExceptionStats{1, std::move(info)});
+    }
+  }
+}
+
+struct Initializer {
+  Initializer() { registerCxaThrowCallback(throwHandler); }
+};
+
+Initializer initializer;
+
+} // namespace
diff --git a/folly/experimental/exception_tracer/ExceptionCounterLib.h b/folly/experimental/exception_tracer/ExceptionCounterLib.h
new file mode 100644 (file)
index 0000000..3e96dc9
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2016 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <vector>
+#include <ostream>
+
+#include <folly/experimental/exception_tracer/ExceptionTracer.h>
+
+namespace folly {
+namespace exception_tracer {
+
+struct ExceptionStats {
+  uint64_t count;
+  ExceptionInfo info;
+};
+
+/**
+ * This function accumulates exception throwing statistics across all threads.
+ * Please note, that during call to this function, other threads might block
+ * on exception throws, so it should be called seldomly.
+ * All pef-thread statistics is being reset by the call.
+ */
+std::vector<ExceptionStats> getExceptionStatistics();
+
+std::ostream& operator<<(std::ostream& out, const ExceptionStats& data);
+
+} // namespace exception_tracer
+} // namespace folly
diff --git a/folly/experimental/exception_tracer/ExceptionStackTraceLib.cpp b/folly/experimental/exception_tracer/ExceptionStackTraceLib.cpp
new file mode 100644 (file)
index 0000000..aee87b8
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2016 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <exception>
+
+#include <folly/experimental/exception_tracer/ExceptionAbi.h>
+#include <folly/experimental/exception_tracer/StackTrace.h>
+#include <folly/experimental/exception_tracer/ExceptionTracer.h>
+#include <folly/experimental/exception_tracer/ExceptionTracerLib.h>
+#include <folly/experimental/symbolizer/Symbolizer.h>
+
+using namespace folly::exception_tracer;
+
+namespace {
+
+// If we somehow ended up in an invalid state, we don't want to print any stack
+// trace at all because in could be bogus
+FOLLY_TLS bool invalid;
+
+FOLLY_TLS StackTraceStack activeExceptions;
+FOLLY_TLS StackTraceStack caughtExceptions;
+
+} // namespace
+
+// This function is exported and may be found via dlsym(RTLD_NEXT, ...)
+extern "C" StackTraceStack* getExceptionStackTraceStack() {
+  return invalid ? nullptr : &caughtExceptions;
+}
+
+namespace {
+
+void addActiveException() {
+  // Capture stack trace
+  if (!invalid) {
+    if (!activeExceptions.pushCurrent()) {
+      activeExceptions.clear();
+      caughtExceptions.clear();
+      invalid = true;
+    }
+  }
+}
+
+void moveTopException(StackTraceStack& from, StackTraceStack& to) {
+  if (invalid) {
+    return;
+  }
+  if (!to.moveTopFrom(from)) {
+    from.clear();
+    to.clear();
+    invalid = true;
+  }
+}
+
+struct Initializer {
+  Initializer() {
+    registerCxaThrowCallback(
+        [](void*, std::type_info*, void (*)(void*)) { addActiveException(); });
+
+    registerCxaBeginCatchCallback(
+        [](void*) { moveTopException(activeExceptions, caughtExceptions); });
+
+    registerCxaRethrowCallback(
+        []() { moveTopException(caughtExceptions, activeExceptions); });
+
+    registerCxaEndCatchCallback([]() {
+      if (invalid) {
+        return;
+      }
+
+      __cxxabiv1::__cxa_exception* top =
+          __cxxabiv1::__cxa_get_globals_fast()->caughtExceptions;
+      // This is gcc specific and not specified in the ABI:
+      // abs(handlerCount) is the number of active handlers, it's negative
+      // for rethrown exceptions and positive (always 1) for regular
+      // exceptions.
+      // In the rethrow case, we've already popped the exception off the
+      // caught stack, so we don't do anything here.
+      if (top->handlerCount == 1) {
+        if (!caughtExceptions.pop()) {
+          activeExceptions.clear();
+          invalid = true;
+        }
+      }
+    });
+
+    registerRethrowExceptionCallback(
+        [](std::exception_ptr) { addActiveException(); });
+
+    try {
+      ::folly::exception_tracer::installHandlers();
+    } catch (...) {
+    }
+  }
+};
+
+Initializer initializer;
+
+} // namespace
index c65398d32d7c5d9edc8adb5e42a9e5e53309e089..593444d50bd5c049d57b55dd78dc48784a9f4e1e 100644 (file)
@@ -55,12 +55,12 @@ std::ostream& operator<<(std::ostream& out, const ExceptionInfo& info) {
       << ")\n";
   try {
     size_t frameCount = info.frames.size();
-    // Skip our own internal frames
-    static constexpr size_t skip = 3;
 
-    if (frameCount > skip) {
-      auto addresses = info.frames.data() + skip;
-      frameCount -= skip;
+    // Skip our own internal frames
+    static constexpr size_t kInternalFramesNumber = 3;
+    if (frameCount > kInternalFramesNumber) {
+      auto addresses = info.frames.data() + kInternalFramesNumber;
+      frameCount -= kInternalFramesNumber;
 
       std::vector<SymbolizedFrame> frames;
       frames.resize(frameCount);
index 1eb303aa0fc05428245bdf1a762326deceff0fc0..f854ddc054cb3291afbb66d14ee4273f2cc7c4e8 100644 (file)
  * limitations under the License.
  */
 
+#include <folly/experimental/exception_tracer/ExceptionTracerLib.h>
+
 #include <dlfcn.h>
-#include <pthread.h>
-#include <stdlib.h>
 
-#include <glog/logging.h>
+#include <vector>
 
 #include <folly/Portability.h>
-#include <folly/experimental/exception_tracer/StackTrace.h>
-#include <folly/experimental/exception_tracer/ExceptionAbi.h>
-#include <folly/experimental/exception_tracer/ExceptionTracer.h>
-#include <folly/experimental/symbolizer/Symbolizer.h>
+#include <folly/SharedMutex.h>
+#include <folly/Synchronized.h>
 
 namespace __cxxabiv1 {
 
@@ -34,102 +32,70 @@ FOLLY_NORETURN void __cxa_throw(void* thrownException,
                                 void (*destructor)(void*));
 void* __cxa_begin_catch(void* excObj) throw();
 FOLLY_NORETURN void __cxa_rethrow(void);
+void __cxa_rethrow(void);
 void __cxa_end_catch(void);
 }
 
-}  // namespace __cxxabiv1
+} // namespace __cxxabiv1
 
 using namespace folly::exception_tracer;
 
 namespace {
 
-FOLLY_TLS bool invalid;
-FOLLY_TLS StackTraceStack activeExceptions;
-FOLLY_TLS StackTraceStack caughtExceptions;
-pthread_once_t initialized = PTHREAD_ONCE_INIT;
+template <typename Function>
+class CallbackHolder {
+ public:
+  void registerCallback(Function f) {
+    SYNCHRONIZED(callbacks_) { callbacks_.push_back(std::move(f)); }
+  }
 
-extern "C" {
-FOLLY_NORETURN typedef void (*CxaThrowType)(void*,
-                                            std::type_info*,
-                                            void (*)(void*));
-typedef void* (*CxaBeginCatchType)(void*);
-FOLLY_NORETURN typedef void (*CxaRethrowType)(void);
-typedef void (*CxaEndCatchType)(void);
-
-CxaThrowType orig_cxa_throw;
-CxaBeginCatchType orig_cxa_begin_catch;
-CxaRethrowType orig_cxa_rethrow;
-CxaEndCatchType orig_cxa_end_catch;
-}  // extern "C"
-
-FOLLY_NORETURN typedef void (*RethrowExceptionType)(std::exception_ptr);
-RethrowExceptionType orig_rethrow_exception;
-
-void initialize() {
-  orig_cxa_throw = (CxaThrowType)dlsym(RTLD_NEXT, "__cxa_throw");
-  orig_cxa_begin_catch =
-    (CxaBeginCatchType)dlsym(RTLD_NEXT, "__cxa_begin_catch");
-  orig_cxa_rethrow =
-    (CxaRethrowType)dlsym(RTLD_NEXT, "__cxa_rethrow");
-  orig_cxa_end_catch = (CxaEndCatchType)dlsym(RTLD_NEXT, "__cxa_end_catch");
-  // Mangled name for std::rethrow_exception
-  // TODO(tudorb): Dicey, as it relies on the fact that std::exception_ptr
-  // is typedef'ed to a type in namespace __exception_ptr
-  orig_rethrow_exception =
-    (RethrowExceptionType)dlsym(
-        RTLD_NEXT,
-        "_ZSt17rethrow_exceptionNSt15__exception_ptr13exception_ptrE");
-
-  if (!orig_cxa_throw || !orig_cxa_begin_catch || !orig_cxa_rethrow ||
-      !orig_cxa_end_catch || !orig_rethrow_exception) {
-    abort();  // what else can we do?
+  // always inline to enforce kInternalFramesNumber
+  template <typename... Args>
+  FOLLY_ALWAYS_INLINE void invoke(Args... args) {
+    SYNCHRONIZED_CONST(callbacks_) {
+      for (auto& cb : callbacks_) {
+        cb(args...);
+      }
+    }
   }
-}
 
-}  // namespace
+ private:
+  folly::Synchronized<std::vector<Function>> callbacks_;
+};
 
-// This function is exported and may be found via dlsym(RTLD_NEXT, ...)
-extern "C" StackTraceStack* getExceptionStackTraceStack() {
-  return invalid ? nullptr : &caughtExceptions;
-}
+} // namespace
 
-namespace {
+namespace folly {
+namespace exception_tracer {
 
-// Make sure we're counting stack frames correctly, don't inline.
-FOLLY_NOINLINE void addActiveException();
-
-void addActiveException() {
-  pthread_once(&initialized, initialize);
-  // Capture stack trace
-  if (!invalid) {
-    if (!activeExceptions.pushCurrent()) {
-      activeExceptions.clear();
-      caughtExceptions.clear();
-      invalid = true;
-    }
+#define DECLARE_CALLBACK(NAME)                         \
+  CallbackHolder<NAME##Type>& get##NAME##Callbacks() { \
+    static CallbackHolder<NAME##Type> Callbacks;       \
+    return Callbacks;                                  \
+  }                                                    \
+  void register##NAME##Callback(NAME##Type callback) { \
+    get##NAME##Callbacks().registerCallback(callback); \
   }
-}
 
-void moveTopException(StackTraceStack& from, StackTraceStack& to) {
-  if (invalid) {
-    return;
-  }
-  if (!to.moveTopFrom(from)) {
-    from.clear();
-    to.clear();
-    invalid = true;
-  }
-}
+DECLARE_CALLBACK(CxaThrow);
+DECLARE_CALLBACK(CxaBeginCatch);
+DECLARE_CALLBACK(CxaRethrow);
+DECLARE_CALLBACK(CxaEndCatch);
+DECLARE_CALLBACK(RethrowException);
 
-}  // namespace
+} // exception_tracer
+} // folly
 
 namespace __cxxabiv1 {
 
 void __cxa_throw(void* thrownException,
                  std::type_info* type,
                  void (*destructor)(void*)) {
-  addActiveException();
+  static auto orig_cxa_throw =
+      reinterpret_cast<decltype(&__cxa_throw)>(dlsym(RTLD_NEXT, "__cxa_throw"));
+  getCxaThrowCallbacks().invoke(thrownException, type, destructor);
   orig_cxa_throw(thrownException, type, destructor);
+  __builtin_unreachable(); // orig_cxa_throw never returns
 }
 
 void __cxa_rethrow() {
@@ -138,57 +104,44 @@ void __cxa_rethrow() {
   // we'll implement something simpler (and slower): we pop the exception from
   // the caught stack, and push it back onto the active stack; this way, our
   // implementation of __cxa_begin_catch doesn't have to do anything special.
-  moveTopException(caughtExceptions, activeExceptions);
+  static auto orig_cxa_rethrow = reinterpret_cast<decltype(&__cxa_rethrow)>(
+      dlsym(RTLD_NEXT, "__cxa_rethrow"));
+  getCxaRethrowCallbacks().invoke();
   orig_cxa_rethrow();
+  __builtin_unreachable(); // orig_cxa_rethrow never returns
 }
 
-void* __cxa_begin_catch(void *excObj) throw() {
+void* __cxa_begin_catch(voidexcObj) throw() {
   // excObj is a pointer to the unwindHeader in __cxa_exception
-  moveTopException(activeExceptions, caughtExceptions);
+  static auto orig_cxa_begin_catch =
+      reinterpret_cast<decltype(&__cxa_begin_catch)>(
+          dlsym(RTLD_NEXT, "__cxa_begin_catch"));
+  getCxaBeginCatchCallbacks().invoke(excObj);
   return orig_cxa_begin_catch(excObj);
 }
 
 void __cxa_end_catch() {
-  if (!invalid) {
-    __cxa_exception* top = __cxa_get_globals_fast()->caughtExceptions;
-    // This is gcc specific and not specified in the ABI:
-    // abs(handlerCount) is the number of active handlers, it's negative
-    // for rethrown exceptions and positive (always 1) for regular exceptions.
-    // In the rethrow case, we've already popped the exception off the
-    // caught stack, so we don't do anything here.
-    if (top->handlerCount == 1) {
-      if (!caughtExceptions.pop()) {
-        activeExceptions.clear();
-        invalid = true;
-      }
-    }
-  }
+  static auto orig_cxa_end_catch = reinterpret_cast<decltype(&__cxa_end_catch)>(
+      dlsym(RTLD_NEXT, "__cxa_end_catch"));
+  getCxaEndCatchCallbacks().invoke();
   orig_cxa_end_catch();
 }
 
-}  // namespace __cxxabiv1
+} // namespace __cxxabiv1
 
 namespace std {
 
 void rethrow_exception(std::exception_ptr ep) {
-  addActiveException();
+  // Mangled name for std::rethrow_exception
+  // TODO(tudorb): Dicey, as it relies on the fact that std::exception_ptr
+  // is typedef'ed to a type in namespace __exception_ptr
+  static auto orig_rethrow_exception =
+      reinterpret_cast<decltype(&rethrow_exception)>(
+          dlsym(RTLD_NEXT,
+                "_ZSt17rethrow_exceptionNSt15__exception_ptr13exception_ptrE"));
+  getRethrowExceptionCallbacks().invoke(ep);
   orig_rethrow_exception(ep);
+  __builtin_unreachable(); // orig_rethrow_exception never returns
 }
 
-}  // namespace std
-
-
-namespace {
-
-struct Initializer {
-  Initializer() {
-    try {
-      ::folly::exception_tracer::installHandlers();
-    } catch (...) {
-    }
-  }
-};
-
-Initializer initializer;
-
-}  // namespace
+} // namespace std
diff --git a/folly/experimental/exception_tracer/ExceptionTracerLib.h b/folly/experimental/exception_tracer/ExceptionTracerLib.h
new file mode 100644 (file)
index 0000000..72d1631
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2016 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FOLLY_EXPERIMENTAL_EXCEPTION_TRACER_EXCEPTIONTRACERLIB_H_
+#define FOLLY_EXPERIMENTAL_EXCEPTION_TRACER_EXCEPTIONTRACERLIB_H_
+
+#include <typeinfo>
+#include <exception>
+
+namespace folly {
+namespace exception_tracer {
+
+namespace detail {
+/*
+ * Unfortunately due to ambiguous nature of exception specifiers,
+ * standard does not allow them to appear in typedefs or alias-declarations.
+ * We, however, want callbacks to be exception safe.
+ * This dummies are an ugly workaround that problem.
+ */
+void dummyCxaThrow(void*, std::type_info*, void (*)(void*)) noexcept;
+void dummyCxaBeginCatch(void*) noexcept;
+void dummyCxaRethrow(void) noexcept;
+void dummyCxaEndCatch(void) noexcept;
+void dummyRethrowException(std::exception_ptr) noexcept;
+}
+
+using CxaThrowType = decltype(&detail::dummyCxaThrow);
+using CxaBeginCatchType = decltype(&detail::dummyCxaBeginCatch);
+using CxaRethrowType = decltype(&detail::dummyCxaRethrow);
+using CxaEndCatchType = decltype(&detail::dummyCxaEndCatch);
+using RethrowExceptionType = decltype(&detail::dummyRethrowException);
+
+void registerCxaThrowCallback(CxaThrowType callback);
+void registerCxaBeginCatchCallback(CxaBeginCatchType callback);
+void registerCxaRethrowCallback(CxaRethrowType callback);
+void registerCxaEndCatchCallback(CxaEndCatchType callback);
+void registerRethrowExceptionCallback(RethrowExceptionType callback);
+}
+}
+
+#endif /* FOLLY_EXPERIMENTAL_EXCEPTION_TRACER_EXCEPTIONTRACERLIB_H_ */
diff --git a/folly/experimental/exception_tracer/test/ExceptionCounterTest.cpp b/folly/experimental/exception_tracer/test/ExceptionCounterTest.cpp
new file mode 100644 (file)
index 0000000..37d6392
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2016 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdexcept>
+#include <typeinfo>
+#include <thread>
+#include <mutex>
+#include <condition_variable>
+#include <sstream>
+
+#include <gtest/gtest.h>
+
+#include <folly/experimental/exception_tracer/ExceptionCounterLib.h>
+
+struct MyException {};
+
+void bar() { throw std::runtime_error("hello"); }
+
+void foo() { throw MyException(); }
+
+void baz() { foo(); }
+
+using namespace folly::exception_tracer;
+
+template <typename F>
+void throwAndCatch(F f) {
+  try {
+    f();
+  } catch (...) {
+    // ignore
+  }
+}
+
+TEST(ExceptionCounter, oneThread) {
+  throwAndCatch(foo);
+  for (int i = 0; i < 10; ++i) {
+    throwAndCatch(bar);
+  }
+
+  auto stats = getExceptionStatistics();
+  EXPECT_EQ(stats.size(), 2);
+  EXPECT_EQ(stats[0].count, 10);
+  EXPECT_EQ(stats[1].count, 1);
+  EXPECT_EQ(*(stats[0].info.type), typeid(std::runtime_error));
+  EXPECT_EQ(*(stats[1].info.type), typeid(MyException));
+}
+
+TEST(ExceptionCounter, testClearExceptionStatistics) {
+  throwAndCatch(foo);
+  auto stats = getExceptionStatistics();
+  EXPECT_EQ(stats.size(), 1);
+  stats = getExceptionStatistics();
+  EXPECT_EQ(stats.size(), 0);
+}
+
+TEST(ExceptionCounter, testDifferentStacks) {
+  throwAndCatch(foo);
+  throwAndCatch(baz);
+  auto stats = getExceptionStatistics();
+  EXPECT_EQ(stats.size(), 2);
+}
+
+TEST(ExceptionCounter, multyThreads) {
+  constexpr size_t kNumIterations = 10000;
+  constexpr size_t kNumThreads = 10;
+  std::vector<std::thread> threads;
+  threads.resize(kNumThreads);
+
+  std::mutex preparedMutex;
+  std::mutex finishedMutex;
+  std::condition_variable preparedBarrier;
+  std::condition_variable finishedBarrier;
+  int preparedThreads = 0;
+  bool finished = false;
+
+  for (auto& t : threads) {
+    t = std::thread([&]() {
+      for (size_t i = 0; i < kNumIterations; ++i) {
+        throwAndCatch(foo);
+      }
+
+      {
+        std::unique_lock<std::mutex> lock(preparedMutex);
+        ++preparedThreads;
+        preparedBarrier.notify_one();
+      }
+
+      std::unique_lock<std::mutex> lock(finishedMutex);
+      finishedBarrier.wait(lock, [&]() { return finished; });
+    });
+  }
+
+  {
+    std::unique_lock<std::mutex> lock(preparedMutex);
+    preparedBarrier.wait(lock,
+                         [&]() { return preparedThreads == kNumThreads; });
+  }
+
+  auto stats = getExceptionStatistics();
+  EXPECT_EQ(stats.size(), 1);
+  EXPECT_EQ(stats[0].count, kNumIterations * kNumThreads);
+
+  {
+    std::unique_lock<std::mutex> lock(finishedMutex);
+    finished = true;
+    finishedBarrier.notify_all();
+  }
+
+  for (auto& t : threads) {
+    t.join();
+  }
+}