diff --git a/apps/cli/src/server/local-store-migrations/steps.ts b/apps/cli/src/server/local-store-migrations/steps.ts index e2ac2f6533..be317a020c 100644 --- a/apps/cli/src/server/local-store-migrations/steps.ts +++ b/apps/cli/src/server/local-store-migrations/steps.ts @@ -114,6 +114,19 @@ const SERVICE_OPERATIONS_HTTP_COLUMNS = [ ["RequestBodyCount", "SimpleAggregateFunction(sum, UInt64)"], ] as const +/** The SLO latency coverage total and eight ladder counters v51 adds to both service-overview rollups. */ +const SERVICE_OVERVIEW_SLO_LATENCY_COLUMNS = [ + ["EstimatedLadderSpanCount", "SimpleAggregateFunction(sum, Float64) DEFAULT 0"], + ["EstimatedUnder50Ms", "SimpleAggregateFunction(sum, Float64) DEFAULT 0"], + ["EstimatedUnder100Ms", "SimpleAggregateFunction(sum, Float64) DEFAULT 0"], + ["EstimatedUnder250Ms", "SimpleAggregateFunction(sum, Float64) DEFAULT 0"], + ["EstimatedUnder500Ms", "SimpleAggregateFunction(sum, Float64) DEFAULT 0"], + ["EstimatedUnder1000Ms", "SimpleAggregateFunction(sum, Float64) DEFAULT 0"], + ["EstimatedUnder2500Ms", "SimpleAggregateFunction(sum, Float64) DEFAULT 0"], + ["EstimatedUnder5000Ms", "SimpleAggregateFunction(sum, Float64) DEFAULT 0"], + ["EstimatedUnder10000Ms", "SimpleAggregateFunction(sum, Float64) DEFAULT 0"], +] as const + export const LOCAL_STORE_STEPS: ReadonlyArray = [ { id: "local-0001-to-0002-error-rollup", @@ -1914,5 +1927,55 @@ export const LOCAL_STORE_STEPS: ReadonlyArray = [ }, ], }, + { + // TODO(v50 -> v51): what changes, and what is NOT backfilled. Fill beforeBootstrap + // with the ADD COLUMN / view drops an IF NOT EXISTS bootstrap cannot do, then the + // plan line and dispositions. The v51 physical verify fails an unfinished row. + // Metadata-only counters on both service-overview rollups; the views are recreated to fill + // them. Existing buckets read 0 and are not backfilled: the hourly tier outlives raw traces. + id: "local-0050-to-0051-service-overview-slo-latency-ladder", + from: 50, + to: 51, + description: + "Add the SLO latency ladder counters to service_overview_hourly and service_overview_minutely and recreate their views", + clonedBefore: "any DDL runs", + beforeBootstrap: [ + addColumns("service_overview_hourly", SERVICE_OVERVIEW_SLO_LATENCY_COLUMNS), + addColumns("service_overview_minutely", SERVICE_OVERVIEW_SLO_LATENCY_COLUMNS), + dropViews("service_overview_hourly_mv", "service_overview_minutely_mv"), + ], + counts: { + group: "rollupRows", + measure: [ + ["hourly", "SELECT toString(count()) AS count FROM service_overview_hourly"], + ["minutely", "SELECT toString(count()) AS count FROM service_overview_minutely"], + ], + checks: [unchangedRowCount("hourly"), unchangedRowCount("minutely")], + }, + plan: [ + [ + "add-service-overview-slo-latency-ladder", + "Add the SLO latency coverage total and eight ladder counters to both service-overview rollups and recreate their views so new buckets count spans under each threshold", + ], + ], + verifies: "Verify the v51 physical schema, retained raw telemetry counts, and the rollup row counts", + dispositions: [ + TRACES_UNDER_REPLACED_VIEWS, + { + name: "service_overview_hourly", + classification: "derived", + disposition: "preserve-exact", + guarantee: + "Nine counter columns are added as metadata-only defaults; no part is rewritten, the row count is verified unchanged, and existing rows read the ladder and its EstimatedLadderSpanCount coverage as zero.", + }, + { + name: "service_overview_minutely", + classification: "derived", + disposition: "preserve-exact", + guarantee: + "Nine counter columns are added as metadata-only defaults; no part is rewritten, the row count is verified unchanged, and existing rows read the ladder and its EstimatedLadderSpanCount coverage as zero.", + }, + ], + }, // local-schema:bump appends the next step above this line. ] diff --git a/apps/cli/src/server/schema-snapshots.ts b/apps/cli/src/server/schema-snapshots.ts index 82807b7cc4..199662db3c 100644 --- a/apps/cli/src/server/schema-snapshots.ts +++ b/apps/cli/src/server/schema-snapshots.ts @@ -48,6 +48,7 @@ import schemaV47Sql from "./schema/local-schema-v47.sql" with { type: "text" } import schemaV48Sql from "./schema/local-schema-v48.sql" with { type: "text" } import schemaV49Sql from "./schema/local-schema-v49.sql" with { type: "text" } import schemaV50Sql from "./schema/local-schema-v50.sql" with { type: "text" } +import schemaV51Sql from "./schema/local-schema-v51.sql" with { type: "text" } import { buildLocalSchemaManifest, type LocalSchemaManifest } from "@maple/local-server/schema-manifest" import { Array as Arr } from "effect" @@ -111,6 +112,7 @@ const SNAPSHOT_SQL: ReadonlyArray = [ schemaV48Sql, schemaV49Sql, schemaV50Sql, + schemaV51Sql, ] export interface LocalSchemaSnapshot { diff --git a/apps/cli/src/server/schema/local-schema-v51.sql b/apps/cli/src/server/schema/local-schema-v51.sql new file mode 100644 index 0000000000..ec8c851409 --- /dev/null +++ b/apps/cli/src/server/schema/local-schema-v51.sql @@ -0,0 +1,2925 @@ +-- This file is generated by scripts/generate-clickhouse-schema-sql.ts +-- Do not edit manually. Run `bun run clickhouse:schema` to regenerate. +-- projectRevision: 2b7126b41d1bab3c0f728acdf90d1eff82b7e47a17f9d9d6ddf8cb90e51173c8 +-- localSchemaVersion: 51 + +CREATE TABLE IF NOT EXISTS ai_crawler_requests ( + OrgId LowCardinality(String), + Timestamp DateTime64(9), + TraceId String, + ServiceName LowCardinality(String), + Crawler LowCardinality(String), + Host LowCardinality(String), + Path String, + HttpStatus UInt16 +) +ENGINE = MergeTree +PARTITION BY toDate(Timestamp) +ORDER BY (OrgId, Timestamp, TraceId) +TTL toDate(Timestamp) + INTERVAL 30 DAY; + +CREATE TABLE IF NOT EXISTS ai_trace_index ( + OrgId LowCardinality(String), + Timestamp DateTime64(9), + TraceId String, + SessionId String, + VendorId LowCardinality(String), + ServiceName LowCardinality(String), + DeploymentEnv LowCardinality(String), + Model LowCardinality(String), + AgentName LowCardinality(String), + ToolName LowCardinality(String), + SpanId String, + ParentSpanId String, + Duration UInt64, + IsError UInt8, + IsLlmCall UInt8, + IsToolCall UInt8, + Tokens Float64, + Cost Float64, + ResponseId String, + VendorVersion LowCardinality(String), + InputTokens Float64, + CacheReadTokens Float64, + CacheWriteTokens Float64, + OutputTokens Float64, + ReasoningTokens Float64, + ErrorType LowCardinality(String), + StatusMessage String, + ToolDescription String, + FailedToolCallResult String, + ErrorFingerprint UInt64 +) +ENGINE = MergeTree +PARTITION BY toDate(Timestamp) +ORDER BY (OrgId, Timestamp, TraceId) +TTL toDate(Timestamp) + INTERVAL 30 DAY; + +CREATE TABLE IF NOT EXISTS alert_checks ( + OrgId LowCardinality(String), + RuleId String, + GroupKey String, + Timestamp DateTime64(3), + Status LowCardinality(String), + SignalType LowCardinality(String), + Comparator LowCardinality(String), + Threshold Float64, + ObservedValue Nullable(Float64), + SampleCount UInt32, + WindowMinutes UInt16, + WindowStart DateTime64(3), + WindowEnd DateTime64(3), + ConsecutiveBreaches UInt16, + ConsecutiveHealthy UInt16, + IncidentId Nullable(String), + IncidentTransition LowCardinality(String), + EvaluationDurationMs UInt32, + ErrorMessage Nullable(String), + ErrorCategory LowCardinality(String), + SkipReason LowCardinality(String) DEFAULT '', + Level LowCardinality(String) DEFAULT '' +) +ENGINE = MergeTree +PARTITION BY toDate(Timestamp) +ORDER BY (OrgId, RuleId, GroupKey, Timestamp) +TTL toDate(Timestamp) + INTERVAL 365 DAY; + +CREATE TABLE IF NOT EXISTS attribute_keys_hourly ( + OrgId LowCardinality(String), + Hour DateTime, + AttributeKey LowCardinality(String), + AttributeScope LowCardinality(String), + UsageCount SimpleAggregateFunction(sum, UInt64) +) +ENGINE = AggregatingMergeTree +PARTITION BY toDate(Hour) +ORDER BY (OrgId, AttributeScope, Hour, AttributeKey) +TTL toDate(Hour) + INTERVAL 90 DAY; + +CREATE TABLE IF NOT EXISTS attribute_values_hourly ( + OrgId LowCardinality(String), + Hour DateTime, + AttributeKey LowCardinality(String), + AttributeValue String, + AttributeScope LowCardinality(String), + UsageCount SimpleAggregateFunction(sum, UInt64) +) +ENGINE = AggregatingMergeTree +PARTITION BY toDate(Hour) +ORDER BY (OrgId, AttributeScope, AttributeKey, Hour, AttributeValue) +TTL toDate(Hour) + INTERVAL 90 DAY; + +CREATE TABLE IF NOT EXISTS audit_log ( + OrgId LowCardinality(String), + Id String, + OccurredAt DateTime64(3), + RecordedAt DateTime64(3), + ActorType LowCardinality(String), + UserId String, + ApiKeyId String, + ActorId String, + ActorLabel String, + AffectedUserId String, + Source LowCardinality(String), + Action LowCardinality(String), + Outcome LowCardinality(String), + DenialReason String, + ResourceType LowCardinality(String), + ResourceId String, + ChangedFields Array(String), + Changes String, + Metadata String, + RequestId String, + OriginIp String, + OriginCountry LowCardinality(String) +) +ENGINE = ReplacingMergeTree +PARTITION BY toYYYYMM(OccurredAt) +ORDER BY (OrgId, OccurredAt, Id) +TTL toDate(OccurredAt) + INTERVAL 2190 DAY; + +CREATE TABLE IF NOT EXISTS ci_jobs ( + OrgId LowCardinality(String), + Timestamp DateTime64(9), + TraceId String, + SpanId String, + Owner LowCardinality(String), + Repository LowCardinality(String), + Pipeline LowCardinality(String), + RunId String, + Attempt UInt16, + Job LowCardinality(String), + Result LowCardinality(String), + StatusCode LowCardinality(String), + Duration UInt64, + QueueMs UInt32, + Url String, + FailureReason String, + RunnerName LowCardinality(String), + RunnerLabels LowCardinality(String) +) +ENGINE = MergeTree +PARTITION BY toYYYYMM(Timestamp) +ORDER BY (OrgId, Timestamp, TraceId) +TTL toDate(Timestamp) + INTERVAL 365 DAY; + +CREATE TABLE IF NOT EXISTS ci_runs ( + OrgId LowCardinality(String), + Timestamp DateTime64(9), + TraceId String, + SpanId String, + Owner LowCardinality(String), + Repository LowCardinality(String), + Pipeline LowCardinality(String), + RunId String, + RunNumber UInt64, + Attempt UInt16, + Result LowCardinality(String), + Duration UInt64, + Url String, + Branch String, + Revision String, + TriggerEvent LowCardinality(String), + Actor LowCardinality(String), + PullRequest UInt32, + WorkflowPath LowCardinality(String) +) +ENGINE = MergeTree +PARTITION BY toYYYYMM(Timestamp) +ORDER BY (OrgId, Timestamp, TraceId) +TTL toDate(Timestamp) + INTERVAL 365 DAY; + +CREATE TABLE IF NOT EXISTS error_events ( + OrgId LowCardinality(String), + Timestamp DateTime, + TraceId String, + SpanId String, + ParentSpanId String DEFAULT '__unset__', + ServiceName LowCardinality(String), + DeploymentEnv LowCardinality(String), + ExceptionType LowCardinality(String), + ExceptionMessage String, + ExceptionStacktrace String, + TopFrame String, + FingerprintHash UInt64, + StatusMessage String, + Duration UInt64, + ErrorLabel String, + ServiceVersion LowCardinality(String), + UserId String DEFAULT '' +) +ENGINE = MergeTree +PARTITION BY toDate(Timestamp) +ORDER BY (OrgId, FingerprintHash, Timestamp) +TTL toDate(Timestamp) + INTERVAL 90 DAY; + +CREATE TABLE IF NOT EXISTS error_events_by_time ( + OrgId LowCardinality(String), + Timestamp DateTime, + TraceId String, + SpanId String, + ParentSpanId String DEFAULT '__unset__', + ServiceName LowCardinality(String), + DeploymentEnv LowCardinality(String), + ExceptionType LowCardinality(String), + ExceptionMessage String, + ExceptionStacktrace String, + TopFrame String, + FingerprintHash UInt64, + StatusMessage String, + Duration UInt64, + ErrorLabel String, + ServiceVersion LowCardinality(String), + UserId String DEFAULT '', + INDEX idx_user_id UserId TYPE bloom_filter GRANULARITY 4 +) +ENGINE = MergeTree +PARTITION BY toDate(Timestamp) +ORDER BY (OrgId, Timestamp, FingerprintHash) +TTL toDate(Timestamp) + INTERVAL 90 DAY; + +CREATE TABLE IF NOT EXISTS error_fingerprint_lineage ( + OrgId LowCardinality(String), + Minute DateTime, + FingerprintHash UInt64, + PreviousFingerprintHash UInt64 +) +ENGINE = AggregatingMergeTree +PARTITION BY toDate(Minute) +ORDER BY (OrgId, Minute, FingerprintHash, PreviousFingerprintHash) +TTL toDate(Minute) + INTERVAL 30 DAY; + +CREATE TABLE IF NOT EXISTS error_fingerprints_minutely ( + OrgId LowCardinality(String), + Minute DateTime, + FingerprintHash UInt64, + ServiceName SimpleAggregateFunction(anyLast, String), + ExceptionType SimpleAggregateFunction(anyLast, String), + ExceptionMessage SimpleAggregateFunction(anyLast, String), + ErrorLabel SimpleAggregateFunction(anyLast, String), + TopFrame SimpleAggregateFunction(anyLast, String), + OccurrenceCount SimpleAggregateFunction(sum, UInt64), + FirstSeen SimpleAggregateFunction(min, DateTime), + LastSeen SimpleAggregateFunction(max, DateTime), + ServiceVersions SimpleAggregateFunction(groupUniqArrayArray, Array(String)) +) +ENGINE = AggregatingMergeTree +PARTITION BY toYYYYMM(Minute) +ORDER BY (OrgId, Minute, FingerprintHash) +TTL toDate(Minute) + INTERVAL 90 DAY; + +CREATE TABLE IF NOT EXISTS identity_links ( + OrgId LowCardinality(String), + VisitorId String, + UserId String, + FirstSeen SimpleAggregateFunction(min, DateTime64(9)) +) +ENGINE = AggregatingMergeTree +PARTITION BY tuple() +ORDER BY (OrgId, VisitorId, UserId) +TTL toDate(FirstSeen) + INTERVAL 365 DAY; + +CREATE TABLE IF NOT EXISTS logs ( + OrgId LowCardinality(String), + Timestamp DateTime64(9), + TimestampTime DateTime, + TraceId String, + SpanId String, + TraceFlags UInt8, + SeverityText LowCardinality(String), + SeverityNumber UInt8, + ServiceName LowCardinality(String), + Body String, + ResourceSchemaUrl String, + ResourceAttributes Map(LowCardinality(String), String), + ScopeSchemaUrl String, + ScopeName String, + ScopeVersion String, + ScopeAttributes Map(LowCardinality(String), String), + LogAttributes Map(LowCardinality(String), String), + ResourceAttributeItems Array(String) DEFAULT arrayMap((k, v) -> concat(k, char(31), v), mapKeys(ResourceAttributes), mapValues(ResourceAttributes)), + ScopeAttributeItems Array(String) DEFAULT arrayMap((k, v) -> concat(k, char(31), v), mapKeys(ScopeAttributes), mapValues(ScopeAttributes)), + LogAttributeItems Array(String) DEFAULT arrayMap((k, v) -> concat(k, char(31), v), mapKeys(LogAttributes), mapValues(LogAttributes)), + INDEX idx_trace_id TraceId TYPE bloom_filter(0.01) GRANULARITY 1, + INDEX idx_resource_attr_keys mapKeys(ResourceAttributes) TYPE bloom_filter(0.01) GRANULARITY 1, + INDEX idx_resource_attr_vals mapValues(ResourceAttributes) TYPE bloom_filter(0.01) GRANULARITY 1, + INDEX idx_scope_attr_keys mapKeys(ScopeAttributes) TYPE bloom_filter(0.01) GRANULARITY 1, + INDEX idx_scope_attr_vals mapValues(ScopeAttributes) TYPE bloom_filter(0.01) GRANULARITY 1, + INDEX idx_log_attr_keys mapKeys(LogAttributes) TYPE bloom_filter(0.01) GRANULARITY 1, + INDEX idx_log_attr_vals mapValues(LogAttributes) TYPE bloom_filter(0.01) GRANULARITY 1, + INDEX idx_lower_body lower(Body) TYPE tokenbf_v1(32768, 3, 0) GRANULARITY 8 +) +ENGINE = MergeTree +PARTITION BY toDate(TimestampTime) +ORDER BY (OrgId, toStartOfFiveMinutes(Timestamp), ServiceName, Timestamp) +TTL toDate(TimestampTime) + INTERVAL 30 DAY; + +CREATE TABLE IF NOT EXISTS logs_aggregates_hourly ( + OrgId LowCardinality(String), + Hour DateTime, + ServiceName LowCardinality(String), + SeverityText LowCardinality(String), + DeploymentEnv LowCardinality(String), + Count SimpleAggregateFunction(sum, UInt64), + SizeBytes SimpleAggregateFunction(sum, UInt64), + ServiceNamespace LowCardinality(String), + INDEX idx_service_namespace ServiceNamespace TYPE set(1000) GRANULARITY 4 +) +ENGINE = AggregatingMergeTree +PARTITION BY toDate(Hour) +ORDER BY (OrgId, Hour, ServiceName, SeverityText, DeploymentEnv, ServiceNamespace) +TTL toDate(Hour) + INTERVAL 365 DAY; + +CREATE TABLE IF NOT EXISTS metric_catalog ( + OrgId LowCardinality(String), + Hour DateTime, + MetricType LowCardinality(String), + ServiceName LowCardinality(String), + MetricName LowCardinality(String), + MetricDescription SimpleAggregateFunction(anyLast, String), + MetricUnit SimpleAggregateFunction(anyLast, String), + IsMonotonic SimpleAggregateFunction(anyLast, UInt8), + DataPointCount SimpleAggregateFunction(sum, UInt64), + FirstSeen SimpleAggregateFunction(min, DateTime), + LastSeen SimpleAggregateFunction(max, DateTime) +) +ENGINE = AggregatingMergeTree +PARTITION BY toDate(Hour) +ORDER BY (OrgId, MetricType, ServiceName, MetricName, Hour) +TTL toDate(Hour) + INTERVAL 90 DAY; + +CREATE TABLE IF NOT EXISTS metrics_exponential_histogram ( + OrgId LowCardinality(String), + ResourceAttributes Map(LowCardinality(String), String), + ResourceSchemaUrl String, + ScopeName String, + ScopeVersion String, + ScopeAttributes Map(LowCardinality(String), String), + ScopeSchemaUrl String, + ServiceName LowCardinality(String), + MetricName LowCardinality(String), + MetricDescription LowCardinality(String), + MetricUnit LowCardinality(String), + Attributes Map(LowCardinality(String), String), + StartTimeUnix DateTime64(9), + TimeUnix DateTime64(9), + Count UInt64, + Sum Float64, + Scale Int32, + ZeroCount UInt64, + PositiveOffset Int32, + PositiveBucketCounts Array(UInt64), + NegativeOffset Int32, + NegativeBucketCounts Array(UInt64), + ExemplarsTraceId Array(String), + ExemplarsSpanId Array(String), + ExemplarsTimestamp Array(DateTime64(9)), + ExemplarsValue Array(Float64), + ExemplarsFilteredAttributes Array(Map(LowCardinality(String), String)), + Flags UInt32, + Min Nullable(Float64), + Max Nullable(Float64), + AggregationTemporality Int32 +) +ENGINE = MergeTree +PARTITION BY toDate(TimeUnix) +ORDER BY (OrgId, ServiceName, MetricName, Attributes, toUnixTimestamp64Nano(TimeUnix)) +TTL toDate(TimeUnix) + INTERVAL 90 DAY; + +CREATE TABLE IF NOT EXISTS metrics_gauge ( + OrgId LowCardinality(String), + ResourceAttributes Map(LowCardinality(String), String), + ResourceSchemaUrl String, + ScopeName String, + ScopeVersion String, + ScopeAttributes Map(LowCardinality(String), String), + ScopeSchemaUrl String, + ServiceName LowCardinality(String), + MetricName LowCardinality(String), + MetricDescription LowCardinality(String), + MetricUnit LowCardinality(String), + Attributes Map(LowCardinality(String), String), + StartTimeUnix DateTime64(9), + TimeUnix DateTime64(9), + Value Float64, + Flags UInt32, + ExemplarsTraceId Array(String), + ExemplarsSpanId Array(String), + ExemplarsTimestamp Array(DateTime64(9)), + ExemplarsValue Array(Float64), + ExemplarsFilteredAttributes Array(Map(LowCardinality(String), String)) +) +ENGINE = MergeTree +PARTITION BY toDate(TimeUnix) +ORDER BY (OrgId, ServiceName, MetricName, Attributes, toUnixTimestamp64Nano(TimeUnix)) +TTL toDate(TimeUnix) + INTERVAL 90 DAY; + +CREATE TABLE IF NOT EXISTS metrics_histogram ( + OrgId LowCardinality(String), + ResourceAttributes Map(LowCardinality(String), String), + ResourceSchemaUrl String, + ScopeName String, + ScopeVersion String, + ScopeAttributes Map(LowCardinality(String), String), + ScopeSchemaUrl String, + ServiceName LowCardinality(String), + MetricName LowCardinality(String), + MetricDescription LowCardinality(String), + MetricUnit LowCardinality(String), + Attributes Map(LowCardinality(String), String), + StartTimeUnix DateTime64(9), + TimeUnix DateTime64(9), + Count UInt64, + Sum Float64, + BucketCounts Array(UInt64), + ExplicitBounds Array(Float64), + ExemplarsTraceId Array(String), + ExemplarsSpanId Array(String), + ExemplarsTimestamp Array(DateTime64(9)), + ExemplarsValue Array(Float64), + ExemplarsFilteredAttributes Array(Map(LowCardinality(String), String)), + Flags UInt32, + Min Nullable(Float64), + Max Nullable(Float64), + AggregationTemporality Int32 +) +ENGINE = MergeTree +PARTITION BY toDate(TimeUnix) +ORDER BY (OrgId, ServiceName, MetricName, Attributes, toUnixTimestamp64Nano(TimeUnix)) +TTL toDate(TimeUnix) + INTERVAL 90 DAY; + +CREATE TABLE IF NOT EXISTS metrics_sum ( + OrgId LowCardinality(String), + ResourceAttributes Map(LowCardinality(String), String), + ResourceSchemaUrl String, + ScopeName String, + ScopeVersion String, + ScopeAttributes Map(LowCardinality(String), String), + ScopeSchemaUrl String, + ServiceName LowCardinality(String), + MetricName LowCardinality(String), + MetricDescription LowCardinality(String), + MetricUnit LowCardinality(String), + Attributes Map(LowCardinality(String), String), + StartTimeUnix DateTime64(9), + TimeUnix DateTime64(9), + Value Float64, + Flags UInt32, + ExemplarsTraceId Array(String), + ExemplarsSpanId Array(String), + ExemplarsTimestamp Array(DateTime64(9)), + ExemplarsValue Array(Float64), + ExemplarsFilteredAttributes Array(Map(LowCardinality(String), String)), + AggregationTemporality Int32, + IsMonotonic Bool +) +ENGINE = MergeTree +PARTITION BY toDate(TimeUnix) +ORDER BY (OrgId, ServiceName, MetricName, Attributes, toUnixTimestamp64Nano(TimeUnix)) +TTL toDate(TimeUnix) + INTERVAL 90 DAY; + +CREATE TABLE IF NOT EXISTS product_events ( + OrgId LowCardinality(String), + Timestamp DateTime64(9), + Source LowCardinality(String) DEFAULT 'browser', + SessionId String DEFAULT '', + Seq UInt32 DEFAULT 0, + VisitorId String DEFAULT '', + UserId String DEFAULT '', + GroupId String DEFAULT '', + Kind LowCardinality(String), + EventName String, + Host LowCardinality(String) DEFAULT '', + PagePath String DEFAULT '', + Url String DEFAULT '', + ServiceName LowCardinality(String) DEFAULT '', + Attributes Map(String, String) DEFAULT map(), + TraceId String DEFAULT '', + SpanId String DEFAULT '', + INDEX idx_event_name EventName TYPE set(64) GRANULARITY 4, + INDEX idx_user_id UserId TYPE bloom_filter GRANULARITY 4, + INDEX idx_trace_id TraceId TYPE bloom_filter GRANULARITY 4 +) +ENGINE = MergeTree +PARTITION BY toDate(Timestamp) +ORDER BY (OrgId, Timestamp, VisitorId, SessionId, Seq) +TTL toDate(Timestamp) + INTERVAL 365 DAY; + +CREATE TABLE IF NOT EXISTS scanner_requests ( + OrgId LowCardinality(String), + Timestamp DateTime64(9), + TraceId String, + ServiceName LowCardinality(String), + Signature LowCardinality(String), + Scanner LowCardinality(String), + Host LowCardinality(String), + Path String, + HttpStatus UInt16, + ClientAddress String, + UserAgent String +) +ENGINE = MergeTree +PARTITION BY toDate(Timestamp) +ORDER BY (OrgId, Timestamp, TraceId) +TTL toDate(Timestamp) + INTERVAL 30 DAY; + +CREATE TABLE IF NOT EXISTS service_address_resolutions_hourly ( + OrgId LowCardinality(String), + Hour DateTime, + SourceService LowCardinality(String), + ParentServerAddress String, + ResolvedTargetService LowCardinality(String), + DeploymentEnv LowCardinality(String) +) +ENGINE = ReplacingMergeTree +PARTITION BY toDate(Hour) +ORDER BY (OrgId, Hour, DeploymentEnv, SourceService, ParentServerAddress, ResolvedTargetService) +TTL toDate(Hour) + INTERVAL 365 DAY; + +CREATE TABLE IF NOT EXISTS service_endpoint_details_hourly ( + OrgId LowCardinality(String), + ServiceName LowCardinality(String), + Hour DateTime, + DeploymentEnv LowCardinality(String), + SpanName String, + HttpStatusCode LowCardinality(String), + ErrorType LowCardinality(String), + SpanCount SimpleAggregateFunction(sum, UInt64), + EstimatedSpanCount SimpleAggregateFunction(sum, Float64), + DurationQuantiles AggregateFunction(quantilesTDigest(0.5, 0.95, 0.99), UInt64), + BodySizeQuantiles AggregateFunction(quantilesTDigestIf(0.5, 0.95, 0.99), UInt64, UInt8), + BodySizeMax SimpleAggregateFunction(max, UInt64), + BodyCount SimpleAggregateFunction(sum, UInt64), + UserAgents AggregateFunction(approx_top_k(10), String), + SlowestDuration SimpleAggregateFunction(max, UInt64), + SlowestTraceId AggregateFunction(argMax, String, UInt64), + SlowestTimestamp AggregateFunction(argMax, DateTime64(9), UInt64), + SlowestBodySize AggregateFunction(argMax, Int64, UInt64) +) +ENGINE = AggregatingMergeTree +PARTITION BY toYYYYMM(Hour) +ORDER BY (OrgId, ServiceName, Hour, DeploymentEnv, SpanName, HttpStatusCode, ErrorType) +TTL toDate(Hour) + INTERVAL 90 DAY; + +CREATE TABLE IF NOT EXISTS service_external_edges_hourly ( + OrgId LowCardinality(String), + Hour DateTime, + ServiceName LowCardinality(String), + TargetType LowCardinality(String), + TargetSystem LowCardinality(String), + TargetName String, + DeploymentEnv LowCardinality(String), + CallCount SimpleAggregateFunction(sum, UInt64), + ErrorCount SimpleAggregateFunction(sum, UInt64), + DurationSumMs SimpleAggregateFunction(sum, Float64), + MaxDurationMs SimpleAggregateFunction(max, Float64), + SampleRateSum SimpleAggregateFunction(sum, Float64), + DurationQuantiles AggregateFunction(quantilesTDigestWeighted(0.5, 0.95), UInt64, UInt32) +) +ENGINE = AggregatingMergeTree +PARTITION BY toDate(Hour) +ORDER BY (OrgId, Hour, DeploymentEnv, ServiceName, TargetType, TargetSystem, TargetName) +TTL toDate(Hour) + INTERVAL 365 DAY; + +CREATE TABLE IF NOT EXISTS service_map_children ( + OrgId LowCardinality(String), + Timestamp DateTime, + TraceId String, + ParentSpanId String, + ServiceName LowCardinality(String), + SpanKind LowCardinality(String), + Duration UInt64, + StatusCode LowCardinality(String), + TraceState String, + DeploymentEnv LowCardinality(String) +) +ENGINE = MergeTree +PARTITION BY toDate(Timestamp) +ORDER BY (OrgId, toStartOfFiveMinutes(Timestamp), TraceId, ParentSpanId) +TTL toDate(Timestamp) + INTERVAL 30 DAY; + +CREATE TABLE IF NOT EXISTS service_map_db_edges_hourly ( + OrgId LowCardinality(String), + Hour DateTime, + ServiceName LowCardinality(String), + DbSystem LowCardinality(String), + DeploymentEnv LowCardinality(String), + CallCount SimpleAggregateFunction(sum, UInt64), + ErrorCount SimpleAggregateFunction(sum, UInt64), + DurationSumMs SimpleAggregateFunction(sum, Float64), + MaxDurationMs SimpleAggregateFunction(max, Float64), + SampledSpanCount SimpleAggregateFunction(sum, UInt64), + UnsampledSpanCount SimpleAggregateFunction(sum, UInt64), + SampleRateSum SimpleAggregateFunction(sum, Float64), + DbNamespace LowCardinality(String), + DurationQuantiles AggregateFunction(quantilesTDigestWeighted(0.5, 0.95), UInt64, UInt32) +) +ENGINE = AggregatingMergeTree +PARTITION BY toDate(Hour) +ORDER BY (OrgId, Hour, DeploymentEnv, ServiceName, DbSystem, DbNamespace) +TTL toDate(Hour) + INTERVAL 365 DAY; + +CREATE TABLE IF NOT EXISTS service_map_db_edges_minutely ( + OrgId LowCardinality(String), + Minute DateTime, + ServiceName LowCardinality(String), + DbSystem LowCardinality(String), + DbNamespace LowCardinality(String), + DeploymentEnv LowCardinality(String), + CallCount SimpleAggregateFunction(sum, UInt64), + ErrorCount SimpleAggregateFunction(sum, UInt64), + DurationSumMs SimpleAggregateFunction(sum, Float64), + MaxDurationMs SimpleAggregateFunction(max, Float64), + SampleRateSum SimpleAggregateFunction(sum, Float64), + DurationQuantiles AggregateFunction(quantilesTDigestWeighted(0.5, 0.95), UInt64, UInt32), + FirstInsertedAt SimpleAggregateFunction(min, DateTime) +) +ENGINE = AggregatingMergeTree +PARTITION BY toDate(Minute) +ORDER BY (OrgId, Minute, DeploymentEnv, ServiceName, DbSystem, DbNamespace) +TTL toDate(Minute) + INTERVAL 30 DAY; + +CREATE TABLE IF NOT EXISTS service_map_db_query_shapes_hourly ( + OrgId LowCardinality(String), + Hour DateTime, + ServiceName LowCardinality(String), + DbSystem LowCardinality(String), + DeploymentEnv LowCardinality(String), + QueryKey String, + QueryLabel SimpleAggregateFunction(any, String), + SampleStatement SimpleAggregateFunction(any, String), + CallCount SimpleAggregateFunction(sum, UInt64), + ErrorCount SimpleAggregateFunction(sum, UInt64), + EstimatedCount SimpleAggregateFunction(sum, Float64), + EstimatedErrorCount SimpleAggregateFunction(sum, Float64), + WeightedDurationSumMs SimpleAggregateFunction(sum, Float64), + DurationQuantiles AggregateFunction(quantilesTDigestWeighted(0.5, 0.95), UInt64, UInt32), + DbNamespace LowCardinality(String) +) +ENGINE = AggregatingMergeTree +PARTITION BY toDate(Hour) +ORDER BY (OrgId, Hour, DeploymentEnv, ServiceName, DbSystem, DbNamespace, QueryKey) +TTL toDate(Hour) + INTERVAL 365 DAY; + +CREATE TABLE IF NOT EXISTS service_map_edges_hourly ( + OrgId LowCardinality(String), + Hour DateTime, + SourceService LowCardinality(String), + TargetService String, + DeploymentEnv LowCardinality(String), + CallCount SimpleAggregateFunction(sum, UInt64), + ErrorCount SimpleAggregateFunction(sum, UInt64), + DurationSumMs SimpleAggregateFunction(sum, Float64), + MaxDurationMs SimpleAggregateFunction(max, Float64), + SampledSpanCount SimpleAggregateFunction(sum, UInt64), + UnsampledSpanCount SimpleAggregateFunction(sum, UInt64), + SampleRateSum SimpleAggregateFunction(sum, Float64) +) +ENGINE = AggregatingMergeTree +PARTITION BY toDate(Hour) +ORDER BY (OrgId, Hour, DeploymentEnv, SourceService, TargetService) +TTL toDate(Hour) + INTERVAL 365 DAY; + +CREATE TABLE IF NOT EXISTS service_map_edges_hourly_ingest ( + OrgId LowCardinality(String), + Hour DateTime, + SourceService LowCardinality(String), + TargetService String, + DeploymentEnv LowCardinality(String), + CallCount UInt64, + ErrorCount UInt64, + DurationSumMs Float64, + MaxDurationMs Float64, + SampledSpanCount UInt64, + UnsampledSpanCount UInt64, + SampleRateSum Float64 +) +ENGINE = Null; + +CREATE TABLE IF NOT EXISTS service_map_spans ( + OrgId LowCardinality(String), + Timestamp DateTime, + TraceId String, + SpanId String, + ParentSpanId String, + ServiceName LowCardinality(String), + SpanKind LowCardinality(String), + Duration UInt64, + StatusCode LowCardinality(String), + TraceState String, + DeploymentEnv LowCardinality(String) +) +ENGINE = MergeTree +PARTITION BY toDate(Timestamp) +ORDER BY (OrgId, toStartOfFiveMinutes(Timestamp), TraceId, SpanId) +TTL toDate(Timestamp) + INTERVAL 30 DAY; + +CREATE TABLE IF NOT EXISTS service_operations_hourly ( + OrgId LowCardinality(String), + Hour DateTime, + ServiceName LowCardinality(String), + DeploymentEnv LowCardinality(String), + SpanName String, + SpanCount SimpleAggregateFunction(sum, UInt64), + EstimatedSpanCount SimpleAggregateFunction(sum, Float64), + ErrorCount SimpleAggregateFunction(sum, UInt64), + EstimatedErrorCount SimpleAggregateFunction(sum, Float64), + DurationSum SimpleAggregateFunction(sum, Float64), + DurationQuantiles AggregateFunction(quantilesTDigest(0.5, 0.95), UInt64), + ClassifiedSpanCount SimpleAggregateFunction(sum, UInt64), + ServerSpanCount SimpleAggregateFunction(sum, UInt64), + RoutedSpanCount SimpleAggregateFunction(sum, UInt64), + HttpClassifiedSpanCount SimpleAggregateFunction(sum, UInt64), + HttpStatusCount SimpleAggregateFunction(sum, UInt64), + Http4xxCount SimpleAggregateFunction(sum, UInt64), + Http5xxCount SimpleAggregateFunction(sum, UInt64), + RequestBodyBytes SimpleAggregateFunction(sum, UInt64), + RequestBodyCount SimpleAggregateFunction(sum, UInt64) +) +ENGINE = AggregatingMergeTree +PARTITION BY toYYYYMM(Hour) +ORDER BY (OrgId, ServiceName, DeploymentEnv, Hour, SpanName) +TTL toDate(Hour) + INTERVAL 365 DAY; + +CREATE TABLE IF NOT EXISTS service_operations_minutely ( + OrgId LowCardinality(String), + Minute DateTime, + ServiceName LowCardinality(String), + DeploymentEnv LowCardinality(String), + SpanName String, + SpanCount SimpleAggregateFunction(sum, UInt64), + EstimatedSpanCount SimpleAggregateFunction(sum, Float64), + ErrorCount SimpleAggregateFunction(sum, UInt64), + EstimatedErrorCount SimpleAggregateFunction(sum, Float64), + DurationSum SimpleAggregateFunction(sum, Float64), + DurationQuantiles AggregateFunction(quantilesTDigest(0.5, 0.95), UInt64), + ClassifiedSpanCount SimpleAggregateFunction(sum, UInt64), + ServerSpanCount SimpleAggregateFunction(sum, UInt64), + RoutedSpanCount SimpleAggregateFunction(sum, UInt64), + HttpClassifiedSpanCount SimpleAggregateFunction(sum, UInt64), + HttpStatusCount SimpleAggregateFunction(sum, UInt64), + Http4xxCount SimpleAggregateFunction(sum, UInt64), + Http5xxCount SimpleAggregateFunction(sum, UInt64), + RequestBodyBytes SimpleAggregateFunction(sum, UInt64), + RequestBodyCount SimpleAggregateFunction(sum, UInt64) +) +ENGINE = AggregatingMergeTree +PARTITION BY toDate(Minute) +ORDER BY (OrgId, ServiceName, DeploymentEnv, Minute, SpanName) +TTL toDate(Minute) + INTERVAL 90 DAY; + +CREATE TABLE IF NOT EXISTS service_overview_hourly ( + OrgId LowCardinality(String), + Hour DateTime, + ServiceName LowCardinality(String), + DeploymentEnv LowCardinality(String), + ServiceNamespace LowCardinality(String), + CommitSha LowCardinality(String), + SpanCount SimpleAggregateFunction(sum, UInt64), + EstimatedSpanCount SimpleAggregateFunction(sum, Float64), + ErrorCount SimpleAggregateFunction(sum, UInt64), + EstimatedErrorCount SimpleAggregateFunction(sum, Float64), + DurationSum SimpleAggregateFunction(sum, Float64), + DurationQuantiles AggregateFunction(quantilesTDigest(0.5, 0.95, 0.99), UInt64), + FirstSeen SimpleAggregateFunction(min, DateTime), + ApdexSatisfiedCount SimpleAggregateFunction(sum, UInt64), + ApdexToleratingCount SimpleAggregateFunction(sum, UInt64), + EstimatedLadderSpanCount SimpleAggregateFunction(sum, Float64) DEFAULT 0, + EstimatedUnder50Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0, + EstimatedUnder100Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0, + EstimatedUnder250Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0, + EstimatedUnder500Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0, + EstimatedUnder1000Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0, + EstimatedUnder2500Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0, + EstimatedUnder5000Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0, + EstimatedUnder10000Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0 +) +ENGINE = AggregatingMergeTree +PARTITION BY toYYYYMM(Hour) +ORDER BY (OrgId, ServiceName, Hour, DeploymentEnv, ServiceNamespace, CommitSha) +TTL toDate(Hour) + INTERVAL 365 DAY; + +CREATE TABLE IF NOT EXISTS service_overview_minutely ( + OrgId LowCardinality(String), + Minute DateTime, + ServiceName LowCardinality(String), + DeploymentEnv LowCardinality(String), + ServiceNamespace LowCardinality(String), + CommitSha LowCardinality(String), + SpanCount SimpleAggregateFunction(sum, UInt64), + EstimatedSpanCount SimpleAggregateFunction(sum, Float64), + ErrorCount SimpleAggregateFunction(sum, UInt64), + EstimatedErrorCount SimpleAggregateFunction(sum, Float64), + DurationSum SimpleAggregateFunction(sum, Float64), + DurationQuantiles AggregateFunction(quantilesTDigest(0.5, 0.95, 0.99), UInt64), + FirstSeen SimpleAggregateFunction(min, DateTime), + ApdexSatisfiedCount SimpleAggregateFunction(sum, UInt64), + ApdexToleratingCount SimpleAggregateFunction(sum, UInt64), + EstimatedLadderSpanCount SimpleAggregateFunction(sum, Float64) DEFAULT 0, + EstimatedUnder50Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0, + EstimatedUnder100Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0, + EstimatedUnder250Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0, + EstimatedUnder500Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0, + EstimatedUnder1000Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0, + EstimatedUnder2500Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0, + EstimatedUnder5000Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0, + EstimatedUnder10000Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0 +) +ENGINE = AggregatingMergeTree +PARTITION BY toDate(Minute) +ORDER BY (OrgId, ServiceName, Minute, DeploymentEnv, ServiceNamespace, CommitSha) +TTL toDate(Minute) + INTERVAL 90 DAY; + +CREATE TABLE IF NOT EXISTS service_overview_spans ( + OrgId LowCardinality(String), + Timestamp DateTime, + ServiceName LowCardinality(String), + Duration UInt64, + StatusCode LowCardinality(String), + TraceState String, + DeploymentEnv LowCardinality(String), + CommitSha LowCardinality(String), + SampleRate Float64 DEFAULT 1, + ServiceNamespace LowCardinality(String), + SpanKind LowCardinality(String) DEFAULT '', + IsRoot UInt8 DEFAULT 0, + INDEX idx_service_namespace ServiceNamespace TYPE set(1000) GRANULARITY 4 +) +ENGINE = MergeTree +PARTITION BY toDate(Timestamp) +ORDER BY (OrgId, ServiceName, Timestamp) +TTL toDate(Timestamp) + INTERVAL 30 DAY; + +CREATE TABLE IF NOT EXISTS service_platforms_hourly ( + OrgId LowCardinality(String), + Hour DateTime, + ServiceName LowCardinality(String), + DeploymentEnv LowCardinality(String), + K8sCluster SimpleAggregateFunction(max, String), + K8sPodName SimpleAggregateFunction(max, String), + K8sDeploymentName SimpleAggregateFunction(max, String), + K8sStatefulSetName SimpleAggregateFunction(max, String), + K8sDaemonSetName SimpleAggregateFunction(max, String), + K8sNamespaceName SimpleAggregateFunction(max, String), + CloudPlatform SimpleAggregateFunction(max, String), + CloudProvider SimpleAggregateFunction(max, String), + FaasName SimpleAggregateFunction(max, String), + MapleSdkType SimpleAggregateFunction(max, String), + ProcessRuntimeName SimpleAggregateFunction(max, String), + SpanCount SimpleAggregateFunction(sum, UInt64) +) +ENGINE = AggregatingMergeTree +PARTITION BY toDate(Hour) +ORDER BY (OrgId, Hour, ServiceName, DeploymentEnv) +TTL toDate(Hour) + INTERVAL 365 DAY; + +CREATE TABLE IF NOT EXISTS service_runtimes_hourly ( + OrgId LowCardinality(String), + Hour DateTime, + ServiceName LowCardinality(String), + DeploymentEnv LowCardinality(String), + RuntimeName LowCardinality(String), + RuntimeVersion LowCardinality(String), + ExecutableName LowCardinality(String), + SdkLanguage LowCardinality(String), + SdkName LowCardinality(String), + SdkVersion LowCardinality(String), + SpanCount UInt64 +) +ENGINE = SummingMergeTree +PARTITION BY toDate(Hour) +ORDER BY (OrgId, Hour, ServiceName, DeploymentEnv, RuntimeName, RuntimeVersion, ExecutableName, SdkLanguage, SdkName, SdkVersion) +TTL toDate(Hour) + INTERVAL 90 DAY; + +CREATE TABLE IF NOT EXISTS service_usage ( + OrgId LowCardinality(String), + ServiceName LowCardinality(String), + Hour DateTime, + LogCount UInt64, + LogSizeBytes UInt64, + TraceCount UInt64, + TraceSizeBytes UInt64, + SumMetricCount UInt64, + SumMetricSizeBytes UInt64, + GaugeMetricCount UInt64, + GaugeMetricSizeBytes UInt64, + HistogramMetricCount UInt64, + HistogramMetricSizeBytes UInt64, + ExpHistogramMetricCount UInt64, + ExpHistogramMetricSizeBytes UInt64 +) +ENGINE = SummingMergeTree +ORDER BY (OrgId, ServiceName, Hour) +TTL Hour + INTERVAL 365 DAY; + +CREATE TABLE IF NOT EXISTS session_events ( + OrgId LowCardinality(String), + SessionId String, + Timestamp DateTime64(9), + Seq UInt32 DEFAULT 0, + Type LowCardinality(String), + Url String DEFAULT '', + TraceId String DEFAULT '', + Level LowCardinality(String) DEFAULT '', + Message String DEFAULT '', + TargetSelector String DEFAULT '', + TargetText String DEFAULT '', + NetMethod LowCardinality(String) DEFAULT '', + NetUrl String DEFAULT '', + NetStatus UInt16 DEFAULT 0, + NetDurationMs UInt32 DEFAULT 0, + ErrorStack String DEFAULT '', + Attributes Map(String, String), + VisitorId String DEFAULT '', + UserId String DEFAULT '', + GroupId String DEFAULT '', + INDEX idx_type Type TYPE set(16) GRANULARITY 4 +) +ENGINE = MergeTree +PARTITION BY toDate(Timestamp) +ORDER BY (OrgId, SessionId, Timestamp, Seq) +TTL toDate(Timestamp) + INTERVAL 30 DAY; + +CREATE TABLE IF NOT EXISTS session_replay_events ( + OrgId LowCardinality(String), + SessionId String, + ChunkSeq UInt32, + Timestamp DateTime64(9), + DurationMs UInt32 DEFAULT 0, + EventCount UInt32 DEFAULT 0, + ByteSize UInt32 DEFAULT 0, + Events String, + IsCheckpoint UInt8 DEFAULT 0 +) +ENGINE = MergeTree +PARTITION BY toDate(Timestamp) +ORDER BY (OrgId, SessionId, ChunkSeq) +TTL toDate(Timestamp) + INTERVAL 30 DAY; + +CREATE TABLE IF NOT EXISTS session_replays ( + OrgId LowCardinality(String), + SessionId String, + StartTime DateTime64(9), + EndTime Nullable(DateTime64(9)), + DurationMs Nullable(UInt32), + Status LowCardinality(String), + UserId String, + UrlInitial String, + UserAgent String, + BrowserName LowCardinality(String), + OsName LowCardinality(String), + DeviceType LowCardinality(String), + Country LowCardinality(String) DEFAULT '', + ServiceName LowCardinality(String), + PageViews UInt32 DEFAULT 0, + ClickCount UInt32 DEFAULT 0, + ErrorCount UInt32 DEFAULT 0, + TraceIds Array(String) DEFAULT [], + ResourceAttributes Map(LowCardinality(String), String), + Version UInt32, + VisitorId String DEFAULT '', + VisitorIsNew UInt8 DEFAULT 0, + UserEmail String DEFAULT '', + UserName String DEFAULT '', + GroupId String DEFAULT '', + GroupName String DEFAULT '', + UserTraits Map(String, String) DEFAULT map(), + Referrer String DEFAULT '', + ReferrerHost LowCardinality(String) DEFAULT '', + UtmSource LowCardinality(String) DEFAULT '', + UtmMedium LowCardinality(String) DEFAULT '', + UtmCampaign LowCardinality(String) DEFAULT '', + UtmTerm String DEFAULT '', + UtmContent String DEFAULT '', + Host LowCardinality(String) DEFAULT '', + EntryPath String DEFAULT '', + ExitPath String DEFAULT '', + Language LowCardinality(String) DEFAULT '', + LastActivityAt Nullable(DateTime64(9)) +) +ENGINE = ReplacingMergeTree +PARTITION BY toDate(StartTime) +ORDER BY (OrgId, SessionId) +TTL toDate(StartTime) + INTERVAL 30 DAY; + +CREATE TABLE IF NOT EXISTS source_usage_hourly ( + OrgId LowCardinality(String), + SourceId String, + Hour DateTime, + SpanCount UInt64, + SpanSizeBytes UInt64, + LogCount UInt64, + LogSizeBytes UInt64, + MetricCount UInt64, + MetricSizeBytes UInt64 +) +ENGINE = SummingMergeTree +ORDER BY (OrgId, SourceId, Hour) +TTL Hour + INTERVAL 90 DAY; + +CREATE TABLE IF NOT EXISTS span_metrics_calls_hourly ( + OrgId LowCardinality(String), + Hour DateTime, + ServiceName LowCardinality(String), + MetricName LowCardinality(String), + SpanKind LowCardinality(String), + AttrFingerprint UInt64, + ResourceFingerprint UInt64, + StartTimeUnix DateTime64(9), + LastValue AggregateFunction(argMax, Float64, DateTime64(9)) +) +ENGINE = AggregatingMergeTree +PARTITION BY toDate(Hour) +ORDER BY (OrgId, Hour, ServiceName, MetricName, SpanKind, AttrFingerprint, ResourceFingerprint, StartTimeUnix) +TTL toDate(Hour) + INTERVAL 90 DAY; + +CREATE TABLE IF NOT EXISTS trace_facets_hourly ( + OrgId LowCardinality(String), + Hour DateTime, + ServiceName LowCardinality(String), + SpanName String, + HttpMethod LowCardinality(String), + HttpStatusCode LowCardinality(String), + DeploymentEnv LowCardinality(String), + ServiceNamespace LowCardinality(String), + HasError UInt8, + TraceCount SimpleAggregateFunction(sum, UInt64), + DurationMin SimpleAggregateFunction(min, UInt64), + DurationMax SimpleAggregateFunction(max, UInt64), + DurationQuantiles AggregateFunction(quantilesTDigest(0.5, 0.95), UInt64) +) +ENGINE = AggregatingMergeTree +PARTITION BY toDate(Hour) +ORDER BY (OrgId, Hour, ServiceName, SpanName, HttpMethod, HttpStatusCode, DeploymentEnv, ServiceNamespace, HasError) +TTL toDate(Hour) + INTERVAL 30 DAY; + +CREATE TABLE IF NOT EXISTS trace_index ( + OrgId LowCardinality(String), + TraceId String, + ServiceName LowCardinality(String), + Bucket DateTime, + MinTimestamp SimpleAggregateFunction(min, DateTime64(9)), + MaxTimestamp SimpleAggregateFunction(max, DateTime64(9)) +) +ENGINE = AggregatingMergeTree +PARTITION BY toDate(Bucket) +ORDER BY (OrgId, TraceId, ServiceName, Bucket) +TTL toDate(Bucket) + INTERVAL 30 DAY; + +CREATE TABLE IF NOT EXISTS trace_list_mv ( + OrgId LowCardinality(String), + TraceId String, + Timestamp DateTime, + ServiceName LowCardinality(String), + SpanName String, + SpanKind LowCardinality(String), + Duration UInt64, + StatusCode LowCardinality(String), + HttpMethod LowCardinality(String), + HttpRoute String, + HttpStatusCode LowCardinality(String), + DeploymentEnv LowCardinality(String), + HasError UInt8, + TraceState String, + ServiceNamespace LowCardinality(String), + INDEX idx_service_namespace ServiceNamespace TYPE set(1000) GRANULARITY 4 +) +ENGINE = MergeTree +PARTITION BY toDate(Timestamp) +ORDER BY (OrgId, Timestamp, TraceId) +TTL toDate(Timestamp) + INTERVAL 30 DAY; + +CREATE TABLE IF NOT EXISTS traces ( + OrgId LowCardinality(String), + Timestamp DateTime64(9), + TraceId String, + SpanId String, + ParentSpanId String, + TraceState String, + SpanName LowCardinality(String), + SpanKind LowCardinality(String), + ServiceName LowCardinality(String), + ResourceSchemaUrl String, + ResourceAttributes Map(LowCardinality(String), String), + ScopeSchemaUrl String, + ScopeName String, + ScopeVersion String, + ScopeAttributes Map(LowCardinality(String), String), + Duration UInt64 DEFAULT 0, + StatusCode LowCardinality(String), + StatusMessage String, + SpanAttributes Map(LowCardinality(String), String), + EventsTimestamp Array(DateTime64(9)), + EventsName Array(LowCardinality(String)), + EventsAttributes Array(Map(LowCardinality(String), String)), + LinksTraceId Array(String), + LinksSpanId Array(String), + LinksTraceState Array(String), + LinksAttributes Array(Map(LowCardinality(String), String)), + SampleRate Float64 DEFAULT multiIf(SpanAttributes['SampleRate'] != '' AND toFloat64OrZero(SpanAttributes['SampleRate']) >= 1.0, toFloat64OrZero(SpanAttributes['SampleRate']), match(TraceState, 'th:[0-9a-f]+'), 1.0 / greatest(1.0 - reinterpretAsUInt64(reverse(unhex(rightPad(extract(TraceState, 'th:([0-9a-f]+)'), 16, '0')))) / pow(2.0, 64), 0.0001), 1.0), + IsEntryPoint UInt8 DEFAULT if(SpanKind IN ('Server', 'Consumer') OR ParentSpanId = '', 1, 0), + ResourceAttributeItems Array(String) DEFAULT arrayMap((k, v) -> concat(k, char(31), v), mapKeys(ResourceAttributes), mapValues(ResourceAttributes)), + ScopeAttributeItems Array(String) DEFAULT arrayMap((k, v) -> concat(k, char(31), v), mapKeys(ScopeAttributes), mapValues(ScopeAttributes)), + SpanAttributeItems Array(String) DEFAULT arrayMap((k, v) -> concat(k, char(31), v), mapKeys(SpanAttributes), mapValues(SpanAttributes)), + INDEX idx_trace_id TraceId TYPE bloom_filter(0.01) GRANULARITY 1, + INDEX idx_span_name SpanName TYPE bloom_filter(0.01) GRANULARITY 1, + INDEX idx_span_attr_keys mapKeys(SpanAttributes) TYPE bloom_filter(0.01) GRANULARITY 1, + INDEX idx_span_attr_vals mapValues(SpanAttributes) TYPE bloom_filter(0.01) GRANULARITY 1, + INDEX idx_resource_attr_keys mapKeys(ResourceAttributes) TYPE bloom_filter(0.01) GRANULARITY 1, + INDEX idx_resource_attr_vals mapValues(ResourceAttributes) TYPE bloom_filter(0.01) GRANULARITY 1, + INDEX idx_scope_attr_keys mapKeys(ScopeAttributes) TYPE bloom_filter(0.01) GRANULARITY 1, + INDEX idx_scope_attr_vals mapValues(ScopeAttributes) TYPE bloom_filter(0.01) GRANULARITY 1 +) +ENGINE = MergeTree +PARTITION BY toDate(Timestamp) +ORDER BY (OrgId, ServiceName, toStartOfFiveMinutes(Timestamp), TraceId) +TTL toDate(Timestamp) + INTERVAL 30 DAY; + +CREATE TABLE IF NOT EXISTS traces_aggregates_hourly ( + OrgId LowCardinality(String), + Hour DateTime, + ServiceName LowCardinality(String), + SpanName LowCardinality(String), + SpanKind LowCardinality(String), + StatusCode LowCardinality(String), + IsEntryPoint UInt8, + DeploymentEnv LowCardinality(String), + WeightedCount SimpleAggregateFunction(sum, Float64), + WeightedDurationSum SimpleAggregateFunction(sum, Float64), + WeightedErrorCount SimpleAggregateFunction(sum, Float64), + DurationQuantiles AggregateFunction(quantilesTDigestWeighted(0.5, 0.95, 0.99), UInt64, UInt32), + DurationMin SimpleAggregateFunction(min, UInt64), + DurationMax SimpleAggregateFunction(max, UInt64) +) +ENGINE = AggregatingMergeTree +PARTITION BY toDate(Hour) +ORDER BY (OrgId, Hour, ServiceName, SpanName, SpanKind, StatusCode, IsEntryPoint, DeploymentEnv) +TTL toDate(Hour) + INTERVAL 365 DAY; + +CREATE TABLE IF NOT EXISTS user_activity_hourly ( + OrgId LowCardinality(String), + ServiceNamespace LowCardinality(String), + UserId String, + Hour DateTime, + ServiceName LowCardinality(String), + SpanCount SimpleAggregateFunction(sum, UInt64), + ErrorSpanCount SimpleAggregateFunction(sum, UInt64), + RequestCount SimpleAggregateFunction(sum, UInt64), + FailedRequestCount SimpleAggregateFunction(sum, UInt64), + TraceIds AggregateFunction(uniqIf, String, UInt8), + SessionIds AggregateFunction(uniqIf, String, UInt8), + RequestDurationQuantiles AggregateFunction(quantilesTDigestIf(0.5, 0.95), UInt64, UInt8), + TopRoutes AggregateFunction(approx_top_k(10), String), + LogCount SimpleAggregateFunction(sum, UInt64), + WarnLogCount SimpleAggregateFunction(sum, UInt64), + ErrorLogCount SimpleAggregateFunction(sum, UInt64), + FirstSeen SimpleAggregateFunction(min, DateTime), + LastSeen SimpleAggregateFunction(max, DateTime), + ClassifiedRequestCount SimpleAggregateFunction(sum, UInt64) DEFAULT 0, + WebRequestCount SimpleAggregateFunction(sum, UInt64) DEFAULT 0, + MobileRequestCount SimpleAggregateFunction(sum, UInt64) DEFAULT 0 +) +ENGINE = AggregatingMergeTree +PARTITION BY toDate(Hour) +ORDER BY (OrgId, ServiceNamespace, UserId, Hour, ServiceName) +TTL toDate(Hour) + INTERVAL 90 DAY; + +CREATE MATERIALIZED VIEW IF NOT EXISTS ai_crawler_requests_mv TO ai_crawler_requests AS +SELECT + OrgId, + Timestamp, + TraceId, + ServiceName, + arrayElement(['GPTBot', 'OAI-SearchBot', 'ChatGPT-User', 'ClaudeBot', 'anthropic-ai', 'Claude-SearchBot', 'Claude-User', 'PerplexityBot', 'Perplexity-User', 'Meta-ExternalAgent', 'Meta-WebIndexer', 'Meta-ExternalFetcher', 'Bytespider', 'MistralAI-User', 'CCBot', 'Amazonbot', 'DuckAssistBot', 'cohere-training-data-crawler', 'cohere-ai'], multiSearchFirstIndexCaseInsensitive(coalesce(nullIf(SpanAttributes['user_agent.original'], ''), nullIf(SpanAttributes['http.user_agent'], ''), ''), ['GPTBot', 'OAI-SearchBot', 'ChatGPT-User', 'ClaudeBot', 'anthropic-ai', 'Claude-SearchBot', 'Claude-User', 'PerplexityBot', 'Perplexity-User', 'meta-externalagent', 'meta-webindexer', 'meta-externalfetcher', 'Bytespider', 'MistralAI-User', 'CCBot', 'Amazonbot', 'DuckAssistBot', 'cohere-training-data-crawler', 'cohere-ai'])) AS Crawler, + lower(replaceRegexpOne(coalesce(nullIf(SpanAttributes['server.address'], ''), nullIf(SpanAttributes['http.host'], ''), nullIf(SpanAttributes['net.host.name'], ''), nullIf(domain(SpanAttributes['url.full']), ''), nullIf(domain(SpanAttributes['http.url']), ''), ''), ':[0-9]+$', '')) AS Host, + leftUTF8(coalesce(nullIf(SpanAttributes['url.path'], ''), nullIf(replaceRegexpOne(SpanAttributes['http.target'], '[?#].*$', ''), ''), nullIf(path(SpanAttributes['url.full']), ''), nullIf(path(SpanAttributes['http.url']), ''), ''), 512) AS Path, + toUInt16OrZero(coalesce(nullIf(SpanAttributes['http.response.status_code'], ''), nullIf(SpanAttributes['http.status_code'], ''), '')) AS HttpStatus + FROM traces + WHERE SpanKind = 'Server' + AND multiSearchFirstIndexCaseInsensitive(coalesce(nullIf(SpanAttributes['user_agent.original'], ''), nullIf(SpanAttributes['http.user_agent'], ''), ''), ['GPTBot', 'OAI-SearchBot', 'ChatGPT-User', 'ClaudeBot', 'anthropic-ai', 'Claude-SearchBot', 'Claude-User', 'PerplexityBot', 'Perplexity-User', 'meta-externalagent', 'meta-webindexer', 'meta-externalfetcher', 'Bytespider', 'MistralAI-User', 'CCBot', 'Amazonbot', 'DuckAssistBot', 'cohere-training-data-crawler', 'cohere-ai']) > 0 + AND leftUTF8(coalesce(nullIf(SpanAttributes['url.path'], ''), nullIf(replaceRegexpOne(SpanAttributes['http.target'], '[?#].*$', ''), ''), nullIf(path(SpanAttributes['url.full']), ''), nullIf(path(SpanAttributes['http.url']), ''), ''), 512) != ''; + +CREATE MATERIALIZED VIEW IF NOT EXISTS ai_trace_index_mv TO ai_trace_index AS +SELECT + OrgId, + Timestamp, + TraceId, + SpanAttributes['maple_ai.session.id'] AS SessionId, + SpanAttributes['maple_ai.vendor.id'] AS VendorId, + ServiceName, + coalesce(nullIf(ResourceAttributes['deployment.environment.name'], ''), ResourceAttributes['deployment.environment']) AS DeploymentEnv, + SpanAttributes['maple_ai.model'] AS Model, + SpanAttributes['maple_ai.agent.name'] AS AgentName, + SpanAttributes['maple_ai.tool.name'] AS ToolName, + SpanId, + ParentSpanId, + Duration, + toUInt8(SpanAttributes['maple_ai.error'] = '1') AS IsError, + toUInt8(SpanAttributes['maple_ai.llm_call'] = '1') AS IsLlmCall, + toUInt8(SpanAttributes['maple_ai.tool_call'] = '1') AS IsToolCall, + toFloat64OrZero(SpanAttributes['maple_ai.usage.input_tokens']) + toFloat64OrZero(SpanAttributes['maple_ai.usage.cache_read_tokens']) + toFloat64OrZero(SpanAttributes['maple_ai.usage.cache_write_tokens']) + toFloat64OrZero(SpanAttributes['maple_ai.usage.output_tokens']) + toFloat64OrZero(SpanAttributes['maple_ai.usage.reasoning_tokens']) AS Tokens, + toFloat64OrZero(SpanAttributes['maple_ai.usage.cost']) AS Cost, + SpanAttributes['maple_ai.response.id'] AS ResponseId, + SpanAttributes['maple_ai.vendor.version'] AS VendorVersion, + toFloat64OrZero(SpanAttributes['maple_ai.usage.input_tokens']) AS InputTokens, + toFloat64OrZero(SpanAttributes['maple_ai.usage.cache_read_tokens']) AS CacheReadTokens, + toFloat64OrZero(SpanAttributes['maple_ai.usage.cache_write_tokens']) AS CacheWriteTokens, + toFloat64OrZero(SpanAttributes['maple_ai.usage.output_tokens']) AS OutputTokens, + toFloat64OrZero(SpanAttributes['maple_ai.usage.reasoning_tokens']) AS ReasoningTokens, + SpanAttributes['error.type'] AS ErrorType, + leftUTF8(StatusMessage, 400) AS StatusMessage, + SpanAttributes['maple_ai.tool.description'] AS ToolDescription, + SpanAttributes['maple_ai.tool.error_result'] AS FailedToolCallResult, + if(SpanAttributes['maple_ai.error'] = '1', cityHash64(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(leftUTF8(coalesce(nullIf(SpanAttributes['maple_ai.tool.error_result'], ''), leftUTF8(StatusMessage, 400)), 400), '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}', 'EMAIL'), 'https?://[^/ )"]+', ''), '/(Users|home)/[^/ ]+', '/~'), '[?][A-Za-z0-9_]+=[^ )"]*', '?#'), '\'[^\' ]*/[^\' ]*\'|\'[^\' ]{25,}\'', '\'#\''), '"[^" ]*/[^" ]*"|"[^" ]{25,}"', '"#"'), '`[^` ]*/[^` ]*`|`[^` ]{25,}`', '`#`'), '[0-9a-fA-F-]{6,}|[0-9]+', '#')), 0) AS ErrorFingerprint + FROM traces + WHERE SpanAttributes['maple_ai.vendor.id'] != ''; + +CREATE MATERIALIZED VIEW IF NOT EXISTS ci_jobs_mv TO ci_jobs AS +SELECT + OrgId, + Timestamp, + TraceId, + SpanId, + ResourceAttributes['vcs.owner.name'] AS Owner, + ResourceAttributes['vcs.repository.name'] AS Repository, + SpanAttributes['cicd.pipeline.name'] AS Pipeline, + SpanAttributes['cicd.pipeline.run.id'] AS RunId, + toUInt16OrZero(SpanAttributes['maple.cicd.run_attempt']) AS Attempt, + SpanAttributes['cicd.pipeline.task.name'] AS Job, + SpanAttributes['cicd.pipeline.task.run.result'] AS Result, + StatusCode, + Duration, + toUInt32OrZero(SpanAttributes['maple.cicd.queue_duration_ms']) AS QueueMs, + SpanAttributes['cicd.pipeline.task.run.url.full'] AS Url, + SpanAttributes['maple.cicd.failure.reason'] AS FailureReason, + SpanAttributes['cicd.worker.name'] AS RunnerName, + SpanAttributes['maple.cicd.runner_labels'] AS RunnerLabels + FROM traces + WHERE ServiceName = 'github-actions' + AND ParentSpanId != '' + AND SpanAttributes['cicd.pipeline.task.name'] != '' + AND SpanAttributes['maple.cicd.step.number'] = ''; + +CREATE MATERIALIZED VIEW IF NOT EXISTS ci_runs_mv TO ci_runs AS +SELECT + OrgId, + Timestamp, + TraceId, + SpanId, + ResourceAttributes['vcs.owner.name'] AS Owner, + ResourceAttributes['vcs.repository.name'] AS Repository, + SpanAttributes['cicd.pipeline.name'] AS Pipeline, + SpanAttributes['cicd.pipeline.run.id'] AS RunId, + toUInt64OrZero(SpanAttributes['maple.cicd.run_number']) AS RunNumber, + toUInt16OrZero(SpanAttributes['maple.cicd.run_attempt']) AS Attempt, + SpanAttributes['cicd.pipeline.result'] AS Result, + Duration, + SpanAttributes['cicd.pipeline.run.url.full'] AS Url, + SpanAttributes['vcs.ref.head.name'] AS Branch, + SpanAttributes['vcs.ref.head.revision'] AS Revision, + SpanAttributes['maple.cicd.trigger_event'] AS TriggerEvent, + SpanAttributes['maple.cicd.actor'] AS Actor, + toUInt32OrZero(SpanAttributes['vcs.change.id']) AS PullRequest, + SpanAttributes['maple.cicd.workflow_path'] AS WorkflowPath + FROM traces + WHERE ServiceName = 'github-actions' + AND ParentSpanId = ''; + +CREATE MATERIALIZED VIEW IF NOT EXISTS error_events_by_time_mv TO error_events_by_time AS +WITH + arrayFirstIndex(n -> n = 'exception', EventsName) AS _ei, + -- Only fill the old Unknown Error bucket. Event values (including + -- empty fields) and spans with StatusMessage keep every hash input. + _ei = 0 AND StatusMessage = '' AS _useAttrs, + if( + _ei > 0, EventsAttributes[_ei]['exception.type'], + if(_useAttrs, coalesce(nullIf(SpanAttributes['exception.type'], ''), SpanAttributes['error.type']), '') + ) AS _exType, + if( + _ei > 0, EventsAttributes[_ei]['exception.message'], + if(_useAttrs, coalesce(nullIf(SpanAttributes['exception.message'], ''), SpanAttributes['error.message']), StatusMessage) + ) AS _exMsg, + if( + _ei > 0, EventsAttributes[_ei]['exception.stacktrace'], + if(_useAttrs, SpanAttributes['exception.stacktrace'], '') + ) AS _exStack, + if(_useAttrs, _exMsg, StatusMessage) AS _msgText, + -- A user key replaces type, frames, message and salt in the hash: the + -- exception event's value first, then the span's. Trimmed and capped. + substringUTF8( + replaceRegexpAll(coalesce(nullIf(if(_ei > 0, EventsAttributes[_ei]['maple.fingerprint'], ''), ''), SpanAttributes['maple.fingerprint']), '^[ \\t\\n\\r]+|[ \\t\\n\\r]+$', ''), + 1, 200 + ) AS _customFp, + -- JSON detection for the message signature below. + isValidJSON(_msgText) AS _isJson, + _isJson AND JSONType(_msgText) = 'Object' AS _isJsonObj, + -- General, KEY-NAME-AGNOSTIC canonical signature: iterate ALL top-level + -- keys, redact volatile tokens (long hex / numbers) in each raw value, then + -- sort by "key=value" so key order & whitespace don't matter. No assumption + -- about which keys exist — works for any producer's JSON shape. (Nested + -- objects are hashed as their raw substring; only top-level is canonicalized.) + arrayStringConcat( + arraySort( + arrayMap( + kv -> concat(kv.1, '=', if( + kv.1 = 'type', + replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(kv.2, '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}', 'EMAIL'), 'https?://[^/ )"]+', ''), '[0-9a-fA-F-]{6,}|[0-9]+', '#'), + replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(kv.2, '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}', 'EMAIL'), 'https?://[^/ )"]+', ''), '/(Users|home)/[^/ ]+', '/~'), '[?][A-Za-z0-9_]+=[^ )"]*', '?#'), '"[^" ]*/[^" ]*"', '"#"'), '[0-9a-fA-F-]{6,}|[0-9]+', '#') + )), + JSONExtractKeysAndValuesRaw(_msgText) + ) + ), + '|' + ) AS _jsonSig, + -- The message signature is folded in ALWAYS, not only when there are no + -- frames. Bundled runtimes minify every module into one file, so the top + -- three frames of a Worker error are `toDatabaseError (worker.js)` for + -- every failing query alike: on frames alone, 25 distinct DatabaseError + -- bugs (316k occurrences) collapse into a single issue. The signature + -- restores that discrimination, and it cannot reinflate cardinality the + -- way a raw prefix would because everything variable is redacted first: + -- emails, URL origins, home directories, query strings, quoted values, + -- then ids and every digit run. See ERROR_MSG_REDACTIONS for the order, + -- what is deliberately kept, and the one residual it cannot reach. + multiIf( + _isJsonObj, _jsonSig, + substringUTF8( + replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(substringUTF8(_msgText, 1, 1000), '\\x1b\\[[0-9;]*m', ''), '^((\\[Error\\]|[\\s\\S]*?DB::Exception:)[\\s\\S]*?)( [Ii]n scope | [Ww]hile processing )[\\s\\S]*', '\\1'), 'RouteNotFound \\(([A-Z]+) [^ \\t\\r\\n]*\\)?', 'RouteNotFound (\\1 PATH)'), '^(GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS) /[^ \\t\\r\\n]* not found([\\r\\n]|$)', '\\1 PATH not found\\2'), '\\bCannot (GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS) /[^ \\t\\r\\n]*', 'Cannot \\1 PATH'), '\\bRoute (GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS):/[^ \\t\\r\\n]* not found', 'Route \\1:PATH not found'), 'No route found for "([A-Z]+) [^"\\r\\n]*"', 'No route found for "\\1 PATH"'), '(^|[ \\t\\r\\n\'"(`=])~/[^ \'"`:,)]*', '\\1PATH'), '(^|[ \\t\\r\\n\'"(`=])/(tmp|var|srv|private|opt|mnt|data|Volumes|System|app|work|run|Users/[^/ \\t\\r\\n\'"`:,)]+|home/[^/ \\t\\r\\n\'"`:,)]+)([/.][^ \\t\\r\\n\'"`:,)]*)?([ \\t\\r\\n\'"`:,)]|$)', '\\1PATH\\4'), '(^|[ \\t\\r\\n\'"(`=])/(tmp|var|srv|private|opt|mnt|data|Volumes|System|app|work|run|Users/[^/ \\t\\r\\n\'"`:,)]+|home/[^/ \\t\\r\\n\'"`:,)]+)([/.][^ \\t\\r\\n\'"`:,)]*)?([ \\t\\r\\n\'"`:,)]|$)', '\\1PATH\\4'), '\\b[a-z]{2,8}_[a-z]*[A-Z0-9][A-Za-z0-9]{8,}\\b', 'ID'), '\\b[0-9A-HJKMNP-TV-Z]{26}\\b', 'ID'), '\\b(HTTP|[Ss]tatus_?[Cc]ode|[Ss]tatus|returned|code|failed|failed with)(:? ?|=)([1-4][0-9][0-9]|500)\\b', '\\1\\2§\\3'), '\\b(HTTP|[Ss]tatus_?[Cc]ode|[Ss]tatus|returned|code|failed|failed with)(:? ?|=)5(0[1-9]|[1-9][0-9])\\b', '\\1\\2§5xx'), '(^|[^§0-9A-Za-z_.])([1-4][0-9][0-9]|500) (GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS) ', '\\1§\\2 \\3 '), '(^|[^§0-9A-Za-z_.])5(0[1-9]|[1-9][0-9]) (GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS) ', '\\1§5xx \\3 '), '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}', 'EMAIL'), 'https?://[^/ )"]+', ''), '/[^/? \\t\\r\\n\'"`:,)]*[~@%$][^/? \\t\\r\\n\'"`:,)]*', '/#'), '\'[^\' ]*([.][.][.]|[*]{3,})[^\' ]*\'', '\'#\''), '"[^" ]*([.][.][.]|[*]{3,})[^" ]*"', '"#"'), '/(Users|home)/[^/ ]+', '/~'), '[?][A-Za-z0-9_]+=[^ )"]*', '?#'), '\'[^\' ]*/[^\' ]*\'|\'[^\' ]{25,}\'', '\'#\''), '"[^" ]*/[^" ]*"|"[^" ]{25,}"', '"#"'), '`[^` ]*/[^` ]*`|`[^` ]{25,}`', '`#`'), '(^|[^A-Za-z0-9_-])([A-Za-z0-9_-]{20,})', '\\1⟦\\2⟧'), '⟦([^⟧0-9]*)⟧', '\\1'), '⟦([^⟧A-Z]*)⟧', '\\1'), '⟦([^⟧a-z]*)⟧', '\\1'), '⟦[^⟧]*⟧', '#'), '(^|[^A-Za-z0-9_-])([A-Za-z0-9_-]{10,})', '\\1⟦\\2⟧'), '⟦([^⟧]*[_-][^⟧]*)⟧', '\\1'), '⟦(0[xX][0-9a-fA-F]*)⟧', '\\1'), '⟦([^⟧]*[a-z][^⟧]*[A-Z][^⟧]*)⟧', '\\1'), '⟦([^⟧]*[A-Z][^⟧]*[a-z][^⟧]*)⟧', '\\1'), '⟦[^⟧]*[0-9][A-Za-z]+[0-9]+[A-Za-z]+[0-9][^⟧]*⟧', '#'), '⟦([^⟧]*)⟧', '\\1'), '(§[1-4][0-9][0-9]|§500|§5xx)|[0-9a-fA-F-]{6,}|[0-9]+', '\\1#'), '§([1-4][0-9][0-9]|500|5xx)#', '\\1'), + 1, 120 + ) + ) AS _msgSig, + -- Frame lines are matched by SHAPE, not by "contains :NUMBER". The old + -- rule accepted any line with a colon-digit, which let non-frame lines + -- in: Drizzle's `params: ` line, and the `Type: message` + -- header (`Code: 62`, `position 1628`, embedded timestamps). Row values + -- and message text then entered the hash and split one bug into + -- thousands of issues — 23,035 fingerprints for six real + -- AnomalyPersistenceError call sites, 15,051 for thirteen DatabaseError + -- ones. + -- + -- The pattern is rendered from FRAME_LINE_PATTERN in fingerprint.ts, + -- as is every redaction below. They used to be hand-copied here, which + -- let the reference implementation the tests exercise drift away from + -- the SQL that actually runs, silently. + arrayFilter( + line -> match(line, '^[ \\t]*at |^[ \\t]*File "|^[ \\t]+from [^ ]+:[0-9]+|^[^ \\t@{"\\[]+@[^ \\t]*:[0-9]+|^[ \\t]*[^ \\t@{"\\[]*@([a-z][a-z0-9+.-]*:)?/[^ \\t]*:[0-9]+|^[ \\t]+[^ \\t]+\\.(go|rs):[0-9]+|^[0-9]+ +\\S.* +0x[0-9a-fA-F]+') + AND position(line, '(definition)') = 0 + AND NOT match(line, '^[ \\t]*at \\[') + AND NOT match(line, '^[ \\t]*at ([^(:]|:[^0-9])*:?$') + AND NOT match(line, '^[ \\t]*at ([^(]* \\()?(\\)?$|native(:|\\)|$)|node:|ext:)'), + splitByChar('\n', _exStack) + ) AS _frameLines, + -- Redact every volatile token a frame line can carry: the URL origin + -- (so preview hosts share one fingerprint), Vite's 8-char bundle + -- content hash (so a deploy does not re-split every triaged browser and + -- Worker issue), then line numbers, hex pointers and long id runs. See + -- FRAME_REDACTIONS for the order and the reasoning. + arrayMap( + line -> replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(line, '@v?[0-9]+\\.[0-9]+\\.[0-9]+[^/ )]*', '@#'), '\\(index [0-9]+\\)', '(index #)'), 'file:///bundle/', ''), 'https?://[^/ )]+', ''), '(^|[ \\t("@])(file://)?([/.][^ \\t()"]*)?/(node_modules|site-packages|dist-packages|gems|pkg/mod)/', '\\1\\4/'), '([/-])v?[0-9]+\\.[0-9]+\\.[0-9]+[^/ )]*/', '\\1#/'), '(/python)[0-9]+\\.[0-9]+/', '\\1#/'), '(/\\$bunfs|[A-Z]:/~BUN)/root/', ''), '(\\$\\$Lambda|\\$Proxy|jdk\\.proxy|Generated[A-Za-z]*Accessor)\\$?[0-9]+', '\\1'), '(\\$(HibernateProxy|ByteBuddy|MockitoMock))\\$[A-Za-z0-9]+', '\\1'), '(-[A-Za-z0-9_-]{8})+\\.js', '.js'), '(-[A-Za-z0-9_-]{8})+\\.css', '.css'), '(/_astro/[^/ ).]+)\\.[A-Za-z0-9_-]{8}\\.js', '\\1.js'), ':[0-9]+|line [0-9]+|0x[0-9a-fA-F]+|[0-9a-fA-F]{8,}|[0-9]{6,}', ''), + arraySlice( + -- Python prints the outermost frame first: read it from the raise site. + if(match(_frameLines[1], '^[ \\t]*File "'), arrayReverse(_frameLines), _frameLines), + 1, 10 + ) + ) AS _normFrames, + -- A repeat (two anonymous closures in one bundle, or recursion) collapses + -- so it does not use up the frames the hash keeps. + arrayFilter((frame, i) -> i = 1 OR frame != _normFrames[i - 1], _normFrames, arrayEnumerate(_normFrames)) AS _distinctFrames, + -- The application's own frames: a dependency's or a runtime's frames vary with + -- its internals and version. A stack with none keeps every frame. + arrayFilter(frame -> NOT match(frame, '(^|[ \\t("@/])(node_modules|site-packages|dist-packages|gems|pkg/mod)/|/python#/|/ruby/#/|/usr/local/go/src/|/rustc/|\\.cargo/registry/|^[ \\t]*at (java|javax|jdk|sun|kotlin|kotlinx|scala)\\.'), _distinctFrames) AS _appFrames, + -- A namespaced type plus its message names one failure: keep only the + -- throw site. With no message the frames are all that is left; keep 3. + if(position(_exType, '/') > 0 AND _msgSig != '', 1, 3) AS _maxFrames, + arraySlice(if(length(_appFrames) > 0, _appFrames, _distinctFrames), 1, _maxFrames) AS _topFrames, + if(length(_topFrames) > 0, _topFrames[1], '') AS _topFrame, + arrayStringConcat(_topFrames, '\n') AS _fpFrames, + -- The previous version's frames, for the issue evaluator's re-key while a bump settles. + arrayStringConcat(arraySlice(_distinctFrames, 1, _maxFrames), '\n') AS _prevFpFrames, + if( + _customFp != '', + cityHash64(OrgId, ServiceName, 'maple.fingerprint', _customFp), + cityHash64(OrgId, ServiceName, _exType, _fpFrames, _msgSig, 'fp4') + ) AS _hash, + if( + _customFp != '', + _hash, + cityHash64(OrgId, ServiceName, _exType, _prevFpFrames, _msgSig, 'fp3') + ) AS _prevHash, + -- Display-only, best-effort human label (decoupled from the fingerprint: + -- many labels may map to one hash). The broad key list here is a DISPLAY + -- heuristic only; the fingerprint above makes no key-name assumption. + -- Only string values: JSONExtractString renders a number or an object + -- as its JSON text, which titled `{"error":{"code":5}}` with the object. + multiIf( + JSONType(_msgText, 'title') = 'String' AND JSONExtractString(_msgText, 'title') != '', JSONExtractString(_msgText, 'title'), + JSONType(_msgText, 'message') = 'String' AND JSONExtractString(_msgText, 'message') != '', JSONExtractString(_msgText, 'message'), + JSONType(_msgText, 'error') = 'String' AND JSONExtractString(_msgText, 'error') != '', JSONExtractString(_msgText, 'error'), + JSONType(_msgText, '_tag') = 'String' AND JSONExtractString(_msgText, '_tag') != '', JSONExtractString(_msgText, '_tag'), + JSONType(_msgText, 'reason') = 'String' AND JSONExtractString(_msgText, 'reason') != '', JSONExtractString(_msgText, 'reason'), + JSONType(_msgText, 'name') = 'String' AND JSONExtractString(_msgText, 'name') != '', JSONExtractString(_msgText, 'name'), + JSONType(_msgText, 'type') = 'String' AND JSONExtractString(_msgText, 'type') != '', extract(JSONExtractString(_msgText, 'type'), '([^/]+)$'), + 'JSON error' + ) AS _jsonLabel, + multiIf( + _msgText = '', 'Unknown Error', + position(_msgText, '{ readonly') = 1 OR position(_msgText, '└─') > 0, + if( + extract(_msgText, 'readonly (\\w+)') != '', + concat('Schema parse error: ', extract(_msgText, 'readonly (\\w+)')), + 'Schema parse error' + ), + _isJsonObj OR position(_msgText, '[') = 1, _jsonLabel, + -- UTF8 variants throughout: a byte cut split a character and + -- left U+FFFD at the end of a non-ASCII title. + leftUTF8(_msgText, multiIf( + positionUTF8(_msgText, ': ') > 3, toInt64(positionUTF8(_msgText, ': ')) - 1, + positionUTF8(_msgText, ' (') > 3, toInt64(positionUTF8(_msgText, ' (')) - 1, + positionUTF8(_msgText, '\n') > 3, toInt64(positionUTF8(_msgText, '\n')) - 1, + 150 + )) + ) AS _statusLabel, + if(_exType != '', _exType, _statusLabel) AS _errorLabel, + -- Both semconv spellings; the current key wins when both are present. + toUInt16OrZero( + if( + SpanAttributes['http.response.status_code'] != '', + SpanAttributes['http.response.status_code'], + SpanAttributes['http.status_code'] + ) + ) AS _httpStatus + SELECT + OrgId, + toDateTime(Timestamp) AS Timestamp, + TraceId, + SpanId, + ParentSpanId, + ServiceName, + coalesce(nullIf(ResourceAttributes['deployment.environment.name'], ''), ResourceAttributes['deployment.environment']) AS DeploymentEnv, + _exType AS ExceptionType, + _exMsg AS ExceptionMessage, + _exStack AS ExceptionStacktrace, + _topFrame AS TopFrame, + _hash AS FingerprintHash, + StatusMessage, + Duration, + _errorLabel AS ErrorLabel, + ResourceAttributes['service.version'] AS ServiceVersion, + if(SpanAttributes['user.id'] != '', SpanAttributes['user.id'], SpanAttributes['enduser.id']) AS UserId + FROM traces + WHERE StatusCode = 'Error' + -- Client-side runtimes (notably the native Cloudflare Workers + -- observability) mark ANY non-2xx fetch span as Error, so 404s from bot + -- traffic arrived here as unlabelled "Unknown Error" issues. Drop a + -- span only when all hold: 4xx, no exception event, no exception.type + -- attribute, and no error.type beyond the status code itself (HTTP + -- semconv sets error.type to the bare status on a non-2xx response, + -- which carries no exception). 5xx and anything carrying a real + -- exception still count, and SpanKind is deliberately not consulted — + -- these are Client spans. + AND NOT ( + _httpStatus >= 400 AND _httpStatus < 500 + AND _ei = 0 + AND SpanAttributes['exception.type'] = '' + AND (SpanAttributes['error.type'] = '' OR SpanAttributes['error.type'] = toString(_httpStatus)) + ); + +CREATE MATERIALIZED VIEW IF NOT EXISTS error_events_mv TO error_events AS +WITH + arrayFirstIndex(n -> n = 'exception', EventsName) AS _ei, + -- Only fill the old Unknown Error bucket. Event values (including + -- empty fields) and spans with StatusMessage keep every hash input. + _ei = 0 AND StatusMessage = '' AS _useAttrs, + if( + _ei > 0, EventsAttributes[_ei]['exception.type'], + if(_useAttrs, coalesce(nullIf(SpanAttributes['exception.type'], ''), SpanAttributes['error.type']), '') + ) AS _exType, + if( + _ei > 0, EventsAttributes[_ei]['exception.message'], + if(_useAttrs, coalesce(nullIf(SpanAttributes['exception.message'], ''), SpanAttributes['error.message']), StatusMessage) + ) AS _exMsg, + if( + _ei > 0, EventsAttributes[_ei]['exception.stacktrace'], + if(_useAttrs, SpanAttributes['exception.stacktrace'], '') + ) AS _exStack, + if(_useAttrs, _exMsg, StatusMessage) AS _msgText, + -- A user key replaces type, frames, message and salt in the hash: the + -- exception event's value first, then the span's. Trimmed and capped. + substringUTF8( + replaceRegexpAll(coalesce(nullIf(if(_ei > 0, EventsAttributes[_ei]['maple.fingerprint'], ''), ''), SpanAttributes['maple.fingerprint']), '^[ \\t\\n\\r]+|[ \\t\\n\\r]+$', ''), + 1, 200 + ) AS _customFp, + -- JSON detection for the message signature below. + isValidJSON(_msgText) AS _isJson, + _isJson AND JSONType(_msgText) = 'Object' AS _isJsonObj, + -- General, KEY-NAME-AGNOSTIC canonical signature: iterate ALL top-level + -- keys, redact volatile tokens (long hex / numbers) in each raw value, then + -- sort by "key=value" so key order & whitespace don't matter. No assumption + -- about which keys exist — works for any producer's JSON shape. (Nested + -- objects are hashed as their raw substring; only top-level is canonicalized.) + arrayStringConcat( + arraySort( + arrayMap( + kv -> concat(kv.1, '=', if( + kv.1 = 'type', + replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(kv.2, '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}', 'EMAIL'), 'https?://[^/ )"]+', ''), '[0-9a-fA-F-]{6,}|[0-9]+', '#'), + replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(kv.2, '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}', 'EMAIL'), 'https?://[^/ )"]+', ''), '/(Users|home)/[^/ ]+', '/~'), '[?][A-Za-z0-9_]+=[^ )"]*', '?#'), '"[^" ]*/[^" ]*"', '"#"'), '[0-9a-fA-F-]{6,}|[0-9]+', '#') + )), + JSONExtractKeysAndValuesRaw(_msgText) + ) + ), + '|' + ) AS _jsonSig, + -- The message signature is folded in ALWAYS, not only when there are no + -- frames. Bundled runtimes minify every module into one file, so the top + -- three frames of a Worker error are `toDatabaseError (worker.js)` for + -- every failing query alike: on frames alone, 25 distinct DatabaseError + -- bugs (316k occurrences) collapse into a single issue. The signature + -- restores that discrimination, and it cannot reinflate cardinality the + -- way a raw prefix would because everything variable is redacted first: + -- emails, URL origins, home directories, query strings, quoted values, + -- then ids and every digit run. See ERROR_MSG_REDACTIONS for the order, + -- what is deliberately kept, and the one residual it cannot reach. + multiIf( + _isJsonObj, _jsonSig, + substringUTF8( + replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(substringUTF8(_msgText, 1, 1000), '\\x1b\\[[0-9;]*m', ''), '^((\\[Error\\]|[\\s\\S]*?DB::Exception:)[\\s\\S]*?)( [Ii]n scope | [Ww]hile processing )[\\s\\S]*', '\\1'), 'RouteNotFound \\(([A-Z]+) [^ \\t\\r\\n]*\\)?', 'RouteNotFound (\\1 PATH)'), '^(GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS) /[^ \\t\\r\\n]* not found([\\r\\n]|$)', '\\1 PATH not found\\2'), '\\bCannot (GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS) /[^ \\t\\r\\n]*', 'Cannot \\1 PATH'), '\\bRoute (GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS):/[^ \\t\\r\\n]* not found', 'Route \\1:PATH not found'), 'No route found for "([A-Z]+) [^"\\r\\n]*"', 'No route found for "\\1 PATH"'), '(^|[ \\t\\r\\n\'"(`=])~/[^ \'"`:,)]*', '\\1PATH'), '(^|[ \\t\\r\\n\'"(`=])/(tmp|var|srv|private|opt|mnt|data|Volumes|System|app|work|run|Users/[^/ \\t\\r\\n\'"`:,)]+|home/[^/ \\t\\r\\n\'"`:,)]+)([/.][^ \\t\\r\\n\'"`:,)]*)?([ \\t\\r\\n\'"`:,)]|$)', '\\1PATH\\4'), '(^|[ \\t\\r\\n\'"(`=])/(tmp|var|srv|private|opt|mnt|data|Volumes|System|app|work|run|Users/[^/ \\t\\r\\n\'"`:,)]+|home/[^/ \\t\\r\\n\'"`:,)]+)([/.][^ \\t\\r\\n\'"`:,)]*)?([ \\t\\r\\n\'"`:,)]|$)', '\\1PATH\\4'), '\\b[a-z]{2,8}_[a-z]*[A-Z0-9][A-Za-z0-9]{8,}\\b', 'ID'), '\\b[0-9A-HJKMNP-TV-Z]{26}\\b', 'ID'), '\\b(HTTP|[Ss]tatus_?[Cc]ode|[Ss]tatus|returned|code|failed|failed with)(:? ?|=)([1-4][0-9][0-9]|500)\\b', '\\1\\2§\\3'), '\\b(HTTP|[Ss]tatus_?[Cc]ode|[Ss]tatus|returned|code|failed|failed with)(:? ?|=)5(0[1-9]|[1-9][0-9])\\b', '\\1\\2§5xx'), '(^|[^§0-9A-Za-z_.])([1-4][0-9][0-9]|500) (GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS) ', '\\1§\\2 \\3 '), '(^|[^§0-9A-Za-z_.])5(0[1-9]|[1-9][0-9]) (GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS) ', '\\1§5xx \\3 '), '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}', 'EMAIL'), 'https?://[^/ )"]+', ''), '/[^/? \\t\\r\\n\'"`:,)]*[~@%$][^/? \\t\\r\\n\'"`:,)]*', '/#'), '\'[^\' ]*([.][.][.]|[*]{3,})[^\' ]*\'', '\'#\''), '"[^" ]*([.][.][.]|[*]{3,})[^" ]*"', '"#"'), '/(Users|home)/[^/ ]+', '/~'), '[?][A-Za-z0-9_]+=[^ )"]*', '?#'), '\'[^\' ]*/[^\' ]*\'|\'[^\' ]{25,}\'', '\'#\''), '"[^" ]*/[^" ]*"|"[^" ]{25,}"', '"#"'), '`[^` ]*/[^` ]*`|`[^` ]{25,}`', '`#`'), '(^|[^A-Za-z0-9_-])([A-Za-z0-9_-]{20,})', '\\1⟦\\2⟧'), '⟦([^⟧0-9]*)⟧', '\\1'), '⟦([^⟧A-Z]*)⟧', '\\1'), '⟦([^⟧a-z]*)⟧', '\\1'), '⟦[^⟧]*⟧', '#'), '(^|[^A-Za-z0-9_-])([A-Za-z0-9_-]{10,})', '\\1⟦\\2⟧'), '⟦([^⟧]*[_-][^⟧]*)⟧', '\\1'), '⟦(0[xX][0-9a-fA-F]*)⟧', '\\1'), '⟦([^⟧]*[a-z][^⟧]*[A-Z][^⟧]*)⟧', '\\1'), '⟦([^⟧]*[A-Z][^⟧]*[a-z][^⟧]*)⟧', '\\1'), '⟦[^⟧]*[0-9][A-Za-z]+[0-9]+[A-Za-z]+[0-9][^⟧]*⟧', '#'), '⟦([^⟧]*)⟧', '\\1'), '(§[1-4][0-9][0-9]|§500|§5xx)|[0-9a-fA-F-]{6,}|[0-9]+', '\\1#'), '§([1-4][0-9][0-9]|500|5xx)#', '\\1'), + 1, 120 + ) + ) AS _msgSig, + -- Frame lines are matched by SHAPE, not by "contains :NUMBER". The old + -- rule accepted any line with a colon-digit, which let non-frame lines + -- in: Drizzle's `params: ` line, and the `Type: message` + -- header (`Code: 62`, `position 1628`, embedded timestamps). Row values + -- and message text then entered the hash and split one bug into + -- thousands of issues — 23,035 fingerprints for six real + -- AnomalyPersistenceError call sites, 15,051 for thirteen DatabaseError + -- ones. + -- + -- The pattern is rendered from FRAME_LINE_PATTERN in fingerprint.ts, + -- as is every redaction below. They used to be hand-copied here, which + -- let the reference implementation the tests exercise drift away from + -- the SQL that actually runs, silently. + arrayFilter( + line -> match(line, '^[ \\t]*at |^[ \\t]*File "|^[ \\t]+from [^ ]+:[0-9]+|^[^ \\t@{"\\[]+@[^ \\t]*:[0-9]+|^[ \\t]*[^ \\t@{"\\[]*@([a-z][a-z0-9+.-]*:)?/[^ \\t]*:[0-9]+|^[ \\t]+[^ \\t]+\\.(go|rs):[0-9]+|^[0-9]+ +\\S.* +0x[0-9a-fA-F]+') + AND position(line, '(definition)') = 0 + AND NOT match(line, '^[ \\t]*at \\[') + AND NOT match(line, '^[ \\t]*at ([^(:]|:[^0-9])*:?$') + AND NOT match(line, '^[ \\t]*at ([^(]* \\()?(\\)?$|native(:|\\)|$)|node:|ext:)'), + splitByChar('\n', _exStack) + ) AS _frameLines, + -- Redact every volatile token a frame line can carry: the URL origin + -- (so preview hosts share one fingerprint), Vite's 8-char bundle + -- content hash (so a deploy does not re-split every triaged browser and + -- Worker issue), then line numbers, hex pointers and long id runs. See + -- FRAME_REDACTIONS for the order and the reasoning. + arrayMap( + line -> replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(line, '@v?[0-9]+\\.[0-9]+\\.[0-9]+[^/ )]*', '@#'), '\\(index [0-9]+\\)', '(index #)'), 'file:///bundle/', ''), 'https?://[^/ )]+', ''), '(^|[ \\t("@])(file://)?([/.][^ \\t()"]*)?/(node_modules|site-packages|dist-packages|gems|pkg/mod)/', '\\1\\4/'), '([/-])v?[0-9]+\\.[0-9]+\\.[0-9]+[^/ )]*/', '\\1#/'), '(/python)[0-9]+\\.[0-9]+/', '\\1#/'), '(/\\$bunfs|[A-Z]:/~BUN)/root/', ''), '(\\$\\$Lambda|\\$Proxy|jdk\\.proxy|Generated[A-Za-z]*Accessor)\\$?[0-9]+', '\\1'), '(\\$(HibernateProxy|ByteBuddy|MockitoMock))\\$[A-Za-z0-9]+', '\\1'), '(-[A-Za-z0-9_-]{8})+\\.js', '.js'), '(-[A-Za-z0-9_-]{8})+\\.css', '.css'), '(/_astro/[^/ ).]+)\\.[A-Za-z0-9_-]{8}\\.js', '\\1.js'), ':[0-9]+|line [0-9]+|0x[0-9a-fA-F]+|[0-9a-fA-F]{8,}|[0-9]{6,}', ''), + arraySlice( + -- Python prints the outermost frame first: read it from the raise site. + if(match(_frameLines[1], '^[ \\t]*File "'), arrayReverse(_frameLines), _frameLines), + 1, 10 + ) + ) AS _normFrames, + -- A repeat (two anonymous closures in one bundle, or recursion) collapses + -- so it does not use up the frames the hash keeps. + arrayFilter((frame, i) -> i = 1 OR frame != _normFrames[i - 1], _normFrames, arrayEnumerate(_normFrames)) AS _distinctFrames, + -- The application's own frames: a dependency's or a runtime's frames vary with + -- its internals and version. A stack with none keeps every frame. + arrayFilter(frame -> NOT match(frame, '(^|[ \\t("@/])(node_modules|site-packages|dist-packages|gems|pkg/mod)/|/python#/|/ruby/#/|/usr/local/go/src/|/rustc/|\\.cargo/registry/|^[ \\t]*at (java|javax|jdk|sun|kotlin|kotlinx|scala)\\.'), _distinctFrames) AS _appFrames, + -- A namespaced type plus its message names one failure: keep only the + -- throw site. With no message the frames are all that is left; keep 3. + if(position(_exType, '/') > 0 AND _msgSig != '', 1, 3) AS _maxFrames, + arraySlice(if(length(_appFrames) > 0, _appFrames, _distinctFrames), 1, _maxFrames) AS _topFrames, + if(length(_topFrames) > 0, _topFrames[1], '') AS _topFrame, + arrayStringConcat(_topFrames, '\n') AS _fpFrames, + -- The previous version's frames, for the issue evaluator's re-key while a bump settles. + arrayStringConcat(arraySlice(_distinctFrames, 1, _maxFrames), '\n') AS _prevFpFrames, + if( + _customFp != '', + cityHash64(OrgId, ServiceName, 'maple.fingerprint', _customFp), + cityHash64(OrgId, ServiceName, _exType, _fpFrames, _msgSig, 'fp4') + ) AS _hash, + if( + _customFp != '', + _hash, + cityHash64(OrgId, ServiceName, _exType, _prevFpFrames, _msgSig, 'fp3') + ) AS _prevHash, + -- Display-only, best-effort human label (decoupled from the fingerprint: + -- many labels may map to one hash). The broad key list here is a DISPLAY + -- heuristic only; the fingerprint above makes no key-name assumption. + -- Only string values: JSONExtractString renders a number or an object + -- as its JSON text, which titled `{"error":{"code":5}}` with the object. + multiIf( + JSONType(_msgText, 'title') = 'String' AND JSONExtractString(_msgText, 'title') != '', JSONExtractString(_msgText, 'title'), + JSONType(_msgText, 'message') = 'String' AND JSONExtractString(_msgText, 'message') != '', JSONExtractString(_msgText, 'message'), + JSONType(_msgText, 'error') = 'String' AND JSONExtractString(_msgText, 'error') != '', JSONExtractString(_msgText, 'error'), + JSONType(_msgText, '_tag') = 'String' AND JSONExtractString(_msgText, '_tag') != '', JSONExtractString(_msgText, '_tag'), + JSONType(_msgText, 'reason') = 'String' AND JSONExtractString(_msgText, 'reason') != '', JSONExtractString(_msgText, 'reason'), + JSONType(_msgText, 'name') = 'String' AND JSONExtractString(_msgText, 'name') != '', JSONExtractString(_msgText, 'name'), + JSONType(_msgText, 'type') = 'String' AND JSONExtractString(_msgText, 'type') != '', extract(JSONExtractString(_msgText, 'type'), '([^/]+)$'), + 'JSON error' + ) AS _jsonLabel, + multiIf( + _msgText = '', 'Unknown Error', + position(_msgText, '{ readonly') = 1 OR position(_msgText, '└─') > 0, + if( + extract(_msgText, 'readonly (\\w+)') != '', + concat('Schema parse error: ', extract(_msgText, 'readonly (\\w+)')), + 'Schema parse error' + ), + _isJsonObj OR position(_msgText, '[') = 1, _jsonLabel, + -- UTF8 variants throughout: a byte cut split a character and + -- left U+FFFD at the end of a non-ASCII title. + leftUTF8(_msgText, multiIf( + positionUTF8(_msgText, ': ') > 3, toInt64(positionUTF8(_msgText, ': ')) - 1, + positionUTF8(_msgText, ' (') > 3, toInt64(positionUTF8(_msgText, ' (')) - 1, + positionUTF8(_msgText, '\n') > 3, toInt64(positionUTF8(_msgText, '\n')) - 1, + 150 + )) + ) AS _statusLabel, + if(_exType != '', _exType, _statusLabel) AS _errorLabel, + -- Both semconv spellings; the current key wins when both are present. + toUInt16OrZero( + if( + SpanAttributes['http.response.status_code'] != '', + SpanAttributes['http.response.status_code'], + SpanAttributes['http.status_code'] + ) + ) AS _httpStatus + SELECT + OrgId, + toDateTime(Timestamp) AS Timestamp, + TraceId, + SpanId, + ParentSpanId, + ServiceName, + coalesce(nullIf(ResourceAttributes['deployment.environment.name'], ''), ResourceAttributes['deployment.environment']) AS DeploymentEnv, + _exType AS ExceptionType, + _exMsg AS ExceptionMessage, + _exStack AS ExceptionStacktrace, + _topFrame AS TopFrame, + _hash AS FingerprintHash, + StatusMessage, + Duration, + _errorLabel AS ErrorLabel, + ResourceAttributes['service.version'] AS ServiceVersion, + if(SpanAttributes['user.id'] != '', SpanAttributes['user.id'], SpanAttributes['enduser.id']) AS UserId + FROM traces + WHERE StatusCode = 'Error' + -- Client-side runtimes (notably the native Cloudflare Workers + -- observability) mark ANY non-2xx fetch span as Error, so 404s from bot + -- traffic arrived here as unlabelled "Unknown Error" issues. Drop a + -- span only when all hold: 4xx, no exception event, no exception.type + -- attribute, and no error.type beyond the status code itself (HTTP + -- semconv sets error.type to the bare status on a non-2xx response, + -- which carries no exception). 5xx and anything carrying a real + -- exception still count, and SpanKind is deliberately not consulted — + -- these are Client spans. + AND NOT ( + _httpStatus >= 400 AND _httpStatus < 500 + AND _ei = 0 + AND SpanAttributes['exception.type'] = '' + AND (SpanAttributes['error.type'] = '' OR SpanAttributes['error.type'] = toString(_httpStatus)) + ); + +CREATE MATERIALIZED VIEW IF NOT EXISTS error_fingerprint_lineage_mv TO error_fingerprint_lineage AS +WITH + arrayFirstIndex(n -> n = 'exception', EventsName) AS _ei, + -- Only fill the old Unknown Error bucket. Event values (including + -- empty fields) and spans with StatusMessage keep every hash input. + _ei = 0 AND StatusMessage = '' AS _useAttrs, + if( + _ei > 0, EventsAttributes[_ei]['exception.type'], + if(_useAttrs, coalesce(nullIf(SpanAttributes['exception.type'], ''), SpanAttributes['error.type']), '') + ) AS _exType, + if( + _ei > 0, EventsAttributes[_ei]['exception.message'], + if(_useAttrs, coalesce(nullIf(SpanAttributes['exception.message'], ''), SpanAttributes['error.message']), StatusMessage) + ) AS _exMsg, + if( + _ei > 0, EventsAttributes[_ei]['exception.stacktrace'], + if(_useAttrs, SpanAttributes['exception.stacktrace'], '') + ) AS _exStack, + if(_useAttrs, _exMsg, StatusMessage) AS _msgText, + -- A user key replaces type, frames, message and salt in the hash: the + -- exception event's value first, then the span's. Trimmed and capped. + substringUTF8( + replaceRegexpAll(coalesce(nullIf(if(_ei > 0, EventsAttributes[_ei]['maple.fingerprint'], ''), ''), SpanAttributes['maple.fingerprint']), '^[ \\t\\n\\r]+|[ \\t\\n\\r]+$', ''), + 1, 200 + ) AS _customFp, + -- JSON detection for the message signature below. + isValidJSON(_msgText) AS _isJson, + _isJson AND JSONType(_msgText) = 'Object' AS _isJsonObj, + -- General, KEY-NAME-AGNOSTIC canonical signature: iterate ALL top-level + -- keys, redact volatile tokens (long hex / numbers) in each raw value, then + -- sort by "key=value" so key order & whitespace don't matter. No assumption + -- about which keys exist — works for any producer's JSON shape. (Nested + -- objects are hashed as their raw substring; only top-level is canonicalized.) + arrayStringConcat( + arraySort( + arrayMap( + kv -> concat(kv.1, '=', if( + kv.1 = 'type', + replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(kv.2, '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}', 'EMAIL'), 'https?://[^/ )"]+', ''), '[0-9a-fA-F-]{6,}|[0-9]+', '#'), + replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(kv.2, '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}', 'EMAIL'), 'https?://[^/ )"]+', ''), '/(Users|home)/[^/ ]+', '/~'), '[?][A-Za-z0-9_]+=[^ )"]*', '?#'), '"[^" ]*/[^" ]*"', '"#"'), '[0-9a-fA-F-]{6,}|[0-9]+', '#') + )), + JSONExtractKeysAndValuesRaw(_msgText) + ) + ), + '|' + ) AS _jsonSig, + -- The message signature is folded in ALWAYS, not only when there are no + -- frames. Bundled runtimes minify every module into one file, so the top + -- three frames of a Worker error are `toDatabaseError (worker.js)` for + -- every failing query alike: on frames alone, 25 distinct DatabaseError + -- bugs (316k occurrences) collapse into a single issue. The signature + -- restores that discrimination, and it cannot reinflate cardinality the + -- way a raw prefix would because everything variable is redacted first: + -- emails, URL origins, home directories, query strings, quoted values, + -- then ids and every digit run. See ERROR_MSG_REDACTIONS for the order, + -- what is deliberately kept, and the one residual it cannot reach. + multiIf( + _isJsonObj, _jsonSig, + substringUTF8( + replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(substringUTF8(_msgText, 1, 1000), '\\x1b\\[[0-9;]*m', ''), '^((\\[Error\\]|[\\s\\S]*?DB::Exception:)[\\s\\S]*?)( [Ii]n scope | [Ww]hile processing )[\\s\\S]*', '\\1'), 'RouteNotFound \\(([A-Z]+) [^ \\t\\r\\n]*\\)?', 'RouteNotFound (\\1 PATH)'), '^(GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS) /[^ \\t\\r\\n]* not found([\\r\\n]|$)', '\\1 PATH not found\\2'), '\\bCannot (GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS) /[^ \\t\\r\\n]*', 'Cannot \\1 PATH'), '\\bRoute (GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS):/[^ \\t\\r\\n]* not found', 'Route \\1:PATH not found'), 'No route found for "([A-Z]+) [^"\\r\\n]*"', 'No route found for "\\1 PATH"'), '(^|[ \\t\\r\\n\'"(`=])~/[^ \'"`:,)]*', '\\1PATH'), '(^|[ \\t\\r\\n\'"(`=])/(tmp|var|srv|private|opt|mnt|data|Volumes|System|app|work|run|Users/[^/ \\t\\r\\n\'"`:,)]+|home/[^/ \\t\\r\\n\'"`:,)]+)([/.][^ \\t\\r\\n\'"`:,)]*)?([ \\t\\r\\n\'"`:,)]|$)', '\\1PATH\\4'), '(^|[ \\t\\r\\n\'"(`=])/(tmp|var|srv|private|opt|mnt|data|Volumes|System|app|work|run|Users/[^/ \\t\\r\\n\'"`:,)]+|home/[^/ \\t\\r\\n\'"`:,)]+)([/.][^ \\t\\r\\n\'"`:,)]*)?([ \\t\\r\\n\'"`:,)]|$)', '\\1PATH\\4'), '\\b[a-z]{2,8}_[a-z]*[A-Z0-9][A-Za-z0-9]{8,}\\b', 'ID'), '\\b[0-9A-HJKMNP-TV-Z]{26}\\b', 'ID'), '\\b(HTTP|[Ss]tatus_?[Cc]ode|[Ss]tatus|returned|code|failed|failed with)(:? ?|=)([1-4][0-9][0-9]|500)\\b', '\\1\\2§\\3'), '\\b(HTTP|[Ss]tatus_?[Cc]ode|[Ss]tatus|returned|code|failed|failed with)(:? ?|=)5(0[1-9]|[1-9][0-9])\\b', '\\1\\2§5xx'), '(^|[^§0-9A-Za-z_.])([1-4][0-9][0-9]|500) (GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS) ', '\\1§\\2 \\3 '), '(^|[^§0-9A-Za-z_.])5(0[1-9]|[1-9][0-9]) (GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS) ', '\\1§5xx \\3 '), '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}', 'EMAIL'), 'https?://[^/ )"]+', ''), '/[^/? \\t\\r\\n\'"`:,)]*[~@%$][^/? \\t\\r\\n\'"`:,)]*', '/#'), '\'[^\' ]*([.][.][.]|[*]{3,})[^\' ]*\'', '\'#\''), '"[^" ]*([.][.][.]|[*]{3,})[^" ]*"', '"#"'), '/(Users|home)/[^/ ]+', '/~'), '[?][A-Za-z0-9_]+=[^ )"]*', '?#'), '\'[^\' ]*/[^\' ]*\'|\'[^\' ]{25,}\'', '\'#\''), '"[^" ]*/[^" ]*"|"[^" ]{25,}"', '"#"'), '`[^` ]*/[^` ]*`|`[^` ]{25,}`', '`#`'), '(^|[^A-Za-z0-9_-])([A-Za-z0-9_-]{20,})', '\\1⟦\\2⟧'), '⟦([^⟧0-9]*)⟧', '\\1'), '⟦([^⟧A-Z]*)⟧', '\\1'), '⟦([^⟧a-z]*)⟧', '\\1'), '⟦[^⟧]*⟧', '#'), '(^|[^A-Za-z0-9_-])([A-Za-z0-9_-]{10,})', '\\1⟦\\2⟧'), '⟦([^⟧]*[_-][^⟧]*)⟧', '\\1'), '⟦(0[xX][0-9a-fA-F]*)⟧', '\\1'), '⟦([^⟧]*[a-z][^⟧]*[A-Z][^⟧]*)⟧', '\\1'), '⟦([^⟧]*[A-Z][^⟧]*[a-z][^⟧]*)⟧', '\\1'), '⟦[^⟧]*[0-9][A-Za-z]+[0-9]+[A-Za-z]+[0-9][^⟧]*⟧', '#'), '⟦([^⟧]*)⟧', '\\1'), '(§[1-4][0-9][0-9]|§500|§5xx)|[0-9a-fA-F-]{6,}|[0-9]+', '\\1#'), '§([1-4][0-9][0-9]|500|5xx)#', '\\1'), + 1, 120 + ) + ) AS _msgSig, + -- Frame lines are matched by SHAPE, not by "contains :NUMBER". The old + -- rule accepted any line with a colon-digit, which let non-frame lines + -- in: Drizzle's `params: ` line, and the `Type: message` + -- header (`Code: 62`, `position 1628`, embedded timestamps). Row values + -- and message text then entered the hash and split one bug into + -- thousands of issues — 23,035 fingerprints for six real + -- AnomalyPersistenceError call sites, 15,051 for thirteen DatabaseError + -- ones. + -- + -- The pattern is rendered from FRAME_LINE_PATTERN in fingerprint.ts, + -- as is every redaction below. They used to be hand-copied here, which + -- let the reference implementation the tests exercise drift away from + -- the SQL that actually runs, silently. + arrayFilter( + line -> match(line, '^[ \\t]*at |^[ \\t]*File "|^[ \\t]+from [^ ]+:[0-9]+|^[^ \\t@{"\\[]+@[^ \\t]*:[0-9]+|^[ \\t]*[^ \\t@{"\\[]*@([a-z][a-z0-9+.-]*:)?/[^ \\t]*:[0-9]+|^[ \\t]+[^ \\t]+\\.(go|rs):[0-9]+|^[0-9]+ +\\S.* +0x[0-9a-fA-F]+') + AND position(line, '(definition)') = 0 + AND NOT match(line, '^[ \\t]*at \\[') + AND NOT match(line, '^[ \\t]*at ([^(:]|:[^0-9])*:?$') + AND NOT match(line, '^[ \\t]*at ([^(]* \\()?(\\)?$|native(:|\\)|$)|node:|ext:)'), + splitByChar('\n', _exStack) + ) AS _frameLines, + -- Redact every volatile token a frame line can carry: the URL origin + -- (so preview hosts share one fingerprint), Vite's 8-char bundle + -- content hash (so a deploy does not re-split every triaged browser and + -- Worker issue), then line numbers, hex pointers and long id runs. See + -- FRAME_REDACTIONS for the order and the reasoning. + arrayMap( + line -> replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(line, '@v?[0-9]+\\.[0-9]+\\.[0-9]+[^/ )]*', '@#'), '\\(index [0-9]+\\)', '(index #)'), 'file:///bundle/', ''), 'https?://[^/ )]+', ''), '(^|[ \\t("@])(file://)?([/.][^ \\t()"]*)?/(node_modules|site-packages|dist-packages|gems|pkg/mod)/', '\\1\\4/'), '([/-])v?[0-9]+\\.[0-9]+\\.[0-9]+[^/ )]*/', '\\1#/'), '(/python)[0-9]+\\.[0-9]+/', '\\1#/'), '(/\\$bunfs|[A-Z]:/~BUN)/root/', ''), '(\\$\\$Lambda|\\$Proxy|jdk\\.proxy|Generated[A-Za-z]*Accessor)\\$?[0-9]+', '\\1'), '(\\$(HibernateProxy|ByteBuddy|MockitoMock))\\$[A-Za-z0-9]+', '\\1'), '(-[A-Za-z0-9_-]{8})+\\.js', '.js'), '(-[A-Za-z0-9_-]{8})+\\.css', '.css'), '(/_astro/[^/ ).]+)\\.[A-Za-z0-9_-]{8}\\.js', '\\1.js'), ':[0-9]+|line [0-9]+|0x[0-9a-fA-F]+|[0-9a-fA-F]{8,}|[0-9]{6,}', ''), + arraySlice( + -- Python prints the outermost frame first: read it from the raise site. + if(match(_frameLines[1], '^[ \\t]*File "'), arrayReverse(_frameLines), _frameLines), + 1, 10 + ) + ) AS _normFrames, + -- A repeat (two anonymous closures in one bundle, or recursion) collapses + -- so it does not use up the frames the hash keeps. + arrayFilter((frame, i) -> i = 1 OR frame != _normFrames[i - 1], _normFrames, arrayEnumerate(_normFrames)) AS _distinctFrames, + -- The application's own frames: a dependency's or a runtime's frames vary with + -- its internals and version. A stack with none keeps every frame. + arrayFilter(frame -> NOT match(frame, '(^|[ \\t("@/])(node_modules|site-packages|dist-packages|gems|pkg/mod)/|/python#/|/ruby/#/|/usr/local/go/src/|/rustc/|\\.cargo/registry/|^[ \\t]*at (java|javax|jdk|sun|kotlin|kotlinx|scala)\\.'), _distinctFrames) AS _appFrames, + -- A namespaced type plus its message names one failure: keep only the + -- throw site. With no message the frames are all that is left; keep 3. + if(position(_exType, '/') > 0 AND _msgSig != '', 1, 3) AS _maxFrames, + arraySlice(if(length(_appFrames) > 0, _appFrames, _distinctFrames), 1, _maxFrames) AS _topFrames, + if(length(_topFrames) > 0, _topFrames[1], '') AS _topFrame, + arrayStringConcat(_topFrames, '\n') AS _fpFrames, + -- The previous version's frames, for the issue evaluator's re-key while a bump settles. + arrayStringConcat(arraySlice(_distinctFrames, 1, _maxFrames), '\n') AS _prevFpFrames, + if( + _customFp != '', + cityHash64(OrgId, ServiceName, 'maple.fingerprint', _customFp), + cityHash64(OrgId, ServiceName, _exType, _fpFrames, _msgSig, 'fp4') + ) AS _hash, + if( + _customFp != '', + _hash, + cityHash64(OrgId, ServiceName, _exType, _prevFpFrames, _msgSig, 'fp3') + ) AS _prevHash, + -- Display-only, best-effort human label (decoupled from the fingerprint: + -- many labels may map to one hash). The broad key list here is a DISPLAY + -- heuristic only; the fingerprint above makes no key-name assumption. + -- Only string values: JSONExtractString renders a number or an object + -- as its JSON text, which titled `{"error":{"code":5}}` with the object. + multiIf( + JSONType(_msgText, 'title') = 'String' AND JSONExtractString(_msgText, 'title') != '', JSONExtractString(_msgText, 'title'), + JSONType(_msgText, 'message') = 'String' AND JSONExtractString(_msgText, 'message') != '', JSONExtractString(_msgText, 'message'), + JSONType(_msgText, 'error') = 'String' AND JSONExtractString(_msgText, 'error') != '', JSONExtractString(_msgText, 'error'), + JSONType(_msgText, '_tag') = 'String' AND JSONExtractString(_msgText, '_tag') != '', JSONExtractString(_msgText, '_tag'), + JSONType(_msgText, 'reason') = 'String' AND JSONExtractString(_msgText, 'reason') != '', JSONExtractString(_msgText, 'reason'), + JSONType(_msgText, 'name') = 'String' AND JSONExtractString(_msgText, 'name') != '', JSONExtractString(_msgText, 'name'), + JSONType(_msgText, 'type') = 'String' AND JSONExtractString(_msgText, 'type') != '', extract(JSONExtractString(_msgText, 'type'), '([^/]+)$'), + 'JSON error' + ) AS _jsonLabel, + multiIf( + _msgText = '', 'Unknown Error', + position(_msgText, '{ readonly') = 1 OR position(_msgText, '└─') > 0, + if( + extract(_msgText, 'readonly (\\w+)') != '', + concat('Schema parse error: ', extract(_msgText, 'readonly (\\w+)')), + 'Schema parse error' + ), + _isJsonObj OR position(_msgText, '[') = 1, _jsonLabel, + -- UTF8 variants throughout: a byte cut split a character and + -- left U+FFFD at the end of a non-ASCII title. + leftUTF8(_msgText, multiIf( + positionUTF8(_msgText, ': ') > 3, toInt64(positionUTF8(_msgText, ': ')) - 1, + positionUTF8(_msgText, ' (') > 3, toInt64(positionUTF8(_msgText, ' (')) - 1, + positionUTF8(_msgText, '\n') > 3, toInt64(positionUTF8(_msgText, '\n')) - 1, + 150 + )) + ) AS _statusLabel, + if(_exType != '', _exType, _statusLabel) AS _errorLabel, + -- Both semconv spellings; the current key wins when both are present. + toUInt16OrZero( + if( + SpanAttributes['http.response.status_code'] != '', + SpanAttributes['http.response.status_code'], + SpanAttributes['http.status_code'] + ) + ) AS _httpStatus + SELECT + OrgId, + toStartOfMinute(toDateTime(Timestamp)) AS Minute, + _hash AS FingerprintHash, + _prevHash AS PreviousFingerprintHash + FROM traces + WHERE StatusCode = 'Error' + -- Client-side runtimes (notably the native Cloudflare Workers + -- observability) mark ANY non-2xx fetch span as Error, so 404s from bot + -- traffic arrived here as unlabelled "Unknown Error" issues. Drop a + -- span only when all hold: 4xx, no exception event, no exception.type + -- attribute, and no error.type beyond the status code itself (HTTP + -- semconv sets error.type to the bare status on a non-2xx response, + -- which carries no exception). 5xx and anything carrying a real + -- exception still count, and SpanKind is deliberately not consulted — + -- these are Client spans. + AND NOT ( + _httpStatus >= 400 AND _httpStatus < 500 + AND _ei = 0 + AND SpanAttributes['exception.type'] = '' + AND (SpanAttributes['error.type'] = '' OR SpanAttributes['error.type'] = toString(_httpStatus)) + ) + AND _hash != _prevHash; + +CREATE MATERIALIZED VIEW IF NOT EXISTS error_fingerprints_minutely_mv TO error_fingerprints_minutely AS +SELECT + OrgId, + toStartOfMinute(Timestamp) AS Minute, + FingerprintHash, + anyLast(ServiceName) AS ServiceName, + anyLast(ExceptionType) AS ExceptionType, + anyLast(ExceptionMessage) AS ExceptionMessage, + anyLast(ErrorLabel) AS ErrorLabel, + anyLast(TopFrame) AS TopFrame, + count() AS OccurrenceCount, + min(Timestamp) AS FirstSeen, + max(Timestamp) AS LastSeen, + -- Distinct builds, not a sample: see ServiceVersions on the datasource. + groupUniqArray(ServiceVersion) AS ServiceVersions + FROM error_events + GROUP BY OrgId, Minute, FingerprintHash; + +CREATE MATERIALIZED VIEW IF NOT EXISTS identity_links_mv TO identity_links AS +SELECT + OrgId, + VisitorId, + UserId, + StartTime AS FirstSeen + FROM session_replays + WHERE VisitorId != '' AND UserId != ''; + +CREATE MATERIALIZED VIEW IF NOT EXISTS log_attribute_keys_mv TO attribute_keys_hourly AS +SELECT + OrgId, + toStartOfHour(toDateTime(Timestamp)) AS Hour, + arrayJoin(mapKeys(LogAttributes)) AS AttributeKey, + 'log' AS AttributeScope, + count() AS UsageCount + FROM logs + WHERE LogAttributes != map() + GROUP BY OrgId, Hour, AttributeKey, AttributeScope; + +CREATE MATERIALIZED VIEW IF NOT EXISTS log_attribute_values_mv TO attribute_values_hourly AS +SELECT + OrgId, + toStartOfHour(toDateTime(Timestamp)) AS Hour, + AttributeKey, + AttributeValue, + 'log' AS AttributeScope, + count() AS UsageCount + FROM logs + ARRAY JOIN + mapKeys(LogAttributes) AS AttributeKey, + mapValues(LogAttributes) AS AttributeValue + WHERE AttributeValue != '' + AND length(AttributeValue) <= 128 + AND NOT (length(AttributeValue) > 4 AND match(AttributeValue, '^[0-9]+([.][0-9]+)?$')) + AND NOT match(AttributeKey, '(_id|[.]id|Id|_ns)$') + AND AttributeKey NOT LIKE 'http.request.header.%' + AND AttributeKey NOT LIKE 'http.response.header.%' + GROUP BY OrgId, Hour, AttributeKey, AttributeValue, AttributeScope; + +CREATE MATERIALIZED VIEW IF NOT EXISTS logs_aggregates_hourly_mv TO logs_aggregates_hourly AS +SELECT + OrgId, + toStartOfHour(TimestampTime) AS Hour, + ServiceName, + SeverityText, + coalesce(nullIf(ResourceAttributes['deployment.environment.name'], ''), ResourceAttributes['deployment.environment']) AS DeploymentEnv, + count() AS Count, + sum(length(Body) + 200) AS SizeBytes, + ResourceAttributes['service.namespace'] AS ServiceNamespace + FROM logs + GROUP BY OrgId, Hour, ServiceName, SeverityText, DeploymentEnv, ServiceNamespace; + +CREATE MATERIALIZED VIEW IF NOT EXISTS metric_attribute_keys_mv TO attribute_keys_hourly AS +SELECT + OrgId, + toStartOfHour(toDateTime(TimeUnix)) AS Hour, + arrayJoin(mapKeys(Attributes)) AS AttributeKey, + 'metric' AS AttributeScope, + count() AS UsageCount + FROM metrics_sum + WHERE Attributes != map() + GROUP BY OrgId, Hour, AttributeKey, AttributeScope; + +CREATE MATERIALIZED VIEW IF NOT EXISTS metric_attribute_values_mv TO attribute_values_hourly AS +SELECT + OrgId, + toStartOfHour(toDateTime(TimeUnix)) AS Hour, + AttributeKey, + AttributeValue, + 'metric' AS AttributeScope, + count() AS UsageCount + FROM metrics_sum + ARRAY JOIN + mapKeys(Attributes) AS AttributeKey, + mapValues(Attributes) AS AttributeValue + WHERE AttributeValue != '' + AND length(AttributeValue) <= 128 + AND NOT (length(AttributeValue) > 4 AND match(AttributeValue, '^[0-9]+([.][0-9]+)?$')) + AND NOT match(AttributeKey, '(_id|[.]id|Id|_ns)$') + AND AttributeKey NOT LIKE 'http.request.header.%' + AND AttributeKey NOT LIKE 'http.response.header.%' + GROUP BY OrgId, Hour, AttributeKey, AttributeValue, AttributeScope; + +CREATE MATERIALIZED VIEW IF NOT EXISTS metric_catalog_exp_histogram_mv TO metric_catalog AS +SELECT + OrgId, + toStartOfHour(toDateTime(TimeUnix)) AS Hour, + 'exponential_histogram' AS MetricType, + ServiceName, + MetricName, + anyLast(MetricDescription) AS MetricDescription, + anyLast(MetricUnit) AS MetricUnit, + toUInt8(0) AS IsMonotonic, + count() AS DataPointCount, + min(toDateTime(TimeUnix)) AS FirstSeen, + max(toDateTime(TimeUnix)) AS LastSeen + FROM metrics_exponential_histogram + GROUP BY OrgId, Hour, MetricType, ServiceName, MetricName; + +CREATE MATERIALIZED VIEW IF NOT EXISTS metric_catalog_gauge_mv TO metric_catalog AS +SELECT + OrgId, + toStartOfHour(toDateTime(TimeUnix)) AS Hour, + 'gauge' AS MetricType, + ServiceName, + MetricName, + anyLast(MetricDescription) AS MetricDescription, + anyLast(MetricUnit) AS MetricUnit, + toUInt8(0) AS IsMonotonic, + count() AS DataPointCount, + min(toDateTime(TimeUnix)) AS FirstSeen, + max(toDateTime(TimeUnix)) AS LastSeen + FROM metrics_gauge + GROUP BY OrgId, Hour, MetricType, ServiceName, MetricName; + +CREATE MATERIALIZED VIEW IF NOT EXISTS metric_catalog_histogram_mv TO metric_catalog AS +SELECT + OrgId, + toStartOfHour(toDateTime(TimeUnix)) AS Hour, + 'histogram' AS MetricType, + ServiceName, + MetricName, + anyLast(MetricDescription) AS MetricDescription, + anyLast(MetricUnit) AS MetricUnit, + toUInt8(0) AS IsMonotonic, + count() AS DataPointCount, + min(toDateTime(TimeUnix)) AS FirstSeen, + max(toDateTime(TimeUnix)) AS LastSeen + FROM metrics_histogram + GROUP BY OrgId, Hour, MetricType, ServiceName, MetricName; + +CREATE MATERIALIZED VIEW IF NOT EXISTS metric_catalog_sum_mv TO metric_catalog AS +SELECT + OrgId, + toStartOfHour(toDateTime(TimeUnix)) AS Hour, + 'sum' AS MetricType, + ServiceName, + MetricName, + anyLast(MetricDescription) AS MetricDescription, + anyLast(MetricUnit) AS MetricUnit, + anyLast(toUInt8(IsMonotonic)) AS IsMonotonic, + count() AS DataPointCount, + min(toDateTime(TimeUnix)) AS FirstSeen, + max(toDateTime(TimeUnix)) AS LastSeen + FROM metrics_sum + GROUP BY OrgId, Hour, MetricType, ServiceName, MetricName; + +CREATE MATERIALIZED VIEW IF NOT EXISTS product_events_mv TO product_events AS +SELECT + OrgId, + Timestamp, + 'browser' AS Source, + SessionId, + Seq, + VisitorId, + UserId, + GroupId, + Type AS Kind, + if(Type = 'navigation', '$pageview', Message) AS EventName, + domain(Url) AS Host, + path(Url) AS PagePath, + Url, + '' AS ServiceName, + Attributes, + '' AS TraceId, + '' AS SpanId + FROM session_events + WHERE Type IN ('navigation', 'custom'); + +CREATE MATERIALIZED VIEW IF NOT EXISTS product_events_traces_mv TO product_events AS +SELECT + OrgId, + Timestamp, + 'trace' AS Source, + SpanAttributes['session.id'] AS SessionId, + 0 AS Seq, + SpanAttributes['maple.product_event.visitor_id'] AS VisitorId, + SpanAttributes['maple.product_event.user_id'] AS UserId, + SpanAttributes['maple.product_event.group_id'] AS GroupId, + 'custom' AS Kind, + SpanAttributes['maple.product_event.name'] AS EventName, + domain(SpanAttributes['maple.product_event.url']) AS Host, + path(SpanAttributes['maple.product_event.url']) AS PagePath, + SpanAttributes['maple.product_event.url'] AS Url, + ServiceName, + mapUpdate( + CAST( + mapFilter( + (k, v) -> NOT startsWith(k, 'maple.product_event.') + AND ( + NOT has(mapKeys(SpanAttributes), 'maple.product_event.include') + OR has( + arrayMap( + key -> trimBoth(key), + splitByChar(',', SpanAttributes['maple.product_event.include']) + ), + k + ) + ), + SpanAttributes + ), + 'Map(String, String)' + ), + mapApply( + (k, v) -> (substring(k, 26), v), + mapFilter((k, v) -> startsWith(k, 'maple.product_event.prop.'), SpanAttributes) + ) + ) AS Attributes, + TraceId, + SpanId + FROM traces + WHERE SpanAttributes['maple.product_event.name'] != ''; + +CREATE MATERIALIZED VIEW IF NOT EXISTS scanner_requests_mv TO scanner_requests AS +SELECT + OrgId, + Timestamp, + TraceId, + ServiceName, + if(multiSearchFirstIndexCaseInsensitive(concat(' ', leftUTF8(coalesce(nullIf(SpanAttributes['url.path'], ''), nullIf(replaceRegexpOne(SpanAttributes['http.target'], '[?#].*$', ''), ''), nullIf(path(SpanAttributes['url.full']), ''), nullIf(path(SpanAttributes['http.url']), ''), ''), 512), ' ', leftUTF8(coalesce(nullIf(SpanAttributes['url.query'], ''), nullIf(extract(SpanAttributes['http.target'], '\\?(.*)'), ''), ''), 1024)), ['/.env', '/.aws/', '/.ssh/', '/id_rsa', '/.npmrc', '/.htpasswd', '/.docker/config.json', '/.vscode/sftp.json', '/.s3cfg', '/wp-config.php', '/.git/', '/.svn/', '/.hg/', '/.ds_store', ' /actuator/env', ' /actuator/heapdump', ' /actuator/configprops', ' /actuator/gateway', ' /server-status', ' /server-info', 'phpinfo', '/info.php', ' /_profiler', ' /debug/pprof', ' /telescope/requests', '../', '..%2f', '%2e%2e', '..%5c', '%252e%252e', '/etc/passwd', '/proc/self/environ', 'win.ini', '/phpmyadmin', '/pma/', '/adminer', '/vendor/phpunit', 'eval-stdin.php', '/boaform', '/hnap1', '/goform/', '/cgi-bin/luci']) > 0, arrayElement(['/.env', '/.aws/', '/.ssh/', '/id_rsa', '/.npmrc', '/.htpasswd', '/.docker/config.json', '/.vscode/sftp.json', '/.s3cfg', '/wp-config.php', '/.git/', '/.svn/', '/.hg/', '/.ds_store', '/actuator/env', '/actuator/heapdump', '/actuator/configprops', '/actuator/gateway', '/server-status', '/server-info', 'phpinfo', '/info.php', '/_profiler', '/debug/pprof', '/telescope/requests', '../', '..%2f', '%2e%2e', '..%5c', '%252e%252e', '/etc/passwd', '/proc/self/environ', 'win.ini', '/phpmyadmin', '/pma/', '/adminer', '/vendor/phpunit', 'eval-stdin.php', '/boaform', '/hnap1', '/goform/', '/cgi-bin/luci'], multiSearchFirstIndexCaseInsensitive(concat(' ', leftUTF8(coalesce(nullIf(SpanAttributes['url.path'], ''), nullIf(replaceRegexpOne(SpanAttributes['http.target'], '[?#].*$', ''), ''), nullIf(path(SpanAttributes['url.full']), ''), nullIf(path(SpanAttributes['http.url']), ''), ''), 512), ' ', leftUTF8(coalesce(nullIf(SpanAttributes['url.query'], ''), nullIf(extract(SpanAttributes['http.target'], '\\?(.*)'), ''), ''), 1024)), ['/.env', '/.aws/', '/.ssh/', '/id_rsa', '/.npmrc', '/.htpasswd', '/.docker/config.json', '/.vscode/sftp.json', '/.s3cfg', '/wp-config.php', '/.git/', '/.svn/', '/.hg/', '/.ds_store', ' /actuator/env', ' /actuator/heapdump', ' /actuator/configprops', ' /actuator/gateway', ' /server-status', ' /server-info', 'phpinfo', '/info.php', ' /_profiler', ' /debug/pprof', ' /telescope/requests', '../', '..%2f', '%2e%2e', '..%5c', '%252e%252e', '/etc/passwd', '/proc/self/environ', 'win.ini', '/phpmyadmin', '/pma/', '/adminer', '/vendor/phpunit', 'eval-stdin.php', '/boaform', '/hnap1', '/goform/', '/cgi-bin/luci'])), arrayElement(['/wp-admin', '/wp-login.php', '/xmlrpc.php', '/wp-includes/', '/wp-content/plugins/'], multiSearchFirstIndexCaseInsensitive(concat(' ', leftUTF8(coalesce(nullIf(SpanAttributes['url.path'], ''), nullIf(replaceRegexpOne(SpanAttributes['http.target'], '[?#].*$', ''), ''), nullIf(path(SpanAttributes['url.full']), ''), nullIf(path(SpanAttributes['http.url']), ''), ''), 512), ' ', leftUTF8(coalesce(nullIf(SpanAttributes['url.query'], ''), nullIf(extract(SpanAttributes['http.target'], '\\?(.*)'), ''), ''), 1024)), ['/wp-admin', '/wp-login.php', '/xmlrpc.php', '/wp-includes/', '/wp-content/plugins/']))) AS Signature, + arrayElement(['ZGrab', 'Masscan', 'Nuclei', 'sqlmap', 'Nikto', 'Nmap', 'Gobuster', 'DirBuster', 'ffuf', 'WPScan', 'WhatWeb', 'Acunetix', 'Nessus', 'Censys', 'Palo Alto Expanse', 'LeakIX', 'LeakIX', 'Modat', 'Driftnet'], multiSearchFirstIndexCaseInsensitive(coalesce(nullIf(SpanAttributes['user_agent.original'], ''), nullIf(SpanAttributes['http.user_agent'], ''), ''), ['zgrab', 'masscan', 'nuclei', 'sqlmap', 'nikto', 'nmap', 'gobuster', 'dirbuster', 'fuzz faster u fool', 'wpscan', 'whatweb', 'acunetix', 'nessus', 'censysinspect', 'palo alto networks', 'l9explore', 'l9tcpid', 'modatscanner', 'internetmeasurement'])) AS Scanner, + leftUTF8(lower(replaceRegexpOne(coalesce(nullIf(SpanAttributes['server.address'], ''), nullIf(SpanAttributes['http.host'], ''), nullIf(SpanAttributes['net.host.name'], ''), nullIf(domain(SpanAttributes['url.full']), ''), nullIf(domain(SpanAttributes['http.url']), ''), ''), ':[0-9]+$', '')), 255) AS Host, + leftUTF8(coalesce(nullIf(SpanAttributes['url.path'], ''), nullIf(replaceRegexpOne(SpanAttributes['http.target'], '[?#].*$', ''), ''), nullIf(path(SpanAttributes['url.full']), ''), nullIf(path(SpanAttributes['http.url']), ''), ''), 512) AS Path, + toUInt16OrZero(coalesce(nullIf(SpanAttributes['http.response.status_code'], ''), nullIf(SpanAttributes['http.status_code'], ''), '')) AS HttpStatus, + leftUTF8(coalesce(nullIf(SpanAttributes['client.address'], ''), nullIf(SpanAttributes['http.client_ip'], ''), nullIf(SpanAttributes['net.peer.ip'], ''), ''), 64) AS ClientAddress, + leftUTF8(coalesce(nullIf(SpanAttributes['user_agent.original'], ''), nullIf(SpanAttributes['http.user_agent'], ''), ''), 256) AS UserAgent + FROM traces + WHERE SpanKind = 'Server' + AND (multiSearchFirstIndexCaseInsensitive(concat(' ', leftUTF8(coalesce(nullIf(SpanAttributes['url.path'], ''), nullIf(replaceRegexpOne(SpanAttributes['http.target'], '[?#].*$', ''), ''), nullIf(path(SpanAttributes['url.full']), ''), nullIf(path(SpanAttributes['http.url']), ''), ''), 512), ' ', leftUTF8(coalesce(nullIf(SpanAttributes['url.query'], ''), nullIf(extract(SpanAttributes['http.target'], '\\?(.*)'), ''), ''), 1024)), ['/.env', '/.aws/', '/.ssh/', '/id_rsa', '/.npmrc', '/.htpasswd', '/.docker/config.json', '/.vscode/sftp.json', '/.s3cfg', '/wp-config.php', '/.git/', '/.svn/', '/.hg/', '/.ds_store', ' /actuator/env', ' /actuator/heapdump', ' /actuator/configprops', ' /actuator/gateway', ' /server-status', ' /server-info', 'phpinfo', '/info.php', ' /_profiler', ' /debug/pprof', ' /telescope/requests', '../', '..%2f', '%2e%2e', '..%5c', '%252e%252e', '/etc/passwd', '/proc/self/environ', 'win.ini', '/phpmyadmin', '/pma/', '/adminer', '/vendor/phpunit', 'eval-stdin.php', '/boaform', '/hnap1', '/goform/', '/cgi-bin/luci']) > 0 OR multiSearchFirstIndexCaseInsensitive(concat(' ', leftUTF8(coalesce(nullIf(SpanAttributes['url.path'], ''), nullIf(replaceRegexpOne(SpanAttributes['http.target'], '[?#].*$', ''), ''), nullIf(path(SpanAttributes['url.full']), ''), nullIf(path(SpanAttributes['http.url']), ''), ''), 512), ' ', leftUTF8(coalesce(nullIf(SpanAttributes['url.query'], ''), nullIf(extract(SpanAttributes['http.target'], '\\?(.*)'), ''), ''), 1024)), ['/wp-admin', '/wp-login.php', '/xmlrpc.php', '/wp-includes/', '/wp-content/plugins/']) > 0 OR multiSearchFirstIndexCaseInsensitive(coalesce(nullIf(SpanAttributes['user_agent.original'], ''), nullIf(SpanAttributes['http.user_agent'], ''), ''), ['zgrab', 'masscan', 'nuclei', 'sqlmap', 'nikto', 'nmap', 'gobuster', 'dirbuster', 'fuzz faster u fool', 'wpscan', 'whatweb', 'acunetix', 'nessus', 'censysinspect', 'palo alto networks', 'l9explore', 'l9tcpid', 'modatscanner', 'internetmeasurement']) > 0) + AND leftUTF8(coalesce(nullIf(SpanAttributes['url.path'], ''), nullIf(replaceRegexpOne(SpanAttributes['http.target'], '[?#].*$', ''), ''), nullIf(path(SpanAttributes['url.full']), ''), nullIf(path(SpanAttributes['http.url']), ''), ''), 512) != ''; + +CREATE MATERIALIZED VIEW IF NOT EXISTS service_endpoint_details_hourly_mv TO service_endpoint_details_hourly AS +SELECT + OrgId, + ServiceName, + toStartOfHour(toDateTime(Timestamp)) AS Hour, + coalesce(nullIf(ResourceAttributes['deployment.environment.name'], ''), ResourceAttributes['deployment.environment']) AS DeploymentEnv, + if(((SpanName LIKE 'http.server %' OR SpanName IN ('GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS')) AND (SpanAttributes['http.route'] != '' OR SpanAttributes['url.path'] != '')), concat(if(SpanName LIKE 'http.server %', replaceOne(SpanName, 'http.server ', ''), SpanName), ' ', if(SpanAttributes['http.route'] != '', SpanAttributes['http.route'], SpanAttributes['url.path'])), SpanName) AS SpanName, + if(SpanAttributes['http.status_code'] != '', SpanAttributes['http.status_code'], SpanAttributes['http.response.status_code']) AS HttpStatusCode, + SpanAttributes['error.type'] AS ErrorType, + count() AS SpanCount, + sum(SampleRate) AS EstimatedSpanCount, + quantilesTDigestState(0.5, 0.95, 0.99)(Duration) AS DurationQuantiles, + quantilesTDigestIfState(0.5, 0.95, 0.99)(ifNull(toUInt64OrNull(if(SpanAttributes['http.request.body.size'] != '', SpanAttributes['http.request.body.size'], SpanAttributes['http.request_content_length'])), 0), toUInt64OrNull(if(SpanAttributes['http.request.body.size'] != '', SpanAttributes['http.request.body.size'], SpanAttributes['http.request_content_length'])) IS NOT NULL) AS BodySizeQuantiles, + maxIf(ifNull(toUInt64OrNull(if(SpanAttributes['http.request.body.size'] != '', SpanAttributes['http.request.body.size'], SpanAttributes['http.request_content_length'])), 0), toUInt64OrNull(if(SpanAttributes['http.request.body.size'] != '', SpanAttributes['http.request.body.size'], SpanAttributes['http.request_content_length'])) IS NOT NULL) AS BodySizeMax, + countIf(toUInt64OrNull(if(SpanAttributes['http.request.body.size'] != '', SpanAttributes['http.request.body.size'], SpanAttributes['http.request_content_length'])) IS NOT NULL) AS BodyCount, + approx_top_kState(10)(coalesce(nullIf(SpanAttributes['user_agent.original'], ''), nullIf(SpanAttributes['http.user_agent'], ''), '')) AS UserAgents, + max(Duration) AS SlowestDuration, + argMaxState(TraceId, Duration) AS SlowestTraceId, + argMaxState(Timestamp, Duration) AS SlowestTimestamp, + argMaxState(ifNull(toInt64(toUInt64OrNull(if(SpanAttributes['http.request.body.size'] != '', SpanAttributes['http.request.body.size'], SpanAttributes['http.request_content_length']))), -1), Duration) AS SlowestBodySize + FROM traces + WHERE SpanKind = 'Server' + AND if(SpanAttributes['http.status_code'] != '', SpanAttributes['http.status_code'], SpanAttributes['http.response.status_code']) != '' + GROUP BY OrgId, ServiceName, Hour, DeploymentEnv, SpanName, HttpStatusCode, ErrorType; + +CREATE MATERIALIZED VIEW IF NOT EXISTS service_external_edges_hourly_mv TO service_external_edges_hourly AS +SELECT + OrgId, + toStartOfHour(toDateTime(Timestamp)) AS Hour, + ServiceName, + multiIf( + coalesce(nullIf(SpanAttributes['messaging.destination.name'], ''), SpanAttributes['messaging.destination']) != '' OR SpanAttributes['messaging.system'] != '', 'messaging', + SpanAttributes['rpc.service'] != '' OR SpanAttributes['rpc.system'] != '', 'rpc', + 'http' + ) AS TargetType, + multiIf( + coalesce(nullIf(SpanAttributes['messaging.destination.name'], ''), SpanAttributes['messaging.destination']) != '' OR SpanAttributes['messaging.system'] != '', SpanAttributes['messaging.system'], + SpanAttributes['rpc.service'] != '' OR SpanAttributes['rpc.system'] != '', SpanAttributes['rpc.system'], + '' + ) AS TargetSystem, + multiIf( + coalesce(nullIf(SpanAttributes['messaging.destination.name'], ''), SpanAttributes['messaging.destination']) != '' OR SpanAttributes['messaging.system'] != '', + if(coalesce(nullIf(SpanAttributes['messaging.destination.name'], ''), SpanAttributes['messaging.destination']) != '', coalesce(nullIf(SpanAttributes['messaging.destination.name'], ''), SpanAttributes['messaging.destination']), SpanAttributes['messaging.system']), + SpanAttributes['rpc.service'] != '' OR SpanAttributes['rpc.system'] != '', + if(SpanAttributes['rpc.service'] != '', SpanAttributes['rpc.service'], SpanAttributes['rpc.system']), + if(SpanAttributes['server.address'] != '', + SpanAttributes['server.address'], + if(SpanAttributes['http.host'] != '', + SpanAttributes['http.host'], + SpanAttributes['url.authority'])) + ) AS TargetName, + coalesce(nullIf(ResourceAttributes['deployment.environment.name'], ''), ResourceAttributes['deployment.environment']) AS DeploymentEnv, + count() AS CallCount, + countIf(StatusCode = 'Error') AS ErrorCount, + sum(Duration / 1000000) AS DurationSumMs, + max(Duration / 1000000) AS MaxDurationMs, + sum(SampleRate) AS SampleRateSum, + quantilesTDigestWeightedState(0.5, 0.95)(Duration, toUInt32(greatest(SampleRate, 1.0))) AS DurationQuantiles + FROM traces + WHERE SpanKind IN ('Client', 'Producer') + AND coalesce(nullIf(SpanAttributes['db.system.name'], ''), SpanAttributes['db.system']) = '' + AND ServiceName != '' + AND ( + SpanAttributes['server.address'] != '' + OR SpanAttributes['http.host'] != '' + OR SpanAttributes['url.authority'] != '' + OR coalesce(nullIf(SpanAttributes['messaging.destination.name'], ''), SpanAttributes['messaging.destination']) != '' + OR SpanAttributes['messaging.system'] != '' + OR SpanAttributes['rpc.service'] != '' + OR SpanAttributes['rpc.system'] != '' + ) + GROUP BY OrgId, Hour, ServiceName, TargetType, TargetSystem, TargetName, DeploymentEnv + HAVING TargetName != ''; + +CREATE MATERIALIZED VIEW IF NOT EXISTS service_map_children_mv TO service_map_children AS +SELECT + OrgId, + toDateTime(Timestamp) AS Timestamp, + TraceId, + ParentSpanId, + ServiceName, + SpanKind, + Duration, + StatusCode, + TraceState, + coalesce(nullIf(ResourceAttributes['deployment.environment.name'], ''), ResourceAttributes['deployment.environment']) AS DeploymentEnv + FROM traces + WHERE SpanKind IN ('Server', 'Consumer') + AND ParentSpanId != ''; + +CREATE MATERIALIZED VIEW IF NOT EXISTS service_map_db_edges_hourly_mv TO service_map_db_edges_hourly AS +SELECT + OrgId, + toStartOfHour(toDateTime(Timestamp)) AS Hour, + ServiceName, + coalesce(nullIf(SpanAttributes['db.system.name'], ''), SpanAttributes['db.system']) AS DbSystem, + if(match(coalesce(nullIf(SpanAttributes['db.namespace'], ''), nullIf(SpanAttributes['db.name'], ''), nullIf(SpanAttributes['server.address'], ''), SpanAttributes['net.peer.name']), '^([0-9a-fA-F]{32}|.*[.]hyperdrive[.]local)$'), 'hyperdrive', coalesce(nullIf(SpanAttributes['db.namespace'], ''), nullIf(SpanAttributes['db.name'], ''), nullIf(SpanAttributes['server.address'], ''), SpanAttributes['net.peer.name'])) AS DbNamespace, + coalesce(nullIf(ResourceAttributes['deployment.environment.name'], ''), ResourceAttributes['deployment.environment']) AS DeploymentEnv, + count() AS CallCount, + countIf(StatusCode = 'Error') AS ErrorCount, + sum(Duration / 1000000) AS DurationSumMs, + max(Duration / 1000000) AS MaxDurationMs, + countIf(TraceState LIKE '%th:%') AS SampledSpanCount, + countIf(TraceState = '' OR TraceState NOT LIKE '%th:%') AS UnsampledSpanCount, + sum(SampleRate) AS SampleRateSum, + quantilesTDigestWeightedState(0.5, 0.95)(Duration, toUInt32(greatest(SampleRate, 1.0))) AS DurationQuantiles + FROM traces + WHERE SpanKind IN ('Client', 'Producer') + AND coalesce(nullIf(SpanAttributes['db.system.name'], ''), SpanAttributes['db.system']) != '' + AND ServiceName != '' + GROUP BY OrgId, Hour, ServiceName, DbSystem, DbNamespace, DeploymentEnv; + +CREATE MATERIALIZED VIEW IF NOT EXISTS service_map_db_edges_minutely_mv TO service_map_db_edges_minutely AS +SELECT + OrgId, + toStartOfMinute(toDateTime(Timestamp)) AS Minute, + ServiceName, + coalesce(nullIf(SpanAttributes['db.system.name'], ''), SpanAttributes['db.system']) AS DbSystem, + if(match(coalesce(nullIf(SpanAttributes['db.namespace'], ''), nullIf(SpanAttributes['db.name'], ''), nullIf(SpanAttributes['server.address'], ''), SpanAttributes['net.peer.name']), '^([0-9a-fA-F]{32}|.*[.]hyperdrive[.]local)$'), 'hyperdrive', coalesce(nullIf(SpanAttributes['db.namespace'], ''), nullIf(SpanAttributes['db.name'], ''), nullIf(SpanAttributes['server.address'], ''), SpanAttributes['net.peer.name'])) AS DbNamespace, + coalesce(nullIf(ResourceAttributes['deployment.environment.name'], ''), ResourceAttributes['deployment.environment']) AS DeploymentEnv, + count() AS CallCount, + countIf(StatusCode = 'Error') AS ErrorCount, + sum(Duration / 1000000) AS DurationSumMs, + max(Duration / 1000000) AS MaxDurationMs, + sum(SampleRate) AS SampleRateSum, + quantilesTDigestWeightedState(0.5, 0.95)(Duration, toUInt32(greatest(SampleRate, 1.0))) AS DurationQuantiles, + now() AS FirstInsertedAt + FROM traces + WHERE SpanKind IN ('Client', 'Producer') + AND coalesce(nullIf(SpanAttributes['db.system.name'], ''), SpanAttributes['db.system']) != '' + AND ServiceName != '' + GROUP BY OrgId, Minute, ServiceName, DbSystem, DbNamespace, DeploymentEnv; + +CREATE MATERIALIZED VIEW IF NOT EXISTS service_map_db_query_shapes_hourly_mv TO service_map_db_query_shapes_hourly AS +SELECT + OrgId, + toStartOfHour(toDateTime(Timestamp)) AS Hour, + ServiceName, + coalesce(nullIf(SpanAttributes['db.system.name'], ''), SpanAttributes['db.system']) AS DbSystem, + if(match(coalesce(nullIf(SpanAttributes['db.namespace'], ''), nullIf(SpanAttributes['db.name'], ''), nullIf(SpanAttributes['server.address'], ''), SpanAttributes['net.peer.name']), '^([0-9a-fA-F]{32}|.*[.]hyperdrive[.]local)$'), 'hyperdrive', coalesce(nullIf(SpanAttributes['db.namespace'], ''), nullIf(SpanAttributes['db.name'], ''), nullIf(SpanAttributes['server.address'], ''), SpanAttributes['net.peer.name'])) AS DbNamespace, + coalesce(nullIf(ResourceAttributes['deployment.environment.name'], ''), ResourceAttributes['deployment.environment']) AS DeploymentEnv, + coalesce( + nullIf(SpanAttributes['db.query.fingerprint'], ''), + nullIf(SpanAttributes['db.statement.fingerprint'], ''), + nullIf(if(coalesce(nullIf(SpanAttributes['db.query.text'], ''), SpanAttributes['db.statement']) != '', toString(cityHash64(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(replaceRegexpAll(lower(coalesce(nullIf(SpanAttributes['db.query.text'], ''), SpanAttributes['db.statement'])), '\'[^\']*\'', '?'), '\\bin\\s*\\([^)]*\\)', 'in (?)'), '[0-9]+(\\.[0-9]+)?', '?'), '\\s+', ' '), '^\\s+|\\s+$', ''))), ''), ''), + toString(cityHash64(coalesce( + nullIf(SpanAttributes['db.query.summary'], ''), + nullIf(if(SpanAttributes['db.operation.name'] != '', trimBoth(concat(SpanAttributes['db.operation.name'], if(coalesce(nullIf(SpanAttributes['db.collection.name'], ''), SpanAttributes['db.namespace']) != '', concat(' ', coalesce(nullIf(SpanAttributes['db.collection.name'], ''), SpanAttributes['db.namespace'])), ''))), ''), ''), + nullIf(if(coalesce(nullIf(SpanAttributes['db.query.text'], ''), SpanAttributes['db.statement']) != '', trimBoth(concat(upper(extract(coalesce(nullIf(SpanAttributes['db.query.text'], ''), SpanAttributes['db.statement']), '^\\s*(\\w+)')), if(extract(coalesce(nullIf(SpanAttributes['db.query.text'], ''), SpanAttributes['db.statement']), '(?i)(?:from|into|update|join|table)\\s+\\W?([\\w.]+)') != '', concat(' ', extract(coalesce(nullIf(SpanAttributes['db.query.text'], ''), SpanAttributes['db.statement']), '(?i)(?:from|into|update|join|table)\\s+\\W?([\\w.]+)')), ''))), ''), ''), + nullIf(SpanAttributes['query.context'], ''), + nullIf(SpanAttributes['db.operation.name'], ''), + nullIf(SpanAttributes['db.operation'], ''), + SpanName +))) +) AS QueryKey, + any(substring(coalesce( + nullIf(SpanAttributes['db.query.summary'], ''), + nullIf(if(SpanAttributes['db.operation.name'] != '', trimBoth(concat(SpanAttributes['db.operation.name'], if(coalesce(nullIf(SpanAttributes['db.collection.name'], ''), SpanAttributes['db.namespace']) != '', concat(' ', coalesce(nullIf(SpanAttributes['db.collection.name'], ''), SpanAttributes['db.namespace'])), ''))), ''), ''), + nullIf(if(coalesce(nullIf(SpanAttributes['db.query.text'], ''), SpanAttributes['db.statement']) != '', trimBoth(concat(upper(extract(coalesce(nullIf(SpanAttributes['db.query.text'], ''), SpanAttributes['db.statement']), '^\\s*(\\w+)')), if(extract(coalesce(nullIf(SpanAttributes['db.query.text'], ''), SpanAttributes['db.statement']), '(?i)(?:from|into|update|join|table)\\s+\\W?([\\w.]+)') != '', concat(' ', extract(coalesce(nullIf(SpanAttributes['db.query.text'], ''), SpanAttributes['db.statement']), '(?i)(?:from|into|update|join|table)\\s+\\W?([\\w.]+)')), ''))), ''), ''), + nullIf(SpanAttributes['query.context'], ''), + nullIf(SpanAttributes['db.operation.name'], ''), + nullIf(SpanAttributes['db.operation'], ''), + SpanName +), 1, 220)) AS QueryLabel, + any(substring(coalesce(nullIf(SpanAttributes['db.query.text'], ''), SpanAttributes['db.statement']), 1, 1000)) AS SampleStatement, + count() AS CallCount, + countIf(StatusCode = 'Error') AS ErrorCount, + sum(SampleRate) AS EstimatedCount, + sumIf(SampleRate, StatusCode = 'Error') AS EstimatedErrorCount, + sum(toFloat64(Duration) * SampleRate / 1000000) AS WeightedDurationSumMs, + quantilesTDigestWeightedState(0.5, 0.95)(Duration, toUInt32(greatest(SampleRate, 1.0))) AS DurationQuantiles + FROM traces + WHERE SpanKind IN ('Client', 'Producer') + AND coalesce(nullIf(SpanAttributes['db.system.name'], ''), SpanAttributes['db.system']) != '' + AND ServiceName != '' + GROUP BY OrgId, Hour, ServiceName, DbSystem, DbNamespace, DeploymentEnv, QueryKey; + +CREATE MATERIALIZED VIEW IF NOT EXISTS service_map_edges_hourly_ingest_mv TO service_map_edges_hourly AS +SELECT + OrgId, + Hour, + SourceService, + TargetService, + DeploymentEnv, + CallCount, + ErrorCount, + DurationSumMs, + MaxDurationMs, + SampledSpanCount, + UnsampledSpanCount, + SampleRateSum + FROM service_map_edges_hourly_ingest; + +CREATE MATERIALIZED VIEW IF NOT EXISTS service_map_spans_mv TO service_map_spans AS +SELECT + OrgId, + toDateTime(Timestamp) AS Timestamp, + TraceId, + SpanId, + ParentSpanId, + ServiceName, + SpanKind, + Duration, + StatusCode, + TraceState, + coalesce(nullIf(ResourceAttributes['deployment.environment.name'], ''), ResourceAttributes['deployment.environment']) AS DeploymentEnv + FROM traces + WHERE SpanKind IN ('Client', 'Producer', 'Server', 'Consumer'); + +CREATE MATERIALIZED VIEW IF NOT EXISTS service_operations_hourly_mv TO service_operations_hourly AS +SELECT + OrgId, + toStartOfHour(Minute) AS Hour, + ServiceName, + DeploymentEnv, + SpanName, + sum(SpanCount) AS SpanCount, + sum(EstimatedSpanCount) AS EstimatedSpanCount, + sum(ErrorCount) AS ErrorCount, + sum(EstimatedErrorCount) AS EstimatedErrorCount, + sum(DurationSum) AS DurationSum, + quantilesTDigestMergeState(0.5, 0.95)(DurationQuantiles) AS DurationQuantiles, + sum(ClassifiedSpanCount) AS ClassifiedSpanCount, + sum(ServerSpanCount) AS ServerSpanCount, + sum(RoutedSpanCount) AS RoutedSpanCount, + sum(HttpClassifiedSpanCount) AS HttpClassifiedSpanCount, + sum(HttpStatusCount) AS HttpStatusCount, + sum(Http4xxCount) AS Http4xxCount, + sum(Http5xxCount) AS Http5xxCount, + sum(RequestBodyBytes) AS RequestBodyBytes, + sum(RequestBodyCount) AS RequestBodyCount + FROM service_operations_minutely + GROUP BY OrgId, Hour, ServiceName, DeploymentEnv, SpanName; + +CREATE MATERIALIZED VIEW IF NOT EXISTS service_operations_minutely_mv TO service_operations_minutely AS +SELECT + OrgId, + toStartOfMinute(toDateTime(Timestamp)) AS Minute, + ServiceName, + coalesce(nullIf(ResourceAttributes['deployment.environment.name'], ''), ResourceAttributes['deployment.environment']) AS DeploymentEnv, + if(((SpanName LIKE 'http.server %' OR SpanName IN ('GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS')) AND (SpanAttributes['http.route'] != '' OR SpanAttributes['url.path'] != '')), concat(if(SpanName LIKE 'http.server %', replaceOne(SpanName, 'http.server ', ''), SpanName), ' ', if(SpanAttributes['http.route'] != '', SpanAttributes['http.route'], SpanAttributes['url.path'])), SpanName) AS SpanName, + count() AS SpanCount, + sum(SampleRate) AS EstimatedSpanCount, + countIf(StatusCode = 'Error') AS ErrorCount, + sumIf(SampleRate, StatusCode = 'Error') AS EstimatedErrorCount, + sum(toFloat64(Duration)) AS DurationSum, + quantilesTDigestState(0.5, 0.95)(Duration) AS DurationQuantiles, + count() AS ClassifiedSpanCount, + countIf(SpanKind IN ('Server', 'Consumer')) AS ServerSpanCount, + countIf(SpanAttributes['http.route'] != '') AS RoutedSpanCount, + count() AS HttpClassifiedSpanCount, + countIf(SpanKind = 'Server' AND if(SpanAttributes['http.status_code'] != '', SpanAttributes['http.status_code'], SpanAttributes['http.response.status_code']) != '') AS HttpStatusCount, + countIf(SpanKind = 'Server' AND toUInt16OrZero(if(SpanAttributes['http.status_code'] != '', SpanAttributes['http.status_code'], SpanAttributes['http.response.status_code'])) BETWEEN 400 AND 499) AS Http4xxCount, + countIf(SpanKind = 'Server' AND toUInt16OrZero(if(SpanAttributes['http.status_code'] != '', SpanAttributes['http.status_code'], SpanAttributes['http.response.status_code'])) BETWEEN 500 AND 599) AS Http5xxCount, + sumIf(ifNull(toUInt64OrNull(if(SpanAttributes['http.request.body.size'] != '', SpanAttributes['http.request.body.size'], SpanAttributes['http.request_content_length'])), 0), SpanKind = 'Server' AND toUInt64OrNull(if(SpanAttributes['http.request.body.size'] != '', SpanAttributes['http.request.body.size'], SpanAttributes['http.request_content_length'])) IS NOT NULL) AS RequestBodyBytes, + countIf(SpanKind = 'Server' AND toUInt64OrNull(if(SpanAttributes['http.request.body.size'] != '', SpanAttributes['http.request.body.size'], SpanAttributes['http.request_content_length'])) IS NOT NULL) AS RequestBodyCount + FROM traces + GROUP BY OrgId, Minute, ServiceName, DeploymentEnv, SpanName; + +CREATE MATERIALIZED VIEW IF NOT EXISTS service_overview_hourly_mv TO service_overview_hourly AS +SELECT + OrgId, + toStartOfHour(toDateTime(Timestamp)) AS Hour, + ServiceName, + coalesce(nullIf(ResourceAttributes['deployment.environment.name'], ''), ResourceAttributes['deployment.environment']) AS DeploymentEnv, + ResourceAttributes['service.namespace'] AS ServiceNamespace, + ResourceAttributes['vcs.ref.head.revision'] AS CommitSha, + count() AS SpanCount, + sum(SampleRate) AS EstimatedSpanCount, + countIf(StatusCode = 'Error') AS ErrorCount, + sumIf(SampleRate, StatusCode = 'Error') AS EstimatedErrorCount, + sum(toFloat64(Duration)) AS DurationSum, + quantilesTDigestState(0.5, 0.95, 0.99)(Duration) AS DurationQuantiles, + min(toDateTime(Timestamp)) AS FirstSeen, + countIf(StatusCode != 'Error' AND Duration < 500000000) AS ApdexSatisfiedCount, + countIf(StatusCode != 'Error' AND Duration >= 500000000 AND Duration < 2000000000) AS ApdexToleratingCount, + sum(SampleRate) AS EstimatedLadderSpanCount, + sumIf(SampleRate, Duration < 50000000) AS EstimatedUnder50Ms, + sumIf(SampleRate, Duration < 100000000) AS EstimatedUnder100Ms, + sumIf(SampleRate, Duration < 250000000) AS EstimatedUnder250Ms, + sumIf(SampleRate, Duration < 500000000) AS EstimatedUnder500Ms, + sumIf(SampleRate, Duration < 1000000000) AS EstimatedUnder1000Ms, + sumIf(SampleRate, Duration < 2500000000) AS EstimatedUnder2500Ms, + sumIf(SampleRate, Duration < 5000000000) AS EstimatedUnder5000Ms, + sumIf(SampleRate, Duration < 10000000000) AS EstimatedUnder10000Ms + FROM traces + WHERE SpanKind IN ('Server', 'Consumer') OR ParentSpanId = '' + GROUP BY OrgId, Hour, ServiceName, DeploymentEnv, ServiceNamespace, CommitSha; + +CREATE MATERIALIZED VIEW IF NOT EXISTS service_overview_minutely_mv TO service_overview_minutely AS +SELECT + OrgId, + toStartOfMinute(toDateTime(Timestamp)) AS Minute, + ServiceName, + coalesce(nullIf(ResourceAttributes['deployment.environment.name'], ''), ResourceAttributes['deployment.environment']) AS DeploymentEnv, + ResourceAttributes['service.namespace'] AS ServiceNamespace, + ResourceAttributes['vcs.ref.head.revision'] AS CommitSha, + count() AS SpanCount, + sum(SampleRate) AS EstimatedSpanCount, + countIf(StatusCode = 'Error') AS ErrorCount, + sumIf(SampleRate, StatusCode = 'Error') AS EstimatedErrorCount, + sum(toFloat64(Duration)) AS DurationSum, + quantilesTDigestState(0.5, 0.95, 0.99)(Duration) AS DurationQuantiles, + min(toDateTime(Timestamp)) AS FirstSeen, + countIf(StatusCode != 'Error' AND Duration < 500000000) AS ApdexSatisfiedCount, + countIf(StatusCode != 'Error' AND Duration >= 500000000 AND Duration < 2000000000) AS ApdexToleratingCount, + sum(SampleRate) AS EstimatedLadderSpanCount, + sumIf(SampleRate, Duration < 50000000) AS EstimatedUnder50Ms, + sumIf(SampleRate, Duration < 100000000) AS EstimatedUnder100Ms, + sumIf(SampleRate, Duration < 250000000) AS EstimatedUnder250Ms, + sumIf(SampleRate, Duration < 500000000) AS EstimatedUnder500Ms, + sumIf(SampleRate, Duration < 1000000000) AS EstimatedUnder1000Ms, + sumIf(SampleRate, Duration < 2500000000) AS EstimatedUnder2500Ms, + sumIf(SampleRate, Duration < 5000000000) AS EstimatedUnder5000Ms, + sumIf(SampleRate, Duration < 10000000000) AS EstimatedUnder10000Ms + FROM traces + WHERE SpanKind IN ('Server', 'Consumer') OR ParentSpanId = '' + GROUP BY OrgId, Minute, ServiceName, DeploymentEnv, ServiceNamespace, CommitSha; + +CREATE MATERIALIZED VIEW IF NOT EXISTS service_overview_spans_mv TO service_overview_spans AS +SELECT + OrgId, + toDateTime(Timestamp) AS Timestamp, + ServiceName, + Duration, + StatusCode, + TraceState, + coalesce(nullIf(ResourceAttributes['deployment.environment.name'], ''), ResourceAttributes['deployment.environment']) AS DeploymentEnv, + ResourceAttributes['vcs.ref.head.revision'] AS CommitSha, + SampleRate, + ResourceAttributes['service.namespace'] AS ServiceNamespace, + SpanKind, + toUInt8(ParentSpanId = '') AS IsRoot + FROM traces + WHERE SpanKind IN ('Server', 'Consumer') OR ParentSpanId = ''; + +CREATE MATERIALIZED VIEW IF NOT EXISTS service_platforms_hourly_mv TO service_platforms_hourly AS +SELECT + OrgId, + toStartOfHour(toDateTime(Timestamp)) AS Hour, + ServiceName, + coalesce(nullIf(ResourceAttributes['deployment.environment.name'], ''), ResourceAttributes['deployment.environment']) AS DeploymentEnv, + max(ResourceAttributes['k8s.cluster.name']) AS K8sCluster, + max(ResourceAttributes['k8s.pod.name']) AS K8sPodName, + max(ResourceAttributes['k8s.deployment.name']) AS K8sDeploymentName, + max(ResourceAttributes['k8s.statefulset.name']) AS K8sStatefulSetName, + max(ResourceAttributes['k8s.daemonset.name']) AS K8sDaemonSetName, + max(ResourceAttributes['k8s.namespace.name']) AS K8sNamespaceName, + max(ResourceAttributes['cloud.platform']) AS CloudPlatform, + max(ResourceAttributes['cloud.provider']) AS CloudProvider, + max(ResourceAttributes['faas.name']) AS FaasName, + max(ResourceAttributes['maple.sdk.type']) AS MapleSdkType, + max(ResourceAttributes['process.runtime.name']) AS ProcessRuntimeName, + count() AS SpanCount + FROM traces + WHERE ServiceName != '' + GROUP BY OrgId, Hour, ServiceName, DeploymentEnv; + +CREATE MATERIALIZED VIEW IF NOT EXISTS service_runtimes_hourly_mv TO service_runtimes_hourly AS +SELECT + OrgId, + toStartOfHour(toDateTime(Timestamp)) AS Hour, + ServiceName, + coalesce(nullIf(ResourceAttributes['deployment.environment.name'], ''), ResourceAttributes['deployment.environment']) AS DeploymentEnv, + leftUTF8(ResourceAttributes['process.runtime.name'], 64) AS RuntimeName, + leftUTF8(ResourceAttributes['process.runtime.version'], 64) AS RuntimeVersion, + leftUTF8(ResourceAttributes['process.executable.name'], 64) AS ExecutableName, + leftUTF8(ResourceAttributes['telemetry.sdk.language'], 64) AS SdkLanguage, + leftUTF8(ResourceAttributes['telemetry.sdk.name'], 64) AS SdkName, + leftUTF8(ResourceAttributes['telemetry.sdk.version'], 64) AS SdkVersion, + count() AS SpanCount + FROM traces + WHERE ServiceName != '' + GROUP BY OrgId, Hour, ServiceName, DeploymentEnv, RuntimeName, RuntimeVersion, ExecutableName, SdkLanguage, SdkName, SdkVersion; + +CREATE MATERIALIZED VIEW IF NOT EXISTS service_usage_logs_mv TO service_usage AS +SELECT + OrgId, + ServiceName, + toStartOfHour(TimestampTime) AS Hour, + count() AS LogCount, + sum(length(Body) + 200) AS LogSizeBytes, + 0 AS TraceCount, + 0 AS TraceSizeBytes, + 0 AS SumMetricCount, + 0 AS SumMetricSizeBytes, + 0 AS GaugeMetricCount, + 0 AS GaugeMetricSizeBytes, + 0 AS HistogramMetricCount, + 0 AS HistogramMetricSizeBytes, + 0 AS ExpHistogramMetricCount, + 0 AS ExpHistogramMetricSizeBytes + FROM logs + GROUP BY OrgId, ServiceName, Hour; + +CREATE MATERIALIZED VIEW IF NOT EXISTS service_usage_metrics_exp_histogram_mv TO service_usage AS +SELECT + OrgId, + ServiceName, + toStartOfHour(toDateTime(TimeUnix)) AS Hour, + 0 AS LogCount, + 0 AS LogSizeBytes, + 0 AS TraceCount, + 0 AS TraceSizeBytes, + 0 AS SumMetricCount, + 0 AS SumMetricSizeBytes, + 0 AS GaugeMetricCount, + 0 AS GaugeMetricSizeBytes, + 0 AS HistogramMetricCount, + 0 AS HistogramMetricSizeBytes, + count() AS ExpHistogramMetricCount, + count() * 300 AS ExpHistogramMetricSizeBytes + FROM metrics_exponential_histogram + GROUP BY OrgId, ServiceName, Hour; + +CREATE MATERIALIZED VIEW IF NOT EXISTS service_usage_metrics_gauge_mv TO service_usage AS +SELECT + OrgId, + ServiceName, + toStartOfHour(toDateTime(TimeUnix)) AS Hour, + 0 AS LogCount, + 0 AS LogSizeBytes, + 0 AS TraceCount, + 0 AS TraceSizeBytes, + 0 AS SumMetricCount, + 0 AS SumMetricSizeBytes, + count() AS GaugeMetricCount, + count() * 150 AS GaugeMetricSizeBytes, + 0 AS HistogramMetricCount, + 0 AS HistogramMetricSizeBytes, + 0 AS ExpHistogramMetricCount, + 0 AS ExpHistogramMetricSizeBytes + FROM metrics_gauge + GROUP BY OrgId, ServiceName, Hour; + +CREATE MATERIALIZED VIEW IF NOT EXISTS service_usage_metrics_histogram_mv TO service_usage AS +SELECT + OrgId, + ServiceName, + toStartOfHour(toDateTime(TimeUnix)) AS Hour, + 0 AS LogCount, + 0 AS LogSizeBytes, + 0 AS TraceCount, + 0 AS TraceSizeBytes, + 0 AS SumMetricCount, + 0 AS SumMetricSizeBytes, + 0 AS GaugeMetricCount, + 0 AS GaugeMetricSizeBytes, + count() AS HistogramMetricCount, + count() * 250 AS HistogramMetricSizeBytes, + 0 AS ExpHistogramMetricCount, + 0 AS ExpHistogramMetricSizeBytes + FROM metrics_histogram + GROUP BY OrgId, ServiceName, Hour; + +CREATE MATERIALIZED VIEW IF NOT EXISTS service_usage_metrics_sum_mv TO service_usage AS +SELECT + OrgId, + ServiceName, + toStartOfHour(toDateTime(TimeUnix)) AS Hour, + 0 AS LogCount, + 0 AS LogSizeBytes, + 0 AS TraceCount, + 0 AS TraceSizeBytes, + count() AS SumMetricCount, + count() * 150 AS SumMetricSizeBytes, + 0 AS GaugeMetricCount, + 0 AS GaugeMetricSizeBytes, + 0 AS HistogramMetricCount, + 0 AS HistogramMetricSizeBytes, + 0 AS ExpHistogramMetricCount, + 0 AS ExpHistogramMetricSizeBytes + FROM metrics_sum + GROUP BY OrgId, ServiceName, Hour; + +CREATE MATERIALIZED VIEW IF NOT EXISTS service_usage_traces_mv TO service_usage AS +SELECT + OrgId, + ServiceName, + toStartOfHour(toDateTime(Timestamp)) AS Hour, + 0 AS LogCount, + 0 AS LogSizeBytes, + count() AS TraceCount, + sum(length(SpanName) + 300) AS TraceSizeBytes, + 0 AS SumMetricCount, + 0 AS SumMetricSizeBytes, + 0 AS GaugeMetricCount, + 0 AS GaugeMetricSizeBytes, + 0 AS HistogramMetricCount, + 0 AS HistogramMetricSizeBytes, + 0 AS ExpHistogramMetricCount, + 0 AS ExpHistogramMetricSizeBytes + FROM traces + GROUP BY OrgId, ServiceName, Hour; + +CREATE MATERIALIZED VIEW IF NOT EXISTS source_usage_logs_mv TO source_usage_hourly AS +SELECT + OrgId, + ResourceAttributes['maple_source_id'] AS SourceId, + toStartOfHour(TimestampTime) AS Hour, + 0 AS SpanCount, + 0 AS SpanSizeBytes, + count() AS LogCount, + sum(length(Body) + 200) AS LogSizeBytes, + 0 AS MetricCount, + 0 AS MetricSizeBytes + FROM logs + WHERE ResourceAttributes['maple_source_id'] != '' + GROUP BY OrgId, SourceId, Hour; + +CREATE MATERIALIZED VIEW IF NOT EXISTS source_usage_metrics_exp_histogram_mv TO source_usage_hourly AS +SELECT + OrgId, + ResourceAttributes['maple_source_id'] AS SourceId, + toStartOfHour(toDateTime(TimeUnix)) AS Hour, + 0 AS SpanCount, + 0 AS SpanSizeBytes, + 0 AS LogCount, + 0 AS LogSizeBytes, + count() AS MetricCount, + count() * 300 AS MetricSizeBytes + FROM metrics_exponential_histogram + WHERE ResourceAttributes['maple_source_id'] != '' + GROUP BY OrgId, SourceId, Hour; + +CREATE MATERIALIZED VIEW IF NOT EXISTS source_usage_metrics_gauge_mv TO source_usage_hourly AS +SELECT + OrgId, + ResourceAttributes['maple_source_id'] AS SourceId, + toStartOfHour(toDateTime(TimeUnix)) AS Hour, + 0 AS SpanCount, + 0 AS SpanSizeBytes, + 0 AS LogCount, + 0 AS LogSizeBytes, + count() AS MetricCount, + count() * 150 AS MetricSizeBytes + FROM metrics_gauge + WHERE ResourceAttributes['maple_source_id'] != '' + GROUP BY OrgId, SourceId, Hour; + +CREATE MATERIALIZED VIEW IF NOT EXISTS source_usage_metrics_histogram_mv TO source_usage_hourly AS +SELECT + OrgId, + ResourceAttributes['maple_source_id'] AS SourceId, + toStartOfHour(toDateTime(TimeUnix)) AS Hour, + 0 AS SpanCount, + 0 AS SpanSizeBytes, + 0 AS LogCount, + 0 AS LogSizeBytes, + count() AS MetricCount, + count() * 250 AS MetricSizeBytes + FROM metrics_histogram + WHERE ResourceAttributes['maple_source_id'] != '' + GROUP BY OrgId, SourceId, Hour; + +CREATE MATERIALIZED VIEW IF NOT EXISTS source_usage_metrics_sum_mv TO source_usage_hourly AS +SELECT + OrgId, + ResourceAttributes['maple_source_id'] AS SourceId, + toStartOfHour(toDateTime(TimeUnix)) AS Hour, + 0 AS SpanCount, + 0 AS SpanSizeBytes, + 0 AS LogCount, + 0 AS LogSizeBytes, + count() AS MetricCount, + count() * 150 AS MetricSizeBytes + FROM metrics_sum + WHERE ResourceAttributes['maple_source_id'] != '' + GROUP BY OrgId, SourceId, Hour; + +CREATE MATERIALIZED VIEW IF NOT EXISTS source_usage_traces_mv TO source_usage_hourly AS +SELECT + OrgId, + ResourceAttributes['maple_source_id'] AS SourceId, + toStartOfHour(toDateTime(Timestamp)) AS Hour, + count() AS SpanCount, + sum(length(SpanName) + 300) AS SpanSizeBytes, + 0 AS LogCount, + 0 AS LogSizeBytes, + 0 AS MetricCount, + 0 AS MetricSizeBytes + FROM traces + WHERE ResourceAttributes['maple_source_id'] != '' + GROUP BY OrgId, SourceId, Hour; + +CREATE MATERIALIZED VIEW IF NOT EXISTS span_metrics_calls_hourly_mv TO span_metrics_calls_hourly AS +SELECT + OrgId, + toStartOfHour(toDateTime(TimeUnix)) AS Hour, + ServiceName, + MetricName, + Attributes['span.kind'] AS SpanKind, + cityHash64(mapKeys(Attributes), mapValues(Attributes)) AS AttrFingerprint, + cityHash64(mapKeys(ResourceAttributes), mapValues(ResourceAttributes)) AS ResourceFingerprint, + StartTimeUnix, + argMaxState(Value, TimeUnix) AS LastValue + FROM metrics_sum + -- 'traces.span.metrics.calls' is the name the collector actually emits: + -- spanmetricsconnector output is namespaced by the pipeline it is attached + -- to. Without it this MV matched nothing and the target sat at 0 rows since + -- it was created, while ~880k rows / 2 days of the real counter flowed past + -- into metrics_sum and every read fell back to the raw window-function scan + -- (~7s p95 -- see queries/metrics.ts). Keep this list in sync with + -- SPAN_METRICS_CALLS_NAMES on the read side. + WHERE MetricName IN ('span.metrics.calls', 'calls', 'traces.span.metrics.calls') AND IsMonotonic + GROUP BY OrgId, Hour, ServiceName, MetricName, SpanKind, AttrFingerprint, ResourceFingerprint, StartTimeUnix; + +CREATE MATERIALIZED VIEW IF NOT EXISTS trace_facets_hourly_mv TO trace_facets_hourly AS +SELECT + OrgId, + toStartOfHour(Timestamp) AS Hour, + ServiceName, + SpanName, + HttpMethod, + HttpStatusCode, + DeploymentEnv, + ServiceNamespace, + HasError, + count() AS TraceCount, + min(Duration) AS DurationMin, + max(Duration) AS DurationMax, + quantilesTDigestState(0.5, 0.95)(Duration) AS DurationQuantiles + FROM trace_list_mv + GROUP BY OrgId, Hour, ServiceName, SpanName, HttpMethod, HttpStatusCode, DeploymentEnv, ServiceNamespace, HasError; + +CREATE MATERIALIZED VIEW IF NOT EXISTS trace_index_mv TO trace_index AS +SELECT + OrgId, + TraceId, + ServiceName, + toStartOfFiveMinutes(Timestamp) AS Bucket, + min(Timestamp) AS MinTimestamp, + max(Timestamp) AS MaxTimestamp + FROM traces + GROUP BY OrgId, TraceId, ServiceName, Bucket; + +CREATE MATERIALIZED VIEW IF NOT EXISTS trace_list_mv_mv TO trace_list_mv AS +SELECT + OrgId, + TraceId, + toDateTime(Timestamp) AS Timestamp, + ServiceName, + if( + (SpanName LIKE 'http.server %' OR SpanName IN ('GET','POST','PUT','PATCH','DELETE','HEAD','OPTIONS')) + AND (SpanAttributes['http.route'] != '' OR SpanAttributes['url.path'] != ''), + concat( + if(SpanName LIKE 'http.server %', replaceOne(SpanName, 'http.server ', ''), SpanName), + ' ', + if(SpanAttributes['http.route'] != '', SpanAttributes['http.route'], SpanAttributes['url.path']) + ), + SpanName + ) AS SpanName, + SpanKind, + Duration, + StatusCode, + if(SpanAttributes['http.method'] != '', SpanAttributes['http.method'], SpanAttributes['http.request.method']) AS HttpMethod, + if(SpanAttributes['http.route'] != '', SpanAttributes['http.route'], if(SpanAttributes['url.path'] != '', SpanAttributes['url.path'], SpanAttributes['http.target'])) AS HttpRoute, + if(SpanAttributes['http.status_code'] != '', SpanAttributes['http.status_code'], SpanAttributes['http.response.status_code']) AS HttpStatusCode, + coalesce(nullIf(ResourceAttributes['deployment.environment.name'], ''), ResourceAttributes['deployment.environment']) AS DeploymentEnv, + toUInt8( + StatusCode = 'Error' + OR (SpanAttributes['http.status_code'] != '' AND toUInt16OrZero(SpanAttributes['http.status_code']) >= 500) + OR (SpanAttributes['http.response.status_code'] != '' AND toUInt16OrZero(SpanAttributes['http.response.status_code']) >= 500) + ) AS HasError, + TraceState, + ResourceAttributes['service.namespace'] AS ServiceNamespace + FROM traces + WHERE ParentSpanId = ''; + +CREATE MATERIALIZED VIEW IF NOT EXISTS trace_resource_attribute_keys_mv TO attribute_keys_hourly AS +SELECT + OrgId, + toStartOfHour(toDateTime(Timestamp)) AS Hour, + arrayJoin(mapKeys(ResourceAttributes)) AS AttributeKey, + 'resource' AS AttributeScope, + count() AS UsageCount + FROM traces + WHERE ResourceAttributes != map() + GROUP BY OrgId, Hour, AttributeKey, AttributeScope; + +CREATE MATERIALIZED VIEW IF NOT EXISTS trace_resource_attribute_values_mv TO attribute_values_hourly AS +SELECT + OrgId, + toStartOfHour(toDateTime(Timestamp)) AS Hour, + AttributeKey, + AttributeValue, + 'resource' AS AttributeScope, + count() AS UsageCount + FROM traces + ARRAY JOIN + mapKeys(ResourceAttributes) AS AttributeKey, + mapValues(ResourceAttributes) AS AttributeValue + WHERE AttributeValue != '' + AND length(AttributeValue) <= 128 + AND NOT (length(AttributeValue) > 4 AND match(AttributeValue, '^[0-9]+([.][0-9]+)?$')) + AND NOT match(AttributeKey, '(_id|[.]id|Id|_ns)$') + AND AttributeKey NOT LIKE 'http.request.header.%' + AND AttributeKey NOT LIKE 'http.response.header.%' + GROUP BY OrgId, Hour, AttributeKey, AttributeValue, AttributeScope; + +CREATE MATERIALIZED VIEW IF NOT EXISTS trace_span_attribute_keys_mv TO attribute_keys_hourly AS +SELECT + OrgId, + toStartOfHour(toDateTime(Timestamp)) AS Hour, + arrayJoin(mapKeys(SpanAttributes)) AS AttributeKey, + 'span' AS AttributeScope, + count() AS UsageCount + FROM traces + WHERE SpanAttributes != map() + GROUP BY OrgId, Hour, AttributeKey, AttributeScope; + +CREATE MATERIALIZED VIEW IF NOT EXISTS trace_span_attribute_values_mv TO attribute_values_hourly AS +SELECT + OrgId, + toStartOfHour(toDateTime(Timestamp)) AS Hour, + AttributeKey, + AttributeValue, + 'span' AS AttributeScope, + count() AS UsageCount + FROM traces + ARRAY JOIN + mapKeys(SpanAttributes) AS AttributeKey, + mapValues(SpanAttributes) AS AttributeValue + WHERE AttributeValue != '' + AND length(AttributeValue) <= 128 + AND NOT (length(AttributeValue) > 4 AND match(AttributeValue, '^[0-9]+([.][0-9]+)?$')) + AND NOT match(AttributeKey, '(_id|[.]id|Id|_ns)$') + AND AttributeKey NOT LIKE 'http.request.header.%' + AND AttributeKey NOT LIKE 'http.response.header.%' + GROUP BY OrgId, Hour, AttributeKey, AttributeValue, AttributeScope; + +CREATE MATERIALIZED VIEW IF NOT EXISTS traces_aggregates_hourly_mv TO traces_aggregates_hourly AS +SELECT + OrgId, + toStartOfHour(toDateTime(Timestamp)) AS Hour, + ServiceName, + SpanName, + SpanKind, + StatusCode, + IsEntryPoint, + coalesce(nullIf(ResourceAttributes['deployment.environment.name'], ''), ResourceAttributes['deployment.environment']) AS DeploymentEnv, + sum(SampleRate) AS WeightedCount, + sum(toFloat64(Duration) * SampleRate) AS WeightedDurationSum, + sumIf(SampleRate, StatusCode = 'Error') AS WeightedErrorCount, + quantilesTDigestWeightedState(0.5, 0.95, 0.99)(Duration, toUInt32(SampleRate)) AS DurationQuantiles, + min(Duration) AS DurationMin, + max(Duration) AS DurationMax + FROM traces + GROUP BY OrgId, Hour, ServiceName, SpanName, SpanKind, StatusCode, IsEntryPoint, DeploymentEnv; + +CREATE MATERIALIZED VIEW IF NOT EXISTS user_activity_hourly_logs_mv TO user_activity_hourly AS +SELECT + OrgId, + ResourceAttributes['service.namespace'] AS ServiceNamespace, + if(LogAttributes['user.id'] != '', LogAttributes['user.id'], LogAttributes['enduser.id']) AS UserId, + toStartOfHour(TimestampTime) AS Hour, + ServiceName, + toUInt64(0) AS SpanCount, + toUInt64(0) AS ErrorSpanCount, + toUInt64(0) AS RequestCount, + toUInt64(0) AS FailedRequestCount, + uniqIfState(TraceId, toUInt8(0)) AS TraceIds, + uniqIfState(LogAttributes['session.id'], LogAttributes['session.id'] != '') AS SessionIds, + quantilesTDigestIfState(0.5, 0.95)(toUInt64(0), toUInt8(0)) AS RequestDurationQuantiles, + approx_top_kState(10)('') AS TopRoutes, + count() AS LogCount, + countIf(SeverityNumber >= 13 AND SeverityNumber < 17) AS WarnLogCount, + countIf(SeverityNumber >= 17) AS ErrorLogCount, + min(TimestampTime) AS FirstSeen, + max(TimestampTime) AS LastSeen, + toUInt64(0) AS ClassifiedRequestCount, + toUInt64(0) AS WebRequestCount, + toUInt64(0) AS MobileRequestCount + FROM logs + WHERE if(LogAttributes['user.id'] != '', LogAttributes['user.id'], LogAttributes['enduser.id']) != '' + GROUP BY OrgId, ServiceNamespace, UserId, Hour, ServiceName; + +CREATE MATERIALIZED VIEW IF NOT EXISTS user_activity_hourly_traces_mv TO user_activity_hourly AS +SELECT + OrgId, + ResourceAttributes['service.namespace'] AS ServiceNamespace, + if(SpanAttributes['user.id'] != '', SpanAttributes['user.id'], SpanAttributes['enduser.id']) AS UserId, + toStartOfHour(toDateTime(Timestamp)) AS Hour, + ServiceName, + count() AS SpanCount, + countIf(StatusCode = 'Error') AS ErrorSpanCount, + countIf(IsEntryPoint = 1) AS RequestCount, + countIf(IsEntryPoint = 1 AND StatusCode = 'Error') AS FailedRequestCount, + uniqIfState(TraceId, TraceId != '') AS TraceIds, + uniqIfState(SpanAttributes['session.id'], SpanAttributes['session.id'] != '') AS SessionIds, + quantilesTDigestIfState(0.5, 0.95)(Duration, IsEntryPoint = 1) AS RequestDurationQuantiles, + approx_top_kState(10)(if(IsEntryPoint = 1, if(((SpanName LIKE 'http.server %' OR SpanName IN ('GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS')) AND (SpanAttributes['http.route'] != '' OR SpanAttributes['url.path'] != '')), concat(if(SpanName LIKE 'http.server %', replaceOne(SpanName, 'http.server ', ''), SpanName), ' ', if(SpanAttributes['http.route'] != '', SpanAttributes['http.route'], SpanAttributes['url.path'])), SpanName), '')) AS TopRoutes, + toUInt64(0) AS LogCount, + toUInt64(0) AS WarnLogCount, + toUInt64(0) AS ErrorLogCount, + min(toDateTime(Timestamp)) AS FirstSeen, + max(toDateTime(Timestamp)) AS LastSeen, + countIf(IsEntryPoint = 1) AS ClassifiedRequestCount, + countIf(IsEntryPoint = 1 AND (ResourceAttributes['maple.sdk.type'] IN ('browser', 'client') OR if(SpanAttributes['user_agent.original'] != '', SpanAttributes['user_agent.original'], SpanAttributes['http.user_agent']) LIKE 'Mozilla/%')) AS WebRequestCount, + countIf(IsEntryPoint = 1 AND (NOT (ResourceAttributes['maple.sdk.type'] IN ('browser', 'client') OR if(SpanAttributes['user_agent.original'] != '', SpanAttributes['user_agent.original'], SpanAttributes['http.user_agent']) LIKE 'Mozilla/%') AND (ResourceAttributes['telemetry.sdk.language'] IN ('swift', 'kotlin') OR ResourceAttributes['os.name'] IN ('iOS', 'iPadOS', 'Android') OR match(if(SpanAttributes['user_agent.original'] != '', SpanAttributes['user_agent.original'], SpanAttributes['http.user_agent']), '^(Dalvik|okhttp)/| CFNetwork/')))) AS MobileRequestCount + FROM traces + WHERE if(SpanAttributes['user.id'] != '', SpanAttributes['user.id'], SpanAttributes['enduser.id']) != '' + GROUP BY OrgId, ServiceNamespace, UserId, Hour, ServiceName; diff --git a/apps/cli/test/local-store-migrations.test.ts b/apps/cli/test/local-store-migrations.test.ts index d0cdb140d9..db33a11720 100644 --- a/apps/cli/test/local-store-migrations.test.ts +++ b/apps/cli/test/local-store-migrations.test.ts @@ -15,7 +15,7 @@ import { LOCAL_SCHEMA_V6, LOCAL_SCHEMA_V10, LOCAL_SCHEMA_V11, - LOCAL_SCHEMA_V50, + LOCAL_SCHEMA_V51, SCHEMA_DIGEST, SCHEMA_FINGERPRINT, } from "@maple/local-server/schema-identity" @@ -100,11 +100,11 @@ const [v10ToV11ProductEventsModule] = Arr.filter( ok(v10ToV11ProductEventsModule !== undefined) describe("current local schema identity", () => { - it("matches the generated v50 revision and keeps the issue-297 identity frozen", () => { + it("matches the generated v51 revision and keeps the issue-297 identity frozen", () => { // oxlint-disable-next-line test-slop/no-constant-pin -- rewritten by local-schema:bump; catches an unbumped schema edit - expect(SCHEMA_FINGERPRINT).toBe("4285696c4a009e8d") + expect(SCHEMA_FINGERPRINT).toBe("252552abda61d2c2") // oxlint-disable-next-line test-slop/no-constant-pin -- rewritten by local-schema:bump; catches an unbumped schema edit - expect(SCHEMA_DIGEST).toBe("4285696c4a009e8dc7c8e1f134778d680e51b3bf11f41423cee8ae2343f3edc3") + expect(SCHEMA_DIGEST).toBe("252552abda61d2c29ed4ee9e8a685973743ce96ce179819e0c7d2ad531202ba9") // oxlint-disable-next-line test-slop/no-constant-pin -- frozen identity that stores already on disk carry expect(ISSUE_297_TARGET_SCHEMA_PROJECT_REVISION).toBe( "506bc745f7a7eca202ec905a6403a6815e86413faf0cd3cbbf73881023edce91", @@ -112,8 +112,8 @@ describe("current local schema identity", () => { expect(CURRENT_SCHEMA_PROJECT_REVISION).toMatch(/^[0-9a-f]{64}$/) expect(LOCAL_SCHEMA_MANIFEST.objects.length).toBeGreaterThan(60) // oxlint-disable-next-line test-slop/no-constant-pin -- anchor that local-schema:bump rewrites on every bump - expect(CURRENT_LOCAL_SCHEMA.version).toBe(50) - expect(CURRENT_LOCAL_SCHEMA).toEqual(LOCAL_SCHEMA_V50) + expect(CURRENT_LOCAL_SCHEMA.version).toBe(51) + expect(CURRENT_LOCAL_SCHEMA).toEqual(LOCAL_SCHEMA_V51) const logs = Option.getOrUndefined( Arr.findFirst(LOCAL_SCHEMA_MANIFEST.objects, (object) => object.name === "logs"), ) @@ -490,7 +490,7 @@ describe("local migration registry", () => { // One past the current tip — bump alongside LOCAL_SCHEMA_VERSION, or this // stops testing the future-store guard and starts testing the // unknown-fingerprint one. - { ...CURRENT_LOCAL_SCHEMA, version: 51, fingerprint: "future", digest: SCHEMA_DIGEST }, + { ...CURRENT_LOCAL_SCHEMA, version: 52, fingerprint: "future", digest: SCHEMA_DIGEST }, CURRENT_LOCAL_SCHEMA, ), ).toThrow(/newer than this build/) @@ -668,6 +668,7 @@ describe("local migration registry", () => { "local-0047-to-0048-user-activity-hourly", "local-0048-to-0049-error-events-user-id", "local-0049-to-0050-user-activity-channels", + "local-0050-to-0051-service-overview-slo-latency-ladder", ] const versions = (id: string) => { const match = /^local-(\d{4})-to-(\d{4})-[a-z0-9]+(-[a-z0-9]+)*$/.exec(id) @@ -1758,6 +1759,7 @@ describe("v10 -> v11 product events module", () => { "local-0047-to-0048-user-activity-hourly", "local-0048-to-0049-error-events-user-id", "local-0049-to-0050-user-activity-channels", + "local-0050-to-0051-service-overview-slo-latency-ladder", ]) expect(chain[0]?.to).toEqual(LOCAL_SCHEMA_V11) // The dropped table is declared, and the backfilled ones say what they diff --git a/apps/cli/test/local-store-slo-latency-ladder.test.ts b/apps/cli/test/local-store-slo-latency-ladder.test.ts new file mode 100644 index 0000000000..a060af556e --- /dev/null +++ b/apps/cli/test/local-store-slo-latency-ladder.test.ts @@ -0,0 +1,168 @@ +import { afterAll, beforeAll, expect, test } from "bun:test" +import { mkdtemp, rm } from "node:fs/promises" +import { tmpdir } from "node:os" +import { join } from "node:path" +import { Schema, Array as Arr, Record as Rec, String as Str } from "effect" +import { Chdb } from "@maple/local-server/chdb" +import { decodeJsonEachRow } from "@maple/local-server/chdb-rows" +import type { MigrationModuleContext } from "../src/server/local-store-migration-module" +import { localSchemaSnapshot } from "../src/server/schema-snapshots" +import { describeWithChdb } from "./libchdb-test-support" +import { migrationStep, openStore } from "./local-store-step-test-support" + +const HOUR_MS = 3_600_000 +const MS_NS = 1_000_000 +const iso = (ms: number) => Str.replace("Z", "")(Str.replace("T", " ")(new Date(ms).toISOString())) +const SPAN_COLUMNS = + "OrgId, Timestamp, TraceId, SpanId, ParentSpanId, SpanKind, ServiceName, Duration, StatusCode, SampleRate" + +interface Span { + readonly durationMs: number + readonly sampleRate: number + readonly status?: "Ok" | "Error" + readonly parent?: string + readonly kind?: "Server" | "Client" +} + +const spanRows = (atMs: number, prefix: string, spans: ReadonlyArray) => + Arr.join( + Arr.map( + spans, + (span, n) => + `('local', '${iso(atMs)}', '${prefix}-trace-${n}', '${prefix}-span-${n}', '${span.parent ?? ""}', '${span.kind ?? "Server"}', 'api', ${span.durationMs * MS_NS}, '${span.status ?? "Ok"}', ${span.sampleRate})`, + ), + ", ", + ) + +// The coverage total first, then the rungs: a pre-v51 bucket reads 0 on all nine. +const LADDER = [ + "EstimatedLadderSpanCount", + "EstimatedUnder50Ms", + "EstimatedUnder100Ms", + "EstimatedUnder250Ms", + "EstimatedUnder500Ms", + "EstimatedUnder1000Ms", + "EstimatedUnder2500Ms", + "EstimatedUnder5000Ms", + "EstimatedUnder10000Ms", +] as const + +// Entry-point spans, sampling-weighted. 500ms and 20s sit on/over a rung, and the +// 300ms error still counts: the ladder ignores status. +const POST_MIGRATION_SPANS: ReadonlyArray = [ + { durationMs: 30, sampleRate: 1 }, + { durationMs: 75, sampleRate: 2 }, + { durationMs: 300, sampleRate: 1, status: "Error" }, + { durationMs: 500, sampleRate: 4 }, + { durationMs: 1200, sampleRate: 1 }, + { durationMs: 20_000, sampleRate: 1 }, + // A non-root client span is not an entry point and never reaches the rollups. + { durationMs: 10, sampleRate: 100, kind: "Client", parent: "x" }, +] + +const decodeLadder = decodeJsonEachRow( + Schema.Struct({ + spans: Schema.String, + ...Rec.fromEntries(Arr.map(LADDER, (column) => [column, Schema.String] as const)), + }), +) + +/** The ladder and span count of one rollup's buckets at or after `fromMs` and before `toMs`. */ +const ladder = ( + db: Chdb, + table: "service_overview_hourly" | "service_overview_minutely", + fromMs: number, + toMs: number, +) => { + const time = table === "service_overview_hourly" ? "Hour" : "Minute" + const sums = Arr.join( + Arr.map(LADDER, (column) => `toString(sum(${column})) AS ${column}`), + ", ", + ) + return decodeLadder( + db.query( + `SELECT toString(sum(SpanCount)) AS spans, ${sums} FROM ${table} WHERE OrgId = 'local' AND ${time} >= toDateTime('${iso(fromMs)}') AND ${time} < toDateTime('${iso(toMs)}')`, + ), + )[0] +} + +describeWithChdb("v50 -> v51 service-overview SLO latency ladder", () => { + const STEP_ID = "local-0050-to-0051-service-overview-slo-latency-ladder" + let migration: ReturnType["migration"] + let root = "" + let sourceDataDir = "" + let targetDataDir = "" + let context: MigrationModuleContext + const nowMs = Date.now() + const currentHourMs = Math.floor(nowMs / HOUR_MS) * HOUR_MS + const oldHourMs = currentHourMs - 3 * HOUR_MS + + beforeAll(async () => { + root = await mkdtemp(join(tmpdir(), "maple-slo-latency-ladder-")) + sourceDataDir = join(root, "source", "data") + targetDataDir = join(root, "target", "data") + const db = Chdb.open({ dataDir: sourceDataDir, schemaSql: localSchemaSnapshot(50).sql }) + db.exec( + `INSERT INTO traces (${SPAN_COLUMNS}) VALUES ${spanRows(oldHourMs + 60_000, "old", [ + { durationMs: 20, sampleRate: 1 }, + { durationMs: 40, sampleRate: 1 }, + ])}`, + ) + db.close() + ;({ migration, context } = migrationStep(STEP_ID, sourceDataDir, targetDataDir, nowMs)) + }) + + afterAll(async () => { + if (root) await rm(root, { recursive: true, force: true }) + }) + + test("keeps every existing bucket and reads the ladder and its coverage as zero on them", async () => { + const state = await migration.preflight(context) + await migration.prepareTarget(context, state) + const progress = await migration.apply(context, state, undefined) + await migration.verify(context, state, progress) + + const old = await openStore( + targetDataDir, + (db) => ({ + hourly: ladder(db, "service_overview_hourly", oldHourMs, oldHourMs + HOUR_MS), + minutely: ladder(db, "service_overview_minutely", oldHourMs, oldHourMs + HOUR_MS), + }), + { schemaSql: localSchemaSnapshot(51).sql, bootstrapSchema: false }, + ) + const zeros = Rec.fromEntries(Arr.map(LADDER, (column) => [column, "0"] as const)) + expect(old.hourly).toEqual({ spans: "2", ...zeros }) + expect(old.minutely).toEqual({ spans: "2", ...zeros }) + }) + + test("the recreated views count sampling-weighted entry-point spans strictly under each rung", async () => { + const fresh = await openStore( + targetDataDir, + (db) => { + db.exec( + `INSERT INTO traces (${SPAN_COLUMNS}) VALUES ${spanRows(nowMs, "new", POST_MIGRATION_SPANS)}`, + ) + return { + hourly: ladder(db, "service_overview_hourly", currentHourMs, currentHourMs + HOUR_MS), + minutely: ladder(db, "service_overview_minutely", currentHourMs, currentHourMs + HOUR_MS), + } + }, + { schemaSql: localSchemaSnapshot(51).sql, bootstrapSchema: false }, + ) + const expected = { + spans: "6", + // Sampling-weighted, so the coverage total is 10 against a raw count of 6. + EstimatedLadderSpanCount: "10", + EstimatedUnder50Ms: "1", + EstimatedUnder100Ms: "3", + EstimatedUnder250Ms: "3", + EstimatedUnder500Ms: "4", + EstimatedUnder1000Ms: "8", + EstimatedUnder2500Ms: "9", + EstimatedUnder5000Ms: "9", + EstimatedUnder10000Ms: "9", + } + expect(fresh.hourly).toEqual(expected) + expect(fresh.minutely).toEqual(expected) + }) +}) diff --git a/apps/cli/test/native-local-store-migration.sh b/apps/cli/test/native-local-store-migration.sh index 071d29c506..cf8608be7f 100755 --- a/apps/cli/test/native-local-store-migration.sh +++ b/apps/cli/test/native-local-store-migration.sh @@ -143,7 +143,7 @@ grep -q "local store migrated" "$ROOT/migrate.out" || fail "native migration did # must be bumped in lockstep with LOCAL_SCHEMA_VERSION and the matching # LOCAL_SCHEMA_V.fingerprint in packages/local-server/src/schema-identity.ts; # leaving it on the previous version is what makes this step fail after a bump. -jq -e '.formatVersion == 2 and .activation == "active" and .schemaVersion == 50 and .schema == "4285696c4a009e8d"' \ +jq -e '.formatVersion == 2 and .activation == "active" and .schemaVersion == 51 and .schema == "252552abda61d2c2"' \ "$ROOT/maple-store-version.json" >/dev/null || fail "native migration wrote the wrong active identity" step "reopening promoted store in a fresh server" diff --git a/apps/ingest/src/clickhouse_insert_mappings.rs b/apps/ingest/src/clickhouse_insert_mappings.rs index afe8bc7c14..d1c7f86c5c 100644 --- a/apps/ingest/src/clickhouse_insert_mappings.rs +++ b/apps/ingest/src/clickhouse_insert_mappings.rs @@ -1,7 +1,7 @@ // This file is generated by scripts/generate-clickhouse-insert-mappings.ts // Do not edit manually. -pub const PROJECT_REVISION: &str = "98e3aa0756a3bd1fee72f2adc1abf24df132d948a76d343c1d43d8b835a46fa1"; +pub const PROJECT_REVISION: &str = "2b7126b41d1bab3c0f728acdf90d1eff82b7e47a17f9d9d6ddf8cb90e51173c8"; // Gate for BYO-ClickHouse ingest readiness — the migration version, NOT the // Tinybird-coupled PROJECT_REVISION. Compared against // org_clickhouse_settings.schema_version. See @maple/domain/clickhouse diff --git a/packages/backend/src/services/warehouse/service-overview-parity.clickhouse.e2e.test.ts b/packages/backend/src/services/warehouse/service-overview-parity.clickhouse.e2e.test.ts index 46309085ac..0a3ecdc58f 100644 --- a/packages/backend/src/services/warehouse/service-overview-parity.clickhouse.e2e.test.ts +++ b/packages/backend/src/services/warehouse/service-overview-parity.clickhouse.e2e.test.ts @@ -26,6 +26,8 @@ // cannot coincide. import { afterAll, assert, beforeAll, describe, it } from "@effect/vitest" +import { SLO_LATENCY_THRESHOLDS_MS } from "@maple/domain/primitives" +import { SLO_LATENCY_COVERAGE_COLUMN, sloLatencyColumnName } from "@maple/domain/tinybird/datasources" import * as CH from "@maple/query-engine/ch" import { normalizeSqlForClickHouseClient } from "@maple/query-engine/execution" import { @@ -278,8 +280,10 @@ describe.skipIf(!clickhouseE2eEnabled)("service overview raw-vs-rollup parity", // which is exactly what a TTL-expired seed produces, silently. const [spans, minutely, hourly] = await Promise.all([ runJson("SELECT count() AS n FROM service_overview_spans"), - runJson("SELECT sum(SpanCount) AS n FROM service_overview_minutely"), - runJson("SELECT sum(SpanCount) AS n FROM service_overview_hourly"), + runJson( + `SELECT sum(SpanCount) AS n FROM service_overview_minutely WHERE OrgId = ${quote(ORG_ID)}`, + ), + runJson(`SELECT sum(SpanCount) AS n FROM service_overview_hourly WHERE OrgId = ${quote(ORG_ID)}`), ]) assert.strictEqual( @@ -293,6 +297,75 @@ describe.skipIf(!clickhouseE2eEnabled)("service overview raw-vs-rollup parity", assert.strictEqual(num(hourly[0]?.n), SEED_SPANS.length, "service_overview_hourly is empty") }) + it("counts the SLO latency ladder in both rollups exactly as a raw scan does", async () => { + // Per rung: sampling-weighted entry-point spans strictly under the threshold, any status. + const ladder = (column: (ms: number) => string) => + Arr.join( + Arr.map(SLO_LATENCY_THRESHOLDS_MS, (ms) => `toFloat64(${column(ms)}) AS r${ms}`), + ", ", + ) + const coverage = `toFloat64(sum(${SLO_LATENCY_COVERAGE_COLUMN})) AS coverage` + const rollup = (table: string) => + runJson( + `SELECT ${coverage}, ${ladder((ms) => `sum(${sloLatencyColumnName(ms)})`)} FROM ${table} WHERE OrgId = ${quote(ORG_ID)}`, + ) + const [raw, minutely, hourly] = await Promise.all([ + runJson( + `SELECT toFloat64(sum(SampleRate)) AS coverage, ${ladder((ms) => `sumIf(SampleRate, Duration < ${ms * 1_000_000})`)} FROM service_overview_spans WHERE OrgId = ${quote(ORG_ID)}`, + ), + rollup("service_overview_minutely"), + rollup("service_overview_hourly"), + ]) + const rungs = (row: Record | undefined) => [ + num(row?.coverage), + ...Arr.map(SLO_LATENCY_THRESHOLDS_MS, (ms) => num(row?.[`r${ms}`])), + ] + // Every seed row is a root Server span, so the oracle is the seed itself. + const covered = Arr.reduce(SEED_SPANS, 0, (sum, row) => sum + row.sampleRate) + const expected = Arr.map(SLO_LATENCY_THRESHOLDS_MS, (ms) => + Arr.reduce(SEED_SPANS, 0, (sum, row) => + row.durationNs < ms * 1_000_000 ? sum + row.sampleRate : sum, + ), + ) + assert.isAbove(Arr.getUnsafe(expected, 0), 0) + assert.isBelow(Arr.getUnsafe(expected, 0), Arr.getUnsafe(expected, expected.length - 1)) + // Head-sampled rows make the coverage total exceed the raw span count. + assert.isAbove(covered, SEED_SPANS.length) + assert.deepStrictEqual(rungs(raw[0]), [covered, ...expected]) + assert.deepStrictEqual(rungs(minutely[0]), rungs(raw[0])) + assert.deepStrictEqual(rungs(hourly[0]), rungs(raw[0])) + }) + + it("reads a bucket written before 0059 as uncovered: zero coverage and zero on every rung", async () => { + // The pre-0059 views named only these columns; the ALTER defaults the rest to 0. + const legacyOrg = "org_service_overview_pre_0059" + const legacy = (table: string, time: string) => + clickhouseExec( + `INSERT INTO ${table} (OrgId, ${time}, ServiceName, DeploymentEnv, ServiceNamespace, CommitSha, SpanCount, EstimatedSpanCount, ErrorCount, EstimatedErrorCount, DurationSum, FirstSeen, ApdexSatisfiedCount, ApdexToleratingCount) + VALUES (${quote(legacyOrg)}, ${quote(chDateTime(FIRST_FULL_HOUR_MS))}, 'api', 'production', 'checkout', 'abc123', 5, 50, 0, 0, 1000000, ${quote(chDateTime(FIRST_FULL_HOUR_MS))}, 5, 0)`, + database, + ) + await legacy("service_overview_hourly", "Hour") + await legacy("service_overview_minutely", "Minute") + const read = (table: string) => + runJson( + `SELECT toFloat64(sum(SpanCount)) AS spans, toFloat64(sum(${SLO_LATENCY_COVERAGE_COLUMN})) AS coverage, toFloat64(sum(${sloLatencyColumnName(10000)})) AS slowest FROM ${table} WHERE OrgId = ${quote(legacyOrg)}`, + ) + for (const rows of await Promise.all([ + read("service_overview_hourly"), + read("service_overview_minutely"), + ])) { + assert.deepStrictEqual( + { + spans: num(rows[0]?.spans), + coverage: num(rows[0]?.coverage), + slowest: num(rows[0]?.slowest), + }, + { spans: 5, coverage: 0, slowest: 0 }, + ) + } + }) + it("puts rows on every seam the splice depends on", async () => { // If these are ever zero the boundary cases above have stopped being // boundary cases and the parity assertions get much weaker without failing. diff --git a/packages/domain/src/clickhouse/migrations/0059_service_overview_slo_latency_ladder.ts b/packages/domain/src/clickhouse/migrations/0059_service_overview_slo_latency_ladder.ts new file mode 100644 index 0000000000..3542819734 --- /dev/null +++ b/packages/domain/src/clickhouse/migrations/0059_service_overview_slo_latency_ladder.ts @@ -0,0 +1,34 @@ +// Migration 0059: SLO latency ladder (`EstimatedUnderMs`, sampling-weighted spans under each +// rung, plus the `EstimatedLadderSpanCount` coverage total) on both service overview rollups, +// ALTERed before the views are recreated. No backfill: older buckets read 0, coverage included. +// Frozen v59 DDL: never re-derive it from a later snapshot. +export const migration_0059_service_overview_slo_latency_ladder = { + version: 59, + description: + "Add the SLO latency ladder counters and their coverage total to service_overview_hourly and service_overview_minutely and recreate their views to populate them", + requiredForIngest: false, + statements: [ + "ALTER TABLE service_overview_hourly ADD COLUMN IF NOT EXISTS EstimatedLadderSpanCount SimpleAggregateFunction(sum, Float64) DEFAULT 0", + "ALTER TABLE service_overview_hourly ADD COLUMN IF NOT EXISTS EstimatedUnder50Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0", + "ALTER TABLE service_overview_hourly ADD COLUMN IF NOT EXISTS EstimatedUnder100Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0", + "ALTER TABLE service_overview_hourly ADD COLUMN IF NOT EXISTS EstimatedUnder250Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0", + "ALTER TABLE service_overview_hourly ADD COLUMN IF NOT EXISTS EstimatedUnder500Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0", + "ALTER TABLE service_overview_hourly ADD COLUMN IF NOT EXISTS EstimatedUnder1000Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0", + "ALTER TABLE service_overview_hourly ADD COLUMN IF NOT EXISTS EstimatedUnder2500Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0", + "ALTER TABLE service_overview_hourly ADD COLUMN IF NOT EXISTS EstimatedUnder5000Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0", + "ALTER TABLE service_overview_hourly ADD COLUMN IF NOT EXISTS EstimatedUnder10000Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0", + "ALTER TABLE service_overview_minutely ADD COLUMN IF NOT EXISTS EstimatedLadderSpanCount SimpleAggregateFunction(sum, Float64) DEFAULT 0", + "ALTER TABLE service_overview_minutely ADD COLUMN IF NOT EXISTS EstimatedUnder50Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0", + "ALTER TABLE service_overview_minutely ADD COLUMN IF NOT EXISTS EstimatedUnder100Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0", + "ALTER TABLE service_overview_minutely ADD COLUMN IF NOT EXISTS EstimatedUnder250Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0", + "ALTER TABLE service_overview_minutely ADD COLUMN IF NOT EXISTS EstimatedUnder500Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0", + "ALTER TABLE service_overview_minutely ADD COLUMN IF NOT EXISTS EstimatedUnder1000Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0", + "ALTER TABLE service_overview_minutely ADD COLUMN IF NOT EXISTS EstimatedUnder2500Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0", + "ALTER TABLE service_overview_minutely ADD COLUMN IF NOT EXISTS EstimatedUnder5000Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0", + "ALTER TABLE service_overview_minutely ADD COLUMN IF NOT EXISTS EstimatedUnder10000Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0", + "DROP VIEW IF EXISTS service_overview_hourly_mv", + "DROP VIEW IF EXISTS service_overview_minutely_mv", + "CREATE MATERIALIZED VIEW IF NOT EXISTS service_overview_hourly_mv TO service_overview_hourly AS\nSELECT\n OrgId,\n toStartOfHour(toDateTime(Timestamp)) AS Hour,\n ServiceName,\n coalesce(nullIf(ResourceAttributes['deployment.environment.name'], ''), ResourceAttributes['deployment.environment']) AS DeploymentEnv,\n ResourceAttributes['service.namespace'] AS ServiceNamespace,\n ResourceAttributes['vcs.ref.head.revision'] AS CommitSha,\n count() AS SpanCount,\n sum(SampleRate) AS EstimatedSpanCount,\n countIf(StatusCode = 'Error') AS ErrorCount,\n sumIf(SampleRate, StatusCode = 'Error') AS EstimatedErrorCount,\n sum(toFloat64(Duration)) AS DurationSum,\n quantilesTDigestState(0.5, 0.95, 0.99)(Duration) AS DurationQuantiles,\n min(toDateTime(Timestamp)) AS FirstSeen,\n countIf(StatusCode != 'Error' AND Duration < 500000000) AS ApdexSatisfiedCount,\n countIf(StatusCode != 'Error' AND Duration >= 500000000 AND Duration < 2000000000) AS ApdexToleratingCount,\n sum(SampleRate) AS EstimatedLadderSpanCount,\n sumIf(SampleRate, Duration < 50000000) AS EstimatedUnder50Ms,\n sumIf(SampleRate, Duration < 100000000) AS EstimatedUnder100Ms,\n sumIf(SampleRate, Duration < 250000000) AS EstimatedUnder250Ms,\n sumIf(SampleRate, Duration < 500000000) AS EstimatedUnder500Ms,\n sumIf(SampleRate, Duration < 1000000000) AS EstimatedUnder1000Ms,\n sumIf(SampleRate, Duration < 2500000000) AS EstimatedUnder2500Ms,\n sumIf(SampleRate, Duration < 5000000000) AS EstimatedUnder5000Ms,\n sumIf(SampleRate, Duration < 10000000000) AS EstimatedUnder10000Ms\n FROM traces\n WHERE SpanKind IN ('Server', 'Consumer') OR ParentSpanId = ''\n GROUP BY OrgId, Hour, ServiceName, DeploymentEnv, ServiceNamespace, CommitSha", + "CREATE MATERIALIZED VIEW IF NOT EXISTS service_overview_minutely_mv TO service_overview_minutely AS\nSELECT\n OrgId,\n toStartOfMinute(toDateTime(Timestamp)) AS Minute,\n ServiceName,\n coalesce(nullIf(ResourceAttributes['deployment.environment.name'], ''), ResourceAttributes['deployment.environment']) AS DeploymentEnv,\n ResourceAttributes['service.namespace'] AS ServiceNamespace,\n ResourceAttributes['vcs.ref.head.revision'] AS CommitSha,\n count() AS SpanCount,\n sum(SampleRate) AS EstimatedSpanCount,\n countIf(StatusCode = 'Error') AS ErrorCount,\n sumIf(SampleRate, StatusCode = 'Error') AS EstimatedErrorCount,\n sum(toFloat64(Duration)) AS DurationSum,\n quantilesTDigestState(0.5, 0.95, 0.99)(Duration) AS DurationQuantiles,\n min(toDateTime(Timestamp)) AS FirstSeen,\n countIf(StatusCode != 'Error' AND Duration < 500000000) AS ApdexSatisfiedCount,\n countIf(StatusCode != 'Error' AND Duration >= 500000000 AND Duration < 2000000000) AS ApdexToleratingCount,\n sum(SampleRate) AS EstimatedLadderSpanCount,\n sumIf(SampleRate, Duration < 50000000) AS EstimatedUnder50Ms,\n sumIf(SampleRate, Duration < 100000000) AS EstimatedUnder100Ms,\n sumIf(SampleRate, Duration < 250000000) AS EstimatedUnder250Ms,\n sumIf(SampleRate, Duration < 500000000) AS EstimatedUnder500Ms,\n sumIf(SampleRate, Duration < 1000000000) AS EstimatedUnder1000Ms,\n sumIf(SampleRate, Duration < 2500000000) AS EstimatedUnder2500Ms,\n sumIf(SampleRate, Duration < 5000000000) AS EstimatedUnder5000Ms,\n sumIf(SampleRate, Duration < 10000000000) AS EstimatedUnder10000Ms\n FROM traces\n WHERE SpanKind IN ('Server', 'Consumer') OR ParentSpanId = ''\n GROUP BY OrgId, Minute, ServiceName, DeploymentEnv, ServiceNamespace, CommitSha", + ], +} as const diff --git a/packages/domain/src/clickhouse/migrations/index.ts b/packages/domain/src/clickhouse/migrations/index.ts index 5569b16e49..6b7ff347ab 100644 --- a/packages/domain/src/clickhouse/migrations/index.ts +++ b/packages/domain/src/clickhouse/migrations/index.ts @@ -57,6 +57,7 @@ import { migration_0055_error_fingerprint_v4 } from "./0055_error_fingerprint_v4 import { migration_0056_user_activity_hourly } from "./0056_user_activity_hourly" import { migration_0057_error_events_user_id } from "./0057_error_events_user_id" import { migration_0058_user_activity_channels } from "./0058_user_activity_channels" +import { migration_0059_service_overview_slo_latency_ladder } from "./0059_service_overview_slo_latency_ladder" import { Array as Arr } from "effect" /** @@ -147,6 +148,7 @@ export const migrations: ReadonlyArray = [ migration_0056_user_activity_hourly, migration_0057_error_events_user_id, migration_0058_user_activity_channels, + migration_0059_service_overview_slo_latency_ladder, ] as const /** Highest migration `version` bundled — i.e. the schema level a fully-applied diff --git a/packages/domain/src/generated/clickhouse-schema.ts b/packages/domain/src/generated/clickhouse-schema.ts index f6e226650b..2465f53814 100644 --- a/packages/domain/src/generated/clickhouse-schema.ts +++ b/packages/domain/src/generated/clickhouse-schema.ts @@ -1,7 +1,7 @@ // This file is generated by scripts/generate-clickhouse-schema.ts // Do not edit manually. -export const projectRevision = "98e3aa0756a3bd1fee72f2adc1abf24df132d948a76d343c1d43d8b835a46fa1" as const +export const projectRevision = "2b7126b41d1bab3c0f728acdf90d1eff82b7e47a17f9d9d6ddf8cb90e51173c8" as const export const latestSnapshotStatements: ReadonlyArray = [ "CREATE TABLE IF NOT EXISTS ai_crawler_requests (\n OrgId LowCardinality(String),\n Timestamp DateTime64(9),\n TraceId String,\n ServiceName LowCardinality(String),\n Crawler LowCardinality(String),\n Host LowCardinality(String),\n Path String,\n HttpStatus UInt16\n)\nENGINE = MergeTree\nPARTITION BY toDate(Timestamp)\nORDER BY (OrgId, Timestamp, TraceId)\nTTL toDate(Timestamp) + INTERVAL 30 DAY", @@ -38,8 +38,8 @@ export const latestSnapshotStatements: ReadonlyArray = [ "CREATE TABLE IF NOT EXISTS service_map_spans (\n OrgId LowCardinality(String),\n Timestamp DateTime,\n TraceId String,\n SpanId String,\n ParentSpanId String,\n ServiceName LowCardinality(String),\n SpanKind LowCardinality(String),\n Duration UInt64,\n StatusCode LowCardinality(String),\n TraceState String,\n DeploymentEnv LowCardinality(String)\n)\nENGINE = MergeTree\nPARTITION BY toDate(Timestamp)\nORDER BY (OrgId, toStartOfFiveMinutes(Timestamp), TraceId, SpanId)\nTTL toDate(Timestamp) + INTERVAL 30 DAY", "CREATE TABLE IF NOT EXISTS service_operations_hourly (\n OrgId LowCardinality(String),\n Hour DateTime,\n ServiceName LowCardinality(String),\n DeploymentEnv LowCardinality(String),\n SpanName String,\n SpanCount SimpleAggregateFunction(sum, UInt64),\n EstimatedSpanCount SimpleAggregateFunction(sum, Float64),\n ErrorCount SimpleAggregateFunction(sum, UInt64),\n EstimatedErrorCount SimpleAggregateFunction(sum, Float64),\n DurationSum SimpleAggregateFunction(sum, Float64),\n DurationQuantiles AggregateFunction(quantilesTDigest(0.5, 0.95), UInt64),\n ClassifiedSpanCount SimpleAggregateFunction(sum, UInt64),\n ServerSpanCount SimpleAggregateFunction(sum, UInt64),\n RoutedSpanCount SimpleAggregateFunction(sum, UInt64),\n HttpClassifiedSpanCount SimpleAggregateFunction(sum, UInt64),\n HttpStatusCount SimpleAggregateFunction(sum, UInt64),\n Http4xxCount SimpleAggregateFunction(sum, UInt64),\n Http5xxCount SimpleAggregateFunction(sum, UInt64),\n RequestBodyBytes SimpleAggregateFunction(sum, UInt64),\n RequestBodyCount SimpleAggregateFunction(sum, UInt64)\n)\nENGINE = AggregatingMergeTree\nPARTITION BY toYYYYMM(Hour)\nORDER BY (OrgId, ServiceName, DeploymentEnv, Hour, SpanName)\nTTL toDate(Hour) + INTERVAL 365 DAY", "CREATE TABLE IF NOT EXISTS service_operations_minutely (\n OrgId LowCardinality(String),\n Minute DateTime,\n ServiceName LowCardinality(String),\n DeploymentEnv LowCardinality(String),\n SpanName String,\n SpanCount SimpleAggregateFunction(sum, UInt64),\n EstimatedSpanCount SimpleAggregateFunction(sum, Float64),\n ErrorCount SimpleAggregateFunction(sum, UInt64),\n EstimatedErrorCount SimpleAggregateFunction(sum, Float64),\n DurationSum SimpleAggregateFunction(sum, Float64),\n DurationQuantiles AggregateFunction(quantilesTDigest(0.5, 0.95), UInt64),\n ClassifiedSpanCount SimpleAggregateFunction(sum, UInt64),\n ServerSpanCount SimpleAggregateFunction(sum, UInt64),\n RoutedSpanCount SimpleAggregateFunction(sum, UInt64),\n HttpClassifiedSpanCount SimpleAggregateFunction(sum, UInt64),\n HttpStatusCount SimpleAggregateFunction(sum, UInt64),\n Http4xxCount SimpleAggregateFunction(sum, UInt64),\n Http5xxCount SimpleAggregateFunction(sum, UInt64),\n RequestBodyBytes SimpleAggregateFunction(sum, UInt64),\n RequestBodyCount SimpleAggregateFunction(sum, UInt64)\n)\nENGINE = AggregatingMergeTree\nPARTITION BY toDate(Minute)\nORDER BY (OrgId, ServiceName, DeploymentEnv, Minute, SpanName)\nTTL toDate(Minute) + INTERVAL 90 DAY", - "CREATE TABLE IF NOT EXISTS service_overview_hourly (\n OrgId LowCardinality(String),\n Hour DateTime,\n ServiceName LowCardinality(String),\n DeploymentEnv LowCardinality(String),\n ServiceNamespace LowCardinality(String),\n CommitSha LowCardinality(String),\n SpanCount SimpleAggregateFunction(sum, UInt64),\n EstimatedSpanCount SimpleAggregateFunction(sum, Float64),\n ErrorCount SimpleAggregateFunction(sum, UInt64),\n EstimatedErrorCount SimpleAggregateFunction(sum, Float64),\n DurationSum SimpleAggregateFunction(sum, Float64),\n DurationQuantiles AggregateFunction(quantilesTDigest(0.5, 0.95, 0.99), UInt64),\n FirstSeen SimpleAggregateFunction(min, DateTime),\n ApdexSatisfiedCount SimpleAggregateFunction(sum, UInt64),\n ApdexToleratingCount SimpleAggregateFunction(sum, UInt64)\n)\nENGINE = AggregatingMergeTree\nPARTITION BY toYYYYMM(Hour)\nORDER BY (OrgId, ServiceName, Hour, DeploymentEnv, ServiceNamespace, CommitSha)\nTTL toDate(Hour) + INTERVAL 365 DAY", - "CREATE TABLE IF NOT EXISTS service_overview_minutely (\n OrgId LowCardinality(String),\n Minute DateTime,\n ServiceName LowCardinality(String),\n DeploymentEnv LowCardinality(String),\n ServiceNamespace LowCardinality(String),\n CommitSha LowCardinality(String),\n SpanCount SimpleAggregateFunction(sum, UInt64),\n EstimatedSpanCount SimpleAggregateFunction(sum, Float64),\n ErrorCount SimpleAggregateFunction(sum, UInt64),\n EstimatedErrorCount SimpleAggregateFunction(sum, Float64),\n DurationSum SimpleAggregateFunction(sum, Float64),\n DurationQuantiles AggregateFunction(quantilesTDigest(0.5, 0.95, 0.99), UInt64),\n FirstSeen SimpleAggregateFunction(min, DateTime),\n ApdexSatisfiedCount SimpleAggregateFunction(sum, UInt64),\n ApdexToleratingCount SimpleAggregateFunction(sum, UInt64)\n)\nENGINE = AggregatingMergeTree\nPARTITION BY toDate(Minute)\nORDER BY (OrgId, ServiceName, Minute, DeploymentEnv, ServiceNamespace, CommitSha)\nTTL toDate(Minute) + INTERVAL 90 DAY", + "CREATE TABLE IF NOT EXISTS service_overview_hourly (\n OrgId LowCardinality(String),\n Hour DateTime,\n ServiceName LowCardinality(String),\n DeploymentEnv LowCardinality(String),\n ServiceNamespace LowCardinality(String),\n CommitSha LowCardinality(String),\n SpanCount SimpleAggregateFunction(sum, UInt64),\n EstimatedSpanCount SimpleAggregateFunction(sum, Float64),\n ErrorCount SimpleAggregateFunction(sum, UInt64),\n EstimatedErrorCount SimpleAggregateFunction(sum, Float64),\n DurationSum SimpleAggregateFunction(sum, Float64),\n DurationQuantiles AggregateFunction(quantilesTDigest(0.5, 0.95, 0.99), UInt64),\n FirstSeen SimpleAggregateFunction(min, DateTime),\n ApdexSatisfiedCount SimpleAggregateFunction(sum, UInt64),\n ApdexToleratingCount SimpleAggregateFunction(sum, UInt64),\n EstimatedLadderSpanCount SimpleAggregateFunction(sum, Float64) DEFAULT 0,\n EstimatedUnder50Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0,\n EstimatedUnder100Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0,\n EstimatedUnder250Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0,\n EstimatedUnder500Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0,\n EstimatedUnder1000Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0,\n EstimatedUnder2500Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0,\n EstimatedUnder5000Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0,\n EstimatedUnder10000Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0\n)\nENGINE = AggregatingMergeTree\nPARTITION BY toYYYYMM(Hour)\nORDER BY (OrgId, ServiceName, Hour, DeploymentEnv, ServiceNamespace, CommitSha)\nTTL toDate(Hour) + INTERVAL 365 DAY", + "CREATE TABLE IF NOT EXISTS service_overview_minutely (\n OrgId LowCardinality(String),\n Minute DateTime,\n ServiceName LowCardinality(String),\n DeploymentEnv LowCardinality(String),\n ServiceNamespace LowCardinality(String),\n CommitSha LowCardinality(String),\n SpanCount SimpleAggregateFunction(sum, UInt64),\n EstimatedSpanCount SimpleAggregateFunction(sum, Float64),\n ErrorCount SimpleAggregateFunction(sum, UInt64),\n EstimatedErrorCount SimpleAggregateFunction(sum, Float64),\n DurationSum SimpleAggregateFunction(sum, Float64),\n DurationQuantiles AggregateFunction(quantilesTDigest(0.5, 0.95, 0.99), UInt64),\n FirstSeen SimpleAggregateFunction(min, DateTime),\n ApdexSatisfiedCount SimpleAggregateFunction(sum, UInt64),\n ApdexToleratingCount SimpleAggregateFunction(sum, UInt64),\n EstimatedLadderSpanCount SimpleAggregateFunction(sum, Float64) DEFAULT 0,\n EstimatedUnder50Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0,\n EstimatedUnder100Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0,\n EstimatedUnder250Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0,\n EstimatedUnder500Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0,\n EstimatedUnder1000Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0,\n EstimatedUnder2500Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0,\n EstimatedUnder5000Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0,\n EstimatedUnder10000Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0\n)\nENGINE = AggregatingMergeTree\nPARTITION BY toDate(Minute)\nORDER BY (OrgId, ServiceName, Minute, DeploymentEnv, ServiceNamespace, CommitSha)\nTTL toDate(Minute) + INTERVAL 90 DAY", "CREATE TABLE IF NOT EXISTS service_overview_spans (\n OrgId LowCardinality(String),\n Timestamp DateTime,\n ServiceName LowCardinality(String),\n Duration UInt64,\n StatusCode LowCardinality(String),\n TraceState String,\n DeploymentEnv LowCardinality(String),\n CommitSha LowCardinality(String),\n SampleRate Float64 DEFAULT 1,\n ServiceNamespace LowCardinality(String),\n SpanKind LowCardinality(String) DEFAULT '',\n IsRoot UInt8 DEFAULT 0,\n INDEX idx_service_namespace ServiceNamespace TYPE set(1000) GRANULARITY 4\n)\nENGINE = MergeTree\nPARTITION BY toDate(Timestamp)\nORDER BY (OrgId, ServiceName, Timestamp)\nTTL toDate(Timestamp) + INTERVAL 30 DAY", "CREATE TABLE IF NOT EXISTS service_platforms_hourly (\n OrgId LowCardinality(String),\n Hour DateTime,\n ServiceName LowCardinality(String),\n DeploymentEnv LowCardinality(String),\n K8sCluster SimpleAggregateFunction(max, String),\n K8sPodName SimpleAggregateFunction(max, String),\n K8sDeploymentName SimpleAggregateFunction(max, String),\n K8sStatefulSetName SimpleAggregateFunction(max, String),\n K8sDaemonSetName SimpleAggregateFunction(max, String),\n K8sNamespaceName SimpleAggregateFunction(max, String),\n CloudPlatform SimpleAggregateFunction(max, String),\n CloudProvider SimpleAggregateFunction(max, String),\n FaasName SimpleAggregateFunction(max, String),\n MapleSdkType SimpleAggregateFunction(max, String),\n ProcessRuntimeName SimpleAggregateFunction(max, String),\n SpanCount SimpleAggregateFunction(sum, UInt64)\n)\nENGINE = AggregatingMergeTree\nPARTITION BY toDate(Hour)\nORDER BY (OrgId, Hour, ServiceName, DeploymentEnv)\nTTL toDate(Hour) + INTERVAL 365 DAY", "CREATE TABLE IF NOT EXISTS service_runtimes_hourly (\n OrgId LowCardinality(String),\n Hour DateTime,\n ServiceName LowCardinality(String),\n DeploymentEnv LowCardinality(String),\n RuntimeName LowCardinality(String),\n RuntimeVersion LowCardinality(String),\n ExecutableName LowCardinality(String),\n SdkLanguage LowCardinality(String),\n SdkName LowCardinality(String),\n SdkVersion LowCardinality(String),\n SpanCount UInt64\n)\nENGINE = SummingMergeTree\nPARTITION BY toDate(Hour)\nORDER BY (OrgId, Hour, ServiceName, DeploymentEnv, RuntimeName, RuntimeVersion, ExecutableName, SdkLanguage, SdkName, SdkVersion)\nTTL toDate(Hour) + INTERVAL 90 DAY", @@ -86,8 +86,8 @@ export const latestSnapshotStatements: ReadonlyArray = [ "CREATE MATERIALIZED VIEW IF NOT EXISTS service_map_spans_mv TO service_map_spans AS\nSELECT\n OrgId,\n toDateTime(Timestamp) AS Timestamp,\n TraceId,\n SpanId,\n ParentSpanId,\n ServiceName,\n SpanKind,\n Duration,\n StatusCode,\n TraceState,\n coalesce(nullIf(ResourceAttributes['deployment.environment.name'], ''), ResourceAttributes['deployment.environment']) AS DeploymentEnv\n FROM traces\n WHERE SpanKind IN ('Client', 'Producer', 'Server', 'Consumer')", "CREATE MATERIALIZED VIEW IF NOT EXISTS service_operations_hourly_mv TO service_operations_hourly AS\nSELECT\n OrgId,\n toStartOfHour(Minute) AS Hour,\n ServiceName,\n DeploymentEnv,\n SpanName,\n sum(SpanCount) AS SpanCount,\n sum(EstimatedSpanCount) AS EstimatedSpanCount,\n sum(ErrorCount) AS ErrorCount,\n sum(EstimatedErrorCount) AS EstimatedErrorCount,\n sum(DurationSum) AS DurationSum,\n quantilesTDigestMergeState(0.5, 0.95)(DurationQuantiles) AS DurationQuantiles,\n sum(ClassifiedSpanCount) AS ClassifiedSpanCount,\n sum(ServerSpanCount) AS ServerSpanCount,\n sum(RoutedSpanCount) AS RoutedSpanCount,\n sum(HttpClassifiedSpanCount) AS HttpClassifiedSpanCount,\n sum(HttpStatusCount) AS HttpStatusCount,\n sum(Http4xxCount) AS Http4xxCount,\n sum(Http5xxCount) AS Http5xxCount,\n sum(RequestBodyBytes) AS RequestBodyBytes,\n sum(RequestBodyCount) AS RequestBodyCount\n FROM service_operations_minutely\n GROUP BY OrgId, Hour, ServiceName, DeploymentEnv, SpanName", "CREATE MATERIALIZED VIEW IF NOT EXISTS service_operations_minutely_mv TO service_operations_minutely AS\nSELECT\n OrgId,\n toStartOfMinute(toDateTime(Timestamp)) AS Minute,\n ServiceName,\n coalesce(nullIf(ResourceAttributes['deployment.environment.name'], ''), ResourceAttributes['deployment.environment']) AS DeploymentEnv,\n if(((SpanName LIKE 'http.server %' OR SpanName IN ('GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS')) AND (SpanAttributes['http.route'] != '' OR SpanAttributes['url.path'] != '')), concat(if(SpanName LIKE 'http.server %', replaceOne(SpanName, 'http.server ', ''), SpanName), ' ', if(SpanAttributes['http.route'] != '', SpanAttributes['http.route'], SpanAttributes['url.path'])), SpanName) AS SpanName,\n count() AS SpanCount,\n sum(SampleRate) AS EstimatedSpanCount,\n countIf(StatusCode = 'Error') AS ErrorCount,\n sumIf(SampleRate, StatusCode = 'Error') AS EstimatedErrorCount,\n sum(toFloat64(Duration)) AS DurationSum,\n quantilesTDigestState(0.5, 0.95)(Duration) AS DurationQuantiles,\n count() AS ClassifiedSpanCount,\n countIf(SpanKind IN ('Server', 'Consumer')) AS ServerSpanCount,\n countIf(SpanAttributes['http.route'] != '') AS RoutedSpanCount,\n count() AS HttpClassifiedSpanCount,\n countIf(SpanKind = 'Server' AND if(SpanAttributes['http.status_code'] != '', SpanAttributes['http.status_code'], SpanAttributes['http.response.status_code']) != '') AS HttpStatusCount,\n countIf(SpanKind = 'Server' AND toUInt16OrZero(if(SpanAttributes['http.status_code'] != '', SpanAttributes['http.status_code'], SpanAttributes['http.response.status_code'])) BETWEEN 400 AND 499) AS Http4xxCount,\n countIf(SpanKind = 'Server' AND toUInt16OrZero(if(SpanAttributes['http.status_code'] != '', SpanAttributes['http.status_code'], SpanAttributes['http.response.status_code'])) BETWEEN 500 AND 599) AS Http5xxCount,\n sumIf(ifNull(toUInt64OrNull(if(SpanAttributes['http.request.body.size'] != '', SpanAttributes['http.request.body.size'], SpanAttributes['http.request_content_length'])), 0), SpanKind = 'Server' AND toUInt64OrNull(if(SpanAttributes['http.request.body.size'] != '', SpanAttributes['http.request.body.size'], SpanAttributes['http.request_content_length'])) IS NOT NULL) AS RequestBodyBytes,\n countIf(SpanKind = 'Server' AND toUInt64OrNull(if(SpanAttributes['http.request.body.size'] != '', SpanAttributes['http.request.body.size'], SpanAttributes['http.request_content_length'])) IS NOT NULL) AS RequestBodyCount\n FROM traces\n GROUP BY OrgId, Minute, ServiceName, DeploymentEnv, SpanName", - "CREATE MATERIALIZED VIEW IF NOT EXISTS service_overview_hourly_mv TO service_overview_hourly AS\nSELECT\n OrgId,\n toStartOfHour(toDateTime(Timestamp)) AS Hour,\n ServiceName,\n coalesce(nullIf(ResourceAttributes['deployment.environment.name'], ''), ResourceAttributes['deployment.environment']) AS DeploymentEnv,\n ResourceAttributes['service.namespace'] AS ServiceNamespace,\n ResourceAttributes['vcs.ref.head.revision'] AS CommitSha,\n count() AS SpanCount,\n sum(SampleRate) AS EstimatedSpanCount,\n countIf(StatusCode = 'Error') AS ErrorCount,\n sumIf(SampleRate, StatusCode = 'Error') AS EstimatedErrorCount,\n sum(toFloat64(Duration)) AS DurationSum,\n quantilesTDigestState(0.5, 0.95, 0.99)(Duration) AS DurationQuantiles,\n min(toDateTime(Timestamp)) AS FirstSeen,\n countIf(StatusCode != 'Error' AND Duration < 500000000) AS ApdexSatisfiedCount,\n countIf(StatusCode != 'Error' AND Duration >= 500000000 AND Duration < 2000000000) AS ApdexToleratingCount\n FROM traces\n WHERE SpanKind IN ('Server', 'Consumer') OR ParentSpanId = ''\n GROUP BY OrgId, Hour, ServiceName, DeploymentEnv, ServiceNamespace, CommitSha", - "CREATE MATERIALIZED VIEW IF NOT EXISTS service_overview_minutely_mv TO service_overview_minutely AS\nSELECT\n OrgId,\n toStartOfMinute(toDateTime(Timestamp)) AS Minute,\n ServiceName,\n coalesce(nullIf(ResourceAttributes['deployment.environment.name'], ''), ResourceAttributes['deployment.environment']) AS DeploymentEnv,\n ResourceAttributes['service.namespace'] AS ServiceNamespace,\n ResourceAttributes['vcs.ref.head.revision'] AS CommitSha,\n count() AS SpanCount,\n sum(SampleRate) AS EstimatedSpanCount,\n countIf(StatusCode = 'Error') AS ErrorCount,\n sumIf(SampleRate, StatusCode = 'Error') AS EstimatedErrorCount,\n sum(toFloat64(Duration)) AS DurationSum,\n quantilesTDigestState(0.5, 0.95, 0.99)(Duration) AS DurationQuantiles,\n min(toDateTime(Timestamp)) AS FirstSeen,\n countIf(StatusCode != 'Error' AND Duration < 500000000) AS ApdexSatisfiedCount,\n countIf(StatusCode != 'Error' AND Duration >= 500000000 AND Duration < 2000000000) AS ApdexToleratingCount\n FROM traces\n WHERE SpanKind IN ('Server', 'Consumer') OR ParentSpanId = ''\n GROUP BY OrgId, Minute, ServiceName, DeploymentEnv, ServiceNamespace, CommitSha", + "CREATE MATERIALIZED VIEW IF NOT EXISTS service_overview_hourly_mv TO service_overview_hourly AS\nSELECT\n OrgId,\n toStartOfHour(toDateTime(Timestamp)) AS Hour,\n ServiceName,\n coalesce(nullIf(ResourceAttributes['deployment.environment.name'], ''), ResourceAttributes['deployment.environment']) AS DeploymentEnv,\n ResourceAttributes['service.namespace'] AS ServiceNamespace,\n ResourceAttributes['vcs.ref.head.revision'] AS CommitSha,\n count() AS SpanCount,\n sum(SampleRate) AS EstimatedSpanCount,\n countIf(StatusCode = 'Error') AS ErrorCount,\n sumIf(SampleRate, StatusCode = 'Error') AS EstimatedErrorCount,\n sum(toFloat64(Duration)) AS DurationSum,\n quantilesTDigestState(0.5, 0.95, 0.99)(Duration) AS DurationQuantiles,\n min(toDateTime(Timestamp)) AS FirstSeen,\n countIf(StatusCode != 'Error' AND Duration < 500000000) AS ApdexSatisfiedCount,\n countIf(StatusCode != 'Error' AND Duration >= 500000000 AND Duration < 2000000000) AS ApdexToleratingCount,\n sum(SampleRate) AS EstimatedLadderSpanCount,\n sumIf(SampleRate, Duration < 50000000) AS EstimatedUnder50Ms,\n sumIf(SampleRate, Duration < 100000000) AS EstimatedUnder100Ms,\n sumIf(SampleRate, Duration < 250000000) AS EstimatedUnder250Ms,\n sumIf(SampleRate, Duration < 500000000) AS EstimatedUnder500Ms,\n sumIf(SampleRate, Duration < 1000000000) AS EstimatedUnder1000Ms,\n sumIf(SampleRate, Duration < 2500000000) AS EstimatedUnder2500Ms,\n sumIf(SampleRate, Duration < 5000000000) AS EstimatedUnder5000Ms,\n sumIf(SampleRate, Duration < 10000000000) AS EstimatedUnder10000Ms\n FROM traces\n WHERE SpanKind IN ('Server', 'Consumer') OR ParentSpanId = ''\n GROUP BY OrgId, Hour, ServiceName, DeploymentEnv, ServiceNamespace, CommitSha", + "CREATE MATERIALIZED VIEW IF NOT EXISTS service_overview_minutely_mv TO service_overview_minutely AS\nSELECT\n OrgId,\n toStartOfMinute(toDateTime(Timestamp)) AS Minute,\n ServiceName,\n coalesce(nullIf(ResourceAttributes['deployment.environment.name'], ''), ResourceAttributes['deployment.environment']) AS DeploymentEnv,\n ResourceAttributes['service.namespace'] AS ServiceNamespace,\n ResourceAttributes['vcs.ref.head.revision'] AS CommitSha,\n count() AS SpanCount,\n sum(SampleRate) AS EstimatedSpanCount,\n countIf(StatusCode = 'Error') AS ErrorCount,\n sumIf(SampleRate, StatusCode = 'Error') AS EstimatedErrorCount,\n sum(toFloat64(Duration)) AS DurationSum,\n quantilesTDigestState(0.5, 0.95, 0.99)(Duration) AS DurationQuantiles,\n min(toDateTime(Timestamp)) AS FirstSeen,\n countIf(StatusCode != 'Error' AND Duration < 500000000) AS ApdexSatisfiedCount,\n countIf(StatusCode != 'Error' AND Duration >= 500000000 AND Duration < 2000000000) AS ApdexToleratingCount,\n sum(SampleRate) AS EstimatedLadderSpanCount,\n sumIf(SampleRate, Duration < 50000000) AS EstimatedUnder50Ms,\n sumIf(SampleRate, Duration < 100000000) AS EstimatedUnder100Ms,\n sumIf(SampleRate, Duration < 250000000) AS EstimatedUnder250Ms,\n sumIf(SampleRate, Duration < 500000000) AS EstimatedUnder500Ms,\n sumIf(SampleRate, Duration < 1000000000) AS EstimatedUnder1000Ms,\n sumIf(SampleRate, Duration < 2500000000) AS EstimatedUnder2500Ms,\n sumIf(SampleRate, Duration < 5000000000) AS EstimatedUnder5000Ms,\n sumIf(SampleRate, Duration < 10000000000) AS EstimatedUnder10000Ms\n FROM traces\n WHERE SpanKind IN ('Server', 'Consumer') OR ParentSpanId = ''\n GROUP BY OrgId, Minute, ServiceName, DeploymentEnv, ServiceNamespace, CommitSha", "CREATE MATERIALIZED VIEW IF NOT EXISTS service_overview_spans_mv TO service_overview_spans AS\nSELECT\n OrgId,\n toDateTime(Timestamp) AS Timestamp,\n ServiceName,\n Duration,\n StatusCode,\n TraceState,\n coalesce(nullIf(ResourceAttributes['deployment.environment.name'], ''), ResourceAttributes['deployment.environment']) AS DeploymentEnv,\n ResourceAttributes['vcs.ref.head.revision'] AS CommitSha,\n SampleRate,\n ResourceAttributes['service.namespace'] AS ServiceNamespace,\n SpanKind,\n toUInt8(ParentSpanId = '') AS IsRoot\n FROM traces\n WHERE SpanKind IN ('Server', 'Consumer') OR ParentSpanId = ''", "CREATE MATERIALIZED VIEW IF NOT EXISTS service_platforms_hourly_mv TO service_platforms_hourly AS\nSELECT\n OrgId,\n toStartOfHour(toDateTime(Timestamp)) AS Hour,\n ServiceName,\n coalesce(nullIf(ResourceAttributes['deployment.environment.name'], ''), ResourceAttributes['deployment.environment']) AS DeploymentEnv,\n max(ResourceAttributes['k8s.cluster.name']) AS K8sCluster,\n max(ResourceAttributes['k8s.pod.name']) AS K8sPodName,\n max(ResourceAttributes['k8s.deployment.name']) AS K8sDeploymentName,\n max(ResourceAttributes['k8s.statefulset.name']) AS K8sStatefulSetName,\n max(ResourceAttributes['k8s.daemonset.name']) AS K8sDaemonSetName,\n max(ResourceAttributes['k8s.namespace.name']) AS K8sNamespaceName,\n max(ResourceAttributes['cloud.platform']) AS CloudPlatform,\n max(ResourceAttributes['cloud.provider']) AS CloudProvider,\n max(ResourceAttributes['faas.name']) AS FaasName,\n max(ResourceAttributes['maple.sdk.type']) AS MapleSdkType,\n max(ResourceAttributes['process.runtime.name']) AS ProcessRuntimeName,\n count() AS SpanCount\n FROM traces\n WHERE ServiceName != ''\n GROUP BY OrgId, Hour, ServiceName, DeploymentEnv", "CREATE MATERIALIZED VIEW IF NOT EXISTS service_runtimes_hourly_mv TO service_runtimes_hourly AS\nSELECT\n OrgId,\n toStartOfHour(toDateTime(Timestamp)) AS Hour,\n ServiceName,\n coalesce(nullIf(ResourceAttributes['deployment.environment.name'], ''), ResourceAttributes['deployment.environment']) AS DeploymentEnv,\n leftUTF8(ResourceAttributes['process.runtime.name'], 64) AS RuntimeName,\n leftUTF8(ResourceAttributes['process.runtime.version'], 64) AS RuntimeVersion,\n leftUTF8(ResourceAttributes['process.executable.name'], 64) AS ExecutableName,\n leftUTF8(ResourceAttributes['telemetry.sdk.language'], 64) AS SdkLanguage,\n leftUTF8(ResourceAttributes['telemetry.sdk.name'], 64) AS SdkName,\n leftUTF8(ResourceAttributes['telemetry.sdk.version'], 64) AS SdkVersion,\n count() AS SpanCount\n FROM traces\n WHERE ServiceName != ''\n GROUP BY OrgId, Hour, ServiceName, DeploymentEnv, RuntimeName, RuntimeVersion, ExecutableName, SdkLanguage, SdkName, SdkVersion", diff --git a/packages/domain/src/generated/tinybird-project-manifest.ts b/packages/domain/src/generated/tinybird-project-manifest.ts index 9a7d329583..0ff503eac4 100644 --- a/packages/domain/src/generated/tinybird-project-manifest.ts +++ b/packages/domain/src/generated/tinybird-project-manifest.ts @@ -1,7 +1,7 @@ // This file is generated by scripts/generate-tinybird-project-manifest.ts // Do not edit manually. -export const projectRevision = "98e3aa0756a3bd1fee72f2adc1abf24df132d948a76d343c1d43d8b835a46fa1" as const +export const projectRevision = "2b7126b41d1bab3c0f728acdf90d1eff82b7e47a17f9d9d6ddf8cb90e51173c8" as const export const datasources = [ { @@ -177,12 +177,12 @@ export const datasources = [ { name: "service_overview_hourly", content: - 'DESCRIPTION >\n Hourly service entry-point aggregates with release dimensions, sampling-aware counts, latency states, and fixed-500ms Apdex counts.\n\nSCHEMA >\n OrgId LowCardinality(String),\n Hour DateTime,\n ServiceName LowCardinality(String),\n DeploymentEnv LowCardinality(String),\n ServiceNamespace LowCardinality(String),\n CommitSha LowCardinality(String),\n SpanCount SimpleAggregateFunction(sum, UInt64),\n EstimatedSpanCount SimpleAggregateFunction(sum, Float64),\n ErrorCount SimpleAggregateFunction(sum, UInt64),\n EstimatedErrorCount SimpleAggregateFunction(sum, Float64),\n DurationSum SimpleAggregateFunction(sum, Float64),\n DurationQuantiles AggregateFunction(quantilesTDigest(0.5, 0.95, 0.99), UInt64),\n FirstSeen SimpleAggregateFunction(min, DateTime),\n ApdexSatisfiedCount SimpleAggregateFunction(sum, UInt64),\n ApdexToleratingCount SimpleAggregateFunction(sum, UInt64)\n\nENGINE "AggregatingMergeTree"\nENGINE_PARTITION_KEY "toYYYYMM(Hour)"\nENGINE_SORTING_KEY "OrgId, ServiceName, Hour, DeploymentEnv, ServiceNamespace, CommitSha"\nENGINE_TTL "toDate(Hour) + INTERVAL 365 DAY"', + 'DESCRIPTION >\n Hourly service entry-point aggregates with release dimensions, sampling-aware counts, latency states, and fixed-500ms Apdex counts.\n\nSCHEMA >\n OrgId LowCardinality(String),\n Hour DateTime,\n ServiceName LowCardinality(String),\n DeploymentEnv LowCardinality(String),\n ServiceNamespace LowCardinality(String),\n CommitSha LowCardinality(String),\n SpanCount SimpleAggregateFunction(sum, UInt64),\n EstimatedSpanCount SimpleAggregateFunction(sum, Float64),\n ErrorCount SimpleAggregateFunction(sum, UInt64),\n EstimatedErrorCount SimpleAggregateFunction(sum, Float64),\n DurationSum SimpleAggregateFunction(sum, Float64),\n DurationQuantiles AggregateFunction(quantilesTDigest(0.5, 0.95, 0.99), UInt64),\n FirstSeen SimpleAggregateFunction(min, DateTime),\n ApdexSatisfiedCount SimpleAggregateFunction(sum, UInt64),\n ApdexToleratingCount SimpleAggregateFunction(sum, UInt64),\n EstimatedLadderSpanCount SimpleAggregateFunction(sum, Float64) DEFAULT 0,\n EstimatedUnder50Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0,\n EstimatedUnder100Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0,\n EstimatedUnder250Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0,\n EstimatedUnder500Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0,\n EstimatedUnder1000Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0,\n EstimatedUnder2500Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0,\n EstimatedUnder5000Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0,\n EstimatedUnder10000Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0\n\nENGINE "AggregatingMergeTree"\nENGINE_PARTITION_KEY "toYYYYMM(Hour)"\nENGINE_SORTING_KEY "OrgId, ServiceName, Hour, DeploymentEnv, ServiceNamespace, CommitSha"\nENGINE_TTL "toDate(Hour) + INTERVAL 365 DAY"', }, { name: "service_overview_minutely", content: - 'DESCRIPTION >\n Minutely service entry-point aggregates with release dimensions, sampling-aware counts, latency states, and fixed-500ms Apdex counts. Serves sub-hour buckets that the hourly rollup cannot.\n\nSCHEMA >\n OrgId LowCardinality(String),\n Minute DateTime,\n ServiceName LowCardinality(String),\n DeploymentEnv LowCardinality(String),\n ServiceNamespace LowCardinality(String),\n CommitSha LowCardinality(String),\n SpanCount SimpleAggregateFunction(sum, UInt64),\n EstimatedSpanCount SimpleAggregateFunction(sum, Float64),\n ErrorCount SimpleAggregateFunction(sum, UInt64),\n EstimatedErrorCount SimpleAggregateFunction(sum, Float64),\n DurationSum SimpleAggregateFunction(sum, Float64),\n DurationQuantiles AggregateFunction(quantilesTDigest(0.5, 0.95, 0.99), UInt64),\n FirstSeen SimpleAggregateFunction(min, DateTime),\n ApdexSatisfiedCount SimpleAggregateFunction(sum, UInt64),\n ApdexToleratingCount SimpleAggregateFunction(sum, UInt64)\n\nENGINE "AggregatingMergeTree"\nENGINE_PARTITION_KEY "toDate(Minute)"\nENGINE_SORTING_KEY "OrgId, ServiceName, Minute, DeploymentEnv, ServiceNamespace, CommitSha"\nENGINE_TTL "toDate(Minute) + INTERVAL 90 DAY"', + 'DESCRIPTION >\n Minutely service entry-point aggregates with release dimensions, sampling-aware counts, latency states, and fixed-500ms Apdex counts. Serves sub-hour buckets that the hourly rollup cannot.\n\nSCHEMA >\n OrgId LowCardinality(String),\n Minute DateTime,\n ServiceName LowCardinality(String),\n DeploymentEnv LowCardinality(String),\n ServiceNamespace LowCardinality(String),\n CommitSha LowCardinality(String),\n SpanCount SimpleAggregateFunction(sum, UInt64),\n EstimatedSpanCount SimpleAggregateFunction(sum, Float64),\n ErrorCount SimpleAggregateFunction(sum, UInt64),\n EstimatedErrorCount SimpleAggregateFunction(sum, Float64),\n DurationSum SimpleAggregateFunction(sum, Float64),\n DurationQuantiles AggregateFunction(quantilesTDigest(0.5, 0.95, 0.99), UInt64),\n FirstSeen SimpleAggregateFunction(min, DateTime),\n ApdexSatisfiedCount SimpleAggregateFunction(sum, UInt64),\n ApdexToleratingCount SimpleAggregateFunction(sum, UInt64),\n EstimatedLadderSpanCount SimpleAggregateFunction(sum, Float64) DEFAULT 0,\n EstimatedUnder50Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0,\n EstimatedUnder100Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0,\n EstimatedUnder250Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0,\n EstimatedUnder500Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0,\n EstimatedUnder1000Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0,\n EstimatedUnder2500Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0,\n EstimatedUnder5000Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0,\n EstimatedUnder10000Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0\n\nENGINE "AggregatingMergeTree"\nENGINE_PARTITION_KEY "toDate(Minute)"\nENGINE_SORTING_KEY "OrgId, ServiceName, Minute, DeploymentEnv, ServiceNamespace, CommitSha"\nENGINE_TTL "toDate(Minute) + INTERVAL 90 DAY"', }, { name: "service_overview_spans", @@ -420,12 +420,12 @@ export const pipes = [ { name: "service_overview_hourly_mv", content: - "DESCRIPTION >\n Pre-aggregates service entry-point spans hourly by environment, namespace, and commit for one-year service history.\n\nNODE service_overview_hourly_mv_node\nSQL >\n SELECT\n OrgId,\n toStartOfHour(toDateTime(Timestamp)) AS Hour,\n ServiceName,\n coalesce(nullIf(ResourceAttributes['deployment.environment.name'], ''), ResourceAttributes['deployment.environment']) AS DeploymentEnv,\n ResourceAttributes['service.namespace'] AS ServiceNamespace,\n ResourceAttributes['vcs.ref.head.revision'] AS CommitSha,\n count() AS SpanCount,\n sum(SampleRate) AS EstimatedSpanCount,\n countIf(StatusCode = 'Error') AS ErrorCount,\n sumIf(SampleRate, StatusCode = 'Error') AS EstimatedErrorCount,\n sum(toFloat64(Duration)) AS DurationSum,\n quantilesTDigestState(0.5, 0.95, 0.99)(Duration) AS DurationQuantiles,\n min(toDateTime(Timestamp)) AS FirstSeen,\n countIf(StatusCode != 'Error' AND Duration < 500000000) AS ApdexSatisfiedCount,\n countIf(StatusCode != 'Error' AND Duration >= 500000000 AND Duration < 2000000000) AS ApdexToleratingCount\n FROM traces\n WHERE SpanKind IN ('Server', 'Consumer') OR ParentSpanId = ''\n GROUP BY OrgId, Hour, ServiceName, DeploymentEnv, ServiceNamespace, CommitSha\n\nTYPE MATERIALIZED\nDATASOURCE service_overview_hourly", + "DESCRIPTION >\n Pre-aggregates service entry-point spans hourly by environment, namespace, and commit for one-year service history.\n\nNODE service_overview_hourly_mv_node\nSQL >\n SELECT\n OrgId,\n toStartOfHour(toDateTime(Timestamp)) AS Hour,\n ServiceName,\n coalesce(nullIf(ResourceAttributes['deployment.environment.name'], ''), ResourceAttributes['deployment.environment']) AS DeploymentEnv,\n ResourceAttributes['service.namespace'] AS ServiceNamespace,\n ResourceAttributes['vcs.ref.head.revision'] AS CommitSha,\n count() AS SpanCount,\n sum(SampleRate) AS EstimatedSpanCount,\n countIf(StatusCode = 'Error') AS ErrorCount,\n sumIf(SampleRate, StatusCode = 'Error') AS EstimatedErrorCount,\n sum(toFloat64(Duration)) AS DurationSum,\n quantilesTDigestState(0.5, 0.95, 0.99)(Duration) AS DurationQuantiles,\n min(toDateTime(Timestamp)) AS FirstSeen,\n countIf(StatusCode != 'Error' AND Duration < 500000000) AS ApdexSatisfiedCount,\n countIf(StatusCode != 'Error' AND Duration >= 500000000 AND Duration < 2000000000) AS ApdexToleratingCount,\n sum(SampleRate) AS EstimatedLadderSpanCount,\n sumIf(SampleRate, Duration < 50000000) AS EstimatedUnder50Ms,\n sumIf(SampleRate, Duration < 100000000) AS EstimatedUnder100Ms,\n sumIf(SampleRate, Duration < 250000000) AS EstimatedUnder250Ms,\n sumIf(SampleRate, Duration < 500000000) AS EstimatedUnder500Ms,\n sumIf(SampleRate, Duration < 1000000000) AS EstimatedUnder1000Ms,\n sumIf(SampleRate, Duration < 2500000000) AS EstimatedUnder2500Ms,\n sumIf(SampleRate, Duration < 5000000000) AS EstimatedUnder5000Ms,\n sumIf(SampleRate, Duration < 10000000000) AS EstimatedUnder10000Ms\n FROM traces\n WHERE SpanKind IN ('Server', 'Consumer') OR ParentSpanId = ''\n GROUP BY OrgId, Hour, ServiceName, DeploymentEnv, ServiceNamespace, CommitSha\n\nTYPE MATERIALIZED\nDATASOURCE service_overview_hourly\nDEPLOYMENT_METHOD alter", }, { name: "service_overview_minutely_mv", content: - "DESCRIPTION >\n Pre-aggregates service entry-point spans minutely by environment, namespace, and commit, for windows whose bucket size is under an hour.\n\nNODE service_overview_minutely_mv_node\nSQL >\n SELECT\n OrgId,\n toStartOfMinute(toDateTime(Timestamp)) AS Minute,\n ServiceName,\n coalesce(nullIf(ResourceAttributes['deployment.environment.name'], ''), ResourceAttributes['deployment.environment']) AS DeploymentEnv,\n ResourceAttributes['service.namespace'] AS ServiceNamespace,\n ResourceAttributes['vcs.ref.head.revision'] AS CommitSha,\n count() AS SpanCount,\n sum(SampleRate) AS EstimatedSpanCount,\n countIf(StatusCode = 'Error') AS ErrorCount,\n sumIf(SampleRate, StatusCode = 'Error') AS EstimatedErrorCount,\n sum(toFloat64(Duration)) AS DurationSum,\n quantilesTDigestState(0.5, 0.95, 0.99)(Duration) AS DurationQuantiles,\n min(toDateTime(Timestamp)) AS FirstSeen,\n countIf(StatusCode != 'Error' AND Duration < 500000000) AS ApdexSatisfiedCount,\n countIf(StatusCode != 'Error' AND Duration >= 500000000 AND Duration < 2000000000) AS ApdexToleratingCount\n FROM traces\n WHERE SpanKind IN ('Server', 'Consumer') OR ParentSpanId = ''\n GROUP BY OrgId, Minute, ServiceName, DeploymentEnv, ServiceNamespace, CommitSha\n\nTYPE MATERIALIZED\nDATASOURCE service_overview_minutely", + "DESCRIPTION >\n Pre-aggregates service entry-point spans minutely by environment, namespace, and commit, for windows whose bucket size is under an hour.\n\nNODE service_overview_minutely_mv_node\nSQL >\n SELECT\n OrgId,\n toStartOfMinute(toDateTime(Timestamp)) AS Minute,\n ServiceName,\n coalesce(nullIf(ResourceAttributes['deployment.environment.name'], ''), ResourceAttributes['deployment.environment']) AS DeploymentEnv,\n ResourceAttributes['service.namespace'] AS ServiceNamespace,\n ResourceAttributes['vcs.ref.head.revision'] AS CommitSha,\n count() AS SpanCount,\n sum(SampleRate) AS EstimatedSpanCount,\n countIf(StatusCode = 'Error') AS ErrorCount,\n sumIf(SampleRate, StatusCode = 'Error') AS EstimatedErrorCount,\n sum(toFloat64(Duration)) AS DurationSum,\n quantilesTDigestState(0.5, 0.95, 0.99)(Duration) AS DurationQuantiles,\n min(toDateTime(Timestamp)) AS FirstSeen,\n countIf(StatusCode != 'Error' AND Duration < 500000000) AS ApdexSatisfiedCount,\n countIf(StatusCode != 'Error' AND Duration >= 500000000 AND Duration < 2000000000) AS ApdexToleratingCount,\n sum(SampleRate) AS EstimatedLadderSpanCount,\n sumIf(SampleRate, Duration < 50000000) AS EstimatedUnder50Ms,\n sumIf(SampleRate, Duration < 100000000) AS EstimatedUnder100Ms,\n sumIf(SampleRate, Duration < 250000000) AS EstimatedUnder250Ms,\n sumIf(SampleRate, Duration < 500000000) AS EstimatedUnder500Ms,\n sumIf(SampleRate, Duration < 1000000000) AS EstimatedUnder1000Ms,\n sumIf(SampleRate, Duration < 2500000000) AS EstimatedUnder2500Ms,\n sumIf(SampleRate, Duration < 5000000000) AS EstimatedUnder5000Ms,\n sumIf(SampleRate, Duration < 10000000000) AS EstimatedUnder10000Ms\n FROM traces\n WHERE SpanKind IN ('Server', 'Consumer') OR ParentSpanId = ''\n GROUP BY OrgId, Minute, ServiceName, DeploymentEnv, ServiceNamespace, CommitSha\n\nTYPE MATERIALIZED\nDATASOURCE service_overview_minutely\nDEPLOYMENT_METHOD alter", }, { name: "service_overview_spans_mv", diff --git a/packages/domain/src/tinybird/datasources.ts b/packages/domain/src/tinybird/datasources.ts index 837d38e066..8c6345e4f8 100644 --- a/packages/domain/src/tinybird/datasources.ts +++ b/packages/domain/src/tinybird/datasources.ts @@ -1,6 +1,6 @@ import { column, defineDatasource, engine, type InferRow, t } from "@maple-dev/effect-orm/tinybird" import * as CH from "@maple-dev/effect-orm/clickhouse" -import { OrgId, SpanId, TraceId } from "@maple/primitives" +import { OrgId, type SloLatencyThresholdMs, SpanId, TraceId } from "@maple/primitives" /** * A `DateTime` column queried as a `DateTime.Utc`. ClickHouse sends the @@ -865,6 +865,27 @@ export const serviceOverviewSpans = defineDatasource("service_overview_spans", { export type ServiceOverviewSpansRow = InferRow +/** The rollup column counting entry-point spans faster than one `SLO_LATENCY_THRESHOLDS_MS` rung. */ +export const sloLatencyColumnName = (ms: Ms): `EstimatedUnder${Ms}Ms` => + `EstimatedUnder${ms}Ms` + +/** Spans the ladder covers (`sum(SampleRate)`): the latency SLI's total, 0 on pre-0059 buckets. */ +export const SLO_LATENCY_COVERAGE_COLUMN = "EstimatedLadderSpanCount" as const + +// Latency SLI ladder: `sumIf(SampleRate, Duration < ms)` per rung, any status. Written only by +// the post-0059 views, so older buckets read 0 on every rung and on the coverage column. +const sloLatencyLadderColumns = { + [SLO_LATENCY_COVERAGE_COLUMN]: t.simpleAggregateFunction("sum", t.float64()).default(0), + EstimatedUnder50Ms: t.simpleAggregateFunction("sum", t.float64()).default(0), + EstimatedUnder100Ms: t.simpleAggregateFunction("sum", t.float64()).default(0), + EstimatedUnder250Ms: t.simpleAggregateFunction("sum", t.float64()).default(0), + EstimatedUnder500Ms: t.simpleAggregateFunction("sum", t.float64()).default(0), + EstimatedUnder1000Ms: t.simpleAggregateFunction("sum", t.float64()).default(0), + EstimatedUnder2500Ms: t.simpleAggregateFunction("sum", t.float64()).default(0), + EstimatedUnder5000Ms: t.simpleAggregateFunction("sum", t.float64()).default(0), + EstimatedUnder10000Ms: t.simpleAggregateFunction("sum", t.float64()).default(0), +} satisfies Record<`EstimatedUnder${SloLatencyThresholdMs}Ms` | typeof SLO_LATENCY_COVERAGE_COLUMN, unknown> + /** * Hour-grain service overview metrics retained independently from the 30-day * entry-point span projection. This is the durable source for service @@ -896,6 +917,7 @@ export const serviceOverviewHourly = defineDatasource("service_overview_hourly", FirstSeen: t.simpleAggregateFunction("min", utcDateTime()), ApdexSatisfiedCount: t.simpleAggregateFunction("sum", t.uint64()), ApdexToleratingCount: t.simpleAggregateFunction("sum", t.uint64()), + ...sloLatencyLadderColumns, }, engine: engine.aggregatingMergeTree({ partitionKey: "toYYYYMM(Hour)", @@ -946,6 +968,7 @@ export const serviceOverviewMinutely = defineDatasource("service_overview_minute FirstSeen: t.simpleAggregateFunction("min", utcDateTime()), ApdexSatisfiedCount: t.simpleAggregateFunction("sum", t.uint64()), ApdexToleratingCount: t.simpleAggregateFunction("sum", t.uint64()), + ...sloLatencyLadderColumns, }, engine: engine.aggregatingMergeTree({ // Daily parts, not monthly: at minute grain a month-wide part is far too diff --git a/packages/domain/src/tinybird/materializations.ts b/packages/domain/src/tinybird/materializations.ts index 7d165f56a0..90087c549d 100644 --- a/packages/domain/src/tinybird/materializations.ts +++ b/packages/domain/src/tinybird/materializations.ts @@ -38,6 +38,8 @@ import { serviceExternalEdgesHourly, servicePlatformsHourly, serviceOverviewSpans, + SLO_LATENCY_COVERAGE_COLUMN, + sloLatencyColumnName, serviceOverviewHourly, serviceOverviewMinutely, errorEvents, @@ -138,6 +140,7 @@ import { WEB_REQUEST_SQL, } from "./user-activity-sql" import { Array as Arr } from "effect" +import { SLO_LATENCY_THRESHOLDS_MS } from "@maple/primitives" /** * Materialized view to aggregate log usage statistics per service per hour @@ -501,6 +504,19 @@ export const serviceOverviewSpansMv = defineMaterializedView("service_overview_s ], }) +// The coverage total, then one sampling-weighted count per SLO latency rung; Duration is +// nanoseconds, status is ignored. +const SLO_LATENCY_LADDER_SQL = Arr.join( + [ + `sum(SampleRate) AS ${SLO_LATENCY_COVERAGE_COLUMN}`, + ...Arr.map( + SLO_LATENCY_THRESHOLDS_MS, + (ms) => `sumIf(SampleRate, Duration < ${ms * 1_000_000}) AS ${sloLatencyColumnName(ms)}`, + ), + ], + ",\n ", +) + /** * Durable service-level rollup for one-year overview and catalog queries. * Entry-point semantics intentionally match service_overview_spans_mv. @@ -509,6 +525,9 @@ export const serviceOverviewHourlyMv = defineMaterializedView("service_overview_ description: "Pre-aggregates service entry-point spans hourly by environment, namespace, and commit for one-year service history.", datasource: serviceOverviewHourly, + // Migration 0059 adds the SLO latency ladder columns. A rebuild would replay `traces` + // (30 days) into this longer-retained target and drop the rest; `alter` adds them in place. + deploymentMethod: "alter", nodes: [ node({ name: "service_overview_hourly_mv_node", @@ -528,7 +547,8 @@ export const serviceOverviewHourlyMv = defineMaterializedView("service_overview_ quantilesTDigestState(0.5, 0.95, 0.99)(Duration) AS DurationQuantiles, min(toDateTime(Timestamp)) AS FirstSeen, countIf(StatusCode != 'Error' AND Duration < 500000000) AS ApdexSatisfiedCount, - countIf(StatusCode != 'Error' AND Duration >= 500000000 AND Duration < 2000000000) AS ApdexToleratingCount + countIf(StatusCode != 'Error' AND Duration >= 500000000 AND Duration < 2000000000) AS ApdexToleratingCount, + ${SLO_LATENCY_LADDER_SQL} FROM traces WHERE SpanKind IN ('Server', 'Consumer') OR ParentSpanId = '' GROUP BY OrgId, Hour, ServiceName, DeploymentEnv, ServiceNamespace, CommitSha @@ -555,6 +575,8 @@ export const serviceOverviewMinutelyMv = defineMaterializedView("service_overvie description: "Pre-aggregates service entry-point spans minutely by environment, namespace, and commit, for windows whose bucket size is under an hour.", datasource: serviceOverviewMinutely, + // Same as service_overview_hourly_mv: a rebuild would drop the history past `traces`' 30 days. + deploymentMethod: "alter", nodes: [ node({ name: "service_overview_minutely_mv_node", @@ -574,7 +596,8 @@ export const serviceOverviewMinutelyMv = defineMaterializedView("service_overvie quantilesTDigestState(0.5, 0.95, 0.99)(Duration) AS DurationQuantiles, min(toDateTime(Timestamp)) AS FirstSeen, countIf(StatusCode != 'Error' AND Duration < 500000000) AS ApdexSatisfiedCount, - countIf(StatusCode != 'Error' AND Duration >= 500000000 AND Duration < 2000000000) AS ApdexToleratingCount + countIf(StatusCode != 'Error' AND Duration >= 500000000 AND Duration < 2000000000) AS ApdexToleratingCount, + ${SLO_LATENCY_LADDER_SQL} FROM traces WHERE SpanKind IN ('Server', 'Consumer') OR ParentSpanId = '' GROUP BY OrgId, Minute, ServiceName, DeploymentEnv, ServiceNamespace, CommitSha diff --git a/packages/domain/src/tinybird/retention-matrix.test.ts b/packages/domain/src/tinybird/retention-matrix.test.ts index f717724ca2..b5bce9a65b 100644 --- a/packages/domain/src/tinybird/retention-matrix.test.ts +++ b/packages/domain/src/tinybird/retention-matrix.test.ts @@ -114,10 +114,12 @@ describe("Tinybird retention matrix", () => { * data movement, which is the only correct shape here. Migration 0023's first * deploy attempt failed on exactly this. */ - it("applies service-operations, endpoint-details and user-activity rollup changes by ALTER, never by rebuild", () => { + it("applies service-operations, service-overview, endpoint-details and user-activity rollup changes by ALTER, never by rebuild", () => { for (const name of [ "service_operations_minutely_mv", "service_operations_hourly_mv", + "service_overview_minutely_mv", + "service_overview_hourly_mv", "service_endpoint_details_hourly_mv", "user_activity_hourly_traces_mv", "user_activity_hourly_logs_mv", diff --git a/packages/local-server/src/local-schema-history.ts b/packages/local-server/src/local-schema-history.ts index 17e3804ab1..2101807016 100644 --- a/packages/local-server/src/local-schema-history.ts +++ b/packages/local-server/src/local-schema-history.ts @@ -646,6 +646,19 @@ export const LOCAL_SCHEMA_HISTORY: ReadonlyArray = Obje manifestDigest: "7b26f625ab2df2ed1fe25317d7a63df5f167ed464ba713c78b442af316d51221", projectRevision: "ed74788ef292834069e0ea6ee3b22d68fc604fb66cb54d2d551db67ce8d20b3a", }), + Object.freeze({ + // TODO(v51): what changed, whether any part is rewritten or any row + // moves, and what this edge does NOT backfill. + // + // projectRevision is carried forward deliberately: it is a hardcoded + // constant that no longer tracks the generator's header, and the identity + // this gate compares is the fingerprint/digest pair. + version: 51, + fingerprint: "252552abda61d2c2", + digest: "252552abda61d2c29ed4ee9e8a685973743ce96ce179819e0c7d2ad531202ba9", + manifestDigest: "44bf4dc56e02618a165b96497468f302d43353dd02327795eb03255f9edf5dc9", + projectRevision: "ed74788ef292834069e0ea6ee3b22d68fc604fb66cb54d2d551db67ce8d20b3a", + }), ] as const) /** Immutable SQLite control DDL identities, checked by clickhouse:schema:check. */ diff --git a/packages/local-server/src/local-schema-version.ts b/packages/local-server/src/local-schema-version.ts index 044374884d..ebadd560a1 100644 --- a/packages/local-server/src/local-schema-version.ts +++ b/packages/local-server/src/local-schema-version.ts @@ -1,7 +1,7 @@ // 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 = 50 as const +export const LOCAL_SCHEMA_VERSION = 51 as const /** SQLite eventing state has its own independent version sequence. */ export const LOCAL_CONTROL_SCHEMA_VERSION = 1 as const diff --git a/packages/local-server/src/schema-identity.ts b/packages/local-server/src/schema-identity.ts index 564dc09801..70a7f3f3b2 100644 --- a/packages/local-server/src/schema-identity.ts +++ b/packages/local-server/src/schema-identity.ts @@ -101,6 +101,7 @@ export const LOCAL_SCHEMA_V47 = localSchemaIdentity(47) export const LOCAL_SCHEMA_V48 = localSchemaIdentity(48) export const LOCAL_SCHEMA_V49 = localSchemaIdentity(49) export const LOCAL_SCHEMA_V50 = localSchemaIdentity(50) +export const LOCAL_SCHEMA_V51 = localSchemaIdentity(51) export const CURRENT_LOCAL_SCHEMA: LocalSchemaIdentity = Object.freeze({ version: LOCAL_SCHEMA_VERSION, diff --git a/packages/local-server/src/schema/local-inserts.json b/packages/local-server/src/schema/local-inserts.json index 7306761ade..5550f45e26 100644 --- a/packages/local-server/src/schema/local-inserts.json +++ b/packages/local-server/src/schema/local-inserts.json @@ -1,5 +1,5 @@ { - "projectRevision": "98e3aa0756a3bd1fee72f2adc1abf24df132d948a76d343c1d43d8b835a46fa1", + "projectRevision": "2b7126b41d1bab3c0f728acdf90d1eff82b7e47a17f9d9d6ddf8cb90e51173c8", "orgPlaceholder": "__ORG__", "datasources": { "traces": { diff --git a/packages/local-server/src/schema/local-schema.sql b/packages/local-server/src/schema/local-schema.sql index fbba5425e1..ec8c851409 100644 --- a/packages/local-server/src/schema/local-schema.sql +++ b/packages/local-server/src/schema/local-schema.sql @@ -1,7 +1,7 @@ -- This file is generated by scripts/generate-clickhouse-schema-sql.ts -- Do not edit manually. Run `bun run clickhouse:schema` to regenerate. --- projectRevision: 98e3aa0756a3bd1fee72f2adc1abf24df132d948a76d343c1d43d8b835a46fa1 --- localSchemaVersion: 50 +-- projectRevision: 2b7126b41d1bab3c0f728acdf90d1eff82b7e47a17f9d9d6ddf8cb90e51173c8 +-- localSchemaVersion: 51 CREATE TABLE IF NOT EXISTS ai_crawler_requests ( OrgId LowCardinality(String), @@ -783,7 +783,16 @@ CREATE TABLE IF NOT EXISTS service_overview_hourly ( DurationQuantiles AggregateFunction(quantilesTDigest(0.5, 0.95, 0.99), UInt64), FirstSeen SimpleAggregateFunction(min, DateTime), ApdexSatisfiedCount SimpleAggregateFunction(sum, UInt64), - ApdexToleratingCount SimpleAggregateFunction(sum, UInt64) + ApdexToleratingCount SimpleAggregateFunction(sum, UInt64), + EstimatedLadderSpanCount SimpleAggregateFunction(sum, Float64) DEFAULT 0, + EstimatedUnder50Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0, + EstimatedUnder100Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0, + EstimatedUnder250Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0, + EstimatedUnder500Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0, + EstimatedUnder1000Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0, + EstimatedUnder2500Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0, + EstimatedUnder5000Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0, + EstimatedUnder10000Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0 ) ENGINE = AggregatingMergeTree PARTITION BY toYYYYMM(Hour) @@ -805,7 +814,16 @@ CREATE TABLE IF NOT EXISTS service_overview_minutely ( DurationQuantiles AggregateFunction(quantilesTDigest(0.5, 0.95, 0.99), UInt64), FirstSeen SimpleAggregateFunction(min, DateTime), ApdexSatisfiedCount SimpleAggregateFunction(sum, UInt64), - ApdexToleratingCount SimpleAggregateFunction(sum, UInt64) + ApdexToleratingCount SimpleAggregateFunction(sum, UInt64), + EstimatedLadderSpanCount SimpleAggregateFunction(sum, Float64) DEFAULT 0, + EstimatedUnder50Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0, + EstimatedUnder100Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0, + EstimatedUnder250Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0, + EstimatedUnder500Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0, + EstimatedUnder1000Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0, + EstimatedUnder2500Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0, + EstimatedUnder5000Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0, + EstimatedUnder10000Ms SimpleAggregateFunction(sum, Float64) DEFAULT 0 ) ENGINE = AggregatingMergeTree PARTITION BY toDate(Minute) @@ -2377,7 +2395,16 @@ SELECT quantilesTDigestState(0.5, 0.95, 0.99)(Duration) AS DurationQuantiles, min(toDateTime(Timestamp)) AS FirstSeen, countIf(StatusCode != 'Error' AND Duration < 500000000) AS ApdexSatisfiedCount, - countIf(StatusCode != 'Error' AND Duration >= 500000000 AND Duration < 2000000000) AS ApdexToleratingCount + countIf(StatusCode != 'Error' AND Duration >= 500000000 AND Duration < 2000000000) AS ApdexToleratingCount, + sum(SampleRate) AS EstimatedLadderSpanCount, + sumIf(SampleRate, Duration < 50000000) AS EstimatedUnder50Ms, + sumIf(SampleRate, Duration < 100000000) AS EstimatedUnder100Ms, + sumIf(SampleRate, Duration < 250000000) AS EstimatedUnder250Ms, + sumIf(SampleRate, Duration < 500000000) AS EstimatedUnder500Ms, + sumIf(SampleRate, Duration < 1000000000) AS EstimatedUnder1000Ms, + sumIf(SampleRate, Duration < 2500000000) AS EstimatedUnder2500Ms, + sumIf(SampleRate, Duration < 5000000000) AS EstimatedUnder5000Ms, + sumIf(SampleRate, Duration < 10000000000) AS EstimatedUnder10000Ms FROM traces WHERE SpanKind IN ('Server', 'Consumer') OR ParentSpanId = '' GROUP BY OrgId, Hour, ServiceName, DeploymentEnv, ServiceNamespace, CommitSha; @@ -2398,7 +2425,16 @@ SELECT quantilesTDigestState(0.5, 0.95, 0.99)(Duration) AS DurationQuantiles, min(toDateTime(Timestamp)) AS FirstSeen, countIf(StatusCode != 'Error' AND Duration < 500000000) AS ApdexSatisfiedCount, - countIf(StatusCode != 'Error' AND Duration >= 500000000 AND Duration < 2000000000) AS ApdexToleratingCount + countIf(StatusCode != 'Error' AND Duration >= 500000000 AND Duration < 2000000000) AS ApdexToleratingCount, + sum(SampleRate) AS EstimatedLadderSpanCount, + sumIf(SampleRate, Duration < 50000000) AS EstimatedUnder50Ms, + sumIf(SampleRate, Duration < 100000000) AS EstimatedUnder100Ms, + sumIf(SampleRate, Duration < 250000000) AS EstimatedUnder250Ms, + sumIf(SampleRate, Duration < 500000000) AS EstimatedUnder500Ms, + sumIf(SampleRate, Duration < 1000000000) AS EstimatedUnder1000Ms, + sumIf(SampleRate, Duration < 2500000000) AS EstimatedUnder2500Ms, + sumIf(SampleRate, Duration < 5000000000) AS EstimatedUnder5000Ms, + sumIf(SampleRate, Duration < 10000000000) AS EstimatedUnder10000Ms FROM traces WHERE SpanKind IN ('Server', 'Consumer') OR ParentSpanId = '' GROUP BY OrgId, Minute, ServiceName, DeploymentEnv, ServiceNamespace, CommitSha;