Claudeを使ってネットワークソケットの使用量を調べるプログラムを作ってみた

別のトピックで書いている「Claudeを使ってUDPファイル転送プログラムを作ってみた」で作ったプログラムの動作を調査するのに使うため、同じくClaudeを使ってネットワークソケットの使用量を調べるプログラムを作ってみた。

このトピックでもClaudeに尋ねたプロントとその回答を順番に載せていく。

まず、単純に以下のプロンプトを書いてClaudeに尋ねてみた。


Solrais11.4 for x86でtcp及びudpのソケットバッファの使用率を1ミリ秒ごとに出力するプログラムのソースコードをC言語を使って作成して下さい。


その回答がこちら。


Solaris 11.4のx86向けに、TCPとUDPのソケットバッファ使用率を1ミリ秒ごとに出力するCプログラムを作成します。
ファイルを作成しました, ファイルを読み取りました
概要・使い方
コンパイル

sh

# Oracle Developer Studio (cc)
cc -o sockbuf_monitor sockbuf_monitor.c -lkstat -lsocket -lnsl

# GCC
gcc -o sockbuf_monitor sockbuf_monitor.c -lkstat -lsocket -lnsl

実行

sh

# 無限ループ(Ctrl-C で停止)
./sockbuf_monitor

# 100 回だけ取得
./sockbuf_monitor 100

出力例

Timestamp           Proto    RcvBuf(B)    RcvMax(B)    Rcv(%)   Proto    SndBuf(B)    SndMax(B)    Snd(%)
12:34:56.001234567  TCP      4096         131072        3.125%   TCP      2048         131072        1.563%
12:34:56.001234567  UDP      512          131072        0.391%   UDP      256          8192          3.125%

設計のポイント
項目 内容
取得手段 Solaris kstat(3KSTAT) — カーネル統計の公式 API
TCP 統計 inDataInorderBytes / outDataBytes の 1ms 差分
UDP 統計 inBytes / outBytes の 1ms 差分
バッファ上限 Solaris デフォルト値 (TCP 128KB、UDP RCV 128KB / SND 8KB)
タイマー精度 clock_nanosleep(CLOCK_MONOTONIC) でナノ秒精度スリープ
タイムスタンプ clock_gettime(CLOCK_REALTIME) でナノ秒精度
シグナル対応 SIGINT / SIGTERM を受けてクリーンに終了
注意事項

kstat へのアクセスは通常 root 権限 が必要です(pfexec ./sockbuf_monitor でも可)。
kstat の差分は「1ms 間にバッファを通過したバイト数」であり、瞬時のバッファ占有量(カーネル内部の so_rcv.sb_cc)とは異なります。真のバッファ占有量を取得するには mdb や dtrace のカーネルプローブが必要です。
実環境で kstat_data_lookup が -1 を返す場合、フィールド名が Solaris バージョンにより異なる可能性があるため、kstat -m tcp -s all コマンドで実際のフィールド名を確認してください。

Sockbuf monitor
C

なんか値が取れていないので、再度下記のプロンプトを投げた。


作成していただいたsoketbuf_monitorをmakeして実行したのですが、値がうまくとれていない様に見えます。以下にsocketbuf_monitorの実行ログを添付します。

Solaris 11.4 Socket Buffer Monitor  (interval=1ms)
Ctrl-C で停止
凡例:
RcvBuf(B) : 1ms 間の受信バッファ到達バイト数
SndBuf(B) : 1ms 間の送信バッファ送出バイト数
RcvMax/SndMax : デフォルトバッファ上限 (TCP=128KB, UDP RCV=128KB/SND=8KB)
Rcv(%) / Snd(%) : バッファ使用率
Timestamp           Proto    RcvBuf(B)    RcvMax(B)    Rcv(%)    Proto    SndBuf(B)    SndMax(B)    Snd(%)


15:50:06.288691957  TCP      0            131072         0.000%  TCP      0            131072         0.000%
15:50:06.288691957  UDP      0            131072         0.000%  UDP      0            8192           0.000%
15:50:06.290196411  TCP      0            131072         0.000%  TCP      0            131072         0.000%
15:50:06.290196411  UDP      0            131072         0.000%  UDP      0            8192           0.000%
15:50:06.291417838  TCP      0            131072         0.000%  TCP      0            131072         0.000%
15:50:06.291417838  UDP      0            131072         0.000%  UDP      0            8192           0.000%
15:50:06.292622518  TCP      0            131072         0.000%  TCP      0            131072         0.000%
15:50:06.292622518  UDP      0            131072         0.000%  UDP      0            8192           0.000%
15:50:06.293819746  TCP      0            131072         0.000%  TCP      0            131072         0.000%
15:50:06.293819746  UDP      0            131072         0.000%  UDP      0            8192           0.000%
<以下同様>

