Skip to content

Commit 28361c9

Browse files
chore: reuse time bin logic across apis
remove binning logic in counts api make reusable component reuse in counts api, errors api, agent-observability related apis
1 parent cbfbfa1 commit 28361c9

2 files changed

Lines changed: 70 additions & 24 deletions

File tree

src/query/mod.rs

Lines changed: 5 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ use crate::metrics::increment_bytes_scanned_in_query_by_date;
7373
use crate::option::Mode;
7474
use crate::parseable::{DEFAULT_TENANT, PARSEABLE};
7575
use crate::storage::{ObjectStorageProvider, ObjectStoreFormat};
76-
use crate::utils::time::TimeRange;
76+
use crate::utils::time::{DATE_BIN_EPOCH_ANCHOR, TimeRange, count_api_bin_interval};
7777

7878
/// Boxed record-batch stream used as the streaming half of query results.
7979
type BoxedBatchStream = SendableRecordBatchStream;
@@ -638,32 +638,13 @@ impl CountsRequest {
638638

639639
let time_range = TimeRange::parse_human_time(&self.start_time, &self.end_time)?;
640640

641-
let dur = time_range.end.signed_duration_since(time_range.start);
642-
643641
let table_name = &self.stream;
644642
let start_time_col_name = "_bin_start_time_";
645643
let end_time_col_name = "_bin_end_time_";
646-
let date_bin = if dur.num_minutes() <= 60 * 5 {
647-
// less than 5 hour = 1 min bin
648-
format!(
649-
"CAST(DATE_BIN('1m', \"{table_name}\".\"{time_column}\", TIMESTAMP '1970-01-01 00:00:00+00') AS TEXT) as {start_time_col_name}, DATE_BIN('1m', \"{table_name}\".\"{time_column}\", TIMESTAMP '1970-01-01 00:00:00+00') + INTERVAL '1m' as {end_time_col_name}"
650-
)
651-
} else if dur.num_minutes() <= 60 * 24 {
652-
// 1 day = 5 min bin
653-
format!(
654-
"CAST(DATE_BIN('5m', \"{table_name}\".\"{time_column}\", TIMESTAMP '1970-01-01 00:00:00+00') AS TEXT) as {start_time_col_name}, DATE_BIN('5m', \"{table_name}\".\"{time_column}\", TIMESTAMP '1970-01-01 00:00:00+00') + INTERVAL '5m' as {end_time_col_name}"
655-
)
656-
} else if dur.num_minutes() < 60 * 24 * 10 {
657-
// 10 days = 1 hour bin
658-
format!(
659-
"CAST(DATE_BIN('1h', \"{table_name}\".\"{time_column}\", TIMESTAMP '1970-01-01 00:00:00+00') AS TEXT) as {start_time_col_name}, DATE_BIN('1h', \"{table_name}\".\"{time_column}\", TIMESTAMP '1970-01-01 00:00:00+00') + INTERVAL '1h' as {end_time_col_name}"
660-
)
661-
} else {
662-
// 1 day
663-
format!(
664-
"CAST(DATE_BIN('1d', \"{table_name}\".\"{time_column}\", TIMESTAMP '1970-01-01 00:00:00+00') AS TEXT) as {start_time_col_name}, DATE_BIN('1d', \"{table_name}\".\"{time_column}\", TIMESTAMP '1970-01-01 00:00:00+00') + INTERVAL '1d' as {end_time_col_name}"
665-
)
666-
};
644+
let bin_interval = count_api_bin_interval(&time_range.start, &time_range.end);
645+
let date_bin = format!(
646+
"CAST(DATE_BIN('{bin_interval}', \"{table_name}\".\"{time_column}\", TIMESTAMP '{DATE_BIN_EPOCH_ANCHOR}') AS TEXT) as {start_time_col_name}, DATE_BIN('{bin_interval}', \"{table_name}\".\"{time_column}\", TIMESTAMP '{DATE_BIN_EPOCH_ANCHOR}') + INTERVAL '{bin_interval}' as {end_time_col_name}"
647+
);
667648

668649
let group_by_cols = count_conditions
669650
.group_by

src/utils/time.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
use chrono::{DateTime, Datelike, NaiveDate, NaiveDateTime, TimeDelta, TimeZone, Timelike, Utc};
2020

21+
pub const DATE_BIN_EPOCH_ANCHOR: &str = "1970-01-01 00:00:00+00";
22+
2123
#[derive(Debug, thiserror::Error)]
2224
pub enum TimeParseError {
2325
#[error("Parsing humantime")]
@@ -55,6 +57,69 @@ pub struct TimeRange {
5557
pub end: DateTime<Utc>,
5658
}
5759

60+
pub fn count_api_bin_interval(start: &DateTime<Utc>, end: &DateTime<Utc>) -> &'static str {
61+
let dur = end.signed_duration_since(*start);
62+
63+
if dur.num_minutes() <= 60 * 5 {
64+
"1m"
65+
} else if dur.num_minutes() <= 60 * 24 {
66+
"5m"
67+
} else if dur.num_minutes() < 60 * 24 * 10 {
68+
"1h"
69+
} else {
70+
"1d"
71+
}
72+
}
73+
74+
pub fn interval_for_num_bins(start: &DateTime<Utc>, end: &DateTime<Utc>, num_bins: u64) -> String {
75+
let total_seconds = end.signed_duration_since(*start).num_seconds().max(1) as u64;
76+
let bin_seconds = (total_seconds / num_bins).max(1);
77+
format!("{bin_seconds} seconds")
78+
}
79+
80+
pub fn expected_time_bins(time_range: &TimeRange, num_bins: u64) -> Vec<(String, String)> {
81+
let total_seconds = time_range
82+
.end
83+
.signed_duration_since(time_range.start)
84+
.num_seconds()
85+
.max(1) as u64;
86+
let bin_seconds = (total_seconds / num_bins).max(1);
87+
88+
(0..num_bins)
89+
.map(|i| {
90+
let bin_start = time_range.start + chrono::Duration::seconds((i * bin_seconds) as i64);
91+
let bin_end = if i == num_bins - 1 {
92+
time_range.end
93+
} else {
94+
time_range.start + chrono::Duration::seconds(((i + 1) * bin_seconds) as i64)
95+
};
96+
(bin_start.to_rfc3339(), bin_end.to_rfc3339())
97+
})
98+
.collect()
99+
}
100+
101+
pub fn match_time_bin_key(sql_bin_start: &str, expected_bins: &[(String, String)]) -> String {
102+
let normalized = if !sql_bin_start.contains('+') && !sql_bin_start.ends_with('Z') {
103+
format!("{sql_bin_start}+00:00")
104+
} else {
105+
sql_bin_start.to_string()
106+
};
107+
108+
if let Ok(sql_ts) = DateTime::parse_from_rfc3339(&normalized) {
109+
let sql_ts = sql_ts.with_timezone(&Utc);
110+
for (bs, _) in expected_bins {
111+
if let Ok(expected_ts) = DateTime::parse_from_rfc3339(bs) {
112+
let expected_ts = expected_ts.with_timezone(&Utc);
113+
if (sql_ts - expected_ts).num_seconds().abs() <= 1 {
114+
return bs.clone();
115+
}
116+
}
117+
}
118+
}
119+
120+
sql_bin_start.to_string()
121+
}
122+
58123
impl TimeRange {
59124
pub fn new(start: DateTime<Utc>, end: DateTime<Utc>) -> Self {
60125
TimeRange { start, end }

0 commit comments

Comments
 (0)