Skip to content

Commit

Permalink
feat: diving supports ci mode
Browse files Browse the repository at this point in the history
  • Loading branch information
vicanso committed Jan 11, 2024
1 parent 7862bd5 commit 7f67929
Show file tree
Hide file tree
Showing 12 changed files with 285 additions and 136 deletions.
155 changes: 82 additions & 73 deletions Cargo.lock

Large diffs are not rendered by default.

21 changes: 11 additions & 10 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@ license = "Apache-2.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
axum = "0.7.2"
axum = "0.7.3"
axum-client-ip = "0.5.0"
bytes = "1.5.0"
bytesize = "1.3.0"
bytesize = {version = "1.3.0", features = ["serde"]}
chrono = "0.4.31"
clap = { version = "4.4.11", features = ["derive"] }
clap = { version = "4.4.14", features = ["derive"] }
colored = "2.1.0"
config = "0.13.4"
crossterm = "0.27.0"
futures = "0.3.29"
futures = "0.3.30"
glob = "0.3.1"
hex = "0.4.3"
home = "0.5.9"
Expand All @@ -32,16 +33,16 @@ pad = "0.1.6"
ratatui = "0.25.0"
regex = "1.10.2"
reqwest = { version = "0.11.23", default-features = false, features = ["rustls-tls", "json"] }
rust-embed = { version = "8.1.0", features = ["compression", "mime-guess"] }
serde = { version = "1.0.193", features = ["derive"] }
serde_json = "1.0.108"
serde_repr = "0.1.17"
rust-embed = { version = "8.2.0", features = ["compression", "mime-guess"] }
serde = { version = "1.0.195", features = ["derive"] }
serde_json = "1.0.111"
serde_repr = "0.1.18"
signal-hook = { version = "0.3.17", default-features = false }
signal-hook-registry = "1.4.1"
snafu = "0.7.5"
snafu = "0.8.0"
substring = "1.4.5"
tar = "0.4.40"
tempfile = "3.8.1"
tempfile = "3.8.0"
time = "0.3.31"
tokio = { version = "1.35.1", features = ["macros", "rt", "rt-multi-thread", "net", "signal", "fs"] }
tokio-cron-scheduler = "0.9.4"
Expand Down
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ dev-web:
cargo watch -w src -x 'run -- --mode=web'
dev-docker:
cargo run -- docker://redis:alpine
dev-ci:
CI=true cargo run -- redis:alpine?arch=amd64

udeps:
cargo +nightly udeps
Expand Down
6 changes: 5 additions & 1 deletion config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,8 @@ layer_path: /opt/diving/layers
# default is 90d
layer_ttl: 180d
# no default value
# threads: 2
# threads: 2
# If the efficiency is measured below X%, mark as failed.
lowest_efficiency: 0.95
# If the amount of wasted space is at least X or larger than X, mark as failed.
highest_wasted_bytes: 20MB
28 changes: 28 additions & 0 deletions src/config/load_config.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use bytesize::ByteSize;
use config::{Config, File};
use home::home_dir;
use once_cell::sync::OnceCell;
Expand All @@ -9,6 +10,9 @@ pub struct DivingConfig {
pub layer_path: Option<String>,
pub layer_ttl: Option<String>,
pub threads: Option<usize>,
pub lowest_efficiency: Option<f64>,
pub highest_wasted_bytes: Option<ByteSize>,
pub highest_user_wasted_percent: Option<f64>,
}

pub fn must_load_config() -> &'static DivingConfig {
Expand Down Expand Up @@ -54,3 +58,27 @@ pub fn get_layer_path() -> &'static PathBuf {
layer_path
})
}

pub fn get_lowest_efficiency() -> f64 {
let config = must_load_config();
if let Some(lowest_efficiency) = config.lowest_efficiency {
return lowest_efficiency;
}
0.95
}

pub fn get_highest_wasted_bytes() -> u64 {
let config = must_load_config();
if let Some(highest_wasted_bytes) = config.highest_wasted_bytes {
return highest_wasted_bytes.0;
}
20 * 1024 * 1024
}

