Chapter 3. PMAPI—The Performance Metrics API

This chapter describes the Performance Metrics Application Programming Interface (PMAPI) provided with Performance Co-Pilot (PCP).

The PMAPI is a set of functions and data structure definitions that allow client applications to access performance data from one or more Performance Metric Collection Daemons (PMCDs) or from PCP archive logs. The PCP utilities are all written using the PMAPI.

The most common use of PCP includes running performance monitoring utilities on a workstation (the monitoring system) while performance data is retrieved from one or more remote collector systems by a number of PCP processes. These processes execute on both the monitoring system and the collector systems. The collector systems are typically servers, and are the targets for the performance investigations.

In the development of the PMAPI the most important question has been, “How easily and quickly will this API enable the user to build new performance tools, or exploit existing tools for newly available performance metrics?” The PMAPI and the standard tools that use the PMAPI have enjoyed a symbiotic evolution throughout the development of Performance Co-Pilot.

It will be convenient to differentiate between code that uses the PMAPI and code that implements the services of the PMAPI. The former will be termed “above the PMAPI” and the latter “below the PMAPI.”

Naming and Identifying Performance Metrics

Across all of the supported performance metric domains, there are a large number of performance metrics. Each metric has its own description, format, and semantics. Performance Co-Pilot presents a uniform interface to these metrics above the PMAPI, independent of the source of the underlying metric data. For example, the performance metric hinv.physmem has a single 32-bit unsigned integer value, representing the number of megabytes of physical memory in the system, while the performance metric irix.disk.dev.total has one 32-bit unsigned integer value per disk spindle, representing the cumulative count of I/O operations involving each associated disk spindle. These concepts are described in greater detail in “Domains, Metrics, and Instances”.

For brevity and efficiency, internally PCP avoids using ASCII names for performance metrics, and instead uses an identification scheme that unambiguously associates a single integer with each known performance metric. This integer is known as a Performance Metric Identifier, or PMID. For routines using the PMAPI, a PMID is defined and manipulated with the typedef pmID.

Below the PMAPI, the integer value of the PMID has an internal structure that reflects the details of the PMCD and PMDA architecture, as described in “Metrics”.

Above the PMAPI, a Performance Metrics Name Space (PMNS) is used to provide a hierarchic classification of external metric names, and a one-to-one mapping of external names to internal PMIDs. A more detailed description of the PMNS can be found in the Performance Co-Pilot User's and Administrator's Guide.

Applications that use the PMAPI may have independent versions of a PMNS, constructed from an initialization file when the application starts. Not all PMIDs need be represented in the PMNS of every application. For example, an application that monitors disk traffic could use a name space that references only the PMIDs for I/O statistics. Other applications require a stable PMNS that can be assumed to be the same on all systems. The distributed implementation includes a default PMNS for just this purpose.

The vast majority of PCP users and applications using the PMAPI will choose to use the default PMNS.

As of PCP release 2.0 the default PMNS comes from the performance metrics source, either a PMCD process or a PCP archive. This PMNS always reflects the available metrics from the performance metrics source, so most applications never use the local version of a PMNS.

Performance Metric Instances

When performance metric values are returned across the PMAPI to a requesting application, there may be more than one value for a particular metric; for example, independent counts for each CPU, or each process, or each disk, or each system call type, and so on. This multiplicity of values is not enumerated in the name space, but rather when performance metrics are delivered across the PMAPI.

The notion of “metric instances” is really a number of related concepts, as follows:

  • A particular performance metric may have a set of associated values or instances.

  • The instances are differentiated by an instance identifier.

  • An instance identifier has an internal encoding (an integer value) and an external encoding (a corresponding external name or label).

  • The set of all possible instance identifiers associated with a performance metric on a particular host constitutes an “instance domain”.

  • Several performance metrics may share the same instance domain.

For example, consider the following;

$ pminfo -f irix.filesys.free

irix.filesys.free
    inst [1 or “/dev/root”] value 1803
    inst [2 or “/dev/usr”] value 22140
    inst [3 or “/dev/dsk/dks0d2s0”] value 157938

The metric irix.filesys.free has three values, currently 1803, 22140, and 157938. These values are respectively associated with the instances identified by the internal identifiers 1, 2 and 3, and the external identifiers /dev/root, /dev/usr and /dev/dsk/dks0d2s0. These instances form an instance domain that is shared by the performance metrics irix.filesys.capacity, irix.filesys.used, irix.filesys.free, irix.filesys.mountdir, and so on.

Each performance metric is associated with an instance domain, while each instance domain may be associated with many performance metrics. Each instance domain is identified by a unique value, as defined by the following typedef declaration:

typedef unsigned long pmInDom;

The special instance domain PM_INDOM_NULL is reserved to indicate that the metric has a single value (a singular instance domain). For example, the performance metric irix.mem.freemem always has exactly one value. Note that this is semantically different to a performance metric like irix.kernel.percpu.syscall that has a non-singular instance domain, but may have only one value available; for example, on a system with a single processor.

In the results returned above the PMAPI, each individual instance, within an instance domain, is identified by an internal integer instance identifier. The special instance identifier PM_IN_NULL is reserved for the single value in a singular instance domain. Performance metric values are delivered across the PMAPI as a set of instance identifier and value pairs.

The instance domain of a metric may change with time. For example, a machine may be shut down, have several disks added, and be rebooted. All performance metrics associated with the instance domain of disk devices would contain additional values after the reboot. The difficult issue of transient performance metrics means that repeated requests for the same PMID may return different numbers of values, or some changes in the particular instance identifiers returned. This means applications need to be aware that metric instantiation is guaranteed to be valid only at the time of collection.


Note: Some instance domains are more dynamic than others. For example, consider the instance domains behind the performance metrics proc.memory.physical.dat (one instance per process), irix.swap.free (one instance per swap partition) and irix.kernel.percpu.cpu.intr (one instance per CPU).


Current PMAPI Context

When performance metrics are retrieved across the PMAPI, they are delivered in the context of a particular source of metrics, a point in time, and a profile of desired instances. This means that the application making the request has already negotiated across the PMAPI to establish the context in which the request should be executed.

A metric's source may be the current performance data from a particular host (a “live” or real-time source), or an archive log of performance data collected by pmlogger at some remote host or earlier time (a retrospective or archive source). The metric's source is specified when the PMAPI context is created by calling the pmNewContext function.

The collection time for a performance metric is always the current time of day for a real-time source, or current position for an archive source. For archives, the collection time may be set to an arbitrary time within the bounds of the archive log by calling the pmSetMode function.

The last component of a PMAPI context is an instance profile that may be used to control which particular instances from an instance domain should be retrieved. When a new PMAPI context is created, the initial state expresses an interest in all possible instances, to be collected at the current time. The instance profile can be manipulated using the functions pmAddProfile and pmDelProfile.

Performance Metric Descriptions

For each defined performance metric, there is associated metadata encoded in a Performance Metric Description (pmDesc structure) that describes the format and semantics of the performance metric. The pmDesc structure provides all of the information required to interpret and manipulate a performance metric through the PMAPI. It has the following declaration:

/* Performance Metric Descriptor */
typedef struct {
pmID    pmid;   /* unique identifier */
int     type;   /* base data type (see below) */
pmInDom indom;  /* instance domain */
int     sem;    /* semantics of value (see below) */
pmUnits units;  /* dimension and units (see below) */
} pmDesc;

The type field in the pmDesc structure describes various encodings of a metric's value. Its value will be one of the following constants:

