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);

类似的代码可以用于记录工具的退出。