Skip to content

Commit 6e3c6ba

Browse files
authored
[Profiler] Use ringbuffer with timer create (#7066)
1 parent fc85543 commit 6e3c6ba

22 files changed

+497
-58
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
2+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2022 Datadog, Inc.
3+
4+
#include "CpuSampleProvider.h"
5+
6+
#include "CpuTimeProvider.h"
7+
8+
#include "RawCpuSample.h"
9+
#include "RawSampleTransformer.h"
10+
11+
CpuSampleProvider::CpuSampleProvider(
12+
SampleValueTypeProvider& valueTypeProvider,
13+
RawSampleTransformer* rawSampleTransformer,
14+
std::unique_ptr<RingBuffer> ringBuffer,
15+
MetricsRegistry& metricsRegistry
16+
)
17+
:
18+
RawSampleCollectorBase<RawCpuSample>("CpuSampleProvider", valueTypeProvider.GetOrRegister(CpuTimeProvider::SampleTypeDefinitions), rawSampleTransformer, std::move(ringBuffer), metricsRegistry)
19+
{
20+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
2+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2022 Datadog, Inc.
3+
4+
#pragma once
5+
6+
#include "MetricsRegistry.h"
7+
#include "RawSampleCollectorBase.h"
8+
#include "RawCpuSample.h"
9+
#include "RingBuffer.h"
10+
11+
#include <memory>
12+
#include <vector>
13+
14+
// forward declarations
15+
class SampleValueTypeProvider;
16+
class RawSampleTransformer;
17+
18+
class CpuSampleProvider
19+
:
20+
public RawSampleCollectorBase<RawCpuSample>
21+
{
22+
public:
23+
CpuSampleProvider(
24+
SampleValueTypeProvider& valueTypeProvider,
25+
RawSampleTransformer* rawSampleTransformer,
26+
std::unique_ptr<RingBuffer> ringBuffer,
27+
MetricsRegistry& metricsRegistry);
28+
};

profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/DiscardReason.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ enum class DiscardReason
1313
UnsufficientSpace,
1414
EmptyBacktrace,
1515
FailedAcquiringLock,
16+
TimedOut, // This is used when we cannot reserve a buffer in the ring buffer
1617

1718
// This item must be the last one
1819
GuardItem
@@ -45,6 +46,8 @@ static const char* to_string(DiscardReason type)
4546
return "_empty_backtrace";
4647
case DiscardReason::FailedAcquiringLock:
4748
return "_failed_acquiring_lock";
49+
case DiscardReason::TimedOut:
50+
return "_timed_out";
4851
case DiscardReason::GuardItem:
4952
// pass through
5053
break;
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
2+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2022 Datadog, Inc.
3+
4+
#pragma once
5+
6+
#include "Callstack.h"
7+
#include "DiscardMetrics.h"
8+
#include "MetricsRegistry.h"
9+
#include "RingBuffer.h"
10+
#include "ProviderBase.h"
11+
#include "ServiceBase.h"
12+
#include "SamplesEnumerator.h"
13+
#include "SampleValueTypeProvider.h"
14+
#include "RawSampleTransformer.h"
15+
16+
#include <memory>
17+
18+
template <typename TRawSample>
19+
class RawSampleCollectorBase : public ServiceBase,
20+
public ProviderBase
21+
{
22+
public:
23+
static constexpr std::size_t SampleSize = sizeof(TRawSample) + Callstack::MaxSize;
24+
25+
class SampleHolder
26+
{
27+
public:
28+
explicit SampleHolder(RingBuffer* ringBuffer, DiscardMetrics* failedReservationMetric)
29+
: _rbw{ringBuffer->GetWriter()}, _sample{nullptr}, _discard{false}
30+
{
31+
bool timedOut = false;
32+
_buffer = _rbw.Reserve(&timedOut);
33+
if (!timedOut && !_buffer.empty())
34+
{
35+
_sample = new(_buffer.data()) TRawSample();
36+
_sample->Stack = Callstack(shared::span<std::uintptr_t>(
37+
reinterpret_cast<std::uintptr_t*>(_sample + 1), Callstack::MaxFrames));
38+
}
39+
40+
if (timedOut)
41+
{
42+
failedReservationMetric->Incr<DiscardReason::TimedOut>();
43+
}
44+
if (_buffer.empty())
45+
{
46+
failedReservationMetric->Incr<DiscardReason::UnsufficientSpace>();
47+
}
48+
}
49+
~SampleHolder()
50+
{
51+
if (_buffer.empty())
52+
{
53+
return;
54+
}
55+
56+
_sample = nullptr;
57+
if (_discard)
58+
{
59+
_rbw.Discard(_buffer);
60+
}
61+
else
62+
{
63+
_rbw.Commit(_buffer);
64+
}
65+
}
66+
67+
SampleHolder(SampleHolder const&) = delete;
68+
SampleHolder& operator=(SampleHolder const&) = delete;
69+
SampleHolder(SampleHolder&&) = delete;
70+
SampleHolder& operator=(SampleHolder&&) = delete;
71+
72+
operator bool() const
73+
{
74+
return _sample != nullptr;
75+
}
76+
77+
TRawSample* operator->()
78+
{
79+
return _sample;
80+
}
81+
82+
void Discard()
83+
{
84+
_discard = true;
85+
}
86+
87+
private:
88+
Buffer _buffer;
89+
RingBuffer::Writer _rbw;
90+
TRawSample* _sample;
91+
bool _discard;
92+
};
93+
94+
public:
95+
RawSampleCollectorBase(
96+
const char* name,
97+
std::vector<SampleValueTypeProvider::Offset> valueOffsets,
98+
RawSampleTransformer* rawSampleTransformer,
99+
std::unique_ptr<RingBuffer> ringBuffer,
100+
MetricsRegistry& metricsRegistry) :
101+
ProviderBase(name),
102+
_valueOffsets{std::move(valueOffsets)},
103+
_rawSampleTransformer{rawSampleTransformer},
104+
_collectedSamples{std::move(ringBuffer)},
105+
_failedReservationMetric{metricsRegistry.GetOrRegister<DiscardMetrics>("dotnet_raw_sample_failed_allocation")}
106+
{
107+
}
108+
109+
~RawSampleCollectorBase() override = default;
110+
111+
SampleHolder GetRawSample()
112+
{
113+
return SampleHolder(_collectedSamples.get(), _failedReservationMetric.get());
114+
}
115+
116+
const char* GetName() override
117+
{
118+
return _name.c_str();
119+
}
120+
121+
std::unique_ptr<SamplesEnumerator> GetSamples() override
122+
{
123+
return std::make_unique<SamplesEnumeratorImpl>(std::move(_collectedSamples->GetReader()), _rawSampleTransformer, _valueOffsets);
124+
}
125+
126+
private:
127+
class SamplesEnumeratorImpl : public SamplesEnumerator
128+
{
129+
public:
130+
SamplesEnumeratorImpl(RingBuffer::Reader&& reader,
131+
RawSampleTransformer* rawSampleTransformer,
132+
std::vector<SampleValueTypeProvider::Offset> const& valueOffsets) :
133+
_reader{std::move(reader)},
134+
_rawSampleTransformer{rawSampleTransformer},
135+
_valueOffsets{valueOffsets}
136+
{
137+
}
138+
139+
SamplesEnumeratorImpl(SamplesEnumeratorImpl const&) = delete;
140+
SamplesEnumeratorImpl& operator=(SamplesEnumeratorImpl const&) = delete;
141+
SamplesEnumeratorImpl(SamplesEnumeratorImpl&&) = delete;
142+
SamplesEnumeratorImpl& operator=(SamplesEnumeratorImpl&&) = delete;
143+
~SamplesEnumeratorImpl() override = default;
144+
145+
// Inherited via SamplesEnumerator
146+
std::size_t size() const override
147+
{
148+
return _reader.AvailableSamples();
149+
}
150+
151+
bool MoveNext(std::shared_ptr<Sample>& sample) override
152+
{
153+
ConstBuffer buffer = _reader.GetNext();
154+
if (buffer.empty())
155+
{
156+
return false;
157+
}
158+
159+
auto* rawSample = const_cast<TRawSample*>(
160+
reinterpret_cast<TRawSample const*>(buffer.data()));
161+
162+
_rawSampleTransformer->Transform(*rawSample, sample, _valueOffsets);
163+
std::destroy_at(rawSample);
164+
return true;
165+
}
166+
167+
private:
168+
RingBuffer::Reader _reader;
169+
RawSampleTransformer* _rawSampleTransformer;
170+
std::vector<SampleValueTypeProvider::Offset> const& _valueOffsets;
171+
};
172+
173+
bool StartImpl() override
174+
{
175+
return true;
176+
}
177+
178+
bool StopImpl() override
179+
{
180+
return true;
181+
}
182+
183+
std::vector<SampleValueTypeProvider::Offset> _valueOffsets;
184+
RawSampleTransformer* _rawSampleTransformer;
185+
std::unique_ptr<RingBuffer> _collectedSamples;
186+
std::shared_ptr<DiscardMetrics> _failedReservationMetric;
187+
};

profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/RingBuffer.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ Buffer RingBuffer::Writer::Reserve(bool* timeout) const
283283
}
284284

285285
auto* rb = _rb;
286-
// \fixme{nsavoire} Not sure if spinlock is the best option here
286+
// \fixme{gleocadie} Not sure if spinlock is the best option here
287287
std::unique_lock const lock{*rb->spinlock, ReserveTimeout};
288288
if (!lock.owns_lock())
289289
{
@@ -360,7 +360,6 @@ RingBuffer::Reader::~Reader()
360360
}
361361
}
362362

