Amazon CloudWatch RUMのデータをQuicksightで分析してみた

「Japan AWS Top Engineers Advent Calendar 2024」(11日目)の記事です。試してみたかったことがあったので、その結果を記事にしてみました。

# はじめに
クラウドサービスを展開している企業にとっては、ユーザエクスペリエンスの向上が重要な課題としてあげられます。弊社のサービスでもユーザエクスペリエンスの向上を目的として、Amazon CloudWatch RUMを導入しています。Amazon CloudWatch RUMを使うことで、簡単にデータは収集できるのですが、用意されているダッシュボードだけでは詳細な分析ができないため、Amazon CloudWatch RUMで収集されたデータをQuicksightに連携し、分析できるかを試しました。

Amazon CloudWatch RUM
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623058/9f5e0c02-ab9b-8090-5fe2-a81d40e261af.png)


# 構成
今回は以下の構成で試してみました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623058/ae63bf8c-d0e5-a8e7-d1d5-2a2f597def4f.png)

まず、CloudWatch RUMで収集したデータをCloudWatch Logsに連携します。
CloudWatch LogsのデータをAthenaを使用してQuicksightに連携します。

:::note
CloudWath RUMやQuickSightの開始方法については本記事では触れません。
各種サービスを利用していることを前提としています。
:::

# CloudWatch Logsへのデータ連携
CloudWath RUMが保持できる期間は30日です。
30日以上データを保持したいときはCloudWatch Logsに保存できます。
この機能を利用して、CloudWatch Logsに収集されるデータを連携します。
CloudWatch Logsにデータを連携していない場合はアプリケーションモニターの編集画面から変更できます。
「データストレージ」でCloudWatch Logsに保存することを許可します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623058/d5bf55a1-f399-4c43-f2bc-0ca93900a0fb.png)

許可することで以下のロググループに収集されたデータが保存されます。
/aws/vendedlogs/RUMService_アプリケーションモニター名+アプリケーションモニターＩＤ(最初の8文字） 

# Athenaでのデータ収集
CloudWatch LogsからAmazon Data Firehoseなどを使ってS3にデータを連携し、S3をもとにAthenaでデータを収集することも考えられます。ですが、今回はより簡単にするため、AthenaのFederated QueryでCloudWatch Logsのデータにアクセスしました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623058/a3c90c26-5eed-a073-e03c-12bf6c8f635c.png)

まず、AthenaのData sources and catalogsから「データソースの作成」を行います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623058/a6675054-293a-f2c1-6c51-f9b4c3e2431d.png)

「データソースを選択」では「Amazon CloudWatch Logs」を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623058/0f21494e-ccc2-0384-d1f2-54e4b9dde92a.png)

データソース名には任意の名称を設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623058/b4797025-c3f3-5399-61ed-131936f0db45.png)

データソース名はAthenaを使用するときの「データソース」として管理されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623058/fb5952dd-668e-66b2-07ae-6906a93a93cf.png)

「接続の詳細」ではデータソースに接続するためのLambda関数を指定します。
「Lambda関数の作成」から、新規にLambda関数を作成することもできます。
今回は新規に作成します。
「Lambda関数の作成」を押下するとLambdaアプリケーションの「設定とデプロイ」画面に遷移します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623058/5747c5b2-7168-eb2e-274a-75be020e4043.png)

Lambdaアプリケーションの「設定とデプロイ」画面では以下を設定します。
アプリケーション名：任意の名称
SpillBucket：Lambda 関数のレスポンスサイズ制限を超えるデータを保存するための、Amazon S3バケット
AthenaCatalogName：Lambda 関数の名前

上記では必須項目のみ記載しています。
詳細は以下を参考にしてください。
[Athena コンソールを使用してデータソースに接続する](https://docs.aws.amazon.com/ja_jp/athena/latest/ug/connect-to-a-data-source-lambda-deploying.html)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623058/911c6172-ea2f-4f3a-1683-b4865ca4eff5.png)

「このアプリがカスタム IAM ロールとリソースポリシーを作成することを承認します。」をオンにしてデプロイします。
デプロイすることでLamdba関数が作成されるので、作成されたLambda関数の**ARN**を「接続の詳細」の「Lambda 関数」に指定します。これで、データソースが作成できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623058/db90c175-1fdd-700b-40c5-921707cd9846.png)

Athenaのクエリエディタでは以下を指定することで、CloudWatch Logsのデータを検索できます。簡単ですね。
データベース：ロググループ
テーブル：all_log_streams

ログストリームごとにテーブルが作成されます。
また、all_log_streamsテーブルを指定することで、すべてのログストリームを対象にして検索できます。
どのログストリームに必要なログがあるかわからないので、all_log_streamsテーブルを使うのがよいです。

# QuickSightへの取込
QuickSightでAthenaをデータソースとしてデータセットを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623058/11c12d06-1182-c87b-57db-9fff9b81a76c.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623058/50a10fa1-5992-ee1b-7e05-527110d68b15.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623058/3767b174-49b4-563f-1572-9d52a5535641.png)