/* pmDesc.type - data type of metric values */
#define PM_TYPE_NOSUPPORT -1   /* not in this version */
#define PM_TYPE_32        0    /* 32-bit signed integer */
#define PM_TYPE_U32       1    /* 32-bit unsigned integer */
#define PM_TYPE_64        2    /* 64-bit signed integer */
#define PM_TYPE_U64       3    /* 64-bit unsigned integer */
#define PM_TYPE_FLOAT     4    /* 32-bit floating point */
#define PM_TYPE_DOUBLE    5    /* 64-bit floating point */
#define PM_TYPE_STRING    6    /* array of char */
#define PM_TYPE_AGGREGATE 7    /* arbitrary binary data */

By convention PM_TYPE_STRING is interpreted as a classic C-style null byte terminated string.

If the value of a performance metric is of type PM_TYPE_AGGREGATE (or indeed PM_TYPE_STRING), the interpretation of that value is unknown to most PCP components. In these cases, the application using the value and the Performance Metrics Domain Agent (PMDA) providing the value must have some common understanding about how the value is structured and interpreted.

PM_TYPE_NOSUPPORT indicates that the PCP collection framework knows about the metric, but the corresponding service or application is either not configured or is at a revision level that does not provide support for this performance metric.

The semantics of the performance metric is described by the sem field of a pmDesc structure and uses the following constants:

/* pmDesc.sem - semantics of metric values */
#define PM_SEM_COUNTER 1  /* cumulative count, monotonic increasing */
#define PM_SEM_INSTANT 3 /* instant. value continuous domain */
#define PM_SEM_DISCRETE 4 /* instant. value discrete domain */

Each value for a performance metric is assumed to be drawn from a set of values that can be described in terms of their dimensionality and scale by a compact encoding, as follows:

  • The dimensionality is defined by a power, or index, in each of three orthogonal dimensions: Space, Time, and Count (dimensionless). For example, I/O throughput is Space1.Time-1, while the running total of system calls is Count1, memory allocation is Space1, and average service time per event is Time1.Count-1.

  • In each dimension, a number of common scale values are defined that may be used to better encode ranges that might otherwise exhaust the precision of a 32-bit value. So, for example, a metric with dimension Space1.Time-1 may have values encoded using the scale megabytes per second.

This information is encoded in the pmUnits data structure, which is embedded in the pmDesc structure:

/*
 * Encoding for the units (dimensions and
 * scale) for Performance Metric Values
 *
 * For example, a pmUnits struct of
 * { 1, -1, 0, PM_SPACE_MBYTE, PM_TIME_SEC, 0 }
 * represents Mbytes/sec, while 
 * { 0, 1, -1, 0, PM_TIME_HOUR, 6 }
 * represents hours/million-events
 */
typedef struct {
int dimSpace:4;   /* space dimension */
int dimTime:4;    /* time dimension */
int dimCount:4;   /* event dimension */
int scaleSpace:4; /* one of PM_SPACE_* below */
int scaleTime:4;  /* one of PM_TIME_* below */
int scaleCount:4; /* one of PM_COUNT_* below */
} pmUnits;   /* dimensional units and scale of value */
/* pmUnits.scaleSpace */
#define PM_SPACE_BYTE 0  /* bytes */
#define PM_SPACE_KBYTE 1 /* Kilobytes (1024) */
#define PM_SPACE_MBYTE 2 /* Megabytes (1024^2) */
#define PM_SPACE_GBYTE 3 /* Gigabytes (1024^3) */
#define PM_SPACE_TBYTE 4 /* Terabytes (1024^4) */
/* pmUnits.scaleTime */
#define PM_TIME_NSEC 0   /* nanoseconds */
#define PM_TIME_USEC 1   /* microseconds */
#define PM_TIME_MSEC 2   /* milliseconds */
#define PM_TIME_SEC 3    /* seconds */
#define PM_TIME_MIN 4    /* minutes */
#define PM_TIME_HOUR 5   /* hours */
/*
 * pmUnits.scaleCount (e.g. count events, syscalls,
 * interrupts, etc.) -- these are simply powers of 10,
 * and not enumerated here.
 * e.g. 6 for 10^6, or -3 for 10^-3
 */
#define PM_COUNT_ONE 0 /* 1 */

Performance Metrics Values

An application may fetch (or store) values for a set of performance metrics, each with a set of associated instances, using a single pmFetch (or pmStore) function call. To accommodate this, values are delivered across the PMAPI in the form of a tree data structure, rooted at a pmResult structure. This encoding is illustrated in Figure 3-1, and uses the following component data structures:

typedef struct {
    int inst;                 /* instance identifier */
    union {
        pmValueBlock *pval;   /* pointer to value-block */
        int           lval;   /* integer value insitu */
    } value;
} pmValue;

Figure 3-1. A Structured Result for Performance Metrics From pmFetch


The internal instance identifier is stored in the inst element. If a value for a particular metric-instance pair is a 32-bit integer (signed or unsigned), then it will be stored in the lval element. If not, the value will be in a pmValueBlock located via pval:

typedef struct {
    unsigned int    vtype : 8;    /* value type */
    unsigned int    vlen : 24;    /* bytes for vtype/vlen + vbuf */
    char            vbuf[1];      /* the value */
} pmValueBlock;

The length of the pmValueBlock (including the vtype and vlen fields) is stored in vlen. Despite the prototype declaration of vbuf, this array really accommodates vlen minus sizeof(vlen) bytes. The vtype field encodes the type of the value in the vbuf[] array, and is one of the PM_TYPE_* macros defined in /usr/include/pmapi.h.

typedef struct {
    pmID    pmid;          /* metric identifier */
    int     numval;        /* number of values */
    int     valfmt;        /* value style, insitu or ptr */
    pmValue vlist[1];      /* set of instances/values */ 
} pmValueSet;

A pmValueSet contains all of the values to be returned from pmFetch for a single performance metric identified by the pmid field. If positive, the numval field identifies the number of value-instance pairs in the vlist array (despite the prototype declaration of size 1). If numval is zero, there are no values available for the associated performance metric and vlist[0] is undefined. A negative value for numval indicates an error condition (see pmErrStr(3)) and vlist[0] is undefined. The valfmt field has the value PM_VAL_INSITU to indicate that the values for the performance metrics should be located directly via the lval member of the value union embedded in the elements of vlist, otherwise metric values are located indirectly via the pval member of the elements of vlist.

/* Result returned by pmFetch() */
typedef struct {
    struct timeval timestamp;    /* stamped by collector */
    int            numpmid;      /* number of PMIDs */
    pmValueSet     *vset[1];     /* set of value sets */
} pmResult

The pmResult structure contains a timestamp and an array of numpmid pointers to pmValueSets. There is one pmValueSet pointer per PMID, with a one-to-one correspondence to the set of requested PMIDs passed to pmFetch.

Along with the metric values, the PMAPI returns a timestamp with each pmResult that serves to identify when the performance metric values were collected. The time is in the format returned by gettimeofday and is typically very close to the time when the metrics are exported across the PMAPI.


Note: There is a question of exactly “when” individual metrics may have been collected, especially given their origin in potentially different performance metric domains, and variability in metric updating frequency by individual PMDAs. PCP uses a pragmatic approach, in which the PMAPI implementation returns all metrics with values accurate as of the timestamp, to the maximum degree possible, and pmcd demands that all PMDAs deliver values within a small realtime window. The resulting inaccuracy is small, and the additional burden of accurate individual timestamping for each returned metric value is neither warranted nor practical (from an implementation viewpoint).

