diff --git a/docs/generated/changelog.html b/docs/generated/changelog.html
index 491c78ecd..35e9de864 100644
--- a/docs/generated/changelog.html
+++ b/docs/generated/changelog.html
@@ -12,6 +12,7 @@
Agent-JS Changelog
Version x.x.x
+ - feat: adds subnet metrics decoding to canisterStatus for `/subnet` path
-
chore: replaces use of localhost with 127.0.0.1 for better node 18 support. Also swaps
Jest for vitest, runs mitm against mainnet, and updates some packages
diff --git a/packages/agent/src/certificate.test.ts b/packages/agent/src/certificate.test.ts
index 44bd5e0a0..e2f68bcb6 100644
--- a/packages/agent/src/certificate.test.ts
+++ b/packages/agent/src/certificate.test.ts
@@ -274,6 +274,15 @@ describe('node keys', () => {
const nodeKeys = cert.cache_node_keys();
expect(nodeKeys).toMatchInlineSnapshot(`
Object {
+ "metrics": Object {
+ "canister_state_bytes": 10007399447n,
+ "consumed_cycles_total": Object {
+ "current": 15136490391288n,
+ "deleted": 0n,
+ },
+ "num_canisters": 451n,
+ "update_transactions_total": 222360n,
+ },
"nodeKeys": Array [
"302a300506032b65700321005b0bdf0329932ab0a78fa7192ad76cf37d67eb2024739774d3b67da7799ebc9c",
"302a300506032b65700321009776d25542873dafb8099303d1fca8e4aa344e73cf5a3d7df5b40f9c8ed5a085",
diff --git a/packages/agent/src/certificate.ts b/packages/agent/src/certificate.ts
index 3682c9b9b..a02076963 100644
--- a/packages/agent/src/certificate.ts
+++ b/packages/agent/src/certificate.ts
@@ -49,6 +49,15 @@ export type SubnetStatus = {
// Principal as a string
subnetId: string;
nodeKeys: string[];
+ metrics?: {
+ num_canisters: bigint;
+ canister_state_bytes: bigint;
+ consumed_cycles_total: {
+ current: bigint;
+ deleted: bigint;
+ };
+ update_transactions_total: bigint;
+ };
};
/**
@@ -164,6 +173,8 @@ export interface CreateCertificateOptions {
maxAgeInMinutes?: number;
}
+type MetricsResult = number | bigint | Map | undefined;
+
export class Certificate {
private readonly cert: Cert;
#nodeKeys: string[] = [];
@@ -216,6 +227,12 @@ export class Certificate {
return this.lookup([label]);
}
+ #toBigInt(n: MetricsResult): bigint {
+ if (typeof n === 'undefined') return BigInt(0);
+ if (typeof n === 'bigint') return n;
+ return BigInt(Number(n));
+ }
+
public cache_node_keys(root_key?: Uint8Array): SubnetStatus {
const tree = this.cert.tree;
let delegation = this.cert.delegation;
@@ -248,10 +265,47 @@ export class Certificate {
}
});
- return {
+ const metricsTree = lookup_path(
+ ['subnet', delegation?.subnet_id as ArrayBuffer, 'metrics'],
+ tree,
+ );
+ let metrics: SubnetStatus['metrics'] | undefined = undefined;
+ if (metricsTree) {
+ const decoded = cbor.decode(metricsTree as ArrayBuffer) as Map<
+ number,
+ Map
+ >;
+
+ // Cbor may decode values as either number or bigint. For consistency, we convert all numbers to bigint
+ const num_canisters = this.#toBigInt(decoded.get(0));
+ const canister_state_bytes = this.#toBigInt(decoded.get(1));
+ const current_consumed_cycles = this.#toBigInt(
+ (decoded.get(2) as Map).get(0),
+ );
+ const deleted_consumed_cycles = this.#toBigInt(
+ (decoded.get(2) as Map).get(1),
+ );
+ const update_transactions_total = this.#toBigInt(decoded.get(3));
+
+ metrics = {
+ num_canisters: num_canisters,
+ canister_state_bytes: canister_state_bytes,
+ consumed_cycles_total: {
+ current: current_consumed_cycles,
+ deleted: deleted_consumed_cycles,
+ },
+ update_transactions_total: update_transactions_total,
+ };
+ }
+
+ const result: SubnetStatus = {
subnetId: Principal.fromUint8Array(new Uint8Array(delegation.subnet_id)).toText(),
nodeKeys: this.#nodeKeys,
};
+ if (metrics) {
+ result.metrics = metrics;
+ }
+ return result;
}
private async verify(): Promise {