Datum
chash_write_stats_to_log(PG_FUNCTION_ARGS)
{
- StringInfoData buf;
- CHashTableStats stats;
+ uint64 stats[CHS_NumberOfStatistics];
+ CHashStatisticsType i;
+ StringInfoData buf;
- CHashStatistics(chash, &stats);
+ CHashStatistics(chash, stats);
initStringInfo(&buf);
- if (stats.s_insert_retry)
- appendStringInfo(&buf, UINT64_FORMAT " inserts retried; ",
- stats.s_insert_retry);
- if (stats.s_delete_retry)
- appendStringInfo(&buf, UINT64_FORMAT " deletes retried; ",
- stats.s_delete_retry);
- if (stats.s_cleanup_scan)
- appendStringInfo(&buf, UINT64_FORMAT " cleanup scans; ",
- stats.s_delete_retry);
- if (stats.s_scan_expunge_ok)
- appendStringInfo(&buf, UINT64_FORMAT " scan expunges ok; ",
- stats.s_scan_expunge_ok);
- if (stats.s_scan_expunge_fail)
- appendStringInfo(&buf, UINT64_FORMAT " scan expunges failed; ",
- stats.s_scan_expunge_fail);
- if (stats.s_scan_restart)
- appendStringInfo(&buf, UINT64_FORMAT " scans restarted; ",
- stats.s_scan_restart);
- if (stats.s_allocate_pop_fail)
- appendStringInfo(&buf, UINT64_FORMAT " allocation pops failed; ",
- stats.s_allocate_pop_fail);
- if (stats.s_gc_pop_fail)
- appendStringInfo(&buf, UINT64_FORMAT " GC pops failed; ",
- stats.s_gc_pop_fail);
- if (stats.s_gc_reclaim_retry)
- appendStringInfo(&buf, UINT64_FORMAT " GC reclaims retried; ",
- stats.s_gc_reclaim_retry);
- if (stats.s_garbage_retry)
- appendStringInfo(&buf, UINT64_FORMAT " garbage enqueues retried; ",
- stats.s_garbage_retry);
- if (stats.s_free_retry)
- appendStringInfo(&buf, UINT64_FORMAT " immediate frees retried; ",
- stats.s_free_retry);
+ for (i = 0; i < CHS_NumberOfStatistics; ++i)
+ {
+ if (stats[i] == 0)
+ continue;
+ appendStringInfo(&buf, UINT64_FORMAT " %s; ", stats[i],
+ CHashStatisticsNames[i]);
+ }
if (buf.len > 1)
{
* unused.
*/
uint32 gc_next; /* next garbage list to reclaim */
- CHashTableStats stats; /* statistics */
+ uint64 stats[CHS_NumberOfStatistics]; /* statistics */
} CHashTableData;
#define CHashTableGetRaw(table, offset) \
#define CHashTableGetNode(table, ptr) \
(AssertMacro(!CHashPtrIsInvalid(ptr)), \
CHashTableGetRaw((table), CHashPtrGetOffset((ptr))))
+#define CHashTableIncrementStatistic(table, stat) \
+ ((table)->stats[(stat)]++)
/*
* Bucket scan.
bool found;
} CHashScanResult;
+/* Human-readable statistics names. */
+char *CHashStatisticsNames[] = {
+ "searches",
+ "searches failed",
+ "inserts",
+ "inserts failed",
+ "inserts retried",
+ "deletions",
+ "deletions failed",
+ "deletions retried",
+ "scan expunges",
+ "scan expunges failed",
+ "scans restarted",
+ "cleanup scans",
+ "cleanup expunges",
+ "cleanup expunges failed",
+ "cleanups restarted",
+ "allocations failed",
+ "garbage enqueues retried",
+ "garbage dequeues failed",
+ "garbage collections",
+ "garbage collection spins",
+ "garbage collection fast reclaims",
+ "garbage collection reclaims retried",
+ "<end>"
+};
+
/* Function prototypes. */
static CHashPtr CHashAllocate(CHashTable table);
static void CHashAddToGarbage(CHashTable table, uint32 bucket, CHashPtr c);
CHashTable table;
uint32 bucket_shift;
+ /* Sanity check. */
+ Assert(!strcmp(CHashStatisticsNames[CHS_NumberOfStatistics], "<end>"));
+
/* Allocate table and copy descriptor. */
table = MemoryContextAllocZero(TopMemoryContext, sizeof(CHashTableData));
memcpy(&table->desc, desc, sizeof(CHashDescriptor));
/* Each arena element must be MAXALIGN'd and include per-node space. */
table->arena_stride = SizeOfCHashNode + MAXALIGN(desc->element_size);
+ /* Zero out statistics. */
+ memset(table->stats, 0, sizeof(uint64) * CHS_NumberOfStatistics);
+
return table;
}
pg_memory_barrier();
MyProc->hazard[0] = NULL;
+ CHashTableIncrementStatistic(table, CHS_Search);
+ if (!scan.found)
+ CHashTableIncrementStatistic(table, CHS_Search_Failed);
return scan.found;
}
if (!__sync_bool_compare_and_swap(scan.pointer_to_target,
scan.target, new))
{
- table->stats.s_insert_retry++;
+ CHashTableIncrementStatistic(table, CHS_Insert_Retry);
goto retry;
}
}
* element makes it back to the freelist is trying to allocate some
* other node.
*/
+ CHashTableIncrementStatistic(table, CHS_Insert);
if (scan.found)
+ {
+ CHashTableIncrementStatistic(table, CHS_Insert_Failed);
CHashAddToGarbage(table, bucket, new);
+ }
/* The insert succeeded if and only if no duplicate was found. */
return !scan.found;
cleanup_scan = true;
break;
}
- table->stats.s_delete_retry++;
+ CHashTableIncrementStatistic(table, CHS_Delete_Retry);
}
/*
*/
if (cleanup_scan)
{
- table->stats.s_cleanup_scan++;
+ CHashTableIncrementStatistic(table, CHS_Cleanup_Scan);
CHashBucketCleanup(table, b, hashcode);
}
MyProc->hazard[0] = NULL;
/* We're done. */
+ CHashTableIncrementStatistic(table, CHS_Delete);
+ if (!scan.found)
+ CHashTableIncrementStatistic(table, CHS_Delete_Failed);
return scan.found;
}
* Provide backend-local statistics to caller.
*/
void
-CHashStatistics(CHashTable table, CHashTableStats *stats)
+CHashStatistics(CHashTable table, uint64 *stats)
{
- memcpy(stats, &table->stats, sizeof(CHashTableStats));
+ memcpy(stats, &table->stats, sizeof(uint64) * CHS_NumberOfStatistics);
}
/*
* all non-deleted items (and possibly some deleted items)
* that were present at the time we began the scan.
*/
- table->stats.s_scan_expunge_ok++;
+ CHashTableIncrementStatistic(table, CHS_Scan_Expunge);
CHashAddToGarbage(table, hashcode & table->bucket_mask,
target);
target = CHashPtrUnmark(next);
* have to be pretty unlucky to have it happen even twice in
* a row.
*/
- table->stats.s_scan_expunge_fail++;
+ CHashTableIncrementStatistic(table, CHS_Scan_Expunge_Fail);
target = *pointer_to_target;
if (CHashPtrIsMarked(target))
{
- table->stats.s_scan_restart++;
+ CHashTableIncrementStatistic(table, CHS_Scan_Restart);
goto retry;
}
}
CHashPtrUnmark(next)))
{
/* We removed the item. */
- table->stats.s_cleanup_expunge_ok++;
+ CHashTableIncrementStatistic(table, CHS_Cleanup_Expunge);
CHashAddToGarbage(table, hashcode & table->bucket_mask,
target);
target = CHashPtrUnmark(next);
else
{
/* Someone else removed the item first. */
- table->stats.s_cleanup_expunge_fail++;
+ CHashTableIncrementStatistic(table, CHS_Cleanup_Expunge_Fail);
target = *pointer_to_target;
if (CHashPtrIsMarked(target))
{
- table->stats.s_cleanup_restart++;
+ CHashTableIncrementStatistic(table, CHS_Cleanup_Restart);
goto retry;
}
}
pg_read_barrier_depends();
if (__sync_bool_compare_and_swap(b, new, n->un.gcnext))
return new;
- table->stats.s_allocate_pop_fail++;
+ CHashTableIncrementStatistic(table, CHS_Allocate_Fail);
new = *b;
}
if (CHashPtrIsInvalid(garbage))
;
else if (!__sync_bool_compare_and_swap(b, garbage, InvalidCHashPtr))
- table->stats.s_gc_pop_fail++;
+ CHashTableIncrementStatistic(table, CHS_Garbage_Dequeue_Fail);
else
{
uint32 i;
* might want to eventually enter a longer sleep here, or PANIC,
* but it's not clear exactly how to calibrate that.
*/
+ CHashTableIncrementStatistic(table, CHS_GC);
MyProc->hazard[0] = NULL;
for (i = 0; i < ProcGlobal->allProcCount; i++)
{
volatile PGPROC *proc = &ProcGlobal->allProcs[i];
void *hazard;
- do
+ hazard = proc->hazard[0];
+ if (hazard == b || hazard == fh)
{
- hazard = proc->hazard[0];
- } while (hazard == b || hazard == fh);
+ CHashTableIncrementStatistic(table, CHS_GC_Spin);
+ do
+ {
+ hazard = proc->hazard[0];
+ } while (hazard == b || hazard == fh);
+ }
}
/* Remove one item from list to satisfy current allocation. */
* we needn't update the list any further; otherwise, we've got
* to adjust the next-pointer in the last of the reclaimed nodes.
*/
- if (!CHashPtrIsInvalid(fhead)
- && !__sync_bool_compare_and_swap(fh, InvalidCHashPtr, fhead))
+ if (CHashPtrIsInvalid(fhead))
+ ;
+ else if (__sync_bool_compare_and_swap(fh, InvalidCHashPtr, fhead))
+ CHashTableIncrementStatistic(table, CHS_GC_Reclaim_Fast);
+ else
{
CHashPtr fcurrent;
CHashPtr fnext;
n->un.gcnext = oldhead;
if (__sync_bool_compare_and_swap(fh, oldhead, fhead))
break;
- table->stats.s_gc_reclaim_retry++;
+ CHashTableIncrementStatistic(table, CHS_GC_Reclaim_Retry);
}
}
n->un.gcnext = g;
if (__sync_bool_compare_and_swap(garbage, g, c))
break;
- table->stats.s_garbage_retry++;
+ CHashTableIncrementStatistic(table, CHS_Garbage_Enqueue_Retry);
}
}
} CHashDescriptor;
/* Concurrent hash table statistics. */
-typedef struct
+typedef enum
{
- uint64 s_insert_retry; /* insert point concurrently updated */
- uint64 s_delete_retry; /* delete point concurrently updated */
- uint64 s_cleanup_scan; /* expunge-after-delete failed */
- uint64 s_scan_expunge_ok; /* scan did expunge */
- uint64 s_scan_expunge_fail; /* expunge failed */
- uint64 s_scan_restart; /* scan restarted */
- uint64 s_cleanup_expunge_ok; /* cleanup scan did expunge */
- uint64 s_cleanup_expunge_fail; /* cleanup scan expunge failed */
- uint64 s_cleanup_restart; /* cleanup scan restarted */
- uint64 s_allocate_pop_fail; /* freelist pop failed */
- uint64 s_gc_pop_fail; /* garbage list pop failed */
- uint64 s_gc_reclaim_retry; /* failed to return GC'd nodes */
- uint64 s_garbage_retry; /* failed to enqueue garbage */
- uint64 s_free_retry; /* failed to perform immediate free */
-} CHashTableStats;
+ CHS_Search, /* search */
+ CHS_Search_Failed, /* search failed (no such key) */
+ CHS_Insert, /* insert */
+ CHS_Insert_Failed, /* insert failed (duplicate key) */
+ CHS_Insert_Retry, /* insert retried (concurrent update) */
+ CHS_Delete, /* delete */
+ CHS_Delete_Failed, /* delete failed (no such key) */
+ CHS_Delete_Retry, /* delete retried (concurrent update) */
+ CHS_Scan_Expunge, /* scan expunged deleted item */
+ CHS_Scan_Expunge_Fail, /* scan failed to expunge */
+ CHS_Scan_Restart, /* concurrent deletes forced a scan restart */
+ CHS_Cleanup_Scan, /* concurrent update forced a cleanup scan */
+ CHS_Cleanup_Expunge, /* cleanup scan expunged an item */
+ CHS_Cleanup_Expunge_Fail, /* cleanup scan failed to expunge */
+ CHS_Cleanup_Restart, /* concurrent deletes forced cleanup restart */
+ CHS_Allocate_Fail, /* allocation failed to pop freelist */
+ CHS_Garbage_Enqueue_Retry, /* enqueue on garbage list retried */
+ CHS_Garbage_Dequeue_Fail, /* dequeue of garbage failed */
+ CHS_GC, /* garbage collection cycle */
+ CHS_GC_Spin, /* GC spun waiting for concurrent process */
+ CHS_GC_Reclaim_Fast, /* GC put garbage on freelist via fast path */
+ CHS_GC_Reclaim_Retry, /* enqueue of garbage on freelist retried */
+ CHS_NumberOfStatistics /* number of statistics */
+} CHashStatisticsType;
+
+/* Human-readable names for statistics. */
+extern char *CHashStatisticsNames[];
/* Opaque handle for a concurrent hash table. */
struct CHashTableData;
extern bool CHashInsert(CHashTable table, void *entry);
extern bool CHashDelete(CHashTable table, void *key);
extern bool CHashSearch(CHashTable table, void *entry);
-extern void CHashStatistics(CHashTable table, CHashTableStats *stats);
+extern void CHashStatistics(CHashTable table, uint64 *stats);
#endif /* CHASH_H */