363-
// todo add a test with _sampleSize + BufferHeader size not aligned
364363
std::size_t RingBuffer::Reader::AvailableSamples() const
365364
{
366365
const auto n2 = align_up(sizeof(BufferHeader) + _rb->sample_size, RingBufferAlignment);

profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/TimerCreateCpuProfiler.cpp

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33

44
#include "TimerCreateCpuProfiler.h"
55

6-
#include "CpuTimeProvider.h"
6+
#include "CpuSampleProvider.h"
77
#include "DiscardMetrics.h"
88
#include "IManagedThreadList.h"
99
#include "Log.h"
1010
#include "OpSysTools.h"
1111
#include "ProfilerSignalManager.h"
12+
#include "IConfiguration.h"
1213

1314
#include <libunwind.h>
1415
#include <sys/syscall.h> /* Definition of SYS_* constants */
@@ -22,14 +23,12 @@ TimerCreateCpuProfiler::TimerCreateCpuProfiler(
2223
IConfiguration* pConfiguration,
2324
ProfilerSignalManager* pSignalManager,
2425
IManagedThreadList* pManagedThreadsList,
25-
CpuTimeProvider* pProvider,
26-
CallstackProvider callstackProvider,
26+
CpuSampleProvider* pProvider,
2727
MetricsRegistry& metricsRegistry) noexcept
2828
:
2929
_pSignalManager{pSignalManager}, // put it as parameter for better testing
3030
_pManagedThreadsList{pManagedThreadsList},
3131
_pProvider{pProvider},
32-
_callstackProvider{std::move(callstackProvider)},
3332
_samplingInterval{pConfiguration->GetCpuProfilingInterval()}
3433
{
3534
Log::Info("Cpu profiling interval: ", _samplingInterval.count(), "ms");
@@ -215,38 +214,32 @@ bool TimerCreateCpuProfiler::Collect(void* ctx)
215214
// Libunwind can overwrite the value of errno - save it beforehand and restore it at the end
216215
ErrnoSaveAndRestore errnoScope;
217216

218-
auto callstack = _callstackProvider.Get();
219-
220-
if (callstack.Capacity() <= 0)
217+
auto rawCpuSample = _pProvider->GetRawSample();
218+
if (!rawCpuSample)
221219
{
222-
_discardMetrics->Incr<DiscardReason::UnsufficientSpace>();
223220
return false;
224221
}
225222

226-
auto buffer = callstack.Data();
223+
auto buffer = rawCpuSample->Stack.AsSpan();
227224
auto* context = reinterpret_cast<unw_context_t*>(ctx);
228225
auto count = unw_backtrace2((void**)buffer.data(), buffer.size(), context, UNW_INIT_SIGNAL_FRAME);
229-
callstack.SetCount(count);
226+
rawCpuSample->Stack.SetCount(count);
230227

231228
if (count == 0)
232229
{
230+
rawCpuSample.Discard();
233231
_discardMetrics->Incr<DiscardReason::EmptyBacktrace>();
234232
return false;
235233
}
236234

237-
RawCpuSample rawCpuSample;
238-
239235
// TO FIX this breaks the CI Visibility.
240236
// No Cpu samples will have the predefined span id, root local span id
241-
std::tie(rawCpuSample.LocalRootSpanId, rawCpuSample.SpanId) = threadInfo->GetTracingContext();
242-
243-
rawCpuSample.Timestamp = OpSysTools::GetTimestampSafe();
244-
rawCpuSample.AppDomainId = threadInfo->GetAppDomainId();
245-
rawCpuSample.Stack = std::move(callstack);
246-
rawCpuSample.ThreadInfo = std::move(threadInfo);
247-
rawCpuSample.Duration = _samplingInterval;
248-
_pProvider->Add(std::move(rawCpuSample));
237+
std::tie(rawCpuSample->LocalRootSpanId, rawCpuSample->SpanId) = threadInfo->GetTracingContext();
249238

239+
rawCpuSample->Timestamp = OpSysTools::GetTimestampSafe();
240+
rawCpuSample->AppDomainId = threadInfo->GetAppDomainId();
241+
rawCpuSample->ThreadInfo = std::move(threadInfo);
242+
rawCpuSample->Duration = _samplingInterval;
250243
return true;
251244
}
252245

profiler/src/ProfilerEngine/Datadog.Profiler.Native.Linux/TimerCreateCpuProfiler.h

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
#pragma once
55

6-
#include "CallstackProvider.h"
76
#include "CounterMetric.h"
87
#include "ManagedThreadInfo.h"
98
#include "MetricsRegistry.h"
@@ -22,8 +21,7 @@ class IConfiguration;
2221
class IThreadInfo;
2322
class IManagedThreadList;
2423
class ProfilerSignalManager;
25-
class CpuTimeProvider;
26-
class CallstackProvider;
24+
class CpuSampleProvider;
2725

2826
class TimerCreateCpuProfiler : public ServiceBase
2927
{
@@ -32,8 +30,7 @@ class TimerCreateCpuProfiler : public ServiceBase
3230
IConfiguration* pConfiguration,
3331
ProfilerSignalManager* pSignalManager,
3432
IManagedThreadList* pManagedThreadsList,
35-
CpuTimeProvider* pProvider,
36-
CallstackProvider calstackProvider,
33+
CpuSampleProvider* pProvider,
3734
MetricsRegistry& metricsRegistry) noexcept;
3835

3936
~TimerCreateCpuProfiler();
@@ -57,8 +54,7 @@ class TimerCreateCpuProfiler : public ServiceBase
5754

5855
ProfilerSignalManager* _pSignalManager;
5956
IManagedThreadList* _pManagedThreadsList;
60-
CpuTimeProvider* _pProvider;
61-
CallstackProvider _callstackProvider;
57+
CpuSampleProvider* _pProvider;
6258
std::chrono::milliseconds _samplingInterval;
6359
std::shared_mutex _registerLock;
6460
std::shared_ptr<CounterMetric> _totalSampling;

0 commit comments

Comments
 (0)