Skip to content

feat(proxy): vm_service_map action for VM fleets on a Prometheus datasource - #167

Merged
PrashantBtkl merged 2 commits into
mainfrom
feat/vm-service-map
Oct 14, 2026
Merged

PrashantBtkl merged 2 commits into
mainfrom
feat/vm-service-map

Conversation

@PrashantBtkl

@PrashantBtkl PrashantBtkl commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Description

The server can already read a VM's metrics through the forager, but had no way to ask which VM calls what. This adds a vm_service_map action to the HTTP proxy on a Prometheus datasource: it returns the hosts that reported in a window and, per call, requests, errors, average latency and bytes, built from the node-agent series.

Decision: The forager computes the answer next to the store and returns a small result; the alternative was the server pulling the raw connection series over the relay and joining them itself. The action sends a fixed query set only, so unlike http_request it cannot be used to read anything else from the store.

Look at: pkg/proxy/http/vmservicemap.go — the PromQL set and how errors are decided (HTTP 5xx, failed for other protocols). The wire shape is documented in docs/proxy-modules.md and is what the server reads.

Part of an internal initiative to let the server build a call graph for VM fleets.

Type of change

  • New feature (non-breaking change which adds functionality)

How Has This Been Tested?

  • Unit tests
  • Manual testing (against the live store on the target forager host: pending)
  1. nudgebee-forager servicemap --url http://<prometheus>:9090 --window 15m on a host that can reach a store holding node-agent series: prints hosts (one per reporting VM, with its instance id and addresses) and edges (one per call).
  2. Through the relay, send vm_service_map with the datasource id of the Prometheus datasource and start/end.

Unit tests cover the aggregation, errors/latency, the TCP fallback and the request path against an HTTP store; the sample label sets in them were copied from a live store.

Checklist

  • CLA signed (the CLA bot will prompt on your first PR)
  • make validate passes (fmt + lint + test, -race)
  • Docs updated if the wire shape, config surface, or proxy module behavior changed
Engineering detail
  • Queries run at end over a [window] range: last_over_time for the four host series, increase() for counters, summed by instance, destination, destination_workload_kind, destination_workload_name. instance is the node-agent's machine id (or md5(machine_id + system_uuid) when a system UUID is present), as written by remote-write, so hostname and cloud instance id join through node_info and node_cloud_info on it.
  • Protocols: http, postgres, mysql, redis, memcached, mongo, kafka, cassandra, clickhouse, zookeeper, foundationdb, dubbo. DNS is excluded on purpose (every host calls its resolver). Destinations with no application protocol fall back to TCP connects.
  • Byte counters exist per destination, not per protocol, so they are attached to the edge with the most requests.
  • A same-host call arrives from the agent as the name localhost (kind localhost) with no address, so it cannot be resolved to a specific local service; it still shows as a visible, unresolved edge to localhost rather than being dropped or mislabeled.
  • gRPC failures are counted: they ride on the same HTTP request counter with a status="grpc:<CODE>" label, caught by a second, separate PromQL query since gRPC statuses can't share a single regex with HTTP status codes.
  • Bounds: window at most 24h, at most 5000 edges (truncated is set), response size follows the datasource's max_response_bytes, 8 concurrent queries; the first failing query cancels the rest.
  • Not verified here: a run against the live store from the target forager host. That is the follow-up check before this is deployed.
  • Not in this PR: an error_code for "this store has no node-agent series" (an empty map is returned), and non-AWS cloud labels.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the vm_service_map action on http-proxy Prometheus datasources, allowing the construction of a VM fleet call graph from node-agent series. It adds a standalone servicemap CLI subcommand, documentation, and tests. Feedback on the implementation highlights a potential integer overflow bug in pkg/proxy/http/vmservicemap.go when maxResp is equal to math.MaxInt64, which would cause io.LimitReader to immediately return EOF.

Comment thread pkg/proxy/http/vmservicemap.go Outdated
…source

The server can already read a VM's metrics through the forager but had no way
to ask which service calls which: that needs the node-agent's per-container
connection series joined per host, and pulling the raw series over the relay
to do it is far more than the answer.