その回答。


ログを拝見しました。値がすべて0になっています。原因として考えられるのは以下の点です。

主な原因:

kstat_data_lookup で参照しているフィールド名が実際のSolaris 11.4のkstatフィールド名と一致していない
1ms間の差分では変化量が小さすぎて0になる

まず実際のkstatフィールド名を確認しましょう。
コマンドを実行しました, ファイルを作成しました, ファイルを読み取りました
対処手順
ステップ1:まず診断ツールを実行してください

sh

cc  -o kstat_dump kstat_dump.c -lkstat
pfexec ./kstat_dump 2>&1 | tee kstat_dump.log

出力されたログ(kstat_dump.logの内容)をそのままご共有ください。それをもとに sockbuf_monitor.c を修正します。
なぜ 0 になっているか(現時点での推定原因)
原因 説明
① フィールド名不一致(最有力) kstat_data_lookup が NULL を返しているため差分が 0 のまま。Solaris 11.4 では inDataInorderBytes ではなく inDataBytes など名前が異なる可能性がある
② インスタンス番号ずれ kstat_lookup(…, -1, …) でインスタンス 0 以外のエントリにしか統計が入っていない場合
③ ゾーン/スタック分離 Exclusive-IP ゾーン内では独立したスタックが使われ、グローバルゾーンの kstat には値が入らない
④ トラフィックがない 計測中に実際の通信がなければ差分は正しく 0(実トラフィックを発生させて再試行)

kstat_dump の出力を確認すれば①②③のどれかすぐ特定でき、正しいフィールド名でコードを修正できます。
Kstat dump
C

kstat_dump.cの内容は以下のとおり。

/*
 * kstat_dump.c
 *
 * Solaris 11.4 上で tcp / udp の kstat フィールドを全列挙する診断ツール。
 * sockbuf_monitor が 0 しか返さない原因調査に使います。
 *
 * コンパイル:
 *   cc  -o kstat_dump kstat_dump.c -lkstat
 *   gcc -o kstat_dump kstat_dump.c -lkstat
 *
 * 実行:
 *   ./kstat_dump 2>&1 | tee kstat_dump.log
 *
 * 出力されたログをそのまま共有してください。
 * どのフィールド名が実際に存在するかが分かります。
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <kstat.h>

/* -------------------------------------------------------
 * 指定 module / name の kstat を全フィールド列挙する
 * ------------------------------------------------------- */
static void
dump_kstat(kstat_ctl_t *kc, const char *module, const char *name)
{
    kstat_t       *ksp;
    kstat_named_t *knp;
    uint_t         i;

    printf("\n=== kstat  module=\"%s\"  name=\"%s\" ===\n", module, name);

    ksp = kstat_lookup(kc, (char *)module, -1, (char *)name);
    if (ksp == NULL) {
        printf("  [NOT FOUND] errno=%d (%s)\n", errno, strerror(errno));
        return;
    }

    printf("  ks_type=%d  ks_ndata=%u  ks_instance=%d\n",
           ksp->ks_type, ksp->ks_ndata, ksp->ks_instance);

    if (kstat_read(kc, ksp, NULL) == -1) {
        printf("  [ERROR] kstat_read: %s\n", strerror(errno));
        return;
    }

    if (ksp->ks_type != KSTAT_TYPE_NAMED) {
        printf("  [SKIP] ks_type is not KSTAT_TYPE_NAMED (%d)\n",
               ksp->ks_type);
        return;
    }

    knp = (kstat_named_t *)ksp->ks_data;
    for (i = 0; i < ksp->ks_ndata; i++, knp++) {
        printf("  [%3u] %-42s type=%d  val=",
               i, knp->name, knp->data_type);
        switch (knp->data_type) {
        case KSTAT_DATA_UINT64:
            printf("%llu\n", (unsigned long long)knp->value.ui64);
            break;
        case KSTAT_DATA_INT64:
            printf("%lld\n", (long long)knp->value.i64);
            break;
        case KSTAT_DATA_UINT32:
            printf("%lu\n",  (unsigned long)knp->value.ui32);
            break;
        case KSTAT_DATA_INT32:
            printf("%ld\n",  (long)knp->value.i32);
            break;
        case KSTAT_DATA_STRING:
            printf("\"%s\"\n", KSTAT_NAMED_STR_PTR(knp));
            break;
        default:
            printf("(data_type=%d)\n", knp->data_type);
            break;
        }
    }
}

/* -------------------------------------------------------
 * kstat チェーンを全走査して module に "tcp" "udp" を
 * 含むエントリをすべてリストアップする
 * ------------------------------------------------------- */
