Skip to content
Open
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
77 changes: 73 additions & 4 deletions apps/api/src/routes/v1/query-engine.http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ import {
TraceId,
SpanId,
} from "@maple/domain/http"
import {
apdexThresholdMsForAppKind,
classifyServiceAppKind,
DEFAULT_APDEX_THRESHOLD_MS,
type ServiceAppKind,
} from "@maple/domain/service-app-kind"
import { Clock, Effect, Match, Option, Schema } from "effect"
import { QueryEngineService } from "@/services/warehouse/QueryEngineService"
import { makeDirectRouteCachePolicy, makeExecuteRawSql } from "@maple/query-engine/runtime"
Expand Down Expand Up @@ -233,22 +239,40 @@ const toServicePlatformRow = (row: CH.ServicePlatformsOutput) => {
const faasName = String(row.faasName ?? "")
const mapleSdkType = String(row.mapleSdkType ?? "")
const processRuntimeName = String(row.processRuntimeName ?? "")
// App-kind signals. Optional on the row so a query compiled before migration
// 0015 (or a cluster that has not applied it) decodes as "no signal".
const telemetrySdkLanguage = String(row.telemetrySdkLanguage ?? "")
const browserPlatform = String(row.browserPlatform ?? "")
const deviceType = String(row.deviceType ?? "")
// cluster.name alone does not prove the service runs in Kubernetes.
const isKubernetes = k8sPodName !== "" || k8sDeploymentName !== ""
// Host infrastructure takes precedence over SDK self-report.
// Host infrastructure takes precedence over SDK self-report. `browser` is
// what Maple's own browser SDK reports (packages/browser); `client` is the
// Effect client SDK.
const platform: "kubernetes" | "cloudflare" | "lambda" | "web" | "unknown" =
cloudPlatform === "cloudflare.workers" || cloudProvider === "cloudflare"
? "cloudflare"
: faasName !== "" || cloudPlatform === "aws_lambda"
? "lambda"
: isKubernetes
? "kubernetes"
: mapleSdkType === "client"
: mapleSdkType === "client" || mapleSdkType === "browser"
? "web"
: "unknown"
return {
serviceName: decodeServiceName(String(row.serviceName ?? "")),
platform,
appKind: classifyServiceAppKind({
browserPlatform,
telemetrySdkLanguage,
mapleSdkType,
deviceType,
cloudPlatform,
cloudProvider,
faasName,
k8sPodName,
k8sDeploymentName,
}),
k8sCluster,
cloudPlatform,
cloudProvider,
Expand Down Expand Up @@ -922,7 +946,7 @@ export const HttpQueryEngineLive = HttpApiBuilder.group(MapleApi, "queryEngine",
// execute-path cache; releases is uncached (mirrors the standalone
// handler); environments is edge-cached on a service-scoped key.
yield* warehouse.warmRoute(tenant)
const [timeseries, releaseRows, environmentRows] = yield* Effect.all(
const [timeseries, releaseRows, environmentRows, appKindRows] = yield* Effect.all(
[
queryEngine.execute(tenant, payload.timeseries),
runQuery(Queries.serviceReleases, tenant, payload),
Expand All @@ -931,9 +955,51 @@ export const HttpQueryEngineLive = HttpApiBuilder.group(MapleApi, "queryEngine",
startTime: payload.startTime,
endTime: payload.endTime,
}),
// What kind of app this is, which is what picks the Apdex
// target below. Runs alongside the rest — it gates only the
// optional override query, not the primary chart.
runQuery(Queries.serviceAppKind, tenant, {
serviceName: payload.serviceName,
startTime: payload.startTime,
endTime: payload.endTime,
}),
],
{ concurrency: 3 },
{ concurrency: 4 },
)

const appKind: ServiceAppKind =
appKindRows.length > 0 ? toServicePlatformRow(appKindRows[0]!).appKind : "unknown"
const apdexThresholdMs = apdexThresholdMsForAppKind(appKind)

// `payload.timeseries` is forwarded untouched so it keeps the
// annual service-overview rollup, whose stored Apdex counters are
// baked at 500 ms (`canUseAnnualServiceOverview` enforces that).
// Threading a different threshold through it would knock
// throughput, latency, AND error rate onto the 30-day raw path for
// the sake of one series — so a non-default target is re-scored by
// this second, narrower query instead.
const apdexOverride =
apdexThresholdMs === DEFAULT_APDEX_THRESHOLD_MS
? undefined
: yield* runQuery(Queries.serviceApdex, tenant, {
serviceName: payload.serviceName,
startTime: payload.startTime,
endTime: payload.endTime,
bucketSeconds:
payload.timeseries.query.kind === "timeseries"
? payload.timeseries.query.bucketSeconds
: payload.releasesBucketSeconds,
apdexThresholdMs,
}).pipe(
Effect.map((rows) =>
rows.map((row) => ({
bucket: String(row.bucket),
apdexScore: Number(row.apdexScore),
totalCount: Number(row.totalCount),
})),
),
)

return new ServiceDetailOverviewResponse({
timeseries,
releases: releaseRows.map((row) => ({
Expand All @@ -945,6 +1011,9 @@ export const HttpQueryEngineLive = HttpApiBuilder.group(MapleApi, "queryEngine",
environments: environmentRows
.map((row) => String(row.environment ?? ""))
.filter((env) => env !== ""),
appKind,
apdexThresholdMs,
...(apdexOverride === undefined ? {} : { apdexOverride }),
})
}),
)
Expand Down
7 changes: 7 additions & 0 deletions apps/cli/src/server/local-schema-history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,11 @@ export const LOCAL_SCHEMA_HISTORY: ReadonlyArray<LocalSchemaHistoryEntry> = Obje
manifestDigest: "826f9363db5dd7722debd0c87a5b74a5b66387f4752abc219d4cc0ce76358a9e",
projectRevision: "27015e7036e9cacaa5156bcc10a3aead96cb4fa2fcb7c615c272c691f2cbf54a",
}),
Object.freeze({
version: 5,
fingerprint: "3099929d42b2ce8b",
digest: "3099929d42b2ce8b18c06a428a8e32a51ce9724300138241110e08a3f09e8193",
manifestDigest: "99d834ae3baab1d0a753f18a96b96a0130bb0af8964a0b119f1f4203e3bc6d0f",
projectRevision: "7637e9d59858fd4d8b7c019e1be7feac249120a6728d7ad4314de1276531677a",
}),
] as const)
2 changes: 1 addition & 1 deletion apps/cli/src/server/local-schema-version.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Increment this value for every structural change to the generated local
// schema. The compatibility manifest and migration registry must be updated in
// the same change before a new value can ship.
export const LOCAL_SCHEMA_VERSION = 4 as const
export const LOCAL_SCHEMA_VERSION = 5 as const
2 changes: 2 additions & 0 deletions apps/cli/src/server/local-store-migrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { legacyToCurrentModule } from "./local-store-migrations/legacy-to-curren
import { v1ToV2ErrorRollupModule } from "./local-store-migrations/v1-to-v2-error-rollup"
import { v2ToV3ServiceMapIngestBridgeModule } from "./local-store-migrations/v2-to-v3-service-map-ingest-bridge"
import { v3ToV4WebEventsModule } from "./local-store-migrations/v3-to-v4-web-events"
import { v4ToV5ServiceAppKindModule } from "./local-store-migrations/v4-to-v5-service-app-kind"
import type {
AnyLocalStoreMigrationModule,
LocalStoreMigration,
Expand Down Expand Up @@ -119,6 +120,7 @@ export const localStoreMigrations: ReadonlyArray<AnyLocalStoreMigrationModule> =
v1ToV2ErrorRollupModule,
v2ToV3ServiceMapIngestBridgeModule,
v3ToV4WebEventsModule,
v4ToV5ServiceAppKindModule,
]

export const validateMigrationRegistry = (
Expand Down
Loading
Loading