27
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

リクルートライフスタイルAdvent Calendar 2017

Day 10

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

Last updated at Posted at 2017-12-09

この記事は リクルートライフスタイル 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については、ドキュメントをご覧ください。

以上が、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!🎄

27
21
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
27
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?