static void
scan_chain(kstat_ctl_t *kc)
{
    kstat_t *ksp;

    printf("\n=== kstat chain scan (tcp / udp 関連エントリ) ===\n");
    printf("  %-20s %-6s %-30s %-10s\n",
           "module", "inst", "name", "type");
    printf("  %-20s %-6s %-30s %-10s\n",
           "--------------------", "------",
           "------------------------------", "----------");

    for (ksp = kc->kc_chain; ksp != NULL; ksp = ksp->ks_next) {
        const char *m = ksp->ks_module;
        if (strcasecmp(m, "tcp") == 0 ||
            strcasecmp(m, "udp") == 0 ||
            strcasecmp(m, "ip")  == 0) {
            printf("  %-20s %-6d %-30s %d\n",
                   ksp->ks_module,
                   ksp->ks_instance,
                   ksp->ks_name,
                   ksp->ks_type);
        }
    }
}

/* -------------------------------------------------------
 * 特定フィールドの存在確認テスト (前バージョンで使った名前)
 * ------------------------------------------------------- */
static const char *tcp_fields_old[] = {
    "inDataInorderBytes", "outDataBytes",
    "inDupBytes",         "retransBytes",
    "connTableSize",
    NULL
};

static const char *tcp_fields_candidate[] = {
    /* RFC/MIB 系 */
    "tcpInSegs",          "tcpOutSegs",
    "tcpInErrs",          "tcpRetransSegs",
    /* Solaris 独自 */
    "inDataBytes",        "outDataBytes",
    "inDataInorderBytes", "inDataUnorderBytes",
    "inDataDupBytes",
    "tcpInDataInorderBytes",
    "tcpOutDataBytes",
    "tcpActiveOpens",     "tcpPassiveOpens",
    "tcpAttemptFails",    "tcpEstabResets",
    "tcpCurrEstab",
    NULL
};

static const char *udp_fields_candidate[] = {
    "inDatagrams",   "outDatagrams",
    "inBytes",       "outBytes",
    "inErrors",      "outErrors",
    "udpInDatagrams","udpOutDatagrams",
    "udpNoPorts",    "udpInErrors",
    NULL
};

static void
check_fields(kstat_ctl_t *kc,
             const char *module, const char *name,
             const char **fields)
{
    kstat_t *ksp;
    int      i;

    printf("\n--- フィールド存在確認: module=%s name=%s ---\n",
           module, name);

    ksp = kstat_lookup(kc, (char *)module, -1, (char *)name);
    if (ksp == NULL) {
        printf("  kstat not found\n");
        return;
    }
    if (kstat_read(kc, ksp, NULL) == -1) {
        printf("  kstat_read error: %s\n", strerror(errno));
        return;
    }

    for (i = 0; fields[i] != NULL; i++) {
        kstat_named_t *knp =
            (kstat_named_t *)kstat_data_lookup(ksp, (char *)fields[i]);
        if (knp) {
            uint64_t v = 0;
            switch (knp->data_type) {
            case KSTAT_DATA_UINT64: v = knp->value.ui64; break;
            case KSTAT_DATA_INT64:  v = (uint64_t)knp->value.i64; break;
            case KSTAT_DATA_UINT32: v = knp->value.ui32; break;
            case KSTAT_DATA_INT32:  v = (uint64_t)knp->value.i32; break;
            default: break;
            }
            printf("  [OK ]  %-42s = %llu\n",
                   fields[i], (unsigned long long)v);
        } else {
            printf("  [NG ]  %-42s  (not found)\n", fields[i]);
        }
    }
}

/* -------------------------------------------------------
 * main
 * ------------------------------------------------------- */
int
main(void)
{
    kstat_ctl_t *kc;

    kc = kstat_open();
    if (kc == NULL) {
        fprintf(stderr, "kstat_open() failed: %s\n", strerror(errno));
        fprintf(stderr, "root / pfexec で実行してください。\n");
        return EXIT_FAILURE;
    }

    /* 1. チェーン全体から tcp/udp/ip 関連エントリを列挙 */
    scan_chain(kc);

    /* 2. よくある module/name の組み合わせを全フィールド列挙 */
    dump_kstat(kc, "tcp", "tcp");
    dump_kstat(kc, "TCP", "tcp");
    dump_kstat(kc, "tcp", "tcpstat");
    dump_kstat(kc, "ip",  "tcp");
    dump_kstat(kc, "udp", "udp");
    dump_kstat(kc, "UDP", "udp");
    dump_kstat(kc, "udp", "udpstat");
    dump_kstat(kc, "ip",  "udp");

    /* 3. 旧バージョンで使ったフィールド名の確認 */
    printf("\n\n########## 旧フィールド名の存在確認 ##########");
    check_fields(kc, "tcp", "tcp", tcp_fields_old);

    /* 4. 候補フィールド名の一括確認 */
    printf("\n\n########## TCP 候補フィールド確認 ##########");
    check_fields(kc, "tcp", "tcp", tcp_fields_candidate);

    printf("\n\n########## UDP 候補フィールド確認 ##########");
    check_fields(kc, "udp", "udp", udp_fields_candidate);

    kstat_close(kc);

    printf("\n\n以上です。このログを共有してください。\n");
    return EXIT_SUCCESS;
}

