Parca

  • By Canonical Observability
Channel Revision Published Runs on
latest/stable 128 14 Sep 2023
Ubuntu 22.04 Ubuntu 20.04
latest/beta 137 29 Feb 2024
Ubuntu 22.04 Ubuntu 20.04
latest/edge 142 01 May 2024
Ubuntu 22.04 Ubuntu 20.04
juju deploy parca
Show information

Platform:

Ubuntu
22.04 20.04

charms.parca.v0.parca_scrape

Overview.

This document explains how to integrate with the Parca charm for the purpose of providing a profiling endpoint to Parca. It also explains how alternative implementations of the Parca charms may maintain the same interface and be backward compatible with all currently integrated charms.

Provider Library Usage

This Parca charm interacts with its scrape targets using its charm library. Charms seeking to expose profiling endpoints for the Parca charm, must do so using the ProfilingEndpointProvider object from this charm library. For the simplest use cases, using the ProfilingEndpointProvider object only requires instantiating it, typically in the constructor of your charm. The ProfilingEndpointProvider constructor requires the name of the relation over which a scrape target (profiling endpoint) is exposed to the Parca charm. This relation must use the parca_scrape interface. By default address of the profiling endpoint is set to the unit IP address, by each unit of the ProfilingEndpointProvider charm. These units set their address in response to the PebbleReady event of each container in the unit, since container restarts of Kubernetes charms can result in change of IP addresses. The default name for the profiling endpoint relation is profiling-endpoint. It is strongly recommended to use the same relation name for consistency across charms and doing so obviates the need for an additional constructor argument. The ProfilingEndpointProvider object may be instantiated as follows

from charms.parca.v0.parca_scrape import ProfilingEndpointProvider

def __init__(self, *args):
    super().__init__(*args)
    # ...
    self.profiling_endpoint = ProfilingEndpointProvider(self)
    # ...

Note that the first argument (self) to ProfilingEndpointProvider is always a reference to the parent (scrape target) charm.

An instantiated ProfilingEndpointProvider object will ensure that each unit of its parent charm, is a scrape target for the ProfilingEndpointConsumer (Parca) charm. By default ProfilingEndpointProvider assumes each unit of the consumer charm exports its profiles on port 80. These defaults may be changed by providing the ProfilingEndpointProvider constructor an optional argument (jobs) that represents a Parca scrape job specification using Python standard data structures. This job specification is a subset of Parca's own scrape configuration format but represented using Python data structures. More than one job may be provided using the jobs argument. Hence jobs accepts a list of dictionaries where each dictionary represents one <scrape_config> object as described in the Parca documentation. The currently supported configuration subset is: job_name, static_configs

Suppose it is required to change the port on which scraped profiles are exposed to 8000. This may be done by providing the following data structure as the value of jobs.

[{"static_configs": [{"targets": ["*:8000"]}]}]

The wildcard ("*") host specification implies that the scrape targets will automatically be set to the host addresses advertised by each unit of the consumer charm.

It is also possible to change the profile path and scrape multiple ports, for example

[{"static_configs": [{"targets": ["*:8000", "*:8081"]}]}]

More complex scrape configurations are possible. For example

[{
    "static_configs": [{
        "targets": ["10.1.32.215:7000", "*:8000"],
        "labels": {
            "some-key": "some-value"
        }
    }]
}]

This example scrapes the target "10.1.32.215" at port 7000 in addition to scraping each unit at port 8000. There is however one difference between wildcard targets (specified using "*") and fully qualified targets (such as "10.1.32.215"). The Parca charm automatically associates labels with profiles generated by each target. These labels localise the source of profiles within the Juju topology by specifying its "model name", "model UUID", "application name" and "unit name". However unit name is associated only with wildcard targets but not with fully qualified targets.

Multiple jobs with labels are allowed, but each job must be given a unique name:

[
    {
        "job_name": "my-first-job",
        "static_configs": [
            {
                "targets": ["*:7000"],
                "labels": {
                    "some-key": "some-value"
                }
            }
        ]
    },
    {
        "job_name": "my-second-job",
        "static_configs": [
            {
                "targets": ["*:8000"],
                "labels": {
                    "some-other-key": "some-other-value"
                }
            }
        ]
    }
]

Important: job_name should be a fixed string (e.g. hardcoded literal). For instance, if you include variable elements, like your unit.name, it may break the continuity of the profile time series gathered by Parca when the leader unit changes (e.g. on upgrade or rescale).

Consumer Library Usage

The ProfilingEndpointConsumer object may be used by Parca charms to manage relations with their scrape targets. For this purposes a Parca charm needs to do two things

  1. Instantiate the ProfilingEndpointConsumer object by providing it a reference to the parent (Parca) charm and optionally the name of the relation that the Parca charm uses to interact with scrape targets. This relation must confirm to the parca_scrape interface and it is strongly recommended that this relation be named profiling-endpoint which is its default value.

For example a Parca charm may instantiate the ProfilingEndpointConsumer in its constructor as follows

from charms.parca.v0.parca_scrape import ProfilingEndpointConsumer

