いつでも DiagnosticCounter 



## ソケット通信
ソケット通信して DiagnosticCounter を取得する。
```typescript:Socket.ts
import { X509Certificate } from 'crypto';
import fs from 'fs';
import tls, { ConnectionOptions } from 'tls';
import { PacketBuffer } from './PacketBuffer.js';

/**
 * DiagnosticCounter
 */
export class DiagnosticCounter {
  constructor(
    /** 項目名 */
    public readonly itemName: string,
    /** 項目値 */
    public readonly itemValue: string
  ) {}
}

/**
 * ソケット
 */
export class Socket {
  /** パケットタイプ */
  private PacketType = {
    DIAGNOSTIC_COUNTERS: 0x300,
  };

  /** コネクションオプション */
  private _connectionOptions: ConnectionOptions;

  /** X509証明書 */
  protected _x509Certificate: X509Certificate | undefined;

  /**
   * コンストラクタ
   * @param host ホスト
   * @param port ポート(デフォルト: 7900)
   * @param timeout タイムアウト(デフォルト: 3000)
   */
  constructor(
    protected readonly host: string,
    protected readonly port: number = 7900,
    private readonly timeout: number = 3000
  ) {
    const currentDir = process.cwd().replace(/\\/g, '/');
    this._connectionOptions = {
      host: this.host,
      port: this.port,
      timeout: this.timeout,
      cert: fs.readFileSync(`${currentDir}/cert/node.full.crt.pem`),
      key: fs.readFileSync(`${currentDir}/cert/node.key.pem`),
      rejectUnauthorized: false,
    };
  }

  /**
   * DiagnosticCounters取得
   * @returns 診断カウンター
   */
  async getDiagnosticCounters(): Promise<DiagnosticCounter[] | undefined> {
    // ピア問合せ
    const socketData = await this.requestSocket(
      this.PacketType.DIAGNOSTIC_COUNTERS
    );
    if (!socketData) return undefined;
    const nodeBufferView = new PacketBuffer(Buffer.from(socketData));
    // 編集
    const diagnosticCounters: DiagnosticCounter[] = [];
    while (nodeBufferView.index < nodeBufferView.length) {
      let itemNameBin = nodeBufferView.readBigUInt64LE();
      let itemNameWork = '';
      for (let i = 0; i < 13; i++) {
        const byte = itemNameBin % 27n;
        const char =
          byte === 0n
            ? ' '
            : Buffer.from((64n + byte).toString(16), 'hex').toString('utf-8');
        itemNameWork = char + itemNameWork;
        itemNameBin /= 27n;
      }
      const itemValueWork = nodeBufferView.readBigUInt64LE();
      const dCounter = new DiagnosticCounter(
        itemNameWork,
        itemValueWork.toString()
      );
      diagnosticCounters.push(dCounter);
    }
    return diagnosticCounters;
  }

  /**
   * catapult サーバへパケットを送信する
   * @param packetType パケットタイプ
   * @param payload ペイロード
   * @param isResponse レスポンス有無(デフォルト: true)
   * @returns レスポンスデータ
   */
  protected async requestSocket(
    packetType: number,
    payload?: Uint8Array,
    isResponse: boolean = true
  ): Promise<Uint8Array | undefined> {
    return new Promise<Uint8Array | undefined>((resolve, reject) => {
      const payloadSize = payload ? payload.length : 0;
      let responseSize = 8; // ヘッダ分のサイズを前もって付与
      let responseData: Uint8Array | undefined = undefined;

      // SSLソケット接続
      const socket = tls.connect(this._connectionOptions, () => {
        // Symbolパケット生成
        const headerSize = 8;
        const packetSize = headerSize + payloadSize;
        const symbolPacketBuffer = new ArrayBuffer(packetSize);
        // Symbolヘッダー編集
        const symbolHeader = new DataView(symbolPacketBuffer);
        symbolHeader.setUint32(0, packetSize, true);
        symbolHeader.setUint32(4, packetType, true);

        if (payload) {
          // Symbolペイロード編集
          const symbolPayload = new Uint8Array(
            symbolPacketBuffer,
            headerSize,
            payloadSize
          );
          symbolPayload.set(payload);
        }

        // Symbolパケット送信
        socket.write(new Uint8Array(symbolPacketBuffer));

        if (!isResponse) {
          // ソケット切断
          socket.destroy();
        }
      });

      // SSL接続
      socket.on('secureConnect', () => {
        const peerX509 = socket.getPeerX509Certificate();
        if (!peerX509) return;
        this._x509Certificate = peerX509;
      });

      // データ受信
      socket.once('data', (data) => {
        // レスポンスデータ（ヘッダ）取得
        const nodeBufferView = Buffer.from(new Uint8Array(data).buffer);
        // レスポンスサイズチェック
        const responseDataSize = nodeBufferView.readUInt32LE(0);
        if (responseDataSize === 0) {
          socket.destroy();
          reject('empty data');
        }
        // レスポンスパケットタイプチェック
        const responsePacketType = nodeBufferView.readUInt32LE(4);
        if (responsePacketType !== packetType) {
          socket.destroy();
          reject(
            `mismatch packet type: expect: ${packetType} actual: ${responsePacketType}`
          );
        }

        // ヘッダが問題なければデータ部取得
        socket.on('data', (data) => {
          const tempResponseData = new Uint8Array(data);
          responseSize += tempResponseData.length;
          if (!responseData) {
            // 初回
            responseData = tempResponseData;
          } else {
            // 連結
            const merged = new Uint8Array(
              responseData.length + tempResponseData.length
            );
            merged.set(responseData);
            merged.set(tempResponseData, responseData.length);
            responseData = merged;
          }
          if (responseDataSize <= responseSize) {
            // 受信が終わったら終了
            socket.end();
          }
        });
      });

      // タイムアウト
      socket.on('timeout', function () {
        socket.destroy();
        reject('timeout');
      });

      // エラー
      socket.on('error', function (error) {
        socket.destroy();
        reject(error);
      });

      // 切断
      socket.on('close', function () {
        resolve(responseData);
      });
    });
  }
}
```