kstat_dumpの結果をClaudeに投げたところ、以下の回答が来た。


ありがとうございます。診断ログで原因が明確になりました。

判明した問題点:

TCP: inDupBytes → 存在しない。正しくは inDataDupBytes
UDP: inBytes/outBytes → 存在しない。UDP にはバイト数フィールドがなく、inDatagrams/outDatagrams(データグラム数)のみ
コード内の get_kstat_uint64 で NULL チェック後にエラーリターンしていたため、1フィールドでも見つからないと以降が全部0になっていた

これらを修正した版を作成します。
2個のコマンドを実行しました, ファイルを読み取りました
修正内容まとめ
原因① フィールド名の誤り(最大の原因)
項目 旧コード(誤) 新コード(正) 根拠
TCP重複受信 inDupBytes inDataDupBytes kstat_dump で確認
TCP受信量 inDataInorderBytes のみ inDataInorderBytes + inDataUnorderBytes 順序外パケットも計上
UDP受信バイト inBytes 存在しない → inDatagrams × 推定ペイロード kstat_dump で [NG] 確認
UDP送信バイト outBytes 存在しない → outDatagrams × 推定ペイロード 同上
原因② ks_get_u64 の戻り値無視

旧版では1つでもフィールドが見つからないと関数が -1 を返してループを抜け、残りのフィールドが0のまま差分計算されていました。新版ではフィールドが無くても警告なしで継続し、存在するフィールドだけで計算します。
UDP ペイロードサイズの調整

UDP_AVG_PAYLOAD_BYTES(デフォルト512バイト)を実環境に合わせて変更してください。

c

/* sockbuf_monitor.c 先頭付近 */
#define UDP_AVG_PAYLOAD_BYTES  512   ← 実際の平均UDPペイロードに合わせて変更

DNS なら約 60B、動画ストリームなら 1400B 程度が目安です。
Sockbuf monitor
C

コンパイルしようとしたらエラーが出たので、その内容をClaudeに投げた。


コンパイルしようとしたら以下のエラーが出ました。

cc -o sockbuf_monitor sockbuf_monitor.c -lkstat -lsocket -lnsl
"sockbuf_monitor.c", line 196: undefined symbol: UINT64_MAX
cc: acomp failed for sockbuf_monitor.c

それに対する、Claudeからの回答。


INT64_MAXはC99以降の<stdint.h>で定義されていますが、SolarisのデフォルトコンパイラはC89モードで動作するため見つからないエラーです。
Stdint.h追加とUINT64_MAX定義、delta64修正

念のためコンパイル行も更新します。C99モードを明示するオプションを追加します。
ファイルを編集しました, ファイルを読み取りました
修正点と解説

エラーの原因: Solaris の cc はデフォルトで C89モード で動作します。UINT64_MAX は C99 の <stdint.h> で定義されているため、C89モードでは見つかりません。

対処を2段階で行いました:

<stdint.h> を明示的にインクルードし、未定義の場合は自前で定義

c

#include <stdint.h>
#ifndef UINT64_MAX
#define UINT64_MAX  ((uint64_t)0xFFFFFFFFFFFFFFFFULL)
#endif

コンパイル時に C99モードを明示 するオプションを追加

sh

# Oracle Developer Studio (cc) の場合
cc -xc99 -o sockbuf_monitor sockbuf_monitor.c -lkstat -lsocket -lnsl

# GCC の場合
gcc -std=c99 -o sockbuf_monitor sockbuf_monitor.c -lkstat -lsocket -lnsl

-xc99 を付けることで clock_nanosleep や uint64_t 等の他のC99機能も確実に有効になります。
Sockbuf monitor
C

できあがったプログラムのソースコードは以下のとおり。


