Line data Source code
1 : use std::{collections::HashMap, time::SystemTime};
2 :
3 : use crate::tenant::{remote_timeline_client::index::LayerFileMetadata, storage_layer::LayerName};
4 :
5 : use serde::{Deserialize, Serialize};
6 : use serde_with::{serde_as, DisplayFromStr, TimestampSeconds};
7 :
8 : use utils::{generation::Generation, id::TimelineId};
9 :
10 0 : #[derive(Serialize, Deserialize)]
11 : pub(crate) struct HeatMapTenant {
12 : /// Generation of the attached location that uploaded the heatmap: this is not required
13 : /// for correctness, but acts as a hint to secondary locations in order to detect thrashing
14 : /// in the unlikely event that two attached locations are both uploading conflicting heatmaps.
15 : pub(super) generation: Generation,
16 :
17 : pub(super) timelines: Vec<HeatMapTimeline>,
18 :
19 : /// Uploaders provide their own upload period in the heatmap, as a hint to downloaders
20 : /// of how frequently it is worthwhile to check for updates.
21 : ///
22 : /// This is optional for backward compat, and because we sometimes might upload
23 : /// a heatmap explicitly via API for a tenant that has no periodic upload configured.
24 : #[serde(default)]
25 : pub(super) upload_period_ms: Option<u128>,
26 : }
27 :
28 : impl HeatMapTenant {
29 0 : pub(crate) fn into_timelines_index(self) -> HashMap<TimelineId, HeatMapTimeline> {
30 0 : self.timelines
31 0 : .into_iter()
32 0 : .map(|htl| (htl.timeline_id, htl))
33 0 : .collect()
34 0 : }
35 : }
36 :
37 : #[serde_as]
38 0 : #[derive(Serialize, Deserialize, Clone)]
39 : pub(crate) struct HeatMapTimeline {
40 : #[serde_as(as = "DisplayFromStr")]
41 : pub(crate) timeline_id: TimelineId,
42 :
43 : pub(crate) layers: Vec<HeatMapLayer>,
44 : }
45 :
46 : #[serde_as]
47 0 : #[derive(Serialize, Deserialize, Clone)]
48 : pub(crate) struct HeatMapLayer {
49 : pub(crate) name: LayerName,
50 : pub(crate) metadata: LayerFileMetadata,
51 :
52 : #[serde_as(as = "TimestampSeconds<i64>")]
53 : pub(crate) access_time: SystemTime,
54 : // TODO: an actual 'heat' score that would let secondary locations prioritize downloading
55 : // the hottest layers, rather than trying to simply mirror whatever layers are on-disk on the primary.
56 : }
57 :
58 : impl HeatMapLayer {
59 108 : pub(crate) fn new(
60 108 : name: LayerName,
61 108 : metadata: LayerFileMetadata,
62 108 : access_time: SystemTime,
63 108 : ) -> Self {
64 108 : Self {
65 108 : name,
66 108 : metadata,
67 108 : access_time,
68 108 : }
69 108 : }
70 : }
71 :
72 : impl HeatMapTimeline {
73 32 : pub(crate) fn new(timeline_id: TimelineId, layers: Vec<HeatMapLayer>) -> Self {
74 32 : Self {
75 32 : timeline_id,
76 32 : layers,
77 32 : }
78 32 : }
79 : }
80 :
81 : pub(crate) struct HeatMapStats {
82 : pub(crate) bytes: u64,
83 : pub(crate) layers: usize,
84 : }
85 :
86 : impl HeatMapTenant {
87 0 : pub(crate) fn get_stats(&self) -> HeatMapStats {
88 0 : let mut stats = HeatMapStats {
89 0 : bytes: 0,
90 0 : layers: 0,
91 0 : };
92 0 : for timeline in &self.timelines {
93 0 : for layer in &timeline.layers {
94 0 : stats.layers += 1;
95 0 : stats.bytes += layer.metadata.file_size;
96 0 : }
97 : }
98 :
99 0 : stats
100 0 : }
101 :
102 0 : pub(crate) fn strip_atimes(self) -> Self {
103 0 : Self {
104 0 : timelines: self
105 0 : .timelines
106 0 : .into_iter()
107 0 : .map(|mut tl| {
108 0 : for layer in &mut tl.layers {
109 0 : layer.access_time = SystemTime::UNIX_EPOCH;
110 0 : }
111 0 : tl
112 0 : })
113 0 : .collect(),
114 0 : generation: self.generation,
115 0 : upload_period_ms: self.upload_period_ms,
116 0 : }
117 0 : }
118 : }
|