re_data_ui/
entity_db.rs

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            // TODO(emilk): standardize this formatting with that in `entity_db_button_ui` (this is
25            // probably dead code, as `entity_db_button_ui` is actually used in all single line
26            // contexts).
27            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                // TODO(#11315): In the future we will want to reduce this to a mere highlighting feature and no longer need this at the store level
84                // as all data will be pulled on-demand from a server.
85                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; // Hide in tests because it is non-deterministic (it's based on `RowId`).
98            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                    // Just confusing and unnecessary to show this.
208                    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                                // The active blueprint is a clone of the selected blueprint.
233                                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}