225 lines
6.9 KiB
Markdown
225 lines
6.9 KiB
Markdown
|
|
# Audit PMO Control - Optimisation Playlist OpenHome
|
||
|
|
|
||
|
|
## Résumé Exécutif
|
||
|
|
|
||
|
|
L'utilisateur rapporte des lenteurs significatives lors de la manipulation de playlists de ~1000 titres avec les renderers OpenHome. Les renderers Chromecast et UPnP (avec queue interne) ne sont pas affectés.
|
||
|
|
|
||
|
|
## État des Optimisations Deja Implémentées
|
||
|
|
|
||
|
|
Le precedent plan dans `Blackboard/Todo/enorme_playlist.md` a deja été partiellement implémenté:
|
||
|
|
|
||
|
|
| optimisation | Statut | Emplacement |
|
||
|
|
|-------------|-------|------------|
|
||
|
|
| MAX_BATCH = 256 pour ReadList | ✅ FAIT | `openhome.rs:1015` |
|
||
|
|
| Élimination double queue_snapshot() | ✅ FAIT | `replace_queue_with_pivot()` et `replace_queue_standard_lcs()` |
|
||
|
|
| LCS préfixe/suffixe (lcs_flags_optimized) | ✅ FAIT | `openhome.rs:869` |
|
||
|
|
| Polling adaptatif (is_active) | ✅ FAIT | `musicrenderer.rs:325-374` |
|
||
|
|
| Consolidation invalidation caches | ✅ FAIT | `openhome.rs:222-235` |
|
||
|
|
|
||
|
|
## Contraintes Protocolaires Découvertes
|
||
|
|
|
||
|
|
L'action `Insert` **ne supporte PAS l'insertion par lot** - chaque appel prend un seul Uri/Metadata.
|
||
|
|
|
||
|
|
## Nouvelles Optimisations (Contre-propositions Utilisateur)
|
||
|
|
|
||
|
|
### OPT-1: Fast Path pour 99% des cas de sync_queue
|
||
|
|
|
||
|
|
**Observation**: 99% des changements de playlist sont:
|
||
|
|
- Insertion de nouvelles tracks en **fin de queue**
|
||
|
|
- Délétion de tracks en **début de queue**
|
||
|
|
- Rarement des changements nécessitant un vrai alignement LCS
|
||
|
|
|
||
|
|
**Solution**: Ajouter une détection de pattern avant d'appeler LCS:
|
||
|
|
|
||
|
|
```rust
|
||
|
|
fn smart_sync(&mut self, items: Vec<PlaybackItem>) -> Result<(), ControlPointError> {
|
||
|
|
let current_ids = self.track_ids()?;
|
||
|
|
|
||
|
|
// Cas 1: Append only (insertion en fin)
|
||
|
|
if items.starts_with(¤t_ids) {
|
||
|
|
// Fast path: juste ajouter les nouveaux items
|
||
|
|
return self.append_only(items.skip(current_ids.len()));
|
||
|
|
}
|
||
|
|
|
||
|
|
// Cas 2: Delete from beginning
|
||
|
|
if current_ids.starts_with(&items) {
|
||
|
|
// Fast path: supprimer de la fin
|
||
|
|
return self.delete_from_beginning(current_ids.len() - items.len());
|
||
|
|
}
|
||
|
|
|
||
|
|
// Cas 3: Full LCS only for complex reorderings
|
||
|
|
return self.replace_queue_standard_lcs(items);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Impact**: 99% des sync_queue passent de O(N²) à O(N)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### OPT-2: Queue FIFO pour Opérations OpenHome (Thread Background)
|
||
|
|
|
||
|
|
**Concept**: Une file d'attente FIFO des opérations SOAP exécutée dans un thread dédié.
|
||
|
|
|
||
|
|
```rust
|
||
|
|
pub struct OpenHomeOpQueue {
|
||
|
|
queue: Arc<Mutex<Vec<OpenHomeOp>>>,
|
||
|
|
worker_handle: Option<JoinHandle<()>>,
|
||
|
|
}
|
||
|
|
|
||
|
|
pub enum OpenHomeOp {
|
||
|
|
Insert { uri: String, metadata: String, after_id: u32 },
|
||
|
|
Delete { track_id: u32 },
|
||
|
|
DeleteAll,
|
||
|
|
SeekId { id: u32 },
|
||
|
|
Play,
|
||
|
|
Pause,
|
||
|
|
Stop,
|
||
|
|
SetVolume { volume: u16 },
|
||
|
|
// Meta operations
|
||
|
|
UpdateMetadata { track_id: u32, metadata: String },
|
||
|
|
}
|
||
|
|
|
||
|
|
impl OpenHomeOpQueue {
|
||
|
|
/// Push operation to the FIFO queue
|
||
|
|
pub fn push(&self, op: OpenHomeOp) {
|
||
|
|
self.queue.lock().unwrap().push(op);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Push with priority (volume, play, stop - need fast response)
|
||
|
|
pub fn push_first(&self, op: OpenHomeOp) {
|
||
|
|
self.queue.lock().unwrap().push_front(op);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Clear all pending operations (client can flush)
|
||
|
|
pub fn clear(&self) {
|
||
|
|
self.queue.lock().unwrap().clear();
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Worker thread consumes operations
|
||
|
|
fn worker_loop(&self) {
|
||
|
|
loop {
|
||
|
|
let op = self.queue.lock().unwrap().pop_front();
|
||
|
|
match op {
|
||
|
|
Some(op) => self.execute(op),
|
||
|
|
None => thread::sleep(Duration::from_millis(10)),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Benefits**:
|
||
|
|
- UI non-bloquante (les operations sont lancées et exec en background)
|
||
|
|
- Batching naturel (plusieurs operations sont executes en sequence)
|
||
|
|
- Priorité via `push_first()` pour play/stop/volume
|
||
|
|
- `clear()` permet d'annuler les operations en attente (ex: playlist changée)
|
||
|
|
|
||
|
|
**Implémentation suggérée**:
|
||
|
|
1. Créer `src/queue/openhome_op_queue.rs` avec la structure
|
||
|
|
2. Intégrer dans `OpenHomeQueue` ou `OpenHomeRenderer`
|
||
|
|
3. Thread de worker lancé au démarrage du control point
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### OPT-3: Métadonnées en Tâche de Fond
|
||
|
|
|
||
|
|
**Observation**: L'appli utilise-t-elle vraiment les métadonnées de la queue OpenHome, ou un cache local?
|
||
|
|
|
||
|
|
Si le control-point maintient son propre cache (plus probable):
|
||
|
|
- Les mises à jour de métadonnées peuvent être traitées en background
|
||
|
|
- Pas besoin de sync immédiate des métadonnées
|
||
|
|
|
||
|
|
**Solution**: Queue séparée pour les operations de métadonnées:
|
||
|
|
|
||
|
|
```rust
|
||
|
|
// Haute priorité (opérations critiques)
|
||
|
|
let high_priority_queue: OpenHomeOpQueue;
|
||
|
|
|
||
|
|
// Basse priorité (métadonnées)
|
||
|
|
let metadata_queue: OpenHomeOpQueue;
|
||
|
|
```
|
||
|
|
|
||
|
|
**Implémentation**:
|
||
|
|
1. Séparer les operations critiques (play/stop/seek/volume) de metadata
|
||
|
|
2. Metadata update traités en background avec délais
|
||
|
|
3. Le cache local du control-point est mis à jour indépendamment
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### OPT-4: Connection Pooling HTTP
|
||
|
|
|
||
|
|
Chaque appel SOAP crée une nouvelle connexion. Avec 1000 insertions:
|
||
|
|
- Overhead TCP: ~10-50ms par appel
|
||
|
|
- Total: 10-50 secondes overhead réseau
|
||
|
|
|
||
|
|
**Solution**: Agent HTTP static avec connection reuse:
|
||
|
|
|
||
|
|
```rust
|
||
|
|
// soap_client.rs
|
||
|
|
static HTTP_AGENT: Lazy<ureq::Agent> = Lazy::new(|| {
|
||
|
|
Agent::config_builder()
|
||
|
|
.timeout_global(Some(Duration::from_secs(30)))
|
||
|
|
.build()
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Plan d'Implémentation Proposé
|
||
|
|
|
||
|
|
### Phase 1: Fast Path LCS (Priorité Haute)
|
||
|
|
|
||
|
|
1. Ajouter `detect_sync_pattern()` dans `openhome.rs`
|
||
|
|
2. Implémenter `append_only()` et `delete_from_beginning()`
|
||
|
|
3. Tester avec playlists réelles
|
||
|
|
|
||
|
|
### Phase 2: Queue FIFO Opérations (Priorité Haute)
|
||
|
|
|
||
|
|
1. Créer `src/queue/openhome_op_queue.rs`
|
||
|
|
2. Implémenter `push()`, `push_first()`, `clear()`
|
||
|
|
3. Thread worker avec loop de consommation
|
||
|
|
4. Intégrer dans `OpenHomeRenderer`
|
||
|
|
|
||
|
|
### Phase 3: Séparation Métadonnées (Priorité Moyenne)
|
||
|
|
|
||
|
|
1. Créer queue séparée pour metadata
|
||
|
|
2. Implémenter batch processing
|
||
|
|
|
||
|
|
### Phase 4: Connection Pooling (Priorité Basse)
|
||
|
|
|
||
|
|
1. Modifier `soap_client.rs` pour agent static
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Questions pour Clarification
|
||
|
|
|
||
|
|
1. **Cache Métadonnées**: Le control-point utilise-t-il vraiment les métadonnées de la queue OpenHome, ou maintient-il son propre cache qui est alimenté indépendamment?
|
||
|
|
|
||
|
|
2. **Priorité des Opérations**: Pour `push_first()`, quelles opérations nécessitent une réponse rapide?
|
||
|
|
- Volume (immédiat)
|
||
|
|
- Play/Pause/Stop (immédiat)
|
||
|
|
- Seek (rapide)
|
||
|
|
- Insert (peut être différé)
|
||
|
|
|
||
|
|
3. **Comportement en cas de conflit**: Si le client fait `clear()` et que le worker est en train d'exécuter une opération:
|
||
|
|
- Annuler l'opération en cours? ( risky - peut laisser le renderer dans un état inconsistent)
|
||
|
|
- Laisser finir l'opération en cours? (plus sur)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Tests Recommandés
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Compiler
|
||
|
|
cargo build -p pmocontrol
|
||
|
|
|
||
|
|
# Benchmark LCS fast paths
|
||
|
|
# - Cas: append 100 tracks to 900 = O(N)
|
||
|
|
# - Cas: delete 100 from 900 = O(N)
|
||
|
|
# - Cas: reorder = O(N²) avec LCS
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
*Plan mis à jour avec contre-propositions utilisateur*
|
||
|
|
*Date: 2026-04-08*
|