
of multiple forks, and each fork can be created and grown separately. The bulk of this patch is about changing the smgr API to include an extra ForkNumber argument in every smgr function. Also, smgrscheduleunlink and smgrdounlink no longer implicitly call smgrclose, because other forks might still exist after unlinking one. The callers of those functions have been modified to call smgrclose instead. This patch in itself doesn't have any user-visible effect, but provides the infrastructure needed for upcoming patches. The additional forks envisioned are a rewritten FSM implementation that doesn't rely on a fixed-size shared memory block, and a visibility map to allow skipping portions of a table in VACUUM that have no dead tuples.
433 lines
9.1 KiB
C
433 lines
9.1 KiB
C
/*
|
|
* dbsize.c
|
|
* object size functions
|
|
*
|
|
* Copyright (c) 2002-2008, PostgreSQL Global Development Group
|
|
*
|
|
* IDENTIFICATION
|
|
* $PostgreSQL: pgsql/src/backend/utils/adt/dbsize.c,v 1.20 2008/08/11 11:05:11 heikki Exp $
|
|
*
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include "access/heapam.h"
|
|
#include "catalog/catalog.h"
|
|
#include "catalog/namespace.h"
|
|
#include "catalog/pg_tablespace.h"
|
|
#include "commands/dbcommands.h"
|
|
#include "commands/tablespace.h"
|
|
#include "miscadmin.h"
|
|
#include "storage/fd.h"
|
|
#include "utils/acl.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/rel.h"
|
|
#include "utils/syscache.h"
|
|
|
|
|
|
/* Return physical size of directory contents, or 0 if dir doesn't exist */
|
|
static int64
|
|
db_dir_size(const char *path)
|
|
{
|
|
int64 dirsize = 0;
|
|
struct dirent *direntry;
|
|
DIR *dirdesc;
|
|
char filename[MAXPGPATH];
|
|
|
|
dirdesc = AllocateDir(path);
|
|
|
|
if (!dirdesc)
|
|
return 0;
|
|
|
|
while ((direntry = ReadDir(dirdesc, path)) != NULL)
|
|
{
|
|
struct stat fst;
|
|
|
|
if (strcmp(direntry->d_name, ".") == 0 ||
|
|
strcmp(direntry->d_name, "..") == 0)
|
|
continue;
|
|
|
|
snprintf(filename, MAXPGPATH, "%s/%s", path, direntry->d_name);
|
|
|
|
if (stat(filename, &fst) < 0)
|
|
{
|
|
if (errno == ENOENT)
|
|
continue;
|
|
else
|
|
ereport(ERROR,
|
|
(errcode_for_file_access(),
|
|
errmsg("could not stat file \"%s\": %m", filename)));
|
|
}
|
|
dirsize += fst.st_size;
|
|
}
|
|
|
|
FreeDir(dirdesc);
|
|
return dirsize;
|
|
}
|
|
|
|
/*
|
|
* calculate size of database in all tablespaces
|
|
*/
|
|
static int64
|
|
calculate_database_size(Oid dbOid)
|
|
{
|
|
int64 totalsize;
|
|
DIR *dirdesc;
|
|
struct dirent *direntry;
|
|
char dirpath[MAXPGPATH];
|
|
char pathname[MAXPGPATH];
|
|
AclResult aclresult;
|
|
|
|
/* User must have connect privilege for target database */
|
|
aclresult = pg_database_aclcheck(dbOid, GetUserId(), ACL_CONNECT);
|
|
if (aclresult != ACLCHECK_OK)
|
|
aclcheck_error(aclresult, ACL_KIND_DATABASE,
|
|
get_database_name(dbOid));
|
|
|
|
/* Shared storage in pg_global is not counted */
|
|
|
|
/* Include pg_default storage */
|
|
snprintf(pathname, MAXPGPATH, "base/%u", dbOid);
|
|
totalsize = db_dir_size(pathname);
|
|
|
|
/* Scan the non-default tablespaces */
|
|
snprintf(dirpath, MAXPGPATH, "pg_tblspc");
|
|
dirdesc = AllocateDir(dirpath);
|
|
if (!dirdesc)
|
|
ereport(ERROR,
|
|
(errcode_for_file_access(),
|
|
errmsg("could not open tablespace directory \"%s\": %m",
|
|
dirpath)));
|
|
|
|
while ((direntry = ReadDir(dirdesc, dirpath)) != NULL)
|
|
{
|
|
if (strcmp(direntry->d_name, ".") == 0 ||
|
|
strcmp(direntry->d_name, "..") == 0)
|
|
continue;
|
|
|
|
snprintf(pathname, MAXPGPATH, "pg_tblspc/%s/%u",
|
|
direntry->d_name, dbOid);
|
|
totalsize += db_dir_size(pathname);
|
|
}
|
|
|
|
FreeDir(dirdesc);
|
|
|
|
/* Complain if we found no trace of the DB at all */
|
|
if (!totalsize)
|
|
ereport(ERROR,
|
|
(ERRCODE_UNDEFINED_DATABASE,
|
|
errmsg("database with OID %u does not exist", dbOid)));
|
|
|
|
return totalsize;
|
|
}
|
|
|
|
Datum
|
|
pg_database_size_oid(PG_FUNCTION_ARGS)
|
|
{
|
|
Oid dbOid = PG_GETARG_OID(0);
|
|
|
|
PG_RETURN_INT64(calculate_database_size(dbOid));
|
|
}
|
|
|
|
Datum
|
|
pg_database_size_name(PG_FUNCTION_ARGS)
|
|
{
|
|
Name dbName = PG_GETARG_NAME(0);
|
|
Oid dbOid = get_database_oid(NameStr(*dbName));
|
|
|
|
if (!OidIsValid(dbOid))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_DATABASE),
|
|
errmsg("database \"%s\" does not exist",
|
|
NameStr(*dbName))));
|
|
|
|
PG_RETURN_INT64(calculate_database_size(dbOid));
|
|
}
|
|
|
|
|
|
/*
|
|
* calculate total size of tablespace
|
|
*/
|
|
static int64
|
|
calculate_tablespace_size(Oid tblspcOid)
|
|
{
|
|
char tblspcPath[MAXPGPATH];
|
|
char pathname[MAXPGPATH];
|
|
int64 totalsize = 0;
|
|
DIR *dirdesc;
|
|
struct dirent *direntry;
|
|
AclResult aclresult;
|
|
|
|
/*
|
|
* User must have CREATE privilege for target tablespace, either
|
|
* explicitly granted or implicitly because it is default for current
|
|
* database.
|
|
*/
|
|
if (tblspcOid != MyDatabaseTableSpace)
|
|
{
|
|
aclresult = pg_tablespace_aclcheck(tblspcOid, GetUserId(), ACL_CREATE);
|
|
if (aclresult != ACLCHECK_OK)
|
|
aclcheck_error(aclresult, ACL_KIND_TABLESPACE,
|
|
get_tablespace_name(tblspcOid));
|
|
}
|
|
|
|
if (tblspcOid == DEFAULTTABLESPACE_OID)
|
|
snprintf(tblspcPath, MAXPGPATH, "base");
|
|
else if (tblspcOid == GLOBALTABLESPACE_OID)
|
|
snprintf(tblspcPath, MAXPGPATH, "global");
|
|
else
|
|
snprintf(tblspcPath, MAXPGPATH, "pg_tblspc/%u", tblspcOid);
|
|
|
|
dirdesc = AllocateDir(tblspcPath);
|
|
|
|
if (!dirdesc)
|
|
ereport(ERROR,
|
|
(errcode_for_file_access(),
|
|
errmsg("could not open tablespace directory \"%s\": %m",
|
|
tblspcPath)));
|
|
|
|
while ((direntry = ReadDir(dirdesc, tblspcPath)) != NULL)
|
|
{
|
|
struct stat fst;
|
|
|
|
if (strcmp(direntry->d_name, ".") == 0 ||
|
|
strcmp(direntry->d_name, "..") == 0)
|
|
continue;
|
|
|
|
snprintf(pathname, MAXPGPATH, "%s/%s", tblspcPath, direntry->d_name);
|
|
|
|
if (stat(pathname, &fst) < 0)
|
|
{
|
|
if (errno == ENOENT)
|
|
continue;
|
|
else
|
|
ereport(ERROR,
|
|
(errcode_for_file_access(),
|
|
errmsg("could not stat file \"%s\": %m", pathname)));
|
|
}
|
|
|
|
if (S_ISDIR(fst.st_mode))
|
|
totalsize += db_dir_size(pathname);
|
|
|
|
totalsize += fst.st_size;
|
|
}
|
|
|
|
FreeDir(dirdesc);
|
|
|
|
return totalsize;
|
|
}
|
|
|
|
Datum
|
|
pg_tablespace_size_oid(PG_FUNCTION_ARGS)
|
|
{
|
|
Oid tblspcOid = PG_GETARG_OID(0);
|
|
|
|
PG_RETURN_INT64(calculate_tablespace_size(tblspcOid));
|
|
}
|
|
|
|
Datum
|
|
pg_tablespace_size_name(PG_FUNCTION_ARGS)
|
|
{
|
|
Name tblspcName = PG_GETARG_NAME(0);
|
|
Oid tblspcOid = get_tablespace_oid(NameStr(*tblspcName));
|
|
|
|
if (!OidIsValid(tblspcOid))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("tablespace \"%s\" does not exist",
|
|
NameStr(*tblspcName))));
|
|
|
|
PG_RETURN_INT64(calculate_tablespace_size(tblspcOid));
|
|
}
|
|
|
|
|
|
/*
|
|
* calculate size of a relation
|
|
*/
|
|
static int64
|
|
calculate_relation_size(RelFileNode *rfn)
|
|
{
|
|
int64 totalsize = 0;
|
|
char *relationpath;
|
|
char pathname[MAXPGPATH];
|
|
unsigned int segcount = 0;
|
|
|
|
/* XXX: This ignores the other forks. */
|
|
relationpath = relpath(*rfn, MAIN_FORKNUM);
|
|
|
|
for (segcount = 0;; segcount++)
|
|
{
|
|
struct stat fst;
|
|
|
|
if (segcount == 0)
|
|
snprintf(pathname, MAXPGPATH, "%s",
|
|
relationpath);
|
|
else
|
|
snprintf(pathname, MAXPGPATH, "%s.%u",
|
|
relationpath, segcount);
|
|
|
|
if (stat(pathname, &fst) < 0)
|
|
{
|
|
if (errno == ENOENT)
|
|
break;
|
|
else
|
|
ereport(ERROR,
|
|
(errcode_for_file_access(),
|
|
errmsg("could not stat file \"%s\": %m", pathname)));
|
|
}
|
|
totalsize += fst.st_size;
|
|
}
|
|
|
|
return totalsize;
|
|
}
|
|
|
|
Datum
|
|
pg_relation_size_oid(PG_FUNCTION_ARGS)
|
|
{
|
|
Oid relOid = PG_GETARG_OID(0);
|
|
Relation rel;
|
|
int64 size;
|
|
|
|
rel = relation_open(relOid, AccessShareLock);
|
|
|
|
size = calculate_relation_size(&(rel->rd_node));
|
|
|
|
relation_close(rel, AccessShareLock);
|
|
|
|
PG_RETURN_INT64(size);
|
|
}
|
|
|
|
Datum
|
|
pg_relation_size_name(PG_FUNCTION_ARGS)
|
|
{
|
|
text *relname = PG_GETARG_TEXT_P(0);
|
|
RangeVar *relrv;
|
|
Relation rel;
|
|
int64 size;
|
|
|
|
relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
|
|
rel = relation_openrv(relrv, AccessShareLock);
|
|
|
|
size = calculate_relation_size(&(rel->rd_node));
|
|
|
|
relation_close(rel, AccessShareLock);
|
|
|
|
PG_RETURN_INT64(size);
|
|
}
|
|
|
|
|
|
/*
|
|
* Compute the on-disk size of files for the relation according to the
|
|
* stat function, including heap data, index data, and toast data.
|
|
*/
|
|
static int64
|
|
calculate_total_relation_size(Oid Relid)
|
|
{
|
|
Relation heapRel;
|
|
Oid toastOid;
|
|
int64 size;
|
|
ListCell *cell;
|
|
|
|
heapRel = relation_open(Relid, AccessShareLock);
|
|
toastOid = heapRel->rd_rel->reltoastrelid;
|
|
|
|
/* Get the heap size */
|
|
size = calculate_relation_size(&(heapRel->rd_node));
|
|
|
|
/* Include any dependent indexes */
|
|
if (heapRel->rd_rel->relhasindex)
|
|
{
|
|
List *index_oids = RelationGetIndexList(heapRel);
|
|
|
|
foreach(cell, index_oids)
|
|
{
|
|
Oid idxOid = lfirst_oid(cell);
|
|
Relation iRel;
|
|
|
|
iRel = relation_open(idxOid, AccessShareLock);
|
|
|
|
size += calculate_relation_size(&(iRel->rd_node));
|
|
|
|
relation_close(iRel, AccessShareLock);
|
|
}
|
|
|
|
list_free(index_oids);
|
|
}
|
|
|
|
/* Recursively include toast table (and index) size */
|
|
if (OidIsValid(toastOid))
|
|
size += calculate_total_relation_size(toastOid);
|
|
|
|
relation_close(heapRel, AccessShareLock);
|
|
|
|
return size;
|
|
}
|
|
|
|
Datum
|
|
pg_total_relation_size_oid(PG_FUNCTION_ARGS)
|
|
{
|
|
Oid relid = PG_GETARG_OID(0);
|
|
|
|
PG_RETURN_INT64(calculate_total_relation_size(relid));
|
|
}
|
|
|
|
Datum
|
|
pg_total_relation_size_name(PG_FUNCTION_ARGS)
|
|
{
|
|
text *relname = PG_GETARG_TEXT_P(0);
|
|
RangeVar *relrv;
|
|
Oid relid;
|
|
|
|
relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
|
|
relid = RangeVarGetRelid(relrv, false);
|
|
|
|
PG_RETURN_INT64(calculate_total_relation_size(relid));
|
|
}
|
|
|
|
/*
|
|
* formatting with size units
|
|
*/
|
|
Datum
|
|
pg_size_pretty(PG_FUNCTION_ARGS)
|
|
{
|
|
int64 size = PG_GETARG_INT64(0);
|
|
char buf[64];
|
|
int64 limit = 10 * 1024;
|
|
int64 mult = 1;
|
|
|
|
if (size < limit * mult)
|
|
snprintf(buf, sizeof(buf), INT64_FORMAT " bytes", size);
|
|
else
|
|
{
|
|
mult *= 1024;
|
|
if (size < limit * mult)
|
|
snprintf(buf, sizeof(buf), INT64_FORMAT " kB",
|
|
(size + mult / 2) / mult);
|
|
else
|
|
{
|
|
mult *= 1024;
|
|
if (size < limit * mult)
|
|
snprintf(buf, sizeof(buf), INT64_FORMAT " MB",
|
|
(size + mult / 2) / mult);
|
|
else
|
|
{
|
|
mult *= 1024;
|
|
if (size < limit * mult)
|
|
snprintf(buf, sizeof(buf), INT64_FORMAT " GB",
|
|
(size + mult / 2) / mult);
|
|
else
|
|
{
|
|
mult *= 1024;
|
|
snprintf(buf, sizeof(buf), INT64_FORMAT " TB",
|
|
(size + mult / 2) / mult);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
PG_RETURN_TEXT_P(cstring_to_text(buf));
|
|
}
|