JavaScript
Node.js
WebGL
reactjs
vizceral

トラフィックをアニメーションで可視化するNetflixのvizceralについて

More than 1 year has passed since last update.

この記事は リクルートライフスタイル Advent Calendar 2017 10日目の記事です。

はじめに

はじめまして、ホットペッパービューティのアプリエンジニアをやっている @applideveloper と申します。

今日は、Netflixのトラフィックをアニメーションで可視化するNetflixのvizceralというオープンソースについて翻訳しながら、紹介したいと思います。
https://github.com/Netflix/vizceral

まずは、以下のGIFアニメーションをご覧ください。

vizceral2.gif

vizceralは、トラフィックデータをWebGLキャンパスに表示するためのコンポーネントです。

トラフィック量に関するデータを含むノード及びエッジのグラフが提供される場合、ノード間の接続量をアニメーションするトラフィックグラフをレンダリングします。イエローのドットはサービス間の劣化、レッドのドットはエラーのレスポンスを示しています。

このコンポーネントは、複数のトラフィクグラフを扱うことができます。そして、リージョンをまたぐトラフィックをサポートするために、各リージョンへの流入トラフィックを全て表示するグローバルなグラフを生成できます。

スクリーンショット 2017-12-09 20.19.34.png

グローバルレベル、エリアレベル、サービスレベルの3つのレベルの情報があり、ノードをクリックまたはダブルクリックすると、レベルを1つ上げることができます。

vizceral3.gif

デモ

GIFアニメーションじゃなくて、触ってみたいという方は、以下のサンプルコードをやってみましょう!
https://github.com/Netflix/vizceral-example

1 ソースを取得し、depsをインストールし、デモサーバーを実行します。

git clone git@github.com:Netflix/vizceral-example.git
cd vizceral-example
npm install
npm run dev

nodeのバージョンが合わなくて動かない方は、nodeのバージョンをv9.2.1にして見るか、dockerで動かしてみましょう!

git clone git@github.com:Netflix/vizceral-example.git
cd vizceral-example
docker build -t vizceral:0.1 .
docker run -d -p 8080:8080 vizceral:0.1

2 ブラウザで、http://localhost:8080/ のURLを開くと、以下のような操作ができるようになります。

vizceral3.gif

使い方

使い方のドキュメントはこちら

Graph Data Format

vizceralのほとんどの機能は、指定されたグラフデータ内の適切なデータをupdateDataに持たせることで有効になります。

サンプルコードでは、trafixFlow.jsxbeginSampleData関数で、sample_data.json を読み込み、updateData関数を呼んでいます。

trafficFlow.jsx
  beginSampleData () {
    this.traffic = { nodes: [], connections: [] };
    request.get('sample_data.json')
      .set('Accept', 'application/json')
      .end((err, res) => {
        if (res && res.status === 200) {
          this.traffic.clientUpdateTime = Date.now();
          this.updateData(res.body);
        }
      });
  }
trafficFlow.jsx
  updateData (newTraffic) {
    const regionUpdateStatus = _.map(_.filter(newTraffic.nodes, n => n.name !== 'INTERNET'), (node) => {
      const updated = node.updated;
      return { region: node.name, updated: updated };
    });
    const lastUpdatedTime = _.max(_.map(regionUpdateStatus, 'updated'));
    this.setState({
      regionUpdateStatus: regionUpdateStatus,
      timeOffset: newTraffic.clientUpdateTime - newTraffic.serverUpdateTime,
      lastUpdatedTime: lastUpdatedTime,
      trafficData: newTraffic
    });
  }

updatedDataに提供されるデータフォーマットの例。

