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

この記事は リクルートライフスタイル [Advent Calendar 2017 10日目](https://qiita.com/advent-calendar/2017/recruitlifestyle)の記事です。

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

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

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

![vizceral2.gif](https://qiita-image-store.s3.amazonaws.com/0/25434/3ea44ba9-69a0-c8f5-40d8-d30ce37974fd.gif)

[vizceral](https://github.com/Netflix/vizceral
)は、トラフィックデータをWebGLキャンパスに表示するためのコンポーネントです。

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

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

<img width="976" alt="スクリーンショット 2017-12-09 20.19.34.png" src="https://qiita-image-store.s3.amazonaws.com/0/25434/ba7d2679-bea7-e1e6-8c48-f9b7c1321c01.png">

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

![vizceral3.gif](https://qiita-image-store.s3.amazonaws.com/0/25434/cd315bf3-a801-395c-e85e-29a607642b7c.gif)

# デモ

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

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

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

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

```sh
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](https://qiita-image-store.s3.amazonaws.com/0/25434/99e5b503-4b43-89f8-6dbc-3c8f1fb7f651.gif)

# 使い方

使い方のドキュメントは[こちら](https://github.com/Netflix/vizceral/wiki/How-to-Use)

## Graph Data Format

[vizceral](https://github.com/Netflix/vizceral
)のほとんどの機能は、指定されたグラフデータ内の適切なデータを`updateData`に持たせることで有効になります。

サンプルコードでは、`trafixFlow.jsx`の`beginSampleData`関数で、[sample_data.json](https://github.com/Netflix/vizceral-example/blob/master/src/sample_data.json) を読み込み、`updateData`関数を呼んでいます。

```js: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);
        }
      });
  }
```

```js: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`に提供されるデータフォーマットの例。 

```js
{
  //このグラフに使用するグラフレンダラーはどれですか（現在は '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に表示されますが、通知のリストを表示するためのポップアップをサポートするには、 [アプリを適切に設定する](https://github.com/Netflix/Vizceral/wiki/Configuration#filters)必要があります 。

| Nodeの警告レベル | 表示 |
|------------|-----|
| info | ![info-node](https://github.com/Netflix/vizceral/wiki/images/notice-node-normal.png) |
| warning | ![waring-node](https://github.com/Netflix/vizceral/wiki/images/notice-node-warning.png) |
| error | ![error-node](https://github.com/Netflix/vizceral/wiki/images/notice-node-danger.png) |

| connectionの警告レベル | 表示 |
|---------------|-----|
| info |![info-connection](https://github.com/Netflix/vizceral/wiki/images/notice-connection-normal.png) |
| warning | ![warning-connection](https://github.com/Netflix/vizceral/wiki/images/notice-connection-warning.png) |
| error | ![error-connection](https://github.com/Netflix/vizceral/wiki/images/notice-connection-danger.png) |

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

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

```html: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変数に同じ値を簡単に使用する必要があります。

## フィルタ

フィルタを設定および更新する方法の詳細については、[設定](https://github.com/Netflix/vizceral/wiki/Configuration)の[フィルタ](https://github.com/Netflix/Vizceral/wiki/Configuration#filters)セクションを確認してください。

## 表示するデータの定義

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

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

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

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

## Node Detailed View

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

```js
{
  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](https://github.com/Netflix/Vizceral/wiki/images/donut_graph.png)

```js
{
  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

![](https://github.com/Netflix/Vizceral/wiki/images/arc_meter.png)

```js
{
  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`は、接続とノードに関する[通知を表示をサポートします](https://github.com/Netflix/Vizceral/wiki/How-to-Use#notices)。 
この機能を使用する場合は、 `vizceral-notice`クラスを持つDOM要素を`vizceral`canvasに兄弟要素として提供する必要があります。

## Filters

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

```js
[
  {
    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

```js
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/blob/master/USAGE.md)をご覧ください。

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

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

## おわりに

トラッフィックをアニメーションで可視化できると楽しいですね。
[vizceral](https://github.com/Netflix/vizceral)に対応させれば、他のトラフィックを可視化することもできます。

例えば、Akkaの[cinnamon](https://developer.lightbend.com/docs/cinnamon/2.6.x/project/migration-2.6.html)は、[vizceral](https://github.com/Netflix/vizceral)を[サポート](https://developer.lightbend.com/blog/2017-11-07-cinnamon-2-6-with-vizceral-support/index.html?_ga=2.179057191.269154569.1512779086-322019588.1502195798)していて、ActorSystemのActorの通信を可視化することができます(商用の　[credentials](https://developer.lightbend.com/docs/reactive-platform/2.0/setup/setup-sbt.html)情報が必要なので注意)。

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

他にも、[vizceral](https://github.com/Netflix/vizceral)とは関係ないのですが、BEAM(Erlang VM)のnodeを可視化する[visualixir](https://github.com/koudelka/visualixir)というライブラリなどがあります。

![vizualizer.gif](https://qiita-image-store.s3.amazonaws.com/0/25434/28d8603b-943c-f37b-98f1-3d39bbf5a41c.gif)

![vizualizer2.gif](https://qiita-image-store.s3.amazonaws.com/0/25434/8da3744c-277c-cb2f-5396-0e0b2db691c8.gif)


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

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