Skip to content

Commit 8af94e2

Browse files
committed
src: add fast api calls to push span strings
This commit introduces performance optimizations for tracing span attribute handling by adding two new fast API calls: 1. FastPushSpanDataString - Optimized version of the existing string push method 2. FastPushSpanDataString3 - New method to efficiently handle three string values in a single call Key changes include: - Avoided JavaScript string concatenation which produces non-flattened V8 strings that cannot trigger fast API paths - Implemented direct passing of individual string components to C++ where concatenation can happen after the fast API boundary - Refactored HTTP URL construction in both client and server to leverage this optimization - Improved span context handling by storing trace ID, span ID, and parent span ID as separate attributes rather than a single concatenated string These optimizations significantly improve performance in high load scenarios: - Up to 3.24% higher throughput in maximum load tests - Up to 57.75% lower latency at P99.9 under constant high rate (50k req/sec) - Substantial improvements across all high percentile latencies (P95-P99.999) - Most dramatic gains observed in tail latencies under sustained heavy load === Benchmark Summary === Benchmark Type: wrk2 (constant rate) Old Version: ./nsolid_baseline New Version: ./nsolid_fast_push_string Iterations: 50 Connections: 10 Duration: 60s Rate: 40k req/sec +------------------+-------------+-------------+------------+-----+ | Metric | Old Version | New Version | Difference | Sig | +------------------+-------------+-------------+------------+-----+ | Avg Latency (ms) | 2.58 | 2.34 | N/A | | | P50 Latency (ms) | 1.09 | 1.10 | +0.80% | *** | | P90 Latency (ms) | 2.22 | 2.20 | -0.63% | *** | | P99 Latency (ms) | 3.09 | 2.99 | -3.31% | *** | | Avg Req/Sec | 39,992.50 | 39,992.53 | +0.00% | | +------------------+-------------+-------------+------------+-----+ === Benchmark Summary === Benchmark Type: wrk2 (constant rate) Old Version: ./nsolid_baseline New Version: ./nsolid_fast_push_string Iterations: 50 Connections: 10 Duration: 60s Rate: 50k req/sec +------------------+-------------+-------------+------------+-----+ | Metric | Old Version | New Version | Difference | Sig | +------------------+-------------+-------------+------------+-----+ | Avg Latency (ms) | 9.92 | 5.20 | N/A | | | P50 Latency (ms) | 0.95 | 0.94 | -0.54% | | | P90 Latency (ms) | 2.54 | 2.35 | -7.47% | *** | | P99 Latency (ms) | 12.00 | 5.58 | -53.49% | *** | | Avg Req/Sec | 49,990.78 | 49,990.72 | -0.00% | | +------------------+-------------+-------------+------------+-----+ === Benchmark Summary === Benchmark Type: wrk (maximum throughput) Old Version: ./nsolid_baseline New Version: ./nsolid_fast_push_string Iterations: 50 Connections: 10 Duration: 60s Threads: 2 +------------------+-------------+-------------+------------+-----+ | Metric | Old Version | New Version | Difference | Sig | +------------------+-------------+-------------+------------+-----+ | Avg Latency (ms) | 0.65 | 0.64 | N/A | | | P50 Latency (ms) | 0.17 | 0.16 | -3.48% | *** | | P90 Latency (ms) | 0.33 | 0.32 | -2.30% | *** | | P99 Latency (ms) | 0.36 | 0.35 | -3.33% | *** | | Avg Req/Sec | 52,398.20 | 54,094.55 | +3.24% | *** | +------------------+-------------+-------------+------------+-----+
1 parent 973e241 commit 8af94e2

File tree

8 files changed

+181
-48
lines changed

8 files changed

+181
-48
lines changed

