[llvm] [llvm]Add a simple Telemetry framework (PR #102323)
Vy Nguyen via llvm-commits
llvm-commits at lists.llvm.org
Mon Dec 16 08:11:17 PST 2024
https://github.com/oontvoo updated https://github.com/llvm/llvm-project/pull/102323
>From dbb8b15edb5e63f37a66dd15e67d46ee1b4f6c1b Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Wed, 7 Aug 2024 11:08:44 -0400
Subject: [PATCH 01/61] [llvm][lib]Propose a simple Telemetry framework.
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
that it should be moved to llvm/lib given telemetry is a common usage to 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
---
llvm/include/llvm/Telemetry/Telemetry.h | 99 +++++++++++++++++++++++++
llvm/lib/CMakeLists.txt | 1 +
llvm/lib/Telemetry/CMakeLists.txt | 6 ++
llvm/lib/Telemetry/Telemetry.cpp | 32 ++++++++
4 files changed, 138 insertions(+)
create mode 100644 llvm/include/llvm/Telemetry/Telemetry.h
create mode 100644 llvm/lib/Telemetry/CMakeLists.txt
create mode 100644 llvm/lib/Telemetry/Telemetry.cpp
diff --git a/llvm/include/llvm/Telemetry/Telemetry.h b/llvm/include/llvm/Telemetry/Telemetry.h
new file mode 100644
index 00000000000000..e34b228b219c10
--- /dev/null
+++ b/llvm/include/llvm/Telemetry/Telemetry.h
@@ -0,0 +1,99 @@
+#ifndef LVM_TELEMETRY_TELEMETRY_H
+#define LVM_TELEMETRY_TELEMETRY_H
+
+#include <chrono>
+#include <ctime>
+#include <memory>
+#include <optional>
+#include <string>
+
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/JSON.h"
+
+namespace llvm {
+namespace telemetry {
+
+using SteadyTimePoint = std::chrono::time_point<std::chrono::steady_clock>;
+
+struct TelemetryConfig {
+ // If true, telemetry will be enabled.
+ bool enable_telemetry;
+
+ // Additional destinations to send the logged entries.
+ // Could be stdout, stderr, or some local paths.
+ // Note: these are destinations are __in addition to__ whatever the default
+ // destination(s) are, as implemented by vendors.
+ std::vector<std::string> additional_destinations;
+};
+
+struct TelemetryEventStats {
+ // REQUIRED: Start time of event
+ SteadyTimePoint m_start;
+ // OPTIONAL: End time of event - may be empty if not meaningful.
+ std::optional<SteadyTimePoint> m_end;
+ // TBD: could add some memory stats here too?
+
+ TelemetryEventStats() = default;
+ TelemetryEventStats(SteadyTimePoint start) : m_start(start) {}
+ TelemetryEventStats(SteadyTimePoint start, SteadyTimePoint end)
+ : m_start(start), m_end(end) {}
+
+ std::string ToString() const;
+};
+
+struct ExitDescription {
+ int exit_code;
+ std::string description;
+
+ std::string ToString() const;
+};
+
+// The base class contains the basic set of data.
+// Downstream implementations can add more fields as needed.
+struct TelemetryInfo {
+ // A "session" corresponds to every time the tool starts.
+ // All entries emitted for the same session will have
+ // the same session_uuid
+ std::string session_uuid;
+
+ TelemetryEventStats stats;
+
+ std::optional<ExitDescription> exit_description;
+
+ // Counting number of entries.
+ // (For each set of entries with the same session_uuid, this value should
+ // be unique for each entry)
+ size_t counter;
+
+ TelemetryInfo() = default;
+ ~TelemetryInfo() = default;
+ virtual std::string ToString() const;
+};
+
+// Where/how to send the telemetry entries.
+class TelemetryDestination {
+public:
+ virtual ~TelemetryDestination() = default;
+ virtual Error EmitEntry(const TelemetryInfo *entry) = 0;
+ virtual std::string name() const = 0;
+};
+
+class Telemeter {
+public:
+ virtual ~Telemeter() = default;
+
+ // Invoked upon tool startup
+ virtual void LogStartup(llvm::StringRef tool_path, TelemetryInfo *entry) = 0;
+
+ // Invoked upon tool exit.
+ virtual void LogExit(llvm::StringRef tool_path, TelemetryInfo *entry) = 0;
+
+ virtual void AddDestination(TelemetryDestination *destination) = 0;
+};
+
+} // namespace telemetry
+} // namespace llvm
+
+#endif // LVM_TELEMETRY_TELEMETRY_H
diff --git a/llvm/lib/CMakeLists.txt b/llvm/lib/CMakeLists.txt
index 638c3bd6f90f53..1d2fb329226484 100644
--- a/llvm/lib/CMakeLists.txt
+++ b/llvm/lib/CMakeLists.txt
@@ -41,6 +41,7 @@ add_subdirectory(ProfileData)
add_subdirectory(Passes)
add_subdirectory(TargetParser)
add_subdirectory(TextAPI)
+add_subdirectory(Telemetry)
add_subdirectory(ToolDrivers)
add_subdirectory(XRay)
if (LLVM_INCLUDE_TESTS)
diff --git a/llvm/lib/Telemetry/CMakeLists.txt b/llvm/lib/Telemetry/CMakeLists.txt
new file mode 100644
index 00000000000000..8208bdadb05e94
--- /dev/null
+++ b/llvm/lib/Telemetry/CMakeLists.txt
@@ -0,0 +1,6 @@
+add_llvm_component_library(LLVMTelemetry
+ Telemetry.cpp
+
+ ADDITIONAL_HEADER_DIRS
+ "${LLVM_MAIN_INCLUDE_DIR}/llvm/Telemetry"
+)
diff --git a/llvm/lib/Telemetry/Telemetry.cpp b/llvm/lib/Telemetry/Telemetry.cpp
new file mode 100644
index 00000000000000..f7100685ee2d2b
--- /dev/null
+++ b/llvm/lib/Telemetry/Telemetry.cpp
@@ -0,0 +1,32 @@
+#include "llvm/Telemetry/Telemetry.h"
+
+namespace llvm {
+namespace telemetry {
+
+std::string TelemetryEventStats::ToString() const {
+ std::string result;
+ llvm::raw_string_ostream os(result);
+ os << "start_timestamp: " << m_start.time_since_epoch().count()
+ << ", end_timestamp: "
+ << (m_end.has_value() ? std::to_string(m_end->time_since_epoch().count())
+ : "<NONE>");
+ return result;
+}
+
+std::string ExitDescription::ToString() const {
+ return "exit_code: " + std::to_string(exit_code) +
+ ", description: " + description + "\n";
+}
+
+std::string TelemetryInfo::ToString() const {
+ return "[TelemetryInfo]\n" + (" session_uuid:" + session_uuid + "\n") +
+ (" stats: " + stats.ToString() + "\n") +
+ (" exit_description: " +
+ (exit_description.has_value() ? exit_description->ToString()
+ : "<NONE>") +
+ "\n") +
+ (" counter: " + std::to_string(counter) + "\n");
+}
+
+} // namespace telemetry
+} // namespace llvm
\ No newline at end of file
>From 6e43f67c9f0412fbf0f4a16f2be68daa40d4b6f4 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Wed, 7 Aug 2024 13:21:32 -0400
Subject: [PATCH 02/61] add header
---
llvm/include/llvm/Telemetry/Telemetry.h | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/llvm/include/llvm/Telemetry/Telemetry.h b/llvm/include/llvm/Telemetry/Telemetry.h
index e34b228b219c10..fab170c61c02ec 100644
--- a/llvm/include/llvm/Telemetry/Telemetry.h
+++ b/llvm/include/llvm/Telemetry/Telemetry.h
@@ -1,3 +1,15 @@
+//===- llvm/Telemetry/Telemetry.h - Telemetry -------------------*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Defines the commom Telemetry framework.
+//
+//===----------------------------------------------------------------------===//
+
#ifndef LVM_TELEMETRY_TELEMETRY_H
#define LVM_TELEMETRY_TELEMETRY_H
>From e25f5fcd79f84086f4fddb7288d353cf0c0858c0 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Wed, 7 Aug 2024 14:25:22 -0400
Subject: [PATCH 03/61] fixed typo
---
llvm/include/llvm/Telemetry/Telemetry.h | 10 +++-------
1 file changed, 3 insertions(+), 7 deletions(-)
diff --git a/llvm/include/llvm/Telemetry/Telemetry.h b/llvm/include/llvm/Telemetry/Telemetry.h
index fab170c61c02ec..dc24f9c3fc3fb8 100644
--- a/llvm/include/llvm/Telemetry/Telemetry.h
+++ b/llvm/include/llvm/Telemetry/Telemetry.h
@@ -5,13 +5,9 @@
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
-//
-// Defines the commom Telemetry framework.
-//
-//===----------------------------------------------------------------------===//
-#ifndef LVM_TELEMETRY_TELEMETRY_H
-#define LVM_TELEMETRY_TELEMETRY_H
+#ifndef LLVM_TELEMETRY_TELEMETRY_H
+#define LLVM_TELEMETRY_TELEMETRY_H
#include <chrono>
#include <ctime>
@@ -108,4 +104,4 @@ class Telemeter {
} // namespace telemetry
} // namespace llvm
-#endif // LVM_TELEMETRY_TELEMETRY_H
+#endif // LLVM_TELEMETRY_TELEMETRY_H
>From 0057bcf63de085a4d41184eac7d4d4fe9ba7f299 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Wed, 28 Aug 2024 22:12:30 -0400
Subject: [PATCH 04/61] add tests and addressed review comments
---
llvm/include/llvm/Telemetry/Telemetry.h | 53 +-
llvm/lib/Telemetry/Telemetry.cpp | 31 +-
llvm/unittests/CMakeLists.txt | 1 +
llvm/unittests/Telemetry/CMakeLists.txt | 9 +
llvm/unittests/Telemetry/TelemetryTest.cpp | 606 +++++++++++++++++++++
5 files changed, 652 insertions(+), 48 deletions(-)
create mode 100644 llvm/unittests/Telemetry/CMakeLists.txt
create mode 100644 llvm/unittests/Telemetry/TelemetryTest.cpp
diff --git a/llvm/include/llvm/Telemetry/Telemetry.h b/llvm/include/llvm/Telemetry/Telemetry.h
index dc24f9c3fc3fb8..935b1feddbed6e 100644
--- a/llvm/include/llvm/Telemetry/Telemetry.h
+++ b/llvm/include/llvm/Telemetry/Telemetry.h
@@ -27,35 +27,37 @@ using SteadyTimePoint = std::chrono::time_point<std::chrono::steady_clock>;
struct TelemetryConfig {
// If true, telemetry will be enabled.
- bool enable_telemetry;
+ bool EnableTelemetry;
// Additional destinations to send the logged entries.
// Could be stdout, stderr, or some local paths.
// Note: these are destinations are __in addition to__ whatever the default
// destination(s) are, as implemented by vendors.
- std::vector<std::string> additional_destinations;
+ std::vector<std::string> AdditionalDestinations;
};
struct TelemetryEventStats {
// REQUIRED: Start time of event
- SteadyTimePoint m_start;
+ SteadyTimePoint Start;
// OPTIONAL: End time of event - may be empty if not meaningful.
- std::optional<SteadyTimePoint> m_end;
+ std::optional<SteadyTimePoint> End;
// TBD: could add some memory stats here too?
TelemetryEventStats() = default;
- TelemetryEventStats(SteadyTimePoint start) : m_start(start) {}
- TelemetryEventStats(SteadyTimePoint start, SteadyTimePoint end)
- : m_start(start), m_end(end) {}
-
- std::string ToString() const;
+ TelemetryEventStats(SteadyTimePoint Start) : Start(Start) {}
+ TelemetryEventStats(SteadyTimePoint Start, SteadyTimePoint End)
+ : Start(Start), End(End) {}
};
struct ExitDescription {
- int exit_code;
- std::string description;
+ int ExitCode;
+ std::string Description;
+};
- std::string ToString() const;
+// For isa, dyn_cast, etc operations on TelemetryInfo.
+typedef unsigned KindType;
+struct EntryKind {
+ static const KindType Base = 0;
};
// The base class contains the basic set of data.
@@ -64,41 +66,46 @@ struct TelemetryInfo {
// A "session" corresponds to every time the tool starts.
// All entries emitted for the same session will have
// the same session_uuid
- std::string session_uuid;
+ std::string SessionUuid;
- TelemetryEventStats stats;
+ TelemetryEventStats Stats;
- std::optional<ExitDescription> exit_description;
+ std::optional<ExitDescription> ExitDesc;
// Counting number of entries.
// (For each set of entries with the same session_uuid, this value should
// be unique for each entry)
- size_t counter;
+ size_t Counter;
TelemetryInfo() = default;
~TelemetryInfo() = default;
- virtual std::string ToString() const;
+
+ virtual json::Object serializeToJson() const;
+
+ // For isa, dyn_cast, etc, operations.
+ virtual KindType getEntryKind() const { return EntryKind::Base; }
+ static bool classof(const TelemetryInfo* T) {
+ return T->getEntryKind() == EntryKind::Base;
+ }
};
// Where/how to send the telemetry entries.
class TelemetryDestination {
public:
virtual ~TelemetryDestination() = default;
- virtual Error EmitEntry(const TelemetryInfo *entry) = 0;
+ virtual Error emitEntry(const TelemetryInfo *Entry) = 0;
virtual std::string name() const = 0;
};
class Telemeter {
public:
- virtual ~Telemeter() = default;
-
// Invoked upon tool startup
- virtual void LogStartup(llvm::StringRef tool_path, TelemetryInfo *entry) = 0;
+ virtual void logStartup(llvm::StringRef ToolPath, TelemetryInfo *Entry) = 0;
// Invoked upon tool exit.
- virtual void LogExit(llvm::StringRef tool_path, TelemetryInfo *entry) = 0;
+ virtual void logExit(llvm::StringRef ToolPath, TelemetryInfo *Entry) = 0;
- virtual void AddDestination(TelemetryDestination *destination) = 0;
+ virtual void addDestination(TelemetryDestination *Destination) = 0;
};
} // namespace telemetry
diff --git a/llvm/lib/Telemetry/Telemetry.cpp b/llvm/lib/Telemetry/Telemetry.cpp
index f7100685ee2d2b..b9605c4a38ecd0 100644
--- a/llvm/lib/Telemetry/Telemetry.cpp
+++ b/llvm/lib/Telemetry/Telemetry.cpp
@@ -3,30 +3,11 @@
namespace llvm {
namespace telemetry {
-std::string TelemetryEventStats::ToString() const {
- std::string result;
- llvm::raw_string_ostream os(result);
- os << "start_timestamp: " << m_start.time_since_epoch().count()
- << ", end_timestamp: "
- << (m_end.has_value() ? std::to_string(m_end->time_since_epoch().count())
- : "<NONE>");
- return result;
-}
-
-std::string ExitDescription::ToString() const {
- return "exit_code: " + std::to_string(exit_code) +
- ", description: " + description + "\n";
-}
-
-std::string TelemetryInfo::ToString() const {
- return "[TelemetryInfo]\n" + (" session_uuid:" + session_uuid + "\n") +
- (" stats: " + stats.ToString() + "\n") +
- (" exit_description: " +
- (exit_description.has_value() ? exit_description->ToString()
- : "<NONE>") +
- "\n") +
- (" counter: " + std::to_string(counter) + "\n");
-}
+llvm::json::Object TelemetryInfo::serializeToJson() const {
+ return json::Object {
+ {"UUID", SessionUuid},
+ };
+};
} // namespace telemetry
-} // namespace llvm
\ No newline at end of file
+} // namespace llvm
diff --git a/llvm/unittests/CMakeLists.txt b/llvm/unittests/CMakeLists.txt
index 911ede701982f6..9d6b3999c43958 100644
--- a/llvm/unittests/CMakeLists.txt
+++ b/llvm/unittests/CMakeLists.txt
@@ -49,6 +49,7 @@ add_subdirectory(Support)
add_subdirectory(TableGen)
add_subdirectory(Target)
add_subdirectory(TargetParser)
+add_subdirectory(Telemetry)
add_subdirectory(Testing)
add_subdirectory(TextAPI)
add_subdirectory(Transforms)
diff --git a/llvm/unittests/Telemetry/CMakeLists.txt b/llvm/unittests/Telemetry/CMakeLists.txt
new file mode 100644
index 00000000000000..a40ae4b2f55607
--- /dev/null
+++ b/llvm/unittests/Telemetry/CMakeLists.txt
@@ -0,0 +1,9 @@
+set(LLVM_LINK_COMPONENTS
+ Telemetry
+ Core
+ Support
+ )
+
+add_llvm_unittest(TelemetryTests
+ TelemetryTest.cpp
+ )
diff --git a/llvm/unittests/Telemetry/TelemetryTest.cpp b/llvm/unittests/Telemetry/TelemetryTest.cpp
new file mode 100644
index 00000000000000..36a4daf55e1018
--- /dev/null
+++ b/llvm/unittests/Telemetry/TelemetryTest.cpp
@@ -0,0 +1,606 @@
+//===- 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 <cstdio>
+#include <vector>
+#include <chrono>
+#include <ctime>
+
+#include <iostream> // TODO: remove this
+
+// Testing parameters.ve
+static thread_local bool HasExitError = false;
+static thread_local std::string ExitMsg = "";
+static thread_local bool HasVendorConfig = false;
+static thread_local bool SanitizeData = false;
+static thread_local std::string Buffer = "";
+static thread_local std::vector<llvm::json::Object> EmittedJsons;
+
+namespace llvm {
+namespace telemetry {
+namespace vendor_code {
+
+// Generate unique (but deterministic "uuid" for testing purposes).
+static std::string nextUuid() {
+ static size_t seed = 1111;
+ return std::to_string(seed++);
+}
+
+struct VendorEntryKind {
+ // TODO: should avoid dup with other vendors' Types?
+ static const KindType VendorCommon = 0b010101000;
+ static const KindType Startup = 0b010101001;
+ static const KindType Exit = 0b010101010;
+ };
+
+
+// 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) {
+ // Subclasses of this is also acceptable.
+ return (T->getEntryKind() & VendorEntryKind::VendorCommon) == VendorEntryKind::VendorCommon;
+
+ }
+
+ KindType getEntryKind() const override { return VendorEntryKind::VendorCommon;}
+
+ virtual void serializeToStream(llvm::raw_ostream &OS) const = 0;
+};
+
+
+struct StartupEvent : public VendorCommonTelemetryInfo {
+ std::string MagicStartupMsg;
+
+ StartupEvent() = default;
+ StartupEvent(const StartupEvent &E) {
+ SessionUuid = E.SessionUuid;
+ Stats = E.Stats;
+ ExitDesc = E.ExitDesc;
+ Counter = E.Counter;
+
+ MagicStartupMsg = E.MagicStartupMsg;
+ }
+
+ static bool classof(const TelemetryInfo* T) {
+ return T->getEntryKind() == VendorEntryKind::Startup;
+ }
+
+ KindType getEntryKind() const override { return VendorEntryKind::Startup;}
+
+ void serializeToStream(llvm::raw_ostream &OS) const override {
+ OS<< "UUID:" << SessionUuid << "\n";
+ OS << "MagicStartupMsg:" << MagicStartupMsg << "\n";
+ }
+
+ json::Object serializeToJson() const override {
+ return json::Object{
+ {"Startup", { {"UUID", SessionUuid},
+ {"MagicStartupMsg", MagicStartupMsg}}},
+ };
+ }
+};
+
+struct ExitEvent : public VendorCommonTelemetryInfo {
+ std::string MagicExitMsg;
+
+ ExitEvent() = default;
+ // Provide a copy ctor because we may need to make a copy
+ // before sanitizing the Entry.
+ ExitEvent(const ExitEvent &E) {
+ SessionUuid = E.SessionUuid;
+ Stats = E.Stats;
+ ExitDesc = E.ExitDesc;
+ Counter = E.Counter;
+
+ MagicExitMsg = E.MagicExitMsg;
+ }
+
+ static bool classof(const TelemetryInfo* T) {
+ return T->getEntryKind() == VendorEntryKind::Exit;
+ }
+
+ unsigned getEntryKind() const override { return VendorEntryKind::Exit;}
+
+ void serializeToStream(llvm::raw_ostream &OS) const override {
+ OS << "UUID:" << SessionUuid << "\n";
+ if (ExitDesc.has_value())
+ OS << "ExitCode:" << ExitDesc->ExitCode << "\n";
+ OS << "MagicExitMsg:" << MagicExitMsg << "\n";
+ }
+
+ json::Object serializeToJson() const override {
+ json::Array I = json::Array{
+ {"UUID", SessionUuid},
+ {"MagicExitMsg", MagicExitMsg},
+ };
+ if (ExitDesc.has_value())
+ I.push_back(json::Value({"ExitCode", ExitDesc->ExitCode}));
+ return json::Object {
+ {"Exit", std::move(I)},
+ };
+ }
+};
+
+struct CustomTelemetryEvent : public VendorCommonTelemetryInfo {
+ std::vector<std::string> Msgs;
+
+ CustomTelemetryEvent() = default;
+ CustomTelemetryEvent(const CustomTelemetryEvent &E) {
+ SessionUuid = E.SessionUuid;
+ Stats = E.Stats;
+ ExitDesc = E.ExitDesc;
+ Counter = E.Counter;
+
+ Msgs = E.Msgs;
+ }
+
+ void serializeToStream(llvm::raw_ostream &OS) const override {
+ int I = 0;
+ for (const std::string &M : Msgs) {
+ OS << "MSG_" << I << ":" << M << "\n";
+ ++I;
+ }
+ }
+
+ json::Object serializeToJson() const override {
+ json::Object Inner;
+ int I = 0;
+ for (const std::string &M : Msgs) {
+ Inner.try_emplace(("MSG_" + llvm::Twine(I)).str(), M);
+ ++I;
+ }
+ return json::Object {
+ {"Midpoint", std::move(Inner)}}
+ ;
+ }
+};
+
+
+// The following classes demonstrate how downstream code can
+// define one or more custom TelemetryDestination(s) to handle
+// Telemetry data differently, specifically:
+// + which data to send (fullset or sanitized)
+// + where to send the data
+// + in what form
+
+const std::string STRING_DEST( "STRING");
+const std::string JSON_DEST ("JSON");
+
+// This Destination sends data to a std::string given at ctor.
+class StringDestination : public TelemetryDestination {
+public:
+ // ShouldSanitize: if true, sanitize the data before emitting, otherwise, emit
+ // the full set.
+ StringDestination(bool ShouldSanitize, std::string& Buf)
+ : ShouldSanitize(ShouldSanitize), OS(Buf) {
+ }
+
+ Error emitEntry(const TelemetryInfo *Entry) override {
+ if (isa<VendorCommonTelemetryInfo>(Entry)) {
+ if (auto *E = dyn_cast<VendorCommonTelemetryInfo>(Entry)) {
+ if (ShouldSanitize) {
+ if (isa<StartupEvent>(E) || isa<ExitEvent>(E)) {
+ // There is nothing to sanitize for this type of data, so keep as-is.
+ E->serializeToStream(OS);
+ } else if (isa<CustomTelemetryEvent>(E)) {
+ auto Sanitized = sanitizeFields(dyn_cast<CustomTelemetryEvent>(E));
+ Sanitized.serializeToStream(OS);
+ } else {
+ llvm_unreachable("unexpected type");
+ }
+ } else {
+ E->serializeToStream(OS);
+ }
+ }
+ } else {
+ // Unfamiliar entries, just send the entry's UUID
+ OS << "UUID:" << Entry->SessionUuid << "\n";
+ }
+ return Error::success();
+ }
+
+ std::string name() const override { return STRING_DEST;}
+
+private:
+ // Returns a copy of the given entry, but with some fields sanitized.
+ CustomTelemetryEvent sanitizeFields(const CustomTelemetryEvent* Entry) {
+ CustomTelemetryEvent Sanitized(*Entry);
+ // Pretend that messages stored at ODD positions are "sensitive",
+ // hence need to be sanitized away.
+ int S = Sanitized.Msgs.size() - 1;
+ for (int I = S % 2 == 0 ? S - 1 : S; I >= 0; I -= 2)
+ Sanitized.Msgs[I]="";
+ return Sanitized;
+ }
+
+ bool ShouldSanitize;
+ llvm::raw_string_ostream OS;
+
+};
+
+// This Destination sends data to some "blackbox" in form of JSON.
+class JsonStreamDestination : public TelemetryDestination {
+public:
+ JsonStreamDestination(bool ShouldSanitize)
+ : ShouldSanitize(ShouldSanitize) {}
+
+ Error emitEntry(const TelemetryInfo *Entry) override {
+ if (auto *E = dyn_cast<VendorCommonTelemetryInfo>(Entry)) {
+ if (ShouldSanitize) {
+ if (isa<StartupEvent>(E) || isa<ExitEvent>(E)) {
+ // There is nothing to sanitize for this type of data, so keep as-is.
+ return SendToBlackbox(E->serializeToJson());
+ } else if (isa<CustomTelemetryEvent>(E)) {
+ auto Sanitized = sanitizeFields(dyn_cast<CustomTelemetryEvent>(E));
+ return SendToBlackbox(Sanitized.serializeToJson());
+ } else {
+ llvm_unreachable("unexpected type");
+ }
+ } else {
+ return SendToBlackbox(E->serializeToJson());
+ }
+ } else {
+ // Unfamiliar entries, just send the entry's UUID
+ return SendToBlackbox(json::Object{{"UUID", Entry->SessionUuid}});
+ }
+ return make_error<StringError>("unhandled codepath in emitEntry",
+ inconvertibleErrorCode());
+ }
+
+ std::string name() const override { return JSON_DEST;}
+private:
+
+ // Returns a copy of the given entry, but with some fields sanitized.
+ CustomTelemetryEvent sanitizeFields(const CustomTelemetryEvent* Entry) {
+ CustomTelemetryEvent Sanitized(*Entry);
+ // Pretend that messages stored at EVEN positions are "sensitive",
+ // hence need to be sanitized away.
+ int S = Sanitized.Msgs.size() - 1;
+ for (int I = S % 2 == 0 ? S : S - 1; I >= 0; I -= 2)
+ Sanitized.Msgs[I]="";
+
+ return Sanitized;
+ }
+
+ llvm::Error SendToBlackbox(json::Object O) {
+ // Here is where the vendor-defined Destination class can
+ // send the data to some internal storage.
+ // For testing purposes, we just queue up the entries to
+ // the vector for validation.
+ EmittedJsons.push_back(std::move(O));
+ return Error::success();
+ }
+ bool ShouldSanitize;
+};
+
+// Custom vendor-defined Telemeter that has additional data-collection point.
+class TestTelemeter : public Telemeter {
+public:
+ TestTelemeter(std::string SessionUuid) : Uuid(SessionUuid), Counter(0) {}
+
+ static std::unique_ptr<TestTelemeter> createInstance(TelemetryConfig *config) {
+ if (!config->EnableTelemetry) return std::unique_ptr<TestTelemeter>(nullptr);
+ std::unique_ptr<TestTelemeter> Telemeter = std::make_unique<TestTelemeter>(nextUuid());
+ // Set up Destination based on the given config.
+ for (const std::string &Dest : config->AdditionalDestinations) {
+ // The destination(s) are ALSO defined by vendor, so it should understand
+ // what the name of each destination signifies.
+ if (Dest == JSON_DEST) {
+ Telemeter->addDestination(new vendor_code::JsonStreamDestination(SanitizeData));
+ } else if (Dest == STRING_DEST) {
+ Telemeter->addDestination(new vendor_code::StringDestination(SanitizeData, Buffer));
+ } else {
+ llvm_unreachable(llvm::Twine("unknown destination: ", Dest).str().c_str());
+ }
+ }
+ return Telemeter;
+ }
+
+ void logStartup(llvm::StringRef ToolPath, TelemetryInfo *Entry) override {
+ ToolName = ToolPath.str();
+
+ // The vendor can add additional stuff to the entry before logging.
+ if (auto* S = dyn_cast<StartupEvent>(Entry)) {
+ S->MagicStartupMsg = llvm::Twine("One_", ToolPath).str();
+ }
+ emitToDestinations(Entry);
+ }
+
+ void logExit(llvm::StringRef ToolPath, TelemetryInfo *Entry) override {
+ // Ensure we're shutting down the same tool we started with.
+ if (ToolPath != ToolName){
+ std::string Str;
+ raw_string_ostream OS(Str);
+ OS << "Expected tool with name" << ToolName << ", but got " << ToolPath;
+ llvm_unreachable(Str.c_str());
+ }
+
+ // The vendor can add additional stuff to the entry before logging.
+ if (auto * E = dyn_cast<ExitEvent>(Entry)) {
+ E->MagicExitMsg = llvm::Twine("Three_", ToolPath).str();
+ }
+
+ emitToDestinations(Entry);
+ }
+
+ void addDestination(TelemetryDestination* Dest) override {
+ Destinations.push_back(Dest);
+ }
+
+ void logMidpoint(TelemetryInfo *Entry) {
+ // The custom Telemeter can record and send additional data.
+ if (auto * C = dyn_cast<CustomTelemetryEvent>(Entry)) {
+ C->Msgs.push_back("Two");
+ C->Msgs.push_back("Deux");
+ C->Msgs.push_back("Zwei");
+ }
+
+ emitToDestinations(Entry);
+ }
+
+ ~TestTelemeter() {
+ for (auto* Dest : Destinations)
+ delete Dest;
+ }
+
+ template <typename T>
+ T makeDefaultTelemetryInfo() {
+ T Ret;
+ Ret.SessionUuid = Uuid;
+ Ret.Counter = Counter++;
+ return Ret;
+ }
+private:
+
+ void emitToDestinations(TelemetryInfo *Entry) {
+ for (TelemetryDestination *Dest : Destinations) {
+ llvm::Error err = Dest->emitEntry(Entry);
+ if(err) {
+ // Log it and move on.
+ }
+ }
+ }
+
+ const std::string Uuid;
+ size_t Counter;
+ std::string ToolName;
+ std::vector<TelemetryDestination *> Destinations;
+};
+
+// Pretend to be a "weakly" defined vendor-specific function.
+void ApplyVendorSpecificConfigs(TelemetryConfig *config) {
+ config->EnableTelemetry = true;
+}
+
+} // namespace vendor_code
+} // namespace telemetry
+} // namespace llvm
+
+namespace {
+
+void ApplyCommonConfig(llvm::telemetry::TelemetryConfig* config) {
+ // Any shareable configs for the upstream tool can go here.
+ // .....
+}
+
+std::shared_ptr<llvm::telemetry::TelemetryConfig> GetTelemetryConfig() {
+ // Telemetry is disabled by default.
+ // The vendor can enable in their config.
+ auto Config = std::make_shared<llvm::telemetry::TelemetryConfig>();
+ Config->EnableTelemetry = false;
+
+ ApplyCommonConfig(Config.get());
+
+ // Apply vendor specific config, if present.
+ // In practice, this would be a build-time param.
+ // Eg:
+ //
+ // #ifdef HAS_VENDOR_TELEMETRY_CONFIG
+ // llvm::telemetry::vendor_code::ApplyVendorSpecificConfigs(config.get());
+ // #endif
+ // But for unit testing, we use the testing params defined at the top.
+ if (HasVendorConfig) {
+ llvm::telemetry::vendor_code::ApplyVendorSpecificConfigs(Config.get());
+ }
+ return Config;
+}
+
+using namespace llvm;
+using namespace llvm::telemetry;
+
+// For deterministic tests, pre-defined certain important time-points
+// rather than using now().
+//
+// Preset StartTime to EPOCH.
+auto StartTime = std::chrono::time_point<std::chrono::steady_clock>{};
+// Pretend the time it takes for the tool's initialization is EPOCH + 5 milliseconds
+auto InitCompleteTime = StartTime + std::chrono::milliseconds(5);
+auto MidPointTime = StartTime + std::chrono::milliseconds(10);
+auto MidPointCompleteTime = MidPointTime + std::chrono::milliseconds(5);
+// Preset ExitTime to EPOCH + 20 milliseconds
+auto ExitTime = StartTime + std::chrono::milliseconds(20);
+// Pretend the time it takes to complete tearing down the tool is 10 milliseconds.
+auto ExitCompleteTime = ExitTime + std::chrono::milliseconds(10);
+
+void AtToolStart(std::string ToolName, vendor_code::TestTelemeter* T) {
+ vendor_code::StartupEvent Entry = T->makeDefaultTelemetryInfo<vendor_code::StartupEvent>();
+ Entry.Stats = {StartTime, InitCompleteTime};
+ T->logStartup(ToolName, &Entry);
+}
+
+void AtToolExit(std::string ToolName, vendor_code::TestTelemeter* T) {
+ vendor_code::ExitEvent Entry = T->makeDefaultTelemetryInfo<vendor_code::ExitEvent>();
+ Entry.Stats = {ExitTime, ExitCompleteTime};
+
+ if (HasExitError) {
+ Entry.ExitDesc = {1, ExitMsg};
+ }
+ T->logExit(ToolName, &Entry);
+}
+
+void AtToolMidPoint (vendor_code::TestTelemeter* T) {
+ vendor_code::CustomTelemetryEvent Entry = T->makeDefaultTelemetryInfo<vendor_code::CustomTelemetryEvent>();
+ Entry.Stats = {MidPointTime, MidPointCompleteTime};
+ T->logMidpoint(&Entry);
+}
+
+// Helper function to print the given object content to string.
+static std::string ValueToString(const json::Value* V) {
+ std::string Ret;
+ llvm::raw_string_ostream P(Ret);
+ P << *V;
+ return Ret;
+}
+
+// Without vendor's implementation, telemetry is not enabled by default.
+TEST(TelemetryTest, TelemetryDefault) {
+ HasVendorConfig = false;
+ std::shared_ptr<llvm::telemetry::TelemetryConfig> Config = GetTelemetryConfig();
+ auto Tool = vendor_code::TestTelemeter::createInstance(Config.get());
+
+ EXPECT_EQ(nullptr, Tool.get());
+}
+
+TEST(TelemetryTest, TelemetryEnabled) {
+ const std::string ToolName = "TestToolOne";
+
+ // Preset some test params.
+ HasVendorConfig = true;
+ SanitizeData = false;
+ Buffer = "";
+ EmittedJsons.clear();
+
+ std::shared_ptr<llvm::telemetry::TelemetryConfig> Config = GetTelemetryConfig();
+
+ // Add some destinations
+ Config->AdditionalDestinations.push_back(vendor_code::STRING_DEST);
+ Config->AdditionalDestinations.push_back(vendor_code::JSON_DEST);
+
+ auto Tool = vendor_code::TestTelemeter::createInstance(Config.get());
+
+ AtToolStart(ToolName, Tool.get());
+ AtToolMidPoint(Tool.get());
+ AtToolExit(ToolName, Tool.get());
+
+
+ // Check that the StringDestination emitted properly
+ {
+ std::string ExpectedBuff = "UUID:1111\n"
+ "MagicStartupMsg:One_TestToolOne\n"
+ "MSG_0:Two\n"
+ "MSG_1:Deux\n"
+ "MSG_2:Zwei\n"
+ "UUID:1111\n"
+ "MagicExitMsg:Three_TestToolOne\n";
+
+ EXPECT_STREQ(ExpectedBuff.c_str(), Buffer.c_str());
+ }
+
+ // Check that the JsonDestination emitted properly
+ {
+
+ // There should be 3 events emitted by the Telemeter (start, midpoint, exit)
+ EXPECT_EQ(3, EmittedJsons.size());
+
+ const json::Value* StartupEntry = EmittedJsons[0].get("Startup");
+ ASSERT_NE(StartupEntry, nullptr);
+ EXPECT_STREQ("[[\"UUID\",\"1111\"],[\"MagicStartupMsg\",\"One_TestToolOne\"]]",
+ ValueToString(StartupEntry).c_str());
+
+ const json::Value* MidpointEntry = EmittedJsons[1].get("Midpoint");
+ ASSERT_NE(MidpointEntry, nullptr);
+ EXPECT_STREQ("{\"MSG_0\":\"Two\",\"MSG_1\":\"Deux\",\"MSG_2\":\"Zwei\"}",
+ ValueToString(MidpointEntry).c_str());
+
+ const json::Value* ExitEntry = EmittedJsons[2].get("Exit");
+ ASSERT_NE(ExitEntry, nullptr);
+ EXPECT_STREQ("[[\"UUID\",\"1111\"],[\"MagicExitMsg\",\"Three_TestToolOne\"]]",
+ ValueToString(ExitEntry).c_str());
+ }
+}
+
+// Similar to previous tests, but toggling the data-sanitization option ON.
+// The recorded data should have some fields removed.
+TEST(TelemetryTest, TelemetryEnabledSanitizeData) {
+ const std::string ToolName = "TestToolOne";
+
+ // Preset some test params.
+ HasVendorConfig = true;
+ SanitizeData = true;
+ Buffer = "";
+ EmittedJsons.clear();
+
+ std::shared_ptr<llvm::telemetry::TelemetryConfig> Config = GetTelemetryConfig();
+
+ // Add some destinations
+ Config->AdditionalDestinations.push_back(vendor_code::STRING_DEST);
+ Config->AdditionalDestinations.push_back(vendor_code::JSON_DEST);
+
+ auto Tool = vendor_code::TestTelemeter::createInstance(Config.get());
+
+ AtToolStart(ToolName, Tool.get());
+ AtToolMidPoint(Tool.get());
+ AtToolExit(ToolName, Tool.get());
+
+
+ // Check that the StringDestination emitted properly
+ {
+ // The StringDestination should have removed the odd-positioned msgs.
+ std::string ExpectedBuff = "UUID:1111\n"
+ "MagicStartupMsg:One_TestToolOne\n"
+ "MSG_0:Two\n"
+ "MSG_1:\n" // was sannitized away.
+ "MSG_2:Zwei\n"
+ "UUID:1111\n"
+ "MagicExitMsg:Three_TestToolOne\n";
+
+ EXPECT_STREQ(ExpectedBuff.c_str(), Buffer.c_str());
+ }
+
+ // Check that the JsonDestination emitted properly
+ {
+
+ // There should be 3 events emitted by the Telemeter (start, midpoint, exit)
+ EXPECT_EQ(3, EmittedJsons.size());
+
+ const json::Value* StartupEntry = EmittedJsons[0].get("Startup");
+ ASSERT_NE(StartupEntry, nullptr);
+ EXPECT_STREQ("[[\"UUID\",\"1111\"],[\"MagicStartupMsg\",\"One_TestToolOne\"]]",
+ ValueToString(StartupEntry).c_str());
+
+ const json::Value* MidpointEntry = EmittedJsons[1].get("Midpoint");
+ ASSERT_NE(MidpointEntry, nullptr);
+ // The JsonDestination should have removed the even-positioned msgs.
+ EXPECT_STREQ("{\"MSG_0\":\"\",\"MSG_1\":\"Deux\",\"MSG_2\":\"\"}",
+ ValueToString(MidpointEntry).c_str());
+
+ const json::Value* ExitEntry = EmittedJsons[2].get("Exit");
+ ASSERT_NE(ExitEntry, nullptr);
+ EXPECT_STREQ("[[\"UUID\",\"1111\"],[\"MagicExitMsg\",\"Three_TestToolOne\"]]",
+ ValueToString(ExitEntry).c_str());
+ }
+}
+
+} // namespace
>From 750e4ac6af7bda76fb730b44b16b86279df42e15 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Wed, 28 Aug 2024 22:24:43 -0400
Subject: [PATCH 05/61] formatting changes
---
llvm/include/llvm/Telemetry/Telemetry.h | 2 +-
llvm/lib/Telemetry/Telemetry.cpp | 4 +-
llvm/unittests/Telemetry/TelemetryTest.cpp | 236 +++++++++++----------
3 files changed, 125 insertions(+), 117 deletions(-)
diff --git a/llvm/include/llvm/Telemetry/Telemetry.h b/llvm/include/llvm/Telemetry/Telemetry.h
index 935b1feddbed6e..7084b81c0304ae 100644
--- a/llvm/include/llvm/Telemetry/Telemetry.h
+++ b/llvm/include/llvm/Telemetry/Telemetry.h
@@ -84,7 +84,7 @@ struct TelemetryInfo {
// For isa, dyn_cast, etc, operations.
virtual KindType getEntryKind() const { return EntryKind::Base; }
- static bool classof(const TelemetryInfo* T) {
+ static bool classof(const TelemetryInfo *T) {
return T->getEntryKind() == EntryKind::Base;
}
};
diff --git a/llvm/lib/Telemetry/Telemetry.cpp b/llvm/lib/Telemetry/Telemetry.cpp
index b9605c4a38ecd0..f49b88e07f1361 100644
--- a/llvm/lib/Telemetry/Telemetry.cpp
+++ b/llvm/lib/Telemetry/Telemetry.cpp
@@ -4,8 +4,8 @@ namespace llvm {
namespace telemetry {
llvm::json::Object TelemetryInfo::serializeToJson() const {
- return json::Object {
- {"UUID", SessionUuid},
+ return json::Object{
+ {"UUID", SessionUuid},
};
};
diff --git a/llvm/unittests/Telemetry/TelemetryTest.cpp b/llvm/unittests/Telemetry/TelemetryTest.cpp
index 36a4daf55e1018..b89bc584717da5 100644
--- a/llvm/unittests/Telemetry/TelemetryTest.cpp
+++ b/llvm/unittests/Telemetry/TelemetryTest.cpp
@@ -20,12 +20,10 @@
#include "llvm/Support/raw_ostream.h"
#include "gtest/gtest.h"
-#include <cstdio>
-#include <vector>
#include <chrono>
#include <ctime>
+#include <vector>
-#include <iostream> // TODO: remove this
// Testing parameters.ve
static thread_local bool HasExitError = false;
@@ -46,30 +44,30 @@ static std::string nextUuid() {
}
struct VendorEntryKind {
- // TODO: should avoid dup with other vendors' Types?
+ // TODO: should avoid dup with other vendors' Types?
static const KindType VendorCommon = 0b010101000;
- static const KindType Startup = 0b010101001;
- static const KindType Exit = 0b010101010;
- };
-
+ static const KindType Startup = 0b010101001;
+ static const KindType Exit = 0b010101010;
+};
// 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) {
+ static bool classof(const TelemetryInfo *T) {
// Subclasses of this is also acceptable.
- return (T->getEntryKind() & VendorEntryKind::VendorCommon) == VendorEntryKind::VendorCommon;
-
+ return (T->getEntryKind() & VendorEntryKind::VendorCommon) ==
+ VendorEntryKind::VendorCommon;
}
- KindType getEntryKind() const override { return VendorEntryKind::VendorCommon;}
+ KindType getEntryKind() const override {
+ return VendorEntryKind::VendorCommon;
+ }
virtual void serializeToStream(llvm::raw_ostream &OS) const = 0;
};
-
struct StartupEvent : public VendorCommonTelemetryInfo {
std::string MagicStartupMsg;
@@ -83,21 +81,21 @@ struct StartupEvent : public VendorCommonTelemetryInfo {
MagicStartupMsg = E.MagicStartupMsg;
}
- static bool classof(const TelemetryInfo* T) {
+ static bool classof(const TelemetryInfo *T) {
return T->getEntryKind() == VendorEntryKind::Startup;
}
- KindType getEntryKind() const override { return VendorEntryKind::Startup;}
+ KindType getEntryKind() const override { return VendorEntryKind::Startup; }
void serializeToStream(llvm::raw_ostream &OS) const override {
- OS<< "UUID:" << SessionUuid << "\n";
+ OS << "UUID:" << SessionUuid << "\n";
OS << "MagicStartupMsg:" << MagicStartupMsg << "\n";
}
json::Object serializeToJson() const override {
return json::Object{
- {"Startup", { {"UUID", SessionUuid},
- {"MagicStartupMsg", MagicStartupMsg}}},
+ {"Startup",
+ {{"UUID", SessionUuid}, {"MagicStartupMsg", MagicStartupMsg}}},
};
}
};
@@ -117,11 +115,11 @@ struct ExitEvent : public VendorCommonTelemetryInfo {
MagicExitMsg = E.MagicExitMsg;
}
- static bool classof(const TelemetryInfo* T) {
+ static bool classof(const TelemetryInfo *T) {
return T->getEntryKind() == VendorEntryKind::Exit;
}
- unsigned getEntryKind() const override { return VendorEntryKind::Exit;}
+ unsigned getEntryKind() const override { return VendorEntryKind::Exit; }
void serializeToStream(llvm::raw_ostream &OS) const override {
OS << "UUID:" << SessionUuid << "\n";
@@ -131,14 +129,14 @@ struct ExitEvent : public VendorCommonTelemetryInfo {
}
json::Object serializeToJson() const override {
- json::Array I = json::Array{
+ json::Array I = json::Array{
{"UUID", SessionUuid},
{"MagicExitMsg", MagicExitMsg},
};
if (ExitDesc.has_value())
I.push_back(json::Value({"ExitCode", ExitDesc->ExitCode}));
- return json::Object {
- {"Exit", std::move(I)},
+ return json::Object{
+ {"Exit", std::move(I)},
};
}
};
@@ -164,20 +162,17 @@ struct CustomTelemetryEvent : public VendorCommonTelemetryInfo {
}
}
- json::Object serializeToJson() const override {
+ json::Object serializeToJson() const override {
json::Object Inner;
int I = 0;
for (const std::string &M : Msgs) {
Inner.try_emplace(("MSG_" + llvm::Twine(I)).str(), M);
++I;
}
- return json::Object {
- {"Midpoint", std::move(Inner)}}
- ;
+ return json::Object{{"Midpoint", std::move(Inner)}};
}
};
-
// The following classes demonstrate how downstream code can
// define one or more custom TelemetryDestination(s) to handle
// Telemetry data differently, specifically:
@@ -185,24 +180,24 @@ struct CustomTelemetryEvent : public VendorCommonTelemetryInfo {
// + where to send the data
// + in what form
-const std::string STRING_DEST( "STRING");
-const std::string JSON_DEST ("JSON");
+const std::string STRING_DEST("STRING");
+const std::string JSON_DEST("JSON");
// This Destination sends data to a std::string given at ctor.
class StringDestination : public TelemetryDestination {
public:
// ShouldSanitize: if true, sanitize the data before emitting, otherwise, emit
// the full set.
- StringDestination(bool ShouldSanitize, std::string& Buf)
- : ShouldSanitize(ShouldSanitize), OS(Buf) {
- }
+ StringDestination(bool ShouldSanitize, std::string &Buf)
+ : ShouldSanitize(ShouldSanitize), OS(Buf) {}
Error emitEntry(const TelemetryInfo *Entry) override {
if (isa<VendorCommonTelemetryInfo>(Entry)) {
if (auto *E = dyn_cast<VendorCommonTelemetryInfo>(Entry)) {
if (ShouldSanitize) {
if (isa<StartupEvent>(E) || isa<ExitEvent>(E)) {
- // There is nothing to sanitize for this type of data, so keep as-is.
+ // There is nothing to sanitize for this type of data, so keep
+ // as-is.
E->serializeToStream(OS);
} else if (isa<CustomTelemetryEvent>(E)) {
auto Sanitized = sanitizeFields(dyn_cast<CustomTelemetryEvent>(E));
@@ -221,65 +216,63 @@ class StringDestination : public TelemetryDestination {
return Error::success();
}
- std::string name() const override { return STRING_DEST;}
+ std::string name() const override { return STRING_DEST; }
private:
// Returns a copy of the given entry, but with some fields sanitized.
- CustomTelemetryEvent sanitizeFields(const CustomTelemetryEvent* Entry) {
+ CustomTelemetryEvent sanitizeFields(const CustomTelemetryEvent *Entry) {
CustomTelemetryEvent Sanitized(*Entry);
// Pretend that messages stored at ODD positions are "sensitive",
// hence need to be sanitized away.
int S = Sanitized.Msgs.size() - 1;
for (int I = S % 2 == 0 ? S - 1 : S; I >= 0; I -= 2)
- Sanitized.Msgs[I]="";
- return Sanitized;
+ Sanitized.Msgs[I] = "";
+ return Sanitized;
}
bool ShouldSanitize;
llvm::raw_string_ostream OS;
-
};
// This Destination sends data to some "blackbox" in form of JSON.
class JsonStreamDestination : public TelemetryDestination {
public:
- JsonStreamDestination(bool ShouldSanitize)
- : ShouldSanitize(ShouldSanitize) {}
+ JsonStreamDestination(bool ShouldSanitize) : ShouldSanitize(ShouldSanitize) {}
Error emitEntry(const TelemetryInfo *Entry) override {
if (auto *E = dyn_cast<VendorCommonTelemetryInfo>(Entry)) {
- if (ShouldSanitize) {
- if (isa<StartupEvent>(E) || isa<ExitEvent>(E)) {
- // There is nothing to sanitize for this type of data, so keep as-is.
- return SendToBlackbox(E->serializeToJson());
- } else if (isa<CustomTelemetryEvent>(E)) {
- auto Sanitized = sanitizeFields(dyn_cast<CustomTelemetryEvent>(E));
- return SendToBlackbox(Sanitized.serializeToJson());
- } else {
- llvm_unreachable("unexpected type");
- }
- } else {
+ if (ShouldSanitize) {
+ if (isa<StartupEvent>(E) || isa<ExitEvent>(E)) {
+ // There is nothing to sanitize for this type of data, so keep as-is.
return SendToBlackbox(E->serializeToJson());
+ } else if (isa<CustomTelemetryEvent>(E)) {
+ auto Sanitized = sanitizeFields(dyn_cast<CustomTelemetryEvent>(E));
+ return SendToBlackbox(Sanitized.serializeToJson());
+ } else {
+ llvm_unreachable("unexpected type");
}
- } else {
+ } else {
+ return SendToBlackbox(E->serializeToJson());
+ }
+ } else {
// Unfamiliar entries, just send the entry's UUID
- return SendToBlackbox(json::Object{{"UUID", Entry->SessionUuid}});
+ return SendToBlackbox(json::Object{{"UUID", Entry->SessionUuid}});
}
return make_error<StringError>("unhandled codepath in emitEntry",
- inconvertibleErrorCode());
+ inconvertibleErrorCode());
}
- std::string name() const override { return JSON_DEST;}
-private:
+ std::string name() const override { return JSON_DEST; }
+private:
// Returns a copy of the given entry, but with some fields sanitized.
- CustomTelemetryEvent sanitizeFields(const CustomTelemetryEvent* Entry) {
+ CustomTelemetryEvent sanitizeFields(const CustomTelemetryEvent *Entry) {
CustomTelemetryEvent Sanitized(*Entry);
// Pretend that messages stored at EVEN positions are "sensitive",
// hence need to be sanitized away.
int S = Sanitized.Msgs.size() - 1;
for (int I = S % 2 == 0 ? S : S - 1; I >= 0; I -= 2)
- Sanitized.Msgs[I]="";
+ Sanitized.Msgs[I] = "";
return Sanitized;
}
@@ -300,29 +293,35 @@ class TestTelemeter : public Telemeter {
public:
TestTelemeter(std::string SessionUuid) : Uuid(SessionUuid), Counter(0) {}
- static std::unique_ptr<TestTelemeter> createInstance(TelemetryConfig *config) {
- if (!config->EnableTelemetry) return std::unique_ptr<TestTelemeter>(nullptr);
- std::unique_ptr<TestTelemeter> Telemeter = std::make_unique<TestTelemeter>(nextUuid());
- // Set up Destination based on the given config.
- for (const std::string &Dest : config->AdditionalDestinations) {
- // The destination(s) are ALSO defined by vendor, so it should understand
- // what the name of each destination signifies.
- if (Dest == JSON_DEST) {
- Telemeter->addDestination(new vendor_code::JsonStreamDestination(SanitizeData));
- } else if (Dest == STRING_DEST) {
- Telemeter->addDestination(new vendor_code::StringDestination(SanitizeData, Buffer));
- } else {
- llvm_unreachable(llvm::Twine("unknown destination: ", Dest).str().c_str());
- }
+ static std::unique_ptr<TestTelemeter>
+ createInstance(TelemetryConfig *config) {
+ if (!config->EnableTelemetry)
+ return std::unique_ptr<TestTelemeter>(nullptr);
+ std::unique_ptr<TestTelemeter> Telemeter =
+ std::make_unique<TestTelemeter>(nextUuid());
+ // Set up Destination based on the given config.
+ for (const std::string &Dest : config->AdditionalDestinations) {
+ // The destination(s) are ALSO defined by vendor, so it should understand
+ // what the name of each destination signifies.
+ if (Dest == JSON_DEST) {
+ Telemeter->addDestination(
+ new vendor_code::JsonStreamDestination(SanitizeData));
+ } else if (Dest == STRING_DEST) {
+ Telemeter->addDestination(
+ new vendor_code::StringDestination(SanitizeData, Buffer));
+ } else {
+ llvm_unreachable(
+ llvm::Twine("unknown destination: ", Dest).str().c_str());
}
- return Telemeter;
+ }
+ return Telemeter;
}
void logStartup(llvm::StringRef ToolPath, TelemetryInfo *Entry) override {
ToolName = ToolPath.str();
// The vendor can add additional stuff to the entry before logging.
- if (auto* S = dyn_cast<StartupEvent>(Entry)) {
+ if (auto *S = dyn_cast<StartupEvent>(Entry)) {
S->MagicStartupMsg = llvm::Twine("One_", ToolPath).str();
}
emitToDestinations(Entry);
@@ -330,7 +329,7 @@ class TestTelemeter : public Telemeter {
void logExit(llvm::StringRef ToolPath, TelemetryInfo *Entry) override {
// Ensure we're shutting down the same tool we started with.
- if (ToolPath != ToolName){
+ if (ToolPath != ToolName) {
std::string Str;
raw_string_ostream OS(Str);
OS << "Expected tool with name" << ToolName << ", but got " << ToolPath;
@@ -338,20 +337,20 @@ class TestTelemeter : public Telemeter {
}
// The vendor can add additional stuff to the entry before logging.
- if (auto * E = dyn_cast<ExitEvent>(Entry)) {
+ if (auto *E = dyn_cast<ExitEvent>(Entry)) {
E->MagicExitMsg = llvm::Twine("Three_", ToolPath).str();
}
emitToDestinations(Entry);
}
- void addDestination(TelemetryDestination* Dest) override {
+ void addDestination(TelemetryDestination *Dest) override {
Destinations.push_back(Dest);
}
void logMidpoint(TelemetryInfo *Entry) {
// The custom Telemeter can record and send additional data.
- if (auto * C = dyn_cast<CustomTelemetryEvent>(Entry)) {
+ if (auto *C = dyn_cast<CustomTelemetryEvent>(Entry)) {
C->Msgs.push_back("Two");
C->Msgs.push_back("Deux");
C->Msgs.push_back("Zwei");
@@ -361,23 +360,22 @@ class TestTelemeter : public Telemeter {
}
~TestTelemeter() {
- for (auto* Dest : Destinations)
+ for (auto *Dest : Destinations)
delete Dest;
}
- template <typename T>
- T makeDefaultTelemetryInfo() {
+ template <typename T> T makeDefaultTelemetryInfo() {
T Ret;
Ret.SessionUuid = Uuid;
Ret.Counter = Counter++;
return Ret;
}
-private:
+private:
void emitToDestinations(TelemetryInfo *Entry) {
for (TelemetryDestination *Dest : Destinations) {
llvm::Error err = Dest->emitEntry(Entry);
- if(err) {
+ if (err) {
// Log it and move on.
}
}
@@ -400,7 +398,7 @@ void ApplyVendorSpecificConfigs(TelemetryConfig *config) {
namespace {
-void ApplyCommonConfig(llvm::telemetry::TelemetryConfig* config) {
+void ApplyCommonConfig(llvm::telemetry::TelemetryConfig *config) {
// Any shareable configs for the upstream tool can go here.
// .....
}
@@ -435,23 +433,27 @@ using namespace llvm::telemetry;
//
// Preset StartTime to EPOCH.
auto StartTime = std::chrono::time_point<std::chrono::steady_clock>{};
-// Pretend the time it takes for the tool's initialization is EPOCH + 5 milliseconds
+// Pretend the time it takes for the tool's initialization is EPOCH + 5
+// milliseconds
auto InitCompleteTime = StartTime + std::chrono::milliseconds(5);
auto MidPointTime = StartTime + std::chrono::milliseconds(10);
auto MidPointCompleteTime = MidPointTime + std::chrono::milliseconds(5);
// Preset ExitTime to EPOCH + 20 milliseconds
auto ExitTime = StartTime + std::chrono::milliseconds(20);
-// Pretend the time it takes to complete tearing down the tool is 10 milliseconds.
+// Pretend the time it takes to complete tearing down the tool is 10
+// milliseconds.
auto ExitCompleteTime = ExitTime + std::chrono::milliseconds(10);
-void AtToolStart(std::string ToolName, vendor_code::TestTelemeter* T) {
- vendor_code::StartupEvent Entry = T->makeDefaultTelemetryInfo<vendor_code::StartupEvent>();
+void AtToolStart(std::string ToolName, vendor_code::TestTelemeter *T) {
+ vendor_code::StartupEvent Entry =
+ T->makeDefaultTelemetryInfo<vendor_code::StartupEvent>();
Entry.Stats = {StartTime, InitCompleteTime};
T->logStartup(ToolName, &Entry);
}
-void AtToolExit(std::string ToolName, vendor_code::TestTelemeter* T) {
- vendor_code::ExitEvent Entry = T->makeDefaultTelemetryInfo<vendor_code::ExitEvent>();
+void AtToolExit(std::string ToolName, vendor_code::TestTelemeter *T) {
+ vendor_code::ExitEvent Entry =
+ T->makeDefaultTelemetryInfo<vendor_code::ExitEvent>();
Entry.Stats = {ExitTime, ExitCompleteTime};
if (HasExitError) {
@@ -460,14 +462,15 @@ void AtToolExit(std::string ToolName, vendor_code::TestTelemeter* T) {
T->logExit(ToolName, &Entry);
}
-void AtToolMidPoint (vendor_code::TestTelemeter* T) {
- vendor_code::CustomTelemetryEvent Entry = T->makeDefaultTelemetryInfo<vendor_code::CustomTelemetryEvent>();
+void AtToolMidPoint(vendor_code::TestTelemeter *T) {
+ vendor_code::CustomTelemetryEvent Entry =
+ T->makeDefaultTelemetryInfo<vendor_code::CustomTelemetryEvent>();
Entry.Stats = {MidPointTime, MidPointCompleteTime};
T->logMidpoint(&Entry);
}
// Helper function to print the given object content to string.
-static std::string ValueToString(const json::Value* V) {
+static std::string ValueToString(const json::Value *V) {
std::string Ret;
llvm::raw_string_ostream P(Ret);
P << *V;
@@ -477,7 +480,8 @@ static std::string ValueToString(const json::Value* V) {
// Without vendor's implementation, telemetry is not enabled by default.
TEST(TelemetryTest, TelemetryDefault) {
HasVendorConfig = false;
- std::shared_ptr<llvm::telemetry::TelemetryConfig> Config = GetTelemetryConfig();
+ std::shared_ptr<llvm::telemetry::TelemetryConfig> Config =
+ GetTelemetryConfig();
auto Tool = vendor_code::TestTelemeter::createInstance(Config.get());
EXPECT_EQ(nullptr, Tool.get());
@@ -492,7 +496,8 @@ TEST(TelemetryTest, TelemetryEnabled) {
Buffer = "";
EmittedJsons.clear();
- std::shared_ptr<llvm::telemetry::TelemetryConfig> Config = GetTelemetryConfig();
+ std::shared_ptr<llvm::telemetry::TelemetryConfig> Config =
+ GetTelemetryConfig();
// Add some destinations
Config->AdditionalDestinations.push_back(vendor_code::STRING_DEST);
@@ -504,7 +509,6 @@ TEST(TelemetryTest, TelemetryEnabled) {
AtToolMidPoint(Tool.get());
AtToolExit(ToolName, Tool.get());
-
// Check that the StringDestination emitted properly
{
std::string ExpectedBuff = "UUID:1111\n"
@@ -524,20 +528,22 @@ TEST(TelemetryTest, TelemetryEnabled) {
// There should be 3 events emitted by the Telemeter (start, midpoint, exit)
EXPECT_EQ(3, EmittedJsons.size());
- const json::Value* StartupEntry = EmittedJsons[0].get("Startup");
+ const json::Value *StartupEntry = EmittedJsons[0].get("Startup");
ASSERT_NE(StartupEntry, nullptr);
- EXPECT_STREQ("[[\"UUID\",\"1111\"],[\"MagicStartupMsg\",\"One_TestToolOne\"]]",
- ValueToString(StartupEntry).c_str());
+ EXPECT_STREQ(
+ "[[\"UUID\",\"1111\"],[\"MagicStartupMsg\",\"One_TestToolOne\"]]",
+ ValueToString(StartupEntry).c_str());
- const json::Value* MidpointEntry = EmittedJsons[1].get("Midpoint");
+ const json::Value *MidpointEntry = EmittedJsons[1].get("Midpoint");
ASSERT_NE(MidpointEntry, nullptr);
EXPECT_STREQ("{\"MSG_0\":\"Two\",\"MSG_1\":\"Deux\",\"MSG_2\":\"Zwei\"}",
ValueToString(MidpointEntry).c_str());
- const json::Value* ExitEntry = EmittedJsons[2].get("Exit");
+ const json::Value *ExitEntry = EmittedJsons[2].get("Exit");
ASSERT_NE(ExitEntry, nullptr);
- EXPECT_STREQ("[[\"UUID\",\"1111\"],[\"MagicExitMsg\",\"Three_TestToolOne\"]]",
- ValueToString(ExitEntry).c_str());
+ EXPECT_STREQ(
+ "[[\"UUID\",\"1111\"],[\"MagicExitMsg\",\"Three_TestToolOne\"]]",
+ ValueToString(ExitEntry).c_str());
}
}
@@ -552,7 +558,8 @@ TEST(TelemetryTest, TelemetryEnabledSanitizeData) {
Buffer = "";
EmittedJsons.clear();
- std::shared_ptr<llvm::telemetry::TelemetryConfig> Config = GetTelemetryConfig();
+ std::shared_ptr<llvm::telemetry::TelemetryConfig> Config =
+ GetTelemetryConfig();
// Add some destinations
Config->AdditionalDestinations.push_back(vendor_code::STRING_DEST);
@@ -564,14 +571,13 @@ TEST(TelemetryTest, TelemetryEnabledSanitizeData) {
AtToolMidPoint(Tool.get());
AtToolExit(ToolName, Tool.get());
-
// Check that the StringDestination emitted properly
{
// The StringDestination should have removed the odd-positioned msgs.
std::string ExpectedBuff = "UUID:1111\n"
"MagicStartupMsg:One_TestToolOne\n"
"MSG_0:Two\n"
- "MSG_1:\n" // was sannitized away.
+ "MSG_1:\n" // was sannitized away.
"MSG_2:Zwei\n"
"UUID:1111\n"
"MagicExitMsg:Three_TestToolOne\n";
@@ -585,21 +591,23 @@ TEST(TelemetryTest, TelemetryEnabledSanitizeData) {
// There should be 3 events emitted by the Telemeter (start, midpoint, exit)
EXPECT_EQ(3, EmittedJsons.size());
- const json::Value* StartupEntry = EmittedJsons[0].get("Startup");
+ const json::Value *StartupEntry = EmittedJsons[0].get("Startup");
ASSERT_NE(StartupEntry, nullptr);
- EXPECT_STREQ("[[\"UUID\",\"1111\"],[\"MagicStartupMsg\",\"One_TestToolOne\"]]",
- ValueToString(StartupEntry).c_str());
+ EXPECT_STREQ(
+ "[[\"UUID\",\"1111\"],[\"MagicStartupMsg\",\"One_TestToolOne\"]]",
+ ValueToString(StartupEntry).c_str());
- const json::Value* MidpointEntry = EmittedJsons[1].get("Midpoint");
+ const json::Value *MidpointEntry = EmittedJsons[1].get("Midpoint");
ASSERT_NE(MidpointEntry, nullptr);
// The JsonDestination should have removed the even-positioned msgs.
EXPECT_STREQ("{\"MSG_0\":\"\",\"MSG_1\":\"Deux\",\"MSG_2\":\"\"}",
ValueToString(MidpointEntry).c_str());
- const json::Value* ExitEntry = EmittedJsons[2].get("Exit");
+ const json::Value *ExitEntry = EmittedJsons[2].get("Exit");
ASSERT_NE(ExitEntry, nullptr);
- EXPECT_STREQ("[[\"UUID\",\"1111\"],[\"MagicExitMsg\",\"Three_TestToolOne\"]]",
- ValueToString(ExitEntry).c_str());
+ EXPECT_STREQ(
+ "[[\"UUID\",\"1111\"],[\"MagicExitMsg\",\"Three_TestToolOne\"]]",
+ ValueToString(ExitEntry).c_str());
}
}
>From 1378ed4c0358b2d9ff51a3a15766e20ce3472687 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Thu, 29 Aug 2024 11:32:13 -0400
Subject: [PATCH 06/61] more formatting
---
llvm/unittests/Telemetry/TelemetryTest.cpp | 88 +++++++++++++---------
1 file changed, 54 insertions(+), 34 deletions(-)
diff --git a/llvm/unittests/Telemetry/TelemetryTest.cpp b/llvm/unittests/Telemetry/TelemetryTest.cpp
index b89bc584717da5..cd3c61478c0b34 100644
--- a/llvm/unittests/Telemetry/TelemetryTest.cpp
+++ b/llvm/unittests/Telemetry/TelemetryTest.cpp
@@ -24,14 +24,17 @@
#include <ctime>
#include <vector>
-
-// Testing parameters.ve
+// Testing parameters.
+// These are set by each test to force certain outcomes.
+// Since the tests may be run in parellel, these should probably
+// be thread_local.
static thread_local bool HasExitError = false;
static thread_local std::string ExitMsg = "";
static thread_local bool HasVendorConfig = false;
static thread_local bool SanitizeData = false;
static thread_local std::string Buffer = "";
static thread_local std::vector<llvm::json::Object> EmittedJsons;
+static thread_local std::string ExpectedUuid = "";
namespace llvm {
namespace telemetry {
@@ -39,8 +42,8 @@ namespace vendor_code {
// Generate unique (but deterministic "uuid" for testing purposes).
static std::string nextUuid() {
- static size_t seed = 1111;
- return std::to_string(seed++);
+ static std::atomic<int> seed = 1111;
+ return std::to_string(seed.fetch_add(1, std::memory_order_acquire));
}
struct VendorEntryKind {
@@ -54,7 +57,6 @@ struct VendorEntryKind {
// 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) {
// Subclasses of this is also acceptable.
return (T->getEntryKind() & VendorEntryKind::VendorCommon) ==
@@ -155,6 +157,7 @@ struct CustomTelemetryEvent : public VendorCommonTelemetryInfo {
}
void serializeToStream(llvm::raw_ostream &OS) const override {
+ OS << "UUID:" << SessionUuid << "\n";
int I = 0;
for (const std::string &M : Msgs) {
OS << "MSG_" << I << ":" << M << "\n";
@@ -164,11 +167,13 @@ struct CustomTelemetryEvent : public VendorCommonTelemetryInfo {
json::Object serializeToJson() const override {
json::Object Inner;
+ Inner.try_emplace ("UUID", SessionUuid);
int I = 0;
for (const std::string &M : Msgs) {
Inner.try_emplace(("MSG_" + llvm::Twine(I)).str(), M);
++I;
}
+
return json::Object{{"Midpoint", std::move(Inner)}};
}
};
@@ -295,10 +300,12 @@ class TestTelemeter : public Telemeter {
static std::unique_ptr<TestTelemeter>
createInstance(TelemetryConfig *config) {
+ llvm::errs() << "============================== createInstance is called" << "\n";
if (!config->EnableTelemetry)
- return std::unique_ptr<TestTelemeter>(nullptr);
+ return nullptr;
+ ExpectedUuid = nextUuid();
std::unique_ptr<TestTelemeter> Telemeter =
- std::make_unique<TestTelemeter>(nextUuid());
+ std::make_unique<TestTelemeter>(ExpectedUuid);
// Set up Destination based on the given config.
for (const std::string &Dest : config->AdditionalDestinations) {
// The destination(s) are ALSO defined by vendor, so it should understand
@@ -359,6 +366,8 @@ class TestTelemeter : public Telemeter {
emitToDestinations(Entry);
}
+ const std::string& getUuid() const {return Uuid;}
+
~TestTelemeter() {
for (auto *Dest : Destinations)
delete Dest;
@@ -412,12 +421,13 @@ std::shared_ptr<llvm::telemetry::TelemetryConfig> GetTelemetryConfig() {
ApplyCommonConfig(Config.get());
// Apply vendor specific config, if present.
- // In practice, this would be a build-time param.
+ // In principle, this would be a build-time param, configured by the vendor.
// Eg:
//
// #ifdef HAS_VENDOR_TELEMETRY_CONFIG
// llvm::telemetry::vendor_code::ApplyVendorSpecificConfigs(config.get());
// #endif
+ //
// But for unit testing, we use the testing params defined at the top.
if (HasVendorConfig) {
llvm::telemetry::vendor_code::ApplyVendorSpecificConfigs(Config.get());
@@ -488,12 +498,12 @@ TEST(TelemetryTest, TelemetryDefault) {
}
TEST(TelemetryTest, TelemetryEnabled) {
- const std::string ToolName = "TestToolOne";
+ const std::string ToolName = "TelemetryTest";
// Preset some test params.
HasVendorConfig = true;
SanitizeData = false;
- Buffer = "";
+ Buffer.clear();
EmittedJsons.clear();
std::shared_ptr<llvm::telemetry::TelemetryConfig> Config =
@@ -509,17 +519,21 @@ TEST(TelemetryTest, TelemetryEnabled) {
AtToolMidPoint(Tool.get());
AtToolExit(ToolName, Tool.get());
+ // Check that the Tool uses the expected UUID.
+ EXPECT_STREQ(Tool->getUuid().c_str(), ExpectedUuid.c_str());
+
// Check that the StringDestination emitted properly
{
- std::string ExpectedBuff = "UUID:1111\n"
- "MagicStartupMsg:One_TestToolOne\n"
- "MSG_0:Two\n"
- "MSG_1:Deux\n"
- "MSG_2:Zwei\n"
- "UUID:1111\n"
- "MagicExitMsg:Three_TestToolOne\n";
+ std::string ExpectedBuffer = ("UUID:" + llvm::Twine(ExpectedUuid) + "\n" +
+ "MagicStartupMsg:One_" + llvm::Twine(ToolName) + "\n" +
+ "UUID:" + llvm::Twine(ExpectedUuid) + "\n" +
+ "MSG_0:Two\n" +
+ "MSG_1:Deux\n" +
+ "MSG_2:Zwei\n" +
+ "UUID:" + llvm::Twine(ExpectedUuid) + "\n" +
+ "MagicExitMsg:Three_" + llvm::Twine(ToolName) + "\n").str();
- EXPECT_STREQ(ExpectedBuff.c_str(), Buffer.c_str());
+ EXPECT_STREQ(ExpectedBuffer.c_str(), Buffer.c_str());
}
// Check that the JsonDestination emitted properly
@@ -531,18 +545,21 @@ TEST(TelemetryTest, TelemetryEnabled) {
const json::Value *StartupEntry = EmittedJsons[0].get("Startup");
ASSERT_NE(StartupEntry, nullptr);
EXPECT_STREQ(
- "[[\"UUID\",\"1111\"],[\"MagicStartupMsg\",\"One_TestToolOne\"]]",
+ ("[[\"UUID\",\"" + llvm::Twine(ExpectedUuid) + "\"],[\"MagicStartupMsg\",\"One_" + llvm::Twine(ToolName)+"\"]]").str().c_str(),
ValueToString(StartupEntry).c_str());
const json::Value *MidpointEntry = EmittedJsons[1].get("Midpoint");
ASSERT_NE(MidpointEntry, nullptr);
- EXPECT_STREQ("{\"MSG_0\":\"Two\",\"MSG_1\":\"Deux\",\"MSG_2\":\"Zwei\"}",
+ // TODO: This is a bit flaky in that the json string printer sort the entries (for now),
+ // so the "UUID" field is put at the end of the array even though it was emitted first.
+ EXPECT_STREQ(("{\"MSG_0\":\"Two\",\"MSG_1\":\"Deux\",\"MSG_2\":\"Zwei\",\"UUID\":\""
+ + llvm::Twine(ExpectedUuid) + "\"}").str().c_str(),
ValueToString(MidpointEntry).c_str());
const json::Value *ExitEntry = EmittedJsons[2].get("Exit");
ASSERT_NE(ExitEntry, nullptr);
EXPECT_STREQ(
- "[[\"UUID\",\"1111\"],[\"MagicExitMsg\",\"Three_TestToolOne\"]]",
+ ("[[\"UUID\",\"" + llvm::Twine(ExpectedUuid) + "\"],[\"MagicExitMsg\",\"Three_" + llvm::Twine(ToolName)+"\"]]").str().c_str(),
ValueToString(ExitEntry).c_str());
}
}
@@ -550,12 +567,12 @@ TEST(TelemetryTest, TelemetryEnabled) {
// Similar to previous tests, but toggling the data-sanitization option ON.
// The recorded data should have some fields removed.
TEST(TelemetryTest, TelemetryEnabledSanitizeData) {
- const std::string ToolName = "TestToolOne";
+ const std::string ToolName = "TelemetryTest_SanitizedData";
// Preset some test params.
HasVendorConfig = true;
SanitizeData = true;
- Buffer = "";
+ Buffer.clear();
EmittedJsons.clear();
std::shared_ptr<llvm::telemetry::TelemetryConfig> Config =
@@ -574,15 +591,16 @@ TEST(TelemetryTest, TelemetryEnabledSanitizeData) {
// Check that the StringDestination emitted properly
{
// The StringDestination should have removed the odd-positioned msgs.
- std::string ExpectedBuff = "UUID:1111\n"
- "MagicStartupMsg:One_TestToolOne\n"
- "MSG_0:Two\n"
- "MSG_1:\n" // was sannitized away.
- "MSG_2:Zwei\n"
- "UUID:1111\n"
- "MagicExitMsg:Three_TestToolOne\n";
- EXPECT_STREQ(ExpectedBuff.c_str(), Buffer.c_str());
+ std::string ExpectedBuffer = ("UUID:" + llvm::Twine(ExpectedUuid) + "\n" +
+ "MagicStartupMsg:One_" + llvm::Twine(ToolName) + "\n" +
+ "UUID:" + llvm::Twine(ExpectedUuid) + "\n" +
+ "MSG_0:Two\n" +
+ "MSG_1:\n" + // <<< was sanitized away.
+ "MSG_2:Zwei\n" +
+ "UUID:" + llvm::Twine(ExpectedUuid) + "\n" +
+ "MagicExitMsg:Three_" + llvm::Twine(ToolName) + "\n").str();
+ EXPECT_STREQ(ExpectedBuffer.c_str(), Buffer.c_str());
}
// Check that the JsonDestination emitted properly
@@ -594,19 +612,21 @@ TEST(TelemetryTest, TelemetryEnabledSanitizeData) {
const json::Value *StartupEntry = EmittedJsons[0].get("Startup");
ASSERT_NE(StartupEntry, nullptr);
EXPECT_STREQ(
- "[[\"UUID\",\"1111\"],[\"MagicStartupMsg\",\"One_TestToolOne\"]]",
+ ("[[\"UUID\",\"" + llvm::Twine(ExpectedUuid) + "\"],[\"MagicStartupMsg\",\"One_" + llvm::Twine(ToolName)+"\"]]").str().c_str(),
ValueToString(StartupEntry).c_str());
const json::Value *MidpointEntry = EmittedJsons[1].get("Midpoint");
ASSERT_NE(MidpointEntry, nullptr);
// The JsonDestination should have removed the even-positioned msgs.
- EXPECT_STREQ("{\"MSG_0\":\"\",\"MSG_1\":\"Deux\",\"MSG_2\":\"\"}",
+ EXPECT_STREQ(("{\"MSG_0\":\"\",\"MSG_1\":\"Deux\",\"MSG_2\":\"\",\"UUID\":\""
+ + llvm::Twine(ExpectedUuid) + "\"}").str().c_str(),
ValueToString(MidpointEntry).c_str());
+
const json::Value *ExitEntry = EmittedJsons[2].get("Exit");
ASSERT_NE(ExitEntry, nullptr);
EXPECT_STREQ(
- "[[\"UUID\",\"1111\"],[\"MagicExitMsg\",\"Three_TestToolOne\"]]",
+ ("[[\"UUID\",\"" + llvm::Twine(ExpectedUuid) + "\"],[\"MagicExitMsg\",\"Three_" + llvm::Twine(ToolName)+"\"]]").str().c_str(),
ValueToString(ExitEntry).c_str());
}
}
>From 02e750ec91015fba0acaee1b3527f790417c0cb8 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Thu, 29 Aug 2024 12:47:46 -0400
Subject: [PATCH 07/61] more formatting changes
---
llvm/unittests/Telemetry/TelemetryTest.cpp | 95 +++++++++++++---------
1 file changed, 56 insertions(+), 39 deletions(-)
diff --git a/llvm/unittests/Telemetry/TelemetryTest.cpp b/llvm/unittests/Telemetry/TelemetryTest.cpp
index cd3c61478c0b34..fde7e31802f682 100644
--- a/llvm/unittests/Telemetry/TelemetryTest.cpp
+++ b/llvm/unittests/Telemetry/TelemetryTest.cpp
@@ -167,7 +167,7 @@ struct CustomTelemetryEvent : public VendorCommonTelemetryInfo {
json::Object serializeToJson() const override {
json::Object Inner;
- Inner.try_emplace ("UUID", SessionUuid);
+ Inner.try_emplace("UUID", SessionUuid);
int I = 0;
for (const std::string &M : Msgs) {
Inner.try_emplace(("MSG_" + llvm::Twine(I)).str(), M);
@@ -300,7 +300,8 @@ class TestTelemeter : public Telemeter {
static std::unique_ptr<TestTelemeter>
createInstance(TelemetryConfig *config) {
- llvm::errs() << "============================== createInstance is called" << "\n";
+ llvm::errs() << "============================== createInstance is called"
+ << "\n";
if (!config->EnableTelemetry)
return nullptr;
ExpectedUuid = nextUuid();
@@ -366,7 +367,7 @@ class TestTelemeter : public Telemeter {
emitToDestinations(Entry);
}
- const std::string& getUuid() const {return Uuid;}
+ const std::string &getUuid() const { return Uuid; }
~TestTelemeter() {
for (auto *Dest : Destinations)
@@ -524,14 +525,13 @@ TEST(TelemetryTest, TelemetryEnabled) {
// Check that the StringDestination emitted properly
{
- std::string ExpectedBuffer = ("UUID:" + llvm::Twine(ExpectedUuid) + "\n" +
- "MagicStartupMsg:One_" + llvm::Twine(ToolName) + "\n" +
- "UUID:" + llvm::Twine(ExpectedUuid) + "\n" +
- "MSG_0:Two\n" +
- "MSG_1:Deux\n" +
- "MSG_2:Zwei\n" +
- "UUID:" + llvm::Twine(ExpectedUuid) + "\n" +
- "MagicExitMsg:Three_" + llvm::Twine(ToolName) + "\n").str();
+ std::string ExpectedBuffer =
+ ("UUID:" + llvm::Twine(ExpectedUuid) + "\n" + "MagicStartupMsg:One_" +
+ llvm::Twine(ToolName) + "\n" + "UUID:" + llvm::Twine(ExpectedUuid) +
+ "\n" + "MSG_0:Two\n" + "MSG_1:Deux\n" + "MSG_2:Zwei\n" +
+ "UUID:" + llvm::Twine(ExpectedUuid) + "\n" + "MagicExitMsg:Three_" +
+ llvm::Twine(ToolName) + "\n")
+ .str();
EXPECT_STREQ(ExpectedBuffer.c_str(), Buffer.c_str());
}
@@ -544,23 +544,33 @@ TEST(TelemetryTest, TelemetryEnabled) {
const json::Value *StartupEntry = EmittedJsons[0].get("Startup");
ASSERT_NE(StartupEntry, nullptr);
- EXPECT_STREQ(
- ("[[\"UUID\",\"" + llvm::Twine(ExpectedUuid) + "\"],[\"MagicStartupMsg\",\"One_" + llvm::Twine(ToolName)+"\"]]").str().c_str(),
- ValueToString(StartupEntry).c_str());
+ EXPECT_STREQ(("[[\"UUID\",\"" + llvm::Twine(ExpectedUuid) +
+ "\"],[\"MagicStartupMsg\",\"One_" + llvm::Twine(ToolName) +
+ "\"]]")
+ .str()
+ .c_str(),
+ ValueToString(StartupEntry).c_str());
const json::Value *MidpointEntry = EmittedJsons[1].get("Midpoint");
ASSERT_NE(MidpointEntry, nullptr);
- // TODO: This is a bit flaky in that the json string printer sort the entries (for now),
- // so the "UUID" field is put at the end of the array even though it was emitted first.
- EXPECT_STREQ(("{\"MSG_0\":\"Two\",\"MSG_1\":\"Deux\",\"MSG_2\":\"Zwei\",\"UUID\":\""
- + llvm::Twine(ExpectedUuid) + "\"}").str().c_str(),
+ // TODO: This is a bit flaky in that the json string printer sort the
+ // entries (for now), so the "UUID" field is put at the end of the array
+ // even though it was emitted first.
+ EXPECT_STREQ(("{\"MSG_0\":\"Two\",\"MSG_1\":\"Deux\",\"MSG_2\":\"Zwei\","
+ "\"UUID\":\"" +
+ llvm::Twine(ExpectedUuid) + "\"}")
+ .str()
+ .c_str(),
ValueToString(MidpointEntry).c_str());
const json::Value *ExitEntry = EmittedJsons[2].get("Exit");
ASSERT_NE(ExitEntry, nullptr);
- EXPECT_STREQ(
- ("[[\"UUID\",\"" + llvm::Twine(ExpectedUuid) + "\"],[\"MagicExitMsg\",\"Three_" + llvm::Twine(ToolName)+"\"]]").str().c_str(),
- ValueToString(ExitEntry).c_str());
+ EXPECT_STREQ(("[[\"UUID\",\"" + llvm::Twine(ExpectedUuid) +
+ "\"],[\"MagicExitMsg\",\"Three_" + llvm::Twine(ToolName) +
+ "\"]]")
+ .str()
+ .c_str(),
+ ValueToString(ExitEntry).c_str());
}
}
@@ -592,14 +602,13 @@ TEST(TelemetryTest, TelemetryEnabledSanitizeData) {
{
// The StringDestination should have removed the odd-positioned msgs.
- std::string ExpectedBuffer = ("UUID:" + llvm::Twine(ExpectedUuid) + "\n" +
- "MagicStartupMsg:One_" + llvm::Twine(ToolName) + "\n" +
- "UUID:" + llvm::Twine(ExpectedUuid) + "\n" +
- "MSG_0:Two\n" +
- "MSG_1:\n" + // <<< was sanitized away.
- "MSG_2:Zwei\n" +
- "UUID:" + llvm::Twine(ExpectedUuid) + "\n" +
- "MagicExitMsg:Three_" + llvm::Twine(ToolName) + "\n").str();
+ std::string ExpectedBuffer =
+ ("UUID:" + llvm::Twine(ExpectedUuid) + "\n" + "MagicStartupMsg:One_" +
+ llvm::Twine(ToolName) + "\n" + "UUID:" + llvm::Twine(ExpectedUuid) +
+ "\n" + "MSG_0:Two\n" + "MSG_1:\n" + // <<< was sanitized away.
+ "MSG_2:Zwei\n" + "UUID:" + llvm::Twine(ExpectedUuid) + "\n" +
+ "MagicExitMsg:Three_" + llvm::Twine(ToolName) + "\n")
+ .str();
EXPECT_STREQ(ExpectedBuffer.c_str(), Buffer.c_str());
}
@@ -611,23 +620,31 @@ TEST(TelemetryTest, TelemetryEnabledSanitizeData) {
const json::Value *StartupEntry = EmittedJsons[0].get("Startup");
ASSERT_NE(StartupEntry, nullptr);
- EXPECT_STREQ(
- ("[[\"UUID\",\"" + llvm::Twine(ExpectedUuid) + "\"],[\"MagicStartupMsg\",\"One_" + llvm::Twine(ToolName)+"\"]]").str().c_str(),
- ValueToString(StartupEntry).c_str());
+ EXPECT_STREQ(("[[\"UUID\",\"" + llvm::Twine(ExpectedUuid) +
+ "\"],[\"MagicStartupMsg\",\"One_" + llvm::Twine(ToolName) +
+ "\"]]")
+ .str()
+ .c_str(),
+ ValueToString(StartupEntry).c_str());
const json::Value *MidpointEntry = EmittedJsons[1].get("Midpoint");
ASSERT_NE(MidpointEntry, nullptr);
// The JsonDestination should have removed the even-positioned msgs.
- EXPECT_STREQ(("{\"MSG_0\":\"\",\"MSG_1\":\"Deux\",\"MSG_2\":\"\",\"UUID\":\""
- + llvm::Twine(ExpectedUuid) + "\"}").str().c_str(),
- ValueToString(MidpointEntry).c_str());
-
+ EXPECT_STREQ(
+ ("{\"MSG_0\":\"\",\"MSG_1\":\"Deux\",\"MSG_2\":\"\",\"UUID\":\"" +
+ llvm::Twine(ExpectedUuid) + "\"}")
+ .str()
+ .c_str(),
+ ValueToString(MidpointEntry).c_str());
const json::Value *ExitEntry = EmittedJsons[2].get("Exit");
ASSERT_NE(ExitEntry, nullptr);
- EXPECT_STREQ(
- ("[[\"UUID\",\"" + llvm::Twine(ExpectedUuid) + "\"],[\"MagicExitMsg\",\"Three_" + llvm::Twine(ToolName)+"\"]]").str().c_str(),
- ValueToString(ExitEntry).c_str());
+ EXPECT_STREQ(("[[\"UUID\",\"" + llvm::Twine(ExpectedUuid) +
+ "\"],[\"MagicExitMsg\",\"Three_" + llvm::Twine(ToolName) +
+ "\"]]")
+ .str()
+ .c_str(),
+ ValueToString(ExitEntry).c_str());
}
}
>From 63e99fc09412fa6b8f23f9820eb1810dc7012f3d Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Thu, 29 Aug 2024 15:00:29 -0400
Subject: [PATCH 08/61] Added header comment to describe the package
---
llvm/include/llvm/Telemetry/Telemetry.h | 66 ++++++++++++++++++++++---
1 file changed, 58 insertions(+), 8 deletions(-)
diff --git a/llvm/include/llvm/Telemetry/Telemetry.h b/llvm/include/llvm/Telemetry/Telemetry.h
index 7084b81c0304ae..d4f192ab9159c5 100644
--- a/llvm/include/llvm/Telemetry/Telemetry.h
+++ b/llvm/include/llvm/Telemetry/Telemetry.h
@@ -5,6 +5,50 @@
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
+///
+/// \file
+/// This file provides the basic framework for Telemetry
+///
+/// It comprises of three important structs/classes:
+///
+/// - Telemeter: The class responsible for collecting and forwarding
+/// telemery data.
+/// - TelemetryInfo: data courier
+/// - TelemetryConfig: this stores configurations on Telemeter.
+///
+/// This framework is intended to be configurable and extensible:
+/// - Any LLVM tool that wants to use Telemetry can extend/customize it.
+/// - Toolchain vendors can also provide custom implementation/config of the
+/// Telemetry library, which could either overrides or extends the given tool's
+/// upstream implementation, to best fit their organization's usage and
+/// security models.
+/// - End users of such tool can also configure telemetry (as allowed
+/// by their vendor).
+///
+/// Note: There are two important points to highlight about this package:
+///
+/// (0) There is (currently) no concrete implementation of Telemetry in
+/// upstream LLVM. We only provide the abstract API here. Any tool
+/// that wants telemetry will have to implement one.
+///
+/// The reason for this is because all the tools in llvm are
+/// very different in what they care about (what/when/where to instrument)
+/// Hence it might not be practical to a single implementation.
+/// However, if in the future when we see any common pattern, we can
+/// extract them into a shared place. That is TBD - contributions welcomed.
+///
+/// (1) No implementation of Telemetry in upstream LLVM shall directly store
+/// any of the collected data due to privacy and security reasons:
+/// + Different organizations have different opinions on which data
+/// is sensitive and which is not.
+/// + Data ownerships and data collection consents are hare to
+/// accommodate from LLVM developers' point of view.
+/// (Eg., the data collected by Telemetry framework is NOT neccessarily
+/// owned by the user of a LLVM tool with Telemetry enabled, hence
+/// their consent to data collection isn't meaningful. On the other
+/// hand, we have no practical way to request consent from "real" owners.
+//===---------------------------------------------------------------------===//
+
#ifndef LLVM_TELEMETRY_TELEMETRY_H
#define LLVM_TELEMETRY_TELEMETRY_H
@@ -23,23 +67,26 @@
namespace llvm {
namespace telemetry {
-using SteadyTimePoint = std::chrono::time_point<std::chrono::steady_clock>;
struct TelemetryConfig {
// If true, telemetry will be enabled.
bool EnableTelemetry;
- // Additional destinations to send the logged entries.
- // Could be stdout, stderr, or some local paths.
- // Note: these are destinations are __in addition to__ whatever the default
- // destination(s) are, as implemented by vendors.
+ // Implementation-defined names of additional destinations to send
+ // telemetry data (Could be stdout, stderr, or some local paths, etc).
+ //
+ // These strings will be interpreted by the vendor's code.
+ // So the users must pick the from their vendor's pre-defined
+ // set of Destinations.
std::vector<std::string> AdditionalDestinations;
};
+using SteadyTimePoint = std::chrono::time_point<std::chrono::steady_clock>;
+
struct TelemetryEventStats {
- // REQUIRED: Start time of event
+ // REQUIRED: Start time of an event
SteadyTimePoint Start;
- // OPTIONAL: End time of event - may be empty if not meaningful.
+ // OPTIONAL: End time of an event - may be empty if not meaningful.
std::optional<SteadyTimePoint> End;
// TBD: could add some memory stats here too?
@@ -60,7 +107,10 @@ struct EntryKind {
static const KindType Base = 0;
};
-// The base class contains the basic set of data.
+// TelemetryInfo is the data courier, used to forward data from
+// the tool being monitored and the Telemery framework.
+//
+// This base class contains only the basic set of telemetry data.
// Downstream implementations can add more fields as needed.
struct TelemetryInfo {
// A "session" corresponds to every time the tool starts.
>From fa885126c6d7e5b0caf6e2f2411dcd034081e187 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Thu, 29 Aug 2024 15:08:18 -0400
Subject: [PATCH 09/61] reformat header
---
llvm/include/llvm/Telemetry/Telemetry.h | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)
diff --git a/llvm/include/llvm/Telemetry/Telemetry.h b/llvm/include/llvm/Telemetry/Telemetry.h
index d4f192ab9159c5..cbc374df632124 100644
--- a/llvm/include/llvm/Telemetry/Telemetry.h
+++ b/llvm/include/llvm/Telemetry/Telemetry.h
@@ -19,9 +19,9 @@
/// This framework is intended to be configurable and extensible:
/// - Any LLVM tool that wants to use Telemetry can extend/customize it.
/// - Toolchain vendors can also provide custom implementation/config of the
-/// Telemetry library, which could either overrides or extends the given tool's
-/// upstream implementation, to best fit their organization's usage and
-/// security models.
+/// Telemetry library, which could either overrides or extends the given
+/// tool's upstream implementation, to best fit their organization's usage
+/// and security models.
/// - End users of such tool can also configure telemetry (as allowed
/// by their vendor).
///
@@ -41,15 +41,15 @@
/// any of the collected data due to privacy and security reasons:
/// + Different organizations have different opinions on which data
/// is sensitive and which is not.
-/// + Data ownerships and data collection consents are hare to
+/// + Data ownerships and data collection consents are hard to
/// accommodate from LLVM developers' point of view.
/// (Eg., the data collected by Telemetry framework is NOT neccessarily
/// owned by the user of a LLVM tool with Telemetry enabled, hence
/// their consent to data collection isn't meaningful. On the other
-/// hand, we have no practical way to request consent from "real" owners.
+/// hand, we have no practical way to request consent from "real"
+/// owners.
//===---------------------------------------------------------------------===//
-
#ifndef LLVM_TELEMETRY_TELEMETRY_H
#define LLVM_TELEMETRY_TELEMETRY_H
@@ -67,7 +67,6 @@
namespace llvm {
namespace telemetry {
-
struct TelemetryConfig {
// If true, telemetry will be enabled.
bool EnableTelemetry;
>From 0866f64dc2863fad28d2027a08e01ab414c6b46a Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Tue, 3 Sep 2024 13:52:20 -0400
Subject: [PATCH 10/61] addressed review comments and added separate docs in
llvm/docs/
---
llvm/docs/Telemetry.rst | 70 ++++++++++++++++++++++
llvm/docs/UserGuides.rst | 3 +
llvm/include/llvm/Telemetry/Telemetry.h | 77 +++++++++++--------------
3 files changed, 106 insertions(+), 44 deletions(-)
create mode 100644 llvm/docs/Telemetry.rst
diff --git a/llvm/docs/Telemetry.rst b/llvm/docs/Telemetry.rst
new file mode 100644
index 00000000000000..997436e78735d0
--- /dev/null
+++ b/llvm/docs/Telemetry.rst
@@ -0,0 +1,70 @@
+===========================
+Telemetry framework in LLVM
+===========================
+
+.. contents::
+ :local:
+
+.. toctree::
+ :hidden:
+
+Objective
+=========
+
+Provides a common framework in LLVM for collecting various usage and performance
+metrics.
+It is located at `llvm/Telemetry/Telemetry.h`
+
+Characteristics
+---------------
+* Configurable and extensible by:
+ * Tools: any tool that wants to use Telemetry can extend and customize it.
+ * Vendors: Toolchain vendors can also provide custom implementation of the
+ library, which could either override or extend the given tool's upstream
+ implementation, to best fit their organization's usage and privacy models.
+ * End users of such tool can also configure Telemetry(as allowed by their
+ vendor).
+
+
+Important notes
+----------------
+
+* There is no concrete implementation of a Telemetry library in upstream LLVM.
+ We only provide the abstract API here. Any tool that wants telemetry will
+ implement one.
+ The rationale for this is that, all the tools in llvm are very different in
+ what they care about(what/where/when to instrument data). Hence, it might not
+ be practical to have a single implementation.
+ However, in the future, if we see enough common pattern, we can extract them
+ into a shared place. This is TBD - contributions are welcomed.
+
+* No implementation of Telemetry in upstream LLVM shall store any of the
+ collected data due to privacy and security reasons:
+ * Different organizations have different privacy models:
+ * Which data is sensitive, which is not?
+ * Whether it is acceptable for instrumented data to be stored anywhere?
+ (to a local file, what not?)
+ * Data ownership and data collection consents are hard to accommodate from
+ LLVM developers' point of view:
+ * Eg., data collected by Telemetry is not neccessarily owned by the user
+ of an LLVM tool with Telemetry enabled, hence the user's consent to data
+ collection is not meaningful. On the other hand, LLVM developers have no
+ practical way to request consent from the "real" owners.
+
+
+High-level design
+=================
+
+Key components
+--------------
+
+The framework is consisted of three important classes:
+* `llvm::telemetry::Telemeter`: The class responsible for collecting and
+ forwarding telemetry data. This is the main point of interaction between
+ the framework and any tool that wants to enable telemery.
+* `llvm::telemetry::TelemetryInfo`: Data courier
+* `llvm::telemetry::Config`: Configurations on the `Telemeter`.
+
+
+// <TODO insert the flow chart that shows the interaction between the classes here>
+
diff --git a/llvm/docs/UserGuides.rst b/llvm/docs/UserGuides.rst
index 86101ffbd9ca5d..171da2053e7311 100644
--- a/llvm/docs/UserGuides.rst
+++ b/llvm/docs/UserGuides.rst
@@ -293,3 +293,6 @@ Additional Topics
:doc:`Sandbox IR <SandboxIR>`
This document describes the design and usage of Sandbox IR, a transactional layer over LLVM IR.
+
+:doc:`Telemetry`
+ This document describes the Telemetry framework in LLVM.
diff --git a/llvm/include/llvm/Telemetry/Telemetry.h b/llvm/include/llvm/Telemetry/Telemetry.h
index cbc374df632124..55a915a4fb894f 100644
--- a/llvm/include/llvm/Telemetry/Telemetry.h
+++ b/llvm/include/llvm/Telemetry/Telemetry.h
@@ -16,38 +16,7 @@
/// - TelemetryInfo: data courier
/// - TelemetryConfig: this stores configurations on Telemeter.
///
-/// This framework is intended to be configurable and extensible:
-/// - Any LLVM tool that wants to use Telemetry can extend/customize it.
-/// - Toolchain vendors can also provide custom implementation/config of the
-/// Telemetry library, which could either overrides or extends the given
-/// tool's upstream implementation, to best fit their organization's usage
-/// and security models.
-/// - End users of such tool can also configure telemetry (as allowed
-/// by their vendor).
-///
-/// Note: There are two important points to highlight about this package:
-///
-/// (0) There is (currently) no concrete implementation of Telemetry in
-/// upstream LLVM. We only provide the abstract API here. Any tool
-/// that wants telemetry will have to implement one.
-///
-/// The reason for this is because all the tools in llvm are
-/// very different in what they care about (what/when/where to instrument)
-/// Hence it might not be practical to a single implementation.
-/// However, if in the future when we see any common pattern, we can
-/// extract them into a shared place. That is TBD - contributions welcomed.
-///
-/// (1) No implementation of Telemetry in upstream LLVM shall directly store
-/// any of the collected data due to privacy and security reasons:
-/// + Different organizations have different opinions on which data
-/// is sensitive and which is not.
-/// + Data ownerships and data collection consents are hard to
-/// accommodate from LLVM developers' point of view.
-/// (Eg., the data collected by Telemetry framework is NOT neccessarily
-/// owned by the user of a LLVM tool with Telemetry enabled, hence
-/// their consent to data collection isn't meaningful. On the other
-/// hand, we have no practical way to request consent from "real"
-/// owners.
+/// Refer to its documentation at llvm/docs/Telemetry.rst for more details.
//===---------------------------------------------------------------------===//
#ifndef LLVM_TELEMETRY_TELEMETRY_H
@@ -67,7 +36,9 @@
namespace llvm {
namespace telemetry {
-struct TelemetryConfig {
+// Configuration for the Telemeter class.
+// This struct can be extended as needed.
+struct Config {
// If true, telemetry will be enabled.
bool EnableTelemetry;
@@ -80,9 +51,12 @@ struct TelemetryConfig {
std::vector<std::string> AdditionalDestinations;
};
+// 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>;
-struct TelemetryEventStats {
+// 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.
@@ -107,22 +81,28 @@ struct EntryKind {
};
// TelemetryInfo is the data courier, used to forward data from
-// the tool being monitored and the Telemery framework.
+// the tool being monitored to the Telemery framework.
//
// This base class contains only the basic set of telemetry data.
// Downstream implementations can add more fields as needed.
struct TelemetryInfo {
- // A "session" corresponds to every time the tool starts.
- // All entries emitted for the same session will have
- // the same session_uuid
- std::string SessionUuid;
+ // This represents a unique-id, conventionally corresponding to
+ // a tools' session - ie., every time the tool starts until it exits.
+ //
+ // Note: a tool could have mutliple sessions running at once, in which
+ // case, these shall be multiple sets of TelemetryInfo with multiple unique
+ // ids.
+ //
+ // Different usages can assign different types of IDs to this field.
+ std::string SessionId;
+ // Time/memory statistics of this event.
TelemetryEventStats Stats;
std::optional<ExitDescription> ExitDesc;
// Counting number of entries.
- // (For each set of entries with the same session_uuid, this value should
+ // (For each set of entries with the same SessionId, this value should
// be unique for each entry)
size_t Counter;
@@ -132,20 +112,29 @@ struct TelemetryInfo {
virtual json::Object serializeToJson() const;
// For isa, dyn_cast, etc, operations.
- virtual KindType getEntryKind() const { return EntryKind::Base; }
+ virtual KindType getKind() const { return EntryKind::Base; }
static bool classof(const TelemetryInfo *T) {
- return T->getEntryKind() == EntryKind::Base;
+ return T->getKind() == EntryKind::Base;
}
};
-// Where/how to send the telemetry entries.
-class TelemetryDestination {
+// This class presents a data sink to which the Telemetry framework
+// sends data.
+//
+// Its implementation is transparent to the framework.
+// It is up to the vendor to decide which pieces of data to forward
+// and where to forward them.
+class Destination {
public:
virtual ~TelemetryDestination() = default;
virtual Error emitEntry(const TelemetryInfo *Entry) = 0;
virtual std::string name() const = 0;
};
+// This class is the main interaction point between any LLVM tool
+// and this framework.
+// It is responsible for collecting telemetry data from the tool being
+// monitored.
class Telemeter {
public:
// Invoked upon tool startup
>From a8523f731c8bb1f6384407e9c3eca2bdfb949da2 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Tue, 3 Sep 2024 21:00:50 -0400
Subject: [PATCH 11/61] updated field names in tests
---
llvm/include/llvm/Telemetry/Telemetry.h | 17 ++--
llvm/lib/Telemetry/Telemetry.cpp | 2 +-
llvm/unittests/Telemetry/TelemetryTest.cpp | 103 +++++++++++----------
3 files changed, 66 insertions(+), 56 deletions(-)
diff --git a/llvm/include/llvm/Telemetry/Telemetry.h b/llvm/include/llvm/Telemetry/Telemetry.h
index 55a915a4fb894f..8f26f6ba9ec26e 100644
--- a/llvm/include/llvm/Telemetry/Telemetry.h
+++ b/llvm/include/llvm/Telemetry/Telemetry.h
@@ -63,9 +63,9 @@ struct EventStats {
std::optional<SteadyTimePoint> End;
// TBD: could add some memory stats here too?
- TelemetryEventStats() = default;
- TelemetryEventStats(SteadyTimePoint Start) : Start(Start) {}
- TelemetryEventStats(SteadyTimePoint Start, SteadyTimePoint End)
+ EventStats() = default;
+ EventStats(SteadyTimePoint Start) : Start(Start) {}
+ EventStats(SteadyTimePoint Start, SteadyTimePoint End)
: Start(Start), End(End) {}
};
@@ -76,6 +76,9 @@ struct ExitDescription {
// For isa, dyn_cast, etc operations on TelemetryInfo.
typedef unsigned KindType;
+// The EntryKind is defined as a struct because it is expectend to be
+// extended by subclasses which may have additional TelemetryInfo
+// types defined.
struct EntryKind {
static const KindType Base = 0;
};
@@ -97,7 +100,7 @@ struct TelemetryInfo {
std::string SessionId;
// Time/memory statistics of this event.
- TelemetryEventStats Stats;
+ EventStats Stats;
std::optional<ExitDescription> ExitDesc;
@@ -114,6 +117,8 @@ struct TelemetryInfo {
// For isa, dyn_cast, etc, operations.
virtual KindType getKind() const { return EntryKind::Base; }
static bool classof(const TelemetryInfo *T) {
+ if (T == nullptr)
+ return false;
return T->getKind() == EntryKind::Base;
}
};
@@ -126,7 +131,7 @@ struct TelemetryInfo {
// and where to forward them.
class Destination {
public:
- virtual ~TelemetryDestination() = default;
+ virtual ~Destination() = default;
virtual Error emitEntry(const TelemetryInfo *Entry) = 0;
virtual std::string name() const = 0;
};
@@ -143,7 +148,7 @@ class Telemeter {
// Invoked upon tool exit.
virtual void logExit(llvm::StringRef ToolPath, TelemetryInfo *Entry) = 0;
- virtual void addDestination(TelemetryDestination *Destination) = 0;
+ virtual void addDestination(Destination *Destination) = 0;
};
} // namespace telemetry
diff --git a/llvm/lib/Telemetry/Telemetry.cpp b/llvm/lib/Telemetry/Telemetry.cpp
index f49b88e07f1361..b05a7483c7408c 100644
--- a/llvm/lib/Telemetry/Telemetry.cpp
+++ b/llvm/lib/Telemetry/Telemetry.cpp
@@ -5,7 +5,7 @@ namespace telemetry {
llvm::json::Object TelemetryInfo::serializeToJson() const {
return json::Object{
- {"UUID", SessionUuid},
+ {"SessionId", SessionId},
};
};
diff --git a/llvm/unittests/Telemetry/TelemetryTest.cpp b/llvm/unittests/Telemetry/TelemetryTest.cpp
index fde7e31802f682..a2ded3cb5a2392 100644
--- a/llvm/unittests/Telemetry/TelemetryTest.cpp
+++ b/llvm/unittests/Telemetry/TelemetryTest.cpp
@@ -47,10 +47,9 @@ static std::string nextUuid() {
}
struct VendorEntryKind {
- // TODO: should avoid dup with other vendors' Types?
static const KindType VendorCommon = 0b010101000;
- static const KindType Startup = 0b010101001;
- static const KindType Exit = 0b010101010;
+ static const KindType Startup = 0b010101001;
+ static const KindType Exit = 0b010101010;
};
// Demonstrates that the TelemetryInfo (data courier) struct can be extended
@@ -58,12 +57,14 @@ struct VendorEntryKind {
// 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->getEntryKind() & VendorEntryKind::VendorCommon) ==
+ return (T->getKind() & VendorEntryKind::VendorCommon) ==
VendorEntryKind::VendorCommon;
}
- KindType getEntryKind() const override {
+ KindType getKind() const override {
return VendorEntryKind::VendorCommon;
}
@@ -75,7 +76,7 @@ struct StartupEvent : public VendorCommonTelemetryInfo {
StartupEvent() = default;
StartupEvent(const StartupEvent &E) {
- SessionUuid = E.SessionUuid;
+ SessionId = E.SessionId;
Stats = E.Stats;
ExitDesc = E.ExitDesc;
Counter = E.Counter;
@@ -84,20 +85,22 @@ struct StartupEvent : public VendorCommonTelemetryInfo {
}
static bool classof(const TelemetryInfo *T) {
- return T->getEntryKind() == VendorEntryKind::Startup;
+ if (T == nullptr)
+ return false;
+ return T->getKind() == VendorEntryKind::Startup;
}
- KindType getEntryKind() const override { return VendorEntryKind::Startup; }
+ KindType getKind() const override { return VendorEntryKind::Startup; }
void serializeToStream(llvm::raw_ostream &OS) const override {
- OS << "UUID:" << SessionUuid << "\n";
+ OS << "SessionId:" << SessionId << "\n";
OS << "MagicStartupMsg:" << MagicStartupMsg << "\n";
}
json::Object serializeToJson() const override {
return json::Object{
{"Startup",
- {{"UUID", SessionUuid}, {"MagicStartupMsg", MagicStartupMsg}}},
+ {{"SessionId", SessionId}, {"MagicStartupMsg", MagicStartupMsg}}},
};
}
};
@@ -109,7 +112,7 @@ struct ExitEvent : public VendorCommonTelemetryInfo {
// Provide a copy ctor because we may need to make a copy
// before sanitizing the Entry.
ExitEvent(const ExitEvent &E) {
- SessionUuid = E.SessionUuid;
+ SessionId = E.SessionId;
Stats = E.Stats;
ExitDesc = E.ExitDesc;
Counter = E.Counter;
@@ -118,13 +121,15 @@ struct ExitEvent : public VendorCommonTelemetryInfo {
}
static bool classof(const TelemetryInfo *T) {
- return T->getEntryKind() == VendorEntryKind::Exit;
+ if (T == nullptr)
+ return false;
+ return T->getKind() == VendorEntryKind::Exit;
}
- unsigned getEntryKind() const override { return VendorEntryKind::Exit; }
+ unsigned getKind() const override { return VendorEntryKind::Exit; }
void serializeToStream(llvm::raw_ostream &OS) const override {
- OS << "UUID:" << SessionUuid << "\n";
+ OS << "SessionId:" << SessionId << "\n";
if (ExitDesc.has_value())
OS << "ExitCode:" << ExitDesc->ExitCode << "\n";
OS << "MagicExitMsg:" << MagicExitMsg << "\n";
@@ -132,7 +137,7 @@ struct ExitEvent : public VendorCommonTelemetryInfo {
json::Object serializeToJson() const override {
json::Array I = json::Array{
- {"UUID", SessionUuid},
+ {"SessionId", SessionId},
{"MagicExitMsg", MagicExitMsg},
};
if (ExitDesc.has_value())
@@ -148,7 +153,7 @@ struct CustomTelemetryEvent : public VendorCommonTelemetryInfo {
CustomTelemetryEvent() = default;
CustomTelemetryEvent(const CustomTelemetryEvent &E) {
- SessionUuid = E.SessionUuid;
+ SessionId = E.SessionId;
Stats = E.Stats;
ExitDesc = E.ExitDesc;
Counter = E.Counter;
@@ -157,7 +162,7 @@ struct CustomTelemetryEvent : public VendorCommonTelemetryInfo {
}
void serializeToStream(llvm::raw_ostream &OS) const override {
- OS << "UUID:" << SessionUuid << "\n";
+ OS << "SessionId:" << SessionId << "\n";
int I = 0;
for (const std::string &M : Msgs) {
OS << "MSG_" << I << ":" << M << "\n";
@@ -167,7 +172,7 @@ struct CustomTelemetryEvent : public VendorCommonTelemetryInfo {
json::Object serializeToJson() const override {
json::Object Inner;
- Inner.try_emplace("UUID", SessionUuid);
+ Inner.try_emplace("SessionId", SessionId);
int I = 0;
for (const std::string &M : Msgs) {
Inner.try_emplace(("MSG_" + llvm::Twine(I)).str(), M);
@@ -179,7 +184,7 @@ struct CustomTelemetryEvent : public VendorCommonTelemetryInfo {
};
// The following classes demonstrate how downstream code can
-// define one or more custom TelemetryDestination(s) to handle
+// define one or more custom Destination(s) to handle
// Telemetry data differently, specifically:
// + which data to send (fullset or sanitized)
// + where to send the data
@@ -189,7 +194,7 @@ const std::string STRING_DEST("STRING");
const std::string JSON_DEST("JSON");
// This Destination sends data to a std::string given at ctor.
-class StringDestination : public TelemetryDestination {
+class StringDestination : public Destination {
public:
// ShouldSanitize: if true, sanitize the data before emitting, otherwise, emit
// the full set.
@@ -216,7 +221,7 @@ class StringDestination : public TelemetryDestination {
}
} else {
// Unfamiliar entries, just send the entry's UUID
- OS << "UUID:" << Entry->SessionUuid << "\n";
+ OS << "SessionId:" << Entry->SessionId << "\n";
}
return Error::success();
}
@@ -240,7 +245,7 @@ class StringDestination : public TelemetryDestination {
};
// This Destination sends data to some "blackbox" in form of JSON.
-class JsonStreamDestination : public TelemetryDestination {
+class JsonStreamDestination : public Destination {
public:
JsonStreamDestination(bool ShouldSanitize) : ShouldSanitize(ShouldSanitize) {}
@@ -260,8 +265,8 @@ class JsonStreamDestination : public TelemetryDestination {
return SendToBlackbox(E->serializeToJson());
}
} else {
- // Unfamiliar entries, just send the entry's UUID
- return SendToBlackbox(json::Object{{"UUID", Entry->SessionUuid}});
+ // Unfamiliar entries, just send the entry's ID
+ return SendToBlackbox(json::Object{{"SessionId", Entry->SessionId}});
}
return make_error<StringError>("unhandled codepath in emitEntry",
inconvertibleErrorCode());
@@ -296,10 +301,10 @@ class JsonStreamDestination : public TelemetryDestination {
// Custom vendor-defined Telemeter that has additional data-collection point.
class TestTelemeter : public Telemeter {
public:
- TestTelemeter(std::string SessionUuid) : Uuid(SessionUuid), Counter(0) {}
+ TestTelemeter(std::string SessionId) : Uuid(SessionId), Counter(0) {}
static std::unique_ptr<TestTelemeter>
- createInstance(TelemetryConfig *config) {
+ createInstance(Config *config) {
llvm::errs() << "============================== createInstance is called"
<< "\n";
if (!config->EnableTelemetry)
@@ -352,7 +357,7 @@ class TestTelemeter : public Telemeter {
emitToDestinations(Entry);
}
- void addDestination(TelemetryDestination *Dest) override {
+ void addDestination(Destination *Dest) override {
Destinations.push_back(Dest);
}
@@ -376,14 +381,14 @@ class TestTelemeter : public Telemeter {
template <typename T> T makeDefaultTelemetryInfo() {
T Ret;
- Ret.SessionUuid = Uuid;
+ Ret.SessionId = Uuid;
Ret.Counter = Counter++;
return Ret;
}
private:
void emitToDestinations(TelemetryInfo *Entry) {
- for (TelemetryDestination *Dest : Destinations) {
+ for (Destination *Dest : Destinations) {
llvm::Error err = Dest->emitEntry(Entry);
if (err) {
// Log it and move on.
@@ -394,11 +399,11 @@ class TestTelemeter : public Telemeter {
const std::string Uuid;
size_t Counter;
std::string ToolName;
- std::vector<TelemetryDestination *> Destinations;
+ std::vector<Destination *> Destinations;
};
// Pretend to be a "weakly" defined vendor-specific function.
-void ApplyVendorSpecificConfigs(TelemetryConfig *config) {
+void ApplyVendorSpecificConfigs(Config *config) {
config->EnableTelemetry = true;
}
@@ -408,15 +413,15 @@ void ApplyVendorSpecificConfigs(TelemetryConfig *config) {
namespace {
-void ApplyCommonConfig(llvm::telemetry::TelemetryConfig *config) {
+void ApplyCommonConfig(llvm::telemetry::Config *config) {
// Any shareable configs for the upstream tool can go here.
// .....
}
-std::shared_ptr<llvm::telemetry::TelemetryConfig> GetTelemetryConfig() {
+std::shared_ptr<llvm::telemetry::Config> GetTelemetryConfig() {
// Telemetry is disabled by default.
// The vendor can enable in their config.
- auto Config = std::make_shared<llvm::telemetry::TelemetryConfig>();
+ auto Config = std::make_shared<llvm::telemetry::Config>();
Config->EnableTelemetry = false;
ApplyCommonConfig(Config.get());
@@ -491,7 +496,7 @@ static std::string ValueToString(const json::Value *V) {
// Without vendor's implementation, telemetry is not enabled by default.
TEST(TelemetryTest, TelemetryDefault) {
HasVendorConfig = false;
- std::shared_ptr<llvm::telemetry::TelemetryConfig> Config =
+ std::shared_ptr<llvm::telemetry::Config> Config =
GetTelemetryConfig();
auto Tool = vendor_code::TestTelemeter::createInstance(Config.get());
@@ -507,7 +512,7 @@ TEST(TelemetryTest, TelemetryEnabled) {
Buffer.clear();
EmittedJsons.clear();
- std::shared_ptr<llvm::telemetry::TelemetryConfig> Config =
+ std::shared_ptr<llvm::telemetry::Config> Config =
GetTelemetryConfig();
// Add some destinations
@@ -526,10 +531,10 @@ TEST(TelemetryTest, TelemetryEnabled) {
// Check that the StringDestination emitted properly
{
std::string ExpectedBuffer =
- ("UUID:" + llvm::Twine(ExpectedUuid) + "\n" + "MagicStartupMsg:One_" +
- llvm::Twine(ToolName) + "\n" + "UUID:" + llvm::Twine(ExpectedUuid) +
+ ("SessionId:" + llvm::Twine(ExpectedUuid) + "\n" + "MagicStartupMsg:One_" +
+ llvm::Twine(ToolName) + "\n" + "SessionId:" + llvm::Twine(ExpectedUuid) +
"\n" + "MSG_0:Two\n" + "MSG_1:Deux\n" + "MSG_2:Zwei\n" +
- "UUID:" + llvm::Twine(ExpectedUuid) + "\n" + "MagicExitMsg:Three_" +
+ "SessionId:" + llvm::Twine(ExpectedUuid) + "\n" + "MagicExitMsg:Three_" +
llvm::Twine(ToolName) + "\n")
.str();
@@ -544,7 +549,7 @@ TEST(TelemetryTest, TelemetryEnabled) {
const json::Value *StartupEntry = EmittedJsons[0].get("Startup");
ASSERT_NE(StartupEntry, nullptr);
- EXPECT_STREQ(("[[\"UUID\",\"" + llvm::Twine(ExpectedUuid) +
+ EXPECT_STREQ(("[[\"SessionId\",\"" + llvm::Twine(ExpectedUuid) +
"\"],[\"MagicStartupMsg\",\"One_" + llvm::Twine(ToolName) +
"\"]]")
.str()
@@ -557,7 +562,7 @@ TEST(TelemetryTest, TelemetryEnabled) {
// entries (for now), so the "UUID" field is put at the end of the array
// even though it was emitted first.
EXPECT_STREQ(("{\"MSG_0\":\"Two\",\"MSG_1\":\"Deux\",\"MSG_2\":\"Zwei\","
- "\"UUID\":\"" +
+ "\"SessionId\":\"" +
llvm::Twine(ExpectedUuid) + "\"}")
.str()
.c_str(),
@@ -565,7 +570,7 @@ TEST(TelemetryTest, TelemetryEnabled) {
const json::Value *ExitEntry = EmittedJsons[2].get("Exit");
ASSERT_NE(ExitEntry, nullptr);
- EXPECT_STREQ(("[[\"UUID\",\"" + llvm::Twine(ExpectedUuid) +
+ EXPECT_STREQ(("[[\"SessionId\",\"" + llvm::Twine(ExpectedUuid) +
"\"],[\"MagicExitMsg\",\"Three_" + llvm::Twine(ToolName) +
"\"]]")
.str()
@@ -585,7 +590,7 @@ TEST(TelemetryTest, TelemetryEnabledSanitizeData) {
Buffer.clear();
EmittedJsons.clear();
- std::shared_ptr<llvm::telemetry::TelemetryConfig> Config =
+ std::shared_ptr<llvm::telemetry::Config> Config =
GetTelemetryConfig();
// Add some destinations
@@ -603,10 +608,10 @@ TEST(TelemetryTest, TelemetryEnabledSanitizeData) {
// The StringDestination should have removed the odd-positioned msgs.
std::string ExpectedBuffer =
- ("UUID:" + llvm::Twine(ExpectedUuid) + "\n" + "MagicStartupMsg:One_" +
- llvm::Twine(ToolName) + "\n" + "UUID:" + llvm::Twine(ExpectedUuid) +
+ ("SessionId:" + llvm::Twine(ExpectedUuid) + "\n" + "MagicStartupMsg:One_" +
+ llvm::Twine(ToolName) + "\n" + "SessionId:" + llvm::Twine(ExpectedUuid) +
"\n" + "MSG_0:Two\n" + "MSG_1:\n" + // <<< was sanitized away.
- "MSG_2:Zwei\n" + "UUID:" + llvm::Twine(ExpectedUuid) + "\n" +
+ "MSG_2:Zwei\n" + "SessionId:" + llvm::Twine(ExpectedUuid) + "\n" +
"MagicExitMsg:Three_" + llvm::Twine(ToolName) + "\n")
.str();
EXPECT_STREQ(ExpectedBuffer.c_str(), Buffer.c_str());
@@ -620,7 +625,7 @@ TEST(TelemetryTest, TelemetryEnabledSanitizeData) {
const json::Value *StartupEntry = EmittedJsons[0].get("Startup");
ASSERT_NE(StartupEntry, nullptr);
- EXPECT_STREQ(("[[\"UUID\",\"" + llvm::Twine(ExpectedUuid) +
+ EXPECT_STREQ(("[[\"SessionId\",\"" + llvm::Twine(ExpectedUuid) +
"\"],[\"MagicStartupMsg\",\"One_" + llvm::Twine(ToolName) +
"\"]]")
.str()
@@ -631,7 +636,7 @@ TEST(TelemetryTest, TelemetryEnabledSanitizeData) {
ASSERT_NE(MidpointEntry, nullptr);
// The JsonDestination should have removed the even-positioned msgs.
EXPECT_STREQ(
- ("{\"MSG_0\":\"\",\"MSG_1\":\"Deux\",\"MSG_2\":\"\",\"UUID\":\"" +
+ ("{\"MSG_0\":\"\",\"MSG_1\":\"Deux\",\"MSG_2\":\"\",\"SessionId\":\"" +
llvm::Twine(ExpectedUuid) + "\"}")
.str()
.c_str(),
@@ -639,7 +644,7 @@ TEST(TelemetryTest, TelemetryEnabledSanitizeData) {
const json::Value *ExitEntry = EmittedJsons[2].get("Exit");
ASSERT_NE(ExitEntry, nullptr);
- EXPECT_STREQ(("[[\"UUID\",\"" + llvm::Twine(ExpectedUuid) +
+ EXPECT_STREQ(("[[\"SessionId\",\"" + llvm::Twine(ExpectedUuid) +
"\"],[\"MagicExitMsg\",\"Three_" + llvm::Twine(ToolName) +
"\"]]")
.str()
>From 47e8b06e52aa074e1d8047fb5ec3332829f9cdb0 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Wed, 4 Sep 2024 12:23:24 -0400
Subject: [PATCH 12/61] reformated doc and added additional details
---
llvm/docs/Telemetry.rst | 24 ++++++++++++++++--------
llvm/docs/llvm_telemery_design.png | Bin 0 -> 94299 bytes
2 files changed, 16 insertions(+), 8 deletions(-)
create mode 100644 llvm/docs/llvm_telemery_design.png
diff --git a/llvm/docs/Telemetry.rst b/llvm/docs/Telemetry.rst
index 997436e78735d0..bca8eda9d13d11 100644
--- a/llvm/docs/Telemetry.rst
+++ b/llvm/docs/Telemetry.rst
@@ -8,6 +8,10 @@ Telemetry framework in LLVM
.. toctree::
:hidden:
+===========================
+Telemetry framework in LLVM
+===========================
+
Objective
=========
@@ -18,6 +22,7 @@ It is located at `llvm/Telemetry/Telemetry.h`
Characteristics
---------------
* Configurable and extensible by:
+
* Tools: any tool that wants to use Telemetry can extend and customize it.
* Vendors: Toolchain vendors can also provide custom implementation of the
library, which could either override or extend the given tool's upstream
@@ -32,6 +37,7 @@ Important notes
* There is no concrete implementation of a Telemetry library in upstream LLVM.
We only provide the abstract API here. Any tool that wants telemetry will
implement one.
+
The rationale for this is that, all the tools in llvm are very different in
what they care about(what/where/when to instrument data). Hence, it might not
be practical to have a single implementation.
@@ -40,18 +46,21 @@ Important notes
* No implementation of Telemetry in upstream LLVM shall store any of the
collected data due to privacy and security reasons:
+
* Different organizations have different privacy models:
+
* Which data is sensitive, which is not?
* Whether it is acceptable for instrumented data to be stored anywhere?
(to a local file, what not?)
+
* Data ownership and data collection consents are hard to accommodate from
LLVM developers' point of view:
* Eg., data collected by Telemetry is not neccessarily owned by the user
of an LLVM tool with Telemetry enabled, hence the user's consent to data
collection is not meaningful. On the other hand, LLVM developers have no
- practical way to request consent from the "real" owners.
-
-
+ reasonable ways to request consent from the "real" owners.
+
+
High-level design
=================
@@ -59,12 +68,11 @@ Key components
--------------
The framework is consisted of three important classes:
+
* `llvm::telemetry::Telemeter`: The class responsible for collecting and
- forwarding telemetry data. This is the main point of interaction between
- the framework and any tool that wants to enable telemery.
+ forwarding telemetry data. This is the main point of interaction between the
+ framework and any tool that wants to enable telemery.
* `llvm::telemetry::TelemetryInfo`: Data courier
* `llvm::telemetry::Config`: Configurations on the `Telemeter`.
-
-// <TODO insert the flow chart that shows the interaction between the classes here>
-
+.. image:: llvm_telemetry_design.png
diff --git a/llvm/docs/llvm_telemery_design.png b/llvm/docs/llvm_telemery_design.png
new file mode 100644
index 0000000000000000000000000000000000000000..0ccb75cf8e30b662c3dcac0faf009bd0753df7c8
GIT binary patch
literal 94299
zcmeFZc_7s7zdt<Ea(9=6N)aj{WyvmMsca#XeOF0#l4b0pR47Z?BL-vNx3P?63K6o5
zZ5W2EGmIt67-r1O^KsWXzw?~ue9t-0|G&>a^~YS-^}gQw^14Jm(9>l3o9k~72*jdw
zPt5=XIs*Kc`O9BNfq$GQhy6jIP>`0|9V0)RmGmQ at jf@%Lv6kMD>zA+CKMH+^+j!FC
zTUU;w)qzhpo^AZraqg_TnyZ8`_;zb+=oW0<N<0w%+5Fkt^hR*wmLjZw3*C&L97~r%
z&Fn9}mH<xpzu*7m!2e$kJP=<(Vl9ZSQm$dTeO$=Cx8zb~*JdG8D<1-wcC1t}7xRMF
zLL4aU at o<}f?DI=MoWAnUH=0z6t)WS+z-rdV_T)&GNklovB8o(qsqNPI$fyPR<z<(0
z=uW1w$+Xx*ltZf|GYItHu=E9K#|09`3exu8^n&C{t}^XtNFi%?+~DAwRRwR0`q}HM
zD~l5=j%1~6v%v43`Qz0ZRt}nzezDCPE%_^Oag+-zhz3`0*;){f_~%0`R&%IW4y*XD
zB$DH3NjEf<QdT-I(xCWpTCDt6cha==>UGud at jmND@%!6>uJ29U79s0s;>6jb<@D~q
zi%Vp)CnS4Z(+NU8&%t^C>wMl+wFDn4tEN~Gt>qql`x+1SUfy(3R}XvB!)hk6ce#MI
zz1ThQ+NZHJd6L6=+Znclud-$DM;Z*9w#H|tuShj$9I`ovHO81t)H;p?+?f;9r7w@?
zujd2I^tPn#tk&sn=UX)JhybqOZf4W;m$TvJlHOzd&6I6g-kmm$6O?Uk$GYiYmB7C5
z+^eeM_iH@*Q}Dr{-&{Y}DBdE~8`HGcGVfY2GHL~R4DX0$I1Sa(R_v3B<e)U))gA5a
zIxLdsCa^zOmiqg1N~Y#^V{dv$eA}Mz4qf_%irJs at H_0w*LG3YV`|0GYx4$_#zW2q8
zW97Jm^%Kna^XjK}E3Cuyrp0i@{j2u&_Cz71U9LOhBnWiLqsDE*zu~MYk7SWoU9JPd
zdC2!`(!}bM{>Is7)WOD~W>yikf4-8I_`KdqxQ2=ctMZ~z<9_H_hVyX!2A?%jbj!%e
zX~`tg-9oi)8t{xCq(l|G3AdgvjJU5&x-ETj!`9knBCflK{B+9O{-bTL?51|dGM+Df
z^UXC5r;leohO2;m*C;{R#M;8gPcMN$^Y_A(X~%d6;SiXxs70+s#aza*58JB$kRozZ
zhMSnMrGaf>Hra)14dr=RBAg&T*(#X+)t^itkVv+1 at hRqHzWP}X-Beks7YQ+H>GyR6
zzVcf9Spc8pZw8r}#e^)mHTHki6n7lSTjdkAtlO)psqwB;lPhmsB{P9;e;jPEtYe4_
z1vYd=vc7}vsO<Yuhj(L}SUG-kL?lQ^7v>_4A~AveNHS2DvHGsbx6$0Nc*2g4H}xf8
zAQz3cS8(`(Q|ZY4vEu&C6Mgmb78=yczYUI`FJwcsaaAm^-(#Qx&8M4&@VkU&T_O6O
zWCCS|7_{79R~ACJj!=@bTs$g%ErT at z`Pl!E4h>ppD__m(Oy7aT=jlRSCTqswks&jn
zI{0585GicaU56}RYfqHjdRx{!^9sz%O$@$v>7P at d&`2cxkP6L=7k$$JZ!p_G^Vw<b
zcicPKK%nJoJV;-qOkkatiLDFmNS(@NHu-Ys?=!#6z3zurb!PZ}wbf0PVIUs2X+VUK
z(YVQwt02%YTdIr;!h{-=o=@VgADos`*^T1d{>PZuD*brm_P~wgubK=KbmmOCfW19l
z2>G!W7a{-xoe{$|dfP63t6f#t4+acmWkPDv|0VD5l at FD{8&ZuMCKG*P;^n)y(6CAI
z83T7A4iM<?@G8CZ#*FD4r)m{eh2^JU`Ve2!mzdv_ru0!WLU21PLl6^n^)@*c?l-_W
zywO}TT!Tor#`5s8=C9NGE%c9^-+v#&CRKRhsSpf28+(+6yZ45$aKlYB&UIx+1rWoc
z6E at 2W1GcPn-@@#fPR$<i_$~T&tKgaRLf>>2IYwc2_E$_~2wuTm^9+y_L})~Fm5wy2
z_rSu}D<)ngtL%2482e3F#K^|G30_L0Q}r`1ZEbBaU2j$HdK!>aM8sD4 at +jD#t9;T_
z4%A8fHvexkS?sA?hdPCyv|R7p+<X_yK|`ONJaru~4%mVP!X0ei>mx_eQLVy#iTZuQ
z`EZBmdV1ZvcSpTAXoGNE%)xIX1$9$bM-Et}cgrJ(Gvd#!=X`(ccGXfPm7v!|Zv<XN
z>$<J&STcbgbqRZn6ye;`8^x3uFY$tYOy~c|F)zrgTcmNp7tAoTuuzsk+wu^6A2ES0
zL6EW at Xy;8VxBCvhzH2(*CFh?7vWnCy?hs+|@|1K!-P)S(Q{cyeqt1ghOHKTG3NA0X
z9siKxGI0zBtIt&LF_Ge+VZ1oR7JQunN+c4J<t9~$G;0Xi&L&j-kI|j!Y>{i*v8r7s
z=jP_JUPV^3;OL?tP)?Rs^)q~(%k&00+$M!^#N>~l?s~OyO08Cq<o3#HN?h+|TTV=^
z;tzpt3!Lq>s0s3GV7jqSZv`7RhravGLbLB{o?m9L7oTcmLnnGxe;opaN<^p0c+jy5
zzy&+GVKl-KbWzaL74S~Ydkw4->)3c1YD2pgQ|0DXx7O<x_BL16Q}@r|IXI-8)>!K)
z>*6|`Z$8WFLm}9s@`f!o>aymvUvTfT-{7s}KH(cT7|NdgA%N#Rht77YKDX{(iR**j
zPnGRFk at 4`r#gwD)o{gkamgA>*(ga>XR2w at p?04dHqJp;{+NsdT#+-(t(p9F-T>h|=
zIbUDSxBHBpZ4i3Q(p_R_%r4s60jE)`75`$w<ICVqCOoDNqCzUduv)YCx2(MNx3^ya
zE^J at Nd=m5^s>THnP(FCg=jVQ%!;h_3&j>wyLH4Y-b`DQe8lGs}{Mq3<f6p#okHoC-
zTS=dYiCH<W)O&XDLW7tm;0{uauG1Mi2kv<TM*m6D_3~_IfzV7$Oq6e+*R6+E|6&H6
z;YjC_ahJ41wZ$@qSQ4`41fdQ<j_`b<$j7b`GsUv1$+!2F>-TjeY at J{KG-(5mjk;Nk
z0eS;&_!>T8+B*tFyzN-PD#qVly#zWVyb5fnxH>SWN9|$xki}``Q at lFdkce|x*VyS1
zr)B<|fh5C%^VA-hPBgf11}?Ec0)t0gn-|M8 at yW@i`=1HNSbf1AfCv3F8l5Ulb49hq
zq^%#-*y|3ronVoj7(2x^JRHNfe(dod at uqGrBy&&jDosqxfLD?}=PvF%uJ&w?COaxr
zj2>QD_x->GTITQNmUZ9!T1MrgG;*I_!0ER<Y4mmRU{%Skq!Gnce*Na*mC+g$-Fd}^
zx>Jh_+^&gj-ps%x_jY(`OzrBJoA0qkhIo3^!2$8W9LEYg<LhcC++fp_x387A*a7jo
ztT31`!!ADdZ+q2Ztfoh5*9WGi_IAz6_0yvBKg+#vH#h%g8cFxmtZM`I;k)KET(B|6
z?JB#U?A=JS@#}Sqyo7CUp(FVo{ZXS6-!%D%k1Xrl+&rM$0_7fQ(Nvy*$$tBV1rH%g
zS2Qk03UoU?uc_(lS!R>-iuhjP>ZYHN-t^e99iCJytI9U>jX(O4wYM9;>N>IGwn|7A
z^bekNwSo?nhKK-OM+9vx=1J`b^0YYM;|Ke!Dx0PS*5A>0p(C6yQ6HgyY%;&e-pFC1
z!P4}fjHJa~P)Lt9zQa at TYLvO@$EqqbXjq*-RO0{M->yS{=THXwE9*wc(_WR|Vk(K}
z4PKG-?Ip=pEhQnlb13brSjfIG+C%yK>OEko0L4VwQJ!2TWV3T9ee!v=?&{-kxs5~D
z{!nlIYaJDB?RF0m*skRe7(C?~v_C?2QxDVdVyEFRyN7duGQ|d(?u5NH)Ndpd?YW1>
zA1EJ>d_e<kOJ61Q-(+&uA;|ohCBpqNU*LjL`FdJ3zX#GI1dvHxWQccQogEA4!FLhk
zhv=V?fG){)vbesOoL_hC>~k|Cv;Gzc at eQZdWbY~Kz~FL5T4~_L=es3URhy>lv?YVO
zFktzIgoK38*uaL6>Y9Q=?&0~Ca9$@ewz!b<<{yC)vs3?W3Sx-uRHdzvC|}0qohVk|
z=iiJG!r$Mx^KpYRPn*L%<MX^=fkZFy%&vpbRqKpv582D*`}Zx1SfkxtfXwO at hP7|>
zAULJTx+sIu<x}5R9Ra&$7rlO+D at bW`kxGBTY)<&v`S}Oo-^2g`+=MIR4sK<kPWdP4
zO*IJ_$jfLfDcPs0F_6&{Ge<$!{?hL0S?6n+TT^Ld5;0Ou1^YVNt5CkPCj1GYKDdeX
zl_x+v+(dvDb{f~g)iot6Y<2y>?f+UC$udm-#G%aSI$@KA!O>Ezofgj7gsQb8?R!jm
z-DZ_y>&*cEuOQNkTt|?F5asM*Y~+ORX|GBj;@{=gmlt(t)bDOyW}o|D<^3h+4x*zC
zOx|+(vt*C&$|R{%Z35c~Boe1_-90xJ6 at N{>e&hc09g-BoKqOn6EFZb2R$?hQwKYZM
zMjNlTyMc|h(pp`A<mz6|i#H4N)Q)N`pkBN=!24p-$p+l at 4G2;74`?fD6BY`C<fEVh
zrS9w3Gp5#m%{^;m>OrV>CiyNKsmNb>f*Txo*(zcL_MFf&9?ZMR?d4<`v2=Cx(Pwt}
z&1J8CNCmU0#WxqBhJY_0g?@<LxDlv;WdPfW`mT;O5cN6o=_^Ny3*fc^6TZZ$_7hPN
zW at Ksa%$p}h^d at T!6KjNjbXZ}}JW+_0IiKHtEsBDNb;!C*{b<?$YHDNIrB13osFn<~
zgICWig#m^lzuMj1?Jz}Zzj0#n_Cf&mjp;a!e2FQ-jPGA>>TT at -XWAL$UfaQHmrZRD
z#*RdW)WwCC@&|i at LQ~;2HQQ at E<S~Yj-0fU1GL2l9Y&CmKU9BXLOW{Gv8)qT+i9&7c
znP+xlZhuRpw{t}qJ~@Nr^=iGo^98QAiS3T7C!Gr1H#4wMxIV~p3{)WTR>o~~AAb;c
zj^ulnHO`!-j at gAg0CXTfi}{Utf$wZ>dQpz<W7);QywgKkC12$QG_r`Ib*xp~9u1`!
zlcaWxGJQ(cZPe8_JDnwRyNe0*BtNI5X`i-L*0dlechlH at T{(SazUzP7^K~5(3sl1a
zZuVMAbAv9>8|4LlH_)R=@3wn^J?$C1J!0fi?hof2!ee?@>!vAp*w}~}Z%>_hmUW<%
zMRBrk##40C?%o%wJUW+f{!Q7CgLEAa7(xQ>OxcO at uO$vDh1{z_SYHYD0)>t_np>Fc
z3^nVcGCb#E3v|;=#%x12!meByRbt_}_Tw&sqi)qm!0qp9iDcO~DSA&>I)o+uw#NSu
zt^-PdEv&Xr6KMud7rs%vD~q1u(xebTpvvcH4NF&AG<Srgxj|Keb^QSAyZ=^uyp0)k
zIXUevH%5F2Sng{~yU>ry3ZUClOGZH8Cyz(M{O??gf6J=Y9sr-}Ukkj5mPxMH|4%03
z-N6RQ at LN?)BP920Sv8WfGk_g38eS07cYgS9!=!^QT^Z#jh4 at a-MU*wP2mdF<$df9o
zuii+S2uZHrX?P#73<CM*1p51bYf7F<UpoSG{a-AXdba;G`q1aJ`j82AC0|geqUVqp
z^<u(MAL=0sUql2+qZx)H%V)cYdBXmafR>`9)?8b9PjMabo5|({gKqQQi=hVsRa$dq
zd}$sRU&LI;1+cSGE{EW*j}?@mMEVR~`$asHI+T~IycKmJgr`J_58AYe at c(rOWkuhG
zS2}!tymHR+eB&CjW38#W(gEhSl5ERG-I*vXU;liIzUQO<-_oMEwU{4u=x}J0)RVMy
z at _sjqe1l`r#!Ek9>7Ab^t?kdEVv5<N(O$AsW95d9QxMx_saW1s%vY at 8s#uU)l6i+X
zOmTed6pw*z+dw2ov=V7}*p3gnVhb-Tz(Wq$|6iJBp41v3fpZg%nm>n50xURCOIgin
zvHGuqoVqf~r}4c~wGZLl?Og^s7pUJjLz)Y~YbmL&4K!brUY6FOii+L@&}0B{7$aVj
z-8#0nr3HGjQTSik>|=$w7%<#6HRoZd6bISK5d<pgigYPyL`%yJ%oi0d9?MP}0F$jy
zdIfn5SvL#wI!`-0=-#(-Q^z=vh^v$Us1Z^w;xW=aCO36+hgcr=@#S?PrigEO|E29M
zl|<G{0bvrQH|e0l40^zFU0YiR1^qcLNB0c7UzX||a!Gl|%z5+az$KI(p^Y_vbVR|`
zJ^gON;YYYy|E+FJnb`eX<uMeVf2BIW{$8|Z{e5c_8 at ki1jm`y7sKC?YsbH!ug!xCD
zW6|ErU5sr<ty@~^r4yPa4cBKlOUd)`A?cr_!d_48;kg4B5<6lp*sbtEcS&~<4PmlN
zAEu$U{`+C9QO$J9Behv(*7M-NT4-5u9}3TF*RRk6lm*9SBL+6C*h9I{y99WL&GVO)
z_;iPrbO;yN{++FOk?h&y(C`MYFgv@>qtLL%HH#`;Vego@>SSA9*AUN{Hyz5`A1bFq
zh!_^yVp=yoMdpTl?W32K`YLWf<LW}-x3e8BJE>3`1t@)djJnQBKc%2wj?f52k_PuC
zJiZb`MvT8!6Mcp)g58 at R(DY`!pd{mkjisS5JKa>H7GGldI-_(%w$;wS4cn|qz{8Gl
zV?KN7{c030ER`jW7K at U=$aPOOjMc32fAVTW=7yg6s`+JDHa|mVP{q}Rq@<Q0p!KuZ
z at YP;R+JE=9;_lDk!{bi*dp-&6E~6z?dX2svbQ;9IAx^B5m%F6rTyxTJjjlk|q)Yc|
zU3p1em*5?Cm+Ibq7s8bG)dswdK!ct_7YgE3SgFZ-;p$9w=a2vh!8?oA%rA>PZycFV
zo+kIlr~b_|$}Q14)^<uq3DU5z?^U!3gGMQBv=_ltiPCFG7yHc{%U at n5l1|;adJieu
zSOSf{tXgD4d{|ycPh#QP#UmUauLvaX;$JWOJTNG=*I`(Vm%}Q{QEM>MkuyK5BpF-}
zHDgXL+e;lW>)hr-Vm>&_h4b;22oW+&hnD>($$o<C7o_ at p*RV?8+<Ufbicj)9lbd>B
z8MtGQnW)R_+v)46i_hyvKon(t(fvU6l@^r59HWRXPV?}9&VNgQk*Mo6n3p!qcYkeK
z^4+6bPi08--{>*<Cy*?})f57m-x5N&P!=&IXo=<MZy9c(cd<_SdlFh|gQ?|@7}c~g
zo8}<m6a*nNT1j-RX)rCX4O+r2`K~i~?5W$_DM4-~Hf}6|t)Dn2w1yV_e13<fK7mM?
zPZ#S4uiIR-c>HVGx0EDTd5n(=-FXAa3&6K$akI)!1cc95H;^`0gY{AF)4MC3>MHB?
zc&6IGSR0$>Cm+tgS&HIc%h{UW5=%=apj4iKKuMv>XIZ#<Gk^}Sc)duZAS+)4*V#Pc
z>pUGZrOkWJ>8h-yB8G#`L%Ou~cg1lcYd-MfUlL9AU={ButtaR*J5v+ooCuxdVHoak
zrbeiGyn}Hei52GGfr3gi)nlFM{@PJUyiqQ_Y;AApqO8chdiDNj`YB>*vVzYj!Z3dr
z?Q4MHR-P;;27F<nd-YiRZV-{Ou7udTQD7!{Cxa2Kt^!N<C5Enxg;F?`w?9WgP@&uo
zU)FV4*O77d=!8Zzw@}YEL;HL^esL(#gvVKKY(?dLNC*&H;>j^!w*1$_-NogfUX+xS
zOuAQ%_}bvGCg~05L at fmvV%jOlh4G>-9FXkk4CdJcIaq8qC6!L()D+$cGp*K5lhIG|
zu||dm1Ksu}udStnzHc&7<=TSTdy~@DRHEC;NN;}QIy7Z<pirl~(mp^mEr_qd$E6J~
z6#N+Mv*do`I at ee!1lz{zJ>2DskiqD-h-n*$X}!30kZ1fOOjk at +yxTy<L+y)Picdu+
zigYLDX{mLNP9F7St at Wm$fMH?$7*P5p*ir7@?q3}3&Cj<DiWU{UQEfM!R!K-{8NMN2
z{%iSMSo^j64i`Oz(#wPQo2LR%f at _$sKx^y!w~4hXh)o_tJ3gGX{mqH!)p04A3!8o5
zLLcd65ewyzAp1{wOBy}Wtt%be(an3$?7~m!iVkgK2?s(R at WHV3HP0NiqweU6QrDq|
z{m|LS;mys>N5inZHD~EmaI-n`_?KQeeNm4g!Si?W+Xst^^OBs-d<F~1r-Dw7{G}ye
zKw_*-S6!E}Z^+5n<f5+F0gaHaj!Lu)m$FX*th$hx447DCA?EH7v|mxmM~bCz8XQ2+
zP0mZYA(8(uxo(Q<>P5JHstJ#hX*s0E3Wd^$v+;_q1h0Is$io<E&Hb7zVh`nZJzaAo
zdj4b&2}?)``t>z)Y8LR at pRkP(=LU at wn?Sdl#!Z%W!&MXRI<#hI3#BkXi=L){Cg;BJ
zH&p?- at FU2W9pmZVhsVUgg}oZBp=ZL!zob^VknNieKe`++byiM at uA$3`Kp;PW4l45|
z$zXq<-AD&)Zjwd!4x#yTFwpla*K0A_?AHa2zAi3DWF&(`u0pWX%=zhQ-Jq4K{;ypa
zw|P-=ApWW3zf0aIPFG?|Nld&xz)12|fw(}1|CV97qV2&9Cw^Uy>^Hd<@@1wA<s{8y
z_5-M9(mmVLi>y52MN#)B*Zr75ADFTGc8eTy%4US4i2 at _@OU!89Y)39<v%JEOX!KL<
zg4gyIoO)WHD(=H~>HF1HR*Z7H$9onoxl<Tjkb9%s at ->lYgZFx?#+%myTENz3L&5L`
zi~fOhJ#Jl4%kC3ra|XSG{!4XPMp{7DLX#htz2JW6d92d)d`%S9-5K|7$^H9mMzK at 3
zrR(FBhnhzsiwX at 5djJnWuSKI4^i^^N17y<Wt=1ow8+OJd+A|-`DZ>>^Yj(9)SZOs9
zngdBiB8HZpFkQF`ynkn<I?>)(0Hg{B>$@amSgzIL91jho(}M@@6ylw`{kJK_<!iPm
z!4awY^^N;tVoc|Y=3&c|n*-H0<0*2=waHV@>zd~&jd6_iBpuxwO>WyKc1qYV<HhUe
z(=XyUlbixJ;>)2=x#gUmV9X%!dB%;6g2Tyl+aWDDIbbiWJdVKtW<haUm1%r_$nL9y
z#eBhbKud9BKDZpYnw9V{YK8>%U3zw`L|%T60R)3&kwE8t&919Sfl5B?q(++{9M29q
zd79KM<7SRIzFxY7OtymL2M4qY+2E{(r`4ehU^*6vW?)7RKO#UOI3aUQ!yh=wW<AP;
zHPBabV)SK^WBf(;ur?CQcc_z=u>6E2A3}rcbPfgD%&Sl)yS+bQcy46Ke at 6Ot^u1vv
zphNocDYy5!kc`jXXgEw~$~01vjNp+9dGSKpWnzaXp3E{)jE$j28LV1jZZWzoS7`k$
zGl|4o-aO6v(uwQXMqXV(v2$cdS56*i$EFMh+0Wt>6ts<RW>v=C=z022F{#7uBH2)X
zI8$+NvL}u_Qea(of1N~J3;B^Dwb?OmaKYWbVD{}4isJfQQ%2=r4)JxdE&llJ=GeCW
zv)?iuS02QP?VIy^|1->7$qOvL=wB{wuoe{Ly4gaBx<DJuNqAvx2c+Kj>F)31AO9Gl
zk at gmC^gRXlONM=LxuDkmrFjRhY}a7|o&%c99f$EPPb}qP%XL|<z7KT?*0A+nUEPVq
zC~q$>sy;cBClym~m>3E55WglW4tp|o at -XPdBcgqP7`T_^+6Wpjj*WPI0}cVD0HdUa
z$igB0a*?5gNykqbs|}dZYgH9i{F60b)yx(JLDNA)Dz;#b at P$+)7cb^omE)#{%Z~=U
zw!nNgIYJOb#@;}zB%1DTC8`Y}+7vAK^tr8HFQ5-+#yQU{@|W2{(~;)t!?sYForPpW
zm^zWyIbf at qoag?*AQAWxy>IP|t^bg2A$dSURH5wSTl5 at uw@t}ZtCO(4hl70e6qwA$
z^ZI2%%h+FWJuVO))>hN#nY|A}@|zCPbTy*?)`)5$+bkoK5Lp at M;8+LAG+ at uiWeS0`
z_3!GPf<Y2ew}KxSrnRDC=};<;tYa?qfQLhu97X4mA^nPo at 6$_Ck=#j7kSx@@jTHV;
zI#Huz10AEJ<qWS6x-BKdBrBW=FwymSH#*cG4)ZptieVRD3m&;(^a*^#?0Cnj at AyPg
zLQ>watwUR{0egaGMv&fSJ-f8F<6Dw63-QC8o~4nvDZ*s<Lb6rb=hQ-{QsgsheGo82
zTzv}6fu;k{5;Q$3Uq}-fxuo#;g-MargvUU)u+KKu at o5@e!x at T}ywI|?cIM~H8|A5=
zcZx7_7Pf|mN4lbww<_~(KS=&9<v*>KhPgIgp;x^qYS7(AHU=(XF>SE^#xYnoKE`)o
zqygjEOm}9WkpYVYQk5~3PvEb~IqjfjWH>JrX_};!j<4luw=&{>F=6Zjlu|~n7=jnX
zx=BjBxi3(hNaGIq%8^n0kcd_=faz<y7wVNO_v9zIDFjjHJt2Qrw46rc$0YL8Vf#&g
z5nWzqv%8 at khB%K>UVUv|=OVh+6W*WD^@>S)!@oPE%QQ-A%N4jw`cx{u+NwZlz0)a^
zZq8o^_aS{7jw at LTps1S)^_#wuq12gFd3!Q=%?*6q>Uu91S=YM>=uOlTe+AEGxNw6)
zMH<LsL*n~iSp)$s-e8tUjrhnRc(@ZX-MpE?t^x}- at sE*hj>v$&e(xI+y)5`RTT51s
zDaE~W(r2_#1moEDt3K`)+AC4St3T1yHr+xz6=W5%*FA?VQ5(v_Z7pnc=G*WiW2~i_
z3Y==!8kW`;ik6yE%aGfHA?5{r7!3gr)T*te21h8 at 5<aO%Uq!b$FHeD3pSP?LyIv1x
zD6BO&IHx~91M_u?;l*rQkXznM^&S5C_YEUnjQ4Z$<Xyx5rIt>qPr8S0-Y1;24h&Q8
ztex^C&#Me4ju|*;!sA;JF}^tR2X&Hkb8T>E-wxfOLkK#0 at 0yQ>ciCu7vGd_a#>a2_
z*wn-ft;V(_+)r>+C|q_DMO91*<83n(YqzEtmePlaYxy?1z8%{^T)BvPworHAuv5Z?
z1;nMjn#ZpcoGG+cnW5IhnF at wLmtS%(h(Z;^RYNvgw0+-VtPF at chaO!2l*eM)3L8oC
z&Nx?u`OX>r;vwEB{U_ll7q(rMXgK9JZP*2q3D6i?@h;QoBbXWUgMgxDBrnN2B(#}6
zB@|cwNkgEBg<xhTem};o=9_HSLnVbgjA6Ph59Xc`4{(jlG?39<1H4=1s$Lgy3DW$t
z$TA+vfk>|4=Mw~x<m6XgORdb6x4?p?OX|Bn%zG-C`t>ds$d=^1#ujQ|kPDywX_R_t
zJ<$~eiu2s5awH_BeUM-mtSqee8jhA`e}EaJ`nBR!1Y(y?Fs0`|%~I~%&S0 at i867c7
zFVX<z>42^;5>hlS-#Fz|Q{EEspyUL{J7A!i`FQihhv&mEjdR2vd7LRsN5N#|uiJ7=
zehUT1;@AD%4&|_YQOJ`o=ru?<9~?A!tjq54p$GWotp`3nu3K)t_jxf6wtl^?EF7Wl
z`~f at j2qwpuUrlx!*dXLR+WS*$i1abXDs1GpE0xQDK;{GWIW^ukEtt)l{`<FwEj*Dn
z1$n8?&IO~*Ct{uA-xF at RZwhd)qfnbcfF6r6)y=$31l;RSO}^ZWVgzH-D|fW`{pfo^
z5W>XTuc?UbwE?%DT6vc4!&XTM$7%B9!2RR5NBoJUj}qXPYe*bWx^C($lYm_IYZ#5%
zV_t1g4xcZHfqBJ1WA9Ie1U at g*2-b*okxmbs{&vCP&`sppV{aputRlSk^xUT(f=mU%
zDn-=*k|SQFm>;gEio_WKLu<>b_|v}L2`3HXu3BhDa`+@<AD4izd;t<s?K2vhVO{pr
z9eqKoR+x{Q00*3(%|7M)uu=XmGo%a%e*Ct8hzwFB_0dTj{)1f{#!ypNCH(m9I;ou3
zaWSyKJ>4mp7YJpbU}k%x5kg>$_?vJ)w>?paQ}Ahi&Xu8n3czI#4NJ2d at -Gj+JQ<s7
z$$ZoL&;yKb{#t3{#<{M!nNz7;FY_I|RKiHu;;BMTt4hMZ9ZRiQOUdXB=FxBbRWwf;
zHX(VXfQ(*Uvd|ye at Y(-qr>A?XCC3?CUY73z!d3n5%m1Tp)motCo+CK$MgPYAFNtw5
zAvDe_S7WL$gFS?D>mm~$&k?b+<q2jdpn7#jA0ZECmX+p8XOqqhc`Ht;n-P7T+cF3Q
z#X|d&M*6lHuR8}znFxZQvy3zETg9*6O$-`p;PNbowH?k})+8>yKTBgtn2+M^@3_fC
z5WAIm7!cI+QGuyjBOiZPts;5?NS&VUXM$^%&LEIv#avYn2{wB)ZrOQr_ho at _Iy&97
z(U+9ZKpLvbNQzjLk?5_uTt$*dx+UP>dU+=u;<(+6%!N`x!!?71=3;ERNo0{@v*V$g
z3bSI?o5I`sKmxuo>6K^?C>(`I#lL+P4@$5g>O&peC~UYh?IS!J=XF7O6J6%cy^RE!
zooN;MG8V(^PE#v16laByi0t!Wv at GSl6z`Mg2o0tf at vaYiRjC^o_Cf+_E3nrvfq4XN
zWJD`D^&!LruLiHkUJE2tc2#|pn@%n%ZvPZA`WJI>598N+4}VPZxFASIeNf5Nu&h-r
zt~k%hDd+X5!(f87wV`2Z+a{pqy7-Y>OK}jc$XrQUALZ74kaKjsqmmES+p3F)%++84
zV-F2bl4(_El(Ts&Xu?e<WpiUPyQOC}d=NItAtr}(hl~-=5(C{nxgDy|o!FUbNYyI|
ztUs<&h<>4c{`%$2j}=9b+3{qt>-`C!;jQJ*-d*5Q{=%nU1sQuDn|MG(ddx;ucRLeb
z`<8!vPqA)A<=cqMIc1bK)CjkGi)oc~J(WHF<$1Ja^ym;iW(rCf?2cBQF*rZNX*<IS
zrd&~7R~^x+<8>1|ejDN(l5*8cZsqt0 at +F1+F8?WGj|uA?3!@}qrh>!yEt5sp{qs?2
zdDLYFpebcbqeBks7LomrfF at 4cbT7{P+P)P_^6{?ZKP7N{dZRBx&~usv=&&B;RbFW7
zfqIAC^O5nV-=2ILKm1hNdlf8j496lliL-?T4WbrjOY{ysApTP4NJ_oFl5qp4tC)TK
z3u&}ivFz!@k5BYH1OByaOo{}&N+cCD>21iE94Ic^%pZ9h^6RCot#h-QQh6R-1F%x6
zhTI;IA|kk}z;_?S at T3JqKUeAmuiaCk52(@`jQ94p3h}&NQMQ`g?=E_(-Bj9*mNUo?
z===mS%9d0tTOw9FQ1LEmMZQ&|)8;OZkqa_~8AHF5yq{tHs81yvzGqsU>fUcL#Ofu@
zP19L*y at qRYEA9`ymp&q?@ao3I;YZ$FFA6lC6ZU^OL{|)6`$Di#w{Q`)dVe$d at S~KV
z!J2F2mYIV0&qZr*GM(3hoMMT$oSDv17)=JU*Y4$@omJG`O$Xh;6Sl=gCpF(W{+*Hc
zNfTpk#O~1_7ISaBiT}?5W#vQ-&KXRNyOKx!xg3p_YnAzoegUQe^c{u#qSqB6;?}AR
zAgZ+$_GT^bYBB_MvgsxP`a}h;hQ^#Ol|q+#w__5VBX1{tjjaLa;@sV=%(vaZR;G)T
zhi|A#pmZBgz3<+h8VMA(61=?_2lB=Yo<O<251~5_ at dsz+*)z3L-@<$vu7YHc)(ey>
z{($}mqEe?r?r{8b<U?FaYU$%cH?6ua8|j=T_|?0+v<%DOWWx)5XKEAcl?a6Rkb9Hl
z0HF1GUYbV6JZFIiIdAU2B*ZMfE8>CRC~MM_VXQkut02$kR@{2mn9kxHvlNcsZWz=~
zwAT&@-cWaLVAt2at`3t(1u0TjhN}o`ommtfIGF7Q`kZL2AV_X{QbolQ7NX#nkq(jd
z9jh3&_EW`K$HLP-);EG&uvLLA8vE)*LWY8}z4nJoCt}yDW1(ebWEA5FoC6V9H7YXf
zDZoXlW9=WAwghWx_WNLt8>RjAPZ?luLA)7ImiO7E#?GvaC#r3_Sa$fIn)txEd{-Jl
zL*Xhey^^}e^zy;FrpNhKoePAumv;&K44&Zb?9Cv+rQB7;UvJ2=W?2uIU6^+a9};g7
z^j8G*!x(-hXH?U`T;472 at S{hv$8RIv5QyCL5>(bi(p{XBGd4m4Zd)U3mojPW02b+Y
zIP?Hc+OAeu(>}Z$43{nID~{33FSgePz2(0f3!b~X-lE;=<favEGSUxZ3{aS3^D>a=
z1UjwuJm=A`Q#Ac&PHPos7T<N?$vTGuZy%STmB_>K57_&oRkG05?YSCg=eA418E?`!
zmfpX5?luNcfA*NSXsJW-Zq|ZG!cRk2qh_E#Kcitrv4)dRt`2!TdJ%0!gQkF at LY{>q
zC1fmt at yO-#nv-2Q8oz{;@eUXAn3%sj8GEwC+nm?Hbd at hv5qjzRr=c7z=fSHrMF^aU
zZF5yjoF6&>d+%yMvpa4x4NyMOm1&i+USs>|5Eo$Fw2RUNLWywYnpg|=mnwK`*<<5Z
zTZivbTtJKOLbS4K3($AE#n~J?DjgN`YiNr=KGB1dS)Dwb>3d_e3}w;zuuDw<mu>h?
z<{Ow|p(bbfusO}6Ok}8oUpwZ_0uMM(a4(zt+dUf36jWslw_BKRfRQn(x`{M=@fpY*
zWJk(cB at ya08?Kk~!2vhj6a&^msC$@lEz-%gK*jh^?4iqM-Yb(bx3HH!XPj}5$V9|U
zQkFkl%gQ|bM&Ud~Gss)%YT<mwIa$msai%f|$Y5U%{w2EliZD8{UNIMk8UE|tFSESE
zkLu4E$oI+>`HCBv*VDO=fB6E4D7xmM<b57<x4vvU?Brd14l$}IMkB+KQs1XU8TM)x
z9#0L($4Fue`vAqjjC?mHuk#8f#;R;(t9MEYfJo#>y{+gXM6-8uGXr9 at iI)d*rH&hS
zvV246iFJMpI#2$+<?u<E>3qk~b8D4oZA_}4#j%0XVzz0@$>SU*3YwMQOF7pJ_{zf4
z`VVJ5N!H-i)D+Yy4K50>NWQ5E1dD5kJEjMeE0c}&U6z&|*h9^1qxsjK+EHVyTXZJ_
zMS<be5mNy=TB41GcW<Em*US1Iu|h4j!mCknx7 at AwD8xcT&)m_`p;IO3`S(%opew~`
zpnV#7+l8pEnd1bRb~Kk}x>k;wQbAoOV|$=201suFEg}&FTEe^`yP=`EW$k at YFU{(P
z<6rJOdnFL|u%TBK1|K?eutb0GvYmd;{NaJX_BOVjd=Ky0nVZ24Fe at Xdig9vjcv>zs
zrG$la at j#GE?4hY#1s_jw+Cns62pJ`6%GC~B6A)0X`KsxytK#E_^tM#^6lJ)n4__Pp
zG$_b}F8rju<CrjXZep#8VAeD0X2AYxL>GkQ79rdsH6iojuFrV-gwz<i%3G)crJTYP
z0D<Y6RyE9OQ{Xz1-TrXvoG7PH$34)|gDT-pzN`;vl~pLd`Z{CXotpPKU#&zT9epYb
z`(ZR=*oA8?pzvvG9!_0zwxHPwnVMJnrc=6Am)|Vguou_o3<Pro%JG=>&_0q(SZG#B
z06hpi2|1CtCiaDECZBxU+Zo30b>pOs(w^L36BZYbf4MoHd8*#BHs>F_LBrKb!Gqhs
zQ?<yKnfz)31Ec+l`Y!@S)by_u<zw6axpSP>fq|PTaZZ$y2zSLCGdQ_HnX^@x#FfYy
zEl>scY1&0x%{^EtW$;cX3H+wBjpPT1p at GIFqb*i{^f-hyiDLO0P9S-Jc=(wjNF(yI
zda~}QPP(nYT5t>#=$>ZwoD}V)gOJGcFimA?iJ(i)JSx_5%kGVPq-<x7_l{S77<V=6
zh;8&2yB~VsjP>ZxpeyCFJ7c}C2K>+kX_wlMm3)GR6lffEW@&`dH~M=`fuC(Z at wSWe
zehWW?^8yNhQcl0I$MlAoJ?wD1lCAS9%CWWAn4cM8lM$F<bO_BOx>WS>75ZVg<l5m(
zo9Z at JGEATdJ949Z7gOd$5;@S1$A>DeBV-MbA43%M(=R5v6s_j9)#(e4B at F)sz9%{9
zZwmhlu<Qb%5kpA<Y~=CyyUS;s+QI~MLEX>oj{3AqE1B}2;FfiEm75GA&TlwY2`c8j
zHjo#PN(Cio6P%)NmD-)BS^MY<)U*a%1u?e)vEllsx;9>_kmi<pzu^<J<KRS>hyDo8
z=m+Rq=#R3gt);$suVFSPMhqL#E*Ej3>hR+}WmsML-jUUkXKA3}XZ#fN<8}yPgq^^f
zk*rZdhNGmRVVtokdGIK=0?=|p(33`aht`W;Ki&lwtU7hhRM at m|gleZQ^RBtD%OK+&
zYnK)4Rxd4PV?Ruk6}i+;su?dg<JV at 33u%Cy7qm}Zzi_UsGJltQbcmCka&ds*7c5pV
zCD}~5s!33vZlhSC8?Yk$_Xk9s4n5F$NT5&@4Ni_ksC>H@?4R9VK at c>@TYGwKS_SL9
zkpLR_%Q}C%`DF*B9_67~!)^bL1GQ6%ux}<zoLen<gBD?NRaFomvE)i}nao+bFZX<2
zhokLSHoJ7{--T%Rthr7q$x0^IAW}Gb-|()wp+;f^7Bb!K)KF$>YwaKFP3+UswPqn+
z3 at ES_LwJ?R4xPVw32pAmv+}Cg0iDO|h0mjl#S8A?b)vr*zR)4AJ9QUnye6%3-{&Y@
zzF4^V3(K#uyU<YdFq+bM?Nh<)6!xfqj5}ekWcH;?u}YeNai?A{vD5Z-cJ`R4{klzb
ztCQIoF{OFILrBIIp6 at j1ONs&-GTjC!Sl*#V65Kg;Q=Rh&R5hB{Xj2cou~rjb*{?W<
zNe;lZqNXc5DGSGV&CJV$r`;RklF%4aLljQIz|CwSfES^w`;u3(eN3ufdM6*YPeEka
zfXPZ3S_16S&4b2_0nDlTH6wUIDtpk+heG2BxiRVS$OnUJ3_l1FxBpYB>u?wFNJSvt
z2k9%Xbe(cBwJPUSL>{5YE*l?FhJgShudDeuBnD&g8k=%;-yD8ZPr*U$MM9#2h4z at E
z!yO)sh5PdPH$Eh)aAY=hSwSIBVdi_dqY at 8hy@<F5Z9m at S5HtF4Q>%Zaj4hXB<E^M*
z at KkH$*=eMScl(pFMRsy>j;@p1yNQgu2tha%^1cY+p13l!hISUmKKh0;Hox9ggz-5T
z)(M_KoX8)2>Rq18CKlk<CLDq_msAKgOCKN*k_Qfk3qeza at 5Tt$Xy;+CLZxsXQ=eb(
zv{1rDfnJ~KUswzN0bP(wJIq&hbiL?Qjl$%b*f?wRO^LmWGlb3utLIAQQeW^XWpqh+
z1g$g+maQew{zKUvOGj-r?TG3tldI+fdrJ9=#Dx;thdA#9pl*0Vx=0Xz(wQD<QL}a1
z=+>dPT<)JS_imS_75kkQ9H<Ga$Pg{@+AC$f3hSX6%uh*_3&gsVw*E?)jgcK4^jdn)
z?3S4${`w5eoA!J at OPPQB$mca~!L99v#gMFg3T05&*7;h7JBk`&^TRmKsNZvlW at iwi
zBbWEgrM at Oi<0;TCDMG|d1H+g9Q<y)6PeB$I&H_`%>6vM|GU&^d7_-2(US%(4%@zPv
zG>ZZv%M+YIc{-I>)4Nw-ZRkHC_0ka+ASUOVD8ubE2%`?g3aW at W*59;^G<P5Po(y23
z_D$JCeQcY3{}n81^x5Ps`i~P1f@><*32V%teH|^QrPu?k>nn}l1jK<#sLKei={Ykq
zXKj~V<(_#Kvhq?ojMXcWT2t6+YWV8_ at Rx7xmk0?_{)0+1tQ)C1?%q_`O~AIu2uS=r
zWS-`!nHHRwl&miW0<i_iIZw_D$vv7kY&&*^e&hHTWch&=0EryG-LzKOOhPpFuWw2!
ze1O6^8NMkWC3yiDMP>3Rerj*;=5L&tF3yB_7`c&m9Z?t&7&3V<<pVfTqvrsivcw}D
z92p)cKG2tyhCyeS_FDP at zH1icUZqvx6fx;%hq`#e_FqGsN>=Nutx$I?jy+FPSAv4B
z1-L5$O~#fNVd}~<{{u{{x^}KF;~3AL@$3aLo8hfvp|g28IrBI$AJqOOx5#GU3^QBh
zQ~|mAUkZ~4;$FYrh0 at W{u`OSrG2fOjG%+yP{QWfGeUS6+ZM71)W!g$hC!6&F`~Tl4
z%li&)ojj!^D+3FP%j~Triv%W6Xm&1i>s)Dz7qfcH|497lSv}wh+B3lncxN{7e(Q`V
znAO4`VI~Lq_&<T`&fT5dgYbWtc`pgPOse^enrI2X?-Qo>zd5q6ws9r*@Xx~EOBF-9
zd>}vmw at c0W%V6n#F;q at _5GYe#Soj8Y{@_{3oh#F+hjwoV;d$&dql>(ETv2*%lu^sV
zemRc}+hdidhpFSivHZw?v at 0e0pQa1D%T2w=hbet`Bf#XS3l1DKI_vdn;4T2}Ibokp
z5mNcLO%-X|mq6hmyIHpRvH<927*E~zLY;bYxnKuq*Z<c-x!j%(;GMO>OpI*03h0bD
z4}fF-;mNFa at qoyyOc5!3Bg+2 at za^4TZ)jpdcbPS}xB$9l at z$F#=Kq%a;_SegFUPJN
zzumt7-<+G(o-VtJg{J!@C9)g{vsB*iyMz1l2vtK6U&NaXkXp%&W!l`oq8eCDzj2Cw
zWkY1*s1pcuO(SOf$pnuIBuw>Us$$Tk5K?00P~H*xzSr{=0K^`=sbweLJUmuhCh0q?
z#%LP&CIDhkiZ`*6xTBY$`yVYFJkz|R`1ln2)j2Ovcjr3jdk)WoMoYT4P;>>-MVcO(
zNQ9gKuqbFrC7s>-aLr?6G|k(VY;Ki*Ut*_)8l}Vtvg?;`8 at o|1z4OT-ibjK=#GWXS
z=$`)quvz_Jw(l8ZZ#|*C`r?Hg;JMZLThQ{bn9plR!{raKT2=u|-Saptp$pXI;tsLu
z`_2aXWsUA1M7;ZLtq%R7kqvwL6o*t8xyc4ZD`>MVzPdk(N3tE7=Z=fw0gsOH#}p{m
zu2dD`#oYg{psQHqIW%nP*;&@@tjl;8R89xYsZ7*Ob2Tm`h0q3`^+q-CK4B=TnIbp=
zCr2pyuVIHL1lCr7=Rs2J{V&RxLbDg%&gr3kf#u}ev%1=jJpu4QXpIh8=Xs&ZyNT!b
zH#=JY^%Fn3q#4!!#IvOmTQzKLJH3W7|cQ6WYGm*JSvdtXCn`vFUeWGe<lGYUAi
zrSm-wKSi0;+kKJ;X-YBv&)Dy$<;p|b<BPnVMFJX+6##-RZb9)-m|qg{B%na7w%)yN
zUbvs9!w+yyXkZ|(Ks+C~x6nbKW?YykG;|)<1LtP&K at L5S*EQ%lc1n6D)g`KV?v!2K
zv at +tpi4fB@nJ4QBhOdZxUQv|-0)-3lg2ox;4lJ>zMu(x*kZGBn161LvtR2|5M8sok
zKtJP(hTBkHyGv|DnHyGQ0YKLqS(Ag*Ouz9S<g{0Q!<UzgCZ1C}5HowGI0f+y5!Um7
znb@=}(Irky0+dDu`hUG?iaY#MnVOU2f_0HTZf4;Opj}Opfac-;JcnJfMN!p#_-Y7E
zhO`fl)l)(Nm5=~+l0=VDltlCuJWRAd#$)7f1?NXBKHj#Xxm$Yh>_^w+{rr5Za3LaP
zoZCaXw&cfHUpfE}GUR!A=^=wg4zX^(-v{HXfjNq3s{N>=L^)4Q0(jRb9_jC63rQ!G
zdulXlEIHY{OljhE9#-53GqMxO7S4s0M?vr6fh0MWFNm%yF5mum*4O6dj}b?pUo=*e
z_14m%J!1H3ibmZVdrv`7d9y6yek6z-Brw7yT~K0}e)#86?p at 3Q%1D80fio%n`CaEv
zaiPfRt$uFmga;jQ+Ouo;yPbgBw(<>ext&)h_7x(=i_qhgrU^UNCo)$$@B+z)rEa(O
zm(8@%6e8-ME(b5{M7y+6VZm*J6b2vQee|?-dQd@|q}Zkd#!ehC_5jXive~nc=8-3e
ztTDf5W&*JKy?EYeq#y`~4o<ZpW{8_wRaWWwu!l0JFz71AMyC`G{qe>=^;8MlNA%_c
zA2EzY&)5m^r#b9Te2n8X*4=zEr4KzA+UBgYlb at X(4yb!K#PtdO at D^Y2Jm!vCNsNUM
zB}(d*JbYXP*SPhD`HAufzJ5ZkjAdk9TvVJf2)gZQ007q8fVbXTuT(Dzy5o<uSw#zi
zFt*d;$%grPf_0~q>#Ib7#x4Ewyp1J%A=?LNS<`lOMAIa{sn|M)&sH1|TST-8oNXUg
z0FVd60k5^S)rj at ZuV>Xc_b?jmE5>no#>J2D>&-iVYj|2i(kr2iRgAjTXKaFSRKK(4
zD)s^u2MJM9YZusfdF++>WvtcOLUObl`jt4>>8(=bHe8FRTR{kl0z9X97q13NQQB#~
zVDmhpt$nrWQA#l|K`APxENsrj8gf`*FY=4 at 03Bjo^+}gTLsx*`s;s)AfmAm}(5G^?
z(EN;LTd?7w2O%5kH+kwI>%+)0%CW3Ku`AC!q_wvCz+Y=<3(2gr5tf{l*JX;V;WZPr
zEXw3FZ8(L9h=wBFpQD!IW?TBUCT}|TUNK29iaZWvu1cbgFyufFJ|`q3(SZkMkpD<q
z=I9t at OCQw-kIfUTsu6r-Rto@K=$C=;vSY@;T0|&W07Igt?ZM$HZuejPj6HlNKj&y+
z^x;QFK36?jZk^8>ZV6s)m~frLXtpo!4I6gstn+yzvm3qnO>$)qIgJmFiPlN`7MJe7
zP;0juMyZo>5qVVSXWvyAyFRBSQ2!qa6ZT1q3FiNYLZDqzH?@pGpLy$;j<GElv9vn$
zz|a?N4_;rfwT!HCOb5&<eb4ce!rmH1c<tKPRBezmc1Bn3Lg1w5*X+K4Q)H{shv(d_
zF3A{=mxe`*n5K&c-^t|ZK~$=G=f7z8c6Kufo*AhtuZ&e%Oy<Ax%mAi at t<Z}^sh-k8
zedN8;0jHR_PuI&Dp=@bTW{`T%UAdd>_OmvCAsfpvjpamELZ&WUtxs?rLI$H>h^DkW
zf4s2wUI*lypziIRQC3=LdVNSGwXA7yT8$rQDioJzSd=ZbP&!9>Am^ztMov-}K(My1
z(#hE5#Dh3mh(AEJtaDm}+1R$$#Zl)2u})t#E(^h2S0)=mc-jlZn;yBw@^;Yajka$i
z%n~go^cjL9$&D799ip1;e#X<?tK68RNC$IAP^RO?tGiaqHtzqBS}%`yKb|5{h!+=Q
zx&}1nQE$&_m>jcKA)*w{B{c%Q7%HZ|k|C01VP^imK3oO!6ks!}jyB-;S!I3Sivq#&
zs*l%)w=B`Lc at 738A_a=t>lh2(Df}GsP2+OCyo`c1Sf{a*jm=u7H7Gy*6;x#!in5J~
zkbzT70shtQ*P)Jq)ZcNQnKdB42zy)x)aS*e&sL at a^95_}%EYY}74L5W<9r0soH$A+
z<tX4&^)B)V-~~Yo)%`~srIC0S<aBZQQLVaPDH_)_`lF?ou6Z;Fj4fb-YYTeQsBmUB
zi4d9VcJSy|^_htKxEE at Bt94%;PTCG$dSXo92(NTVKO5v(IxZ2#rBPx%()j|2Fbk+)
zWzaQ)rp(jH$Yk!5UNWhmiwe^6zGPfSS;Mmr^@a_jAfqckluToIc}nIuDI`oty3^1r
znavuVGWtvoVqY3Nn-6>SB6|3EKXgfe5p*lix1s~XpgXKE9XlM^dduroRibIxK97mn
zWs>4*6L)Y6Ur2$-5JR(vH{d&}JVVK%lZQz-U!OHDoQsr?js{#PqpVV+VyjYvzX5;g
zN$=kFP{*nBk5&|jMm#+g^*px*$5`aHWBIQvPlsDve?(mkgKjk}M3`-jK81nf89S;>
znfLx(<|E$6zubMOg_`h?+etKyN|$I8XI&ky-h>P|^3`2rdq|9Np34B at 9L~p7!84@@
z8$P;)B-@})vb{I#bvB0v+Z{VqLOJ2AR>I>_)t}x4rDlO1<oMmWEaoFFcYe|NvJgln
z^=6Q4A!$c>_Yxu|52T4XtqAxGz!PPm=v?=w2OXb^vYn2(sxcgQ#=7w+xW3Uo!b}ms
zUsi_mJOi?v4x3<~Uq at bA9%FOCq^@{5Y)*$<10LtvJc!W2^{~8cu=sxb_IRC#Gy4M~
zo#(~90ncgRxd(}y at YhozS+yS>>n&2gDB3H;Jp})rEp*SPV%I25e)Aqf{>mtnQ{=Ct
z6g2)-(&tYyJ>_8$ZyxLJG)V1v*q#BsJ0pIrJ$G&4Xq%Pj_Tisnm0wRGXQagR1diag
z*w{Gp^ZeY9g_XgS^VDB$A-B#YOF0}(XOO41dQMl;ix!@lSO}ZlNFu8|nX!xyUb|19
z;5-s~<=ctBJqrUHfcS|R50@?ZFyT&2=6to@;<EB<#(2H9*gAB*p;LvGO~Miu*f(}3
zEQ&|=$JlX>{TCv<9!(Ns?M^dy0lpKHZG-Js`oP>1&5f0096p)<$zzM(hY0<LLn at Su
z3or0NFN(B_jA%Io9KEur^DPB<x?QW2oT*x1{RO9B#xS1M=b!ax85dV=r_Ai{bz}tl
zR`9MY<#SW1>mfX9B31_?)lzyN&k72?()x$`(`;<Tcn{Qtz)|x%oxkemUh#ARd!d4X
zstfEpuOrw_DM$(=pxee6^kkkHHe!*?IKD3@<6GA;&~3JvBX8=5n<pOuk1zK&sO!;6
zKSLi0egDWmel2HAasSs(5+s)Y32qx68-;tsY<KMB&7E(Yv=?e6;D1y%;G7U22&De{
zk9z?A=*6O`4;SF2#-^I1gMnvG7m4{^c&>?t>PnBMndHk)a1Y`2vGP}TCGkbYQ8YXs
z>eQ9d|A((H4}`jF`yZu6wxTFP%91?_p`o%xcG>qNJIR(ATZAM_)*=+L at 7vghED_3N
zU&fGq8DnG{jNy0YzVGMx^}O%f^H=}m%=w;k?Vrze9icyb)Xc9C<{-1nRX%+?zx<_!
z<nWE?kpu%Ct3X5M!Mb9}@zX0SfL+ at xJn6!wP?++E{j}#u$7d>CKvxx%$*kI~#PfkJ
zEQ{i%#xeubDGuefmaRd9;91g`BP(&W-e2nSkn^85V<4lH<{nk%jAnsv_O=6UusDGY
z8}n;4ld*0h_J9j(S;v-;mD0N2^ZIrg9Qe&bjz4T_=)K&*c{QQ_F`X+)=ZOpHU{Ay}
z51p6?PrzYY-3;(w`}Eq&RSs8aDd~*>C&clGO*xuJGN2s<tl?W?AT`^6T8QR<SNct?
zy>T-ZIGHN!VsHEQiv5Zg`htveGBD~}D6+olpr&V!LO#4)`^U>DD71~~Qe`v;N3ux|
zj0~G^beFB}eZA7YjkR3?3JR2eLnF;;C6tu~-4sx&5@!My`b+WuuZ29P?pnJxVMbOl
zkr;otb<<8uNw8->2qnmo?#xR=_ntK8l{d=A?~B+4r!^gJV=p}<Rr3(X-wjmU+y&X~
z<$sxVFrw$${tUe}N=9VlszU0ZQN_NrA>zHX_%o>Ox3%umqUKf9#7IdNbXjKE33j`c
zZ|}$oLidN0qV7)j7u$}J4@!L`()c#=?sDCjoh7!2H1qO&`b_(=ZUGwB#2xQ#86W~o
zNq^9fkR{&q^D at 3QWl-38I0_0xk~ZNpuD at f1H+>WAr^%mGRaTFC?1XS>pYTI~<Njh#
zwqAL*ZtD~fEroxX;LDdES`pW~+l%Y+%L+<dRtie?<_vda38tmkMSK$x9JgPBog87@
zvWs*`vM6Ol07Dz`r}M6x0^MBo;L2U<i^|gd=98?g>Sv<vJZynRy&T-=RgT0Bc8 at q$
zQw?D}y3d^X!{K0~HWp6QcRBcdZl$x<Cf3bLPEuyZ-J7^P-w`b*;WZN(i5o_ZETj{_
z67qjqBTaQ5P+b&0Fa)X6)OnV!T@<~1&#<QBj3|8Jo9K3KX3&l|IYT8OEKZyeb@;=<
zVDvIOZ*Uvx28%0>2z9jRguent7kTIptSmD==ve!NM_d`33`_mtL37j+Q2REIKWUg>
zmDEcKs@}((V>jBsN^avdmSKH98~b7kYIw(z3-w+6?Zs7zWq=zBf=A(=Kj_8Y43uyl
zof%fv8pcZO<OO~u6pnXZ5$)IkAEsB7T1#AQ_rf`>n1OG5`Et~dZ1tCo-ur@@-I;&+
z!<R1&oAx&}twkDd8ggJ`<lpibyj&Jv`q4ZB+Q at +1ytD>Qc}^)3?P;+5vZjM&kX(Hm
z`qO at v3XR&?eIAu=6iW96>`#Ga&W$~6 at X~8)<~^I2UskN6FQDrYWhSo9WJ at P?`C;*&
z&c_vK<_HcUSq=9F`kMMe_hT+CZKcah5rVu>Zlci0Bao9 at OpGj~ZGdY}8u-JGrnMp#
z>De4sem4JQb)v-aSt`v@*>Z;~Nt{d(?(>w64;@B>-dqXBl#_Fv!LmQ-dQAgfmTbpB
z#_^i80}8{AL-O?E+aH2~(bzJJRB8l$Ju;qRAaXD)IeTWN;t$L03>+Z>n~HAqi}Bt2
zrQAd}^qPe<Kz2WkOG%tV1|89oUW0m(X71lDxe9?Hd#m{ed5$npheG!jEi+@>dqlSh
zlO*Z$P2S^Y)V>eZ1-e{#=(mn`jl@{fTi1JuZ}-RmkJxszuv6BXXG>5+76N&7@}FRZ
zJj)7KSqoG$$9Lc=gLc(a9hL>YHF)6Gw)a7a&Em<OHJjE>URENu1emY3nm?@3rlsE9
zAK#FmFSZ at Ry#MqyDq8kb_M?D9q(!J<lHb0YHJl*fwX^oK`#6XYMP2^SZxXercEMcQ
zzb)T(-U*Za4X{P$g6WU5ZGV at fXV7(#YM-IvJzq~;1v{%-%t>ddU&?OZ#yPH3p!R at h
zi+?&`Bd4U at 0f%P4<pZ1#8w)$DUmLz2@}N1yidchwMWs({Pas$N9qy_}n8NI>JEFF_
zPh($!AOOBn)?V$%>?Dz8I6VXk2rATh=^3^voyQ@!wf`N3ycKaQ7rMffS#_?}7A3J=
zGxOTHzkQ^lM{FB0xi9 at zj>CUs<bb4-Y>9CvJDS=rBO0l%G2Rwl%)pnI#q9CH662kK
z_MQ8;_U)GQpv4}F8ly#h$8K1APmGQTn+0jevWB$fWSd)fya~8irsX$7Yi(fieMEfd
z+IA6wobirT{<pQRiu$%tT8GhI_xf?k8ia--9m at rg43#F!NftnRLT0 at uFVrujH8UUe
z9-ji{g()axK(n_Z>k;s}x&QXM-+-S<JQLA-H)1L|e=J9{^y^_52XF;Y)UEyzT=hou
z6{eto1N)T_=HubZaT;RVNfw|~^fKfj=a%lKAm1o(mi+&AmK!$e)i!MpD^FY34CU3Y
zt6Ix=Tq8%>y|-Q1OvM&Zl3t at Wk!;u7MdS<B(|yJ4QVw^c%Pf5V+FkO0-Cfj_`}}$M
zbT~+6ZQq|+d5j%u9`~ES at K?+cEw?Y$mb~4s8N9_u+!o(n${{Pl-Kzg~cFYfVHkaC0
z>k7(JWmw_;>g@*`(H1A%-HEtJD9PjS=0kNUZ<kWWXP{-*9L6W}07Rb^|82dbhdXWI
z?Zp*QWk-)+)5)zP!^Ax-x<X~yV4lH<c4p9v16ksEVk<pazv?Xi+u;efuPJkgd)3ap
z#@GS;D_~NjW2Ip>*0SOp;pRhA2Lyi5K+<dR$EA<>#4DGFxc{5{dWwf0k`QIXri at N!
zGqe>z9eTL=OF`4r=jZLRJ0C1U-)dI9L7BrZpg%>FAtqOWkPIpQ*Y%*Gj%;OdnsgGU
zq;NO>>T?g)BX$Y2d*OQ~X=k*tduATm*D$86d*4zP$mO{LCLK9NRBS9QK%V}cD2Q4N
zJu*=eRKnh;Ib6SQO&Dn<s}JaolsMJ-1xlcium%CBen(E4ShXgHHDbJP1SLmy`q;Yv
zCL()Gt-R`DUdXN2xJkKtw`=AWi8Imd)vm59K%Q-0*6xkkw!4%mmTD}#6(##5zyDiL
z#geM`!PJZGY+?tvwsb<5DS37te8x|6R8p4MD-hA$VeTX1g9_IT*zQVf>#*>X^FcZd
zOtwQOx4>MYSWgZP!gB+CK at BidYeHi07<}j at Hr5<IoaqIIgCSvVdBc`qY!errJh)~>
zcScUNk!MDZ)Lf7vf_0<`n5O#6+tEtgYm4#Tf2y`M<``)P$61EyBWOff#gT-anc$_y
zF-xWX0J0z3>NMTsb1V4Y=F8o=TcAjKK)7l^r*rnL2~++f!-Zmub4QO^92E!RmasQy
z6`93vkcTdobGs3a($UMUTu)YKz}6yUHHR=8A3!_jUAMl?S}<HoM3V=GDPPXdG66&P
zkWFR<63#dugYlI?6k-pfZ))>3gSc&BRSKQ0>5n~>+xw(qPV&ve*5m8<G9+qiR;5Au
zM<a1*XIY?$?zpg_<w|}53mon2e_?w1LEp21tJX4r+lfa;k`CY?4SVSK;4t**p_!h^
z0THkKTYcrshE1{a?zj6T_tV_hL~~#^A6c|NNfSBTf()2)b~PYiR!nz<Qhq0SY?luy
z!NBCoT+M^0ozTfDVJ?VB{nEtjbqA04Ci-k(_}9p+XA-&Lgxoulc?rw~y#imIljM&3
z!rwpH3l&M9APp3B5}itS*JhCx&usJAe7Z^7a%g<Auk-lcZu$9#HA+0GFo#m at mj~TS
zluLe~NrLvvXaz~E&7*NQyG%Zma`_O{klOZ~ib?S|2P>{pK_Fkk|COCSZ2VRw)C8K?
zt3iLP*E{oW at GYvj09fRT6Lc|pKQBYExVPAi=P8l%4Z}2)I~7s`UT5^+_4(@*&sve1
zJSBO(nM>I(<e*cG=nNYHbc5o38JBg0xliMOhc&cA>Wb at H*L3Auut|sm*9Rp^hescj
zPS$z8!UWUPc|>1P8oVkOx!s>D7kOa4ymMIL&0(qYVCkU4<lEaoPTS5-g|jrdxmV9a
znjt)A8I`V4zq-n=@<lNB<%htNmoHs9dFm9%t_7F#Q%o^~TdNaDV$&1}U-CE~vG=}H
z4dvnOH at UET>!zoMUe0pAUI{PcWoG$APF7sn{X6V|mtLL^9ln|s!QSmm^Zb=gn!C at B
zAMU}VH1p~n0o|a|J$ASus+E}K)D$pum-Sh@)KJxel~>n~DG9%c^7!@0^n+eucF(x)
z7PY<ef at ZeCs9SQm@V#2*(Fzgd6f_FvYW8f9c8It*gL|PHI>tzCa1Ic{r<J4gX7X0E
z!qV60S9GJ>dDGy0S7kPH8zuH*wYSHVo7D4kb&hC7Uw<~@(>z0SljH=q+uq=tQ{;vW
z2kaot+*+T>OphFRjWn^F;9CXo{&70W_~CPs6HMQ%B=_cJiU^{ll at Z3{+>D-%)h-oN
zSCrAsVQe<MbnK5e^j+4{(kCh+lrVN-1S#PWi|F0GjwT|(`ddKnUfMI&wTr~G8#m9h
z7vPdBG*R{#;^gs?64Wo=(YCP1$Iz!(>xkRKa%k?%4z6^Qk}FYEQAQ&R=ldL`>n0&^
zD@;8f+LH!3IR#(l;SrzAObhF|z;;;aE=;q$yx&`0nilI%I+4nLi+&3-AS)p0$KX4^
zhhLP+oOmxyAI0$`pY7>vSu^p&hVG&4{wDQ=yNJp~^po#YxwRDH${*%Wd34XHArA+r
zwU87TvD+Ja_Wp9;H*$6l7S*XQ^>n$X#|p!QckLlTm!{8~XT}J_1QDW?{Hi`ZbEEgo
zX}4Kn6ZcA1w&6J^Y~ncHrH*H}MOIhgY65=9l$v!M4Qdh>yiu4*nOM#D5iz47b8Mr+
zbJgu7uCl+Ux?8wDn~OE!WkG at g??`dh8wRg2Wh8^=YdbMzDo$s|p$xsX5w>mo#r+~K
zLyxl4xXA)S?;Vt4f<Sm)mgZ*Fxg^Lf>mBYpMg`SuK^?st-jkUv41p0Ae!Op)U6ahb
zsTmwq8*o(w*+hf-VklvK{t=X$us!0giG8~EQ9sB0DAxb$nM8ve^Cu13);yHx8x8uM
zC8^#MU%b%BI%LhS%-I9j;dY*pz|D41(uv~_lv`a}(~@U+<j&qYT=QHc{n##WtrZwy
z;#r>^zh8W6z8~#gf^^<UlFM!YVDGM}mhwX2ywbpi{XmwHYHI9NsvOGqQT=sh9Y3Li
zO?xKc4cDF*yl~&~SvIwwT>AVI+E at D#b>n^a+Db$*%>>7wb8ns5nyio^>G4N*2)p-s
zjAe>!>p^f&>6j<>M0C;cM;}hif-wVIH)-Nah(6VjL$k5CVZn*|Ik!(f5OIzV^T#~8
zPg at xG8VC>_LM)0lv57}-k1w)xA&c|*j}3pOywi50L5b6B)6m8G<b(M2v~JbYd=zuS
z>%B+=yruSutq1*+%RZkvw4&?mkWZdRmhxJH-X{j$F`2cGQW3#8RY(Xr{;f`nntNRc
zn*D9nwRa8FjDgEvJ~br-zLrIG7qc=uark`y-s!B$WREV=<)Avy$CoZ{BxEoI>gLEX
zI?4I%eBpyq56Ww!Tg at FqxFKk~x7M{y&)zYiPUivuRWD0AFmR=|%zz}{FJXt8(Ir1e
zA*s4R(xRqmek8(0H at cx+J;elhJTgenwG{@sb{yu65awaLh;tH at H`O4^*DW at B44Pln
zF4N||_*jkgqr<+6jbQ03m`!JRpP$Jd)+^0jyA%ZvCdJo8xX`0m6R`Me6uBt#jopx~
zN}VdE0&Ul(Cn+#|Uhzcwlav$)&7=%+(khiUeV_B)4{i!Wufq*Fsua;HO~dF>Ti%s=
z{JykX=G1%RGll|3K><5mq)BarikGWUJ!L<u)`Sk?_Q~-op)Nmvv6ky(&`alMk$3AB
z%f{PJ=U$zICbee8FnCN!aDR_~8P20pX&DMjQ-q_#=Y!Cm2uv`$>z)i~RXSNnG*pcV
zQQ_xK%g5DNtKPkq<>&btY?|E>M#KFiV=MlFV_$<}%p+<<2=pYzTIpN at WMC$@<AY37
zs(A%$BcxMAlH#WKu4D6Lh*09w?=|6f-oZ?k$)9c2-V0kE%ue&|B^ank59*aDkcvKj
z-6QUyvtiMU!aW}qr8d3Ys6M;tvTKYQ;IXTN1t*Xat<EeGMYAh;wic)Nk(PtmmXrQR
zH8<GnZ>rYtNiC&G?wL?M-nu+E3A!1OiWuv~4ZUd88orMUl3=R8E~aJ~HV~(^bq|s_
z8L+S1;!?{U at G&OEMRmmDaCta9E?QRA5K~7*>`w<Ve{d*a;)(phlgZAwjj+QyPspVn
z5863%1HE(5B+5Id%V<lq?=eFY(dUnx`_!;d9_z%ykjEK2C9##OTT-z2M3q)b at 3JzL
zOcE2L{b;tImWUdJNutoO^+8qOivtsPQr^VYOwO~d8`0 at qpy?IX!1rc$h!N^KNW1V^
z_sClAjK=1&A~?(kn)kT(#)o-Td6sqy(tt?7fAIOttq#xpxDs5R!Yg}v05o;pw}8vd
zl at YMxLW>w?Og7j1WrqWBnrVT1CL3aP(U}B28)K4zCxHHX{HGG)JI;L1%Qa7#g3Hbq
z6NBoe_Tf_t`L&zlei4~m84LxhUcE7{&}@cUt0O;1Y6{S+Xpa$*U2l7az&%0n7bDO_
z*!6sNs?Rt={UrBF!v=$XiFpgV|IG>N5K}m=X)M@%ANSR-!j`vCJ9A&r{b21x(?y8Z
z0Wsd=hr7yY$}{LSmJp9#>)J?8E$!+B(;+R*E#LkaEfU6KGO<%3_3E8W_U8a6?Wt<9
z<D?#>WmH^^(F6!Thp&USmS*R5#6%TB<5XA6qj(tIvgkN0<BQ+}gXn at LAa~<WU68pz
z*_V$o%j#RTus;PY#=ecoti2fCvveikoQy>!_eNA9`I6QiK)Wuw?N&8Us)p8e(1$>&
z!QtdfT5P(_mf0Fm+DK~D at a~vw9C3(3mYTZn@=@jc&|&;C2jCAjl$W7>va4&DtKW&a
z9S>YTlQp=%$RLgKgY12wtZ8<ETS+Pq{rB{;_eMlJW>G%XkHRh3Pb{Ast+a13hkqB5
zcNr_^wt~WW3YDW at I8Ur39fcgI4{NP@>go0V{{za?cMh{`yo9(z$=|a5zI$rl*Bg56
zx*?zbp&uF=ojstv?%EVw2P4+00_OH|bv|=ylStObN`7Dwfx-DA2M#9deJDeiJQTr8
zG4esX-_rjUqOn^YG66_Todi at 7b}Wt3^5**yGoRXPHtG<5(QNjY%U^Q2(iprAFgG|q
zY1Hhwc<7lMjKHs}aV<AUeYj6wl4e&AGhJ>Hkfs=Eut~Wa7F6Q9(f<L+!Ye}GeJbn#
zpA{0mz^gpIZi;wf)v}EinSgT6{0!q*hc`+%y)8AWSYLSVfrWtY#bq5V6u`~&^gN(R
zmrh=;o)SN`RKAc_Pl==)S2~k>674QrkdruX^jCB=|FS#ioK@>YAZL+N!ECaOhv!zJ
zNRVGEm!3Vi({J(u5XWDy7lT;$T%G$xTPS~i__%PKicb=FCCWi}(d4(|;BL05*=@H{
zCT13E>2Kl+f4WLi<Q|jqu+kklav|w8J0sUnnva{`8ey9Y(pr1`M4DR^;bt<g7F4no
z#S0AkWT}Q5)$Lw)J+Z9}ZOP;KOmo{x(@ra-TynG~m5g@)vP9Z at AVTb7EL)o7>O(;o
zs?w}AaLZef7t*SzH2%oOFq+a5X7 at 3sg9D`*1<LTHll$!@aotfe!dALDd+0Z$8zqw%
zZ{9+U)P2rLw{#){L=`)1;e9fj`e{i){;T2z2H-p;$j)<l2;J>}2yfCR%SBqZ at sfUo
zmR%#V*)|QIAcu4^yJnFxzt<n&m=Y at lJY!Z2Q|^!IK7dr${fY~&lkZ`%VwK(XD?J~=
zU^K=9Y^1=C<=Q-DgPOOY)k}t|4T{VIBjB(Y0*oUK!fzDG7C5Ws+D|bI4)ZsUfewJV
zw&vDYG++rvD-^vioXIozsK2I%#Hm1D&HwBt7M+Wc^xx~SY-SKTmC<JAK at qvMVL`J!
zxY4&}?koWMg?PloT`k_eb+<of$0m<|Gf(xt>8W+;dKrw@;mvtybZ7K8jUnI_g|^Sg
z79R{gv3I6t^f;Fa892gk{7Au_!a>mvc|y8l>utXBk!#3<ZNv)<%<<qZr}4!pa at nZX
z4qd$;a;fofLPUOB2S4P6V;Cy2Xpub}7dZhri>t>MB;5>Av*E8n0fEaV3tV5hCzLqL
z_^-3<p8TqLuy}LXem*gR>Fw77Gs*oa8oS~uQE!d=LwsQV01{l2+1vOtTRgXH7T10@
zfiBmpAN887X5u^a>09`sdsKX0lMjR8UERHK*(ipGs!u{ZPXW8qk;OEl^;Ed71a^HH
zNsD!MJ)#B04ipIQozFNXGSo=)EKiFKmNqvmMt#bn{NN!s`|^Xvjqrt2v3}wXFw~uN
zeC0dphCP<hLKMHTLHPVNdqHq4XSqEFWp3>4w;VHx|LVb!!!?E?O?-T2eAcS%<$lOr
z_Gg#a3o at fvlS(<|fkBp4T?FV`i!nlDWPQ$G3i9C*zR^Oa*3gpYYK<`0KL_%(t=G(H
zRE&HecNO1vCv&JHKab<IQ=0Zg4Vw+zDfve;&EdUIX>ocjj-;?jIFWzpobb?;OB1}^
z6mB`x@<BNTc69kXOY+5{9CJ at i*~6!6BQUzDh5&8YL2sLt)c~tzhe&=}Q~`S+>>@Wb
z;blZ3Gl6A`eYwic;=B7|qGS5cDiZd5FHV)e7>3;#=-3^`8elv*0J+w7G?YGgHV1Y=
zmazV~#I$l6_f4_jC`^5dn6D@~UiXnAC!LLpP+4Nabj~kY0eT%XeD>3Z5_0W<fqXco
zPWAYSk}Z-e(og)GX-ku5RIZE1`ErI#MWn>mahkZ&<)U2tE14!Mdw at y7!}@j?Gxw*;
zoX5CUb{9zNrt8rGT!l8>FN%!kk~n4T>JnL9zu~`2+3U0%>}odAK|XlQ%c8rYR^UP2
ze;c%9Iv`;hWC?q^ea!a6Mn_HLWvw&(8>UJwIt=RWJG4;OEQ3Vnfrtl46$}^doaAfz
zMuAU8_r820jpNz6=I4?as}B#TWL8SY&80l&y7tYj54SA*FNmSvOHnxvT(w$wA2%61
zD0Bi6|NX5A-ASd-HN$#(io@=COXQU4EIDPe=AE!zpquP;Jp25W*1H9<rxBq8=1;zO
zHfy2HywOmbuTQH_8B`CQwxlHv?nfpQH09gsw{LyJni;c5rxB6zqa^$xkJJ3OeXi~c
zwp=SIu0X+l#_KXj_%}4My<5T|7>5cBTI#$tM^aq(<{*LVQf{}E>jA+RiOtW?z}60U
zK<=D&I|X8-q!7o6azB%mRq<E16?4D2U#L}VeeZc-w}>aF&0Q4<<`MIB1CESE8n;&V
z?<3C at -@o7Sq{;hgQ_~^T3V}&%(t4?{N7}V*(vSf7^*(2og&(*F_x&Ycc at Otrh}*@a
zzddL=+yH*1OWE0>sKKwn-X&kB*^~3mY0<xewC`?u^*!u`bT2Fa`uUvF-LvlDOp0!Z
zgoH-xRRSFvA041J62i<==z at 8no|0%n9io;7Y*S%9o1W&vD3rhj4D$;!clDNoJp*vz
zmc$06rz9Go$BIwR=U;;Lbe;flO(r?6k+!nrND*yyz4#obH at YBpT5>O{c2efS`)Iiy
z&BQ8)gwhad(z;U?7Z|ox+q9-qZG at Dz!i~*|sBP>nkhSvR_|581sZ!Ho|7+#s?Y0W9
zXRe_XnbUgPwKQ#!e{KDoD{kxsn}=2l7{F+iBC$I+`WRF9LfkR2w21^ljk&%jD{-1V
znqnd*^B~UPj|x4Mu-8Y=7mfck)79fPLrLudiFrtt7!8)k1_CA0t%2H8{6y2^(@K+~
zKo>TD+~urz%w6a<^CH#DcpH`JJ!C{xe*g=R;SY-~k;KHy at y@h`QxO;BNYaqNS5;m(
zm!WETWjriD|3A(}#=ULStCdy;7Rgl#&7TJAEBw9w9_mnS<OS7kcuH?3<91Thj%WGv
zd8vVmVJ!12iJZtZe`&clzT2ZzL=v9#6mlDOu^$m0nfk2B8>%<x0bQ6q3KAP_a$-|7
zvRO7ahwFOLpR1+kCVbQ_1OFDt@!Sv9B4&&C=?eWVw4)=z*pFchUTtR%ys|omigsJS
z)_ZnGew>BGs8=^2GY4G_<*qQl>)(1GM-Mbu321P_RGaP|`h`rsPb9aNxt=Fj_v7E|
z?rrwJNPYIO#kg8+gp41(Q)^AG_fQ#mFXexMzb9Ib$EoclSvX61)4rw$cNHCl+$+h9
zcD9oo_eXdOB~bmod7Ec$Gs^k`u98Fye*ZY*)#!Zbq~|q1yuSg1?$EI3;4fBWi+bTa
zzL;d2kC;$3Yz?I%&M`O9pck4BWvu$Mx&M++KXJS$<Uv9RwRhDJ&KR9oayV^v-{#0~
zm#k$ZT(hsr>ct~)B=g~eIUq&P|B at m-WoZ3ll!jq{HWjNFmle8t*Hi3GsF;P<yC$|d
z!g at EA<D{&WACU(DW&7X~=61G*Ux+(ZMt?W09IKD&I$M|KzD at xml&cL&AIlqljnWTQ
z`an&}ADt-qMcI|lkFvQ$x5?(-<edvU+)-{>ZMY7yu7YCKk at Ulr;Z82&uUUXPF10)^
z79Xh}#(wRRRgp2b(~=H3ZKw@>uivc!#oofR at W(CwrfFR_h#>}rF39CVwJ at Ci;DT5T
z_OGA#dNG-Z>$|@Z7O3$qc9c5g*6yPg*yh)C<{v`xo#dDyk}ajQp<#d4l_y{ba+;Z5
zP6spQ>0}FhTyK477uSD$ikTPYyiCo)#2u022+m`w0wS(qYbR)Lvn<NY`{QGLZcuK{
z#xYjo8>vzxQ6eZxmPX&jk5nnL7!mnjX+AtwVBqCSVFklC at ZQ0N=>&rUBb>CjQ|BI`
z0gh4AePX73T0yPV at hm@DX7a$HgZ{i_aM((#gXe9UahRk3U6sQ+8wh{<-WC;hNT?|X
zgDoM>>EVLu$?29qN)bWR=snS?ne2s2HluEW+%AR_FfRs+E0Um%mY=-SQ^GxHS9vIb
za%ckO&>LyM at nT_5B<mL+#s=Ol?mv~Sdaz`Hpt=18wzu>`+%>KAPA~&tJ^B0S^*L0v
zh7r-fTvcPtpWHcU2nVLC)E at GcOZ8x|peYFhwJ!D!NXhd!#Q*>0N7p(z3f-e!yyi!J
zw&f0IBuhby%*p|_i9y2c;lX}jl0{l+y8k-%!Lg#mkt~%K38%I0=~c4q5->@J?;=?e
zO2M3OV8HXELP_(3J;k%>iwBeHCxKN>2Uaz|a%qF3&;Wm>2=C at uJ8}&2;RxTa954+-
zRrMsl=;BbG6Ezc;(;;bt(cHzzw8$u~l#Y$TYo&~exT#APqAGU(N0QwINX;P{Vq_nw
zDdjZKZdst+dN^E3K=sFk$jW)4^%W$+Yn{au^x^okoGRpOW??DhY;p0i(apdDrW4L>
zV{4d-_2xAzArl^_PKLkM9dIWN^gA3cAU$pk;t1*eZRK~<#U~yc7d5bW^^de~A0DJn
zLXH9_**|^Bm`fod#IE-=qM`gMr%}C^uR#@wILYX>@qHKH%;wFtl76^JK8c~0kb2`*
zB{(|IHuZ1m^#0CNcj(P{1SlFWNCn~wVOV!^el(P??g#gGOB+57egb#h)x2dJBWcB(
z{=O6?yU{HW%wU<5I`4||bYGZ91wWgAM at 7WBl5OcAjEHj>T6hZ^%6Aq>skN)}Zc29k
z{a$(MK&jlZ|6?33z*5(ww3NdA7=_Vmy};++K>nkfJ&U4$w)APY09EJNw<LFX-Ac%q
zYh^BpRBoD;aD`q*(8{c?^yDeXql_v??`rF4N4qW7p$b;sach&tg9z{F<jDW{trTg}
z1?)I7X)w<ORB4|Epx2brKn$`+D-rnZ(`@<}I|-Xek!u`q;NDVP#~*NWIn|qIh2wAy
zo7KHON9EYhg9!WVz?m2!tZGQwqXIxY<?uB~%$@4SicHLn>Tq!v<L)u9MN8||I at HA8
zGHL>9FVQbv^KVfYy&YGa7BDJ4RA;7)cElrBb{GkW6qh9 at _XR{1K`z&R(l<^nCkmZ=
zG#9oBp%j~}cK*v&s;BTcZLho_O&;!g_E~)ne4+mKrz^^frS{1oiK at UvQm%P#1Le;K
zxxKf<u1$Ew;a>wFh94vCn4t-cn|W1(g;fX~IzJ)Rvw2R!b?!L+;JU%@qVH7k7%P#o
z^Fxr;zw at e0IiBj!p$2W-+OF<QSRQOWPDg%K at 3eb8t`YvEBp0rPky&QfiO_7U+`W#M
zz0Q2Df&TBJD|k!nl0#psrqRq}qA at -*8u6vXWvfe|4<HSX0eH}ZPIFFd`c<~I<XtZA
zGB&>I<CJDV2f1z2|C>mIUlRTv?6D=}ZB*1CEwS@$1DD|4^lSM6rF!K=h9`I#>({Qi
zk;KU6F1^>3p5%zExZ~)j;faSS2-55z=!xS0?YoYFN8mCF57&s6QCTci>p_&mDHi*k
z1r<nkxR>HJh5Ma15Q1^)zH2H4lci-_tk05*fUK8Xgaqbi5d$PSZ=Y81ZE*yzR6$hB
zom#_&2yMEB=D|u{pfWNxG`T-mPyvlskiqvAAaU;&N0TN>d!Te>+o^Pw?;`Z!a}da*
zBbdW_Dhd!QGn!{YAWK<kQ0C}5zMWyAqcMJuo7P^jjci#u4*!Cr$Q~+|G&EFxHTd2z
zW}oi<1YQU~L|fkUFvrz-ZM2=>zeRNla$E6tn*xc^P(8 at Q^)|U&2wV~0>dh%q*<kWo
z)b?`*WoC09A5%I1SDyGmG}?Ty+J7Sg^Mr_L_I)XjVzn>v+cYTwH~LO#_@~QVfM at Nz
z7`_U*E!}(c^j9<E&og5tyvM at JbVKP7;&|V=K|X5ku^WZkz|O#I^laMq=YSfYAOj5N
z5*H~UAH at F0apOL+yDgad+LSV^el6&58GguKk<{Sexh!4^%F7c11!@8keoo2+R!i_=
zP({7%x2-M;$XAcw3=WW?50?c4W~^maXF2r|{_^#n%U9;ou4p2uQ0M)_5lAt+gc*QF
zy-oktF#i}0JP!F|MsE`i|NqB8AP=&n?hhL+tRf3Ni5lCB<DblZREGT4Qs)NqY&Q&C
zHoN-l$#P0kQV-&qzn>zvLEZ+>e{SOv+YOIuqX_*W>`QB>sboaM`t<9R^-|pAr+VZD
zuEg9((WKNN&QogVho#L7FH`Y4c at 3riv4&Zt*4HhBdR4=ir}NJX3nR%z=mzN!?P+|V
zFmxqRg&t;eP7z8Q_Fnw*GZ&CdS*&t)1HrlcpMulI{~0p76&+yPsvJc(Kct(o)^5u!
zayBE)N42L2erQqx at V=@kVDlIY&3*UQBAm%tvnK at d)$2cY)2AZ2=K$X<F*$z8zp~Dy
z>3PmE0TOrxQ7h)PJ63^K=CJR>!iRcf!=~I7V+7U?1K$$fD18Khl!q5LV_twn>lamt
zHYCs^<HV)FmRlRsdzKSOeP@{wd3PrG+(&S~&CB9;k2HnMu?Yzw4AC8%9CqYe&g)PA
zMLrtw|JV%VL%te(+6W|VRcyW^+SQg at Ddx3mzIKUZh%Fp>jCy09IpEX*?~73O=muD!
z^TE0@`J!v@{f~?OglIY#b*Y<~uxxCi$Vs&+K=@7#F1anf at xm3wHQ>Iy+yCn1_#-!O
z3u<3l<O at c+0rfV`{!jH5YYx@)cU`dXQvYFsZz*so1u(6sQT5KCcj04mXZS8o1W7Vo
zRv7_M?wyimfWCGLoPb=S`^~W&fk1BaXuBdAH7+<*VTTZ)WFB*IPiA`E5myrz_H0WY
zE4$O9A+yl(fp_YyOiqI1SMWe(?cM+UmPZ-20_F%8Bpo8rc?=z_DRgRnqyI8{|3cGS
z9nJ8 at CntX`Eto&T%I#0cFhL-0nZJL;rBuUX=Ra?gT`9;IO~?vLK7b;hZi($7g8;fN
zc8k(JV+8&z*Qs>4PX$5*Eb!^K1;oD85t?WZ at CVL>Tgf-zodwN at IB#r*L!r-tfXP97
zhe~omXVJ{qUP at E;$`wUtW at sq<^^Z`^gP(&*fKmdeA2 at xdJp$H*xH at d^BcZ*n=^TCF
z*$w$n3FQB6{c1&n^j4uin2oeN%4|5vs96>onKBlyyzp#`0`gMEEcoAMQfn~HZ;6(S
zZR-~qSCu|jEkmykI<J*(1W|shcYP0)_O|aLdH at +mN^_68<)p?ECG96#1iWcEq@?FW
zqX8yt&>?PpupaL>=6UPV$N|8rH)I{{$DJ(j=Q)u|h(@)y+iwjJH|B){gchZyT~L&R
zUgM&>dr{J>3MIEvejGi`DYWv_KY-#AW3Jy*v>?!-W6l@<PVsY9>AxA<)vuHnc%RXK
z3!`NjEvMyxb-XOdzXx%!)7um+z?Uh9LvIYqU#UYarr>F+nG+!K%#!}IY|1zGyNd)s
z0laa2RZ?Q at wR=!j1y8tl_w_r%+>qxnJ+|Jgz+H^-LTYn=`UhQl`4(^t9x!ne2UrF7
zO$4 at ZNX^K2g&yJh)yFs)Hajyo`(t2I*BGE=6q$z2Gn!3V?+qlrnvJ08Gd>+^wp7ul
zRa9CWYYepEOgzAo2VO<5mQ_yD$*lYwOehC1Rol2D-Y=H;*yPBQUj-(?1XKB`alb&d
zoQ+Y;amc&CZK~VC{59$SDq&Xs>X#cMo3VKzlM|6B0F9_7no0UURb^Np(-a&zND?-O
zaP}12`qgT<DwMl%ydyj_UlkI?a-DO|jkqZ at TEN)1HfD!m4`Y8PC?HJnY=tVg!CL*T
zL~8*vKd5;MC at T@ZR|BLV%NY8wq~VuWX#16d#c at vr#ciGsen(;=POb8SEW;jT8Pawq
z7v5bFu7DE8WVlBe)FElU%kTJX0C8a>>dJ!oQ$1DWnIsGBpj)2zM<&f at WaTX{|8W4=
z1Jlm*$h`^IppMb~xoC4E?UoS1S1{x!Jw2+=daizHViuVUm8zR{TeF+)BC0-7#v|N<
z?u>zvDH56o-oP<$_aK_GFJiMeTMx_-qm6t-eWfE>D1-DA^ClC07ULo@(*f)5;ecOV
zX53(SIqXpLg7i33p?|;3 at fdrLo;oz-65|h9im!^t2sD3(OCK-u)jn-2BSo100F7}M
zedl-iY=95IvuT<e5?o3HKhF(MvT(&b7BZh1JX3TJ-nl4h{`6j)w7_W1BNBW<HX}r+
zGU&rajO*=dPI{;c|4g1v>w(+^+az=N{ii!+FrI3cVNs*Tjh=c+fip}=1O2i@@1?Z$
z at f)|cUr!}bhASZaRr2{Mt^z}^;j<#M3Lh`XB=AeWHVNnFmTP?q5Y?ZR6*{3iJtnoB
zbI*)_i5d|$D|EMo$P*&NNIIch5k2&N+brg*Lz5sHyCYIxdcx5z8cHY!SZM_)$E|O>
zsrzl(lUWITRLE4XF{$(O#qKRd9Q at 8luBoPXBcoBaZ*Peu_UQg5{j$&hmVPIZE&u-a
z&b=?*-lvK|<5SbhE!%eLIkuL`-r`PdNsWTB)qKhLd75yKXy#8}luq40OUnXtDev(=
z;Yk}SwA#qYjg5cgfe1 at RI5BcXj(40V(>Yuo;Oe%z$Ab&_83Pd-eq3{9a1Or)gN5*r
z0-fcncUXkH0n|U5M-bUJk&kSxBXcf370W6UTnWFZXL_VABpB4YsG32uCm0dt at e@tJ
z<<5Za_gh+G<CqT5 at lN?uxI4xVj%am4zvqlD?8F7NRlsinn#4OZzizW;_zF9Mzx33#
zyV7zPXGjc{T{>hoBZznul*U-hGqucFPDuei&mDSxwqZRG_{bcVJ35VGIi3+;#XesK
zXe_e>U`||nvH8X}ab(EV|JOFXz1vG;A5WcaJ$eB)nYxkST-#|sP1&UKfEyI&b?8g3
z5YsGAcw(0lLZrR%=e`9j at L08vds=k;5<l^<(iFAy_TXsI!jzv5Of^5uq%zD at C0~pp
zw at t^xMTy%05QqF!*|_g#qFl$m9h(r%m{Wx)!9%u}{XEKxW1XhFQVl?4mt5`&(5Ee9
zmK&;2$%L4GsVdFL<cY=*Xyd%72R2VRE3K*C3`vC{MS#C9@*_C1LbszX8z>L)f8V3y
zd$+V|@-BYAe}f#xPAe`vucw8GQQ#N at O&>h50MR$OZqZe6_ at N*9A~@EHMTz)RH{zB0
zSO`<$*x7Fc(9{2^t&<ipX|@RFr?WE at 6|`Xhw}~;{{?mYCySkc6w%tj)$|Qhh4?IDE
zST;d0x(CY3lFh<a=R<g1%Qu(Lk!bJIVAt0Pt&UAm)eoHm#lXhKj0ZMHwBp-d%QnJ}
zh1w)Hi6k%fop$h^s0SPzMvml!eK-PBfSz}jJLWlv78);pWaI+|Eunt(5Yz9C8pzj+
zi%fP&%Bc^z!<`&{#Xeb;OASV-!LH4 at _nRgqdy9=90+xyH_AvYmTb-=!Qe8af{L{n2
z5I$7Z4XV#q*(_Rfy-UoYb2S-{>P(u%Kv2$^8oeq3`AS)-33;mBOafsTx!ime5FT=O
z2VX7Y3@&c{)Me-~!y&FMyD_UDaX2<$cR3rxt<4XqSFPWJjE}D)oTp%%@@LzLbhT0M
zu1t5Gu0eMZ<6<s7kJY+X;>9Gv{;~|BhX12#!uX9F5`x3<#YE;$`QWP at i*pHcoeLHk
zd@@@r)D0wR(AB%>y@`C#axdMxau`$7NuMA?ag|%>NJd&7e5Ka5+_ba4g-PlH;K%TF
zT=3gS?G#Hr+FRZ-U*tRgWs9#wyzXv!yg0?s^jsat%^)fYcOIPYQyoJ_FD0$-=%qp^
z?@Dw0t{WhE`dc@^fVBa~`_wMCs-6`YG;yh(v=^n?S`=`Rm812cA-t0&y^Ajg)SLx2
zgUXnDm1${k^7MWT*ZihWw@#Nx!}4+bPVWnCkHzKE4H5NG!mncjaRSYuJ at uQ*k7`3H
znI*z3t at 5Qo(~YCY4kGHD at Z;&m)fNUzK;Ef>O25}b$6t*Ab8F*zKs6|;cfCTSu1_X3
zH_86Sb8nDx1>=@?^YM3Tka}pkiAGMLTMR?}*Ou)>I?KT{v-RrGab;$6sy7gY>>s;}
zE=L2^ZWAdlF`pZ!2X&`@P<L84uJpQpZT1OZc5N`wWweFq_eau23PynBxk|w0Jf8bT
z{6!k9-N#_NccC+N9cO4&B6;d7ud4j at g|#d3l at W;+AlviaU%WPHccc`r5)5oKEw=z>
zhB+Ht;&kzB;iKdU?BXjVaAow>wM3=_$i=5aFK4Ss(%uyUE)7Dvh^PYTCBVZulWbQD
zyI9w8QZcLq;WEiLlFd2MOA>!Dh7lPE_;G>HbC6X+P(b~Nmdc0<z1%s$G)8r$&`NdW
znG3K8wT~Npzr_wB|HKaH`_PX!F6s!3F7F6)p9BG#L(DyDg0^Ovz2+^}LiWPi=F0p$
z`1s;4jL(r)g>Z(~y*TOY`hcs_4{F?_<zvy{4UziOVPN(qqms at 8Fq_%i%>KLCuR8Dj
zmPdDwJekiJ<U at UQM70J@r1v^AoBALT>1P3*<*vYZHkV<}(R!{!{T4OPzITuThr!;b
zMfy1QD`(Aoc|ap*h*So#zJNnH_2x}a|IQ?dfJAa&va>JXdZKj<gIBz2xIbgD&>Fji
z_lc%@z at 3`@yB1RX6A at lzY0N#wl+ns!X8cv&6JwPcc8(@pP!$jzRq{$Eo{zDsGljz_
z>xi$I0$vW0GuqB_4<MJ;SkHR-K(mnVR)%lY?m_ZxEj+kwxr at flqZNy8n*av_T|9;T
z&NDS03Fw~<fXk)Zg&|bcP(JoJXPcTiR?PXCrH9o8|Liui>*SB;X7n0E{YRvGQH{Bj
zfD!fZK_<`lAben23eoU3&kvvF-(-&WADQ#e61hg}_nPOe0(AR9ame+`rw%n{opjuu
z<1;TqPb)J&*XrX|dr~;E>DKit!B^1flV#B+OOd44G^tZeQpMm6)o0-P;>t_v`wHy1
zEs+*B>SHzG`5+E{#o*n$aOM-L6?l0NTJ-urG83J5u3eo>qx|`Lw_A66(Dtm*@aeTh
zVn&F~__^1L;jN2qHy%g{%6zyVJ?oWPvRt;X+O%0k(2ez7hF%a7;ip)X4d{*WDpE5W
zsZFN>Sq^7v$?sQj<RAQvm)8EGe`oIO!!v+zFuptEFwjLEYT~jpOSn at Dlg~pW>bFM@
zxifO7))Q3u3@!_f#i<qyDlPC0xKnY+lLbU;&Q2_k<bUE*;nn)}@0z+yr?sQ65k?f<
z0N2H(kn+AtWta1_*Ng7`4hR=ai$Yl5t5_58t)75wHnBkmg6!8lN6$O_$h9x`g);=M
zmd at Q*il(YASTHy2FjF*;_sqnn^mIeF#|4ZU;akghP+v;N)#=C~+CBZSIT$_KYh3PA
zlzvK-EGia|i)^0t#aKxM%(cgp#NNaPz<TzEh2$F+Z$0 at xLSh@O^+(^#2%o<7=Dbkx
zI5*m-rK3Rgez9yp(2SWT{q<0Hfv(8Cv;4oN+2vc~Gl at B1azIX+-<__5$P}C-xu(Ei
z-up#tmRyeL5Pp>c5iDAaF-3$uwpWO&`3MSHo6Ach<oJ3y^woU`*-v=i;+|?*2PA~$
zEoFk<Wp($v^Ml2#c@^R|+!oqF#tRRuO11#OF8fv}!w;8f<LFFE{2dGoM!jJ7k<R;L
zJRnacU(r#y|6LqBfTs4TO%w{@lurNM?o<6^_Y>m3YClv&6>zCuJ(uVkmlrMT3{s4G
zgl0Sgf5`VUZ&xXQf+xmL7e2<yawNgrYdeHAc(@_TlfF3yC_fI<FWiuX at uv$EuZ7#o
zpkN5j*Z{iMuaUv5Y;P*Z;@;AnK|FOyT{E0UTSQKXW_rkb>*nS?{QEetzuOZ1yDPsb
zj^;m#lb at XyXiL+S^>rd}S<3U#BxBIA8{>YNiO7RHKklmud{80`Uv$FIOI<d+)r?W@
zO9j}u#ld;5SEo#>^yE#bWge at D@louoqb;(z3LmvpUNA at IPBR4dWq8)dKNX5$NOaab
zqLqy at nJ+RfdStOFs-SX<R!>CmOm3R5bQFB$>UU3{xkyBgU_3*ggwU%EK&^%osi<zx
zUQR{*t`mSbNDdOda<Rbjtb&Yx+&*;hzs{{a4*Q}_@)3}vBs$itfuwDyKv!>oxKf)W
z$-B at m;1FV(mCoSGBFmm;8zQuMMu*HvKQrJrU7Mr>JC&Hobd5^Ro4Ww>^b(yIN%G`v
z-(GSy<A8qeOO8nZ1`jHCVr?YJdx!F>y85PWtvi<HYTt=_0KNx3BRS?9t4AJiyG3g=
zkfPI{Osc0J{!F;yKRp2!QU9NdY)Rg8Z>9tM1%#zu?(mro%7T`9qFy35cCrFgSeBhj
z_?NkHjmq3^%sTukNx}5$tMyO5o0$7l8}5w}hXQ^qO?btp_8cRp74<=r(tisRxdO{B
zQFw$Ve8x3qUItehU at KxZ*QYwWdMj%wJ8QHy$@Cl-Pt~O)6LUR4-RxO=lNpS1DM1o`
zb!mA?GmnFi9e2tf`*#|81d<gO8}L^(E%X^!Pzw|_RCto(b9?ibB8FVb7A$<gQ*`ev
z>kUfM1m#w88z&ByvTC(jJ9+^1hH0h_m!_<%!06={CD$q79jzT}dJm$65<t+{Fn565
zdsSek=pX+H3cZ^Ug{v-LcwdZaiP3<r<Lawnq%;9#{I;;7l}_ft$mKh#4k7O2=c;~a
z8!-fe`s({z6Zr^PLRUlvd(4<}#8dFPE17MrrJsDP>^Ln{a at SPZ+`YoUe8er?xM2wX
zO-qI9^Wt4?_oSxz1ZJC=oB*2b-XLv|VC2M$ar^0IQ<m0S#BcW#6AmYwwz}k}Ho*Lp
z++Esq!y5jZ`t}Pc<De>Z9FJMyE at 7(G at e_V!0^%F?Q~m<~Iw?p3C)mDiRE^-fEGf6O
zYbCcln$Hc70_wDi$^l;|(<(Z<)qg^kTszRpW`EI(10?hJ;&uZ!YF+K{ehwqzGdw(4
z3?LMmv$-)oS7oo7#9t~gA5IG!4bn@|!O9YJ%J;iiT at h~#qN at OwDpXsfZ_bt?8c++k
z?pMfuC4Oe>Rzq~r;Tjz88LAow at m=3dDpJIH at pWLVQPAK$RR={ZQxc?sG_lm|9?6~q
ziu at 8wQ#m at z_MSVXzHM{E$B{CbQ0c8Sf#w2KIlN?ouY-&s75OX_#Lp~AEkh&N<&CRw
zmK97TDnkH9DU4s&x^bpvE5#ycgMaVfeti at ehVp%JX8Ao;_ZCYAUL9|}_i5m}4Gc+E
zTCZa`slWC0F9pI$+5yTX4<nx{f9>2CK41@^G~VO(IhOD$Ht$MMH#45e{vOxy1`w9%
zmJ at j>ZsNji<2nS?wxHYhRMZq113j^lGzUeXeAXpP9LTlbVio7Bbo#tx3op6;0Cb6<
zt|YHKjrIdQC!Yp at cYW5+M#x at a2CXM{N^%__e#{*%AmqAL_)!bV)J+D)FWi_dJfkRE
zZJq*~%QFY;TwdA>`m<%Al>nydIspE^7iN!-XeQ^c+U5T3PhWHo*}j=P2ZC<o`gA-m
z#ZAD#i6h$WJgzZol}mu+PnNosvXoWl5k at z6MR>6I+Tn>i(5(#J+R$?T3+EHl#XO!}
zd!g5AQ1$RhjakPysNOWof+<#ZZ(bo9_bQN}q{fG2NcaW=O)t%|y6mZO<Bgi%jVG`)
z<jU8POI9d3?j>^KTk1TQtr at Fg=p4<11cbR_KLU1^Sl5vT=gtxO0l&c09{;F6zXt?u
zkS_ZL)AJ3?EZgINfjYYlY8Ru~vU?t-s4FNz6_-hyB(pduW5Q<|gn&IOT|^p4KOQ}-
z at XI+Ghtfp}6_#*W-~47g*S{ciKT+IuRC=^x6W}L21{t!Y*yc{cWiSN^RC(#=^=^Sy
zfo5yPNvvu1 at G9^&P6j`!8UM-(0Cd%-vJqqd%y!&AI9{O-P;ChM_6gwZ03rs??p{%7
zVQ8yERVk>$PR~OwO74DqWK=StoD<Jio6T8hwfn;UdJqmA5<_sW6=E4Yd&l<<(8ys~
zAvP>^%d(n$Ak_U9fsM0KHCODuF)l7BL(C9}LxTiw0zgHb&aEOB5}v-tx~3!xrtE)R
zu_V8lk`~$P!<5<DRCQWy)6n>rlpwWS`l4;l#Jk=-)y7a8)s|3ntMlqb5G}DM@;yH<
zgq2%-{m7_h*#rD7YPu at gKc!sLbaAU5RKn2bD)Ilq3<F<fy-0^~+S8S5obal`6yU8u
ztPz2s^{PDavsJU{X99E1Evnm4r5eo#*zMGnhWjPkOz5kihPa+*%hP?z#D>sLMc at KM
zn!tDqCNSt+rPU6vfMXRRM?FT_?7^$nDuCZluE-1a`-T{MT+K3nY4vV?ikiSDNO2R?
zVq?)a2xgl%d)wYc<?Vq{W&Wx4pKj%lF($$K{XG-iF7T?Rpf3KNbN|7V1T=cl8jGaq
zUTruGpLJ_-28n69w_&!`mtJedh06`__VEw?Hz=NIA=3CdUNIv5|Mo%Ksef0s0M2G_
z$5H_42n$VgxS2Pv;HY(N7_Hrrem4FKPV!!>;8yadn%aT(Ns`?)UtQ!>i!=EOrB`LA
zv^2cWoZfoaPvlr-{p3SYQfGg++y#7BYt(XzAYet_#s3<l78az3*8mfs%;x=2l~f^I
zbIWL+=PY)>F-A}T7?Kh`K>>|TKS4o?T&dl0zf+l%smscYfxWXCreZL)@^7RSg7ct+
z5fcw2Q$94p9pDGC9aIP~zP^R&Ip1rBoO}*-$o7)=)2^~<1;8Xe4jC=};K%zntMfNN
z@@fBtRuQ^+)Bc6V4Pn5wKCFZS`%i{f#LD_WM*;F_ at 00E)g9fy;gBRgsZR58(i&0i7
zF9k!>y8uGV_tr$S)g@&W4c;}0ryMBLE2*Khtn5h9zv{&yuTlh<@{{&J9!GL6EwR1=
z?|g8!e#EImqH5iZ%hix`@s@&WBQxqizm?$7{Dt at R0rv#16!@ziS&O-%lco#)t<K}K
z0hztb%A`~+uFui+7yI?i@$Qtk4k~Ot)n|ac at OH;v^o%}ZyuUm|zRX+{6jpe%7wZi|
z?`i?NNvgjJ(I5g~ghFPNBWUgJoDjMtYPYyHyIn&%E`(mQih0tHWs>~$i9Miq&^Y0w
zu8xL{DoqI5ac)p$)b*e%va{Uo$dhgA5=wUh->>uR%KDAF_NqE|09|eN`rgaz(tpvb
zx8r3`*uu6;zke at hL9(M3x_}=z-E*zw#ZdbDe7dn%5Un3~ry%DyojC9{B^M+`Yrm%+
z)uPU}+cM)ONw^aEM)X0Vws<j|F&mnwV?q7$s!!hTVFps6m8*U}`Sp;T|L43;>yU`(
z7n??yjgf{&>zWx<w^O5g_uMju at qe46#($e5{-9V5)y*3&)g>4?N2?##`Bq50Zu_)4
zmsLt+vaGT}q)BPw!AW~T=GVcjziN31^r#|oywYrA$Me>Q`%mHEzKUlkI&sOt!lqjC
zt>r0IcITW7`s}`B?|<P?5EyVEM#<nFMfaE`UtFzU4aVz)G7tbFFJxHN33!B-08D&4
zK9S`UF+0ty2NHZ8HPjVB4p3lfuFV~MmhhGup}a6H00cYjjqy7J^k!oy{!0X_T>0Ly
z)=|aI+s-9l!FxD*7D68~2uA3HaT;co-cmd{>A$6MlRhi<o3WYUq?B{OTApI3wY}n0
z>F@{r3_6SSZCVbQWXJ=NuZ!-H|67?MrO0 at -AEye#F8Vk<J=goG1evbG8`X42Rbh+6
z118q26Auu{IUFmF?fj=Uhnpy}5%r;LrPMSmv6{)|L!Pe6aM~2jAfJ>(>x(p4G at t0A
ztUo#Cp+uf(wbs)Eg~<4b#_cCjZCVjSxS%igEqmaq6+2rO{d=vu%6Ms1hl;*ZeozRz
z#JoMSij5N9=2Tq`MKXex%$gDFNOrjL`b{p##?t?`xwv1z`UkV?fRc&d5BKjv^j~%*
z at 2QAzf81nO$U|g><|>7Gg$@MczBck^4H)3%xG^mg!DQQh{GypJR~BbuulaLM+7smX
zDQ+wNDh$9Zx=}ekTNx?}+*I^<_xD0Zhvr>nprCyI)<KKjP-Fjdz7i8EwJk$a01@(e
zf_m_)`3_x?hDEnc*>u0&1U^(r24mo!)8^Qwks?Puz+LNI_Gja*zmiGmD2-O6UN+Zi
zhyms_*YnJdzFTpCVIwm4hmTulk(`^djH-ikq~jB6cWC#bE$>x^!e50Z*3E at b5sVJn
zo5EHTACY at isYG!(AM3wxKL&4Ohh6d^Y?gaA2h`6s|B~GizPH(9+`u#1dR^E4YlV3<
zOu+@^K6o(-cDUK70H at DRVR&v#qZD1lW$huWY_K*;!nzM#3}cleWf~OwCHz!NJE3vp
z{Gk%L*q7)0Z{{MHUtO-FncLjjLk+ubZ!V2!xVC?s;_Cx)dH2nj1TP1ud%q^LPRNMv
zO+w+sdI(?~z^{FCZ{-bG>xyV`Jsqf~_`eqIQ|Xkef|6?l+r6b-bz7|2g}KJYzpY&T
zZ>seFqwK5WqT1TFH=u+d7=+TO2$Bj)mmoHwl++;7UD7oQh=LMINQ)>SDV;+~$DlL=
z4Bar~F!Z;^Q+S^9p6B`e&mXY&T6f>qeXX^BPVgTubvz-~x-kO!;N|AKK4?}bDgZz9
zdx)C)gX{`s+I6mLfzthCDHH`@w8-BnOU>uy0~vDR=ajdtV*aGBf?Gn2>XL&q!Lis3
zfY$rm_ at E7=J at oEFyMj1dlN=(oU~z6vL%1fGrt31p5muG|>?Erk>)sS;#<?9|cr^`M
zZEg<yKUSECKT#dDBwU3RPfh<x-|=w&=SRYyKA{;|SOMK=rcn%5$aJOzKN!b07tvK-
zhXiQwQ- at E5ZGUjAk3zRO*blm}Gl0GrWdJ51>fG5uLjtqcvok&(e*VKwJR|i|`Aq`o
z$17%dl-0b=zBwJt(Wl#{>l--XJsqxb;h$v&cX3-xLJ#QQ+9=>7VKKKr?+tKgoBkhC
zTUi4M3QzG70;MyaPKa&peD=>GVTE0k+*Q4dM#pVRkA{Cf{m;9Tk+9sGq7w%(Gt;hf
z3cGO6#$|H0{-13tWC!Mn5$@RwIS<&8Nk7RYli~lz+drL7hcpl+EqZyxZ=wfYwqzey
z-*ar?CjaME{3YXA;r*psK3n at s@mG4e(=88);qQjq-M6}-XW6KEJ_B$Y07oMe=Bn10
zVw|}wOAQGG3~&^+5#sCU`jfxc(K6PqAUG{nWs#slJ(ch at q-t)GTqemt0*Eupi~BEP
z3p5eLk>K5r=dgeYYT!?fk7x+_j3)TNN7FA{4_4tpkx}NBblqO&=)H0cVlc{ZIZVrh
z;0uLYEi8**RGa}OrIz4O3Bn%G$i4+H9UOX}VRm?oR1S8B$*_S+q+7^y-v_Yaz at z@g
z^J?u5mjbN>o<gxOr at ge=j3?vS{6ePr at v2*_b385P;v?WOw=;m}eo!tY{*%P{n{Cp}
zzd?F^HBdTZ7)QDAgx_kvm78P??sOD_zn%Sy2745ear%j(0Gb#6{(?eRloCwz1l>9h
zF*wb$o%!?+W+(v^vbB^@&|yB=os)AKv)K$UP9$N4AogF_eDKx{Fg7QC6|{Yv0kuE}
z!!v;(c+D at YF&SgGy>WE^sAo_fo<WDForM#DP0If&)7CtI@$_WKbBlw+ at mM_c1vqx#
zZscE7^XasMWB3qYmK<5zQ@~R_fO4R)7l-8z0zpCh57c%WKmPiJp42;nF_rd7c+o>)
z5r1boJ)Q+rJnebFC+b;SmMR2=Nx40AaI=i??>6zlyiL2LnqJ7-d|KDdv4gyWmn0-0
zz79O|er5O}YFb+R;-(_i)K1LyOegFtyar78{zCziC}83X;2H!#7}iF8EYJq;grLXn
zze;UVE6*{8sVg&OLn=AJ3e<rxQSe(|5x>ae!zzAa)@{+zcoQbsGrWb>00bxqN+tc9
z{gjmMTeZe at KHu7B_An3qDK*s#e`BGU#~tVcnx!F;qIe6QAl3pqr~*VN2;z$VRcrz7
zR8`?NUgm(EV(F<wZ)xL!1*ZLBVQe at H@Sf>^d36Z0;1qumIQoy8$#Hfuf|`T>L1<jy
zB8a>Dm&j%!2>PS|FBVwhFAq_+yV|Nz>v?TKqt<%}FRQ=t5 at qT-2wAjd<jB2DN2!W_
z3V`RA2YlVbZ at arW-N0Zeb}Ag$C={-6n4KjfHo2{%i}Bb=e*@TPczMPH?Cj_6)1Loo
zUCoo-V!|G)?(?4arNvXVm9&AHep;6*%^{)_KqJG_B_X=Zp(kzHg at v8%B^`wxdvw{1
zGTRg8c=P&|?g`J00tQp<;a!9lAL1qo at hTb2I|Giwd_><pL{$Bp2QVjmhg^u~SCxk|
z0K*CQVTR+a at bGS+yLd~^0JHy$hQwO~fck~!VM6fx9}iK&K>Ei!#x?UFtuOQhxMq&d
zMBu$L>gF#`3J}sCFaYZUu*>*QxkcSQ{Bs#Gqj?=&8}GfmxGTG-z||MwygF+UKgD4v
z at T+}=y-9b>0(7(a!SNH~<w!d$p1bwivQjK;|Jo&MTy7 at G2#6&)Af}KYx8iKP!Rl2J
zmKGYou#f$s*ASb9Z#($30s<9~nrLp0$rZe654)typw%$;-EoO=MvLx3A70NzPoM<j
ze~4u#Oz=TdjH$0KNK!up0S=)2^th1240A&0Teurl_m|$PtWvY*KmEv1 at bEray8$*^
z1UQj7VDj3gNH5TeogZwY7cx85XVj}(%kmI!y)_Fkgqtm*Kh;cS-tLi&_+l*i;`WM)
z<#xGIA)sQp^=8Q2Io8hmJFfjLZpvGw#s-k&GBg7t)RWW|=7$bA#n+kz7wbu~b7r&e
z=gB9ulV$9*1Y+yGvu>W1!2O^W_ioj!uC&%vbD2yra#}FErCH{qg&IR9VDTbPs~KR4
zyqGPT!i#9RiS~>-Ua?Fqo~mJzUNOz=^mvoGdN;)(B5N(n<k>HwoHfgsPs_^ZMZAT3
zZ>s)~`)2<4ciH)(+R5&827OY$6P7iNv~epcJ?53PJqH;Eccus-ct+xU#11Uh)I`_w
z#Wkz$*61R0GYuVYi_7+Pi29?lhf~?h7(CC6iE74jr_jtSa!Be&gx57>S)*)SQy4RR
zlc%KCygSpZ_kP(R(6_F at XI%n#n!tFsnUL+bSL)}plQ$Ty_R(ezjp?ot9=4R1fg}l9
zhM5C}MOYH2+*fPXMQ^<ICUx(#ZO%AU*bbJc7d^0Yf4Yc(iNY7&fop>aK5%7j>A!jt
zd%4EEyDcwid*dhBJ?q&?y~8=O(Gl>@p5Rgb=t5I#Xa3TbGF#cDAMz`s9N64Fcc*+K
zJ>+IHfgy(*L at 2{`{A9(GF?sboCj|ztSv73EiL^`ax0fu3&1WK4`=X at jHS_M=z<QYJ
zytn44E)Sb?oBJl2MNi?Y<E0}v+!wPwuz-laiNM8}EaC)%3bx-c4B$Dsz4b5YetFhM
zd at +Q-N2g%6uYIagxU)<|*1F7MUnE^K<;9yQuQz!64Vh=w5v`lMmIm0zFeAJtJ7>Oj
z={Q8$m&wvB9F at Z}d5<%C9pgsSx4x*%%yr0SG2e*k$%|rO!84<wu_tw#NX_gD5){Eu
z>A)PXo=b0Qqvv5AG_oTr)?ajIqN}nhF<O{&q-sgIQ*&AyJJXpB-*q$RLrIR(m0N=(
zCo^bzB|oqs!Q^<%RpVzNXAT!kGFk7z8I$fbUDT4>ln`c&L at h-?eLfbk^}wRoh6sG<
z?xhtjI1-;w;UZ;|EAu?r)=V*o2{jBoeaXGUjQEnr0H{$<S7dU3yfrj at lqyZV)Eye$
zo;Z#V!pO5A*flE5T5CY8bVGYJOh9SOcdE`Du2^IW-NAXg>aS?==^tzSim(I6&v8!u
ztQLd0iIRI*3J_$L(q4mP7ZSMSr7zoY8W1rtF1_Hxt-lzMvDTsnxtESNOFE*yY1M|V
z at 4Ek@4I*e>KmB02T!WIe2wy-nW2PFi$)}DO>W2)b917uZOQ&79iq**S^<lr`_PyiT
zl8-l{y9(B<#RMO1^n#PUkjx5SxlysBRB?(G=m#*{^d)kK!*~B2fSI)BgBC4^^kD0J
z=HM0+7Z-eUG2utC35<i2Ka+BBJQdvKk;+hRVL3s0*uD&t at bxWyfqDP#rm-8{)kaBR
zulgLvF(x~OHpXk{X+f}nJ{bT0lxDh0WtL=)ZMbvho52siY at lnp`1In(96A>|fkuj6
zX&1H2(vj=pYmeqVa0Jzrqe5pz^?i%<^dl@|ESSm)^3Ibm8(SRYf5T^HnXUV7Sapm3
ztT at Ep26A{qC^zA7rp|2x6zkk%-e}ZzLZmz0l|~8^9lN_O)ek;BJFmEmb&xFIwoV^n
zMcy3ts=zw}93TZ%y+H{Jx(?@eXmp%^N5)uV%E4&>a4N#RY-dZnJdn;XsG(<Qntk_U
zni?wc+bAk2IViu}tze;oj>F9e8cPB9g>yM?5<p>=OX9)WnFFprt4a$S!=mRFHou2-
z!*@TU9Oc}EZZT_~=@YZeu5_W4%Ruj=i>KW-kS!UQvz{4k1ept4$SIdeXza_MPi=oS
zy`C4^8?=#y2pu}+b^dp>GDJa)xugH0WXrLy{U17FHkeiqJRjE?W>@fRKMg7vuKdg-
zz3`s(B*^hEv){VWrVkF0K+0lsM<12Jbn&MwFxw3M=_A5#f5)-=Wx2x^QH4ewWvvL)
z>r-SlJ<h3xa?J{E>3do(jz$AN$}-f4qJ_5O?;!*rWmLB4^`yl+w<UwNtbiOHW>yfE
zgA*hO3tPkN=jc0sY&!6|9I^l)>)*;AT{q at 3KeOC-w%5&(pV>RU+w2jFS<@jH=pj+o
zotERwJ~>3AKetw-J?-P}(mT=!J6ief_kltqG5(Y#xZkQ17{$D92CGAf at Vx7WMgO**
z`h}L>-LKz*M7EUYyOMl(_srvqq at f!nwQ%P?wo3miwcZ4ecV_9W60~;uenzOVM#ZC*
z;2s|Ql~M$a#Nd7>gP~xFzB1+O#tt?;{EW`lx%bl{VVmAA;#jma`z2CH{sD5V^o at B+
zJ~zd|QFwquO at Bjw!8nIo at 6Sht|M at 5%|2UJR<jcZ3g|8 at yhK-7JEhla0h*2E8#ogvT
zU-X3~?F)*iAnJxi&DLc-C{?8N=Dee-4MvNx|I0|xpIZ2!8AvN+(()8@?r0Q$xh9Lq
z*b&rnk($tMz5exwwo at -i5T%gvsUj2n&OVG~;PypP>Gi{f8^YcjSq-6{U`YFidCaOK
zHcGl%)9X%T^L&n3Gw*i#8pXi;HNL?quN^`4l?E+2-Ax4lLFz^;oK*`}bo*U$X3W6z
zZX37WiAJekLe|sh1I+TizdIdABW0;5MzFZ(n)&z3Gskh;+1v;*NSRh2`Q*g`p-kPp
z^VAimmav_23wuMBJ at wK*&q^7V0^*yENsw4=naHJ9sRFFxS$g6RF5B(J=<}?dezowk
zc>QBOYWt%~BK78U-<{u~*1`PV at w0ZuT{TP{T5mMPjD5N+)*!s^a2EdXhjaZNGeMN1
zWFXZ{_p8xLD1^93((xUW;D<_Qg`kC`d5J%vH~4*LncggHW3+pD;Jca`M(5LvP(U)~
z^^Rnk`TE3B<&!g`6k$kwFlE9!DE{U7{(mU;se<;B#Tq(cRyUUV=5%q)2&8<~(_|3y
zK&|E5^byvbjtKBQkC{%<p0Ap6m}tiSTZ at nH3V~BtkNm(Xtf(x-J-5Pz`K{r)JNi$9
z%E%}o`A+=8)e7!=C_=eZR%FUj+!gNaz(i*8FHU8;-$V?kDe24fM_D`7&<<4EJ1Ok{
z_SY8PI_6~>=(e(px3CYy- at iQ$cn0d=kg7~_Iv5#$0fs8NbWsGNRzRQKbXV;bSP>;?
zl*(t9*_tWU?nN(M6U_}Cs0J4UgR80Vln;TpcJ0lYPn>D<_&>_zL1#C=eT5{G!6xz&
zGzag1X&Ca#TXOIY<Dc*7be)HVQOi3H9U*~#44>Rl)BNiNcIfvE>!LU9 at qii+-rb&x
z*$f4~{g|4C7h-;{M<v(2i&7j=zklHoPU$5E2Op`1+k8ND?|+I;aXQoe!9(h9N4&Pw
zBQP39O at 9i!!PCIHWBN?fQ6Q?kpQ4&AcjUht at UhhTIg!lw=GDD4o+w-_WXLjb>ceia
zP3!S(#`7ikI}2#R$vs1{xS}QbI}cQQ6>PzmxHK#D!r4zJoQ5yRX%mLAY`!VEo?vi+
z4&b(}0Jp`Tq3A(yUJ95i#cPkdWivh3aLj=)#MAU1e099yitvoX>j^@@jnH&?Ll`pq
zbhwm`UH*Zta5cdPQ==^}wTM48?KLPoMeG?As)Og(kpCg<kG~09SqNFa2 at WbbWDZF<
z<hKnz%|`48e}=_ at JFg^Hn=dfz;MA~HbK;iW2~(&V{`jvO-r5V@@^zfQuO}epnlw)i
ziC*+WE`0vAIjE7?m^zQ`E8Qtylqtm<>A%YCvqP_gL56GD#Zf=mhkMD6R;z2m?i<_#
zFAlWKC4**TJqCqH3cm(gi*w^JCd1x+>A?XHepd;#aBw9S-b+h88PSlvhUIU4=D%bD
zs*;UDlecAbN?%8XDc1_mi+=}8NWlcV1K(6x)>in94E~F7!z?G_rin4SNwmioO8xl?
zQVIuclqz>Zba$$5x1?)yg7jN$o#2ir(v$yTFel15Sah$0e5GH>xQ|yUsL+QJjf_VN
zb^fw4zYWN8pfqPO7sWS>{egvAUmO`}FIzBdh<6GrV(NeOm*E*+1cul5cf;cxe%+f)
zOjc1yQjj0%tu+$Pk9+!JF~`{vm4Y2EoI)a9CuxiZno&MTB>+?id{X+umejRTMB4q<
z-Od|@k+ftoszQI+;X&Ln;6B!Wcb@>~zE_gZHy3k`JG&btk~bt64Q(@?S#J%r_I6pk
z!C*i8st2<mCFC;ks(KfEmZ@#XdB06=F{d|$SaSP?uIF84=b6-JmulU*J2A!jw}_GB
zFP|Zvd(wJt-LUo8+YCTUzOx^6hcViu=nP7W^rX7;=Q~zPu~5)eIB!`EJKS3N!)KH|
z2$k1%Hl)k49cKoFZm-9=*D7moq5Ll4uO!MkF!Fm->i&tz<G!#(Z^@etso4`Txii=>
zu|R3>f$apx416Wbu=#DHXM85BCr4YH!DFSVaGMH=ugOqHg%b?o+ at 5|Va&yeRhbgWS
z+HJUZso~Mi_>af#n^JK;4We$gHtvt>CJeX8_1cd!%N+=Q8m&0~{Gi9;Sc{33Tk*GE
z=l(dlBf*KV(6r9^VrC(Sak)#5$|Zlf;(cShE9(B`irKsQtteVpMll&BwE|hGt~0uB
zPAF|LJlwFOZtwY~w!;x8 at i`fbjtuMNqC$Vip8fL6eNJ7M{E1%?9lx^Ffv~cDmzn50
z=b7qum$Y!e8~wq04Xf~6zfuXYCj;)gq%7w=uwy8)JRz|Ze+^3-RI-*Oj+)h=T#n}m
z(CFJtPcs^Gko`_pMWz&75a%>Jt7j!6$>7#~LVT;-t8jYQeK*}I)g-Xe<)S9|ICU22
z=&#o(E>?qX^4MGRoy~<a9{b;2abX^vLfu2 at _3=HM>q!|q-*ul`=>|(QmNMFXxMqR6
zaC_jIRi{Xw*C_Aw#Z8U3U)54*s~sfVz=^?jN0Uc*7yz6Tdko4}iaBb)it=nfgR)Cu
z%G6)mZt6{eeRo3)@Ov<_(`i37wRT_1OPTCW-^ea>)QDM`yNcP9ojerzub;SK3C?i*
z?zUeF4GKn#?+ZWK<WC5 at Pnrhey3F?}ZwW~kq#F~ujhnmueM8nUEQ4*e9d-b%M_M?m
zx0ksn+*T|A?jnU8lQPx(YWHeKEACQUEXr&|JIyaxyhGv7HTW6$RE8T;XmF at ws?>L`
zQ5C*}c*<%j^wfAkm_DWirO^#8xi2xVzgFQdv)##KkvMySfNCBsSkeZ#2TdtRx4~A1
zhe4u-6YvjhE|z<H)!<&ouQ!LKE|Cc{X`{N&FK_5+5Tn+$mGczeu%4vrVGtSfWzBKM
z{1AVK6597vyWFMmN>Z}dwoLqp3utm_wzsD$r3G&jGCRp;PYAI?8y+v-(4X2xYzGIv
zGfJh$XVcl8aTv(oGY<DbN&<Hkg6xgIXd$L-_gWb^!^r^nJR|<|pB#dV<bp6K*9THN
zzdGRzBJ#C2h8EgOgh>t-FN!E%-N@~9cUqW5AachRdh-yAY7R>78`*&m3+FMOtJqv8
zT`dxCf3m6|$pN>9R8+B3ZhY890+-vB*3}A|+?AiCmOS|tNasQ!9&v=tlDs#*MDeXT
z4e#Ca#x5OcPhLGB63OFly-aMNM}HN|UbYZ=jpmD2|GBGdC%KMY(xwgwi<6BoyyNt0
zo9- at G^QpUr8d%4QgXx{Yl1 at paAS%us*;=ycRgvAh6D~SE=zv6+lpwdBw!02{!m{``
z3Uzz3+_}$|XG(8<+0{M{ZJ|Ak;<v>pheRNcd=2fbB1ve16=_#$qUWte$x^xCSi78d
zoa=#V#o#KMk>bT9MYzVXG)$f;l4omsNlu*oxC&9#b(QTVvx^>S-ctK4%DY>*V{-d3
zL^80Y0&l+VWWS7O24phzEBi@>->cV_-CAGBCVCX+`w+?)_kW#d<<%*R8=icO$u#Q%
zm%hw|a+E#vN#;$}`D{+D(&03(KD>LBGt0v$t+!aTVBVA*4vQYpi=Sv4*v~5e{Mvs-
z4eo(5ndhUrxFOqA+YKjrSU4B#x+*>%?+lJ2l95q`B9Ey03&zu-%^MC1N#MH~QjVqW
z&2*6(OZ^9mO3)cy)&Q<<?v%DbpIh;i1RvIL?kWs>nr#6sN1DKIGfKhq?7EwkySKwB
zaOzsjCY8r~u0Vq>bAqBl*>fs-4LDr;b!H_CD#-oKjIj%5x?wY-)&%2%yd0ZN+DgN8
z=>Rb?U1A?Capl2)`||3th!^vRy#jNnk<Cym!KOa)-DEx^baSHJ>=A9FmPiC-$cT|P
z*9JE)XE#`KQa7926X%4A&BvGI@@E2>)`!a{uRf{S5$JPYZ?f(hc^08sVSg>)lIo3p
z+>PnLME4f8kEL&-zsm(nvZpWzIUQ4o-XP<Bw88GV;=NPgD(ICtz)5zDGE76S&qunU
zK7ig<rnyj0`%*$;WaVv`RcsP6sQt5Di~si*c?pf$Hd|bg#EWyLojLW3^GzGe;M3xF
z2u9>2nY)t5my!L@<*T=vtIv}VlR|NkgZts#8O8TQJfObh^u at vh5@w4F2{Dne!IcSY
zDUKA728%9_UITJLC8cQoN=9C64>N>iLbWUp-y!($<NFH(sQ7W@;uVq{n!Dy}Mf1X0
z&Oa==p|6gSQ?vUXjL<60Tu=+4CV9gt`0Me#TUPIUrbjp4ANP~pvNR((3J<f~-oMPd
zKV}gvcA!EpYFA3JFmQk|Gw;iwQA at RFq2W|j-A|opib-e=OTX<%FC}9ozN at 4t{Iu23
z&1C9HbfrCa=3b at eKzUp>CQ;~a*9bBv=OZy=NNI9#cO)7Wi at 9hqzA$#nIlOZ{rbBL@
z>16mjuAWz&$p+yVetw&haqlv5i05J237X2HmqPqI;jjND5Z8`zd(XB5>rx>^^A%`f
z*n8`){?a9lI7M)>54Xi~Z{jLr*OxVgAbQBj?aIp9;{Ba9HYlH&@g5`)w8>JSKpF4f
zYam2 at Y%=dN6Lr<5<@Tuaw&9TQRmbXMZhMR09%H^Nb)~vmq$wAO(eT8cS&*Mj`eDSa
z97n8R^tE^1FVbvg=FU3ywfxnCJl^#!;jE+Xvvn;Wh3-DA=}aRa`Lu+1lQg+mU)1B}
zI;&!}8-SixHhk(!vC$K+!|C5`p{o+l>99N{!h6tGWaNM;-z_R5s=}^VO?3OOIVoIe
zO-E}>PCNx$o^IU_*Zj=}L|oe*l~jmfU2nw1CzLf5;Z_Cra5fuf;U0nfn_=^PDwoIG
zrCp?_Z9@;7*dfC~Hd1tS+0r-r+NZmNvP(xkVcy5zsV#Q4U%J${zxk=V5V`51x3^}8
zsomWwK2N>RK=5H><Pl+rorujkWjS+RqB3U!@8Sn&*q|joDc`WRXnntd+vw}&0z9!R
zQH9EU_c24-qs$i+SllNrE+rTjjYhnAugy6hMEsuKM~z_*|BGJ5Ayb)sJU$4P{o+q;
z(&@`i>3_;+@%wP*gfpFX&XMB at o9B4NFTVp{GL^$(W3odsJ8AZdJhwNCUcW0ERL{_k
zh+v9k_h0eevSHpIuD at dc9*BzO)>NZ*gh9Ymk}xgeMxa+bU#+y1+4SrQNDLXZ$DQ}c
zE{b&v3UTk8WDORt9cjN>d`lO58!2QjS-99EV5W5qiY?UtC;<z5$$b-(#iZ2Ytt<0R
z{msc8XRosq_8d$Sap?pjeM&bQXK|ulox+vp(I$gGLhYktOYA$EYY#SfTI2keuh`72
zKeHFx+QLQo&P}`5W4CY*sIk50{%<qeF5Gu{9bvs2l+3Tg?t{S5>3t5;b$j?Og6hHK
zj(O8#BoduqFF%>sCt&o&7;zIsq-6ACH?yuA(U<qmN3YG^JiFlOy`X2cuik(HQRfbs
zQaW%cA+IdG;NE=>#Zs&5(dR3+ZE%Cn1}tDPf^hpAFIvosZ#~}UN7GA~kA{3Q8)SlV
z&x&7sm?Ka~X|T|;x%<szqVhm^`N5(mt7H3uUR2$0A=<L`3(e|T*InuN3p?z3nhm4q
z!oUO$g%o1=YjNi>{h)?e!n)PYFk}O}!Z?V#FOXiZ?5Ep|ToRrb1$4A#4_~{#*_y8V
zwjPBJmS)kTN%fMVJYbY&A(WY~&N;$5?sD(|H8J9P%DYIJGb}j5Ng1B8HaRS9+MgV^
zUj4BfY}~rTV%?<|)|GE0p7sz?NP*oYBIFcsrXEre3o`Gr;`Bi<dy;$)>E-Cq?xgEj
zm}D^gTC~$BZ?!-Cyhyv?!P`^u>c>S`_k`l~?v87e-{kl12qsqY47!-b%;w&wmXbHF
zc>SjLs=)D>em$pC9~E1w&!L}`<05AVEKEGt3Jnk3hrY<$4_x#147<9}S(`U`nG)RS
z$}fF2aB9&@!$~N2Ic0OQ6tj1e&PwcdoZyazan6{)^#PoE-)0X&UZq#*!v=?RcYVqW
zIVrEjavO9ClSYrl_>g|Xq+ at QwV=tB^#v4pF4MjX_-QI&LIbsq?2~@enLF~|3|FxT#
zs`-^lP6z~IEf9!pEBU>*-$mFA%d_yn&if#weEd7dhuk`eGMSUpuFW+*=4WLMIp;8$
z{Py$GXL?8a#>d(233O(SjYX!rJ8Nb=xf=p~D94HCLf(3{e;Lh)q?knS&Ee$omBCZB
zwmUTN(;9C&U_ at UWj1QW5)O9XG%Mw5HW;+9dD7)d{BLpI)1&EZadxd)*u~I|1F;Y6a
zB^20g%Cpy%p1Maw7kK%m^<;4SAgZcZ175buvp;E3-VoaW0b|{P$ZWZB<*N07M0CN^
zI0~%Oz85S;`a<wuda81Ntt)gtYux-ecQZF7dT2Sh+%YPF;8CHQ+4kI=$$-UQwh6_P
z;1gD at Ym)QG22PI}4yv9^Q1$2}XM1f0s$1^G8++uc6T9!h=<g^=F)6j7B<^;bcfYHK
zDJx%|eEss%*k%~`UNM^A2VuQJWZLo4?yA$APISoxIq)rKT&$3{?QxqT^P~&x5}EY2
zaOblEnN*Y9mh<m at R~*Q-CA#1DB)~Eh$j$yiP;yV)&jO{H0;Sm$8XVl&UTBdN=wsMu
zCt2TZNt*e>>^W2IFV=V>TRHRzH8rE`sHpwJoVPjlkLGVBe9A&%Q|SDIKj&cDLbJoG
zhZFM#cG(;bg-n+eHShLFNviQQ>0XgMZzEc6zSEG)&xF?A3)DoDeaoK*Z#0j<`<3zy
z4$%4bs9=FucY`1PT?gU9shxtJU*yXg3}0##o7x>CJD#-6D4J^e%6;Oida%TGT{meZ
zA{diGQcCOFY+b~-$#x}CO2L*-!J#EUpIUzpu29w5&s;HVKRh$<A}MA_0LrR{yW*UW
zntChaKdR|1Do|jJJILkG9~32b9<=z24?IUaVduQc^SY*{_RH;+x38*)$Gb{;*RDS9
zi&>wVh$?(+v!k@$W<!cCKl+#aA(`gb{z`#Bnn*H8f(q{+CL3!vZ?nLHDK(*Eeyce@
zD33)ws}-S~6p0-V391#NTofrEKM`CjLAfC^I35!0Z#UC0%Hj`aBwn=;Wt<36j+1r~
z9=7hjdFQT+*0m7`9wz#$Ecw#g8LAP7w^aPxCSej~DjGYEGT*{#RkTIe6iuE8wyB&%
z*={-MRPaXnuUM6Ol<}bpTZ%BN7F$lRicF0-X=r>=-74&pZ;BcbC3HLL_ZUF{pZ*yE
z2R(;LHGO)M<Y at 60oRRM%YhPSTzJz;TE4oDLBmQk9=t{8<1?5U{Eya?Ek9fyO&{dZ+
z&KcEAmbSMT21ki2Bg98Wf<(Huh(vt;awLe==#L=&yQAF0xSoH+ at TYAwJ+_&8%Wt~S
zRdUtzo5&okG<?7bmq4)+fL4jNzj@;!a1t6Gov^g-v|D)3!Fv$ild>x%b&1`%3%!)|
z_EsaE-~^Y6{!K{@JHZ1NNszVk-`U7z5|v>OuglscRxH2oP&&igda{so$B+Cu1s24M
zdu>|MviSG`;s~1$f{M;tF~PcD0p%r?#Uujdp8B6&a)kj{!uw;b#^l%~Ix$jDKjFOo
z=ZLl%snK%Vdm>yO>^vuvNwZ0Hed}d4*|-=eCq|yRl&LYWUm7(7oelJG>h~H88gaGd
zA!rnd6?P+m1@|j+szzwY2x))SP%`CTKRMh8o-agJNjmL|>bI;OcGCZ{oOStX3~!&s
zxv<l#hWEUI-$CjBs&w1o8Kyr>t8Fr}jdr1%hM6Y*^i89 at qwWlT34VDsahAXn{J-9J
zQ92U=etwOnnf!bIpx(fBf~vXyVbwp~;;xAi5-ir7oQfQI>^xo=8#S=e{E!gvK+p3Y
zT=!4ITENelPjHdC<{9Z8TK5G3u9|&05@@z+=XEKI(KQ&KBjDxa{S?cvcbAZ;OZ+!1
z^D#gB)G=A!#JCRaGoj#_nWw#|6^-&1MQ-^WoK`2rkk{hhIVH=^<}EZI>7)#O6~nt6
z8RWvhum+7#{*_s7$<f1QUF~R2n;)YhL*(`oic?}MPGCvBP4X5`H2B*1A$@0Gbvn{8
zQ*)7D=`T+Kfgawyg0CZ?DtG+^63Dp<UrzicSNb0ibu&?Vs<NCZ_d2kK1!HmxKNjD2
z{gw>dEH%zGcSn7`&6bk!`f|xWv#ikgOQg^U;wzE}E`F%|J#GdaGfLz-r%E?KiZskS
z>Xhe;^uzd_2G3efs8Q4pU)CRcszlD~mnK_nnHmNnAM}U`Z>>k5ayqOlWJ`kYG$^Ov
z3_t5lVmmIA$n%hKY>$0`R-Ih%l)odL7FoBi>>ZUP0j>YG46gZrm$FL$gc$3I6DPMG
zF7+JgSrm`-6PK;YL>1lH6~0F1Bs`h_MIm7U_pR5Wb~3-$X7GdHbo=+vqy|&;zTWIX
z#a>XMXF*&c2Dgo+a>vFF7ug&r-3nsTc32s!3l+u;x-Qf&qNs)|Jj*I at _tmUV)xA2X
zKgRBNPS%?3_DKBGA((_-wH_&Tjh`uuI7dRkX1ILq)1!(MU;l6D<8`;v9p-7($`n;4
z?6jmEy<o8ax+4p&EBVd*66_vaj5Jx~WMO`en at hT4-un`Z!-$xh!ORK at F+!i;zdv&f
zeA-G{T6%SJGv(z=(zJ)4n}>!G?s~ephD}K6z4gv at ZjG#RPS~)Xi at b~4nG5Bt65RQx
zn~#MWlF#loVz-!9zD_i*G;h||5;f9tO`Z7IL_4EK$9!s=3`2U)_f1V?%|-pVug@&e
z!u$rhY;^rKa&jVf4iNrA?X`G-;9vD=<Yo8a!UXjknE|jcZ@)6IhqWn%?ZqOr>GDXm
z>et8DkCBpwB<h*h)YQx*`ZJf7+D|sKw8u-939+&12fpuazoGYz*JbCWWi_k61C8HD
zSt+&vOAV4K1x9sQ&YOkl#EZu+C^lSY(aULgrh7dxw&OM~;j;33Ru9?2O$p9eTE0+T
zRm7#i+q)Rv(SWZSXU<=}h;nxjX?pggYM{X4JTWEXBlXnYGG`3e`J1m_!g38?RhBYK
z=KnVbQk|mi&*B`1UlO at unRfp5Ic?>L$x-wh24U0AWChhSC#z$Wls(TY@~QcBL`L^E
z(Uq>->$BX>m|?{I!jU&}MHV%pxqkMHl<g;A=TT~IlD_eV<RmE<f?~MtpF`1joNmH0
zZP3J at 5WAp=tff7MCaoklX`-F8D~=q-gw|H=)$okE=IVZav^dokGdb)wpGmMe<cvnT
zu1z7-%UxCkGqsDDX;@fTlCo5&@XhIC$MT`HH>aRwSm=Y3SYmES=EX|R4ao{{b?CCx
zr^2S{wF}Ht527>;%f^DUpK$Z5vY|3l<eHoc=>3f}-B1|tJk!O20%|(C$gK4Jhc!jS
zm22(dPp4X==E=y&JT`{Za*tX$H`2BaUDQ_IEV8|g)5P(v*tnZEj*FhyyH$L<kbBEz
z0MkSpv41Z1b}S!4U+-=mW-I-x+9TH2BHsJEt!^80amUEW?tJymiWPN;&wP+{a~!;t
zICZ{<=&+SR9=*L9y(*u at CMUhS?I37G?6)*@ml-Zl_LB2{ae^hXc6 at qlg*W|P6LwI#
z!ko8-i;eA>)8YX5)RsnaRh0~HmxDS86lT at Wj~4OiRqUp`-_suz>9?+%JQ3ofNV$kZ
z at 0N%%8ZuI(v88%y#Rp|yn6M4WzL<~xF>j7*z%*5lGwa7JM_mSA3ar?f9CpVbzIbH$
z9;4xU8W<dmOw7`z;yEO8XjxaVB9_OS0arOE0ztM+KU;-c28?ilf<~u#{>^}vq7uH-
zJ*4S=x`t(r-QBD$pSPzIyk|!JSt5|DDz)XWx%4VLYKqWa?qk?R6#DIA8Y0P%93#?|
zH;%NTB2w9=bP;1l>NDjjrN8hy`-Qy5*>IlI*M?{c71YocFS*n3Q9h8KunPP`U9;%1
zls0LPJ>j_L_V}-S252rAw~y$g)uLwy=|O<+**jYPU3}T?YCQpGXMknVzC8zBoL^hb
zrP0^Rx5g^BnuVNwHP3Q^7J7eoE4P;F+Bb)zo7566%Oe&XO7Vld!!P8j3=E$!2QdXi
z`efhNxn39~^Mc&R?*S1B<scmslP79r4Sd))HeDq}(euhMgBam=%45)P%{m~Bp}!UF
zP4UNS><dlszojZ7AcEdQzh#IklrmB3hV#qgQloOYk}d6C8}#U&=G?0%#)G7_3N7!j
zv9W2roKEZO>+>Bdb52maeR~9DnMu8Gw~}jnqD8~N9%>xb+XX*Lk;L{Hq?|u@?rr=*
z!|F7$GTpL2cW~a;$q_KWei4jKE_&a>9|b3X2C2Xz*j9>889AK{tyB)N)HqlXoDw<W
zkikRst>R$copLUJxt$5eRTV6?%{e;Povyhrs=^6l8B8RV-mxj~*xBBgcQEfxb7bS>
zY+Tk1puUtl0nCRtE6d;)FNKyBuqf|ETZYRJd<U<I3;XIHTlvm4^DkQ^*^uruVkyKM
znpYoN*1tZh`ay<(x7;C;fQYzbw$o!Yi9Im8Y9i=vF^93&J~|2QeXyrp7f2_a<P{V^
z!(GvtYt(WE>=EC5{a%FOl|PE|T%!pPIsz{~eg>-A!`BI?ytyA3gJ54d?JHk%=Wa<-
zAWwRvo>$bzk00|#+I5*-Cr)m!DYf_UXqHPAJCB{N(WrSDC1}zf{OVQ72=46e-X5mV
z^!r%d6@&T-_1+wR0ABoA-#|F#4J>-0Kd<FrOz1HLZPtJ`_4Y2}MwIT2t2f?r?6T^x
zlk2RcsG}=9F9`@dBwrf*tY7=$=|tnn4XaG`a+u2=W2}HtbN}<B<imEm6hQ47F~GLn
z!0%t56T^0ZcIS{eB at O?fLGqs(bjEA&yV$7nC`;~0x3|vT_J-g!$g(Rnl;a3I_tdFV
zOO(tKB5X?W5-*S^fVGP7 at nvzj-XdYuIh3-Y5pA3hdWO(7e>r&c&?e&Wn^;e`z10-O
zqgMOkxZnou2qlwP$I5s^cTs`iP>I8>;ao;~dUG6wd?4-6!!Hj%|Crm}82NR{ABXqT
zYpkjuXvqfv9U(Y|S0K90e4rQ5%mhK#z?_5ChxqtMAnsHfi>6YSm2=%`YWJ|P1>9LK
z)zr&Hnt8_ZT^sY&0EvJyg7AG0I2}^K{`pU<8u at 8eH-S~H!lC5`7Vl3#KHsybjuSsZ
zO#Fi*!9%t;OIHQm^R~bQ$qd3z`KX}n85gwYCgMkl!?eVIu2*0JvESus%JIc~?=C)y
zSg_QOclQD|UOoOzzeN(200~xT0tG`ATBQ$R=O~Su!xBAruq$~RT|h9r+J&$0M(~*7
zC<q7$ispJUcN#$=kp0hH1QJQ_Ta6r~jYCkC<8L7B8#|~u>>L#-1e(wEbztYoL)&x5
zKzRE0^5oO~UDpj>Ud>&}*UXa9z}0PVj3-Z?^o|5COn(1ONw<>uSGAU%fC!=YcuN5#
zDL&vloFE`z?cJE~oy}=rPFh~J<!x*sr98#0mUg3ZcQvp<XRo29AK~syxl5724ssc~
zuD(7(tvA#Ea7hp0_>RCKXbNxXBO9i#iIkJ6U2j8;b^5w*=&x~da^5CikELN^;<ZWH
z2s1gb8!C5w7k$<2T(-{BT^{)WYRxlrbQ<?{i<l3WjnK${F4iy+T3#9|d%A<QDs`NX
zpeVzkC0bz~M+cJ?Vp5;f1)QZM*lMl>A4-iDvZCKxLrPO(mLkUPk-0v at T-fi;bX=c#
zkfu|Ti;V)|Y4t8xTlp8F6MVlWG*XBFYW4exz<obM^5Ooa<}oNlPLgs*<h8i%2$ZQ?
z7BPiPP+FU6dv_q^vdpSm?h<M}Sj?7Ur02bNtvg*K*nXmMv`dh{$H!;V;mCA*+`ULX
z-G_8_Om&H9-cjQ*Z&A{x+E#<bS4xIMFcF*&K6(OsMEoxcsewX#Do=ALc6_??E)^tw
zYFgUl7uo{(>6&>L5qntF(}lh(u8yq!aFD1zRr`>%$OP-94AWZBoj4%_!bljm2H#vq
zA`z&twcBeyjX}#o{e&|02Tmv=;uNI>ML%*nA&-g=A3l(84mv0?<vu21<>PzzOKAH~
zJsUlM8j-`=#kOqW{CZNlMQf|!UUqhNxx$Ytv^u=5vFcqDF>XDRrdKI7*%GPb6cQ?E
zqWDU#5>yn*2~wV>4M9v%E9KG++VI69f--h!0x4!YBICM~JHB~N3L~{r!u?7uN>{f?
zQ};>EUbntt*neBjb;xD1nZ|80TqFN7w<OM7nsT>2R at 7hy5qaPVyk(%$TY7J~mTJ9I
zNxCWhh(h at Qze-?&AFe}=>Vc_g>gW#mly2$6V%yP*0*l_DX5*X_$~=YKElIz`8gc?w
z2ww7g!#NIxU6PLiqcp2Z5D}9kTvts;VQwo{)j&~UNaFSIQrpqz3k0GiCd|Y~kLvQN
zrYd8a!?~yN&+hR1H8T1^C!i{wLw^79J_WSJh4_3g(j1x7LRx`vZi~K_>u^Gk)5}S}
z#C|d#Q_aW8IrFw#R;GrxYQ*3tIsgjC|8$+OR|Kr+kJ>4Uan#}qw(1*m-DaR*e>xqc
zEsg>KN-O_m03CmpCrQxr=g;?fb&8h-LDUUY?R8KOd$e%b@%`yv!|gwa;0y!={qD(=
zC;1~>NjRrIP|tOKPr7v+Nwi*1f6Y8>VdBQ#?i_D5_(vBA{4bwIRwk$K``l%Y!<{qa
zK}RS9K at NWvX(|B%m!OICb<onJI6fKWqO5vBaaI;@u$PWZJS%E?Be`lU at 3=UihP7P4
z^|ypwI&YIQWpYl8lFs`;JL6H#5=fq}m3y6y!yip7$l5s;|JSVmQEdveNKSzv(5~J-
zD$Wu3Rq5uidjW)Gl9iP;1*OT;*&pm1RwaDo4ZfAW-ba^c`;wl^z&Jo4O at YIq+G($;
zC==7k=5W<?b>50SjXgy^YLL8ChDseB(&i-u11t$1YyJ-_xj~YxSE-n;ksa~uAz(93
zcnez%+|bC<xigz4mSO=4{Xa^ia at VyVKtS*2sV7)QZ;djHa*X=g$}hhsAz>DK`jSgM
zBh_;XWa*aSXU<E5cYs9h^Bs}U%QyYbr+Jd_Eic5AP3ipcP-6&HIkQ1Wpv!cBRrH at h
zKjHCJBO6A2@}xd#Fs|@NqMScIWp+t$$#rLH3pa<J_osbQqSBvhWMor98TZ`Qt%+a+
zrl0p!zVG}lo6ZRl-ls}6W=}HpD&y~la~a_>Jm%L{CtF_CYEznt9+^;l&_$_^yITb+
zT*c!ILam8e?t+1~M_F;KzadBekY>6<*yJdun%><a?>yswXK-f$cx~7H<}YI>a%BCE
zY5CI_n+}(YGD&&h)MaIB<a39xWqN&=Ea`+oc}aJ*aMy8aI292vhePEN^3ssn7YjSi
zsRBKPj5<s;hX+>dEQ#0BM58^{j;p1sE07b&$;lz<_+h)RjN799(&y0`rvk at 0R8{=N
zAN*20{ipZ2u1<)iN@=%$uceZ{56V{OhmB#OW7A!BCNy*9ZB=Xrn at d9nq8_+q`<0$?
zzan4J8ZP~Uud2A)I2o5)?^*AkmO5EMO-bs}f1c#IhaYR%odd at UZ@<(bpLpzVO<w{k
zeSy?0FjpHWvPsa_q9jrwBS0D%lXB8F=$U@`?~2{s-K9)pi%tT%`026N-wD<wsvhj`
zP10C^<()cn=6wgybMWRu{~G*}ubV8%sOzZO=(SP##Js9;i(}V(Z`Rb-<kg(f&#Gb@
zJ2on)Pt+gJ?h;|C|2UbN1a38uAIcOb;#Chy#S3LH+ztSl#MQn$l%KzP;p)|ORZB}t
zk}H*$9mQ>(U&)hEm!{UnasHQ_TzQpHW~pSeG4#B7d)U$#%HN_ZHBmRzxG*Fngc}4i
z7vrY4?5=fBCQjZZ?e^7 at rAZ8{8n5wXWqZ_k=>~b7Z>}tRqU<@Zp>e~qC(Gi`${2<A
zob)R^qZ1ZWhNbpaTlhO?J5#!w-p!?FS9}D~nuCrozWw!y5KFX~&3|{>NfUdh*a{`Y
z1uHKWLbskcdv?o2?!mnXp3%zzHg_fuUR>CZJZR7IV!@8t8M+5C9Yn6Ii#F0;kh<V;
zyAb8KMer*5=(F9}%Hm>?+Uc>&w%CRKr(3H=j;o)>xCyq(riXprkr60`bE(aR0+ITr
zC7^WW0k9|fzpYH<$v4t~fPf!UF`BCrO>0&lqqGD}E~`kp-wd!!1PQxQ<Al>GV=^?k
zhiE*?oo0U~DLC~}&XX5NYHI2m+C|om3yUIk1Gz@`a at ig~deqkh+O9(3`MWZ?x)1u5
zn3it*+J4DGq|jr&-++vI5y<H7R at 4~?SU?QDu=RbA3kEy{Rh|(ZrBwx?`s;ST?xkE^
znnsNy^<=?hr~Q~}F6BDXk43QBPBeywfdZp;dX$?B*kO`lwB<@TJVw|?QRoN-71cr6
z{U`w{r<o46+j|~awEbE6jGz7o!w2aVc-}qsdl6kzvy>hTmY at c}T9!||G+P4b7OnsQ
zcJf~m(A at yHdUjIQD3Pw;?evtk&(_&zW_lkZ at 7lTamN=N;<KF1nRjYw8pp8$8 at lRGf
zdNQ?djjg4lHJ(3`Uco0<W$DOt_F*Aa7tQ}gK-7@^%vIM_!^OF4&X;scuPVI1%r=Ci
zqoANDfB5mntCZ+#7PMJjBukvR8*bXMmXef8=e2LKd0cz^!iIc`QWbRPDY_+_;HzUl
zUY+LXYEt2au_Sn`zM^lu_!2WAdYY(;ZO-<;W!+}vr=P`kSC+20Yk0hvlJm^>B$B<2
zUOj;$_iY-_(9&Wxfali6OS%)Mp?$;wl!8#Fb|ZE0WAO{zbrkWqHk;)`Le;G8+>NYG
z*sAd(pPMl2X^#`1RD)Bly(M at Iz{K<2$T9QK#k260zpbZ=0*~d at 3qZwW7>&6KVyLHn
zCksDLdgsD;GfLO%;!(Kw?g~lxC6${o3F6CE87$trS9B3>;rL2C`(@zk*Imi?Zpwm!
z#JQoaF5Foog%x`aB}gC&@$gFAFEk<a;@*`zO96q(``+if;Q}7S74)_sU9UQbYnM4K
z{veb&Z9_1nour-qw^$^&yfQT(lTJ|eW?7sP%!+;vbHLCcOmx!ZAtYimEV1Mv2bT2h
z+qVv+^Z}2J#iW=!mZ!kHn+u`AaPfw`JZRT)4Iiwm<fWti^W+Oh_-{9!ALPDD2cwQ4
zYw*=BwE0$&i<Z>|=yC0;LvXVPd%|9xxJuJexbYBSEBo*&^WSQ*$HY>@siQ}sV&cki
zFz@@VwFA?JC>5`ZN8pT#j`KYV^W`<VAU7yA1hWX2qirhpZh}5#+GNkHGrd?P1ON>|
zZ{ECVOZQ at Frv{*ijx2lb#*y99ud;ngd^%eVbqQuwZ~}p42OX#V*E`rn6%Hj0b~-46
zOD)aDpOR_Ni~Mq=%RLP at x}HPG#APBDo33R-NXG|l2O;r|e%1W<5aJK5m!wY<Ryj<!
zH<|Q)PVpap?SesU&ir6sGTf{jtFi=b6*U7xOxEUcVC!0-K8XkGZ~NlKG0VGBxG#G)
zcVoZo#z>ZH_FMAm1O#sFHIukHY;mS|P`C7HXpx4=r?=z~H$VwKSoCI<bRmtO9WJo{
zeN7<6KL|Z1pYK{CcjJaHMQ-uClLgl33Wd%TrC#e8g*&%zH-34`ey`B-`$ze&+lWtU
zhGpCQ4_#z%G9tJ6)^Ra9D{4Vc`rlexO)+~2TMynAwi#CCP{2zH97A!m1#u>+3u*Gd
zE=ZUJ?lGwaa&RZ=l7A=YXYV!ytt4%{<zHen==g9wOx)Rev7qTT$bb#VF+uhDjzpiz
z?V(QqK%6*tbA~J9WP1oh)~*B3KHt{X3yYqNL;%Qq$5b9D7Fiq3gg(!GO9p+>Dlq2<
zRh})?Z}k=YEY2nA1sya(zZ*p4!u(svesL8tQrs#Lh02{4=|M}tt&1FMc_}>X0&cx3
z{!o#b$OkLG%_R2p1HcXzH=2rL#GRK4a&mGuLl)Dd#3^w&S~SEKV#(xrd1+Qyr;(%U
z#7**D<%;=<`t*Z^I<}EGap#HdH)m&YwK-r(r at 1t;Bp!KafF^tp{u{LOFZOLs2^CqP
zZq^0RbWV*^f)00mGE%SkGEe51QI<wF^U<S6&DQSJ$LW;VFTM^6g1wXW-tYE1MxzC~
z)zJ++K&1+%NW3+L4?o|v;#Wy-?H${}fuviEX^!NtOaWT+2f&+CjuooV8PwLd%tqVq
z!MwJNwpSWh1U(qCxx;lJkQeK&D at kn`T053~l{uXG-aiW#Hg_R_vZ^s6VBY^5G;*6r
zc`7U+hNudR03O6kxaMya6K%rXam%Amb;%k!7y5F1nj?7c0hDN<Ty=kc-`NlKYAN-b
z#KMx;ZBhz~`y(M&5F+1rR8vp1#Rx0SH-1~V%Liqse)%DD1P(1#<GDQq(7%|uFfrW2
zh*>&`Uqs|lOtAQJ-X1Q>WtJBfA1uA!mrr>|Ai(|7)CpVQ<g=yG-*v$T0W{NFsaxhW
z13t!c{P*_Kcx3zyJq&_d{6`F&u7{RW61+b4^*yp;UaHbqvBT61Hu($I6%ADEZ_kGb
z+rX^iHDAe9X~10l+G9s6<OEWH$KDI)>duz)rwpBQ(*K_wgc$ygLn+x~Yt?Mc)*k?-
z9U!hnWTCvf at e_tr0J?+$sKKg2^NT at 4&^kZ?Qv)ru at L`KbQ=kGU|K$JvuFBUWaA!Yp
zLUgSgHyhh%`}trAS$%!|0g1uHTfxjv=ejN#o&2G}NY5<o6$?`H#f0jVcJcJ5pe5yt
z6c3cPxa+hu7-b3d<(VLFZ)Z;%5IMRxef!@6Q|0TUaK`PGTAUZ17j4B3n6 at 2&uuJXy
zKC!J<O#%tP_?KBQgt^W!jsT*sz`f&v1I1=3UT>{WFauLL>v>XNX|yofLk=mm!&6{A
z6b&W`Uq)e}U^+n2!nVK94uD{Wk4Z_r$Mh!JVw^oIJhp^L88pH8NIDAy#M&&~vKOhS
zpr*s!|G#)-I|-a|e~;JlG%fkK at 3ZlSVDw3VG0S-(@CmVTn%sB-tJg3_DXgCH=sNq|
zun>aSZK<eNL?vK$QoI|rP*-$H*s?FArnc6IgQsZ}_Ocrp*VnSb>xU3FbQ0v^GB*L3
z2iL2jrYvjei-$q?e+gZpbkHhqC7mBj6Yc(+lgxDVy_wpZF`!S-o|_-SW3=nNdfxq!
z)t|={Z(O|po>fksvQ|Lb(C~sJm at enrprwki5a_Orw_<N>lsWhVc&r5^Pj7rYe>Rr6
z>lX{Awvkn}t)#>Se-RU))~W`EYvo|rG~&z%cv8RN#GeQ_9h&*~2w3&f>NiLBFL|)H
z(!L;7sN9}QF9jPDo55fbK2!{=F-9vy3Ha^rZR){@i at E@AX5 at e7Nr_;05R>@K@@VbT
zl`;8J2md_PO&UFdM_?YSVr`S9oLJKa0KA}bup^?e%u>>Ezq2(*5+gg5IXRD{p(4ru
zR}CJ8?^cz{)rjfmc4(&pSrthEQ%~vcEtSjCDKVU;S)y!p@$xtdGj{0Ztu$Ym9u2u7
zB^5tVXvLjCG(+i8Fa~Ph)4(R(w94DA=ML?AY8J=i^q4C&&Ty;J)HF(J95FvMp83C{
zq at Ea2m9OuSrHx1;mI^}Kyvtgq{Hhr`C9CB?1>AEMLy2ac$#}$5_OmXyzW!}oz%tDX
zIzAm+@`}~B(=m_l at wqZ6pT55zgE5*4Uyo4MeaVW-tjStGmvIQo`xjH5C53{SC7Y{}
z$8$m9r9rTCa`;3vleklc;&8d^=Jw7G`hng&Wzx+zZ?A`O^Z+;-A9A{q6&SL6!GFY$
z(_a-)1KxB1V4DH|^S6RLaKJtJ(yeWN!7%^Da6g}ic7ea!oIEM*8%!j#zlkv{o6Ku^
zdGh?2!$hOqUC%giTV#QDp=GGX2iGTUv7#zi5yn%e#!i3Rz6)|iIj>G&K!BL2c~|P^
zIqcLgUD>}$ov^CbpUMH(P_)1L*=6@~#kk=-LGgok7Zs*_eSLM*^`<F-`A5bXyO?|8
zM*RVr$+qaQMYwe0qL2`_v=kxw3l#EyXNt(1(4*x0X93y^Z4$1&sX9Ph+GT%T3Q+CS
zpn8xdAtsKt-@%acm&=$S<FEOe$E#K_LIOFu#kr>~{x|y>Ab4C;;ki?I`a5H;AAIm+
zBbojxm=n;}efzeXUQy3PNJxlB=$?)fOS^Rlw)7IoB>#UX=On)Vom&F^%=C%x<Ta;8
z!)%pZOlCR~(bLW0^U2%v&YYleTSQkTxF<Nac1fh2fX+cj&^3osbd5@;bl&@6l!%zF
z)WQ6DP(DTNEnbMuG59Dn7-LnIE9-TOWicx4&An_j at 75&!JlF?)!JW<q_Y|{n1d~?-
zfdtiW=o}zW38d`)AcYJJ3~a1{d2*gCIH%pff+l0|O{9{-jo1JeP9EGxfkDpp4yN+>
zv*?uYY!dk3m6!jlFqgDVp`aY$-Oly;kCFMk92`p(4^{J at vRM^}d*Hf{xQ)ie4=lgd
zTI;>FDsJA96(qzb2`JH#HTEwqdiElfJZ{F+qek(8Z`mvTCjnz0`=ON&fHqZQb%A=C
z<t}z6=eRf|*zeQaBx9;8)*~V$TuRHf^WR#V?(9w<oc%f-!A}AQ_0KxM>C65}<D*~H
zS at c-5MI5NXDI-}2U(?!1rr6I2DN%JQs7Ozbec)dI7sn!!e7m!g=1^Y_T5fd%i*a46
zGa9%$OnInL0 at O=^biC*SX}GaSFjt8_{lGf$MTeRx=sPl_LqkI=fvcBELOjYJpv9rG
z-XxoY$-7EWBVv-v&?SP<ae(K1gziKCyqTF9i#RdIflAPLDEza(jVc77wSCX6O?16T
ziJT;=vf!Q+%YO*<jeV*AmUw|bMyy at qP_}s5NT_Tau18Psc(lfkpMps&pZVm`V-IAH
zCe8pck^B4k?OjN=bB7)kT53j%Idb7qRXh=dgo^nSXs^D50&y11%9u}6By&JKuyR})
zs9~K=0Rr7(67M=?x1!q+%p{KeTr})vF;i?e4r3NHZv7B?{+5)qVPlAiiw9`!1`5GM
z-xb8T=q`{59ss0>Va0n9hXIYlHA4^vY^u^!ztqg7B!geuM12{5Si3g8>A74 at 60?>I
z5c!QzFxb8Zs9JZ7a at x()P%*zC at 4v-fKjAyPkdD*ii;nbz&*t(|72lwGldfaxJ0l}C
z&LrRA1IYgp?7;ML!NQ<#n(&wVOz{?S4$g`~_w&X!MZ5O8In1^aHLu!jkh(2<znS at e
zjJ<h0)a&~P{E<o<(n4h`DwQqCzKoJoin5k%l#nfk3^HaoiXvN!C0mLnW0}O**TfK+
zvK!0TWjA9TGt6^O=RDtU=bZ2Fc|HB9na^DJb>G+azTVgU*-%uND3<FR at pD&R9(w7J
zQLRllmYh<8f?KU$wbtpJej03?c}wBpTUJdzAn(7Z!yq5XcnPi*S%%o*dc`Sj6K?W9
z!zu;7Vld3%v%b#m<sAwcD<7KA-ZdAUlw)EVwtCi#^Q5i*uo|s=u at iD&QPez=EjDiH
z*Nm56x+a=w;APPswP4}x6rE_l at HpM$byw)IC%|=kS8msMK33gWJ6D|&3-4JPjmRIW
z7jnH-#_^jK1Ln4!DLq{nN)|nBqa`i2V>k2Bz!yx&XjA-xGX}%znRK?SB}FvG%BJih
zh4M>(J#w(mlcZssW2u5O$>b-vbU#=1p1leh{_h5BLRw#*G`a`UxO{*-tc at kO{j3V)
ze6Y6YSb|=EXlQef<z^ooGO-;3F-s#4F<e&*(&9ZwAMH at -$gvWn0t(v|HRN=CF)#w5
zsZKoaICUs*Yvchrv7bS_n%e-m0c-JuzU4!kW6S5pFO7<TbrS{hjxA3k=VV9Z?z`&0
z`mIg(SBUFBnhv>t{yMl%=+K4eAAE~i#5LnWfNAp1W6zk_+Bt|VPqaZ%HKpd?G8^Wy
z?<6T7+{7=%r;Xk~FSaoJU%XG)81+%%4si>t4m753(2Uh{o(R<iitv*5?$t4wG<##|
zD?aK@>~vGDe0i!Z;zdoSLe>d9uF>qo>rFwM?k!5lf+-l!@KZ~TjgHd`LvLnZtc!lV
zO%N?3p~a5EYVzEW(JZvzw at WqN)rCK4@(iEi)wz*PRn<l*FKP~HT85ne{6~gixrgyv
zmm4b2V4bnCny=0smen?{uzacEhosLP7oLs9kMrpG9Q38DFrAtAG<=;EE9YJ=xgClj
z3jVU||2V_hRNxHrIjcOEq3={Lp?Q!SOKsn_tth9WP6{}^eFqMl;QS2g>Ch)n-t-oG
z8a;S$c9fKovV&jh66ltv?m2eFef*el1<Lo}0Y$g|#AKhL?V*v8Sav(6$E{L2MXo+@
zu)wwF9$<_-82Ylk<iW%qK+Pb=4HaJpTh1m-eza?hsTHcUa63ue|0uI2 at o#uLz_>1Q
z%a&dC0Ez0;81XZf)){rI0$vQJQTgC?f^W&KmeUH*h-Ck&+HmHUnVQur<rgE{(gUg>
zb7tcMl_n8rtSKpoe~zKW(}S=zG5kOrJOIB&tT9_xfp5L7-mFQ|O>vdhK1*!A;<Ls$
zWb(ZFAZ#uw<e2x)k6=EQM4R1eQB-FpmZPp{5S;ie#pUH$>ETS4&)Q}9v*#ErKSWz;
zFcTq%6i#)Y=Ml|&aIfWb1v#4Y27`jCz1!eXdqQ38+1SkM=Vue~8<N+hQY>MD&FDKf
zj~j&i7l?}1hF&&|#v&n~jamVC*0n28y%$MiW!H{fyVUU&b*EYc5771p3mvGdt-X<F
z{|!WSZKs&nRN(i0SnDhJS>XCk%K0Lf7)y(LQchkM{$JVE;5AU#(mO0Rdfoh&Un^?B
zbNTn}If}nGT-gpq;Xe-#T=(yL4tGtph>aC_f$8C_()BAmbCo#kyZXgMd{<3l#|iP8
z(;F)Vl9G4p&B_sWGyX+HIJ&&tp|5R|R>jxr{u=Q8^$TM?UNbdLXS2`Kcjz|0iD2nV
zLnAEJX1B at X7No`qjddKyz_XbGy8?_-gmsdJR)pVZ%{T#(yz9Rq&{pv!eKa-#I$bOY
z6 at xyl@k!87j!Mba-Zn!>4(8P6*%>PfmvIwC5?b~`imv1b!;P|)mZn!{vFOTV>k;!0
z8UM|v4=!}4KF()$Zpo+3pLZ34R_&p#0>6c{fH1c~uP=ilkqLziE0t_pXw~=5Y1218
z=oznP_^#m|3tw|TsLLDWUE{Zqx9YR{hiJe2u5pV;3jz(2tZ$^*VMm|t_LYI2SH6~0
zIR#a+1Sl%S?dPh<GyI7W1gc$>_04PnadZ4Yb2>kSE1aV3_g7~Y=Nd?W0cx!ZLOk}e
zj5~n~+w1vJwfP0r!Ei+Z`l#6N)()ubB5acXSS<0%u98B@)df#4t+);LW(36O`tr
z0|EjhC#TfA12{NG<TqPoFK!OF_WJBCCBO$%rhSIK*Z#MTm(v5Y%+DW)k{eFvP>TA4
zse=01->u#3ESj at p3ah9qx87YgQE}-noD<Y+1#^W5tQQ7eTzgSRm~H7joio>E9o%_e
zeJOr98A=Xb;Z-P`KMp|O<=x at hN)`K+RF(sp!HBEA_No_(kf>VTXX{V?>No=|)b;0T
z|1w~xXc7k_INq#>s-O$rsA5X7AsR=p*QB9ol}|4QH4eaPqADX_^BTXxcw&WwEXi(0
z_8D6OS`{M|nq)ONY*>YUUmZarYHlG?u^vw(_>G-&O_XSC0wx2{!GCHBv}^Q14<_%_
zvAvK7um!yRy?Z~;VK5kc(T*q+ytNhb=#W#o1myDlP4zfH!BGc2W}H;qeq)a2&HHsL
zJX}TB6ovTZD?Wq!p)!^qO8U)(6+VwCA9uELt?5`-dRDHSfge=R(G$==<ZV#ACm1nP
zQh26pIjl0qac=v7qmPAY%}%%HmQa^wML4rp=x0W2rVyvrtk9#HGd#Q7CyaA82P|g}
z6?xzGsyOAqsa2PflCq^}dF1>ltKzijrZ0#xFh$C at z}4Ug=&PTaHjvMSh#H$Rht&^a
zj(fp5A?M+BQ}#skWf$zbKOHu^f3F9&A at di;d=wZ;;+nZZEoo<O5kj2(f8Q1pao~i2
z?zJT#3sw1TH-m8hExhmWqh;0Hc0zu;@{;c5WlTxb-v&VEv;V9^d2;v&&2t6)E=*Bl
zMc>bWK9mne^XP?ZyR<5F;?$Pir%%Qq=1Ng^Z#1 at f0<KK+c^ZF{ww%twF^w?rR<cPx
zkD~Q>UuLgna>{y)O^hj|JYL%1q3(U}bKra1xtgXvjsa|lLs)?mvinA8i4-c~Or^E|
z at weDvQrfIDWaC;!!MS?Ot3RE2 at UnZ9U7&rmsg^H)xKt8pxD6^E8XmR<;3Na>?mk~R
zIi=D5;l{h4O!|V;`*CCTX8(9w8|zj<x7WEc%2 at jGn_29vj7=)s63{Ma_Nr0%fY2ql
z^-b}R^!;Ecn~HF1&xmhZN|)ds-VObsG~{~gufXH|=>~|j-QC3R2iHekb9+w)mj^}g
z8z|_%Hsyw9NZ*Hh+-C~E(M(*_xnUkQpf>(`8|-#d|Bdb5Be at _m^f<6$*pw9I9$77f
z>&9|#=S>|#=-i`~D2<r-J8lWcGERTaPDGB}t~Tc_o8^ISWy)L|eb5K at H$;Qio$<tX
z=%O87O}^5b4ESO(?_lzq$4LHyRQ)<l(-VhAECT!hqfD9*aOGKJKLGdM?muOGU at t#E
zvP1Ab*I0i5zz>|RE+%Gy69IsOUNzggXLr?ZQK|pXjnbBY9$Ei$sY7XTv89H6w97TE
zJ0PPVXm=T(TceZDuRwAz#0fQ(Ijs3jdC_C~h#5E3UDZJPy1SX`#dkx+aq+p?^Y;h^
z=x(|7+)?c-#Fd7<d(*wXR2Q<yM9&W~$zTe4X4LUDtoxFn!$qiQG-b5y?ThSp`-r60
zfmHIDeB;>aR;q*1-&RQxU>^byB5{5zvnD5AK~Z|oNN5oL#<KyS9s+nysDlR%_*f;y
zN<0k>eai-40UzbvmxmL0uwKxPQ7O6i<L&V~w8KkkOaEB1)kDrIf#Y?JJlZ!&gI12j
zS4(76H<!inW7mlMEM*Ix at d%~qnqYtBOFR~9jg}9cs%$R{yQq^hrKQR8l8)9>`ckoT
zwPs7y)%P=k-&gJM&U21R3z^<8VoIl?Bwhq#_d`5BjN&vqKg1`5r3c4AXpm?ln5Jm#
zqh)a2qrJ{0)1aIC|I}?!tOaBLgN8xLW%jUiFv;)7cfR009Biz?_d^Yxe{j2GNbARV
za0ZKo+*KmRC40dn`l|<$xxy6HrW*RG3pEU;N{Xq_k62GIEPVDp{WM(@^;i_1{XxHG
zwZMAzuI&<a%VK*wX~geC;YqbatcgsIz6JGm1WG}qGTBd~fOnWwUDIaq+}GEqXxQYD
zvjOUABk#ACu|^;BiY<(9wX%&4!non`EttZ;+MwCL+Mr$fx}Xi}$Q6J!^4t2x98fH_
zXM)yzJZM^!cfC0mv at RznSIEi9NuZl2RaU~TC8 at q*<B1BLdfvcRf$tul0FJwm??uQ5
zN_ymf$(&Dbt(H%>2tt}vlwZxfcV|TBSYy$+72|BG%J~-$zl}yUX_h$!U0Ixbb`IE&
zOPs{8MQi^ph1Z(Bs!wSnku7z3Jk*)|6+3 at o*TbZ{V4VK)p=49&XOlb>f}Vei+#hAN
zKoo2(z;rk at Zn04)E5{9P at u_6=w7o3f<=2y4Xj;v9Rf6XTAW;7T4hm{-*t4VaVHoG>
zbc=x6kJEp^-)bGPte1TW16-X*fK|<{f_ at BM_U6X`FlV6y65I5ssNFLfS(D(;SQv{b
zfAYI$0mxnIh(x$$CEDLl&iBLG`kK#wn>BHdDUCAa90Nn;dEA8v#2ak$N}l%4r=n~3
zQ{G?e<a$x3vNb@(bM*98Z0NJlH$r at aWOv<V9!;S>N=XC=8CLf-^UgRHsc}}d#UyCZ
zt_r#qLs`LvCIJ7P`%<`!Jk(~5tgD{#|L#X}Yz^?6U&;Ktw{`#1+s5_EL$#HhkIF!a
zU-12=&rP3;2aZYYtKe_$-R}iBjjF!1qw8x^_(l47N~4Vn=oOv4_x<p}UC8OKXc3<Q
z4xDwlD&T~K0yO_5Xzy4*9=~Lzn73cr{N*yBIDk=>Gk at wq6R<mD74P at 1#>J^OA<Eyl
z9|lvJY*<PTIBH;-TBg~zN7DvTUX-!C%w6~y4Vq((Gd;$Vp-hCLsISl{$@z|jOgs0R
zpIi_%K6Ut8{&(eERSI8|k<N|>h-ODsD^|c1OXENkvDQS=_P}^ENKU?E;l81YiRR~e
zWYGGu27)#|)&h~NRmQ<!U+5;cTPA3f^*aJ|=9AG;v?isd-27{QaJyrbhEL`1$r^CI
z%741vrW6>f0l?v=Vq&mlYi1|>#9{RlWnxzaVs`XAqC9H-Y&RI}9vgI;Sb`Yd3VhL1
z(5%z~Rcdtg0ARsQfUfBSKo$8#23;MFq|yBVk>ou9+FPF;l)pFh<>^_MuG5o!rAivq
zFME6aWZJWCy%-uAQcg<)RT&uS`W{2sf9~A5eD}fgB at dTyYjQ0nps->rPI&qPRzu|8
zO<IKia_O2r&)JP%g_NDDuh%G7gm+R2wO{vLJ|tj~nsz(!5TwaiYYnCbQT+Q)SFD!n
zaFY&NE-}PvR*y$2o-Rw)R$?eC2t9BBd9?G+8lMg~OShV{2Hevhu5sEND@$UVk1SgA
z&6*I*9oCO;^XG=L!dEQVSDQ%Waz~sgWw!7 at tjX>?2^eRT8<jwrY~&c&`*{-}2$})J
z(x|>`)PtwithcY+FP7cw?!?1m0|&uO#RPB>XHJjwc&MBPqc3lE?ZT{0NB{syz_qXB
zl_q0a{<Vf*!LD5)yrTMSz(GGr^#(w{?q2$UuE_sFB({RtjBJ at ZC8t1c393-MOMh8>
zrQYU%x_^W|d0^NSnN#rDG{lMm?)lmKb&3}FD5O*U`>g?!CLV6mqIL&JNkPiM at iJFC
ztQo5ei+06lqG!|LEky`D-9vT7@*pYJ7b50?s}>Y`MXW|QLdj<#)6YE_zZ#VPJU at zm
zou6l+p(P+^8}<g^2J9n%v#4w~6}ykH;lH*>cL&5M*ta;j;ijxxc^gS!6d at d7ZW7yG
zc7smdWk4Ev5qJcC+lNjYO{<EkQLFgu-WVedcng{S75RY26pdzt%ep+M0Dkm-U&#{Y
z4$a-GZ}lGzr#T*2Q at iUjf~l at P%NHB6kT*A7LtnP8(CMIhIYC8(a|YbQ64 at fo2T0{S
zY7q{pgGVk8PZ6^04s>SO4yV}-^#=(P*~_;MXdk`fduL<nW7 at rp&txA+a&X3&TNbKP
zD1&D(4na2s=L($?gwzf02nyLD>FIpX)~#Fnxp{djcY{>r)X}3OcR at zCeZ%eU-I2#}
zadCp6MP6bATI8hwY`8q&FlM)<qGb-?5KbHRCwR62OPPHjaWpkGHQ)Z*p at UMY78AQ5
zJUBe%332KnCHGNf|2X<CP>~Bo-D7%NklW~}B&WE?_|-!lI8V<Rjdp0hai{aHVF|q4
z)&SXSe&-T&oj7bZKKqWkD^%Ie?t<EFT63;9{BgMz%t{#u^8R(=p`zdw$-gJ)LF@$m
z>?SRP at 0Az~UA?+3)J<~iO_bV!8}O7EGq}IPw*a|fP`<A+7f5gl;B3L<d}aQ}o^(S|
z?f1;39hv1OdaaKP!VBlnMJAQkK+ajr$xWm>L91UGoCM#reUJ>x>s-sct{<V at 8hAMO
z30Y49J<sw-^vPz<DkmQ`lbW57JNJLMOMeCJt?Zz!xd-w<V8z&+GwYO?U~a({0N1s5
zr{4lD1nx8VxCg|ng?pbg%;ZW-(5RxnYcn<)a$kJg`MeGs7pJosuNRe8#xnIuO}ij?
zf{L=EvbXWuismGa!<DhBto>Y?XRfw>Orol7`FN;bzn#s-srhGotp at Qmg%>paEUQ4<
zist%_tZw)M3<{9JATX-#jr^-*$Q`hMym>`6%Pw8DzO`-4>- at G&#s8LykuJN7ROb1z
zH;)k)&tcPZU;0HbUh~sUza%wKlTG+6#J>u56q%q?7I?b at 3R7BRII6Dvf9nab;7Ff)
zB~NoboK!a)o|}^eTtv{?Z at cy092~h~v*DyWwC3}-j~H*`W43#Z+Wx)JDt)#&AZtp`
z;Lv;9>eT8_x|Uw%_V!SHst~W_9*awU+ at 20l{pu;=ZBd at Lmow|GXX=03HT+rpb3Tv$
z{~Nra&UtkDzeDx7Hkg<J6Ge8!Ba`YaG%!ri3fO0<%`)xafGLCNg at Gq`#lbM-j>{L$
z8MA}7EK at yDjWa{mc+cC0LT#=8Qd`+ou-#dRxmdVmWPHvcuAr7B^8J0Dyd=bAMjoRn
z-WBP1KNUoFjw=Oh*Lz3dsrPEij2rsIc7e<(8 at T@eVF)3>5MZJ^A%jHab6_Mi#%w8L
z;qo;AZ8(B{HSN^rMN^P^gUQ4`z85L`kf1-r`^fbCeOg~n;D5W<?0^;f16aa+>zdOu
zjD-G<_qjl(2Pl!jO!=*j_}jBPAU(6t^vAHKv at Y5)?giU2mxd+FT{b#p-~P8$k$*`=
zS3!d)@m5I1<KDt$ZkUogs2F}RTVqbf?BzIY(A+R|ZSb|b`Rb*6r$&xac5(bJR3V;>
zP##l`@SFD%!f*A8P~MeipoCi1Bm3wI at u)U?gDN7Jnu&(rJ-})(@XYs}_v<1W&{2s(
zbzt%#xY1O^`a^kewq{A!={X%g`-63nx75`gdy9^V?-~}WjHEVN$JSJ=d1s at p5j7+0
zGkM@~yHlp~xHz&x|Kjm9{E3GAMvDSyk?6j992jMKZhsSm__(MjEx;CwHCqHq*8|nx
z$o at wY(h{6n#G%tsu-)D~Erw6{{-`hf<F_*aL4U%nz+JBjbAcOoe%-tUt{(WBsETH;
zo(s&H1ZezwP-i<=$BR2`(#lz9tQc8ZCMu at n1BgEuOj%%?WokW+KqEuD=d(UqJ`up}
z^nGMgZD18?ee-u(`v2LB)w#gMYD~;BL-n|tn at y<*Lj6UEAkfg)13sQyX1I6Hp7WBX
zS#Ln7%*?x5U<pi5d9%8N%ifDSvKu<iU9vu+Wff~}`v32zG!H-?;Roe|0o5G)O;VaY
z=+}y+^Z7f*3U1Pp;o*>tlHQznLS4M==p07z&A+<{|AU+s9l14tJ$eNq_>}}RbI^*?
z;o=dx?hxxz4CY2Yt?X$!G7xfJBK64tKF2`z)Su>OfbnndlG_S_=7hK5 at 4-@_u(WtR
zo87dWr<yfzUbo*zzX$n%X%Emx=ljJd)4O)z!e&r#Jq58nz1dI_aH=d(6e?m+ at OJ+7
zYuijau>!x^A0;1!2J`7nSF#bCpn>~LwTxfXy1$aNu%5o#DxdS&2&Cr5DPQR8Gci#R
zWWdx^lG3SDR}$b(V90cIrba+!-l{kw7BI#(4<711Uv=#6lyFyq8|N>oWvtS1(QM#i
zof9FbNRXxfyYWp8A-x|2-pgiRau-?2o*;i$ugj^G1FAkFaN`4ko}La$CHB;*7t$Qi
z``D-F`F#5Pqo at QhBY5-T55FtlK|MsM+YSYkQs>fsPp4$=dkch1;|RYi$sMY%wg6K_
zCl4RiN^o#0T8R3E2=tF`s2IwCgcx0e0Ylcd=VbsN+~(l^OtW_ at O6A!K`ViC`j*<91
zHhCdROYeNUVh&OkUQx^|b;$1GjXHqi9t!6?skYHGV+XuAhc(-}JlETN-v at KntWYp;
zP2JN34_m07GG{WFHJ@<W=-%N+8B?uczh;Z95{M#LvwqPJzkW{|TyWBVb1S4YxPGOt
z6RIjH>g!v3ie&$w!4Tqr9g_WXGCb?ky;dF-_F#8C$C^?Bf7yBAYNL^Xh7K`uVR+S0
z2EaB#8%YA_-KIN?9yg!A0n8x9+W40ltd*brBl*yLT!%84_?{!W3Y3EG$V0EXfi%QU
zBwDL at VeGjpjB$3q)2r4o at QDfH%&cb3R_9;fTV?o(yfDk1P at mOugpB@9%(ihb6zfTB
zg60AB$2j%;mhR#ZRZ81SsawxMT_>rw_j1M=K&a=-*%sTdY{it$+G{P4yl?V_G;?80
zB;e0LeQkBX7Dp*b{!^Ysx~Sg0-sgNYN-y&EmN4S?$#)M!t^<_%eTlS_q0RL-E}T5;
z13GJKlMf#*FJT?QeP!fD at Y<9ThDB#;q5Zcu@~>;#w804VEeUAkXt*1qPpoPG`<-yo
zYlW%T at f-u7_nZL`boPAWHzfr?^3V67-YG$b%a at Eua&in at 8w~hpy86t8v}SQ?*YO_d
z$}NxGtKt9gR~>WeTE*`?8~3Ub0>Nl+a-?<{$n}q}mk(9xwk;5HXp|$V)otLW)folc
zk+~)8_xasqaBV30A)2rYni*DPuAk&aVI!9hLD1=;Wn|9JOXh_OX1^?f#A?@q?9N9k
zY|`>uCEhlZNR9t)?y~}#n#yqYu(`752<QbZE%&$?f$4t&{e8L{rDC$(FZin#RPQJj
zg0_9s`RMXzK`^E-Zo18|Gev+6oy>3U-8=t>mFT_jeGoK%b+1`j^dh*yGAp&i>a}z0
zM{reVDz{z(d03Bd+vQuX!OzD}sh2UbJ+Kt(6qs=W4^-8O at DtR4{CG84W}F-^x9i*z
zg*;ZhZ>$>oT&kD7u<gfr;z^_TLQkJQy?i}LUN>iJ0N|*<We+tkTm#felb?l at B7+FF
zY7bL38s(WcSG8^)zPej=$I7<A$+d+ccF~nQ at B>@&edpc{HD68$BAsz&l+v!i7!+R3
zZe#0stbXi4{+W0keDi^qiz~gXgo^;W`SG3;npf!UN2g8Mnp+5|?@eK420~vSz0Kl<
z at H^o<H6CyJ13u(I-%dELpI9V&{P<YTp+prA1*ptpbO(FHtiL0(SxsSZaBvZH+3I_q
zyvW?O8TiWm19G=3KkD8*sxJOgd=4|4V#|hN45~;RSwB`av!lVDcjSYT!5l-Y446yB
zTC}ho(L~ljP4<gjg at -E(XQdC`#I<rjKBb4Q;aXPX<?{=aeUAS}c_4u8iHOQ}=(%{F
z=ZN2s$CjPa64zcosGo+SoVz~Wekoxf;@a<cQ0~rVKt9$_O3K;=Y`pNX;3kq!!fDGQ
z_>;6Rb3`t-<M8=ojwdO-{(~jEpOu{o1iKZPQa;{wnP|kR)&g!mbZqtgV|z-(TSvj;
z$JaH`-BXL~)MEmI%C;BgiN6!OTWaAN7UVFrfKk}36 at UGu7sr6r;G>V=mKRp-_9N$!
zM~qKMsjGQSwpH%fcfw{;y2?^e(IrZQfm_hm1C*<~2A>;V?c{h<p0lHJTV4ZF(0w~C
zDHCF6MOdE^OwtF^Plx=v9T0T4VkWjCs(gvenpJy`oqnR7)GW9wN%Q->)aTCe^5-s*
zUw|Dw>*=0RC3KPp28vgiaPH05kMFayf_xskXl(H8!$r>&V^BSScEc2^Vmva#UQ0l#
z%7ad0C>|8|pe6m`8%zP!J^=EKiDlP{d!(B(TLaX!D>lt at -LCkqbHaef?D9k3UmGds
z-2LzrVvd#PEn at TBc&T4gP|IWY4UWKLC&ivT;oG}o$J-NojQ_ZB?#82?Vs9_)IeNwP
zsa~^+LxakWQ|HcvUf?SV_A5{6Fmp3YiEqU|w0H1+ at 9j`RS at G?6D8w0<VU4g;tOTR>
zFuHRU4kurq2`_px$0`*rG_s9>_rDhy5eROb_Z)}2!kKHMVa_wgCd5!7xa{(*PH4k8
z+){08XRhQDMr7M1t`qMuP<<rYy$|+BHW<>DP0IUOxh%>t;E^u)QpDsl9`aU)m at Abm
zE0?@)inZzsO-4N%Hu`a=Q<bMb)`WIi`$<zq^0x*scVF7I(JZM^wAhm_`KqG)!PsZ$
zx>s9i(~d>~MiN1H=8cYtiEjTCtA+JWSQqYnFSlh<G>7mbGmN_sj+YaE&Z5u?Ged=W
z*R8X|ag+CAhNtZME3eLUSr7}_SiXl33%`j&a~!S~t&BNW<mG+Cs`LKimhRA_-%sJ3
zS=m+(Z6;n3Mf|1(cS?QKl2+CeehTew2EyJMd at PXTin)>l2tKv)B=r|=_nkPk`bBdX
zMBae;uzIyuITps98y2_+$u04r{o7b?6i<AJIL52Gym%*me{7C${s-LTeEDc4ewx7=
zB_$h46?JVQD5 at 4UJ?~`_1(#%D7<4X!s at 6%)z(pLVln<4L2I<FHsx5cqO8>w5HGnP^
z)#7_JEsQHy{q9YSUm2HtEzf#QdfbiK`D)h07>1^{0hE at 7-y&f7kv}m#!Wg>rq>xE#
zRI*<hjg*Uf6wIDPx8=Y6)p&D28O(BSF1MvsukeIF{KopJGce}46J*J1c-k+)=lsUP
z?>tSX+!A_F-A~{YFfmC&ZfA|b=cT2wpWX+)(N7rHKwY7huUE8xLe1;IG6_Z|NjE$!
z%fFn_I~$R=VCyE_W5^m040sT5XAQKHn6~yk>FJDDXu^YrxAb?S4W5IC0uuM2&QI)3
z$G`wC$dWJLRE1feg4b>pbh+LnM-(Cu4_wU=^l7;)u^+<_j8&L+I0f?c6(Ym){)Bz{
zoK#ZzgF8RUWbUlRFKr{7JLi=}4|~xp&2K#<TpNxl&q6})a5!ymdZe^P;zN?0f8)f-
zO7cO2vURg?=dJG>iG2?)WGCS!7o&zJ9>-dKP-eEZIuUzSm(w>$oE&@;!cIYdOx|3S
zch0)uLg+b82s&V5BHTqNw9M;bC2BBYT1A(>9~@4Noi>~Hth?{usB7)#j#%?2wu22s
ze$Gbr<gYHi&TNYy5Y*EC_QcR0(NkmbZplMU6d|};)D4sNEBYFJ^jKDVA*#X1;i385
ziP)(v8<UekYcagVJ}E<=iTZe;UEv%dU6MSdw|3N<S%A#S?mNOh7f&wDT^HPEOvIH6
z`}pLxdFbs^DtqWS6L8UlAYg|S%~Kho)fyz;uw)GB5Rj_K-q`>*0c at Y23x%eMottdB
zW+Gf51d}c99jZ0p>90gS%kC5Z{jr}?SYOGt<`$?_<n?zr^3LQ3#OT`2%0eXNOl7a%
zhB&s3?6^|&g9k1x!x~ocZ}dxE6~{l##2HBwNtyW4i!)v3rFxSr_1(Lp`%~Em%g)XB
zC;iTkV*-*$)jOr+z;K*Xp>M+rhXUr0pm-bRlMDU&e%kuu?_s^biq~RRly8rltC_SL
zM<fVXIYke}gs?2t3OZ(=ufcdXUr6)*z`X-!+o$KYZY9Us<mOuZqy*=r<g*}Xwh!Lr
zLx>3&f3p)~>WmFFV@~wmS$FN6u&AIFFHFng$YR9C1zBYbV#0N$Ga`e)_-&KG6YBOC
z?sG#=C4|+;_EaL at yu+!+^saKj-8QQr;+-peL$Uf{4jMk^b4PO$7Nt{G=1<oC=oB`0
z#^TD45F5Z3%W4mr!OhfSI%Z$~CTZUgbsTQeq{d1}+WrA&dj*}Yw63#IZm at nC(VjvA
zn=G88yS^56hp1iIS=baAmguqagMh at 5{XJ0XbIgT(M7R%P=74#r*>4&}zHqSebsm0T
zg9nd%*B&Ihb_+4h9kL`nhglY>RjE|V?a(K*&U$x<)LLVwTMALV*L2U?Zy%d6_Ht=N
zP?<X`(v^o_3px>-$$h^J_Q at g-uqHxJ*+>jU5q;p%&Ucjg$#g={VUzZWldu-Vmr<@o
zX{Ak7g1kWBq#t|sFo!}p88H~IrE|4*q(m#A1Y7NhAQh1 at E|^U5Oa)mk4_W!k^NN*g
zyI#LObCK>f(B8S>kvrm{m!D{k#gQFlS8E)lVP#d0u}%43dom)BN-XNd+KCn6&O%gI
zI%9ulnfq;EtPz?j8&)30wPEcYR_}`FgX2!buVlgBrB3Kus?rYi9ZSG=ITc<D6NHzR
zA>Gq(s+M^4;2%R(3(J!h$4}GooP{|+*;8o8R#Ks53T=FR&ZKv-gp!SWcR9jaWj4v7
zqBzWoryq}W$tpkgn?02FHM(IwtTqXlO7dINglo?UOk1g4H#A8)<c1y&jX8*B-kEzy
zxs?f2o^Wi{!mIpMm<0=MLh&epf0$g19qwg&8n$29XVy!%nTfmUXYkurE{EzSqL8^?
zN at JyB&@44U>~QT{9TTrB9 at 6<18;q}YEH`?ONiA*){WabI_f!CexR|>f<Gd^_Zxq!^
zSwP}cKY6G(BBY6hdQy2lh*d{yM?@gO`!|2)g!lbE@*>R6^kzX^{-Ri|H>SYu-Mt3h
z_r`?6QS#*pLbEBwrhVuw(Ex01`|>Yiw}+8Gy2U%&CjWUBRb4%pUlCHoyv8z2{Lq`d
z5t+gZxZiicOk;!D7)Gd|67GPPwVu*kjb9PyVKLSCC2$&(qZoB8;*^`E^|%H6)Nu-m
z?Sb_ZLaaZ*8wqlxB8&&3zXrIJjL9mG#IMv-#B9%RY7V&_byNFZ$d-LXKFd7xx7GNK
z599Z7iTI&%myg3G`EPkd*Xg`~6^~u14er64O*1oR!rtN4@^7RKaO17b)eac7=Z>>O
z1K(P$9eU=$oDs$loYo|&1d;h>b)umZm@;boMs2!5iitCptmlz<Fj7TjV?_Do at QnyP
z6-}$V4UfYJN>+Gf8`=->YRE={t$DlUZyV^8Y1X&yju;pj4s}JYpQF|;b)Je at AMmK%
z6ee|D>f_Izuy}`f6?8O?{VyU;$ibtm0Wtnb)Usf8Xe<0wq^`HiX0%z~0)s4<Ir-9M
zS(<O+1_(nq;L{#5-pMYnvp()^K3P%Jyp^DcQ&qLX<YkBL%&9DN^Q9G=?f2H8#sXhy
zB7E~Vk#IO<(26>}vw{WpsadZrjkQSaxM)IrpNZPgtCi+mFLt9B<>61v`mZgqzdIBA
zo4djR*V-fEo9i>xBOo>FKS`*g`IE|SE59M9f2zbx<~g5T_Kb-?=+m;L5}1?8j8_J0
zK}+kx!w~YDH#nRszZEJYJuGu>Lz_ZNiu67Apxt_r)`Nj(70(9yH!7*Gx*%o=-t2g}
z{Q5Wl02Z(lJsajMKuq!WqIf)1eZzjMT3C*(B7GU at -^ffIXtBVUfqf&-{C4h4OuWvP
zj25WvXROn{RE#>$`euQ3dR-AgO&_e~fzfYKXvYef1x3sKM0OHedhKsIirBiMx!84w
z*tareQ8AgLYk_VsGx0jgq>^|XpD7{e%&FOv_czu(fiuS0TK(;-&D$hWJneEvk+5kC
z`{0y3rm@$<(pp6|eqy38_)(S4-dnxYkXR>n=KS|%CW%<1S~+VkQhMLJ(K}aQw=CV)
z%h|sm-=a`KBMiM2#IzQomQ4x;ykT at FcCK>o at 4J&U-w^^oQSoWxxPN0}v)}Zs*k9SB
zDLZ!?3X=5yR5TO4(a4vGsFe<1^0hE&|8e{`Uk0-1uXwlRnW1K)^{j>FK>dp_zZ7(N
zhrWrI)K|hLSbht3EoScFkZic~HV`dLbc6odq#6wE+&Q3c^S;}i{-Kg{s5CcUig?6I
zh?+6MVdTu#OpI at N#i)hInkRxjQ^`(UlKB4Gscm3}jU|A|XyV#xV$z<vy710Gvbk)i
zqhgdZQZ=3GT<!~Ez4c4cz1{9%bIa16*{xHv7#t9Z&}z71#2Swec(S-{SmNp`Lz-6u
zE+}MdV$$RLnclfi!rI-HLOViC>MlBr?$<XlnVR+gnOj=`TXUq)_Is;a`bg#ATD9vA
zfxX+iJ6h!S=Pyq&<%59^PC-F9+$maPtZot-L7_!st=c|i;c%_9ifS{~)1Ip%6{C&!
z8UBr4MnGRzCeeR=KUD~_E?b+0wQc1BMsrO?nOS>zd`UV5-F>ZgmW%lbhzyDpFQwkH
zI8^g1|Hjx}rVV1w2+uz4`q^#kK$XW2i)nm*6mS19rt<6<TZ-jiZ{}e7kQvsxtFX!Y
zb4<_rNA_c|m7<Kr at ld_{bg;r&C5`LOARF1IQkS5N7=w-d(#T$i*Avm6`0`9e<qtR~
z?}rq5w|%-PzK)bL90P~obaQZaa1k<dzBD{=0RC{>-iWCd_v<EJr!yE5`iT?Kf2ss@
zaFv9;m-n-px!8*mp+`sL+gN&q6G8<s4B{`(qGs(!*;`AKL>IZyn~Cs~ImP3-?Egpv
zi)5v4kmT8kb2Dtd{sl`-6zq=I^j8IIw&D0UI_H$$MpQ4YbN~%Ft at w1jD%_u9Jrm~N
zh~iD7Y{8LxMBGOjn49M5phOcZuaSdw4F?D;RD**?j3|Vy-5DmT4bj-PGFpPh;mCd>
z=%M1;PQ`AV8$FmS#T5k~nK33P^*WL`t>6BYZ!h<`OwFV;fYr1Qs;-o|$$XW&NN`Rw
z7+<$E3BgkY$gXStsqcP#tH`U#vt#utJ-I}pt;^=*Rqv$_ya72wBQ?E1-%H9l#dPM`
ztOePXj0HYsOjS_*@;c at D)+d(?ayNE}T24?=K$pTf)j{x?Gc&EYq#yozrPmnw5f?KU
z87zwyh;QPgCaqu#KW(3li7+CFtCDO-#+s<TW5gg6mK}w|EBvdy-FM9En*aJ}zve=d
z+e2x#_ClQJ2h2*ZGV+}q9#C5Dw`yj;`w>q$+c?$6l6C}^uy437Qj|sN5uplH8++a5
z7GXNYI{qeX{^fFmK7&7Hb(2F6ebFVAFXxaCr{YwF{w0R1;T-64wTha*00QgCXspJy
zZY1;W!QpD;eC9K)-us&q<V)Nf3msEBevF;dP1)d`+=$r+y#4ca#eSzA?83S8CSJ;0
zdC`Q%e1lBn#TDrp#hF%Ba_@*mkA`+1<@qL{uumMY?f=(9k1uI at Vf1zYt7!1>Br(Ru
z|F0*e^<!t<E^}N+lZbx`-(0eg8|%#Z7&n=JNPV?k1}KSqu&*$`C*NS|-gDJT`pznO
zLK-C2lJG1=nOf*ziC)0as)4BH|5Df4%Si_1l-JOcOS1BGMk%04SK(Ce{A)$8dj};L
zWm at GF!}YLB26^-l)3r8Rx(!fj_P;;N93YyGacL>KWo+oAVSPJ=Je%nrnm0e-^fUi;
zUXEuhWUN@=FnklPbp~<svFym{T6NtdiT_`lC2dGC8ZR{65EDCMxtdpVyfT^h5O7*|
z{&ikz{dp3YTfNYF?XqHqv~`otn^RQO_y7IOoVCYz^>axS!=<n%FQt at -iJs*z|BuJ&
zO!`eSV{q^bo7s}~K33{wrJx4DW6l31DtC;RMkmWJhcG0-H!&XPitJq?(FcIG2>;U7
zA?7SE(l2?fBAqP2cX{=z;hgzc18V+%KeobiVntoYpJF%@_T*@j!tJ3im6YZBFapN~
z6ECM-|1^b*dKv~+27i7!?U|s`Y-l;J+A;yP`v%C}|0#3Q4-h;Mc%#lj(={<MFGZ`_
zby1X<=mldFY~4UL9vQ#v*xvlJBE1h7g+u<F)u6V4VJB9n;>jhovG~)7A<BJF<C3-B
z;#7_Q_2_6rq7ex^*_bXSHgf=6Y*Fhcw3=zQPz}WX at Gp&suTPL7QQ;3O2%iP`4#siE
zS|fci+&4|I-T#s!ZO(eu-{1zR(0D~m3{bUJx9_zs9ZM!Uafae1um4*im)A9c&N?&3
zEpUE((I?#(?J3=Z2J|;T>V5x``o{g`zB!n#FvYMv?8&Y`-X^(mOIgZFlOQ3GU7-A1
z?zH<@G&aI5V^Zfr at aI+PFlM3ANS3LqSp-gg!@p#IRH?f3783ysG_WJo`oi6xF6Ey?
zG{+(XKTH}6{?n?TMU^D2m8ZWJ;EUfeb_jO+GaO$wJjfd1e1 at B}3FgyW)z~P at 40Cg1
z4WHAX<%c<~2yQnh9|<Pga*r%k`j#MBNc0L<>3#-lGL5|UV=O7M+|oiR+xp?u+ at ZN2
zxeiv=rLHIX8EwwB%cav+D3ImuOR=<D8XDP&&m>HwiT7bm2%q*cI+7wQ4Fyx)`H4Ps
zLG-N~w9m?07WVm)$hL%q0bA0CzQQD8lE2Qob#7jDqf*ziD$gl<i+VOHPgE?dr?hqO
zwztZbX55^b5)=?!{V5Upt&*`$o at uSoUvzA*E*pvnp;IF}KTs at izI@RcBUK8Wee)A2
zfE5Who&L;K*|YHz8}7<8R!1S{AS<H*Z${M8FDp=;RpRHg;x?XOA5XYx7cSe`5y|i7
zzxw#C5F<?3n}P?J)WfFuScU#~*&EK|WLNEI27*N&!&p$0N6n;~i-uy%6ZF5 at j!zkT
zts?JGN=8#*ops}F-Q#h2%8RRc&MSfuF7lW}BpQ*#?X==5apb^^Y}w!j+4WtmwnU0a
z*_6gycD86lWA=RDouM!BPE#Rb)w3>nB>mlnQjCiIVO~n)w;lEm>&Ij8Ml)&fwaJU5
zm+P}thsDlZ<(H{^1h1s8klME%GHDOoC61aG<gDuBWtt$mT`S$D!YkKpXVBt<Anl{j
zlw*5oJ$0U`ml{%P`V1<}OEGG?eh at pQP_Q1pw*xX;9ncLc9*-|98s?R4zn>ry at gT*t
z{j2`8g&c%pg_z>PTVb%Z{5!M0l-_OA_t*MToK})d at kQ&YF~nQmHQxkDfkeGdUa$A7
zH at 88sjrjIZC6W@xb=_S^AM#m!Iv%m=-I?F at R5M#Os%@fes%<8G((1z9xWrHGNHvy<
zsEe@)*O_PY)o!~Wl_HW`LchnUQzX0ENK_fJ=eh!vMbSJ)DW7J9rpKTST-O&p(ir(T
zQ=soqjsaa*@s$}xW<T%B&z<A6o{L|v6>i|`Y!iK|351^Dw;VR>1t)#0q9r0jZ6C9b
zN+7cXsRlDB*Z(0Sc4m(hY|15%W%ohe!q}u{n<!J?74oSGBJof84=02lwC#0URv9}{
zk>4L&+wRRu?TEScc(Y!<pwfNITxO`K4BGeW76S4|^`@%Q<W)U(K_k^)P<1|BF|SG0
zB6lseQ!=M)Avy**v#F|8rabpXmf@;9MMnVsscSs|UXb6t@?<z{3nljwyQtfra?Af)
zoufWT)lX=alLZ9xuIQHxbp9$o5g7d-^3Q~xeJh&l4;vMagX)R8!Hda4z{t`|vvw9a
zy7jDaOwXq99v_xZ8JxnpJA5dN$@(cTbMm-!uJQ0j;@7%e2`l~s;?j|UWd`c8vKX&-
zbsnYAyhRV|!%g6;rJ5_&B8Idj{c#Ajp7nVmtj}9j0soYk<oG^bkj5&y*6UR%9(t^w
zK08 at 4OjJtNlgQyxY4JBVkyOidyLJ+K7K>a3={zBeGCtoB4xNgKM0uk?MXHp8-`^wT
z({?f(aSs&S3LAD!8*BEI9E~~~j>A)$MJ*D7w#u#tYKjzZliyoG^#tz1UL<xX+YSB^
z(UX<;%@l>s#`Bx?E!}JMk42Mc;ZX}jm|7Zsrbhs!Yl|9x?Jszr+E=%-cj_RP1gfGK
zhUdX;6|ik9LyV8hTN4VJtVhtOSgJKu)4lwBhddf%o9QY at deKQ7&bykR9if&tikO#R
z2=^Dw{4q7>Dm4!Jm#|*_BI3%Fx?3`yn%FgqS@)T%KuTg}ch;6Il&2$lP;S1?jf(Nb
ztZrFUI<@?O+qg;}JBp6W??Jvb$SioVRyN&<kMX}x2UcqMQir-<ZMI}ij}8z~ax>c%
z8G)IlQ(x}rRb<Bbjw$U=K>3vpVYx)xw<RghW1$jIZZpUpf$jx*)t?^|Psa2YH+h5~
zcAV3W%y1Rt+5v4R$8lHt7tel2SE}cB4NAbAE%S1yMJpOVIu&^#17+6hx{}8d($>s*
zEX=Lfo1oC?sID)Ci~1Pm<p#euSjT(&<JBAdDZGKgo#*drR3DmZ-Ax4;iQ~Szo0ChY
zJ at 4gGWc5#I-q3U)-`L$6>^wzf-YRUtn!2iM)*gN=IK^-7^W$RfT80S>?ozO{T&q;M
z5>&Uw)Kx)yO+F4^+SevkxDz`0`lQs!pQenKlJN0G!nJ})$rJ2S${!{|iz%!qzmZP`
zM at kc3a5<VZHL@^FpdQ>E^Pc2T^dGMiv?XA(6EpBJH3mlhvh)nr8-hSZriga0&e}-b
zRlltsKC`U;^TWkbZ>cjGeEI&<ttrlrT<OER_CX?EYqjAp`+1GpxLO|rADtdyWFHo9
zB?8fe^IE>9xT>e%@0Z!JysxUr(2}&S^UpP&Y|a{HjH{{5nls%=oPv#;ol%r>?^<33
zYgtNQ)^K^zT)fhul~IkSM}#O6>dovbw+UbrwL^N+eMM4RXI5{#*WtH#%Xj{a42w3h
zAUNe~g)-QEI)_G=-LF^S96$6tLDU5$u^_HbXMCByPyRBh4vp}7Q%fBJAM-mhpYY>D
zU>9;Vw_;Htduh4%!||wWYT{~t`OiAa*?!&%QD0$H4%IK1^y<0-M!$xz7nBuAZR2ed
z=PulRM6UwDf*KpWkuGa8qtEtWbx at Rh2z<G*&lexGMn0n*Q1HlNLNP*fp`~sY3 at sdG
z7rJQ&6k7furgtW?qU(gs3FMvB7pm8x`lcNnZY%G~%{$~d1MeI!Kj13@?NK%SFj~1>
ze^qIdT8?0?$1dn3t_6wMDxP<6Sk>O?`iV1~(RQED{&8tX!GjJ?9nsq&IzsXiw>oF{
z4IF?B6R9F!mINq^TEqV7Z?2^5-O>e8`F8Jb6 at i<yI;ipKD|XArr|4V8igFM;=1XH|
zEXMptmu_6I`@G-Rb>GT9l(<T%?SCEpVOwd;)hB0UJ|^cz-MT&Dzb)>#G_|?zW_*rA
z)|e*q$fMB%5PD>9isL~)kDe_7sLtI5R7={F^ImJd=e02YQI5lyHk<eX>2iv1Wko4f
zBRZnGOur)?c6I+8Nz}b%Kz at VoB$_mxKh6<u_s6G2#H#jwTmLoP3?vp>SZ`Qgi0>-X
z{DZXUH=F1@>ym%0{@K*l9guJ0qIspIXY}|Bv}m1CKx=#wq5!4Y7`;2XK- at PY?4HD0
zzLSiX%93w4MRWIs3m0G0^p-!nBFQt4QS&!UzHx(?t){?n`1T>}K!IdajPD#n at G&2Y
zwQ-+OAZ-{mgRm%tKNUIw-K`S|G?v<9<Qvgs6V~Vm_G()J@#HP~`U#=p at LPdWMCf!%
zU)@QkEYi*^o0NB)-5_P;=ilCXVMAH2*Y-VEBd{jNRQ}PkHult7J)PZ&XBa`B&lDV#
zf|@WI@!d*U4*a(*W4Qtn*K`C65 at 4dqN`<4?6>i;vSiDjDV9!&3NkC6%4rqj3C~ku|
zgbEQmoK?kz7DRvAs38=QiMItfs}^}>W?TfIJ?C>}Sns$zCbr)XtF{0K=nYjj6N%{>
z_{5XAM=+M2tVh(li&X0AnO0NF&!Qm5H3X+f6_eneMT#qvL#h(BdCzQm<(e8eju}OC
z|HykAT3mS|;H)9`Hm9f5XmQw-!VT<~#VL4%3NN%>m0sbhwD;wlBzjx$icF<z5!(Te
z#SlGZ4O6)IqIszOx9sDWOzepxoKZL at rvrmQlnj+Eao?x3MJ<X8L{1*=;^AmtOK|O5
z#i0E)?-m>`l+!+3>G}qWy7L at 5o#YDMWFWlqH19(zbeiAa^P7gj44P{G2c?VV7hV8{
zFjY|#ww~N4MAR at jiL`y3TKX at BDmHu3hj;6H2tGdS$_4!3M;h^Ipw+Ic!)N3#-bnz7
z0%}YgyZT8p*$~W~)Sq<EZ`kSc3 at eI#JmzlaM^`lwu1M-C+^V+a!0^p0A7XMG5{F6y
z8%5niWnJGQ?5#m|^h$|x-w6cnaor^HO<k?n$Qmj+(^BwN0eIX*ozYsvQ+Y<0CU)&T
zDa1LuC3vp&frk8!Tcje=8H(Cwce?FBliTZbs<7Z3TgN_0IocI!zTuV*N>*bY{Enr-
zGWB(v3mY82IR=0OVObhI%B{%KpQ)%{xGP9s8~kcJHf5}9I7wCdrrHB<tXS?pEY@<a
zx%W<j at VX9wYTEvaZ#04Rz)e-MS?f!6$P+xat8I#q`tb8QR}lKnNdDm--Yv*^Ff8vZ
z^ej&-F6nt;WZl$M?b}zPv`eCXCpZrG3|5rQq8LAXLfdS>j)xV=PenURoiPfwv%l+p
z=swy0BXn9LduPZVsIK)9uyr|O1;|)<9pr<r4WTESQBB4Xd9~Z07V!q|u0GuP+E(h8
zL(n%|?(v;aq#7fXtY`$j)AY$+IQCIJk at 9mQwy*780$0E at OjA~454P at B7oZ3woc7n0
zgPLTGq5&P{+>@2#6y(`*%~>su!$+)3WWG*o>-UT<POXL<I{zd88~E#-Lq++?tpSCw
zwwod+prb&bw%nimJ<I_*t!OR_DCu^LROL&!o~%6{_ge7c>VUV?Caut;Idd=Zy7m)t
ztr0AuQ9#ePp at Ekc_nifh+<<w`=N%e@?Bq_H5A8Jjd=?rJ at b9)x9F6^2aN;XF|31`M
zvmLU91RUt;XE-CB<!+qKQzvj7-ea?8lc77}wK*C`y~Gu;FE?LhIjhl`bS!z()uPuF
z#q9rtSncgl at tA05uQ+t{?mG=w=u0?f)YJzL3#M(2`zEc8xSA5>M%h=0sJT#|lOZ5t
znP*#S<NNH6EBmj|dKi_q!FwTE%>k5mTIa)Xn(o_0foQBER5k!ERNy!q>prlDPo1>*
z at R9O!u%GrFV)CjB3{xyb`cd>q2MtDNFQk};8-^G~#;&_GfILZ9dB7+$hx$PCKnCvO
zuSEPBbO7^ed0aQ8<I~3KLeBktB248ijp3pkjtk;@65lt<?(w){va8<^WJL7+<{d`)
z=~E|Tx=bJTr2$>#QgVOZ3GWV~i<)iQ(rvFkgH+}?j1TJ;PpsP?RPj at w!@4s4O?c{E
z^Aq}jp{s&W6SzBGoTeid at c^oSJh!m3N*tzop5yD)@5tNJ`KrI?xs|Je2Cz}yb5aZI
zz{fAz3XK*hzvq^Kjy at R5H_Pdajr2pjzb&*vgHA`Vnyy}*BZ^wZHN+(&)e at m7SpP@n
zi*+7+UkYq?UhfM+IQ1^`o%w^0&~D(A;79D=AE9v2XT%ReLs&D$9a}#XK7FOnj#6{T
zwP*c?%P;44F{7qY6REYEFJ8aNL<B1HSxZ-9xoIBGz}@-f-L^y9XNv08w(tp<V|TxK
zGgzXt&$_dB;U?T7=j0b;{8F{!B})mn?v?DWZ81d=Ru!%wx}<&dqi|`q6wQ?g-nSM)
zepacCT-Gc?oZ2s-Vu0_7W$idkidM`Y)eO<s%tVAr_Pi~6Dqt(B4?QW<ENH-M?YSxP
zplQM%tFxf at G`G)d1=fDAu<Cr+S+qjDjmH%Zn_XudYw)&Xg^>@SCrtMpGbhgbO8W_W
zJQxnK_K|IAZM*n6dbSm3-u)}gJPV{pUaS at 67>tx*zE+;0_YZ`~Zp%aVumr;6Yd2qb
z7Rda*d4|r%P3Dn+E}TDAcS7bxRUCRg*I}Qt|BPUGf#W^kwwmn4m7P&UmwcUJwP4Y#
zJIBQ-q7%GgV3+O&53;kiuH%?TLx?yz(uxGdY63q&Td(prv$D;u5Y>q{gz6cM{-={?
z6IEL{4(IXEUE(w9okt5+hs6{3yMyj&TY;LT+7GNr^jhh3sq8C%mmK!_?Y<LT8^g5K
zJn`pF!0nw+dxndS*Sk4ORCZjt{aNaC@=h%Mx(%V!TB!4>#ogRvhh|P>nS!!gu+#r*
zkIwdC9~tUH?+m8uuu=iJ0B at 64Z(E?fp+|TT<KyryK(CW;lJAt^OLzDV<s6Hb$+NY1
z7Oe|wiPCQLchvQXgNfP;qv at G~gskEGoBGrt0!?}08>`^X!W_ENbTi-UGulDjVNJ;+
zozH!zk(IB7`W}W at 9qKt+)#x^C_M{m0_^u_<(LJjXQyv!;z2q49?P}74vCA)83AXd~
zjB9dTv-z`w|1kr(`(rShz!xz$aa68Q>B7E at W&f&3erm|pWL{dxp3k@!vLzJ=_@;AY
zVRqeT*D)TM at U`g-ISEbwM(8=9OAq3+<&V at +);UqTYply2tHFD(mj(KhorMvdIwxY$
z#E(f#!)=y55shckrnY@<>EyF}FFMr<x2+tkb`Zoo65BnB*fz4dWz<O4aeekk;?a-8
zZFkY($zIIWZF#dT$c#sMi7};ZEpr)t!M(9jiZToS%?r*-Mv-%!t3~yq+Z_`sMaz)(
zjD at 1`xpLaN_;r_+u&?5`L8^Aj5EHxZ90s;C=^zX-^o8NAF(7~+;e3H}I-tW<IWbWl
zq|%1eR%j+C at P~L(G7xE^<5LOfOu5!G(s!`2;_LL$i1z?YzXo1(7?zvXI(&IDKcc4f
zVP%F_nrMho#&#L+F0T}_YirE}wL79fGrMnamTO~3){X-W^YYO%0f0o(O$2yvgYVKB
zMcqp94d566$bC7{G9bA%i)_`JA^2yXAiMv0{Nuzcz2BCC{LOXEmjx#8PtL_SSzfrS
z>RA;6+i=9}zF=Yj0L<lQiT4{%nkAa+KtAnOksag*L4N|V?SdYY3bBa;3uF7HnalTE
zG_%ELbYawZhYzK#V&FL!Pxk;cG+7m3Sd at CMQzkQ30N~URmuD4qsl%N1$Udip|Jjf+
zb*sk<EQm`nw>4Z!F;ypn9>S%ET~r5_(vqT=x88oRrl6a407dbk$Al!~xo_gg3ID=A
z4GrN&4n}T;#uPCz`M7sLj`rjRE@{vu{3om`d=i%Xb&_Q0P-xsICiX6%n+U-4E)oxH
zwjDKK*xoPy&&Er1RLSeL*Q%xIll*)Kk7X&hUX`zWSewKPnr&4;z5fDvi>!T#HLTQ}
z_Z9au3Un at 9JP1q7|6)i&GHU%zyxjjKPDI;}Fll--%+RdRxElx*Mcw=VwD+A|O?6wi
zp;x6y??pjSx^xH#q6i34Km??T(xgf+A#_lXBE7~a2t=AjI)ow;krG~zF1<;ObVvxf
zTRnH&G2ZhF?igo%*q>J0bCo&Q^USrTm{EhnTdDdJQ*chX8;<MWSgiLC0Lam}rFqAu
zkmGlW%=(vb+4)8Qi`c6T(>5KMk^T2CxE>7lIHQgE?`Tfnvq?-ydItXH&mfInizH<;
zxg#fu|0PbTd&<hygrk45kXAPyC)FH;?X?|Xe-nZp5vC*CO8>6EJToM>DHq>Zk_U*$
zvEWx^CU~b0 at LIUb`_rs8^uIVCw_ at Y7IRkiDR#Rii3!p at FpS)*hjP=;`&wN-Pjk@`k
z?Z2f|$TLRlRwn7U8cqMWrw9DO-4$K{gubA at 6+D-C5>bkrcKtUCqnnA(QOgZ!EC~Tx
zSiEX^-cyF(XQA;-zQ&E|zeEbrSSa>A`7)bbmTrx7Vo8&BD0zOtIppAzq1V|hFbJS`
zx&J*185&z3^UrSE7(svBlS)kR<F+dTkS@}*nc6_2378q6TmP#S`=Zc(8<P57ABy=5
z+E`-1!LoL56#gX3(hhz6ZuZm;ECe+4U!}Bd{T98>X0M_K4(SiP4gIf*3 at y9MO>Lsu
z{7$x*NNr*EW+NZG{{8z~#oL7*hpG)Lvm8?99}@IaYPR@%`%a>SU%MUs`(K_)A8Z2$
z>jRMkFGEG3dPnzOBW)9!`&-ig9)~+610^#h+aBdndN)Ecq#DFtYcal=)cp7S;VD7?
z&gfAd4rD8+-rHy|wF%kN+Kn_d#}xgWf)nwVI*{`BG#t4iB61F`;j>^=E#R}DTD?M1
zhD?_FcMmjMDxAxomU~$vZCF at b9ZG=J(sCR<eFT#h)CZXFe at pMEPJIhaf|mnjtdS3a
z61}egZwOde;W=d(9_>6+b`J2F!T)=PhMKOA0=W4C_xgkNiLCYeH*QcrK?5($O at CYf
z_QoXu_ISW+Z|?8+Z<^$&===NcmEWtse~>b-Oh{mSZfRhW6Ns)qU0cW(JhlK2BG-Q^
z{%i4UOO>0JTa;VpAG`-jL{~N at tao6F&YpqifXvtaotd9Wb$j0;PwVi5zu|$Th{)%K
zw*cH5w7rq<NnC0*X|v at G1N!yv0S;{Y{&oZKIw>tPDL1u7-e+OqW|;w=PiWZSwDtQS
zUflptIlVNG%=tA3G;#U7H0{V~5vUq&6{!|RL=Son;}W;C)cof4I9EepW7+Vdsi=em
zzoeZKt;fg%TN*)P7+C22ANML`@+ZJB>tpCQ4Tp_a80rD)q-FtNq67(F#ld^=)&e1D
zC6)hVJ#VpoXWe++WPPwjmYpSSKp}MPE`WMZCDI6n;uBA_EC1H0Rw?l82e-^!uX}+?
z8zw9)xshQ`r^h=cXp*jA=u#6+(ODqa<^NV`zHYE?xNfxG-vR=v<i=E#&=>>!x?T83
zoCC16c$@!8BNC0h=y>AurdRlnmd-B{zFvO(<@e|k4r4jOPrn;_hG`1+{mp9bTP|2G
zS}tCGWlzTxfL{bpMSyqOff+Ra8UEk|@e~I=CGB?D{LP#hlGc=lPgDD7X#!Nr^{8ry
z_*=QG-<&<&7X7n()>!=Qz)U(mRV at w3{=rh~cUGsKA%)*gA(>M3#QC>@;4=e{U;oLv
zogI?elnP}3U at 7w=L7(%(!!;_ekm0K`Ft5^SlJ`-1^(dMVaLEDEZ-3gUmH1GNo0|GI
z;}uMLH;NPIqpGs|uMOwhP6oZl5w}-nwT^J7y?}$q?}R}=X+08L2d`_bw>xQG77@|x
z+AS$f0<=jr;HuWk^e~w4nUoH*Ec-JQ>*MM8WVMu#l%{G+((?p;roH4Lc#5Fb*+`%i
zVTR_s`dP*5t_;)Ps`-icuu{aLBd|8~R^eF15fDxkKW722lWz6OvUDcwjVaBnE04_(
za!}-d3*7nuoHE8q_uB4a?qU@;)L<hPH*UQ9N at I`$1X=lE$7$)_LlZc;7ZL9nx<pmY
z{<lkN^u^$}#&2!kdcF;RI_e`LB4XO_Uc#Gc`!Kw`1-+!6ebz5c5Wo)tD|R1#%Ao(Z
zDfQLgEBG`B{#oFi^jn~Ur=w55djN$+JAahqXW8BjpJ at 3or_K@Vo$loY!%~gS60FX?
z6#t!%pR+>}n-cMfYDpm#mf3gV&w-_op=vlLty#A}q1-RO0SE7Y$iz(or!IjOz^jdv
z1#>O`E5D4AqBm7<&~FCb%(#u(e7F1V0JN!)^u#dmYU}I$%v|U2VR)A9Av6%?J5{)?
zx)x=p1zmNN8AFf%9p6yP^^5D5*2UJv*DtS^JK5+#^$IVEh{(HHHu>!h^^~+p5aE_i
zg<H=r%{1<7KCN!ssSM>0+b25P`b at zAvFTM``YSdYDSxH|CV%DwW`C9g7XN!F+c%zX
z!rvsb0viL$=ql7}4PQc&VQYma48eZShD_DfJObiNcF0&E{DJ?0HM-_-%#%2f+&q1n
z4rOY_)${;<*#m&$TYHl)a4HJWx?Ut7z#r;68uC}-rSacIv#0URux_5%kl3at*~IhB
zaJ9IQxTZLKoLYQHd{e0<2|{z1=`QD8!MoysO!q0hWQh7 at Xv#pzY1Dqq(4IlH4D{1}
zc^533<L^KyXs at T0vX)y^F_{ye>6EQ=uk)<)uJf()1JNV at m(rvc(o4iMqzh+d4OEc(
zp(o#je@#GItG?{=s<rbVLMmcE9-KU at e&)BPbn>lS2r77 at 5nOF?XSDyaSa!I&-9%i!
z&Ow3mH!m(RhK#c+zk7AzH#@@vt=4dcaLLy4{ZfY at LZ@^I&C-?9v_{<n%P_^Z>rPzp
zaq9{_niYPT#nTV$dmx%e*d=A7Tiw~VrcK7-(FwoPzLIqYN68iEjav*Pn~>}1XSD9T
z$gzWt)421$K0`D|KlVSNuSwKpw>6-J$!N;hsZu&g5&hS|Jxxrp>#apukf7p|wjqVx
zOb-<$Gi-3yX`Dl6ct_nWhS1n`fAqbO+iSN=0+q*FZSiSm?@Yf`mY4utWYIA#!s=7K
zkPj<j8bRN%=OY at HfyFgi+SbaPOCXx7m3zGJ6rN_IA^4S3>OXU5&E{q(==k}dV8xwt
z!wR5>7tL_9oBhkmvFjhsfv&t$1SL7#;Q<YBBacE<6u#YjfuN4S(U16waS2GEAK=?c
za9_Q0uGgfTiJL`kxlee&rHRgEPFIq9J$dP=Bi?4m8qO9~7Z}ij=!nmbD~>l$TEIoq
zN><!bWI{izLms2=0o&z`8%=^b$fGz(?b`s(ULbB3OT353d#T9G=5{4x06v at AULN?#
z@>-h%7xT4Wr$MI$8lVC*{Ps|;W3-a2ZlY2_w_?fbM>|9H%UAM^BtQl4{c<5xx^ma}
zXlDX#7c<CZW<tTU^()F3f8m3ET<XlQvr`XhZQ14nPc0j8_IbI;LqPgMN)l$oH`AdL
z(jdO1alxQu(-3Ib%@)q+gIhD9wwySN=Hb9$Q0h?xM!;Rrg@%~6Dpz}broUu$>B at z1
z+h(F>eE65ey>P~OT+BMkN}&zWBZp`z1Z&k617O;!ro%Ud{2A6s91}n<w9xGHZ!3=<
za&xYslFBJMR=+*y+~8l!g}<dUR=AV^Rxq0%`y>ki0dct3_wcvFbuW<XgA+Rr;SDLR
z3XEr#vI264dyRKoVM6=vV<yxLAnuJdCJjzB1$7j<_bHEy235D30Q`gNqX6s2dk`wz
zRySY<0clOvR8mr{!>WWS9(lO~7P}4v<!b!I0HV{Z@~TQXGg*_-efnW%9%8d`#Ut>R
zOW}uGvd=u%+R~NS!t%vDs71A?j?obI{DJ_(8n*gi3zyazJAmZs5-^zbahr&{tv;u?
zWsR>N#PccZ^JYF|L&`pC8=UogI~YR$3-fB~&6Rk1`oZ@)8=)PmK4C6WMs$HHv9HG3
zH&Q%c7uLz>doAMJ4$a*I9bH<bABI8K2nVY}2kA-_9|G5SOL()-+91<`q!kY~q_g<O
zqHmwG%?g<iHr~VA$^{ShnO)BJ$eGWYiTg3S&1O&k6fXw7R=2$O)~j?d#goZ=HdJxH
z{s}1v21Cgr_ERD3*bnF6pNkNR4=#e1{N;M%uyS>pTAw(MPbAva6|?DjZQaiV*QVC?
zxDt*9D)MI`yC*|hF=rmE1I*}U)b2P2M at QTW(n8<GTjkW5O0OBZueP7YiBY~x*CB;!
z2VEx!c2U!5tfF24Jli%8Ud!=N7%$-Pke)Ekj_`-s!(MdwH-l0kY0zJS{Znto;w9~0
zFdxcN7f*#te?Jnfvdr18-xtNN+QY)Q-ZRJWv*Ar9N|;cy=QutsGjf1FrqoPV0x_Qk
zT8`FM4~)vXU$RAg-bVqfoL(6XKFp+r4L>EfLR9%qN^M7DxL$#`V-Y+I2pf<-+|fuM
zLyMPjyfq0E2gW~ltxoXVh`*v}(w$BC+IW}mwB~pC6-VZjo(4eM0%@3=JUUy~{6=`a
z-<QD&i1z`6o#$bEqTn5&*J>(edb(G4_duypF_486MBeoEvcvMB-ux}}g!`??Tl0t7
zW!t?*l%2BVF&G_~oX+v}e`;M(=R71{Uw#wM8URc at k`=xbLIi#OhM?yU^0YWo-T)?J
zX;9^r>?#Yqd)bRO*RB at uA?sqmEw|W$*_<l~Aw$X7m#-=5YK+g1Fn+uSD*pM#-tlAs
zqA87&AY-JG)LR+TIk08s=*FCc|BP>s8vCkPP)9eSM7t1#rPUSbQ+j(2>V|^!e_z_W
z`1Rs`l7rMd_0clQ`H-zTB`O26+{j3sVkq?asaV(RAlpl3aIHxG6g at 6XU+zfW#&KWP
z%D|mnfK5V#xX<Y7XgYPd3nDSWH4{!~hz;gNKxy2WX$8QRP=fb;bqbGSky6mefK6u>
zJPR?s<`D)`JEdwySQaJr^GvOvDOLNZ@!DmbVxu0fJ}ri8NMmzlw}(r_<Id1*+?PR1
z(387mcoD+tFd_s_$4D)D7^`u*;EQOR<wgn$3o%X!n;|AjZmZ+_uOPHq7Kp3CHFm#3
z-T|+SqJ)bV{ij)x#FSJYp?6B4r2~0smT$w at V06o<A6z`f!Dyk at W5ylRwtq{<_Aowx
zOo$le0r-5_4vOdc8#McEs!Ev=3w=CyAg!2~vKHt43&#d!+kq}+-4yu6>b#AgCe!rL
zd}y>5Xvb;-!<8+5$%8ryn}o~1o!inD=4lc)s$KXx4`UBsN`WM4R=kD%vb^w<eAdp#
zE(uQMpt<o=D~;q<wa8?Cx)W6w7GB|Km-q|A0Vb8|)_P=d*Z))>yEe5%Hg^nxI{RpK
zyByx`PG8L1;z}3Y_nk?UiC;f}NthAXs-N#L2WcEqbxU<WDDpZz-$SaQa^E`(EBT{%
zrJI%g8~1wkMD(Od&%8~JKieJN%FEX=Pw3CDEPa3&d at VFUp{=7EMMgit<`gox{c~kk
zn at WR9+2fH)2O~B4bG8?+hjkAax=h~yUN(!n#}X~~$r<UTg(6;0J3yr*FTZ-wcH1}i
zQA(7gmQD5+a{=q*7Z2c>D7zPJ8P`CRIyPCu9;}CVmpt<-D55UVe^q$pPZ{w8{z(4P
zfcpznBQ2ZDQ6rjzeP0~Uc_bb)4`xG4w1{;k?jHa*#d9#@qSn|XzERVlQfBF$+``-n
zLkjJv`6-<Pt7o*(aVN$vjt_LXF7!~ebJeXVU~}emWpB-4LK*i(C#$%56;(?b!2^eC
z;$~E#O(v|~zw(Ae@<FLvm<7HdnF$zew<rZw1kFKhnNXiS(RKpQ_X1zKmx*Xv;UxI;
zWPuQ5Q<q-H5c7IlvL#|fD&SclW|ed4M=FKFnB&iA<D7K)!DF&5a_z~gf(i=q$D^=M
zjYgL{cUK8kW?kMViwJ<k=3qHnA!yaWYOpttNe4ahztm`{wT2#wxj!QK+<dSUBdZqP
z;1p7$Q--D(kEgwrTdB;oKQV6i%#O+2KC4skTF&w}a8YNH=Jo}GqN7%C-w&gCR051^
zZf{kN%XiP`R~O_Uzg{sr_)d#}&H|f2yqxE~GMb2(LCT!q<6g&|cwPa{V`-<h`Nk62
zIyf703q;_=DnqNn1Z&!gVHGc!!FvUJj-dbOLpEx at B~vaIR%XhjFFpD!(Ix47ygD1@
zF2SJjWGY?7(BuGdrLnszEc>#Ku2=|rdoUT5PXM_vg96WrOKb28U<*@)DvHNlmD>b@
z5Tl0f<;Zp5xAJ-^nXj5^-1&C#^?V)ICJX4E(HBet<0X(g##&c#Dtb;H`jRb{Em+m(
zKK)VjhOXkqC2?Yy<9L@*+wI&w at Vt@})%hEYz(#mansFrbdXBopUN at xQK50n-<a^nE
z0$d%Bc|kS`A=i%5%35TsU~}^G;YDpk9ObO?QFPOm(!HA+Z*4S!47gVxhp#e&Y%{AF
zM>b$emcO~yf_~wvUn%zZ at LD)?1IC|p<YG@)!+dx<1Z54=?ZNS{qXUL{KsPz)M~obz
z<ch9L-RMm4=~pu41?{4jWKB~%Hb)<x`IqTr9&@Gn!M;+o2lVkHEwOD(BT3WxjB_Se
zZA8Cg$PyMM;KHO7#50+WKngP64oMspVa%QxByU?Yt9QgmysohVThbTSc&k;&jKmOb
z_!6`FpEzCe6qvLVPt^+gCk|7PaZ<VBA@{&hNdVz(JlVDjs^LHFh`?Ry-fn(%&=*jQ
zyKtE`XLXV77ly13+wfUX^f<cLUUoS5a1(&mfF#d9Z>AB&OuuW43MxB)I&7LV`414B
z;=4%a6=Qyph{XUMzBA1aZI+Jj&RrMVNRv%%Pcy9)iHTRf=t0|D#ZakGzEFYwt^x$+
z1nD~zT`)ntJ&xf5t)L3-yH~A#k`dbcDyXg;mjGNmKmNw;2Kws!>_(ZB8_TbMRs<3B
z?0}M3d#uX(S-Mlep2Mg)>RK$O*Un_y{);~&h_h$3p3wKqaU5XL+4?AJ3edTS at No>S
zy*)tY3t#A!duVuKQ06(+(AXDhl#`kc3rEGdHOIv6G*0s)wdJG^=|ID|$!Qz98+{)D
zw*i%Oh2?jU!Wl4gjP<7O{+A3ccczu{{<`WIH$itAvMz6mcE0ZB*|>Gpt_c({j&-5v
z)14TPE at D#qbGfIolT;D=^)|Y-vn=1d)!~{1vB*b{{~6TcWeF}C%L-R?{VaHWeDyf#
zk$U%)Izh}kgib>dEDrnWlgn}(#7k=s-Fm&Oewl4NGJ)Xac80k^zGWBZ*0(}z25b+|
z at Okp~(%sBEDy_;z0r>-8FJ(9qQsU26FTfYY^F3U!dp%kV<UPlh?wLQ7Qe#|xVIekZ
z4!?VpG!}!4fx=v8jA!SAO8}|xV`ZS4r-)_LZQgFi?gGCoDF=mF=e2RR=fXcFPtN at r
z at gd9IsPgoy97qZhagSGi7nCX#*%_T0VOo^a(~<PzfjfNMofGlZO4UqD{(6lbFa0I@
z(~J at wf619;1c`h_%WbCHJ>`OYw#&?@H0adkBj0Z=d{DeZW?x`_>w4oZsM}P7&gSym
zvt3X*pcOXdb+nwCjLw@!9IQdu{kEJy;LNMHfL7ZVBhT`uSWTiGspq~?<P>%0U7*l*
z at -JadU`ejfNa*W}yau}1{>X`c>LRF^%TC=4<@*Stq)0aRynk8cscbAZV`|h(dQV^&
z{%jX3qZ8C`q(tu-Wj9$I3A}Z|-Scb-FZ&5pGwmzJEHuu~C~)-6-DY#FK;TaYIdd~H
z6=i&2nW%hMfZVpCMN>z4C#+&}$N>kORucqF))(*u at -oO0-K24khH?qen-8jR0nRXI
zw|;hp8jyriPw$l-(&=CTXOmwvtdnvKiCXJ5ttv$~xMbQ`ki*J$wC22r8|9JQx#Ew{
zT1xb;sK<v8T1;+%S=T+dH)x`;QeId_eI4;`4lrEAY;mds0Xr3-2aY`LtDSX=N)zDT
z)17D>|KVO>?0QIfMGBdH4E7avVQEnC*UWf}R5)qJ<`W=kt#D>qyP;E(_7Y&7yd1+o
z>E=$(Uu0VjeKA%*9l>H8EGE06`ca1YuySPR4NnT)(YVQiV7jrZ>)j5O9uMk>KNG at +
zF?K?miUC1g+I#l~P*EgxFXerSzAk%BJvMJQ-VS=<+w`#<TeJtX8kgT`O%A<j!oEj9
zQ?rNHK&fxCpExBhLlW)=eR=H!cten!`AeJL_02wn5B%hD0|mL3IbnVUN%ZlF%$+>@
zDf42FoHfX6XlyJQSdqiJ>>OWKUv;jft at XhHK~l-XEzZx&*7r|66fgiCFRnbZ-KUrj
zoTQ240m(1Mjiu^9y2?>4D=NcI4P^FcyI-nk7Nn(CbVuD at E_^R$y|FeQaf9nilC at 4y
z!&Nh!E|?+>lCY{MMK<Sw;PIlZoMa&hvDEh;cgio`pm(I$01R^Gm)^2Z4j{EpI5=fR
zlh9dM>Psq*ve*bg0-)}lbwdAg#x320c;>m*N3d_03!TC<hU7lS56YyoQ*~+#9(-Li
za%*+%Lm8LZatf~p>JWzCly+s1w{uAkDIYI_2>gzZX9Q^}T#w3E<P}WDw%Qsyj1VLy
z=CFWI%cSO#mwCSmG0wN)ku<vgJl+G}5bJL at vB4nyRE#{%lpY)XL46yu`Br1>G&CD`
z1#qE?r|urX9?kxUI4YRSKZ at 1*T2cesIi{fM_`9qxL2i(|Tnb;_`l_AU;Tp&GE*KP~
z2TbF`M+q#>+Fuq3rGQ0-9(-s5x|Q}hiCI8KQ}cOLY_6gYN4LG78tCHqg65;cM^-KO
zQZ at OJujQ)*-9Y**H~+~R_&NuDdkz(U at 4Q>pDl~;r5%i;?vBkzN at r7>Q#;oAGQ+$wS
zH2W4{E>Xr;I4e2Qr`#6KEBcsA9l=)pBju51Y>nc;yBESk)_J|ILYaUCV%7J`LCK-O
zhc+=zvL*QHKIa$Rb09j@@sHWbu{ZR*K+wOGj?dn}n=a|sx)L^W|GCDs&wluR$76J_
zfEIBtoEy0%Z^}UXae=^7FL&!FX(xA|xB`8s`iUvTRO#FZl&*-*_}<T_r@~QF7M at j!
zk(V5`@dBHi$N*sZHub0bCrSOtM(ZV<=Z!~BpZ{?8f>Zy8B5)Q>gq+Z7iipI{{?53a
z(>Zf{-PD}8t1+v6?JpGMvFAsUY;}$^e02{dy1bjM=bB^N!&Csb;N3zNjL+fi1Ts0;
zRsDHiXjQOv7&yi$j3XF!@uIV)g9!2G1f-wQsG8;L10B+vlx~RU5yS(wHC{$XkaN6>
z6=niC&RUl=@*z?_7C~6V04V9FxI|+7s9^b{ZlOgUN5>dqJSY$Mov*tT7Kayn{s#jp
z?zPVn&|`5RLolPrawN3vS)F5+$>ziWnCwE-C1Xnwt{^otDr%Hm#N1pK#3Ru(|8g>I
z8Er-pBe9LVuaBqhR^-Q9!vtObHmu?l<IPeEZTl5^J&saa`KhGA#4+vb-F at X>Ar#uL
z%ZlgPz-FZoO#5aUq_woA{-IXVu$-J`W?&ha5S*-o0=xGK0;TSYdUFn8veb+Sf%duJ
zEnT2c-iuq4asVz9w*BKKeD1 at VW3{4<v}-nX1m&F=EL#apJd(uEjl|}LcO2e#sarl^
zsT#~W+JORQapyq4|Nc3=3hn|lb2V+Cfe)C44R=UO_(v^vElM-alfNeP;s7h_p(pLg
zVeZ`?=NphLnow0O+7aTgy&$bxjs(U?gPfwS_v+orU9YBT$GnZTJ<51Wp`>#a>Q`Mc
z5|b5%pX&pdb(pD#Ip_-LTxoH~r<U0ROO#-y(c#%6`jOBM!l55vDJMC$R4wWpG>wCC
z7WkGRsD>CZ?<hTB6Qw~7W!wdM-z$sKjIzNG)a&?9N^fvE2Qlh^6L3 at xY*X(v7cV*t
z0jm3FvxbRIB{6 at Cma0+Eq3!#8>m$1m$#lreqnA57n$SvuGi4DMawqyI_skG(PZaA9
zNID4P_ at Ncf5fk;vAt`~GOTN2>4cVR?(AW2tp8$)oe4QkxA#*4=$-+96EJq1wQOnw-
zOZ!i|CAuGx>!Z`T?YK%GfsAKY(3>gl`531HC%#0O3SOtI4kM_vFy1Ao)nKP4cJo+Z
zA at t^-sT2aDpApqLV2EZjr(~qO$3j1LL1&}yQ@@U4di-IGd~>W at 9VLQYj~*o!QL|Nw
zkZf@=>@X_3;JkDLxH;u&@d@!7(Xi at Qj?hj`<4|Ioj=87WcLs#SNsWDzy!-`kWoG17
zh5d}d#vo-ERYgd4svgY9F;b#CzjxE+DI;C^nZ;fmcIq!RnYlJG-0twX2y#y}q_FcV
zkq$G5LDXu$l#Nad2;*P at C-UpE*L<`AE7x%*e)|fswNoFZ8*!}5Z|rh}MC|cQh(R}x
zA#fjGzcME)y at tZMBy_|n{B^Pj9Aj14=DJ8?>(Zb`xg2Y_qTGh4j~QuU3!%NM8L-3s
zw<_skCDW_*&&qTRjZYl&DI-q$1j(ztoHrt5BZxwsXI+Pj;ecTxaP(=A)W~9`u;*fM
zl at -YxAtW2meJ?)F%`R!`N!{JB%?z3Nv!~5FCs$dJtC<4>r}y>_{rb2Voq|}=nrujn
zxcy^QI2qM`pg>|>3sz%5UFYByxz5Q~X+#M2{(1A`&jB$0Nm4syb-I>XF at XYM`$KR%
zFsPtfDLWF9>yvgEvlLbZpvj7AuDlek;}^|R?pPNoR7;UWDNy#7*r%n_2Z(iw6R-sP
zzQxSy!$b4#kA5u_P8qv2f at e9Mgx+IqJF&9m at YzkTNm?>)y9H+-n=+^OD{!iLQV*hy
zAHh9qts1tv#e$TJ3s8<3HAQnz1)Wc-XTeLP7!qayFkuMWDClvbRfF5(ztAT7Gcl>u
zP|+);>qEf at 1t?K9sZ_75+vU$$5ny8nx&SxlORBZrNTJA$PwiJlI)7?Vo&>oRwq6u*
z<UFJ8sJLlt$N4ump!@ZKGk>;a#G27t1gpuDJI)<)q3dxFn|L|6s)UFmgVWfv5T9^O
zqyJmGancPCZGpj8iK-JLv_ywQMuAA5 at 7y)rx{d={I+9AJ?Tv1whmz6Img#cRAI<pt
zkTVzJm>+XEW?sP4Fmq{4r?;9#iOuQ!h~fY(OXC37SlSX#gou1yE;I7!YqWCP^*?Ll
zf1m#+fsj{<24e#j1KLg!)5!grp=rZxiRog$dqYmwK7_9!d*n&Z(=PS^nH0FU0?<2P
zYy8`=Wv==D-+1(={fHrcgKSS%Ibtpw66-hb-umg|-Ljd|fbe&I+h^|-3QXdV at H<}U
z`%M5_^S8~W^eIEZUW at Z-iiRmY7U$DZhKWNY2_g;>n|`%AUx`QWv}FI|<6IKTGeiM^
z;|(lsk8ZIck31V6Pw&U9?mt}P+~e&A*-1gA%y4$wxh1GW<-)28PULDkTAX4J3nBfu
z*9gr=k1Avh8<OnM&(v!4%eu#zmX!YxQVIG;as{AYDJ5e|SK>QjhDZz}zIly}+lCJA
zR1v%}l0o at 1vKy&K&$$EBQexdWmaWtMhtuC0dJR}4lVwQ~E!IR!;I!s#F<%T#7)A(T
z`o7cq?>NcZpP#&NjflWSLi%%zY~;;Q6m;)MrcJCbon<#NwzH}Wj89wkl at V<ziGMnw
zpDmw9-ma?4?kSrtB|l2u&aKxB^1k`erL6Nu?Jn?lT680!jnC=?y0g!!_Hho6qmbn#
zKr1$eGK`-$dMP6USFc>S&on|fet#M#;D~_qgNy9|78OYP*}r0kdVjNa(q?400D#?6
zKE|6V4!MQP at 5Q;y3>=`0FB7fHoQ8hEsvM~nbrc!47a-Hh;8ErLdZsQ51Cw*+ch4YY
zbtfmX-XMJtt6lHsk~YQ>2 at jU4sQ~+rjBL&BfdqGAeE!C4ZY6f&(|#O8Q;dwNbQEAB
zri>~2yfOsXki(nX60I`(cD`w{&ySY7As%~Y5F4444WX5=%VszxT>sk=@l5G|TDCS2
zlhn9ja|CIp5iMGMCET;!2zc%9OP_{7`h-?<ykyuNVJ-(J)>1aqoyi$X{MM&`g!uT7
zNAGO(yH|O%)NPqOW`~B}M2&?RuL<fLprOiUV-J=T{dajhwHQW1b8V;OVomd45H;(%
zBCvtbzy{u<tS(*4r0P<G>)I_{61el}B0g$AKBGQ<-704 at zQqKf6|H<U>H+WdrT|VL
zp7ngf5S5jc88q>%2Yr(l55n!ZL=PT+fwUSIiI=Xg5Pb~*uI;g%Ghx+fW0nIsQJ56m
zlD$71aQz}N?`ftjFEZdL&TR!}<oXuU+EyTM%!}XgDM_sT1h51>{Uim^w6i;;ufm^}
zl)6`G2|+)yg9efm_b$~_I0~n9^xf)C-q7`j4t~un3B1_*cf*s9J25V!w<U*b8k#Vg
zry1#|1IBn<bvD?#30BrAtdj#%p1ON{o&$7GV^1YJZXHdCMgDv|9ni0|m6HeH0Wtn;
z(`@ze@$0H>e0T=ys0+qBhPtUzI{sRnR8ik(9XQ82-8ftH7B-rM&8jz|AU2}OPub50
zoYU at X$TomWL*Jc{jzxuc4xS0t1-q}2vSt8YCX$*Q?6js~+hkH^JQZ5Ip9v`(RryUP
zcBn-e2%R<;Hzp58U^G<C6b^Dmvd$jR;>05_VdGO{#z<ym+sz(BRs1dWEg0t|<&0BV
zvninhgy6^Nwk$9=vi*t<a47F}+|4^lngy2K(wXa2+KMP*9k=h%c~1u+(Sh#V0}k2H
zOA(GFp^V$Q?czGMOuLh`n?b|^w?61vZ(4mEvh7sVY+eaRcJ#920S{=GZ*9X}nR<5r
zToLIFz#h;71RCqje_+ExF4wM;ObCi_P25`Z at Xcu15<p&ATW2bk$`WQcZYUhH;M)Rh
zIv#jOZpzvuuOD#c?0}@St9xECmj0X8NwAoXq}aYZr&*}ikaR1x&~+68UuoXzulS5r
zU04~~_vdW(AuW->O1!v*+GjA1PcE(9!w+d?$x5RNuRUP<qXg6BzCJ|IH*8BnO+SXG
zL)gQCyIbz;^k2TD`!iF>I(^jJHI2x1z3Kf5i=5w)n4_zsjt<bEp0~Nk1hSwtzivEV
zXj}inK4Oe3G2H2fkCv_DQU#>e0m>7`{HZuL^nBatH^(8dbLWrFkJP<m0}aK!eo5FZ
zkeCbBtZ?R)pj59AZ=r}{?&NdQDM7u?vF!2{X`$57+s+=i{|x|ecs~|U-_bt)h8Iou
zkD+vjEkEX;r-X|tj8aEq97ENeHi8A|)M!oiu-iBU{f)?oSZ(H6KHkh0Ewc{psqM!g
zTI)E;%ZndU=aP=%&vN2W+V=YS7d{?@-K5lZg^3j)JPN4jePblY5rQ|zXM;$?*mOa2
zVak;=0Js^8pi3HO8lK4NlNX(H)Oy|}D|J}uca^KZ1)$s?p5EoK-DtM5`YsH**f*t_
zPTY!vc*JX6E1tiK5P%1(DGhRRV}nw1uQLK9DHDstW at 5#vuXD48-I;#c09#?(z6m1l
z4)1LW?YSM=lr>HkuygmQ%KKbs9HD^I$~k%W{aOokcRnq^wI*^Wroy)S)RqG9Er0gg
z1je*$HC$56N{~4p`UeBU2t`owSEBt#zWIH_y6ZI at 07qe|tBAU!b&y&UpDWJxCR#27
z<UPA0f8GAPLP^8RVS);KO0JS?*^SihKEbF=mBg2;y-+20>3Y!tt3|VUHgeUdNCag>
z47tQ={5tx?wOD&R5Vl96)c&DW7|Ax@`h0!{fWg66qNu>7eB^XaOkZRpfn8r$wa=U2
zi<XZ*dW9iAstZq935jaq>$Hqw=&VhNLrx!ATbaqePn}ZZ2aywl;vt1Ax3zl~MTCD~
zUIoOMR=_hV5`PsbY$)geN+9F2#PWXpS5pLZkjMGy7r+4^VL8(=I>u3#o%Mqq82~Fi
z=DC=1X`IC68`Nso>)9&|N`hNR62Q0(PlV#B<KY#uDT$sYea4{0oa^=&!P~n>X=k0Q
zI$tpl)Cc#HU at 8yZG#1cA*wKz`Jp>D}Iulsp$c4eZTiw^gZx=$J%Q15WPT+<kRAO;g
z>%LsI{%b9Ai}XPr-S5Wi at j3}<(Nl0H7R2RHN3<&zD>0266w`_=RER#j<O*EwV4-*F
zEL}A4q8q7 at +a}mgmYoAGG6j1{d>`h1oTl<wirS>$CPpH?uSGOsv5sBWzv&8UFv1ih
zv65?}$czDGzLr!#i%N)N1+9!Ni!hf|Kb*a}Z9>LG3Ht#vAi7>An;S-`Ro3(OdA{{v
z9Hj*fkFhsOn&H;$Pc6#oGUFa=%XWm|Z=B`9J!a}2uQ~OTs_#!%#&X3NrGR8(?RQNP
z_$Q8?JTq`EKa!dmRswcM|G_tp9VQCOh^?ynn|3936bheck(04^HE8NMDYOsevKq6T
z&&K;+FV%51umacPJTDsoAV#-Y(U`Qyr~>cHGa#xj-Y+KL1doedXIl*;b|8#3*mLF@
zY0YOHS8UK)%f+Q26rg-w5hJ0*I7qCYgb&A$VZ<5u8INrJ4h^-vm|`h}iMsUk89Y=M
z8k(ZyMF>NA2!Q14c2qx9>v5rmETj8aAM>`zoE$Eq*(h$3Ydh7Pr$lH$tEVLh|It$*
zHd{7aU&HH!w?7O1L6HYCjqJGvw#vLf1B~_Mmy!H>;;~{1a|?<nz;{F9qT|=kYhIL^
zl=gjb^Kv&uKubIUY&%=PVaP+ZggpoPS)PoxgQ|lEo3^E(o=Tdv3h_q~wPh1ix0Xr=
z$f(XB<n0sb{=dx1CJSW~C^^;R*3V8hKNtL7H+N&Ww-Qj}DkIFa4YcvTz%X{V=L)IB
z4&R+n-W|*RR at -yjLB2gJ4N9smHzgCA@9jpA^0I&;zm7S7uEsQ;JiUE~LL2|k7(gmf
zNm~~*R=b4MsUtpk?RuT%+;!?AClgB1V#|^E8emG-9=v^mH;F$pq%4;AWS&{x$R!%6
zWLBFv-yCA1`^VO0=4K8isUd)1uRa8lebNi at ND<PQ=BSQ5h;bKZ6oT1<y1#UgE6$~e
zyQgS9Sekm?ojV!~qJ1cn>c8)M=KZ;D$g({t+H^8|<{x)M(730PYm84ER{iYmU&A^i
zW?HvZcDP(Q4mcoh4EP{zn=PLXQ#kGIXzZHGM?!?CBML-rcH9w02LT8l*Nw`&ie3tB
zdcR5Ln&r~`5B;-F8_xjo2#h>T{^Iei2+<@J_ryJASTa{Jz~*P0lEV)%2XQ?mtyFog
zollEg#l;$e at 1#H%6BaX;_VG4pLEpsOnUMrvp1|`RlAXYoq>~b9Ji8oxfxHXySOvJ%
z^-CpF?oxPw++(BLb_&&z0ex(}^Plc}ZU7G`bLoK2w8M4DQToB>o0YR0l`ax5L(ip!
zO#{`;`MEoBtW>ZgG08wBD~sg_i4#YYJ0KO&31F3p6Qp6UbM75a1E{lh0<2i5IMEww
zFj|`*`}ILq5fYn&S=F_>E!=SF^c}3r6g6xpcEcrmNE#GoQHItXrwKA{DGE-XN}(G$
zoO&63vI+)N{?^CJ3%k63Mf%wO@)E7(<)4k70$=7J&Xs;IsB{lnE{B<NoEvd(0s?~=
zh{g&<-lUI}o+qoXEUJKsnRQq_CP8X at Oo*r2zQoTH_}`O|(&4JYTbbCSoU!Z2-LBCr
z<X1YCjLTmtxCHTaN|WDyu^F4UL9?)k2<dA at 9l+hH0M1!9F4L}m)Yt7|q>sj(jMZ-6
zWsK#$aRIW0On at R8mMpqY8w_eN@l_=<Mp_0^GAdu(0IpZc8)jfm?$?DEu7nuR8|6xv
zmEOC6{57#-xyu#fy at uA+3Rg6n`e_E7u|s+ty(N9s7u<WWirI at 5AGZMZnwf_s%-6t)
zG4VGj|274hZfapl+Z9y71?sN3))vm&AzzBzZR|E3!x(_{Bl^x*Og<EG0IOG(c(e?8
z`P_#?0FE6-<kc3^xSU1`ZDDt=?T5v=G0snL&Q6_ at ETyC*rI|(=_DECBj_kKzs3b44
zyIrP at dd`q{IVvyr=LtP^cSsJ8Ye3KQ=Lz6Qt^_9gi#c35+$SKhA`^S3Dm%B0y1)Qu
z=YXU0jf;DcaZ?j{GeyYlxU=atxBJZDmY^I-Lm2-apWBamFqbc>lB!{(_B*uhX*p~a
z0%}OE{ly9bMTin-9cICHDX7=4-LiG^-hK3-XxEAEd?CsC{+b%MLd*MSRT9aqZLYP3
zc4l{Lzl%Fk7sguFN at Rq1^c`6s?q@#4&00X~_%49%>rF)aARY+Yn+I3Un%A0bZ5P#9
z(>sK=dRTW at TpoWq<zHKMk84yngsJC_T^Ovd=Ji?gZO}N6ljb<*A-n7+Y*gbchLNUj
zB=4mqp^UXG9O~RRSG(BgJNZr)sYE2Qn{84@#3sJHj<GQR*^>iXO_9Ttez5ht0OC*?
zk(tG{%#4rV_ydLK-a97S408ukBuENw#3MNvJhk{0UY|Jk)yZhMtB#po-JFzs94sq+
z-<p51#7F7i)8bYGm~W4Z9v1?gQpP3)OI5lJBkOsxyUXgPg!aclzcS^jKZC7>?FgGZ
zRd!RW^dN?+yiQv?sDjfa`{5Xi%&F|st=(eEh?GuqGJ75 at zBDzM>dBS%jiN!~2g2i{
zl(W4#tPe+Vovq7JPgV55a5s|3{xwjbf)sJN%L6w1lKW$;;(+9X`r+mystCc9i8>Eu
z_X^3KdY>IDyldZKVSFzFRI`UV^0OlYf#&Ee8Z4-+nNXUW>|FcI_(hdAYI~+MSYe&(
z74PHuwyl0SiWgpjbp at v3V`uc@^rH0T_x1l7jo!WGSmio`rFuXB#_F9HuEO9cAuvRH
zHZI-JTnRdw?D#%@WhT&lq!;CQXwl(_yRvx_)(4swBeTzQvUyzx*0n}FV!vTc<!p|t
zM0;NOI%Uit9b;vl=Mf8SCbOr%a{ob7T_;NCZRHeYgv9qyS!v^R_f0i;o#WVSld^PO
z<)a$M_gDr)Tnb9&O0ef}RSh7e_k(W(ro2L0laV3}PLoG<)kwaL9~0;B8#qa4hR64(
z$Pw?nJ^6V- at 5-yG44%RcoE<_*wq*8d^-a<i`m^_g#(g|Xa~MF_r;=XKk;Zy9 at __2`
z<P(|2N0*$&w&{b$M$hU-x~L+jIW1hKM}5q%H|cb4GJqJWc`?b(>dA)Jm$sobwHARm
z*Ld=FBtvs}6BY!7{>Zre_&v|^$+Vq2TbG%S+0S~U?V&`F8uzN80E11Q)!0UgDbrW}
z#hwN(0ZnOWt}Zcf*nUlZ3$@<d+4!DK2Mu<5GK9ujKY3`%$K-nYUO&X=h%-e$GnR at K
zw|%x?wrWm%Ak=bBcL<wZ>5+v*(l<#n+^AcJw_(22Uao%cxW+ZhtFW)i_R0_HZ>)4P
zeo|m#SuSOM(Ao2w&+4ryPwk!qHc*>p724$8)5_3k^Rb7HcVlOG7f-FCQobEpS&bxN
z*1`9+Prhu+0Dl8IFA?#6y^OwO1No-%MVVxJoz+n_q}rfPH&5wwZ#%JFRZ446{(O8C
z5W@!)VK~k08}ewxd(7PX0hNR4cU($imd)_neAY&Fn-lpvCY)~V;pS}0tqmnW6b%r_
zbMw7V_#KR+V&)D?t}S)GBWJ@*iCABr(Y`g!k1{x{8(sot?EljLB=G;c1cI7Q8T?&8
V$Qk)Rarpf&xAlx~RO#49{T~zh??nIr
literal 0
HcmV?d00001
>From 690c6ab3f6fb14968b06bd9d8e8a9b7b0d4b0d4a Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Wed, 4 Sep 2024 12:43:16 -0400
Subject: [PATCH 13/61] fix formatting
---
llvm/docs/Telemetry.rst | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)
diff --git a/llvm/docs/Telemetry.rst b/llvm/docs/Telemetry.rst
index bca8eda9d13d11..be7d7c673c21ef 100644
--- a/llvm/docs/Telemetry.rst
+++ b/llvm/docs/Telemetry.rst
@@ -55,10 +55,10 @@ Important notes
* Data ownership and data collection consents are hard to accommodate from
LLVM developers' point of view:
- * Eg., data collected by Telemetry is not neccessarily owned by the user
- of an LLVM tool with Telemetry enabled, hence the user's consent to data
- collection is not meaningful. On the other hand, LLVM developers have no
- reasonable ways to request consent from the "real" owners.
+ * Eg., data collected by Telemetry is not neccessarily owned by the user
+ of an LLVM tool with Telemetry enabled, hence the user's consent to data
+ collection is not meaningful. On the other hand, LLVM developers have no
+ reasonable ways to request consent from the "real" owners.
High-level design
@@ -67,7 +67,7 @@ High-level design
Key components
--------------
-The framework is consisted of three important classes:
+The framework is consisted of four important classes:
* `llvm::telemetry::Telemeter`: The class responsible for collecting and
forwarding telemetry data. This is the main point of interaction between the
@@ -76,3 +76,8 @@ The framework is consisted of three important classes:
* `llvm::telemetry::Config`: Configurations on the `Telemeter`.
.. image:: llvm_telemetry_design.png
+
+How to implement and interact with the API
+------------------------------------------
+
+// TODO: walk through a simple usage here.
>From db668f4847d7a8adb5587b24fe11e8b5edff0eee Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Wed, 4 Sep 2024 13:52:55 -0400
Subject: [PATCH 14/61] details
---
llvm/docs/Telemetry.rst | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/llvm/docs/Telemetry.rst b/llvm/docs/Telemetry.rst
index be7d7c673c21ef..afce9af040a6ec 100644
--- a/llvm/docs/Telemetry.rst
+++ b/llvm/docs/Telemetry.rst
@@ -70,11 +70,16 @@ Key components
The framework is consisted of four important classes:
* `llvm::telemetry::Telemeter`: The class responsible for collecting and
- forwarding telemetry data. This is the main point of interaction between the
+ transmitting telemetry data. This is the main point of interaction between the
framework and any tool that wants to enable telemery.
* `llvm::telemetry::TelemetryInfo`: Data courier
+* `llvm::telemetry::Destination`: Data sink to which the Telemetry framework
+ sends data.
+ Its implementation is transparent to the framework.
+ It is up to the vendor to decide which pieces of data to forward and where
+ to forward them to their final storage.
* `llvm::telemetry::Config`: Configurations on the `Telemeter`.
-
+
.. image:: llvm_telemetry_design.png
How to implement and interact with the API
>From 0290a1491593135da6db2838a5ab019361cd6f2c Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Wed, 4 Sep 2024 14:33:52 -0400
Subject: [PATCH 15/61] formatted TelemetryTest.cpp
---
llvm/unittests/Telemetry/TelemetryTest.cpp | 39 ++++++++++------------
1 file changed, 17 insertions(+), 22 deletions(-)
diff --git a/llvm/unittests/Telemetry/TelemetryTest.cpp b/llvm/unittests/Telemetry/TelemetryTest.cpp
index a2ded3cb5a2392..fcd1f4c750d313 100644
--- a/llvm/unittests/Telemetry/TelemetryTest.cpp
+++ b/llvm/unittests/Telemetry/TelemetryTest.cpp
@@ -47,9 +47,9 @@ static std::string nextUuid() {
}
struct VendorEntryKind {
- static const KindType VendorCommon = 0b010101000;
- static const KindType Startup = 0b010101001;
- static const KindType Exit = 0b010101010;
+ static const KindType VendorCommon = 168; // 0b010101000
+ static const KindType Startup = 169; // 0b010101001
+ static const KindType Exit = 170; // 0b010101010
};
// Demonstrates that the TelemetryInfo (data courier) struct can be extended
@@ -64,9 +64,7 @@ struct VendorCommonTelemetryInfo : public TelemetryInfo {
VendorEntryKind::VendorCommon;
}
- KindType getKind() const override {
- return VendorEntryKind::VendorCommon;
- }
+ KindType getKind() const override { return VendorEntryKind::VendorCommon; }
virtual void serializeToStream(llvm::raw_ostream &OS) const = 0;
};
@@ -303,8 +301,7 @@ class TestTelemeter : public Telemeter {
public:
TestTelemeter(std::string SessionId) : Uuid(SessionId), Counter(0) {}
- static std::unique_ptr<TestTelemeter>
- createInstance(Config *config) {
+ static std::unique_ptr<TestTelemeter> createInstance(Config *config) {
llvm::errs() << "============================== createInstance is called"
<< "\n";
if (!config->EnableTelemetry)
@@ -496,8 +493,7 @@ static std::string ValueToString(const json::Value *V) {
// Without vendor's implementation, telemetry is not enabled by default.
TEST(TelemetryTest, TelemetryDefault) {
HasVendorConfig = false;
- std::shared_ptr<llvm::telemetry::Config> Config =
- GetTelemetryConfig();
+ std::shared_ptr<llvm::telemetry::Config> Config = GetTelemetryConfig();
auto Tool = vendor_code::TestTelemeter::createInstance(Config.get());
EXPECT_EQ(nullptr, Tool.get());
@@ -512,8 +508,7 @@ TEST(TelemetryTest, TelemetryEnabled) {
Buffer.clear();
EmittedJsons.clear();
- std::shared_ptr<llvm::telemetry::Config> Config =
- GetTelemetryConfig();
+ std::shared_ptr<llvm::telemetry::Config> Config = GetTelemetryConfig();
// Add some destinations
Config->AdditionalDestinations.push_back(vendor_code::STRING_DEST);
@@ -531,11 +526,11 @@ TEST(TelemetryTest, TelemetryEnabled) {
// Check that the StringDestination emitted properly
{
std::string ExpectedBuffer =
- ("SessionId:" + llvm::Twine(ExpectedUuid) + "\n" + "MagicStartupMsg:One_" +
- llvm::Twine(ToolName) + "\n" + "SessionId:" + llvm::Twine(ExpectedUuid) +
- "\n" + "MSG_0:Two\n" + "MSG_1:Deux\n" + "MSG_2:Zwei\n" +
- "SessionId:" + llvm::Twine(ExpectedUuid) + "\n" + "MagicExitMsg:Three_" +
- llvm::Twine(ToolName) + "\n")
+ ("SessionId:" + llvm::Twine(ExpectedUuid) + "\n" +
+ "MagicStartupMsg:One_" + llvm::Twine(ToolName) + "\n" + "SessionId:" +
+ llvm::Twine(ExpectedUuid) + "\n" + "MSG_0:Two\n" + "MSG_1:Deux\n" +
+ "MSG_2:Zwei\n" + "SessionId:" + llvm::Twine(ExpectedUuid) + "\n" +
+ "MagicExitMsg:Three_" + llvm::Twine(ToolName) + "\n")
.str();
EXPECT_STREQ(ExpectedBuffer.c_str(), Buffer.c_str());
@@ -590,8 +585,7 @@ TEST(TelemetryTest, TelemetryEnabledSanitizeData) {
Buffer.clear();
EmittedJsons.clear();
- std::shared_ptr<llvm::telemetry::Config> Config =
- GetTelemetryConfig();
+ std::shared_ptr<llvm::telemetry::Config> Config = GetTelemetryConfig();
// Add some destinations
Config->AdditionalDestinations.push_back(vendor_code::STRING_DEST);
@@ -608,9 +602,10 @@ TEST(TelemetryTest, TelemetryEnabledSanitizeData) {
// The StringDestination should have removed the odd-positioned msgs.
std::string ExpectedBuffer =
- ("SessionId:" + llvm::Twine(ExpectedUuid) + "\n" + "MagicStartupMsg:One_" +
- llvm::Twine(ToolName) + "\n" + "SessionId:" + llvm::Twine(ExpectedUuid) +
- "\n" + "MSG_0:Two\n" + "MSG_1:\n" + // <<< was sanitized away.
+ ("SessionId:" + llvm::Twine(ExpectedUuid) + "\n" +
+ "MagicStartupMsg:One_" + llvm::Twine(ToolName) + "\n" +
+ "SessionId:" + llvm::Twine(ExpectedUuid) + "\n" + "MSG_0:Two\n" +
+ "MSG_1:\n" + // <<< was sanitized away.
"MSG_2:Zwei\n" + "SessionId:" + llvm::Twine(ExpectedUuid) + "\n" +
"MagicExitMsg:Three_" + llvm::Twine(ToolName) + "\n")
.str();
>From 14b52340b67aa7cce366717b35aa1327dd4b48d5 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Wed, 4 Sep 2024 14:36:58 -0400
Subject: [PATCH 16/61] fix formatting in docs
---
llvm/docs/Telemetry.rst | 1 +
1 file changed, 1 insertion(+)
diff --git a/llvm/docs/Telemetry.rst b/llvm/docs/Telemetry.rst
index afce9af040a6ec..4dc305362cfed8 100644
--- a/llvm/docs/Telemetry.rst
+++ b/llvm/docs/Telemetry.rst
@@ -55,6 +55,7 @@ Important notes
* Data ownership and data collection consents are hard to accommodate from
LLVM developers' point of view:
+
* Eg., data collected by Telemetry is not neccessarily owned by the user
of an LLVM tool with Telemetry enabled, hence the user's consent to data
collection is not meaningful. On the other hand, LLVM developers have no
>From 6ca87e532a66fdb61b6c924f84368d062353da91 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Wed, 4 Sep 2024 14:57:38 -0400
Subject: [PATCH 17/61] convert comments to doxygen style
---
...y_design.png => llvm_telemetry_design.png} | Bin
llvm/include/llvm/Telemetry/Telemetry.h | 62 +++++++++++-------
llvm/unittests/Telemetry/TelemetryTest.cpp | 1 -
3 files changed, 38 insertions(+), 25 deletions(-)
rename llvm/docs/{llvm_telemery_design.png => llvm_telemetry_design.png} (100%)
diff --git a/llvm/docs/llvm_telemery_design.png b/llvm/docs/llvm_telemetry_design.png
similarity index 100%
rename from llvm/docs/llvm_telemery_design.png
rename to llvm/docs/llvm_telemetry_design.png
diff --git a/llvm/include/llvm/Telemetry/Telemetry.h b/llvm/include/llvm/Telemetry/Telemetry.h
index 8f26f6ba9ec26e..9febb08c9caed6 100644
--- a/llvm/include/llvm/Telemetry/Telemetry.h
+++ b/llvm/include/llvm/Telemetry/Telemetry.h
@@ -36,8 +36,13 @@
namespace llvm {
namespace telemetry {
-// Configuration for the Telemeter class.
-// This struct can be extended as needed.
+/// Configuration for the Telemeter class.
+/// This stores configurations from both users and vendors and is passed
+/// to the Telemeter upon contruction. (Any changes to the config after
+/// the Telemeter's construction will not have effect on it).
+///
+/// This struct can be extended as needed to add additional configuration
+/// points specific to a vendor's implementation.
struct Config {
// If true, telemetry will be enabled.
bool EnableTelemetry;
@@ -51,11 +56,11 @@ struct Config {
std::vector<std::string> AdditionalDestinations;
};
-// Defines a convenient type for timestamp of various events.
-// This is used by the EventStats below.
+/// 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.
+/// Various time (and possibly memory) statistics of an event.
struct EventStats {
// REQUIRED: Start time of an event
SteadyTimePoint Start;
@@ -69,25 +74,34 @@ struct EventStats {
: Start(Start), End(End) {}
};
+/// Describes the exit signal of an event.
+/// This is used by TelemetryInfo below.
struct ExitDescription {
int ExitCode;
std::string Description;
};
-// For isa, dyn_cast, etc operations on TelemetryInfo.
+/// For isa, dyn_cast, etc operations on TelemetryInfo.
typedef unsigned KindType;
-// The EntryKind is defined as a struct because it is expectend to be
-// extended by subclasses which may have additional TelemetryInfo
-// types defined.
+/// This struct is used by TelemetryInfo to support isa<>, dyn_cast<>
+/// operations.
+/// It is defined as a struct(rather than an enum) because it is
+/// expectend to be extended by subclasses which may have
+/// additional TelemetryInfo types defined to describe different events.
struct EntryKind {
static const KindType Base = 0;
};
-// TelemetryInfo is the data courier, used to forward data from
-// the tool being monitored to the Telemery framework.
-//
-// This base class contains only the basic set of telemetry data.
-// Downstream implementations can add more fields as needed.
+/// TelemetryInfo is the data courier, used to move instrumented data
+/// the tool being monitored to the Telemery framework.
+///
+/// This base class contains only the basic set of telemetry data.
+/// Downstream implementations can add more fields as needed to describe
+/// additional events.
+///
+/// For eg., The LLDB debugger can define a DebugCommandInfo subclass
+/// which has additional fields about the debug-command being instrumented,
+/// such as `CommandArguments` or `CommandName`.
struct TelemetryInfo {
// This represents a unique-id, conventionally corresponding to
// a tools' session - ie., every time the tool starts until it exits.
@@ -123,12 +137,12 @@ struct TelemetryInfo {
}
};
-// This class presents a data sink to which the Telemetry framework
-// sends data.
-//
-// Its implementation is transparent to the framework.
-// It is up to the vendor to decide which pieces of data to forward
-// and where to forward them.
+/// This class presents a data sink to which the Telemetry framework
+/// sends data.
+///
+/// Its implementation is transparent to the framework.
+/// It is up to the vendor to decide which pieces of data to forward
+/// and where to forward them.
class Destination {
public:
virtual ~Destination() = default;
@@ -136,10 +150,10 @@ class Destination {
virtual std::string name() const = 0;
};
-// This class is the main interaction point between any LLVM tool
-// and this framework.
-// It is responsible for collecting telemetry data from the tool being
-// monitored.
+/// This class is the main interaction point between any LLVM tool
+/// and this framework.
+/// It is responsible for collecting telemetry data from the tool being
+/// monitored and transmitting the data elsewhere.
class Telemeter {
public:
// Invoked upon tool startup
diff --git a/llvm/unittests/Telemetry/TelemetryTest.cpp b/llvm/unittests/Telemetry/TelemetryTest.cpp
index fcd1f4c750d313..82bdaaa8876c1d 100644
--- a/llvm/unittests/Telemetry/TelemetryTest.cpp
+++ b/llvm/unittests/Telemetry/TelemetryTest.cpp
@@ -19,7 +19,6 @@
#include "llvm/Support/SourceMgr.h"
#include "llvm/Support/raw_ostream.h"
#include "gtest/gtest.h"
-
#include <chrono>
#include <ctime>
#include <vector>
>From 4155adde942153cffbbeab94ff4009f2f822ac74 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Wed, 4 Sep 2024 15:14:35 -0400
Subject: [PATCH 18/61] fix doc toc
---
llvm/docs/UserGuides.rst | 1 +
1 file changed, 1 insertion(+)
diff --git a/llvm/docs/UserGuides.rst b/llvm/docs/UserGuides.rst
index 171da2053e7311..feb07bcf0a7ef6 100644
--- a/llvm/docs/UserGuides.rst
+++ b/llvm/docs/UserGuides.rst
@@ -72,6 +72,7 @@ intermediate LLVM representation.
SupportLibrary
TableGen/index
TableGenFundamentals
+ Telemetry
Vectorizers
WritingAnLLVMPass
WritingAnLLVMNewPMPass
>From 11ead4adede52690936936f3ed2784fce4bedaaa Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Thu, 5 Sep 2024 13:53:00 -0400
Subject: [PATCH 19/61] group all the testing-params into a Context struct
---
llvm/unittests/Telemetry/TelemetryTest.cpp | 178 ++++++++++++---------
1 file changed, 104 insertions(+), 74 deletions(-)
diff --git a/llvm/unittests/Telemetry/TelemetryTest.cpp b/llvm/unittests/Telemetry/TelemetryTest.cpp
index 82bdaaa8876c1d..d46543beec87f7 100644
--- a/llvm/unittests/Telemetry/TelemetryTest.cpp
+++ b/llvm/unittests/Telemetry/TelemetryTest.cpp
@@ -25,15 +25,32 @@
// Testing parameters.
// These are set by each test to force certain outcomes.
-// Since the tests may be run in parellel, these should probably
-// be thread_local.
-static thread_local bool HasExitError = false;
-static thread_local std::string ExitMsg = "";
-static thread_local bool HasVendorConfig = false;
-static thread_local bool SanitizeData = false;
-static thread_local std::string Buffer = "";
-static thread_local std::vector<llvm::json::Object> EmittedJsons;
-static thread_local std::string ExpectedUuid = "";
+// 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 = "";
+
+ // Controllilng 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 {
@@ -289,7 +306,7 @@ class JsonStreamDestination : public Destination {
// send the data to some internal storage.
// For testing purposes, we just queue up the entries to
// the vector for validation.
- EmittedJsons.push_back(std::move(O));
+ CurrentContext->EmittedJsons.push_back(std::move(O));
return Error::success();
}
bool ShouldSanitize;
@@ -305,19 +322,19 @@ class TestTelemeter : public Telemeter {
<< "\n";
if (!config->EnableTelemetry)
return nullptr;
- ExpectedUuid = nextUuid();
+ CurrentContext->ExpectedUuid = nextUuid();
std::unique_ptr<TestTelemeter> Telemeter =
- std::make_unique<TestTelemeter>(ExpectedUuid);
+ std::make_unique<TestTelemeter>(CurrentContext->ExpectedUuid);
// Set up Destination based on the given config.
for (const std::string &Dest : config->AdditionalDestinations) {
// The destination(s) are ALSO defined by vendor, so it should understand
// what the name of each destination signifies.
if (Dest == JSON_DEST) {
- Telemeter->addDestination(
- new vendor_code::JsonStreamDestination(SanitizeData));
+ Telemeter->addDestination(new vendor_code::JsonStreamDestination(
+ CurrentContext->SanitizeData));
} else if (Dest == STRING_DEST) {
- Telemeter->addDestination(
- new vendor_code::StringDestination(SanitizeData, Buffer));
+ Telemeter->addDestination(new vendor_code::StringDestination(
+ CurrentContext->SanitizeData, CurrentContext->Buffer));
} else {
llvm_unreachable(
llvm::Twine("unknown destination: ", Dest).str().c_str());
@@ -431,7 +448,7 @@ std::shared_ptr<llvm::telemetry::Config> GetTelemetryConfig() {
// #endif
//
// But for unit testing, we use the testing params defined at the top.
- if (HasVendorConfig) {
+ if (CurrentContext->HasVendorConfig) {
llvm::telemetry::vendor_code::ApplyVendorSpecificConfigs(Config.get());
}
return Config;
@@ -468,8 +485,8 @@ void AtToolExit(std::string ToolName, vendor_code::TestTelemeter *T) {
T->makeDefaultTelemetryInfo<vendor_code::ExitEvent>();
Entry.Stats = {ExitTime, ExitCompleteTime};
- if (HasExitError) {
- Entry.ExitDesc = {1, ExitMsg};
+ if (CurrentContext->HasExitError) {
+ Entry.ExitDesc = {1, CurrentContext->ExitMsg};
}
T->logExit(ToolName, &Entry);
}
@@ -491,7 +508,11 @@ static std::string ValueToString(const json::Value *V) {
// Without vendor's implementation, telemetry is not enabled by default.
TEST(TelemetryTest, TelemetryDefault) {
- HasVendorConfig = false;
+ // Preset some test params.
+ TestContext Context;
+ Context.HasVendorConfig = false;
+ CurrentContext = &Context;
+
std::shared_ptr<llvm::telemetry::Config> Config = GetTelemetryConfig();
auto Tool = vendor_code::TestTelemeter::createInstance(Config.get());
@@ -502,10 +523,12 @@ TEST(TelemetryTest, TelemetryEnabled) {
const std::string ToolName = "TelemetryTest";
// Preset some test params.
- HasVendorConfig = true;
- SanitizeData = false;
- Buffer.clear();
- EmittedJsons.clear();
+ TestContext Context;
+ Context.HasVendorConfig = true;
+ Context.SanitizeData = false;
+ Context.Buffer.clear();
+ Context.EmittedJsons.clear();
+ CurrentContext = &Context;
std::shared_ptr<llvm::telemetry::Config> Config = GetTelemetryConfig();
@@ -520,56 +543,59 @@ TEST(TelemetryTest, TelemetryEnabled) {
AtToolExit(ToolName, Tool.get());
// Check that the Tool uses the expected UUID.
- EXPECT_STREQ(Tool->getUuid().c_str(), ExpectedUuid.c_str());
+ EXPECT_STREQ(Tool->getUuid().c_str(), CurrentContext->ExpectedUuid.c_str());
// Check that the StringDestination emitted properly
{
std::string ExpectedBuffer =
- ("SessionId:" + llvm::Twine(ExpectedUuid) + "\n" +
- "MagicStartupMsg:One_" + llvm::Twine(ToolName) + "\n" + "SessionId:" +
- llvm::Twine(ExpectedUuid) + "\n" + "MSG_0:Two\n" + "MSG_1:Deux\n" +
- "MSG_2:Zwei\n" + "SessionId:" + llvm::Twine(ExpectedUuid) + "\n" +
+ ("SessionId:" + llvm::Twine(CurrentContext->ExpectedUuid) + "\n" +
+ "MagicStartupMsg:One_" + llvm::Twine(ToolName) + "\n" +
+ "SessionId:" + llvm::Twine(CurrentContext->ExpectedUuid) + "\n" +
+ "MSG_0:Two\n" + "MSG_1:Deux\n" + "MSG_2:Zwei\n" +
+ "SessionId:" + llvm::Twine(CurrentContext->ExpectedUuid) + "\n" +
"MagicExitMsg:Three_" + llvm::Twine(ToolName) + "\n")
.str();
- EXPECT_STREQ(ExpectedBuffer.c_str(), Buffer.c_str());
+ EXPECT_STREQ(ExpectedBuffer.c_str(), CurrentContext->Buffer.c_str());
}
// Check that the JsonDestination emitted properly
{
// There should be 3 events emitted by the Telemeter (start, midpoint, exit)
- EXPECT_EQ(3, EmittedJsons.size());
+ EXPECT_EQ(3, CurrentContext->EmittedJsons.size());
- const json::Value *StartupEntry = EmittedJsons[0].get("Startup");
+ const json::Value *StartupEntry =
+ CurrentContext->EmittedJsons[0].get("Startup");
ASSERT_NE(StartupEntry, nullptr);
- EXPECT_STREQ(("[[\"SessionId\",\"" + llvm::Twine(ExpectedUuid) +
- "\"],[\"MagicStartupMsg\",\"One_" + llvm::Twine(ToolName) +
- "\"]]")
- .str()
- .c_str(),
- ValueToString(StartupEntry).c_str());
+ EXPECT_STREQ(
+ ("[[\"SessionId\",\"" + llvm::Twine(CurrentContext->ExpectedUuid) +
+ "\"],[\"MagicStartupMsg\",\"One_" + llvm::Twine(ToolName) + "\"]]")
+ .str()
+ .c_str(),
+ ValueToString(StartupEntry).c_str());
- const json::Value *MidpointEntry = EmittedJsons[1].get("Midpoint");
+ const json::Value *MidpointEntry =
+ CurrentContext->EmittedJsons[1].get("Midpoint");
ASSERT_NE(MidpointEntry, nullptr);
// TODO: This is a bit flaky in that the json string printer sort the
// entries (for now), so the "UUID" field is put at the end of the array
// even though it was emitted first.
EXPECT_STREQ(("{\"MSG_0\":\"Two\",\"MSG_1\":\"Deux\",\"MSG_2\":\"Zwei\","
"\"SessionId\":\"" +
- llvm::Twine(ExpectedUuid) + "\"}")
+ llvm::Twine(CurrentContext->ExpectedUuid) + "\"}")
.str()
.c_str(),
ValueToString(MidpointEntry).c_str());
- const json::Value *ExitEntry = EmittedJsons[2].get("Exit");
+ const json::Value *ExitEntry = CurrentContext->EmittedJsons[2].get("Exit");
ASSERT_NE(ExitEntry, nullptr);
- EXPECT_STREQ(("[[\"SessionId\",\"" + llvm::Twine(ExpectedUuid) +
- "\"],[\"MagicExitMsg\",\"Three_" + llvm::Twine(ToolName) +
- "\"]]")
- .str()
- .c_str(),
- ValueToString(ExitEntry).c_str());
+ EXPECT_STREQ(
+ ("[[\"SessionId\",\"" + llvm::Twine(CurrentContext->ExpectedUuid) +
+ "\"],[\"MagicExitMsg\",\"Three_" + llvm::Twine(ToolName) + "\"]]")
+ .str()
+ .c_str(),
+ ValueToString(ExitEntry).c_str());
}
}
@@ -579,10 +605,12 @@ TEST(TelemetryTest, TelemetryEnabledSanitizeData) {
const std::string ToolName = "TelemetryTest_SanitizedData";
// Preset some test params.
- HasVendorConfig = true;
- SanitizeData = true;
- Buffer.clear();
- EmittedJsons.clear();
+ TestContext Context;
+ Context.HasVendorConfig = true;
+ Context.SanitizeData = true;
+ Context.Buffer.clear();
+ Context.EmittedJsons.clear();
+ CurrentContext = &Context;
std::shared_ptr<llvm::telemetry::Config> Config = GetTelemetryConfig();
@@ -599,51 +627,53 @@ TEST(TelemetryTest, TelemetryEnabledSanitizeData) {
// Check that the StringDestination emitted properly
{
// The StringDestination should have removed the odd-positioned msgs.
-
std::string ExpectedBuffer =
- ("SessionId:" + llvm::Twine(ExpectedUuid) + "\n" +
+ ("SessionId:" + llvm::Twine(CurrentContext->ExpectedUuid) + "\n" +
"MagicStartupMsg:One_" + llvm::Twine(ToolName) + "\n" +
- "SessionId:" + llvm::Twine(ExpectedUuid) + "\n" + "MSG_0:Two\n" +
- "MSG_1:\n" + // <<< was sanitized away.
- "MSG_2:Zwei\n" + "SessionId:" + llvm::Twine(ExpectedUuid) + "\n" +
+ "SessionId:" + llvm::Twine(CurrentContext->ExpectedUuid) + "\n" +
+ "MSG_0:Two\n" + "MSG_1:\n" + // <<< was sanitized away.
+ "MSG_2:Zwei\n" +
+ "SessionId:" + llvm::Twine(CurrentContext->ExpectedUuid) + "\n" +
"MagicExitMsg:Three_" + llvm::Twine(ToolName) + "\n")
.str();
- EXPECT_STREQ(ExpectedBuffer.c_str(), Buffer.c_str());
+ EXPECT_STREQ(ExpectedBuffer.c_str(), CurrentContext->Buffer.c_str());
}
// Check that the JsonDestination emitted properly
{
// There should be 3 events emitted by the Telemeter (start, midpoint, exit)
- EXPECT_EQ(3, EmittedJsons.size());
+ EXPECT_EQ(3, CurrentContext->EmittedJsons.size());
- const json::Value *StartupEntry = EmittedJsons[0].get("Startup");
+ const json::Value *StartupEntry =
+ CurrentContext->EmittedJsons[0].get("Startup");
ASSERT_NE(StartupEntry, nullptr);
- EXPECT_STREQ(("[[\"SessionId\",\"" + llvm::Twine(ExpectedUuid) +
- "\"],[\"MagicStartupMsg\",\"One_" + llvm::Twine(ToolName) +
- "\"]]")
- .str()
- .c_str(),
- ValueToString(StartupEntry).c_str());
+ EXPECT_STREQ(
+ ("[[\"SessionId\",\"" + llvm::Twine(CurrentContext->ExpectedUuid) +
+ "\"],[\"MagicStartupMsg\",\"One_" + llvm::Twine(ToolName) + "\"]]")
+ .str()
+ .c_str(),
+ ValueToString(StartupEntry).c_str());
- const json::Value *MidpointEntry = EmittedJsons[1].get("Midpoint");
+ const json::Value *MidpointEntry =
+ CurrentContext->EmittedJsons[1].get("Midpoint");
ASSERT_NE(MidpointEntry, nullptr);
// The JsonDestination should have removed the even-positioned msgs.
EXPECT_STREQ(
("{\"MSG_0\":\"\",\"MSG_1\":\"Deux\",\"MSG_2\":\"\",\"SessionId\":\"" +
- llvm::Twine(ExpectedUuid) + "\"}")
+ llvm::Twine(CurrentContext->ExpectedUuid) + "\"}")
.str()
.c_str(),
ValueToString(MidpointEntry).c_str());
- const json::Value *ExitEntry = EmittedJsons[2].get("Exit");
+ const json::Value *ExitEntry = CurrentContext->EmittedJsons[2].get("Exit");
ASSERT_NE(ExitEntry, nullptr);
- EXPECT_STREQ(("[[\"SessionId\",\"" + llvm::Twine(ExpectedUuid) +
- "\"],[\"MagicExitMsg\",\"Three_" + llvm::Twine(ToolName) +
- "\"]]")
- .str()
- .c_str(),
- ValueToString(ExitEntry).c_str());
+ EXPECT_STREQ(
+ ("[[\"SessionId\",\"" + llvm::Twine(CurrentContext->ExpectedUuid) +
+ "\"],[\"MagicExitMsg\",\"Three_" + llvm::Twine(ToolName) + "\"]]")
+ .str()
+ .c_str(),
+ ValueToString(ExitEntry).c_str());
}
}
>From 48228ee076d15fe4bcab5e0843a5adbd5c7636f2 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Thu, 5 Sep 2024 14:14:56 -0400
Subject: [PATCH 20/61] fix conversion warnings
---
llvm/unittests/Telemetry/TelemetryTest.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/llvm/unittests/Telemetry/TelemetryTest.cpp b/llvm/unittests/Telemetry/TelemetryTest.cpp
index d46543beec87f7..3497cd0b139c0d 100644
--- a/llvm/unittests/Telemetry/TelemetryTest.cpp
+++ b/llvm/unittests/Telemetry/TelemetryTest.cpp
@@ -563,7 +563,7 @@ TEST(TelemetryTest, TelemetryEnabled) {
{
// There should be 3 events emitted by the Telemeter (start, midpoint, exit)
- EXPECT_EQ(3, CurrentContext->EmittedJsons.size());
+ EXPECT_EQ(static_cast<size_t>(3), CurrentContext->EmittedJsons.size());
const json::Value *StartupEntry =
CurrentContext->EmittedJsons[0].get("Startup");
@@ -643,7 +643,7 @@ TEST(TelemetryTest, TelemetryEnabledSanitizeData) {
{
// There should be 3 events emitted by the Telemeter (start, midpoint, exit)
- EXPECT_EQ(3, CurrentContext->EmittedJsons.size());
+ EXPECT_EQ(static_cast<size_t>(3), CurrentContext->EmittedJsons.size());
const json::Value *StartupEntry =
CurrentContext->EmittedJsons[0].get("Startup");
>From 990e1ca8520349cf154351ae9a6ceb6f8bac396b Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Thu, 5 Sep 2024 15:33:44 -0400
Subject: [PATCH 21/61] finish up todos
---
llvm/docs/Telemetry.rst | 126 ++++++++++++++++++++-
llvm/include/llvm/Telemetry/Telemetry.h | 2 +-
llvm/unittests/Telemetry/TelemetryTest.cpp | 22 ++--
3 files changed, 136 insertions(+), 14 deletions(-)
diff --git a/llvm/docs/Telemetry.rst b/llvm/docs/Telemetry.rst
index 4dc305362cfed8..af976058e66bc3 100644
--- a/llvm/docs/Telemetry.rst
+++ b/llvm/docs/Telemetry.rst
@@ -86,4 +86,128 @@ The framework is consisted of four important classes:
How to implement and interact with the API
------------------------------------------
-// TODO: walk through a simple usage here.
+To use Telemetry in your tool, you need to provide a concrete implementation of the `Telemeter` class and `Destination`.
+
+1) Define a custom `Telemeter` and `Destination`
+
+.. code-block:: c++
+ // This destiantion just prints the given entry to a stdout.
+ // In "real life", this would be where you forward the data to your
+ // custom data storage.
+ class MyStdoutDestination : public llvm::telemetry::Destiantion {
+ public:
+ Error emitEntry(const TelemetryInfo* Entry) override {
+ return sendToBlackBox(Entry);
+
+ }
+
+ private:
+ Error sendToBlackBox(const TelemetryInfo* Entry) {
+ // This could send the data anywhere.
+ // But we're simply sending it to stdout for the example.
+ llvm::outs() << entryToString(Entry) << "\n";
+ return llvm::success();
+ }
+
+ std::string entryToString(const TelemetryInfo* Entry) {
+ // make a string-representation of the given entry.
+ }
+ };
+
+ // This defines a custom TelemetryInfo that has an addition Msg field.
+ struct MyTelemetryInfo : public llvm::telemetry::TelemetryInfo {
+ std::string Msg;
+
+ json::Object serializeToJson() const {
+ json::Object Ret = TelemeteryInfo::serializeToJson();
+ Ret.emplace_back("MyMsg", Msg);
+ return std::move(Ret);
+ }
+
+ // TODO: implement getKind() and classof() to support dyn_cast operations.
+ };
+
+ class MyTelemeter : public llvm::telemery::Telemeter {
+ public:
+ static std::unique_ptr<MyTelemeter> createInstatnce(llvm::telemetry::Config* config) {
+ // If Telemetry is not enabled, then just return null;
+ if (!config->EnableTelemetry) return nullptr;
+
+ std::make_unique<MyTelemeter>();
+ }
+ MyTelemeter() = default;
+
+ void logStartup(llvm::StringRef ToolName, TelemetryInfo* Entry) override {
+ if (MyTelemetryInfo* M = dyn_cast<MyTelemetryInfo>(Entry)) {
+ M->Msg = "Starting up tool with name: " + ToolName;
+ emitToAllDestinations(M);
+ } else {
+ emitToAllDestinations(Entry);
+ }
+ }
+
+ void logExit(llvm::StringRef ToolName, TelemetryInfo* Entry) override {
+ if (MyTelemetryInfo* M = dyn_cast<MyTelemetryInfo>(Entry)) {
+ M->Msg = "Exitting tool with name: " + ToolName;
+ emitToAllDestinations(M);
+ } else {
+ emitToAllDestinations(Entry);
+ }
+ }
+
+ void addDestination(Destination* dest) override {
+ destinations.push_back(dest);
+ }
+
+ // You can also define additional instrumentation points.)
+ void logAdditionalPoint(TelemetryInfo* Entry) {
+ // .... code here
+ }
+ private:
+ void emitToAllDestinations(const TelemetryInfo* Entry) {
+ // Note: could do this in paralle, if needed.
+ for (Destination* Dest : Destinations)
+ Dest->emitEntry(Entry);
+ }
+ std::vector<Destination> Destinations;
+ };
+
+2) Use the library in your tool.
+
+Logging the tool init-process:
+
+.. code-block:: c++
+
+ // At tool's init code
+ auto StartTime = std::chrono::time_point<std::chrono::steady_clock>::now();
+ llvm::telemetry::Config MyConfig = makeConfig(); // build up the appropriate Config struct here.
+ auto Telemeter = MyTelemeter::createInstance(&MyConfig);
+ std::string CurrentSessionId = ...; // Make some unique ID corresponding to the current session here.
+
+ // 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.SessionId = CurrentSessionId ; // Assign some unique ID here.
+ Entry.Stats = {StartTime, EndTime};
+ Telemeter->logStartup("MyTool", &Entry);
+
+Similar code can be used for logging the tool's exit.
+
+Additionall, at any other point in the tool's lifetime, it can also log telemetry:
+
+.. code-block:: c++
+
+ // At some execution point:
+ auto StartTime = std::chrono::time_point<std::chrono::steady_clock>::now();
+
+ // ... other events happening here
+
+ auto EndTime = std::chrono::time_point<std::chrono::steady_clock>::now();
+ MyTelemetryInfo Entry;
+ Entry.SessionId = CurrentSessionId ; // Assign some unique ID here.
+ Entry.Stats = {StartTime, EndTime};
+ Telemeter->logAdditionalPoint(&Entry);
diff --git a/llvm/include/llvm/Telemetry/Telemetry.h b/llvm/include/llvm/Telemetry/Telemetry.h
index 9febb08c9caed6..f682e1cdf5cafa 100644
--- a/llvm/include/llvm/Telemetry/Telemetry.h
+++ b/llvm/include/llvm/Telemetry/Telemetry.h
@@ -93,7 +93,7 @@ struct EntryKind {
};
/// TelemetryInfo is the data courier, used to move instrumented data
-/// the tool being monitored to the Telemery framework.
+/// from the tool being monitored to the Telemery framework.
///
/// This base class contains only the basic set of telemetry data.
/// Downstream implementations can add more fields as needed to describe
diff --git a/llvm/unittests/Telemetry/TelemetryTest.cpp b/llvm/unittests/Telemetry/TelemetryTest.cpp
index 3497cd0b139c0d..173976bdb1650f 100644
--- a/llvm/unittests/Telemetry/TelemetryTest.cpp
+++ b/llvm/unittests/Telemetry/TelemetryTest.cpp
@@ -318,8 +318,6 @@ class TestTelemeter : public Telemeter {
TestTelemeter(std::string SessionId) : Uuid(SessionId), Counter(0) {}
static std::unique_ptr<TestTelemeter> createInstance(Config *config) {
- llvm::errs() << "============================== createInstance is called"
- << "\n";
if (!config->EnableTelemetry)
return nullptr;
CurrentContext->ExpectedUuid = nextUuid();
@@ -348,7 +346,7 @@ class TestTelemeter : public Telemeter {
// The vendor can add additional stuff to the entry before logging.
if (auto *S = dyn_cast<StartupEvent>(Entry)) {
- S->MagicStartupMsg = llvm::Twine("One_", ToolPath).str();
+ S->MagicStartupMsg = llvm::Twine("Startup_", ToolPath).str();
}
emitToDestinations(Entry);
}
@@ -364,7 +362,7 @@ class TestTelemeter : public Telemeter {
// The vendor can add additional stuff to the entry before logging.
if (auto *E = dyn_cast<ExitEvent>(Entry)) {
- E->MagicExitMsg = llvm::Twine("Three_", ToolPath).str();
+ E->MagicExitMsg = llvm::Twine("Exit_", ToolPath).str();
}
emitToDestinations(Entry);
@@ -549,11 +547,11 @@ TEST(TelemetryTest, TelemetryEnabled) {
{
std::string ExpectedBuffer =
("SessionId:" + llvm::Twine(CurrentContext->ExpectedUuid) + "\n" +
- "MagicStartupMsg:One_" + llvm::Twine(ToolName) + "\n" +
+ "MagicStartupMsg:Startup_" + llvm::Twine(ToolName) + "\n" +
"SessionId:" + llvm::Twine(CurrentContext->ExpectedUuid) + "\n" +
"MSG_0:Two\n" + "MSG_1:Deux\n" + "MSG_2:Zwei\n" +
"SessionId:" + llvm::Twine(CurrentContext->ExpectedUuid) + "\n" +
- "MagicExitMsg:Three_" + llvm::Twine(ToolName) + "\n")
+ "MagicExitMsg:Exit_" + llvm::Twine(ToolName) + "\n")
.str();
EXPECT_STREQ(ExpectedBuffer.c_str(), CurrentContext->Buffer.c_str());
@@ -570,7 +568,7 @@ TEST(TelemetryTest, TelemetryEnabled) {
ASSERT_NE(StartupEntry, nullptr);
EXPECT_STREQ(
("[[\"SessionId\",\"" + llvm::Twine(CurrentContext->ExpectedUuid) +
- "\"],[\"MagicStartupMsg\",\"One_" + llvm::Twine(ToolName) + "\"]]")
+ "\"],[\"MagicStartupMsg\",\"Startup_" + llvm::Twine(ToolName) + "\"]]")
.str()
.c_str(),
ValueToString(StartupEntry).c_str());
@@ -592,7 +590,7 @@ TEST(TelemetryTest, TelemetryEnabled) {
ASSERT_NE(ExitEntry, nullptr);
EXPECT_STREQ(
("[[\"SessionId\",\"" + llvm::Twine(CurrentContext->ExpectedUuid) +
- "\"],[\"MagicExitMsg\",\"Three_" + llvm::Twine(ToolName) + "\"]]")
+ "\"],[\"MagicExitMsg\",\"Exit_" + llvm::Twine(ToolName) + "\"]]")
.str()
.c_str(),
ValueToString(ExitEntry).c_str());
@@ -629,12 +627,12 @@ TEST(TelemetryTest, TelemetryEnabledSanitizeData) {
// The StringDestination should have removed the odd-positioned msgs.
std::string ExpectedBuffer =
("SessionId:" + llvm::Twine(CurrentContext->ExpectedUuid) + "\n" +
- "MagicStartupMsg:One_" + llvm::Twine(ToolName) + "\n" +
+ "MagicStartupMsg:Startup_" + llvm::Twine(ToolName) + "\n" +
"SessionId:" + llvm::Twine(CurrentContext->ExpectedUuid) + "\n" +
"MSG_0:Two\n" + "MSG_1:\n" + // <<< was sanitized away.
"MSG_2:Zwei\n" +
"SessionId:" + llvm::Twine(CurrentContext->ExpectedUuid) + "\n" +
- "MagicExitMsg:Three_" + llvm::Twine(ToolName) + "\n")
+ "MagicExitMsg:Exit_" + llvm::Twine(ToolName) + "\n")
.str();
EXPECT_STREQ(ExpectedBuffer.c_str(), CurrentContext->Buffer.c_str());
}
@@ -650,7 +648,7 @@ TEST(TelemetryTest, TelemetryEnabledSanitizeData) {
ASSERT_NE(StartupEntry, nullptr);
EXPECT_STREQ(
("[[\"SessionId\",\"" + llvm::Twine(CurrentContext->ExpectedUuid) +
- "\"],[\"MagicStartupMsg\",\"One_" + llvm::Twine(ToolName) + "\"]]")
+ "\"],[\"MagicStartupMsg\",\"Startup_" + llvm::Twine(ToolName) + "\"]]")
.str()
.c_str(),
ValueToString(StartupEntry).c_str());
@@ -670,7 +668,7 @@ TEST(TelemetryTest, TelemetryEnabledSanitizeData) {
ASSERT_NE(ExitEntry, nullptr);
EXPECT_STREQ(
("[[\"SessionId\",\"" + llvm::Twine(CurrentContext->ExpectedUuid) +
- "\"],[\"MagicExitMsg\",\"Three_" + llvm::Twine(ToolName) + "\"]]")
+ "\"],[\"MagicExitMsg\",\"Exit_" + llvm::Twine(ToolName) + "\"]]")
.str()
.c_str(),
ValueToString(ExitEntry).c_str());
>From 479cd134064d92144e3e22e5179fb6ff60c839d7 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Fri, 20 Sep 2024 12:35:45 -0400
Subject: [PATCH 22/61] reformat docs
---
llvm/docs/Telemetry.rst | 1 +
1 file changed, 1 insertion(+)
diff --git a/llvm/docs/Telemetry.rst b/llvm/docs/Telemetry.rst
index af976058e66bc3..e6459faf7a50b7 100644
--- a/llvm/docs/Telemetry.rst
+++ b/llvm/docs/Telemetry.rst
@@ -91,6 +91,7 @@ To use Telemetry in your tool, you need to provide a concrete implementation of
1) Define a custom `Telemeter` and `Destination`
.. code-block:: c++
+
// This destiantion just prints the given entry to a stdout.
// In "real life", this would be where you forward the data to your
// custom data storage.
>From 3a17dbb36263dd01b09de194e374fab101aea314 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Tue, 24 Sep 2024 10:44:57 -0400
Subject: [PATCH 23/61] Update llvm/docs/Telemetry.rst
Co-authored-by: Alina Sbirlea <alina.g.simion at gmail.com>
---
llvm/docs/Telemetry.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/docs/Telemetry.rst b/llvm/docs/Telemetry.rst
index e6459faf7a50b7..0fcdc96f435913 100644
--- a/llvm/docs/Telemetry.rst
+++ b/llvm/docs/Telemetry.rst
@@ -68,7 +68,7 @@ High-level design
Key components
--------------
-The framework is consisted of four important classes:
+The framework consists of four important classes:
* `llvm::telemetry::Telemeter`: The class responsible for collecting and
transmitting telemetry data. This is the main point of interaction between the
>From a6a6c7b5858a833ff93ebd2bce879bb3fa678314 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Tue, 24 Sep 2024 10:45:13 -0400
Subject: [PATCH 24/61] Update llvm/docs/Telemetry.rst
Co-authored-by: Alina Sbirlea <alina.g.simion at gmail.com>
---
llvm/docs/Telemetry.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/docs/Telemetry.rst b/llvm/docs/Telemetry.rst
index 0fcdc96f435913..eb40039504eea6 100644
--- a/llvm/docs/Telemetry.rst
+++ b/llvm/docs/Telemetry.rst
@@ -27,7 +27,7 @@ Characteristics
* Vendors: Toolchain vendors can also provide custom implementation of the
library, which could either override or extend the given tool's upstream
implementation, to best fit their organization's usage and privacy models.
- * End users of such tool can also configure Telemetry(as allowed by their
+ * End users of such tool can also configure Telemetry (as allowed by their
vendor).
>From f30dec6fffb4068d9780de0e5b3f0236e9654648 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Tue, 24 Sep 2024 10:45:24 -0400
Subject: [PATCH 25/61] Update llvm/docs/Telemetry.rst
Co-authored-by: Alina Sbirlea <alina.g.simion at gmail.com>
---
llvm/docs/Telemetry.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/docs/Telemetry.rst b/llvm/docs/Telemetry.rst
index eb40039504eea6..d32e035166a796 100644
--- a/llvm/docs/Telemetry.rst
+++ b/llvm/docs/Telemetry.rst
@@ -92,7 +92,7 @@ To use Telemetry in your tool, you need to provide a concrete implementation of
.. code-block:: c++
- // This destiantion just prints the given entry to a stdout.
+ // This destination just prints the given entry to a stdout.
// In "real life", this would be where you forward the data to your
// custom data storage.
class MyStdoutDestination : public llvm::telemetry::Destiantion {
>From fd3da20966da123504faab66374424fa08220b17 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Tue, 24 Sep 2024 10:45:37 -0400
Subject: [PATCH 26/61] Update llvm/include/llvm/Telemetry/Telemetry.h
Co-authored-by: Alina Sbirlea <alina.g.simion at gmail.com>
---
llvm/include/llvm/Telemetry/Telemetry.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/include/llvm/Telemetry/Telemetry.h b/llvm/include/llvm/Telemetry/Telemetry.h
index f682e1cdf5cafa..f851a4687a0eb9 100644
--- a/llvm/include/llvm/Telemetry/Telemetry.h
+++ b/llvm/include/llvm/Telemetry/Telemetry.h
@@ -104,7 +104,7 @@ struct EntryKind {
/// such as `CommandArguments` or `CommandName`.
struct TelemetryInfo {
// This represents a unique-id, conventionally corresponding to
- // a tools' session - ie., every time the tool starts until it exits.
+ // a tool's session - i.e., every time the tool starts until it exits.
//
// Note: a tool could have mutliple sessions running at once, in which
// case, these shall be multiple sets of TelemetryInfo with multiple unique
>From 60616ef017da2d1f9eef7e1b57997e198e1e7a19 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Tue, 24 Sep 2024 10:45:54 -0400
Subject: [PATCH 27/61] Update llvm/unittests/Telemetry/TelemetryTest.cpp
Co-authored-by: Alina Sbirlea <alina.g.simion at gmail.com>
---
llvm/unittests/Telemetry/TelemetryTest.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/unittests/Telemetry/TelemetryTest.cpp b/llvm/unittests/Telemetry/TelemetryTest.cpp
index 173976bdb1650f..cca9285accef9c 100644
--- a/llvm/unittests/Telemetry/TelemetryTest.cpp
+++ b/llvm/unittests/Telemetry/TelemetryTest.cpp
@@ -33,7 +33,7 @@ struct TestContext {
bool HasExitError = false;
std::string ExitMsg = "";
- // Controllilng whether there is a vendor-provided config for
+ // Controlling whether there is a vendor-provided config for
// Telemetry.
bool HasVendorConfig = false;
>From 8c0ac5a3057b1d8aa139b2d36d41ce41fee8079d Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Tue, 24 Sep 2024 10:46:03 -0400
Subject: [PATCH 28/61] Update llvm/include/llvm/Telemetry/Telemetry.h
Co-authored-by: Alina Sbirlea <alina.g.simion at gmail.com>
---
llvm/include/llvm/Telemetry/Telemetry.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/include/llvm/Telemetry/Telemetry.h b/llvm/include/llvm/Telemetry/Telemetry.h
index f851a4687a0eb9..a0a6579aa59b73 100644
--- a/llvm/include/llvm/Telemetry/Telemetry.h
+++ b/llvm/include/llvm/Telemetry/Telemetry.h
@@ -106,7 +106,7 @@ struct TelemetryInfo {
// This represents a unique-id, conventionally corresponding to
// a tool's session - i.e., every time the tool starts until it exits.
//
- // Note: a tool could have mutliple sessions running at once, in which
+ // Note: a tool could have multiple sessions running at once, in which
// case, these shall be multiple sets of TelemetryInfo with multiple unique
// ids.
//
>From c8be8296d9ef2d0e6ebb91e9b0be716e46abd3c9 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Tue, 24 Sep 2024 10:46:22 -0400
Subject: [PATCH 29/61] Update llvm/include/llvm/Telemetry/Telemetry.h
Co-authored-by: Alina Sbirlea <alina.g.simion at gmail.com>
---
llvm/include/llvm/Telemetry/Telemetry.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/include/llvm/Telemetry/Telemetry.h b/llvm/include/llvm/Telemetry/Telemetry.h
index a0a6579aa59b73..d997e611e34792 100644
--- a/llvm/include/llvm/Telemetry/Telemetry.h
+++ b/llvm/include/llvm/Telemetry/Telemetry.h
@@ -85,7 +85,7 @@ struct ExitDescription {
typedef unsigned KindType;
/// This struct is used by TelemetryInfo to support isa<>, dyn_cast<>
/// operations.
-/// It is defined as a struct(rather than an enum) because it is
+/// It is defined as a struct (rather than an enum) because it is
/// expectend to be extended by subclasses which may have
/// additional TelemetryInfo types defined to describe different events.
struct EntryKind {
>From 1393c1f9990b7c634d2ac990f9ca69ba72440746 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Tue, 24 Sep 2024 10:46:31 -0400
Subject: [PATCH 30/61] Update llvm/docs/Telemetry.rst
Co-authored-by: Alina Sbirlea <alina.g.simion at gmail.com>
---
llvm/docs/Telemetry.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/docs/Telemetry.rst b/llvm/docs/Telemetry.rst
index d32e035166a796..3fc5fc7b24f298 100644
--- a/llvm/docs/Telemetry.rst
+++ b/llvm/docs/Telemetry.rst
@@ -39,7 +39,7 @@ Important notes
implement one.
The rationale for this is that, all the tools in llvm are very different in
- what they care about(what/where/when to instrument data). Hence, it might not
+ what they care about (what/where/when to instrument data). Hence, it might not
be practical to have a single implementation.
However, in the future, if we see enough common pattern, we can extract them
into a shared place. This is TBD - contributions are welcomed.
>From eb07577d3cb8b9347c93c958dd01f59721bc0bdd Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Tue, 24 Sep 2024 10:46:37 -0400
Subject: [PATCH 31/61] Update llvm/docs/Telemetry.rst
Co-authored-by: Alina Sbirlea <alina.g.simion at gmail.com>
---
llvm/docs/Telemetry.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/docs/Telemetry.rst b/llvm/docs/Telemetry.rst
index 3fc5fc7b24f298..e69eb9c3407e05 100644
--- a/llvm/docs/Telemetry.rst
+++ b/llvm/docs/Telemetry.rst
@@ -38,7 +38,7 @@ Important notes
We only provide the abstract API here. Any tool that wants telemetry will
implement one.
- The rationale for this is that, all the tools in llvm are very different in
+ The rationale for this is that, all the tools in LLVM are very different in
what they care about (what/where/when to instrument data). Hence, it might not
be practical to have a single implementation.
However, in the future, if we see enough common pattern, we can extract them
>From 0376abc07667b6c8c962d5288992086e40eec1ca Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Tue, 24 Sep 2024 10:55:24 -0400
Subject: [PATCH 32/61] fix comment
---
llvm/include/llvm/Telemetry/Telemetry.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/include/llvm/Telemetry/Telemetry.h b/llvm/include/llvm/Telemetry/Telemetry.h
index d997e611e34792..c74a74877131ab 100644
--- a/llvm/include/llvm/Telemetry/Telemetry.h
+++ b/llvm/include/llvm/Telemetry/Telemetry.h
@@ -99,7 +99,7 @@ struct EntryKind {
/// Downstream implementations can add more fields as needed to describe
/// additional events.
///
-/// For eg., The LLDB debugger can define a DebugCommandInfo subclass
+/// For example, The LLDB debugger can define a DebugCommandInfo subclass
/// which has additional fields about the debug-command being instrumented,
/// such as `CommandArguments` or `CommandName`.
struct TelemetryInfo {
>From e2f7c234089d0f4ba123b6908afdc7f36661db97 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Tue, 24 Sep 2024 13:54:57 -0400
Subject: [PATCH 33/61] Update llvm/include/llvm/Telemetry/Telemetry.h
Co-authored-by: Alina Sbirlea <alina.g.simion at gmail.com>
---
llvm/include/llvm/Telemetry/Telemetry.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/include/llvm/Telemetry/Telemetry.h b/llvm/include/llvm/Telemetry/Telemetry.h
index c74a74877131ab..f62dd8c06f7773 100644
--- a/llvm/include/llvm/Telemetry/Telemetry.h
+++ b/llvm/include/llvm/Telemetry/Telemetry.h
@@ -51,7 +51,7 @@ struct Config {
// telemetry data (Could be stdout, stderr, or some local paths, etc).
//
// These strings will be interpreted by the vendor's code.
- // So the users must pick the from their vendor's pre-defined
+ // So the users must pick the from their vendor's pre-defined
// set of Destinations.
std::vector<std::string> AdditionalDestinations;
};
>From 17dfac7331e8993a621c7fd97112ff009b0496cb Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Tue, 24 Sep 2024 14:18:51 -0400
Subject: [PATCH 34/61] Update llvm/docs/Telemetry.rst
Co-authored-by: Alina Sbirlea <alina.g.simion at gmail.com>
---
llvm/docs/Telemetry.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/docs/Telemetry.rst b/llvm/docs/Telemetry.rst
index e69eb9c3407e05..f81addac441d89 100644
--- a/llvm/docs/Telemetry.rst
+++ b/llvm/docs/Telemetry.rst
@@ -166,7 +166,7 @@ To use Telemetry in your tool, you need to provide a concrete implementation of
}
private:
void emitToAllDestinations(const TelemetryInfo* Entry) {
- // Note: could do this in paralle, if needed.
+ // Note: could do this in parallel, if needed.
for (Destination* Dest : Destinations)
Dest->emitEntry(Entry);
}
>From 1ea0906d8b7d24fef6ff77a80894a7982e1da998 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Tue, 24 Sep 2024 14:18:59 -0400
Subject: [PATCH 35/61] Update llvm/docs/Telemetry.rst
Co-authored-by: Alina Sbirlea <alina.g.simion at gmail.com>
---
llvm/docs/Telemetry.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/docs/Telemetry.rst b/llvm/docs/Telemetry.rst
index f81addac441d89..4a3df3d5b36bd2 100644
--- a/llvm/docs/Telemetry.rst
+++ b/llvm/docs/Telemetry.rst
@@ -95,7 +95,7 @@ To use Telemetry in your tool, you need to provide a concrete implementation of
// This destination just prints the given entry to a stdout.
// In "real life", this would be where you forward the data to your
// custom data storage.
- class MyStdoutDestination : public llvm::telemetry::Destiantion {
+ class MyStdoutDestination : public llvm::telemetry::Destination {
public:
Error emitEntry(const TelemetryInfo* Entry) override {
return sendToBlackBox(Entry);
>From 39fd0e7517cb55ca1736bca2549dceb6815055e9 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Tue, 24 Sep 2024 14:32:13 -0400
Subject: [PATCH 36/61] Update llvm/include/llvm/Telemetry/Telemetry.h
Co-authored-by: Alina Sbirlea <alina.g.simion at gmail.com>
---
llvm/include/llvm/Telemetry/Telemetry.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/include/llvm/Telemetry/Telemetry.h b/llvm/include/llvm/Telemetry/Telemetry.h
index f62dd8c06f7773..c1622aa4199ef2 100644
--- a/llvm/include/llvm/Telemetry/Telemetry.h
+++ b/llvm/include/llvm/Telemetry/Telemetry.h
@@ -38,7 +38,7 @@ namespace telemetry {
/// Configuration for the Telemeter class.
/// This stores configurations from both users and vendors and is passed
-/// to the Telemeter upon contruction. (Any changes to the config after
+/// to the Telemeter upon construction. (Any changes to the config after
/// the Telemeter's construction will not have effect on it).
///
/// This struct can be extended as needed to add additional configuration
>From 67b768800322a2835e0289c67d39f153159e0814 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Tue, 24 Sep 2024 15:07:09 -0400
Subject: [PATCH 37/61] Addressed review comments: remove most of the member
fields from TelemetryInfo, leaving only the ID. Also renamed method to avoid
'log'
---
llvm/include/llvm/Telemetry/Telemetry.h | 39 ++------------------
llvm/unittests/Telemetry/TelemetryTest.cpp | 41 ++++++++++++++++++----
2 files changed, 37 insertions(+), 43 deletions(-)
diff --git a/llvm/include/llvm/Telemetry/Telemetry.h b/llvm/include/llvm/Telemetry/Telemetry.h
index c1622aa4199ef2..6c75582148b56d 100644
--- a/llvm/include/llvm/Telemetry/Telemetry.h
+++ b/llvm/include/llvm/Telemetry/Telemetry.h
@@ -56,31 +56,6 @@ struct Config {
std::vector<std::string> AdditionalDestinations;
};
-/// 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) {}
-};
-
-/// Describes the exit signal of an event.
-/// This is used by TelemetryInfo below.
-struct ExitDescription {
- int ExitCode;
- std::string Description;
-};
-
/// For isa, dyn_cast, etc operations on TelemetryInfo.
typedef unsigned KindType;
/// This struct is used by TelemetryInfo to support isa<>, dyn_cast<>
@@ -113,16 +88,6 @@ struct TelemetryInfo {
// Different usages can assign different types of IDs to this field.
std::string SessionId;
- // Time/memory statistics of this event.
- EventStats Stats;
-
- std::optional<ExitDescription> ExitDesc;
-
- // Counting number of entries.
- // (For each set of entries with the same SessionId, this value should
- // be unique for each entry)
- size_t Counter;
-
TelemetryInfo() = default;
~TelemetryInfo() = default;
@@ -157,10 +122,10 @@ class Destination {
class Telemeter {
public:
// Invoked upon tool startup
- virtual void logStartup(llvm::StringRef ToolPath, TelemetryInfo *Entry) = 0;
+ virtual void atStartup(llvm::StringRef ToolPath, TelemetryInfo *Entry) = 0;
// Invoked upon tool exit.
- virtual void logExit(llvm::StringRef ToolPath, TelemetryInfo *Entry) = 0;
+ virtual void atExit(llvm::StringRef ToolPath, TelemetryInfo *Entry) = 0;
virtual void addDestination(Destination *Destination) = 0;
};
diff --git a/llvm/unittests/Telemetry/TelemetryTest.cpp b/llvm/unittests/Telemetry/TelemetryTest.cpp
index cca9285accef9c..8d13a43a46fac1 100644
--- a/llvm/unittests/Telemetry/TelemetryTest.cpp
+++ b/llvm/unittests/Telemetry/TelemetryTest.cpp
@@ -68,6 +68,31 @@ struct VendorEntryKind {
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.
@@ -83,6 +108,10 @@ struct VendorCommonTelemetryInfo : public TelemetryInfo {
KindType getKind() const override { return VendorEntryKind::VendorCommon; }
virtual void serializeToStream(llvm::raw_ostream &OS) const = 0;
+
+ std::optional<ExitDescription> ExitDesc;
+ EventStats Stats;
+ size_t Counter;
};
struct StartupEvent : public VendorCommonTelemetryInfo {
@@ -341,7 +370,7 @@ class TestTelemeter : public Telemeter {
return Telemeter;
}
- void logStartup(llvm::StringRef ToolPath, TelemetryInfo *Entry) override {
+ void atStartup(llvm::StringRef ToolPath, TelemetryInfo *Entry) override {
ToolName = ToolPath.str();
// The vendor can add additional stuff to the entry before logging.
@@ -351,7 +380,7 @@ class TestTelemeter : public Telemeter {
emitToDestinations(Entry);
}
- void logExit(llvm::StringRef ToolPath, TelemetryInfo *Entry) override {
+ void atExit(llvm::StringRef ToolPath, TelemetryInfo *Entry) override {
// Ensure we're shutting down the same tool we started with.
if (ToolPath != ToolName) {
std::string Str;
@@ -372,7 +401,7 @@ class TestTelemeter : public Telemeter {
Destinations.push_back(Dest);
}
- void logMidpoint(TelemetryInfo *Entry) {
+ void atMidpoint(TelemetryInfo *Entry) {
// The custom Telemeter can record and send additional data.
if (auto *C = dyn_cast<CustomTelemetryEvent>(Entry)) {
C->Msgs.push_back("Two");
@@ -475,7 +504,7 @@ void AtToolStart(std::string ToolName, vendor_code::TestTelemeter *T) {
vendor_code::StartupEvent Entry =
T->makeDefaultTelemetryInfo<vendor_code::StartupEvent>();
Entry.Stats = {StartTime, InitCompleteTime};
- T->logStartup(ToolName, &Entry);
+ T->atStartup(ToolName, &Entry);
}
void AtToolExit(std::string ToolName, vendor_code::TestTelemeter *T) {
@@ -486,14 +515,14 @@ void AtToolExit(std::string ToolName, vendor_code::TestTelemeter *T) {
if (CurrentContext->HasExitError) {
Entry.ExitDesc = {1, CurrentContext->ExitMsg};
}
- T->logExit(ToolName, &Entry);
+ T->atExit(ToolName, &Entry);
}
void AtToolMidPoint(vendor_code::TestTelemeter *T) {
vendor_code::CustomTelemetryEvent Entry =
T->makeDefaultTelemetryInfo<vendor_code::CustomTelemetryEvent>();
Entry.Stats = {MidPointTime, MidPointCompleteTime};
- T->logMidpoint(&Entry);
+ T->atMidpoint(&Entry);
}
// Helper function to print the given object content to string.
>From efd25d8753fbf26e3fa92453f10c254f988193b4 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Tue, 24 Sep 2024 15:17:30 -0400
Subject: [PATCH 38/61] use llvm::StringLiteral
---
llvm/include/llvm/Telemetry/Telemetry.h | 2 +-
llvm/unittests/Telemetry/TelemetryTest.cpp | 20 ++++++++++----------
2 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/llvm/include/llvm/Telemetry/Telemetry.h b/llvm/include/llvm/Telemetry/Telemetry.h
index 6c75582148b56d..1390ba4dfce462 100644
--- a/llvm/include/llvm/Telemetry/Telemetry.h
+++ b/llvm/include/llvm/Telemetry/Telemetry.h
@@ -112,7 +112,7 @@ class Destination {
public:
virtual ~Destination() = default;
virtual Error emitEntry(const TelemetryInfo *Entry) = 0;
- virtual std::string name() const = 0;
+ virtual llvm::StringLiteral name() const = 0;
};
/// This class is the main interaction point between any LLVM tool
diff --git a/llvm/unittests/Telemetry/TelemetryTest.cpp b/llvm/unittests/Telemetry/TelemetryTest.cpp
index 8d13a43a46fac1..7b1c04eb2dddfd 100644
--- a/llvm/unittests/Telemetry/TelemetryTest.cpp
+++ b/llvm/unittests/Telemetry/TelemetryTest.cpp
@@ -233,8 +233,8 @@ struct CustomTelemetryEvent : public VendorCommonTelemetryInfo {
// + where to send the data
// + in what form
-const std::string STRING_DEST("STRING");
-const std::string JSON_DEST("JSON");
+static constexpr llvm::StringLiteral STRING_DEST("STRING");
+static constexpr llvm::StringLiteral JSON_DEST("JSON");
// This Destination sends data to a std::string given at ctor.
class StringDestination : public Destination {
@@ -269,7 +269,7 @@ class StringDestination : public Destination {
return Error::success();
}
- std::string name() const override { return STRING_DEST; }
+ llvm::StringLiteral name() const override { return STRING_DEST; }
private:
// Returns a copy of the given entry, but with some fields sanitized.
@@ -315,7 +315,7 @@ class JsonStreamDestination : public Destination {
inconvertibleErrorCode());
}
- std::string name() const override { return JSON_DEST; }
+ llvm::StringLiteral name() const override { return JSON_DEST; }
private:
// Returns a copy of the given entry, but with some fields sanitized.
@@ -356,10 +356,10 @@ class TestTelemeter : public Telemeter {
for (const std::string &Dest : config->AdditionalDestinations) {
// The destination(s) are ALSO defined by vendor, so it should understand
// what the name of each destination signifies.
- if (Dest == JSON_DEST) {
+ if (Dest == JSON_DEST.str()) {
Telemeter->addDestination(new vendor_code::JsonStreamDestination(
CurrentContext->SanitizeData));
- } else if (Dest == STRING_DEST) {
+ } else if (Dest == STRING_DEST.str()) {
Telemeter->addDestination(new vendor_code::StringDestination(
CurrentContext->SanitizeData, CurrentContext->Buffer));
} else {
@@ -560,8 +560,8 @@ TEST(TelemetryTest, TelemetryEnabled) {
std::shared_ptr<llvm::telemetry::Config> Config = GetTelemetryConfig();
// Add some destinations
- Config->AdditionalDestinations.push_back(vendor_code::STRING_DEST);
- Config->AdditionalDestinations.push_back(vendor_code::JSON_DEST);
+ Config->AdditionalDestinations.push_back(vendor_code::STRING_DEST.str());
+ Config->AdditionalDestinations.push_back(vendor_code::JSON_DEST.str());
auto Tool = vendor_code::TestTelemeter::createInstance(Config.get());
@@ -642,8 +642,8 @@ TEST(TelemetryTest, TelemetryEnabledSanitizeData) {
std::shared_ptr<llvm::telemetry::Config> Config = GetTelemetryConfig();
// Add some destinations
- Config->AdditionalDestinations.push_back(vendor_code::STRING_DEST);
- Config->AdditionalDestinations.push_back(vendor_code::JSON_DEST);
+ Config->AdditionalDestinations.push_back(vendor_code::STRING_DEST.str());
+ Config->AdditionalDestinations.push_back(vendor_code::JSON_DEST.str());
auto Tool = vendor_code::TestTelemeter::createInstance(Config.get());
>From 4a8276f9af1e3ce9c93fe0c6632ac7e2237cd24a Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Wed, 25 Sep 2024 21:14:02 -0400
Subject: [PATCH 39/61] Addressed review comment: - typo in doc - removed cpy
ctor (replaced it wih = default) - used unique_ptr<> for storing Destination
(rather than raw ptrs) - constructed expected json objects and compare the
objects (rather than their string-reps) - used StringRef() comparisons
rather than .str() == - removed thread_local usages (and passed the
TestContext obj down from the test body)
---
llvm/docs/Telemetry.rst | 2 +-
llvm/include/llvm/Telemetry/Telemetry.h | 4 +-
llvm/unittests/Telemetry/TelemetryTest.cpp | 178 +++++++++------------
3 files changed, 82 insertions(+), 102 deletions(-)
diff --git a/llvm/docs/Telemetry.rst b/llvm/docs/Telemetry.rst
index 4a3df3d5b36bd2..29af0d57811f42 100644
--- a/llvm/docs/Telemetry.rst
+++ b/llvm/docs/Telemetry.rst
@@ -198,7 +198,7 @@ Logging the tool init-process:
Similar code can be used for logging the tool's exit.
-Additionall, at any other point in the tool's lifetime, it can also log telemetry:
+Additionally, at any other point in the tool's lifetime, it can also log telemetry:
.. code-block:: c++
diff --git a/llvm/include/llvm/Telemetry/Telemetry.h b/llvm/include/llvm/Telemetry/Telemetry.h
index 1390ba4dfce462..d9c5e545c0009e 100644
--- a/llvm/include/llvm/Telemetry/Telemetry.h
+++ b/llvm/include/llvm/Telemetry/Telemetry.h
@@ -89,7 +89,7 @@ struct TelemetryInfo {
std::string SessionId;
TelemetryInfo() = default;
- ~TelemetryInfo() = default;
+ virtual ~TelemetryInfo() = default;
virtual json::Object serializeToJson() const;
@@ -127,7 +127,7 @@ class Telemeter {
// Invoked upon tool exit.
virtual void atExit(llvm::StringRef ToolPath, TelemetryInfo *Entry) = 0;
- virtual void addDestination(Destination *Destination) = 0;
+ virtual void addDestination(std::unique_ptr<Destination> Destination) = 0;
};
} // namespace telemetry
diff --git a/llvm/unittests/Telemetry/TelemetryTest.cpp b/llvm/unittests/Telemetry/TelemetryTest.cpp
index 7b1c04eb2dddfd..9bc57da3273d33 100644
--- a/llvm/unittests/Telemetry/TelemetryTest.cpp
+++ b/llvm/unittests/Telemetry/TelemetryTest.cpp
@@ -49,9 +49,6 @@ struct TestContext {
std::string ExpectedUuid = "";
};
-// This is set by the test body.
-static thread_local TestContext *CurrentContext = nullptr;
-
namespace llvm {
namespace telemetry {
namespace vendor_code {
@@ -118,14 +115,7 @@ struct StartupEvent : public VendorCommonTelemetryInfo {
std::string MagicStartupMsg;
StartupEvent() = default;
- StartupEvent(const StartupEvent &E) {
- SessionId = E.SessionId;
- Stats = E.Stats;
- ExitDesc = E.ExitDesc;
- Counter = E.Counter;
-
- MagicStartupMsg = E.MagicStartupMsg;
- }
+ StartupEvent(const StartupEvent &E) = default;
static bool classof(const TelemetryInfo *T) {
if (T == nullptr)
@@ -154,14 +144,7 @@ struct ExitEvent : public VendorCommonTelemetryInfo {
ExitEvent() = default;
// Provide a copy ctor because we may need to make a copy
// before sanitizing the Entry.
- ExitEvent(const ExitEvent &E) {
- SessionId = E.SessionId;
- Stats = E.Stats;
- ExitDesc = E.ExitDesc;
- Counter = E.Counter;
-
- MagicExitMsg = E.MagicExitMsg;
- }
+ ExitEvent(const ExitEvent &E) = default;
static bool classof(const TelemetryInfo *T) {
if (T == nullptr)
@@ -195,14 +178,7 @@ struct CustomTelemetryEvent : public VendorCommonTelemetryInfo {
std::vector<std::string> Msgs;
CustomTelemetryEvent() = default;
- CustomTelemetryEvent(const CustomTelemetryEvent &E) {
- SessionId = E.SessionId;
- Stats = E.Stats;
- ExitDesc = E.ExitDesc;
- Counter = E.Counter;
-
- Msgs = E.Msgs;
- }
+ CustomTelemetryEvent(const CustomTelemetryEvent &E) = default;
void serializeToStream(llvm::raw_ostream &OS) const override {
OS << "SessionId:" << SessionId << "\n";
@@ -290,7 +266,8 @@ class StringDestination : public Destination {
// This Destination sends data to some "blackbox" in form of JSON.
class JsonStreamDestination : public Destination {
public:
- JsonStreamDestination(bool ShouldSanitize) : ShouldSanitize(ShouldSanitize) {}
+ JsonStreamDestination(bool ShouldSanitize, TestContext *Ctxt)
+ : ShouldSanitize(ShouldSanitize), CurrentContext(Ctxt) {}
Error emitEntry(const TelemetryInfo *Entry) override {
if (auto *E = dyn_cast<VendorCommonTelemetryInfo>(Entry)) {
@@ -298,21 +275,17 @@ class JsonStreamDestination : public Destination {
if (isa<StartupEvent>(E) || isa<ExitEvent>(E)) {
// There is nothing to sanitize for this type of data, so keep as-is.
return SendToBlackbox(E->serializeToJson());
- } else if (isa<CustomTelemetryEvent>(E)) {
+ }
+ if (isa<CustomTelemetryEvent>(E)) {
auto Sanitized = sanitizeFields(dyn_cast<CustomTelemetryEvent>(E));
return SendToBlackbox(Sanitized.serializeToJson());
- } else {
- llvm_unreachable("unexpected type");
}
- } else {
- return SendToBlackbox(E->serializeToJson());
+ llvm_unreachable("unexpected type");
}
- } else {
- // Unfamiliar entries, just send the entry's ID
- return SendToBlackbox(json::Object{{"SessionId", Entry->SessionId}});
+ return SendToBlackbox(E->serializeToJson());
}
- return make_error<StringError>("unhandled codepath in emitEntry",
- inconvertibleErrorCode());
+ // Unfamiliar entries, just send the entry's ID
+ return SendToBlackbox(json::Object{{"SessionId", Entry->SessionId}});
}
llvm::StringLiteral name() const override { return JSON_DEST; }
@@ -339,6 +312,7 @@ class JsonStreamDestination : public Destination {
return Error::success();
}
bool ShouldSanitize;
+ TestContext *CurrentContext;
};
// Custom vendor-defined Telemeter that has additional data-collection point.
@@ -346,7 +320,8 @@ class TestTelemeter : public Telemeter {
public:
TestTelemeter(std::string SessionId) : Uuid(SessionId), Counter(0) {}
- static std::unique_ptr<TestTelemeter> createInstance(Config *config) {
+ static std::unique_ptr<TestTelemeter>
+ createInstance(Config *config, TestContext *CurrentContext) {
if (!config->EnableTelemetry)
return nullptr;
CurrentContext->ExpectedUuid = nextUuid();
@@ -356,17 +331,20 @@ class TestTelemeter : public Telemeter {
for (const std::string &Dest : config->AdditionalDestinations) {
// The destination(s) are ALSO defined by vendor, so it should understand
// what the name of each destination signifies.
- if (Dest == JSON_DEST.str()) {
- Telemeter->addDestination(new vendor_code::JsonStreamDestination(
- CurrentContext->SanitizeData));
- } else if (Dest == STRING_DEST.str()) {
- Telemeter->addDestination(new vendor_code::StringDestination(
- CurrentContext->SanitizeData, CurrentContext->Buffer));
+ if (llvm::StringRef(Dest) == JSON_DEST) {
+ Telemeter->addDestination(
+ std::make_unique<vendor_code::JsonStreamDestination>(
+ CurrentContext->SanitizeData, CurrentContext));
+ } else if (llvm::StringRef(Dest) == STRING_DEST) {
+ Telemeter->addDestination(
+ std::make_unique<vendor_code::StringDestination>(
+ CurrentContext->SanitizeData, CurrentContext->Buffer));
} else {
llvm_unreachable(
llvm::Twine("unknown destination: ", Dest).str().c_str());
}
}
+ Telemeter->CurrentContext = CurrentContext;
return Telemeter;
}
@@ -397,8 +375,8 @@ class TestTelemeter : public Telemeter {
emitToDestinations(Entry);
}
- void addDestination(Destination *Dest) override {
- Destinations.push_back(Dest);
+ void addDestination(std::unique_ptr<Destination> Dest) override {
+ Destinations.push_back(std::move(Dest));
}
void atMidpoint(TelemetryInfo *Entry) {
@@ -414,10 +392,7 @@ class TestTelemeter : public Telemeter {
const std::string &getUuid() const { return Uuid; }
- ~TestTelemeter() {
- for (auto *Dest : Destinations)
- delete Dest;
- }
+ ~TestTelemeter() = default;
template <typename T> T makeDefaultTelemetryInfo() {
T Ret;
@@ -426,9 +401,11 @@ class TestTelemeter : public Telemeter {
return Ret;
}
+ TestContext *CurrentContext = nullptr;
+
private:
void emitToDestinations(TelemetryInfo *Entry) {
- for (Destination *Dest : Destinations) {
+ for (const auto &Dest : Destinations) {
llvm::Error err = Dest->emitEntry(Entry);
if (err) {
// Log it and move on.
@@ -439,7 +416,7 @@ class TestTelemeter : public Telemeter {
const std::string Uuid;
size_t Counter;
std::string ToolName;
- std::vector<Destination *> Destinations;
+ std::vector<std::unique_ptr<Destination>> Destinations;
};
// Pretend to be a "weakly" defined vendor-specific function.
@@ -458,7 +435,8 @@ void ApplyCommonConfig(llvm::telemetry::Config *config) {
// .....
}
-std::shared_ptr<llvm::telemetry::Config> GetTelemetryConfig() {
+std::shared_ptr<llvm::telemetry::Config>
+GetTelemetryConfig(TestContext *CurrentContext) {
// Telemetry is disabled by default.
// The vendor can enable in their config.
auto Config = std::make_shared<llvm::telemetry::Config>();
@@ -512,8 +490,8 @@ void AtToolExit(std::string ToolName, vendor_code::TestTelemeter *T) {
T->makeDefaultTelemetryInfo<vendor_code::ExitEvent>();
Entry.Stats = {ExitTime, ExitCompleteTime};
- if (CurrentContext->HasExitError) {
- Entry.ExitDesc = {1, CurrentContext->ExitMsg};
+ if (T->CurrentContext->HasExitError) {
+ Entry.ExitDesc = {1, T->CurrentContext->ExitMsg};
}
T->atExit(ToolName, &Entry);
}
@@ -525,23 +503,17 @@ void AtToolMidPoint(vendor_code::TestTelemeter *T) {
T->atMidpoint(&Entry);
}
-// Helper function to print the given object content to string.
-static std::string ValueToString(const json::Value *V) {
- std::string Ret;
- llvm::raw_string_ostream P(Ret);
- P << *V;
- return Ret;
-}
-
// Without vendor's implementation, telemetry is not enabled by default.
TEST(TelemetryTest, TelemetryDefault) {
// Preset some test params.
TestContext Context;
Context.HasVendorConfig = false;
- CurrentContext = &Context;
+ TestContext *CurrentContext = &Context;
- std::shared_ptr<llvm::telemetry::Config> Config = GetTelemetryConfig();
- auto Tool = vendor_code::TestTelemeter::createInstance(Config.get());
+ std::shared_ptr<llvm::telemetry::Config> Config =
+ GetTelemetryConfig(CurrentContext);
+ auto Tool =
+ vendor_code::TestTelemeter::createInstance(Config.get(), CurrentContext);
EXPECT_EQ(nullptr, Tool.get());
}
@@ -555,15 +527,17 @@ TEST(TelemetryTest, TelemetryEnabled) {
Context.SanitizeData = false;
Context.Buffer.clear();
Context.EmittedJsons.clear();
- CurrentContext = &Context;
+ TestContext *CurrentContext = &Context;
- std::shared_ptr<llvm::telemetry::Config> Config = GetTelemetryConfig();
+ std::shared_ptr<llvm::telemetry::Config> Config =
+ GetTelemetryConfig(CurrentContext);
// Add some destinations
Config->AdditionalDestinations.push_back(vendor_code::STRING_DEST.str());
Config->AdditionalDestinations.push_back(vendor_code::JSON_DEST.str());
- auto Tool = vendor_code::TestTelemeter::createInstance(Config.get());
+ auto Tool =
+ vendor_code::TestTelemeter::createInstance(Config.get(), CurrentContext);
AtToolStart(ToolName, Tool.get());
AtToolMidPoint(Tool.get());
@@ -595,34 +569,32 @@ TEST(TelemetryTest, TelemetryEnabled) {
const json::Value *StartupEntry =
CurrentContext->EmittedJsons[0].get("Startup");
ASSERT_NE(StartupEntry, nullptr);
- EXPECT_STREQ(
+ llvm::Expected<json::Value> ExpectedStartup = json::parse(
("[[\"SessionId\",\"" + llvm::Twine(CurrentContext->ExpectedUuid) +
"\"],[\"MagicStartupMsg\",\"Startup_" + llvm::Twine(ToolName) + "\"]]")
- .str()
- .c_str(),
- ValueToString(StartupEntry).c_str());
+ .str());
+ ASSERT_TRUE((bool)ExpectedStartup);
+ EXPECT_EQ(ExpectedStartup.get(), *StartupEntry);
const json::Value *MidpointEntry =
CurrentContext->EmittedJsons[1].get("Midpoint");
ASSERT_NE(MidpointEntry, nullptr);
- // TODO: This is a bit flaky in that the json string printer sort the
- // entries (for now), so the "UUID" field is put at the end of the array
- // even though it was emitted first.
- EXPECT_STREQ(("{\"MSG_0\":\"Two\",\"MSG_1\":\"Deux\",\"MSG_2\":\"Zwei\","
- "\"SessionId\":\"" +
- llvm::Twine(CurrentContext->ExpectedUuid) + "\"}")
- .str()
- .c_str(),
- ValueToString(MidpointEntry).c_str());
+ llvm::Expected<json::Value> ExpectedMidpoint =
+ json::parse(("{\"MSG_0\":\"Two\",\"MSG_1\":\"Deux\",\"MSG_2\":\"Zwei\","
+ "\"SessionId\":\"" +
+ llvm::Twine(CurrentContext->ExpectedUuid) + "\"}")
+ .str());
+ ASSERT_TRUE((bool)ExpectedMidpoint);
+ EXPECT_EQ(ExpectedMidpoint.get(), *MidpointEntry);
const json::Value *ExitEntry = CurrentContext->EmittedJsons[2].get("Exit");
ASSERT_NE(ExitEntry, nullptr);
- EXPECT_STREQ(
+ llvm::Expected<json::Value> ExpectedExit = json::parse(
("[[\"SessionId\",\"" + llvm::Twine(CurrentContext->ExpectedUuid) +
"\"],[\"MagicExitMsg\",\"Exit_" + llvm::Twine(ToolName) + "\"]]")
- .str()
- .c_str(),
- ValueToString(ExitEntry).c_str());
+ .str());
+ ASSERT_TRUE((bool)ExpectedExit);
+ EXPECT_EQ(ExpectedExit.get(), *ExitEntry);
}
}
@@ -637,15 +609,17 @@ TEST(TelemetryTest, TelemetryEnabledSanitizeData) {
Context.SanitizeData = true;
Context.Buffer.clear();
Context.EmittedJsons.clear();
- CurrentContext = &Context;
- std::shared_ptr<llvm::telemetry::Config> Config = GetTelemetryConfig();
+ TestContext *CurrentContext = &Context;
+ std::shared_ptr<llvm::telemetry::Config> Config =
+ GetTelemetryConfig(CurrentContext);
// Add some destinations
Config->AdditionalDestinations.push_back(vendor_code::STRING_DEST.str());
Config->AdditionalDestinations.push_back(vendor_code::JSON_DEST.str());
- auto Tool = vendor_code::TestTelemeter::createInstance(Config.get());
+ auto Tool =
+ vendor_code::TestTelemeter::createInstance(Config.get(), CurrentContext);
AtToolStart(ToolName, Tool.get());
AtToolMidPoint(Tool.get());
@@ -675,32 +649,38 @@ TEST(TelemetryTest, TelemetryEnabledSanitizeData) {
const json::Value *StartupEntry =
CurrentContext->EmittedJsons[0].get("Startup");
ASSERT_NE(StartupEntry, nullptr);
- EXPECT_STREQ(
+ llvm::Expected<json::Value> ExpectedStartup = json::parse(
("[[\"SessionId\",\"" + llvm::Twine(CurrentContext->ExpectedUuid) +
"\"],[\"MagicStartupMsg\",\"Startup_" + llvm::Twine(ToolName) + "\"]]")
.str()
- .c_str(),
- ValueToString(StartupEntry).c_str());
+
+ );
+ ASSERT_TRUE((bool)ExpectedStartup);
+ EXPECT_EQ(ExpectedStartup.get(), *StartupEntry);
const json::Value *MidpointEntry =
CurrentContext->EmittedJsons[1].get("Midpoint");
ASSERT_NE(MidpointEntry, nullptr);
// The JsonDestination should have removed the even-positioned msgs.
- EXPECT_STREQ(
+ llvm::Expected<json::Value> ExpectedMidpoint = json::parse(
("{\"MSG_0\":\"\",\"MSG_1\":\"Deux\",\"MSG_2\":\"\",\"SessionId\":\"" +
llvm::Twine(CurrentContext->ExpectedUuid) + "\"}")
.str()
- .c_str(),
- ValueToString(MidpointEntry).c_str());
+
+ );
+ ASSERT_TRUE((bool)ExpectedMidpoint);
+ EXPECT_EQ(ExpectedMidpoint.get(), *MidpointEntry);
const json::Value *ExitEntry = CurrentContext->EmittedJsons[2].get("Exit");
ASSERT_NE(ExitEntry, nullptr);
- EXPECT_STREQ(
+ llvm::Expected<json::Value> ExpectedExit = json::parse(
("[[\"SessionId\",\"" + llvm::Twine(CurrentContext->ExpectedUuid) +
"\"],[\"MagicExitMsg\",\"Exit_" + llvm::Twine(ToolName) + "\"]]")
.str()
- .c_str(),
- ValueToString(ExitEntry).c_str());
+
+ );
+ ASSERT_TRUE((bool)ExpectedExit);
+ EXPECT_EQ(ExpectedExit.get(), *ExitEntry);
}
}
>From a16344b9f3ead51b9cb6af14198bc574eeba4dcc Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Fri, 27 Sep 2024 09:35:09 -0400
Subject: [PATCH 40/61] pass test context as arg to ctor
---
llvm/unittests/Telemetry/TelemetryTest.cpp | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/llvm/unittests/Telemetry/TelemetryTest.cpp b/llvm/unittests/Telemetry/TelemetryTest.cpp
index 9bc57da3273d33..30f72c0df78bc4 100644
--- a/llvm/unittests/Telemetry/TelemetryTest.cpp
+++ b/llvm/unittests/Telemetry/TelemetryTest.cpp
@@ -318,15 +318,16 @@ class JsonStreamDestination : public Destination {
// Custom vendor-defined Telemeter that has additional data-collection point.
class TestTelemeter : public Telemeter {
public:
- TestTelemeter(std::string SessionId) : Uuid(SessionId), Counter(0) {}
+ TestTelemeter(std::string SessionId, TestContext *Ctxt)
+ : Uuid(SessionId), Counter(0), CurrentContext(Ctxt) {}
static std::unique_ptr<TestTelemeter>
createInstance(Config *config, TestContext *CurrentContext) {
if (!config->EnableTelemetry)
return nullptr;
CurrentContext->ExpectedUuid = nextUuid();
- std::unique_ptr<TestTelemeter> Telemeter =
- std::make_unique<TestTelemeter>(CurrentContext->ExpectedUuid);
+ std::unique_ptr<TestTelemeter> Telemeter = std::make_unique<TestTelemeter>(
+ CurrentContext->ExpectedUuid, CurrentContext);
// Set up Destination based on the given config.
for (const std::string &Dest : config->AdditionalDestinations) {
// The destination(s) are ALSO defined by vendor, so it should understand
@@ -344,7 +345,6 @@ class TestTelemeter : public Telemeter {
llvm::Twine("unknown destination: ", Dest).str().c_str());
}
}
- Telemeter->CurrentContext = CurrentContext;
return Telemeter;
}
>From 26ee5eb9cb934d96164bfa98931d8ecfcc962342 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Mon, 30 Sep 2024 14:02:25 -0400
Subject: [PATCH 41/61] construct expected json directly rather than parsing
from strings
---
llvm/unittests/Telemetry/TelemetryTest.cpp | 80 +++++++++-------------
1 file changed, 33 insertions(+), 47 deletions(-)
diff --git a/llvm/unittests/Telemetry/TelemetryTest.cpp b/llvm/unittests/Telemetry/TelemetryTest.cpp
index 30f72c0df78bc4..772cc93f3d4d5b 100644
--- a/llvm/unittests/Telemetry/TelemetryTest.cpp
+++ b/llvm/unittests/Telemetry/TelemetryTest.cpp
@@ -190,11 +190,10 @@ struct CustomTelemetryEvent : public VendorCommonTelemetryInfo {
}
json::Object serializeToJson() const override {
- json::Object Inner;
- Inner.try_emplace("SessionId", SessionId);
+ json::Array Inner{{"SessionId", SessionId}};
int I = 0;
for (const std::string &M : Msgs) {
- Inner.try_emplace(("MSG_" + llvm::Twine(I)).str(), M);
+ Inner.push_back(json::Value({("MSG_" + llvm::Twine(I)).str(), M}));
++I;
}
@@ -319,7 +318,7 @@ class JsonStreamDestination : public Destination {
class TestTelemeter : public Telemeter {
public:
TestTelemeter(std::string SessionId, TestContext *Ctxt)
- : Uuid(SessionId), Counter(0), CurrentContext(Ctxt) {}
+ : CurrentContext(Ctxt), Uuid(SessionId), Counter(0) {}
static std::unique_ptr<TestTelemeter>
createInstance(Config *config, TestContext *CurrentContext) {
@@ -569,32 +568,28 @@ TEST(TelemetryTest, TelemetryEnabled) {
const json::Value *StartupEntry =
CurrentContext->EmittedJsons[0].get("Startup");
ASSERT_NE(StartupEntry, nullptr);
- llvm::Expected<json::Value> ExpectedStartup = json::parse(
- ("[[\"SessionId\",\"" + llvm::Twine(CurrentContext->ExpectedUuid) +
- "\"],[\"MagicStartupMsg\",\"Startup_" + llvm::Twine(ToolName) + "\"]]")
- .str());
- ASSERT_TRUE((bool)ExpectedStartup);
- EXPECT_EQ(ExpectedStartup.get(), *StartupEntry);
+ json::Value ExpectedStartup({
+ {"SessionId", CurrentContext->ExpectedUuid},
+ {"MagicStartupMsg", ("Startup_" + llvm::Twine(ToolName)).str()},
+ });
+ EXPECT_EQ(ExpectedStartup, *StartupEntry);
const json::Value *MidpointEntry =
CurrentContext->EmittedJsons[1].get("Midpoint");
ASSERT_NE(MidpointEntry, nullptr);
- llvm::Expected<json::Value> ExpectedMidpoint =
- json::parse(("{\"MSG_0\":\"Two\",\"MSG_1\":\"Deux\",\"MSG_2\":\"Zwei\","
- "\"SessionId\":\"" +
- llvm::Twine(CurrentContext->ExpectedUuid) + "\"}")
- .str());
- ASSERT_TRUE((bool)ExpectedMidpoint);
- EXPECT_EQ(ExpectedMidpoint.get(), *MidpointEntry);
+ json::Value ExpectedMidpoint{{"SessionId", CurrentContext->ExpectedUuid},
+ {"MSG_0", "Two"},
+ {"MSG_1", "Deux"},
+ {"MSG_2", "Zwei"}};
+ EXPECT_EQ(ExpectedMidpoint, *MidpointEntry);
const json::Value *ExitEntry = CurrentContext->EmittedJsons[2].get("Exit");
ASSERT_NE(ExitEntry, nullptr);
- llvm::Expected<json::Value> ExpectedExit = json::parse(
- ("[[\"SessionId\",\"" + llvm::Twine(CurrentContext->ExpectedUuid) +
- "\"],[\"MagicExitMsg\",\"Exit_" + llvm::Twine(ToolName) + "\"]]")
- .str());
- ASSERT_TRUE((bool)ExpectedExit);
- EXPECT_EQ(ExpectedExit.get(), *ExitEntry);
+ json::Value ExpectedExit({
+ {"SessionId", CurrentContext->ExpectedUuid},
+ {"MagicExitMsg", ("Exit_" + llvm::Twine(ToolName)).str()},
+ });
+ EXPECT_EQ(ExpectedExit, *ExitEntry);
}
}
@@ -649,38 +644,29 @@ TEST(TelemetryTest, TelemetryEnabledSanitizeData) {
const json::Value *StartupEntry =
CurrentContext->EmittedJsons[0].get("Startup");
ASSERT_NE(StartupEntry, nullptr);
- llvm::Expected<json::Value> ExpectedStartup = json::parse(
- ("[[\"SessionId\",\"" + llvm::Twine(CurrentContext->ExpectedUuid) +
- "\"],[\"MagicStartupMsg\",\"Startup_" + llvm::Twine(ToolName) + "\"]]")
- .str()
-
- );
- ASSERT_TRUE((bool)ExpectedStartup);
- EXPECT_EQ(ExpectedStartup.get(), *StartupEntry);
+ json::Value ExpectedStartup({
+ {"SessionId", CurrentContext->ExpectedUuid},
+ {"MagicStartupMsg", ("Startup_" + llvm::Twine(ToolName)).str()},
+ });
+ EXPECT_EQ(ExpectedStartup, *StartupEntry);
const json::Value *MidpointEntry =
CurrentContext->EmittedJsons[1].get("Midpoint");
ASSERT_NE(MidpointEntry, nullptr);
// The JsonDestination should have removed the even-positioned msgs.
- llvm::Expected<json::Value> ExpectedMidpoint = json::parse(
- ("{\"MSG_0\":\"\",\"MSG_1\":\"Deux\",\"MSG_2\":\"\",\"SessionId\":\"" +
- llvm::Twine(CurrentContext->ExpectedUuid) + "\"}")
- .str()
-
- );
- ASSERT_TRUE((bool)ExpectedMidpoint);
- EXPECT_EQ(ExpectedMidpoint.get(), *MidpointEntry);
+ json::Value ExpectedMidpoint{{"SessionId", CurrentContext->ExpectedUuid},
+ {"MSG_0", ""},
+ {"MSG_1", "Deux"},
+ {"MSG_2", ""}};
+ EXPECT_EQ(ExpectedMidpoint, *MidpointEntry);
const json::Value *ExitEntry = CurrentContext->EmittedJsons[2].get("Exit");
ASSERT_NE(ExitEntry, nullptr);
- llvm::Expected<json::Value> ExpectedExit = json::parse(
- ("[[\"SessionId\",\"" + llvm::Twine(CurrentContext->ExpectedUuid) +
- "\"],[\"MagicExitMsg\",\"Exit_" + llvm::Twine(ToolName) + "\"]]")
- .str()
-
- );
- ASSERT_TRUE((bool)ExpectedExit);
- EXPECT_EQ(ExpectedExit.get(), *ExitEntry);
+ json::Value ExpectedExit({
+ {"SessionId", CurrentContext->ExpectedUuid},
+ {"MagicExitMsg", ("Exit_" + llvm::Twine(ToolName)).str()},
+ });
+ EXPECT_EQ(ExpectedExit, *ExitEntry);
}
}
>From b766e3c94afc8af8a922e27270dc68f0aa8f44d1 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Mon, 30 Sep 2024 14:06:00 -0400
Subject: [PATCH 42/61] remove unnecessary use of atomic seed
---
llvm/unittests/Telemetry/TelemetryTest.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/llvm/unittests/Telemetry/TelemetryTest.cpp b/llvm/unittests/Telemetry/TelemetryTest.cpp
index 772cc93f3d4d5b..afe4c5c1062fc7 100644
--- a/llvm/unittests/Telemetry/TelemetryTest.cpp
+++ b/llvm/unittests/Telemetry/TelemetryTest.cpp
@@ -55,8 +55,8 @@ 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));
+ static int seed = 1111;
+ return std::to_string(seed++);
}
struct VendorEntryKind {
>From c8cddabba1f3a4888f59fa848b57f79b5e5813d7 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Thu, 21 Nov 2024 15:20:47 -0500
Subject: [PATCH 43/61] addressed review comments
---
llvm/docs/Telemetry.rst | 62 +++++++++++-----------
llvm/include/llvm/Telemetry/Telemetry.h | 37 ++++---------
llvm/unittests/Telemetry/TelemetryTest.cpp | 36 ++++++-------
3 files changed, 58 insertions(+), 77 deletions(-)
diff --git a/llvm/docs/Telemetry.rst b/llvm/docs/Telemetry.rst
index 29af0d57811f42..b7610b0400333d 100644
--- a/llvm/docs/Telemetry.rst
+++ b/llvm/docs/Telemetry.rst
@@ -17,7 +17,7 @@ Objective
Provides a common framework in LLVM for collecting various usage and performance
metrics.
-It is located at `llvm/Telemetry/Telemetry.h`
+It is located at ``llvm/Telemetry/Telemetry.h``
Characteristics
---------------
@@ -30,7 +30,6 @@ Characteristics
* End users of such tool can also configure Telemetry (as allowed by their
vendor).
-
Important notes
----------------
@@ -38,7 +37,7 @@ Important notes
We only provide the abstract API here. Any tool that wants telemetry will
implement one.
- The rationale for this is that, all the tools in LLVM are very different in
+ The rationale for this is that all the tools in LLVM are very different in
what they care about (what/where/when to instrument data). Hence, it might not
be practical to have a single implementation.
However, in the future, if we see enough common pattern, we can extract them
@@ -56,7 +55,7 @@ Important notes
* Data ownership and data collection consents are hard to accommodate from
LLVM developers' point of view:
- * Eg., data collected by Telemetry is not neccessarily owned by the user
+ * E.g., data collected by Telemetry is not necessarily owned by the user
of an LLVM tool with Telemetry enabled, hence the user's consent to data
collection is not meaningful. On the other hand, LLVM developers have no
reasonable ways to request consent from the "real" owners.
@@ -70,42 +69,41 @@ Key components
The framework consists of four important classes:
-* `llvm::telemetry::Telemeter`: The class responsible for collecting and
+* ``llvm::telemetry::Manager``: The class responsible for collecting and
transmitting telemetry data. This is the main point of interaction between the
- framework and any tool that wants to enable telemery.
-* `llvm::telemetry::TelemetryInfo`: Data courier
-* `llvm::telemetry::Destination`: Data sink to which the Telemetry framework
+ framework and any tool that wants to enable telemetry.
+* ``llvm::telemetry::TelemetryInfo``: Data courier
+* ``llvm::telemetry::Destination``: Data sink to which the Telemetry framework
sends data.
Its implementation is transparent to the framework.
It is up to the vendor to decide which pieces of data to forward and where
- to forward them to their final storage.
-* `llvm::telemetry::Config`: Configurations on the `Telemeter`.
+ to forward them to for their final storage.
+* ``llvm::telemetry::Config``: Configurations for the ``Manager``.
.. image:: llvm_telemetry_design.png
How to implement and interact with the API
------------------------------------------
-To use Telemetry in your tool, you need to provide a concrete implementation of the `Telemeter` class and `Destination`.
+To use Telemetry in your tool, you need to provide a concrete implementation of the ``Manager`` class and ``Destination``.
-1) Define a custom `Telemeter` and `Destination`
+1) Define a custom ``Manager`` and ``Destination``
.. code-block:: c++
- // This destination just prints the given entry to a stdout.
+ // This destination just prints the given entry to stdout.
// In "real life", this would be where you forward the data to your
// custom data storage.
class MyStdoutDestination : public llvm::telemetry::Destination {
public:
Error emitEntry(const TelemetryInfo* Entry) override {
return sendToBlackBox(Entry);
-
}
private:
Error sendToBlackBox(const TelemetryInfo* Entry) {
- // This could send the data anywhere.
- // But we're simply sending it to stdout for the example.
+ // This could send the data anywhere, but we're simply sending it to
+ // stdout for the example.
llvm::outs() << entryToString(Entry) << "\n";
return llvm::success();
}
@@ -116,11 +114,11 @@ To use Telemetry in your tool, you need to provide a concrete implementation of
};
// This defines a custom TelemetryInfo that has an addition Msg field.
- struct MyTelemetryInfo : public llvm::telemetry::TelemetryInfo {
+ struct MyTelemetryInfo : public telemetry::TelemetryInfo {
std::string Msg;
- json::Object serializeToJson() const {
- json::Object Ret = TelemeteryInfo::serializeToJson();
+ json::Object serializeToJson() oconst {
+ json::Object Ret = ManageryInfo::serializeToJson();
Ret.emplace_back("MyMsg", Msg);
return std::move(Ret);
}
@@ -128,17 +126,17 @@ To use Telemetry in your tool, you need to provide a concrete implementation of
// TODO: implement getKind() and classof() to support dyn_cast operations.
};
- class MyTelemeter : public llvm::telemery::Telemeter {
+ class MyManager : public telemery::Manager {
public:
- static std::unique_ptr<MyTelemeter> createInstatnce(llvm::telemetry::Config* config) {
+ static std::unique_ptr<MyManager> createInstatnce(telemetry::Config* config) {
// If Telemetry is not enabled, then just return null;
if (!config->EnableTelemetry) return nullptr;
- std::make_unique<MyTelemeter>();
+ std::make_unique<MyManager>();
}
- MyTelemeter() = default;
+ MyManager() = default;
- void logStartup(llvm::StringRef ToolName, TelemetryInfo* Entry) override {
+ void logStartup(StringRef ToolName, TelemetryInfo* Entry) override {
if (MyTelemetryInfo* M = dyn_cast<MyTelemetryInfo>(Entry)) {
M->Msg = "Starting up tool with name: " + ToolName;
emitToAllDestinations(M);
@@ -147,9 +145,9 @@ To use Telemetry in your tool, you need to provide a concrete implementation of
}
}
- void logExit(llvm::StringRef ToolName, TelemetryInfo* Entry) override {
+ void logExit(StringRef ToolName, TelemetryInfo* Entry) override {
if (MyTelemetryInfo* M = dyn_cast<MyTelemetryInfo>(Entry)) {
- M->Msg = "Exitting tool with name: " + ToolName;
+ M->Msg = "Exiting tool with name: " + ToolName;
emitToAllDestinations(M);
} else {
emitToAllDestinations(Entry);
@@ -160,7 +158,7 @@ To use Telemetry in your tool, you need to provide a concrete implementation of
destinations.push_back(dest);
}
- // You can also define additional instrumentation points.)
+ // You can also define additional instrumentation points.
void logAdditionalPoint(TelemetryInfo* Entry) {
// .... code here
}
@@ -179,10 +177,10 @@ Logging the tool init-process:
.. code-block:: c++
- // At tool's init code
+ // In tool's initialization code.
auto StartTime = std::chrono::time_point<std::chrono::steady_clock>::now();
- llvm::telemetry::Config MyConfig = makeConfig(); // build up the appropriate Config struct here.
- auto Telemeter = MyTelemeter::createInstance(&MyConfig);
+ telemetry::Config MyConfig = makeConfig(); // Build up the appropriate Config struct here.
+ auto Manager = MyManager::createInstance(&MyConfig);
std::string CurrentSessionId = ...; // Make some unique ID corresponding to the current session here.
// Any other tool's init code can go here
@@ -194,7 +192,7 @@ Logging the tool init-process:
MyTelemetryInfo Entry;
Entry.SessionId = CurrentSessionId ; // Assign some unique ID here.
Entry.Stats = {StartTime, EndTime};
- Telemeter->logStartup("MyTool", &Entry);
+ Manager->logStartup("MyTool", &Entry);
Similar code can be used for logging the tool's exit.
@@ -211,4 +209,4 @@ Additionally, at any other point in the tool's lifetime, it can also log telemet
MyTelemetryInfo Entry;
Entry.SessionId = CurrentSessionId ; // Assign some unique ID here.
Entry.Stats = {StartTime, EndTime};
- Telemeter->logAdditionalPoint(&Entry);
+ Manager->logAdditionalPoint(&Entry);
diff --git a/llvm/include/llvm/Telemetry/Telemetry.h b/llvm/include/llvm/Telemetry/Telemetry.h
index d9c5e545c0009e..b22af683e4db44 100644
--- a/llvm/include/llvm/Telemetry/Telemetry.h
+++ b/llvm/include/llvm/Telemetry/Telemetry.h
@@ -8,52 +8,35 @@
///
/// \file
/// This file provides the basic framework for Telemetry
-///
-/// It comprises of three important structs/classes:
-///
-/// - Telemeter: The class responsible for collecting and forwarding
-/// telemery data.
-/// - TelemetryInfo: data courier
-/// - TelemetryConfig: this stores configurations on Telemeter.
-///
/// Refer to its documentation at llvm/docs/Telemetry.rst for more details.
//===---------------------------------------------------------------------===//
#ifndef LLVM_TELEMETRY_TELEMETRY_H
#define LLVM_TELEMETRY_TELEMETRY_H
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/JSON.h"
#include <chrono>
#include <ctime>
#include <memory>
#include <optional>
#include <string>
-#include "llvm/ADT/StringExtras.h"
-#include "llvm/ADT/StringRef.h"
-#include "llvm/Support/Error.h"
-#include "llvm/Support/JSON.h"
-
namespace llvm {
namespace telemetry {
/// Configuration for the Telemeter class.
/// This stores configurations from both users and vendors and is passed
/// to the Telemeter upon construction. (Any changes to the config after
-/// the Telemeter's construction will not have effect on it).
+/// the Telemeter's construction will not have any effect on it).
///
/// This struct can be extended as needed to add additional configuration
/// points specific to a vendor's implementation.
struct Config {
// If true, telemetry will be enabled.
bool EnableTelemetry;
-
- // Implementation-defined names of additional destinations to send
- // telemetry data (Could be stdout, stderr, or some local paths, etc).
- //
- // These strings will be interpreted by the vendor's code.
- // So the users must pick the from their vendor's pre-defined
- // set of Destinations.
- std::vector<std::string> AdditionalDestinations;
};
/// For isa, dyn_cast, etc operations on TelemetryInfo.
@@ -61,18 +44,18 @@ typedef unsigned KindType;
/// This struct is used by TelemetryInfo to support isa<>, dyn_cast<>
/// operations.
/// It is defined as a struct (rather than an enum) because it is
-/// expectend to be extended by subclasses which may have
+/// expected to be extended by subclasses which may have
/// additional TelemetryInfo types defined to describe different events.
struct EntryKind {
static const KindType Base = 0;
};
/// TelemetryInfo is the data courier, used to move instrumented data
-/// from the tool being monitored to the Telemery framework.
+/// from the tool being monitored to the Telemetry framework.
///
/// This base class contains only the basic set of telemetry data.
-/// Downstream implementations can add more fields as needed to describe
-/// additional events.
+/// Downstream implementations can define more subclasses with
+/// additional fields to describe different events and concepts.
///
/// For example, The LLDB debugger can define a DebugCommandInfo subclass
/// which has additional fields about the debug-command being instrumented,
@@ -119,7 +102,7 @@ class Destination {
/// and this framework.
/// It is responsible for collecting telemetry data from the tool being
/// monitored and transmitting the data elsewhere.
-class Telemeter {
+class Manager {
public:
// Invoked upon tool startup
virtual void atStartup(llvm::StringRef ToolPath, TelemetryInfo *Entry) = 0;
diff --git a/llvm/unittests/Telemetry/TelemetryTest.cpp b/llvm/unittests/Telemetry/TelemetryTest.cpp
index afe4c5c1062fc7..257b7454bf0eb9 100644
--- a/llvm/unittests/Telemetry/TelemetryTest.cpp
+++ b/llvm/unittests/Telemetry/TelemetryTest.cpp
@@ -314,29 +314,29 @@ class JsonStreamDestination : public Destination {
TestContext *CurrentContext;
};
-// Custom vendor-defined Telemeter that has additional data-collection point.
-class TestTelemeter : public Telemeter {
+// Custom vendor-defined Manager that has additional data-collection point.
+class TestManager : public Manager {
public:
- TestTelemeter(std::string SessionId, TestContext *Ctxt)
+ TestManager(std::string SessionId, TestContext *Ctxt)
: CurrentContext(Ctxt), Uuid(SessionId), Counter(0) {}
- static std::unique_ptr<TestTelemeter>
+ static std::unique_ptr<TestManager>
createInstance(Config *config, TestContext *CurrentContext) {
if (!config->EnableTelemetry)
return nullptr;
CurrentContext->ExpectedUuid = nextUuid();
- std::unique_ptr<TestTelemeter> Telemeter = std::make_unique<TestTelemeter>(
+ std::unique_ptr<TestManager> Manager = std::make_unique<TestManager>(
CurrentContext->ExpectedUuid, CurrentContext);
// Set up Destination based on the given config.
for (const std::string &Dest : config->AdditionalDestinations) {
// The destination(s) are ALSO defined by vendor, so it should understand
// what the name of each destination signifies.
if (llvm::StringRef(Dest) == JSON_DEST) {
- Telemeter->addDestination(
+ Manager->addDestination(
std::make_unique<vendor_code::JsonStreamDestination>(
CurrentContext->SanitizeData, CurrentContext));
} else if (llvm::StringRef(Dest) == STRING_DEST) {
- Telemeter->addDestination(
+ Manager->addDestination(
std::make_unique<vendor_code::StringDestination>(
CurrentContext->SanitizeData, CurrentContext->Buffer));
} else {
@@ -344,7 +344,7 @@ class TestTelemeter : public Telemeter {
llvm::Twine("unknown destination: ", Dest).str().c_str());
}
}
- return Telemeter;
+ return Manager;
}
void atStartup(llvm::StringRef ToolPath, TelemetryInfo *Entry) override {
@@ -379,7 +379,7 @@ class TestTelemeter : public Telemeter {
}
void atMidpoint(TelemetryInfo *Entry) {
- // The custom Telemeter can record and send additional data.
+ // The custom Manager can record and send additional data.
if (auto *C = dyn_cast<CustomTelemetryEvent>(Entry)) {
C->Msgs.push_back("Two");
C->Msgs.push_back("Deux");
@@ -391,7 +391,7 @@ class TestTelemeter : public Telemeter {
const std::string &getUuid() const { return Uuid; }
- ~TestTelemeter() = default;
+ ~TestManager() = default;
template <typename T> T makeDefaultTelemetryInfo() {
T Ret;
@@ -477,14 +477,14 @@ auto ExitTime = StartTime + std::chrono::milliseconds(20);
// milliseconds.
auto ExitCompleteTime = ExitTime + std::chrono::milliseconds(10);
-void AtToolStart(std::string ToolName, vendor_code::TestTelemeter *T) {
+void AtToolStart(std::string ToolName, vendor_code::TestManager *T) {
vendor_code::StartupEvent Entry =
T->makeDefaultTelemetryInfo<vendor_code::StartupEvent>();
Entry.Stats = {StartTime, InitCompleteTime};
T->atStartup(ToolName, &Entry);
}
-void AtToolExit(std::string ToolName, vendor_code::TestTelemeter *T) {
+void AtToolExit(std::string ToolName, vendor_code::TestManager *T) {
vendor_code::ExitEvent Entry =
T->makeDefaultTelemetryInfo<vendor_code::ExitEvent>();
Entry.Stats = {ExitTime, ExitCompleteTime};
@@ -495,7 +495,7 @@ void AtToolExit(std::string ToolName, vendor_code::TestTelemeter *T) {
T->atExit(ToolName, &Entry);
}
-void AtToolMidPoint(vendor_code::TestTelemeter *T) {
+void AtToolMidPoint(vendor_code::TestManager *T) {
vendor_code::CustomTelemetryEvent Entry =
T->makeDefaultTelemetryInfo<vendor_code::CustomTelemetryEvent>();
Entry.Stats = {MidPointTime, MidPointCompleteTime};
@@ -512,7 +512,7 @@ TEST(TelemetryTest, TelemetryDefault) {
std::shared_ptr<llvm::telemetry::Config> Config =
GetTelemetryConfig(CurrentContext);
auto Tool =
- vendor_code::TestTelemeter::createInstance(Config.get(), CurrentContext);
+ vendor_code::TestManager::createInstance(Config.get(), CurrentContext);
EXPECT_EQ(nullptr, Tool.get());
}
@@ -536,7 +536,7 @@ TEST(TelemetryTest, TelemetryEnabled) {
Config->AdditionalDestinations.push_back(vendor_code::JSON_DEST.str());
auto Tool =
- vendor_code::TestTelemeter::createInstance(Config.get(), CurrentContext);
+ vendor_code::TestManager::createInstance(Config.get(), CurrentContext);
AtToolStart(ToolName, Tool.get());
AtToolMidPoint(Tool.get());
@@ -562,7 +562,7 @@ TEST(TelemetryTest, TelemetryEnabled) {
// Check that the JsonDestination emitted properly
{
- // There should be 3 events emitted by the Telemeter (start, midpoint, exit)
+ // There should be 3 events emitted by the Manager (start, midpoint, exit)
EXPECT_EQ(static_cast<size_t>(3), CurrentContext->EmittedJsons.size());
const json::Value *StartupEntry =
@@ -614,7 +614,7 @@ TEST(TelemetryTest, TelemetryEnabledSanitizeData) {
Config->AdditionalDestinations.push_back(vendor_code::JSON_DEST.str());
auto Tool =
- vendor_code::TestTelemeter::createInstance(Config.get(), CurrentContext);
+ vendor_code::TestManager::createInstance(Config.get(), CurrentContext);
AtToolStart(ToolName, Tool.get());
AtToolMidPoint(Tool.get());
@@ -638,7 +638,7 @@ TEST(TelemetryTest, TelemetryEnabledSanitizeData) {
// Check that the JsonDestination emitted properly
{
- // There should be 3 events emitted by the Telemeter (start, midpoint, exit)
+ // There should be 3 events emitted by the Manager (start, midpoint, exit)
EXPECT_EQ(static_cast<size_t>(3), CurrentContext->EmittedJsons.size());
const json::Value *StartupEntry =
>From 5f8d65f318ff2028afa2a7129deadf81c08ac81c Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Thu, 21 Nov 2024 15:25:55 -0500
Subject: [PATCH 44/61] s/emitEntry/receiveEntry
---
llvm/include/llvm/Telemetry/Telemetry.h | 2 +-
llvm/unittests/Telemetry/TelemetryTest.cpp | 6 +++---
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/llvm/include/llvm/Telemetry/Telemetry.h b/llvm/include/llvm/Telemetry/Telemetry.h
index b22af683e4db44..ad12cb635f85a0 100644
--- a/llvm/include/llvm/Telemetry/Telemetry.h
+++ b/llvm/include/llvm/Telemetry/Telemetry.h
@@ -94,7 +94,7 @@ struct TelemetryInfo {
class Destination {
public:
virtual ~Destination() = default;
- virtual Error emitEntry(const TelemetryInfo *Entry) = 0;
+ virtual Error receiveEntry(const TelemetryInfo *Entry) = 0;
virtual llvm::StringLiteral name() const = 0;
};
diff --git a/llvm/unittests/Telemetry/TelemetryTest.cpp b/llvm/unittests/Telemetry/TelemetryTest.cpp
index 257b7454bf0eb9..48062874940df2 100644
--- a/llvm/unittests/Telemetry/TelemetryTest.cpp
+++ b/llvm/unittests/Telemetry/TelemetryTest.cpp
@@ -219,7 +219,7 @@ class StringDestination : public Destination {
StringDestination(bool ShouldSanitize, std::string &Buf)
: ShouldSanitize(ShouldSanitize), OS(Buf) {}
- Error emitEntry(const TelemetryInfo *Entry) override {
+ Error receiveEntry(const TelemetryInfo *Entry) override {
if (isa<VendorCommonTelemetryInfo>(Entry)) {
if (auto *E = dyn_cast<VendorCommonTelemetryInfo>(Entry)) {
if (ShouldSanitize) {
@@ -268,7 +268,7 @@ class JsonStreamDestination : public Destination {
JsonStreamDestination(bool ShouldSanitize, TestContext *Ctxt)
: ShouldSanitize(ShouldSanitize), CurrentContext(Ctxt) {}
- Error emitEntry(const TelemetryInfo *Entry) override {
+ Error receiveEntry(const TelemetryInfo *Entry) override {
if (auto *E = dyn_cast<VendorCommonTelemetryInfo>(Entry)) {
if (ShouldSanitize) {
if (isa<StartupEvent>(E) || isa<ExitEvent>(E)) {
@@ -405,7 +405,7 @@ class TestManager : public Manager {
private:
void emitToDestinations(TelemetryInfo *Entry) {
for (const auto &Dest : Destinations) {
- llvm::Error err = Dest->emitEntry(Entry);
+ llvm::Error err = Dest->receiveEntry(Entry);
if (err) {
// Log it and move on.
}
>From dffeacd19cb42e2b77a4cbc2bfdc9670f2b9a5af Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Tue, 26 Nov 2024 20:54:00 -0500
Subject: [PATCH 45/61] Addressed review comments: - reworked serialization
interface (ie., use a Serializer) - updated tests - fix styling issues
---
llvm/include/llvm/Telemetry/Telemetry.h | 30 +-
llvm/lib/Telemetry/Telemetry.cpp | 8 +-
llvm/unittests/Telemetry/TelemetryTest.cpp | 737 ++++++---------------
3 files changed, 211 insertions(+), 564 deletions(-)
diff --git a/llvm/include/llvm/Telemetry/Telemetry.h b/llvm/include/llvm/Telemetry/Telemetry.h
index ad12cb635f85a0..fb4a204ff5f68f 100644
--- a/llvm/include/llvm/Telemetry/Telemetry.h
+++ b/llvm/include/llvm/Telemetry/Telemetry.h
@@ -18,8 +18,6 @@
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/JSON.h"
-#include <chrono>
-#include <ctime>
#include <memory>
#include <optional>
#include <string>
@@ -27,6 +25,18 @@
namespace llvm {
namespace telemetry {
+class Serializer {
+public:
+ virtual llvm::Error start() = 0;
+ virtual void writeBool(StringRef KeyName, bool Value) = 0;
+ virtual void writeInt32(StringRef KeyName, int Value) = 0;
+ virtual void writeSizeT(StringRef KeyName, size_t Value) = 0;
+ virtual void writeString(StringRef KeyName, StringRef Value) = 0;
+ virtual void writeKeyValueMap(StringRef KeyName,
+ std::map<std::string, std::string> Value) = 0;
+ virtual llvm::Error finish() = 0;
+};
+
/// Configuration for the Telemeter class.
/// This stores configurations from both users and vendors and is passed
/// to the Telemeter upon construction. (Any changes to the config after
@@ -36,7 +46,10 @@ namespace telemetry {
/// points specific to a vendor's implementation.
struct Config {
// If true, telemetry will be enabled.
- bool EnableTelemetry;
+ const bool EnableTelemetry;
+ Config(bool E) : EnableTelemetry(E) {}
+
+ virtual std::string makeSessionId() { return "0"; }
};
/// For isa, dyn_cast, etc operations on TelemetryInfo.
@@ -74,7 +87,7 @@ struct TelemetryInfo {
TelemetryInfo() = default;
virtual ~TelemetryInfo() = default;
- virtual json::Object serializeToJson() const;
+ virtual void serialize(Serializer &serializer) const;
// For isa, dyn_cast, etc, operations.
virtual KindType getKind() const { return EntryKind::Base; }
@@ -104,11 +117,10 @@ class Destination {
/// monitored and transmitting the data elsewhere.
class Manager {
public:
- // Invoked upon tool startup
- virtual void atStartup(llvm::StringRef ToolPath, TelemetryInfo *Entry) = 0;
-
- // Invoked upon tool exit.
- virtual void atExit(llvm::StringRef ToolPath, TelemetryInfo *Entry) = 0;
+ // Dispatch Telemetry data to the Destination(s).
+ // This is non-const because the Manager may add or remove
+ // data from the entry.
+ virtual Error dispatch(TelemetryInfo *Entry) = 0;
virtual void addDestination(std::unique_ptr<Destination> Destination) = 0;
};
diff --git a/llvm/lib/Telemetry/Telemetry.cpp b/llvm/lib/Telemetry/Telemetry.cpp
index b05a7483c7408c..b7ee3c2bb1778b 100644
--- a/llvm/lib/Telemetry/Telemetry.cpp
+++ b/llvm/lib/Telemetry/Telemetry.cpp
@@ -3,11 +3,9 @@
namespace llvm {
namespace telemetry {
-llvm::json::Object TelemetryInfo::serializeToJson() const {
- return json::Object{
- {"SessionId", SessionId},
- };
-};
+void TelemetryInfo::serialize(Serializer &serializer) const {
+ serializer.writeString("SessionId", SessionId);
+}
} // namespace telemetry
} // namespace llvm
diff --git a/llvm/unittests/Telemetry/TelemetryTest.cpp b/llvm/unittests/Telemetry/TelemetryTest.cpp
index 48062874940df2..b509456e287fff 100644
--- a/llvm/unittests/Telemetry/TelemetryTest.cpp
+++ b/llvm/unittests/Telemetry/TelemetryTest.cpp
@@ -23,651 +23,288 @@
#include <ctime>
#include <vector>
+#include <iostream>
+
+namespace llvm {
+namespace telemetry {
// 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;
+ // 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;
- // These two fields data emitted by the framework for later
+ // These two fields contain data emitted by the framework for later
// verifications by the tests.
std::string Buffer = "";
- std::vector<llvm::json::Object> EmittedJsons;
+ std::vector<json::Object> EmittedJsons;
// The expected Uuid generated by the fake tool.
std::string ExpectedUuid = "";
};
-namespace llvm {
-namespace telemetry {
-namespace vendor_code {
-
-// Generate unique (but deterministic "uuid" for testing purposes).
-static std::string nextUuid() {
- static int seed = 1111;
- return std::to_string(seed++);
-}
-
-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) {}
-};
+class JsonSerializer : public Serializer {
+public:
+ json::Object *getOutputObject() { return object.get(); }
-// 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;
+ llvm::Error start() override {
+ if (started)
+ return createStringError("Serializer already in use");
+ started = true;
+ object = std::make_unique<json::Object>();
+ return Error::success();
}
- KindType getKind() const override { return VendorEntryKind::VendorCommon; }
-
- virtual void serializeToStream(llvm::raw_ostream &OS) const = 0;
-
- std::optional<ExitDescription> ExitDesc;
- EventStats Stats;
- size_t Counter;
-};
-
-struct StartupEvent : public VendorCommonTelemetryInfo {
- std::string MagicStartupMsg;
+ void writeBool(StringRef KeyName, bool Value) override {
+ writeHelper(KeyName, Value);
+ }
- StartupEvent() = default;
- StartupEvent(const StartupEvent &E) = default;
+ void writeInt32(StringRef KeyName, int Value) override {
+ writeHelper(KeyName, Value);
+ }
- static bool classof(const TelemetryInfo *T) {
- if (T == nullptr)
- return false;
- return T->getKind() == VendorEntryKind::Startup;
+ void writeSizeT(StringRef KeyName, size_t Value) override {
+ writeHelper(KeyName, Value);
+ }
+ void writeString(StringRef KeyName, StringRef Value) override {
+ writeHelper(KeyName, Value);
}
- KindType getKind() const override { return VendorEntryKind::Startup; }
+ void writeKeyValueMap(StringRef KeyName,
+ std::map<std::string, std::string> Value) override {
+ json::Object Inner;
+ for (auto kv : Value) {
+ Inner.try_emplace(kv.first, kv.second);
+ }
+ writeHelper(KeyName, json::Value(std::move(Inner)));
+ }
- void serializeToStream(llvm::raw_ostream &OS) const override {
- OS << "SessionId:" << SessionId << "\n";
- OS << "MagicStartupMsg:" << MagicStartupMsg << "\n";
+ llvm::Error finish() override {
+ if (!started)
+ return createStringError("Serializer not currently in use");
+ started = false;
+ return Error::success();
}
- json::Object serializeToJson() const override {
- return json::Object{
- {"Startup",
- {{"SessionId", SessionId}, {"MagicStartupMsg", MagicStartupMsg}}},
- };
+private:
+ template <typename T> void writeHelper(StringRef Name, T Value) {
+ assert(started && "serializer not started");
+ object->try_emplace(Name, Value);
}
+ bool started = false;
+ std::unique_ptr<json::Object> object;
};
-struct ExitEvent : public VendorCommonTelemetryInfo {
- std::string MagicExitMsg;
+class StringSerializer : public Serializer {
+public:
+ const std::string &getString() { return Buffer; }
- ExitEvent() = default;
- // Provide a copy ctor because we may need to make a copy
- // before sanitizing the Entry.
- ExitEvent(const ExitEvent &E) = default;
+ llvm::Error start() override {
+ if (started)
+ return createStringError("Serializer already in use");
+ started = true;
+ Buffer.clear();
+ return Error::success();
+ }
- static bool classof(const TelemetryInfo *T) {
- if (T == nullptr)
- return false;
- return T->getKind() == VendorEntryKind::Exit;
+ void writeBool(StringRef KeyName, bool Value) override {
+ writeHelper(KeyName, Value);
}
- unsigned getKind() const override { return VendorEntryKind::Exit; }
+ void writeInt32(StringRef KeyName, int Value) override {
+ writeHelper(KeyName, Value);
+ }
- void serializeToStream(llvm::raw_ostream &OS) const override {
- OS << "SessionId:" << SessionId << "\n";
- if (ExitDesc.has_value())
- OS << "ExitCode:" << ExitDesc->ExitCode << "\n";
- OS << "MagicExitMsg:" << MagicExitMsg << "\n";
+ void writeSizeT(StringRef KeyName, size_t Value) override {
+ writeHelper(KeyName, Value);
+ }
+ void writeString(StringRef KeyName, StringRef Value) override {
+ assert(started && "serializer not started");
}
- json::Object serializeToJson() const override {
- json::Array I = json::Array{
- {"SessionId", SessionId},
- {"MagicExitMsg", MagicExitMsg},
- };
- if (ExitDesc.has_value())
- I.push_back(json::Value({"ExitCode", ExitDesc->ExitCode}));
- return json::Object{
- {"Exit", std::move(I)},
- };
+ void writeKeyValueMap(StringRef KeyName,
+ std::map<std::string, std::string> Value) override {
+ std::string Inner;
+ for (auto kv : Value) {
+ writeHelper(StringRef(kv.first), StringRef(kv.second), &Inner);
+ }
+ writeHelper(KeyName, StringRef(Inner));
}
-};
-struct CustomTelemetryEvent : public VendorCommonTelemetryInfo {
- std::vector<std::string> Msgs;
+ llvm::Error finish() override {
+ if (!started)
+ return createStringError("Serializer not currently in use");
+ started = false;
+ return Error::success();
+ }
- CustomTelemetryEvent() = default;
- CustomTelemetryEvent(const CustomTelemetryEvent &E) = default;
+private:
+ template <typename T>
+ void writeHelper(StringRef Name, T Value, std::string *Buff) {
+ assert(started && "serializer not started");
+ Buff->append((Name + ":" + llvm::Twine(Value) + "\n").str());
+ }
- void serializeToStream(llvm::raw_ostream &OS) const override {
- OS << "SessionId:" << SessionId << "\n";
- int I = 0;
- for (const std::string &M : Msgs) {
- OS << "MSG_" << I << ":" << M << "\n";
- ++I;
- }
+ template <typename T> void writeHelper(StringRef Name, T Value) {
+ writeHelper(Name, Value, &Buffer);
}
- json::Object serializeToJson() const override {
- json::Array Inner{{"SessionId", SessionId}};
- int I = 0;
- for (const std::string &M : Msgs) {
- Inner.push_back(json::Value({("MSG_" + llvm::Twine(I)).str(), M}));
- ++I;
- }
+ bool started = false;
+ std::string Buffer;
+};
- return json::Object{{"Midpoint", std::move(Inner)}};
+namespace vendor {
+struct VendorConfig : public Config {
+ VendorConfig(bool Enable) : Config(Enable) {}
+ std::string makeSessionId() override {
+ static int seed = 0;
+ return std::to_string(seed++);
}
};
-// The following classes demonstrate how downstream code can
-// define one or more custom Destination(s) to handle
-// Telemetry data differently, specifically:
-// + which data to send (fullset or sanitized)
-// + where to send the data
-// + in what form
-
-static constexpr llvm::StringLiteral STRING_DEST("STRING");
-static constexpr llvm::StringLiteral JSON_DEST("JSON");
+std::shared_ptr<Config> GetTelemetryConfig(const TestContext &Ctxt) {
+ return std::make_shared<VendorConfig>(/*EnableTelemetry*/ true);
+}
-// This Destination sends data to a std::string given at ctor.
-class StringDestination : public Destination {
+class JsonStorageDestination : public Destination {
public:
- // ShouldSanitize: if true, sanitize the data before emitting, otherwise, emit
- // the full set.
- StringDestination(bool ShouldSanitize, std::string &Buf)
- : ShouldSanitize(ShouldSanitize), OS(Buf) {}
+ JsonStorageDestination(TestContext *Ctxt) : CurrentContext(Ctxt) {}
Error receiveEntry(const TelemetryInfo *Entry) override {
- if (isa<VendorCommonTelemetryInfo>(Entry)) {
- if (auto *E = dyn_cast<VendorCommonTelemetryInfo>(Entry)) {
- if (ShouldSanitize) {
- if (isa<StartupEvent>(E) || isa<ExitEvent>(E)) {
- // There is nothing to sanitize for this type of data, so keep
- // as-is.
- E->serializeToStream(OS);
- } else if (isa<CustomTelemetryEvent>(E)) {
- auto Sanitized = sanitizeFields(dyn_cast<CustomTelemetryEvent>(E));
- Sanitized.serializeToStream(OS);
- } else {
- llvm_unreachable("unexpected type");
- }
- } else {
- E->serializeToStream(OS);
- }
- }
- } else {
- // Unfamiliar entries, just send the entry's UUID
- OS << "SessionId:" << Entry->SessionId << "\n";
+ if (Error err = serializer.start()) {
+ return err;
}
+ Entry->serialize(serializer);
+ if (Error err = serializer.finish()) {
+ return err;
+ }
+
+ json::Object copied = *serializer.getOutputObject();
+ CurrentContext->EmittedJsons.push_back(std::move(copied));
+
return Error::success();
}
- llvm::StringLiteral name() const override { return STRING_DEST; }
+ llvm::StringLiteral name() const override { return "JsonDestination"; }
private:
- // Returns a copy of the given entry, but with some fields sanitized.
- CustomTelemetryEvent sanitizeFields(const CustomTelemetryEvent *Entry) {
- CustomTelemetryEvent Sanitized(*Entry);
- // Pretend that messages stored at ODD positions are "sensitive",
- // hence need to be sanitized away.
- int S = Sanitized.Msgs.size() - 1;
- for (int I = S % 2 == 0 ? S - 1 : S; I >= 0; I -= 2)
- Sanitized.Msgs[I] = "";
- return Sanitized;
- }
-
- bool ShouldSanitize;
- llvm::raw_string_ostream OS;
+ TestContext *CurrentContext;
+ JsonSerializer serializer;
};
-// This Destination sends data to some "blackbox" in form of JSON.
-class JsonStreamDestination : public Destination {
-public:
- JsonStreamDestination(bool ShouldSanitize, TestContext *Ctxt)
- : ShouldSanitize(ShouldSanitize), CurrentContext(Ctxt) {}
-
- Error receiveEntry(const TelemetryInfo *Entry) override {
- if (auto *E = dyn_cast<VendorCommonTelemetryInfo>(Entry)) {
- if (ShouldSanitize) {
- if (isa<StartupEvent>(E) || isa<ExitEvent>(E)) {
- // There is nothing to sanitize for this type of data, so keep as-is.
- return SendToBlackbox(E->serializeToJson());
- }
- if (isa<CustomTelemetryEvent>(E)) {
- auto Sanitized = sanitizeFields(dyn_cast<CustomTelemetryEvent>(E));
- return SendToBlackbox(Sanitized.serializeToJson());
- }
- llvm_unreachable("unexpected type");
- }
- return SendToBlackbox(E->serializeToJson());
- }
- // Unfamiliar entries, just send the entry's ID
- return SendToBlackbox(json::Object{{"SessionId", Entry->SessionId}});
- }
-
- llvm::StringLiteral name() const override { return JSON_DEST; }
+struct StartupInfo : public TelemetryInfo {
+ std::string ToolName;
-private:
- // Returns a copy of the given entry, but with some fields sanitized.
- CustomTelemetryEvent sanitizeFields(const CustomTelemetryEvent *Entry) {
- CustomTelemetryEvent Sanitized(*Entry);
- // Pretend that messages stored at EVEN positions are "sensitive",
- // hence need to be sanitized away.
- int S = Sanitized.Msgs.size() - 1;
- for (int I = S % 2 == 0 ? S : S - 1; I >= 0; I -= 2)
- Sanitized.Msgs[I] = "";
-
- return Sanitized;
+ void serialize(Serializer &serializer) const override {
+ TelemetryInfo::serialize(serializer);
+ serializer.writeString("ToolName", ToolName);
}
+};
- llvm::Error SendToBlackbox(json::Object O) {
- // Here is where the vendor-defined Destination class can
- // send the data to some internal storage.
- // For testing purposes, we just queue up the entries to
- // the vector for validation.
- CurrentContext->EmittedJsons.push_back(std::move(O));
- return Error::success();
+struct ExitInfo : public TelemetryInfo {
+ int ExitCode;
+ std::string ExitDesc;
+ void serialize(Serializer &serializer) const override {
+ TelemetryInfo::serialize(serializer);
+ serializer.writeInt32("ExitCode", ExitCode);
+ serializer.writeString("ExitDesc", ExitDesc);
}
- bool ShouldSanitize;
- TestContext *CurrentContext;
};
-// Custom vendor-defined Manager that has additional data-collection point.
class TestManager : public Manager {
public:
- TestManager(std::string SessionId, TestContext *Ctxt)
- : CurrentContext(Ctxt), Uuid(SessionId), Counter(0) {}
-
static std::unique_ptr<TestManager>
createInstance(Config *config, TestContext *CurrentContext) {
if (!config->EnableTelemetry)
return nullptr;
- CurrentContext->ExpectedUuid = nextUuid();
- std::unique_ptr<TestManager> Manager = std::make_unique<TestManager>(
- CurrentContext->ExpectedUuid, CurrentContext);
- // Set up Destination based on the given config.
- for (const std::string &Dest : config->AdditionalDestinations) {
- // The destination(s) are ALSO defined by vendor, so it should understand
- // what the name of each destination signifies.
- if (llvm::StringRef(Dest) == JSON_DEST) {
- Manager->addDestination(
- std::make_unique<vendor_code::JsonStreamDestination>(
- CurrentContext->SanitizeData, CurrentContext));
- } else if (llvm::StringRef(Dest) == STRING_DEST) {
- Manager->addDestination(
- std::make_unique<vendor_code::StringDestination>(
- CurrentContext->SanitizeData, CurrentContext->Buffer));
- } else {
- llvm_unreachable(
- llvm::Twine("unknown destination: ", Dest).str().c_str());
- }
- }
- return Manager;
- }
+ CurrentContext->ExpectedUuid = config->makeSessionId();
+ std::unique_ptr<TestManager> Ret = std::make_unique<TestManager>(
+ CurrentContext, CurrentContext->ExpectedUuid);
- void atStartup(llvm::StringRef ToolPath, TelemetryInfo *Entry) override {
- ToolName = ToolPath.str();
+ // Add a few destinations.
+ Ret->addDestination(
+ std::make_unique<JsonStorageDestination>(CurrentContext));
- // The vendor can add additional stuff to the entry before logging.
- if (auto *S = dyn_cast<StartupEvent>(Entry)) {
- S->MagicStartupMsg = llvm::Twine("Startup_", ToolPath).str();
- }
- emitToDestinations(Entry);
+ return Ret;
}
- void atExit(llvm::StringRef ToolPath, TelemetryInfo *Entry) override {
- // Ensure we're shutting down the same tool we started with.
- if (ToolPath != ToolName) {
- std::string Str;
- raw_string_ostream OS(Str);
- OS << "Expected tool with name" << ToolName << ", but got " << ToolPath;
- llvm_unreachable(Str.c_str());
- }
+ TestManager(TestContext *Ctxt, std::string Id)
+ : CurrentContext(Ctxt), SessionId(Id) {}
- // The vendor can add additional stuff to the entry before logging.
- if (auto *E = dyn_cast<ExitEvent>(Entry)) {
- E->MagicExitMsg = llvm::Twine("Exit_", ToolPath).str();
+ Error dispatch(TelemetryInfo *Entry) override {
+ Entry->SessionId = SessionId;
+ for (auto &Dest : Destinations) {
+ if (Error err = Dest->receiveEntry(Entry)) {
+ return err;
+ }
}
-
- emitToDestinations(Entry);
+ return Error::success();
}
void addDestination(std::unique_ptr<Destination> Dest) override {
Destinations.push_back(std::move(Dest));
}
- void atMidpoint(TelemetryInfo *Entry) {
- // The custom Manager can record and send additional data.
- if (auto *C = dyn_cast<CustomTelemetryEvent>(Entry)) {
- C->Msgs.push_back("Two");
- C->Msgs.push_back("Deux");
- C->Msgs.push_back("Zwei");
- }
-
- emitToDestinations(Entry);
- }
-
- const std::string &getUuid() const { return Uuid; }
+ std::string getSessionId() { return SessionId; }
- ~TestManager() = default;
+ Error atStartup(StartupInfo *Info) { return dispatch(Info); }
- template <typename T> T makeDefaultTelemetryInfo() {
- T Ret;
- Ret.SessionId = Uuid;
- Ret.Counter = Counter++;
- return Ret;
- }
-
- TestContext *CurrentContext = nullptr;
+ Error atExit(ExitInfo *Info) { return dispatch(Info); }
private:
- void emitToDestinations(TelemetryInfo *Entry) {
- for (const auto &Dest : Destinations) {
- llvm::Error err = Dest->receiveEntry(Entry);
- if (err) {
- // Log it and move on.
- }
- }
- }
-
- const std::string Uuid;
- size_t Counter;
- std::string ToolName;
+ TestContext *CurrentContext;
+ const std::string SessionId;
std::vector<std::unique_ptr<Destination>> Destinations;
};
-// Pretend to be a "weakly" defined vendor-specific function.
-void ApplyVendorSpecificConfigs(Config *config) {
- config->EnableTelemetry = true;
-}
-
-} // namespace vendor_code
-} // namespace telemetry
-} // namespace llvm
-
-namespace {
-
-void ApplyCommonConfig(llvm::telemetry::Config *config) {
- // Any shareable configs for the upstream tool can go here.
- // .....
-}
-
-std::shared_ptr<llvm::telemetry::Config>
-GetTelemetryConfig(TestContext *CurrentContext) {
- // Telemetry is disabled by default.
- // The vendor can enable in their config.
- auto Config = std::make_shared<llvm::telemetry::Config>();
- Config->EnableTelemetry = false;
-
- ApplyCommonConfig(Config.get());
-
- // Apply vendor specific config, if present.
- // In principle, this would be a build-time param, configured by the vendor.
- // Eg:
- //
- // #ifdef HAS_VENDOR_TELEMETRY_CONFIG
- // llvm::telemetry::vendor_code::ApplyVendorSpecificConfigs(config.get());
- // #endif
- //
- // But for unit testing, we use the testing params defined at the top.
- if (CurrentContext->HasVendorConfig) {
- llvm::telemetry::vendor_code::ApplyVendorSpecificConfigs(Config.get());
- }
- return Config;
-}
+} // namespace vendor
-using namespace llvm;
-using namespace llvm::telemetry;
+std::shared_ptr<Config> GetTelemetryConfig(const TestContext &Ctxt) {
+ if (Ctxt.HasVendorPlugin)
+ return vendor::GetTelemetryConfig(Ctxt);
-// For deterministic tests, pre-defined certain important time-points
-// rather than using now().
-//
-// Preset StartTime to EPOCH.
-auto StartTime = std::chrono::time_point<std::chrono::steady_clock>{};
-// Pretend the time it takes for the tool's initialization is EPOCH + 5
-// milliseconds
-auto InitCompleteTime = StartTime + std::chrono::milliseconds(5);
-auto MidPointTime = StartTime + std::chrono::milliseconds(10);
-auto MidPointCompleteTime = MidPointTime + std::chrono::milliseconds(5);
-// Preset ExitTime to EPOCH + 20 milliseconds
-auto ExitTime = StartTime + std::chrono::milliseconds(20);
-// Pretend the time it takes to complete tearing down the tool is 10
-// milliseconds.
-auto ExitCompleteTime = ExitTime + std::chrono::milliseconds(10);
-
-void AtToolStart(std::string ToolName, vendor_code::TestManager *T) {
- vendor_code::StartupEvent Entry =
- T->makeDefaultTelemetryInfo<vendor_code::StartupEvent>();
- Entry.Stats = {StartTime, InitCompleteTime};
- T->atStartup(ToolName, &Entry);
-}
-
-void AtToolExit(std::string ToolName, vendor_code::TestManager *T) {
- vendor_code::ExitEvent Entry =
- T->makeDefaultTelemetryInfo<vendor_code::ExitEvent>();
- Entry.Stats = {ExitTime, ExitCompleteTime};
-
- if (T->CurrentContext->HasExitError) {
- Entry.ExitDesc = {1, T->CurrentContext->ExitMsg};
- }
- T->atExit(ToolName, &Entry);
-}
-
-void AtToolMidPoint(vendor_code::TestManager *T) {
- vendor_code::CustomTelemetryEvent Entry =
- T->makeDefaultTelemetryInfo<vendor_code::CustomTelemetryEvent>();
- Entry.Stats = {MidPointTime, MidPointCompleteTime};
- T->atMidpoint(&Entry);
-}
-
-// Without vendor's implementation, telemetry is not enabled by default.
-TEST(TelemetryTest, TelemetryDefault) {
- // Preset some test params.
- TestContext Context;
- Context.HasVendorConfig = false;
- TestContext *CurrentContext = &Context;
-
- std::shared_ptr<llvm::telemetry::Config> Config =
- GetTelemetryConfig(CurrentContext);
- auto Tool =
- vendor_code::TestManager::createInstance(Config.get(), CurrentContext);
-
- EXPECT_EQ(nullptr, Tool.get());
+ return std::make_shared<Config>(false);
}
TEST(TelemetryTest, TelemetryEnabled) {
- const std::string ToolName = "TelemetryTest";
-
- // Preset some test params.
- TestContext Context;
- Context.HasVendorConfig = true;
- Context.SanitizeData = false;
- Context.Buffer.clear();
- Context.EmittedJsons.clear();
- TestContext *CurrentContext = &Context;
-
- std::shared_ptr<llvm::telemetry::Config> Config =
- GetTelemetryConfig(CurrentContext);
+ const std::string ToolName = "TelemetryTestTool";
- // Add some destinations
- Config->AdditionalDestinations.push_back(vendor_code::STRING_DEST.str());
- Config->AdditionalDestinations.push_back(vendor_code::JSON_DEST.str());
-
- auto Tool =
- vendor_code::TestManager::createInstance(Config.get(), CurrentContext);
-
- AtToolStart(ToolName, Tool.get());
- AtToolMidPoint(Tool.get());
- AtToolExit(ToolName, Tool.get());
-
- // Check that the Tool uses the expected UUID.
- EXPECT_STREQ(Tool->getUuid().c_str(), CurrentContext->ExpectedUuid.c_str());
-
- // Check that the StringDestination emitted properly
- {
- std::string ExpectedBuffer =
- ("SessionId:" + llvm::Twine(CurrentContext->ExpectedUuid) + "\n" +
- "MagicStartupMsg:Startup_" + llvm::Twine(ToolName) + "\n" +
- "SessionId:" + llvm::Twine(CurrentContext->ExpectedUuid) + "\n" +
- "MSG_0:Two\n" + "MSG_1:Deux\n" + "MSG_2:Zwei\n" +
- "SessionId:" + llvm::Twine(CurrentContext->ExpectedUuid) + "\n" +
- "MagicExitMsg:Exit_" + llvm::Twine(ToolName) + "\n")
- .str();
-
- EXPECT_STREQ(ExpectedBuffer.c_str(), CurrentContext->Buffer.c_str());
- }
-
- // Check that the JsonDestination emitted properly
- {
-
- // There should be 3 events emitted by the Manager (start, midpoint, exit)
- EXPECT_EQ(static_cast<size_t>(3), CurrentContext->EmittedJsons.size());
-
- const json::Value *StartupEntry =
- CurrentContext->EmittedJsons[0].get("Startup");
- ASSERT_NE(StartupEntry, nullptr);
- json::Value ExpectedStartup({
- {"SessionId", CurrentContext->ExpectedUuid},
- {"MagicStartupMsg", ("Startup_" + llvm::Twine(ToolName)).str()},
- });
- EXPECT_EQ(ExpectedStartup, *StartupEntry);
-
- const json::Value *MidpointEntry =
- CurrentContext->EmittedJsons[1].get("Midpoint");
- ASSERT_NE(MidpointEntry, nullptr);
- json::Value ExpectedMidpoint{{"SessionId", CurrentContext->ExpectedUuid},
- {"MSG_0", "Two"},
- {"MSG_1", "Deux"},
- {"MSG_2", "Zwei"}};
- EXPECT_EQ(ExpectedMidpoint, *MidpointEntry);
-
- const json::Value *ExitEntry = CurrentContext->EmittedJsons[2].get("Exit");
- ASSERT_NE(ExitEntry, nullptr);
- json::Value ExpectedExit({
- {"SessionId", CurrentContext->ExpectedUuid},
- {"MagicExitMsg", ("Exit_" + llvm::Twine(ToolName)).str()},
- });
- EXPECT_EQ(ExpectedExit, *ExitEntry);
- }
-}
-
-// Similar to previous tests, but toggling the data-sanitization option ON.
-// The recorded data should have some fields removed.
-TEST(TelemetryTest, TelemetryEnabledSanitizeData) {
- const std::string ToolName = "TelemetryTest_SanitizedData";
-
- // Preset some test params.
+ // Preset some params
TestContext Context;
- Context.HasVendorConfig = true;
- Context.SanitizeData = true;
+ Context.HasVendorPlugin = true;
Context.Buffer.clear();
Context.EmittedJsons.clear();
- TestContext *CurrentContext = &Context;
- std::shared_ptr<llvm::telemetry::Config> Config =
- GetTelemetryConfig(CurrentContext);
-
- // Add some destinations
- Config->AdditionalDestinations.push_back(vendor_code::STRING_DEST.str());
- Config->AdditionalDestinations.push_back(vendor_code::JSON_DEST.str());
-
- auto Tool =
- vendor_code::TestManager::createInstance(Config.get(), CurrentContext);
-
- AtToolStart(ToolName, Tool.get());
- AtToolMidPoint(Tool.get());
- AtToolExit(ToolName, Tool.get());
-
- // Check that the StringDestination emitted properly
- {
- // The StringDestination should have removed the odd-positioned msgs.
- std::string ExpectedBuffer =
- ("SessionId:" + llvm::Twine(CurrentContext->ExpectedUuid) + "\n" +
- "MagicStartupMsg:Startup_" + llvm::Twine(ToolName) + "\n" +
- "SessionId:" + llvm::Twine(CurrentContext->ExpectedUuid) + "\n" +
- "MSG_0:Two\n" + "MSG_1:\n" + // <<< was sanitized away.
- "MSG_2:Zwei\n" +
- "SessionId:" + llvm::Twine(CurrentContext->ExpectedUuid) + "\n" +
- "MagicExitMsg:Exit_" + llvm::Twine(ToolName) + "\n")
- .str();
- EXPECT_STREQ(ExpectedBuffer.c_str(), CurrentContext->Buffer.c_str());
- }
-
- // Check that the JsonDestination emitted properly
- {
-
- // There should be 3 events emitted by the Manager (start, midpoint, exit)
- EXPECT_EQ(static_cast<size_t>(3), CurrentContext->EmittedJsons.size());
-
- const json::Value *StartupEntry =
- CurrentContext->EmittedJsons[0].get("Startup");
- ASSERT_NE(StartupEntry, nullptr);
- json::Value ExpectedStartup({
- {"SessionId", CurrentContext->ExpectedUuid},
- {"MagicStartupMsg", ("Startup_" + llvm::Twine(ToolName)).str()},
- });
- EXPECT_EQ(ExpectedStartup, *StartupEntry);
-
- const json::Value *MidpointEntry =
- CurrentContext->EmittedJsons[1].get("Midpoint");
- ASSERT_NE(MidpointEntry, nullptr);
- // The JsonDestination should have removed the even-positioned msgs.
- json::Value ExpectedMidpoint{{"SessionId", CurrentContext->ExpectedUuid},
- {"MSG_0", ""},
- {"MSG_1", "Deux"},
- {"MSG_2", ""}};
- EXPECT_EQ(ExpectedMidpoint, *MidpointEntry);
-
- const json::Value *ExitEntry = CurrentContext->EmittedJsons[2].get("Exit");
- ASSERT_NE(ExitEntry, nullptr);
- json::Value ExpectedExit({
- {"SessionId", CurrentContext->ExpectedUuid},
- {"MagicExitMsg", ("Exit_" + llvm::Twine(ToolName)).str()},
- });
- EXPECT_EQ(ExpectedExit, *ExitEntry);
- }
+ 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;
+
+ Error startupEmitStatus = Manager->atStartup(&S);
+ EXPECT_FALSE(startupEmitStatus);
+ const json::Object &StartupEntry = Context.EmittedJsons[0];
+ json::Object ExpectedStartup(
+ {{"SessionId", Context.ExpectedUuid}, {"ToolName", ToolName}});
+ EXPECT_EQ(ExpectedStartup, StartupEntry);
+
+ vendor::ExitInfo E;
+ E.ExitCode = 0;
+ E.ExitDesc = "success";
+ Error exitEmitStatus = Manager->atExit(&E);
+ EXPECT_FALSE(exitEmitStatus);
+ const json::Object &ExitEntry = Context.EmittedJsons[1];
+ json::Object ExpectedExit({{"SessionId", Context.ExpectedUuid},
+ {"ExitCode", 0},
+ {"ExitDesc", "success"}});
+ EXPECT_EQ(ExpectedExit, ExitEntry);
}
-} // namespace
+} // namespace telemetry
+} // namespace llvm
>From 398ed3e37d656bc998684fba6c69f643ed10dfd9 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Tue, 26 Nov 2024 21:11:35 -0500
Subject: [PATCH 46/61] updated doc
---
llvm/docs/Telemetry.rst | 192 +++++++++++++++++++++-------------------
1 file changed, 101 insertions(+), 91 deletions(-)
diff --git a/llvm/docs/Telemetry.rst b/llvm/docs/Telemetry.rst
index b7610b0400333d..dce1444ce273df 100644
--- a/llvm/docs/Telemetry.rst
+++ b/llvm/docs/Telemetry.rst
@@ -87,89 +87,113 @@ How to implement and interact with the API
To use Telemetry in your tool, you need to provide a concrete implementation of the ``Manager`` class and ``Destination``.
-1) Define a custom ``Manager`` and ``Destination``
+1) Define a custom ``Serializer``, ``Manager`` and ``Destination``
.. code-block:: c++
- // This destination just prints the given entry to stdout.
- // In "real life", this would be where you forward the data to your
- // custom data storage.
- class MyStdoutDestination : public llvm::telemetry::Destination {
- public:
- Error emitEntry(const TelemetryInfo* Entry) override {
- return sendToBlackBox(Entry);
- }
-
- private:
- Error sendToBlackBox(const TelemetryInfo* Entry) {
- // This could send the data anywhere, but we're simply sending it to
- // stdout for the example.
- llvm::outs() << entryToString(Entry) << "\n";
- return llvm::success();
- }
-
- std::string entryToString(const TelemetryInfo* Entry) {
- // make a string-representation of the given entry.
- }
- };
+class JsonSerializer : public Serializer {
+public:
+ json::Object *getOutputObject() { return object.get(); }
+
+ llvm::Error start() override {
+ if (started)
+ return createStringError("Serializer already in use");
+ started = true;
+ object = std::make_unique<json::Object>();
+ return Error::success();
+ }
+
+ void writeBool(StringRef KeyName, bool Value) override {
+ writeHelper(KeyName, Value);
+ }
+
+ void writeInt32(StringRef KeyName, int Value) override {
+ writeHelper(KeyName, Value);
+ }
+
+ void writeSizeT(StringRef KeyName, size_t Value) override {
+ writeHelper(KeyName, Value);
+ }
+ void writeString(StringRef KeyName, StringRef Value) override {
+ writeHelper(KeyName, Value);
+ }
+
+ void writeKeyValueMap(StringRef KeyName,
+ std::map<std::string, std::string> Value) override {
+ json::Object Inner;
+ for (auto kv : Value) {
+ Inner.try_emplace(kv.first, kv.second);
+ }
+ writeHelper(KeyName, json::Value(std::move(Inner)));
+ }
+
+ llvm::Error finish() 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");
+ object->try_emplace(Name, Value);
+ }
+ bool started = false;
+ std::unique_ptr<json::Object> object;
+};
- // This defines a custom TelemetryInfo that has an addition Msg field.
- struct MyTelemetryInfo : public telemetry::TelemetryInfo {
- std::string Msg;
-
- json::Object serializeToJson() oconst {
- json::Object Ret = ManageryInfo::serializeToJson();
- Ret.emplace_back("MyMsg", Msg);
- return std::move(Ret);
- }
+// This defines a custom TelemetryInfo that has an addition Msg field.
+struct MyTelemetryInfo : public telemetry::TelemetryInfo {
+ std::string Msg;
+
+ Error serialize(Serializer& serializer) const override {
+ TelemetryInfo::serialize(serializer);
+ serializer.writeString("MyMsg", Msg);
+ }
- // TODO: implement getKind() and classof() to support dyn_cast operations.
- };
+ // Note: implement getKind() and classof() to support dyn_cast operations.
+};
- 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;
-
- std::make_unique<MyManager>();
- }
- MyManager() = default;
+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 dispatch(TelemetryInfo* Entry) const override {
+ Entry->SessionId = SessionId;
+ emitToAllDestinations(Entry);
+}
+
+Error logStartup(MyTelemetryInfo* Entry) {
+ // Optionally add additional field
+ Entry->Msg = "CustomMsg";
+ return dispatch(Entry);
+
+}
- void logStartup(StringRef ToolName, TelemetryInfo* Entry) override {
- if (MyTelemetryInfo* M = dyn_cast<MyTelemetryInfo>(Entry)) {
- M->Msg = "Starting up tool with name: " + ToolName;
- emitToAllDestinations(M);
- } else {
- emitToAllDestinations(Entry);
- }
- }
+void addDestination(std::unique_ptr<Destination> dest) override {
+ destinations.push_back(std::move(dest));
+}
- void logExit(StringRef ToolName, TelemetryInfo* Entry) override {
- if (MyTelemetryInfo* M = dyn_cast<MyTelemetryInfo>(Entry)) {
- M->Msg = "Exiting tool with name: " + ToolName;
- emitToAllDestinations(M);
- } else {
- emitToAllDestinations(Entry);
- }
- }
-
- void addDestination(Destination* dest) override {
- destinations.push_back(dest);
- }
-
- // You can also define additional instrumentation points.
- void logAdditionalPoint(TelemetryInfo* Entry) {
- // .... code here
- }
- private:
- void emitToAllDestinations(const TelemetryInfo* Entry) {
- // Note: could do this in parallel, if needed.
- for (Destination* Dest : Destinations)
- Dest->emitEntry(Entry);
- }
- std::vector<Destination> Destinations;
- };
+// You can also define additional instrumentation points.
+void logAdditionalPoint(TelemetryInfo* Entry) {
+// .... code here
+}
+private:
+void emitToAllDestinations(const TelemetryInfo* Entry) {
+// Note: could do this in parallel, if needed.
+for (Destination* Dest : Destinations)
+Dest->receiveEntry(Entry);
+}
+std::vector<Destination> Destinations;
+const std::string SessionId;
+};
2) Use the library in your tool.
@@ -181,7 +205,7 @@ Logging the tool init-process:
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);
- std::string CurrentSessionId = ...; // Make some unique ID corresponding to the current session here.
+
// Any other tool's init code can go here
// ...
@@ -190,23 +214,9 @@ Logging the tool init-process:
// init process to finish
auto EndTime = std::chrono::time_point<std::chrono::steady_clock>::now();
MyTelemetryInfo Entry;
- Entry.SessionId = CurrentSessionId ; // Assign some unique ID here.
+
Entry.Stats = {StartTime, EndTime};
Manager->logStartup("MyTool", &Entry);
Similar code can be used for logging the tool's exit.
-Additionally, at any other point in the tool's lifetime, it can also log telemetry:
-
-.. code-block:: c++
-
- // At some execution point:
- auto StartTime = std::chrono::time_point<std::chrono::steady_clock>::now();
-
- // ... other events happening here
-
- auto EndTime = std::chrono::time_point<std::chrono::steady_clock>::now();
- MyTelemetryInfo Entry;
- Entry.SessionId = CurrentSessionId ; // Assign some unique ID here.
- Entry.Stats = {StartTime, EndTime};
- Manager->logAdditionalPoint(&Entry);
>From e462908c8407ff23af5ed37c99a3e708bed00908 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Wed, 4 Dec 2024 14:52:44 -0500
Subject: [PATCH 47/61] fix build warning
---
llvm/docs/Telemetry.rst | 177 +++++++++++++++++++---------------------
1 file changed, 86 insertions(+), 91 deletions(-)
diff --git a/llvm/docs/Telemetry.rst b/llvm/docs/Telemetry.rst
index dce1444ce273df..56473ffba0ea2a 100644
--- a/llvm/docs/Telemetry.rst
+++ b/llvm/docs/Telemetry.rst
@@ -91,109 +91,104 @@ To use Telemetry in your tool, you need to provide a concrete implementation of
.. code-block:: c++
-class JsonSerializer : public Serializer {
-public:
- json::Object *getOutputObject() { return object.get(); }
-
- llvm::Error start() override {
- if (started)
- return createStringError("Serializer already in use");
- started = true;
- object = std::make_unique<json::Object>();
- return Error::success();
- }
+ class JsonSerializer : public Serializer {
+ public:
+ json::Object *getOutputObject() { return object.get(); }
+
+ llvm::Error start() override {
+ if (started)
+ return createStringError("Serializer already in use");
+ started = true;
+ object = std::make_unique<json::Object>();
+ return Error::success();
+ }
- void writeBool(StringRef KeyName, bool Value) override {
- writeHelper(KeyName, Value);
- }
+ void writeBool(StringRef KeyName, bool Value) override {
+ writeHelper(KeyName, Value);
+ }
- void writeInt32(StringRef KeyName, int Value) override {
- writeHelper(KeyName, Value);
- }
+ void writeInt32(StringRef KeyName, int Value) override {
+ writeHelper(KeyName, Value);
+ }
- void writeSizeT(StringRef KeyName, size_t Value) override {
- writeHelper(KeyName, Value);
- }
- void writeString(StringRef KeyName, StringRef Value) override {
- writeHelper(KeyName, Value);
- }
+ void writeSizeT(StringRef KeyName, size_t Value) override {
+ writeHelper(KeyName, Value);
+ }
+ void writeString(StringRef KeyName, StringRef Value) override {
+ writeHelper(KeyName, Value);
+ }
- void writeKeyValueMap(StringRef KeyName,
- std::map<std::string, std::string> Value) override {
- json::Object Inner;
- for (auto kv : Value) {
- Inner.try_emplace(kv.first, kv.second);
+ void writeKeyValueMap(StringRef KeyName,
+ std::map<std::string, std::string> Value) override {
+ json::Object Inner;
+ for (auto kv : Value) {
+ Inner.try_emplace(kv.first, kv.second);
+ }
+ writeHelper(KeyName, json::Value(std::move(Inner)));
}
- writeHelper(KeyName, json::Value(std::move(Inner)));
- }
- llvm::Error finish() override {
- if (!started)
- return createStringError("Serializer not currently in use");
- started = false;
- return Error::success();
- }
+ llvm::Error finish() 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");
- object->try_emplace(Name, Value);
- }
- bool started = false;
- std::unique_ptr<json::Object> object;
-};
+ private:
+ template <typename T> void writeHelper(StringRef Name, T Value) {
+ assert(started && "serializer not started");
+ object->try_emplace(Name, Value);
+ }
+ bool started = false;
+ std::unique_ptr<json::Object> object;
+ };
+
+ // This defines a custom TelemetryInfo that has an addition 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.
+ };
-// This defines a custom TelemetryInfo that has an addition Msg field.
-struct MyTelemetryInfo : public telemetry::TelemetryInfo {
- std::string Msg;
+ 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;
- Error serialize(Serializer& serializer) const override {
- TelemetryInfo::serialize(serializer);
- serializer.writeString("MyMsg", Msg);
+ return std::make_unique<MyManager>();
+ }
+ MyManager() = default;
+
+ Error dispatch(TelemetryInfo* Entry) const override {
+ Entry->SessionId = SessionId;
+ emitToAllDestinations(Entry);
}
- // Note: implement getKind() and classof() to support dyn_cast operations.
-};
-
-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 dispatch(TelemetryInfo* Entry) const override {
- Entry->SessionId = SessionId;
- emitToAllDestinations(Entry);
-}
-
-Error logStartup(MyTelemetryInfo* Entry) {
- // Optionally add additional field
- Entry->Msg = "CustomMsg";
- return dispatch(Entry);
-
-}
-
-void addDestination(std::unique_ptr<Destination> dest) override {
- destinations.push_back(std::move(dest));
-}
+ void addDestination(std::unique_ptr<Destination> dest) override {
+ destinations.push_back(std::move(dest));
+ }
-// You can also define additional instrumentation points.
-void logAdditionalPoint(TelemetryInfo* Entry) {
-// .... code here
-}
-private:
-void emitToAllDestinations(const TelemetryInfo* Entry) {
-// Note: could do this in parallel, if needed.
-for (Destination* Dest : Destinations)
-Dest->receiveEntry(Entry);
-}
-std::vector<Destination> Destinations;
-const std::string SessionId;
-};
+ // You can also define additional instrumentation points.
+ void logAdditionalPoint(TelemetryInfo* Entry) {
+ // .... code here
+ }
+
+ private:
+ void emitToAllDestinations(const TelemetryInfo* Entry) {
+ for (Destination* Dest : Destinations)
+ Dest->receiveEntry(Entry);
+ }
+ }
+
+ std::vector<Destination> Destinations;
+ const std::string SessionId;
+ };
2) Use the library in your tool.
>From df8a9af01f208cd80463dce5e4f01ddda44a6ce0 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Wed, 4 Dec 2024 15:13:31 -0500
Subject: [PATCH 48/61] more cleanup
---
llvm/docs/Telemetry.rst | 61 ++++++++++++++++++++++++++++++-----------
1 file changed, 45 insertions(+), 16 deletions(-)
diff --git a/llvm/docs/Telemetry.rst b/llvm/docs/Telemetry.rst
index 56473ffba0ea2a..b25c1bb1d9fed9 100644
--- a/llvm/docs/Telemetry.rst
+++ b/llvm/docs/Telemetry.rst
@@ -87,7 +87,7 @@ How to implement and interact with the API
To use Telemetry in your tool, you need to provide a concrete implementation of the ``Manager`` class and ``Destination``.
-1) Define a custom ``Serializer``, ``Manager`` and ``Destination``
+1) Define a custom ``Serializer``, ``Manager``, ``Destination`` and optionally a subclass of ``TelemetryInfo``
.. code-block:: c++
@@ -142,19 +142,7 @@ To use Telemetry in your tool, you need to provide a concrete implementation of
bool started = false;
std::unique_ptr<json::Object> object;
};
-
- // This defines a custom TelemetryInfo that has an addition 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.
- };
-
+
class MyManager : public telemery::Manager {
public:
static std::unique_ptr<MyManager> createInstatnce(telemetry::Config* config) {
@@ -173,8 +161,14 @@ To use Telemetry in your tool, you need to provide a concrete implementation of
void addDestination(std::unique_ptr<Destination> dest) override {
destinations.push_back(std::move(dest));
}
-
+
// 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
}
@@ -189,6 +183,40 @@ To use Telemetry in your tool, you need to provide a concrete implementation of
std::vector<Destination> Destinations;
const std::string SessionId;
};
+
+ class MyDestination : public telemetry::Destination {
+ public:
+ Error receiveEntry(const TelemetryInfo* Entry) override {
+ if (Error err = serializer.start()) {
+ return err;
+ }
+ Entry->serialize(serializer);
+ if (Error err = serializer.finish()) {
+ return err;
+ }
+
+ json::Object copied = *serializer.getOutputObject();
+ // Send the `copied` object to wherever.
+ return Error::success();
+ }
+
+ };
+
+ // This defines a custom TelemetryInfo that has an addition Msg field.
+ struct MyTelemetryInfo : public telemetry::TelemetryInfo {
+ std::chrono::time_point<std::chrono::steady_clock> Start;
+ std::chrono::time_point<std::chrono::steady_clock> End;
+
+ 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.
+ };
+
2) Use the library in your tool.
@@ -210,7 +238,8 @@ Logging the tool init-process:
auto EndTime = std::chrono::time_point<std::chrono::steady_clock>::now();
MyTelemetryInfo Entry;
- Entry.Stats = {StartTime, EndTime};
+ Entry.Start = StartTime;
+ Entry.End = EndTime;
Manager->logStartup("MyTool", &Entry);
Similar code can be used for logging the tool's exit.
>From 70f47428d1696d1f8c2538962f9e76e89f381c01 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Wed, 4 Dec 2024 15:16:23 -0500
Subject: [PATCH 49/61] spacing
---
llvm/docs/Telemetry.rst | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/llvm/docs/Telemetry.rst b/llvm/docs/Telemetry.rst
index b25c1bb1d9fed9..77b862947dae76 100644
--- a/llvm/docs/Telemetry.rst
+++ b/llvm/docs/Telemetry.rst
@@ -93,7 +93,7 @@ To use Telemetry in your tool, you need to provide a concrete implementation of
class JsonSerializer : public Serializer {
public:
- json::Object *getOutputObject() { return object.get(); }
+ json::Object *getOutputObject() { return object.get(); }
llvm::Error start() override {
if (started)
@@ -199,7 +199,9 @@ To use Telemetry in your tool, you need to provide a concrete implementation of
// Send the `copied` object to wherever.
return Error::success();
}
-
+
+ private:
+ JsonSerializer serializer;
};
// This defines a custom TelemetryInfo that has an addition Msg field.
>From 8eab77a12f8c0ffbba9b1f5ce864145681db2a0c Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Wed, 4 Dec 2024 15:20:54 -0500
Subject: [PATCH 50/61] more docs
---
llvm/docs/Telemetry.rst | 2 +-
llvm/include/llvm/Telemetry/Telemetry.h | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/llvm/docs/Telemetry.rst b/llvm/docs/Telemetry.rst
index 77b862947dae76..d83b5b68caa913 100644
--- a/llvm/docs/Telemetry.rst
+++ b/llvm/docs/Telemetry.rst
@@ -242,7 +242,7 @@ Logging the tool init-process:
Entry.Start = StartTime;
Entry.End = EndTime;
- Manager->logStartup("MyTool", &Entry);
+ Manager->logStartup(&Entry);
Similar code can be used for logging the tool's exit.
diff --git a/llvm/include/llvm/Telemetry/Telemetry.h b/llvm/include/llvm/Telemetry/Telemetry.h
index fb4a204ff5f68f..9da67f4699d9b3 100644
--- a/llvm/include/llvm/Telemetry/Telemetry.h
+++ b/llvm/include/llvm/Telemetry/Telemetry.h
@@ -118,10 +118,11 @@ class Destination {
class Manager {
public:
// Dispatch Telemetry data to the Destination(s).
- // This is non-const because the Manager may add or remove
+ // The argument is non-const because the Manager may add or remove
// data from the entry.
virtual Error dispatch(TelemetryInfo *Entry) = 0;
+ // Register a Destination.
virtual void addDestination(std::unique_ptr<Destination> Destination) = 0;
};
>From f9e1a6583137ce698c3e995f3845271936165221 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Mon, 9 Dec 2024 15:15:13 -0500
Subject: [PATCH 51/61] pass map arg as const ref
---
llvm/include/llvm/Telemetry/Telemetry.h | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/llvm/include/llvm/Telemetry/Telemetry.h b/llvm/include/llvm/Telemetry/Telemetry.h
index 9da67f4699d9b3..f6198bd4d34010 100644
--- a/llvm/include/llvm/Telemetry/Telemetry.h
+++ b/llvm/include/llvm/Telemetry/Telemetry.h
@@ -32,8 +32,9 @@ class Serializer {
virtual void writeInt32(StringRef KeyName, int Value) = 0;
virtual void writeSizeT(StringRef KeyName, size_t Value) = 0;
virtual void writeString(StringRef KeyName, StringRef Value) = 0;
- virtual void writeKeyValueMap(StringRef KeyName,
- std::map<std::string, std::string> Value) = 0;
+ virtual void
+ writeKeyValueMap(StringRef KeyName,
+ const std::map<std::string, std::string> &Value) = 0;
virtual llvm::Error finish() = 0;
};
>From 2a1fbe59b59d3682eb2766940b762556e853abd5 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Wed, 11 Dec 2024 10:08:36 -0500
Subject: [PATCH 52/61] Update llvm/docs/Telemetry.rst
Co-authored-by: James Henderson <James.Henderson at sony.com>
---
llvm/docs/Telemetry.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/docs/Telemetry.rst b/llvm/docs/Telemetry.rst
index d83b5b68caa913..98ddf35fa27daf 100644
--- a/llvm/docs/Telemetry.rst
+++ b/llvm/docs/Telemetry.rst
@@ -127,7 +127,7 @@ To use Telemetry in your tool, you need to provide a concrete implementation of
writeHelper(KeyName, json::Value(std::move(Inner)));
}
- llvm::Error finish() override {
+ Error finish() override {
if (!started)
return createStringError("Serializer not currently in use");
started = false;
>From 6e3a85dad0a2e511bf56ff09967c2052171c9bcf Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Wed, 11 Dec 2024 10:08:59 -0500
Subject: [PATCH 53/61] Update llvm/docs/Telemetry.rst
Co-authored-by: James Henderson <James.Henderson at sony.com>
---
llvm/docs/Telemetry.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/docs/Telemetry.rst b/llvm/docs/Telemetry.rst
index 98ddf35fa27daf..8cce9405f3ec56 100644
--- a/llvm/docs/Telemetry.rst
+++ b/llvm/docs/Telemetry.rst
@@ -204,7 +204,7 @@ To use Telemetry in your tool, you need to provide a concrete implementation of
JsonSerializer serializer;
};
- // This defines a custom TelemetryInfo that has an addition Msg field.
+ // This defines a custom TelemetryInfo that has an additional Msg field.
struct MyTelemetryInfo : public telemetry::TelemetryInfo {
std::chrono::time_point<std::chrono::steady_clock> Start;
std::chrono::time_point<std::chrono::steady_clock> End;
>From f9b1cce143c0aeb517bca9ad0cba2fc83cfdf53d Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Wed, 11 Dec 2024 10:48:35 -0500
Subject: [PATCH 54/61] addressed review comments
---
llvm/docs/Telemetry.rst | 26 +++++++-------
llvm/include/llvm/Telemetry/Telemetry.h | 23 ++++++------
llvm/lib/Telemetry/Telemetry.cpp | 2 +-
llvm/unittests/Telemetry/TelemetryTest.cpp | 42 +++++++++++-----------
4 files changed, 45 insertions(+), 48 deletions(-)
diff --git a/llvm/docs/Telemetry.rst b/llvm/docs/Telemetry.rst
index 8cce9405f3ec56..88220fe869bd63 100644
--- a/llvm/docs/Telemetry.rst
+++ b/llvm/docs/Telemetry.rst
@@ -95,7 +95,7 @@ To use Telemetry in your tool, you need to provide a concrete implementation of
public:
json::Object *getOutputObject() { return object.get(); }
- llvm::Error start() override {
+ llvm::Error init() override {
if (started)
return createStringError("Serializer already in use");
started = true;
@@ -103,23 +103,24 @@ To use Telemetry in your tool, you need to provide a concrete implementation of
return Error::success();
}
- void writeBool(StringRef KeyName, bool Value) override {
+ // Serialize the given value.
+ void write(StringRef KeyName, bool Value) override {
writeHelper(KeyName, Value);
}
- void writeInt32(StringRef KeyName, int Value) override {
+ void write(StringRef KeyName, int Value) override {
writeHelper(KeyName, Value);
}
- void writeSizeT(StringRef KeyName, size_t Value) override {
+ void write(StringRef KeyName, size_t Value) override {
writeHelper(KeyName, Value);
}
- void writeString(StringRef KeyName, StringRef Value) override {
+ void write(StringRef KeyName, StringRef Value) override {
writeHelper(KeyName, Value);
}
- void writeKeyValueMap(StringRef KeyName,
- std::map<std::string, std::string> Value) override {
+ void write(StringRef KeyName,
+ const std::map<std::string, std::string>& Value) override {
json::Object Inner;
for (auto kv : Value) {
Inner.try_emplace(kv.first, kv.second);
@@ -127,7 +128,7 @@ To use Telemetry in your tool, you need to provide a concrete implementation of
writeHelper(KeyName, json::Value(std::move(Inner)));
}
- Error finish() override {
+ Error finalize() override {
if (!started)
return createStringError("Serializer not currently in use");
started = false;
@@ -175,7 +176,7 @@ To use Telemetry in your tool, you need to provide a concrete implementation of
private:
void emitToAllDestinations(const TelemetryInfo* Entry) {
- for (Destination* Dest : Destinations)
+ for (Destination* Dest : Destinations) {
Dest->receiveEntry(Entry);
}
}
@@ -187,11 +188,11 @@ To use Telemetry in your tool, you need to provide a concrete implementation of
class MyDestination : public telemetry::Destination {
public:
Error receiveEntry(const TelemetryInfo* Entry) override {
- if (Error err = serializer.start()) {
+ if (Error err = serializer.init()) {
return err;
}
Entry->serialize(serializer);
- if (Error err = serializer.finish()) {
+ if (Error err = serializer.finalize()) {
return err;
}
@@ -206,9 +207,6 @@ To use Telemetry in your tool, you need to provide a concrete implementation of
// This defines a custom TelemetryInfo that has an additional Msg field.
struct MyTelemetryInfo : public telemetry::TelemetryInfo {
- std::chrono::time_point<std::chrono::steady_clock> Start;
- std::chrono::time_point<std::chrono::steady_clock> End;
-
std::string Msg;
Error serialize(Serializer& serializer) const override {
diff --git a/llvm/include/llvm/Telemetry/Telemetry.h b/llvm/include/llvm/Telemetry/Telemetry.h
index f6198bd4d34010..f2a70195c3c00d 100644
--- a/llvm/include/llvm/Telemetry/Telemetry.h
+++ b/llvm/include/llvm/Telemetry/Telemetry.h
@@ -27,21 +27,20 @@ namespace telemetry {
class Serializer {
public:
- virtual llvm::Error start() = 0;
- virtual void writeBool(StringRef KeyName, bool Value) = 0;
- virtual void writeInt32(StringRef KeyName, int Value) = 0;
- virtual void writeSizeT(StringRef KeyName, size_t Value) = 0;
- virtual void writeString(StringRef KeyName, StringRef Value) = 0;
- virtual void
- writeKeyValueMap(StringRef KeyName,
- const std::map<std::string, std::string> &Value) = 0;
- virtual llvm::Error finish() = 0;
+ virtual llvm::Error init() = 0;
+ virtual void write(StringRef KeyName, bool Value) = 0;
+ virtual void write(StringRef KeyName, int Value) = 0;
+ virtual void write(StringRef KeyName, size_t Value) = 0;
+ virtual void write(StringRef KeyName, StringRef Value) = 0;
+ virtual void write(StringRef KeyName,
+ const std::map<std::string, std::string> &Value) = 0;
+ virtual llvm::Error finalize() = 0;
};
-/// Configuration for the Telemeter class.
+/// Configuration for the Manager class.
/// This stores configurations from both users and vendors and is passed
-/// to the Telemeter upon construction. (Any changes to the config after
-/// the Telemeter's construction will not have any effect on it).
+/// to the Manager upon construction. (Any changes to the config after
+/// the Manager's construction will not have any effect on it).
///
/// This struct can be extended as needed to add additional configuration
/// points specific to a vendor's implementation.
diff --git a/llvm/lib/Telemetry/Telemetry.cpp b/llvm/lib/Telemetry/Telemetry.cpp
index b7ee3c2bb1778b..e7640a64195bf3 100644
--- a/llvm/lib/Telemetry/Telemetry.cpp
+++ b/llvm/lib/Telemetry/Telemetry.cpp
@@ -4,7 +4,7 @@ namespace llvm {
namespace telemetry {
void TelemetryInfo::serialize(Serializer &serializer) const {
- serializer.writeString("SessionId", SessionId);
+ serializer.write("SessionId", SessionId);
}
} // namespace telemetry
diff --git a/llvm/unittests/Telemetry/TelemetryTest.cpp b/llvm/unittests/Telemetry/TelemetryTest.cpp
index b509456e287fff..c860ef761ab8f2 100644
--- a/llvm/unittests/Telemetry/TelemetryTest.cpp
+++ b/llvm/unittests/Telemetry/TelemetryTest.cpp
@@ -51,7 +51,7 @@ class JsonSerializer : public Serializer {
public:
json::Object *getOutputObject() { return object.get(); }
- llvm::Error start() override {
+ llvm::Error init() override {
if (started)
return createStringError("Serializer already in use");
started = true;
@@ -59,23 +59,23 @@ class JsonSerializer : public Serializer {
return Error::success();
}
- void writeBool(StringRef KeyName, bool Value) override {
+ void write(StringRef KeyName, bool Value) override {
writeHelper(KeyName, Value);
}
- void writeInt32(StringRef KeyName, int Value) override {
+ void write(StringRef KeyName, int Value) override {
writeHelper(KeyName, Value);
}
- void writeSizeT(StringRef KeyName, size_t Value) override {
+ void write(StringRef KeyName, size_t Value) override {
writeHelper(KeyName, Value);
}
- void writeString(StringRef KeyName, StringRef Value) override {
+ void write(StringRef KeyName, StringRef Value) override {
writeHelper(KeyName, Value);
}
- void writeKeyValueMap(StringRef KeyName,
- std::map<std::string, std::string> Value) override {
+ void write(StringRef KeyName,
+ const std::map<std::string, std::string> &Value) override {
json::Object Inner;
for (auto kv : Value) {
Inner.try_emplace(kv.first, kv.second);
@@ -83,7 +83,7 @@ class JsonSerializer : public Serializer {
writeHelper(KeyName, json::Value(std::move(Inner)));
}
- llvm::Error finish() override {
+ llvm::Error finalize() override {
if (!started)
return createStringError("Serializer not currently in use");
started = false;
@@ -103,7 +103,7 @@ class StringSerializer : public Serializer {
public:
const std::string &getString() { return Buffer; }
- llvm::Error start() override {
+ llvm::Error init() override {
if (started)
return createStringError("Serializer already in use");
started = true;
@@ -111,23 +111,23 @@ class StringSerializer : public Serializer {
return Error::success();
}
- void writeBool(StringRef KeyName, bool Value) override {
+ void write(StringRef KeyName, bool Value) override {
writeHelper(KeyName, Value);
}
- void writeInt32(StringRef KeyName, int Value) override {
+ void write(StringRef KeyName, int Value) override {
writeHelper(KeyName, Value);
}
- void writeSizeT(StringRef KeyName, size_t Value) override {
+ void write(StringRef KeyName, size_t Value) override {
writeHelper(KeyName, Value);
}
- void writeString(StringRef KeyName, StringRef Value) override {
+ void write(StringRef KeyName, StringRef Value) override {
assert(started && "serializer not started");
}
- void writeKeyValueMap(StringRef KeyName,
- std::map<std::string, std::string> Value) override {
+ void write(StringRef KeyName,
+ const std::map<std::string, std::string> &Value) override {
std::string Inner;
for (auto kv : Value) {
writeHelper(StringRef(kv.first), StringRef(kv.second), &Inner);
@@ -135,7 +135,7 @@ class StringSerializer : public Serializer {
writeHelper(KeyName, StringRef(Inner));
}
- llvm::Error finish() override {
+ llvm::Error finalize() override {
if (!started)
return createStringError("Serializer not currently in use");
started = false;
@@ -175,11 +175,11 @@ class JsonStorageDestination : public Destination {
JsonStorageDestination(TestContext *Ctxt) : CurrentContext(Ctxt) {}
Error receiveEntry(const TelemetryInfo *Entry) override {
- if (Error err = serializer.start()) {
+ if (Error err = serializer.init()) {
return err;
}
Entry->serialize(serializer);
- if (Error err = serializer.finish()) {
+ if (Error err = serializer.finalize()) {
return err;
}
@@ -201,7 +201,7 @@ struct StartupInfo : public TelemetryInfo {
void serialize(Serializer &serializer) const override {
TelemetryInfo::serialize(serializer);
- serializer.writeString("ToolName", ToolName);
+ serializer.write("ToolName", ToolName);
}
};
@@ -210,8 +210,8 @@ struct ExitInfo : public TelemetryInfo {
std::string ExitDesc;
void serialize(Serializer &serializer) const override {
TelemetryInfo::serialize(serializer);
- serializer.writeInt32("ExitCode", ExitCode);
- serializer.writeString("ExitDesc", ExitDesc);
+ serializer.write("ExitCode", ExitCode);
+ serializer.write("ExitDesc", ExitDesc);
}
};
>From 0f38a74d2dc73450919e3999fa9f6396ccc2c706 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Mon, 16 Dec 2024 11:08:24 -0500
Subject: [PATCH 55/61] Update llvm/docs/Telemetry.rst
Co-authored-by: James Henderson <James.Henderson at sony.com>
---
llvm/docs/Telemetry.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/docs/Telemetry.rst b/llvm/docs/Telemetry.rst
index 88220fe869bd63..c9e58552be5669 100644
--- a/llvm/docs/Telemetry.rst
+++ b/llvm/docs/Telemetry.rst
@@ -120,7 +120,7 @@ To use Telemetry in your tool, you need to provide a concrete implementation of
}
void write(StringRef KeyName,
- const std::map<std::string, std::string>& Value) override {
+ const std::map<std::string, std::string> &Value) override {
json::Object Inner;
for (auto kv : Value) {
Inner.try_emplace(kv.first, kv.second);
>From ad57099fa43032fcc0ba09c1eda4033499f8dc7b Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Mon, 16 Dec 2024 11:08:51 -0500
Subject: [PATCH 56/61] Update llvm/docs/Telemetry.rst
Co-authored-by: James Henderson <James.Henderson at sony.com>
---
llvm/docs/Telemetry.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/docs/Telemetry.rst b/llvm/docs/Telemetry.rst
index c9e58552be5669..bb55071917e45b 100644
--- a/llvm/docs/Telemetry.rst
+++ b/llvm/docs/Telemetry.rst
@@ -230,7 +230,7 @@ Logging the tool init-process:
auto Manager = MyManager::createInstance(&MyConfig);
- // Any other tool's init code can go here
+ // 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
>From a2fcd4d53cb3f4f553815420843ce13f26438f13 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Mon, 16 Dec 2024 11:09:06 -0500
Subject: [PATCH 57/61] Update llvm/include/llvm/Telemetry/Telemetry.h
Co-authored-by: James Henderson <James.Henderson at sony.com>
---
llvm/include/llvm/Telemetry/Telemetry.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/include/llvm/Telemetry/Telemetry.h b/llvm/include/llvm/Telemetry/Telemetry.h
index f2a70195c3c00d..91c86504bedb5a 100644
--- a/llvm/include/llvm/Telemetry/Telemetry.h
+++ b/llvm/include/llvm/Telemetry/Telemetry.h
@@ -7,7 +7,7 @@
//===----------------------------------------------------------------------===//
///
/// \file
-/// This file provides the basic framework for Telemetry
+/// This file provides the basic framework for Telemetry.
/// Refer to its documentation at llvm/docs/Telemetry.rst for more details.
//===---------------------------------------------------------------------===//
>From 628e7e9674bacc3fc9f3569ae160a5fca1f2482f Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Mon, 16 Dec 2024 11:09:20 -0500
Subject: [PATCH 58/61] Update llvm/include/llvm/Telemetry/Telemetry.h
Co-authored-by: James Henderson <James.Henderson at sony.com>
---
llvm/include/llvm/Telemetry/Telemetry.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/include/llvm/Telemetry/Telemetry.h b/llvm/include/llvm/Telemetry/Telemetry.h
index 91c86504bedb5a..b432a7c163f686 100644
--- a/llvm/include/llvm/Telemetry/Telemetry.h
+++ b/llvm/include/llvm/Telemetry/Telemetry.h
@@ -79,7 +79,7 @@ struct TelemetryInfo {
//
// Note: a tool could have multiple sessions running at once, in which
// case, these shall be multiple sets of TelemetryInfo with multiple unique
- // ids.
+ // IDs.
//
// Different usages can assign different types of IDs to this field.
std::string SessionId;
>From 6ea4e927ada8ed82c83461997ae5723fa3b1fa7a Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Mon, 16 Dec 2024 11:10:07 -0500
Subject: [PATCH 59/61] Update llvm/docs/Telemetry.rst
Co-authored-by: James Henderson <James.Henderson at sony.com>
---
llvm/docs/Telemetry.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/docs/Telemetry.rst b/llvm/docs/Telemetry.rst
index bb55071917e45b..e84256cec5e37c 100644
--- a/llvm/docs/Telemetry.rst
+++ b/llvm/docs/Telemetry.rst
@@ -234,7 +234,7 @@ Logging the tool init-process:
// ...
// Finally, take a snapshot of the time now so we know how long it took the
- // init process to finish
+ // init process to finish.
auto EndTime = std::chrono::time_point<std::chrono::steady_clock>::now();
MyTelemetryInfo Entry;
>From 08cf32f1e2cc9f83fad120014d278cfc1ed98259 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Mon, 16 Dec 2024 11:10:19 -0500
Subject: [PATCH 60/61] Update llvm/docs/Telemetry.rst
Co-authored-by: James Henderson <James.Henderson at sony.com>
---
llvm/docs/Telemetry.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/docs/Telemetry.rst b/llvm/docs/Telemetry.rst
index e84256cec5e37c..4cf024337a92f9 100644
--- a/llvm/docs/Telemetry.rst
+++ b/llvm/docs/Telemetry.rst
@@ -17,7 +17,7 @@ Objective
Provides a common framework in LLVM for collecting various usage and performance
metrics.
-It is located at ``llvm/Telemetry/Telemetry.h``
+It is located at ``llvm/Telemetry/Telemetry.h``.
Characteristics
---------------
>From 3c524010385e803a67b2d386a3d7236883b92018 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Mon, 16 Dec 2024 11:10:34 -0500
Subject: [PATCH 61/61] Update llvm/include/llvm/Telemetry/Telemetry.h
Co-authored-by: James Henderson <James.Henderson at sony.com>
---
llvm/include/llvm/Telemetry/Telemetry.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/include/llvm/Telemetry/Telemetry.h b/llvm/include/llvm/Telemetry/Telemetry.h
index b432a7c163f686..b9a6540e463a68 100644
--- a/llvm/include/llvm/Telemetry/Telemetry.h
+++ b/llvm/include/llvm/Telemetry/Telemetry.h
@@ -27,7 +27,7 @@ namespace telemetry {
class Serializer {
public:
- virtual llvm::Error init() = 0;
+ virtual Error init() = 0;
virtual void write(StringRef KeyName, bool Value) = 0;
virtual void write(StringRef KeyName, int Value) = 0;
virtual void write(StringRef KeyName, size_t Value) = 0;
More information about the llvm-commits
mailing list