1use jiff::SignedDuration;
2use jiff::fmt::friendly::{FractionalUnit, SpanPrinter};
3
4use re_byte_size::SizeBytes as _;
5use re_chunk_store::ChunkStoreConfig;
6use re_entity_db::EntityDb;
7use re_log_types::StoreKind;
8use re_smart_channel::SmartChannelSource;
9use re_ui::UiExt as _;
10use re_viewer_context::{UiLayout, ViewerContext};
11
12use crate::item_ui::{app_id_button_ui, data_source_button_ui};
13
14impl crate::DataUi for EntityDb {
15 fn data_ui(
16 &self,
17 ctx: &ViewerContext<'_>,
18 ui: &mut egui::Ui,
19 ui_layout: UiLayout,
20 _query: &re_chunk_store::LatestAtQuery,
21 _db: &re_entity_db::EntityDb,
22 ) {
23 if ui_layout.is_single_line() {
24 let mut string = self.store_id().recording_id().to_string();
28 if let Some(data_source) = &self.data_source {
29 string += &format!(", {data_source}");
30 }
31 string += &format!(", {}", self.store_id().application_id());
32
33 ui.label(string);
34 return;
35 }
36
37 egui::Grid::new("entity_db").num_columns(2).show(ui, |ui| {
38 {
39 ui.grid_left_hand_label(&format!("{} ID", self.store_id().kind()));
40 ui.label(self.store_id().recording_id().to_string());
41 ui.end_row();
42 }
43
44 if let Some(SmartChannelSource::RedapGrpcStream { uri: re_uri::DatasetPartitionUri { partition_id, .. }, .. }) = &self.data_source {
45 ui.grid_left_hand_label("Partition ID");
46 ui.label(partition_id);
47 ui.end_row();
48 }
49
50 if let Some(store_info) = self.store_info() && ui_layout.is_selection_panel() {
51 let re_log_types::StoreInfo {
52 store_id,
53 cloned_from,
54 store_source,
55 store_version,
56 is_partial,
57 } = store_info;
58
59 if let Some(cloned_from) = cloned_from {
60 ui.grid_left_hand_label("Clone of");
61 crate::item_ui::store_id_button_ui(ctx, ui, cloned_from, ui_layout);
62 ui.end_row();
63 }
64
65 ui.grid_left_hand_label("Application ID");
66 app_id_button_ui(ctx, ui, store_id.application_id());
67 ui.end_row();
68
69 ui.grid_left_hand_label("Source");
70 ui.label(store_source.to_string());
71 ui.end_row();
72
73 if let Some(store_version) = store_version {
74 ui.grid_left_hand_label("Source RRD version");
75 ui.label(store_version.to_string());
76 ui.end_row();
77 } else {
78 re_log::debug_once!(
79 "store version is undefined for this recording, this is a bug"
80 );
81 }
82
83 ui.grid_left_hand_label("Partial store");
86 ui.monospace(format!("{is_partial:?}"))
87 .on_hover_text(
88 "If true, only a subset of the recording is presented in the viewer.",
89 );
90 ui.end_row();
91
92 ui.grid_left_hand_label("Kind");
93 ui.label(store_id.kind().to_string());
94 ui.end_row();
95 }
96
97 let show_last_modified_time = !ctx.global_context.is_test; if show_last_modified_time
99 && let Some(latest_row_id) = self.latest_row_id()
100 && let Ok(nanos_since_epoch) =
101 i64::try_from(latest_row_id.nanos_since_epoch())
102 {
103 let time = re_log_types::Timestamp::from_nanos_since_epoch(nanos_since_epoch);
104 ui.grid_left_hand_label("Modified");
105 ui.label(time.format(ctx.app_options().timestamp_format));
106 ui.end_row();
107 }
108
109 if let Some(tl_name) = self.timelines().keys()
110 .find(|k| **k == re_log_types::TimelineName::log_time())
111 && let Some(range) = self.time_range_for(tl_name)
112 && let delta_ns = (range.max() - range.min()).as_i64()
113 && delta_ns > 0
114 {
115 let duration = SignedDuration::from_nanos(delta_ns);
116
117 let printer = SpanPrinter::new()
118 .fractional(Some(FractionalUnit::Second))
119 .precision(Some(2));
120
121 let pretty = printer.duration_to_string(&duration);
122
123 ui.grid_left_hand_label("Duration");
124 ui.label(pretty)
125 .on_hover_text("Duration between earliest and latest log_time.");
126 ui.end_row();
127 }
128
129
130 {
131 ui.grid_left_hand_label("Size");
132 ui.label(re_format::format_bytes(self.total_size_bytes() as _))
133 .on_hover_text(
134 "Approximate size in RAM (decompressed).\n\
135 If you hover an entity in the streams view (bottom panel) you can see the \
136 size of individual entities.",
137 );
138 ui.end_row();
139 }
140
141 if ui_layout.is_selection_panel() {
142 let &ChunkStoreConfig {
143 enable_changelog: _,
144 chunk_max_bytes,
145 chunk_max_rows,
146 chunk_max_rows_if_unsorted,
147 } = self.storage_engine().store().config();
148
149 ui.grid_left_hand_label("Compaction");
150 ui.label(format!(
151 "{} rows ({} if unsorted) or {}",
152 re_format::format_uint(chunk_max_rows),
153 re_format::format_uint(chunk_max_rows_if_unsorted),
154 re_format::format_bytes(chunk_max_bytes as _),
155 ))
156 .on_hover_text(
157 unindent::unindent(&format!("\
158 The current compaction configuration for this recording is to merge chunks until they \
159 reach either a maximum of {} rows ({} if unsorted) or {}, whichever comes first.
160
161 The viewer compacts chunks together as they come in, in order to find the right \
162 balance between space and compute overhead.
163 This is not to be confused with the SDK's batcher, which does a similar job, with \
164 different goals and constraints, on the logging side (SDK).
165 These two functions (SDK batcher & viewer compactor) complement each other.
166
167 Higher thresholds generally translate to better space overhead, but require more compute \
168 for both ingestion and queries.
169 Lower thresholds generally translate to worse space overhead, but faster ingestion times
170 and more responsive queries.
171 This is a broad oversimplification -- use the defaults if unsure, they fit most workfloads well.
172
173 To modify the current configuration, set these environment variables before starting the viewer:
174 * {}
175 * {}
176 * {}
177
178 This compaction process is an ephemeral, in-memory optimization of the Rerun viewer.\
179 It will not modify the recording itself: use the `Save` command of the viewer, or the \
180 `rerun rrd compact` CLI tool if you wish to persist the compacted results, which will \
181 make future runs cheaper.
182 ",
183 re_format::format_uint(chunk_max_rows),
184 re_format::format_uint(chunk_max_rows_if_unsorted),
185 re_format::format_bytes(chunk_max_bytes as _),
186 ChunkStoreConfig::ENV_CHUNK_MAX_ROWS,
187 ChunkStoreConfig::ENV_CHUNK_MAX_ROWS_IF_UNSORTED,
188 ChunkStoreConfig::ENV_CHUNK_MAX_BYTES,
189 )),
190 );
191 ui.end_row();
192 }
193
194 if let Some(data_source) = &self.data_source && ui_layout.is_selection_panel() {
195 ui.grid_left_hand_label("Data source");
196 data_source_button_ui(ctx, ui, data_source);
197 ui.end_row();
198 }
199 });
200
201 let hub = ctx.storage_context.hub;
202 let store_id = Some(self.store_id());
203
204 match self.store_kind() {
205 StoreKind::Recording => {
206 if false {
207 if store_id == hub.active_store_id() {
209 ui.add_space(8.0);
210 ui.label("This is the active recording.");
211 }
212 }
213 }
214 StoreKind::Blueprint => {
215 let active_app_id = ctx.store_context.application_id();
216 let is_active_app_id = self.application_id() == active_app_id;
217
218 if is_active_app_id {
219 let is_default = hub.default_blueprint_id_for_app(active_app_id) == store_id;
220 let is_active = hub.active_blueprint_id_for_app(active_app_id) == store_id;
221
222 match (is_default, is_active) {
223 (false, false) => {}
224 (true, false) => {
225 ui.add_space(8.0);
226 ui.label("This is the default blueprint for the current application.");
227
228 if let Some(active_blueprint) =
229 hub.active_blueprint_for_app(active_app_id)
230 && active_blueprint.cloned_from() == Some(self.store_id())
231 {
232 if self.latest_row_id() == active_blueprint.latest_row_id() {
234 ui.label("The active blueprint is a clone of this blueprint.");
235 } else {
236 ui.label("The active blueprint is a modified clone of this blueprint.");
237 }
238 }
239 }
240 (false, true) => {
241 ui.add_space(8.0);
242 ui.label(format!("This is the active blueprint for the current application, '{active_app_id}'"));
243 }
244 (true, true) => {
245 ui.add_space(8.0);
246 ui.label(format!("This is both the active and default blueprint for the current application, '{active_app_id}'"));
247 }
248 }
249 } else {
250 ui.add_space(8.0);
251 ui.label("This blueprint is not for the active application");
252 }
253 }
254 }
255 }
256}