New Relic APM入門 第5.3章 - コードレベルパフォーマンス分析
📖 ナビゲーション
メイン: 第5章 New Relic APM(高度化)
前セクション: 第5.2章 分散トレーシング
次セクション: 第6章 New Relic Log Management
💡 この章で学べること
従来のAPM監視は「どのサービスが遅いか」を教えてくれますが、「なぜ遅いのか」「どのコードが原因か」までは分からません。New RelicのCode-Level Metricsとプロファイリング機能を使えば、関数レベルまで詳細に分析し、パフォーマンス問題の真の原因を特定できます。
学習目標
- [ ] Code-Level Metrics:関数・メソッドレベルでの詳細パフォーマンス監視
- [ ] プロファイリング:CPU使用率とメモリ使用量の詳細分析
- [ ] メモリリーク検出:メモリ使用パターンの異常発見と対処法
- [ ] ホットスポット特定:パフォーマンス問題の根本原因特定
- [ ] 最適化戦略:コードレベルでの具体的改善手法
- [ ] 継続的監視:パフォーマンス劣化の予防的検出システム
5.3.1 Code-Level Metricsの基本概念
コードレベル監視とは
Code-Level Metricsは、アプリケーション内の個別の関数やメソッドの実行時間、呼び出し回数、CPU使用率を追跡する機能です。これにより、「重いAPI」だけでなく、**「その中のどの処理が重いのか」**まで正確に特定できます。
従来監視 vs コードレベル監視
# 監視粒度の比較
Traditional_APM_Monitoring:
granularity: "サービス・エンドポイント単位"
example:
- "POST /checkout が遅い (3.2秒)"
- "在庫サービスに問題あり"
limitation: "どの処理が重いかは不明"
Code_Level_Monitoring:
granularity: "関数・メソッド単位"
example:
- "POST /checkout が遅い (3.2秒)"
- "└ inventory.checkStock() が遅い (2.8秒)"
- " └ database.executeQuery() が遅い (2.5秒)"
- " └ 'SELECT * FROM inventory WHERE...' が遅い"
advantage: "ピンポイントで改善箇所を特定"
Code-Level Metricsが監視する内容
// New Relic Code-Level Metricsの監視対象例
const codeMetricsExample = {
// 関数レベルメトリクス
function_metrics: {
function_name: "calculateShippingCost",
file_path: "/src/services/shipping.js",
line_number: 142,
metrics: {
call_count: 1247, // 呼び出し回数
total_time: "45.2s", // 累計実行時間
average_time: "36.2ms", // 平均実行時間
max_time: "340ms", // 最大実行時間
cpu_time: "42.1s", // CPU使用時間
wall_clock_time: "45.2s" // 実時間
},
performance_score: 0.73 // パフォーマンススコア
},
// メソッドチェーン分析
method_chain: [
{
method: "OrderService.processOrder",
duration: "1.8s",
self_time: "50ms", // 自身の処理時間
children_time: "1.75s" // 子メソッドの処理時間
},
{
method: "├─ PaymentService.processPayment",
duration: "800ms",
self_time: "20ms",
children_time: "780ms"
},
{
method: "│ └─ StripeAPI.createCharge",
duration: "780ms",
self_time: "780ms", // 外部API呼び出し
children_time: "0ms"
},
{
method: "├─ InventoryService.reserveItems",
duration: "950ms",
self_time: "100ms",
children_time: "850ms"
},
{
method: "│ └─ DatabaseService.updateInventory",
duration: "850ms", // ← ボトルネック特定!
self_time: "850ms",
query: "UPDATE inventory SET quantity = quantity - ? WHERE..."
}
]
};
New Relic でのCode-Level Metrics設定
Node.js実装
// newrelic.js 設定(Code-Level Metrics有効化)
'use strict';
exports.config = {
app_name: ['ECommerce-API'],
license_key: process.env.NEW_RELIC_LICENSE_KEY,
// Code-Level Metrics 有効化
code_level_metrics: {
enabled: true
},
// 詳細トランザクション トレーシング
transaction_tracer: {
enabled: true,
transaction_threshold: 0.1, // 100ms以上の関数を詳細記録
record_sql: 'raw',
explain_enabled: true,
explain_threshold: 0.5,
stack_trace_threshold: 0.5 // 500ms以上でスタックトレース記録
},
// 関数レベル監視設定
attributes: {
enabled: true,
include: [
'request.*',
'response.*',
'custom.*'
]
}
};
// app.js - 詳細な関数監視
require('newrelic');
const express = require('express');
const newrelic = require('newrelic');
const app = express();
// 在庫管理サービス(詳細監視対象)
class InventoryService {
// 自動的にCode-Level Metricsで監視される
async checkStock(items) {
// 個別関数の詳細監視
const validationResult = await this.validateItems(items);
const availability = await this.checkAvailability(validationResult);
const reservationResult = await this.createReservation(availability);
return reservationResult;
}
// @newrelic.agent.function_trace() デコレータで手動監視
@newrelic.agent.function_trace()
async validateItems(items) {
// バリデーション処理
const startTime = Date.now();
try {
const validItems = [];
for (const item of items) {
// 各アイテムの詳細検証(監視対象)
const validation = await this.validateSingleItem(item);
if (validation.valid) {
validItems.push(validation.item);
} else {
// 検証失敗の詳細記録
newrelic.addCustomAttribute(`validation.failed.${item.id}`, validation.reason);
}
}
// 成功メトリクス
const duration = Date.now() - startTime;
newrelic.recordMetric('Custom/Inventory/Validation/Duration', duration);
newrelic.recordMetric('Custom/Inventory/Validation/ItemCount', items.length);
return validItems;
} catch (error) {
// エラー詳細(関数レベル)
newrelic.noticeError(error, {
'function': 'validateItems',
'items_count': items.length,
'processing_time': Date.now() - startTime
});
throw error;
}
}
// CPU集約的処理の監視例
async checkAvailability(validItems) {
return await newrelic.startBackgroundTransaction('availability_check', async () => {
const availabilityChecks = [];
// 並列処理での詳細監視
for (const item of validItems) {
const checkPromise = this.checkSingleItemAvailability(item);
availabilityChecks.push(checkPromise);
}
// Promise.all のパフォーマンス監視
const startTime = Date.now();
const results = await Promise.all(availabilityChecks);
const parallelProcessingTime = Date.now() - startTime;
// 並列処理効率メトリクス
newrelic.recordMetric('Custom/Inventory/Parallel/Duration', parallelProcessingTime);
newrelic.recordMetric('Custom/Inventory/Parallel/Efficiency',
(validItems.length * 100) / parallelProcessingTime); // items/second * 100
return results;
});
}
// データベース集約処理の詳細監視
async checkSingleItemAvailability(item) {
// データベースクエリの詳細分析
const queryStartTime = Date.now();
try {
// 複雑な在庫チェッククエリ(自動監視)
const query = `
SELECT
i.quantity,
i.reserved,
w.available_quantity,
p.reorder_point
FROM inventory i
JOIN warehouses w ON i.warehouse_id = w.id
JOIN products p ON i.product_id = p.id
WHERE i.product_id = ?
AND i.warehouse_id IN (
SELECT id FROM warehouses
WHERE region = ?
AND status = 'active'
)
`;
const result = await database.query(query, [item.product_id, item.region]);
// クエリパフォーマンス記録
const queryDuration = Date.now() - queryStartTime;
newrelic.recordMetric('Custom/Database/InventoryCheck/Duration', queryDuration);
// 遅いクエリのアラート
if (queryDuration > 200) { // 200ms以上
newrelic.recordMetric('Custom/Database/InventoryCheck/SlowQuery', 1);
newrelic.addCustomAttribute('slow_query.product_id', item.product_id);
newrelic.addCustomAttribute('slow_query.duration', queryDuration);
}
// ビジネスロジックの処理時間監視
const businessLogicStart = Date.now();
const availability = this.calculateAvailability(result[0]);
const businessLogicDuration = Date.now() - businessLogicStart;
newrelic.recordMetric('Custom/BusinessLogic/AvailabilityCalc/Duration', businessLogicDuration);
return availability;
} catch (error) {
const queryDuration = Date.now() - queryStartTime;
newrelic.noticeError(error, {
'function': 'checkSingleItemAvailability',
'product_id': item.product_id,
'query_duration': queryDuration,
'database_error': error.code
});
throw error;
}
}
// メモリ使用量監視
calculateAvailability(inventoryData) {
const memUsageBefore = process.memoryUsage();
try {
// 複雑な在庫計算ロジック
const calculation = {
available: inventoryData.quantity - inventoryData.reserved,
buffer: inventoryData.reorder_point,
warehouse_stock: inventoryData.available_quantity
};
// 在庫状況の詳細判定
let status = 'available';
if (calculation.available <= 0) {
status = 'out_of_stock';
} else if (calculation.available <= calculation.buffer) {
status = 'low_stock';
}
// メモリ使用量の変化監視
const memUsageAfter = process.memoryUsage();
const memoryDelta = {
heapUsed: memUsageAfter.heapUsed - memUsageBefore.heapUsed,
heapTotal: memUsageAfter.heapTotal - memUsageBefore.heapTotal,
external: memUsageAfter.external - memUsageBefore.external
};
// メモリ使用量メトリクス
newrelic.recordMetric('Custom/Memory/AvailabilityCalc/HeapUsed', memoryDelta.heapUsed);
// メモリリークの早期警告
if (memoryDelta.heapUsed > 1024 * 1024) { // 1MB以上の増加
newrelic.recordMetric('Custom/Memory/PotentialLeak', 1);
newrelic.addCustomAttribute('memory.large_allocation', memoryDelta.heapUsed);
}
return {
...calculation,
status: status,
calculated_at: new Date().toISOString()
};
} catch (error) {
newrelic.noticeError(error, {
'function': 'calculateAvailability',
'inventory_data': JSON.stringify(inventoryData)
});
throw error;
}
}
}
Python実装
# Python でのCode-Level Metrics実装
import newrelic.agent
import time
import psutil
import os
from memory_profiler import profile
# newrelic.ini または環境変数で設定
# code_level_metrics.enabled = true
class OrderProcessor:
def __init__(self):
self.processed_orders = 0
@newrelic.agent.function_trace() # 関数レベル監視有効化
def process_order(self, order_data):
"""注文処理メインロジック"""
# ビジネスコンテキスト追加
newrelic.agent.add_custom_attribute('order.id', order_data.get('id'))
newrelic.agent.add_custom_attribute('order.items_count', len(order_data.get('items', [])))
start_time = time.time()
try:
# ステップ1:注文バリデーション(詳細監視)
validation_result = self.validate_order(order_data)
# ステップ2:在庫引当(詳細監視)
inventory_result = self.reserve_inventory(order_data['items'])
# ステップ3:価格計算(詳細監視)
pricing_result = self.calculate_pricing(order_data, inventory_result)
# ステップ4:注文保存(詳細監視)
order_record = self.save_order(order_data, pricing_result)
# 成功メトリクス
processing_time = (time.time() - start_time) * 1000 # ミリ秒
newrelic.agent.record_custom_metric('Custom/Order/ProcessingTime', processing_time)
newrelic.agent.record_custom_metric('Custom/Order/Success', 1)
self.processed_orders += 1
return order_record
except Exception as e:
# エラー詳細(関数コンテキスト付き)
processing_time = (time.time() - start_time) * 1000
newrelic.agent.notice_error(attributes={
'function': 'process_order',
'order_id': order_data.get('id'),
'processing_time': processing_time,
'step_failed': getattr(e, 'step', 'unknown')
})
raise e
@newrelic.agent.function_trace('order_validation')
def validate_order(self, order_data):
"""注文データバリデーション(CPU集約的処理)"""
validation_start = time.time()
# CPUプロファイリング対象処理
errors = []
# 顧客情報検証
customer_validation_time = time.time()
if not self.validate_customer(order_data.get('customer', {})):
errors.append('Invalid customer data')
customer_validation_duration = (time.time() - customer_validation_time) * 1000
# 商品情報検証(重い処理)
items_validation_time = time.time()
for item in order_data.get('items', []):
if not self.validate_item(item):
errors.append(f'Invalid item: {item.get("id")}')
items_validation_duration = (time.time() - items_validation_time) * 1000
# 配送先検証
shipping_validation_time = time.time()
if not self.validate_shipping_address(order_data.get('shipping_address', {})):
errors.append('Invalid shipping address')
shipping_validation_duration = (time.time() - shipping_validation_time) * 1000
# バリデーション詳細メトリクス
newrelic.agent.record_custom_metric('Custom/Validation/Customer/Duration', customer_validation_duration)
newrelic.agent.record_custom_metric('Custom/Validation/Items/Duration', items_validation_duration)
newrelic.agent.record_custom_metric('Custom/Validation/Shipping/Duration', shipping_validation_duration)
total_validation_time = (time.time() - validation_start) * 1000
newrelic.agent.record_custom_metric('Custom/Validation/Total/Duration', total_validation_time)
if errors:
validation_error = ValueError(f'Validation failed: {", ".join(errors)}')
validation_error.step = 'validation'
raise validation_error
return {'valid': True, 'validation_time': total_validation_time}
@newrelic.agent.function_trace('inventory_reservation')
def reserve_inventory(self, items):
"""在庫引当処理(データベース集約的処理)"""
reservation_start = time.time()
reservations = []
# メモリ使用量監視開始
process = psutil.Process(os.getpid())
memory_before = process.memory_info().rss
try:
for item in items:
# データベース処理(自動監視)
item_reservation_start = time.time()
reservation = self.reserve_single_item(item)
reservations.append(reservation)
# アイテム別処理時間
item_duration = (time.time() - item_reservation_start) * 1000
newrelic.agent.record_custom_metric('Custom/Inventory/SingleItem/Duration', item_duration)
# 遅い処理の詳細記録
if item_duration > 100: # 100ms以上
newrelic.agent.add_custom_attribute(f'slow_reservation.item_{item.get("id")}', item_duration)
# メモリ使用量変化
memory_after = process.memory_info().rss
memory_delta = memory_after - memory_before
newrelic.agent.record_custom_metric('Custom/Memory/InventoryReservation', memory_delta)
# メモリリーク検出
if memory_delta > 10 * 1024 * 1024: # 10MB以上増加
newrelic.agent.record_custom_metric('Custom/Memory/PotentialLeak', 1)
newrelic.agent.add_custom_attribute('memory.large_increase', memory_delta)
total_reservation_time = (time.time() - reservation_start) * 1000
newrelic.agent.record_custom_metric('Custom/Inventory/TotalReservation/Duration', total_reservation_time)
return {'reservations': reservations, 'processing_time': total_reservation_time}
except Exception as e:
newrelic.agent.notice_error(attributes={
'function': 'reserve_inventory',
'items_count': len(items),
'processed_items': len(reservations),
'memory_delta': memory_after - memory_before if 'memory_after' in locals() else 0
})
raise e
@profile # memory_profiler による詳細メモリ分析
@newrelic.agent.function_trace('pricing_calculation')
def calculate_pricing(self, order_data, inventory_result):
"""価格計算(メモリ集約的処理)"""
# 大量のデータ処理によるメモリ使用パターン分析
pricing_data = {}
large_calculation_cache = {} # メモリ使用量監視対象
try:
# 基本価格計算
base_prices = []
for item in order_data['items']:
# 価格履歴データの大量読み込み(メモリ使用増加要因)
price_history = self.get_price_history(item['id']) # 大量データ
current_price = self.calculate_current_price(item, price_history)
base_prices.append(current_price)
# キャッシュへの蓄積(メモリリーク可能性)
large_calculation_cache[item['id']] = price_history
# 割引計算(CPU集約的)
discount_calculation_start = time.time()
discount_result = self.calculate_discounts(order_data, base_prices)
discount_duration = (time.time() - discount_calculation_start) * 1000
newrelic.agent.record_custom_metric('Custom/Pricing/Discount/Duration', discount_duration)
# 税額計算
tax_result = self.calculate_taxes(order_data, discount_result)
# 配送料計算
shipping_cost = self.calculate_shipping_cost(order_data['shipping_address'])
# 最終価格
final_pricing = {
'subtotal': sum(base_prices),
'discount': discount_result['total_discount'],
'tax': tax_result['total_tax'],
'shipping': shipping_cost,
'total': sum(base_prices) - discount_result['total_discount'] + tax_result['total_tax'] + shipping_cost
}
# キャッシュクリア(メモリリーク防止)
large_calculation_cache.clear()
return final_pricing
except Exception as e:
# メモリ状況と共にエラー記録
newrelic.agent.notice_error(attributes={
'function': 'calculate_pricing',
'cache_size': len(large_calculation_cache),
'memory_info': str(psutil.Process(os.getpid()).memory_info())
})
raise e
Java実装
// Java でのCode-Level Metrics実装
import com.newrelic.api.agent.Trace;
import com.newrelic.api.agent.NewRelic;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.util.List;
@Component
public class OrderProcessorService {
private final MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
@Trace(dispatcher = true) // トランザクションエントリーポイント
public OrderResult processOrder(OrderRequest request) {
// ビジネスコンテキスト
NewRelic.addCustomAttribute("order.id", request.getOrderId());
NewRelic.addCustomAttribute("order.items", request.getItems().size());
long startTime = System.currentTimeMillis();
try {
// 処理ステップの詳細監視
ValidationResult validation = validateOrder(request);
InventoryResult inventory = reserveInventory(request.getItems());
PricingResult pricing = calculatePricing(request, inventory);
OrderResult order = saveOrder(request, pricing);
// 成功メトリクス
long processingTime = System.currentTimeMillis() - startTime;
NewRelic.recordMetric("Custom/Order/ProcessingTime", processingTime);
NewRelic.recordMetric("Custom/Order/Success", 1);
return order;
} catch (Exception e) {
// エラー詳細
NewRelic.noticeError(e, Map.of(
"function", "processOrder",
"orderId", request.getOrderId(),
"processingTime", System.currentTimeMillis() - startTime
));
throw e;
}
}
@Trace(metricName = "Custom/Order/Validation") // カスタムメトリクス名
private ValidationResult validateOrder(OrderRequest request) {
long startTime = System.currentTimeMillis();
// CPU使用率監視
long cpuStart = getCpuTime();
try {
// 複雑なバリデーションロジック
List<ValidationError> errors = new ArrayList<>();
// 顧客検証(詳細監視)
if (!validateCustomer(request.getCustomer())) {
errors.add(new ValidationError("Invalid customer"));
}
// 商品検証(CPU集約的処理)
for (OrderItem item : request.getItems()) {
if (!validateItem(item)) {
errors.add(new ValidationError("Invalid item: " + item.getId()));
}
}
// 配送先検証
if (!validateShippingAddress(request.getShippingAddress())) {
errors.add(new ValidationError("Invalid shipping address"));
}
// バリデーション結果メトリクス
long validationTime = System.currentTimeMillis() - startTime;
long cpuUsed = getCpuTime() - cpuStart;
NewRelic.recordMetric("Custom/Validation/Duration", validationTime);
NewRelic.recordMetric("Custom/Validation/CpuTime", cpuUsed);
NewRelic.recordMetric("Custom/Validation/ItemCount", request.getItems().size());
if (!errors.isEmpty()) {
ValidationException e = new ValidationException("Validation failed");
e.setErrors(errors);
throw e;
}
return new ValidationResult(true, validationTime);
} catch (Exception e) {
NewRelic.noticeError(e, Map.of(
"function", "validateOrder",
"itemCount", request.getItems().size(),
"validationTime", System.currentTimeMillis() - startTime
));
throw e;
}
}
@Trace(metricName = "Custom/Inventory/Reservation")
private InventoryResult reserveInventory(List<OrderItem> items) {
long startTime = System.currentTimeMillis();
// メモリ使用量監視開始
long memoryBefore = memoryMXBean.getHeapMemoryUsage().getUsed();
try {
List<InventoryReservation> reservations = new ArrayList<>();
for (OrderItem item : items) {
// 個別アイテム処理時間監視
long itemStartTime = System.currentTimeMillis();
InventoryReservation reservation = reserveSingleItem(item);
reservations.add(reservation);
long itemDuration = System.currentTimeMillis() - itemStartTime;
NewRelic.recordMetric("Custom/Inventory/SingleItem", itemDuration);
// 遅い処理の詳細記録
if (itemDuration > 100) { // 100ms以上
NewRelic.addCustomAttribute("slowReservation.item." + item.getId(), itemDuration);
}
}
// メモリ使用量変化監視
long memoryAfter = memoryMXBean.getHeapMemoryUsage().getUsed();
long memoryDelta = memoryAfter - memoryBefore;
NewRelic.recordMetric("Custom/Memory/InventoryReservation", memoryDelta);
// メモリリーク検出
if (memoryDelta > 10 * 1024 * 1024) { // 10MB以上
NewRelic.recordMetric("Custom/Memory/PotentialLeak", 1);
NewRelic.addCustomAttribute("memory.largeIncrease", memoryDelta);
}
long totalTime = System.currentTimeMillis() - startTime;
NewRelic.recordMetric("Custom/Inventory/TotalReservation", totalTime);
return new InventoryResult(reservations, totalTime);
} catch (Exception e) {
long memoryAfter = memoryMXBean.getHeapMemoryUsage().getUsed();
NewRelic.noticeError(e, Map.of(
"function", "reserveInventory",
"itemCount", items.size(),
"memoryDelta", memoryAfter - memoryBefore
));
throw e;
}
}
@Trace(metricName = "Custom/Pricing/Calculation")
private PricingResult calculatePricing(OrderRequest request, InventoryResult inventory) {
// 複雑な価格計算の詳細監視
long startTime = System.currentTimeMillis();
// メモリ集約的処理
Map<String, List<PriceHistory>> priceCache = new HashMap<>();
try {
// 基本価格計算
BigDecimal subtotal = BigDecimal.ZERO;
for (OrderItem item : request.getItems()) {
// 価格履歴取得(メモリ使用増加)
List<PriceHistory> history = getPriceHistory(item.getId());
priceCache.put(item.getId(), history); // キャッシュに蓄積
BigDecimal itemPrice = calculateItemPrice(item, history);
subtotal = subtotal.add(itemPrice);
}
// 割引計算(CPU集約的)
long discountStartTime = System.currentTimeMillis();
DiscountResult discount = calculateDiscounts(request, subtotal);
long discountDuration = System.currentTimeMillis() - discountStartTime;
NewRelic.recordMetric("Custom/Pricing/Discount/Duration", discountDuration);
// 税計算
TaxResult tax = calculateTaxes(request, discount);
// 配送料計算
BigDecimal shipping = calculateShipping(request.getShippingAddress());
// 最終価格
BigDecimal total = subtotal
.subtract(discount.getAmount())
.add(tax.getAmount())
.add(shipping);
// キャッシュクリア(メモリリーク防止)
priceCache.clear();
long totalTime = System.currentTimeMillis() - startTime;
NewRelic.recordMetric("Custom/Pricing/Total/Duration", totalTime);
return new PricingResult(subtotal, discount.getAmount(), tax.getAmount(), shipping, total);
} catch (Exception e) {
NewRelic.noticeError(e, Map.of(
"function", "calculatePricing",
"cacheSize", priceCache.size(),
"processingTime", System.currentTimeMillis() - startTime
));
throw e;
}
}
// ユーティリティメソッド
private long getCpuTime() {
return ManagementFactory.getThreadMXBean().getCurrentThreadCpuTime() / 1_000_000; // ms
}
}
5.3.2 プロファイリングによる詳細分析
New Relic プロファイリング機能
プロファイリングは、アプリケーション実行中のCPU使用率とメモリ使用パターンを継続的にサンプリングし、パフォーマンスのホットスポットを特定する技術です。
プロファイリングの種類と用途
# プロファイリング手法の比較
CPU_Profiling:
purpose: "CPU使用率の高い関数・処理の特定"
measurement: "CPU時間の消費パターン"
use_cases:
- "レスポンス時間が遅い原因特定"
- "CPU集約的処理の最適化"
- "アルゴリズム効率性評価"
example: "画像処理、データ変換、複雑計算処理"
Memory_Profiling:
purpose: "メモリ使用量とメモリリークの検出"
measurement: "ヒープメモリの割り当て・解放パターン"
use_cases:
- "メモリリーク検出"
- "ガベージコレクション最適化"
- "メモリ使用効率改善"
example: "大量データ処理、キャッシュ管理、セッション管理"
Thread_Profiling:
purpose: "スレッド利用効率とデッドロック検出"
measurement: "スレッドの状態とブロック時間"
use_cases:
- "並行処理の最適化"
- "デッドロック問題解決"
- "スレッドプール効率化"
example: "並列データ処理、非同期処理、WebSocket通信"
実践的プロファイリング実装
Node.js パフォーマンスプロファイリング
// Node.js プロファイリング統合実装
const newrelic = require('newrelic');
const v8Profiler = require('v8-profiler-next');
const fs = require('fs').promises;
class PerformanceProfiler {
constructor() {
this.activeProfiles = new Map();
this.samplingInterval = 1000; // 1秒間隔
this.setupContinuousProfiler();
}
setupContinuousProfiler() {
"""継続的プロファイリングセットアップ"""
// CPU使用率監視
setInterval(() => {
this.sampleCpuUsage();
}, this.samplingInterval);
// メモリ使用量監視
setInterval(() => {
this.sampleMemoryUsage();
}, this.samplingInterval);
// ガベージコレクション監視
this.setupGCMonitoring();
}
sampleCpuUsage() {
"""CPU使用率サンプリング"""
const cpuUsage = process.cpuUsage();
const memUsage = process.memoryUsage();
// New Relic メトリクス記録
newrelic.recordMetric('Custom/Performance/CPU/User', cpuUsage.user / 1000); // マイクロ秒 → ミリ秒
newrelic.recordMetric('Custom/Performance/CPU/System', cpuUsage.system / 1000);
newrelic.recordMetric('Custom/Performance/Memory/HeapUsed', memUsage.heapUsed);
newrelic.recordMetric('Custom/Performance/Memory/HeapTotal', memUsage.heapTotal);
newrelic.recordMetric('Custom/Performance/Memory/RSS', memUsage.rss);
// 高CPU使用率アラート
const totalCpu = (cpuUsage.user + cpuUsage.system) / 1000;
if (totalCpu > 1000) { // 1秒以上のCPU時間消費
newrelic.recordMetric('Custom/Performance/CPU/HighUsage', 1);
this.triggerCpuProfileCapture();
}
}
sampleMemoryUsage() {
"""メモリ使用パターン分析"""
const memUsage = process.memoryUsage();
// メモリリーク検出ロジック
const currentHeapUsed = memUsage.heapUsed;
const currentTime = Date.now();
if (!this.memoryBaseline) {
this.memoryBaseline = { heap: currentHeapUsed, time: currentTime };
return;
}
const timeDiff = currentTime - this.memoryBaseline.time;
const heapDiff = currentHeapUsed - this.memoryBaseline.heap;
// 30分間で100MB以上の継続的増加 = メモリリーク疑い
if (timeDiff > 30 * 60 * 1000 && heapDiff > 100 * 1024 * 1024) {
newrelic.recordMetric('Custom/Performance/Memory/PotentialLeak', 1);
newrelic.addCustomAttribute('memory.leak_rate_mb_per_hour',
(heapDiff / 1024 / 1024) / (timeDiff / 60 / 60 / 1000));
this.triggerMemoryProfileCapture();
// ベースライン更新
this.memoryBaseline = { heap: currentHeapUsed, time: currentTime };
}
}
triggerCpuProfileCapture() {
"""CPU プロファイル取得"""
const profileId = `cpu-profile-${Date.now()}`;
// V8 CPU プロファイリング開始
v8Profiler.startProfiling(profileId, true);
// 10秒後に停止・解析
setTimeout(async () => {
try {
const profile = v8Profiler.stopProfiling(profileId);
const profileData = JSON.stringify(profile);
// プロファイルデータ解析
const analysis = this.analyzeCpuProfile(profile);
// New Relic カスタムイベント送信
newrelic.recordCustomEvent('CPUProfileCaptured', {
profileId: profileId,
captureTime: new Date().toISOString(),
hotFunctions: JSON.stringify(analysis.hotFunctions),
totalSamples: analysis.totalSamples,
topCpuConsumer: analysis.topFunction
});
// プロファイルファイル保存(オプション)
await fs.writeFile(`/tmp/profiles/${profileId}.cpuprofile`, profileData);
console.log(`CPU profile captured: ${profileId}`);
} catch (error) {
newrelic.noticeError(error, { context: 'cpu_profile_capture' });
}
}, 10000);
}
triggerMemoryProfileCapture() {
"""メモリ プロファイル取得"""
const profileId = `memory-profile-${Date.now()}`;
try {
// ヒープスナップショット取得
const snapshot = v8Profiler.takeSnapshot(profileId);
// メモリ使用状況解析
const analysis = this.analyzeMemorySnapshot(snapshot);
// New Relic カスタムイベント送信
newrelic.recordCustomEvent('MemoryProfileCaptured', {
profileId: profileId,
captureTime: new Date().toISOString(),
totalObjects: analysis.totalObjects,
largestObjects: JSON.stringify(analysis.largestObjects),
suspiciousLeaks: JSON.stringify(analysis.suspiciousLeaks)
});
// メモリスナップショット解放
snapshot.delete();
console.log(`Memory profile captured: ${profileId}`);
} catch (error) {
newrelic.noticeError(error, { context: 'memory_profile_capture' });
}
}
analyzeCpuProfile(profile) {
"""CPU プロファイル解析"""
const functionStats = new Map();
let totalSamples = 0;
// プロファイルノード解析
function traverseNode(node) {
if (node.functionName && node.functionName !== '(root)') {
const key = `${node.functionName}:${node.url}:${node.lineNumber}`;
if (!functionStats.has(key)) {
functionStats.set(key, {
functionName: node.functionName,
url: node.url,
lineNumber: node.lineNumber,
hitCount: 0,
selfTime: 0
});
}
const stats = functionStats.get(key);
stats.hitCount += node.hitCount;
stats.selfTime += node.selfTime || 0;
totalSamples += node.hitCount;
}
// 子ノード解析
if (node.children) {
node.children.forEach(traverseNode);
}
}
traverseNode(profile.head);
// CPU使用率上位関数特定
const sortedFunctions = Array.from(functionStats.values())
.sort((a, b) => b.hitCount - a.hitCount)
.slice(0, 10);
return {
totalSamples: totalSamples,
hotFunctions: sortedFunctions,
topFunction: sortedFunctions[0]?.functionName || 'unknown'
};
}
analyzeMemorySnapshot(snapshot) {
"""メモリスナップショット解析"""
// 注意:実際の実装では v8-profiler-next の詳細API使用
// ここでは概念的な解析例を示す
const analysis = {
totalObjects: 0,
largestObjects: [],
suspiciousLeaks: []
};
// スナップショット内のオブジェクト分析
// (実装は v8-profiler-next のドキュメント参照)
return analysis;
}
setupGCMonitoring() {
"""ガベージコレクション監視"""
// GCイベント監視(Node.js v14+)
if (process.env.NODE_ENV === 'production') {
const { PerformanceObserver } = require('perf_hooks');
const gcObserver = new PerformanceObserver((list) => {
const entries = list.getEntries();
entries.forEach(entry => {
if (entry.entryType === 'gc') {
// GC詳細記録
newrelic.recordMetric(`Custom/GC/${entry.detail.kind}/Duration`, entry.duration);
newrelic.recordMetric(`Custom/GC/${entry.detail.kind}/Count`, 1);
// 長時間GCのアラート
if (entry.duration > 100) { // 100ms以上
newrelic.recordMetric('Custom/GC/LongPause', 1);
newrelic.addCustomAttribute('gc.long_pause_duration', entry.duration);
}
}
});
});
gcObserver.observe({ entryTypes: ['gc'] });
}
}
}
// 使用例:Express アプリケーションでの統合
const app = express();
const profiler = new PerformanceProfiler();
// CPU集約的エンドポイント(プロファイリング対象)
app.post('/api/heavy-computation', async (req, res) => {
const startTime = Date.now();
try {
// 重い計算処理(プロファイリング対象)
const result = await performHeavyComputation(req.body.data);
const processingTime = Date.now() - startTime;
// パフォーマンスメトリクス
newrelic.recordMetric('Custom/HeavyComputation/Duration', processingTime);
res.json({
result: result,
processingTime: processingTime
});
} catch (error) {
newrelic.noticeError(error);
res.status(500).json({ error: error.message });
}
});
async function performHeavyComputation(data) {
// CPU集約的処理例
let result = 0;
for (let i = 0; i < data.iterations; i++) {
// 複雑な数値計算
result += Math.sqrt(Math.pow(data.values[i % data.values.length], 2.7));
// メモリ使用量増加処理
if (i % 10000 === 0) {
// 中間結果の保存(メモリ使用量監視対象)
const intermediateResults = new Array(1000).fill(result);
// ... 処理続行
}
}
return result;
}
Python メモリプロファイリング
# Python メモリリーク検出・分析
import newrelic.agent
import tracemalloc
import gc
import psutil
import os
import time
from memory_profiler import profile
from pympler import tracker, muppy, summary
class MemoryProfiler:
def __init__(self):
self.process = psutil.Process(os.getpid())
self.memory_tracker = tracker.SummaryTracker()
self.baseline_memory = None
# メモリトレースマロック有効化
tracemalloc.start(10) # 10フレームまでスタックトレース保存
self.setup_continuous_monitoring()
def setup_continuous_monitoring(self):
"""継続的メモリ監視セットアップ"""
# 定期的メモリチェック(30秒間隔)
import threading
self.monitoring_timer = threading.Timer(30.0, self.periodic_memory_check)
self.monitoring_timer.daemon = True
self.monitoring_timer.start()
def periodic_memory_check(self):
"""定期メモリチェック"""
try:
current_memory = self.process.memory_info().rss
# ベースライン設定
if self.baseline_memory is None:
self.baseline_memory = current_memory
return
# メモリ増加量チェック
memory_increase = current_memory - self.baseline_memory
memory_increase_mb = memory_increase / 1024 / 1024
# New Relic メトリクス記録
newrelic.agent.record_custom_metric('Custom/Memory/RSS/Current', current_memory)
newrelic.agent.record_custom_metric('Custom/Memory/RSS/Increase', memory_increase)
# メモリリーク疑いの検出
if memory_increase_mb > 50: # 50MB以上の増加
newrelic.agent.record_custom_metric('Custom/Memory/PotentialLeak/Detected', 1)
# 詳細分析実行
self.analyze_memory_leak()
# ベースライン更新(継続監視のため)
self.baseline_memory = current_memory
except Exception as e:
newrelic.agent.notice_error(e, attributes={'context': 'periodic_memory_check'})
finally:
# 次回チェックのスケジュール
self.monitoring_timer = threading.Timer(30.0, self.periodic_memory_check)
self.monitoring_timer.daemon = True
self.monitoring_timer.start()
@newrelic.agent.function_trace('memory_leak_analysis')
def analyze_memory_leak(self):
"""メモリリーク詳細分析"""
analysis_start = time.time()
try:
# 1. tracemalloc による分析
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')
leak_suspects = []
# メモリ使用量上位10ファイル・行を分析
for index, stat in enumerate(top_stats[:10]):
leak_suspects.append({
'rank': index + 1,
'filename': stat.traceback.filename,
'lineno': stat.traceback.lineno,
'size_mb': stat.size / 1024 / 1024,
'count': stat.count
})
# 2. オブジェクト統計分析
all_objects = muppy.get_objects()
object_summary = summary.summarize(all_objects)
large_object_types = []
for row in object_summary[:5]: # 上位5つのオブジェクトタイプ
large_object_types.append({
'type': str(row[0]),
'count': row[1],
'total_size': row[2]
})
# 3. ガベージコレクション統計
gc_stats = gc.get_stats()
# New Relic カスタムイベント送信
newrelic.agent.record_custom_event('MemoryLeakAnalysis', {
'analysis_time': time.time() - analysis_start,
'top_memory_consumers': str(leak_suspects[:3]),
'large_object_types': str(large_object_types),
'gc_generation_0': gc_stats[0]['collections'] if gc_stats else 0,
'gc_generation_1': gc_stats[1]['collections'] if len(gc_stats) > 1 else 0,
'gc_generation_2': gc_stats[2]['collections'] if len(gc_stats) > 2 else 0
})
# メモリダンプ生成(本番環境では慎重に実行)
if os.environ.get('ENABLE_MEMORY_DUMPS', 'false').lower() == 'true':
self.generate_memory_dump(leak_suspects, large_object_types)
print(f"Memory leak analysis completed in {time.time() - analysis_start:.2f}s")
except Exception as e:
newrelic.agent.notice_error(e, attributes={'context': 'memory_leak_analysis'})
def generate_memory_dump(self, leak_suspects, large_object_types):
"""メモリダンプファイル生成"""
timestamp = int(time.time())
dump_filename = f"/tmp/memory_dump_{timestamp}.txt"
try:
with open(dump_filename, 'w') as f:
f.write(f"Memory Dump - {time.strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write("=" * 50 + "\n\n")
# メモリ使用量上位箇所
f.write("Top Memory Consumers:\n")
for suspect in leak_suspects:
f.write(f"{suspect['rank']}. {suspect['filename']}:{suspect['lineno']} "
f"- {suspect['size_mb']:.2f}MB ({suspect['count']} objects)\n")
f.write("\n" + "-" * 30 + "\n")
# 大きなオブジェクトタイプ
f.write("Large Object Types:\n")
for obj_type in large_object_types:
f.write(f"- {obj_type['type']}: {obj_type['count']} objects, "
f"{obj_type['total_size']} bytes\n")
# ダンプファイル生成成功
newrelic.agent.record_custom_metric('Custom/Memory/DumpGenerated', 1)
except Exception as e:
newrelic.agent.notice_error(e, attributes={
'context': 'memory_dump_generation',
'dump_filename': dump_filename
})
# 使用例:メモリ集約的処理での統合
profiler = MemoryProfiler()
@profile # memory_profiler デコレータ
@newrelic.agent.function_trace('data_processing')
def process_large_dataset(dataset):
"""大量データ処理(メモリリーク監視対象)"""
processing_start = time.time()
# メモリ使用量ベースライン
initial_memory = psutil.Process().memory_info().rss
try:
# データ処理バッファ(メモリリーク可能性あり)
processing_buffer = {}
results = []
for batch_num, batch in enumerate(dataset.batches()):
batch_start = time.time()
# バッチ処理(メモリ使用量増加)
batch_result = process_batch(batch)
# 中間結果をバッファに保存(潜在的メモリリーク源)
processing_buffer[f'batch_{batch_num}'] = {
'intermediate_data': batch_result,
'metadata': generate_metadata(batch),
'timestamp': time.time()
}
results.append(batch_result)
batch_duration = (time.time() - batch_start) * 1000
newrelic.agent.record_custom_metric('Custom/DataProcessing/BatchDuration', batch_duration)
# 定期的なメモリチェック
if batch_num % 100 == 0:
current_memory = psutil.Process().memory_info().rss
memory_increase = current_memory - initial_memory
newrelic.agent.record_custom_metric('Custom/DataProcessing/MemoryIncrease', memory_increase)
# メモリ使用量が異常に増加している場合
if memory_increase > 100 * 1024 * 1024: # 100MB以上
newrelic.agent.record_custom_metric('Custom/DataProcessing/HighMemoryUsage', 1)
# バッファクリア(メモリリーク対策)
old_buffer_size = len(processing_buffer)
processing_buffer = {k: v for k, v in processing_buffer.items()
if time.time() - v['timestamp'] < 300} # 5分以内のデータのみ保持
cleared_entries = old_buffer_size - len(processing_buffer)
newrelic.agent.record_custom_metric('Custom/DataProcessing/BufferCleared', cleared_entries)
# 最終メモリ使用量
final_memory = psutil.Process().memory_info().rss
total_memory_increase = final_memory - initial_memory
processing_duration = (time.time() - processing_start) * 1000
# 処理完了メトリクス
newrelic.agent.record_custom_metric('Custom/DataProcessing/TotalDuration', processing_duration)
newrelic.agent.record_custom_metric('Custom/DataProcessing/TotalMemoryIncrease', total_memory_increase)
newrelic.agent.record_custom_metric('Custom/DataProcessing/BatchCount', len(results))
# メモリリーク疑いの最終チェック
if total_memory_increase > 200 * 1024 * 1024: # 200MB以上
newrelic.agent.record_custom_metric('Custom/DataProcessing/PotentialMemoryLeak', 1)
profiler.analyze_memory_leak()
return results
except Exception as e:
# エラー時のメモリ状況記録
error_memory = psutil.Process().memory_info().rss
newrelic.agent.notice_error(e, attributes={
'function': 'process_large_dataset',
'initial_memory': initial_memory,
'error_memory': error_memory,
'memory_increase_at_error': error_memory - initial_memory,
'buffer_size': len(processing_buffer) if 'processing_buffer' in locals() else 0
})
raise e
5.3.3 メモリリーク検出と対処法
メモリリーク検出の実践手法
メモリリークは、アプリケーションが使用したメモリを適切に解放せず、徐々にメモリ使用量が増加し続ける現象です。New Relicのモニタリングと組み合わせて、早期検出・対処を実現します。
一般的なメモリリークパターン
// 一般的なメモリリークパターンと検出方法
// パターン1: イベントリスナーの適切な削除忘れ
class ProblematicEventHandler {
constructor() {
this.data = new Array(10000).fill('large data'); // 大量データ保持
// ❌ 問題:イベントリスナーが削除されない
document.addEventListener('click', this.handleClick.bind(this));
setInterval(this.periodicTask.bind(this), 1000);
}
handleClick(event) {
// イベント処理中にさらにメモリを使用
this.temporaryData = new Array(5000).fill(event.target.innerText);
}
periodicTask() {
// タイマー処理でメモリ使用増加
this.additionalData = (this.additionalData || []).concat(new Array(1000).fill(Date.now()));
}
// ❌ 問題:デストラクタでリソース解放していない
}
// ✅ 改善版:適切なリソース管理
class ImprovedEventHandler {
constructor() {
this.data = new Array(10000).fill('large data');
// イベントハンドラーの参照保持
this.boundHandleClick = this.handleClick.bind(this);
this.intervalId = null;
document.addEventListener('click', this.boundHandleClick);
this.intervalId = setInterval(this.periodicTask.bind(this), 1000);
// New Relic でメモリ使用量監視
this.monitorMemoryUsage();
}
handleClick(event) {
// 一時データは適切にスコープ管理
const temporaryData = new Array(5000).fill(event.target.innerText);
// 処理後、temporaryData は自動的にガベージコレクション対象
}
periodicTask() {
// データサイズ制限
if (!this.additionalData) this.additionalData = [];
this.additionalData.push(Date.now());
// 古いデータの削除(メモリリーク防止)
if (this.additionalData.length > 1000) {
this.additionalData = this.additionalData.slice(-1000); // 最新1000件のみ保持
}
// メモリ使用量記録
const memUsage = process.memoryUsage();
newrelic.recordMetric('Custom/Memory/PeriodicTask/HeapUsed', memUsage.heapUsed);
}
monitorMemoryUsage() {
setInterval(() => {
const memUsage = process.memoryUsage();
// ヒープメモリ監視
newrelic.recordMetric('Custom/Memory/EventHandler/HeapUsed', memUsage.heapUsed);
newrelic.recordMetric('Custom/Memory/EventHandler/HeapTotal', memUsage.heapTotal);
// メモリリーク検出ロジック
if (!this.memoryBaseline) {
this.memoryBaseline = memUsage.heapUsed;
this.baselineTime = Date.now();
return;
}
const timeDiff = Date.now() - this.baselineTime;
const memoryDiff = memUsage.heapUsed - this.memoryBaseline;
// 10分間で10MB以上の継続増加 = リーク疑い
if (timeDiff > 10 * 60 * 1000 && memoryDiff > 10 * 1024 * 1024) {
newrelic.recordMetric('Custom/Memory/EventHandler/PotentialLeak', 1);
// アラート情報
newrelic.addCustomAttribute('memory.leak_rate_mb_per_min',
(memoryDiff / 1024 / 1024) / (timeDiff / 60 / 1000));
// ベースライン更新
this.memoryBaseline = memUsage.heapUsed;
this.baselineTime = Date.now();
}
}, 30000); // 30秒間隔
}
// ✅ 適切なクリーンアップ
destroy() {
// イベントリスナー削除
document.removeEventListener('click', this.boundHandleClick);
// タイマー停止
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
// データクリア
this.data = null;
this.additionalData = null;
newrelic.recordMetric('Custom/Memory/EventHandler/Destroyed', 1);
}
}
// パターン2: 循環参照によるメモリリーク
class CircularReferenceExample {
// ❌ 問題:循環参照でガベージコレクションされない
static createProblematicStructure() {
const parent = { children: [], data: new Array(5000).fill('data') };
const child1 = { parent: parent, siblings: [], data: new Array(3000).fill('child1') };
const child2 = { parent: parent, siblings: [], data: new Array(3000).fill('child2') };
// 循環参照作成
parent.children.push(child1, child2);
child1.siblings.push(child2);
child2.siblings.push(child1);
// さらに複雑な参照
child1.reference = child2;
child2.reference = child1;
return parent; // 循環参照でメモリが解放されない
}
// ✅ 改善版:WeakMap を使用した弱参照
static createImprovedStructure() {
const parent = { children: [], data: new Array(5000).fill('data') };
const child1 = { data: new Array(3000).fill('child1') };
const child2 = { data: new Array(3000).fill('child2') };
// WeakMap で弱参照関係を作成
const parentChildMap = new WeakMap();
const siblingMap = new WeakMap();
// 親子関係
parentChildMap.set(child1, parent);
parentChildMap.set(child2, parent);
parent.children.push(child1, child2);
// 兄弟関係
siblingMap.set(child1, [child2]);
siblingMap.set(child2, [child1]);
// メモリ使用量監視
newrelic.recordMetric('Custom/Memory/Structure/Created', 1);
return {
parent,
getParent: (child) => parentChildMap.get(child),
getSiblings: (child) => siblingMap.get(child)
};
}
}
// パターン3: クロージャーによる意図しないメモリ保持
function createProblematicClosure() {
const largeData = new Array(100000).fill('large data array'); // 大量データ
const moreData = new Array(50000).fill('more data');
// ❌ 問題:クロージャーが大量データを保持し続ける
return function smallOperation(input) {
// 実際には largeData は使用しないが、クロージャーにより参照が残る
return input * 2 + moreData.length; // moreData のみ使用
};
}
// ✅ 改善版:必要なデータのみ抽出
function createImprovedClosure() {
const largeData = new Array(100000).fill('large data array');
const moreData = new Array(50000).fill('more data');
// 必要なデータのみ抽出
const moreDataLength = moreData.length;
// largeData, moreData への参照を削除
// (実際のコードでは適切なスコープ管理で自動的に解放)
return function smallOperation(input) {
return input * 2 + moreDataLength; // 数値のみ保持
};
}
自動メモリリーク検出システム
# Python 自動メモリリーク検出システム
import newrelic.agent
import gc
import sys
import threading
import time
import weakref
from collections import defaultdict, deque
from typing import Dict, List, Any, Optional
class MemoryLeakDetector:
def __init__(self, check_interval: int = 60):
self.check_interval = check_interval
self.object_growth_history = defaultdict(deque)
self.memory_snapshots = deque(maxlen=10)
self.leak_candidates = set()
self.monitoring_active = True
# 監視対象オブジェクトタイプ
self.monitored_types = {
'list', 'dict', 'set', 'tuple',
'str', 'bytes', 'bytearray',
'function', 'type', 'module'
}
self.start_monitoring()
def start_monitoring(self):
"""メモリリーク検出開始"""
# バックグラウンドスレッドで定期チェック
self.monitor_thread = threading.Thread(
target=self._monitoring_loop,
daemon=True
)
self.monitor_thread.start()
print(f"Memory leak detection started (interval: {self.check_interval}s)")
def _monitoring_loop(self):
"""メモリ監視ループ"""
while self.monitoring_active:
try:
self._take_memory_snapshot()
self._analyze_object_growth()
self._detect_potential_leaks()
except Exception as e:
newrelic.agent.notice_error(e, attributes={'context': 'memory_monitoring'})
time.sleep(self.check_interval)
@newrelic.agent.function_trace('memory_snapshot')
def _take_memory_snapshot(self):
"""メモリスナップショット取得"""
snapshot_start = time.time()
# ガベージコレクション実行(正確な測定のため)
collected = gc.collect()
# オブジェクト統計取得
object_counts = defaultdict(int)
total_objects = 0
for obj in gc.get_objects():
obj_type = type(obj).__name__
if obj_type in self.monitored_types:
object_counts[obj_type] += 1
total_objects += 1
# システムメモリ情報
import psutil
process = psutil.Process()
memory_info = process.memory_info()
snapshot = {
'timestamp': time.time(),
'object_counts': dict(object_counts),
'total_objects': total_objects,
'rss_bytes': memory_info.rss,
'vms_bytes': memory_info.vms,
'gc_collected': collected
}
self.memory_snapshots.append(snapshot)
# New Relic メトリクス送信
newrelic.agent.record_custom_metric('Custom/Memory/TotalObjects', total_objects)
newrelic.agent.record_custom_metric('Custom/Memory/RSS', memory_info.rss)
newrelic.agent.record_custom_metric('Custom/GC/CollectedObjects', collected)
for obj_type, count in object_counts.items():
newrelic.agent.record_custom_metric(f'Custom/Memory/Objects/{obj_type}', count)
snapshot_duration = (time.time() - snapshot_start) * 1000
newrelic.agent.record_custom_metric('Custom/Memory/SnapshotDuration', snapshot_duration)
def _analyze_object_growth(self):
"""オブジェクト増加パターン分析"""
if len(self.memory_snapshots) < 3:
return
current = self.memory_snapshots[-1]
previous = self.memory_snapshots[-2]
baseline = self.memory_snapshots[0]
# オブジェクトタイプ別成長率分析
for obj_type in self.monitored_types:
current_count = current['object_counts'].get(obj_type, 0)
previous_count = previous['object_counts'].get(obj_type, 0)
baseline_count = baseline['object_counts'].get(obj_type, 0)
# 成長履歴記録
growth_history = self.object_growth_history[obj_type]
growth_history.append(current_count)
# 履歴サイズ制限
if len(growth_history) > 20:
growth_history.popleft()
# 成長パターン分析
if len(growth_history) >= 5:
recent_growth = self._calculate_growth_trend(growth_history)
# 急激な増加検出
if recent_growth > 0.1 and current_count > baseline_count * 1.5:
self.leak_candidates.add(obj_type)
newrelic.agent.record_custom_metric(
f'Custom/Memory/LeakCandidate/{obj_type}/GrowthRate',
recent_growth
)
def _calculate_growth_trend(self, growth_history: deque) -> float:
"""成長トレンド計算(線形回帰近似)"""
if len(growth_history) < 2:
return 0.0
# 簡易線形回帰
n = len(growth_history)
x_values = list(range(n))
y_values = list(growth_history)
x_mean = sum(x_values) / n
y_mean = sum(y_values) / n
numerator = sum((x_values[i] - x_mean) * (y_values[i] - y_mean) for i in range(n))
denominator = sum((x_values[i] - x_mean) ** 2 for i in range(n))
if denominator == 0:
return 0.0
slope = numerator / denominator # 傾き = 成長率
return slope
@newrelic.agent.function_trace('leak_detection')
def _detect_potential_leaks(self):
"""メモリリーク検出"""
if len(self.memory_snapshots) < 5:
return
current = self.memory_snapshots[-1]
baseline = self.memory_snapshots[0]
# RSS メモリ増加チェック
rss_increase = current['rss_bytes'] - baseline['rss_bytes']
rss_increase_mb = rss_increase / 1024 / 1024
time_elapsed = current['timestamp'] - baseline['timestamp']
time_elapsed_hours = time_elapsed / 3600
if rss_increase_mb > 50 and time_elapsed_hours > 0.5: # 50MB増加、30分経過
leak_rate = rss_increase_mb / time_elapsed_hours # MB/hour
# リーク疑いレポート
leak_report = self._generate_leak_report(current, baseline, leak_rate)
# New Relic カスタムイベント送信
newrelic.agent.record_custom_event('MemoryLeakDetected', {
'leak_rate_mb_per_hour': leak_rate,
'total_increase_mb': rss_increase_mb,
'time_elapsed_hours': time_elapsed_hours,
'leak_candidates': list(self.leak_candidates),
'total_objects_increase': current['total_objects'] - baseline['total_objects']
})
# 詳細調査のトリガー
self._trigger_detailed_analysis()
print(f"Potential memory leak detected: {leak_rate:.2f} MB/hour")
def _generate_leak_report(self, current: Dict, baseline: Dict, leak_rate: float) -> Dict:
"""メモリリークレポート生成"""
report = {
'detection_time': time.strftime('%Y-%m-%d %H:%M:%S'),
'leak_rate_mb_per_hour': leak_rate,
'memory_increase': {
'rss_mb': (current['rss_bytes'] - baseline['rss_bytes']) / 1024 / 1024,
'vms_mb': (current['vms_bytes'] - baseline['vms_bytes']) / 1024 / 1024
},
'object_increases': {},
'leak_candidates': list(self.leak_candidates)
}
# オブジェクトタイプ別増加量
for obj_type in self.monitored_types:
current_count = current['object_counts'].get(obj_type, 0)
baseline_count = baseline['object_counts'].get(obj_type, 0)
increase = current_count - baseline_count
if increase > 0:
report['object_increases'][obj_type] = {
'increase': increase,
'percentage': (increase / baseline_count * 100) if baseline_count > 0 else float('inf')
}
return report
def _trigger_detailed_analysis(self):
"""詳細分析トリガー"""
try:
# 現在のオブジェクト参照解析
self._analyze_object_references()
# ガベージコレクションの詳細統計
self._analyze_gc_statistics()
except Exception as e:
newrelic.agent.notice_error(e, attributes={'context': 'detailed_leak_analysis'})
def _analyze_object_references(self):
"""オブジェクト参照関係分析"""
# 最も多いオブジェクトタイプの詳細分析
if not self.leak_candidates:
return
top_candidate = max(self.leak_candidates,
key=lambda x: self.memory_snapshots[-1]['object_counts'].get(x, 0))
# そのタイプのオブジェクトを詳細調査
target_objects = []
for obj in gc.get_objects():
if type(obj).__name__ == top_candidate and len(target_objects) < 100:
target_objects.append(obj)
# 参照カウント分析
high_refcount_objects = []
for obj in target_objects:
refcount = sys.getrefcount(obj)
if refcount > 10: # 高参照カウントオブジェクト
high_refcount_objects.append({
'object_id': id(obj),
'refcount': refcount,
'size': sys.getsizeof(obj)
})
if high_refcount_objects:
newrelic.agent.record_custom_event('HighRefCountObjects', {
'object_type': top_candidate,
'high_refcount_count': len(high_refcount_objects),
'max_refcount': max(obj['refcount'] for obj in high_refcount_objects)
})
def _analyze_gc_statistics(self):
"""ガベージコレクション統計分析"""
gc_stats = gc.get_stats()
for generation, stats in enumerate(gc_stats):
newrelic.agent.record_custom_metric(
f'Custom/GC/Generation{generation}/Collections',
stats['collections']
)
newrelic.agent.record_custom_metric(
f'Custom/GC/Generation{generation}/Collected',
stats['collected']
)
newrelic.agent.record_custom_metric(
f'Custom/GC/Generation{generation}/Uncollectable',
stats['uncollectable']
)
def stop_monitoring(self):
"""監視停止"""
self.monitoring_active = False
if hasattr(self, 'monitor_thread'):
self.monitor_thread.join(timeout=5)
print("Memory leak detection stopped")
# 使用例:Djangoアプリケーションでの統合
def setup_memory_monitoring():
"""メモリ監視セットアップ(Django settings.pyなどで呼び出し)"""
if not hasattr(setup_memory_monitoring, '_detector'):
setup_memory_monitoring._detector = MemoryLeakDetector(check_interval=60)
return setup_memory_monitoring._detector
# アプリケーション起動時の初期化
if __name__ == "__main__":
# 本番環境でのメモリリーク検出有効化
import os
if os.environ.get('ENABLE_MEMORY_LEAK_DETECTION', 'false').lower() == 'true':
detector = setup_memory_monitoring()
print("Memory leak detection enabled")
まとめ
本章では、New Relicのコードレベルパフォーマンス分析により、従来のAPM監視では発見できない詳細な問題を特定・解決する手法を解説しました。
🎯 重要なポイント
1. Code-Level Metricsの価値
- 関数レベルの可視性:「どのサービスが遅い」から「どの関数が遅い」まで詳細特定
- ボトルネックの正確な局在化:推測ではなくデータに基づく最適化
- 開発効率の向上:デバッグ時間の大幅短縮
2. プロファイリングによる深い洞察
- CPU使用パターン:処理効率の定量的評価
- メモリ使用分析:メモリリークの早期検出と予防
- 継続的監視:パフォーマンス劣化の予防的検出
3. メモリリーク対策の実装
- 自動検出システム:人手に頼らない継続的監視
- 根本原因分析:循環参照・イベントリスナー等の特定
- 予防的対策:適切なリソース管理パターンの実装
4. 実践的最適化戦略
- データ駆動型改善:推測ではなく実測に基づく最適化
- 段階的改善:高インパクト・低コストから着手
- 継続的改善サイクル:監視→分析→改善→検証の反復
💡 期待される成果
技術的改善:
- パフォーマンス向上: 関数レベル最適化による20-50%の応答時間改善
- メモリ効率化: メモリリーク対策による安定したリソース使用
- CPU効率向上: ホットスポット最適化による処理能力向上
ビジネス効果:
- ユーザーエクスペリエンス向上: 快適な操作性による満足度向上
- インフラコスト削減: 効率化によるサーバーリソース節約
- 開発生産性向上: 問題特定時間短縮による開発速度向上
運用改善:
- 予防的運用: 問題が顕在化する前の検出・対処
- 根拠のある意思決定: データに基づいた技術選択
- 技術負債削減: 継続的なコード品質改善
🚀 次のステップ
実装ロードマップ:
- Code-Level Metrics有効化: 主要サービスでの詳細監視開始
- プロファイリング導入: CPU・メモリの継続的分析
- メモリリーク検出: 自動検出システムの構築
- 最適化サイクル: 定期的な分析・改善の仕組み化
New Relicのコードレベル分析機能を活用することで、「なんとなく遅い」から「どこが、なぜ遅いか」を正確に把握し、科学的なパフォーマンス最適化を実現できます。これにより、真に高性能で信頼性の高いシステムを構築・運用することが可能になります。
📖 ナビゲーション
メイン: 第5章 New Relic APM(高度化)
前セクション: 第5.2章 分散トレーシング
次セクション: 第6章 New Relic Log Management
関連記事:
第5.1章: APM基本機能
第4章: New Relic Infrastructure
パフォーマンス最適化実践ガイド