misoai-android
Version:
Appium-based Android automation library for misoAI
387 lines (344 loc) • 12.7 kB
text/typescript
/**
* Example for continuous performance monitoring during test execution using misoai-android
*/
import { agentFromLocalAppium, type AppiumBaseCapabilities } from 'misoai-android';
import { PerformanceMonitor } from '../src/performance';
import * as fs from 'fs';
import * as path from 'path';
async function main() {
try {
console.log('Starting Android performance monitoring example...');
// Define capabilities for the Android device
const capabilities: AppiumBaseCapabilities = {
platformName: 'Android',
'appium:automationName': 'UiAutomator2',
'appium:deviceName': 'Android Device',
// Uncomment and set your device ID if you have multiple devices connected
// 'appium:udid': 'your_device_id_here',
'appium:autoGrantPermissions': true
};
// Create an agent using the local Appium server
console.log('Connecting to local Appium server...');
const agent = await agentFromLocalAppium(capabilities);
// Launch an app (e.g., settings app)
const appPackage = 'com.android.settings';
console.log(`Launching app: ${appPackage}...`);
await agent.launch(appPackage);
// Create a performance monitor with auto-detection of active app
console.log('Initializing performance monitor with active app detection...');
// Note: We don't pass a fixed package name, so it will auto-detect the active app
const performanceMonitor = new PerformanceMonitor(agent.page);
// Initialize the monitor and get available metrics
const availableMetrics = await performanceMonitor.initialize();
console.log('Available performance metrics:', availableMetrics);
// Get device information
console.log('Getting device information...');
const deviceInfo = await performanceMonitor.getDeviceInfo();
console.log('Device information:', deviceInfo);
// Start monitoring performance metrics every 2 seconds
console.log('Starting performance monitoring...');
performanceMonitor.startMonitoring(2000);
// Perform some actions to generate performance data
console.log('Performing actions to generate performance data...');
// Get screen size
const size = await agent.page.size();
// Scroll down a few times
for (let i = 0; i < 5; i++) {
console.log(`Scrolling down (${i + 1}/5)...`);
await agent.page.scrollDown(300);
await new Promise(resolve => setTimeout(resolve, 1000));
}
// Scroll up a few times
for (let i = 0; i < 5; i++) {
console.log(`Scrolling up (${i + 1}/5)...`);
await agent.page.scrollUp(300);
await new Promise(resolve => setTimeout(resolve, 1000));
}
// Tap in the middle of the screen a few times
for (let i = 0; i < 3; i++) {
console.log(`Tapping in the middle of the screen (${i + 1}/3)...`);
await agent.page.tap(size.width / 2, size.height / 2);
await new Promise(resolve => setTimeout(resolve, 1000));
}
// Stop monitoring
console.log('Stopping performance monitoring...');
performanceMonitor.stopMonitoring();
// Get collected metrics
const metrics = performanceMonitor.getMetrics();
console.log(`Collected ${metrics.length} performance metrics samples`);
// Export metrics to JSON file using the new method
const timestamp = new Date().toISOString().replace(/:/g, '-');
const resultsDir = path.join(process.cwd(), 'performance-results');
const metricsFilePath = path.join(resultsDir, `metrics-${timestamp}.json`);
await performanceMonitor.exportMetricsToFile(metricsFilePath);
console.log(`Metrics saved to: ${metricsFilePath}`);
// Get metrics for the settings app specifically
const settingsAppMetrics = performanceMonitor.getMetricsForPackage('com.android.settings');
console.log(`Collected ${settingsAppMetrics.length} metrics specifically for the settings app`);
// Save device info to a file
const deviceInfoFilePath = path.join(resultsDir, `device-info-${timestamp}.json`);
fs.writeFileSync(deviceInfoFilePath, JSON.stringify(deviceInfo, null, 2));
console.log(`Device info saved to: ${deviceInfoFilePath}`);
// Generate a simple HTML report
const htmlReport = generateHtmlReport(deviceInfo, metrics);
const htmlReportPath = path.join(resultsDir, `report-${timestamp}.html`);
fs.writeFileSync(htmlReportPath, htmlReport);
console.log(`HTML report saved to: ${htmlReportPath}`);
// Disconnect from the Appium server
console.log('Disconnecting from Appium server...');
await agent.page.disconnect();
console.log('Example completed successfully!');
} catch (error) {
console.error('Error in Android performance monitoring example:', error);
}
}
/**
* Generates a simple HTML report from the collected metrics
*
* @param deviceInfo - Device information
* @param metrics - Collected performance metrics
* @returns HTML report
*/
function generateHtmlReport(deviceInfo: any, metrics: any[]): string {
// Group metrics by package name
const packageGroups: Record<string, any[]> = {};
metrics.forEach(metric => {
const packageName = metric.packageName || 'unknown';
if (!packageGroups[packageName]) {
packageGroups[packageName] = [];
}
packageGroups[packageName].push(metric);
});
// Get unique package names
const packageNames = Object.keys(packageGroups);
// Create CPU usage data for chart
const cpuData = metrics
.filter(m => m.cpuInfo)
.map(m => ({
timestamp: new Date(m.timestamp).toLocaleTimeString(),
packageName: m.packageName,
user: m.cpuInfo?.user || 0,
system: m.cpuInfo?.system || 0,
idle: m.cpuInfo?.idle || 0
}));
// Create memory usage data for chart
const memoryData = metrics
.filter(m => m.memoryInfo)
.map(m => ({
timestamp: new Date(m.timestamp).toLocaleTimeString(),
packageName: m.packageName,
usedMemPercent: m.memoryInfo?.usedMemPercent || 0
}));
return `
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Android Performance Report</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.container { max-width: 1200px; margin: 0 auto; }
.card { border: 1px solid #ddd; border-radius: 8px; padding: 20px; margin-bottom: 20px; }
.chart-container { height: 300px; margin-bottom: 30px; }
table { width: 100%; border-collapse: collapse; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; }
</style>
</head>
<body>
<div class="container">
<h1>Android Performance Report</h1>
<div class="card">
<h2>Device Information</h2>
<table>
<tr><th>Model</th><td>${deviceInfo.model}</td></tr>
<tr><th>Manufacturer</th><td>${deviceInfo.manufacturer}</td></tr>
<tr><th>Android Version</th><td>${deviceInfo.androidVersion}</td></tr>
<tr><th>CPU Architecture</th><td>${deviceInfo.cpuArchitecture}</td></tr>
<tr><th>CPU Cores</th><td>${deviceInfo.cpuCores}</td></tr>
<tr><th>Total RAM</th><td>${deviceInfo.totalRam}</td></tr>
<tr><th>Screen Density</th><td>${deviceInfo.screenDensity}</td></tr>
</table>
</div>
<div class="card">
<h2>Monitored Applications</h2>
<table>
<tr>
<th>Package Name</th>
<th>Number of Metrics</th>
<th>Time Range</th>
</tr>
${packageNames.map(pkg => {
const pkgMetrics = packageGroups[pkg];
const startTime = new Date(Math.min(...pkgMetrics.map(m => m.timestamp))).toLocaleTimeString();
const endTime = new Date(Math.max(...pkgMetrics.map(m => m.timestamp))).toLocaleTimeString();
return `<tr>
<td>${pkg}</td>
<td>${pkgMetrics.length}</td>
<td>${startTime} - ${endTime}</td>
</tr>`;
}).join('')}
</table>
</div>
<div class="card">
<h2>CPU Usage</h2>
<div class="chart-container">
<canvas id="cpuChart"></canvas>
</div>
</div>
<div class="card">
<h2>Memory Usage</h2>
<div class="chart-container">
<canvas id="memoryChart"></canvas>
</div>
</div>
<div class="card">
<h2>Application Switching Timeline</h2>
<div class="chart-container">
<canvas id="appSwitchChart"></canvas>
</div>
</div>
<div class="card">
<h2>Raw Metrics</h2>
<pre>${JSON.stringify(metrics, null, 2)}</pre>
</div>
</div>
<script>
// CPU Chart
const cpuCtx = document.getElementById('cpuChart').getContext('2d');
new Chart(cpuCtx, {
type: 'line',
data: {
labels: ${JSON.stringify(cpuData.map(d => d.timestamp))},
datasets: [
{
label: 'User CPU (%)',
data: ${JSON.stringify(cpuData.map(d => d.user))},
borderColor: 'rgba(75, 192, 192, 1)',
backgroundColor: 'rgba(75, 192, 192, 0.2)',
tension: 0.1
},
{
label: 'System CPU (%)',
data: ${JSON.stringify(cpuData.map(d => d.system))},
borderColor: 'rgba(255, 99, 132, 1)',
backgroundColor: 'rgba(255, 99, 132, 0.2)',
tension: 0.1
},
{
label: 'Idle CPU (%)',
data: ${JSON.stringify(cpuData.map(d => d.idle))},
borderColor: 'rgba(54, 162, 235, 1)',
backgroundColor: 'rgba(54, 162, 235, 0.2)',
tension: 0.1
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
max: 100
}
}
}
});
// Memory Chart
const memCtx = document.getElementById('memoryChart').getContext('2d');
new Chart(memCtx, {
type: 'line',
data: {
labels: ${JSON.stringify(memoryData.map(d => d.timestamp))},
datasets: [
{
label: 'Memory Usage (%)',
data: ${JSON.stringify(memoryData.map(d => d.usedMemPercent))},
borderColor: 'rgba(153, 102, 255, 1)',
backgroundColor: 'rgba(153, 102, 255, 0.2)',
tension: 0.1
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
max: 100
}
}
}
});
// Application Switching Timeline
const appSwitchCtx = document.getElementById('appSwitchChart').getContext('2d');
// Create datasets for each package
const appSwitchDatasets = ${JSON.stringify(packageNames)}.map((pkg, index) => {
// Create a color based on index
const hue = (index * 137) % 360; // Use golden ratio to spread colors
const color = \`hsl(\${hue}, 70%, 60%)\`;
// Create data points where the package is active
const dataPoints = ${JSON.stringify(metrics.map(m => ({
timestamp: new Date(m.timestamp).toLocaleTimeString(),
packageName: m.packageName
})))}.map(point => ({
x: point.timestamp,
y: point.packageName === pkg ? 1 : 0
}));
return {
label: pkg,
data: dataPoints,
backgroundColor: color,
borderColor: color,
stepped: true,
fill: false
};
});
new Chart(appSwitchCtx, {
type: 'line',
data: {
datasets: appSwitchDatasets
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
type: 'category',
labels: ${JSON.stringify(metrics.map(m => new Date(m.timestamp).toLocaleTimeString()))}
},
y: {
beginAtZero: true,
max: 1.2,
ticks: {
stepSize: 1,
callback: function(value) {
return value === 1 ? 'Active' : (value === 0 ? 'Inactive' : '');
}
}
}
},
plugins: {
tooltip: {
callbacks: {
label: function(context) {
const pkg = context.dataset.label;
const status = context.raw.y === 1 ? 'Active' : 'Inactive';
return \`\${pkg}: \${status}\`;
}
}
}
}
}
});
</script>
</body>
</html>
`;
}
// Run the example
main().catch(console.error);