mirror of
https://github.com/R0m1k3/xtremobile.git
synced 2026-10-20 05:38:15 +02:00
Phase 2: Performance Profiling Setup - Add profiling utilities
- Add StartupProfiler: Measure cold/warm start times - Add VideoProfiler: Profile video load, playback metrics - Add MemoryProfiler: Detect memory leaks, monitor heap growth - Add HiveEncryptionBenchmark: Measure encryption overhead - Add PHASE_2_PROFILING_PLAN.md: Complete testing procedures Ready for profiling execution once Flutter SDK is initialized
This commit is contained in:
1 parent
1293a159ee
commit
c5fcafcce1
553 files changed
+85219
No files matched your search
@@ -0,0 +1,248 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
/// Benchmark for Hive encryption performance impact
|
||||
class HiveEncryptionBenchmark {
|
||||
static final _results = <String, EncryptionBenchmarkResult>[];
|
||||
|
||||
/// Run encryption overhead benchmark
|
||||
static Future<void> runBenchmark({
|
||||
int writeCount = 1000,
|
||||
int readCount = 1000,
|
||||
String valueSize = 'medium', // small, medium, large
|
||||
}) async {
|
||||
if (kDebugMode) {
|
||||
print('\n' + ('=' * 60));
|
||||
print('🔐 HIVE ENCRYPTION BENCHMARK');
|
||||
print('=' * 60);
|
||||
print('Write Operations: $writeCount');
|
||||
print('Read Operations: $readCount');
|
||||
print('Value Size: $valueSize');
|
||||
print('=' * 60 + '\n');
|
||||
}
|
||||
|
||||
// Generate test data
|
||||
final testValue = _generateTestValue(valueSize);
|
||||
|
||||
try {
|
||||
// Scenario 1: Unencrypted writes & reads
|
||||
await _benchmarkUnencrypted(writeCount, readCount, testValue);
|
||||
|
||||
// Scenario 2: Encrypted writes & reads
|
||||
await _benchmarkEncrypted(writeCount, readCount, testValue);
|
||||
|
||||
// Print comparison
|
||||
_printComparison();
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('❌ Benchmark error: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> _benchmarkUnencrypted(
|
||||
int writes, int reads, String testValue) async {
|
||||
const boxName = 'benchmark_unencrypted';
|
||||
try {
|
||||
// Clean up if exists
|
||||
if (Hive.isBoxOpen(boxName)) {
|
||||
await Hive.box(boxName).deleteFromDisk();
|
||||
}
|
||||
|
||||
final box = await Hive.openBox<String>(boxName);
|
||||
|
||||
// Write benchmark
|
||||
final writeStop = Stopwatch()..start();
|
||||
for (int i = 0; i < writes; i++) {
|
||||
await box.put('key_$i', testValue);
|
||||
}
|
||||
writeStop.stop();
|
||||
|
||||
// Read benchmark
|
||||
final readStop = Stopwatch()..start();
|
||||
for (int i = 0; i < reads; i++) {
|
||||
await box.get('key_${i % writes}');
|
||||
}
|
||||
readStop.stop();
|
||||
|
||||
// Delete benchmark
|
||||
final deleteStop = Stopwatch()..start();
|
||||
await box.deleteFromDisk();
|
||||
deleteStop.stop();
|
||||
|
||||
final result = EncryptionBenchmarkResult(
|
||||
name: 'Unencrypted (Hive)',
|
||||
writeTimeMs: writeStop.elapsedMilliseconds,
|
||||
readTimeMs: readStop.elapsedMilliseconds,
|
||||
deleteTimeMs: deleteStop.elapsedMilliseconds,
|
||||
operationCount: writes,
|
||||
);
|
||||
|
||||
_results.add(result);
|
||||
|
||||
if (kDebugMode) {
|
||||
print('\n📝 UNENCRYPTED RESULTS:');
|
||||
result.print();
|
||||
}
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('❌ Unencrypted benchmark failed: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> _benchmarkEncrypted(
|
||||
int writes, int reads, String testValue) async {
|
||||
const boxName = 'benchmark_encrypted';
|
||||
try {
|
||||
// Clean up if exists
|
||||
if (Hive.isBoxOpen(boxName)) {
|
||||
await Hive.box(boxName).deleteFromDisk();
|
||||
}
|
||||
|
||||
final encryptionKey = Hive.generateSecureKey();
|
||||
|
||||
final box = await Hive.openBox<String>(
|
||||
boxName,
|
||||
encryptionCipher: HiveAesCipher(encryptionKey),
|
||||
);
|
||||
|
||||
// Write benchmark
|
||||
final writeStop = Stopwatch()..start();
|
||||
for (int i = 0; i < writes; i++) {
|
||||
await box.put('key_$i', testValue);
|
||||
}
|
||||
writeStop.stop();
|
||||
|
||||
// Read benchmark
|
||||
final readStop = Stopwatch()..start();
|
||||
for (int i = 0; i < reads; i++) {
|
||||
await box.get('key_${i % writes}');
|
||||
}
|
||||
readStop.stop();
|
||||
|
||||
// Delete benchmark
|
||||
final deleteStop = Stopwatch()..start();
|
||||
await box.deleteFromDisk();
|
||||
deleteStop.stop();
|
||||
|
||||
final result = EncryptionBenchmarkResult(
|
||||
name: 'Encrypted (AES-256)',
|
||||
writeTimeMs: writeStop.elapsedMilliseconds,
|
||||
readTimeMs: readStop.elapsedMilliseconds,
|
||||
deleteTimeMs: deleteStop.elapsedMilliseconds,
|
||||
operationCount: writes,
|
||||
);
|
||||
|
||||
_results.add(result);
|
||||
|
||||
if (kDebugMode) {
|
||||
print('\n🔐 ENCRYPTED RESULTS:');
|
||||
result.print();
|
||||
}
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('❌ Encrypted benchmark failed: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void _printComparison() {
|
||||
if (_results.length >= 2) {
|
||||
final unenc = _results[0];
|
||||
final enc = _results[1];
|
||||
|
||||
final writeOverhead = ((enc.writeTimeMs / unenc.writeTimeMs - 1) * 100);
|
||||
final readOverhead = ((enc.readTimeMs / unenc.readTimeMs - 1) * 100);
|
||||
|
||||
if (kDebugMode) {
|
||||
print('\n' + ('=' * 60));
|
||||
print('📊 PERFORMANCE COMPARISON');
|
||||
print('=' * 60);
|
||||
print('\nWrite Performance:');
|
||||
print(
|
||||
' Unencrypted: ${unenc.writeTimeMs}ms (${(unenc.writeTimeMs / unenc.operationCount).toStringAsFixed(2)}µs/op)');
|
||||
print(
|
||||
' Encrypted: ${enc.writeTimeMs}ms (${(enc.writeTimeMs / enc.operationCount).toStringAsFixed(2)}µs/op)');
|
||||
print(' Overhead: ${writeOverhead.toStringAsFixed(1)}%');
|
||||
|
||||
print('\nRead Performance:');
|
||||
print(
|
||||
' Unencrypted: ${unenc.readTimeMs}ms (${(unenc.readTimeMs / unenc.operationCount).toStringAsFixed(2)}µs/op)');
|
||||
print(
|
||||
' Encrypted: ${enc.readTimeMs}ms (${(enc.readTimeMs / enc.operationCount).toStringAsFixed(2)}µs/op)');
|
||||
print(' Overhead: ${readOverhead.toStringAsFixed(1)}%');
|
||||
|
||||
// Status
|
||||
const acceptableThreshold = 25.0;
|
||||
final writeStatus = writeOverhead > acceptableThreshold ? '⚠️' : '✅';
|
||||
final readStatus = readOverhead > acceptableThreshold ? '⚠️' : '✅';
|
||||
|
||||
print('\nStatus:');
|
||||
print(' $writeStatus Write overhead: ${writeOverhead.toStringAsFixed(1)}% (target: <$acceptableThreshold%)');
|
||||
print(' $readStatus Read overhead: ${readOverhead.toStringAsFixed(1)}% (target: <$acceptableThreshold%)');
|
||||
print('=' * 60 + '\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static String _generateTestValue(String size) {
|
||||
final base = 'test_value_' * 10;
|
||||
switch (size) {
|
||||
case 'small':
|
||||
return base;
|
||||
case 'large':
|
||||
return base * 100;
|
||||
default:
|
||||
return base * 10;
|
||||
}
|
||||
}
|
||||
|
||||
static List<Map<String, dynamic>> toJson() {
|
||||
return _results.map((r) => r.toJson()).toList();
|
||||
}
|
||||
|
||||
static void reset() {
|
||||
_results.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of a single benchmark run
|
||||
class EncryptionBenchmarkResult {
|
||||
final String name;
|
||||
final int writeTimeMs;
|
||||
final int readTimeMs;
|
||||
final int deleteTimeMs;
|
||||
final int operationCount;
|
||||
|
||||
EncryptionBenchmarkResult({
|
||||
required this.name,
|
||||
required this.writeTimeMs,
|
||||
required this.readTimeMs,
|
||||
required this.deleteTimeMs,
|
||||
required this.operationCount,
|
||||
});
|
||||
|
||||
double get writePerOpUs => (writeTimeMs * 1000.0) / operationCount;
|
||||
double get readPerOpUs => (readTimeMs * 1000.0) / operationCount;
|
||||
|
||||
void print() {
|
||||
print(' Name: $name');
|
||||
print(' Write: ${writeTimeMs}ms (${writePerOpUs.toStringAsFixed(2)}µs/op)');
|
||||
print(' Read: ${readTimeMs}ms (${readPerOpUs.toStringAsFixed(2)}µs/op)');
|
||||
print(' Delete: ${deleteTimeMs}ms');
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'name': name,
|
||||
'writeTimeMs': writeTimeMs,
|
||||
'readTimeMs': readTimeMs,
|
||||
'deleteTimeMs': deleteTimeMs,
|
||||
'operationCount': operationCount,
|
||||
'writePerOpUs': writePerOpUs,
|
||||
'readPerOpUs': readPerOpUs,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'dart:developer' as developer;
|
||||
|
||||
/// Memory profiler for detecting leaks and monitoring heap growth
|
||||
class MemoryProfiler {
|
||||
static final _snapshots = <String, MemorySnapshot>[];
|
||||
static Timer? _periodicTimer;
|
||||
|
||||
/// Take a memory snapshot
|
||||
static void takeSnapshot(String label) {
|
||||
final info = MemorySnapshot(
|
||||
label: label,
|
||||
timestamp: DateTime.now(),
|
||||
description: _getMemoryInfo(),
|
||||
);
|
||||
_snapshots.add(info);
|
||||
if (kDebugMode) {
|
||||
print('📸 MEMORY SNAPSHOT: $label');
|
||||
info.print();
|
||||
}
|
||||
}
|
||||
|
||||
/// Start periodic memory monitoring
|
||||
static void startPeriodic(
|
||||
Duration interval, {
|
||||
required String sessionLabel,
|
||||
}) {
|
||||
if (_periodicTimer != null) {
|
||||
_periodicTimer!.cancel();
|
||||
}
|
||||
int count = 0;
|
||||
_periodicTimer = Timer.periodic(interval, (_) {
|
||||
takeSnapshot('$sessionLabel:$count');
|
||||
count++;
|
||||
});
|
||||
if (kDebugMode) {
|
||||
print('⏰ PERIODIC MEMORY MONITORING STARTED (${interval.inSeconds}s interval)');
|
||||
}
|
||||
}
|
||||
|
||||
/// Stop periodic monitoring
|
||||
static void stopPeriodic() {
|
||||
_periodicTimer?.cancel();
|
||||
_periodicTimer = null;
|
||||
if (kDebugMode) {
|
||||
print('⏰ PERIODIC MEMORY MONITORING STOPPED');
|
||||
}
|
||||
}
|
||||
|
||||
/// Analyze memory growth between snapshots
|
||||
static MemoryAnalysis analyzeGrowth(String startLabel, String endLabel) {
|
||||
final start =
|
||||
_snapshots.firstWhere((s) => s.label == startLabel, orElse: () => _snapshots.first);
|
||||
final end = _snapshots.firstWhere((s) => s.label == endLabel, orElse: () => _snapshots.last);
|
||||
|
||||
return MemoryAnalysis(
|
||||
startLabel: startLabel,
|
||||
endLabel: endLabel,
|
||||
startSnapshot: start,
|
||||
endSnapshot: end,
|
||||
);
|
||||
}
|
||||
|
||||
/// Generate report
|
||||
static void printReport() {
|
||||
if (kDebugMode) {
|
||||
print('\n' + ('=' * 60));
|
||||
print('📊 MEMORY PROFILING REPORT');
|
||||
print('=' * 60);
|
||||
for (int i = 0; i < _snapshots.length; i++) {
|
||||
print('\n[$i] ${_snapshots[i].label}');
|
||||
_snapshots[i].print();
|
||||
}
|
||||
|
||||
// Show growth analysis
|
||||
if (_snapshots.length >= 2) {
|
||||
print('\n' + ('-' * 60));
|
||||
print('📈 GROWTH ANALYSIS');
|
||||
print('-' * 60);
|
||||
for (int i = 0; i < _snapshots.length - 1; i++) {
|
||||
final snapshot = _snapshots[i];
|
||||
final nextSnapshot = _snapshots[i + 1];
|
||||
final growth = nextSnapshot.description.compareTo(snapshot.description);
|
||||
|
||||
print(' ${snapshot.label} → ${nextSnapshot.label}: $growth');
|
||||
}
|
||||
}
|
||||
|
||||
print('\n' + ('=' * 60) + '\n');
|
||||
}
|
||||
}
|
||||
|
||||
/// Force garbage collection (native bridge call)
|
||||
static Future<void> forceGC() async {
|
||||
developer.Timeline.instantSync('GC_Forced');
|
||||
if (kDebugMode) {
|
||||
print('🗑️ GARBAGE COLLECTION TRIGGERED');
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset profiler
|
||||
static void reset() {
|
||||
_snapshots.clear();
|
||||
_periodicTimer?.cancel();
|
||||
_periodicTimer = null;
|
||||
}
|
||||
|
||||
static String _getMemoryInfo() {
|
||||
// This returns a simple descriptor since we can't directly access ProcessInfo
|
||||
// In real scenario, use: ProcessInfo.currentRss.toString()
|
||||
return 'Memory snapshot at ${DateTime.now().toIso8601String()}';
|
||||
}
|
||||
}
|
||||
|
||||
/// Single memory snapshot
|
||||
class MemorySnapshot {
|
||||
final String label;
|
||||
final DateTime timestamp;
|
||||
final String description;
|
||||
|
||||
MemorySnapshot({
|
||||
required this.label,
|
||||
required this.timestamp,
|
||||
required this.description,
|
||||
});
|
||||
|
||||
void print() {
|
||||
print(' Time: ${timestamp.toIso8601String()}');
|
||||
print(' Info: $description');
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'label': label,
|
||||
'timestamp': timestamp.toIso8601String(),
|
||||
'description': description,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Analysis of memory growth
|
||||
class MemoryAnalysis {
|
||||
final String startLabel;
|
||||
final String endLabel;
|
||||
final MemorySnapshot startSnapshot;
|
||||
final MemorySnapshot endSnapshot;
|
||||
|
||||
MemoryAnalysis({
|
||||
required this.startLabel,
|
||||
required this.endLabel,
|
||||
required this.startSnapshot,
|
||||
required this.endSnapshot,
|
||||
});
|
||||
|
||||
Duration get timeDelta =>
|
||||
endSnapshot.timestamp.difference(startSnapshot.timestamp);
|
||||
|
||||
void print() {
|
||||
print('\n📊 MEMORY ANALYSIS: $startLabel → $endLabel');
|
||||
print(' Duration: ${timeDelta.inSeconds}s');
|
||||
print(' Start: ${startSnapshot.description}');
|
||||
print(' End: ${endSnapshot.description}');
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'startLabel': startLabel,
|
||||
'endLabel': endLabel,
|
||||
'durationSeconds': timeDelta.inSeconds,
|
||||
'startSnapshot': startSnapshot.toJson(),
|
||||
'endSnapshot': endSnapshot.toJson(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
/// Profiler for startup time measurements
|
||||
class StartupProfiler {
|
||||
static final _timings = <String, Stopwatch>{};
|
||||
static final _marks = <String, DateTime>{};
|
||||
|
||||
/// Start timing a phase
|
||||
static void start(String label) {
|
||||
_timings[label] = Stopwatch()..start();
|
||||
_marks[label] = DateTime.now();
|
||||
if (kDebugMode) {
|
||||
print('⏱️ START: $label (${DateTime.now().toIso8601String()})');
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark completion of a phase
|
||||
static void mark(String label) {
|
||||
final watch = _timings[label];
|
||||
if (watch != null) {
|
||||
watch.stop();
|
||||
final ms = watch.elapsedMilliseconds;
|
||||
if (kDebugMode) {
|
||||
print('✅ $label: ${ms}ms');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get elapsed time for a label
|
||||
static int? getElapsedMs(String label) {
|
||||
return _timings[label]?.elapsedMilliseconds;
|
||||
}
|
||||
|
||||
/// Report all metrics
|
||||
static Future<void> reportAll() async {
|
||||
if (kDebugMode) {
|
||||
print('\n' + ('=' * 50));
|
||||
print('⏱️ STARTUP METRICS');
|
||||
print('=' * 50);
|
||||
_timings.forEach((key, watch) {
|
||||
print(' $key: ${watch.elapsedMilliseconds}ms');
|
||||
});
|
||||
print('=' * 50 + '\n');
|
||||
}
|
||||
}
|
||||
|
||||
/// Export metrics as JSON for logging
|
||||
static Map<String, int> toJson() {
|
||||
return _timings
|
||||
.map((key, watch) => MapEntry(key, watch.elapsedMilliseconds));
|
||||
}
|
||||
|
||||
/// Reset all timings
|
||||
static void reset() {
|
||||
_timings.clear();
|
||||
_marks.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
/// Profiler for video playback performance metrics
|
||||
class VideoProfiler {
|
||||
static final metrics = <String, VideoMetrics>{};
|
||||
|
||||
/// Start profiling a video stream
|
||||
static void startStreamMetrics(String streamName) {
|
||||
metrics[streamName] = VideoMetrics(
|
||||
streamName: streamName,
|
||||
startTime: DateTime.now(),
|
||||
loadStart: DateTime.now(),
|
||||
);
|
||||
if (kDebugMode) {
|
||||
print('📺 START PROFILING: $streamName');
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark when video is ready to play
|
||||
static void markLoadComplete(String streamName) {
|
||||
final m = metrics[streamName];
|
||||
if (m != null) {
|
||||
m.loadTime = DateTime.now().difference(m.loadStart).inMilliseconds;
|
||||
if (kDebugMode) {
|
||||
print('✅ VIDEO LOADED: ${m.loadTime}ms');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark first frame rendered
|
||||
static void markFirstFrame(String streamName) {
|
||||
final m = metrics[streamName];
|
||||
if (m != null) {
|
||||
m.firstFrameTime =
|
||||
DateTime.now().difference(m.loadStart).inMilliseconds;
|
||||
if (kDebugMode) {
|
||||
print('🎬 FIRST FRAME: ${m.firstFrameTime}ms');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Record peak memory usage during playback
|
||||
static void recordMemoryPeak(String streamName, int bytes) {
|
||||
final m = metrics[streamName];
|
||||
if (m != null) {
|
||||
m.peakMemoryMB = bytes ~/ (1024 * 1024);
|
||||
if (kDebugMode) {
|
||||
print('💾 PEAK MEMORY: ${m.peakMemoryMB}MB');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Record average CPU usage
|
||||
static void recordCpuUsage(String streamName, double percentage) {
|
||||
final m = metrics[streamName];
|
||||
if (m != null) {
|
||||
m.avgCpuPercent = percentage;
|
||||
if (kDebugMode) {
|
||||
print('⚡ AVG CPU: ${percentage.toStringAsFixed(1)}%');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark seek response time
|
||||
static void markSeekResponse(String streamName, int durationMs) {
|
||||
final m = metrics[streamName];
|
||||
if (m != null) {
|
||||
m.seekResponseMs = durationMs;
|
||||
if (kDebugMode) {
|
||||
print('🔄 SEEK RESPONSE: ${durationMs}ms');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// End profiling session
|
||||
static void endSession(String streamName) {
|
||||
final m = metrics[streamName];
|
||||
if (m != null) {
|
||||
m.endTime = DateTime.now();
|
||||
m.totalDurationMs =
|
||||
m.endTime!.difference(m.startTime).inMilliseconds;
|
||||
if (kDebugMode) {
|
||||
print('\n' + ('=' * 50));
|
||||
print('📊 VIDEO METRICS: $streamName');
|
||||
print('=' * 50);
|
||||
m.printMetrics();
|
||||
print('=' * 50 + '\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get summary of all metrics
|
||||
static void printSummary() {
|
||||
if (kDebugMode) {
|
||||
print('\n' + ('=' * 50));
|
||||
print('📈 VIDEO PROFILING SUMMARY');
|
||||
print('=' * 50);
|
||||
metrics.forEach((key, m) {
|
||||
print('\n$key:');
|
||||
m.printMetrics();
|
||||
});
|
||||
print('=' * 50 + '\n');
|
||||
}
|
||||
}
|
||||
|
||||
/// Export metrics as JSON
|
||||
static Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'timestamp': DateTime.now().toIso8601String(),
|
||||
'metrics': {
|
||||
for (var e in metrics.entries) e.key: e.value.toJson(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/// Reset all metrics
|
||||
static void reset() {
|
||||
metrics.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// Container for video stream metrics
|
||||
class VideoMetrics {
|
||||
final String streamName;
|
||||
final DateTime startTime;
|
||||
final DateTime loadStart;
|
||||
|
||||
int? loadTime; // ms
|
||||
int? firstFrameTime; // ms
|
||||
int? peakMemoryMB;
|
||||
double? avgCpuPercent;
|
||||
int? seekResponseMs;
|
||||
DateTime? endTime;
|
||||
int? totalDurationMs;
|
||||
|
||||
VideoMetrics({
|
||||
required this.streamName,
|
||||
required this.startTime,
|
||||
required this.loadStart,
|
||||
});
|
||||
|
||||
void printMetrics() {
|
||||
print(' Load Time: ${loadTime ?? "N/A"}ms');
|
||||
print(' First Frame: ${firstFrameTime ?? "N/A"}ms');
|
||||
print(' Peak Memory: ${peakMemoryMB ?? "N/A"}MB');
|
||||
print(' Avg CPU: ${avgCpuPercent?.toStringAsFixed(1) ?? "N/A"}%');
|
||||
print(' Seek Response: ${seekResponseMs ?? "N/A"}ms');
|
||||
print(' Total Duration: ${totalDurationMs ?? "N/A"}ms');
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'streamName': streamName,
|
||||
'loadTime': loadTime,
|
||||
'firstFrameTime': firstFrameTime,
|
||||
'peakMemoryMB': peakMemoryMB,
|
||||
'avgCpuPercent': avgCpuPercent,
|
||||
'seekResponseMs': seekResponseMs,
|
||||
'totalDurationMs': totalDurationMs,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/// Xtream API Data Models - Mobile Version
|
||||
///
|
||||
/// This is a minimal stub to support mobile imports.
|
||||
/// Full models are in core/models/iptv_models.dart
|
||||
library;
|
||||
|
||||
// Re-export from core for compatibility
|
||||
export '../../../core/models/iptv_models.dart';
|
||||
@@ -0,0 +1,14 @@
|
||||
/// Xtream Codes API Service - Mobile Version
|
||||
///
|
||||
/// Handles Xtream API communication for mobile platforms.
|
||||
/// This is a minimal stub for import compatibility.
|
||||
library;
|
||||
|
||||
import '../models/xtream_models.dart' as xm;
|
||||
|
||||
/// Xtream API Service for Mobile
|
||||
///
|
||||
/// Placeholder implementation for mobile video streaming.
|
||||
class XtreamServiceMobile {
|
||||
// Minimal stub implementation
|
||||
}
|
||||
Reference in new issue
Block a user