この記事は リクルートライフスタイル Advent Calendar 2017 10日目の記事です。
はじめに
はじめまして、ホットペッパービューティのアプリエンジニアをやっている @applideveloper と申します。
今日は、Netflixのトラフィックをアニメーションで可視化するNetflixのvizceralというオープンソースについて翻訳しながら、紹介したいと思います。
https://github.com/Netflix/vizceral
まずは、以下のGIFアニメーションをご覧ください。
vizceralは、トラフィックデータをWebGLキャンパスに表示するためのコンポーネントです。
トラフィック量に関するデータを含むノード及びエッジのグラフが提供される場合、ノード間の接続量をアニメーションするトラフィックグラフをレンダリングします。イエローのドットはサービス間の劣化、レッドのドットはエラーのレスポンスを示しています。
このコンポーネントは、複数のトラフィクグラフを扱うことができます。そして、リージョンをまたぐトラフィックをサポートするために、各リージョンへの流入トラフィックを全て表示するグローバルなグラフを生成できます。
グローバルレベル、エリアレベル、サービスレベルの3つのレベルの情報があり、ノードをクリックまたはダブルクリックすると、レベルを1つ上げることができます。
デモ
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を開くと、以下のような操作ができるようになります。
使い方
使い方のドキュメントはこちら
Graph Data Format
vizceralのほとんどの機能は、指定されたグラフデータ内の適切なデータをupdateData
に持たせることで有効になります。
サンプルコードでは、trafixFlow.jsx
のbeginSampleData
関数で、sample_data.json を読み込み、updateData
関数を呼んでいます。
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);
}
});
}
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 | |
warning | |
error |
connectionの警告レベル | 表示 |
---|---|
info | |
warning | |
error |
スタイリング
このコンポーネントは、変数のマップを使用してすべてのスタイルを設定します。 任意の数のデフォルトスタイルを上書きすることができます。 次の例は、コンポーネントで使用されるすべてのスタイルを示しています。
サンプルコードでは、以下のようなコードでスタイリングの設定がされていました。
<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
{
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要素をvizceral
canvasに兄弟要素として提供する必要があります。
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というライブラリなどがあります。
今回はサンプルコードを動かすだけでしたが、機会があったら、他のトラフィックも可視化したいなと思いました。
それでは、皆様良いクリスマスになりますようにMerry Christmas!🎄