{
  //このグラフに使用するグラフレンダラーはどれですか(現在は 'global'と 'region'のみ)
  renderer: 'global',
  //ルートオブジェクトはノードなので、名前もあります。
  name: 'edge',
  //オプション:粒子密度を相対的に測定するために最近見られた最大流量。  この 'グローバル' maxVolumeは、必要なサブノードmaxVolumesのすべてを使用して計算できるため、オプションです。
  maxVolume: 100000,
  // OPTIONAL:グローバルグラフへのエントリノードの名前。  省略された場合、 'INTERNET'ノードとみなされます
  entryNode: 'INTERNET',
  //このグラフのノードのリスト
  nodes: [
    {
      renderer: 'region',
      layout: 'ltrTree',
      // OPTIONALレンダラーに使用されるデフォルトレイアウトをオーバーライドします。
      name: 'us-west-2',
      // Unixタイムスタンプ。  このノードのレベルでのみチェックされます。  データが最後に更新されたとき(ロード時にクライアントが古いデータを渡す可能性があるために必要)
      updated: 1462471847,
      //粒子密度を相対的に測定するために最近見た最大流量
      maxVolume: 100000,
      nodes: [
        {
          name: 'INTERNET' //必須...これはエントリノードです
        },
        {
          name: 'apiproxy-prod',
          // OPTIONALラベルの名前を上書きする
          displayName: 'proxy',
          // OPTIONALサイドバーに表示したい通知です。詳しくはnoticesのセクションを参照してください。
          notices: [
            {
              //通知に表示するタイトル
              title: 'Notice about something',
              //通知をクリックするとユーザを送信するオプションのリンク
              link: 'http://link/to/relevant/thing',
              // オプション: infoレベルは0(default) 、警告レベルは1、エラーレベルは2 (CSSスタイル適用)
              severity: 1
            }
          ],
          //ノードのクラス。  提供されていなければ、デフォルトは 'normal'になります。  UIの色付けは 'normal'、 'warning'、 'danger'に基づいているため、UIの色付けを一致させる場合は、それらのクラス名を使用してください。  あなたが提供するクラスは、 'class'が 'fuzzy'の場合、 'colorClassName'というスタイルを持つことが期待されます。また、 'vizceral.updateStyles({colorTraffic:{fuzzy:' #aaaaaa '}})'
          class: 'normal',
          // OPTIONALプラグインやその他のデータによって処理される可能性がある、vizceral自体にとって重要ではないデータ(例えば、vizceralを含むページに項目を表示したい場合)。  それは完全にオプションでvizceralで処理されないので、技術的には任意のインデックスを使用できますが、これが私たちが使用する規約です。
          metadata: {}
        }
      ],
      connections: [
        {
          //接続のソースノードは、ノードが存在しない場合に警告を記録します。
          source: 'INTERNET',
          //接続のターゲットノードは、ノードが存在しない場合に警告を記録します。
          target: 'apiproxy-prod',
          //これらは、コンポーネントで使用可能な3つのデフォルトの型/色とノード自体に使用される色です。  あなたは他人を追加したり、他の名前を使用することができます。代わりに、UIカラーリングと適切に一致しないかもしれません。
          metrics: {
            normal: 5000,
            danger: 5,
            warning: 0
          },
          // OPTIONALサイドバーに表示したい通知
          notices: [],
          // OPTIONALプラグインやその他のデータによって処理される可能性がある、vizceral自体にとって重要ではないデータ(例えば、vizceralを含むページに項目を表示したい場合)。  それは完全にオプションでvizceralで処理されないので、技術的には任意のインデックスを使用できますが、これが私たちが使用する規約です。
          metadata: {}
        }
      ]
    }
  ]
}

通知

上記のGraph Data formatで説明したように、Vizceralは接続とノードに関する通知をサポートしています。 データ形式とコンセプトは同じですが、違いはUIでどのように表示されるかです。

通知がUIに表示されますが、通知のリストを表示するためのポップアップをサポートするには、 アプリを適切に設定する必要があります 。

Nodeの警告レベル 表示
info info-node
warning waring-node
error error-node
connectionの警告レベル 表示
info info-connection
warning warning-connection
error error-connection

スタイリング

このコンポーネントは、変数のマップを使用してすべてのスタイルを設定します。 任意の数のデフォルトスタイルを上書きすることができます。 次の例は、コンポーネントで使用されるすべてのスタイルを示しています。

サンプルコードでは、以下のようなコードでスタイリングの設定がされていました。

