https://github.com/open-telemetry/opentelemetry-cpp/blob/f987c9c094f276336569eeea85f17e361de5e518/sdk/src/trace/span.h
在 OpenTelemetry C++ 的 sdk/src/trace 目录中,不同的 span 定义和实现是为了支持追踪(Tracing)功能的多样化需求。以下是一些关键点来帮助您理解它们的区别:
Span 是 OpenTelemetry 中的核心追踪单元,表示一次独立的操作或事件。
每个 Span 包含以下信息:
在 OpenTelemetry 的代码中,Span 有不同的实现和变体,用于适应多种场景:
定义在 api/include/opentelemetry/trace/span.h。
这是一个抽象类,定义了 Span 的基本接口,如 SetAttribute、AddEvent、End 等方法。
它提供了一个标准化的接口,供所有 Span 实现遵循。
定义在 sdk/src/trace/span.h 和实现中(如 span.cc)。
这是 API 层 Span 的具体实现,处理真正的追踪逻辑。
它负责:
记录和存储追踪数据。
与 Tracer 和 SpanProcessor 协同工作,将数据导出到后端。
默认的空操作 Span(DefaultSpan)
定义在 api/include/opentelemetry/trace/default_span.h。
用于在没有实际追踪上下文的情况下,提供一个占位的 Span 实现。
它不会记录任何数据,仅用于传播上下文。
API 层的 Span 是一个接口,定义了所有 Span 的通用行为。
SDK 层的 Span 是具体实现,负责处理数据。
不同的 Span 实现可以适应不同的需求,比如实际追踪、空操作实现等。
默认的空操作 Span 减少了不必要的开销。
API(Application Programming Interface)层 是为开发者提供的接口,定义了功能的规范和使用方法:
作用:
opentelemetry::trace::Span
是一个抽象接口,定义了追踪单元的核心操作(如 SetAttribute
、AddEvent
等)。特点:
示例:
在 OpenTelemetry 中,API 层定义了所有核心行为,例如 Tracer
、Span
和 SpanContext
,目的是为用户提供一个统一的追踪接口。
SDK(Software Development Kit) 层是 API 的具体实现,负责实现 API 中定义的功能:
作用:
sdk::trace::Span
是 opentelemetry::trace::Span
的实现类,用于记录和管理追踪数据。特点:
示例:
在 OpenTelemetry 中,sdk::trace::Tracer
和 sdk::trace::Span
是 API 层 Tracer
和 Span
的具体实现。SDK 层实现了追踪数据的记录、处理和导出。
可以将 API 层和 SDK 层的关系比作一个家电:
Recordable
是 OpenTelemetry C++ SDK 中的一个核心接口,用于表示可记录的数据结构,如日志记录或追踪(Tracing)数据。这是一个抽象类,具体实现取决于导出器(Exporter)或处理器(Processor)的需求。以下是关键点:
Recordable
的定义sdk/include/opentelemetry/sdk/trace/recordable.h
class Recordable
{
public:
virtual ~Recordable() = default;
virtual void SetIdentity(const opentelemetry::trace::SpanContext &span_context,
opentelemetry::trace::SpanId parent_span_id) noexcept = 0;
virtual void SetAttribute(nostd::string_view key,
const opentelemetry::common::AttributeValue &value) noexcept = 0;
virtual void AddEvent(nostd::string_view name,
opentelemetry::common::SystemTimestamp timestamp,
const opentelemetry::common::KeyValueIterable &attributes) noexcept = 0;
virtual void AddLink(const opentelemetry::trace::SpanContext &span_context,
const opentelemetry::common::KeyValueIterable &attributes) noexcept = 0;
virtual void SetName(nostd::string_view name) noexcept = 0;
virtual void SetTraceFlags(opentelemetry::trace::TraceFlags flags) noexcept = 0;
virtual void SetStartTime(opentelemetry::common::SystemTimestamp start_time) noexcept = 0;
virtual void SetDuration(std::chrono::nanoseconds duration) noexcept = 0;
};
Recordable
的主要方法SetIdentity
:Recordable
的标识,包括 SpanContext
和父级 SpanId
。SetAttribute
:Recordable
的属性(键值对)。AddEvent
:AddLink
:Span
。SetName
:Recordable
的名称。SetTraceFlags
:SetStartTime
和 SetDuration
:Recordable
的开始时间和持续时间。Recordable
是一个抽象类,具体功能由不同的导出器实现,例如:
exporters/zipkin/recordable.h
opentelemetry-cpp/exporters/zipkin/src/recordable.cc
exporters/otlp/src/otlp_file_exporter.cc
SpanProcessor
的关系Recordable
是 SpanProcessor
的核心工具,用于将数据从内存中提取并格式化为导出器可用的形式。SpanProcessor::MakeRecordable
创建 Recordable
的实例。SpanProcessor::OnStart
和 SpanProcessor::OnEnd
使用 Recordable
来处理 Span
数据。Recordable
是 OpenTelemetry 的一个核心抽象,用于在追踪和日志记录数据的生命周期中充当一个可记录的数据存储单元。它的具体实现由导出器(如 Zipkin 或 OTLP)决定,提供了灵活性以支持各种后端服务。如果需要进一步理解某个导出器的实现,可以查看它的具体代码文件。
https://opentelemetry.opendocs.io/docs/specs/otel/overview/
OpenTelemetry C++ 项目是一个实现分布式追踪和指标采集的客户端库,支持多种后端系统。它的架构分为以下几个关键组件或层次,每个组件负责特定的功能:
API 层是开发者直接与 OpenTelemetry 交互的接口。它定义了一组抽象类和接口,允许开发者记录追踪(Tracing)数据和指标(Metrics),而无需了解底层实现。
opentelemetry/trace/span.h
:定义了 Span
类,用于描述分布式追踪中的操作。opentelemetry/trace/tracer.h
:定义了 Tracer
类,负责创建和管理 Span
。opentelemetry/metrics
:用于指标的记录和管理。Span
、设置属性、记录事件。SDK 层是 API 的具体实现,负责将追踪数据和指标从内存中提取、处理,并通过导出器(Exporter)发送到后端。
sdk/trace/span.h
:实现了 Span
的具体功能。sdk/trace/processor.h
:定义 SpanProcessor
,用于处理和批处理 Span
数据。sdk/metrics
:实现指标的采集和管理。Tracer
和 Span
的实现。导出器负责将追踪数据和指标发送到后端系统,例如 Zipkin、Jaeger、Prometheus 或 OpenTelemetry Collector。
exporters/zipkin
:实现 Zipkin 的导出功能。exporters/otlp
:实现 OpenTelemetry Protocol (OTLP) 的导出功能。Recordable
(可记录的数据结构)转换为后端支持的格式(如 JSON 或 Protobuf)。nostd
(Non-Standard Library):
nostd::span
,用于兼容没有 C++17 支持的环境。common
:
api/test
和 sdk/test
目录中。examples
目录中。以下是项目目录结构的简要说明:
api
:API 层定义的接口和抽象类。sdk
:SDK 的具体实现,包括追踪和指标的处理。exporters
:支持的导出器模块(如 OTLP、Zipkin)。examples
:示例代码,演示如何使用 OpenTelemetry C++。common
:通用工具和数据结构。nostd
:非标准库的实现,用于兼容不同的 C++ 标准。tests
:测试代码,验证项目的正确性。Tracer
创建 Span
,并记录操作信息。SpanProcessor
处理和存储 Span
数据。OpenTelemetry C++ 项目通过模块化设计,将接口、实现和导出器分离,提供了高效、灵活的分布式追踪和指标采集方案。它的架构保证了可扩展性,允许用户根据需求配置或扩展功能。如果需要更深入的说明,可以指定某个模块或文件!
OpenTelemetry C++ 项目架构设计中有许多值得学习和借鉴的地方,特别是在构建灵活、高效、可扩展的分布式追踪和指标采集系统方面。以下是一些关键学习点:
SpanExporter
)。nostd::span
、nostd::string_view
),以减少对外部库的依赖。std::mutex
)来保证多线程环境下的安全性。BatchSpanProcessor
使用缓冲区和后台线程来批量导出数据,减少了对性能的影响。SpanProcessor::MakeRecordable
),进一步增强了灵活性。api
、sdk
、exporters
、examples
和 tests
。OpenTelemetry C++ 项目的架构体现了模块化、灵活性和高性能的特点。这种设计值得学习,特别是在开发分布式系统、异步处理和可扩展性框架时,可以借鉴其 API/SDK 分离、可插拔设计和高效的并发处理等技术思想。
在 OpenTelemetry C++ 项目中,导出器(Exporter)的设计使用了接口和工厂模式,这种设计模式有助于实现模块化、灵活性和可扩展性。以下是具体解释:
SpanExporter
接口,该接口定义了导出追踪数据的标准方法。SpanExporter
(位于 sdk/include/opentelemetry/sdk/trace/exporter.h
):class SpanExporter
{
public:
virtual ~SpanExporter() = default;
virtual std::unique_ptr<Recordable> MakeRecordable() noexcept = 0;
virtual ExportResult Export(
const nostd::span<std::unique_ptr<Recordable>> &spans) noexcept = 0;
virtual bool Shutdown(std::chrono::microseconds timeout = (std::chrono::microseconds::max)()) noexcept = 0;
};
MakeRecordable
:创建一个 Recordable
(可记录的数据结构)。Export
:将一批追踪数据导出到后端。Shutdown
:关闭导出器并完成清理工作。new
操作符。MakeRecordable
方法就是一种工厂方法,用于生成 Recordable
对象。例如:std::unique_ptr<sdk::trace::Recordable> ZipkinExporter::MakeRecordable() noexcept
{
return std::unique_ptr<sdk::trace::Recordable>(new ZipkinRecordable());
}
ZipkinExporter
使用工厂方法创建一个 ZipkinRecordable
对象,而用户无需直接调用 new
。假设用户想使用 Zipkin 导出器:
SpanExporter
定义的方法,例如 Export
。MakeRecordable
创建 Recordable
对象,并将其导出为后端可识别的数据格式。通过使用接口和工厂模式,OpenTelemetry C++ 项目实现了导出器的模块化和灵活性。接口定义了功能规范,工厂模式提供了动态创建对象的能力,使得用户可以轻松扩展和维护系统,同时支持多种后端。
OpenTelemetry C++ 项目在高并发环境下的设计,重点在于高效的数据收集、线程安全性以及批量处理机制。以下是对其高并发设计的详细分析:
Span
的线程安全设计
Span
可能同时被创建和记录数据。Span
的实现中使用了线程安全的机制(如 std::mutex
),以确保在高并发环境下数据一致性。sdk::trace::Span
的实现中,使用了 mutable std::mutex
锁来保护共享资源。Recordable
的抽象
Recordable
是一个线程兼容的类,用于存储追踪数据。高并发系统中,频繁导出数据会导致性能下降,因此 OpenTelemetry C++ 使用批处理和异步处理来优化性能。
批处理(Batch Processing)
BatchSpanProcessor
,用于将多个 Span
数据合并到批次中,再统一导出。Span
。异步处理
BatchSpanProcessor
使用后台线程异步处理数据:
std::condition_variable
)来唤醒工作线程,避免忙等(busy-waiting)。Span
记录,不会受到导出操作的影响。锁的使用
Span
的属性),使用了 std::mutex
或其他同步原语来保护资源。BatchSpanProcessor
的缓冲区操作实现是线程安全的。无锁设计
Recordable
的创建和记录中,尽量减少对全局状态的依赖。BatchSpanProcessor
以及其他批处理器会启动专用线程处理异步任务,而不会影响主线程的性能。高并发场景下,内存使用和延迟控制至关重要。
缓冲区限制
SpanProcessor
都有最大队列大小和最大批量大小的限制,防止内存溢出。超时控制
BatchSpanProcessor
支持自定义超时时间,当导出操作超过指定时间时会被强制终止。高效的数据收集和处理:
Span
和 Recordable
的线程安全设计确保了数据一致性。资源分配合理:
灵活的配置和扩展:
可维护性和可扩展性:
OpenTelemetry C++ 项目在高并发设计中的关键思想包括:
这些设计思路可以广泛应用于高性能和高并发的系统开发中。
https://opentelemetry.io/docs/concepts/signals/traces/#spans
CSCI 4717 – High Performance Counter
High Resolution Counter
Let’s play around with the high resolution counter again. Remember that this counter counts the number of clock cycles of the system clock since the system was last powered up. The function we use to read this timer is QueryPerformanceCounter(). Since function reads the processor’s high resolution timer which is incremented once with every clock pulse of the processor’s clock, then for a 3.59 GHz machine, this timer is incremented approximately 3,590,000,000 times in a second. QueryPerformanceCounter() takes as its single parameter a pointer to a LARGE_INTEGER which you define in your code. This gives us a way to read how many clock cycles have occurred since the machine was turned on.
LARGE_INTEGER current_time;
QueryPerformanceCounter(¤t_time);
The number returned by QueryPerformanceCounter() is huge and not very useful for timing at the “seconds” level. As I mentioned earlier, it is incremented once with every pulse of the system clock. If we knew the system clock frequency, we could figure out how many seconds had passed since turning on the machine. This can be done with QueryPerformanceFrequency(). This function returns the frequency of the processor once again using a LARGE_INTEGER passed as a pointer for the function’s only parameter.
LARGE_INTEGER frequency;
QueryPerformanceFrequency(&frequency);
Dividing the counter value by the frequency will convert the time to seconds. Dividing the counter value by the frequency divided by 1,000 will convert the time to milliseconds. Dividing the counter value by the frequency divided by 1,000,000 will convert the time to microseconds and so on.
Laboratory Exercise
In today’s exercise, we will be using the counter to measure time. For the most part, we will be measuring time in microseconds. Begin by creating a console application in Visual Studio 2005. Below is some sample code that should work well for a template as we go through some of these examples. Remember that LARGE_INTEGER is a UNION, so to access the value stored in it, you must define the way you want the data returned. To do this, use the QuadPart component.
#include "stdafx.h"
#include
#include
#include "Windows.h"
using namespace std;
int main( )
{
LARGE_INTEGER current, frequency;
QueryPerformanceFrequency(&frequency);
QueryPerformanceCounter(¤t);
cout << current.QuadPart/(frequency.QuadPart/1000000) << " uS have passed since resetting the high performance counter.\n";
return 0;
}
Today, we will see if we can get some semi-accurate measurements of the following programming functions.
Time to read the high performance counter
Time to perform a function call
Time to return from a function call
Time between interruptions from the O/S
Comparison between times to call cout and printf
Comparison between times to perform recursive and non-recursive functions
C++ Windows时间函数 QueryPerformanceCounter()与QueryPerformanceFrequency()
https://zhuanlan.zhihu.com/p/532434005