Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/common/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
mod relative_date_time;
mod stopwatch;

pub use relative_date_time::RelativeDateTime;
pub use relative_date_time::parse_datetime_or_date;
pub use stopwatch::Stopwatch;
150 changes: 150 additions & 0 deletions src/common/relative_date_time.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
use chrono::{DateTime, Local, NaiveDate, NaiveDateTime, TimeDelta};
use std::{
fmt::Display,
ops::{AddAssign, SubAssign},
str::FromStr,
};

pub fn parse_datetime_or_date(value: &str) -> Result<DateTime<Local>, String> {
let mut errors = Vec::new();
// Parse without timezone
match value.parse::<NaiveDateTime>() {
Ok(datetime) => return Ok(datetime.and_local_timezone(Local).unwrap()),
Err(err) => errors.push(err),
}
// Parse *with* timezone
match value.parse::<DateTime<Local>>() {
Ok(datetime) => return Ok(datetime),
Err(err) => errors.push(err),
}
// Parse as date
match value.parse::<NaiveDate>() {
Ok(date) => {
return Ok(date
.and_hms_opt(0, 0, 0)
.unwrap()
.and_local_timezone(Local)
.unwrap());
}
Err(err) => errors.push(err),
}
return Err(format!(
"Valid RFC3339-formatted (YYYY-MM-DDTHH:MM:SS[.ssssss][±hh:mm|Z]) datetime or date while parsing '{}':\n{}",
value,
errors
.iter()
.map(|e| e.to_string())
.collect::<Vec<String>>()
.join("\n")
));
}

#[derive(Clone, Debug)]
pub struct RelativeDateTime {
date_time: Option<DateTime<Local>>,
// Always subtracted
offset: Option<TimeDelta>,
}

impl RelativeDateTime {
pub fn new(offset: Option<TimeDelta>) -> Self {
Self {
date_time: None,
offset,
}
}

pub fn get_date_time(&self) -> Option<DateTime<Local>> {
self.date_time
}

pub fn to_sql_datetime_64(&self) -> Option<String> {
match (self.date_time, self.offset) {
(Some(date_time), Some(offset)) => Some(format!(
"fromUnixTimestamp64Nano({}) - INTERVAL {} NANOSECOND",
date_time.timestamp_nanos_opt()?,
offset.num_nanoseconds()?
)),
(None, Some(offset)) => Some(format!(
"now() - INTERVAL {} NANOSECOND",
offset.num_nanoseconds()?
)),
(Some(date_time), None) => Some(format!(
"fromUnixTimestamp64Nano({})",
date_time.timestamp_nanos_opt()?
)),
(None, None) => Some("now()".to_string()),
}
}
}

impl From<DateTime<Local>> for RelativeDateTime {
fn from(value: DateTime<Local>) -> Self {
RelativeDateTime {
date_time: Some(value),
offset: None,
}
}
}

impl From<Option<DateTime<Local>>> for RelativeDateTime {
fn from(value: Option<DateTime<Local>>) -> Self {
RelativeDateTime {
date_time: value,
offset: None,
}
}
}

impl FromStr for RelativeDateTime {
type Err = anyhow::Error;

fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
// Empty string is a special case for relative "now"
// (i.e. it will be always calculated from current time)
if s.is_empty() {
Ok(RelativeDateTime {
date_time: None,
offset: None,
})
} else {
Ok(RelativeDateTime {
date_time: None,
offset: Some(TimeDelta::from_std(
s.parse::<humantime::Duration>()?.into(),
)?),
})
}
}
}

impl From<RelativeDateTime> for DateTime<Local> {
fn from(value: RelativeDateTime) -> Self {
let mut date_time = value.date_time.unwrap_or(Local::now());
if let Some(offset) = value.offset {
date_time -= offset;
}
return date_time;
}
}

impl Display for RelativeDateTime {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"{:?} (offset={:?})",
self.date_time, self.offset
))
}
}

impl AddAssign<TimeDelta> for RelativeDateTime {
fn add_assign(&mut self, rhs: TimeDelta) {
self.offset = Some(rhs);
}
}

impl SubAssign<TimeDelta> for RelativeDateTime {
fn sub_assign(&mut self, rhs: TimeDelta) {
self.offset = Some(rhs);
}
}
42 changes: 19 additions & 23 deletions src/interpreter/clickhouse.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use crate::interpreter::{ClickHouseAvailableQuirks, ClickHouseQuirks, options::ClickHouseOptions};
use crate::{
common::RelativeDateTime,
interpreter::{ClickHouseAvailableQuirks, ClickHouseQuirks, options::ClickHouseOptions},
};
use anyhow::{Error, Result};
use chrono::{DateTime, Local};
use clickhouse_rs::{
Expand Down Expand Up @@ -190,22 +193,18 @@ impl ClickHouse {
pub async fn get_slow_query_log(
&self,
filter: &String,
start: DateTime<Local>,
end: DateTime<Local>,
start: RelativeDateTime,
end: RelativeDateTime,
limit: u64,
) -> Result<Columns> {
let start = start
.timestamp_nanos_opt()
.ok_or(Error::msg("Invalid start"))?;
let end = end.timestamp_nanos_opt().ok_or(Error::msg("Invalid end"))?;
let dbtable = self.get_table_name("system", "query_log");
return self
.execute(
format!(
r#"
WITH
fromUnixTimestamp64Nano({start}) AS start_,
fromUnixTimestamp64Nano({end}) AS end_,
{start} AS start_,
{end} AS end_,
slow_queries_ids AS (
SELECT DISTINCT initial_query_id
FROM {db_table}
Expand Down Expand Up @@ -246,6 +245,8 @@ impl ClickHouse {
type != 'QueryStart' AND
initial_query_id GLOBAL IN slow_queries_ids
"#,
start = start.to_sql_datetime_64().ok_or(Error::msg("Invalid start"))?,
end = end.to_sql_datetime_64().ok_or(Error::msg("Invalid end"))?,
db_table = dbtable,
internal = if self.options.internal_queries {
"".to_string()
Expand All @@ -266,14 +267,10 @@ impl ClickHouse {
pub async fn get_last_query_log(
&self,
filter: &String,
start: DateTime<Local>,
end: DateTime<Local>,
start: RelativeDateTime,
end: RelativeDateTime,
limit: u64,
) -> Result<Columns> {
let start = start
.timestamp_nanos_opt()
.ok_or(Error::msg("Invalid start"))?;
let end = end.timestamp_nanos_opt().ok_or(Error::msg("Invalid end"))?;
// TODO:
// - propagate sort order from the table
// - distributed_group_by_no_merge=2 is broken for this query with WINDOW function
Expand All @@ -283,8 +280,8 @@ impl ClickHouse {
format!(
r#"
WITH
fromUnixTimestamp64Nano({start}) AS start_,
fromUnixTimestamp64Nano({end}) AS end_,
{start} AS start_,
{end} AS end_,
last_queries_ids AS (
SELECT DISTINCT initial_query_id
FROM {db_table}
Expand Down Expand Up @@ -323,6 +320,8 @@ impl ClickHouse {
type != 'QueryStart' AND
initial_query_id GLOBAL IN last_queries_ids
"#,
start = start.to_sql_datetime_64().ok_or(Error::msg("Invalid start"))?,
end = end.to_sql_datetime_64().ok_or(Error::msg("Invalid end"))?,
db_table = dbtable,
internal = if self.options.internal_queries {
"".to_string()
Expand Down Expand Up @@ -768,7 +767,7 @@ impl ClickHouse {
&self,
query_ids: &Option<Vec<String>>,
start_microseconds: DateTime<Local>,
end_microseconds: Option<DateTime<Local>>,
end: RelativeDateTime,
) -> Result<Columns> {
// TODO:
// - optional flush, but right now it gives "blocks should not be empty." error
Expand All @@ -787,7 +786,7 @@ impl ClickHouse {
r#"
WITH
fromUnixTimestamp64Nano({}) AS start_time_,
fromUnixTimestamp64Nano({}) AS end_time_
{} AS end_time_
SELECT
hostName() AS host_name,
event_time,
Expand All @@ -807,10 +806,7 @@ impl ClickHouse {
start_microseconds
.timestamp_nanos_opt()
.ok_or(Error::msg("Invalid start time"))?,
end_microseconds
.unwrap_or(Local::now())
.timestamp_nanos_opt()
.ok_or(Error::msg("Invalid end time"))?,
end.to_sql_datetime_64().ok_or(Error::msg("Invalid end time"))?,
dbtable,
if let Some(query_ids) = query_ids {
format!("AND query_id IN ('{}')", query_ids.join("','"))
Expand Down
43 changes: 5 additions & 38 deletions src/interpreter/options.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::common::RelativeDateTime;
use anyhow::{Result, anyhow};
use chrono::{DateTime, Duration, Local, NaiveDate, NaiveDateTime};
use clap::{ArgAction, Args, CommandFactory, Parser, Subcommand, builder::ArgPredicate};
use clap_complete::{Shell, generate};
use percent_encoding::{NON_ALPHANUMERIC, utf8_percent_encode};
Expand Down Expand Up @@ -179,39 +179,6 @@ pub struct ClickHouseOptions {
pub no_internal_queries: bool,
}

pub fn parse_datetime_or_date(value: &str) -> Result<DateTime<Local>, String> {
let mut errors = Vec::new();
// Parse without timezone
match value.parse::<NaiveDateTime>() {
Ok(datetime) => return Ok(datetime.and_local_timezone(Local).unwrap()),
Err(err) => errors.push(err),
}
// Parse *with* timezone
match value.parse::<DateTime<Local>>() {
Ok(datetime) => return Ok(datetime),
Err(err) => errors.push(err),
}
// Parse as date
match value.parse::<NaiveDate>() {
Ok(date) => {
return Ok(date
.and_hms_opt(0, 0, 0)
.unwrap()
.and_local_timezone(Local)
.unwrap());
}
Err(err) => errors.push(err),
}
return Err(format!(
"Valid RFC3339-formatted (YYYY-MM-DDTHH:MM:SS[.ssssss][±hh:mm|Z]) datetime or date:\n{}",
errors
.iter()
.map(|e| e.to_string())
.collect::<Vec<String>>()
.join("\n")
));
}

#[derive(Args, Clone)]
pub struct ViewOptions {
#[arg(
Expand All @@ -233,12 +200,12 @@ pub struct ViewOptions {
pub no_subqueries: bool,

// Use short option -b, like atop(1) has
#[arg(long, short('b'), value_parser = parse_datetime_or_date, default_value_t = Local::now() - Duration::try_hours(1).unwrap())]
#[arg(long, short('b'), default_value = "1hour")]
/// Begin of the time interval to look at
pub start: DateTime<Local>,
#[arg(long, short('e'), value_parser = parse_datetime_or_date, default_value_t = Local::now())]
pub start: RelativeDateTime,
#[arg(long, short('e'), default_value = "")]
/// End of the time interval
pub end: DateTime<Local>,
pub end: RelativeDateTime,

/// Wrap long lines (more CPU greedy)
#[arg(long, action = ArgAction::SetTrue)]
Expand Down
19 changes: 11 additions & 8 deletions src/interpreter/worker.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use crate::{
common::Stopwatch,
interpreter::clickhouse::{Columns, TraceType},
interpreter::{ContextArc, flamegraph},
common::{RelativeDateTime, Stopwatch},
interpreter::{
ContextArc,
clickhouse::{Columns, TraceType},
flamegraph,
},
utils::{highlight_sql, open_graph_in_browser},
view::{self, Navigation},
};
Expand All @@ -22,15 +25,15 @@ pub enum Event {
// [filter, limit]
UpdateProcessList(String, u64),
// [filter, start, end, limit]
UpdateSlowQueryLog(String, DateTime<Local>, DateTime<Local>, u64),
UpdateSlowQueryLog(String, RelativeDateTime, RelativeDateTime, u64),
// [filter, start, end, limit]
UpdateLastQueryLog(String, DateTime<Local>, DateTime<Local>, u64),
UpdateLastQueryLog(String, RelativeDateTime, RelativeDateTime, u64),
// (view_name, [query_ids], start, end)
GetQueryTextLog(
&'static str,
Option<Vec<String>>,
DateTime<Local>,
Option<DateTime<Local>>,
RelativeDateTime,
),
// [bool (true - show in TUI, false - open in browser), type, start, end]
ShowServerFlameGraph(bool, TraceType, DateTime<Local>, DateTime<Local>),
Expand Down Expand Up @@ -316,9 +319,9 @@ async fn process_event(context: ContextArc, event: Event, need_clear: &mut bool)
}))
.map_err(|_| anyhow!("Cannot send message to UI"))?;
}
Event::GetQueryTextLog(view_name, query_ids, start_microseconds, end_microseconds) => {
Event::GetQueryTextLog(view_name, query_ids, start_microseconds, end) => {
let block = clickhouse
.get_query_logs(&query_ids, start_microseconds, end_microseconds)
.get_query_logs(&query_ids, start_microseconds, end)
.await?;
cb_sink
.send(Box::new(move |siv: &mut cursive::Cursive| {
Expand Down
Loading
Loading