Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 56 additions & 2 deletions .github/workflows/bench.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,9 @@ jobs:
# Optional: Run on a self-hosted runner with a real device
device-test-adb:
if: github.event_name == 'workflow_dispatch' || contains(github.event.head_commit.message, '[device-test-adb]')
needs: build-package
needs:
- build-package
- dry-run
runs-on: self-hosted
steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -141,6 +143,58 @@ jobs:
if: always()
uses: actions/upload-artifact@v4
with:
name: benchmark-results
name: benchmark-results-adb
path: experiments/results/
retention-days: 30

# Test SSH connection to localhost
device-test-ssh:
needs:
- build-package
- dry-run
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'

- name: Install dependencies
run: |
pip install --upgrade pip
pip install -r requirements.txt
pip install -e .

- name: Set up SSH server
run: |
# Generate and run SSH setup script
python scripts/generate_ssh_config.py --type setup
bash scripts/setup_ssh_ci.sh

- name: List SSH devices
run: |
ovmobilebench list-ssh-devices || echo "Command not yet implemented"

- name: Test SSH deployment
run: |
# Generate and run SSH test script
python scripts/generate_ssh_config.py --type test
python scripts/test_ssh_device_ci.py

- name: Run benchmark dry-run via SSH
run: |
# Generate SSH config using Python script
python scripts/generate_ssh_config.py --type config

# Run in dry-run mode
ovmobilebench all -c experiments/ssh_localhost_ci.yaml --dry-run || true

- name: Upload SSH test results
if: always()
uses: actions/upload-artifact@v4
with:
name: benchmark-results-ssh
path: experiments/results/
retention-days: 30
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,9 @@ dmypy.json
.idea
.claude
CLAUDE.md