lib/_http_client.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -481,10 +481,9 @@ function ClientRequest(input, options, cb) {
481481
api.context.with(api.trace.setSpan(api.context.active(), span), () => {
482482
fn.call(this);
483483
// At this point we know the request is actually being initiated
484-
const u = `${protocol}//${this.getHeader('host')}${this.path}`;
485484
span._pushSpanDataString(kSpanHttpMethod, method);
486485
span._pushSpanDataString(kSpanHttpProtocolVersion, '1.1');
487-
span._pushSpanDataString(kSpanHttpReqUrl, u);
486+
span._pushSpanDataString3(kSpanHttpReqUrl, protocol, this.getHeader('host') || 'localhost', this.path);
488487
this.prependOnceListener('error', requestOnError);
489488
this.once('close', requestOnClose);
490489
}, this);

lib/_http_server.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1111,8 +1111,7 @@ function parserOnIncoming(server, socket, state, req, keepAlive) {
11111111
let api;
11121112
let span;
11131113
if (generateSpan(kSpanHttpServer)) {
1114-
const protocol = req.connection.encrypted ? 'https' : 'http';
1115-
const u = `${protocol}://${req.headers.host || 'localhost'}${req.originalUrl || req.url}`;
1114+
const protocol = req.connection.encrypted ? 'https:' : 'http:';
11161115
api = getApi();
11171116
const tracer = api.trace.getTracer('http');
11181117
const ctxt = extractSpanContextFromHttpHeaders(api.ROOT_CONTEXT,
@@ -1126,7 +1125,7 @@ function parserOnIncoming(server, socket, state, req, keepAlive) {
11261125
req[nsolid_span_id_s] = span;
11271126
span._pushSpanDataString(kSpanHttpMethod, req.method);
11281127
span._pushSpanDataString(kSpanHttpProtocolVersion, req.httpVersion);
1129-
span._pushSpanDataString(kSpanHttpReqUrl, u);
1128+
span._pushSpanDataString3(kSpanHttpReqUrl, protocol, req.headers.host || 'localhost', req.originalUrl || req.url);
11301129
}
11311130

11321131
if (req.upgrade) {

lib/internal/otel/trace.js

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ const {
88
const binding = internalBinding('nsolid_api');
99
const { getSpanId,
1010
getTraceId,
11-
pushSpanDataString,
1211
nsolid_consts } = binding;
1312

1413
const {
@@ -38,7 +37,7 @@ class SpanContext {
3837
}
3938
}
4039

41-
function Span(internalId, ids_str, spanContext, startTime, type, kind, links) {
40+
function Span(internalId, parentSpanId, spanContext, startTime, type, kind, links) {
4241
this._spanContext = spanContext;
4342
this.ended = false;
4443
this.internalId = internalId;
@@ -56,7 +55,11 @@ function Span(internalId, ids_str, spanContext, startTime, type, kind, links) {
5655
this._pushSpanDataUint64(nsolid_consts.kSpanKind, this.kind);
5756
}
5857

59-
this._pushSpanDataString(nsolid_consts.kSpanOtelIds, ids_str);
58+
this._pushSpanDataString(nsolid_consts.kSpanTraceId, spanContext.traceId);
59+
this._pushSpanDataString(nsolid_consts.kSpanSpanId, spanContext.spanId);
60+
if (parentSpanId) {
61+
this._pushSpanDataString(nsolid_consts.kSpanParentSpanId, parentSpanId);
62+
}
6063
}
6164
}
6265

@@ -210,7 +213,15 @@ Span.prototype._pushSpanDataDouble = function(type, name) {
210213

211214
Span.prototype._pushSpanDataString = function(type, name) {
212215
if (this._isSampled()) {
213-
pushSpanDataString(this.internalId, type, name);
216+
binding.pushSpanDataString(this.internalId, type, name);
217+
}
218+
219+
return this;
220+
};
221+
222+
Span.prototype._pushSpanDataString3 = function(type, val1, val2, val3) {
223+
if (this._isSampled()) {
224+
binding.pushSpanDataString3(this.internalId, type, val1, val2, val3);
214225
}
215226

216227
return this;
@@ -240,20 +251,17 @@ class Tracer {
240251
}
241252
}
242253

243-
let ids_str;
244254
let traceState;
245255
let traceId;
246256

247257
const spanId = getSpanId();
248258
if (!parentContext) {
249259
// New root span.
250260
traceId = getTraceId();
251-
ids_str = `${traceId}:${spanId}`;
252261
} else {
253262
// New child span.
254263
traceId = parentContext.traceId;
255264
traceState = parentContext.traceState;
256-
ids_str = `${traceId}:${spanId}:${parentContext.spanId}`;
257265
}
258266

259267
const internalId = newInternalSpanId();
@@ -269,7 +277,7 @@ class Tracer {
269277

270278
const startTime = options.startTime || now();
271279
const span = new Span(internalId,
272-
ids_str,
280+
parentContext?.spanId,
273281
spanContext,
274282
startTime,
275283
options.type || nsolid_consts.kSpanCustom,

src/node_external_reference.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,19 @@ using CFunctionBufferCopy =
8888
uint32_t source_start,
8989
uint32_t to_copy);
9090

91+
using CFunctionPushSpanDataString =
92+
void (*)(v8::Local<v8::Object> receiver,
93+
uint32_t trace_id,
94+
uint32_t type,
95+
const v8::FastOneByteString& val);
96+
using CFunctionPushSpanDataString3 =
97+
void (*)(v8::Local<v8::Object> receiver,
98+
uint32_t trace_id,
99+
uint32_t type,
100+
const v8::FastOneByteString& val1,
101+
const v8::FastOneByteString& val2,
102+
const v8::FastOneByteString& val3);
103+
91104
// This class manages the external references from the V8 heap
92105
// to the C++ addresses in Node.js.
93106
class ExternalReferenceRegistry {
@@ -117,6 +130,8 @@ class ExternalReferenceRegistry {
117130
V(CFunctionWithBool) \
118131
V(CFunctionBufferCopy) \
119132
V(CFunctionWriteString) \
133+
V(CFunctionPushSpanDataString) \
134+
V(CFunctionPushSpanDataString3) \
120135
V(const v8::CFunctionInfo*) \
121136
V(v8::FunctionCallback) \
122137
V(v8::AccessorNameGetterCallback) \

src/nsolid/nsolid_api.cc

Lines changed: 103 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,11 @@ using nsuv::ns_timer;
3535

3636
using tracing::Span;
3737
using tracing::SpanItem;
38-
using tracing::SpanPropBase;
3938

4039
using v8::ArrayBuffer;
4140
using v8::BackingStore;
4241
using v8::Context;
42+
using v8::FastOneByteString;
4343
using v8::Float64Array;
4444
using v8::Function;
4545
using v8::FunctionCallbackInfo;
@@ -2319,34 +2319,94 @@ void BindingData::PushSpanDataUint64Impl(BindingData* data,
23192319
SpanItem{ trace_id, envinst->thread_id(), std::move(prop) });
23202320
}
23212321

2322-
2323-
static void PushSpanDataString(const FunctionCallbackInfo<Value>& args) {
2322+
void BindingData::SlowPushSpanDataString(
2323+
const FunctionCallbackInfo<Value>& args) {
23242324
Isolate* isolate = args.GetIsolate();
2325-
EnvInst* envinst = EnvInst::GetEnvLocalInst(isolate);
2326-
DCHECK_NE(envinst, nullptr);
23272325
DCHECK_EQ(args.Length(), 3);
23282326
DCHECK(args[0]->IsUint32());
23292327
DCHECK(args[1]->IsUint32());
23302328
DCHECK(args[2]->IsString());
23312329
uint32_t trace_id = args[0].As<Uint32>()->Value();
2332-
Span::PropType prop_type =
2333-
static_cast<Span::PropType>(args[1].As<Uint32>()->Value());
2334-
std::unique_ptr<SpanPropBase> prop;
2330+
uint32_t type = args[1].As<Uint32>()->Value();
23352331
Local<String> value_s = args[2].As<String>();
2336-
if (prop_type == Span::kSpanOtelIds && value_s->IsOneByte()) {
2337-
char ids[67];
2338-
int len = value_s->WriteOneByte(isolate, reinterpret_cast<uint8_t*>(ids));
2339-
ids[len] = '\0';
2340-
prop = Span::createSpanProp<std::string>(prop_type, ids);
2341-
} else {
2342-
String::Utf8Value value_str(isolate, value_s);
2343-
prop = Span::createSpanProp<std::string>(prop_type, *value_str);
2344-
}
2332+
BindingData* data = FromJSObject<BindingData>(args.This());
2333+
const std::string val = *String::Utf8Value(isolate, value_s);
2334+
PushSpanDataStringImpl(data, trace_id, type, val);
2335+
}
2336+
23452337

2346-
SpanItem item = { trace_id,
2347-
envinst->thread_id(),
2348-
std::move(prop) };
2349-
EnvList::Inst()->GetTracer()->pushSpanData(std::move(item));
2338+
void BindingData::FastPushSpanDataString(v8::Local<v8::Object> receiver,
2339+
uint32_t trace_id,
2340+
uint32_t type,
2341+
const FastOneByteString& val) {
2342+
PushSpanDataStringImpl(FromJSObject<BindingData>(receiver),
2343+
trace_id,
2344+
type,
2345+
std::string(val.data, val.length));
2346+
}
2347+
2348+
2349+
void BindingData::PushSpanDataStringImpl(BindingData* data,
2350+
uint32_t trace_id,
2351+
uint32_t type,
2352+
const std::string& val) {
2353+
Span::PropType prop_type = static_cast<Span::PropType>(type);
2354+
auto prop = Span::createSpanProp<std::string>(prop_type, val);
2355+
EnvInst* envinst = data->env()->envinst_.get();
2356+
EnvList::Inst()->GetTracer()->pushSpanData(
2357+
SpanItem{ trace_id, envinst->thread_id(), std::move(prop) });
2358+
}
2359+
2360+
2361+
void BindingData::SlowPushSpanDataString3(
2362+
const FunctionCallbackInfo<Value>& args) {
2363+
Isolate* isolate = args.GetIsolate();
2364+
DCHECK_EQ(args.Length(), 5);
2365+
DCHECK(args[0]->IsUint32());
2366+
DCHECK(args[1]->IsUint32());
2367+
DCHECK(args[2]->IsString());
2368+
DCHECK(args[3]->IsString());
2369+
DCHECK(args[4]->IsString());
2370+
uint32_t trace_id = args[0].As<Uint32>()->Value();
2371+
uint32_t type = args[1].As<Uint32>()->Value();
2372+
Local<String> value_s1 = args[2].As<String>();
2373+
Local<String> value_s2 = args[3].As<String>();
2374+
Local<String> value_s3 = args[4].As<String>();
2375+
BindingData* data = FromJSObject<BindingData>(args.This());
2376+
const std::string val1 = *String::Utf8Value(isolate, value_s1);
2377+
const std::string val2 = *String::Utf8Value(isolate, value_s2);
2378+
const std::string val3 = *String::Utf8Value(isolate, value_s3);
2379+
PushSpanDataStringImpl3(data, trace_id, type, val1, val2, val3);
2380+
}
2381+
2382+
2383+
void BindingData::FastPushSpanDataString3(v8::Local<v8::Object> receiver,
2384+
uint32_t trace_id,
2385+
uint32_t type,
2386+
const FastOneByteString& val1,
2387+
const FastOneByteString& val2,
2388+
const FastOneByteString& val3) {
2389+
PushSpanDataStringImpl3(FromJSObject<BindingData>(receiver),
2390+
trace_id,
2391+
type,
2392+
std::string(val1.data, val1.length),
2393+
std::string(val2.data, val2.length),
2394+
std::string(val3.data, val3.length));
2395+
}
2396+
2397+
void BindingData::PushSpanDataStringImpl3(BindingData* data,
2398+
uint32_t trace_id,
2399+
uint32_t type,
2400+
const std::string& val1,
2401+
const std::string& val2,
2402+
const std::string& val3) {
2403+
Span::PropType prop_type = static_cast<Span::PropType>(type);
2404+
ASSERT_EQ(prop_type, Span::kSpanHttpReqUrl);
2405+
auto prop =
2406+
Span::createSpanProp<std::string>(prop_type, val1 + "//" + val2 + val3);
2407+
EnvInst* envinst = data->env()->envinst_.get();
2408+
EnvList::Inst()->GetTracer()->pushSpanData(
2409+
SpanItem{ trace_id, envinst->thread_id(), std::move(prop) });
23502410
}
23512411

23522412

@@ -2900,6 +2960,10 @@ v8::CFunction BindingData::fast_push_span_data_double_(
29002960
v8::CFunction::Make(FastPushSpanDataDouble));
29012961
v8::CFunction BindingData::fast_push_span_data_uint64_(
29022962
v8::CFunction::Make(FastPushSpanDataUint64));
2963+
v8::CFunction BindingData::fast_push_span_data_string_(
2964+
v8::CFunction::Make(FastPushSpanDataString));
2965+
v8::CFunction BindingData::fast_push_span_data_string3_(
2966+
v8::CFunction::Make(FastPushSpanDataString3));
29032967

29042968

29052969
void BindingData::Initialize(Local<Object> target,
@@ -2939,10 +3003,19 @@ void BindingData::Initialize(Local<Object> target,
29393003
"pushSpanDataUint64",
29403004
SlowPushSpanDataUint64,
29413005
&fast_push_span_data_uint64_);
3006+
SetFastMethod(context,
3007+
target,
3008+
"pushSpanDataString",
3009+
SlowPushSpanDataString,
3010+
&fast_push_span_data_string_);
3011+
SetFastMethod(context,
3012+
target,
3013+
"pushSpanDataString3",
3014+
SlowPushSpanDataString3,
3015+
&fast_push_span_data_string3_);
29423016

29433017
SetMethod(context, target, "agentId", AgentId);
29443018
SetMethod(context, target, "writeLog", WriteLog);
2945-
SetMethod(context, target, "pushSpanDataString", PushSpanDataString);
29463019
SetMethod(context, target, "getEnvMetrics", GetEnvMetrics);
29473020
SetMethod(context, target, "getProcessMetrics", GetProcessMetrics);
29483021
SetMethod(context, target, "getProcessInfo", GetProcessInfo);
@@ -3072,9 +3145,16 @@ void BindingData::RegisterExternalReferences(
30723145
registry->Register(FastPushSpanDataUint64);
30733146
registry->Register(fast_push_span_data_uint64_.GetTypeInfo());
30743147

3148+
registry->Register(SlowPushSpanDataString);
3149+
registry->Register(FastPushSpanDataString);
3150+
registry->Register(fast_push_span_data_string_.GetTypeInfo());
3151+
3152+
registry->Register(SlowPushSpanDataString3);
3153+
registry->Register(FastPushSpanDataString3);
3154+
registry->Register(fast_push_span_data_string3_.GetTypeInfo());
3155+
30753156
registry->Register(AgentId);
30763157
registry->Register(WriteLog);
3077-
registry->Register(PushSpanDataString);
30783158
registry->Register(GetEnvMetrics);
30793159
registry->Register(GetProcessMetrics);
30803160
registry->Register(GetProcessInfo);

src/nsolid/nsolid_bindings.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
55

66
#include "node_snapshotable.h"
7+
#include "v8-fast-api-calls.h"
78

89
namespace node {
910
namespace nsolid {
@@ -58,6 +59,31 @@ class BindingData : public SnapshotableObject {
5859
uint32_t type,
5960
uint64_t val);
6061

62+
static void SlowPushSpanDataString(
63+
const v8::FunctionCallbackInfo<v8::Value>& args);
64+
static void FastPushSpanDataString(v8::Local<v8::Object> receiver,
65+
uint32_t trace_id,
66+
uint32_t type,
67+
const v8::FastOneByteString& val);
68+
static void PushSpanDataStringImpl(BindingData* data,
69+
uint32_t trace_id,
70+
uint32_t type,
71+
const std::string& val);
72+
static void SlowPushSpanDataString3(
73+
const v8::FunctionCallbackInfo<v8::Value>& args);
74+
static void FastPushSpanDataString3(v8::Local<v8::Object> receiver,
75+
uint32_t trace_id,
76+
uint32_t type,
77+
const v8::FastOneByteString& val1,
78+
const v8::FastOneByteString& val2,
79+
const v8::FastOneByteString& val3);
80+
static void PushSpanDataStringImpl3(BindingData* data,
81+
uint32_t trace_id,
82+
uint32_t type,
83+
const std::string& val1,
84+
const std::string& val2,
85+
const std::string& val3);
86+
6187
static void Initialize(v8::Local<v8::Object> target,
6288
v8::Local<v8::Value> unused,
6389
v8::Local<v8::Context> context,
@@ -71,6 +97,8 @@ class BindingData : public SnapshotableObject {
7197
static v8::CFunction fast_push_server_bucket_;
7298
static v8::CFunction fast_push_span_data_double_;
7399
static v8::CFunction fast_push_span_data_uint64_;
100+
static v8::CFunction fast_push_span_data_string_;
101+
static v8::CFunction fast_push_span_data_string3_;
74102
};
75103

76104
} // namespace nsolid

src/nsolid/nsolid_trace.cc

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,19 @@ void Span::add_prop(const SpanPropBase& prop) {
4949
stor_.start = performance_process_start_timestamp + prop.val<double>();
5050
}
5151
break;
52-
case Span::kSpanOtelIds:
52+
case Span::kSpanTraceId:
5353
{
54-
auto res = utils::split(prop.val<std::string>(), ':', 3);
55-
size_t size = res.size();
56-
DCHECK(size == 2 || size == 3);
57-
stor_.trace_id = res[0];
58-
stor_.span_id = res[1];
59-
if (size == 3) {
60-
stor_.parent_id = res[2];
61-
}
54+
stor_.trace_id = prop.val<std::string>();
55+
}
56+
break;
57+
case Span::kSpanSpanId:
58+
{
59+
stor_.span_id = prop.val<std::string>();
60+
}
61+
break;
62+
case Span::kSpanParentSpanId:
63+
{
64+
stor_.parent_id = prop.val<std::string>();
6265
}
6366
break;
6467
case Span::kSpanEnd:

0 commit comments

Comments
 (0)