Edited at

トラフィックをアニメーションで可視化する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!🎄