The PMAPI provides functions to extract, rescale, and print values from the above structures; refer to “PMAPI Ancillary Support Services”.

General Issues of PMAPI Programming Style and Interaction

The following sections specify the programming style used in the PMAPI:

Variable Length Argument and Results Lists

All arguments and results involving a “list of something” are encoded as an array with an associated argument or function value to identify the number of elements in the array. This encoding scheme avoids both the varargs approach and sentinel-terminated lists. Where the size of a result is known at the time of a call, it is the caller's responsibility to allocate (and possibly free) the storage, and the called function assumes that the resulting argument is of an appropriate size.

Where a result is of variable size and that size cannot be known in advance (for example, pmGetChildren, pmGetInDom, pmNameInDom, pmNameID, pmLookupText and pmFetch), the underlying implementation uses dynamic allocation through malloc in the called routine, with the caller responsible for subsequently calling free to release the storage when no longer required. In the case of the result from pmFetch, there is a routine (pmFreeResult) to release the storage, due to the complexity of the data structure and the need to make multiple calls to free in the correct sequence. As a general rule, if the called routine returns an error status, then no allocation is done, the pointer to the variable sized result is undefined, and free or pmFreeResult should not be called.

PMAPI Error Handling

Where error conditions may arise, the functions that compose the PMAPI conform to a single, simple error notification scheme, as follows:

  • The function returns an int. Values greater than or equal to zero indicate no error, and perhaps some positive status: for example, the number of items processed.

  • Values less than zero indicate an error, as determined by a global table of error conditions and messages.

A PMAPI library routine along the lines of strerror is provided to translate error conditions into error messages; see pmErrStr. The error condition is returned as the function value from a previous PMAPI call; there is no global error indicator (unlike errno). This is an attempt to anticipate and accommodate a programming environment that does not hinder the implementation of multi-threaded performance tools.

The available error codes may be displayed with the following command:

pmerr -l 

PMAPI Procedural Interface

The following sections describe all of the PMAPI routines that provide access to the PCP infrastructure on behalf of a client application:

PMAPI Name Space Services

pmGetChildren

int pmGetChildren(const char *name, char ***offspring)

Given a full pathname to a node in the current PMNS, as identified by name, return through offspring a list of the relative names of all the immediate descendents of name in the current PMNS. As a special case, if name is an empty string, (that is, "" but not NULL or (char *)0), the immediate descendents of the root node in the PMNS are returned.

Normally, pmGetChildren returns the number of descendent names discovered, or a value less than zero for an error. The value zero indicates that the name is valid, and associated with a leaf node in the PMNS.

The resulting list of pointers (offspring) and the values (relative metric names) that the pointers reference are allocated by pmGetChildren with a single call to malloc, and it is the responsibility of the caller to issue a free(offspring) system call to release the space when it is no longer required. When the result of pmGetChildren is less than one, offspring is undefined (no space is allocated, and so calling free is counterproductive).

pmGetChildrenStatus

int
pmGetChildrenStatus(const char *name, char ***offspring, int **status)

The pmGetChildrenStatus function is an extension of pmGetChildren that optionally returns status information about each of the descendent names.

Given a fully qualified pathname to a node in the current PMNS, as identified by name, pmGetChildrenStatus returns by means of offspring a list of the relative names of all of the immediate descendent nodes of name in the current PMNS. If name is the empty string (””), it returns the immediate descendents of the root node in the PMNS.

If status is not NULL, then pmGetChildrenStatus also returns the status of each child by means of status. This refers to either a leaf node (with value PMNS_LEAF_STATUS) or a non-leaf node (with value PMNS_NONLEAF_STATUS ).

Normally, pmGetChildrenStatus returns the number of descendent names discovered, or else a value less than zero to indicate an error. The value zero indicates that name is a valid metric name, being associated with a leaf node in the PMNS.

The resulting list of pointers (offspring) and the values (relative metric names) that the pointers reference are allocated by pmGetChildrenStatus with a single call to malloc, and it is the responsibility of the caller to free(offspring) to release the space when it is no longer required. The same holds true for the status array.

pmGetPMNSLocation

int pmGetPMNSLocation(void)

If an application needs to know where the origin of a PMNS, pmGetPMNSLocation returns whether it is an archive (PMNS_ARCHIVE), a local PMNS file (PMNS_LOCAL), or a remote pmcd (PMNS_REMOTE). This information may be useful in determining an appropriate error message depending on PMNS location.

pmLoadNameSpace

int pmLoadNameSpace(const char *filename)

Before requesting any services involving a local Performance Metrics Name Space (PMNS), the application must load the PMNS using pmLoadNameSpace.

The filename argument designates the PMNS of interest. For applications that do not require a tailored name space, the special value PM_NS_DEFAULT may be used for filename, to force a default local PMNS to be established. Externally a PMNS may be stored in either an ASCII or binary format. The utility pmnscomp is used to create the binary format from the ASCII format.


Note: The distributed PMNS services in PCP 2.0 avoid the need for a local PMNS in most cases, so applications typically would not use pmLoadNameSpace. If applications do not call pmLoadNameSpace, the default PMNS is the one at the source of the performance metrics.


pmLookupName

int pmLookupName(int numpmid, char *namelist[], pmID pmidlist[])

Given a list in namelist containing numpmid full pathnames for performance metrics from the current PMNS, pmLookupName returns the list of associated PMIDs through the pmidlist parameter. Invalid metrics names are translated to the “error” PMID value of PM_ID_NULL.

The result from pmLookupName is the number of names translated in the absence of errors, or an error indication. Note that argument definition and the error protocol guarantee a one-to-one relationship between the elements of namelist and pmidlist; both lists contain exactly numpmid elements.

pmNameID

int pmNameID(pmID pmid, char **name)

Given a performance metric ID in pmid, pmNameID determines the corresponding metric name, if any, in the current PMNS, and returns this through name.

In the absence of errors, pmNameID returns zero. The name argument is a null byte terminated string, allocated by pmNameID using malloc. It is the caller's responsibility to call free to release the space when it is no longer required.

pmTraversePMNS

int pmTraversePMNS(const char *name, void (*dometric)(char *))

The routine pmTraversePMNS may be used to perform a depth-first traversal of the PMNS. The traversal starts at the node identified by name—if name is an empty string, the traversal starts at the root of the PMNS. Usually name would be the pathname of a non-leaf node in the PMNS.

For each leaf node (actual performance metrics) found in the traversal, the user-supplied routine dometric is called with the full pathname of that metric in the PMNS as the single argument; this argument is a null byte-terminated string, and is constructed from a buffer that is managed internally to pmTraversePMNS. Consequently the value is valid only during the call to dometric—if the pathname needs to be retained, it should be copied using strdup before returning from dometric; see strdup(3C).

pmTrimNameSpace

int pmTrimNameSpace(void)

If the current PMAPI context corresponds to a version 1 PCP archive log of performance metrics (as collected by pmlogger in PCP 1.x releases), and pmLoadNameSpace has been called to load a local PMNS, then this PMNS is trimmed to exclude metrics for which no description can be found in the archive. The PMNS is further trimmed to remove empty subtrees that contain no performance metrics.

Since the PCP archives usually contain some subset of all metrics named in a local PMNS, pmTrimNameSpace effectively trims the application's PMNS to contain only the names of the metrics in the archive. Before any trimming, the PMNS is restored to the state as of the completion of the last pmLoadNameSpace operation, so the effects of consecutive calls to pmTrimNameSpace with archive contexts are not cumulative.