データソースを作成しようとしたところ上記のエラーが発生しました。「aws-quicksight-service-role-v0」ロールにLambdaの実行権限が不足していることが原因のようです。
「aws-quicksight-service-role-v0」にLambdaの実行権限を与えるとデータセットを作成できました。
all_log_streamsテーブルをもとにデータセットを作成しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623058/442462c7-d12c-1ccc-5684-3089ff60e3de.png)

データセット（all_log_streamsテーブル）には以下の項目が存在しています。
| 項目 | 型 |
|:-:|:-:|
| time | integer |
| message | 文字列  |
| log_stream | 文字列 |

time項目はUNIX時間が設定されているので、integer型で判断されています。データセットの編集画面で日付型に変更しておきます。
※日付型にしてもUTC時間で表記されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623058/8547f707-550b-e038-9ae6-b922f00920b6.png)

messageにCloudWatch RUMのRaw eventデータが格納されているので、計算フィールドで各項目を取得できるようにしていきます。

<details><summary>Raw eventデータ例</summary>

```
{
  "event_timestamp": 1733399992000,
  "event_type": "com.amazon.rum.performance_resource_event",
  "event_id": "04abc91b-428b-42b2-ba33-f52c926c0c38",
  "event_version": "1.0.0",
  "log_stream": "2024-12-5T20",
  "application_id": "fd5a557f-d844-453d-ab4a-f0a65ad69ad6",
  "application_version": "1.0.0",
  "metadata": {
    "version": "1.0.0",
    "browserLanguage": "ja",
    "browserName": "Edge",
    "browserVersion": "131.0.0.0",
    "osName": "Windows",
    "osVersion": "10",
    "deviceType": "desktop",
    "platformType": "web",
    "pageId": "/aaaa/bbbb",
    "title": "wwwwww",
    "domain": "xxxxxx.com",
    "aws:client": "arw-script",
    "aws:clientVersion": "1.14.0",
    "countryCode": "JP",
    "subdivisionCode": "13"
  },
  "user_details": {
    "userId": "00000000-0000-0000-0000-000000000000",
    "sessionId": "2a2d9483-6750-42ab-bc53-2860635eb4a6"
  },
  "event_details": {
    "version": "1.0.0",
    "targetUrl": "https://xxxxxx.com/yyyyy/zzzzz.js",
    "initiatorType": "script",
    "duration": 46.60000038146973,
    "transferSize": 0,
    "fileType": "script"
  }
}
```
</details>

CloudWatch RUMで収集されるRaw eventデータについては以下を参照してください。
[CloudWatch RUM ウェブクライアントによって収集される情報](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/monitoring/CloudWatch-RUM-datacollected.html)

:::note warn
データセットをSPICEで管理する場合、parseJsonはデータセット編集時の計算フィールドで使用できます。分析作成時の計算フィールドでは使用できません。
[parseJson](https://docs.aws.amazon.com/quicksight/latest/user/parseJson-function.html)
:::

設定例
```計算フィールド:calcfield_event_timestamp
addDateTime(9,'HH',epochDate(parseInt((parseJson({message},"$.event_timestamp")))))
```
event_timestampはUNIX時間で設定されています。そのため、epochDateで日付型に変換しています。また、日本時間で参照したいため、addDateTimeで調整しています。

```計算フィールド:calcfield_event_type
parseJson({message},"$.event_type")
```

```計算フィールド:calcfield_metadata-browserName
parseJson({message},"$.metadata.browserName")
```

```計算フィールド:calcfield_metadata-pageId
parseJson({message},"$.metadata.pageId")
```

```計算フィールド:calcfield_event_details-targetUrl
parseJson({message},"$.event_details.targetUrl")
```

```計算フィールド:calcfield_event_details-duration
parseInt(parseJson({message},"$.event_details.duration"))
```
event_details.durationにはページのロード時間がミリ秒で設定されています。parseJsonの結果は文字列として扱われるので、parseIntで整数として扱うことにしています。


計算フィールドを定義することで、下記のようにmessage項目にjson形式で格納されている値を抽出できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623058/ca6338db-6cc6-80b5-004f-e765db7981c4.png)

このデータセットをもとに分析を作成します。
今回はロード時間が長いページ処理を表示するようにしてみました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3623058/7f5787fb-8686-4a5c-01f4-56378955c7ab.png)
※calcfield_event_type（イベントタイプ）の値が「com.amazon.rum.performance_resource_event」のデータだけ抽出されるようにフィルタしています。

Raw eventデータにどのような値が設定されるかの確認は必要ですが、自由な分析を行うことができそうです。

# まとめ
Amazon CloudWatch RUMで収集されたデータをQuickSightで分析できるようになりました。自社のサービスにあった分析を行うことでボトルネックとなっている個所を改善し、ユーザエクスペリエンスの向上につなげていきたいです。