#![warn(clippy::all, rust_2018_idioms)]
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use egui::{Color32, NumExt};
use rand::Rng;
use std::collections::BTreeMap;
use legion_prof_viewer::data::{
DataSourceDescription, DataSourceInfo, DataSourceMut, EntryID, EntryInfo, Field, FieldID,
FieldSchema, Item, ItemMeta, ItemUID, SlotMetaTile, SlotMetaTileData, SlotTile, SlotTileData,
SummaryTile, SummaryTileData, TileID, TileSet, UtilPoint,
};
#[cfg(not(target_arch = "wasm32"))]
use legion_prof_viewer::deferred_data::DeferredDataSourceWrapper;
use legion_prof_viewer::timestamp::{Interval, Timestamp};
#[cfg(target_arch = "wasm32")]
use legion_prof_viewer::http::client::HTTPClientDataSource;
#[cfg(target_arch = "wasm32")]
use url::Url;
#[cfg(target_arch = "wasm32")]
const DEFAULT_URL: &str = "http://127.0.0.1:8080";
#[cfg(not(target_arch = "wasm32"))]
fn main() {
legion_prof_viewer::app::start(vec![Box::new(DeferredDataSourceWrapper::new(
RandomDataSource::new(),
))]);
}
#[cfg(target_arch = "wasm32")]
fn main() {
let loc: web_sys::Location = web_sys::window().unwrap().location();
let href: String = loc.href().expect("Unable to get window URL");
let browser_url = Url::parse(&href).expect("Unable to parse location URL");
let url = Url::parse(
browser_url
.query_pairs()
.find(|(key, _)| key == "url")
.map(|(_, value)| value)
.as_deref()
.unwrap_or(DEFAULT_URL),
)
.expect("Unable to parse query URL");
legion_prof_viewer::app::start(vec![Box::new(HTTPClientDataSource::new(url))]);
}
type SlotCacheTile = (Vec<Vec<Item>>, Vec<Vec<ItemMeta>>);
struct ItemUIDGenerator {
next: ItemUID,
}
impl Default for ItemUIDGenerator {
fn default() -> Self {
Self { next: ItemUID(0) }
}
}
impl ItemUIDGenerator {
fn next(&mut self) -> ItemUID {
let result = self.next;
self.next.0 += 1;
result
}
}
struct RandomDataSource {
info: DataSourceInfo,
item_uid_field: FieldID,
interval_field: FieldID,
summary_cache: BTreeMap<EntryID, Vec<UtilPoint>>,
slot_cache: BTreeMap<EntryID, SlotCacheTile>,
rng: rand::rngs::ThreadRng,
item_uid_generator: ItemUIDGenerator,
}
impl RandomDataSource {
fn new() -> Self {
let mut rng = rand::rngs::ThreadRng::default();
let entry_info = Self::entry_info(&mut rng);
let mut field_schema = FieldSchema::new();
let item_uid_field = field_schema.insert("Item UID".to_owned(), false);
let interval_field = field_schema.insert("Interval".to_owned(), false);
let info = DataSourceInfo {
entry_info,
interval: Self::interval(&mut rng),
tile_set: TileSet::default(),
field_schema,
};
Self {
info,
item_uid_field,
interval_field,
summary_cache: BTreeMap::new(),
slot_cache: BTreeMap::new(),
rng,
item_uid_generator: ItemUIDGenerator::default(),
}
}
fn interval(rng: &mut rand::rngs::ThreadRng) -> Interval {
Interval::new(Timestamp(0), Timestamp(rng.gen_range(1_000_000..2_000_000)))
}
fn generate_point(
&mut self,
first: UtilPoint,
last: UtilPoint,
level: i32,
max_level: i32,
utilization: &mut Vec<UtilPoint>,
) {
let time = Timestamp((first.time.0 + last.time.0) / 2);
let util = (first.util + last.util) * 0.5;
let diff = (self.rng.gen::<f32>() - 0.5) / 1.2_f32.powi(max_level - level);
let util = (util + diff).at_least(0.0).at_most(1.0);
let point = UtilPoint { time, util };
if level > 0 {
self.generate_point(first, point, level - 1, max_level, utilization);
}
utilization.push(point);
if level > 0 {
self.generate_point(point, last, level - 1, max_level, utilization);
}
}
fn generate_summary(&mut self, entry_id: &EntryID) -> &Vec<UtilPoint> {
if !self.summary_cache.contains_key(entry_id) {
const LEVELS: i32 = 8;
let first = UtilPoint {
time: self.info.interval.start,
util: self.rng.gen(),
};
let last = UtilPoint {
time: self.info.interval.stop,
util: self.rng.gen(),
};
let mut utilization = Vec::new();
utilization.push(first);
self.generate_point(first, last, LEVELS, LEVELS, &mut utilization);
utilization.push(last);
self.summary_cache.insert(entry_id.clone(), utilization);
}
self.summary_cache.get(entry_id).unwrap()
}
fn generate_slot(&mut self, entry_id: &EntryID) -> &SlotCacheTile {
if !self.slot_cache.contains_key(entry_id) {
let entry = self.info.entry_info.get(entry_id);
let max_rows = if let EntryInfo::Slot { max_rows, .. } = entry.unwrap() {
max_rows
} else {
panic!("trying to fetch tile on something that is not a slot")
};
let mut items = Vec::new();
let mut item_metas = Vec::new();
for row in 0..*max_rows {
let mut row_items = Vec::new();
let mut row_item_metas = Vec::new();
const N: u64 = 1000;
for i in 0..N {
let start = self.info.interval.lerp((i as f32 + 0.05) / (N as f32));
let stop = self.info.interval.lerp((i as f32 + 0.95) / (N as f32));
let color = match (row * N + i) % 7 {
0 => Color32::BLUE,
1 => Color32::GREEN,
2 => Color32::RED,
3 => Color32::YELLOW,
4 => Color32::KHAKI,
5 => Color32::DARK_GREEN,
6 => Color32::DARK_BLUE,
_ => Color32::WHITE,
};
let item_uid = self.item_uid_generator.next();
row_items.push(Item {
item_uid,
interval: Interval::new(start, stop),
color,
});
row_item_metas.push(ItemMeta {
item_uid,
original_interval: Interval::new(start, stop),
title: "Test Item".to_owned(),
fields: vec![
(
self.interval_field,
Field::Interval(Interval::new(start, stop)),
),
(self.item_uid_field, Field::U64(item_uid.0)),
],
});
}
items.push(row_items);
item_metas.push(row_item_metas);
}
self.slot_cache
.insert(entry_id.clone(), (items, item_metas));
}
self.slot_cache.get(entry_id).unwrap()
}
fn entry_info(rng: &mut rand::rngs::ThreadRng) -> EntryInfo {
let kinds = vec![
"CPU".to_string(),
"GPU".to_string(),
"OMP".to_string(),
"Py".to_string(),
"Util".to_string(),
"Chan".to_string(),
"SysMem".to_string(),
];
const NODES: i32 = 8192;
const PROCS: i32 = 8;
let mut node_slots = Vec::new();
for node in 0..NODES {
let mut kind_slots = Vec::new();
let colors = &[Color32::BLUE, Color32::GREEN, Color32::RED, Color32::YELLOW];
for (i, kind) in kinds.iter().enumerate() {
let color = colors[i % colors.len()];
let mut proc_slots = Vec::new();
for proc in 0..PROCS {
let rows: u64 = rng.gen_range(0..64);
proc_slots.push(EntryInfo::Slot {
short_name: format!(
"{}{}",
kind.chars().next().unwrap().to_lowercase(),
proc
),
long_name: format!("Node {node} {kind} {proc}"),
max_rows: rows,
});
}
kind_slots.push(EntryInfo::Panel {
short_name: kind.to_lowercase(),
long_name: format!("Node {node} {kind}"),
summary: Some(Box::new(EntryInfo::Summary { color })),
slots: proc_slots,
});
}
node_slots.push(EntryInfo::Panel {
short_name: format!("n{node}"),
long_name: format!("Node {node}"),
summary: None,
slots: kind_slots,
});
}
EntryInfo::Panel {
short_name: "root".to_owned(),
long_name: "root".to_owned(),
summary: None,
slots: node_slots,
}
}
}
impl DataSourceMut for RandomDataSource {
fn fetch_description(&self) -> DataSourceDescription {
DataSourceDescription {
source_locator: vec!["Random Data Source".to_string()],
}
}
fn fetch_info(&mut self) -> DataSourceInfo {
self.info.clone()
}
fn fetch_summary_tile(
&mut self,
entry_id: &EntryID,
tile_id: TileID,
_full: bool,
) -> SummaryTile {
let utilization = self.generate_summary(entry_id);
let mut tile_utilization = Vec::new();
let mut last_point = None;
for point in utilization {
let UtilPoint { time, util } = *point;
if let Some(last_point) = last_point {
let UtilPoint {
time: last_time,
util: last_util,
} = last_point;
let last_interval = Interval::new(last_time, time);
if last_interval.contains(tile_id.0.start) {
let relative = last_interval.unlerp(tile_id.0.start);
let start_util = (last_util - util) * relative + last_util;
tile_utilization.push(UtilPoint {
time: tile_id.0.start,
util: start_util,
});
}
if tile_id.0.contains(time) {
tile_utilization.push(*point);
}
if last_interval.contains(tile_id.0.stop) {
let relative = last_interval.unlerp(tile_id.0.stop);
let stop_util = (last_util - util) * relative + last_util;
tile_utilization.push(UtilPoint {
time: tile_id.0.stop,
util: stop_util,
});
}
}
last_point = Some(*point);
}
SummaryTile {
entry_id: entry_id.clone(),
tile_id,
data: SummaryTileData {
utilization: tile_utilization,
},
}
}
fn fetch_slot_tile(&mut self, entry_id: &EntryID, tile_id: TileID, _full: bool) -> SlotTile {
let items = &self.generate_slot(entry_id).0;
let mut slot_items = Vec::new();
for row in items {
let mut slot_row = Vec::new();
for item in row {
if tile_id.0.overlaps(item.interval) {
let mut new_item = item.clone();
new_item.interval = new_item.interval.intersection(tile_id.0);
slot_row.push(new_item);
}
}
slot_items.push(slot_row);
}
SlotTile {
entry_id: entry_id.clone(),
tile_id,
data: SlotTileData { items: slot_items },
}
}
fn fetch_slot_meta_tile(
&mut self,
entry_id: &EntryID,
tile_id: TileID,
_full: bool,
) -> SlotMetaTile {
let (items, item_metas) = &self.generate_slot(entry_id);
let mut slot_items = Vec::new();
for (row, row_meta) in items.iter().zip(item_metas.iter()) {
let mut slot_row = Vec::new();
for (item, item_meta) in row.iter().zip(row_meta.iter()) {
if tile_id.0.overlaps(item.interval) {
slot_row.push(item_meta.clone());
}
}
slot_items.push(slot_row);
}
SlotMetaTile {
entry_id: entry_id.clone(),
tile_id,
data: SlotMetaTileData { items: slot_items },
}
}
}