If the current PMAPI context corresponds to a host, rather than an archive, the PMNS reverts to all names loaded into the PMNS at completion of the last pmLoadNameSpace operation. For example, any trimming is undone.

The PMNS services in PCP 2.0 avoid the need for a local PMNS in most cases (and by default use only the PMNS of the metrics in a PCP archive) so applications would typically not call pmTrimNameSpace.

pmUnloadNameSpace

int pmUnloadNameSpace(void)

If a local PMNS was loaded with pmLoadNameSpace, calling pmUnloadNameSpace frees up the memory associated with the PMNS and force all subsequent namespace routines to use the distributed PMNS. If pmUnloadNameSpace is called before calling pmLoadNameSpace, it has no effect.

PMAPI Metric Description Services

pmLookupDesc

int pmLookupDesc(pmID pmid, pmDesc *desc)

Given a Performance Metrics Identifier as pmid, pmLookupDesc returns the associated pmDesc structure through the parameter desc from the current PMAPI context. For more information about pmDesc, see “Performance Metric Descriptions”.

pmLookupText

int pmLookupText(pmID pmid, int level, char **buffer)

Provided the source of metrics from the current PMAPI context is a host, retrieve descriptive text about the performance metric identified by pmid. The argument level should be PM_TEXT_ONELINE for a one-line summary, or PM_TEXT_HELP for a more verbose description, suited to a help dialog.

The space pointed to by buffer is allocated in pmLookupText with malloc, and it is the responsibility of the caller to free the space when it is no longer required; see malloc(3C) and free(3C).

The help text files used to implement pmLookupText are created using newhelp and accessed by the appropriate PMDA in response to requests forwarded to the PMDA by pmcd. Further details may be found in “PMDA Help Text”.

pmLookupInDomText

int pmLookupInDomText(pmInDom indom, int level, char **buffer)

Provided the source of metrics from the current PMAPI context is a host, retrieve descriptive text about the performance metrics instance domain identified by indom.

The argument level should be PM_TEXT_ONELINE for a one-line summary, or PM_TEXT_HELP for a more verbose description suited to a help dialog. The space pointed to by buffer is allocated in pmLookupInDomText with malloc, and it is the responsibility of the caller to free unneeded space; see malloc(3C) and free(3C).

The help text files used to implement pmLookupInDomText are created using newhelp and accessed by the appropriate PMDA response to requests forwarded to the PMDA by pmcd. Further details may be found in “PMDA Help Text”.

PMAPI Instance Domain Services

pmGetInDom

int pmGetInDom(pmInDom indom, int **instlist, char ***namelist)

In the current PMAPI context, locate the description of the instance domain indom, and return through instlist the internal instance identifiers for all instances, and through namelist the full external identifiers for all instances. The number of instances found is returned as the function value (or less than zero to indicate an error).

The resulting lists of instance identifiers (instlist and namelist), and the names that the elements of namelist point to, are allocated by pmGetInDom with two calls to malloc, and it is the responsibility of the caller to use free(instlist) and free(namelist) to release the space when it is no longer required. When the result of pmGetInDom is less than one, both instlist and namelist are undefined (no space is allocated, and so calling free is a bad idea); see malloc(3C) and free(3C).

pmLookupInDom

int pmLookupInDom(pmInDom indom, char *name)

For the instance domain indom, in the current PMAPI context, locate the instance with the external identification given by name, and return the internal instance identifier.

pmNameInDom

int pmNameInDom(pmInDom indom, int inst, char **name)

For the instance domain indom, in the current PMAPI context, locate the instance with the internal instance identifier given by inst, and return the full external identification through name. The space for the value of name is allocated in pmNameInDom with malloc, and it is the responsibility of the caller to free the space when it is no longer required; see malloc(3C) and free(3C).

PMAPI Context Services

The following table shows which of the three components of a PMAPI context (metrics source, instance profile, and collection time) are relevant for various PMAPI functions. Those PMAPI functions not shown in this table either manipulate the PMAPI context directly, or are executed independently of the current PMAPI context.

Table 3-1. Context Components of PMAPI Functions

Function Name

Metrics Source

Instance Profile

Collection Time

Notes

pmAddProfile

yes

yes

 

 

pmDelProfile

yes

yes

 

 

pmDupContext

yes

yes

yes

 

pmFetch

yes

yes

yes

 

pmFetchArchive

yes

 

yes

(1)

pmGetArchiveEnd

yes

 

 

(1)

pmGetArchiveLabel

yes

 

 

(1)

pmGetChildren

yes

 

 

(5)

pmGetChildrenStatus

yes

 

 

(5)

pmGetPMNSLocation

yes

 

 

 

pmGetInDom

yes

 

yes

(2)

pmGetInDomArchive

yes

 

 

(1)

pmLookupDesc

yes

 

 

(3)

pmLookupInDom

yes

 

yes

(2)

pmLookupInDomArchive

yes

 

 

(1,2)

pmLookupInDomText

yes

 

 

(4)

pmLookupName

yes

 

 

(5)

pmLookupText

yes

 

 

(4)

pmNameID

yes

 

 

(5)

pmNameInDom

yes

 

yes

(2)

pmNameInDomArchive

yes

 

 

(1,2)

pmSetMode

yes

 

yes

 

pmStore

yes

 

 

(6)

pmTraversePMNS

yes

 

 

(5)

pmTrimNameSpace

yes

 

 

 

Notes:

  1. Operation supported only for PMAPI contexts where the source of metrics is an archive.

  2. A specific instance domain is included in the arguments to these routines, and the result is independent of the instance profile for any PMAPI context.

  3. The metadata that describes a performance metric is sensitive to the source of the metrics, but independent of any instance profile and of the collection time.

  4. Operation supported only for PMAPI contexts where the source of metrics is a host. The text associated with a metric is assumed to be invariant with time and is definitely insensitive to the current members of the instance domain. In all cases this information is unavailable from an archive context (it is not included in the archive logs), and is directly available from a PMDA via pmcd in the other cases.

  5. PMNS service routines using a local PMNS do not depend on the PMAPI context, whereas PCP 2.0 distributed PMNS services are dependent on the source of metrics.

  6. This operation is supported only for contexts where the source of the metrics is a host. Further, the instance identifiers are included in the argument to the routine, and the effects upon the current values of the metrics are immediate (retrospective changes are not allowed). Consequently, from the current PMAPI context, neither the instance profile nor the collection time influence the result of this routine.

pmNewContext

int pmNewContext(int type, char *name)

The pmNewContext function may be used to establish a new PMAPI context. The source of metrics is identified by name, and may be a host name (type is PM_CONTEXT_HOST) or the basename of an archive log (type is PM_CONTEXT_ARCHIVE).

In the case where type is PM_CONTEXT_LOCAL, name is ignored, and the context uses a standalone connection to the PMDA methods used by pmcd. When this type of context is in effect, the range of accessible performance metrics is constrained to those from the operating system, and optionally the proc and sample PMDAs.

The initial instance profile is set up to select all instances in all instance domains, and the initial collection time is the “current” time at the time of each request for a host, or the time at the start of the log for an archive. In the case of archives, the initial collection time results in the earliest set of metrics being returned from the archive at the first pmFetch.

Once established, the association between a PMAPI context and a source of metrics is fixed for the life of the context; however, routines are provided to independently manipulate both the instance profile and the collection time components of a context.

The function returns a “handle” that may be used in subsequent calls to pmUseContext.

This new PMAPI context stays in effect for all subsequent context sensitive calls across the PMAPI until another call to pmNewContext is made, or the context is explicitly changed with a call to pmDupContext or pmUseContext.

