Mackerel上にあるメトリックデータをcsvでダウンロード保存してみたときの備忘録

Mackerel上にあるメトリックデータをcsvでダウンロード保存してみたときの備忘録

表題にある通りMackerelにあるメトリクスのデータをcsvでダウンロードして使いたかったのでやってみたときの備忘録です。

前提

  • Node.js(TypeScript)を使ってやります。
  • Node.jsのインストール手順は省略します。

手順

1. Node.jsのプロジェクト作成

適当なフォルダ(今回はmackerel-csv-converterとします)を作って以下の内容のpackage.jsonファイルを作成します。

json
1{ 2 "name": "mackerel-csv-converter", 3 "version": "1.0.0", 4 "description": "Convert Mackerel API response to CSV", 5 "main": "dist/index.js", 6 "scripts": { 7 "build": "tsc", 8 "start": "node dist/index.js" 9 }, 10 "dependencies": { 11 "axios": "^1.6.7", 12 "csv-writer": "^1.6.0", 13 "dotenv": "^16.4.1" 14 }, 15 "devDependencies": { 16 "@types/node": "^20.11.16", 17 "typescript": "^5.3.3" 18 } 19}

同じディレクトリにtsconfig.jsonファイルも作成して以下の内容をコピペします。

json
1{ 2 "compilerOptions": { 3 "target": "es2020", 4 "module": "commonjs", 5 "outDir": "./dist", 6 "rootDir": "./src", 7 "strict": true, 8 "esModuleInterop": true, 9 "skipLibCheck": true, 10 "forceConsistentCasingInFileNames": true 11 }, 12 "include": ["src/**/*"], 13 "exclude": ["node_modules"] 14}

2. npm install

package.jsonと同じディレクトリでnpm installを実行。

3. Mackerelの管理画面で「APIキー」と「ホストID」を確認して控えておく

管理画面にアクセスし左上の「オーガニゼーション詳細ページへ」をクリック。
2025-02-24_1.webp
「APIキー」タブがあるのでそこでAPIキーをコピーして控えておく。(なければ作成してください)
2025-02-24_2.webp

左ペインの「ホスト」からメトリクスをダウンロードしたいホストのIDを確認して控えておく。
2025-02-24_3.webp
2025-02-24_4.webp

4. ホストにあるメトリクス一覧をcurlで取得する

控えておいたAPIキーとホストIDで以下のコマンドを実行して、取得可能なメトリクス名一覧を取得します。

curl -H "X-Api-Key: <APIキー>" "https://api.mackerelio.com/api/v0/hosts/<ホストID>/metric-names"

実行するとjson形式でレスポンスが来るはずです。

5. metric-names.json作成

package.jsonと同じディレクトリにmetric-names.jsonというファイルを作成して、先程取得したレスポンスをコピペして保存します。

json
1{ 2 "names": [ 3 "cpu.guest.percentage", 4 "cpu.idle.percentage", 5 "cpu.iowait.percentage", 6 ...省略 7 ] 8}

6. .envファイル作成

package.jsonと同じディレクトリに.envファイルを作成し以下のようにAPIキーとホストIDを書いておきます。

MACKEREL_API_KEY=<APIキー>
MACKEREL_HOST_ID=<ホストID> 

7. index.ts作成

package.jsonと同じディレクトリにsrcフォルダを作成し、その中にindex.tsファイルを作成し、以下のコードをコピペして保存します。

ts
1import axios from 'axios'; 2import { createObjectCsvWriter } from 'csv-writer'; 3import dotenv from 'dotenv'; 4import path from 'path'; 5 6dotenv.config(); 7 8interface MetricValue { 9 time: number; 10 value: number; 11} 12 13interface MetricData { 14 metrics: MetricValue[]; 15 name: string; 16} 17 18interface GroupedMetricData { 19 [timestamp: number]: { 20 time: number; 21 [metric: string]: number | null; 22 }; 23} 24 25const MACKEREL_API_KEY = process.env.MACKEREL_API_KEY; 26const MACKEREL_HOST_ID = process.env.MACKEREL_HOST_ID; 27 28if (!MACKEREL_API_KEY || !MACKEREL_HOST_ID) { 29 console.error('環境変数 MACKEREL_API_KEY と MACKEREL_HOST_ID を設定してください。'); 30 process.exit(1); 31} 32 33const fetchMetrics = async (metricName: string, from: number, to: number): Promise<MetricData> => { 34 const url = `https://api.mackerelio.com/api/v0/hosts/${MACKEREL_HOST_ID}/metrics`; 35 const response = await axios.get(url, { 36 headers: { 37 'X-Api-Key': MACKEREL_API_KEY, 38 }, 39 params: { 40 name: metricName, 41 from, 42 to, 43 }, 44 }); 45 46 return { 47 name: metricName, 48 metrics: response.data.metrics, 49 }; 50}; 51 52const convertToCsv = async (groupName: string, data: GroupedMetricData, outputDir: string) => { 53 // ヘッダーを動的に生成 54 const firstRecord = Object.values(data)[0]; 55 const metrics = Object.keys(firstRecord).filter(key => key !== 'time'); 56 const header = [ 57 { id: 'time_jst', title: 'TIMESTAMP_JST' }, 58 ...metrics.map(metric => { 59 // ドットが含まれない場合は元の名前をそのまま使用 60 if (!metric.includes('.')) { 61 return { 62 id: metric, 63 title: metric, 64 }; 65 } 66 // ドットが含まれる場合はプレフィックスを除去 67 return { 68 id: metric, 69 title: metric.split('.').slice(1).join('.'), 70 }; 71 }), 72 ]; 73 74 const csvWriter = createObjectCsvWriter({ 75 path: path.join(outputDir, `${groupName}.csv`), 76 header, 77 }); 78 79 // 時刻でソートされた配列に変換 80 const records = Object.entries(data) 81 .sort(([timeA], [timeB]) => parseInt(timeA) - parseInt(timeB)) 82 .map(([_, record]) => ({ 83 time_jst: new Date(record.time * 1000 + (9 * 60 * 60 * 1000)).toISOString(), 84 ...metrics.reduce((acc, metric) => ({ 85 ...acc, 86 [metric]: record[metric], 87 }), {}), 88 })); 89 90 await csvWriter.writeRecords(records); 91 console.log(`${groupName}.csv を作成しました。`); 92}; 93 94// データ取得期間の定数を定義(メモ:最小は2024-09-24) 95const START_DATE = '2025-01-01T00:00:00+09:00'; // 開始日時をJST(日本時間)で指定 96const HOURS_PER_REQUEST = 20; // 1リクエストあたりの時間範囲(時間) 97const SECONDS_PER_HOUR = 3600; 98 99// 日付文字列からUNIXタイムスタンプを取得する関数(JSTを考慮) 100const getUnixTimestamp = (dateStr: string): number => { 101 // タイムゾーン付きの日付文字列をパースしてUNIXタイムスタンプを取得 102 return Math.floor(new Date(dateStr).getTime() / 1000); 103}; 104 105// 指定された期間のメトリクスを取得する関数 106const fetchMetricsForPeriod = async (metricName: string, from: number, to: number): Promise<MetricValue[]> => { 107 const timeRangeInSeconds = to - from; 108 const timeRangeInHours = timeRangeInSeconds / SECONDS_PER_HOUR; 109 const metrics: MetricValue[] = []; 110 const totalRequests = Math.ceil(timeRangeInHours / HOURS_PER_REQUEST); 111 let currentRequest = 1; 112 113 console.log(`${metricName}: ${totalRequests}回のリクエストに分割して取得します...`); 114 115 // 20時間ごとにリクエストを分割 116 for (let currentFrom = from; currentFrom < to; currentFrom += HOURS_PER_REQUEST * SECONDS_PER_HOUR) { 117 const currentTo = Math.min(currentFrom + HOURS_PER_REQUEST * SECONDS_PER_HOUR, to); 118 console.log(`${metricName}: ${currentRequest}/${totalRequests} リクエスト実行中...`); 119 const data = await fetchMetrics(metricName, currentFrom, currentTo); 120 metrics.push(...data.metrics); 121 currentRequest++; 122 } 123 124 console.log(`${metricName}: 全${metrics.length}件のデータを取得しました。`); 125 return metrics; 126}; 127 128const main = async () => { 129 try { 130 // 現在時刻(JST)から5分前までのデータを取得 131 const FIVE_MINUTES = 5 * 60; // 5分を秒に変換 132 const now = new Date(); 133 // JSTでの現在時刻を取得 134 const to = Math.floor(now.getTime() / 1000) - FIVE_MINUTES; 135 const from = getUnixTimestamp(START_DATE); 136 137 // メトリクス名のリストを読み込み 138 const metricNames = require('../metric-names.json').names; 139 140 // メトリクス名をグループ化 141 const groupedMetrics: { [prefix: string]: string[] } = {}; 142 metricNames.forEach((name: string) => { 143 let groupKey: string; 144 const parts = name.split('.'); 145 146 if (parts[0] === 'custom') { 147 // custom.xxx.yyy.zzzの形式の場合、xxx-yyyをグループキーとして使用 148 groupKey = `${parts[1]}-${parts[2]}`; 149 } else if (parts[0].startsWith('loadavg')) { 150 // loadavgから始まるメトリクスは全てloadavgグループに 151 groupKey = 'loadavg'; 152 } else { 153 // それ以外は最初の部分でグルーピング 154 groupKey = parts[0]; 155 } 156 157 if (!groupedMetrics[groupKey]) { 158 groupedMetrics[groupKey] = []; 159 } 160 groupedMetrics[groupKey].push(name); 161 }); 162 163 // 出力ディレクトリを作成 164 const outputDir = path.join(__dirname, '../output'); 165 require('fs').mkdirSync(outputDir, { recursive: true }); 166 167 // グループごとにデータを取得してCSVに変換 168 for (const [groupName, metrics] of Object.entries(groupedMetrics)) { 169 try { 170 console.log(`${groupName}グループのデータを取得中...`); 171 const groupData: GroupedMetricData = {}; 172 173 // グループ内の各メトリクスのデータを取得 174 for (const metricName of metrics) { 175 const metricData = await fetchMetricsForPeriod(metricName, from, to); 176 177 // データをグループ化して整理 178 metricData.forEach(metric => { 179 if (!groupData[metric.time]) { 180 const initialMetrics = metrics.reduce<{ [key: string]: number | null }>((acc, name) => { 181 acc[name] = null; 182 return acc; 183 }, {}); 184 185 groupData[metric.time] = { 186 time: metric.time, 187 ...initialMetrics, 188 }; 189 } 190 groupData[metric.time][metricName] = metric.value; 191 }); 192 } 193 194 await convertToCsv(groupName, groupData, outputDir); 195 } catch (error) { 196 console.error(`${groupName}グループの処理中にエラーが発生しました:`, error); 197 } 198 } 199 200 console.log('すべての処理が完了しました。'); 201 } catch (error) { 202 console.error('エラーが発生しました:', error); 203 process.exit(1); 204 } 205}; 206 207main();

コードのconst START_DATEの中身は、ダウンロードしたいメトリクスの範囲の開始日時を任意で変更してください。

ts
1const START_DATE = '2025-01-01T00:00:00+09:00';

※ END_DATEに該当するものはなく、現在時刻になるようになっています。

他はとくに修正しなくてokです。
HOURS_PER_REQUESTの値は大きくするとapiから返却されるデータの粒度が変わるので注意してください。

8. ビルドする

ターミナルでnpm run buildを実行します。
distというディレクトリが作成されます。

9. 実行する

npm run startを実行します。
outputディレクトリが作成され、その中にメトリクスデータをダウンロードして作られたcsvデータが出力されます。

以上。

Share this post