hana-cli
Version:
HANA Developer Command Line Interface
292 lines (268 loc) • 11.1 kB
JavaScript
// @ts-check
import * as baseLite from '../utils/base-lite.js'
import { buildDocEpilogue } from '../utils/doc-linker.js'
export const command = 'diagnose'
export const aliases = ['diag']
export const describe = baseLite.bundle.getText("diagnose")
export const builder = (yargs) => yargs.options(baseLite.getBuilder({
checks: {
alias: ['c'],
type: 'string',
default: 'all',
desc: baseLite.bundle.getText("checks")
},
limit: {
alias: ['l'],
type: 'number',
default: 50,
desc: baseLite.bundle.getText("limit")
}
})).wrap(160).example('hana-cli diagnose --checks all', baseLite.bundle.getText("diagnoseExample")).wrap(160).epilog(buildDocEpilogue('diagnose', 'system-admin', ['healthCheck', 'systemInfo', 'status']))
export let inputPrompts = {
checks: {
description: baseLite.bundle.getText("checks"),
type: 'string',
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, runDiagnostics, inputPrompts)
}
/**
* Run comprehensive system diagnostics
* @param {object} prompts - Input prompts
* @returns {Promise<void>}
*/
export async function runDiagnostics(prompts) {
const base = await import('../utils/base.js')
base.debug('runDiagnostics')
try {
base.setPrompts(prompts)
const db = await base.createDBConnection()
try {
const limit = base.validateLimit(prompts.limit)
const checksFilter = prompts.checks || 'all'
const checksArray = checksFilter.toLowerCase().split(',').map(c => c.trim())
const runAll = checksArray.includes('all')
let issues = []
let warnings = []
base.output('')
base.output(base.colors.bold(base.bundle.getText('diagnosticsHeader')))
base.output('')
// Check 1: Services Status
if (runAll || checksArray.includes('services')) {
base.output(base.colors.cyan('► ' + base.bundle.getText('checkingServices')))
try {
const servicesQuery = `
SELECT
HOST,
PORT,
SERVICE_NAME,
ACTIVE_STATUS,
DETAIL
FROM SYS.M_SERVICES
WHERE ACTIVE_STATUS != 'YES'
`
const servicesResults = await db.execSQL(servicesQuery)
if (servicesResults && servicesResults.length > 0) {
issues.push({
category: base.bundle.getText('servicesStatus'),
severity: 'critical',
count: servicesResults.length,
message: base.bundle.getText('inactiveServicesFound')
})
base.output(base.colors.red(` ✗ ${servicesResults.length} ${base.bundle.getText('inactiveServicesFound')}`))
} else {
base.output(base.colors.green(` ✓ ${base.bundle.getText('allServicesActive')}`))
}
} catch (err) {
base.output(base.colors.yellow(` ⚠ ${base.bundle.getText('checkSkipped')}: ${err.message}`))
}
}
// Check 2: Memory Usage
if (runAll || checksArray.includes('memory')) {
base.output(base.colors.cyan('► ' + base.bundle.getText('checkingMemory')))
try {
const memoryQuery = `
SELECT
HOST,
INSTANCE_TOTAL_MEMORY_USED_SIZE,
INSTANCE_TOTAL_MEMORY_ALLOCATED_SIZE,
ROUND(INSTANCE_TOTAL_MEMORY_USED_SIZE / INSTANCE_TOTAL_MEMORY_ALLOCATED_SIZE * 100, 2) AS "Memory Usage %"
FROM SYS.M_HOST_INFORMATION
WHERE INSTANCE_TOTAL_MEMORY_ALLOCATED_SIZE > 0
`
const memoryResults = await db.execSQL(memoryQuery)
let highMemory = false
if (memoryResults && memoryResults.length > 0) {
memoryResults.forEach(row => {
const usagePercent = row['Memory Usage %']
if (usagePercent > 90) {
highMemory = true
issues.push({
category: base.bundle.getText('memoryUsage'),
severity: 'critical',
count: 1,
message: `${row.HOST}: ${usagePercent}% ${base.bundle.getText('memoryUsed')}`
})
} else if (usagePercent > 80) {
warnings.push({
category: base.bundle.getText('memoryUsage'),
severity: 'warning',
count: 1,
message: `${row.HOST}: ${usagePercent}% ${base.bundle.getText('memoryUsed')}`
})
}
})
}
if (highMemory) {
base.output(base.colors.red(` ✗ ${base.bundle.getText('highMemoryUsage')}`))
} else if (warnings.some(w => w.category === base.bundle.getText('memoryUsage'))) {
base.output(base.colors.yellow(` ⚠ ${base.bundle.getText('moderateMemoryUsage')}`))
} else {
base.output(base.colors.green(` ✓ ${base.bundle.getText('memoryUsageNormal')}`))
}
} catch (err) {
base.output(base.colors.yellow(` ⚠ ${base.bundle.getText('checkSkipped')}: ${err.message}`))
}
}
// Check 3: Critical Alerts
if (runAll || checksArray.includes('alerts')) {
base.output(base.colors.cyan('► ' + base.bundle.getText('checkingAlerts')))
try {
const alertsQuery = `
SELECT
COUNT(*) AS "Critical Count"
FROM SYS.M_ALERTS
WHERE SEVERITY = 'CRITICAL'
`
const alertsResults = await db.execSQL(alertsQuery)
if (alertsResults && alertsResults.length > 0 && alertsResults[0]['Critical Count'] > 0) {
const criticalCount = alertsResults[0]['Critical Count']
issues.push({
category: base.bundle.getText('activeAlerts'),
severity: 'critical',
count: criticalCount,
message: base.bundle.getText('criticalAlertsFound')
})
base.output(base.colors.red(` ✗ ${criticalCount} ${base.bundle.getText('criticalAlertsFound')}`))
} else {
base.output(base.colors.green(` ✓ ${base.bundle.getText('noCriticalAlerts')}`))
}
} catch (err) {
base.output(base.colors.yellow(` ⚠ ${base.bundle.getText('checkSkipped')}: ${err.message}`))
}
}
// Check 4: Lock Count
if (runAll || checksArray.includes('locks')) {
base.output(base.colors.cyan('► ' + base.bundle.getText('checkingLocks')))
try {
const locksQuery = `
SELECT
COUNT(*) AS "Lock Count"
FROM SYS.M_LOCKS
`
const locksResults = await db.execSQL(locksQuery)
if (locksResults && locksResults.length > 0) {
const lockCount = locksResults[0]['Lock Count']
if (lockCount > 100) {
warnings.push({
category: base.bundle.getText('lockCount'),
severity: 'warning',
count: lockCount,
message: base.bundle.getText('highLockCount')
})
base.output(base.colors.yellow(` ⚠ ${lockCount} ${base.bundle.getText('activeLocks')}`))
} else {
base.output(base.colors.green(` ✓ ${lockCount} ${base.bundle.getText('activeLocks')}`))
}
}
} catch (err) {
base.output(base.colors.yellow(` ⚠ ${base.bundle.getText('checkSkipped')}: ${err.message}`))
}
}
// Check 5: Last Backup
if (runAll || checksArray.includes('backup')) {
base.output(base.colors.cyan('► ' + base.bundle.getText('checkingBackup')))
try {
const backupQuery = `
SELECT
MAX(SYS_END_TIME) AS "Last Backup"
FROM SYS.M_BACKUP_CATALOG
WHERE STATE_NAME = 'successful'
AND ENTRY_TYPE_NAME = 'complete data backup'
`
const backupResults = await db.execSQL(backupQuery)
if (backupResults && backupResults.length > 0 && backupResults[0]['Last Backup']) {
const lastBackup = new Date(backupResults[0]['Last Backup'])
const now = new Date()
const hoursSinceBackup = (now.getTime() - lastBackup.getTime()) / (1000 * 60 * 60)
if (hoursSinceBackup > 48) {
warnings.push({
category: base.bundle.getText('lastBackup'),
severity: 'warning',
count: 1,
message: `${Math.floor(hoursSinceBackup)} ${base.bundle.getText('hoursSinceLastBackup')}`
})
base.output(base.colors.yellow(` ⚠ ${base.bundle.getText('lastBackupOld')}: ${Math.floor(hoursSinceBackup)} ${base.bundle.getText('hoursAgo')}`))
} else {
base.output(base.colors.green(` ✓ ${base.bundle.getText('recentBackupFound')}: ${Math.floor(hoursSinceBackup)} ${base.bundle.getText('hoursAgo')}`))
}
} else {
warnings.push({
category: base.bundle.getText('lastBackup'),
severity: 'warning',
count: 1,
message: base.bundle.getText('noBackupFound')
})
base.output(base.colors.yellow(` ⚠ ${base.bundle.getText('noBackupFound')}`))
}
} catch (err) {
base.output(base.colors.yellow(` ⚠ ${base.bundle.getText('checkSkipped')}: ${err.message}`))
}
}
// Summary
base.output('')
base.output(base.colors.bold(base.bundle.getText('diagnosticsSummary')))
base.output('')
if (issues.length === 0 && warnings.length === 0) {
base.output(base.colors.green('✓ ' + base.bundle.getText('systemHealthy')))
} else {
if (issues.length > 0) {
base.output(base.colors.red(`✗ ${issues.length} ${base.bundle.getText('criticalIssuesFound')}`))
issues.forEach(issue => {
base.output(base.colors.red(` • ${issue.category}: ${issue.message}`))
})
}
if (warnings.length > 0) {
base.output(base.colors.yellow(`⚠ ${warnings.length} ${base.bundle.getText('warningsFound')}`))
warnings.forEach(warning => {
base.output(base.colors.yellow(` • ${warning.category}: ${warning.message}`))
})
}
}
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)
}
}