pmDestroyContext

int pmDestroyContext(int handle)

The PMAPI context identified by handle is destroyed. Typically this implies terminating a connection to PMCD or closing an archive file, and orderly clean-up. The PMAPI context must have been previously created using pmNewContext or pmDupContext.

On success, pmDestroyContext returns zero. If handle was the current PMAPI context, then the current context becomes undefined. This means the application must explicitly re-establish a valid PMAPI context with pmUseContext, or create a new context with pmNewContext or pmDupContext, before the next PMAPI operation requiring a PMAPI context.

pmDupContext

int pmDupContext(void)

Replicate the current PMAPI context (source, instance profile, and collection time). This routine returns a “handle” for the new context, which may be used with subsequent calls to pmUseContext. The newly replicated PMAPI context becomes the current context.

pmUseContext

int pmUseContext(int handle)

Calling pmUseContext causes the current PMAPI context to be set to the context identified by handle. The value of handle must be one returned from an earlier call to pmNewContext or pmDupContext.

Below the PMAPI, all contexts used by an application are saved in their most recently modified state, so pmUseContext restores the context to the state it was in the last time the context was used, not the state of the context when it was established.

pmWhichContext

int pmWhichContext(void)

Returns the “handle” for the current PMAPI context (source, instance profile, and collection time).

pmAddProfile

int pmAddProfile(pmInDom indom, int numinst, int instlist[])

Add new instance specifications to the instance profile of the current PMAPI context. In the simplest variant, the list of instances identified by the instlist argument for the indom instance domain are added to the instance profile. The list of instance identifiers contains numinst values.

If indom equals PM_INDOM_NULL, or numinst is zero, then all instance domains are selected. If instlist is NULL, then all instances are selected. To enable all available instances in all domains, use this syntax:

pmAddProfile(PM_INDOM_NULL, 0, NULL).

pmDelProfile

int pmDelProfile(pmInDom indom, int numinst, int instlist[])

Delete instance specifications from the instance profile of the current PMAPI context. In the simplest variant, the list of instances identified by the instlist argument for the indom instance domain is removed from the instance profile. The list of instance identifiers contains numinst values.

If indom equals PM_INDOM_NULL, then all instance domains are selected for deletion. If instlist is NULL, then all instances in the selected domains are removed from the profile. To disable all available instances in all domains, use this syntax:

pmDelProfile(PM_INDOM_NULL, 0, NULL) 

pmSetMode

int pmSetMode(int mode, const struct timeval *when, int delta)

This routine defines the collection time and mode for accessing performance metrics and metadata in the current PMAPI context. This mode affects the semantics of subsequent calls to the following PMAPI routines: pmFetch, pmFetchArchive, pmLookupDesc, pmGetInDom, pmLookupInDom and pmNameInDom.

The pmSetMode routine requires the current PMAPI context to be of type PM_CONTEXT_ARCHIVE.

The when parameter defines a time origin, and all requests for metadata (metrics descriptions and instance identifiers from the instance domains) are processed to reflect the state of the metadata as of the time origin. For example, use the last state of this information at, or before, the time origin.

If the mode is PM_MODE_INTERP then, in the case of pmFetch, the underlying code uses an interpolation scheme to compute the values of the metrics from the values recorded for times in the proximity of the time origin.

If the mode is PM_MODE_FORW, then, in the case of pmFetch, the collection of recorded metric values is scanned forward, until values for at least one of the requested metrics is located after the time origin. Then all requested metrics stored in the PCP archive at that time are returned with a corresponding timestamp. This is the default mode when an archive context is first established with pmNewContext.

If the mode is PM_MODE_BACK, then the situation is the same as for PM_MODE_FORW, except a pmFetch is serviced by scanning the collection of recorded metrics backward for metrics before the time origin.

After each successful pmFetch, the time origin is reset to the timestamp returned through the pmResult. The pmSetMode parameter delta defines an additional number of milliseconds that should be used to adjust the time origin (forward or backward) after the new time origin from the pmResult has been determined. This is useful when moving through archives with a fixed sampling rate.

Using these mode options, an application can implement replay, playback, fast forward, or reverse for performance metric values held in a PCP archive log by alternating calls to pmSetMode and pmFetch.

For example, the following code fragment may be used to dump only those values stored in correct temporal sequence, for the specified performance metric my.metric.name:

int     sts;
pmID    pmid;
char    *name = “my.metric.name”;
    sts = pmNewContext(PM_CONTEXT_ARCHIVE, “myarchive”);
    sts = pmLookupName(1, &name, &pmid);
    for ( ; ; ) {
        sts = pmFetch(1, &pmid, &result);
        if (sts < 0)
            break;
        /* dump value(s) from result->vset[0]->vlist[] */
        pmFreeResult(result);
    }

Alternatively, the following code fragment may be used to replay interpolated metrics from an archive in reverse chronological order, at ten-second intervals (of recorded time):

int             sts;
pmID            pmid;
char            *name = “my.metric.name”;
struct timeval  endtime;
    sts = pmNewContext(PM_CONTEXT_ARCHIVE, “myarchive”);
    sts = pmLookupName(1, &name, &pmid);
    sts = pmGetArchiveEnd(&endtime);
    sts = pmSetMode(PM_MODE_INTERP, &endtime, -10000);
    while (pmFetch(1, &pmid, &result) != PM_ERR_EOL) {
        /*
         * process interpolated metric values as of result->timestamp
         */
        pmFreeResult(result);
    }

pmReconnectContext

int pmReconnectContext(int handle)

As a result of network, host, or PMCD (Performance Metrics Coordinating Daemon) failure, an application's connection to PMCD may be established and then lost.

The routine pmReconnectContext allows an application to request that the PMAPI context identified by handle be re-established, provided the associated PMCD is accessible.


Note: handle may or may not be the current context.

To avoid flooding the system with reconnect requests, pmReconnectContext attempts a reconnection only after a suitable delay from the previous attempt. This imposed restriction on the reconnect re-try time interval uses a default exponential back-off so that the initial delay is 5 seconds after the first unsuccessful attempt, then 10 seconds, then 20 seconds, then 40 seconds, and then 80 seconds thereafter. The intervals between reconnection attempts may be modified using the environment variable PMCD_RECONNECT_TIMEOUT and the time to wait before an attempted connection is deemed to have failed is controlled by the environment variable PMCD_CONNECT_TIMEOUT; see PCPIntro(1).

If the reconnection succeeds, pmReconnectContext returns handle. Note that even in the case of a successful reconnection, pmReconnectContext does not change the current PMAPI context.

PMAPI Metrics Services

pmFetch

int pmFetch(int numpmid, pmID pmidlist[], pmResult **result)

The most common PMAPI operation is likely to be calls to pmFetch, specifying a list of PMIDs (for example, as constructed by pmLookupName) through pmidlist and numpmid. The call to pmFetch is executed in the context of a source of metrics, instance profile, and collection time, previously established by calls to the routines described in “PMAPI Context Services”.

The principal result from pmFetch is returned as a tree structured result, described in the section “Performance Metrics Values”.

If one value (for example, associated with a particular instance) for a requested metric is unavailable at the requested time, then there is no associated pmValue structure in the result. If there are no available values for a metric, then numval is zero and the associated pmValue[] instance is empty; valfmt is undefined in these circumstances, but pmid is correctly set to the PMID of the metric with no values.

