[llvm] [llvm]Add a simple Telemetry framework (PR #102323)

Pavel Labath via llvm-commits llvm-commits at lists.llvm.org
Wed Sep 25 02:21:47 PDT 2024


================
@@ -0,0 +1,707 @@
+//===- llvm/unittest/Telemetry/TelemetryTest.cpp - Telemetry unittests ---===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Telemetry/Telemetry.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/IR/Constants.h"
+#include "llvm/IR/DataLayout.h"
+#include "llvm/IR/DebugInfoMetadata.h"
+#include "llvm/IR/LLVMContext.h"
+#include "llvm/IR/Module.h"
+#include "llvm/Support/Casting.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/JSON.h"
+#include "llvm/Support/SourceMgr.h"
+#include "llvm/Support/raw_ostream.h"
+#include "gtest/gtest.h"
+#include <chrono>
+#include <ctime>
+#include <vector>
+
+// Testing parameters.
+// These are set by each test to force certain outcomes.
+// Since the tests may run in parallel, each test will have
+// its own TestContext populated.
+struct TestContext {
+  // Controlling whether there should be an Exit error (if so, what the
+  // expected exit message/description should be).
+  bool HasExitError = false;
+  std::string ExitMsg = "";
+
+  // Controlling whether there is a vendor-provided config for
+  // Telemetry.
+  bool HasVendorConfig = false;
+
+  // Controlling whether the data should be sanitized.
+  bool SanitizeData = false;
+
+  // These two fields data emitted by the framework for later
+  // verifications by the tests.
+  std::string Buffer = "";
+  std::vector<llvm::json::Object> EmittedJsons;
+
+  // The expected Uuid generated by the fake tool.
+  std::string ExpectedUuid = "";
+};
+
+// This is set by the test body.
+static thread_local TestContext *CurrentContext = nullptr;
+
+namespace llvm {
+namespace telemetry {
+namespace vendor_code {
+
+// Generate unique (but deterministic "uuid" for testing purposes).
+static std::string nextUuid() {
+  static std::atomic<int> seed = 1111;
+  return std::to_string(seed.fetch_add(1, std::memory_order_acquire));
+}
+
+struct VendorEntryKind {
+  static const KindType VendorCommon = 168; // 0b010101000
+  static const KindType Startup = 169;      // 0b010101001
+  static const KindType Exit = 170;         // 0b010101010
+};
+
+// Describes the exit signal of an event.
+// This is used by TelemetryInfo below.
+struct ExitDescription {
+  int ExitCode;
+  std::string Description;
+};
+
+// Defines a convenient type for timestamp of various events.
+// This is used by the EventStats below.
+using SteadyTimePoint = std::chrono::time_point<std::chrono::steady_clock>;
+
+// Various time (and possibly memory) statistics of an event.
+struct EventStats {
+  // REQUIRED: Start time of an event
+  SteadyTimePoint Start;
+  // OPTIONAL: End time of an event - may be empty if not meaningful.
+  std::optional<SteadyTimePoint> End;
+  // TBD: could add some memory stats here too?
+
+  EventStats() = default;
+  EventStats(SteadyTimePoint Start) : Start(Start) {}
+  EventStats(SteadyTimePoint Start, SteadyTimePoint End)
+      : Start(Start), End(End) {}
+};
+
+// Demonstrates that the TelemetryInfo (data courier) struct can be extended
+// by downstream code to store additional data as needed.
+// It can also define additional data serialization method.
+struct VendorCommonTelemetryInfo : public TelemetryInfo {
+  static bool classof(const TelemetryInfo *T) {
+    if (T == nullptr)
+      return false;
+    // Subclasses of this is also acceptable.
+    return (T->getKind() & VendorEntryKind::VendorCommon) ==
+           VendorEntryKind::VendorCommon;
----------------
labath wrote:

This may be fine (if we can assume that a single place in control of all of the kinds telemetry entries, so that it can play these bit tricks -- and I think that's a safe assumption here), but I want to make sure you're aware that there's a different (slightly less performant, but also more versatile) way to implement this kind of open polymorphism.
```
class Base {
  static char ID;
  virtual bool isA(const void *ClassID) { return ClassID == &ID; }
public:
  static bool classof(const Base *obj) { return obj->isA(&ID); }
};

class Sub1: public Base {
  static char ID;
  virtual bool isA(const void *ClassID) { return ClassID == &ID || Base::isA(ClassID); }
public:
  static bool classof(const Base *obj) { return obj->isA(&ID); }
};

class Sub2: public Sub1 {
  static char ID;
  virtual bool isA(const void *ClassID) { return ClassID == &ID || Sub1::isA(ClassID); }
public:
  static bool classof(const Base *obj) { return obj->isA(&ID); }
};

```

etc. Basically you use a pointer as class identity, `classof` fetches the pointer of the target class and then calls virtual `isA` which compares it to the identifiers of all of the base classes of the actual object (implemented by  chaining `isA` calls).

https://github.com/llvm/llvm-project/pull/102323


More information about the llvm-commits mailing list