PMWEBAPI(3)

๐Ÿ’– PMWEBAPI(3) โ€” the Library Functions Manual glow-up โœจ

okay bestie, sit down, hydrate, and get comfy because we are about to read the manual page for PMWEBAPI โ€” yes, from the Library Functions Manual section 3, and yes it's a whole vibe ๐Ÿ’…


๐ŸŒธ NAME ๐ŸŒธ

PMWEBAPI โ€” introduction to the Performance Metrics Web Application Programming Interface ๐Ÿฆ‹

โœจ HTTP SYNOPSIS โœจ

like, here's the shape of it, no cap:

GET /metrics
GET /series/...
GET /search/...
GET /pmapi/...
POST /logger/...

๐Ÿ’œ C SYNOPSIS ๐Ÿ’œ

and if you're coming at it from C land:

#include <pcp/pmwebapi.h>

 ... assorted routines ...

cc ... -lpcp_web -lpcp

๐ŸŽ€ DESCRIPTION ๐ŸŽ€

So. The PMWEBAPI is literally a collection of interfaces that provide Performance Co-Pilot services for web applications ๐ŸŒˆ. It's giving "best of both worlds" because it's both:

  • APIs for web applications that wanna query and analyse live performance data and historical performance data ๐Ÿ“Š
  • APIs that get used by web servers themselves ๐Ÿ‘‘

The usual HTTP URL-encoded optional parameter rules apply (iykyk), and PMWEBAPI REST requests always follow this convention, periodt:

/api/endpoint?parameter1=value1&parameter2=value2

