/* -------------------------------------------------------------------------
 *
 * pgstat_slru.c
 *	  Implementation of SLRU statistics.
 *
 * This file contains the implementation of SLRU statistics. It is kept
 * separate from pgstat.c to enforce the line between the statistics access /
 * storage implementation and the details about individual types of
 * statistics.
 *
 * Copyright (c) 2001-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  src/backend/utils/activity/pgstat_slru.c
 * -------------------------------------------------------------------------
 */

#include "postgres.h"

#include "utils/pgstat_internal.h"
#include "utils/timestamp.h"


static inline PgStat_SLRUStats *get_slru_entry(int slru_idx);
static void pgstat_reset_slru_counter_internal(int index, TimestampTz ts);


/*
 * SLRU statistics counts waiting to be flushed out.  We assume this variable
 * inits to zeroes.  Entries are one-to-one with slru_names[].  Changes of
 * SLRU counters are reported within critical sections so we use static memory
 * in order to avoid memory allocation.
 */
static PgStat_SLRUStats pending_SLRUStats[SLRU_NUM_ELEMENTS];
bool		have_slrustats = false;


/*
 * Reset counters for a single SLRU.
 *
 * Permission checking for this function is managed through the normal
 * GRANT system.
 */
void
pgstat_reset_slru(const char *name)
{
	TimestampTz ts = GetCurrentTimestamp();

	AssertArg(name != NULL);

	pgstat_reset_slru_counter_internal(pgstat_get_slru_index(name), ts);
}

/*
 * SLRU statistics count accumulation functions --- called from slru.c
 */

void
pgstat_count_slru_page_zeroed(int slru_idx)
{
	get_slru_entry(slru_idx)->blocks_zeroed += 1;
}

void
pgstat_count_slru_page_hit(int slru_idx)
{
	get_slru_entry(slru_idx)->blocks_hit += 1;
}

void
pgstat_count_slru_page_exists(int slru_idx)
{
	get_slru_entry(slru_idx)->blocks_exists += 1;
}

void
pgstat_count_slru_page_read(int slru_idx)
{
	get_slru_entry(slru_idx)->blocks_read += 1;
}

void
pgstat_count_slru_page_written(int slru_idx)
{
	get_slru_entry(slru_idx)->blocks_written += 1;
}

void
pgstat_count_slru_flush(int slru_idx)
{
	get_slru_entry(slru_idx)->flush += 1;
}

void
pgstat_count_slru_truncate(int slru_idx)
{
	get_slru_entry(slru_idx)->truncate += 1;
}

/*
 * Support function for the SQL-callable pgstat* functions. Returns
 * a pointer to the slru statistics struct.
 */
PgStat_SLRUStats *
pgstat_fetch_slru(void)
{
	pgstat_snapshot_fixed(PGSTAT_KIND_SLRU);

	return pgStatLocal.snapshot.slru;
}

/*
 * Returns SLRU name for an index. The index may be above SLRU_NUM_ELEMENTS,
 * in which case this returns NULL. This allows writing code that does not
 * know the number of entries in advance.
 */
const char *
pgstat_get_slru_name(int slru_idx)
{
	if (slru_idx < 0 || slru_idx >= SLRU_NUM_ELEMENTS)
		return NULL;

	return slru_names[slru_idx];
}

/*
 * Determine index of entry for a SLRU with a given name. If there's no exact
 * match, returns index of the last "other" entry used for SLRUs defined in
 * external projects.
 */
int
pgstat_get_slru_index(const char *name)
{
	int			i;

	for (i = 0; i < SLRU_NUM_ELEMENTS; i++)
	{
		if (strcmp(slru_names[i], name) == 0)
			return i;
	}

	/* return index of the last entry (which is the "other" one) */
	return (SLRU_NUM_ELEMENTS - 1);
}

/*
 * Flush out locally pending SLRU stats entries
 *
 * If nowait is true, this function returns false on lock failure. Otherwise
 * this function always returns true. Writer processes are mutually excluded
 * using LWLock, but readers are expected to use change-count protocol to avoid
 * interference with writers.
 *
 * If nowait is true, this function returns true if the lock could not be
 * acquired. Otherwise return false.
 */
bool
pgstat_slru_flush(bool nowait)
{
	PgStatShared_SLRU *stats_shmem = &pgStatLocal.shmem->slru;
	int			i;

	if (!have_slrustats)
		return false;

	if (!nowait)
		LWLockAcquire(&stats_shmem->lock, LW_EXCLUSIVE);
	else if (!LWLockConditionalAcquire(&stats_shmem->lock, LW_EXCLUSIVE))
		return true;

	for (i = 0; i < SLRU_NUM_ELEMENTS; i++)
	{
		PgStat_SLRUStats *sharedent = &stats_shmem->stats[i];
		PgStat_SLRUStats *pendingent = &pending_SLRUStats[i];

#define SLRU_ACC(fld) sharedent->fld += pendingent->fld
		SLRU_ACC(blocks_zeroed);
		SLRU_ACC(blocks_hit);
		SLRU_ACC(blocks_read);
		SLRU_ACC(blocks_written);
		SLRU_ACC(blocks_exists);
		SLRU_ACC(flush);
		SLRU_ACC(truncate);
#undef SLRU_ACC
	}

	/* done, clear the pending entry */
	MemSet(pending_SLRUStats, 0, sizeof(pending_SLRUStats));

	LWLockRelease(&stats_shmem->lock);

	have_slrustats = false;

	return false;
}

void
pgstat_slru_reset_all_cb(TimestampTz ts)
{
	for (int i = 0; i < SLRU_NUM_ELEMENTS; i++)
		pgstat_reset_slru_counter_internal(i, ts);
}

void
pgstat_slru_snapshot_cb(void)
{
	PgStatShared_SLRU *stats_shmem = &pgStatLocal.shmem->slru;

	LWLockAcquire(&stats_shmem->lock, LW_SHARED);

	memcpy(pgStatLocal.snapshot.slru, &stats_shmem->stats,
		   sizeof(stats_shmem->stats));

	LWLockRelease(&stats_shmem->lock);
}

/*
 * Returns pointer to entry with counters for given SLRU (based on the name
 * stored in SlruCtl as lwlock tranche name).
 */
static inline PgStat_SLRUStats *
get_slru_entry(int slru_idx)
{
	pgstat_assert_is_up();

	/*
	 * The postmaster should never register any SLRU statistics counts; if it
	 * did, the counts would be duplicated into child processes via fork().
	 */
	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);

	Assert((slru_idx >= 0) && (slru_idx < SLRU_NUM_ELEMENTS));

	have_slrustats = true;

	return &pending_SLRUStats[slru_idx];
}

static void
pgstat_reset_slru_counter_internal(int index, TimestampTz ts)
{
	PgStatShared_SLRU *stats_shmem = &pgStatLocal.shmem->slru;

	LWLockAcquire(&stats_shmem->lock, LW_EXCLUSIVE);

	memset(&stats_shmem->stats[index], 0, sizeof(PgStat_SLRUStats));
	stats_shmem->stats[index].stat_reset_timestamp = ts;

	LWLockRelease(&stats_shmem->lock);
}