trafixFlow.jsx
<Vizceral traffic={this.state.trafficData}
  view={this.state.currentView}
  showLabels={this.state.displayOptions.showLabels}
  filters={this.state.filters}
  viewChanged={this.viewChanged}
  viewUpdated={this.viewUpdated}
  objectHighlighted={this.objectHighlighted}
  nodeContextSizeChanged={this.nodeContextSizeChanged}
  objectToHighlight={this.state.objectToHighlight}
  matchesFound={this.matchesFound}
  match={this.state.searchTerm}
  modes={this.state.modes}
  allowDraggingOfNodes={this.state.displayOptions.allowDraggingOfNodes}
/>

Q なぜ普通のCSSではないのか?

A 主な基礎となるレンダリングはthree.jsを介して行われるため、色はjavascriptで設定されているため、コンポーネントでCSSとJS変数に同じ値を簡単に使用する必要があります。

フィルタ

フィルタを設定および更新する方法の詳細については、設定フィルタセクションを確認してください。

表示するデータの定義

定義はオプションです。 それらは、Vizceralの特定のセクションでどのようなデータがレンダリングされるかを設定する方法です。 現在、詳細なノードビューのコンフィグレーションのみが存在します。

次の構造体に似た構造体をsetDefinitions(definitions)に渡すと、その構成要素に定義が追加されます。
構造は次のとおりです。

{
  'definition title': { // ここではdetailedNodeのみをサポートしています。
    'display mode': { // 私たちは今のところボリュームをサポートしています。
      'default': {}, // デフォルトのレンダラーです。 これは必須であり、特定のオーバーライドがない限り、詳細なノードに表示されるものです。
      'override': {}, //エントリノードを別の方法で表示する場合は 'entry'、使用されているレンダラーの名前(レンダラーは 'global'と 'region')になります。
      'override2': {}, //複数のオーバーライドをサポートしています...
    }
  }
}

例を使用した詳細な実装については、以下を確認してください。

Node Detailed View

ノードの詳細なビューの構成パラメーター。

{
  detailedNode: { //これらの定義は、詳細ノードが示すものを切り替えるためのものです
    volume: { `volume` //(デフォルトモード)はすでに内部的に定義されていますが、別の設定パラメータで再度渡すことでカスタマイズできます
      default: { //デフォルトは必須です
        //詳細ノードのトップメトリック。  空白にしたい場合はnullに設定します。
        // `header`は表示されるテキストヘッダです
        // `data`はノードオブジェクトに表示するデータへのパスです
        // `format`はnumeric.jsを使ってデータをフォーマットする方法です
        top: { header: '% RPS', data: 'data.volumePercent', format: '0.00%' },
        //詳細ノードの下部メトリック。  空白にしたい場合はnullに設定します。
        // `header`は表示されるテキストヘッダです
        // `data`はノードオブジェクトに表示するデータへのパスです
        // `format`はnumeric.jsを使ってデータをフォーマットする方法です
        bottom: { header: 'ERROR RATE', data: 'data.classPercents.danger', format: '0.00%' },
        donut: {} //詳細ノードの周りのドーナツグラフを塗りつぶすには、詳細についてはドーナツグラフのヘッダーを確認してください
        arc: {} //詳細ノード内の円弧メーターを埋めるもの。  不在の場合、アークメーターは描画されません
       },
      },
      region: { //リージョンレンダラーのオーバーライド
        top: { header: 'SERVICE RPS', data: 'data.volume', format: '0.0' }
      },
      entry: { //エントリノードを上書きする
        top: { header: 'TOTAL RPS', data: 'data.volume', format: '0.0' }
      }
    }
  }
}

Donut Graph

donus

{
  detailedNode: {
    volume: {
      default: {
        // ...
        //詳細ノードの周りのドーナツグラフを塗りつぶす説明
        donut: {
          // OPTIONAL:ドーナツグラフにカーソルを置いたときに別のものを表示したい場合は、
          // ここでは上部と下部をオーバーライドすることができます。  あなたが1つだけオーバーライドすると、vizceralは
          // もう一方は空白にする必要があります。
          top: {},
          bottom: {},
          // ドーナツグラフに表示されるデータへのパス。
          // キー/値ペアのオブジェクトをポイントする必要があります。値は小数点以下のパーセンテージ(0〜1)
          data: 'data.globalClassPercents',
          // デフォルトでは、ドーナツスライスの色付けは、データのキーによって、そして不確定な順序でマップされます
          // 別のクラスにマッピングしたり、レンダー順を強制したりする場合は、ここでオーバーライドします。
          // キーを持つオブジェクトの配列と、キー自体と異なる場合はオプションのクラス
          indices: [
            { key: 'danger' },
            { key: 'warning' },
            { key: 'normal', class: 'normalDonut' }
          ]
        }
        // ...
      }
    }
  }
}