Every single example in all the sections that follow uses the curl(1) command line utility, pointed at a local pmproxy(1) server listening on port 44322 (that's the default port, bestie ๐Ÿ’–).

We also use the pmjson(1) utility to neatly format any JSON output โ€” because the default form is the compact (minimal whitespace) one and honestly? not the serve. โœจ

And heads up: the examples in the scalable time series section use historical data recorded by the pmlogger(1) service, working together with a local key-value server ๐Ÿซถ

๐Ÿฆ‹ OPEN METRICS AND OPEN TELEMETRY ๐Ÿฆ‹

Exporting of live performance metrics is available in either:

By default, all requests are performed on the web server host โ€” unless you pass a hostspec parameter, in which case it goes wherever you point it ๐ŸŒธ


๐Ÿ’… GET /metrics ๐Ÿ’…

Here are the params you can throw at it, one bullet per parameter because we don't do tables in this house:

  • names โ€” string โ€” comma-separated list of metric names
  • filter โ€” string โ€” comma-separated list of excluded metric names
  • match โ€” string โ€” pattern matching style (exact, glob or regex)
  • times โ€” boolean โ€” append sample times (milliseconds since epoch)
  • hostspec โ€” string โ€” host specification as described in PCPIntro(1)

So what does it do? It fetches current values and metadata for all metrics, or only the metrics you name in that comma-separated list of names ๐ŸŽฏ

For all numeric metrics with the given NAME prefixes, it creates an open text export format giving their current value and related metadata.

The choice of output format is selected using the HTTP Accept header ๐Ÿ‘€

  • With no header, the default response format is Open Metrics text.
  • Using the header with value application/json switches the response to the Open Telemetry JSON format.
In Open Telemetry response mode, sample timestamps are always included and are reported using nanosecond precision โฑ๏ธ
$ curl -H Accept:application/json -s http://localhost:44322/metrics?names=disk.dev,filesys | pmjson
{
    "resourceMetrics": [
        {
            "resource": {
                "attributes": [ ... ]
            },
            "scopeMetrics": [
                {
                    "scope": { ... },
                    "metrics": [
                        {
                            "name": "disk.dev.total.bytes",
                            "description": "per-disk count of total bytes read and written",
                            "unit": "KiBy",
                            "sum": {
                                "aggregationTemporality": "CUMULATIVE",
                                "isMonotonic": true,
                                "dataPoints": [
                                    {
                                        "attributes": [ ... ]
                                        "timeUnixNano": "1753934255798173",
                                        "asInt": "68927569"
                                    }
                                ]
                            }
                        },
                        ...

In Open Metrics response mode, the native PCP metric metadata โ€” that's metric name, type, indom, semantics and units โ€” is output first for each metric with the # PCP prefix ๐ŸŒŸ

The metadata reported is of the form described on pmTypeStr(3), pmInDomStr(3), pmSemStr(3) and pmUnitsStr(3) respectively. If the pmUnitsStr(3) units string is empty, then none is output. And psst โ€” the units metadata string may contain spaces and extends to the end of the line ๐Ÿ‘€

PCP metric names are mapped so that the . separators are exchanged with _ (that's ':' in back-compatibility mode, where "# PCP" is the identifying line suffix). Both metric labels and instances are represented as Open Metrics labels, with external instance names being quoted and the flattened PCP label hierarchy being presented with each value ๐Ÿท๏ธ

When metric names pattern matching is performed using the filter option, the default mode is glob-based matching. The match option can be used to instead request regex-based or exact-match metric filtering ๐Ÿ”

$ curl -s http://localhost:44322/metrics?names=proc.nprocs,kernel.pernode.cpu.intr,filesys.blocksize

# PCP5 proc.nprocs 3.8.99 u32 PM_INDOM_NULL instant none
# HELP proc_nprocs instantaneous number of processes
# TYPE proc_nprocs gauge
proc_nprocs {hostname="app1"} 7

# PCP5 kernel.pernode.cpu.intr 60.0.66 u64 60.19 counter millisec
# HELP kernel_pernode_cpu_intr total interrupt CPU [...]
# TYPE kernel_pernode_cpu_intr counter
kernel_pernode_cpu_intr{hostname="app1",instname="node0"} 25603

# PCP5 filesys.blocksize 60.5.9 u32 60.5 instant byte
# HELP filesys_blocksize Size of each block on mounted file[...]
# TYPE filesys_blocksize gauge
filesys_blocksize{hostname="app1",instname="/dev/sda1"} 4096
filesys_blocksize{hostname="app1",instname="/dev/sda2"} 4096

๐Ÿ”ฅ SCALABLE TIME SERIES ๐Ÿ”ฅ

okay this is the part that ate. The fast, scalable time series query capabilities provided by the pmseries(1) command are also available through a REST API ๐ŸŒˆ

These queries give you access to performance data (metric metadata and values) from multiple hosts simultaneously, in a fashion suited to efficient retrieval by any number of web applications. iconic. ๐Ÿ‘‘

Every request in this group can be accompanied by an optional client parameter. Whatever value you pass in the request gets sent back in the response โ€” and in that case all responses will be in JSON object form, with top level "client" and "result" fields ๐Ÿ’–

REST API clients can optionally submit an URL-encoded query string in the body of the HTTP request, unless otherwise noted. In that case the POST method must be used instead of the GET method ๐Ÿ“ฎ


๐ŸŒŸ GET /series/ping ๐ŸŒŸ

A simple liveness test, bestie โ€” for clients to check whether the server is up and supports the /series API ๐Ÿซถ


๐Ÿ’œ GET /series/query - pmSeriesQuery(3) ๐Ÿ’œ

Params, coming right up:

  • expr โ€” string โ€” query string in pmseries(1) format
  • client โ€” string โ€” request identifier sent back with response

This performs a time series query for either matching identifiers, or matching identifiers with series of time-stamped values ๐ŸŽฏ

The query is in the format described in pmseries(1) and is passed to the server via either the expr parameter (HTTP GET) or via the message body (HTTP POST).

When you're querying for time series matches only, no time window options are specified, and matching series identifiers are returned in a JSON array โœจ

$ curl -s 'http://localhost:44322/series/query?expr=disk.dev.read*' | pmjson
[
  "9d8c7fb51ce160eb82e3669aac74ba675dfa8900",
  "ddff1bfe286a3b18cebcbadc1678a68a964fbe9d",
  "605fc77742cd0317597291329561ac4e50c0dd12"
]

When you're querying for time series values as well, a time window must be specified as part of the query string. The simplest form is to just request the most recent sample ๐Ÿ‘€

$ curl -s 'http://localhost:44322/series/query?expr=disk.dev.read*[samples:1]' | pmjson
[
  {
    "series": "9d8c7fb51ce160eb82e3669aac74ba675dfa8900",
    "instance": "c3795d8b757506a2901c6b08b489ba56cae7f0d4",
    "timestamp": 1547483646.2147431,
    "value": "12499"
  }, {
    "series": "ddff1bfe286a3b18cebcbadc1678a68a964fbe9d",
    "instance": "6b08b489ba56cae7f0d4c3795d8b757506a2901c",
    "timestamp": 1547485701.7431218,
    "value": "1118623"
  }, {
    "series": "605fc77742cd0317597291329561ac4e50c0dd12",
    "instance": "c3795d8b757506a2901c6b08b489ba56cae7f0d4",
    "timestamp": 1547483646.2147431,
    "value": "71661"
  }
]

GET /series/values โ€” pmSeriesValues(3) ๐Ÿ’–

Okay bestie, this one does values retrievals for one or more time series identifiers. The JSON response contains the same info as the pmseries -q/--query option using any of the time window parameters described on pmseries(1). If no time window parameters are specified, the single most recent value observed is retrieved. Slay.

Params, fr fr:

  • series (string) โ€” comma-separated list of series identifiers
  • client (string) โ€” request identifier sent back with response
  • samples (number) โ€” count of samples to return
  • interval (string) โ€” time between successive samples
  • start (string) โ€” sample window start time
  • finish (string) โ€” sample window end time
  • offset (string) โ€” sample window offset
  • align (string) โ€” sample time alignment
  • zone (string) โ€” time window timezone

Example:

         $ curl -s http://localhost:44322/series/values?series=605fc77742cd0317597291329561ac4e50c0dd12 | pmjson
         [
           {
             "series": "605fc77742cd0317597291329561ac4e50c0dd12",
             "timestamp": 1317633022959.959241041,
             "value": "71660"
           }
         ]

GET /series/descs โ€” pmSeriesDescs(3) ๐Ÿฆ‹

This one performs a descriptor lookup for one or more time series identifiers. The JSON response contains the same information as the pmseries -d/--desc option. Vibe check passed.

Params, bestie:

  • series (string) โ€” comma-separated list of series identifiers
  • client (string) โ€” request identifier sent back with response

Example:

         $ curl -s http://localhost:44322/series/descs?series=605fc77742cd0317597291329561ac4e50c0dd12 | pmjson
         [
           {
             "series": "605fc77742cd0317597291329561ac4e50c0dd12",
             "source": "f5ca7481da8c038325d15612bb1c6473ce1ef16f",
             "pmid": "60.0.4",
             "indom": "60.1",
             "semantics": "counter",
             "type": "u32",
             "units": "count",
           }
         ]

GET /series/labels โ€” pmSeriesLabels(3), pmSeriesLabelValues(3) ๐ŸŽ€

This command operates in one of three modes. It can perform a label set lookup for one or more time series identifiers, when given the series parameter). It can produce a list of all known label names, in the absence of name, names or series parameters. The JSON responses for these modes contains the same information as the pmseries -l/--labels option.

Alternatively, it can produce a list of all known label values for a given label name or names. The JSON response for this mode contains the same information as the pmseries -v/--values option.

Params, periodt:

  • series (string) โ€” comma-separated list of series identifiers
  • match (string) โ€” Glob pattern string to match on all labels
  • name (string) โ€” Find all known label values for given name
  • names (string) โ€” comma-separated list of label names
  • client (string) โ€” request identifier sent back with response

Example โ€” label set lookup:

         $ curl -s http://localhost:44322/series/labels?series=605fc77742cd0317597291329561ac4e50c0dd12 | pmjson
         [
           {
             "series": "605fc77742cd0317597291329561ac4e50c0dd12",
             "labels": {
               "agent": "linux",
               "domainname": "acme.com",
               "groupid": 1000,
               "hostname": "www.acme.com",
               "latitude": -25.28496,
               "longitude": 152.87886,
               "machineid": "295b16e3b6074cc8bdbda8bf96f6930a",
               "platform": "dev",
               "userid": 1000
             }
           }
         ]

Alternatively, with no name, names or series parameters, return the list of all known label names.

Example โ€” all label names:

         $ curl -s http://localhost:44322/series/labels | pmjson
         [
             "agent",
             "appversion",
             "domainname",
             "groupid",
             "hostname",
             "jobid",
             "latitude",
             "longitude",
             "machineid",
             "platform",
             "userid"
         ]

Use the name or names parameters to find all possible label values for the given name(s).

Example โ€” values for given names:

         $ curl -s http://localhost:44322/series/labels?names=hostname,domainname | pmjson
         {
             "hostname": [ "app", "nas" ],
             "domainname": [ "acme.com" ]
         }

GET /series/metrics โ€” pmSeriesMetrics(3) ๐Ÿ”ฅ

This one performs a metric name lookup for one or more time series identifiers. The JSON response contains the same information as the pmseries -m/--metrics option. Iconic behavior.

Params, iykyk:

  • series (string) โ€” comma-separated list of series identifiers
  • match (string) โ€” Glob pattern string to match on all names
  • client (string) โ€” request identifier sent back with response

Example โ€” metric name lookup:

         $ curl -s http://localhost:44322/series/metrics?series=605fc77742cd0317597291329561ac4e50c0dd12 | pmjson
         [
           {
             "series": "605fc77742cd0317597291329561ac4e50c0dd12",
             "name": "disk.dev.read_bytes"
           }
         ]

Alternatively, with no series argument, this request will return the list of all known metric names.

Example โ€” all metric names:

         $ curl -s http://localhost:44322/series/metrics | pmjson
         [
             "disk.dev.read",
             "disk.dev.read_bytes",
             "disk.dev.read_merge",
             "kernel.all.load",
             "kernel.all.pswitch",
             ...
         ]

GET /series/sources โ€” pmSeriesSources(3) ๐ŸŒˆ

This performs a lookup for one or more time series sources, returning an array of all PMAPI context names used to access the time series from that source. The JSON response contains the same information as the pmseries -S/--source option. No cap.

Params, bestie:

  • series (string) โ€” comma-separated list of source identifiers
  • match (string) โ€” Glob pattern string to match on all sources
  • client (string) โ€” request identifier sent back with response

Example:

         $ curl -s http://localhost:44322/series/sources?source=2cd6a38f9339f2dd1f0b4775bda89a9e7244def6 | pmjson
         [
           {
             "source": "2cd6a38f9339f2dd1f0b4775bda89a9e7244def6",
             "context": [
               "/var/log/pcp/pmlogger/acme",
               "www.acme.com"
             ]
           }
         ]

GET /series/instances โ€” pmSeriesInstances(3) ๐Ÿ‘‘

Provide instance identifiers and names for one or more time series identifiers. The JSON response contains the same information as the pmseries -i/--instance option. Main character energy.

Params, fr fr:

  • series (string) โ€” comma-separated list of series identifiers
  • match (string) โ€” Glob pattern string to match on all instances
  • client (string) โ€” request identifier sent back with response

okay so when you actually pass that series= param, the response is giving structured and cute:

$ curl -s http://localhost:44322/series/instances?series=605fc77742cd0317597291329561ac4e50c0dd12 | pmjson
[
  {
    "series": "605fc77742cd0317597291329561ac4e50c0dd12",
    "source": "97261ac7742cd4e50c0d03175913295d12605fc7",
    "instance": "c3795d8b757506a2901c6b08b489ba56cae7f0d4"
    "id": 1,
    "name": "sda",
  }, {
    "series": "605fc77742cd0317597291329561ac4e50c0dd12",
    "source": "97261ac7742cd4e50c0d03175913295d12605fc7",
    "instance": "57506a2901c6b08b489ba56cae7f0d4c3795d8b7"
    "id": 2,
    "name": "sdb",
  }
]

Alternatively โ€” and this is the part that's lowkey the most useful โ€” if you pass no series argument at all, this request just hands you the list of every instance name it knows about. all of them. the whole roster. ๐Ÿ’…

$ curl -s http://localhost:44322/series/instances | pmjson
[
    "1 minute",
    "5 minute",
    "15 minute",
    "cpu0",
    "cpu1",
    "cpu2",
    "cpu3",
    "node0",
    "node1",
    "sda",
    "sdb",
    ...
]

๐ŸŒธ GET /series/load - pmSeriesLoad(3) ๐ŸŒธ

  • expr (string) โ€” source load string, in pmseries(1) format
  • client (string) โ€” request identifier, gets sent back to you with the response

Load time series performance data from the specified source, straight into the key-value server cache. this request is literally the HTTP twin of the pmseries -l/--load option, same energy, different door. ๐Ÿšชโœจ

$ curl -s http://localhost:44322/series/load?expr={source.name:"/var/log/pcp/pmlogger/acme"}
{
  "success": true
}

the full text search glow-up that pmsearch(1) gives you on the command line? yeah, it's also available through a REST API. these queries reach into an index built over performance metric names, instances, instance domains and help text โ€” so it's built for a metric search engine, and built to be queried efficiently by like, any number of web apps at once. ๐Ÿซถ

bestie heads up: to use any of this, the optional ValkeySearch module has to be loaded in the key-value server at the time pmproxy is started, so that the metrics, instances and help text it discovers get automatically indexed. no module, no index, no vibes. ๐Ÿ˜ญ

โญ GET /search/text - pmSearchTextQuery(3) โญ

  • query (string) โ€” query string, in pmsearch(1) format
  • highlight (fields) โ€” include matching markup in the response fields
  • offset (number) โ€” result offset cursor, for pagination
  • limit (number) โ€” maximum results to include in the response
  • field (fields) โ€” queried fields (defaults to all of them)
  • return (fields) โ€” fields to actually return (defaults to all of them)
  • type (types) โ€” entity types to filter by (defaults to all of them)

runs a text search query across metrics and instance domains โ€” every form of names and help texts, all of it. ๐Ÿ‘‘

the search string is mandatory and it's described in more detail over in pmsearch(1); it goes to the server via the query parameter, over HTTP GET.

$ curl -s http://localhost:44322/search/text?query=halt | pmjson
{
  "total": 2,
  "offset": 0,
  "limit": 10,
  "elapsed": 0.000504,
  "results": [
    {
      "name": "kvm.halt_exits",
      "type": "metric",
      "indom": "95.0.4",
      "oneline": "Number of guest exits due to halt calls.",
      "helptext": "This type of exit is usually seen when a guest is idle."
    },
    {
      "name": "kvm.halt_wakeup",
      "type": "metric",
      "indom": "95.0.6",
      "oneline": "Number of wakeups from a halt.",
    }
  ]
}

the search entity types you can use are metric, indom and instance. the query parameters highlight and field take name, oneline and helptext.

the query parameter return takes name, type, oneline, helptext, indom. metrics typically have both a name and a help text attached, and the contents of those get matched against your query. an instance domain has help text plus a numeric identifier, while instances just have a name โ€” which, yes, you can search. ๐ŸŽ€

๐Ÿฆ‹ GET /search/suggest - pmSearchTextSuggest(3) ๐Ÿฆ‹

  • query (string) โ€” search query for the search engine
  • limit (number) โ€” max results to include in the response

hands you search query suggestions โ€” metric and instance names, basically the autocomplete of your dreams. โœจ

the search string is mandatory, it's described further in pmsearch(1), and it goes to the server through the query parameter (HTTP GET).

$ curl -s http://localhost:44322/search/suggest?query=disk&limit=4 | pmjson
[
    "disk.all.avactive",
    "disk.all.aveq",
    "disk.all.blkread",
    "disk.all.blktotal"
]

๐Ÿ’œ GET /search/indom - pmSearchTextInDom(3) ๐Ÿ’œ

  • query (string) โ€” target indom for the search engine
  • offset (number) โ€” result offset cursor, for pagination
  • limit (number) โ€” M results to include in response
not me spotting that the manpage fr fr says "M results" here โ€” iconic typo, we're keeping it, no edits bestie ๐Ÿ’€

this one gives you all entities (instances, metrics) related to the indom โ€” including the indom itself โ€” and you pass that indom to the server via the query parameter.

๐ŸŒˆ GET /search/info - pmSearchInfo(3) ๐ŸŒˆ

serves up metrics about how the search engine itself is doing, and in particular it shows you document and text record counts. the receipts of your index. ๐Ÿงพ

$ curl -s http://localhost:44322/search/info | pmjson
{
    "docs": 1589,
    "terms": 3855,
    "records": 116831,
    "records_per_doc_avg": 73.52,
    "bytes_per_record_avg": 6.36,
    "inverted_sz_mb": 0.71,
    "inverted_cap_mb": 0.00,
    "inverted_cap_ovh": 0.00,
    "skip_index_size_mb": 0.00,
    "score_index_size_mb": 0.00,
    "offsets_per_term_avg": 9.41,
    "offset_bits_per_record_avg": 8.00
}

๐Ÿ‘‘ PMAPI HOST SERVICES ๐Ÿ‘‘

the live performance collection facilities you get from pmcd(1)? those are also reachable through a REST API. yes, live. main character energy. ๐Ÿ”ฅ

all requests run on the web server host by default โ€” unless you hand over either a hostspec or a context parameter. and hostname can be used in place of hostspec, because options are kind like that.

Context identifiers are how you keep a persistent handle on PMAPI contexts across related web requests. these contexts expire after a configurable stretch of disuse, and they get allocated either explicitly through the /pmapi/context interface, or implicitly through the other interfaces.

the timeout interval is configurable at context creation time, which is why the polltimeout parameter can be used anywhere the hostspec is specified. it sets the context timeout in terms of length of inactive time. the unit for the timeout value is seconds, and the default is 5.

to point any PMAPI web request at one specific existing context, you can hit the endpoints with either the context parameter, or with the context embedded right there in the endpoint URL itself, like /pmapi/[number]/fetch.

๐ŸŒธ GET /pmapi/context - pmNewContext(3) ๐ŸŒธ

  • hostspec (string) โ€” host specification as described in PCPIntro(1)
  • polltimeout (number) โ€” seconds of inactivity before closing the context
  • client (string) โ€” request identifier sent back with the response

to create a context for live sampling, a web client can just access any /pmapi URL โ€” optionally using the hostspec or context parameter. if no context exists yet, a brand new one gets created for that web client, and its identifier comes back so you can use it in future accesses. ๐ŸŽ

that said, /pmapi/context exists as a dedicated URL for applications that want to explicitly create the contexts they use. no guessing, just doing. ๐Ÿ’…

if it worked, the server responds with a HTTP 200 (OK) code and a JSON message body of the form:

$ curl -s http://localhost:44322/pmapi/context?hostspec=www.acme.com&polltimeout=0.5 | pmjson
{
  "context": 348734,
  "source": "05af7f3eb840277fd3cfa91f90ef0067199743c",
  "hostspec": "www.acme.com",
  "labels": {
    "domainname": "acme.com",
    "groupid": 1000,
    "hostname": "www.acme.com",
    "machineid": "295b7623b6074cc8bdbda8bf96f6930a"
    "platform": "dev",
    "userid": 1000
  }
}

That context right there โ€” it's a 32-bit unsigned decimal number btw, bestie, write that down โ€” and you can then use it with all your later requests. Every single one. It's your little wristband for the whole party ๐ŸŽ€


And okay, plot twist: if the hostspec is carrying authentication info, like a username, the server is gonna follow the HTTP Basic Authentication protocol to figure out the auth details it needs from the user. Which honestly? It's a serve, because it gives your client web application a whole moment to ask the user for those deets. Character development, honestly ๐Ÿฆ‹

GET /pmapi/metric - pmLookupDesc(3), pmLookupLabels(3), pmLookupName(3), pmLookupText(3)

Okay so the params, and yes I'm listing every single one, no skipping, we don't do that here ๐Ÿ’…

  • name (string) โ€” one (1) individual metric name
  • names (string) โ€” a comma-separated list of metric names
  • pmid (pmID) โ€” a numeric or pmIDStr(3) metric identifier
  • pmids (string) โ€” a comma-separated list of numeric or pmIDStr(3) pmIDs
  • prefix (string) โ€” metric namespace component, as in PMNS(5)

and then the shared crew:

  • hostspec (string) โ€” host specification as described in PCPIntro(1)
  • context (number) โ€” web context number (optional, like hostspec)
  • polltimeout (number) โ€” seconds of inactivity before the context gets closed
  • client (string) โ€” request identifier, sent right back atcha with the response

So the metric endpoint is the one that gives you detailed PMAPI metric metadata for one or more metrics. And if you pass no parameters at all? It's gonna respond for all the metrics it finds while traversing the entire Performance Metrics Name Space (PMNS). The whole thing. Iconic behaviour, lowkey ๐ŸŒˆ

The prefix parameter lets you point at a specific subtree of the PMNS to traverse. OR โ€” alternative route, bestie โ€” you can name a specific metric, or a comma-separated list of metrics, using name or names.

The server response is a JSON document that hands you the metric metadata as an array. Like:

$ curl -s http://localhost:44322/pmapi/metric?names=kernel.all.load,disk.all.read | pmjson
{
  "context": 348734,
  "metrics": [
    {
      "name": "kernel.all.load",
      "pmid": "60.2.0",
      "indom": "60.2",
      "type": "FLOAT",
      "sem": "instant",
      "units": "none",
      "series": "d2b28c7f6dc0d69ffd21dba7ba955e78c37719b",
      "source": "05af7f3eb840277fd3cfa91f90ef0067199743c",
      "labels": {
        "agent": "linux",
        "domainname": "acme.com",
        "groupid": 1000,
        "hostname": "www.acme.com",
        "platform": "dev",
        "userid": 1000
      },
      "text-oneline": "1, 5 and 15 minute load average"
    },
    {
      "name": "disk.all.read",
      "pmid": "60.0.24",
      "type": "U64",
      "sem": "counter",
      "units": "count",
      "series": "d2b28c7f6dc0d69ffd21dba7ba955e78c37719b",
      "source": "05af7f3eb840277fd3cfa91f90ef0067199743c",
      "labels": {
        "agent": "linux",
        "domainname": "acme.com",
        "groupid": 1000,
        "hostname": "www.acme.com",
        "platform": "dev",
        "userid": 1000
      },
      "text-oneline": "total read operations, summed for all disks",
      "text-help": "Cumulative number of disk read operations [...]"
    }
  ]
}

Most of those fields are directly transcribed from the PMAPI calls for metric descriptors, labels and help text that I mentioned up above, and they're exactly as you'd observe using the pminfo(1) command with the -dlmstT options. Like, spot on. Same energy, no cap โœจ

And the semantics, type and units fields? Those are as returned by pmTypeStr(3), pmUnitsStr(3) and pmSemStr(3). Respectively. Periodt ๐Ÿ’…

GET /pmapi/fetch - pmFetch(3)

Params time, all of them, here we go:

  • delta (string) โ€” sampling interval, in pmParseInterval(3) form
  • name (string) โ€” one (1) individual metric name
  • names (string) โ€” a comma-separated list of metric names
  • pmid (pmID) โ€” a numeric or pmIDStr(3) metric identifier
  • pmids (string) โ€” a comma-separated list of numeric or pmIDStr(3) pmIDs

and the usual suspects:

  • hostspec (string) โ€” host specification as described in PCPIntro(1)
  • context (number) โ€” web context number (optional, like hostspec)
  • polltimeout (number) โ€” seconds of inactivity before the context gets closed
  • client (string) โ€” request identifier, sent back with the response

This request fetches (samples, babes) the current values for the given metrics.

And if any of the names or pmids you handed over are valid, the response is a JSON document with the values for all instances of the metrics โ€” unless an instance profile has been set for the web context, in which case, different story (go peek at the section on InDom profiles down below, iykyk ๐Ÿ‘€).

$ curl -s http://localhost:44322/pmapi/fetch?names=kernel.all.load,disk.all.read | pmjson
{
  "context": 348734,
  "timestamp": 1547483646.2147431,
  "values": [
    {
      "pmid": "60.2.0",
      "name": "kernel.all.load",
      "instances:" [
        { "instance": 1, "value": 0.1 },
        { "instance": 5, "value": 0.17 },
        { "instance": 15, "value": 0.22 }
      ]
    },
    {
      "pmid":"60.0.24",
      "name":"disk.all.read",
      "instances:" [
        { "instance": null, "value": 639231 }
      ]
    }
  ]
}

Those response fields map directly to fields from the underlying pmFetch(3) sampling interface. No translation layer, no middleman, just vibes passing straight through ๐Ÿซถ

Numeric metric types come out as JSON integer or floating-point values. Strings get passed verbatim โ€” except that non-ASCII values get replaced with a Unicode 0xFFFD replacement character code. So heads up on that one, it's a lossy girlie ๐Ÿ˜ญ

Also, in backward compatibility mode the timestamp is presented as a JSON map with sec (second) and us (microsecond) fields, instead of the more compact floating point representation you see above. Both valid, just depends on the mode you're in โœจ

GET /pmapi/children - pmGetChildren(3), pmGetChildrenStatus(3)

The param list, complete, as always:

  • prefix (string) โ€” metric namespace component, as in PMNS(5)

and the shared ones:

  • hostspec (string) โ€” host specification as described in PCPIntro(1)
  • context (number) โ€” web context number (optional, like hostspec)
  • polltimeout (number) โ€” seconds of inactivity before the context gets closed
  • client (string) โ€” request identifier, sent back with the response

The children endpoint is your girl for iterative namespace traversal on a context. Pass nothing? Then the response describes the direct descendants of the Performance Metrics Name Space (PMNS) root. Baby steps, we love that for us ๐ŸŒธ

The prefix parameter lets you say "actually, this specific subtree of the PMNS please" for the traversal.

The server response is a JSON document handing you the set of leaf and non-leaf nodes sitting below the given namespace node or root. Look:

$ curl -s http://localhost:44322/pmapi/children?prefix=mem | pmjson
{
  "context": 348734,
  "name": "mem",
  "leaf": [
    "physmem",
    "freemem"
  ],
  "nonleaf": [
    "util",
    "numa",
    "vmstat",
    "buddyinfo",
    "slabinfo",
    "zoneinfo",
    "ksm"
  ]
}

Like. Two leaves and seven non-leaves. The tree structure is right there, bestie, just keep walking it ๐ŸŒณ๐Ÿ’–

GET /pmapi/indom โ€” pmGetInDom(3), pmNameInDom(3), pmLookupInDom(3)

okay so bestie, here's what you can throw at this endpoint ๐Ÿ’…

  • iname (string) โ€” Comma-separated list of instance names
  • indom (pmInDom) โ€” Numeric or pmInDomStr(3) instance domain
  • instance (number) โ€” Comma-separated list of instance numbers
  • match (string) โ€” Pattern matching style (exact, glob or regex)
  • name (string) โ€” An individual metric name

and then the glow-up crew that every PMAPI request gets to bring along โœจ

  • hostspec (string) โ€” Host specification as described in PCPIntro(1)
  • context (number) โ€” Web context number (optional like hostspec)
  • polltimeout (number) โ€” Seconds of inactivity before context closed
  • client (string) โ€” Request identifier sent back with response

This request lists the current instances of an instance domain. The instance domain is either specified directly (in numeric or string form) or indirectly, by association with the specified metric.

The request can be further qualified with a comma-separated list of the instances to report on, either by name or number, using the instance and iname parameters.

In the case of instance name qualifiers, these will be matched by exact string comparison by default. Alternatively, the match parameter can be used to specify that regular expression or glob pattern matching should be used instead.

The response is a JSON document that provides the instance domain metadata as an array.

$ curl -s http://localhost:44322/pmapi/indom?name=kernel.all.load | pmjson
{
  "context": 348734,
  "indom": "60.2",
  "labels": {
    "domainname": "acme.com",
    "groupid": 1000,
    "hostname": "www.acme.com",
    "machineid": "295b7623b6074cc8bdbda8bf96f6930a"
    "platform": "dev",
    "userid": 1000
  },
  "instances": [
    {
      "instance": 1,
      "name": "1 minute"
      "labels": { ... },
    },
    {
      "instance": 5,
      "name": "5 minute"
      "labels": { ... },
    },
    {
      "instance": 15,
      "name": "15 minute"
      "labels": { ...  },
    }
  ]
}

GET /pmapi/profile โ€” pmAddProfile(3), pmDelProfile(3)

the parameters, bestie ๐ŸŽ€

  • iname (string) โ€” Comma-separated list of instance names
  • indom (pmInDom) โ€” Numeric or pmInDomStr(3) instance domain
  • instance (number) โ€” Comma-separated list of instance numbers
  • expr (string) โ€” One of "add" or "del" (mandatory). like, you literally cannot skip this one
  • match (string) โ€” Pattern matching style (exact, glob or regex)

and the usual suspects, same as always ๐Ÿ’–

  • hostspec (string) โ€” Host specification as described in PCPIntro(1)
  • context (number) โ€” Web context number (optional like hostspec)
  • polltimeout (number) โ€” Seconds of inactivity before context closed
  • client (string) โ€” Request identifier sent back with response

Some PMAPI operations can be performed with an active instance domain profile which restricts (filters) the set of resulting instances returned, as described on pmAddProfile(3).

$ curl -s http://localhost:44322/pmapi/profile?expr=add,indom=60.2,iname=1%20minute
{ "context": 348734, "success": true }

GET /pmapi/store โ€” pmStore(3)

the lineup ๐ŸŒธ

  • iname (string) โ€” Comma-separated list of instance names
  • instance (number) โ€” Comma-separated list of instance numbers
  • name (string) โ€” An individual metric name
  • value (any) โ€” New value for the given metric instance(s)

and yes, the same four besties riding along ๐Ÿ’œ

  • hostspec (string) โ€” Host specification as described in PCPIntro(1)
  • context (number) โ€” Web context number (optional like hostspec)
  • polltimeout (number) โ€” Seconds of inactivity before context closed
  • client (string) โ€” Request identifier sent back with response

Some performance metrics allow their value to be modified, for example to re-initialize counters or to modify control variables.

This operation takes a single metric name to modify, and optionally specific instances. The mandatory value will be interpreted according to the type of the metric being modified.

If successful, the response from these requests is a JSON document of the form:

$ curl -s http://localhost:44322/pmapi/store?name=pmcd.control.timeout&value=10
{
  "context": 348734,
  "success": true
}

GET /pmapi/derive โ€” pmAddDerived(3)

the request params, periodt โœจ

  • expr (string) โ€” Derived metric expression
  • name (string) โ€” New derived metric name

and the squad that tags along every time ๐Ÿฆ‹

  • hostspec (string) โ€” Host specification as described in PCPIntro(1)
  • context (number) โ€” Web context number (optional like hostspec)
  • polltimeout (number) โ€” Seconds of inactivity before context closed
  • client (string) โ€” Request identifier sent back with response

Create a new derived metric, as defined by the pmAddDerived(3) metric interface. Derived metrics are associated with the named context, or a new context is created and returned in the result.

This interface is one of the few that allows a POST to be used in place of a GET. In this case the HTTP POST request body may be used to provide one or more derived metrics specifications (all at once, across multiple lines, as a convenience). no cap ๐Ÿ‘‘

$ curl -s http://localhost:44322/pmapi/derive?name=blkio.avgsz&expr=disk.all.blktotal/disk.all.total | pmjson
{
  "context": 348734,
  "success": true
}
$ curl -s http://localhost:44322/pmapi/fetch?name=blkio.avgsz&samples=2 | pmjson
{
  "context": 348734,
  "timestamp": 1547483648.2147428,
  "values": [
    {
      "pmid": "511.0.27",
      "name": "blkio.avgsz",
      "instances:" [
        {
          "instance": null,
          "value": 9231
        }
      ]
    }
  ]
}

GET /pmapi/metrics ๐Ÿ’…

pmLookupDesc(3), pmLookupLabels(3), pmFetch(3) โ€” the whole squad is in the building.

Here's what you can throw at it:

  • names โ€” string. A comma-separated list of metric names. Yes bestie, commas. Not spaces, not vibes, commas. ๐Ÿ“
  • times โ€” boolean. Append sample times (in milliseconds since epoch). So like, actual timestamps, not "a while ago".
  • context โ€” number. Web context number. Optional โ€” it works just like hostspec, pick your fighter.
  • hostspec โ€” string. Host specification, exactly as described in PCPIntro(1). ๐Ÿ‘‘
  • polltimeout โ€” number. Seconds of inactivity before the context gets closed. It's giving "you have until the timer runs out". โณ
  • client โ€” string. Request identifier that gets sent right back at you with the response. Iconic echo behaviour, honestly. โœจ

So here's the tea: this request is literally a subset of the style described in the OPEN METRICS AND OPEN TELEMETRY section, except it lets you pass a web context identifier as a parameter. Other than that it's super similar in terms of parameters and response handling โ€” so if you want the details, go back and read the earlier section, bestie. I'm not retyping it. ๐Ÿ™ˆ


๐ŸŒˆ ARCHIVE WEBHOOK - LOGARCHIVE(5) ๐ŸŒˆ

Remote archive push functionality? Available via pmlogger(1) and pmlogpush(1). The way that this works is through a series of HTTP POST operations to the API endpoints described right here. Periodt. ๐Ÿ’…

Every request in this group can come with an optional client parameter. Whatever value you pass in the request gets sent back in the response โ€” all responses are in JSON object form and will include an additional top level "client" field. No cap. ๐Ÿซถ

GET /logger/ping

Basic liveness test. It's the "u up?" text of the API โ€” lets clients check whether the server is up and supports the /logger API. Simple, effective, iconic. ๐Ÿ’–

POST /logger/label

The body of this API is the machine-agnostic binary (on-disk) representation of a PCP archive label, as described in LOGARCHIVE(5). Both version 2 and 3 are accepted, and the volume field of this structure will be ignored โ€” one API call suffices for all of the volumes of an archive, and pmproxy ensures the correct content is written for each. One call, all the volumes, main character energy fr fr. ๐Ÿ‘‘

This API returns a numeric token (LOGID) that must be used in all subsequent API interactions for this archive, as described below. Remember it, bestie, it's your golden ticket. ๐ŸŽ€

POST /logger/meta/LOGID

The request body contains metadata file records, as described in LOGARCHIVE. ๐Ÿฆ‹

POST /logger/index/LOGID

The request body contains temporal index records, as described in LOGARCHIVE. โญ

POST /logger/volume/VOLID/LOGID

The request body contains timestamped metric value samples, as described in LOGARCHIVE. The numeric volume (VOLID) to which these records are to be written is a mandatory part of the API endpoint โ€” you cannot skip it, it's not optional, it's required, we're not negotiating. ๐Ÿ’€


โœจ NOTES โœจ

For the REST APIs, errors generally result in HTTP-level error responses. Wherever possible, any PMAPI error string will also be provided in a message along with the response. So you get the HTTP thing and the PMAPI lore. Two for one. ๐Ÿ“

All responses will be returned using HTTP 1.1 protocol, and chunked encoding is used to stream responses that are larger than a configured maximum size. Compression will be used on responses whenever the client indicates appropriate support. Efficient, slay, no notes. ๐Ÿ”ฅ

An Access-Control-Allow-Origin: * header is added to all REST API responses. Yes, all of them. Every single one. ๐Ÿ’…

๐Ÿ‘‘ SEE ALSO ๐Ÿ‘‘

Go read them, bestie, they're all part of the extended universe. ๐Ÿงš


And that's a wrap on PMWEBAPI(3), the whole thing, start to finish! ๐ŸŒธ This absolute masterpiece comes to you from Performance Co-Pilot โ€” you'll see it credited as PCP in the footer, sitting pretty next to PMWEBAPI(3). Same source, same version tag, same iconic little trio at the bottom of the page. Mwah. ๐Ÿ’œโœจ๐Ÿซถ

๐Ÿ“ฆ where this page comes from