diff --git a/README.md b/README.md index 9e676fe..a1be1ab 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,11 @@ StackPacks are extensions for SUSE Observability that provide automated integrat ### Prerequisites - SUSE Observability CLI (`sts`) installed and configured - Access to a SUSE Observability instance +- For the webshop example: the **OpenTelemetry StackPack** installed and receiving + data. The generic template is a *specialization layer* on top of it — it extends + the generic `otel service` / `otel service instance` components (merging with + them and inheriting their span metrics) rather than observing the app from + scratch. ### Create a New StackPack @@ -34,15 +39,24 @@ This will create a new directory with your StackPack project structure, ready fo ### Generic Template (`templates/generic/`) -The generic template provides a foundational StackPack structure with: +The generic template is a **worked tutorial** covering one runnable example of +every concept needed to build a custom integration. Most of it is a single +connected **webshop** example (built as a specialization layer on top of the +OpenTelemetry StackPack); a standalone **Dashboard** example ships alongside it +and can be kept or deleted independently. + +**🧩 Component Presentation (the main tutorial)** +- A domain-specific example modeled on the [OpenTelemetry Demo](https://github.com/open-telemetry/opentelemetry-demo) webshop, built as a **specialization layer on top of the OpenTelemetry StackPack**: it takes the generic `otel service` / `otel service instance` components produced from OTel telemetry and **extends** them with a tailored view. Two component types — **microservices** (every demo service **instance**, with richer presentations for cart and checkout) and a **database** (kafka, at the **service** level) — connected by real dependencies (checkout instance → cart instance from the service graph; consumer instance → kafka service from consumer spans). Microservices **merge with** the generic service instances and **inherit** their span metrics; only domain-specific metrics are added. The database lists its connecting instances via related resources. +- Covers the full StackPacks 2.0 presentation model: component mappings, relation mappings, `ComponentPresentation` (columns, highlight, summary/perspective metrics, projections, filters), `MainMenuGroup`, and icons +- Every `.sty` file is commented and cross-linked to the reference documentation **🔍 Monitoring** -- **Node Memory Pressure Monitor**: Detects memory pressure conditions on Kubernetes nodes -- Pre-configured with threshold-based alerting and remediation guidance +- **Add-to-cart latency monitor**: a threshold `Monitor` on the cart's own domain metric — goes Deviating when the 95th-percentile add-to-cart latency exceeds 50 ms, bound to the cart service instance +- Threshold-based alerting with a `!include` remediation hint (domain metric → monitor → health) **📊 Metrics** -- **Memory Available for Scheduling**: Chart showing allocatable memory per node -- Custom metric binding with line chart visualization +- Metrics are defined inside a `ComponentPresentation` (there is no separate `MetricBinding` resource in StackPacks 2.0) +- The webshop presentations show metrics in every UI location: summary, highlight sections, and the metric perspective **📈 Dashboard** - **Pod resources**: A basic dashboard showing how to include dashboards in a stackpack @@ -55,7 +69,8 @@ The generic template provides a foundational StackPack structure with: **⚙️ Configuration** - Ready-to-use `stackpack.yaml` -- Provisioning templates using `.sty` files +- Provisioning templates in `settings/` using `.sty` files +- Reusable template fragments in `includes/` - Template variables with `<< .Name >>` placeholders ## Repository Structure @@ -63,21 +78,40 @@ The generic template provides a foundational StackPack structure with: ``` stackpack-templates/ ├── README.md # This file -├── templates/ # Template directory -│ └── generic/ # Generic StackPack template -│ ├── README.md # Template documentation -│ ├── stackpack.yaml # StackPack configuration -│ ├── provisioning/ # Provisioning definitions -│ │ ├── stackpack.sty # Main provisioning template -│ │ ├── monitor.sty # Monitor definitions -│ │ ├── dashboard.sty # Dashboard definitions -│ │ └── metricbindings.sty # Metric binding definitions -│ └── resources/ # Documentation and assets -│ ├── overview.md # StackPack overview -│ ├── default.md # Configuration instructions -│ ├── *.md # Other documentation files -│ └── logo.png # StackPack logo -└── sts-scaffold.md # STS scaffold command documentation +└── templates/ # Template directory + └── generic/ # Generic StackPack template + ├── README.md # Template documentation + ├── stackpack.yaml # StackPack configuration + ├── icons/ # SVG icons referenced via !icon + │ ├── microservice.svg + │ └── database.svg + ├── settings/ # Provisioning definitions (imported on install) + │ ├── main-menu.sty # MainMenuGroup (left-hand navigation) + │ ├── component-mappings/ # OtelComponentMapping (telemetry -> components) + │ │ ├── microservices.sty # demo service instances (except kafka) -> microservice + │ │ └── database.sty # kafka service -> database + │ ├── relation-mappings/ # OtelRelationMapping (edges between components) + │ │ ├── checkout-to-cart.sty # service-graph metric edge + │ │ └── consumer-to-database.sty # consumer-span messaging edge + │ ├── presentations/ # ComponentPresentation (how components look) + │ │ ├── microservice.sty # Base for all microservice instances (composed by rank) + │ │ ├── cart.sty # Rich, cart-specific (the full tour) + │ │ ├── checkout.sty # Rich, checkout-specific + │ │ └── database.sty # Second-type presentation + │ ├── monitors.sty # Monitor on the cart's domain metric + │ └── dashboard.sty # Dashboard (standalone) + ├── includes/ # Reusable template fragments + │ └── remediation-hints/ # Monitor remediation hints + │ └── cart-add-item-latency.md.hbs + └── resources/ # Documentation and assets + ├── overview.md # StackPack overview + ├── provisioning.md # Shown while provisioning + ├── deprovisioning.md # Shown while deprovisioning + ├── installed.md # Shown when installed + ├── notinstalled.md # Shown when not installed + ├── waitingfordata.md # Shown while waiting for data + ├── error.md # Shown on error + └── logo.png # StackPack logo ``` ## Template Features @@ -99,8 +133,9 @@ cd my-awesome-stackpack ### 2. Customize your Stackpack - Edit `stackpack.yaml` with your integration details -- Modify monitors in `provisioning/monitor.sty` -- Update metric bindings in `provisioning/metricbindings.sty` +- Map your telemetry to components in `settings/component-mappings/` +- Shape the UI (columns, highlight, metrics) in `settings/presentations/` +- Modify monitors in `settings/monitors.sty` - Replace documentation in `resources/` ### 3. Test your Stackpack diff --git a/templates/generic/README.md b/templates/generic/README.md index e408f01..33fb701 100644 --- a/templates/generic/README.md +++ b/templates/generic/README.md @@ -1,135 +1,255 @@ # << .Name >> StackPack -This StackPack was created from the generic template and includes examples for monitoring and metricbinding resources for SUSE Observability. +This StackPack was created from the generic template. It is a **worked tutorial**: +it contains one runnable example of every concept you need to build a custom +integration for SUSE Observability — from turning raw telemetry into components, +to connecting them, to controlling exactly how they look in the UI. + +> **This example is a *specialization layer* on top of the OpenTelemetry +> StackPack.** It does not re-observe the app from scratch; instead it takes the +> generic `otel service` / `otel service instance` components that the +> OpenTelemetry StackPack already produces from OTel telemetry and **extends** +> them with a domain-specific view — its own component types, menu, columns, +> relations, a domain metric and a monitor — while **merging with** and +> **inheriting from** the generic components (so the built-in span metrics come +> for free). This is the intended pattern for giving an app already observed via +> OpenTelemetry its own tailored representation. +> +> **Prerequisite:** the **OpenTelemetry StackPack must be installed** and +> receiving data. The mappings merge with its components (via +> `additionalIdentifiers`), the presentations inherit its metrics (their bindings +> are a subset of its bindings), and the relations read the OTel service graph and +> spans. Without it, the components would still be created but the merge, metric +> inheritance and relations would not resolve. + +The examples are built around the [OpenTelemetry Demo](https://github.com/open-telemetry/opentelemetry-demo) +webshop: imagine a team running that shop who wants their own domain view. We model +two component **types** — `microservice` (the demo's services) and `database` +(the Kafka broker they use, treated here as a generic backing datastore) — +connected by two real dependencies: checkout → cart (a synchronous call, from the +service graph) and consumer → kafka (a messaging edge, read from consumer spans). +Change the service names and queries to match your own integration and you have a +head start. + +Each component is a **service instance** (one running replica, keyed by +`service.instance.id`) rather than a whole service. Instances are the finest unit +OpenTelemetry reports, so every component drills into one replica's metrics — +and, crucially, relations are drawn instance-to-instance: Kafka's "Used by" panel +shows the specific consuming **instance**. Our instance components share their +identity with the OpenTelemetry StackPack's `otel service instance`, so they +**merge** into one component (rather than creating a duplicate) — this is the +specialization-on-top-of-OTel pattern the callout above describes. + +> **Demo version:** this example was developed against **OpenTelemetry Demo +> v2.2.0**, where the services are named `cart`, `checkout`, `kafka`, etc. Older +> demo versions used `cartservice` / `checkoutservice`; if you run one of those, +> update the `service.name` values in `settings/presentations/cart.sty`, +> `checkout.sty` and the relation mappings accordingly. ## What's Included -This StackPack template comes up with examples for: -- **Monitor Definition**: A copy of `Node Memory Pressure monitor` for Kubernetes nodes -- **Metric Binding**: A copy of MetricBinding for `Memory available for scheduling` for Kubernetes nodes -- **Basic Configuration**: Ready-to-customize stackpack.conf +Almost everything is one connected **webshop** example (component mappings, +relations, presentations, a domain metric and a monitor — all built on the +OpenTelemetry StackPack). A single standalone **Dashboard** ships alongside it and +can be kept or deleted independently. + +**The webshop example — the main tutorial** +| File | Concept | +|------|---------| +| `settings/main-menu.sty` | `MainMenuGroup` — adds a section to the left-hand navigation | +| `settings/component-mappings/microservices.sty` | `OtelComponentMapping` — every demo service **instance** → `microservice` type | +| `settings/component-mappings/database.sty` | `OtelComponentMapping` — kafka **service** → `database` type | +| `settings/relation-mappings/checkout-to-cart.sty` | `OtelRelationMapping` — checkout → cart edge (instance-to-instance) | +| `settings/relation-mappings/consumer-to-database.sty` | `OtelRelationMapping` — consumer instance → kafka edge, read from consumer **spans** (messaging.system=kafka) | +| `settings/presentations/microservice.sty` | Base presentation for all microservice instances — shared list, columns & filters (`rank.specificity` composition) | +| `settings/presentations/cart.sty` | Rich `ComponentPresentation` for cart instances — the full tour, incl. a domain metric | +| `settings/presentations/checkout.sty` | Rich presentation for checkout instances — focuses on what it depends on | +| `settings/presentations/database.sty` | Second-type presentation for the database (service-level; lists connecting instances via related resources) | +| `settings/monitors.sty` | `Monitor` on the cart's **own domain metric** — Deviating when p95 add-to-cart latency > 50 ms, bound to the cart instance (domain metric → monitor → health), with an `!include` remediation hint | +| `icons/microservice.svg`, `database.svg` | Icons referenced via `!icon` | + +The microservices mapping onboards **every** service instance in the demo (any +`service.namespace` containing `telemetry-demo` that carries a +`service.instance.id`). They all share the `microservice` type, so they appear +together in one **Microservices** list. The base presentation +(`microservice.sty`) covers every instance; `cart.sty` and `checkout.sty` bind to +their specific service (by the per-instance `service.name` tag) at a higher +specificity to add richer detail — while keeping the same list, so no extra menu +items appear. Kafka is modelled as a separate `database` type with its own +**Databases** list. Both lists live under the one menu group defined in +`settings/main-menu.sty`. + +The database is deliberately modelled at the **service** level (one Kafka box, +not one per broker replica) — a shared backing store reads better as a single +component. Its callers, however, are **instances**. The two relations use two +different signals, which is itself instructive: + +- **checkout → cart** comes from the **service-graph metric** + (`traces_service_graph_request_total`) — the right source for synchronous + request/response calls. It is instance-to-instance via the graph's + `client_service.instance.id` / `server_service.instance.id`. +- **consumer → kafka** comes from **consumer spans**. Kafka is a messaging + system, so it does *not* appear in the service graph; instead each consumer + emits a span with `messaging.system = kafka`. The mapping reads that span to + draw the consumer *instance* → kafka *service* edge. (It matches on + `messaging.system` alone — the demo's consumer spans are not reliably tagged + with `span.kind = CONSUMER`, so gating on that would match nothing.) + +So Kafka's "Used by" panel surfaces the specific consuming **instances** — the +service-level database reaches instance-level detail one hop away through its +related resources rather than being an instance itself. + +### Inheriting metrics by composition (no duplication) + +Our microservice components deliberately rank **below** the generic OpenTelemetry +mapping, so their primary type stays `otel service instance` (our own type name +survives as the `source-type:<< .Name >> microservice` label). Each of our +presentations then **binds with the generic query as a prefix** — e.g. +`(label = "stackpack:open-telemetry" and type = "otel service instance" and +label = "service.name:cart")`. Because that is a strict subset of the generic +service-instance presentation's binding, presentation composition applies the +generic **span metrics** (rate / errors / duration) to our components for free. +We therefore define **only** our own domain metrics (e.g. the cart's +`add-item-latency`) and inherit the rest — no copy-pasted PromQL to maintain. + +**Standalone example (not part of the webshop)** +- `settings/dashboard.sty` — a `Dashboard` (Pod resources), included only to show + how to ship a dashboard in a StackPack. Delete it if you don't need it. + +> Metrics no longer use a separate `MetricBinding` resource. In StackPacks 2.0 +> metrics are defined inside a `ComponentPresentation` (see the `metrics` block +> and the `summary` / `highlight.metrics` / `metricPerspective` sections in +> `settings/presentations/cart.sty`). + +Every `.sty` file starts with a one-line header comment explaining what it is. +Start with `settings/presentations/cart.sty` — it is the richest example. The +matching reference documentation is linked at the bottom of this README. ## Project Structure ``` << .Name >>/ ├── README.md -├── settings -│ ├── metricbindings.sty -│ ├── monitor.sty -│ └── stackpack.sty -├── resources -│ ├── deprovisioning.md -│ ├── error.md -│ ├── installed.md -│ ├── logo.png -│ ├── notinstalled.md -│ ├── overview.md -│ ├── provisioning.md -│ └── waitingfordata.md -└── stackpack.yaml - -``` - - -## Understanding the Provisioning System - -This StackPack uses SUSE Observability's provisioning system to deploy resources. The key components are: - -### Settings folder - -The `settings/` folder contains all settings that are automatically imported. - -### Adding Your Own Resources - -To add additional resources to your StackPack: - -1. **Create a new .sty file** in the `settings/` directory (e.g., `components.sty`, `monitors.sty`, `metricbindings.sty`) - -2. **Define your resources** using YAML format in the new file: -```yaml -nodes: -- _type: Component - name: My Custom Component - identifier: urn:stackpack:<< .Name >>:component:my-component - # ... other component properties - -- _type: CheckTemplate - name: My Custom Check - identifier: urn:stackpack:<< .Name >>:check:my-check - # ... other check properties +├── stackpack.yaml # StackPack manifest +├── icons/ # SVG icons used by !icon +│ ├── microservice.svg +│ └── database.svg +├── settings/ # Everything here is imported on install +│ ├── main-menu.sty # MainMenuGroup +│ ├── component-mappings/ # OtelComponentMapping (telemetry -> components) +│ │ ├── microservices.sty # demo service instances (except kafka) -> microservice +│ │ └── database.sty # kafka service -> database +│ ├── relation-mappings/ # OtelRelationMapping (edges between components) +│ │ ├── checkout-to-cart.sty # service-graph metric edge +│ │ └── consumer-to-database.sty # consumer-span messaging edge +│ ├── presentations/ # ComponentPresentation (how components look) +│ │ ├── microservice.sty # base for all microservice instances +│ │ ├── cart.sty # rich, cart-specific (the full tour) +│ │ ├── checkout.sty # rich, checkout-specific +│ │ └── database.sty +│ ├── monitors.sty # Monitor on the cart's domain metric +│ └── dashboard.sty # Dashboard (standalone) +├── includes/ # Large values pulled in via !include +│ └── remediation-hints/ +│ └── cart-add-item-latency.md.hbs +└── resources/ # Markdown shown for each lifecycle state + logo + ├── overview.md + ├── installed.md + ├── notinstalled.md + ├── provisioning.md + ├── deprovisioning.md + ├── waitingfordata.md + ├── error.md + └── logo.png ``` -### Supported Resource Types - -You can include various SUSE Observability resource types: -- **Monitors**: Health checks and alerting rules -- **MetricBindings**: Custom charts and visualizations -- **And more**: more resources are coming soon! - -### Extract large values - -Large values can be extracted to their own file instead of being embedded in a settings yaml. These values live as files under the `includes/` folder. -They can be included by using the `!include` yaml tag: - -```yaml -nodes: -- _type: Monitor - remediationHint: !include "remediation-hints/node-memory-pressure.md.hbs" - # ... other monitor properties -``` +## How the presentation example fits together + +The three concepts build on each other — this is the mental model for any +custom integration: + +1. **Mapping** (`component-mappings/`) — a component only exists if a mapping + creates it from incoming telemetry. Each mapping selects some telemetry + (here, by `service.namespace` + `service.instance.id`) and emits a component + with a stable URN and a type. Every demo service instance is a `microservice`, + so many instances share one type. `output.required.additionalIdentifiers` + claims the generic OpenTelemetry service-instance URN so the components merge. +2. **Relation** (`relation-mappings/`) — connects two components into a topology. + The two examples use two signals: `checkout-to-cart.sty` reads the + service-graph **metric** (for the synchronous checkout instance → cart instance + call), while `consumer-to-database.sty` reads consumer **spans** + (`messaging.system = kafka`) to link each consumer instance → the kafka + service. Kafka is not in the service graph, so the span is the only honest + signal — a good illustration that the right source depends on the interaction. +3. **Presentation** (`presentations/`) — binds to a set of components and + describes their columns, detail page, metrics, filters, icon and menu + placement. Presentations **compose two ways**: (a) our own base + `microservice.sty` is refined by `cart.sty` / `checkout.sty` at higher + `rank.specificity`; and (b) because our bindings are a subset of the generic + OpenTelemetry service-instance binding, we **inherit** the generic span metrics + and only add our domain-specific ones. Metric queries are scoped to one + instance with `service_instance_id`. + +Concepts demonstrated across the presentation files: bindings, +`rank` composition, **metric inheritance via binding subset**, icon, filters +(definition & reference), overview name/mainMenu/columns/sort, projection types +(Health, ComponentLink, Text, Tag, Metric), +highlight fields/provisioning/relatedResources/**links**/events, summary metrics, +highlight metrics, metric perspective (tabs → sections), metric definitions with +charts, and a **span-level** relation mapping (the Kafka consumer edge). The +checkout highlight adds a **link** to that instance's traces. + +A few projection types are intentionally **not** used because the webshop's OTel +data has nothing honest to drive them — see the reference docs for +`RatioProjection`, `NumericProjection`, `ContainerImageProjection`, +`MapProjection`, and `DurationProjection`. (`DurationProjection` needs an ISO8601 +start time; an OpenTelemetry service instance carries no portable start timestamp +— `k8s.pod.start_time` only exists on Kubernetes — so we leave it out rather than +ship a column that is blank off-Kubernetes.) + +## Reference documentation + +The StackPacks 2.0 reference docs are the companion to these examples: + +- Presentation concepts: +- Full presentation schema: +- Projections: +- Metrics: +- OpenTelemetry mappings: + +> These reference pages are part of the StackPacks 2.0 documentation and may be +> gated until the 2.0 release ships. ## Customization Guide -### 1. Update StackPack Configuration - -Edit `stackpack.yaml` to customize: +1. **Manifest** — edit `stackpack.yaml`: `displayName`, `categories`, `version`. +2. **Point at your own services** — in `component-mappings/`, change the + `condition` (`service.name == '...'`), the `output.identifier` template, and + the `output.typeName`. Keep the relation mapping's URN templates in sync. +3. **Shape the UI** — edit `presentations/`. Reuse shared columns/filters from + `microservice.sty`; add type-specific columns, metrics and highlight fields per type. +4. **Extract large values** — long strings (like remediation hints) can live in + `includes/` and be pulled in with the `!include` tag, e.g. + `remediationHint: !include "remediation-hints/cart-add-item-latency.md.hbs"`. +5. **Lifecycle messages** — edit `resources/*.md`. -```yaml -name: "<< .Name >>" # Update with your StackPack name -displayName: "<< .Name >>" # Update display name -categories: [ "Test" ] # Update categories -version: "0.0.1" # Set appropriate version -``` +## Development Workflow -### 2. Customize Monitors +```bash +# Validate the StackPack settings +sts stackpack validate -Edit `settings/monitor.sty` or create additional monitor files: +# Package it into a shareable .sts file +sts stackpack package -```yaml -nodes: -- _type: Monitor - name: My Custom Monitor - identifier: urn:stackpack:<< .Name >>:shared:monitor:my-custom-monitor - # ... monitor configuration +# Upload to a test instance of SUSE Observability +sts stackpack upload << .Name >>-0.0.1.sts ``` -### 3. Customize Metric Bindings +The quickest inner loop is `test-deploy`, which validates, bumps a throwaway +version, packages and uploads (installing or upgrading) in one step — handy for +iterating against a dev instance. It is still behind the experimental flag: -Edit `settings/metricbindings.sty` or create additional metric binding files: - -```yaml -nodes: -- _type: MetricBinding - name: My Custom Chart - identifier: urn:stackpack:<< .Name >>:shared:metric-binding:my-chart - # ... metric binding configuration +```bash +STS_EXPERIMENTAL_STACKPACKS=true sts stackpack test-deploy ``` - -### 4. Add Additional Resources - -Create new `.sty` files for other resources in the `settings/` folder. - -### 5. Update Documentation - -- **`resources/*.md`**: Customize messages given to the user during different StackPack states - -## Development Workflow - -### 1. Modify Your StackPack - -### 2. Validate Your StackPack - -### 3. Package the StackPack - -### 4. Upload to the test Instance of SUSE Observability diff --git a/templates/generic/icons/database.svg b/templates/generic/icons/database.svg new file mode 100644 index 0000000..9292aa4 --- /dev/null +++ b/templates/generic/icons/database.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/templates/generic/icons/microservice.svg b/templates/generic/icons/microservice.svg new file mode 100644 index 0000000..0a13673 --- /dev/null +++ b/templates/generic/icons/microservice.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/templates/generic/includes/remediation-hints/cart-add-item-latency.md.hbs b/templates/generic/includes/remediation-hints/cart-add-item-latency.md.hbs new file mode 100644 index 0000000..cdabaa8 --- /dev/null +++ b/templates/generic/includes/remediation-hints/cart-add-item-latency.md.hbs @@ -0,0 +1,6 @@ +## Troubleshooting slow add-to-cart latency + +1. Open the [cart service instance]( /#/components/{{ componentUrnForUrl }} ) and review the Add-to-cart latency chart to see when it started degrading. +2. Check the instance's [traces]( /#/components/{{ componentUrnForUrl }}/traces ) for slow AddItem spans and where the time is spent. +3. Inspect the cart's dependencies in the [topology view]( /#/components/{{ componentUrnForUrl }}/topology ) — a slow backing store (e.g. its cache) often shows up here. +4. Correlate with recent deploys or config changes in the instance [events]( /#/components/{{ componentUrnForUrl }}/events ). diff --git a/templates/generic/includes/remediation-hints/node-memory-pressure.md.hbs b/templates/generic/includes/remediation-hints/node-memory-pressure.md.hbs deleted file mode 100644 index 2865b55..0000000 --- a/templates/generic/includes/remediation-hints/node-memory-pressure.md.hbs +++ /dev/null @@ -1,6 +0,0 @@ -## Troubleshooting Node Memory Pressure - -1. Check the node's current memory usage and pods on the [node highlight page](/#/components/{{ componentUrnForUrl }}) -2. Review running pods and their memory requests/limits in the [topology view](/#/components/{{ componentUrnForUrl }}/topology) -3. Examine recent events and changes in the [node events](/#/components/{{ componentUrnForUrl }}/events) -4. Consider scaling up the node or redistributing workloads to reduce memory pressure diff --git a/templates/generic/resources/overview.md b/templates/generic/resources/overview.md index f28ccbc..f2d2070 100644 --- a/templates/generic/resources/overview.md +++ b/templates/generic/resources/overview.md @@ -1,2 +1,9 @@ -# << .Name >> Stackpack -General information about the StackPack. +# << .Name >> StackPack + +General information about the StackPack, shown on the StackPack overview page. + +This StackPack was scaffolded from the generic tutorial template. It ships a +worked example that gives an OpenTelemetry-observed webshop its own domain view — +custom component types (microservices and a database), relations, tailored UI and +a domain-metric monitor — built on top of the OpenTelemetry StackPack. Replace +this text with a description of what your integration does. diff --git a/templates/generic/settings/component-mappings/database.sty b/templates/generic/settings/component-mappings/database.sty new file mode 100644 index 0000000..f399915 --- /dev/null +++ b/templates/generic/settings/component-mappings/database.sty @@ -0,0 +1,54 @@ +# Component mapping: the Kafka broker -> a service-level database component. +nodes: + - _type: "OtelComponentMapping" + name: "Webshop database" + description: "Turns Kafka broker telemetry into a database component" + identifier: "urn:stackpack:<< .Name >>:otel-component-mapping:database" + input: + signal: + - "TRACES" + - "METRICS" + resource: + action: "'CREATE'" + condition: "resource.attributes['service.name'] == 'kafka'" + vars: + - name: "namespace" + value: >- + resource.attributes['service.namespace'] != '' ? + resource.attributes['service.namespace'] : + 'default' + - name: "service" + value: "resource.attributes['service.name']" + - name: "clusterName" + value: "resource.attributes['k8s.cluster.name']" + output: + identifier: >- + 'urn:stackpack:<< .Name >>:database/' + vars.namespace + '/' + vars.service + name: "vars.service" + typeName: "'<< .Name >> database'" + required: + additionalIdentifiers: + - "'urn:opentelemetry:namespace/' + vars.namespace + ':service/' + vars.service" + tags: + - source: "'<< .Name >>'" + target: "stackpack" + - source: "vars.service" + target: "service.name" + - source: "vars.namespace" + target: "service.namespace" + - source: "vars.clusterName" + target: "cluster-name" + - source: "resource.attributes" + pattern: "telemetry.sdk\\.(.*)" + target: "telemetry.sdk.${1}" + optional: + tags: + - source: "resource.attributes['service.version']" + target: "service.version" + expireAfter: 900000 + # Deliberately BELOW the generic OTel service mapping (specificity 200) so the + # merged component keeps the primary typeName "otel service". That generic type is + # what our presentation binding + span-metric inheritance rely on; our own type + # survives as the "source-type:<< .Name >> database" label. + rank: + specificity: 150 diff --git a/templates/generic/settings/component-mappings/microservices.sty b/templates/generic/settings/component-mappings/microservices.sty new file mode 100644 index 0000000..db841a7 --- /dev/null +++ b/templates/generic/settings/component-mappings/microservices.sty @@ -0,0 +1,65 @@ +# Component mapping: every demo service instance (except kafka) -> microservice. +nodes: + - _type: "OtelComponentMapping" + name: "Webshop microservice instance" + description: "Turns webshop service-instance telemetry into a microservice component" + identifier: "urn:stackpack:<< .Name >>:otel-component-mapping:microservice" + input: + signal: + - "TRACES" + - "METRICS" + resource: + action: "'CREATE'" + condition: >- + 'service.name' in resource.attributes && + 'service.namespace' in resource.attributes && + 'service.instance.id' in resource.attributes && + resource.attributes['service.namespace'].contains('telemetry-demo') && + resource.attributes['service.name'] != 'kafka' + vars: + - name: "namespace" + value: >- + resource.attributes['service.namespace'] != '' ? + resource.attributes['service.namespace'] : + 'default' + - name: "service" + value: "resource.attributes['service.name']" + - name: "instanceId" + value: "resource.attributes['service.instance.id']" + - name: "instanceName" + value: "resource.attributes['service.name'] + ' - ' + resource.attributes['service.instance.id']" + - name: "clusterName" + value: "resource.attributes['k8s.cluster.name']" + output: + identifier: >- + 'urn:stackpack:<< .Name >>:microservice/' + vars.namespace + '/' + vars.service + '/' + vars.instanceId + name: "vars.instanceName" + typeName: "'<< .Name >> microservice'" + required: + additionalIdentifiers: + - "'urn:opentelemetry:namespace/' + vars.namespace + ':service/' + vars.service + ':serviceInstance/' + vars.instanceId" + tags: + - source: "'<< .Name >>'" + target: "stackpack" + - source: "vars.service" + target: "service.name" + - source: "vars.namespace" + target: "service.namespace" + - source: "vars.instanceId" + target: "service.instance.id" + - source: "vars.clusterName" + target: "cluster-name" + - source: "resource.attributes" + pattern: "telemetry.sdk\\.(.*)" + target: "telemetry.sdk.${1}" + optional: + tags: + - source: "resource.attributes['service.version']" + target: "service.version" + expireAfter: 900000 + # Deliberately BELOW the generic OTel service-instance mapping (specificity 210) + # so the merged component keeps the primary typeName "otel service instance". That + # generic type is what our presentation binding + span-metric inheritance rely on; + # our own type survives as the "source-type:<< .Name >> microservice" label. + rank: + specificity: 150 diff --git a/templates/generic/settings/main-menu.sty b/templates/generic/settings/main-menu.sty new file mode 100644 index 0000000..fc651f4 --- /dev/null +++ b/templates/generic/settings/main-menu.sty @@ -0,0 +1,8 @@ +# Main menu group: the left-hand nav section the presentations place their lists into. +nodes: + - _type: MainMenuGroup + name: "<< .Name >>" + iconbase64: !icon microservice.svg + defaultOpen: true + items: [] + identifier: "urn:stackpack:<< .Name >>:main-menu-group:microservices" diff --git a/templates/generic/settings/metricbindings.sty b/templates/generic/settings/metricbindings.sty deleted file mode 100644 index 2303939..0000000 --- a/templates/generic/settings/metricbindings.sty +++ /dev/null @@ -1,16 +0,0 @@ -nodes: -- _type: MetricBinding - chartType: line - enabled: true - identifier: urn:stackpack:<< .Name >>:shared:metric-binding:node-memory-bytes-available-scheduling - layout: - metricPerspective: - section: Resources - tab: Kubernetes Node - name: Memory available for scheduling (Custom) - priority: medium - queries: - - alias: ${cluster_name} - ${node} - expression: max_over_time(kubernetes_state_node_memory_allocatable{cluster_name="${tags.cluster-name}", node="${name}"}[${__interval}]) - scope: (label = "stackpack:kubernetes" and type = "node") - unit: bytes(IEC) diff --git a/templates/generic/settings/monitors.sty b/templates/generic/settings/monitors.sty index 44c0b75..1152fd2 100644 --- a/templates/generic/settings/monitors.sty +++ b/templates/generic/settings/monitors.sty @@ -1,22 +1,31 @@ +# Threshold monitor on the cart's domain metric: DEVIATING when the 95th +# percentile add-to-cart latency exceeds 50ms. Binds to the cart service instance. +# (p95 from the histogram buckets -- alerts on tail latency, not the mean.) nodes: - _type: Monitor function: urn:stackpack:common:monitor-function:threshold arguments: - comparator: LTE + comparator: GT failureState: DEVIATING metric: - aliasTemplate: Node Memory Pressure OK - query: kubernetes_state_node_by_condition{condition="MemoryPressure"} - unit: short - threshold: 0.0 - urnTemplate: urn:kubernetes:/${cluster_name}:node/${node} + aliasTemplate: Add-to-cart latency 95th percentile + query: >- + histogram_quantile(0.95, sum by (service_namespace, service_name, service_instance_id, le) + (rate(app_cart_add_item_latency_seconds_bucket{service_name=~"cart|cartservice"}[5m]))) * 1000 + unit: ms + threshold: 50.0 + titleTemplate: Add-to-cart latency + urnTemplate: "urn:opentelemetry:namespace/${service_namespace}:service/${service_name}:serviceInstance/${service_instance_id}" description: |- - Node memory pressure occurs when memory resources on a Kubernetes node are excessively strained, typically due to misconfigured resource requests/limits for containers or memory-intensive applications. This can lead to performance issues and requires proper resource allocation, monitoring of memory usage, and adjustment of container resource configurations to maintain optimal node performance. - identifier: urn:stackpack:<< .Name >>:shared:monitor:<< .Name >>:node-memory-pressure + Monitors the cart service's own add-to-cart latency (the app_cart_add_item_latency + histogram). When the 95th percentile exceeds the threshold (default 50ms) the + cart service instance goes to a Deviating state. This is a domain-specific health + signal driven by the same metric shown on the cart presentation. + identifier: urn:stackpack:<< .Name >>:shared:monitor:cart-add-item-latency intervalSeconds: 30 - name: Node Memory Pressure (Custom) - remediationHint: !include "remediation-hints/node-memory-pressure.md.hbs" + name: Add-to-cart latency (Custom) + remediationHint: !include "remediation-hints/cart-add-item-latency.md.hbs" status: ENABLED tags: - - nodes - - pressure + - cart + - latency diff --git a/templates/generic/settings/presentations/cart.sty b/templates/generic/settings/presentations/cart.sty new file mode 100644 index 0000000..e5bf572 --- /dev/null +++ b/templates/generic/settings/presentations/cart.sty @@ -0,0 +1,80 @@ +# Rich presentation for cart instances: inherits the shared overview + generic span +# metrics, adds the cart's own domain metric (add-to-cart latency). +nodes: + - _type: ComponentPresentation + name: "Cart presentation" + description: "Rich presentation for the cart microservice" + identifier: "urn:stackpack:<< .Name >>:component-presentation:cart" + binding: + _type: ComponentPresentationQueryBinding + # Matches both demo naming schemes: the newer "cart" and the older "cartservice". + query: (label = "stackpack:open-telemetry" and type = "otel service instance" and label IN ("service.name:cart", "service.name:cartservice")) + # Above the microservice base (210) so cart-specific overrides win; the icon is + # inherited from the base by composition. + rank: + specificity: 220 + presentation: + filters: + - filterId: "namespace" + - filterId: "cluster" + highlight: + title: "Cart instance" + provisioning: + topologySourceSelector: "source.mapping.identifier == 'urn:stackpack:<< .Name >>:otel-component-mapping:microservice'" + showConfiguration: false + showStatus: false + relatedResources: + - resourceId: "callers" + title: "Called by" + order: 1.0 + topologyQuery: '(withNeighborsOf(direction = "up", components = (identifier = "${identifiers[0]}"), levels = "1")) and label = "source-type:<< .Name >> microservice"' + presentationIdentifier: "urn:stackpack:<< .Name >>:component-presentation:microservice-base" + events: + showEvents: true + relatedResourcesQuery: | + identifier = "${identifiers[0]}" + metrics: + - sectionId: cart + title: Cart operations + description: "Business metrics from the cart service itself" + order: 90 + metrics: + - metricId: cart-add-item-latency + order: 100 + summary: + metrics: + - summaryMetricId: cart_add_item_latency_summary + name: Add-to-cart latency + order: 100 + decimalPlaces: 3 + unit: s + query: |- + sum by (service_namespace, service_name) (rate(app_cart_add_item_latency_seconds_sum{service_namespace="${tags.singleValue('service.namespace')}", service_name="${tags.singleValue('service.name')}", service_instance_id="${tags.singleValue('service.instance.id')}"}[${__rate_interval}])) + / sum by (service_namespace, service_name) (rate(app_cart_add_item_latency_seconds_count{service_namespace="${tags.singleValue('service.namespace')}", service_name="${tags.singleValue('service.name')}", service_instance_id="${tags.singleValue('service.instance.id')}"}[${__rate_interval}])) + metricId: cart-add-item-latency + metricPerspective: + tabs: + - tabId: cart + title: Cart operations + order: 20 + sections: + - sectionId: operations + title: Operations + order: 10 + metrics: + - metricId: cart-add-item-latency + metrics: + - metricId: cart-add-item-latency + name: Add-to-cart latency + description: Latency of the cart service's AddItem operation + metricQueries: + - expression: |- + sum by (service_namespace, service_name) (rate(app_cart_add_item_latency_seconds_sum{service_namespace="${tags.singleValue('service.namespace')}", service_name="${tags.singleValue('service.name')}", service_instance_id="${tags.singleValue('service.instance.id')}"}[${__rate_interval}])) + / sum by (service_namespace, service_name) (rate(app_cart_add_item_latency_seconds_count{service_namespace="${tags.singleValue('service.namespace')}", service_name="${tags.singleValue('service.name')}", service_instance_id="${tags.singleValue('service.instance.id')}"}[${__rate_interval}])) + alias: "average" + - expression: |- + histogram_quantile(0.95, sum(rate(app_cart_add_item_latency_seconds_bucket{service_namespace="${tags.singleValue('service.namespace')}", service_name="${tags.singleValue('service.name')}", service_instance_id="${tags.singleValue('service.instance.id')}"}[${__rate_interval}])) by (le)) + alias: "95th percentile" + chart: + _type: TimeSeriesChart + unit: s diff --git a/templates/generic/settings/presentations/checkout.sty b/templates/generic/settings/presentations/checkout.sty new file mode 100644 index 0000000..6e00018 --- /dev/null +++ b/templates/generic/settings/presentations/checkout.sty @@ -0,0 +1,53 @@ +# Presentation for checkout instances: inherits the shared overview + generic span +# metrics; adds a "Depends on" panel and a highlight link to this instance's traces. +nodes: + - _type: ComponentPresentation + name: "Checkout presentation" + description: "Rich presentation for checkout microservice instances" + identifier: "urn:stackpack:<< .Name >>:component-presentation:checkout" + binding: + _type: ComponentPresentationQueryBinding + # Matches both demo naming schemes: the newer "checkout" and the older "checkoutservice". + query: (label = "stackpack:open-telemetry" and type = "otel service instance" and label IN ("service.name:checkout", "service.name:checkoutservice")) + # Above the microservice base (210) so checkout-specific overrides win; the icon + # is inherited from the base by composition. + rank: + specificity: 220 + presentation: + filters: + - filterId: "namespace" + - filterId: "cluster" + highlight: + title: "Checkout instance" + # Most fields (Namespace / Instance ID / Version) are inherited from + # microservice.sty; here we add the Go runtime version to show a + # checkout-specific field pulled straight from an OTel resource tag. + fields: + - fieldId: "runtime" + title: "Runtime version" + order: 60.0 + projection: + _type: TextProjection + value: "tags.singleValue('process.runtime.version')" + provisioning: + topologySourceSelector: "source.mapping.identifier == 'urn:stackpack:<< .Name >>:otel-component-mapping:microservice'" + showConfiguration: false + showStatus: false + relatedResources: + - resourceId: "dependencies" + title: "Depends on" + order: 1.0 + topologyQuery: '(withNeighborsOf(direction = "down", components = (identifier = "${identifiers[0]}"), levels = "1"))' + presentationIdentifier: "urn:stackpack:<< .Name >>:component-presentation:microservice-base" + links: + - linkId: "checkout-traces" + title: "Traces for ${tags.singleValue('service.name')}" + target: "/#/traces?serviceName=${tags.singleValue('service.name')}&serviceInstanceId=${tags.singleValue('service.instance.id')}" + tooltip: "Open the traces explorer for this checkout instance" + filter: "'service.instance.id' in tags" + openInNewTab: true + order: 100 + events: + showEvents: true + relatedResourcesQuery: | + identifier = "${identifiers[0]}" diff --git a/templates/generic/settings/presentations/database.sty b/templates/generic/settings/presentations/database.sty new file mode 100644 index 0000000..6f86e4c --- /dev/null +++ b/templates/generic/settings/presentations/database.sty @@ -0,0 +1,137 @@ +# Presentation for the Kafka database (service-level): own list/menu/icon, a +# "Used by" panel listing the consumer instances, and an incoming-rate metric. +nodes: + - _type: ComponentPresentation + name: "Database presentation" + description: "How the << .DisplayName >> database component is shown in the UI" + identifier: "urn:stackpack:<< .Name >>:component-presentation:database" + binding: + _type: ComponentPresentationQueryBinding + query: (label = "stackpack:open-telemetry" and type = "otel service" and label = "source-type:<< .Name >> database") + # 200-299 is the documented "Application / service" band, so our database + # presentation ranks above the generic OTel base and its icon wins on the node. + # Inheritance is by binding subset, not rank. + rank: + specificity: 210 + presentation: + icon: !icon database.svg + filters: + - filterId: "namespace" + displayName: + singular: "Namespace" + plural: "Namespaces" + filter: + _type: "TagFilter" + tagKey: "service.namespace" + menuSection: "<< .DisplayName >>" + - filterId: "cluster" + displayName: + singular: "Cluster" + plural: "Clusters" + filter: + _type: "TagFilter" + tagKey: "cluster-name" + menuSection: "<< .DisplayName >>" + overview: + name: + singular: "database" + plural: "databases" + title: "Databases" + mainMenu: + group: "<< .Name >>" + order: 80 + columns: + - columnId: health + title: "Health" + projection: + _type: "HealthProjection" + value: "healthState" + - columnId: name + title: "Database" + projection: + _type: "ComponentLinkProjection" + name: "name" + identifier: "identifiers[0]" + - columnId: namespace + title: "Namespace" + projection: + _type: "TextProjection" + value: "tags.singleValue('service.namespace')" + - columnId: cluster + title: "Cluster" + projection: + _type: "TextProjection" + value: "tags.singleValue('cluster-name')" + sort: + - columnId: health + - columnId: name + highlight: + title: "Database" + fields: + - fieldId: "namespace" + title: "Namespace" + order: 90.0 + projection: + _type: TextProjection + value: "tags.singleValue('service.namespace')" + - fieldId: "labels" + title: "Labels" + order: 10.0 + projection: + _type: TagProjection + provisioning: + topologySourceSelector: "source.mapping.identifier == 'urn:stackpack:<< .Name >>:otel-component-mapping:database'" + showConfiguration: false + showStatus: false + relatedResources: + - resourceId: "users" + title: "Used by" + order: 1.0 + topologyQuery: '(withNeighborsOf(direction = "up", components = (identifier = "${identifiers[0]}"), levels = "1")) and label = "source-type:<< .Name >> microservice"' + presentationIdentifier: "urn:stackpack:<< .Name >>:component-presentation:microservice-base" + events: + showEvents: true + relatedResourcesQuery: | + identifier = "${identifiers[0]}" + metrics: + - sectionId: throughput + title: Throughput + description: "Traffic into this database" + order: 100 + metrics: + - metricId: database-incoming-rate + order: 100 + summary: + metrics: + - summaryMetricId: database_incoming_rate_summary + name: Incoming rate + order: 100 + decimalPlaces: 2 + unit: reqps + query: |- + sum by (server_service_namespace, server) (rate(traces_service_graph_request_total{ + server_service_namespace="${tags.singleValue('service.namespace')}", server="${tags.singleValue('service.name')}" + }[${__rate_interval}])) + metricId: database-incoming-rate + metricPerspective: + tabs: + - tabId: throughput + title: Throughput + order: 10 + sections: + - sectionId: requests + title: Requests + order: 10 + metrics: + - metricId: database-incoming-rate + metrics: + - metricId: database-incoming-rate + name: Incoming rate + description: Requests per second into the database, by caller + metricQueries: + - expression: sum by (client_service_namespace, client) (rate(traces_service_graph_request_total{server_service_namespace="${tags.singleValue('service.namespace')}", server="${tags.singleValue('service.name')}"}[${__rate_interval}])) + alias: "from ${client}" + componentIdentifierTemplate: "urn:opentelemetry:namespace/${client_service_namespace}:service/${client}" + chart: + _type: TimeSeriesChart + unit: reqps diff --git a/templates/generic/settings/presentations/microservice.sty b/templates/generic/settings/presentations/microservice.sty new file mode 100644 index 0000000..f120338 --- /dev/null +++ b/templates/generic/settings/presentations/microservice.sty @@ -0,0 +1,118 @@ +# Base presentation for all microservice instances: shared list, columns, filters and +# highlight. Binding = generic OTel service-instance query + our narrowing label, so +# the generic span metrics are inherited by composition (keep it a subset). +nodes: + - _type: ComponentPresentation + name: "Microservices base" + description: "Shared list view, columns and filters for all << .DisplayName >> microservices" + identifier: "urn:stackpack:<< .Name >>:component-presentation:microservice-base" + binding: + _type: ComponentPresentationQueryBinding + query: (label = "stackpack:open-telemetry" and type = "otel service instance" and label = "source-type:<< .Name >> microservice") + # 200-299 is the documented "Application / service" band, so our microservice + # presentation ranks above the generic OTel base and its icon wins on the node. + # Metric/column/filter inheritance is driven by the binding being a subset -- NOT + # by rank -- so ranking above them keeps inheritance. + rank: + specificity: 210 + presentation: + icon: !icon microservice.svg + overview: + name: + singular: "microservice" + plural: "microservices" + title: "Microservices" + mainMenu: + group: "<< .Name >>" + order: 100 + columns: + - columnId: "health" + title: "Health" + projection: + _type: "HealthProjection" + value: "healthState" + - columnId: "name" + title: "Instance" + projection: + _type: "ComponentLinkProjection" + name: "name" + identifier: "identifiers[0]" + - columnId: "service" + title: "Service" + projection: + _type: "TextProjection" + value: "tags.singleValue('service.name')" + - columnId: "language" + title: "Language" + projection: + _type: "TextProjection" + value: "tags.singleValue('telemetry.sdk.language')" + - columnId: "namespace" + title: "Namespace" + projection: + _type: "TextProjection" + value: "tags.singleValue('service.namespace')" + - columnId: "cluster" + title: "Cluster" + projection: + _type: "TextProjection" + value: "tags.singleValue('cluster-name')" + - columnId: "started" + title: "Started" + projection: + _type: "TextProjection" + value: "tags.singleValue('k8s.pod.start_time')" + sort: + - columnId: health + - columnId: name + filters: + - filterId: "namespace" + displayName: + singular: "Namespace" + plural: "Namespaces" + filter: + _type: "TagFilter" + tagKey: "service.namespace" + menuSection: "<< .DisplayName >>" + - filterId: "cluster" + displayName: + singular: "Cluster" + plural: "Clusters" + filter: + _type: "TagFilter" + tagKey: "cluster-name" + menuSection: "<< .DisplayName >>" + highlight: + title: "Microservice instance" + fields: + - fieldId: "service" + title: "Service" + order: 95.0 + projection: + _type: TextProjection + value: "tags.singleValue('service.name')" + - fieldId: "namespace" + title: "Namespace" + order: 90.0 + projection: + _type: TextProjection + value: "tags.singleValue('service.namespace')" + - fieldId: "instance" + title: "Instance ID" + order: 80.0 + projection: + _type: TextProjection + value: "tags.singleValue('service.instance.id')" + - fieldId: "version" + title: "Version" + order: 70.0 + projection: + _type: TextProjection + value: "tags.singleValue('service.version')" + - fieldId: "labels" + title: "Labels" + order: 10.0 + projection: + _type: TagProjection + events: + showEvents: true diff --git a/templates/generic/settings/relation-mappings/checkout-to-cart.sty b/templates/generic/settings/relation-mappings/checkout-to-cart.sty new file mode 100644 index 0000000..fd4eb6f --- /dev/null +++ b/templates/generic/settings/relation-mappings/checkout-to-cart.sty @@ -0,0 +1,55 @@ +# Relation mapping: checkout instance -> cart instance, from the service-graph metric. +nodes: + - _type: "OtelRelationMapping" + name: "Checkout calls Cart" + description: "Connects the checkout service to the cart service it calls" + identifier: "urn:stackpack:<< .Name >>:otel-relation-mapping:checkout-to-cart" + input: + signal: + - "METRICS" + resource: + scope: + condition: "scope.name == 'traces_service_graph'" + metric: + condition: "metric.name == 'traces_service_graph_request_total'" + datapoint: + action: "'CREATE'" + # Matches both demo naming schemes: checkout(service) -> cart(service). + condition: >- + 'client' in datapoint.attributes && + 'server' in datapoint.attributes && + datapoint.attributes['client'] in ['checkout', 'checkoutservice'] && + datapoint.attributes['server'] in ['cart', 'cartservice'] + vars: + - name: "clientService" + value: "datapoint.attributes['client']" + - name: "serverService" + value: "datapoint.attributes['server']" + - name: "clientNamespace" + value: >- + datapoint.attributes['client_service.namespace'] != '' ? + datapoint.attributes['client_service.namespace'] : + 'default' + - name: "serverNamespace" + value: >- + datapoint.attributes['server_service.namespace'] != '' ? + datapoint.attributes['server_service.namespace'] : + 'default' + - name: "clientInstanceId" + value: >- + 'client_service.instance.id' in datapoint.attributes ? + datapoint.attributes['client_service.instance.id'] : + datapoint.attributes['client'] + - name: "serverInstanceId" + value: >- + 'server_service.instance.id' in datapoint.attributes ? + datapoint.attributes['server_service.instance.id'] : + datapoint.attributes['server'] + output: + sourceId: >- + 'urn:stackpack:<< .Name >>:microservice/' + vars.clientNamespace + '/' + vars.clientService + '/' + vars.clientInstanceId + targetId: >- + 'urn:stackpack:<< .Name >>:microservice/' + vars.serverNamespace + '/' + vars.serverService + '/' + vars.serverInstanceId + typeName: "'calls'" + dependencyType: "'CONNECTION'" + expireAfter: 900000 diff --git a/templates/generic/settings/relation-mappings/consumer-to-database.sty b/templates/generic/settings/relation-mappings/consumer-to-database.sty new file mode 100644 index 0000000..f2d40b4 --- /dev/null +++ b/templates/generic/settings/relation-mappings/consumer-to-database.sty @@ -0,0 +1,42 @@ +# Relation mapping: Kafka consumer instance -> kafka database, from consumer spans +# (Kafka is not in the service graph; we match the messaging span instead. Note: +# we intentionally do NOT gate on span.kind == SPAN_KIND_CONSUMER -- the demo's +# consumer spans are not tagged with that kind, so it would match nothing.) +nodes: + - _type: "OtelRelationMapping" + name: "Consumer uses Kafka" + description: "Connects each Kafka consumer instance to the Kafka database" + identifier: "urn:stackpack:<< .Name >>:otel-relation-mapping:consumer-to-database" + input: + signal: + - "TRACES" + resource: + condition: "'service.name' in resource.attributes" + scope: + span: + action: "'CREATE'" + condition: >- + 'messaging.system' in span.attributes && + span.attributes['messaging.system'] == 'kafka' + vars: + - name: "namespace" + value: >- + 'service.namespace' in resource.attributes && + resource.attributes['service.namespace'] != '' ? + resource.attributes['service.namespace'] : + 'default' + - name: "service" + value: "resource.attributes['service.name']" + - name: "instanceId" + value: >- + 'service.instance.id' in resource.attributes ? + resource.attributes['service.instance.id'] : + resource.attributes['service.name'] + output: + sourceId: >- + 'urn:stackpack:<< .Name >>:microservice/' + vars.namespace + '/' + vars.service + '/' + vars.instanceId + targetId: >- + 'urn:stackpack:<< .Name >>:database/' + vars.namespace + '/kafka' + typeName: "'consumes from'" + dependencyType: "'CONNECTION'" + expireAfter: 900000