If the source of the performance metrics is able to provide a reason why no values are available for a particular metric, this reason is encoded as a standard error code in the corresponding numval; see pmerr(1) and pmErrStr(3). Since all error codes are negative, values for a requested metric are unavailable if numval is less than or equal to zero.

The argument definition and the result specifications have been constructed to ensure that for each PMID in the requested pmidlist there is exactly one pmValueSet in the result, and that the PMIDs appear in exactly the same sequence in both pmidlist and result. This makes the number and order of entries in result completely deterministic, and greatly simplifies the application programming logic after the call to pmFetch.

The result structure returned by pmFetch is dynamically allocated using one or more calls to malloc and specialized allocation strategies, and should be released when no longer required by calling pmFreeResult. Under no circumstances should free be called directly to release this space.

As common error conditions are encoded in the result data structure, only serious events (such as loss of connection to PMCD, malloc failure, and so on.) would cause an error value to be returned by pmFetch. Otherwise the value returned by the pmFetch function is zero.

The following code fragment dumps the values (assumed to be stored in the lval element of the pmValue structure) of selected performance metrics once every 10 seconds:

int       numpmid, i, j, sts;
pmID      pmidlist[10];
pmResult  *result;
time_t    now;
/* set up PMAPI context, numpmid and pmidlist[] ... */
while ((sts = pmFetch(&result)) >= 0) {
    now = (time_t)result->timestamp.tv_sec;
    printf("\n@ %s", ctime(&now));
    for (i = 0; i < result->numpmid; i++) {
        printf("PMID: %s", pmIDStr(result->vset[i]->pmid));
        for (j = 0; j < result->vset[i]->numval; j++) {
            printf(" 0x%x", result->vset[i]->vlist[j].value.lval);
            putchar('\n');
        }
    }
    pmFreeResult(result);
    sleep(10);
}


Note: If a response is not received back from PMCD within 10 seconds, the pmFetch will time out and return PM_ERR_TIMEOUT. This is most likely to occur when the PMAPI client and PMCD are communicating over a slow network connection, but may also occur when one of the hosts is extremely busy. The time out period may be modified using the environment variable PMCD_REQUEST_TIMEOUT; see PCPIntro(1).


pmFreeResult

void pmFreeResult(pmResult *result)

Release the storage previously allocated for a result by pmFetch.

pmStore

int pmStore(const pmResult *request)

In some special cases it may be helpful to modify the current values of performance metrics in one or more underlying domains, for example to reset a counter to zero, or to modify a “metric,” which is a control variable within a Performance Metric Domain.

The routine pmStore is a lightweight inverse of pmFetch. The caller must build the pmResult data structure (which could have been returned from an earlier pmFetch call) and then call pmStore. It is an error to pass a request to pmStore in which the numval field within any of the pmValueSet structure has a value less than one.

The current PMAPI context must be one with a host as the source of metrics, and the current value of the nominated metrics is changed. For example, pmStore cannot be used to make retrospective changes to information in a PCP archive log!

PMAPI Archive-Specific Services

pmGetArchiveLabel

int pmGetArchiveLabel(int handle, pmLogLabel *lp)

Provided the current PMAPI context is associated with a PCP archive log, the pmGetArchiveLabel function may be used to fetch the label record from the archive.
The structure returned through lp is as follows:

/*
 * Label Record at the start of every log file - as exported above the PMAPI ...
 */
#define PM_LOG_MAXHOSTLEN               64
#define PM_LOG_MAGIC    0x50052600
#define PM_LOG_VERS01   0x1
#define PM_LOG_VERS02   0x2
#define PM_LOG_VOL_TI   -2      /* temporal index */
#define PM_LOG_VOL_META -1      /* meta data */
typedef struct {
    int            ll_magic;          /* PM_LOG_MAGIC | log format version no. */
    pid_t          ll_pid;            /* PID of logger */
    struct timeval ll_start;          /* start of this log */
    char           ll_hostname[PM_LOG_MAXHOSTLEN]; /* name of collection host */
    char           ll_tz[40];         /* $TZ at collection host */
} pmLogLabel;

pmGetArchiveEnd

int pmGetArchiveEnd(struct timeval *tvp)

Provided the current PMAPI context is associated with a PCP archive log, pmGetArchiveEnd finds the logical end of file (after the last complete record in the archive), and returns the last recorded timestamp with tvp. This timestamp may be passed to pmSetMode to reliably position the context at the last valid log record, for example, in preparation for subsequent reading in reverse chronological order.

For archive logs that are not concurrently being written, the physical end of file and the logical end of file are co-incident. However, if an archive log is being written by pmlogger at the same time that an application is trying to read the archive, the logical end of file may be before the physical end of file due to write buffering that is not aligned with the logical record boundaries.

pmGetInDomArchive

int pmGetInDomArchive(pmInDom indom, int **instlist, char ***namelist)

Provided the current PMAPI context is associated with a PCP archive log, pmGetInDomArchive scans the metadata to generate the union of all instances for the instance domain indom that can be found in the archive log, and returns through instlist the internal instance identifiers, and through namelist the full external identifiers.

This routine is a specialized version of the more general PMAPI routine pmGetInDom.

The function returns the number of instances found (a value less than zero indicates an error).

The resulting lists of instance identifiers (instlist and namelist), and the names that the elements of namelist point to, are allocated by pmGetInDomArchive with two calls to malloc, and it is the responsibility of the caller to use free(instlist) and free(namelist) to release the space when it is no longer required; see malloc(3C) and free(3C).

When the result of pmGetInDomArchive is less than one, both instlist and namelist are undefined (no space is allocated, so calling free is a singularly bad idea).

pmLookupInDomArchive

int pmLookupInDomArchive(pmInDom indom, const char *name)

Provided the current PMAPI context is associated with a PCP archive log, pmLookupInDomArchive scans the metadata for the instance domain indom, locates the first instance with the external identification given by name, and returns the internal instance identifier.

This routine is a specialized version of the more general PMAPI routine pmLookupInDom.

The pmLookupInDomArchive routine returns a positive instance identifier on success.

pmNameInDomArchive

int pmNameInDomArchive(pmInDom indom, int inst, char **name)

Provided the current PMAPI context is associated with a PCP archive log, pmNameInDomArchive scans the metadata for the instance domain indom, locates the first instance with the internal instance identifier given by inst, and returns the full external instance identification through name.

This routine is a specialized version of the more general PMAPI routine pmNameInDom.

The space for the value of name is allocated in pmNameInDomArchive with malloc, and it is the responsibility of the caller to free the space when it is no longer required; see malloc(3C) and free(3C).

pmFetchArchive

int pmFetchArchive(pmResult **result)

This is a variant of pmFetch that may be used only when the current PMAPI context is associated with a PCP archive log. The result is instantiated with all of the metrics (and instances) from the next archive record; consequently there is no notion of a list of desired metrics, and the instance profile is ignored.

It is expected that pmFetchArchive would be used to create utilities that scan archive logs (for example, pmdumplog), and the more common access to the archives would be through the pmFetch interface.

Time Control Services

The PMAPI provides a common framework for client applications to control time and to synchronize time with other applications. The user interface component of this service is fully described in the companion Performance Co-Pilot User's and Administrator's Guide. See also pmtime(1).

This service is most useful when processing PCP archive logs, to control parameters such as the current archive position, update interval, replay rate, and timezone, but it can also be used in live mode to control a subset of these parameters.

Applications such as pmchart, pmview, oview, and pmval use the time control services to connect to an instance of the time control server process, pmtime, which provides a uniform graphical user interface to the time control services.

