Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 136 additions & 0 deletions apps/api/src/routes/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import * as Integrations from "@maple/query-engine-integrations"
import { defineQuery } from "@maple/query-engine/registry"
import { Queries as Core } from "@maple/query-engine/registry"
import type {
CloudflareInfraZoneBreakdownRequest,
HostInfraTimeseriesRequest,
CloudflareInfraZoneFacetsRequest,
CloudflareInfraZoneDetailRequest,
ServicePlanetScaleStatsRequest,
Expand All @@ -24,6 +26,7 @@ import type {
WorkloadInfraTimeseriesRequest,
} from "@maple/domain/http"
import {
hostMetricSpec,
nodeMetricSpec,
partitionWindowAround,
podMetricSpec,
Expand Down Expand Up @@ -469,6 +472,133 @@ const cloudflareInfraZoneFacets = defineQuery({
}),
})

// --- hostInfraTimeseries: two query families behind one endpoint ----------
//
// Network reads a counter family, everything else a gauge family, so they are
// separate defs rather than one def with a branch — the row shapes differ and
// the handler maps them differently. Both keep the id "hostInfraTimeseries",
// which is what their spans already report; renaming would break continuity of
// existing telemetry for no gain.

const hostInfraNetworkTimeseries = defineQuery({
id: "hostInfraTimeseries",
profile: "aggregation",
cache: undefined,
compile: (payload: HostInfraTimeseriesRequest, orgId: string) =>
CH.compile(CH.hostNetworkTimeseriesQuery({ hostName: payload.hostName }), {
orgId,
startTime: payload.startTime,
endTime: payload.endTime,
bucketSeconds: payload.bucketSeconds ?? 60,
}),
})

const hostInfraGaugeTimeseries = defineQuery({
id: "hostInfraTimeseries",
profile: "aggregation",
cache: undefined,
compile: (payload: HostInfraTimeseriesRequest, orgId: string) => {
const spec = hostMetricSpec(payload.metric)
return CH.compile(
CH.hostGaugeTimeseriesQuery({
hostName: payload.hostName,
metricName: spec.metricName,
groupByAttributeKey: spec.groupByAttributeKey,
}),
{
orgId,
startTime: payload.startTime,
endTime: payload.endTime,
bucketSeconds: payload.bucketSeconds ?? 60,
},
)
},
})

// --- cloudflareInfraZoneBreakdown: three parallel, then one dependent -----

const zoneBreakdownParams = (payload: CloudflareInfraZoneBreakdownRequest, orgId: string) => ({
orgId,
serviceName: payload.serviceName,
startTime: payload.startTime,
endTime: payload.endTime,
})

const cloudflareInfraZoneBreakdownTotals = defineQuery({
id: "cloudflareInfraZoneBreakdownTotals",
profile: "aggregation",
cache: undefined,
compile: (payload: CloudflareInfraZoneBreakdownRequest, orgId: string) =>
CH.compile(
Integrations.cloudflareZoneBreakdownTotalsSQL(
payload.dimension,
toCloudflareFilters(payload),
payload.limit ?? 100,
),
zoneBreakdownParams(payload, orgId),
{ rowSchema: Integrations.cloudflareZoneBreakdownTotalsRowSchema },
),
})

/**
* Coverage is deliberately UNFILTERED: it answers "what did the poller collect
* here", which the UI needs in order to say "not collected yet" rather than "no
* traffic" for a window that predates the dataset. Do not thread filters in.
*/
const cloudflareInfraZoneBreakdownCoverage = defineQuery({
id: "cloudflareInfraZoneBreakdownCoverage",
profile: "aggregation",
cache: undefined,
compile: (payload: CloudflareInfraZoneBreakdownRequest, orgId: string) =>
CH.compile(
Integrations.cloudflareZoneBreakdownCoverageSQL(payload.dimension),
zoneBreakdownParams(payload, orgId),
{ rowSchema: Integrations.cloudflareZoneBreakdownCoverageRowSchema },
),
})

