From 8217cfbd991856d25d73b0f7afcf43d99f90b653 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Sat, 28 Nov 2009 23:38:08 +0000 Subject: [PATCH] Add support for an application_name parameter, which is displayed in pg_stat_activity and recorded in log entries. Dave Page, reviewed by Andres Freund --- doc/src/sgml/config.sgml | 68 ++++-- doc/src/sgml/libpq.sgml | 77 ++++-- doc/src/sgml/monitoring.sgml | 14 +- src/backend/catalog/system_views.sql | 5 +- src/backend/postmaster/pgstat.c | 82 ++++++- src/backend/utils/adt/pgstatfuncs.c | 16 +- src/backend/utils/error/elog.c | 18 +- src/backend/utils/misc/guc.c | 39 +++- src/backend/utils/misc/postgresql.conf.sample | 1 + src/include/catalog/catversion.h | 4 +- src/include/catalog/pg_proc.h | 4 +- src/include/pgstat.h | 8 +- src/include/utils/guc.h | 4 +- src/interfaces/libpq/fe-connect.c | 219 +++++++++++++++--- src/interfaces/libpq/libpq-int.h | 13 +- src/test/regress/expected/rules.out | 2 +- 16 files changed, 479 insertions(+), 95 deletions(-) diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 2cb006fa752..d5f55e0539b 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -1,4 +1,4 @@ - + Server Configuration @@ -2882,6 +2882,26 @@ local0.* /var/log/postgresql + + application_name (string) + + application_name configuration parameter + + + + The application_name can be any string of less than + NAMEDATALEN characters (64 characters in a standard build). + It is typically set by an application upon connection to the server. + The name will be displayed in the pg_stat_activity view + and included in CSV log entries. It can also be included in regular + log entries via the parameter. + Only printable ASCII characters may be used in the + application_name value. Other characters will be + replaced with question marks (?). + + + + debug_print_parse (boolean) debug_print_rewritten (boolean) @@ -3049,7 +3069,7 @@ local0.* /var/log/postgresql that are replaced with status information as outlined below. Unrecognized escapes are ignored. Other characters are copied straight to the log line. Some escapes are - only recognized by session processes, and do not apply to + only recognized by session processes, and are ignored by background processes such as the main server process. This parameter can only be set in the postgresql.conf file or on the server command line. The default is an empty string. @@ -3064,6 +3084,11 @@ local0.* /var/log/postgresql + + %a + Application name + yes + %u User name @@ -3287,19 +3312,35 @@ FROM pg_stat_activity; Using CSV-Format Log Output - Including csvlog in the log_destination list - provides a convenient way to import log files into a database table. + Including csvlog in the log_destination list + provides a convenient way to import log files into a database table. This option emits log lines in comma-separated-value format, - with these columns: timestamp with milliseconds, user name, database - name, process ID, host:port number, session ID, per-session or -process line - number, command tag, session start time, virtual transaction ID, - regular transaction id, error severity, SQL state code, error message, - error message detail, hint, internal query that led to the error (if - any), character count of the error position thereof, error context, + with these columns: + timestamp with milliseconds, + user name, + database name, + process ID, + client host:port number, + session ID, + per-session line number, + command tag, + session start time, + virtual transaction ID, + regular transaction ID, + error severity, + SQL state code, + error message, + error message detail, + hint, + internal query that led to the error (if any), + character count of the error position therein, + error context, user query that led to the error (if any and enabled by - log_min_error_statement), character count of the error - position thereof, location of the error in the PostgreSQL source code - (if log_error_verbosity is set to verbose). + log_min_error_statement), + character count of the error position therein, + location of the error in the PostgreSQL source code + (if log_error_verbosity is set to verbose), + and application name. Here is a sample table definition for storing CSV-format log output: @@ -3327,6 +3368,7 @@ CREATE TABLE postgres_log query text, query_pos integer, location text, + application_name text, PRIMARY KEY (session_id, session_line_num) ); diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index 85ba6dadd1d..b93e381b1e6 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -1,4 +1,4 @@ - + <application>libpq</application> - C Library @@ -249,6 +249,32 @@ + + application_name + + + Specifies a value for the + configuration parameter. + + + + + + fallback_application_name + + + Specifies a fallback value for the configuration parameter. + This value will be used if no value has been given for + application_name via a connection parameter or the + PGAPPNAME environment variable. Specifying + a fallback name is useful in generic utility programs that + wish to set a default application name but allow it to be + overridden by the user. + + + + tty @@ -5672,7 +5698,7 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough) PGHOST - PGHOST behaves the same as PGHOST behaves the same as the connection parameter. @@ -5682,7 +5708,7 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough) PGHOSTADDR - PGHOSTADDR behaves the same as PGHOSTADDR behaves the same as the connection parameter. This can be set instead of or in addition to PGHOST to avoid DNS lookup overhead. @@ -5694,7 +5720,7 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough) PGPORT - PGPORT behaves the same as PGPORT behaves the same as the connection parameter. @@ -5704,7 +5730,7 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough) PGDATABASE - PGDATABASE behaves the same as PGDATABASE behaves the same as the connection parameter. @@ -5714,9 +5740,8 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough) PGUSER - PGUSER behaves the same as PGUSER behaves the same as the connection parameter. - database. @@ -5725,12 +5750,12 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough) PGPASSWORD - PGPASSWORD behaves the same as PGPASSWORD behaves the same as the connection parameter. Use of this environment variable - is not recommended for security reasons (some operating systems + is not recommended for security reasons, as some operating systems allow non-root users to see process environment variables via - ps); instead consider using the + ps; instead consider using the ~/.pgpass file (see ). @@ -5751,7 +5776,7 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough) PGSERVICE - PGSERVICE behaves the same as PGSERVICE behaves the same as the connection parameter. @@ -5777,17 +5802,27 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough) PGOPTIONS - PGOPTIONS behaves the same as PGOPTIONS behaves the same as the connection parameter. + + + + PGAPPNAME + + PGAPPNAME behaves the same as the connection parameter. + + + PGSSLMODE - PGSSLMODE behaves the same as PGSSLMODE behaves the same as the connection parameter. @@ -5797,7 +5832,7 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough) PGREQUIRESSL - PGREQUIRESSL behaves the same as PGREQUIRESSL behaves the same as the connection parameter. @@ -5807,7 +5842,7 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough) PGSSLCERT - PGSSLCERT behaves the same as PGSSLCERT behaves the same as the connection parameter. @@ -5817,7 +5852,7 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough) PGSSLKEY - PGSSLKEY behaves the same as PGSSLKEY behaves the same as the connection parameter. @@ -5827,7 +5862,7 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough) PGSSLROOTCERT - PGSSLROOTCERT behaves the same as PGSSLROOTCERT behaves the same as the connection parameter. @@ -5837,7 +5872,7 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough) PGSSLCRL - PGSSLCRL behaves the same as PGSSLCRL behaves the same as the connection parameter. @@ -5847,7 +5882,7 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough) PGKRBSRVNAME - PGKRBSRVNAME behaves the same as PGKRBSRVNAME behaves the same as the connection parameter. @@ -5857,7 +5892,7 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough) PGGSSLIB - PGGSSLIB behaves the same as PGGSSLIB behaves the same as the connection parameter. @@ -5867,7 +5902,7 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough) PGCONNECT_TIMEOUT - PGCONNECT_TIMEOUT behaves the same as PGCONNECT_TIMEOUT behaves the same as the connection parameter. diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 7bd21a74b42..94fbce63ad6 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -1,4 +1,4 @@ - + Monitoring Database Activity @@ -238,12 +238,12 @@ postgres: user database host ID, user OID, user name, current query, query's waiting status, time at which the current transaction and current query began execution, time at which the process was - started, and client's address and port number. The columns that - report data on the current query are available unless the parameter - track_activities has been turned off. - Furthermore, these columns are only visible if the user examining - the view is a superuser or the same as the user owning the process - being reported on. + started, client's address and port number, and application name. + The columns that report data on the current query are available unless + the parameter track_activities has been turned off. + Furthermore, these columns and the application name are only visible if + the user examining the view is a superuser or the same as the user + owning the process being reported on. diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index c2fbfcdf0a3..5bf76478166 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -3,7 +3,7 @@ * * Copyright (c) 1996-2009, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/backend/catalog/system_views.sql,v 1.61 2009/10/07 22:14:18 alvherre Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/system_views.sql,v 1.62 2009/11/28 23:38:07 tgl Exp $ */ CREATE VIEW pg_roles AS @@ -339,7 +339,8 @@ CREATE VIEW pg_stat_activity AS S.query_start, S.backend_start, S.client_addr, - S.client_port + S.client_port, + S.application_name FROM pg_database D, pg_stat_get_activity(NULL) AS S, pg_authid U WHERE S.datid = D.oid AND S.usesysid = U.oid; diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c index fac2100e831..4fa2d9fd637 100644 --- a/src/backend/postmaster/pgstat.c +++ b/src/backend/postmaster/pgstat.c @@ -13,7 +13,7 @@ * * Copyright (c) 2001-2009, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/backend/postmaster/pgstat.c,v 1.192 2009/10/02 22:49:50 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/postmaster/pgstat.c,v 1.193 2009/11/28 23:38:07 tgl Exp $ * ---------- */ #include "postgres.h" @@ -2073,6 +2073,7 @@ pgstat_fetch_global(void) static PgBackendStatus *BackendStatusArray = NULL; static PgBackendStatus *MyBEEntry = NULL; +static char *BackendAppnameBuffer = NULL; static char *BackendActivityBuffer = NULL; @@ -2084,14 +2085,17 @@ BackendStatusShmemSize(void) { Size size; - size = add_size(mul_size(sizeof(PgBackendStatus), MaxBackends), + size = mul_size(sizeof(PgBackendStatus), MaxBackends); + size = add_size(size, + mul_size(NAMEDATALEN, MaxBackends)); + size = add_size(size, mul_size(pgstat_track_activity_query_size, MaxBackends)); return size; } /* - * Initialize the shared status array and activity string buffer during - * postmaster startup. + * Initialize the shared status array and activity/appname string buffers + * during postmaster startup. */ void CreateSharedBackendStatus(void) @@ -2114,6 +2118,24 @@ CreateSharedBackendStatus(void) MemSet(BackendStatusArray, 0, size); } + /* Create or attach to the shared appname buffer */ + size = mul_size(NAMEDATALEN, MaxBackends); + BackendAppnameBuffer = (char *) + ShmemInitStruct("Backend Application Name Buffer", size, &found); + + if (!found) + { + MemSet(BackendAppnameBuffer, 0, size); + + /* Initialize st_appname pointers. */ + buffer = BackendAppnameBuffer; + for (i = 0; i < MaxBackends; i++) + { + BackendStatusArray[i].st_appname = buffer; + buffer += NAMEDATALEN; + } + } + /* Create or attach to the shared activity buffer */ size = mul_size(pgstat_track_activity_query_size, MaxBackends); BackendActivityBuffer = (char *) @@ -2159,7 +2181,8 @@ pgstat_initialize(void) * pgstat_bestart() - * * Initialize this backend's entry in the PgBackendStatus array. - * Called from InitPostgres. MyDatabaseId and session userid must be set + * Called from InitPostgres. + * MyDatabaseId, session userid, and application_name must be set * (hence, this cannot be combined with pgstat_initialize). * ---------- */ @@ -2214,12 +2237,18 @@ pgstat_bestart(void) beentry->st_userid = userid; beentry->st_clientaddr = clientaddr; beentry->st_waiting = false; + beentry->st_appname[0] = '\0'; beentry->st_activity[0] = '\0'; - /* Also make sure the last byte in the string area is always 0 */ + /* Also make sure the last byte in each string area is always 0 */ + beentry->st_appname[NAMEDATALEN - 1] = '\0'; beentry->st_activity[pgstat_track_activity_query_size - 1] = '\0'; beentry->st_changecount++; Assert((beentry->st_changecount & 1) == 0); + + /* Update app name to current GUC setting */ + if (application_name) + pgstat_report_appname(application_name); } /* @@ -2302,6 +2331,38 @@ pgstat_report_activity(const char *cmd_str) Assert((beentry->st_changecount & 1) == 0); } +/* ---------- + * pgstat_report_appname() - + * + * Called to update our application name. + * ---------- + */ +void +pgstat_report_appname(const char *appname) +{ + volatile PgBackendStatus *beentry = MyBEEntry; + int len; + + if (!beentry) + return; + + /* This should be unnecessary if GUC did its job, but be safe */ + len = pg_mbcliplen(appname, strlen(appname), NAMEDATALEN - 1); + + /* + * Update my status entry, following the protocol of bumping + * st_changecount before and after. We use a volatile pointer here to + * ensure the compiler doesn't try to get cute. + */ + beentry->st_changecount++; + + memcpy((char *) beentry->st_appname, appname, len); + beentry->st_appname[len] = '\0'; + + beentry->st_changecount++; + Assert((beentry->st_changecount & 1) == 0); +} + /* * Report current transaction start timestamp as the specified value. * Zero means there is no active transaction. @@ -2364,7 +2425,8 @@ pgstat_read_current_status(void) volatile PgBackendStatus *beentry; PgBackendStatus *localtable; PgBackendStatus *localentry; - char *localactivity; + char *localappname, + *localactivity; int i; Assert(!pgStatRunningInCollector); @@ -2376,6 +2438,9 @@ pgstat_read_current_status(void) localtable = (PgBackendStatus *) MemoryContextAlloc(pgStatLocalContext, sizeof(PgBackendStatus) * MaxBackends); + localappname = (char *) + MemoryContextAlloc(pgStatLocalContext, + NAMEDATALEN * MaxBackends); localactivity = (char *) MemoryContextAlloc(pgStatLocalContext, pgstat_track_activity_query_size * MaxBackends); @@ -2405,6 +2470,8 @@ pgstat_read_current_status(void) * strcpy is safe even if the string is modified concurrently, * because there's always a \0 at the end of the buffer. */ + strcpy(localappname, (char *) beentry->st_appname); + localentry->st_appname = localappname; strcpy(localactivity, (char *) beentry->st_activity); localentry->st_activity = localactivity; } @@ -2422,6 +2489,7 @@ pgstat_read_current_status(void) if (localentry->st_procpid > 0) { localentry++; + localappname += NAMEDATALEN; localactivity += pgstat_track_activity_query_size; localNumBackends++; } diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 89fb28a402b..6d93c5cf751 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/adt/pgstatfuncs.c,v 1.54 2009/06/11 14:49:04 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/pgstatfuncs.c,v 1.55 2009/11/28 23:38:07 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -416,7 +416,7 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); - tupdesc = CreateTemplateTupleDesc(10, false); + tupdesc = CreateTemplateTupleDesc(11, false); TupleDescInitEntry(tupdesc, (AttrNumber) 1, "datid", OIDOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 2, "procpid", INT4OID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 3, "usesysid", OIDOID, -1, 0); @@ -427,6 +427,7 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) TupleDescInitEntry(tupdesc, (AttrNumber) 8, "backend_start", TIMESTAMPTZOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 9, "client_addr", INETOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 10, "client_port", INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 11, "application_name", TEXTOID, -1, 0); funcctx->tuple_desc = BlessTupleDesc(tupdesc); @@ -478,8 +479,8 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) if (funcctx->call_cntr < funcctx->max_calls) { /* for each row */ - Datum values[10]; - bool nulls[10]; + Datum values[11]; + bool nulls[11]; HeapTuple tuple; PgBackendStatus *beentry; SockAddr zero_clientaddr; @@ -599,6 +600,12 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) nulls[9] = true; } } + + /* application name */ + if (beentry->st_appname) + values[10] = CStringGetTextDatum(beentry->st_appname); + else + nulls[10] = true; } else { @@ -610,6 +617,7 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) nulls[7] = true; nulls[8] = true; nulls[9] = true; + nulls[10] = true; } tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c index 5f30e783756..59fa07a379a 100644 --- a/src/backend/utils/error/elog.c +++ b/src/backend/utils/error/elog.c @@ -42,7 +42,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/error/elog.c,v 1.218 2009/10/17 00:24:50 mha Exp $ + * $PostgreSQL: pgsql/src/backend/utils/error/elog.c,v 1.219 2009/11/28 23:38:07 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -68,6 +68,7 @@ #include "storage/ipc.h" #include "storage/proc.h" #include "tcop/tcopprot.h" +#include "utils/guc.h" #include "utils/memutils.h" #include "utils/ps_status.h" @@ -1798,6 +1799,16 @@ log_line_prefix(StringInfo buf, ErrorData *edata) /* process the option */ switch (Log_line_prefix[i]) { + case 'a': + if (MyProcPort) + { + const char *appname = application_name; + + if (appname == NULL || *appname == '\0') + appname = _("[unknown]"); + appendStringInfo(buf, "%s", appname); + } + break; case 'u': if (MyProcPort) { @@ -2103,6 +2114,11 @@ write_csvlog(ErrorData *edata) appendCSVLiteral(&buf, msgbuf.data); pfree(msgbuf.data); } + appendStringInfoCharMacro(&buf, ','); + + /* application name */ + if (application_name) + appendCSVLiteral(&buf, application_name); appendStringInfoChar(&buf, '\n'); diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 33f38a20c48..04ba14c2dbb 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -10,7 +10,7 @@ * Written by Peter Eisentraut . * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.523 2009/10/21 20:38:58 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.524 2009/11/28 23:38:07 tgl Exp $ * *-------------------------------------------------------------------- */ @@ -168,6 +168,7 @@ static bool assign_maxconnections(int newval, bool doit, GucSource source); static bool assign_autovacuum_max_workers(int newval, bool doit, GucSource source); static bool assign_effective_io_concurrency(int newval, bool doit, GucSource source); static const char *assign_pgstat_temp_directory(const char *newval, bool doit, GucSource source); +static const char *assign_application_name(const char *newval, bool doit, GucSource source); static char *config_enum_get_options(struct config_enum * record, const char *prefix, const char *suffix, @@ -378,6 +379,8 @@ char *pgstat_temp_directory; char *default_do_language; +char *application_name; + int tcp_keepalives_idle; int tcp_keepalives_interval; int tcp_keepalives_count; @@ -2534,6 +2537,16 @@ static struct config_string ConfigureNamesString[] = "plpgsql", NULL, NULL }, + { + {"application_name", PGC_USERSET, LOGGING, + gettext_noop("Sets the application name to be reported in statistics and logs."), + NULL, + GUC_IS_NAME | GUC_NOT_IN_SAMPLE + }, + &application_name, + "", assign_application_name, NULL + }, + /* End-of-list marker */ { {NULL, 0, 0, NULL, NULL}, NULL, NULL, NULL, NULL @@ -7717,4 +7730,28 @@ assign_pgstat_temp_directory(const char *newval, bool doit, GucSource source) return newval; } +static const char * +assign_application_name(const char *newval, bool doit, GucSource source) +{ + if (doit) + { + /* Only allow clean ASCII chars in the application name */ + char *repval = guc_strdup(ERROR, newval); + char *p; + + for (p = repval; *p; p++) + { + if (*p < 32 || *p > 126) + *p = '?'; + } + + /* Update the pg_stat_activity view */ + pgstat_report_appname(repval); + + return repval; + } + else + return newval; +} + #include "guc-file.c" diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index 4c5f1590de4..f2accd263e6 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -334,6 +334,7 @@ #log_duration = off #log_hostname = off #log_line_prefix = '' # special values: + # %a = application name # %u = user name # %d = database name # %r = remote host and port diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 4be0e0fce68..61b7ccd822b 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -37,7 +37,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.551 2009/11/20 20:38:11 tgl Exp $ + * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.552 2009/11/28 23:38:07 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 200911201 +#define CATALOG_VERSION_NO 200911281 #endif diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index cd249d841b8..f0988892c74 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.552 2009/10/09 21:02:56 petere Exp $ + * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.553 2009/11/28 23:38:07 tgl Exp $ * * NOTES * The script catalog/genbki.sh reads this file and generates .bki @@ -2999,7 +2999,7 @@ DATA(insert OID = 2784 ( pg_stat_get_last_autoanalyze_time PGNSP PGUID 12 1 0 0 DESCR("statistics: last auto analyze time for a table"); DATA(insert OID = 1936 ( pg_stat_get_backend_idset PGNSP PGUID 12 1 100 0 f f f t t s 0 0 23 "" _null_ _null_ _null_ _null_ pg_stat_get_backend_idset _null_ _null_ _null_ )); DESCR("statistics: currently active backend IDs"); -DATA(insert OID = 2022 ( pg_stat_get_activity PGNSP PGUID 12 1 100 0 f f f f t s 1 0 2249 "23" "{23,26,23,26,25,16,1184,1184,1184,869,23}" "{i,o,o,o,o,o,o,o,o,o,o}" "{pid,datid,procpid,usesysid,current_query,waiting,xact_start,query_start,backend_start,client_addr,client_port}" _null_ pg_stat_get_activity _null_ _null_ _null_ )); +DATA(insert OID = 2022 ( pg_stat_get_activity PGNSP PGUID 12 1 100 0 f f f f t s 1 0 2249 "23" "{23,26,23,26,25,16,1184,1184,1184,869,23,25}" "{i,o,o,o,o,o,o,o,o,o,o,o}" "{pid,datid,procpid,usesysid,current_query,waiting,xact_start,query_start,backend_start,client_addr,client_port,application_name}" _null_ pg_stat_get_activity _null_ _null_ _null_ )); DESCR("statistics: information about currently active backends"); DATA(insert OID = 2026 ( pg_backend_pid PGNSP PGUID 12 1 0 0 f f f t f s 0 0 23 "" _null_ _null_ _null_ _null_ pg_backend_pid _null_ _null_ _null_ )); DESCR("statistics: current backend PID"); diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 7fa25175f44..7c4efb9f3bd 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -5,7 +5,7 @@ * * Copyright (c) 2001-2009, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/include/pgstat.h,v 1.83 2009/06/11 14:49:08 momjian Exp $ + * $PostgreSQL: pgsql/src/include/pgstat.h,v 1.84 2009/11/28 23:38:08 tgl Exp $ * ---------- */ #ifndef PGSTAT_H @@ -564,6 +564,9 @@ typedef struct PgBackendStatus /* Is backend currently waiting on an lmgr lock? */ bool st_waiting; + /* application name; MUST be null-terminated */ + char *st_appname; + /* current command string; MUST be null-terminated */ char *st_activity; } PgBackendStatus; @@ -641,7 +644,8 @@ extern void pgstat_report_analyze(Relation rel, extern void pgstat_initialize(void); extern void pgstat_bestart(void); -extern void pgstat_report_activity(const char *what); +extern void pgstat_report_activity(const char *cmd_str); +extern void pgstat_report_appname(const char *appname); extern void pgstat_report_xact_timestamp(TimestampTz tstamp); extern void pgstat_report_waiting(bool waiting); extern const char *pgstat_get_backend_current_activity(int pid, bool checkUser); diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h index ad76d06ffc8..53704ec38bb 100644 --- a/src/include/utils/guc.h +++ b/src/include/utils/guc.h @@ -7,7 +7,7 @@ * Copyright (c) 2000-2009, PostgreSQL Global Development Group * Written by Peter Eisentraut . * - * $PostgreSQL: pgsql/src/include/utils/guc.h,v 1.108 2009/10/13 14:18:40 alvherre Exp $ + * $PostgreSQL: pgsql/src/include/utils/guc.h,v 1.109 2009/11/28 23:38:08 tgl Exp $ *-------------------------------------------------------------------- */ #ifndef GUC_H @@ -183,6 +183,8 @@ extern char *external_pid_file; extern char *default_do_language; +extern char *application_name; + extern int tcp_keepalives_idle; extern int tcp_keepalives_interval; extern int tcp_keepalives_count; diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 1be948505e5..f218dbe2c82 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/interfaces/libpq/fe-connect.c,v 1.377 2009/09/27 03:43:10 tgl Exp $ + * $PostgreSQL: pgsql/src/interfaces/libpq/fe-connect.c,v 1.378 2009/11/28 23:38:08 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -164,6 +164,12 @@ static const PQconninfoOption PQconninfoOptions[] = { {"options", "PGOPTIONS", DefaultOption, NULL, "Backend-Debug-Options", "D", 40}, + {"application_name", "PGAPPNAME", NULL, NULL, + "Application-Name", "", 64}, + + {"fallback_application_name", NULL, NULL, NULL, + "Fallback-Application-Name", "", 64}, + #ifdef USE_SSL /* @@ -256,6 +262,7 @@ static int parseServiceInfo(PQconninfoOption *options, static char *pwdfMatchesString(char *buf, char *token); static char *PasswordFromFile(char *hostname, char *port, char *dbname, char *username); +static PostgresPollingStatusType pqAppnamePoll(PGconn *conn); static void default_threadlock(int acquire); @@ -416,6 +423,10 @@ connectOptions1(PGconn *conn, const char *conninfo) conn->pgtty = tmp ? strdup(tmp) : NULL; tmp = conninfo_getval(connOptions, "options"); conn->pgoptions = tmp ? strdup(tmp) : NULL; + tmp = conninfo_getval(connOptions, "application_name"); + conn->appname = tmp ? strdup(tmp) : NULL; + tmp = conninfo_getval(connOptions, "fallback_application_name"); + conn->fbappname = tmp ? strdup(tmp) : NULL; tmp = conninfo_getval(connOptions, "dbname"); conn->dbName = tmp ? strdup(tmp) : NULL; tmp = conninfo_getval(connOptions, "user"); @@ -1064,7 +1075,7 @@ PQconnectPoll(PGconn *conn) case CONNECTION_MADE: break; - /* We allow pqSetenvPoll to decide whether to proceed. */ + /* pqSetenvPoll/pqAppnamePoll will decide whether to proceed. */ case CONNECTION_SETENV: break; @@ -1888,6 +1899,12 @@ keep_going: /* We will come back to here until there is conn->addrlist = NULL; conn->addr_cur = NULL; + /* + * Note: To avoid changing the set of application-visible + * connection states, v2 environment setup and v3 application + * name setup both happen in the CONNECTION_SETENV state. + */ + /* Fire up post-connection housekeeping if needed */ if (PG_PROTOCOL_MAJOR(conn->pversion) < 3) { @@ -1896,6 +1913,13 @@ keep_going: /* We will come back to here until there is conn->next_eo = EnvironmentOptions; return PGRES_POLLING_WRITING; } + else if (conn->sversion >= 80500 && + (conn->appname || conn->fbappname)) + { + conn->status = CONNECTION_SETENV; + conn->appname_state = APPNAME_STATE_CMD_SEND; + return PGRES_POLLING_WRITING; + } /* Otherwise, we are open for business! */ conn->status = CONNECTION_OK; @@ -1903,42 +1927,49 @@ keep_going: /* We will come back to here until there is } case CONNECTION_SETENV: - - /* - * Do post-connection housekeeping (only needed in protocol 2.0). - * - * We pretend that the connection is OK for the duration of these - * queries. - */ - conn->status = CONNECTION_OK; - - switch (pqSetenvPoll(conn)) { - case PGRES_POLLING_OK: /* Success */ - break; + PostgresPollingStatusType ret; - case PGRES_POLLING_READING: /* Still going */ - conn->status = CONNECTION_SETENV; - return PGRES_POLLING_READING; + /* + * Do post-connection housekeeping (only needed in protocol + * 2.0), or send the application name in PG8.5+. + * + * We pretend that the connection is OK for the duration of + * these queries. + */ + conn->status = CONNECTION_OK; - case PGRES_POLLING_WRITING: /* Still going */ - conn->status = CONNECTION_SETENV; - return PGRES_POLLING_WRITING; + if (PG_PROTOCOL_MAJOR(conn->pversion) < 3) + ret = pqSetenvPoll(conn); + else /* must be here to send app name */ + ret = pqAppnamePoll(conn); - default: - goto error_return; + switch (ret) + { + case PGRES_POLLING_OK: /* Success */ + break; + + case PGRES_POLLING_READING: /* Still going */ + conn->status = CONNECTION_SETENV; + return PGRES_POLLING_READING; + + case PGRES_POLLING_WRITING: /* Still going */ + conn->status = CONNECTION_SETENV; + return PGRES_POLLING_WRITING; + + default: + goto error_return; + } + + /* We are open for business! */ + conn->status = CONNECTION_OK; + return PGRES_POLLING_OK; } - /* We are open for business! */ - conn->status = CONNECTION_OK; - return PGRES_POLLING_OK; - default: appendPQExpBuffer(&conn->errorMessage, - libpq_gettext( - "invalid connection state %c, " - "probably indicative of memory corruption\n" - ), + libpq_gettext("invalid connection state %d, " + "probably indicative of memory corruption\n"), conn->status); goto error_return; } @@ -2000,6 +2031,7 @@ makeEmptyPGconn(void) conn->options_valid = false; conn->nonblocking = false; conn->setenv_state = SETENV_STATE_IDLE; + conn->appname_state = APPNAME_STATE_IDLE; conn->client_encoding = PG_SQL_ASCII; conn->std_strings = false; /* unless server says differently */ conn->verbosity = PQERRORS_DEFAULT; @@ -2082,6 +2114,10 @@ freePGconn(PGconn *conn) free(conn->connect_timeout); if (conn->pgoptions) free(conn->pgoptions); + if (conn->appname) + free(conn->appname); + if (conn->fbappname) + free(conn->fbappname); if (conn->dbName) free(conn->dbName); if (conn->pguser) @@ -4005,6 +4041,129 @@ pqGetHomeDirectory(char *buf, int bufsize) #endif } +/* + * pqAppnamePoll + * + * Polls the process of passing the application name to the backend. + * + * Ideally, we'd include the appname in the startup packet, but that would + * cause old backends to reject the unknown parameter. So we send it in a + * separate query after we have determined the backend version. Once there + * is no interest in pre-8.5 backends, this should be folded into the startup + * packet logic. + */ +static PostgresPollingStatusType +pqAppnamePoll(PGconn *conn) +{ + PGresult *res; + + if (conn == NULL || conn->status == CONNECTION_BAD) + return PGRES_POLLING_FAILED; + + /* Check whether there is any data for us */ + switch (conn->appname_state) + { + /* This is a reading state. */ + case APPNAME_STATE_CMD_WAIT: + { + /* Load waiting data */ + int n = pqReadData(conn); + + if (n < 0) + goto error_return; + if (n == 0) + return PGRES_POLLING_READING; + + break; + } + + /* This is a writing state, so we just proceed. */ + case APPNAME_STATE_CMD_SEND: + break; + + /* Should we raise an error if called when not active? */ + case APPNAME_STATE_IDLE: + return PGRES_POLLING_OK; + + default: + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("invalid appname state %d, " + "probably indicative of memory corruption\n"), + conn->appname_state); + goto error_return; + } + + /* We will loop here until there is nothing left to do in this call. */ + for (;;) + { + switch (conn->appname_state) + { + case APPNAME_STATE_CMD_SEND: + { + const char *val; + char escVal[NAMEDATALEN*2 + 1]; + char setQuery[NAMEDATALEN*2 + 26 + 1]; + + /* Use appname if present, otherwise use fallback */ + val = conn->appname ? conn->appname : conn->fbappname; + + /* + * Escape the data as needed. We can truncate to NAMEDATALEN, + * so there's no need to cope with malloc. + */ + PQescapeStringConn(conn, escVal, val, NAMEDATALEN, NULL); + + sprintf(setQuery, "SET application_name = '%s'", escVal); + + if (!PQsendQuery(conn, setQuery)) + goto error_return; + + conn->appname_state = APPNAME_STATE_CMD_WAIT; + break; + } + + case APPNAME_STATE_CMD_WAIT: + { + if (PQisBusy(conn)) + return PGRES_POLLING_READING; + + res = PQgetResult(conn); + + if (res) + { + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + goto error_return; + } + PQclear(res); + /* Keep reading until PQgetResult returns NULL */ + } + else + { + /* Query finished, so we're done */ + conn->appname_state = APPNAME_STATE_IDLE; + return PGRES_POLLING_OK; + } + break; + } + + default: + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("invalid appname state %d, " + "probably indicative of memory corruption\n"), + conn->appname_state); + goto error_return; + } + } + + /* Unreachable */ + +error_return: + conn->appname_state = APPNAME_STATE_IDLE; + return PGRES_POLLING_FAILED; +} + /* * To keep the API consistent, the locking stubs are always provided, even * if they are not required. diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index d93ba4d25e2..a496aebbba3 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -12,7 +12,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/interfaces/libpq/libpq-int.h,v 1.144 2009/07/24 17:58:31 tgl Exp $ + * $PostgreSQL: pgsql/src/interfaces/libpq/libpq-int.h,v 1.145 2009/11/28 23:38:08 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -244,6 +244,14 @@ typedef enum SETENV_STATE_IDLE } PGSetenvStatusType; +/* PGAppnameStatusType defines the state of the PQAppname state machine */ +typedef enum +{ + APPNAME_STATE_CMD_SEND, /* About to send the appname */ + APPNAME_STATE_CMD_WAIT, /* Waiting for above send to complete */ + APPNAME_STATE_IDLE +} PGAppnameStatusType; + /* Typedef for the EnvironmentOptions[] array */ typedef struct PQEnvironmentOption { @@ -295,6 +303,8 @@ struct pg_conn * displayed (OBSOLETE, NOT USED) */ char *connect_timeout; /* connection timeout (numeric string) */ char *pgoptions; /* options to start the backend with */ + char *appname; /* application name */ + char *fbappname; /* fallback application name */ char *dbName; /* database name */ char *pguser; /* Postgres username and password, if any */ char *pgpass; @@ -349,6 +359,7 @@ struct pg_conn struct addrinfo *addr_cur; /* the one currently being tried */ int addrlist_family; /* needed to know how to free addrlist */ PGSetenvStatusType setenv_state; /* for 2.0 protocol only */ + PGAppnameStatusType appname_state; const PQEnvironmentOption *next_eo; /* Miscellaneous stuff */ diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 9561a235594..a4025436a90 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1289,7 +1289,7 @@ SELECT viewname, definition FROM pg_views WHERE schemaname <> 'information_schem pg_rules | SELECT n.nspname AS schemaname, c.relname AS tablename, r.rulename, pg_get_ruledef(r.oid) AS definition FROM ((pg_rewrite r JOIN pg_class c ON ((c.oid = r.ev_class))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE (r.rulename <> '_RETURN'::name); pg_settings | SELECT a.name, a.setting, a.unit, a.category, a.short_desc, a.extra_desc, a.context, a.vartype, a.source, a.min_val, a.max_val, a.enumvals, a.boot_val, a.reset_val, a.sourcefile, a.sourceline FROM pg_show_all_settings() a(name, setting, unit, category, short_desc, extra_desc, context, vartype, source, min_val, max_val, enumvals, boot_val, reset_val, sourcefile, sourceline); pg_shadow | SELECT pg_authid.rolname AS usename, pg_authid.oid AS usesysid, pg_authid.rolcreatedb AS usecreatedb, pg_authid.rolsuper AS usesuper, pg_authid.rolcatupdate AS usecatupd, pg_authid.rolpassword AS passwd, (pg_authid.rolvaliduntil)::abstime AS valuntil, s.setconfig AS useconfig FROM (pg_authid LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid)))) WHERE pg_authid.rolcanlogin; - pg_stat_activity | SELECT s.datid, d.datname, s.procpid, s.usesysid, u.rolname AS usename, s.current_query, s.waiting, s.xact_start, s.query_start, s.backend_start, s.client_addr, s.client_port FROM pg_database d, pg_stat_get_activity(NULL::integer) s(datid, procpid, usesysid, current_query, waiting, xact_start, query_start, backend_start, client_addr, client_port), pg_authid u WHERE ((s.datid = d.oid) AND (s.usesysid = u.oid)); + pg_stat_activity | SELECT s.datid, d.datname, s.procpid, s.usesysid, u.rolname AS usename, s.current_query, s.waiting, s.xact_start, s.query_start, s.backend_start, s.client_addr, s.client_port, s.application_name FROM pg_database d, pg_stat_get_activity(NULL::integer) s(datid, procpid, usesysid, current_query, waiting, xact_start, query_start, backend_start, client_addr, client_port, application_name), pg_authid u WHERE ((s.datid = d.oid) AND (s.usesysid = u.oid)); pg_stat_all_indexes | SELECT c.oid AS relid, i.oid AS indexrelid, n.nspname AS schemaname, c.relname, i.relname AS indexrelname, pg_stat_get_numscans(i.oid) AS idx_scan, pg_stat_get_tuples_returned(i.oid) AS idx_tup_read, pg_stat_get_tuples_fetched(i.oid) AS idx_tup_fetch FROM (((pg_class c JOIN pg_index x ON ((c.oid = x.indrelid))) JOIN pg_class i ON ((i.oid = x.indexrelid))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char"])); pg_stat_all_tables | SELECT c.oid AS relid, n.nspname AS schemaname, c.relname, pg_stat_get_numscans(c.oid) AS seq_scan, pg_stat_get_tuples_returned(c.oid) AS seq_tup_read, (sum(pg_stat_get_numscans(i.indexrelid)))::bigint AS idx_scan, ((sum(pg_stat_get_tuples_fetched(i.indexrelid)))::bigint + pg_stat_get_tuples_fetched(c.oid)) AS idx_tup_fetch, pg_stat_get_tuples_inserted(c.oid) AS n_tup_ins, pg_stat_get_tuples_updated(c.oid) AS n_tup_upd, pg_stat_get_tuples_deleted(c.oid) AS n_tup_del, pg_stat_get_tuples_hot_updated(c.oid) AS n_tup_hot_upd, pg_stat_get_live_tuples(c.oid) AS n_live_tup, pg_stat_get_dead_tuples(c.oid) AS n_dead_tup, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze FROM ((pg_class c LEFT JOIN pg_index i ON ((c.oid = i.indrelid))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char"])) GROUP BY c.oid, n.nspname, c.relname; pg_stat_bgwriter | SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints_timed, pg_stat_get_bgwriter_requested_checkpoints() AS checkpoints_req, pg_stat_get_bgwriter_buf_written_checkpoints() AS buffers_checkpoint, pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_written_backend() AS buffers_backend, pg_stat_get_buf_alloc() AS buffers_alloc;