LLVM 中的遙測框架

目標

在 LLVM 中提供一個通用框架,用於收集各種使用情況和效能指標。它位於 llvm/Telemetry/Telemetry.h

特性

  • 可配置和可擴展,透過:

    • 工具:任何想要使用遙測功能的工具都可以擴展和自訂它。

    • 供應商:工具鏈供應商也可以提供該函式庫的自訂實作,它可以覆寫或擴展給定工具的上游實作,以最符合其組織的使用情況和隱私模型。

    • 此類工具的最終使用者也可以配置遙測功能(在其供應商允許的範圍內)。

重要注意事項

  • 在上游 LLVM 中沒有遙測函式庫的具體實作。我們在這裡僅提供抽象 API。任何想要遙測功能的工具都將實作一個。

    這樣做的理由是 LLVM 中的所有工具在它們關心的內容(要檢測哪些/在哪裡/何時的資料)方面都非常不同。因此,擁有單一實作可能不切實際。但是,將來,如果我們看到足夠多的通用模式,我們可以將它們提取到共享位置。這待定 - 歡迎貢獻。

  • 由於隱私和安全原因,上游 LLVM 中的任何遙測實作都不應儲存任何收集到的資料

    • 不同的組織有不同的隱私模型

      • 哪些資料是敏感的,哪些不是?

      • 檢測資料儲存在任何地方(例如本地檔案)是否可以接受?

    • 從 LLVM 開發人員的角度來看,資料所有權和資料收集同意很難處理

      • 例如,遙測功能收集的資料不一定屬於啟用遙測功能的 LLVM 工具的使用者,因此使用者對資料收集的同意沒有意義。另一方面,LLVM 開發人員沒有合理的方法可以向「真正」的所有者請求同意。

高階設計

主要組件

該框架由四個重要的類別組成

  • llvm::telemetry::Manager:負責收集和傳輸遙測資料的類別。這是框架與任何想要啟用遙測功能的工具之間的主要互動點。

  • llvm::telemetry::TelemetryInfo:資料傳遞者

  • llvm::telemetry::Destination:遙測框架將資料傳送到的資料接收器。它的實作對於框架是透明的。由供應商決定要轉發哪些資料以及將它們轉發到哪裡以進行最終儲存。

  • llvm::telemetry::ConfigManager 的組態。

_images/llvm_telemetry_design.png

如何實作和與 API 互動

要在您的工具中使用遙測功能,您需要提供 Manager 類別和 Destination 的具體實作。

  1. 定義自訂的 SerializerManagerDestination,以及可選的 TelemetryInfo 子類別

class JsonSerializer : public Serializer {
public:
  json::Object *getOutputObject() { return Out.get(); }

  Error init() override {
    if (Started)
      return createStringError("Serializer already in use");
    started = true;
    Out = std::make_unique<json::Object>();
    return Error::success();
  }

  // Serialize the given value.
  void write(StringRef KeyName, bool Value) override {
    writeHelper(KeyName, Value);
  }

  void write(StringRef KeyName, int Value) override {
    writeHelper(KeyName, Value);
  }

  void write(StringRef KeyName, long Value) override {
    writeHelper(KeyName, Value);
  }

  void write(StringRef KeyName, long long Value ) override {
    writeHelper(KeyName, Value);
  }

  void write(StringRef KeyName, unsigned int Value) override {
    writeHelper(KeyName, Value);
  }

  void write(StringRef KeyName, unsigned long Value) override {
    writeHelper(KeyName, Value);
  }

  void write(StringRef KeyName, unsigned long long Value) override {
    writeHelper(KeyName, Value);
  }

  void write(StringRef KeyName, StringRef Value) override {
    writeHelper(KeyName, Value);
  }

  void beginObject(StringRef KeyName) override {
    Children.push_back(json::Object());
    ChildrenNames.push_back(KeyName.str());
  }

  void endObject() override {
    assert(!Children.empty() && !ChildrenNames.empty());
    json::Value Val = json::Value(std::move(Children.back()));
    std::string Name = ChildrenNames.back();

    Children.pop_back();
    ChildrenNames.pop_back();
    writeHelper(Name, std::move(Val));
  }

  Error finalize() override {
    if (!Started)
      return createStringError("Serializer not currently in use");
    Started = false;
    return Error::success();
  }

private:
  template <typename T> void writeHelper(StringRef Name, T Value) {
    assert(Started && "serializer not started");
    if (Children.empty())
      Out->try_emplace(Name, Value);
    else
      Children.back().try_emplace(Name, Value);
  }
  bool Started = false;
  std::unique_ptr<json::Object> Out;
  std::vector<json::Object> Children;
  std::vector<std::string> ChildrenNames;
};

class MyManager : public telemery::Manager {
public:
static std::unique_ptr<MyManager> createInstatnce(telemetry::Config *Config) {
  // If Telemetry is not enabled, then just return null;
  if (!Config->EnableTelemetry)
    return nullptr;
  return std::make_unique<MyManager>();
}
MyManager() = default;

Error preDispatch(TelemetryInfo *Entry) override {
  Entry->SessionId = SessionId;
  return Error::success();
}

// You can also define additional instrumentation points.
void logStartup(TelemetryInfo *Entry) {
  // Add some additional data to entry.
  Entry->Msg = "Some message";
  dispatch(Entry);
}

void logAdditionalPoint(TelemetryInfo *Entry) {
  // .... code here
}

private:
  const std::string SessionId;
};

class MyDestination : public telemetry::Destination {
public:
  Error receiveEntry(const TelemetryInfo *Entry) override {
    if (Error Err = Serializer.init())
      return Err;

    Entry->serialize(Serializer);
    if (Error Err = Serializer.finalize())
      return Err;

    json::Object Copied = *Serializer.getOutputObject();
    // Send the `Copied` object to wherever.
    return Error::success();
  }

private:
  JsonSerializer Serializer;
};

// This defines a custom TelemetryInfo that has an additional Msg field.
struct MyTelemetryInfo : public telemetry::TelemetryInfo {
  std::string Msg;

  Error serialize(Serializer &Serializer) const override {
    TelemetryInfo::serialize(serializer);
    Serializer.writeString("MyMsg", Msg);
  }

  // Note: implement getKind() and classof() to support dyn_cast operations.
};
  1. 在您的工具中使用該函式庫。

記錄工具初始化過程

// In tool's initialization code.
auto StartTime = std::chrono::time_point<std::chrono::steady_clock>::now();
telemetry::Config MyConfig = makeConfig(); // Build up the appropriate Config struct here.
auto Manager = MyManager::createInstance(&MyConfig);


// Any other tool's init code can go here.
// ...

// Finally, take a snapshot of the time now so we know how long it took the
// init process to finish.
auto EndTime = std::chrono::time_point<std::chrono::steady_clock>::now();
MyTelemetryInfo Entry;

Entry.Start = StartTime;
Entry.End = EndTime;
Manager->logStartup(&Entry);

類似的程式碼可用於記錄工具的退出。