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

表題にある通りMackerelにあるメトリクスのデータをcsvでダウンロードして使いたかったのでやってみたときの備忘録です。
前提
- Node.js(TypeScript)を使ってやります。
- Node.jsのインストール手順は省略します。
手順
1. Node.jsのプロジェクト作成
適当なフォルダ(今回はmackerel-csv-converter
とします)を作って以下の内容のpackage.jsonファイルを作成します。
{
"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
ファイルも作成して以下の内容をコピペします。
{
"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」を確認して控えておく
管理画面にアクセスし左上の「オーガニゼーション詳細ページへ」をクリック。
「APIキー」タブがあるのでそこでAPIキーをコピーして控えておく。(なければ作成してください)
左ペインの「ホスト」からメトリクスをダウンロードしたいホストのIDを確認して控えておく。
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
というファイルを作成して、先程取得したレスポンスをコピペして保存します。
{
"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
ファイルを作成し、以下のコードをコピペして保存します。
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
の中身は、ダウンロードしたいメトリクスの範囲の開始日時を任意で変更してください。
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