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