Skip to content

Commit 6f31207

Browse files
committed
Add latency stats for projection transform
1 parent fc94c88 commit 6f31207

1 file changed

Lines changed: 68 additions & 36 deletions

File tree

src/mongo/db/exec/projection.cpp

Lines changed: 68 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828

2929
#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kQuery
3030

31+
#include <atomic>
32+
3133
#include "mongo/db/exec/projection.h"
3234

3335
#include "mongo/db/exec/plan_stage.h"
@@ -40,6 +42,8 @@
4042
#include "mongo/util/log.h"
4143
#include "mongo/util/mongoutils/str.h"
4244

45+
#include <butil/time.h>
46+
4347
namespace mongo {
4448

4549
using std::endl;
@@ -143,49 +147,77 @@ void ProjectionStage::transformSimpleInclusion(const BSONObj& in,
143147
}
144148

145149
Status ProjectionStage::transform(WorkingSetMember* member) {
146-
// The default no-fast-path case.
147-
if (ProjectionStageParams::NO_FAST_PATH == _projImpl) {
148-
return _exec->transform(member);
149-
}
150-
151-
BSONObjBuilder bob;
150+
// Track latency and statistics for transform (lock-free, resets every 10000 calls)
151+
static std::atomic<uint64_t> transformCallCount{0};
152+
static std::atomic<uint64_t> totalLatencyUs{0};
153+
static constexpr uint64_t WINDOW_SIZE = 100000;
152154

153-
// Note that even if our fast path analysis is bug-free something that is
154-
// covered might be invalidated and just be an obj. In this case we just go
155-
// through the SIMPLE_DOC path which is still correct if the covered data
156-
// is not available.
157-
//
158-
// SIMPLE_DOC implies that we expect an object so it's kind of redundant.
159-
if ((ProjectionStageParams::SIMPLE_DOC == _projImpl) || member->hasObj()) {
160-
// If we got here because of SIMPLE_DOC the planner shouldn't have messed up.
161-
invariant(member->hasObj());
155+
butil::Timer timer;
156+
timer.start();
162157

163-
// Apply the SIMPLE_DOC projection.
164-
transformSimpleInclusion(member->obj.value(), _includedFields, bob);
158+
// The default no-fast-path case.
159+
Status result;
160+
if (ProjectionStageParams::NO_FAST_PATH == _projImpl) {
161+
result = _exec->transform(member);
165162
} else {
166-
invariant(ProjectionStageParams::COVERED_ONE_INDEX == _projImpl);
167-
// We're pulling data out of the key.
168-
invariant(1 == member->keyData.size());
169-
size_t keyIndex = 0;
170-
171-
// Look at every key element...
172-
BSONObjIterator keyIterator(member->keyData[0].keyData);
173-
while (keyIterator.more()) {
174-
BSONElement elt = keyIterator.next();
175-
// If we're supposed to include it...
176-
if (_includeKey[keyIndex]) {
177-
// Do so.
178-
bob.appendAs(elt, _keyFieldNames[keyIndex]);
163+
BSONObjBuilder bob;
164+
165+
// Note that even if our fast path analysis is bug-free something that is
166+
// covered might be invalidated and just be an obj. In this case we just go
167+
// through the SIMPLE_DOC path which is still correct if the covered data
168+
// is not available.
169+
//
170+
// SIMPLE_DOC implies that we expect an object so it's kind of redundant.
171+
if ((ProjectionStageParams::SIMPLE_DOC == _projImpl) || member->hasObj()) {
172+
// If we got here because of SIMPLE_DOC the planner shouldn't have messed up.
173+
invariant(member->hasObj());
174+
175+
// Apply the SIMPLE_DOC projection.
176+
transformSimpleInclusion(member->obj.value(), _includedFields, bob);
177+
} else {
178+
invariant(ProjectionStageParams::COVERED_ONE_INDEX == _projImpl);
179+
// We're pulling data out of the key.
180+
invariant(1 == member->keyData.size());
181+
size_t keyIndex = 0;
182+
183+
// Look at every key element...
184+
BSONObjIterator keyIterator(member->keyData[0].keyData);
185+
while (keyIterator.more()) {
186+
BSONElement elt = keyIterator.next();
187+
// If we're supposed to include it...
188+
if (_includeKey[keyIndex]) {
189+
// Do so.
190+
bob.appendAs(elt, _keyFieldNames[keyIndex]);
191+
}
192+
++keyIndex;
179193
}
180-
++keyIndex;
181194
}
195+
196+
member->keyData.clear();
197+
member->recordId = RecordId();
198+
member->obj = Snapshotted<BSONObj>(SnapshotId(), bob.obj());
199+
member->transitionToOwnedObj();
200+
result = Status::OK();
201+
}
202+
203+
timer.stop();
204+
uint64_t latencyUs = timer.u_elapsed();
205+
206+
// Update statistics (lock-free using atomics)
207+
uint64_t count = transformCallCount.fetch_add(1, std::memory_order_relaxed) + 1;
208+
totalLatencyUs.fetch_add(latencyUs, std::memory_order_relaxed);
209+
210+
// Log every 10000 calls with statistics from recent window
211+
if (count % WINDOW_SIZE == 0) {
212+
// Use memory_order_acquire to ensure we see all updates before reading
213+
uint64_t windowTotalLatencyUs = totalLatencyUs.exchange(0, std::memory_order_acq_rel);
214+
uint64_t avgLatencyUs = windowTotalLatencyUs / WINDOW_SIZE;
215+
MONGO_LOG(0) << "ProjectionStage::transform statistics (recent " << WINDOW_SIZE
216+
<< " calls): "
217+
<< "average latency: " << avgLatencyUs << " us";
182218
}
183219

184-
member->keyData.clear();
185-
member->recordId = RecordId();
186-
member->obj = Snapshotted<BSONObj>(SnapshotId(), bob.obj());
187-
member->transitionToOwnedObj();
188-
return Status::OK();
220+
return result;
189221
}
190222

191223
bool ProjectionStage::isEOF() {

0 commit comments

Comments
 (0)