From 264f0f6f3429eddb359ae259555acb44212226e9 Mon Sep 17 00:00:00 2001 From: Attila Szegedi Date: Fri, 24 Jul 2026 10:19:41 +0200 Subject: [PATCH] feat: add doc_url field to Profile message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pprof spec's Profile message carries doc_url (field 15, an int64 index into the string table pointing at documentation for the profile). Add it to the Profile message — ProfileInput type, constructor, length, encode (field 15, varint), and decode — mirroring the existing defaultSampleType field. Also add doc_url to the reference testing proto so the protobuf.js interop test round-trips it, and add Profile encode/decode tests for it. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/index.test.ts | 21 +++++++++++++++++++++ src/index.ts | 12 ++++++++++++ testing/proto/profile.d.ts | 6 ++++++ testing/proto/profile.js | 37 +++++++++++++++++++++++++++++++++++++ testing/proto/profile.proto | 2 ++ 5 files changed, 78 insertions(+) diff --git a/src/index.test.ts b/src/index.test.ts index 3d5687f..a99b3f2 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -319,6 +319,27 @@ test('Profile', async (t) => { }) }) +test('Profile docUrl', async (t) => { + // doc_url is field 15, an int64 index into the string table: tag byte + // (15 << 3) | kTypeVarInt = 0x78, followed by the varint value. + await t.test('encodes docUrl', () => { + // emptyTableToken excludes the string table so only docUrl is encoded. + const profile = new Profile({ + stringTable: new StringTable(emptyTableToken), + docUrl: 5, + }) + assert.strictEqual(bufToHex(profile.encode()), '7805') + }) + + await t.test('decodes docUrl', () => { + assert.strictEqual(Number(Profile.decode(hexToBuf('7805')).docUrl), 5) + }) + + await t.test('defaults docUrl to 0 when absent', () => { + assert.strictEqual(Number(new Profile().docUrl), 0) + }) +}) + function encodeStringTable(strings: StringTable) { return strings.strings.map(s => { const buf = new TextEncoder().encode(s) diff --git a/src/index.ts b/src/index.ts index f0ed278..ab93603 100644 --- a/src/index.ts +++ b/src/index.ts @@ -970,6 +970,7 @@ export type ProfileInput = { period?: Numeric comment?: Array defaultSampleType?: Numeric + docUrl?: Numeric } export class Profile { @@ -987,6 +988,7 @@ export class Profile { period: Numeric comment: Array defaultSampleType: Numeric + docUrl: Numeric constructor(data: ProfileInput = {}) { this.sampleType = (data.sampleType || []).map(ValueType.create) @@ -1003,6 +1005,7 @@ export class Profile { this.period = data.period || 0 this.comment = data.comment || [] this.defaultSampleType = data.defaultSampleType || 0 + this.docUrl = data.docUrl || 0 } get length() { @@ -1021,6 +1024,7 @@ export class Profile { total += measureNumberField(this.period) total += measureNumberArrayField(this.comment) total += measureNumberField(this.defaultSampleType) + total += measureNumberField(this.docUrl) return total } @@ -1114,6 +1118,11 @@ export class Profile { offset = encodeNumber(buffer, offset, this.defaultSampleType) } + if (this.docUrl) { + buffer[offset++] = 120 // (15 << 3) + kTypeVarInt + offset = encodeNumber(buffer, offset, this.docUrl) + } + return offset } @@ -1209,6 +1218,9 @@ export class Profile { case 14: data.defaultSampleType = decodeNumber(buffer) break + case 15: + data.docUrl = decodeNumber(buffer) + break } } diff --git a/testing/proto/profile.d.ts b/testing/proto/profile.d.ts index 0a73602..9e31bf9 100644 --- a/testing/proto/profile.d.ts +++ b/testing/proto/profile.d.ts @@ -50,6 +50,9 @@ export namespace perftools { /** Profile defaultSampleType */ defaultSampleType?: (number|Long|null); + + /** Profile docUrl */ + docUrl?: (number|Long|null); } /** Represents a Profile. */ @@ -103,6 +106,9 @@ export namespace perftools { /** Profile defaultSampleType. */ public defaultSampleType: (number|Long); + /** Profile docUrl. */ + public docUrl: (number|Long); + /** * Creates a new Profile instance using the specified properties. * @param [properties] Properties to set diff --git a/testing/proto/profile.js b/testing/proto/profile.js index 37c5993..c47c112 100644 --- a/testing/proto/profile.js +++ b/testing/proto/profile.js @@ -47,6 +47,7 @@ $root.perftools = (function() { * @property {number|Long|null} [period] Profile period * @property {Array.|null} [comment] Profile comment * @property {number|Long|null} [defaultSampleType] Profile defaultSampleType + * @property {number|Long|null} [docUrl] Profile docUrl */ /** @@ -183,6 +184,14 @@ $root.perftools = (function() { */ Profile.prototype.defaultSampleType = $util.Long ? $util.Long.fromBits(0,0,false) : 0; + /** + * Profile docUrl. + * @member {number|Long} docUrl + * @memberof perftools.profiles.Profile + * @instance + */ + Profile.prototype.docUrl = $util.Long ? $util.Long.fromBits(0,0,false) : 0; + /** * Creates a new Profile instance using the specified properties. * @function create @@ -245,6 +254,8 @@ $root.perftools = (function() { } if (message.defaultSampleType != null && Object.hasOwnProperty.call(message, "defaultSampleType")) writer.uint32(/* id 14, wireType 0 =*/112).int64(message.defaultSampleType); + if (message.docUrl != null && Object.hasOwnProperty.call(message, "docUrl")) + writer.uint32(/* id 15, wireType 0 =*/120).int64(message.docUrl); return writer; }; @@ -354,6 +365,10 @@ $root.perftools = (function() { message.defaultSampleType = reader.int64(); break; } + case 15: { + message.docUrl = reader.int64(); + break; + } default: reader.skipType(tag & 7); break; @@ -471,6 +486,9 @@ $root.perftools = (function() { if (message.defaultSampleType != null && message.hasOwnProperty("defaultSampleType")) if (!$util.isInteger(message.defaultSampleType) && !(message.defaultSampleType && $util.isInteger(message.defaultSampleType.low) && $util.isInteger(message.defaultSampleType.high))) return "defaultSampleType: integer|Long expected"; + if (message.docUrl != null && message.hasOwnProperty("docUrl")) + if (!$util.isInteger(message.docUrl) && !(message.docUrl && $util.isInteger(message.docUrl.low) && $util.isInteger(message.docUrl.high))) + return "docUrl: integer|Long expected"; return null; }; @@ -616,6 +634,15 @@ $root.perftools = (function() { message.defaultSampleType = object.defaultSampleType; else if (typeof object.defaultSampleType === "object") message.defaultSampleType = new $util.LongBits(object.defaultSampleType.low >>> 0, object.defaultSampleType.high >>> 0).toNumber(); + if (object.docUrl != null) + if ($util.Long) + (message.docUrl = $util.Long.fromValue(object.docUrl)).unsigned = false; + else if (typeof object.docUrl === "string") + message.docUrl = parseInt(object.docUrl, 10); + else if (typeof object.docUrl === "number") + message.docUrl = object.docUrl; + else if (typeof object.docUrl === "object") + message.docUrl = new $util.LongBits(object.docUrl.low >>> 0, object.docUrl.high >>> 0).toNumber(); return message; }; @@ -673,6 +700,11 @@ $root.perftools = (function() { object.defaultSampleType = options.longs === String ? long.toString() : options.longs === Number ? long.toNumber() : long; } else object.defaultSampleType = options.longs === String ? "0" : 0; + if ($util.Long) { + var long = new $util.Long(0, 0, false); + object.docUrl = options.longs === String ? long.toString() : options.longs === Number ? long.toNumber() : long; + } else + object.docUrl = options.longs === String ? "0" : 0; } if (message.sampleType && message.sampleType.length) { object.sampleType = []; @@ -744,6 +776,11 @@ $root.perftools = (function() { object.defaultSampleType = options.longs === String ? String(message.defaultSampleType) : message.defaultSampleType; else object.defaultSampleType = options.longs === String ? $util.Long.prototype.toString.call(message.defaultSampleType) : options.longs === Number ? new $util.LongBits(message.defaultSampleType.low >>> 0, message.defaultSampleType.high >>> 0).toNumber() : message.defaultSampleType; + if (message.docUrl != null && message.hasOwnProperty("docUrl")) + if (typeof message.docUrl === "number") + object.docUrl = options.longs === String ? String(message.docUrl) : message.docUrl; + else + object.docUrl = options.longs === String ? $util.Long.prototype.toString.call(message.docUrl) : options.longs === Number ? new $util.LongBits(message.docUrl.low >>> 0, message.docUrl.high >>> 0).toNumber() : message.docUrl; return object; }; diff --git a/testing/proto/profile.proto b/testing/proto/profile.proto index ee1a018..bfdd62e 100644 --- a/testing/proto/profile.proto +++ b/testing/proto/profile.proto @@ -93,6 +93,8 @@ message Profile { // Index into the string table of the type of the preferred sample // value. If unset, clients should default to the last sample value. int64 default_sample_type = 14; + // Documentation link for this profile. Index into string table. + int64 doc_url = 15; } // ValueType describes the semantics and measurement units of a value.