hana-cli
Version:
HANA Developer Command Line Interface
196 lines (171 loc) • 6.89 kB
JavaScript
// @ts-check
import * as baseLite from '../utils/base-lite.js'
import { buildDocEpilogue } from '../utils/doc-linker.js'
export const command = 'memoryLeaks'
export const aliases = ['memleak', 'ml']
export const describe = baseLite.bundle.getText("memoryLeaks")
export const builder = (yargs) => yargs.options(baseLite.getBuilder({
component: {
alias: ['c'],
type: 'string',
default: null,
desc: baseLite.bundle.getText("component")
},
threshold: {
alias: ['t'],
type: 'number',
default: 10,
desc: baseLite.bundle.getText("threshold")
},
limit: {
alias: ['l'],
type: 'number',
default: 50,
desc: baseLite.bundle.getText("limit")
}
})).wrap(160).example('hana-cli memoryLeaks --threshold 25 --limit 100', baseLite.bundle.getText('memoryLeaksExample')).wrap(160).epilog(buildDocEpilogue('memoryLeaks', 'performance-monitoring', ['memoryAnalysis', 'healthCheck']))
export let inputPrompts = {
component: {
description: baseLite.bundle.getText("component"),
type: 'string',
required: false
},
threshold: {
description: baseLite.bundle.getText("threshold"),
type: 'number',
required: false
},
limit: {
description: baseLite.bundle.getText("limit"),
type: 'number',
required: false
}
}
/**
* Command handler function
* @param {object} argv - Command line arguments from yargs
* @returns {Promise<void>}
*/
export async function handler(argv) {
const base = await import('../utils/base.js')
base.promptHandler(argv, detectMemoryLeaks, inputPrompts)
}
/**
* Detect potential memory leaks
* @param {object} prompts - Input prompts
* @returns {Promise<void>}
*/
export async function detectMemoryLeaks(prompts) {
const base = await import('../utils/base.js')
base.debug('detectMemoryLeaks')
try {
base.setPrompts(prompts)
const db = await base.createDBConnection()
try {
const limit = base.validateLimit(prompts.limit)
const component = prompts.component || null
const threshold = prompts.threshold || 10
base.output('')
base.output(base.colors.bold(base.bundle.getText('memoryLeaksHeader')))
base.output(`${base.bundle.getText('thresholdPercent')}: ${threshold}%`)
base.output('')
// Check service memory usage
base.output(base.colors.cyan('► ' + base.bundle.getText('checkingServiceMemory')))
base.output('')
let serviceQuery = `
SELECT
HOST,
PORT,
SERVICE_NAME,
ROUND(HEAP_MEMORY_ALLOCATED_SIZE / 1024 / 1024 / 1024, 2) AS "Heap Allocated (GB)",
ROUND(HEAP_MEMORY_USED_SIZE / 1024 / 1024 / 1024, 2) AS "Heap Used (GB)",
ROUND(HEAP_MEMORY_USED_SIZE / HEAP_MEMORY_ALLOCATED_SIZE * 100, 2) AS "Heap Usage %",
ROUND(SHARED_MEMORY_ALLOCATED_SIZE / 1024 / 1024 / 1024, 2) AS "Shared Allocated (GB)",
ROUND(SHARED_MEMORY_USED_SIZE / 1024 / 1024 / 1024, 2) AS "Shared Used (GB)"
FROM SYS.M_SERVICE_MEMORY
WHERE HEAP_MEMORY_ALLOCATED_SIZE > 0
ORDER BY HEAP_MEMORY_USED_SIZE DESC
`
if (limit && base.sqlInjection.isAcceptableParameter(limit.toString())) {
serviceQuery += ` LIMIT ${limit.toString()}`
}
const serviceResults = await db.execSQL(serviceQuery)
let highMemoryServices = []
if (!serviceResults || serviceResults.length === 0) {
base.output(base.bundle.getText('noDataFound'))
} else {
base.outputTableFancy(serviceResults)
base.output('')
// Identify high memory usage
highMemoryServices = serviceResults.filter(row => row['Heap Usage %'] > threshold)
if (highMemoryServices.length > 0) {
base.output(base.colors.yellow(`⚠ ${highMemoryServices.length} ${base.bundle.getText('servicesHighMemory')}`))
highMemoryServices.forEach(service => {
base.output(base.colors.yellow(` • ${service.SERVICE_NAME}: ${service['Heap Usage %']}% ${base.bundle.getText('heapUsed')}`))
})
base.output('')
}
}
// Check memory objects by component
base.output(base.colors.cyan('► ' + base.bundle.getText('checkingMemoryObjects')))
base.output('')
let objectsQuery = `
SELECT
CATEGORY,
COMPONENT,
COUNT(*) AS "Object Count",
ROUND(SUM(EXCLUSIVE_SIZE_IN_USE) / 1024 / 1024, 2) AS "Total Size (MB)"
FROM SYS.M_HEAP_MEMORY
`
if (component && base.sqlInjection.isAcceptableParameter(component)) {
objectsQuery += ` WHERE COMPONENT LIKE '%${component}%'`
}
objectsQuery += `
GROUP BY CATEGORY, COMPONENT
ORDER BY SUM(EXCLUSIVE_SIZE_IN_USE) DESC
`
if (limit && base.sqlInjection.isAcceptableParameter(limit.toString())) {
objectsQuery += ` LIMIT ${limit.toString()}`
}
const objectsResults = await db.execSQL(objectsQuery)
if (!objectsResults || objectsResults.length === 0) {
base.output(base.bundle.getText('noDataFound'))
} else {
base.outputTableFancy(objectsResults)
base.output('')
}
// Summary and recommendations
base.output(base.colors.bold(base.bundle.getText('memoryLeaksSummary')))
base.output('')
const suspiciousServices = serviceResults ? serviceResults.filter(row => row['Heap Usage %'] > 90) : []
if (suspiciousServices.length > 0) {
base.output(base.colors.red(`✗ ${base.bundle.getText('potentialMemoryLeaks')}`))
base.output('')
base.output(base.colors.bold(base.bundle.getText('recommendations')))
base.output(base.colors.cyan('1. ' + base.bundle.getText('memoryRec1')))
base.output(base.colors.cyan('2. ' + base.bundle.getText('memoryRec2')))
base.output(base.colors.cyan('3. ' + base.bundle.getText('memoryRec3')))
base.output(base.colors.cyan('4. ' + base.bundle.getText('memoryRec4')))
} else if (highMemoryServices.length > 0) {
base.output(base.colors.yellow(`⚠ ${base.bundle.getText('moderateMemoryUsage')}`))
base.output('')
base.output(base.colors.bold(base.bundle.getText('recommendations')))
base.output(base.colors.cyan('1. ' + base.bundle.getText('memoryRec1')))
base.output(base.colors.cyan('2. ' + base.bundle.getText('memoryRec2')))
} else {
base.output(base.colors.green('✓ ' + base.bundle.getText('noLeaksDetected')))
}
base.output('')
await base.end()
} catch (innerError) {
if (innerError.message && innerError.message.includes('Could not find table')) {
base.output(base.colors.yellow('⚠️ ' + base.bundle.getText('viewNotAccessible')))
} else {
throw innerError
}
await base.end()
}
} catch (error) {
await base.error(error)
}
}