Objective:
- Provide a common framework in LLVM for collecting various usage
metrics
- Characteristics:
- Extensible and configurable by:
- tools in LLVM that want to use it
- vendors in their downstream codebase
- tools users (as allowed by vendor)
Background:
The framework was originally proposed only for LLDB, but there were
quite a few requests to move it to llvm/lib given telemetry
is a common use case in a lot of tools, not just LLDB.
See more details on the design and discussions here on the RFC:
https://discourse.llvm.org/t/rfc-lldb-telemetry-metrics/64588/20?u=oontvoo
---------
Co-authored-by: Alina Sbirlea <alina.g.simion@gmail.com>
Co-authored-by: James Henderson <James.Henderson@sony.com>
Co-authored-by: Pavel Labath <pavel@labath.sk>
243 lines
6.8 KiB
C++
243 lines
6.8 KiB
C++
//===- 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/Support/Casting.h"
|
|
#include "llvm/Support/Error.h"
|
|
#include "gtest/gtest.h"
|
|
#include <optional>
|
|
#include <vector>
|
|
|
|
namespace llvm {
|
|
namespace telemetry {
|
|
// Testing parameters.
|
|
//
|
|
// These are set by each test to force certain outcomes.
|
|
struct TestContext {
|
|
// Controlling whether there is vendor plugin. In "real" implementation, the
|
|
// plugin-registration framework will handle the overrides but for tests, we
|
|
// just use a bool flag to decide which function to call.
|
|
bool HasVendorPlugin = false;
|
|
|
|
// This field contains data emitted by the framework for later
|
|
// verification by the tests.
|
|
std::string Buffer = "";
|
|
|
|
// The expected Uuid generated by the fake tool.
|
|
std::string ExpectedUuid = "";
|
|
};
|
|
|
|
class StringSerializer : public Serializer {
|
|
public:
|
|
const std::string &getString() { return Buffer; }
|
|
|
|
Error init() override {
|
|
if (Started)
|
|
return createStringError("Serializer already in use");
|
|
Started = true;
|
|
Buffer.clear();
|
|
return Error::success();
|
|
}
|
|
|
|
void write(StringRef KeyName, bool Value) override {
|
|
writeHelper(KeyName, Value);
|
|
}
|
|
|
|
void write(StringRef KeyName, StringRef Value) override {
|
|
writeHelper(KeyName, Value);
|
|
}
|
|
|
|
void beginObject(StringRef KeyName) override {
|
|
Children.push_back(std::string("\n"));
|
|
ChildrenNames.push_back(KeyName.str());
|
|
}
|
|
|
|
void endObject() override {
|
|
assert(!Children.empty() && !ChildrenNames.empty());
|
|
std::string ChildBuff = Children.back();
|
|
std::string Name = ChildrenNames.back();
|
|
Children.pop_back();
|
|
ChildrenNames.pop_back();
|
|
writeHelper(Name, ChildBuff);
|
|
}
|
|
|
|
Error finalize() override {
|
|
assert(Children.empty() && ChildrenNames.empty());
|
|
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())
|
|
Buffer.append((Name + ":" + Twine(Value) + "\n").str());
|
|
else
|
|
Children.back().append((Name + ":" + Twine(Value) + "\n").str());
|
|
}
|
|
|
|
void writeUnsigned(StringRef KeyName, unsigned long long Value) override {
|
|
writeHelper(KeyName, Value);
|
|
}
|
|
|
|
void writeSigned(StringRef KeyName, long long Value) override {
|
|
writeHelper(KeyName, Value);
|
|
}
|
|
|
|
bool Started = false;
|
|
std::string Buffer;
|
|
std::vector<std::string> Children;
|
|
std::vector<std::string> ChildrenNames;
|
|
};
|
|
|
|
namespace vendor {
|
|
struct VendorConfig : public Config {
|
|
VendorConfig(bool Enable) : Config(Enable) {}
|
|
std::optional<std::string> makeSessionId() override {
|
|
static int seed = 0;
|
|
return std::to_string(seed++);
|
|
}
|
|
};
|
|
|
|
std::shared_ptr<Config> getTelemetryConfig(const TestContext &Ctxt) {
|
|
return std::make_shared<VendorConfig>(/*EnableTelemetry=*/true);
|
|
}
|
|
|
|
class TestStorageDestination : public Destination {
|
|
public:
|
|
TestStorageDestination(TestContext *Ctxt) : CurrentContext(Ctxt) {}
|
|
|
|
Error receiveEntry(const TelemetryInfo *Entry) override {
|
|
if (Error Err = serializer.init())
|
|
return Err;
|
|
|
|
Entry->serialize(serializer);
|
|
if (Error Err = serializer.finalize())
|
|
return Err;
|
|
|
|
CurrentContext->Buffer.append(serializer.getString());
|
|
return Error::success();
|
|
}
|
|
|
|
StringLiteral name() const override { return "TestDestination"; }
|
|
|
|
private:
|
|
TestContext *CurrentContext;
|
|
StringSerializer serializer;
|
|
};
|
|
|
|
struct StartupInfo : public TelemetryInfo {
|
|
std::string ToolName;
|
|
std::map<std::string, std::string> MetaData;
|
|
|
|
void serialize(Serializer &serializer) const override {
|
|
TelemetryInfo::serialize(serializer);
|
|
serializer.write("ToolName", ToolName);
|
|
serializer.write("MetaData", MetaData);
|
|
}
|
|
};
|
|
|
|
struct ExitInfo : public TelemetryInfo {
|
|
int ExitCode;
|
|
std::string ExitDesc;
|
|
void serialize(Serializer &serializer) const override {
|
|
TelemetryInfo::serialize(serializer);
|
|
serializer.write("ExitCode", ExitCode);
|
|
serializer.write("ExitDesc", ExitDesc);
|
|
}
|
|
};
|
|
|
|
class TestManager : public Manager {
|
|
public:
|
|
static std::unique_ptr<TestManager>
|
|
createInstance(Config *Config, TestContext *CurrentContext) {
|
|
if (!Config->EnableTelemetry)
|
|
return nullptr;
|
|
CurrentContext->ExpectedUuid = *(Config->makeSessionId());
|
|
std::unique_ptr<TestManager> Ret = std::make_unique<TestManager>(
|
|
CurrentContext, CurrentContext->ExpectedUuid);
|
|
|
|
// Add a destination.
|
|
Ret->addDestination(
|
|
std::make_unique<TestStorageDestination>(CurrentContext));
|
|
|
|
return Ret;
|
|
}
|
|
|
|
TestManager(TestContext *Ctxt, std::string Id)
|
|
: CurrentContext(Ctxt), SessionId(Id) {}
|
|
|
|
Error preDispatch(TelemetryInfo *Entry) override {
|
|
Entry->SessionId = SessionId;
|
|
return Error::success();
|
|
}
|
|
|
|
std::string getSessionId() { return SessionId; }
|
|
|
|
private:
|
|
TestContext *CurrentContext;
|
|
const std::string SessionId;
|
|
};
|
|
} // namespace vendor
|
|
|
|
std::shared_ptr<Config> getTelemetryConfig(const TestContext &Ctxt) {
|
|
if (Ctxt.HasVendorPlugin)
|
|
return vendor::getTelemetryConfig(Ctxt);
|
|
|
|
return std::make_shared<Config>(false);
|
|
}
|
|
|
|
TEST(TelemetryTest, TelemetryDisabled) {
|
|
TestContext Context;
|
|
Context.HasVendorPlugin = false;
|
|
|
|
std::shared_ptr<Config> Config = getTelemetryConfig(Context);
|
|
auto Manager = vendor::TestManager::createInstance(Config.get(), &Context);
|
|
EXPECT_EQ(nullptr, Manager);
|
|
}
|
|
|
|
TEST(TelemetryTest, TelemetryEnabled) {
|
|
const std::string ToolName = "TelemetryTestTool";
|
|
|
|
// Preset some params.
|
|
TestContext Context;
|
|
Context.HasVendorPlugin = true;
|
|
Context.Buffer.clear();
|
|
|
|
std::shared_ptr<Config> Config = getTelemetryConfig(Context);
|
|
auto Manager = vendor::TestManager::createInstance(Config.get(), &Context);
|
|
|
|
EXPECT_STREQ(Manager->getSessionId().c_str(), Context.ExpectedUuid.c_str());
|
|
|
|
vendor::StartupInfo S;
|
|
S.ToolName = ToolName;
|
|
S.MetaData["a"] = "A";
|
|
S.MetaData["b"] = "B";
|
|
|
|
Error startupEmitStatus = Manager->dispatch(&S);
|
|
EXPECT_FALSE(startupEmitStatus);
|
|
std::string ExpectedBuffer =
|
|
"SessionId:0\nToolName:TelemetryTestTool\nMetaData:\na:A\nb:B\n\n";
|
|
EXPECT_EQ(ExpectedBuffer, Context.Buffer);
|
|
Context.Buffer.clear();
|
|
|
|
vendor::ExitInfo E;
|
|
E.ExitCode = 0;
|
|
E.ExitDesc = "success";
|
|
Error exitEmitStatus = Manager->dispatch(&E);
|
|
EXPECT_FALSE(exitEmitStatus);
|
|
ExpectedBuffer = "SessionId:0\nExitCode:0\nExitDesc:success\n";
|
|
EXPECT_EQ(ExpectedBuffer, Context.Buffer);
|
|
}
|
|
|
|
} // namespace telemetry
|
|
} // namespace llvm
|