Skip to content

Commit

Permalink
add tablespaces section
Browse files Browse the repository at this point in the history
- few renamings for better consistency

Change-Id: I0ac452899bb4100a946dccad3f0888535f74a248
  • Loading branch information
s-kipnis committed Nov 17, 2023
1 parent 35cd939 commit 08d0b6e
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 15 deletions.
119 changes: 110 additions & 9 deletions packages/check-sql/src/ms_sql/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
// conditions defined in the file COPYING, which is part of this source code package.

use std::vec;

use crate::config::{self, CheckConfig};
use crate::emit::header;
use crate::ms_sql::queries;
Expand All @@ -22,6 +24,7 @@ pub const SQL_TCP_ERROR_TAG: &str = "[SQL TCP ERROR]";
const INSTANCE_SECTION_NAME: &str = "instance";
const COUNTERS_SECTION_NAME: &str = "counters";
const BLOCKED_SESSIONS_SECTION_NAME: &str = "blocked_sessions";
const TABLE_SPACES_SECTION_NAME: &str = "tablespaces";

pub enum Credentials<'a> {
SqlServer { user: &'a str, password: &'a str },
Expand Down Expand Up @@ -94,7 +97,8 @@ impl InstanceEngine {
) -> String {
let mut result = String::new();
let instance_section = Section::new(INSTANCE_SECTION_NAME); // this is important section always present
match self.create_client(&ms_sql.endpoint(), None).await {
let endpoint = &ms_sql.endpoint();
match self.create_client(endpoint, None).await {
Ok(mut client) => {
for section in sections {
result += &section.to_header();
Expand All @@ -105,9 +109,11 @@ impl InstanceEngine {
.generate_details_entry(&mut client, instance_section.sep())
.await;
}
COUNTERS_SECTION_NAME | BLOCKED_SESSIONS_SECTION_NAME => {
COUNTERS_SECTION_NAME
| BLOCKED_SESSIONS_SECTION_NAME
| TABLE_SPACES_SECTION_NAME => {
result += &self
.generate_known_section(&mut client, &section.name)
.generate_known_sections(&mut client, endpoint, &section.name)
.await;
}
_ => {
Expand Down Expand Up @@ -171,21 +177,30 @@ impl InstanceEngine {
format!("{}{sep}state{sep}{}\n", self.mssql_name(), accessible as u8)
}

pub async fn generate_known_section(&self, client: &mut Client, name: &str) -> String {
pub async fn generate_known_sections(
&self,
client: &mut Client,
endpoint: &config::ms_sql::Endpoint,
name: &str,
) -> String {
let sep = Section::new(name).sep();
match name {
COUNTERS_SECTION_NAME => {
self.generate_utc_entry(client, sep).await
+ &self.generate_counters_entry(client, sep).await
}
BLOCKED_SESSIONS_SECTION_NAME => {
self.generate_waiting_sessions_entry(
self.generate_blocking_sessions_section(
client,
&queries::get_blocked_sessions_query(),
&queries::get_blocking_sessions_query(),
sep,
)
.await
}
TABLE_SPACES_SECTION_NAME => {
self.generate_table_spaces_section(client, endpoint, sep)
.await
}
_ => format!("{} not implemented\n", name).to_string(),
}
}
Expand All @@ -210,7 +225,7 @@ impl InstanceEngine {
Ok(z.join(""))
}

pub async fn generate_waiting_sessions_entry(
pub async fn generate_blocking_sessions_section(
&self,
client: &mut Client,
query: &str,
Expand All @@ -231,6 +246,53 @@ impl InstanceEngine {
}
}

pub async fn generate_table_spaces_section(
&self,
client: &mut Client,
endpoint: &config::ms_sql::Endpoint,
sep: char,
) -> String {
let format_error = |d: &str, e: &anyhow::Error| {
format!(
"{}{sep}{} - - - - - - - - - - - - {:?}\n",
self.mssql_name(),
d.replace(' ', "_"),
e
)
.to_string()
};
let databases = self.generate_databases(client).await;
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_SPACE_USED)
.await
.map(|rows| to_table_spaces_entry(&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)
.await
.and_then(validate_rows)
.map(|rows| self.process_databases_rows(&rows));
match result {
Ok(result) => result,
Err(err) => {
log::error!("Failed to get databases: {}", err);
vec![]
}
}
}
fn process_blocked_sessions_rows(&self, rows: &[Vec<Row>], sep: char) -> String {
let rows = &rows[0];
let z: Vec<String> = rows
Expand All @@ -241,11 +303,11 @@ impl InstanceEngine {
}

async fn generate_utc_entry(&self, client: &mut Client, sep: char) -> String {
let x = run_query(client, queries::QUERY_UTC)
let result = run_query(client, queries::QUERY_UTC)
.await
.and_then(validate_rows)
.and_then(|rows| self.process_utc_rows(&rows, sep));
match x {
match result {
Ok(result) => result,
Err(err) => {
log::error!("Failed to get UTC: {}", err);
Expand All @@ -260,6 +322,13 @@ impl InstanceEngine {
Ok(format!("None{sep}utc_time{sep}None{sep}{utc}\n"))
}

fn process_databases_rows(&self, rows: &[Vec<Row>]) -> Vec<String> {
let row = &rows[0];
row.iter()
.map(|row| row.get_value_by_idx(0))
.collect::<Vec<String>>()
}

fn process_details_rows(&self, rows: Vec<Vec<Row>>, sep: char) -> String {
if rows.is_empty() || rows[0].is_empty() {
const ERROR: &str = "No output from query";
Expand Down Expand Up @@ -294,6 +363,38 @@ fn validate_rows(rows: Vec<Vec<Row>>) -> Result<Vec<Vec<Row>>> {
}
}

fn to_table_spaces_entry(
instance_name: &str,
database_name: &str,
rows: &Vec<Vec<Row>>,
sep: char,
) -> String {
let extract = |rows: &Vec<Vec<Row>>, part: usize, name: &str| {
if (rows.len() < part) || rows[part].is_empty() {
String::new()
} else {
rows[part][0].get_value_by_name(name).trim().to_string()
}
};
let db_size = extract(rows, 0, "database_size");
let unallocated = extract(rows, 0, "unallocated space");
let reserved = extract(rows, 1, "reserved");
let data = extract(rows, 1, "data");
let index_size = extract(rows, 1, "index_size");
let unused = extract(rows, 1, "unused");
format!(
"{}{sep}{}{sep}{}{sep}{}{sep}{}{sep}{}{sep}{}{sep}{}\n",
instance_name,
database_name.replace(' ', "_"),
db_size,
unallocated,
reserved,
data,
index_size,
unused
)
}

fn to_counter_entry(row: &Row, sep: char) -> String {
let counter = row
.get_value_by_idx(0)
Expand Down
5 changes: 3 additions & 2 deletions packages/check-sql/src/ms_sql/queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,15 +126,16 @@ pub const QUERY_DETAILS_VERSION_PARAM: &str = "prod_version";
pub const QUERY_DETAILS_LEVEL_PARAM: &str = "prod_level";
pub const QUERY_DETAILS_EDITION_PARAM: &str = "prod_edition";

pub const SYS_DATABASES: &str = "SELECT name FROM sys.databases";
pub const QUERY_DATABASES: &str = "SELECT name FROM sys.databases";
pub const QUERY_SPACE_USED: &str = "EXEC sp_spaceused";
pub const BAD_QUERY: &str = "SELEC name FROM sys.databases";

pub fn get_instances_query() -> String {
QUERY_ALL_BASE.to_string()
}

// production
pub fn get_blocked_sessions_query() -> String {
pub fn get_blocking_sessions_query() -> String {
format!("{} WHERE blocking_session_id <> 0 ", QUERY_WAITING_TASKS).to_string()
}

Expand Down
45 changes: 41 additions & 4 deletions packages/check-sql/tests/test_ms_sql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
mod common;

use check_sql::ms_sql::{api::Client, api::InstanceEngine, queries};
use check_sql::{config::CheckConfig, ms_sql::api};
use check_sql::{config::ms_sql::Endpoint, config::CheckConfig, ms_sql::api};
use common::tools::{self, SqlDbEndpoint};
use tempfile::TempDir;
use yaml_rust::YamlLoader;
Expand Down Expand Up @@ -147,9 +147,10 @@ async fn test_validate_all_instances_remote() {
validate_blocked_sessions(&i, &mut c).await;
validate_all_sessions_to_check_format(&i, &mut c).await;
assert!(&i
.generate_waiting_sessions_entry(&mut c, queries::BAD_QUERY, '|',)
.generate_blocking_sessions_section(&mut c, queries::BAD_QUERY, '|',)
.await
.contains(" error: "),);
validate_table_spaces(&i, &mut c, &cfg.endpoint()).await;
}
Err(e) => {
panic!("Unexpected error: `{:?}`", e);
Expand All @@ -174,7 +175,7 @@ async fn validate_counters(instance: &InstanceEngine, client: &mut Client) {

async fn validate_blocked_sessions(instance: &InstanceEngine, client: &mut Client) {
let blocked_sessions = &instance
.generate_waiting_sessions_entry(client, &queries::get_blocked_sessions_query(), '|')
.generate_blocking_sessions_section(client, &queries::get_blocking_sessions_query(), '|')
.await;
assert_eq!(
blocked_sessions,
Expand All @@ -184,7 +185,7 @@ async fn validate_blocked_sessions(instance: &InstanceEngine, client: &mut Clien

async fn validate_all_sessions_to_check_format(instance: &InstanceEngine, client: &mut Client) {
let all_sessions = &instance
.generate_waiting_sessions_entry(client, queries::QUERY_WAITING_TASKS, '|')
.generate_blocking_sessions_section(client, queries::QUERY_WAITING_TASKS, '|')
.await;

let lines: Vec<&str> = all_sessions.split('\n').collect::<Vec<&str>>();
Expand All @@ -206,6 +207,42 @@ async fn validate_all_sessions_to_check_format(instance: &InstanceEngine, client
}
}

async fn validate_table_spaces(
instance: &InstanceEngine,
client: &mut Client,
endpoint: &Endpoint,
) {
let databases = instance.generate_databases(client).await;
let expected = vec!["master", "tempdb", "model", "msdb"];
// O^2, but enough good for testing
assert!(
expected
.iter()
.map(|&s| s.to_string())
.all(|item| databases.contains(&item)),
"{:?} {:?}",
databases,
expected
);

let result = instance
.generate_table_spaces_section(client, endpoint, ' ')
.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);
assert!(values[2].parse::<f64>().is_ok(), "bad line: {}", l);
assert!(values[4].parse::<f64>().is_ok(), "bad line: {}", l);
assert!(values[6].parse::<u32>().is_ok(), "bad line: {}", l);
assert!(values[8].parse::<u32>().is_ok(), "bad line: {}", l);
assert!(values[10].parse::<u32>().is_ok(), "bad line: {}", l);
assert!(values[12].parse::<u32>().is_ok(), "bad line: {}", l);
}
}

/// 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 08d0b6e

Please sign in to comment.