モバイル&ワイヤレスブロードバンドでインターネットへ

gwaw.jp
 
WebGPU / WGSL · log visualization

ログの、銀河
WebGPU でアクセスログを見る

アクセスログは、大量の点だ。テーブルは点を「数える」。でも可視化は点を「見る」。 時刻とサブネットの座標に何万もの点を配置し、加算ブレンドで密集を光らせると—— 分散スキャンは赤い帯、単発集中は輝く塊として、構造そのものが浮かび上がる。WebGPU の土俵です。

WebGPU WGSL instanced rendering additive blend 100k+ points

[ § 0 ] 導入

テーブルは数える。可視化は、見る

集計APIダッシュボードで、ログを「数えて」きました。top offenders、サブネット別件数——数字は正確です。 でも数字は、構造を見せてくれません。

アクセスログは本質的に大量の点です。時刻があり、アクセス元があり、種別がある。 これを座標に置いて描くと、テーブルでは埋もれていたパターンがとして現れます。 そして大量の点を滑らかに描くのは、ティックベンチで見たとおり WebGPU の得意分野です。

この記事のデータは擬似生成です。 本番ログではなく、正常トラフィックに分散スキャン・単発集中・正規クローラを合成した擬似データを描きます。 攻撃パターンを意図的に仕込めるので、それが視覚的に浮かぶ様子を確かめられます。
[ § 1 ] データを点にする

log を、座標へ

各アクセスを、1つの点に変換します。座標の割り当てはこう置きました:

X = 時刻(左→右で時間経過)、Y = サブネット(同じ /24 は同じ高さに揃う)、 色 = 分類(正常=シアン、攻撃=赤、正規クローラ=緑、その他=琥珀。ダッシュボードの分類と一貫)、 輝度 = 濃さ(短時間の連続や密集で強くなる)。

galaxy.js — 擬似ログを点群に
// 1点 = { x:時刻[0,1], y:サブネット[0,1], cls:分類, intensity:濃さ }
function genGalaxy(total, withAttack) {
  const pts = [];
  // 正常トラフィック:全体に淡く散る星々
  for (let i=0; i<total*0.62; i++)
    pts.push({ x:rand(), y:rand(), cls:0, intensity:0.15+rand()*0.2 });

  if (withAttack) {
    // 分散スキャン:同一サブネット(同じY)・短時間(狭いX) → 横一列の赤帯
    for (let i=0; i<total*0.12; i++)
      pts.push({ x:0.55+rand()*0.2, y:0.35+jitter(), cls:1, intensity:0.5+rand()*0.4 });
    // 単発集中:一点に密集 → 輝く塊
    for (let i=0; i<total*0.08; i++)
      pts.push({ x:0.30+jitter(), y:0.68+jitter(), cls:1, intensity:0.7+rand()*0.3 });
  }
  return pts;
}

点は Float32Array[x, y, cls, intensity] の順で詰め、GPU の インスタンスバッファとして一括で渡します(stride 16 バイト)。数万点でも、転送は一度きりです。

[ § 2 ] WGSL で点を描く

1点を、光の粒に

各点を、小さな四角(ビルボード)として instanced 描画します。頂点シェーダで点の位置に四角を置き、 フラグメントシェーダで中心が明るく縁が消える円にする。これが「星」1粒です。

galaxy.wgsl — 頂点/フラグメント
struct U {
  aspect: f32, pointSize: f32, glow: f32, time: f32,
};
// バインディングは必ず全部使う(layout:'auto' は未使用を落とす)
@group(0) @binding(0) var<uniform> u: U;

fn classColor(c: f32) -> vec3<f32> {
  if (c < 0.5) { return vec3<f32>(0.0, 0.85, 1.0); } // 正常=シアン
  if (c < 1.5) { return vec3<f32>(1.0, 0.18, 0.30); } // 攻撃=赤
  if (c < 2.5) { return vec3<f32>(0.22, 1.0, 0.30); } // 正規=緑
  return vec3<f32>(1.0, 0.70, 0.12);            // その他=琥珀
}

@vertex
fn vs(@location(0) corner: vec2<f32>,   // 四角の隅(-1..1)
      @location(1) inst: vec2<f32>,     // 点の x,y(インスタンス)
      @location(2) ci: vec2<f32>) -> VSOut { // cls, intensity
  var out: VSOut;
  let center = vec2<f32>(inst.x*2.0-1.0, inst.y*2.0-1.0);
  let size = u.pointSize * (0.5 + ci.y);
  // aspect補正で点を真円に
  out.pos = vec4<f32>(center + vec2<f32>(corner.x*size/u.aspect, corner.y*size), 0.0, 1.0);
  out.uv = corner;
  out.color = classColor(ci.x);
  out.alpha = ci.y * u.glow;
  return out;
}

