Skip to content

Commit 6264b3c

Browse files
authored
[Profiler] Fix flakiness due to Libraries Info Cache (#8997)
1 parent fcc5881 commit 6264b3c

14 files changed

Lines changed: 147 additions & 11 deletions

File tree

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,14 @@ bool AutoResetEvent::Wait(std::chrono::milliseconds timeout)
5656
clock_gettime(CLOCK_REALTIME, &ts);
5757
ts.tv_nsec += timeout.count() % 1000 * 1'000'000;
5858
ts.tv_sec += timeout.count() / 1000;
59+
60+
// pthread_cond_timedwait rejects tv_nsec >= 1s with EINVAL, which this loop would
61+
// spin on instead of timing out. Both operands are < 1s, so one carry is enough.
62+
if (ts.tv_nsec >= 1'000'000'000)
63+
{
64+
ts.tv_nsec -= 1'000'000'000;
65+
ts.tv_sec += 1;
66+
}
5967
}
6068

6169
while(!_impl->_isSet)

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

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,8 @@ LibrariesInfoCache::LibrariesInfoCache(IConfiguration* configuration, shared::pm
152152
_newSymbols{_wrappersAllocator},
153153
#endif
154154
_stopRequested{false},
155-
_event(true)
155+
_event(true),
156+
_startTimeout(configuration->GetLibrariesInfoCacheStartTimeout())
156157
{
157158
if (_tracker)
158159
{
@@ -186,17 +187,11 @@ bool LibrariesInfoCache::StartImpl()
186187

187188
// Wait for the thread to be fully started and the cache populated
188189
// before setting s_instance and registering with libunwind.
189-
// Cache population can be slow under sanitizers (ASAN/UBSAN add overhead to
190-
// every allocation and memory access) — use a longer timeout in that case.
191-
#if defined(DD_SANITIZERS)
192-
constexpr auto startTimeout = 10s;
193-
#else
194-
constexpr auto startTimeout = 2s;
195-
#endif
196-
if (!startEvent->Wait(startTimeout))
190+
191+
if (!startEvent->Wait(_startTimeout))
197192
{
198-
Log::Error("Failed to populate LibrariesInfoCache within timeout. "
199-
"Not registering custom iterate_phdr_function with libunwind.");
193+
Log::Error("Failed to populate LibrariesInfoCache within timeout: ", _startTimeout,
194+
". Not registering custom iterate_phdr_function with libunwind.");
200195
_stopRequested = true;
201196
_event.Set();
202197
_worker.join();

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "shared/src/native-src/dd_memory_resource.hpp"
1414

1515
#include <atomic>
16+
#include <chrono>
1617
#include <link.h>
1718
#include <memory>
1819
#include <shared_mutex>
@@ -122,4 +123,7 @@ class LibrariesInfoCache : public ServiceBase
122123
std::thread _worker;
123124
std::atomic<bool> _stopRequested;
124125
AutoResetEvent _event;
126+
// How long StartImpl waits for the first cache population. Longer on slow/loaded
127+
// machines (CI), where the default is not enough and the cache is silently skipped.
128+
std::chrono::milliseconds _startTimeout;
125129
};

profiler/src/ProfilerEngine/Datadog.Profiler.Native/Configuration.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,14 @@ CpuProfilerType const Configuration::DefaultCpuProfilerType =
3535
#endif
3636
std::chrono::minutes const Configuration::DefaultDevHeapSnapshotInterval = 1min;
3737
std::chrono::minutes const Configuration::DefaultProdHeapSnapshotInterval = 5min;
38+
std::chrono::milliseconds const Configuration::DefaultLibrariesInfoCacheStartTimeout =
39+
#if defined(DD_SANITIZERS)
40+
// Sanitizers instrument every allocation and memory access, so populating the
41+
// cache takes considerably longer than in a regular build.
42+
10s;
43+
#else
44+
2s;
45+
#endif
3846

3947
Configuration::Configuration()
4048
{
@@ -126,6 +134,7 @@ Configuration::Configuration()
126134
_heapSnapshotCheckInterval = ExtractHeapSnapshotCheckInterval();
127135
_heapSnapshotMemoryPressureThreshold = GetEnvironmentValue(EnvironmentVariables::HeapSnapshotMemoryPressureThreshold, 50);
128136
_testHeapSnapshotInterval = ExtractTestHeapSnapshotInterval();
137+
_librariesInfoCacheStartTimeout = ExtractLibrariesInfoCacheStartTimeout();
129138
_heapHandleLimit = ExtractHeapHandleLimit();
130139
bool defaultUseManagedCodeCache =
131140
#if ARM64
@@ -937,6 +946,27 @@ std::chrono::seconds Configuration::GetTestHeapSnapshotInterval() const
937946
return _testHeapSnapshotInterval;
938947
}
939948

949+
std::chrono::milliseconds Configuration::ExtractLibrariesInfoCacheStartTimeout() const
950+
{
951+
// A negative value parses into a negative duration, and zero would make the wait
952+
// return immediately, silently disabling the cache: neither is ever intended.
953+
auto timeout = GetEnvironmentValue(EnvironmentVariables::LibrariesInfoCacheStartTimeout, DefaultLibrariesInfoCacheStartTimeout);
954+
if (timeout <= 0ms)
955+
{
956+
Log::Warn("Configuration: ", EnvironmentVariables::LibrariesInfoCacheStartTimeout,
957+
" env var must be strictly positive but is set to '", timeout,
958+
"' - '", DefaultLibrariesInfoCacheStartTimeout, "' is used instead");
959+
return DefaultLibrariesInfoCacheStartTimeout;
960+
}
961+
962+
return timeout;
963+
}
964+
965+
std::chrono::milliseconds Configuration::GetLibrariesInfoCacheStartTimeout() const
966+
{
967+
return _librariesInfoCacheStartTimeout;
968+
}
969+
940970
int32_t Configuration::ExtractHeapHandleLimit() const
941971
{
942972
// default handle count limit is 4096; could be changed via env vars from 1024 to 16000

profiler/src/ProfilerEngine/Datadog.Profiler.Native/Configuration.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ class Configuration final : public IConfiguration
9090
std::chrono::milliseconds GetHeapSnapshotCheckInterval() const override;
9191
uint32_t GetHeapSnapshotMemoryPressureThreshold() const override;
9292
std::chrono::seconds GetTestHeapSnapshotInterval() const override;
93+
std::chrono::milliseconds GetLibrariesInfoCacheStartTimeout() const override;
9394
uint32_t GetHeapHandleLimit() const override;
9495
bool UseManagedCodeCache() const override;
9596
bool IsMemoryFootprintEnabled() const override;
@@ -123,6 +124,7 @@ class Configuration final : public IConfiguration
123124
std::chrono::milliseconds ExtractHeapSnapshotCheckInterval() const;
124125
std::chrono::minutes GetDefaultHeapSnapshotInterval() const;
125126
std::chrono::seconds ExtractTestHeapSnapshotInterval() const;
127+
std::chrono::milliseconds ExtractLibrariesInfoCacheStartTimeout() const;
126128
int32_t ExtractHeapHandleLimit() const;
127129
uint32_t ExtractReferenceTreeFormat() const;
128130

@@ -140,6 +142,7 @@ class Configuration final : public IConfiguration
140142
static CpuProfilerType const DefaultCpuProfilerType;
141143
static std::chrono::minutes const DefaultDevHeapSnapshotInterval;
142144
static std::chrono::minutes const DefaultProdHeapSnapshotInterval;
145+
static std::chrono::milliseconds const DefaultLibrariesInfoCacheStartTimeout;
143146

144147
bool _isProfilingEnabled;
145148
bool _isCpuProfilingEnabled;
@@ -211,6 +214,7 @@ class Configuration final : public IConfiguration
211214
std::chrono::milliseconds _heapSnapshotCheckInterval;
212215
uint32_t _heapSnapshotMemoryPressureThreshold; // in % of used memory
213216
std::chrono::seconds _testHeapSnapshotInterval;
217+
std::chrono::milliseconds _librariesInfoCacheStartTimeout;
214218
bool _useManagedCodeCache;
215219
bool _isMemoryFootprintEnabled;
216220
uint32_t _referenceTreeFormat;

profiler/src/ProfilerEngine/Datadog.Profiler.Native/EnvironmentVariables.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ class EnvironmentVariables final
8585
// used for tests only
8686
inline static const shared::WSTRING ForceHttpSampling = WStr("DD_INTERNAL_PROFILING_FORCE_HTTP_SAMPLING");
8787
inline static const shared::WSTRING TestHeapSnapshotInterval = WStr("DD_INTERNAL_PROFILING_TEST_HEAPSNAPSHOT_INTERVAL");
88+
inline static const shared::WSTRING LibrariesInfoCacheStartTimeout = WStr("DD_INTERNAL_PROFILING_LIBRARIES_CACHE_START_TIMEOUT");
8889

8990
inline static const shared::WSTRING CIVisibilityEnabled = WStr("DD_CIVISIBILITY_ENABLED");
9091
inline static const shared::WSTRING InternalCIVisibilitySpanId = WStr("DD_INTERNAL_CIVISIBILITY_SPANID");

profiler/src/ProfilerEngine/Datadog.Profiler.Native/IConfiguration.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ class IConfiguration
8989
virtual std::chrono::milliseconds GetHeapSnapshotCheckInterval() const = 0;
9090
virtual uint32_t GetHeapSnapshotMemoryPressureThreshold() const = 0;
9191
virtual std::chrono::seconds GetTestHeapSnapshotInterval() const = 0;
92+
virtual std::chrono::milliseconds GetLibrariesInfoCacheStartTimeout() const = 0;
9293
virtual uint32_t GetHeapHandleLimit() const = 0;
9394
virtual bool UseManagedCodeCache() const = 0;
9495
virtual bool IsMemoryFootprintEnabled() const = 0;

profiler/test/Datadog.Profiler.IntegrationTests/Helpers/EnvironmentHelper.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,10 @@ internal void PopulateEnvironmentVariables(StringDictionary environmentVariables
195195
// Linux ARM64: native profiler requires DD_INTERNAL_PROFILING_ENABLED_ARM64; align managed enablement.
196196
environmentVariables["DD_INTERNAL_PROFILING_ENABLED_ARM64"] = "1";
197197

198+
// Test machines are slow and loaded enough that the default 2s is not always enough to
199+
// populate the libraries cache, and the profiler silently falls back to slower unwinding.
200+
environmentVariables[EnvironmentVariables.LibrariesInfoCacheStartTimeout] = "10000";
201+
198202
environmentVariables["DD_TRACE_ENABLED"] = "0";
199203

200204
environmentVariables["DD_PROFILING_UPLOAD_PERIOD"] = profilingExportIntervalInSeconds.ToString();

profiler/test/Datadog.Profiler.IntegrationTests/Helpers/EnvironmentVariables.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,6 @@ internal class EnvironmentVariables
4747
public const string TestHeapSnapshotInterval = "DD_INTERNAL_PROFILING_TEST_HEAPSNAPSHOT_INTERVAL";
4848
public const string UseManagedCodeCache = "DD_INTERNAL_PROFILING_USE_MANAGED_CODE_CACHE";
4949
public const string MemoryFootprintEnabled = "DD_INTERNAL_PROFILING_MEMORY_FOOTPRINT_ENABLED";
50+
public const string LibrariesInfoCacheStartTimeout = "DD_INTERNAL_PROFILING_LIBRARIES_CACHE_START_TIMEOUT";
5051
}
5152
}

profiler/test/Datadog.Profiler.Native.Tests/AutoResetEventTest.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
#include "profiler/src/ProfilerEngine/Datadog.Profiler.Native/AutoResetEvent.h"
77

88
#include <future>
9+
#include <thread>
10+
#include <time.h>
911

1012
#include "gtest/gtest.h"
1113

@@ -130,6 +132,23 @@ TEST(AutoResetEventTest, EnsureEventIsSignaledIfSetIsCalledWhileWaitingWithTimeo
130132
ASSERT_DURATION_LE(50ms, event->Set());
131133
}
132134

135+
TEST(AutoResetEventTest, EnsureTimeoutExpiresWhenSubSecondTimeoutOverflowsCurrentSecond)
136+
{
137+
// The timeout is added to the current wall clock, so the sub-second part of that sum
138+
// only overflows into the next second when the clock is already late enough in the
139+
// current one. Wait for that window, keeping enough margin to still be in it below.
140+
struct timespec now;
141+
do
142+
{
143+
std::this_thread::sleep_for(1ms);
144+
clock_gettime(CLOCK_REALTIME, &now);
145+
} while (now.tv_nsec < 900'000'000 || now.tv_nsec > 980'000'000);
146+
147+
auto event = CreateEvent(false);
148+
ASSERT_DURATION_LE(500ms, event->Wait(200ms));
149+
ASSERT_FALSE(event->IsSet());
150+
}
151+
133152
TEST(AutoResetEventTest, CheckCaseWhenEventHasTimeOutButSignaledLater)
134153
{
135154
auto event = CreateEvent(false);

0 commit comments

Comments
 (0)