jamtop 0.1.26

A version of the classic Unix CLI utility top but for a JAM instance
use std::fmt::Display;

use corevm_tooling::CoreVmCodeInfo;
use crossterm::style::Stylize;
use jam_program_blob_common::CrateInfo;
use jam_std_common::{Service, ServiceActivityRecord};
use jam_tooling::{
	format::{amount, bytes, gas, percent},
	CodeInfo,
};
use jam_types::{core_count, hex, max_accumulate_gas, max_refine_gas, ServiceId, UnsignedGas};

use crate::{ACC_COL, PROV_COL, REF_COL};

#[allow(dead_code)]
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum Column {
	Id,
	Version,
	Name,
	Flags,
	Items,
	RefPercent,
	RefPercentCore,
	RefGas,
	RefItems,
	AccPercent,
	AccPercentCore,
	AccGas,
	AccItems,
	PreimageSize,
	PreimageCount,
	ExportsCount,
	ImportsCount,
	ExtrinsicsCount,
	ExtrinsicsSize,
	Balance,
	MinBalance,
	FreeBalance,
	StorageSize,
	StorageItems,
}
impl Display for Column {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		use Column::*;
		match self {
			Id => write!(f, "ID"),
			Version => write!(f, "VERSION"),
			Name => write!(f, "NAME"),
			Flags => write!(f, "FLGS"),
			Items => write!(f, "ITMS"),
			RefPercent => write!(f, "%RG"),
			RefPercentCore => write!(f, "%RGC"),
			RefGas => write!(f, "RGAS"),
			RefItems => write!(f, "REFS"),
			AccPercent => write!(f, "%AG"),
			AccPercentCore => write!(f, "%AGC"),
			AccGas => write!(f, "AGAS"),
			AccItems => write!(f, "ACCS"),
			PreimageSize => write!(f, "PRVS"),
			PreimageCount => write!(f, "PRVZ"),
			ImportsCount => write!(f, "IMPS"),
			ExportsCount => write!(f, "EXPS"),
			ExtrinsicsCount => write!(f, "XTS"),
			ExtrinsicsSize => write!(f, "XTZ"),
			Balance => write!(f, "BAL"),
			MinBalance => write!(f, "MBAL"),
			FreeBalance => write!(f, "FBAL"),
			StorageItems => write!(f, "STRS"),
			StorageSize => write!(f, "STRZ"),
		}
	}
}
impl Column {
	pub fn extract(
		&self,
		id: ServiceId,
		info: &Service,
		maybe_meta: &CodeInfo<CrateInfo>,
		maybe_inner_meta: &Option<CoreVmCodeInfo>,
		activity: &ServiceActivityRecord,
	) -> String {
		use CodeInfo::*;
		use Column::*;
		let max_ref_gas = max_refine_gas() * core_count() as UnsignedGas;
		let max_acc_gas = max_accumulate_gas() * core_count() as UnsignedGas;
		match (self, maybe_meta.as_ref(), maybe_inner_meta.as_ref()) {
			(Id, _, _) => format!("{id:08x}"),
			(Items, _, _) => amount(activity.refinement_count + activity.accumulate_count),
			(Flags, _, _) => format!(
				"{}{}{}",
				if activity.refinement_count > 0 {
					"R".bold().with(REF_COL)
				} else {
					"-".dark_grey()
				},
				if activity.accumulate_count > 0 {
					"A".bold().with(ACC_COL)
				} else {
					"-".dark_grey()
				},
				if activity.provided_count > 0 {
					"P".bold().with(PROV_COL)
				} else {
					"-".dark_grey()
				},
			),
			(RefPercent, _, _) => percent(activity.refinement_gas_used, max_ref_gas).to_string(),
			(RefPercentCore, _, _) =>
				percent(activity.refinement_gas_used, max_refine_gas()).to_string(),
			(RefGas, _, _) => gas(activity.refinement_gas_used),
			(RefItems, _, _) => amount(activity.refinement_count),
			(AccPercent, _, _) => percent(activity.accumulate_gas_used, max_acc_gas).to_string(),
			(AccPercentCore, _, _) =>
				percent(activity.accumulate_gas_used, max_accumulate_gas()).to_string(),
			(AccGas, _, _) => gas(activity.accumulate_gas_used),
			(AccItems, _, _) => amount(activity.accumulate_count),
			(Name, CodeNotProvided(h), _) => format!("{}…???", hex::to_hex(&h[..4])),
			(Name, _, Some(CoreVmCodeInfo::NotProvided(r))) =>
				format!("{} {}…???", "vm:".dark_grey(), hex::to_hex(&r.hash.0[..4])),
			(Version, CodeNotProvided(_), _) |
			(Version, _, Some(CoreVmCodeInfo::NotProvided(_))) => String::new(),
			(Name, Undefined(h), _) => format!("{}…", hex::to_hex(&h[..4])),
			(Name, _, Some(CoreVmCodeInfo::Undefined(r))) =>
				format!("{} {}…", "vm:".dark_grey(), hex::to_hex(&r.hash.0[..4])),
			(Version, Undefined(..), _) | (Version, _, Some(CoreVmCodeInfo::Undefined(..))) =>
				String::new(),
			(Name, Known(meta), Some(CoreVmCodeInfo::Known(inner_meta))) =>
				if meta.name == "corevm" {
					format!("{} {}", "vm:".dark_grey(), self.fit_minus(&inner_meta.name, 4))
				} else {
					self.fit(&format!("{}: {}", meta.name, inner_meta.name))
				},
			(Name, Known(meta), _) => self.fit(&meta.name),
			(Version, Known(meta), _) => self.fit(&meta.version),
			(PreimageSize, _, _) => bytes(activity.provided_size),
			(PreimageCount, _, _) => amount(activity.provided_count),
			(ImportsCount, _, _) => amount(activity.imports),
			(ExportsCount, _, _) => amount(activity.exports),
			(ExtrinsicsSize, _, _) => bytes(activity.extrinsic_size),
			(ExtrinsicsCount, _, _) => amount(activity.extrinsic_count),
			(Balance, _, _) => amount(info.balance),
			(MinBalance, _, _) => amount(info.threshold()),
			(FreeBalance, _, _) => amount(info.free()),
			(StorageSize, _, _) => bytes(info.bytes),
			(StorageItems, _, _) => amount(info.items),
		}
	}
	pub fn width(&self) -> usize {
		use Column::*;
		format!("{self}").len().max(match self {
			Id => 8,
			Name => 20,
			Version => 7,
			Flags => 4,
			Items => 4,
			RefPercent => 5,
			RefPercentCore => 5,
			RefGas => 5,
			RefItems => 4,
			AccPercent => 5,
			AccPercentCore => 5,
			AccGas => 5,
			AccItems => 4,
			PreimageSize => 5,
			PreimageCount => 4,
			ImportsCount => 5,
			ExportsCount => 5,
			ExtrinsicsSize => 5,
			ExtrinsicsCount => 4,
			Balance => 5,
			MinBalance => 5,
			FreeBalance => 5,
			StorageSize => 5,
			StorageItems => 4,
		})
	}
	pub fn fit(&self, s: &str) -> String {
		if s.len() <= self.width() || matches!(self, Column::Flags) {
			s.to_string()
		} else {
			format!("{}…", s.split_at(self.width() - 1).0)
		}
	}
	pub fn fit_minus(&self, s: &str, m: usize) -> String {
		if s.len() + m <= self.width() || matches!(self, Column::Flags) {
			s.to_string()
		} else {
			format!("{}…", s.split_at(self.width().max(m + 1) - m - 1).0)
		}
	}
	pub fn sort_services(
		&self,
		mut services: Vec<(
			ServiceId,
			(Service, CodeInfo<CrateInfo>, Option<CoreVmCodeInfo>, ServiceActivityRecord),
		)>,
	) -> Vec<(
		ServiceId,
		(Service, CodeInfo<CrateInfo>, Option<CoreVmCodeInfo>, ServiceActivityRecord),
	)> {
		use Column::*;
		match self {
			Id => services.sort_by_key(|&(id, ..)| id),
			Name =>
				services.sort_by_key(|&(_, (_, ref meta, _, _))| meta.clone().map(|x| x.name)),
			Version =>
				services.sort_by_key(|&(_, (_, ref meta, _, _))| meta.clone().map(|x| x.version)),
			Flags => services.sort_by_key(|&(_, (_, _, _, ref stats))|
				if stats.refinement_count > 0 { -4 } else { 0 }
				+ if stats.accumulate_count > 0 { -2 } else { 0 }
				+ if stats.provided_count > 0 { -1 } else { 0 }
			),
			Items =>
				services.sort_by_key(|&(_, (_, _, _, ref stats))| -((stats.refinement_count + stats.accumulate_count) as i128)),
			RefGas | RefPercent | RefPercentCore =>
				services.sort_by_key(|&(_, (_, _, _, ref stats))| -(stats.refinement_gas_used as i128)),
			RefItems =>
				services.sort_by_key(|&(_, (_, _, _, ref stats))| -(stats.refinement_count as i128)),
			AccGas | AccPercent | AccPercentCore =>
				services.sort_by_key(|&(_, (_, _, _, ref stats))| -(stats.accumulate_gas_used as i128)),
			AccItems =>
				services.sort_by_key(|&(_, (_, _, _, ref stats))| -(stats.accumulate_count as i128)),
			PreimageSize =>
				services.sort_by_key(|&(_, (_, _, _, ref stats))| -(stats.provided_size as i128)),
			PreimageCount =>
				services.sort_by_key(|&(_, (_, _, _, ref stats))| -(stats.provided_count as i128)),
			ImportsCount =>
				services.sort_by_key(|&(_, (_, _, _, ref stats))| -(stats.imports as i128)),
			ExportsCount =>
				services.sort_by_key(|&(_, (_, _, _, ref stats))| -(stats.exports as i128)),
			ExtrinsicsSize =>
				services.sort_by_key(|&(_, (_, _, _, ref stats))| -(stats.extrinsic_size as i128)),
			ExtrinsicsCount =>
				services.sort_by_key(|&(_, (_, _, _, ref stats))| -(stats.extrinsic_count as i128)),
			Balance =>
				services.sort_by_key(|&(_, (ref info, _, _, _))| -(info.balance as i128)),
			MinBalance =>
				services.sort_by_key(|&(_, (ref info, _, _, _))| -(info.threshold() as i128)),
			FreeBalance =>
				services.sort_by_key(|&(_, (ref info, _, _, _))| -(info.free() as i128)),
			StorageSize =>
				services.sort_by_key(|&(_, (ref info, _, _, _))| -(info.bytes as i128)),
			StorageItems =>
				services.sort_by_key(|&(_, (ref info, _, _, _))| -(info.items as i128)),
		}
		services
	}
}