mirror of
https://github.com/percona/pg_stat_monitor.git
synced 2026-02-04 05:56:21 +00:00
PG-254: Removal of pgss_query_hash hash table.
There was no necessity for using a separate hash table to keep track of queries as all the necessary information is already available in the pgss_hash table. The code that moves pending queries' text in hash_query_entry_dealloc was merged into hash_entry_dealloc. Couple functions were not necessary anymore, thus were removed: - hash_create_query_entry - pgss_store_query_info - pgss_get_entry (this logic was added directly into pgss_store). We simplified the logic in pgss_store, it basically works as follows: 1. Create a key (bucketid, queryid, etc...) for the query event. 2. Lookup the key in pgss_hash, if no entry exists for the key, create a new one, save the query text into the current query buffer. 3. If jstate == NULL, then update stats counters for the entry.
This commit is contained in:
278
hash_query.c
278
hash_query.c
@@ -22,7 +22,6 @@
|
||||
|
||||
static pgssSharedState *pgss;
|
||||
static HTAB *pgss_hash;
|
||||
static HTAB *pgss_query_hash;
|
||||
|
||||
static HTAB* hash_init(const char *hash_name, int key_size, int entry_size, int hash_size);
|
||||
/*
|
||||
@@ -55,7 +54,6 @@ pgss_startup(void)
|
||||
|
||||
pgss = NULL;
|
||||
pgss_hash = NULL;
|
||||
pgss_query_hash = NULL;
|
||||
|
||||
/*
|
||||
* Create or attach to the shared memory state, including hash table
|
||||
@@ -85,7 +83,6 @@ pgss_startup(void)
|
||||
}
|
||||
|
||||
pgss_hash = hash_init("pg_stat_monitor: bucket hashtable", sizeof(pgssHashKey), sizeof(pgssEntry), MAX_BUCKET_ENTRIES);
|
||||
pgss_query_hash = hash_init("pg_stat_monitor: query hashtable", sizeof(pgssQueryHashKey), sizeof(pgssQueryEntry),MAX_BUCKET_ENTRIES);
|
||||
|
||||
LWLockRelease(AddinShmemInitLock);
|
||||
|
||||
@@ -169,146 +166,64 @@ hash_entry_alloc(pgssSharedState *pgss, pgssHashKey *key, int encoding)
|
||||
elog(DEBUG1, "%s", "pg_stat_monitor: out of memory");
|
||||
return entry;
|
||||
}
|
||||
|
||||
/*
|
||||
* Reset all the entries.
|
||||
* Prepare resources for using the new bucket:
|
||||
* - Deallocate finished hash table entries in new_bucket_id (entries whose
|
||||
* state is PGSS_FINISHED or PGSS_FINISHED).
|
||||
* - Clear query buffer for new_bucket_id.
|
||||
* - If old_bucket_id != -1, move all pending hash table entries in
|
||||
* old_bucket_id to the new bucket id, also move pending queries from the
|
||||
* previous query buffer (query_buffer[old_bucket_id]) to the new one
|
||||
* (query_buffer[new_bucket_id]).
|
||||
*
|
||||
* Caller must hold an exclusive lock on pgss->lock.
|
||||
*/
|
||||
void
|
||||
hash_query_entryies_reset()
|
||||
{
|
||||
HASH_SEQ_STATUS hash_seq;
|
||||
pgssQueryEntry *entry;
|
||||
|
||||
hash_seq_init(&hash_seq, pgss_query_hash);
|
||||
while ((entry = hash_seq_search(&hash_seq)) != NULL)
|
||||
entry = hash_search(pgss_query_hash, &entry->key, HASH_REMOVE, NULL);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Deallocate finished entries in new_bucket_id.
|
||||
*
|
||||
* Move all pending queries in query_buffer[old_bucket_id] to
|
||||
* query_buffer[new_bucket_id].
|
||||
*
|
||||
* Caller must hold an exclusive lock on pgss->lock.
|
||||
*/
|
||||
void
|
||||
hash_query_entry_dealloc(int new_bucket_id, int old_bucket_id, unsigned char *query_buffer[])
|
||||
{
|
||||
HASH_SEQ_STATUS hash_seq;
|
||||
pgssQueryEntry *entry;
|
||||
pgssSharedState *pgss = pgsm_get_ss();
|
||||
/*
|
||||
* Store pending query ids from the previous bucket.
|
||||
* If there are more pending queries than MAX_PENDING_QUERIES then
|
||||
* we try to dynamically allocate memory for them.
|
||||
*/
|
||||
#define MAX_PENDING_QUERIES 128
|
||||
uint64 pending_query_ids[MAX_PENDING_QUERIES];
|
||||
uint64 *pending_query_ids_buf = NULL;
|
||||
size_t n_pending_queries = 0;
|
||||
bool out_of_memory = false;
|
||||
|
||||
/* Clear all queries in the query buffer for the new bucket. */
|
||||
memset(query_buffer[new_bucket_id], 0, pgss->query_buf_size_bucket);
|
||||
|
||||
hash_seq_init(&hash_seq, pgss_query_hash);
|
||||
while ((entry = hash_seq_search(&hash_seq)) != NULL)
|
||||
{
|
||||
/* Remove previous finished query entries matching new bucket id. */
|
||||
if (entry->key.bucket_id == new_bucket_id)
|
||||
{
|
||||
if (entry->state == PGSS_FINISHED || entry->state == PGSS_ERROR)
|
||||
{
|
||||
entry = hash_search(pgss_query_hash, &entry->key, HASH_REMOVE, NULL);
|
||||
}
|
||||
}
|
||||
/* Set up a list of pending query ids from the previous bucket. */
|
||||
else if (entry->key.bucket_id == old_bucket_id &&
|
||||
(entry->state == PGSS_PARSE ||
|
||||
entry->state == PGSS_PLAN ||
|
||||
entry->state == PGSS_EXEC))
|
||||
{
|
||||
if (n_pending_queries < MAX_PENDING_QUERIES)
|
||||
{
|
||||
pending_query_ids[n_pending_queries] = entry->key.queryid;
|
||||
++n_pending_queries;
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* No. of pending queries exceeds MAX_PENDING_QUERIES.
|
||||
* Try to allocate memory from heap to keep track of pending query ids.
|
||||
* If allocation fails we manually copy pending query to the next query buffer.
|
||||
*/
|
||||
if (!out_of_memory && !pending_query_ids_buf)
|
||||
{
|
||||
/* Allocate enough room for query ids. */
|
||||
pending_query_ids_buf = malloc(sizeof(uint64) * hash_get_num_entries(pgss_query_hash));
|
||||
if (pending_query_ids_buf != NULL)
|
||||
memcpy(pending_query_ids_buf, pending_query_ids, n_pending_queries * sizeof(uint64));
|
||||
else
|
||||
out_of_memory = true;
|
||||
}
|
||||
|
||||
if (!out_of_memory)
|
||||
{
|
||||
/* Store pending query id in the dynamic buffer. */
|
||||
pending_query_ids_buf[n_pending_queries] = entry->key.queryid;
|
||||
++n_pending_queries;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* No memory, manually copy query from previous buffer. */
|
||||
char query_txt[1024];
|
||||
|
||||
if (read_query(query_buffer[old_bucket_id], old_bucket_id, entry->key.queryid, query_txt) != 0
|
||||
|| read_query_buffer(old_bucket_id, entry->key.queryid, query_txt) == MAX_QUERY_BUFFER_BUCKET)
|
||||
{
|
||||
SaveQueryText(new_bucket_id, entry->key.queryid, query_buffer[new_bucket_id], query_txt, strlen(query_txt));
|
||||
}
|
||||
else
|
||||
/* There was no space available to store the pending query text. */
|
||||
elog(WARNING, "hash_query_entry_dealloc: Failed to move pending query %lX, %s",
|
||||
entry->key.queryid,
|
||||
(PGSM_OVERFLOW_TARGET == OVERFLOW_TARGET_NONE) ?
|
||||
"insufficient shared space for query" :
|
||||
"I/O error reading query from disk");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Copy all detected pending queries from previous bucket id to the new one. */
|
||||
if (n_pending_queries > 0) {
|
||||
if (n_pending_queries < MAX_PENDING_QUERIES)
|
||||
pending_query_ids_buf = pending_query_ids;
|
||||
|
||||
copy_queries(query_buffer, new_bucket_id, old_bucket_id, pending_query_ids_buf, n_pending_queries);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Deallocate least-used entries.
|
||||
*
|
||||
* If old_bucket_id != -1, move all pending queries in old_bucket_id
|
||||
* to the new bucket id.
|
||||
*
|
||||
* Caller must hold an exclusive lock on pgss->lock.
|
||||
*/
|
||||
bool
|
||||
hash_entry_dealloc(int new_bucket_id, int old_bucket_id)
|
||||
hash_entry_dealloc(int new_bucket_id, int old_bucket_id, unsigned char *query_buffer[])
|
||||
{
|
||||
HASH_SEQ_STATUS hash_seq;
|
||||
pgssEntry *entry = NULL;
|
||||
List *pending_entries = NIL;
|
||||
ListCell *pending_entry;
|
||||
pgssSharedState *pgss = pgsm_get_ss();
|
||||
|
||||
#define MAX_PENDING_QUERIES 128
|
||||
/*
|
||||
* Variables used to store pending queries from the previous bucket.
|
||||
*
|
||||
* We use a linked list to keep a full copy of entries from the hash table
|
||||
* that must be moved to the new bucket.
|
||||
*
|
||||
* We use an array to keep a list of pending query IDs only, the array will
|
||||
* be used in copy_queries() as a filter of which queries to copy.
|
||||
* The reason we use a separate array to keep pending queries IDs is that it
|
||||
* is faster to iterate than the linked list, as following pointers in a list
|
||||
* almost always make bad use of cpu cache, while a small array of uint64 is
|
||||
* a good candidate to be stored in L1 cache.
|
||||
*
|
||||
* If there are more pending queries than MAX_PENDING_QUERIES then
|
||||
* we try to dynamically allocate memory for them.
|
||||
*/
|
||||
List *pending_entries = NIL;
|
||||
ListCell *pending_entry;
|
||||
uint64 pending_query_ids[MAX_PENDING_QUERIES];
|
||||
uint64 *pending_query_ids_buf = NULL;
|
||||
size_t n_pending_queries = 0;
|
||||
bool out_of_memory = false;
|
||||
|
||||
if (new_bucket_id != -1)
|
||||
{
|
||||
/* Clear all queries in the query buffer for the new bucket. */
|
||||
memset(query_buffer[new_bucket_id], 0, pgss->query_buf_size_bucket);
|
||||
}
|
||||
|
||||
/* Iterate over the hash table. */
|
||||
hash_seq_init(&hash_seq, pgss_hash);
|
||||
while ((entry = hash_seq_search(&hash_seq)) != NULL)
|
||||
{
|
||||
/*
|
||||
* Remove all entries if new_bucket_id == -1.
|
||||
* Otherwise remove entry in new_bucket_id if it's finished already.
|
||||
*/
|
||||
if (new_bucket_id < 0 ||
|
||||
(entry->key.bucket_id == new_bucket_id &&
|
||||
(entry->counters.state == PGSS_FINISHED || entry->counters.state == PGSS_ERROR)))
|
||||
@@ -333,6 +248,7 @@ hash_entry_dealloc(int new_bucket_id, int old_bucket_id)
|
||||
if (!bkp_entry)
|
||||
{
|
||||
/* No memory, remove pending query entry from the previous bucket. */
|
||||
elog(ERROR, "hash_entry_dealloc: out of memory");
|
||||
entry = hash_search(pgss_hash, &entry->key, HASH_REMOVE, NULL);
|
||||
continue;
|
||||
}
|
||||
@@ -346,12 +262,63 @@ hash_entry_dealloc(int new_bucket_id, int old_bucket_id)
|
||||
/* Add the entry to a list of nodes to be processed later. */
|
||||
pending_entries = lappend(pending_entries, bkp_entry);
|
||||
|
||||
/* Add pending query ID to the array. */
|
||||
if (n_pending_queries < MAX_PENDING_QUERIES)
|
||||
{
|
||||
pending_query_ids[n_pending_queries] = entry->key.queryid;
|
||||
++n_pending_queries;
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* No. of pending queries exceeds MAX_PENDING_QUERIES.
|
||||
* Try to dynamically allocate memory to keep track of pending query ids.
|
||||
* If allocation fails we manually copy pending query to the next query buffer.
|
||||
*/
|
||||
if (!out_of_memory && !pending_query_ids_buf)
|
||||
{
|
||||
/* Allocate enough room for query ids. */
|
||||
pending_query_ids_buf = malloc(sizeof(uint64) * hash_get_num_entries(pgss_hash));
|
||||
if (pending_query_ids_buf != NULL)
|
||||
memcpy(pending_query_ids_buf, pending_query_ids, n_pending_queries * sizeof(uint64));
|
||||
else
|
||||
out_of_memory = true;
|
||||
}
|
||||
|
||||
if (!out_of_memory)
|
||||
{
|
||||
/* Store pending query id in the dynamic buffer. */
|
||||
pending_query_ids_buf[n_pending_queries] = entry->key.queryid;
|
||||
++n_pending_queries;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* No memory, manually copy query from previous buffer. */
|
||||
char query_txt[1024];
|
||||
|
||||
if (read_query(query_buffer[old_bucket_id], old_bucket_id, entry->key.queryid, query_txt) != 0
|
||||
|| read_query_buffer(old_bucket_id, entry->key.queryid, query_txt) == MAX_QUERY_BUFFER_BUCKET)
|
||||
{
|
||||
SaveQueryText(new_bucket_id, entry->key.queryid, query_buffer[new_bucket_id], query_txt, strlen(query_txt));
|
||||
}
|
||||
else
|
||||
/* There was no space available to store the pending query text. */
|
||||
elog(ERROR, "hash_entry_dealloc: Failed to move pending query %lX, %s",
|
||||
entry->key.queryid,
|
||||
(PGSM_OVERFLOW_TARGET == OVERFLOW_TARGET_NONE) ?
|
||||
"insufficient shared space for query" :
|
||||
"I/O error reading query from disk");
|
||||
}
|
||||
}
|
||||
|
||||
/* Finally remove the pending query from the expired bucket id. */
|
||||
entry = hash_search(pgss_hash, &entry->key, HASH_REMOVE, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Assert(list_length(pending_entries) == n_pending_queries);
|
||||
|
||||
/*
|
||||
* Iterate over the list of pending queries in order
|
||||
* to add them back to the hash table with the updated bucket id.
|
||||
@@ -375,9 +342,15 @@ hash_entry_dealloc(int new_bucket_id, int old_bucket_id)
|
||||
free(old_entry);
|
||||
}
|
||||
|
||||
list_free(pending_entries);
|
||||
/* Copy all detected pending queries from previous bucket id to the new one. */
|
||||
if (n_pending_queries > 0) {
|
||||
if (n_pending_queries < MAX_PENDING_QUERIES)
|
||||
pending_query_ids_buf = pending_query_ids;
|
||||
|
||||
return true;
|
||||
copy_queries(query_buffer, new_bucket_id, old_bucket_id, pending_query_ids_buf, n_pending_queries);
|
||||
}
|
||||
|
||||
list_free(pending_entries);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -401,45 +374,6 @@ hash_entry_reset()
|
||||
LWLockRelease(pgss->lock);
|
||||
}
|
||||
|
||||
/* Caller must acquire a lock */
|
||||
pgssQueryEntry*
|
||||
hash_create_query_entry(uint64 bucket_id, uint64 queryid, uint64 dbid, uint64 userid, uint64 ip, uint64 appid)
|
||||
{
|
||||
pgssQueryHashKey key;
|
||||
pgssQueryEntry *entry;
|
||||
bool found;
|
||||
|
||||
key.queryid = queryid;
|
||||
key.bucket_id = bucket_id;
|
||||
key.dbid = dbid;
|
||||
key.userid = userid;
|
||||
key.ip = ip;
|
||||
key.appid = appid;
|
||||
|
||||
entry = (pgssQueryEntry *) hash_search(pgss_query_hash, &key, HASH_ENTER_NULL, &found);
|
||||
return entry;
|
||||
}
|
||||
|
||||
/* Caller must acquire a lock */
|
||||
pgssQueryEntry*
|
||||
hash_find_query_entry(uint64 bucket_id, uint64 queryid, uint64 dbid, uint64 userid, uint64 ip, uint64 appid)
|
||||
{
|
||||
pgssQueryHashKey key;
|
||||
pgssQueryEntry *entry;
|
||||
bool found;
|
||||
|
||||
key.queryid = queryid;
|
||||
key.bucket_id = bucket_id;
|
||||
key.dbid = dbid;
|
||||
key.userid = userid;
|
||||
key.ip = ip;
|
||||
key.appid = appid;
|
||||
|
||||
/* Lookup the hash table entry with shared lock. */
|
||||
entry = (pgssQueryEntry *) hash_search(pgss_query_hash, &key, HASH_FIND, &found);
|
||||
return entry;
|
||||
}
|
||||
|
||||
bool
|
||||
IsHashInitialize(void)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user