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() {
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
124 ui.grid_left_hand_label("Duration");
125 ui.label(pretty)
126 .on_hover_text("Duration between earliest and latest log_time.");
127 ui.end_row();
128 }
129
130
131 {
132 ui.grid_left_hand_label("Size");
133 ui.label(re_format::format_bytes(self.total_size_bytes() as _))
134 .on_hover_text(
135 "Approximate size in RAM (decompressed).\n\
136 If you hover an entity in the streams view (bottom panel) you can see the \
137 size of individual entities.",
138 );
139 ui.end_row();
140 }
141
142 {
143 let &ChunkStoreConfig {
144 enable_changelog: _,
145 chunk_max_bytes,
146 chunk_max_rows,
147 chunk_max_rows_if_unsorted,
148 } = self.storage_engine().store().config();
149
150 ui.grid_left_hand_label("Compaction");
151 ui.label(format!(
152 "{} rows ({} if unsorted) or {}",
153 re_format::format_uint(chunk_max_rows),
154 re_format::format_uint(chunk_max_rows_if_unsorted),
155 re_format::format_bytes(chunk_max_bytes as _),
156 ))
157 .on_hover_text(
158 unindent::unindent(&format!("\
159 The current compaction configuration for this recording is to merge chunks until they \
160 reach either a maximum of {} rows ({} if unsorted) or {}, whichever comes first.
161
162 The viewer compacts chunks together as they come in, in order to find the right \
163 balance between space and compute overhead.
164 This is not to be confused with the SDK's batcher, which does a similar job, with \
165 different goals and constraints, on the logging side (SDK).
166 These two functions (SDK batcher & viewer compactor) complement each other.
167
168 Higher thresholds generally translate to better space overhead, but require more compute \
169 for both ingestion and queries.
170 Lower thresholds generally translate to worse space overhead, but faster ingestion times
171 and more responsive queries.
172 This is a broad oversimplification -- use the defaults if unsure, they fit most workfloads well.
173
174 To modify the current configuration, set these environment variables before starting the viewer:
175 * {}
176 * {}
177 * {}
178
179 This compaction process is an ephemeral, in-memory optimization of the Rerun viewer.\
180 It will not modify the recording itself: use the `Save` command of the viewer, or the \
181 `rerun rrd compact` CLI tool if you wish to persist the compacted results, which will \
182 make future runs cheaper.
183 ",
184 re_format::format_uint(chunk_max_rows),
185 re_format::format_uint(chunk_max_rows_if_unsorted),
186 re_format::format_bytes(chunk_max_bytes as _),
187 ChunkStoreConfig::ENV_CHUNK_MAX_ROWS,
188 ChunkStoreConfig::ENV_CHUNK_MAX_ROWS_IF_UNSORTED,
189 ChunkStoreConfig::ENV_CHUNK_MAX_BYTES,
190 )),
191 );
192 ui.end_row();
193 }
194
195 if let Some(data_source) = &self.data_source {
196 ui.grid_left_hand_label("Data source");
197 data_source_button_ui(ctx, ui, data_source);
198 ui.end_row();
199 }
200 });
201
202 let hub = ctx.storage_context.hub;
203 let store_id = Some(self.store_id());
204
205 match self.store_kind() {
206 StoreKind::Recording => {
207 if false {
208 if store_id == hub.active_store_id() {
210 ui.add_space(8.0);
211 ui.label("This is the active recording.");
212 }
213 }
214 }
215 StoreKind::Blueprint => {
216 let active_app_id = ctx.store_context.application_id();
217 let is_active_app_id = self.application_id() == active_app_id;
218
219 if is_active_app_id {
220 let is_default = hub.default_blueprint_id_for_app(active_app_id) == store_id;
221 let is_active = hub.active_blueprint_id_for_app(active_app_id) == store_id;
222
223 match (is_default, is_active) {
224 (false, false) => {}
225 (true, false) => {
226 ui.add_space(8.0);
227 ui.label("This is the default blueprint for the current application.");
228
229 if let Some(active_blueprint) =
230 hub.active_blueprint_for_app(active_app_id)
231 && active_blueprint.cloned_from() == Some(self.store_id())
232 {
233 if self.latest_row_id() == active_blueprint.latest_row_id() {
235 ui.label("The active blueprint is a clone of this blueprint.");
236 } else {
237 ui.label("The active blueprint is a modified clone of this blueprint.");
238 }
239 }
240 }
241 (false, true) => {
242 ui.add_space(8.0);
243 ui.label(format!("This is the active blueprint for the current application, '{active_app_id}'"));
244 }
245 (true, true) => {
246 ui.add_space(8.0);
247 ui.label(format!("This is both the active and default blueprint for the current application, '{active_app_id}'"));
248 }
249 }
250 } else {
251 ui.add_space(8.0);
252 ui.label("This blueprint is not for the active application");
253 }
254 }
255 }
256 }
257}