VueJSとGridDBによるタイムサンプリングデータの可視化

時系列データのグラフを改善するために、秒、分、時間などの時間軸や「解像度」を選択できるようにすることができます。

こうすることで、高解像度(例えば秒)のデータからミクロなトレンドを見たり、低解像度(例えば時間)のデータからマクロなトレンドを見たりすることができるようになります。

この記事では、VueとChart.jsを使用して時系列データの可視化をセットアップします。また、バックエンドのデータベースからGridDBのタイムサンプリングクエリを使用して、ユーザーが異なる時間解像度を選択できるようにする予定です。

この記事は、Node ExpressバックエンドとGridDBを使用してサーバーモニターダッシュボードを構築し、Vueでデータを視覚化するシリーズの続きです。まずは第一回第二回を読んでおくとよいでしょう。

このチュートリアルのコードはGitHubのこちらから取得できます。

VueのChartkickでデータをグラフ化する

このシリーズの前回のチュートリアルで作ったVueアプリは、データの可視化にVueChartJsライブラリを使用していました。

残念ながら、今回行うようにアプリのライフサイクルの中で継続的にデータを更新する必要がある場合、このライブラリはあまり向いていません。

この点で、より柔軟性の高いライブラリは、Vue Chartkickです。そこで、ドキュメントヘッドで読み込んでいるvue-chartjsスクリプトをvue-chartkickに置き換えてみましょう。

index.html

<head>
  ...
  <script src="<https://cdn.jsdelivr.net/npm/vue-chartkick@0.6.1/dist/vue-chartkick.min.js>"></script>
</head>

Then we can make a minor modification to our Vue app so it uses the Chartkick line chart component instead of VueChartJs.

index.html

<div id="app">
  <line-chart v-if="serverData" :data="serverData"></line-chart>
</div>
<script type="text/javascript">
new Vue({
  el: "#app",
  data: () => ({
    serverData: []
  }),
  async mounted () {
    const res = await fetch("/data");
    this.serverData = await res.json();
  }
})
</script>

この時点で、Vueアプリは以前と同じように見えるはずですが、より柔軟なChartkickライブラリを使っていることだけは確かです。

解像度セレクタを追加する

現在、Vueアプリはサーバーに時系列データのペイロードを要求していますが、その時間基準は常に同じ「秒」です。

そこで、アプリのテンプレートに HTML の select 要素を追加して、ユーザが異なる時間基準を選択できるようにします。

秒、分、時間の時間軸を選択できるようにします。さらに、v-model を変数 resolution にバインドしているので、Vue アプリで選択した値を簡単に取得することができます。

index.html

<div id="app">
  <div>
    <line-chart v-if="serverData" :data="serverData"></line-chart>
    <select name="resolution" class="resolution-select" v-model="resolution">
      <option value="seconds">Seconds</option>
      <option value="minutes">Minutes</option>
            <option value="hours">Hours</option>
    </select>
  </div>
</div>

それでは、Vueのインスタンスに新しいリアクティブデータプロパティ resolution を追加して、デフォルトの値として seconds を与えてみましょう。

index.html

new Vue({
  el: "#app",
  data: () => ({
    serverData: [],
    resolution: 'seconds'
  }),
  ...
})

また、ドキュメントヘッドにCSSを追加することで、セレクトのスタイルをより良くすることができます。

<style>
  .resolution-select {
    margin: 2rem 0 0 1rem;
    padding: 0.5rem;
  }
</style>

これで、チャートの下にセレクタが表示されるようになりました。

セレクト変更時にデータを再読み込みする

ユーザが異なる解像度の値を選択すると、Vueアプリは新しいデータペイロードを得るためにサーバを呼び出す必要があります。現在、データは、アプリがマウントされたときにのみ要求されます。

これを可能にするために、以前のようにサーバを呼び出すロジックを含む getServerData メソッドを作成するつもりです。

このメソッドはマウントされたフックで呼び出されるので、アプリが初期化されたときにデータを取得します。しかし、resolutionの値が変更されるたびに、このメソッドを呼び出すことにします。これは watch 関数を使用することで実現できます。この関数は、reactive の値が変化するたびに呼び出されます。

index.html

new Vue({
  el: "#app",
  data: () => ({
    serverData: [],
    resolution: 'seconds'
  }),
  methods: {
    async getServerData() {
      const res = await fetch("/data");
      this.serverData = await res.json();
    }
  },
  watch: {
    resolution() {
      this.getServerData();
    }
  },
  mounted () {
    this.getServerData();
  }
})

サーバーに解像度値を渡す

次のステップは、ユーザーが選択した解像度をサーバーに渡し、サーバーが正しいデータを返せるようにすることです。

