new: container stats (#272)

This commit is contained in:
lollipopkit
2024-02-20 15:00:13 +08:00
parent 483bf51c2f
commit 828752e354
19 changed files with 284 additions and 145 deletions

View File

@@ -1,14 +1,15 @@
import 'dart:async';
import 'dart:convert';
import 'package:dartssh2/dartssh2.dart';
import 'package:flutter/material.dart';
import 'package:toolbox/core/extension/listx.dart';
import 'package:toolbox/core/extension/ssh_client.dart';
import 'package:toolbox/data/model/app/shell_func.dart';
import 'package:toolbox/data/model/container/image.dart';
import 'package:toolbox/data/model/container/ps.dart';
import 'package:toolbox/data/model/app/error.dart';
import 'package:toolbox/data/model/container/type.dart';
import 'package:toolbox/data/model/container/version.dart';
import 'package:toolbox/data/res/logger.dart';
import 'package:toolbox/data/res/store.dart';
import 'package:toolbox/core/extension/uint8list.dart';
@@ -74,14 +75,20 @@ class ContainerProvider extends ChangeNotifier {
final sudo =
await _requiresSudo() && Stores.setting.containerTrySudo.fetch();
final includeStats = Stores.setting.containerParseStat.fetch();
await client?.execWithPwd(
_wrap(ContainerCmdType.execAll(type, sudo: sudo)),
final code = await client?.execWithPwd(
_wrap(ContainerCmdType.execAll(
type,
sudo: sudo,
includeStats: includeStats,
)),
context: context,
onStdout: (data, _) => raw = '$raw$data',
);
if (raw.contains(_dockerNotFound)) {
/// Code 127 means command not found
if (code == 127 || raw.contains(_dockerNotFound)) {
error = ContainerErr(type: ContainerErrType.notInstalled);
notifyListeners();
return;
@@ -99,12 +106,10 @@ class ContainerProvider extends ChangeNotifier {
return;
}
// Parse docker version
// Parse version
final verRaw = ContainerCmdType.version.find(segments);
debugPrint('version raw = $verRaw\n');
try {
final containerVersion = Containerd.fromRawJson(verRaw);
version = containerVersion.client.version;
version = json.decode(verRaw)['Client']['Version'];
} catch (e, trace) {
error = ContainerErr(
type: ContainerErrType.invalidVersion,
@@ -115,14 +120,23 @@ class ContainerProvider extends ChangeNotifier {
notifyListeners();
}
// Parse docker ps
// Parse ps
final psRaw = ContainerCmdType.ps.find(segments);
try {
final lines = psRaw.split('\n');
lines.removeWhere((element) => element.isEmpty);
items = lines.map((e) => ContainerPs.fromRawJson(e, type)).toList();
} catch (e, trace) {
error = ContainerErr(
type: ContainerErrType.parsePs,
message: '$e',
);
Loggers.parse.warning('Container ps failed', e, trace);
} finally {
notifyListeners();
}
final lines = psRaw.split('\n');
lines.removeWhere((element) => element.isEmpty);
items = lines.map((e) => ContainerPs.fromRawJson(e, type)).toList();
// Parse docker images
// Parse images
final imageRaw = ContainerCmdType.images.find(segments);
try {
final imgLines = imageRaw.split('\n');
@@ -138,29 +152,31 @@ class ContainerProvider extends ChangeNotifier {
notifyListeners();
}
// Parse docker stats
// final statsRaw = DockerCmdType.stats.find(segments);
// try {
// final statsLines = statsRaw.split('\n');
// statsLines.removeWhere((element) => element.isEmpty);
// if (statsLines.isNotEmpty) statsLines.removeAt(0);
// for (var item in items!) {
// final statsLine = statsLines.firstWhere(
// (element) => element.contains(item.containerId),
// orElse: () => '',
// );
// if (statsLine.isEmpty) continue;
// item.parseStats(statsLine);
// }
// } catch (e, trace) {
// error = DockerErr(
// type: DockerErrType.parseStats,
// message: '$e',
// );
// _logger.warning('Parse docker stats: $statsRaw', e, trace);
// } finally {
// notifyListeners();
// }
// Parse stats
final statsRaw = ContainerCmdType.stats.find(segments);
try {
final statsLines = statsRaw.split('\n');
statsLines.removeWhere((element) => element.isEmpty);
for (var item in items!) {
final id = item.id;
if (id == null) continue;
final statsLine = statsLines.firstWhereOrNull(
/// Use 5 characters to match the container id, possibility of mismatch
/// is very low.
(element) => element.contains(id.substring(0, 5)),
);
if (statsLine == null) continue;
item.parseStats(statsLine);
}
} catch (e, trace) {
error = ContainerErr(
type: ContainerErrType.parseStats,
message: '$e',
);
Loggers.parse.warning('Parse docker stats: $statsRaw', e, trace);
} finally {
notifyListeners();
}
}
Future<ContainerErr?> stop(String id) async => await run('stop $id');
@@ -223,21 +239,32 @@ const _jsonFmt = '--format "{{json .}}"';
enum ContainerCmdType {
version,
ps,
//stats,
stats,
images,
;
String exec(ContainerType type, {bool sudo = false}) {
String exec(
ContainerType type, {
bool sudo = false,
bool includeStats = false,
}) {
final prefix = sudo ? 'sudo -S ${type.name}' : type.name;
return switch (this) {
ContainerCmdType.version => '$prefix version $_jsonFmt',
ContainerCmdType.ps => '$prefix ps -a $_jsonFmt',
// DockerCmdType.stats => '$prefix stats --no-stream';
ContainerCmdType.stats =>
includeStats ? '$prefix stats --no-stream $_jsonFmt' : 'echo PASS',
ContainerCmdType.images => '$prefix image ls $_jsonFmt',
};
}
static String execAll(ContainerType type, {bool sudo = false}) => values
.map((e) => e.exec(type, sudo: sudo))
.join(' && echo $seperator && ');
static String execAll(
ContainerType type, {
bool sudo = false,
bool includeStats = false,
}) {
return ContainerCmdType.values
.map((e) => e.exec(type, sudo: sudo))
.join(' && echo $seperator && ');
}
}