Skip to content

DLPX-100030 telegraf: add fluentd HTTP output for 3rd-party metric forwarding - #135

Open
dbshah12 wants to merge 17 commits into
releasefrom
dlpx/pr/dbshah12/dlpx-100030-telegraf-fluentd-output
Open

dbshah12 wants to merge 17 commits into
releasefrom
dlpx/pr/dbshah12/dlpx-100030-telegraf-fluentd-output

Conversation

@dbshah12

@dbshah12 dbshah12 commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Jira

DLPX-100030 — part of epic APIGW-12274

Design

Telegraf → Fluentd Metric Forwarding (Backward-Compat for DCT-Managed Engines) — kept up to date, source of truth for the full field matrix and the review-driven decisions.

Companion app-gate PR: delphix/dlpx-app-gate#5043 (fluentd HTTP source + record_transformer that nests data: {} and the Java passthrough)

Background

Adding a DE to DCT turns off Java analytics on the engine so DCT's own Grafana dashboards aren't fed from two collectors at once. One side effect of that analyticsManager.stop() is that pushInsightMetric — the long-running path that pushed six KPI metrics into Fluentd, from where customers' Splunk / Datadog / Dynatrace integrations picked them up — also stops. Those customers see nothing in their monitoring tools, with no obvious signal that anything changed.

Problem

Telegraf is already running on every engine and already collecting the raw inputs we need. What was missing: an HTTP output to Fluentd, a transform that reshapes the raw Telegraf metrics into the exact event shape customer dashboards were built against, and a way to flip the whole thing on and off from the setup screen.

Solution

The PR adds three files and modifies two.

telegraf/telegraf.outputs.fluentd — HTTP output pointed at localhost:9880. data_format = "splunkmetric" with splunkmetric_hec_routing = true gives us the HEC envelope that app-gate's record_transformer expects. use_batch_format = false is the subtle but important bit: the batched splunkmetric serializer concatenates JSON objects without a delimiter, and Fluentd's @type http JSON parser silently truncates everything after the first object — one POST per metric avoids that landmine entirely. 60s flush, 10s timeout, 2048 metric buffer so a short Fluentd blip doesn't lose data.

telegraf/telegraf.processors.fluentd — Starlark processor. Explicit order = 4 so it runs after the base-config storage_io processors (otherwise the name=total row from estat could land in our accumulators alongside the read + write rows and double-count NFS/iSCSI). Maps six Telegraf measurements to the Java metric names and matches Java's push semantics:

Java metric_name Source Semantics
system.cpu.util.pct inputs.cpu (cpu-total) gauge; last 100 - usage_idle per 60 s
system.net.total.bytes inputs.net sum of per-interface deltas of bytes_recv + bytes_sent over 60 s
system.disk.ops.count inputs.diskio sum of per-device deltas of reads + writes over 60 s
system.nfs.ops.count inputs.execd (estat nfs -jm 10) sum of iops(/s) × 10 across read + write rows over 60 s
system.iscsi.ops.count inputs.execd (estat iscsi -jm 10) same shape as nfs
system.tcp.total.bytes inputs.execd (connstat-stats.sh) sum of per-endpoint deltas of inbytes + outbytes over 60 s

Each compat metric carries a data.* breakdown that gets nested into a Java-shaped data: {} object by the record_transformer in app-gate #5043: 7 fields for CPU, 4 for net, 6 for disk, 6 each for NFS/iSCSI, 3 for TCP — 35 total. systemUniqueId / systemVersion are injected per compat metric (not Telegraf-global, so InfluxDB cardinality is unaffected), with values substituted at config-regen time from env vars the service script exports.