これを行うには、fetch リクエストを POST 型に変更して、リクエストボディの JSON ペイロードの一部として解決値を送信することになります。これを行うには、fetch API に設定オブジェクトを渡します。このオブジェクトは methodPOST で、headers サブオブジェクトが Content-Typeapplication/json で、body プロパティが解決値を含む JSON 文字列を生成するものとなっています。

index.html

async getServerData() {
  const res = await fetch("/data", {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ resolution: this.resolution })
  });
  this.serverData = await res.json();
}

Express で POST を処理する

以前は、Express サーバは /data への GET リクエストを受信すると時系列データを返していました。

今度は POST 動詞を使用して、JSON ペイロードで resolution パラメータを受信できるようにします。これを可能にするために、まず Express サーバーに JSON ミドルウェアを追加して、この機能を持たせることにします。

server.js

app.use(express.json());

次に、GET /dataルートをPOST /dataに変更します。そして、 getLatestRowsreq.body オブジェクトのプロパティである解決パラメータを渡すことができるようになります。これは、GridDBからデータを取得するために作成したメソッドです。

server.js

/* Before */
app.get('/data', async (req, res) => {
  const rows = await container.getLatestRows();
});

/* After */
app.post('/data', async (req, res) => {
  const rows = await container.getLatestRows(req.body.resolution);
  res.send(rows);
});

タイムサンプリングクエリ

前回のチュートリアルでは、サーバーが動作している間、毎秒サーバーメモリのスナップショットを取得するようにしました。

getLatestRows` 関数のコンセプトは、クエリを使用して過去 5 分間のデータを取得し、それを配列として返すというものです。

db.js

const getLatestRows = (container) => async () => {
  try {
    const query = container.query("select * where timestamp > TIMESTAMPADD(MINUTE, NOW(), -5)");
    const rowset = await query.fetch();
    const data = [];
    while (rowset.hasNext()) {
      data.push(rowset.next());
    }
    return data;
  } catch(err) {
    console.log(err);
  }
}

ここで、この関数に resolution パラメータを渡します。これを使うと、GridDBに秒単位ではなく、分単位や時間単位でデータのサンプルを取得するように指示することができます。

そのために、GridDBのTIME_SAMPLING関数を使用します。この関数は、時系列データの平均を指定した間隔で、指定した範囲に渡って返します。

それでは、TIME_SAMPLE をデータベース上の select 文で使ってみましょう。第一引数には、サンプリングしたいデータを指定します。この場合、 freeMemPercentage となります。

第二引数には、範囲の先頭を指定します。3番目の引数は範囲の終端です。この場合、クエリの1時間前から始めたいので、現在の時刻から60分を引いて TIMESTAMPADD(MINUTE, NOW(), -60) とし、現在の時刻で NOW() を終了させます。

第4引数にはサンプリングしたい間隔、第5引数には時間軸を指定します。今回は、1分ごとにサンプリングしています。

select TIME_SAMPLING(freeMemPercentage, TIMESTAMPADD(MINUTE, NOW(), -60), NOW(), 1, MINUTE)

それでは、 getLatestRows 関数を変更して、送信した解決パラメータに依存するクエリを実行するようにしましょう。ここでは switch 文を使用し、上記のようなクエリを使用します。

db.js

const getLatestRows = (container) => async (resolution) => {
  try {
    let tql;
    switch(resolution) {
      case 'hours':
        tql = "select TIME_SAMPLING(freeMemPercentage, TIMESTAMPADD(HOUR, NOW(), -24), NOW(), 1, HOUR)"
        break;
      case 'minutes':
        tql = "select TIME_SAMPLING(freeMemPercentage, TIMESTAMPADD(MINUTE, NOW(), -60), NOW(), 1, MINUTE)"
        break;
      default:
        tql = "select TIME_SAMPLING(freeMemPercentage, TIMESTAMPADD(MINUTE, NOW(), -1), NOW(), 1, SECOND)"
    }
    const query = container.query(tql);
    ...
  }
  ...
}

これで、GridDBは正しい解像度でデータを返してくれるようになりました。では、ブラウザでテストしてみましょう。

時間軸に “seconds” を選択すると、以下のような表示になります。

以下は、”minutes” を選択した場合の表示です。

ブログの内容について疑問や質問がある場合は Q&A サイトである Stack Overflow に質問を投稿しましょう。 GridDB 開発者やエンジニアから速やかな回答が得られるようにするためにも "griddb" タグをつけることをお忘れなく。 https://stackoverflow.com/questions/ask?tags=griddb

Leave a Reply

Your email address will not be published. Required fields are marked *