mirror of
https://github.com/postgresml/pgcat.git
synced 2026-03-25 10:06:28 +00:00
@@ -57,6 +57,7 @@ pub struct Client {
|
|||||||
default_server_role: Option<Role>,
|
default_server_role: Option<Role>,
|
||||||
|
|
||||||
// Client parameters, e.g. user, client_encoding, etc.
|
// Client parameters, e.g. user, client_encoding, etc.
|
||||||
|
#[allow(dead_code)]
|
||||||
parameters: HashMap<String, String>,
|
parameters: HashMap<String, String>,
|
||||||
|
|
||||||
// Statistics
|
// Statistics
|
||||||
@@ -302,8 +303,11 @@ impl Client {
|
|||||||
|
|
||||||
// Release server
|
// Release server
|
||||||
if self.transaction_mode {
|
if self.transaction_mode {
|
||||||
|
self.stats.client_idle();
|
||||||
|
|
||||||
shard = None;
|
shard = None;
|
||||||
role = self.default_server_role;
|
role = self.default_server_role;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -371,8 +375,11 @@ impl Client {
|
|||||||
self.stats.transaction();
|
self.stats.transaction();
|
||||||
|
|
||||||
if self.transaction_mode {
|
if self.transaction_mode {
|
||||||
|
self.stats.client_idle();
|
||||||
|
|
||||||
shard = None;
|
shard = None;
|
||||||
role = self.default_server_role;
|
role = self.default_server_role;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ pub enum Role {
|
|||||||
pub struct Address {
|
pub struct Address {
|
||||||
pub host: String,
|
pub host: String,
|
||||||
pub port: String,
|
pub port: String,
|
||||||
|
pub shard: usize,
|
||||||
pub role: Role,
|
pub role: Role,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
19
src/pool.rs
19
src/pool.rs
@@ -57,7 +57,7 @@ impl ConnectionPool {
|
|||||||
let mut pools = Vec::new();
|
let mut pools = Vec::new();
|
||||||
let mut replica_addresses = Vec::new();
|
let mut replica_addresses = Vec::new();
|
||||||
|
|
||||||
for server in &shard.servers {
|
for (idx, server) in shard.servers.iter().enumerate() {
|
||||||
let role = match server.2.as_ref() {
|
let role = match server.2.as_ref() {
|
||||||
"primary" => Role::Primary,
|
"primary" => Role::Primary,
|
||||||
"replica" => Role::Replica,
|
"replica" => Role::Replica,
|
||||||
@@ -71,6 +71,7 @@ impl ConnectionPool {
|
|||||||
host: server.0.clone(),
|
host: server.0.clone(),
|
||||||
port: server.1.to_string(),
|
port: server.1.to_string(),
|
||||||
role: role,
|
role: role,
|
||||||
|
shard: idx,
|
||||||
};
|
};
|
||||||
|
|
||||||
let manager = ServerPool::new(
|
let manager = ServerPool::new(
|
||||||
@@ -165,6 +166,9 @@ impl ConnectionPool {
|
|||||||
None => 0, // TODO: pick a shard at random
|
None => 0, // TODO: pick a shard at random
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// We are waiting for a server now.
|
||||||
|
self.stats.client_waiting();
|
||||||
|
|
||||||
let addresses = &self.addresses[shard];
|
let addresses = &self.addresses[shard];
|
||||||
|
|
||||||
// Make sure if a specific role is requested, it's available in the pool.
|
// Make sure if a specific role is requested, it's available in the pool.
|
||||||
@@ -237,7 +241,8 @@ impl ConnectionPool {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if !with_health_check {
|
if !with_health_check {
|
||||||
self.stats.checkout_time(now.elapsed().as_millis());
|
self.stats.checkout_time(now.elapsed().as_micros());
|
||||||
|
self.stats.client_active();
|
||||||
return Ok((conn, address.clone()));
|
return Ok((conn, address.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,7 +258,8 @@ impl ConnectionPool {
|
|||||||
// Check if health check succeeded
|
// Check if health check succeeded
|
||||||
Ok(res) => match res {
|
Ok(res) => match res {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
self.stats.checkout_time(now.elapsed().as_millis());
|
self.stats.checkout_time(now.elapsed().as_micros());
|
||||||
|
self.stats.client_active();
|
||||||
return Ok((conn, address.clone()));
|
return Ok((conn, address.clone()));
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
@@ -385,13 +391,10 @@ impl ManageConnection for ServerPool {
|
|||||||
println!(">> Creating a new connection for the pool");
|
println!(">> Creating a new connection for the pool");
|
||||||
|
|
||||||
Server::startup(
|
Server::startup(
|
||||||
&self.address.host,
|
&self.address,
|
||||||
&self.address.port,
|
&self.user,
|
||||||
&self.user.name,
|
|
||||||
&self.user.password,
|
|
||||||
&self.database,
|
&self.database,
|
||||||
self.client_server_map.clone(),
|
self.client_server_map.clone(),
|
||||||
self.address.role,
|
|
||||||
self.stats.clone(),
|
self.stats.clone(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use tokio::io::{AsyncReadExt, BufReader};
|
|||||||
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
|
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
|
|
||||||
use crate::config::{Address, Role};
|
use crate::config::{Address, User};
|
||||||
use crate::errors::Error;
|
use crate::errors::Error;
|
||||||
use crate::messages::*;
|
use crate::messages::*;
|
||||||
use crate::stats::Reporter;
|
use crate::stats::Reporter;
|
||||||
@@ -16,11 +16,9 @@ use crate::ClientServerMap;
|
|||||||
|
|
||||||
/// Server state.
|
/// Server state.
|
||||||
pub struct Server {
|
pub struct Server {
|
||||||
// Server host, e.g. localhost
|
// Server host, e.g. localhost,
|
||||||
host: String,
|
// port, e.g. 5432, and role, e.g. primary or replica.
|
||||||
|
address: Address,
|
||||||
// Server port: e.g. 5432
|
|
||||||
port: String,
|
|
||||||
|
|
||||||
// Buffered read socket
|
// Buffered read socket
|
||||||
read: BufReader<OwnedReadHalf>,
|
read: BufReader<OwnedReadHalf>,
|
||||||
@@ -50,9 +48,6 @@ pub struct Server {
|
|||||||
// Mapping of clients and servers used for query cancellation.
|
// Mapping of clients and servers used for query cancellation.
|
||||||
client_server_map: ClientServerMap,
|
client_server_map: ClientServerMap,
|
||||||
|
|
||||||
// Server role, e.g. primary or replica.
|
|
||||||
role: Role,
|
|
||||||
|
|
||||||
// Server connected at
|
// Server connected at
|
||||||
connected_at: chrono::naive::NaiveDateTime,
|
connected_at: chrono::naive::NaiveDateTime,
|
||||||
|
|
||||||
@@ -64,25 +59,23 @@ impl Server {
|
|||||||
/// Pretend to be the Postgres client and connect to the server given host, port and credentials.
|
/// Pretend to be the Postgres client and connect to the server given host, port and credentials.
|
||||||
/// Perform the authentication and return the server in a ready-for-query mode.
|
/// Perform the authentication and return the server in a ready-for-query mode.
|
||||||
pub async fn startup(
|
pub async fn startup(
|
||||||
host: &str,
|
address: &Address,
|
||||||
port: &str,
|
user: &User,
|
||||||
user: &str,
|
|
||||||
password: &str,
|
|
||||||
database: &str,
|
database: &str,
|
||||||
client_server_map: ClientServerMap,
|
client_server_map: ClientServerMap,
|
||||||
role: Role,
|
|
||||||
stats: Reporter,
|
stats: Reporter,
|
||||||
) -> Result<Server, Error> {
|
) -> Result<Server, Error> {
|
||||||
let mut stream = match TcpStream::connect(&format!("{}:{}", host, port)).await {
|
let mut stream =
|
||||||
Ok(stream) => stream,
|
match TcpStream::connect(&format!("{}:{}", &address.host, &address.port)).await {
|
||||||
Err(err) => {
|
Ok(stream) => stream,
|
||||||
println!(">> Could not connect to server: {}", err);
|
Err(err) => {
|
||||||
return Err(Error::SocketError);
|
println!(">> Could not connect to server: {}", err);
|
||||||
}
|
return Err(Error::SocketError);
|
||||||
};
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Send the startup packet.
|
// Send the startup packet.
|
||||||
startup(&mut stream, user, database).await?;
|
startup(&mut stream, &user.name, database).await?;
|
||||||
|
|
||||||
let mut server_info = BytesMut::with_capacity(25);
|
let mut server_info = BytesMut::with_capacity(25);
|
||||||
let mut backend_id: i32 = 0;
|
let mut backend_id: i32 = 0;
|
||||||
@@ -117,7 +110,8 @@ impl Server {
|
|||||||
Err(_) => return Err(Error::SocketError),
|
Err(_) => return Err(Error::SocketError),
|
||||||
};
|
};
|
||||||
|
|
||||||
md5_password(&mut stream, user, password, &salt[..]).await?;
|
md5_password(&mut stream, &user.name, &user.password, &salt[..])
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Authentication handshake complete.
|
// Authentication handshake complete.
|
||||||
@@ -189,8 +183,7 @@ impl Server {
|
|||||||
let (read, write) = stream.into_split();
|
let (read, write) = stream.into_split();
|
||||||
|
|
||||||
return Ok(Server {
|
return Ok(Server {
|
||||||
host: host.to_string(),
|
address: address.clone(),
|
||||||
port: port.to_string(),
|
|
||||||
read: BufReader::new(read),
|
read: BufReader::new(read),
|
||||||
write: write,
|
write: write,
|
||||||
buffer: BytesMut::with_capacity(8196),
|
buffer: BytesMut::with_capacity(8196),
|
||||||
@@ -201,7 +194,6 @@ impl Server {
|
|||||||
data_available: false,
|
data_available: false,
|
||||||
bad: false,
|
bad: false,
|
||||||
client_server_map: client_server_map,
|
client_server_map: client_server_map,
|
||||||
role: role,
|
|
||||||
connected_at: chrono::offset::Utc::now().naive_utc(),
|
connected_at: chrono::offset::Utc::now().naive_utc(),
|
||||||
stats: stats,
|
stats: stats,
|
||||||
});
|
});
|
||||||
@@ -382,8 +374,8 @@ impl Server {
|
|||||||
(
|
(
|
||||||
self.backend_id,
|
self.backend_id,
|
||||||
self.secret_key,
|
self.secret_key,
|
||||||
self.host.clone(),
|
self.address.host.clone(),
|
||||||
self.port.clone(),
|
self.address.port.clone(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -422,11 +414,7 @@ impl Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn address(&self) -> Address {
|
pub fn address(&self) -> Address {
|
||||||
Address {
|
self.address.clone()
|
||||||
host: self.host.to_string(),
|
|
||||||
port: self.port.to_string(),
|
|
||||||
role: self.role,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
106
src/stats.rs
106
src/stats.rs
@@ -14,6 +14,9 @@ pub enum StatisticName {
|
|||||||
Transactions,
|
Transactions,
|
||||||
DataSent,
|
DataSent,
|
||||||
DataReceived,
|
DataReceived,
|
||||||
|
ClientsWaiting,
|
||||||
|
ClientsActive,
|
||||||
|
ClientsIdle,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -76,6 +79,54 @@ impl Reporter {
|
|||||||
|
|
||||||
let _ = self.tx.try_send(statistic);
|
let _ = self.tx.try_send(statistic);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn client_waiting(&mut self) {
|
||||||
|
let statistic = Statistic {
|
||||||
|
name: StatisticName::ClientsWaiting,
|
||||||
|
value: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = self.tx.try_send(statistic);
|
||||||
|
|
||||||
|
let statistic = Statistic {
|
||||||
|
name: StatisticName::ClientsIdle,
|
||||||
|
value: -1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = self.tx.try_send(statistic);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn client_active(&mut self) {
|
||||||
|
let statistic = Statistic {
|
||||||
|
name: StatisticName::ClientsWaiting,
|
||||||
|
value: -1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = self.tx.try_send(statistic);
|
||||||
|
|
||||||
|
let statistic = Statistic {
|
||||||
|
name: StatisticName::ClientsActive,
|
||||||
|
value: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = self.tx.try_send(statistic);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn client_idle(&mut self) {
|
||||||
|
let statistic = Statistic {
|
||||||
|
name: StatisticName::ClientsActive,
|
||||||
|
value: -1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = self.tx.try_send(statistic);
|
||||||
|
|
||||||
|
let statistic = Statistic {
|
||||||
|
name: StatisticName::ClientsIdle,
|
||||||
|
value: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = self.tx.try_send(statistic);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Collector {
|
pub struct Collector {
|
||||||
@@ -93,12 +144,18 @@ impl Collector {
|
|||||||
|
|
||||||
pub async fn collect(&mut self) {
|
pub async fn collect(&mut self) {
|
||||||
let mut stats = HashMap::from([
|
let mut stats = HashMap::from([
|
||||||
("queries", 0),
|
("total_query_count", 0),
|
||||||
("transactions", 0),
|
("total_xact_count", 0),
|
||||||
("data_sent", 0),
|
("total_sent", 0),
|
||||||
("data_received", 0),
|
("total_received", 0),
|
||||||
("checkout_time", 0),
|
("total_wait_time", 0),
|
||||||
|
("maxwait_us", 0),
|
||||||
|
("maxwait", 0),
|
||||||
|
("cl_waiting", 0),
|
||||||
|
("cl_active", 0),
|
||||||
|
("cl_idle", 0),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let mut now = Instant::now();
|
let mut now = Instant::now();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
@@ -113,32 +170,61 @@ impl Collector {
|
|||||||
// Some are counters, some are gauges...
|
// Some are counters, some are gauges...
|
||||||
match stat.name {
|
match stat.name {
|
||||||
StatisticName::Queries => {
|
StatisticName::Queries => {
|
||||||
let counter = stats.entry("queries").or_insert(0);
|
let counter = stats.entry("total_query_count").or_insert(0);
|
||||||
*counter += stat.value;
|
*counter += stat.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
StatisticName::Transactions => {
|
StatisticName::Transactions => {
|
||||||
let counter = stats.entry("transactions").or_insert(0);
|
let counter = stats.entry("total_xact_count").or_insert(0);
|
||||||
*counter += stat.value;
|
*counter += stat.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
StatisticName::DataSent => {
|
StatisticName::DataSent => {
|
||||||
let counter = stats.entry("data_sent").or_insert(0);
|
let counter = stats.entry("total_sent").or_insert(0);
|
||||||
*counter += stat.value;
|
*counter += stat.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
StatisticName::DataReceived => {
|
StatisticName::DataReceived => {
|
||||||
let counter = stats.entry("data_received").or_insert(0);
|
let counter = stats.entry("total_received").or_insert(0);
|
||||||
*counter += stat.value;
|
*counter += stat.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
StatisticName::CheckoutTime => {
|
StatisticName::CheckoutTime => {
|
||||||
let counter = stats.entry("checkout_time").or_insert(0);
|
let counter = stats.entry("total_wait_time").or_insert(0);
|
||||||
|
*counter += stat.value;
|
||||||
|
|
||||||
|
let counter = stats.entry("maxwait_us").or_insert(0);
|
||||||
|
|
||||||
// Report max time here
|
// Report max time here
|
||||||
if stat.value > *counter {
|
if stat.value > *counter {
|
||||||
*counter = stat.value;
|
*counter = stat.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let counter = stats.entry("maxwait").or_insert(0);
|
||||||
|
let seconds = *counter / 1_000_000;
|
||||||
|
|
||||||
|
if seconds > *counter {
|
||||||
|
*counter = seconds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StatisticName::ClientsActive => {
|
||||||
|
let counter = stats.entry("cl_active").or_insert(0);
|
||||||
|
|
||||||
|
*counter += stat.value;
|
||||||
|
*counter = std::cmp::max(*counter, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
StatisticName::ClientsWaiting => {
|
||||||
|
let counter = stats.entry("cl_waiting").or_insert(0);
|
||||||
|
*counter += stat.value;
|
||||||
|
*counter = std::cmp::max(*counter, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
StatisticName::ClientsIdle => {
|
||||||
|
let counter = stats.entry("cl_idle").or_insert(0);
|
||||||
|
*counter += stat.value;
|
||||||
|
*counter = std::cmp::max(*counter, 0);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user