A full description of the PMAPI time control functions along with code examples can be found in the reference page pmtime(3).

PMAPI Ancillary Support Services

The routines described in this section provide services that are complementary to, but not necessarily a part of, the distributed manipulation of performance metrics delivered by the PCP components.

pmErrStr

char *pmErrStr(int code)

This routine translates an error code into a text string, suitable for generating a diagnostic message. By convention within PCP, all error codes are negative. The small values are assumed to be negated versions of the UNIX error codes as defined in <errno.h>, and the strings returned are according to strerror. The large, negative error codes are PMAPI error conditions, and pmErrStr returns an appropriate PMAPI error string, as determined by code.

The string value is held in a single static buffer, so the returned value is valid only until the next call to pmErrStr.

pmExtractValue

int pmExtractValue(int valfmt, const pmValue *ival, int itype,
                   pmAtomValue *oval, int otype)

The pmValue structure is embedded within the pmResult structure, which is used to return one or more performance metrics; see the description of pmFetch.

All performance metric values may be encoded in a pmAtomValue union, defined as follows:

/* Generic Union for Value-Type conversions */
typedef union {
    _int32_t   l;     /* 32-bit signed */
    _uint32_t  ul;    /* 32-bit unsigned */
    _int64_t   ll;    /* 64-bit signed */
    _uint64_t  ull;   /* 64-bit unsigned */
    float      f;     /* 32-bit floating point */
    double     d;     /* 64-bit floating point */
    char       *cp;   /* char ptr */
    void       *vp;   /* void ptr */
} pmAtomValue;

The routine pmExtractValue provides a convenient mechanism for extracting values from the pmValue part of a pmResult structure, optionally converting the data type, and making the result available to the application programmer.

The itype argument defines the data type of the input value held in ival according to the storage format defined by valfmt (see pmFetch). The otype argument defines the data type of the result to be placed in oval. The value for itype is typically extracted from a pmDesc structure, following a call to pmLookupDesc for a particular performance metric.

Table 3-2 defines the various possibilities for the type conversion. The input type (itype) is shown vertically, and the output type (otype) horizontally. The following rules apply:

  • Y means the conversion is always acceptable.

  • N means conversion can never be performed (function returns PM_ERR_CONV).

  • P means the conversion may lose accuracy (but no error status is returned).

  • T means the result may be subject to high-order truncation (if this occurs the function returns PM_ERR_TRUNC).

  • S means the conversion may be impossible due to the sign of the input value (if this occurs the function returns PM_ERR_SIGN).

If an error occurs, oval is set to zero (or NULL). Note that some of the conversions involving the types PM_TYPE_STRING and PM_TYPE_AGGREGATE are indeed possible, but are marked N; the rationale is that pmExtractValue should not attempt to duplicate functionality already available in the C library through sscanf and sprintf.

Table 3-2. PMAPI Type Conversion

TYPE

32

U32

64

U64

FLOAT

DBLE

STRIN G

AGGR

32

Y

S

Y

S

P

P

N

N

U32

T

Y

Y

Y

P

P

N

N

64

T

T,S

Y

S

P

P

N

N

u64

T

T

T

Y

P

P

N

N

FLOAT

P, T

P, T, S

P, T

P, T, S

Y

Y

N

N

DBLE

P, T

P, T, S

P, T

P, T, S

P

Y

N

N

STRING

N

N

N

N

N

N

Y

N

AGGR

N

N

N

N

N

N

N

Y

In the cases where multiple conversion errors could occur, the first encountered error is returned, and the order of checking is not defined.

If the output conversion is to one of the pointer types, such as otype PM_TYPE_STRING or PM_TYPE_AGGREGATE, then the value buffer is allocated by pmExtractValue using malloc, and it is the caller's responsibility to free the space when it is no longer required; see malloc(3C) and free(3C).

Although this function appears rather complex, it has been constructed to assist the development of performance tools that convert values, whose type is known only through the type field in a pmDesc structure, into a canonical type for local processing.

pmConvScale

int
pmConvScale(int type, const pmAtomValue *ival, const pmUnits *iunit,
            pmAtomValue *oval, pmUnits *ounit)

Given a performance metric value pointed to by ival, multiply it by a scale factor and return the value in oval. The scaling takes place from the units defined by iunit into the units defined by ounit. Both input and output units must have the same dimensionality.

The performance metric type for both input and output values is determined by type, the value for which is typically extracted from a pmDesc structure, following a call to pmLookupDesc for a particular performance metric.

pmConvScale is most useful when values returned through pmFetch (and possibly extracted using pmExtractValue) need to be normalized into some canonical scale and units for the purposes of computation.

pmUnitsStr

const char *pmUnitsStr(const pmUnits *pu)

As an aid to labeling graphs and tables, or for error messages, pmUnitsStr takes a dimension and scale specification as per pu, and returns the corresponding text string.

pu is typically from a pmDesc structure, for example, as returned by pmLookupDesc.

For example, if *pu were {1, -2, 0, PM_SPACE_MBYTE, PM_TIME_MSEC, 0}, then the result string would be “Mbyte/sec^2.”

The string value is held in a single static buffer, so concurrent calls to pmUnitsStr may not produce the desired results.

pmIDStr

const char *pmIDStr(pmID pmid)

For use in error and diagnostic messages, return a “human readable” version of the specified PMID, with each of the internal domain, cluster, and item subfields appearing as decimal numbers, separated by periods.

The string value is held in a single static buffer, so concurrent calls to pmIDStr may not produce the desired results.

pmInDomStr

const char *pmInDomStr(pmInDom indom)

For use in error and diagnostic messages, return a “human readable” version of the specified instance domain identifier, with each of the internal domain and serial subfields appearing as decimal numbers, separated by periods.

The string value is held in a single static buffer, so concurrent calls to pmInDomStr may not produce the desired results.

pmTypeStr

const char *pmTypeStr(int type)

Given a performance metric type, produce a terse ASCII equivalent, appropriate for use in error and diagnostic messages.

Examples are “32” (for PM_TYPE_32), “U64” (for PM_TYPE_U64), “AGGREGATE” (for PM_TYPE_AGGREGATE), and so on.

The string value is held in a single static buffer, so concurrent calls to pmTypeStr may not produce the desired results.

pmAtomStr

const char *pmAtomStr(const pmAtomValue *avp, int type)

Given the pmAtomValue identified by avp, and a performance metric type, generate the corresponding metric value as a string, suitable for diagnostic or report output.

The string value is held in a single static buffer, so concurrent calls to pmAtomStr may not produce the desired results.

pmPrintValue

void pmPrintValue(FILE *f, int valfmt, int type, const pmValue *val,
                  int minwidth)

The value of a single performance metric (as identified by val) is printed on the standard I/O stream identified by f. The value of the performance metric is interpreted according to the format of val as defined by valfmt (from a pmValueSet within a pmResult) and the generic description of the metric's type from a pmDesc structure, passed in through type.

If the converted value is less than minwidth characters wide, it will have leading spaces to pad the output to a width of minwidth characters.

The following example illustrates using pmPrintValue to print the values from a pmResult structure returned via pmFetch:

