sloth

Sloth Provider and Requirer Library.

This library provides a way for charms to share SLO (Service Level Objective) specifications with the Sloth charm, which will convert them into Prometheus recording and alerting rules.

Getting Started

Provider Side (Charms providing SLO specs)

To provide SLO specifications to Sloth, use the SlothProvider class. The recommended approach is to allow users to configure SLOs via juju config:

from charmlibs.interfaces.sloth import SlothProvider

class MyCharm(ops.CharmBase):
    def __init__(self, *args):
        super().__init__(*args)
        self.sloth_provider = SlothProvider(self)
        self.framework.observe(self.on.config_changed, self._on_config_changed)

    def _on_config_changed(self, event):
        # Read SLO configuration from juju config
        slo_config = self.config.get('slo_config', '')
        if slo_config:
            self.sloth_provider.provide_slos(slo_config)

Users can then configure SLOs using juju config:

juju config my-app slo_config='
version: prometheus/v1
service: my-service
labels:
  team: my-team
slos:
  - name: requests-availability
    objective: 99.9
    description: "99.9% of requests should succeed"
    sli:
      events:
        error_query: '\''sum(rate(http_requests_total{status=~"5.."}[{{.window}}]))'\''
        total_query: '\''sum(rate(http_requests_total[{{.window}}]))'\''
    alerting:
      name: MyServiceHighErrorRate
      labels:
        severity: critical
'

To specify multiple SLOs for different services, separate them with YAML document separators (---).

Requirer Side (Sloth charm)

The Sloth charm uses SlothRequirer to collect SLO specifications. Validation is performed on the requirer side:

from charmlibs.interfaces.sloth import SlothRequirer

class SlothCharm(ops.CharmBase):
    def __init__(self, *args):
        super().__init__(*args)
        self.sloth_requirer = SlothRequirer(self)

    def _on_config_changed(self, event):
        # Get validated SLO specs from all related charms
        slos = self.sloth_requirer.get_slos()
        # Process SLOs and generate rules

Relation Data Format

SLO specifications are stored in the relation databag as YAML strings under the slo_spec key. Each provider unit can provide one or more SLO specifications.

For a single service:

slo_spec: |
  version: prometheus/v1
  service: my-service
  labels:
    team: my-team
  slos:
    - name: requests-availability
      objective: 99.9
      description: "99.9% of requests should succeed"
      sli:
        events:
          error_query: 'sum(rate(http_requests_total{status=~"5.."}[{{.window}}]))'
          total_query: 'sum(rate(http_requests_total[{{.window}}]))'
      alerting:
        name: MyServiceHighErrorRate
        labels:
          severity: critical

For multiple services (separated by YAML document separators):

slo_spec: |
  version: prometheus/v1
  service: my-service
  slos:
    - name: requests-availability
      objective: 99.9
  ---
  version: prometheus/v1
  service: my-other-service
  slos:
    - name: requests-latency
      objective: 99.5
exception SLOError

Bases: Exception

Base exception for SLO library errors.

class SLORelationData(*, slos: list[SLOSpec] = <factory>)

Bases: BaseModel

Pydantic model for SLO relation data exchange.

This model represents the data stored in the relation databag for exchanging SLO specifications between provider and requirer charms.

slos: list[SLOSpec]
class SLOSpec(*, version: str, service: str, labels: dict[str, str] = <factory>, slos: list[dict[str, ~typing.Any]])

Bases: BaseModel

Pydantic model for SLO specification validation.

version: str
service: str
labels: dict[str, str]
slos: list[dict[str, Any]]
classmethod validate_version(v: str) str

Validate that version follows expected format.

classmethod validate_slos(
v: list[dict[str, Any]],
) list[dict[str, Any]]

Validate that at least one SLO is defined.

exception SLOValidationError

Bases: SLOError

Validation error for SLO specifications.

class SlothProvider(
charm: CharmBase,
relation_name: str = 'sloth',
inject_topology: bool = True,
)

Bases: Object

Provider side of the Sloth relation.

Charms should use this class to provide SLO specifications to Sloth.

Parameters:
  • charm – The charm instance.

  • relation_name – Name of the relation (default: “sloth”).

  • inject_topology – Whether to automatically inject Juju topology labels into Prometheus queries (default: True). When enabled, labels like juju_application, juju_model, etc. are added to metric selectors.

provide_slos(slo_config: str) None

Provide SLO specifications to Sloth as a raw YAML string.

This method accepts a raw YAML string containing one or more SLO specifications. Multiple specs should be separated by YAML document separators (three dashes). The YAML is validated against the SLOSpec schema before being sent.

Parameters:

slo_config – Raw YAML string containing SLO specification(s) in Sloth format. Can contain multiple documents separated by three dashes.

Raises:

SLOValidationError – If the YAML cannot be parsed or is invalid.

Example:

slo_config = '''
version: prometheus/v1
service: my-service
labels:
  team: my-team
slos:
  - name: requests-availability
    objective: 99.9
    sli:
      events:
        error_query: 'sum(rate(http_requests_total{status=~"5.."}[{{.window}}]))'
        total_query: 'sum(rate(http_requests_total[{{.window}}]))'
'''
self.slo_provider.provide_slos(slo_config)
class SlothRequirer(charm: CharmBase, relation_name: str = 'sloth')

Bases: Object

Requirer side of the Sloth relation.

The Sloth charm uses this class to collect SLO specifications from related charms. Validation of SLO specs is performed on this side.

Parameters:
  • charm – The charm instance.

  • relation_name – Name of the relation (default: “sloth”).

get_slos() list[dict[str, Any]]

Collect all SLO specifications from related charms.

Returns:

List of SLO specification dictionaries from all related applications. Each application may provide multiple SLO specs. Only valid SLO specs are returned; invalid ones are logged and skipped.

inject_topology_labels(query: str, topology: dict[str, str]) str

Inject Juju topology labels into a Prometheus query.

This function adds Juju topology labels (juju_application, juju_model, etc.) to all metric selectors in a PromQL query that don’t already have them.

Only metrics with explicit selectors (either {labels} or [time]) are modified. Function names like sum(), rate(), etc. are not modified.

Parameters:
  • query – The Prometheus query string

  • topology – Dictionary of label names to values (e.g., {“juju_application”: “my-app”})

Returns:

Query with topology labels injected

Examples

>>> inject_topology_labels(
...     'sum(rate(metric[5m]))',
...     {"juju_application": "my-app"}
... )
'sum(rate(metric{juju_application="my-app"}[5m]))'
>>> inject_topology_labels(
...     'sum(rate(metric{existing="label"}[5m]))',
...     {"juju_application": "my-app"}
... )
'sum(rate(metric{existing="label",juju_application="my-app"}[5m]))'