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

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

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

前提

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

手順

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

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

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

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

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

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 3 4 5 6 7 8 { "names": [ "cpu.guest.percentage", "cpu.idle.percentage", "cpu.iowait.percentage", ...省略 ] }

6. .envファイル作成

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

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

7. index.ts作成

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

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

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

ts
1 const 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