パケットの編集など面倒なので専用のクラスを作ってます。
```typescript:PacketBuffer.ts
export class PacketBuffer {
  private _length: number;
  private _index: number;

  constructor(private readonly _buffer: Buffer) {
    this._length = _buffer.length;
    this._index = 0;
  }

  addOffset(addOffset: number): number {
    this._index += addOffset;
    return this._index;
  }

  readUInt8(addOffset: number = 0): number {
    this.addOffset(addOffset);
    const readData = this._buffer.readUInt8(this._index);
    this._index += 1;
    return readData;
  }

  readUInt16LE(addOffset: number = 0): number {
    this.addOffset(addOffset);
    const readData = this._buffer.readUInt16LE(this._index);
    this._index += 2;
    return readData;
  }

  readUInt32LE(addOffset: number = 0): number {
    this.addOffset(addOffset);
    const readData = this._buffer.readUInt32LE(this._index);
    this._index += 4;
    return readData;
  }

  readBigUInt64LE(addOffset: number = 0): bigint {
    this.addOffset(addOffset);
    const readData = this._buffer.readBigUInt64LE(this._index);
    this._index += 8;
    return readData;
  }

  readString(length: number, addOffset: number = 0): string {
    this.addOffset(addOffset);
    const readData = this._buffer.toString('utf8', this._index, this._index + length);
    this._index += length;
    return readData;
  }

  readHexString(length: number, addOffset: number = 0): string {
    this.addOffset(addOffset);
    const readData = this._buffer.toString('hex', this._index, this._index + length);
    this._index += length;
    return readData;
  }

  get index(): number {
    return this._index;
  }

  get length(): number {
    return this._length;
  }
}
```

## メイン
```typescript:main.ts
import { Socket } from './Socket.js';

const socket = new Socket('sakia.harvestasya.com');
const diagnosticCounters = await socket.getDiagnosticCounters();
if (diagnosticCounters === undefined) {
  console.error('取得出来ませんでした');
} else {
  for (const diagnosticCounter of diagnosticCounters) {
    console.log(
      `${diagnosticCounter.itemName}: ${diagnosticCounter.itemValue}`
    );
  }
}
```

## 使う
証明書は各自トップレベルにcertディレクトリを作成して準備してください。
ノードの trustedHosts に実行する PC の IP を入れておいて下さい。
```plaintext
trustedHosts: 172.20.0.1, 203.135.231.85
```

実行する
```plaintext
> yarn tsx .\src\main.ts
  MEM CUR RSS: 1209
  MEM MAX RSS: 1504
 MEM CUR VIRT: 2821
  MEM SHR RSS: 22
TOT CONF TXES: 1513816
     ACNTST C: 53452
 ACNTST C HVA: 447
     BLKDIF C: 88
       HASH C: 7
         NS C: 186
      NS C AS: 398
      NS C DS: 482
 MOSAICREST C: 4490
   METADATA C: 163188
   MULTISIG C: 15369
   HASHLOCK C: 1
     MOSAIC C: 4735
 SECRETLOCK C: 6
   ACCTREST C: 1885
     UT CACHE: 0
 UT CACHE MEM: 0
        NODES: 781
      BAN ACT: 0
      BAN ALL: 0
FIN MIN EPOCH: 1
FIN MIN POINT: 1
FIN MAX EPOCH: 1
FIN MAX POINT: 1
 FIN PREV EST: 1
  FIN CUR EST: 1
      WRITERS: 11
 BLK ELEM TOT: 373874
 BLK ELEM ACT: 0
 BLK ELEM MEM: 0
  TX ELEM TOT: 35202
  TX ELEM ACT: 0
  TX ELEM MEM: 0
RB COMMIT ALL: 472
RB COMMIT RCT: 3
RB IGNORE ALL: 1219
RB IGNORE RCT: 17
 UNLKED ACCTS: 3
  FIN WRITERS: 10
 ACTIVE PINGS: 0
  TOTAL PINGS: 857
SUCCESS PINGS: 35
TS OFFSET ABS: 0
TS OFFSET DIR: 0
  TS NODE AGE: 154
 TS TOTAL REQ: 1901
      READERS: 20
        TASKS: 13
```