Add the vm_service_map action to the http proxy. It runs a fixed set of
queries against the datasource's store and returns the hosts that reported,
the active services on them (systemd units and containers, with image,
application types and listen addresses), and per-call requests, errors,
average latency and bytes between services. A destination that belongs to a
reporting host is resolved to that host and the service listening on the
port. Failures use the same verdict as the Kubernetes runner's service map.
Nothing from the request reaches PromQL.

Also add `nudgebee-forager servicemap --url ...` so the answer can be checked
against a store without a relay.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

@mayankpande88 mayankpande88 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked the query set against the series node-agent actually writes (node-agent @ d64f632). Metric names, group-by labels, histogram _sum/_count, the ok/failed statuses and IP:port destinations for vm/private kinds all line up, and go test -race ./pkg/proxy/http/ passes.

Two things to fix before merge (inline): loopback calls never reach the resolver with the agent's default config, and gRPC failures are not counted as errors. Plus:

  • HTTP error verdict: the PR body says 5xx, code and docs say 4xx/5xx. Please pick one; counting 4xx makes every 404/401 an error.
  • Public repo: the PR body links issues/PRs in a private repo, and the docs name internal components (see inline). Please describe these generically ("the server").

Comment thread pkg/proxy/http/vmservicemap.go Outdated
Comment thread pkg/proxy/http/vmservicemap.go
Comment thread pkg/proxy/http/vmservicemap.go Outdated
Comment thread pkg/proxy/http/vmservicemap.go Outdated
Comment thread pkg/proxy/http/vmservicemap.go
Comment thread pkg/proxy/http/vmservicemap_test.go Outdated
Comment thread docs/proxy-modules.md Outdated
Comment thread docs/proxy-modules.md Outdated
Comment thread cmd/standalone.go
Fix the integer overflow in instantQuerier's read limit when
max_response_bytes equals math.MaxInt64, cancel outstanding queries on
the first error instead of letting up to 24h of them keep running, and
count gRPC failures (status="grpc:<CODE>" on container_http_requests_total)
as HTTP errors since the existing 5xx/failed pattern can't match them.

Drop the loopback-address resolution path: with the agent's default
config a same-host call never carries a loopback IP, it arrives as the
name "localhost/localhost" (kind localhost) with no port to resolve
against a listener. Stop dropping it and instead normalize its kind
explicitly, so the call stays visible as an edge to "localhost" rather
than disappearing or being mislabeled "external".

Revert the failure verdict to 5xx-only (4xx included every 404/401),
and reword the docs and code comments to describe internal components
generically for the public repo.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@PrashantBtkl

Copy link
Copy Markdown
Contributor Author

Pushed c496b45 addressing all review feedback:

  • Integer overflow (gemini-code-assist): fixed with the same clamp-before-increment pattern already used in proxy.go.
  • Loopback edges dropped: removed the IP-literal loopback resolution path (dead code against real agent output) and stopped dropping the localhost/localhost name entirely — it now shows as a visible, unresolved localhost edge instead of disappearing.
  • localhost kind mapped to external: normalizeKind now passes localhost through explicitly.
  • gRPC failures uncounted: added a second query matching status=~"grpc:.*",status!="grpc:OK" on container_http_requests_total, folded into the same edges as http errors, plus a test.
  • HTTP error verdict (4xx vs 5xx): reverted to 5xx-only, matching the PR description; docs updated to match.
  • Public repo internal naming: removed references to the Kubernetes runner and the api-server's vmservicemap package from docs and the PR description, generalized to "the server".
  • instance label comment: corrected to mention the md5(machine_id + system_uuid) fallback.
  • Query cancellation: queryAll now derives a cancelable context and cancels on the first error.
  • Loopback test fixture: rewritten to use the real label set (localhost/localhost, kind localhost) instead of a 127.0.0.1:port address the agent never emits.
  • --user without --password-env: cmdServiceMap now errors instead of silently running unauthenticated.

make validate passes (fmt + lint + test -race).

@PrashantBtkl
PrashantBtkl merged commit 7513e29 into main Oct 14, 2026
7 of 8 checks passed
@PrashantBtkl
PrashantBtkl deleted the feat/vm-service-map branch October 14, 2026 09:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants