refactor: DockerData refactors

Use a croner in the docker_data, instead of in the ui thread, as this thread will be paused when in exec mode.

is_initilised is again done in docker_data, after stats have been calculated

use bollard from git, waiting for new release due to Docker changes
This commit is contained in:
Jack Wills
2023-11-15 16:12:18 +00:00
parent 6a4cf6490d
commit 650aa0fc91
3 changed files with 122 additions and 60 deletions
+3 -1
View File
@@ -13,7 +13,9 @@ categories = ["command-line-utilities"]
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
bollard = "0.15" # bollard = "0.15"
bollard = { git = "https://www.github.com/fussybeaver/bollard.git", rev = "cef1cd5" }
cansi = "2.2" cansi = "2.2"
clap = { version = "4.4", features = ["derive", "unicode", "color"] } clap = { version = "4.4", features = ["derive", "unicode", "color"] }
crossterm = "0.27" crossterm = "0.27"
+10 -1
View File
@@ -121,6 +121,9 @@ pub enum State {
} }
impl State { impl State {
pub const fn is_alive(self) -> bool {
matches!(self, Self::Running)
}
pub const fn get_color(self) -> Color { pub const fn get_color(self) -> Color {
match self { match self {
Self::Paused => Color::Yellow, Self::Paused => Color::Yellow,
@@ -158,6 +161,12 @@ impl From<&str> for State {
} }
} }
impl From<Option<String>> for State {
fn from(input: Option<String>) -> Self {
input.map_or(Self::Unknown, |input| Self::from(input.as_str()))
}
}
impl fmt::Display for State { impl fmt::Display for State {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let disp = match self { let disp = match self {
@@ -216,7 +225,7 @@ impl fmt::Display for DockerControls {
Self::Restart => "restart", Self::Restart => "restart",
Self::Start => "start", Self::Start => "start",
Self::Stop => "stop", Self::Stop => "stop",
Self::Unpause => "unpause", Self::Unpause => "resume",
}; };
write!(f, "{disp}") write!(f, "{disp}")
} }
+109 -58
View File
@@ -10,13 +10,19 @@ use futures_util::StreamExt;
use parking_lot::Mutex; use parking_lot::Mutex;
use std::{ use std::{
collections::HashMap, collections::HashMap,
sync::{atomic::AtomicBool, Arc}, sync::{
atomic::{AtomicBool, AtomicUsize},
Arc,
},
};
use tokio::{
sync::mpsc::{Receiver, Sender},
task::JoinHandle,
}; };
use tokio::{sync::mpsc::Receiver, task::JoinHandle};
use uuid::Uuid; use uuid::Uuid;
use crate::{ use crate::{
app_data::{AppData, ContainerId, DockerControls}, app_data::{AppData, ContainerId, DockerControls, State},
app_error::AppError, app_error::AppError,
parse_args::CliArgs, parse_args::CliArgs,
ui::{GuiState, Status}, ui::{GuiState, Status},
@@ -50,6 +56,11 @@ impl Binate {
} }
} }
// struct Init {
// done: ,
// len:
// }
pub struct DockerData { pub struct DockerData {
app_data: Arc<Mutex<AppData>>, app_data: Arc<Mutex<AppData>>,
args: CliArgs, args: CliArgs,
@@ -57,6 +68,7 @@ pub struct DockerData {
docker: Arc<Docker>, docker: Arc<Docker>,
gui_state: Arc<Mutex<GuiState>>, gui_state: Arc<Mutex<GuiState>>,
is_running: Arc<AtomicBool>, is_running: Arc<AtomicBool>,
init: Option<Arc<AtomicUsize>>,
receiver: Receiver<DockerMessage>, receiver: Receiver<DockerMessage>,
spawns: Arc<Mutex<HashMap<SpawnId, JoinHandle<()>>>>, spawns: Arc<Mutex<HashMap<SpawnId, JoinHandle<()>>>>,
} }
@@ -96,67 +108,93 @@ impl DockerData {
app_data: Arc<Mutex<AppData>>, app_data: Arc<Mutex<AppData>>,
docker: Arc<Docker>, docker: Arc<Docker>,
id: ContainerId, id: ContainerId,
is_running: bool, init: Option<(Arc<AtomicUsize>, usize)>,
state: State,
spawn_id: SpawnId, spawn_id: SpawnId,
spawns: Arc<Mutex<HashMap<SpawnId, JoinHandle<()>>>>, spawns: Arc<Mutex<HashMap<SpawnId, JoinHandle<()>>>>,
) { ) {
// if dead and !init then inspect!
let mut stream = docker if state.is_alive() || init.is_some() {
.stats( // // if state == State::Paused && init.is_some() {
id.get(), // // app_data.lock().debug_string.push_str("is paused");
Some(StatsOptions {
stream: false,
one_shot: !is_running,
}),
)
.take(1);
while let Some(Ok(stats)) = stream.next().await { // // if let Ok(result) = docker.inspect_container(id.get(), Some(InspectContainerOptions{size:false})).await {
let mem_stat = stats.memory_stats.usage.unwrap_or_default(); // // let mem_limit = format!("{}", result.host_config.map_or(0, |i|i.memory.unwrap_or_default()));
let mem_limit = stats.memory_stats.limit.unwrap_or_default();
let op_key = stats // // app_data.lock().debug_string.push_str(&mem_limit);
.networks // // }
.as_ref()
.and_then(|networks| networks.keys().next().cloned());
let cpu_stats = Self::calculate_usage(&stats); // // }else if state.is_alive() || init.is_some() {
let mut stream = docker
.stats(
id.get(),
Some(StatsOptions {
stream: false,
one_shot: false,
}),
)
.take(1);
let (rx, tx) = if let Some(key) = op_key { // let a = stream.next().await;
stats // app_data.lock().debug_string.push_str(&format!("{:?}", a.is_some()));
// let bb = a.unwrap().unwrap();
// // }
// app_data.lock().debug_string.push_str("jkl");
while let Some(Ok(stats)) = stream.next().await {
let mem_stat = if state.is_alive() {
Some(stats.memory_stats.usage.unwrap_or_default())
} else {
None
};
let mem_limit = stats.memory_stats.limit.unwrap_or_default();
let op_key = stats
.networks .networks
.unwrap_or_default() .as_ref()
.get(&key) .and_then(|networks| networks.keys().next().cloned());
.map_or((0, 0), |f| (f.rx_bytes, f.tx_bytes))
} else { let cpu_stats = if state.is_alive() {
(0, 0) Some(Self::calculate_usage(&stats))
}; } else {
None
};
let (rx, tx) = if let Some(key) = op_key {
stats
.networks
.unwrap_or_default()
.get(&key)
.map_or((0, 0), |f| (f.rx_bytes, f.tx_bytes))
} else {
(0, 0)
};
if is_running {
app_data.lock().update_stats(
&id,
Some(cpu_stats),
Some(mem_stat),
mem_limit,
rx,
tx,
);
} else {
app_data app_data
.lock() .lock()
.update_stats(&id, None, None, mem_limit, rx, tx); .update_stats(&id, cpu_stats, mem_stat, mem_limit, rx, tx);
} }
spawns.lock().remove(&spawn_id); }
spawns.lock().remove(&spawn_id);
if let Some((target, _)) = init {
target.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
} }
} }
/// Update all stats, spawn each container into own tokio::spawn thread /// Update all stats, spawn each container into own tokio::spawn thread
fn update_all_container_stats(&mut self, all_ids: &[(bool, ContainerId)]) { fn update_all_container_stats(&mut self, all_ids: &[(State, ContainerId)]) {
for (is_running, id) in all_ids { // let thing =all_ids.len();
for (state, id) in all_ids {
// let init = self.init.as_ref().map_or_else(|| None, |x| Some((Arc::clone(x), all_ids.len())));
let docker = Arc::clone(&self.docker); let docker = Arc::clone(&self.docker);
let app_data = Arc::clone(&self.app_data); let app_data = Arc::clone(&self.app_data);
let spawns = Arc::clone(&self.spawns); let spawns = Arc::clone(&self.spawns);
let spawn_id = SpawnId::Stats((id.clone(), self.binate)); let spawn_id = SpawnId::Stats((id.clone(), self.binate));
let init = self.init.as_ref().map(|i| (Arc::clone(i), all_ids.len()));
self.spawns self.spawns
.lock() .lock()
.entry(spawn_id.clone()) .entry(spawn_id.clone())
@@ -165,7 +203,8 @@ impl DockerData {
app_data, app_data,
docker, docker,
id.clone(), id.clone(),
*is_running, init,
*state,
spawn_id, spawn_id,
spawns, spawns,
)) ))
@@ -177,7 +216,7 @@ impl DockerData {
/// Get all current containers, handle into ContainerItem in the app_data struct rather than here /// Get all current containers, handle into ContainerItem in the app_data struct rather than here
/// Just make sure that items sent are guaranteed to have an id /// Just make sure that items sent are guaranteed to have an id
/// If in a containerised runtime, will ignore any container that uses the `/app/oxker` as an entry point, unless the `-s` flag is set /// If in a containerised runtime, will ignore any container that uses the `/app/oxker` as an entry point, unless the `-s` flag is set
pub async fn update_all_containers(&mut self) -> Vec<(bool, ContainerId)> { pub async fn update_all_containers(&mut self) -> Vec<(State, ContainerId)> {
let containers = self let containers = self
.docker .docker
.list_containers(Some(ListContainersOptions::<String> { .list_containers(Some(ListContainersOptions::<String> {
@@ -212,13 +251,7 @@ impl DockerData {
output output
.into_iter() .into_iter()
.filter_map(|i| { .filter_map(|i| {
i.id.map(|id| { i.id.map(|id| (State::from(i.state), ContainerId::from(id.as_str())))
(
i.state == Some("running".to_owned())
|| i.state == Some("restarting".to_owned()),
ContainerId::from(id.as_str()),
)
})
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
} }
@@ -253,7 +286,7 @@ impl DockerData {
} }
/// Update all logs, spawn each container into own tokio::spawn thread /// Update all logs, spawn each container into own tokio::spawn thread
fn init_all_logs(&mut self, all_ids: &[(bool, ContainerId)]) { fn init_all_logs(&mut self, all_ids: &[(State, ContainerId)]) {
for (_, id) in all_ids { for (_, id) in all_ids {
let docker = Arc::clone(&self.docker); let docker = Arc::clone(&self.docker);
let app_data = Arc::clone(&self.app_data); let app_data = Arc::clone(&self.app_data);
@@ -275,6 +308,7 @@ impl DockerData {
.lock() .lock()
.entry(SpawnId::Log(container.id.clone())) .entry(SpawnId::Log(container.id.clone()))
.or_insert_with(|| { .or_insert_with(|| {
// TODO make a struct that can create this data
let app_data = Arc::clone(&self.app_data); let app_data = Arc::clone(&self.app_data);
let docker = Arc::clone(&self.docker); let docker = Arc::clone(&self.docker);
let id = container.id.clone(); let id = container.id.clone();
@@ -291,17 +325,17 @@ impl DockerData {
self.gui_state.lock().status_push(Status::Init); self.gui_state.lock().status_push(Status::Init);
let loading_uuid = Uuid::new_v4(); let loading_uuid = Uuid::new_v4();
let loading_handle = GuiState::start_loading_animation(&self.gui_state, loading_uuid); let loading_handle = GuiState::start_loading_animation(&self.gui_state, loading_uuid);
// let handle = self.gui_state.lock().st
let all_ids = self.update_all_containers().await; let all_ids = self.update_all_containers().await;
self.update_all_container_stats(&all_ids); self.update_all_container_stats(&all_ids);
self.init_all_logs(&all_ids); self.init_all_logs(&all_ids);
// wait until all logs have initialised while let Some(x) = self.init.as_ref() {
while !self.app_data.lock().initialised(&all_ids) {
tokio::time::sleep(std::time::Duration::from_millis(100)).await; tokio::time::sleep(std::time::Duration::from_millis(100)).await;
if x.load(std::sync::atomic::Ordering::SeqCst) == all_ids.len() {
self.init = None;
}
} }
self.gui_state self.gui_state
.lock() .lock()
@@ -422,11 +456,26 @@ impl DockerData {
} }
} }
/// Send an update message every x ms, where x is the args.docker_interval
fn croner(args: &CliArgs, docker_tx: Sender<DockerMessage>) {
let update_duration = std::time::Duration::from_millis(u64::from(args.docker_interval));
let mut now = std::time::Instant::now();
tokio::spawn(async move {
loop {
let to_sleep = update_duration.saturating_sub(now.elapsed());
tokio::time::sleep(to_sleep).await;
docker_tx.send(DockerMessage::Update).await.ok();
now = std::time::Instant::now();
}
});
}
/// Initialise self, and start the message receiving loop /// Initialise self, and start the message receiving loop
pub async fn init( pub async fn init(
app_data: Arc<Mutex<AppData>>, app_data: Arc<Mutex<AppData>>,
docker: Docker, docker: Docker,
docker_rx: Receiver<DockerMessage>, docker_rx: Receiver<DockerMessage>,
docker_tx: Sender<DockerMessage>,
gui_state: Arc<Mutex<GuiState>>, gui_state: Arc<Mutex<GuiState>>,
is_running: Arc<AtomicBool>, is_running: Arc<AtomicBool>,
) { ) {
@@ -434,15 +483,17 @@ impl DockerData {
if app_data.lock().get_error().is_none() { if app_data.lock().get_error().is_none() {
let mut inner = Self { let mut inner = Self {
app_data, app_data,
args, args: args.clone(),
binate: Binate::One, binate: Binate::One,
docker: Arc::new(docker), docker: Arc::new(docker),
gui_state, gui_state,
init: Some(Arc::new(AtomicUsize::new(0))),
is_running, is_running,
receiver: docker_rx, receiver: docker_rx,
spawns: Arc::new(Mutex::new(HashMap::new())), spawns: Arc::new(Mutex::new(HashMap::new())),
}; };
inner.initialise_container_data().await; inner.initialise_container_data().await;
Self::croner(&args, docker_tx);
inner.message_handler().await; inner.message_handler().await;
} }
} }