Skip to content

Commit 297ce16

Browse files
authored
Merge pull request #2779 from cloudflare/mar/memory-cache-lwt
Add lock wait time metrics for Memory Cache.
2 parents 59e6a73 + 76a905c commit 297ce16

File tree

6 files changed

+76
-21
lines changed

6 files changed

+76
-21
lines changed

src/workerd/api/memory-cache.c++

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,13 @@ static bool hasExpired(const kj::Maybe<double>& expiration, bool allowOutsideIoC
3838

3939
SharedMemoryCache::SharedMemoryCache(kj::Maybe<const MemoryCacheProvider&> provider,
4040
kj::StringPtr id,
41-
kj::Maybe<AdditionalResizeMemoryLimitHandler&> additionalResizeMemoryLimitHandler)
41+
kj::Maybe<AdditionalResizeMemoryLimitHandler&> additionalResizeMemoryLimitHandler,
42+
const kj::MonotonicClock& timer)
4243
: data(),
4344
provider(provider),
4445
id(kj::str(id)),
45-
additionalResizeMemoryLimitHandler(additionalResizeMemoryLimitHandler) {}
46+
additionalResizeMemoryLimitHandler(additionalResizeMemoryLimitHandler),
47+
timer(timer) {}
4648

4749
SharedMemoryCache::~SharedMemoryCache() noexcept(false) {
4850
KJ_IF_SOME(p, provider) {
@@ -219,8 +221,9 @@ void SharedMemoryCache::removeIfExistsWhileLocked(
219221
kj::Own<const SharedMemoryCache> SharedMemoryCache::create(
220222
kj::Maybe<const MemoryCacheProvider&> provider,
221223
kj::StringPtr id,
222-
kj::Maybe<AdditionalResizeMemoryLimitHandler&> handler) {
223-
return kj::atomicRefcounted<const SharedMemoryCache>(provider, id, handler);
224+
kj::Maybe<AdditionalResizeMemoryLimitHandler&> handler,
225+
const kj::MonotonicClock& timer) {
226+
return kj::atomicRefcounted<const SharedMemoryCache>(provider, id, handler, timer);
224227
}
225228

226229
SharedMemoryCache::Use::Use(kj::Own<const SharedMemoryCache> cache, const Limits& limits)
@@ -240,14 +243,22 @@ SharedMemoryCache::Use::~Use() noexcept(false) {
240243
}
241244

242245
kj::Maybe<kj::Own<CacheValue>> SharedMemoryCache::Use::getWithoutFallback(
243-
const kj::String& key) const {
244-
auto data = cache->data.lockExclusive();
246+
const kj::String& key, SpanBuilder& span) const {
247+
kj::Locked<ThreadUnsafeData> data = [&] {
248+
auto memoryCacheLockRecord =
249+
ScopedDurationTagger(span, memoryCachekLockWaitTimeTag, cache->timer);
250+
return cache->data.lockExclusive();
251+
}();
245252
return cache->getWhileLocked(*data, key);
246253
}
247254

248255
kj::OneOf<kj::Own<CacheValue>, kj::Promise<SharedMemoryCache::Use::GetWithFallbackOutcome>>
249-
SharedMemoryCache::Use::getWithFallback(const kj::String& key) const {
250-
auto data = cache->data.lockExclusive();
256+
SharedMemoryCache::Use::getWithFallback(const kj::String& key, SpanBuilder& span) const {
257+
kj::Locked<ThreadUnsafeData> data = [&] {
258+
auto memoryCacheLockRecord =
259+
ScopedDurationTagger(span, memoryCachekLockWaitTimeTag, cache->timer);
260+
return cache->data.lockExclusive();
261+
}();
251262
KJ_IF_SOME(existingValue, cache->getWhileLocked(*data, key)) {
252263
return kj::mv(existingValue);
253264
} else KJ_IF_SOME(existingInProgress, data->inProgress.find(key)) {
@@ -374,7 +385,7 @@ jsg::Promise<jsg::JsRef<jsg::JsValue>> MemoryCache::read(jsg::Lock& js,
374385
auto readSpan = IoContext::current().makeTraceSpan("memory_cache_read"_kjc);
375386

376387
KJ_IF_SOME(fallback, optionalFallback) {
377-
KJ_SWITCH_ONEOF(cacheUse.getWithFallback(key.value)) {
388+
KJ_SWITCH_ONEOF(cacheUse.getWithFallback(key.value, readSpan)) {
378389
KJ_CASE_ONEOF(result, kj::Own<CacheValue>) {
379390
// Optimization: Don't even release the isolate lock if the value is aleady in cache.
380391
jsg::Deserializer deserializer(js, result->bytes.asPtr());
@@ -423,7 +434,7 @@ jsg::Promise<jsg::JsRef<jsg::JsValue>> MemoryCache::read(jsg::Lock& js,
423434
}
424435
KJ_UNREACHABLE;
425436
} else {
426-
KJ_IF_SOME(cacheValue, cacheUse.getWithoutFallback(key.value)) {
437+
KJ_IF_SOME(cacheValue, cacheUse.getWithoutFallback(key.value, readSpan)) {
427438
jsg::Deserializer deserializer(js, cacheValue->bytes.asPtr());
428439
return js.resolvedPromise(jsg::JsRef(js, deserializer.readValue(js)));
429440
}
@@ -433,10 +444,11 @@ jsg::Promise<jsg::JsRef<jsg::JsValue>> MemoryCache::read(jsg::Lock& js,
433444

434445
// ======================================================================================
435446

436-
MemoryCacheProvider::MemoryCacheProvider(
447+
MemoryCacheProvider::MemoryCacheProvider(const kj::MonotonicClock& timer,
437448
kj::Maybe<SharedMemoryCache::AdditionalResizeMemoryLimitHandler>
438449
additionalResizeMemoryLimitHandler)
439-
: additionalResizeMemoryLimitHandler(kj::mv(additionalResizeMemoryLimitHandler)) {}
450+
: additionalResizeMemoryLimitHandler(kj::mv(additionalResizeMemoryLimitHandler)),
451+
timer(timer) {}
440452

441453
MemoryCacheProvider::~MemoryCacheProvider() noexcept(false) {
442454
// TODO(cleanup): Later, assuming progress is made on kj::Ptr<T>, we ought to be able
@@ -456,7 +468,7 @@ kj::Own<const SharedMemoryCache> MemoryCacheProvider::getInstance(
456468
-> SharedMemoryCache::AdditionalResizeMemoryLimitHandler& {
457469
return const_cast<SharedMemoryCache::AdditionalResizeMemoryLimitHandler&>(handler);
458470
});
459-
return SharedMemoryCache::create(provider, id, handler);
471+
return SharedMemoryCache::create(provider, id, handler, timer);
460472
};
461473

462474
KJ_IF_SOME(cid, cacheId) {

src/workerd/api/memory-cache.h

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
#pragma once
22

3+
#include <workerd/io/trace.h>
34
#include <workerd/jsg/jsg.h>
45
#include <workerd/util/uuid.h>
56

67
#include <kj/hash.h>
78
#include <kj/map.h>
89
#include <kj/mutex.h>
910
#include <kj/table.h>
11+
#include <kj/timer.h>
1012

1113
#include <set>
1214

@@ -150,7 +152,8 @@ class SharedMemoryCache: public kj::AtomicRefcounted {
150152

151153
SharedMemoryCache(kj::Maybe<const MemoryCacheProvider&> provider,
152154
kj::StringPtr id,
153-
kj::Maybe<AdditionalResizeMemoryLimitHandler&> additionalResizeMemoryLimitHandler);
155+
kj::Maybe<AdditionalResizeMemoryLimitHandler&> additionalResizeMemoryLimitHandler,
156+
const kj::MonotonicClock& timer);
154157

155158
~SharedMemoryCache() noexcept(false);
156159

@@ -160,7 +163,8 @@ class SharedMemoryCache: public kj::AtomicRefcounted {
160163

161164
static kj::Own<const SharedMemoryCache> create(kj::Maybe<const MemoryCacheProvider&> provider,
162165
kj::StringPtr id,
163-
kj::Maybe<AdditionalResizeMemoryLimitHandler&> additionalResizeMemoryLimitHandler);
166+
kj::Maybe<AdditionalResizeMemoryLimitHandler&> additionalResizeMemoryLimitHandler,
167+
const kj::MonotonicClock& timer);
164168

165169
public:
166170
// RAII class that attaches itself to a cache, suggests cache limits to the
@@ -176,7 +180,8 @@ class SharedMemoryCache: public kj::AtomicRefcounted {
176180
// Returns a cached value for the given key if one exists (and has not
177181
// expired). If no such value exists, nothing is returned, regardless of any
178182
// in-progress fallbacks trying to produce such a value.
179-
kj::Maybe<kj::Own<CacheValue>> getWithoutFallback(const kj::String& key) const;
183+
kj::Maybe<kj::Own<CacheValue>> getWithoutFallback(
184+
const kj::String& key, SpanBuilder& span) const;
180185

181186
struct FallbackResult {
182187
kj::Own<CacheValue> value;
@@ -191,7 +196,7 @@ class SharedMemoryCache: public kj::AtomicRefcounted {
191196
// or to a FallbackDoneCallback. In the latter case, the caller should
192197
// invoke the fallback function.
193198
kj::OneOf<kj::Own<CacheValue>, kj::Promise<GetWithFallbackOutcome>> getWithFallback(
194-
const kj::String& key) const;
199+
const kj::String& key, SpanBuilder& span) const;
195200

196201
private:
197202
// Creates a new FallbackDoneCallback associated with the given
@@ -210,6 +215,7 @@ class SharedMemoryCache: public kj::AtomicRefcounted {
210215
void handleFallbackFailure(InProgress& inProgress) const;
211216

212217
kj::Own<const SharedMemoryCache> cache;
218+
static constexpr auto memoryCachekLockWaitTimeTag = "memory_cache_lock_wait_time_ns"_kjc;
213219
Limits limits;
214220
};
215221

@@ -453,6 +459,8 @@ class SharedMemoryCache: public kj::AtomicRefcounted {
453459
// Same as above, the MemoryCacheProvider owns the actual handler here. Since that is guaranteed
454460
// to outlive this SharedMemoryCache instance, so is the handler.
455461
kj::Maybe<AdditionalResizeMemoryLimitHandler&> additionalResizeMemoryLimitHandler;
462+
463+
const kj::MonotonicClock& timer;
456464
};
457465

458466
// JavaScript class that allows accessing an in-memory cache.
@@ -488,8 +496,9 @@ class MemoryCache: public jsg::Object {
488496
// the in memory cache is being used.
489497
class MemoryCacheProvider {
490498
public:
491-
MemoryCacheProvider(kj::Maybe<SharedMemoryCache::AdditionalResizeMemoryLimitHandler>
492-
additionalResizeMemoryLimitHandler = kj::none);
499+
MemoryCacheProvider(const kj::MonotonicClock& timer,
500+
kj::Maybe<SharedMemoryCache::AdditionalResizeMemoryLimitHandler>
501+
additionalResizeMemoryLimitHandler = kj::none);
493502
KJ_DISALLOW_COPY_AND_MOVE(MemoryCacheProvider);
494503
~MemoryCacheProvider() noexcept(false);
495504

@@ -507,6 +516,8 @@ class MemoryCacheProvider {
507516
// to avoid the use of the bare pointer to SharedMemoryCache* here. When the SharedMemoryCache
508517
// is destroyed, it will remove itself from this cache by calling removeInstance.
509518
kj::MutexGuarded<kj::HashMap<kj::String, const SharedMemoryCache*>> caches;
519+
520+
const kj::MonotonicClock& timer;
510521
};
511522

512523
// clang-format off

src/workerd/io/trace.c++

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -795,4 +795,19 @@ void WorkerTracer::setTrace(rpc::Trace::Reader reader) {
795795
trace->mergeFrom(reader, pipelineLogLevel);
796796
}
797797

798+
ScopedDurationTagger::ScopedDurationTagger(
799+
SpanBuilder& span, kj::ConstString key, const kj::MonotonicClock& timer)
800+
: span(span),
801+
key(kj::mv(key)),
802+
timer(timer),
803+
startTime(timer.now()) {}
804+
805+
ScopedDurationTagger::~ScopedDurationTagger() noexcept(false) {
806+
auto duration = timer.now() - startTime;
807+
if (isPredictableModeForTest()) {
808+
duration = 0 * kj::NANOSECONDS;
809+
}
810+
span.setTag(kj::mv(key), duration / kj::NANOSECONDS);
811+
}
812+
798813
} // namespace workerd

src/workerd/io/trace.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,4 +704,21 @@ struct TraceParentContext {
704704
SpanParent limeParentSpan;
705705
};
706706

707+
// RAII object that measures the time duration over its lifetime. It tags this duration onto a
708+
// given request span using a specified tag name. Ideal for automatically tracking and logging
709+
// execution times within a scoped block.
710+
class ScopedDurationTagger {
711+
public:
712+
explicit ScopedDurationTagger(
713+
SpanBuilder& span, kj::ConstString key, const kj::MonotonicClock& timer);
714+
~ScopedDurationTagger() noexcept(false);
715+
KJ_DISALLOW_COPY_AND_MOVE(ScopedDurationTagger);
716+
717+
private:
718+
SpanBuilder& span;
719+
kj::ConstString key;
720+
const kj::MonotonicClock& timer;
721+
const kj::TimePoint startTime;
722+
};
723+
707724
} // namespace workerd

src/workerd/server/server.c++

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ Server::Server(kj::Filesystem& fs,
164164
entropySource(entropySource),
165165
reportConfigError(kj::mv(reportConfigError)),
166166
consoleMode(consoleMode),
167-
memoryCacheProvider(kj::heap<api::MemoryCacheProvider>()),
167+
memoryCacheProvider(kj::heap<api::MemoryCacheProvider>(timer)),
168168
tasks(*this) {}
169169

170170
Server::~Server() noexcept(false) {}

src/workerd/tests/test-fixture.c++

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ TestFixture::TestFixture(SetupParams&& params)
321321
false),
322322
isolateLimitEnforcer(kj::heap<MockIsolateLimitEnforcer>()),
323323
errorReporter(kj::heap<MockErrorReporter>()),
324-
memoryCacheProvider(kj::heap<api::MemoryCacheProvider>()),
324+
memoryCacheProvider(kj::heap<api::MemoryCacheProvider>(*timer)),
325325
api(kj::heap<server::WorkerdApi>(testV8System,
326326
params.featureFlags.orDefault(CompatibilityFlags::Reader()),
327327
*isolateLimitEnforcer,

0 commit comments

Comments
 (0)