int         numpmid, i, j, sts;
pmID        pmidlist[10];
pmDesc      desc[10];
pmResult    *result;
/* set up PMAPI context, numpmid and pmidlist[] ... */
/* get metric descriptors */
for (i = 0; i < numpmid; i++) {
    if ((sts = pmLookupDesc(pmidlist[i], &desc[i])) < 0) {
        printf("pmLookupDesc(pmid=%s): %s\n",
                      pmIDStr(pmidlist[i]), pmErrStr(sts));
        exit(1);
    }
}
if ((sts = pmFetch(numpmid, pmidlist, &result)) >= 0) {
    /* once per metric */
    for (i = 0; i < result->numpmid; i++) {
        printf("PMID: %s", pmIDStr(result->vset[i]->pmid));
        /* once per instance for this metric */
        for (j = 0; j < result->vset[i]->numval; j++) {
            printf(" [%d]", result->vset[i]->vlist[j].inst);
            pmPrintValue(stdout, result->vset[i]->valfmt,
                                desc[i].type,
                                &result->vset[i]->vlist[j],
                                8);
        }
        putchar('\n');
    }
    pmFreeResult(result);
}
else
    printf("pmFetch: %s\n", pmErrStr(sts));

pmSortInstances

void pmSortInstances(pmResult *result)

The routine pmSortInstances may be used to guarantee that for each performance metric in the result from pmFetch, the instances are in ascending internal instance identifier sequence. This is useful when trying to compute rates from two consecutive pmFetch results, where the underlying instance domain or metric availability is not static.

PMAPI Programming Issues and Examples

The following issues and examples are provided to enable you to create better custom performance monitoring tools.

The source code for a sample client (pmclient) using the PMAPI is shipped as part of the pcp.sw.demo subsystem of the Performance Co-Pilot product. See the pmclient(1) reference page, and the source code, located in /var/pcp/demos/pmclient.

Symbolic Association Between a Metric's Name and Value

A common problem in building specific performance tools is how to maintain the association between a performance metric's name, its access (instantiation) method, and the application program variable that contains the metric's value. Generally this results in code that is easily broken by bug fixes or changes in the underlying data structures. The PMAPI provides a uniform method for instantiating and accessing the values independent of the underlying implementation, although it does not solve the name-variable association problem. However, it does provide a framework within which a manageable solution may be developed.

Fundamentally, the goal is to be able to name a metric and reference the metric's value in a manner that is independent of the order of operations on other metrics; for example, to associate the macro BINGO with the name “irix.sys.statistic.bingo”, and then be able to use BINGO to get at the value of the corresponding metric.

The one-to-one association between the ordinal position of the metric names is input to pmLookupName and the PMIDs returned by this routine, and the one-to-one association between the PMIDs input to pmFetch and the values returned by this routine provide the basis for an automated solution.

The tool pmgenmap takes the specification of a list of metric names and symbolic tags, in the order they should be passed to pmLookupName and pmFetch. For example:

# one line comment
mystuff {
    irix.sys.statistic.bingo  BINGO
    oracle.latchstats.lru.miss  MISSED
}

The above pmgenmap(1) input produces the following C code, suitable for including with the #include statement:

/*
 * Performance Metrics Name Space Map
 * Built by pmgenmap from the file
 * /usr/people/kenmcd/swa/ptg/src/kstat.pcp/x
 * on Thu Feb 24 20:37:53 EST 1994
 *
 * Do not edit this file!
 */
/* one line comment */
char *mystuff[] = {
#define BINGO 0
    "irix.sys.statistic.bingo",
#define MISSED 1
    "oracle.latchstats.lru.miss",
};

Initializing New Metrics

Using the code generated by pmgenmap, we are now able to easily initialize the application's metric specifications as follows:

#define MAX_MID 3
int       trip = 0;
int       numpmid = sizeof(mystuff)/sizeof(mystuff[0]);
double    duration;
pmResult  *resp;
pmResult  *prev;
pmID      pmidlist[MAX_MID];
pmLookupName(numpmid, mystuff, pmidlist);

At this stage, pmidlist contains the PMID for the two metrics of interest.

Iterative Processing of Values

Assuming the tool is required to report values every five seconds, use code similar to the following:

while (1) {
    pmFetch(numpmid, pmidlist, &resp);
    if (trip) {
        /* see pmclient.c for tv_sub() declaration */
        duration = tv_sub(&resp->timestamp, &prev->timestamp);
        /*
         * irix.sys.boring.bozo is an instantaneous value,
         * so report the most recent value
         * oracle.latchstats.lru.miss is a free running counter,
         * so report the rate over the last two samples
         */
        printf("%6d %5.2f\n", resp->vset[BOZO]->vlist[0].value.lval,
            (resp->vset[MISSED]->vlist[0].value.lval - 
             prev->vset[MISSED]->vlist[0].value.lval) / duration);
    }
    if (trip >= 1)
        pmFreeResult(prev);
    else
        trip++;
    prev = resp;
    sleep(5);
}

Accommodating Program Evolution

The flexibility provided by the PMAPI and the pmgenmap utility is demonstrated by this example. Consider the requirement for reporting a third metric “irix.sys.boring.new” (an instantaneous value) in the middle of the two already reported. Add this line to the middle of the specification file:

irix.sys.boring.new NEW 

Then regenerate the #include file, and amend the printf statement as follows:

printf("%6d %6d %5.2f\n",
    resp->vlist[BOZO]->vlist[0].value.lval,
    resp->vlist[NEW]->vlist[0].value.lval,
    (resp->vlist[MISSED]->vlist[0].value.lval -
        prev->vlist[MISSED]->vlist[0].value.lval) / duration);

Handling PMAPI Errors

The following simple but complete PMAPI application demonstrates the recommended style for handling PMAPI error conditions:

#include <stdio.h>
#include <pcp/pmapi.h>
int
main(int argc, char* argv[])
{
    int                 sts = 0;
    char                *host = "localhost";
    char                *metric = "irix.mem.freemem";
    pmID                pmid;
    pmDesc              desc;
    pmResult            *result;
    sts = pmNewContext(PM_CONTEXT_HOST, host);
    if (sts < 0) {
        fprintf(stderr, "Error connecting to pmcd on %s: %s\n",
                host, pmErrStr(sts));
        exit(1);
    }
    sts = pmLookupName(1, &metric, &pmid);
    if (sts < 0) {
        fprintf(stderr, "Error looking up %s: %s\n", metric,
                pmErrStr(sts));
        exit(1);
    }
    sts = pmLookupDesc(pmid, &desc);
    if (sts < 0) {
        fprintf(stderr, "Error getting descriptor for %s:%s: %s\n",
                host, metric, pmErrStr(sts));
        exit(1);
    }
    sts = pmFetch(1, &pmid, &result);
    if (sts < 0) {
        fprintf(stderr, "Error fetching %s:%s: %s\n", host, metric,
                pmErrStr(sts));
        exit(1);
    }
    sts = result->vset[0]->numval;
    if (sts < 0) {
        fprintf(stderr, "Error fetching %s:%s: %s\n", host, metric,
                pmErrStr(sts));
        exit(1);
    }
    fprintf(stdout, "%s:%s = ", host, metric);
    if (sts == 0)
        puts("(no value)");
    else {
        pmValueSet      *vsp = result->vset[0];
        pmPrintValue(stdout, vsp->valfmt, desc.type,
                             &vsp->vlist[0], 5);
        printf(" %s\n", pmUnitsStr(&desc.units));
    }
    return 0;
}

Compiling and Linking PMAPI Applications

Typical PMAPI applications require the following line to include the function prototype and data structure definitions used by the PMAPI. Some applications may also require these header files: <pcp/impl.h>, <pcp/util.h> and <pcp/pmda.h>.

#include <pcp/pmapi.h>

The run-time environment of the PMAPI is mostly found in libpcp.so, so to link a generic PMAPI application requires something akin to the following command:

cc mycode.c -lpcp