Skip to content

Commit 894d4bd

Browse files
authored
Merge pull request #5 from sigmacomputing/wes/optimize-override-tags
Optimize overrideTags for objects
2 parents 090a4b3 + c8b254b commit 894d4bd

File tree

6 files changed

+2376
-1242
lines changed

6 files changed

+2376
-1242
lines changed

.github/workflows/node.js.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
strategy:
1616
matrix:
1717
os: [ubuntu-latest, windows-latest]
18-
node-version: [10.x, 12.x, 14.x, 16.x, 18.x]
18+
node-version: [18.x]
1919
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
2020
runs-on: ${{ matrix.os }}
2121
steps:

lib/helpers.js

+58-2
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@ function formatTags(tags, telegraf) {
2727
/**
2828
* Overrides tags in parent with tags from child with the same name (case sensitive) and return the result as new
2929
* array. parent and child are not mutated.
30+
*
31+
* OverrideTags2 is faster, but it requires that the parent/child are both objects, not arrays.
3032
*/
31-
function overrideTags (parent, child, telegraf) {
33+
function overrideTagsUnoptimized (parent, child, telegraf) {
3234
const seenKeys = {};
3335
const toAppend = [];
3436

@@ -61,6 +63,58 @@ function overrideTags (parent, child, telegraf) {
6163
return toAppend.length > 0 ? tags.concat(toAppend) : tags;
6264
}
6365

66+
/**
67+
* Overrides tags in parent with tags from child with the same name (case sensitive) and return the result as a string.
68+
*/
69+
function overrideTagsToStringUnoptimized(parent, child, telegraf, separator) {
70+
const tags = overrideTagsUnoptimized(parent, child, telegraf);
71+
return tags.join(separator);
72+
}
73+
74+
/**
75+
* Overrides tags in parent with tags from child with the same name (case sensitive) and return the result as a string.
76+
*
77+
* More performant than overrideTagsUnoptimized. But it requires that globalTags and child are both objects, not arrays.
78+
*/
79+
function overrideTags2 (globalTags, globalTagsFullMemo, child, telegraf, separator) {
80+
let result = '';
81+
const usedGlobalKeys = new Set();
82+
// eslint-disable-next-line require-jsdoc
83+
function addToResult(tag, addToFront) {
84+
if (result === '') {
85+
result += tag;
86+
}
87+
// this is just here to match the behavior of overrideTagsUnoptimized
88+
else if (addToFront) {
89+
result = tag + separator + result;
90+
}
91+
else {
92+
result += separator + tag;
93+
}
94+
}
95+
for (const c of Object.keys(child)) {
96+
// there is a global tag with the same name as the child tag - use child
97+
const formattedChildKey = sanitizeTags(c);
98+
if (Object.hasOwn(globalTags, formattedChildKey)) {
99+
usedGlobalKeys.add(formattedChildKey);
100+
}
101+
const serializedTagWithValue = `${formattedChildKey}:${sanitizeTags(child[c], telegraf)}`;
102+
addToResult(serializedTagWithValue);
103+
}
104+
if (usedGlobalKeys.size === 0) {
105+
addToResult(globalTagsFullMemo, true);
106+
}
107+
else {
108+
for (const g of Object.keys(globalTags)) {
109+
if (!usedGlobalKeys.has(g)) {
110+
const serializedTagWithValue = `${g}:${sanitizeTags(globalTags[g], telegraf)}`;
111+
addToResult(serializedTagWithValue, true);
112+
}
113+
}
114+
}
115+
return result;
116+
}
117+
64118
/**
65119
* Formats a date for use with DataDog
66120
*/
@@ -112,7 +166,9 @@ function getDefaultRoute() {
112166

113167
module.exports = {
114168
formatTags: formatTags,
115-
overrideTags: overrideTags,
169+
overrideTagsUnoptimized: overrideTagsUnoptimized,
170+
overrideTagsToStringUnoptimized: overrideTagsToStringUnoptimized,
171+
overrideTags2: overrideTags2,
116172
formatDate: formatDate,
117173
getDefaultRoute: getDefaultRoute,
118174
sanitizeTags: sanitizeTags

lib/statsFunctions.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ function applyStatsFns (Client) {
250250

251251
let mergedTags = this.globalTags;
252252
if (tags && typeof(tags) === 'object') {
253-
mergedTags = helpers.overrideTags(mergedTags, tags, this.telegraf);
253+
mergedTags = helpers.overrideTagsUnoptimized(mergedTags, tags, this.telegraf);
254254
}
255255
if (mergedTags.length > 0) {
256256
check.push(`#${mergedTags.join(',')}`);

lib/statsd.js

+36-10
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,12 @@ const Client = function (host, port, prefix, suffix, globalize, cacheDns, mock,
6565
this.prefix = options.prefix || '';
6666
this.suffix = options.suffix || '';
6767
this.tagPrefix = options.tagPrefix || '#';
68-
this.tagSeparator = options.tagSeparator || ',';
68+
this.telegraf = options.telegraf || false;
69+
this.tagSeparator = this.telegraf ? ',' : options.tagSeparator || ',';
6970
this.mock = options.mock;
71+
/**
72+
* this is always an array. It's always populated.
73+
*/
7074
this.globalTags = typeof options.globalTags === 'object' ?
7175
helpers.formatTags(options.globalTags, options.telegraf) : [];
7276
const availableDDEnvs = Object.keys(DD_ENV_GLOBAL_TAGS_MAPPING).filter(key => process.env[key]);
@@ -75,7 +79,21 @@ const Client = function (host, port, prefix, suffix, globalize, cacheDns, mock,
7579
filter((item) => !availableDDEnvs.some(env => item.startsWith(`${DD_ENV_GLOBAL_TAGS_MAPPING[env]}:`))).
7680
concat(availableDDEnvs.map(env => `${DD_ENV_GLOBAL_TAGS_MAPPING[env]}:${helpers.sanitizeTags(process.env[env])}`));
7781
}
78-
this.telegraf = options.telegraf || false;
82+
/**
83+
* This is an object or `null`. It's populated if the original input is an object.
84+
* We can't necessarily convert an array to an object, since array supports keys without values
85+
*/
86+
this.globalTagsObjectSanitized = null;
87+
if (typeof options.globalTags === 'object' && !Array.isArray(options.globalTags)) {
88+
this.globalTagsObjectSanitized = {};
89+
for (const key of Object.keys(options.globalTags)) {
90+
const value = options.globalTags[key];
91+
const sanitizedKey = helpers.sanitizeTags(key);
92+
const sanitizedVal = helpers.sanitizeTags(value);
93+
this.globalTagsObjectSanitized[sanitizedKey] = sanitizedVal;
94+
}
95+
}
96+
this.globalTagsMemo = helpers.overrideTagsToStringUnoptimized(this.globalTags, [], this.telegraf, this.tagSeparator);
7997
this.maxBufferSize = options.maxBufferSize || 0;
8098
this.sampleRate = options.sampleRate || 1;
8199
this.bufferFlushInterval = options.bufferFlushInterval || 1000;
@@ -242,22 +260,30 @@ Client.prototype.sendStat = function (stat, value, type, sampleRate, tags, callb
242260
* @param callback {Function=} Callback when message is done being delivered (only if maxBufferSize == 0). Optional.
243261
*/
244262
Client.prototype.send = function (message, tags, callback) {
245-
let mergedTags = this.globalTags;
246-
if (tags && typeof tags === 'object') {
247-
mergedTags = helpers.overrideTags(mergedTags, tags, this.telegraf);
248-
}
249-
if (mergedTags.length > 0) {
263+
const tagsString = getTagString(this.globalTags, this.globalTagsObjectSanitized, this.globalTagsMemo, tags, this.telegraf, this.tagSeparator);
264+
if (tagsString.length > 0) {
250265
if (this.telegraf) {
251266
message = message.split(':');
252-
message = `${message[0]},${mergedTags.join(',').replace(/:/g, '=')}:${message.slice(1).join(':')}`;
267+
message = `${message[0]},${tagsString.replace(/:/g, '=')}:${message.slice(1).join(':')}`;
253268
} else {
254-
message += `|${this.tagPrefix}${mergedTags.join(this.tagSeparator)}`;
269+
message += `|${this.tagPrefix}${tagsString}`;
255270
}
256271
}
257272

258273
this._send(message, callback);
259274
};
260275

276+
// eslint-disable-next-line require-jsdoc
277+
function getTagString(globalTags, globalTagsObject, globalTagsMemo, tags, telegraf, tagSeparator) {
278+
if (!(tags && typeof tags === 'object')) {
279+
return globalTagsMemo;
280+
}
281+
if (globalTagsObject !== null && tags && !Array.isArray(tags)) {
282+
return helpers.overrideTags2(globalTagsObject, globalTagsMemo, tags, telegraf, tagSeparator);
283+
}
284+
return helpers.overrideTagsToStringUnoptimized(globalTags, tags, telegraf, tagSeparator);
285+
}
286+
261287
/**
262288
* Send a stat or event across the wire
263289
* @param message {String} The constructed message without tags
@@ -505,7 +531,7 @@ const ChildClient = function (parent, options) {
505531
mock : parent.mock,
506532
// Append child's tags to parent's tags
507533
globalTags : typeof options.globalTags === 'object' ?
508-
helpers.overrideTags(parent.globalTags, options.globalTags, parent.telegraf) : parent.globalTags,
534+
helpers.overrideTagsUnoptimized(parent.globalTags, options.globalTags, parent.telegraf) : parent.globalTags,
509535
maxBufferSize : parent.maxBufferSize,
510536
bufferFlushInterval: parent.bufferFlushInterval,
511537
telegraf : parent.telegraf,

0 commit comments

Comments
 (0)