/*
 * sockbuf_monitor.c  (v2 - kstat_dump ログ反映版)
 *
 * Solaris 11.4 x86 向け
 * TCP および UDP のソケットバッファ使用率を 1 ミリ秒ごとに出力する。
 *
 * kstat_dump で確認した実在フィールド:
 *   TCP (module=tcp, name=tcp):
 *     inDataInorderBytes  [29] uint32  受信順序通りデータ累積バイト
 *     inDataUnorderBytes  [31] uint32  受信順序外データ累積バイト
 *     inDataDupBytes      [33] uint32  重複受信累積バイト
 *     outDataBytes        [15] uint32  送信データ累積バイト
 *     retransBytes        [16] uint32  再送累積バイト
 *     connTableSize       [12] int32   現在の接続数 (IPv4)
 *     connTableSize6      [52] int32   現在の接続数 (IPv6)
 *
 *   UDP (module=udp, name=udp):
 *     inDatagrams   [0] uint64  受信データグラム累積数
 *     outDatagrams  [2] uint64  送信データグラム累積数
 *     inErrors      [1] uint32  受信エラー累積数
 *     outErrors     [5] uint32  送信エラー累積数
 *     ※ inBytes/outBytes は存在しないため、
 *       データグラム数 × 平均ペイロードサイズで推定する。
 *
 * コンパイル:
 *   cc  -xc99 -o sockbuf_monitor sockbuf_monitor.c -lkstat -lsocket -lnsl
 *   gcc -std=c99 -o sockbuf_monitor sockbuf_monitor.c -lkstat -lsocket -lnsl
 *
 * 実行:
 *   pfexec ./sockbuf_monitor           # 無限ループ
 *   pfexec ./sockbuf_monitor 1000      # 1000 サンプル取得して終了
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <signal.h>
#include <kstat.h>
#include <sys/types.h>

/* Solaris の cc は C89 モードがデフォルトのため
 * UINT64_MAX が <stdint.h> から来ない場合がある。
 * 明示的にインクルードし、未定義時は自前で定義する。 */
#include <stdint.h>
#ifndef UINT64_MAX
#define UINT64_MAX  ((uint64_t)0xFFFFFFFFFFFFFFFFULL)
#endif

/* ================================================================
 * 定数
 * ================================================================ */

#define INTERVAL_MS          1           /* サンプリング間隔 (ms) */
#define NSEC_PER_MSEC        1000000L

/*
 * Solaris 11.4 デフォルトのソケットバッファサイズ
 * (ndd -get /dev/tcp tcp_recv_hiwat 等で確認可能)
 */
#define TCP_DEFAULT_RCVBUF   (128 * 1024)   /* 128 KB */
#define TCP_DEFAULT_SNDBUF   (128 * 1024)   /* 128 KB */
#define UDP_DEFAULT_RCVBUF   (128 * 1024)   /* 128 KB */
#define UDP_DEFAULT_SNDBUF   (  8 * 1024)   /*   8 KB */

/*
 * UDP のバイト数推定用: inBytes/outBytes が存在しないため
 * データグラム数 × この値でバイト量を近似する。
 * 実環境に合わせて調整してください。
 */
#define UDP_AVG_PAYLOAD_BYTES  512

/* ================================================================
 * 構造体
 * ================================================================ */

/* TCP kstat スナップショット */
typedef struct tcp_snap {
    uint64_t inDataInorderBytes;   /* 順序通り受信累積 */
    uint64_t inDataUnorderBytes;   /* 順序外受信累積   */
    uint64_t outDataBytes;         /* 送信累積         */
    uint64_t retransBytes;         /* 再送累積         */
    uint64_t connTable4;           /* IPv4 接続数      */
    uint64_t connTable6;           /* IPv6 接続数      */
} tcp_snap_t;

/* UDP kstat スナップショット */
typedef struct udp_snap {
    uint64_t inDatagrams;    /* 受信データグラム累積数 */
    uint64_t outDatagrams;   /* 送信データグラム累積数 */
    uint64_t inErrors;       /* 受信エラー累積数       */
    uint64_t outErrors;      /* 送信エラー累積数       */
} udp_snap_t;

/* 表示用バッファ統計 */
typedef struct buf_stat {
    uint64_t rcv_bytes;    /* 1ms 間の受信バイト (推定) */
    uint64_t snd_bytes;    /* 1ms 間の送信バイト (推定) */
    uint64_t rcvbuf_max;   /* バッファ上限 */
    uint64_t sndbuf_max;
    double   rcv_pct;
    double   snd_pct;
    /* TCP 追加情報 */
    uint64_t retrans_bytes;
    uint64_t conn4;
    uint64_t conn6;
    /* UDP 追加情報 */
    uint64_t in_dgrams;
    uint64_t out_dgrams;
    uint64_t in_errors;
} buf_stat_t;

/* ================================================================
 * グローバル
 * ================================================================ */
static volatile sig_atomic_t g_running = 1;

/* ================================================================
 * シグナルハンドラ
 * ================================================================ */
static void sig_handler(int s) { (void)s; g_running = 0; }

