📝

ブラウザゲームの分析結果を Data Firehose に送信して Athena で分析してみた

に公開

ブラウザのじゃんけんゲームのログを送信、分析してみました。

01. IAM ユーザーの作成

  • AdministratorAccess アクセス権限を付与した IAM ユーザーを作成
  • アクセスキーを発行

02. S3 バケットの作成

  • デフォルト設定で作成

03. Data Firehose ストリームの作成

  • ソース: Direct PUT
  • 送信先: Amazon S3
  • Firehose ストリーム名: 任意
  • 送信先の S3 バケット: 02 で作成した S3 バケット
  • S3 バケットプレフィックス: year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/hour=!{timestamp:HH}/
  • S3 バケットエラー出力プレフィックス: error/!{firehose:error-output-type}/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/
  • バッファサイズ: 1 MiB
  • バッファ間隔: 60 秒

04. じゃんけんゲーム作成

以下のコードで index.html を作成します。

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>じゃんけんゲーム</title>
    <style>
        body { font-family: Arial, sans-serif; text-align: center; padding: 50px; }
        .choice { font-size: 50px; margin: 20px; cursor: pointer; padding: 20px; border: 2px solid #ccc; display: inline-block; }
        .choice:hover { background-color: #f0f0f0; }
        .result { font-size: 24px; margin: 30px; }
        .score { font-size: 18px; margin: 20px; }
        .config { background: #f5f5f5; padding: 20px; margin: 20px; border-radius: 5px; }
        .config input { margin: 5px; padding: 5px; width: 300px; }
        .config button { padding: 10px 20px; margin: 10px; }
    </style>
</head>
<body>
    <h1>じゃんけんゲーム</h1>
    
    <!-- AWS設定セクション -->
    <div class="config">
        <h3>AWS設定</h3>
        <input type="text" id="accessKey" placeholder="AWS Access Key ID">
        <input type="password" id="secretKey" placeholder="AWS Secret Access Key">
        <button onclick="configureAWS()">AWS設定を保存</button>
        <p id="awsStatus">AWS未設定</p>
    </div>
    
    <div class="score">
        <p>勝ち: <span id="wins">0</span> | 負け: <span id="losses">0</span> | あいこ: <span id="draws">0</span></p>
    </div>
    
    <div>
        <div class="choice" onclick="playGame('rock')"><br>グー</div>
        <div class="choice" onclick="playGame('paper')"><br>パー</div>
        <div class="choice" onclick="playGame('scissors')">✌️<br>チョキ</div>
    </div>
    
    <div class="result" id="result"></div>
    
    <!-- AWS SDK -->
    <script src="https://sdk.amazonaws.com/js/aws-sdk-2.1563.0.min.js"></script>
    
    <script>
        let wins = 0, losses = 0, draws = 0;
        let firehose = null;
        let gameSessionId = 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
        
        function configureAWS() {
            const accessKey = document.getElementById('accessKey').value;
            const secretKey = document.getElementById('secretKey').value;
            
            if (!accessKey || !secretKey) {
                alert('Access KeyとSecret Keyを入力してください');
                return;
            }
            
            AWS.config.update({
                accessKeyId: accessKey,
                secretAccessKey: secretKey,
                region: 'ap-northeast-1'
            });
            
            firehose = new AWS.Firehose();
            document.getElementById('awsStatus').textContent = 'AWS設定完了';
            document.getElementById('awsStatus').style.color = 'green';
        }
        
        function playGame(playerChoice) {
            const choices = ['rock', 'paper', 'scissors'];
            const computerChoice = choices[Math.floor(Math.random() * 3)];
            
            let result;
            if (playerChoice === computerChoice) {
                result = 'draw';
                draws++;
            } else if (
                (playerChoice === 'rock' && computerChoice === 'scissors') ||
                (playerChoice === 'paper' && computerChoice === 'rock') ||
                (playerChoice === 'scissors' && computerChoice === 'paper')
            ) {
                result = 'win';
                wins++;
            } else {
                result = 'lose';
                losses++;
            }
            
            updateDisplay(playerChoice, computerChoice, result);
            updateScore();
            
            // ログをFirehoseに送信
            sendLogToFirehose(playerChoice, computerChoice, result);
        }
        
        function sendLogToFirehose(playerChoice, computerChoice, result) {
            if (!firehose) {
                console.log('AWS未設定のため、ローカルログのみ:', {
                    timestamp: new Date().toISOString(),
                    sessionId: gameSessionId,
                    playerChoice: playerChoice,
                    computerChoice: computerChoice,
                    result: result
                });
                return;
            }
            
            const logData = {
                timestamp: new Date().toISOString(),
                sessionId: gameSessionId,
                playerChoice: playerChoice,
                computerChoice: computerChoice,
                result: result,
                userAgent: navigator.userAgent,
                totalGames: wins + losses + draws
            };
            
            const params = {
                DeliveryStreamName: '<your-stream-name>',
                Record: {
                    Data: JSON.stringify(logData) + '\n'
                }
            };
            
            firehose.putRecord(params, function(err, data) {
                if (err) {
                    console.log('Firehose送信エラー:', err);
                } else {
                    console.log('Firehose送信成功:', data);
                }
            });
        }
        
        function updateDisplay(player, computer, result) {
            const choiceEmoji = {rock: '✊', paper: '✋', scissors: '✌️'};
            const resultText = {win: '勝ち!', lose: '負け...', draw: 'あいこ'};
            
            document.getElementById('result').innerHTML = 
                `あなた: ${choiceEmoji[player]} vs コンピュータ: ${choiceEmoji[computer]}<br>${resultText[result]}`;
        }
        
        function updateScore() {
            document.getElementById('wins').textContent = wins;
            document.getElementById('losses').textContent = losses;
            document.getElementById('draws').textContent = draws;
        }
    </script>
</body>
</html>
  • DeliveryStreamName: '<your-stream-name>' を 03 で作成したストリーム名に置換します。
  • ブラウザで html を開き、AWS 認証情報に 01 で発行した認証情報を入力して保存します

05. Data Firehose へのデータ送信

  • ブラウザでゲームを実行し、コンソールログに「Firehose送信成功」が表示されることを確認
  • 数分後、02 で作成した S3 バケットにデータが保存されていることを確認

06. Athena の初期設定

  • 初めて利用する場合、クエリの結果の場所場所となる S3 バケットを指定
    • 今回は 02 の S3 バケットに「athena-results」というフォルダを作成して保存場所に指定

07. Athena のテーブル作成

以下のクエリでテーブルを作成します。
S3 バケット名は 02 で作成したバケット名に置換してください。

CREATE EXTERNAL TABLE janken_game_logs (
  log_data string
)
PARTITIONED BY (
  year string,
  month string,
  day string,
  hour string
)
STORED AS TEXTFILE
LOCATION 's3://[バケット名]/'
TBLPROPERTIES ('has_encrypted_data'='false');

以下のクエリでパーティション追加します。
S3 バケット名は 02 で作成したバケット名に置換してください。
year, month, hours は S3 バケットに作成されたプレフィックスに合わせてください。

ALTER TABLE janken_game_logs ADD PARTITION (
  year='YYYY',
  month='MM', 
  day='DD',
  hour='HH'
) LOCATION 's3://[バケット名]/year=YYYY/month=MM/day=DD/hour=HH/';

08. データ分析

以下のクエリを実行し、ゲームの結果が表示されれば OK です。

SELECT 
    json_extract_scalar(log_data, '$.timestamp') as timestamp,
    json_extract_scalar(log_data, '$.playerChoice') as playerChoice,
    json_extract_scalar(log_data, '$.computerChoice') as computerChoice,
    json_extract_scalar(log_data, '$.result') as result,
    json_extract_scalar(log_data, '$.sessionId') as sessionId
FROM janken_game_logs 
LIMIT 5;

以下のクエリでプレイヤーの選択傾向を分析できます。

SELECT 
    json_extract_scalar(log_data, '$.playerChoice') as playerChoice,
    COUNT(*) as count,
    ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER(), 2) as percentage
FROM janken_game_logs 
GROUP BY json_extract_scalar(log_data, '$.playerChoice')
ORDER BY count DESC;

以下のクエリで勝敗結果を分析できます。

SELECT 
    json_extract_scalar(log_data, '$.result') as result,
    COUNT(*) as count,
    ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER(), 2) as percentage
FROM janken_game_logs 
GROUP BY json_extract_scalar(log_data, '$.result')
ORDER BY count DESC;

以下のクエリで選択肢別勝率を分析できます。

SELECT 
    json_extract_scalar(log_data, '$.playerChoice') as playerChoice,
    COUNT(*) as total_games,
    SUM(CASE WHEN json_extract_scalar(log_data, '$.result') = 'win' THEN 1 ELSE 0 END) as wins,
    ROUND(
        SUM(CASE WHEN json_extract_scalar(log_data, '$.result') = 'win' THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 
        2
    ) as win_rate_percent
FROM janken_game_logs 
GROUP BY json_extract_scalar(log_data, '$.playerChoice')
ORDER BY win_rate_percent DESC;

まとめ

今回はブラウザゲームの分析結果を Data Firehose に送信して Athena で分析してみました。
どなたかの参考になれば幸いです。

Discussion