pub fn get_highest_user_wasted_percent() -> f64 {
let config = must_load_config();
if let Some(highest_user_wasted_percent) = config.highest_user_wasted_percent {
return highest_user_wasted_percent;
}
0.2
}
5 changes: 4 additions & 1 deletion src/config/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
mod load_config;

pub use self::load_config::{get_config_path, get_layer_path, must_load_config};
pub use self::load_config::{
get_config_path, get_highest_user_wasted_percent, get_highest_wasted_bytes, get_layer_path,
get_lowest_efficiency, must_load_config,
};
54 changes: 54 additions & 0 deletions src/image/docker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,60 @@ pub struct DockerAnalyzeResult {
pub big_modified_file_list: Vec<BigModifiedFileInfo>,
}

#[derive(Default, Debug, Clone, PartialEq)]
pub struct ImageFileWastedSummary {
pub path: String,
pub total_size: u64,
pub count: u32,
}

#[derive(Default, Debug, Clone)]
pub struct DockerAnalyzeSummary {
pub wasted_list: Vec<ImageFileWastedSummary>,
pub wasted_size: u64,
pub wasted_percent: f64,
pub score: u64,
}

impl DockerAnalyzeResult {
pub fn summary(&self) -> DockerAnalyzeSummary {
let mut wasted_list: Vec<ImageFileWastedSummary> = vec![];
let mut wasted_size = 0;
for file in self.file_summary_list.iter() {
let mut found = false;
let info = &file.info;
wasted_size += info.size;
for wasted in wasted_list.iter_mut() {
if wasted.path == info.path {
found = true;
wasted.count += 1;
wasted.total_size += info.size;
}
}
if !found {
wasted_list.push(ImageFileWastedSummary {
path: info.path.clone(),
count: 1,
total_size: info.size,
});
}
}
wasted_list.sort_by(|a, b| b.total_size.cmp(&a.total_size));

let mut score = 100 - wasted_size * 100 / self.total_size;
// 有浪费空间,则分数-1
if wasted_size != 0 {
score -= 1;
}
DockerAnalyzeSummary {
wasted_list,
wasted_size,
wasted_percent: (wasted_size as f64) / (self.total_size as f64),
score,
}
}
}