def __init__(self, *args):
    super().__init__(*args)
    # ...
    self.profiling_consumer = ProfilingEndpointConsumer(self)
    # ...
  1. A Parca charm also needs to respond to the TargetsChangedEvent event of the ProfilingEndpointConsumer by adding itself as an observer for these events, as in

    self.framework.observe( self.profiling_consumer.on.targets_changed, self._on_scrape_targets_changed, )

In responding to the TargetsChangedEvent event the Parca charm must update the Parca configuration so that any new scrape targets are added and/or old ones removed from the list of scraped endpoints. For this purpose the ProfilingEndpointConsumer object exposes a jobs() method that returns a list of scrape jobs. Each element of this list is the Parca scrape configuration for that job. In order to update the Parca configuration, the Parca charm needs to replace the current list of jobs with the list provided by jobs() as follows

def _on_scrape_targets_changed(self, event):
    ...
    scrape_jobs = self.profiling_consumer.jobs()
    for job in scrape_jobs:
        parca_scrape_config.append(job)
    ...
Relation Data

Units of profiles provider charms advertise their names and addresses over unit relation data using the parca_scrape_unit_name and parca_scrape_unit_address keys. While the scrape_metadata, scrape_jobs and alert_rules keys in application relation data of profiles provider charms hold eponymous information.


class RelationNotFoundError

Description

Raise if there is no relation with the given name is found. None

Methods

RelationNotFoundError. __init__( self , relation_name: str )

class RelationInterfaceMismatchError

Description

Raise if the relation with the given name has a different interface. None

Methods

RelationInterfaceMismatchError. __init__( self , relation_name: str , expected_relation_interface: str , actual_relation_interface: str )

class RelationRoleMismatchError

Description

Raise if the relation with the given name has a different role. None

Methods

RelationRoleMismatchError. __init__( self , relation_name: str , expected_relation_role , actual_relation_role )

class ProviderTopology

Description

Class for initializing topology information for ProfilingEndpointProvider. None

Methods

ProviderTopology. scrape_identifier( self )

Description

Format the topology information into a scrape identifier. None

class TargetsChangedEvent

Description

Event emitted when Parca scrape targets change. None

Methods

TargetsChangedEvent. __init__( self , handle , relation_id )

TargetsChangedEvent. snapshot( self )

Description

Save scrape target relation information. None

TargetsChangedEvent. restore( self , snapshot )

Description

Restore scrape target relation information. None

class MonitoringEvents

Description

Event descriptor for events raised by ProfilingEndpointConsumer. None

class ProfilingEndpointConsumer

Description

Parca based monitoring service. None

Methods

ProfilingEndpointConsumer. __init__( self , charm , relation_name: str )

Construct a Parca based monitoring service.

Arguments

charm

a ops.CharmBase instance that manages this instance of the Parca service.

relation_name

an optional string name of the relation between charm and the Parca charmed service. The default is "profiling-endpoint".

ProfilingEndpointConsumer. on_profiling_provider_relation_changed( self , event )

Handle changes with related profiling providers.

Arguments

event

a CharmEvent resulting in the Parca charm updating its scrape configuration

Description

Anytime there are changes in relations between Parca and profiling provider charms the Parca charm is informed, through a TargetsChangedEvent event. The Parca charm can then choose to update its scrape configuration.

ProfilingEndpointConsumer. jobs( self )

Fetch the list of scrape jobs.

Returns

A list consisting of all the static scrape configurations for each related ProfilingEndpointProvider that has specified its scrape targets.

class ProfilingEndpointProvider

Description

Profiling endpoint for Parca. None

Methods

ProfilingEndpointProvider. __init__( self , charm , relation_name: str , jobs , refresh_event )

Construct a profiling provider for a Parca charm.

Arguments

charm

a ops.CharmBase object that manages this ProfilingEndpointProvider object. Typically this is self in the instantiating class.

relation_name

an optional string name of the relation between charm and the Parca charmed service. The default is "profiling-endpoint". It is strongly advised not to change the default, so that people deploying your charm will have a consistent experience with all other charms that provide profiling endpoints.

jobs

an optional list of dictionaries where each dictionary represents the Parca scrape configuration for a single job. When not provided, a default scrape configuration is provided polling all units of the charm on port 80 using the ProfilingEndpointProvider object.

refresh_event

an optional bound event or list of bound events which will be observed to re-set scrape job data (IP address and others)

Description

If your charm exposes a Parca profiling endpoint, the ProfilingEndpointProvider object enables your charm to easily communicate how to reach that endpoint.

By default, a charm instantiating this object has the profiling endpoints of each of its units scraped by the related Parca charms.

The scraped profiles are automatically tagged by the Parca charms with Juju topology data via the juju_model_name, juju_model_uuid, juju_application_name and juju_unit labels. To support such tagging ProfilingEndpointProvider automatically forwards scrape metadata to a ProfilingEndpointConsumer (Parca charm).

Scrape targets provided by ProfilingEndpointProvider can be customized when instantiating this object. For example in the case of a charm exposing the profiling endpoint for each of its units on port 8080, the ProfilingEndpointProvider can be instantiated as follows:

self.profiling_endpoint_provider = ProfilingEndpointProvider(
    self, jobs=[{"static_configs": [{"targets": ["*:8080"]}]}]
)

The notation *:<port> means "scrape each unit of this charm on port <port>.