telegraf/perf_fluentd — enable | disable | status CLI installed to /usr/bin/. Manages /etc/telegraf/FLUENTD_ENABLED and restarts delphix-telegraf. Idempotent (no-op when the flag is already in the desired state so we don't bounce the service for no reason), dies loudly on touch failure. Off by default; app-gate wires perf_fluentd enable into the 3rd-party integration lifecycle in #5043.

telegraf/delphix-telegraf-service (modified) — exports DELPHIX_SYSTEM_UUID and DELPHIX_SYSTEM_VERSION from get-system-uuid / get-appliance-version, validates them against a safe char class before envsubst inlines them into the Starlark triple-quoted TOML string (bad chars fall back to "unknown" with a syslog warning rather than failing to boot). Includes the fluentd fragments when the flag is set; the storage_io inputs are now included when either InfluxDB or fluentd is enabled (previously only InfluxDB). Missing-file warnings fire for both outputs symmetrically. Discard fallback only when neither output is configured.

debian/rules (modified) — installs perf_fluentd. The two config fragments are picked up by the existing telegraf/telegraf* glob.

Backward compatibility

The full field matrix is in the design doc §6. In short: metric_name, _value, _time, event: "metric", type: "g", systemUniqueId, systemVersion, 1-event/minute cadence, and the nested data: {} breakdown all match Java's shape. Dataset and capacity events (system.dataset.total.bytes, system.capacity.consumer.size, system.capacity.source.size) flow through unchanged via Java's untouched logMetric / pushInsightMetrics paths — only the KPI pushInsightMetric singular was removed in app-gate.

What customers do lose: TCP per-connection dimensions (data.service, data.localAddress, data.remoteAddress, window sizes, RTT) because the aggregated system.tcp.total.bytes can't preserve them; and performanceMetricsResolution = SECOND (hardcoded 60s bucket). Customer migration cost: Splunk HEC users do nothing (template change ships in #5043); Datadog (×2) and Dynatrace (×1) customers add a 5-line <source @type http port 9880> stanza to their custom Fluentd template.

Review-driven changes

Three Copilot findings fixed in this PR, four more accepted and documented as follow-ups in the design doc §6.

Fixed in the code:

  • Processor ordering (order = 4 on the Starlark block) — Copilot #4409213790
  • TCP data.* cut from 8 fields to 3 — the five I dropped (send/congestion/receive window sizes, RTT, connections) were last-write-wins across endpoints and had no meaningful value at the aggregated level
  • envsubst safe-char guard on UUID / version in the service script

Accepted and documented (not blocking):

  • Idle source last-bucket flush — bucket emission is sample-driven, so a minute of zero activity leaves the final bucket buffered. Impact is a short gap at the start of idle periods, not wrong values. Copilot #4409213817
  • connstat-stats.sh endpoint totals are non-monotonic (socket close shrinks the aggregate), so bytes from a closing session are undercounted. Rare on Delphix workloads; follow-up to make connstat emit true cumulative counters. Copilot #4409213874
  • iops(/s) * 10 loses precision below one op per second (bcchelper does integer division). _value and data.* share the same source so they stay internally consistent. Copilot #4409213850
  • No shell / Starlark test harness in the repo today; manual deploy-and-capture is how this PR was verified. Follow-up to add a debian/tests/ autopkgtest or Actions job that generates the config and runs telegraf --test against fixture input.

Testing

Deployed to ev-dev.dlpxdc.co and dbs-test.dlpxdc.co and captured end-to-end:

  • perf_fluentd enable|disable|status behaves correctly across all state transitions; idempotent reruns are no-ops.
  • Service regenerates telegraf.conf correctly for every flag combination; envsubst substitutes the real UUID/version into the Starlark source; missing-file warnings fire as expected.
  • Captured raw POSTs to :9880 and confirmed each is one JSON object with the full HEC envelope + 35 data.* fields populated.
  • End-to-end through Fluentd into Splunk at qa-splunk.ops.delphix.com, index=suyash_metrics: all six KPI events arrive with correct metric_name, _value, nested data: {}, systemUniqueId, systemVersion. Dataset and capacity events pass through from Java with their flat schema preserved. No null events once the record_transformer fallback landed in #5043.

Live samples for each metric_name are in the design doc §10.

…rwarding

Add [[outputs.http]] stanza (telegraf.outputs.fluentd) that POSTs performance
analytics metrics to the local fluentd HTTP source at
http://localhost:9880/delphix.metrics.platform, where fluentd routes them to
third-party monitoring systems (Splunk, Datadog) via existing
<match delphix.metrics.**> rules.

- namepass: cpu, diskio, net, zfs, estat_nfs, estat_iscsi
- flush_interval: 60s (matches performanceMetricsResolution = MINUTE)
- data_format: json
- Gated on FLUENTD_ENABLED flag file (same pattern as INFLUXDB_ENABLED)

Add perf_fluentd enable|disable CLI to manage the flag and restart Telegraf.

Update delphix-telegraf-service to include the fluentd output when the flag is
set, and extend the STORAGE_IO_INPUTS inclusion guard so estat_nfs/estat_iscsi
are collected whenever either InfluxDB or fluentd is enabled.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@dbshah12
dbshah12 requested review from prakashsurya and sebroy and a balanced review from Copilot and removed request for Copilot October 27, 2026 13:49

This comment was marked as resolved.

dbshah12 and others added 11 commits October 27, 2026 21:13
…luentd

Add json_transformation = "metrics" to strip the {"metrics":[...]} wrapper
and send a top-level array that fluentd emits as individual metric records.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Change 1: Switch fluentd output to data_format=splunkmetric with
splunkmetric_hec_routing=true. Produces one HEC event per metric field
(metric_name, _value, _time) — same structure Java's InsightLogger sent.
Requires app-gate PR #5043 (data_type metric in fluentd match block).

Change 2: Add telegraf.global_tags with systemUniqueId/systemVersion,
included when fluentd is enabled. delphix-telegraf-service exports
DELPHIX_SYSTEM_UUID (get-system-uuid) and DELPHIX_SYSTEM_VERSION
(get-appliance-version) at startup so Telegraf can interpolate them.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…processor

Add processors.starlark (telegraf.processors.fluentd) that maps Telegraf
measurements to Java InsightLogger metric names for fluentd output:

  system.cpu.util.pct   = 100 - cpu.usage_idle (cpu-total)
  system.net.total.bytes = sum(bytes_recv+bytes_sent) across interfaces
  system.disk.ops.count  = sum(reads+writes) across devices
  system.nfs.ops.count   = estat_nfs.read_ops + write_ops
  system.iscsi.ops.count = estat_iscsi.read_ops + write_ops
  system.tcp.total.bytes = sum(inbytes+outbytes) across tcp endpoints

Cross-device aggregation uses Telegraf's built-in state dict with 10s
timestamp rounding to handle sub-second timing differences across devices.
Each metric type keeps its original measurement flowing to InfluxDB; only
the Java-named metrics reach the fluentd output (namepass updated).

Verified on dbs-test.dlpxdc.co: all 4 testable metrics confirmed correct
(nfs/iscsi require active NFS/iSCSI activity, not present on test engine).

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
… findings

Reconciled implementation with Java InsightLogger semantics (confirmed by
reading dlpx-app-gate statcollector source):

- net/diskio/tcp fields in Telegraf are cumulative kernel counters (same
  source as Java's /proc/net/dev etc.); Java computed deltas between consecutive
  polls. The starlark processor now does the same via `state`, keyed per
  interface / device / tcp endpoint. First-sample delta is 0; counter wrap
  guarded to 0.
- cpu usage_idle is already a per-window percentage, keep as gauge.
- estat_nfs/iscsi: fixed field name (iops(/s), not read_ops), multiplied by
  the 10s collection window to get ops-in-window.
- All bucketing moved into starlark (no aggregator plugin) — basicstats would
  have suffixed field names with "_sum", corrupting the splunkmetric-derived
  metric_name. Uses nanosecond timestamps correctly (ts // 60e9).
- systemUniqueId / systemVersion scoped to the compat metrics inside starlark
  via envsubst of DELPHIX_SYSTEM_UUID / DELPHIX_SYSTEM_VERSION — no longer
  Telegraf-global, so InfluxDB cardinality is unaffected (dropped
  telegraf.global_tags).
- Added logger warnings for missing FLUENTD_OUTPUT / FLUENTD_PROCESSOR files
  (asymmetric fallback with InfluxDB path is now symmetric).
- outputs.http: added timeout=10s and metric_buffer_limit=2048 so brief
  fluentd restarts don't lose data.
- perf_fluentd: added status subcommand, idempotency check (no needless
  telegraf restart when flag is already in desired state), explicit die on
  touch failure.
- Added explicit None guards for missing cpu/estat fields (previously
  returned 0, producing spurious 100% CPU or 0-ops metrics).

Verified on dbs-test.dlpxdc.co: exactly one event per Java metric name per
minute, with correct metric_name/_value/systemUniqueId/systemVersion fields
matching Java InsightLogger format.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…rwarding

Covers architecture, user controls, config assembly, metric transform
pipeline, wire format comparison with Java InsightLogger, open questions,
and a two-track stop-gap plan. Key context: DCT-managed engines already
have zero fluentd forwarding (Java disabled on DCT add) — the Telegraf
path is a bug fix for those customers, not a refactor.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Replace the complex multi-subgraph architecture diagram, sequence diagram,
and flowchart with three focused diagrams:
1. Single end-to-end flow (what it does in one picture)
2. On/off switch flow (how enable/disable works)
3. Before/after comparison (what stays the same for customers)

Also corrected "Why Replace It" to lead with DCT context as the real driver.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
83 lines total. Problem → Solution → Data flow diagram → What customers
receive → 3 new files → 5 open questions → recommendation table.
Everything needed for a fast decision, nothing else.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Keep the doc crisp but spell out the end-to-end flow in 5 numbered steps
with context a reader without background can follow. Drop the open
questions section — those are tracked separately.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…onfluence + Slack

Explain the broader initiative (Telegraf as single collector for DCT engine
dashboards), why that initiative disables Java (and silently kills Splunk
forwarding), what we're building to restore it, the backward-compat matrix,
the one acceptable compromise (data sub-counters), and per-customer migration
cost. Links to the DCT performance analytics design page and the Slack thread
where the trade-offs were negotiated with Seb.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…for review

Reference-PRs table at the top of the doc links to the sample implementation
alongside the design, so reviewers can see the exact Starlark transform, CLI,
Fluentd template, and FluentServiceImpl lifecycle wiring that this doc
describes.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@dbshah12
dbshah12 changed the base branch from develop to release October 29, 2026 05:24
…ead)

Design document is now the source-of-truth artefact at:
https://perforce.atlassian.net/wiki/spaces/DLXEN/pages/3722117178

Keeping an in-repo copy risks drift; the Confluence page is linked from the
PR description.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

This comment was marked as resolved.

dbshah12 and others added 4 commits October 29, 2026 12:16
…nt silent event loss

splunkmetric_hec_routing serializes a batch as raw-concatenated JSON objects
with no delimiter or newline. Fluentd's <source @type http> JSON parser
consumes only the first object per POST, silently dropping the rest — ~83%
loss once all six compat metrics are flowing in production.

use_batch_format=false serializes one metric per POST (6 small localhost
requests per minute, negligible overhead) so every event lands.

Verified on dbs-test.dlpxdc.co and ev-dev.dlpxdc.co: each POST body now
contains exactly 1 top-level JSON object, all compat metric_names observed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Each compat metric now carries rich data.* breakdown alongside _value,
closer to Java's pushInsightMetric payload shape:

  system.cpu.util.pct    7 fields: usage_user/system/idle/iowait/
                                     irq/softirq/steal (percentages)
  system.net.total.bytes 4 fields: inBytes, outBytes, inPackets, outPackets
  system.disk.ops.count  6 fields: reads, writes, bytesRead, bytesWritten,
                                     readTime, writeTime
  system.nfs.ops.count   6 fields: readOps, writeOps, read/writeLatency,
                                     read/writeThroughput
  system.iscsi.ops.count 6 fields: same shape as nfs
  system.tcp.total.bytes 8 fields: inBytes, outBytes, retransmittedSegs,
                                     sendWindowSize, congestionWindowSize,
                                     receiveWindowSize, roundTripTime, connections

Note: round() triggers a Telegraf 1.40.2 starlark resolver bug ("undefined:
state" at compile time). We serialise numeric data via plain str() instead,
which is why percentages and rates show full float precision.

Verified end-to-end on ev-dev.dlpxdc.co and dbs-test.dlpxdc.co: processor
loads cleanly, all data.* fields appear in captured HEC events alongside
metric_name/_value/systemUniqueId/systemVersion.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…data.* fields

Rename data.usage_* -> data.* on the cpu compat metric so Splunk reads e.g.
data.idle, data.user, data.system (matching Java's naming convention) instead
of data.usage_idle. State keys (cpu_d_usr etc.) are unchanged.

Verified on ev-dev and dbs-test: processor loads cleanly, HEC fields now
carry data.user / data.system / data.idle / data.iowait / data.irq /
data.softirq / data.steal.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…-write-wins, guard envsubst

Three focused fixes from PR #135 review:

1. Add order = 4 on [[processors.starlark]] so our processor runs AFTER the
   base-config storage_io processors (orders 1-3). Without this, the storage
   name=total filter may race with our estat accumulation, causing read + write
   + total rows all to be summed into nfs/iscsi counts (double-count).

2. Drop 5 TCP data.* fields that were last-write-wins across endpoints
   (sendWindowSize, congestionWindowSize, receiveWindowSize, roundTripTime,
   connections). Values reflected whichever connection happened to be the
   last one processed in the bucket, not any meaningful aggregate. The
   remaining 3 fields (data.inBytes, data.outBytes, data.retransmittedSegs)
   are true per-bucket deltas via cumulative_delta.

3. Validate DELPHIX_SYSTEM_UUID / DELPHIX_SYSTEM_VERSION against a safe char
   set ([A-Za-z0-9._+-]+) before envsubst inlines them into the Starlark
   triple-quoted TOML string. Unsafe values fall back to "unknown" with a
   syslog warning, so the service still starts (metrics tag identity as
   unknown rather than Telegraf failing to parse the config).

Verified end-to-end on ev-dev.dlpxdc.co and dbs-test.dlpxdc.co:
- Both engines load cleanly
- All 6 compat metrics still emitting (system.cpu.util.pct through
  system.tcp.total.bytes)
- TCP now has 3 data.* fields (previously 8); other metrics unchanged
- Identity tags still present with real UUID/version values

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants