Skip to content

Commit

Permalink
add transaction logs
Browse files Browse the repository at this point in the history
Change-Id: I5ef4670d5c2a73779b40f0ca380778f3a304d052
  • Loading branch information
s-kipnis committed Nov 18, 2023
1 parent 892e731 commit 8264424
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 2 deletions.
90 changes: 88 additions & 2 deletions packages/check-sql/src/ms_sql/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const COUNTERS_SECTION_NAME: &str = "counters";
const BLOCKED_SESSIONS_SECTION_NAME: &str = "blocked_sessions";
const TABLE_SPACES_SECTION_NAME: &str = "tablespaces";
const BACKUP_SECTION_NAME: &str = "backup";
const TRANSACTION_LOG_SECTION_NAME: &str = "transactionlogs";

pub enum Credentials<'a> {
SqlServer { user: &'a str, password: &'a str },
Expand Down Expand Up @@ -59,6 +60,7 @@ impl Section {

pub trait Column {
fn get_bigint_by_idx(&self, idx: usize) -> i64;
fn get_bigint_by_name(&self, idx: &str) -> i64;
fn get_value_by_idx(&self, idx: usize) -> String;
fn get_optional_value_by_idx(&self, idx: usize) -> Option<String>;
fn get_value_by_name(&self, idx: &str) -> String;
Expand Down Expand Up @@ -114,7 +116,8 @@ impl InstanceEngine {
COUNTERS_SECTION_NAME
| BLOCKED_SESSIONS_SECTION_NAME
| TABLE_SPACES_SECTION_NAME
| BACKUP_SECTION_NAME => {
| BACKUP_SECTION_NAME
| TRANSACTION_LOG_SECTION_NAME => {
result += &self
.generate_known_sections(&mut client, endpoint, &section.name)
.await;
Expand Down Expand Up @@ -206,6 +209,10 @@ impl InstanceEngine {
.await
}
BACKUP_SECTION_NAME => self.generate_backup_section(client, &databases, sep).await,
TRANSACTION_LOG_SECTION_NAME => {
self.generate_transaction_logs_section(endpoint, &databases, sep)
.await
}
_ => format!("{} not implemented\n", name).to_string(),
}
}
Expand Down Expand Up @@ -312,6 +319,38 @@ impl InstanceEngine {
}
}

pub async fn generate_transaction_logs_section(
&self,
endpoint: &config::ms_sql::Endpoint,
databases: &Vec<String>,
sep: char,
) -> String {
let format_error = |d: &str, e: &anyhow::Error| {
format!(
"{}{sep}{}|-|-|-|-|-|-|{:?}\n",
self.mssql_name(),
d.replace(' ', "_"),
e
)
.to_string()
};
let mut result = String::new();
for d in databases {
match self.create_client(endpoint, Some(d.clone())).await {
Ok(mut c) => {
result += &run_query(&mut c, queries::QUERY_TRANSACTION_LOGS)
.await
.map(|rows| to_transaction_logs_entries(&self.mssql_name(), d, &rows, sep))
.unwrap_or_else(|e| format_error(d, &e));
}
Err(err) => {
result += &format_error(d, &err);
}
}
}
result
}

/// doesn't return error - the same behavior as plugin
pub async fn generate_databases(&self, client: &mut Client) -> Vec<String> {
let result = run_query(client, queries::QUERY_DATABASES)
Expand Down Expand Up @@ -474,6 +513,47 @@ fn to_table_spaces_entry(
)
}

fn to_transaction_logs_entries(
instance_name: &str,
database_name: &str,
rows: &[Vec<Row>],
sep: char,
) -> String {
if rows.is_empty() {
return String::new();
}
rows[0]
.iter()
.map(|row| to_transaction_logs_entry(row, instance_name, database_name, sep))
.collect::<Vec<String>>()
.join("")
}

fn to_transaction_logs_entry(
row: &Row,
instance_name: &str,
database_name: &str,
sep: char,
) -> String {
let name = row.get_value_by_name("name");
let physical_name = row.get_value_by_name("physical_name");
let max_size = row.get_bigint_by_name("MaxSize");
let allocated_size = row.get_bigint_by_name("AllocatedSize");
let used_size = row.get_bigint_by_name("UsedSize");
let unlimited = row.get_bigint_by_name("Unlimited");
format!(
"{}{sep}{}{sep}{}{sep}{}{sep}{}{sep}{}{sep}{}{sep}{}\n",
instance_name,
database_name.replace(' ', "_"),
name,
physical_name,
max_size,
allocated_size,
used_size,
unlimited
)
}

fn to_backup_entry(
instance_name: &str,
database_name: &str,
Expand Down Expand Up @@ -550,6 +630,12 @@ impl Column for Row {
.unwrap_or_default()
}

fn get_bigint_by_name(&self, idx: &str) -> i64 {
self.try_get::<i64, &str>(idx)
.unwrap_or_default()
.unwrap_or_default()
}

fn get_value_by_idx(&self, idx: usize) -> String {
self.try_get::<&str, usize>(idx)
.unwrap_or_default()
Expand Down Expand Up @@ -654,7 +740,7 @@ async fn generate_result(
// place all futures now in vector for future asynchronous processing
let tasks = instances
.iter()
.map(move |instance| instance.generate_sections(ms_sql, sections.clone()));
.map(move |instance| instance.generate_sections(ms_sql, sections));

// processing here
let results = stream::iter(tasks)
Expand Down
7 changes: 7 additions & 0 deletions packages/check-sql/src/ms_sql/queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,13 @@ END
EXEC (@SQLCommand)
";

pub const QUERY_TRANSACTION_LOGS: &str = "SELECT name, physical_name,\
cast(max_size/128 as bigint) as MaxSize,\
cast(size/128 as bigint) as AllocatedSize,\
cast(FILEPROPERTY (name, 'spaceused')/128 as bigint) as UsedSize,\
case when max_size = '-1' then '1' else '0' end as Unlimited \
FROM sys.database_files WHERE type_desc = 'LOG'";

pub fn get_instances_query() -> String {
QUERY_ALL_BASE.to_string()
}
Expand Down
35 changes: 35 additions & 0 deletions packages/check-sql/tests/test_ms_sql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ async fn test_validate_all_instances_remote() {
.contains(" error: "),);
validate_table_spaces(&i, &mut c, &cfg.endpoint()).await;
validate_backup(&i, &mut c).await;
validate_transaction_logs(&i, &mut c, &cfg.endpoint()).await;
}
Err(e) => {
panic!("Unexpected error: `{:?}`", e);
Expand Down Expand Up @@ -271,6 +272,40 @@ async fn validate_backup(instance: &InstanceEngine, client: &mut Client) {
assert!(to_be_found.is_empty());
}

async fn validate_transaction_logs(
instance: &InstanceEngine,
client: &mut Client,
endpoint: &Endpoint,
) {
let databases = instance.generate_databases(client).await;
let expected: HashSet<String> = ["master", "tempdb", "model", "msdb"]
.iter()
.map(|&s| s.to_string())
.collect();
let mut found: HashSet<String> = HashSet::new();

let result = instance
.generate_transaction_logs_section(endpoint, &databases, '|')
.await;
let lines: Vec<&str> = result.split('\n').collect();
assert!(lines.len() >= expected.len(), "{:?}", lines);
assert!(lines[lines.len() - 1].is_empty());
for l in lines[..lines.len() - 1].iter() {
let values = l.split('|').collect::<Vec<&str>>();
assert_eq!(values[0], instance.mssql_name(), "bad line: {}", l);
if expected.contains(&values[1].to_string()) {
found.insert(values[1].to_string());
}
assert!(values[2].to_lowercase().ends_with("log"), "bad line: {}", l);
assert!(values[3].starts_with("C:\\Program"), "bad line: {}", l);
assert!(values[4].parse::<u64>().is_ok(), "bad line: {}", l);
assert!(values[5].parse::<u64>().is_ok(), "bad line: {}", l);
assert!(values[6].parse::<u64>().is_ok(), "bad line: {}", l);
assert!(values[7].parse::<u64>().is_ok(), "bad line: {}", l);
}
assert_eq!(found, expected);
}

/// This test is ignored because it requires real credentials and real server
/// Intended to be used manually by dev to check whether all instances are accessible.
/// TODO(sk): remove on branching
Expand Down

0 comments on commit 8264424

Please sign in to comment.