@fragment
fn fs(in: VSOut) -> @location(0) vec4<f32> {
  let a = smoothstep(1.0, 0.0, length(in.uv)) * in.alpha; // 円形フォールオフ
  return vec4<f32>(in.color * a, a);
}
標準ルールは、ここでも効く。 layout:'auto' は使っていないバインディングを黙って落とすので、宣言した u は全フィールド使い切る。 これは WGSL を書くときの毎回の作法です。今回は描画だけなので float32 テクスチャは登場しませんが、 使うなら rgba32floattextureLoad の原則も同じく効きます。
[ § 3 ] 攻撃を光らせる

加算ブレンドで、密集が発光する

「銀河」らしさの核心は加算ブレンドです。点が重なるほど色が足し算されて明るくなる。 散らばった正常アクセスは淡い星のまま、密集した攻撃は白く輝く。数密度が、そのまま輝度になります。

pipeline.js — 加算ブレンド設定
blend: {
  color: { srcFactor: 'one', dstFactor: 'one', operation: 'add' },
  alpha: { srcFactor: 'one', dstFactor: 'one', operation: 'add' },
}
// クリアは暗い宇宙色。点の色が足し合わされて銀河になる
clearValue: { r:0.02, g:0.03, b:0.05, a:1 }

これで、同一サブネットに短時間で集まる分散スキャンは赤い帯として横に伸び、 一点に殺到する単発集中は輝く塊になります。テーブルで「192.0.2.0/24 が96件」と読むより、 赤い塊が光っている方が、脅威は一瞬で伝わります。

検出と、可視化の呼応

ダッシュボードの「分散(ip_count多) / 単発集中(ip_count=1)」の区別が、そのまま銀河では「帯 / 塊」という形の違いになります。数える側と見る側で、同じ構造を別の言語で語っているわけです。

[ § 4 ] 実機で見る

Pixel 10 Pro と iPad mini A17 Pro で、描画は違う

描画性能もまた端末で差が出ます。WebLLM の decode が8倍違ったように、 点群描画のフレームレートも Pixel 10 Pro と iPad mini A17 Pro で変わります。 加算ブレンドは fill-rate(塗り面積)を食うので、点が大きく密集するほど GPU 負荷が上がります。

点数と輝度は、トレードオフ。 点を増やすほど銀河は緻密になりますが、加算ブレンドの重なりが増えて fill-rate を圧迫します。 モバイルでは点数を欲張らず、輝度で密度を見せる方が滑らかです。デモの点数スライダーで、 自分の端末の余裕を確かめてください。
[ § 5 ] 体感デモ

ログの銀河

擬似ログを WebGPU で描きます。攻撃注入を切り替えると、分散スキャンの赤い帯と単発集中の輝く塊が 現れたり消えたりします。点数スライダーで、あなたの端末の描画の余裕も見えます。

Interactive · WebGPU point galaxy
ログの銀河
X=時刻 / Y=サブネット / 色=分類 / 輝度=密度。擬似データ・加算ブレンド。WebGPU非対応環境は Canvas 2D の簡易描画にフォールバックします。
backend:
points:
fps:
正常 攻撃 正規 その他
サブネット →
← 過去時刻現在 →
点数 80,000
輝度 glow 0.40

攻撃をONにすると、画面の中ほどに赤い帯(分散スキャン)と、 その下に輝く塊(単発集中)が浮かびます。OFFにすると、 あとには淡いシアンの星々(正常)と、規則的な緑の点(正規クローラ)だけが残る。 異常が"形"として見える——これが可視化の力です。

[ § 6 ] まとめ

数えると、見る

ログ基盤には、2つの目が要ります。数える(集計API・ダッシュボード)と、見る(銀河)。 数字は正確さを、可視化は構造を教えてくれる。WebGPU の instanced rendering と加算ブレンドがあれば、 数万の点を滑らかに描き、攻撃を"形"として浮かび上がらせられます。

この記事のまとめ

  • ログは大量の点。テーブルは数え、可視化は見る。両方あって全体が分かる。
  • X=時刻 / Y=サブネット / 色=分類 / 輝度=密度、で座標へ写す。
  • instanced billboard + 円形フォールオフで、1点を光の粒に。
  • 加算ブレンドで数密度が輝度になる。分散=帯、単発集中=塊。
  • WGSL標準ルール(全バインディング使用・rgba32floattextureLoad)はここでも効く。

擬似データで構造を確かめたら、次は本番ログを流す番です。集計APIに時刻・サブネット・分類の点列を返すエンドポイントを足せば、 この銀河はそのまま実データのリアルタイム可視化になります。数える基盤の上に、見る目を重ねる——ログ基盤の、次の一手です。

『ログの銀河 — WebGPU でアクセスログを可視化する』を公開しました。