# Generated CI configs
experiments/ssh_localhost_ci.yaml
experiments/results/
scripts/test_ssh_device_ci.py
scripts/setup_ssh_ci.sh
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ cat experiments/results/*.csv

- 🔨 **Automated Build** - Cross-compile OpenVINO for Android/Linux ARM
- 📦 **Smart Packaging** - Bundle runtime, libraries, and models
- 🚀 **Multi-Device** - Deploy via ADB (Android) or SSH (Linux)
- 🚀 **Multi-Device** - Deploy via ADB (Android) or SSH (Linux using paramiko)
- ⚡ **Matrix Testing** - Test multiple configurations automatically
- 📊 **Rich Reports** - JSON/CSV output with detailed metrics
- 🌡️ **Device Control** - Temperature monitoring, performance tuning
Expand All @@ -48,8 +48,8 @@ cat experiments/results/*.csv

| Platform | Architecture | Transport | Status |
|----------|-------------|-----------|--------|
| Android | ARM64 (arm64-v8a) | ADB | ✅ Stable |
| Linux | ARM64/ARM32 | SSH | ✅ Stable |
| Android | ARM64 (arm64-v8a) | ADB (adbutils) | ✅ Stable |
| Linux | ARM64/ARM32 | SSH (paramiko) | ✅ Stable |
| iOS | ARM64 | USB | 🚧 Planned |

## 📋 Requirements
Expand Down
61 changes: 60 additions & 1 deletion docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ class Device(ABC):

### `ovmobilebench.devices.android`

Android device implementation using Python adbutils library for direct device control.
Android device implementation using **adbutils** library for direct Python-based device control.

#### `AndroidDevice`

Expand Down Expand Up @@ -264,9 +264,45 @@ class AndroidDevice(Device):
"""Take screenshot and save to path"""
```

### `ovmobilebench.devices.linux_ssh`

Linux device implementation using **paramiko** library for secure SSH connections and operations.

#### `LinuxSSHDevice`

```python
class LinuxSSHDevice(Device):
"""Linux device via SSH using paramiko"""

def __init__(
self,
host: str,
username: str,
password: Optional[str] = None,
key_filename: Optional[str] = None,
port: int = 22,
push_dir: str = "/tmp/ovmobilebench"
):
"""
Initialize SSH connection to Linux device.

Args:
host: Hostname or IP address
username: SSH username
password: SSH password (optional if using key)
key_filename: Path to SSH private key
port: SSH port (default 22)
push_dir: Remote directory for deployment
"""

def get_env(self) -> Dict[str, str]:
"""Get environment variables for benchmark execution"""
```

#### Example Usage

```python
# Example 1: Android Device
from ovmobilebench.devices.android import AndroidDevice

# Create device connection
Expand All @@ -291,6 +327,29 @@ temp = device.get_temperature()
print(f"Temperature: {temp}°C")

device.disconnect()

# Example 2: Linux SSH Device
from ovmobilebench.devices.linux_ssh import LinuxSSHDevice

# Connect via SSH with key authentication
device = LinuxSSHDevice(
host="192.168.1.100",
username="ubuntu",
key_filename="~/.ssh/id_rsa",
push_dir="/home/ubuntu/ovmobilebench"
)

# Transfer files via SFTP
device.push(Path("model.xml"), "/home/ubuntu/ovmobilebench/model.xml")

# Execute remote commands
ret, stdout, stderr = device.shell("uname -a")
print(f"System: {stdout}")

# Get device info
info = device.info()
print(f"Hostname: {info['hostname']}")
print(f"CPU cores: {info['cpu_cores']}")
```

## Builder API
Expand Down
15 changes: 12 additions & 3 deletions docs/device-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,18 +147,27 @@ ssh user@device.local "uname -a && lscpu"

### Linux SSH Configuration

OVMobileBench uses the **paramiko** library for SSH connections, providing secure and reliable communication with Linux devices. Paramiko supports both password and key-based authentication.

```yaml
device:
kind: "linux_ssh"
type: "linux_ssh" # or kind: "linux_ssh"
host: "192.168.1.100" # Or hostname
port: 22
user: "ubuntu"
key_path: "~/.ssh/id_rsa"
username: "ubuntu" # SSH username (or 'user' for compatibility)
password: "optional" # Optional if using key authentication
key_filename: "~/.ssh/id_rsa" # Path to SSH private key (or 'key_path')
push_dir: "/home/ubuntu/ovmobilebench"
env_vars:
LD_LIBRARY_PATH: "/home/ubuntu/ovmobilebench/lib:$LD_LIBRARY_PATH"
```

**Note:** The SSH implementation uses paramiko for all operations including:
- Secure SSH connections
- SFTP file transfers
- Remote command execution
- Automatic retry with exponential backoff

### Common Linux ARM Devices

| Device | SoC | CPU | RAM | Recommended Config |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,11 @@ class Device(ABC):
- Optional: thermal and CPU governor inspection

### Linux (SSH)
- Paramiko-based `push` via SFTP, `shell` via exec
- **Paramiko-based** SSH implementation for secure remote operations
- `push` via SFTP for file transfers
- `shell` via exec_command for remote command execution
- Support for both password and key-based authentication
- Automatic retry with exponential backoff for reliability
- Env and run directory configurable
- Useful for SBCs/Jetson when Android is not applicable

Expand Down
44 changes: 44 additions & 0 deletions experiments/ssh_localhost.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# SSH localhost test configuration
project:
name: "ssh-localhost-test"
run_id: "local-test"

# SSH device configuration
device:
type: "linux_ssh"
host: "localhost"
username: "${USER}" # Will use current user
# key_filename: "~/.ssh/id_rsa" # Optional, will use SSH agent
push_dir: "/tmp/ovmobilebench"

# Build configuration (optional for testing)
build:
enabled: false
openvino_repo: "/tmp/openvino" # Dummy path, not used when disabled

# Models for testing
models:
- name: "dummy_model"
path: "/tmp/dummy_model.xml"
precision: "FP32"

# Run configuration
run:
repeats: 1
warmup: 0
cooldown_sec: 0
matrix:
niter: [10]
nstreams: ["1"]
device: ["CPU"]

# Reporting
report:
sinks:
- type: "csv"
path: "experiments/results/ssh_test.csv"
- type: "json"
path: "experiments/results/ssh_test.json"
tags:
test_type: "ssh_localhost"
ci: false
22 changes: 22 additions & 0 deletions ovmobilebench/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,5 +152,27 @@ def list_devices():
console.print(f" • {serial} [[{status_color}]{status}[/{status_color}]]")


@app.command("list-ssh-devices")
def list_ssh_devices():
"""List available SSH devices."""
from .devices.linux_ssh import list_ssh_devices as list_ssh
from rich.console import Console

console = Console()
devices = list_ssh()

if not devices:
console.print("[yellow]No SSH devices configured[/yellow]")
console.print("\nTo configure SSH devices, add them to your experiment YAML")
return

console.print("[bold green]Available SSH devices:[/bold green]")
for device in devices:
status_color = "green" if device.get("status") == "available" else "yellow"
serial = device.get("serial", "unknown")
status = device.get("status", "unknown")
console.print(f" • {serial} [[{status_color}]{status}[/{status_color}]]")


if __name__ == "__main__":
app()
36 changes: 33 additions & 3 deletions ovmobilebench/config/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,47 @@ class DeviceConfig(BaseModel):
"""Device configuration."""

kind: Literal["android", "linux_ssh", "ios"] = Field("android", description="Device type")
type: Optional[Literal["android", "linux_ssh", "ios"]] = Field(
None, description="Alternative type field"
)
serials: List[str] = Field(default_factory=list, description="Device serials (Android)")
host: Optional[str] = Field(None, description="SSH host (Linux)")
user: Optional[str] = Field(None, description="SSH user (Linux)")
key_path: Optional[str] = Field(None, description="SSH key path (Linux)")
username: Optional[str] = Field(None, description="SSH username (Linux)")
user: Optional[str] = Field(None, description="SSH user (Linux) - deprecated, use username")
password: Optional[str] = Field(None, description="SSH password (Linux)")
key_filename: Optional[str] = Field(None, description="SSH key file path (Linux)")
key_path: Optional[str] = Field(
None, description="SSH key path (Linux) - deprecated, use key_filename"
)
port: Optional[int] = Field(22, description="SSH port (Linux)")
push_dir: str = Field(default="/data/local/tmp/ovmobilebench", description="Remote directory")
use_root: bool = Field(default=False, description="Use root access")

@model_validator(mode="after")
def validate_device(self):
# Support both 'kind' and 'type' fields
if self.type and not self.kind:
self.kind = self.type
elif self.kind and not self.type:
self.type = self.kind

# Support deprecated field names
if self.user and not self.username:
self.username = self.user
if self.key_path and not self.key_filename:
self.key_filename = self.key_path

# Validate based on device type
if self.kind == "android" and not self.serials:
raise ValueError("Android device requires at least one serial")
# For Android, allow empty serials (will auto-detect)
pass
elif self.kind == "linux_ssh" or self.type == "linux_ssh":
# For SSH, create a dummy serial if not provided
if not self.serials:
if self.host and self.username:
self.serials = [f"{self.username}@{self.host}:{self.port}"]
elif self.host:
self.serials = [f"{self.host}:{self.port}"]
return self


Expand Down
4 changes: 4 additions & 0 deletions ovmobilebench/devices/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,7 @@ def cleanup(self, remote_path: str) -> None:
"""Clean up temporary files on device."""
if self.exists(remote_path):
self.rm(remote_path, recursive=True)

def get_env(self) -> Dict[str, str]:
"""Get environment variables for benchmark execution."""
return {}
Loading
Loading