const cloudflareInfraZoneBreakdownZoneTotal = defineQuery({
id: "cloudflareInfraZoneBreakdownZoneTotal",
profile: "aggregation",
cache: undefined,
compile: (payload: CloudflareInfraZoneBreakdownRequest, orgId: string) =>
CH.compile(
Integrations.cloudflareZoneCountersSQL(toCloudflareFilters(payload)),
zoneBreakdownParams(payload, orgId),
{ rowSchema: Integrations.cloudflareZoneCountersRowSchema },
),
})

/**
* The chart runs AFTER the totals rather than beside them: totals are already
* ranked by requests, so they name the series worth plotting. Without that the
* grouping is unbounded — a zone taking scanner traffic returns a distinct path
* per probe, and the response grows to buckets x thousands of keys. One extra
* round trip over the same warm scan buys a payload that can't blow up.
*
* `topKeys` therefore rides in the PAYLOAD rather than being derived inside
* `compile`: it is the output of a previous query, which a def has no way to
* see. The caller must also skip this entirely when `topKeys` is empty.
*/
const cloudflareInfraZoneBreakdownTimeseries = defineQuery({
id: "cloudflareInfraZoneBreakdownTimeseries",
profile: "aggregation",
cache: undefined,
compile: (
payload: CloudflareInfraZoneBreakdownRequest & { readonly topKeys: ReadonlyArray<string> },
orgId: string,
) =>
CH.compile(
Integrations.cloudflareZoneBreakdownTimeseriesSQL(
payload.dimension,
toCloudflareFilters(payload),
payload.topKeys,
),
{ ...zoneBreakdownParams(payload, orgId), bucketSeconds: payload.bucketSeconds },
{ rowSchema: Integrations.cloudflareZoneBreakdownTimeseriesRowSchema },
),
})

export const Queries = {
...Core,

Expand Down Expand Up @@ -651,4 +781,10 @@ export const Queries = {
planetscaleServiceConnections,
planetscaleServiceStorage,
cloudflareInfraZoneFacets,
hostInfraNetworkTimeseries,
hostInfraGaugeTimeseries,
cloudflareInfraZoneBreakdownTotals,
cloudflareInfraZoneBreakdownCoverage,
cloudflareInfraZoneBreakdownZoneTotal,
cloudflareInfraZoneBreakdownTimeseries,
} as const
48 changes: 48 additions & 0 deletions apps/api/src/routes/query-helpers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as Integrations from "@maple/query-engine-integrations"
import { formatWarehouseDateTime, parseWarehouseDateTime } from "@maple/query-engine"
import type {
HostInfraTimeseriesRequest,
NodeInfraTimeseriesRequest,
PodInfraTimeseriesRequest,
WorkloadInfraTimeseriesRequest,
Expand Down Expand Up @@ -111,3 +112,50 @@ export const workloadMetricSpec = (metric: WorkloadInfraTimeseriesRequest["metri
}
}
}

/**
* Metric name, grouping key, unit and query-family flag for a host metric.
*
* Shared like the pod/node/workload specs: the registry needs `metricName` and
* `isNetwork` to build the query, the handler needs `unit` and
* `groupByAttributeKey` for its response.
*/
export const hostMetricSpec = (metric: HostInfraTimeseriesRequest["metric"]) => {
switch (metric) {
case "cpu":
return {
metricName: "system.cpu.utilization",
groupByAttributeKey: "state",
unit: "percent" as const,
isNetwork: false,
}
case "memory":
return {
metricName: "system.memory.utilization",
groupByAttributeKey: "state",
unit: "percent" as const,
isNetwork: false,
}
case "filesystem":
return {
metricName: "system.filesystem.utilization",
groupByAttributeKey: "mountpoint",
unit: "percent" as const,
isNetwork: false,
}
case "load15":
return {
metricName: "system.cpu.load_average.15m",
groupByAttributeKey: undefined,
unit: "load" as const,
isNetwork: false,
}
case "network":
return {
metricName: "system.network.io",
groupByAttributeKey: "direction",
unit: "bytes_per_second" as const,
isNetwork: true,
}
}
}
Loading
Loading