/* ================================================================
 * kstat ユーティリティ: named value → uint64
 * 戻り値: 0=成功 / -1=フィールドなし (out は 0 のまま)
 * ================================================================ */
static int
ks_get_u64(kstat_t *ksp, const char *field, uint64_t *out)
{
    kstat_named_t *knp =
        (kstat_named_t *)kstat_data_lookup(ksp, (char *)field);
    if (knp == NULL) return -1;

    switch (knp->data_type) {
    case KSTAT_DATA_UINT64: *out = knp->value.ui64;           break;
    case KSTAT_DATA_INT64:  *out = (uint64_t)knp->value.i64; break;
    case KSTAT_DATA_UINT32: *out = (uint64_t)knp->value.ui32; break;
    case KSTAT_DATA_INT32:  *out = (uint64_t)knp->value.i32; break;
    default: return -1;
    }
    return 0;
}

/* ================================================================
 * TCP kstat 読み取り
 * ================================================================ */
static int
read_tcp(kstat_ctl_t *kc, tcp_snap_t *s)
{
    kstat_t *ksp = kstat_lookup(kc, "tcp", -1, "tcp");
    if (ksp == NULL) {
        fprintf(stderr, "[ERROR] kstat_lookup(tcp/tcp): %s\n", strerror(errno));
        return -1;
    }
    if (kstat_read(kc, ksp, NULL) == -1) {
        fprintf(stderr, "[ERROR] kstat_read(tcp): %s\n", strerror(errno));
        return -1;
    }

    memset(s, 0, sizeof(*s));
    ks_get_u64(ksp, "inDataInorderBytes",  &s->inDataInorderBytes);
    ks_get_u64(ksp, "inDataUnorderBytes",  &s->inDataUnorderBytes);
    ks_get_u64(ksp, "outDataBytes",        &s->outDataBytes);
    ks_get_u64(ksp, "retransBytes",        &s->retransBytes);
    ks_get_u64(ksp, "connTableSize",       &s->connTable4);
    ks_get_u64(ksp, "connTableSize6",      &s->connTable6);
    return 0;
}

/* ================================================================
 * UDP kstat 読み取り
 * ================================================================ */
static int
read_udp(kstat_ctl_t *kc, udp_snap_t *s)
{
    kstat_t *ksp = kstat_lookup(kc, "udp", -1, "udp");
    if (ksp == NULL) {
        fprintf(stderr, "[ERROR] kstat_lookup(udp/udp): %s\n", strerror(errno));
        return -1;
    }
    if (kstat_read(kc, ksp, NULL) == -1) {
        fprintf(stderr, "[ERROR] kstat_read(udp): %s\n", strerror(errno));
        return -1;
    }

    memset(s, 0, sizeof(*s));
    ks_get_u64(ksp, "inDatagrams",  &s->inDatagrams);
    ks_get_u64(ksp, "outDatagrams", &s->outDatagrams);
    ks_get_u64(ksp, "inErrors",     &s->inErrors);
    ks_get_u64(ksp, "outErrors",    &s->outErrors);
    return 0;
}

/* ================================================================
 * 差分ユーティリティ (カウンタ折り返し対策: uint64 wrap)
 * ================================================================ */
static uint64_t
delta64(uint64_t prev, uint64_t curr)
{
    return (curr >= prev) ? (curr - prev)
                          : (UINT64_MAX - prev + curr + 1);
}

/* ================================================================
 * TCP バッファ統計計算
 *
 * 考え方:
 *   1ms 間に受信バッファに届いたデータ量 =
 *       Δ(inDataInorderBytes) + Δ(inDataUnorderBytes)
 *   再送を除いた正味送信量 =
 *       Δ(outDataBytes) − Δ(retransBytes)
 *
 *   これをバッファ上限で除して使用率とする。
 *   1ms 間のスループットがバッファ上限を超える場合は 100% にクリップ。
 * ================================================================ */
static void
calc_tcp(const tcp_snap_t *prev, const tcp_snap_t *curr,
         buf_stat_t *out)
{
    uint64_t d_rcv_inorder  = delta64(prev->inDataInorderBytes,
                                       curr->inDataInorderBytes);
    uint64_t d_rcv_unorder  = delta64(prev->inDataUnorderBytes,
                                       curr->inDataUnorderBytes);
    uint64_t d_snd          = delta64(prev->outDataBytes,
                                       curr->outDataBytes);
    uint64_t d_retrans      = delta64(prev->retransBytes,
                                       curr->retransBytes);

    out->rcvbuf_max    = TCP_DEFAULT_RCVBUF;
    out->sndbuf_max    = TCP_DEFAULT_SNDBUF;

    out->rcv_bytes     = d_rcv_inorder + d_rcv_unorder;
    out->snd_bytes     = (d_snd > d_retrans) ? d_snd - d_retrans : 0;
    out->retrans_bytes = d_retrans;
    out->conn4         = curr->connTable4;
    out->conn6         = curr->connTable6;

    /* 上限クリップ */
    if (out->rcv_bytes > out->rcvbuf_max) out->rcv_bytes = out->rcvbuf_max;
    if (out->snd_bytes > out->sndbuf_max) out->snd_bytes = out->sndbuf_max;

    out->rcv_pct = (double)out->rcv_bytes / (double)out->rcvbuf_max * 100.0;
    out->snd_pct = (double)out->snd_bytes / (double)out->sndbuf_max * 100.0;
}

/* ================================================================
 * UDP バッファ統計計算
 *
 * UDP は inBytes/outBytes が存在しないため、
 *   バイト推定量 = Δdatagrams × UDP_AVG_PAYLOAD_BYTES
 * ================================================================ */
static void
calc_udp(const udp_snap_t *prev, const udp_snap_t *curr,
         buf_stat_t *out)
{
    uint64_t d_in  = delta64(prev->inDatagrams,  curr->inDatagrams);
    uint64_t d_out = delta64(prev->outDatagrams, curr->outDatagrams);

    out->rcvbuf_max  = UDP_DEFAULT_RCVBUF;
    out->sndbuf_max  = UDP_DEFAULT_SNDBUF;

    out->in_dgrams   = d_in;
    out->out_dgrams  = d_out;
    out->in_errors   = delta64(prev->inErrors, curr->inErrors);

    /* バイト推定 */
    out->rcv_bytes   = d_in  * UDP_AVG_PAYLOAD_BYTES;
    out->snd_bytes   = d_out * UDP_AVG_PAYLOAD_BYTES;

    if (out->rcv_bytes > out->rcvbuf_max) out->rcv_bytes = out->rcvbuf_max;
    if (out->snd_bytes > out->sndbuf_max) out->snd_bytes = out->sndbuf_max;

    out->rcv_pct = (double)out->rcv_bytes / (double)out->rcvbuf_max * 100.0;
    out->snd_pct = (double)out->snd_bytes / (double)out->sndbuf_max * 100.0;
}

/* ================================================================
 * タイムスタンプ (HH:MM:SS.nnnnnnnnn)
 * ================================================================ */
static void
timestamp(char *buf, size_t n)
{
    struct timespec ts;
    struct tm       tm;
    clock_gettime(CLOCK_REALTIME, &ts);
    localtime_r(&ts.tv_sec, &tm);
    snprintf(buf, n, "%02d:%02d:%02d.%09ld",
             tm.tm_hour, tm.tm_min, tm.tm_sec, ts.tv_nsec);
}

/* ================================================================
 * 1ms スリープ
 * ================================================================ */
static void
sleep_1ms(void)
{
    struct timespec req = { 0, NSEC_PER_MSEC };
    while (clock_nanosleep(CLOCK_MONOTONIC, 0, &req, &req) == EINTR)
        if (!g_running) break;
}

/* ================================================================
 * ヘッダー
 * ================================================================ */
static void
print_header(void)
{
    printf("\n");
    printf("%-19s %-5s %10s %10s %7s %10s %10s %7s %12s %12s %11s %9s\n",
           "Timestamp",
           "Proto",
           "Rcv(B/ms)", "RcvMax(B)", "Rcv(%)",
           "Snd(B/ms)", "SndMax(B)", "Snd(%)",
           "Retrans(B)", "Conn(v4/v6)",
           "DGin/ms", "DGout/ms");
    printf("%-19s %-5s %10s %10s %7s %10s %10s %7s %12s %12s %11s %9s\n",
           "-------------------", "-----",
           "----------", "----------", "-------",
           "----------", "----------", "-------",
           "------------", "------------",
           "-----------", "---------");
}

/* ================================================================
 * TCP 行出力
 * ================================================================ */
static void
print_tcp(const char *ts, const buf_stat_t *s)
{
    char conn[24];
    snprintf(conn, sizeof(conn), "%llu/%llu",
             (unsigned long long)s->conn4,
             (unsigned long long)s->conn6);

    printf("%-19s %-5s %10llu %10llu %6.2f%% %10llu %10llu %6.2f%% %12llu %12s %11s %9s\n",
           ts, "TCP",
           (unsigned long long)s->rcv_bytes,
           (unsigned long long)s->rcvbuf_max,
           s->rcv_pct,
           (unsigned long long)s->snd_bytes,
           (unsigned long long)s->sndbuf_max,
           s->snd_pct,
           (unsigned long long)s->retrans_bytes,
           conn,
           "-", "-");
}

/* ================================================================
 * UDP 行出力
 * ================================================================ */
static void
print_udp(const char *ts, const buf_stat_t *s)
{
    char dgin[16], dgout[16];
    snprintf(dgin,  sizeof(dgin),  "%llu", (unsigned long long)s->in_dgrams);
    snprintf(dgout, sizeof(dgout), "%llu", (unsigned long long)s->out_dgrams);

    printf("%-19s %-5s %10llu %10llu %6.2f%% %10llu %10llu %6.2f%% %12s %12s %11s %9s\n",
           ts, "UDP",
           (unsigned long long)s->rcv_bytes,
           (unsigned long long)s->rcvbuf_max,
           s->rcv_pct,
           (unsigned long long)s->snd_bytes,
           (unsigned long long)s->sndbuf_max,
           s->snd_pct,
           "-", "-",
           dgin, dgout);
}

/* ================================================================
 * main
 * ================================================================ */
int
main(int argc, char *argv[])
{
    kstat_ctl_t *kc;
    tcp_snap_t   tcp_prev, tcp_curr;
    udp_snap_t   udp_prev, udp_curr;
    buf_stat_t   tcp_stat, udp_stat;
    char         ts[32];
    long         max_iter = -1;
    long         iter     = 0;
    int          hdr_lines = 0;
    const int    HDR_EVERY = 40;  /* 40 行ごとにヘッダーを再表示 */

    if (argc >= 2) {
        max_iter = atol(argv[1]);
        if (max_iter <= 0) {
            fprintf(stderr, "Usage: %s [count]\n", argv[0]);
            return EXIT_FAILURE;
        }
    }

    signal(SIGINT,  sig_handler);
    signal(SIGTERM, sig_handler);

    kc = kstat_open();
    if (kc == NULL) {
        fprintf(stderr, "kstat_open() failed: %s\n", strerror(errno));
        fprintf(stderr, "pfexec / root で実行してください。\n");
        return EXIT_FAILURE;
    }

    printf("Solaris 11.4 Socket Buffer Monitor  [interval=%dms]\n", INTERVAL_MS);
    printf("Ctrl-C で停止\n");
    printf("\n");
    printf("凡例:\n");
    printf("  Rcv(B/ms)  : 1ms 間の受信バッファ到達バイト数\n");
    printf("               TCP = inDataInorderBytes + inDataUnorderBytes の差分\n");
    printf("               UDP = 受信データグラム数 × 推定ペイロード(%dB)\n",
           UDP_AVG_PAYLOAD_BYTES);
    printf("  Snd(B/ms)  : 1ms 間の正味送信バイト数 (TCP: 再送除く)\n");
    printf("               UDP = 送信データグラム数 × 推定ペイロード(%dB)\n",
           UDP_AVG_PAYLOAD_BYTES);
    printf("  Rcv/Snd(%%) : バッファ上限に対する使用率\n");
    printf("  Retrans(B) : TCP 再送バイト数 (1ms 差分)\n");
    printf("  Conn       : TCP 現在接続数 (IPv4/IPv6)\n");
    printf("  DGin/ms    : UDP 受信データグラム数 (1ms 差分)\n");
    printf("  DGout/ms   : UDP 送信データグラム数 (1ms 差分)\n");

    /* ベースライン取得 */
    if (read_tcp(kc, &tcp_prev) != 0 ||
        read_udp(kc, &udp_prev) != 0) {
        kstat_close(kc);
        return EXIT_FAILURE;
    }

    sleep_1ms();

    /* メインループ */
    while (g_running) {

        if (kstat_chain_update(kc) == -1) {
            /* チェーン更新失敗は警告のみ継続 */
            fprintf(stderr, "[WARN] kstat_chain_update: %s\n", strerror(errno));
        }

        if (read_tcp(kc, &tcp_curr) != 0 ||
            read_udp(kc, &udp_curr) != 0)
            break;

        calc_tcp(&tcp_prev, &tcp_curr, &tcp_stat);
        calc_udp(&udp_prev, &udp_curr, &udp_stat);

        timestamp(ts, sizeof(ts));

        if (hdr_lines % HDR_EVERY == 0)
            print_header();
        hdr_lines += 2;  /* TCP + UDP で 2 行 */

        print_tcp(ts, &tcp_stat);
        print_udp(ts, &udp_stat);

        tcp_prev = tcp_curr;
        udp_prev = udp_curr;

        iter++;
        if (max_iter > 0 && iter >= max_iter)
            break;

        sleep_1ms();
    }

    kstat_close(kc);
    printf("\n合計 %ld サンプル取得\n", iter);
    return EXIT_SUCCESS;
}