Arc Graph

{
  detailedNode: {
    volume: {
      default: {
        // ...
        // 詳細ノード内の円弧メーターを塗りつぶす説明。  不在の場合、アークメーターは描画されません
        arc: {
          // OPTIONAL:円弧グラフの上にカーソルを置いたときに別のものを表示したい場合は、
          // ここでは上部と下部をオーバーライドすることができます。  あなたが1つだけオーバーライドすると、vizceralは
          // もう一方は空白にする必要があります。
          top: {},
          bottom: {},
          // 円弧グラフ上に表示されるデータへのパス。
          // 次のような構造が必要です。
          // {
          //   values: [ // valueの配列
          //     { name: 'foo', value: 30 }, // Valuesにはvalue、name、およびoptionalのオーバーライドクラスがあります。  classが存在しない場合は、クラス名としてnameを使用します。
          //     { name: 'bar', value, 70, class 'barclass' }
          //  ],
          //   total: 100, //合計:100、//円弧グラフの100%に等しい合計値
          //   line: 0.9 // [optional]任意のマーキング行を入力するためのパーセント値を10進形式で指定します。
          // }
          data: 'metadata.something',
          // 0から1までの行位置を与える上記データオブジェクトのインデックス(10進数パーセント)。不在の場合、線は描画されません
          lineIndex: 'line'
        }
        // ...
      }
    }
  }
}

通知

Vizceralは、接続とノードに関する通知を表示をサポートします
この機能を使用する場合は、 vizceral-noticeクラスを持つDOM要素をvizceralcanvasに兄弟要素として提供する必要があります。

Filters

フィルタはオプションです。 この構造体をsetFilters(filters)に渡すと、フィルターを通過しないすべての要素(ノードと接続)が除外されます。 setFiltersを呼び出すたびに新しいフィルタが追加されます。フィルタが同じ名前を持つ場合は、値が更新されます。 最初の呼び出しでは、setFiltersはデフォルトのフィルタ値を設定します。

[
  {
    name: 'rps', // フィルタの一意の名前
    type: 'connection', //フィルタが適用されるオブジェクトタイプ( 'node'または 'connection')
    passes: (object, value) => { //オブジェクトの値と現在の値を比較する関数
      return value < 0 || object.volume.total <= value;
    },
    value: -1 /フィルタの現在の値
  }
]

サンプルコードだと、以下の箇所で使われていました。
https://github.com/Netflix/vizceral-example/blob/master/src/components/filterStore.js#L20

const store = {
  filters: {
    rps: {
      name: 'rps',
      type: 'connection',
      passes: (object, value) => object.volumeTotal >= value,
      value: -1
    }
  },
  // 省略
};

APIとEvent

APIと、Eventについては、ドキュメントをご覧ください。

https://github.com/Netflix/vizceral/wiki/How-to-Use

以上が、vizceralの紹介でした。

おわりに

トラッフィックをアニメーションで可視化できると楽しいですね。
vizceralに対応させれば、他のトラフィックを可視化することもできます。

例えば、Akkaのcinnamonは、vizceralサポートしていて、ActorSystemのActorの通信を可視化することができます(商用の credentials情報が必要なので注意)。


https://developer.lightbend.com/blog/2017-11-07-cinnamon-2-6-with-vizceral-support/index.html?_ga=2.190413096.269154569.1512779086-322019588.1502195798 より引用

他にも、vizceralとは関係ないのですが、BEAM(Erlang VM)のnodeを可視化するvisualixirというライブラリなどがあります。

vizualizer.gif

vizualizer2.gif

今回はサンプルコードを動かすだけでしたが、機会があったら、他のトラフィックも可視化したいなと思いました。

それでは、皆様良いクリスマスになりますようにMerry Christmas!🎄