impl DockerTokenInfo {
// 判断docker token是否已过期
fn expired(&self) -> bool {
Expand Down
4 changes: 3 additions & 1 deletion src/image/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ mod docker;
mod layer;
mod oci_image;

pub use docker::{analyze_docker_image, parse_image_info, DockerAnalyzeResult};
pub use docker::{
analyze_docker_image, parse_image_info, DockerAnalyzeResult, DockerAnalyzeSummary,
};
pub use layer::{
get_file_content_from_layer, get_file_content_from_tar, get_file_size_from_tar,
get_files_from_layer,
Expand Down
54 changes: 52 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use axum::{error_handling::HandleErrorLayer, middleware::from_fn, Router};
use bytesize::ByteSize;
use clap::Parser;
use colored::*;
use std::net::SocketAddr;
use std::time::Duration;
use std::{env, str::FromStr};
Expand Down Expand Up @@ -91,6 +93,10 @@ async fn start_scheduler() {
scheduler.start().await.unwrap();
}

fn is_ci() -> bool {
env::var_os("CI").unwrap_or_default() == "true"
}

// 分析镜像(错误直接以字符串返回)
async fn analyze(image: String) -> Result<(), String> {
// 命令行模式下清除过期数据
Expand All @@ -99,7 +105,50 @@ async fn analyze(image: String) -> Result<(), String> {
let result = analyze_docker_image(image_info)
.await
.map_err(|item| item.to_string())?;
ui::run_app(result).map_err(|item| item.to_string())?;
if is_ci() {
let summary = result.summary();
let lowest_efficiency = (config::get_lowest_efficiency() * 100.0) as u64;
let highest_wasted_bytes = config::get_highest_wasted_bytes();
let highest_user_wasted_percent = config::get_highest_user_wasted_percent();
println!("{}", "Analyze result:".bold().green());
println!(" efficiency: {} %", summary.score);
println!(
" wasted bytes: {} bytes ({})",
summary.wasted_size,
ByteSize(summary.wasted_size)
);

let mut passed = true;
if summary.score < lowest_efficiency {
println!(
"{}: lowest efficiency check, lowest: {}",
"FAIL".red(),
lowest_efficiency
);
passed = false;
}
if summary.wasted_size > highest_wasted_bytes {
println!(
"{}: highest wasted bytes check, highest: {}",
"FAIL".red(),
ByteSize(highest_wasted_bytes)
);
passed = false;
}
if summary.wasted_percent > highest_user_wasted_percent {
println!(
"{}: highest user wasted percent check, highest: {:.2}",
"FAIL".red(),
highest_user_wasted_percent
);
passed = false;
}
if !passed {
return Err("CI check fail".to_string());
}
} else {
ui::run_app(result).map_err(|item| item.to_string())?;
}
Ok(())
}

Expand All @@ -114,7 +163,8 @@ async fn run() {
TRACE_ID
.scope(generate_trace_id(), async {
if let Err(err) = analyze(value).await {
error!(err, "analyze image fail")
error!(err, "analyze image fail");
std::process::exit(1)
}
})
.await;
Expand Down
68 changes: 32 additions & 36 deletions src/ui/image_detail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use pad::PadStr;
use ratatui::{prelude::*, widgets::*};

use super::util;
use crate::image::ImageFileSummary;
use crate::image::DockerAnalyzeSummary;

pub struct ImageDetailWidget<'a> {
pub widget: Paragraph<'a>,
Expand All @@ -15,48 +15,44 @@ pub struct ImageDetailWidgetOption {
pub os: String,
pub total_size: u64,
pub size: u64,
pub file_summary_list: Vec<ImageFileSummary>,
}

#[derive(Default, Debug, Clone, PartialEq)]
struct ImageFileWastedSummary {
pub path: String,
pub total_size: u64,
pub count: u32,
pub summary: DockerAnalyzeSummary,
}

pub fn new_image_detail_widget<'a>(opt: ImageDetailWidgetOption) -> ImageDetailWidget<'a> {
let total_size = opt.total_size;
let size = opt.size;
let wasted_size = opt.summary.wasted_size;
let score = opt.summary.score;
let wasted_list = opt.summary.wasted_list;

let mut wasted_list: Vec<ImageFileWastedSummary> = vec![];
let mut wasted_size = 0;
for file in opt.file_summary_list.iter() {
let mut found = false;
let info = &file.info;
wasted_size += info.size;
for wasted in wasted_list.iter_mut() {
if wasted.path == info.path {
found = true;
wasted.count += 1;
wasted.total_size += info.size;
}
}
if !found {
wasted_list.push(ImageFileWastedSummary {
path: info.path.clone(),
count: 1,
total_size: info.size,
});
}
}
wasted_list.sort_by(|a, b| b.total_size.cmp(&a.total_size));
// let mut wasted_list: Vec<ImageFileWastedSummary> = vec![];
// let mut wasted_size = 0;
// for file in opt.file_summary_list.iter() {
// let mut found = false;
// let info = &file.info;
// wasted_size += info.size;
// for wasted in wasted_list.iter_mut() {
// if wasted.path == info.path {
// found = true;
// wasted.count += 1;
// wasted.total_size += info.size;
// }
// }
// if !found {
// wasted_list.push(ImageFileWastedSummary {
// path: info.path.clone(),
// count: 1,
// total_size: info.size,
// });
// }
// }
// wasted_list.sort_by(|a, b| b.total_size.cmp(&a.total_size));

let mut score = 100 - wasted_size * 100 / total_size;
// 有浪费空间,则分数-1
if wasted_size != 0 {
score -= 1;
}
// let mut score = 100 - wasted_size * 100 / total_size;
// // 有浪费空间,则分数-1
// if wasted_size != 0 {
// score -= 1;
// }

// 生成浪费空间的文件列表
let space_span = Span::from(" ");
Expand Down
Loading

0 comments on commit 7f67929

Please sign in to comment.