@mastra/clickhouse 1.26.0-alpha.2 → 1.26.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/integrations-databases-clickhouse.md +1 -1
- package/dist/index.cjs +401 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +401 -38
- package/dist/index.js.map +1 -1
- package/dist/storage/domains/observability/v-next/index.d.ts +4 -1
- package/dist/storage/domains/observability/v-next/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/preview-compat.d.ts +1 -0
- package/dist/storage/domains/observability/v-next/preview-compat.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/span-query.d.ts +1 -0
- package/dist/storage/domains/observability/v-next/span-query.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/thread-query.d.ts +55 -0
- package/dist/storage/domains/observability/v-next/thread-query.d.ts.map +1 -0
- package/dist/storage/domains/observability/v-next/trace-aggregate.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/trace-query.d.ts +13 -2
- package/dist/storage/domains/observability/v-next/trace-query.d.ts.map +1 -1
- package/package.json +5 -6
package/dist/index.js
CHANGED
|
@@ -6841,6 +6841,14 @@ async function getScorePercentiles(client, args) {
|
|
|
6841
6841
|
//#endregion
|
|
6842
6842
|
//#region src/storage/domains/observability/v-next/trace-query.ts
|
|
6843
6843
|
const TRACE_STATUS_SQL = `if(isNotNull(r.error), 'error', 'success')`;
|
|
6844
|
+
/**
|
|
6845
|
+
* Core versions without the field reject it in strict query results, so it is only returned when core knows it.
|
|
6846
|
+
* Core before 1.68 has no field registry at all.
|
|
6847
|
+
*/
|
|
6848
|
+
const timeToFirstChunkSupported = "timeToFirstChunkMs" in (coreStorage.TRACE_QUERY_FIELD_REGISTRY?.trace ?? {});
|
|
6849
|
+
/** Core before the sort rejects `orderBy` on the field, so the sort is only declared when core plans it. */
|
|
6850
|
+
const timeToFirstChunkSortSupported = coreStorage.TRACE_QUERY_ORDER_FIELDS?.includes("timeToFirstChunkMs") === true;
|
|
6851
|
+
const timeToFirstChunkFeatures = timeToFirstChunkSupported ? timeToFirstChunkSortSupported ? ["trace-query-time-to-first-chunk", "trace-query-time-to-first-chunk-sort"] : ["trace-query-time-to-first-chunk"] : [];
|
|
6844
6852
|
function durationMsSql(startedAt, endedAt) {
|
|
6845
6853
|
return `dateDiff('millisecond', ${startedAt}, ${endedAt})`;
|
|
6846
6854
|
}
|
|
@@ -6889,6 +6897,10 @@ const TRACE_FIELDS = {
|
|
|
6889
6897
|
sql: durationMsSql("r.startedAt", "r.endedAt"),
|
|
6890
6898
|
parameterType: "Float64"
|
|
6891
6899
|
},
|
|
6900
|
+
timeToFirstChunkMs: {
|
|
6901
|
+
sql: "r.timeToFirstChunkMs",
|
|
6902
|
+
parameterType: "Float64"
|
|
6903
|
+
},
|
|
6892
6904
|
entityName: {
|
|
6893
6905
|
sql: "r.entityName",
|
|
6894
6906
|
parameterType: "String"
|
|
@@ -6897,6 +6909,14 @@ const TRACE_FIELDS = {
|
|
|
6897
6909
|
sql: "r.entityType",
|
|
6898
6910
|
parameterType: "String"
|
|
6899
6911
|
},
|
|
6912
|
+
name: {
|
|
6913
|
+
sql: "r.name",
|
|
6914
|
+
parameterType: "String"
|
|
6915
|
+
},
|
|
6916
|
+
spanType: {
|
|
6917
|
+
sql: "r.spanType",
|
|
6918
|
+
parameterType: "String"
|
|
6919
|
+
},
|
|
6900
6920
|
environment: {
|
|
6901
6921
|
sql: "r.environment",
|
|
6902
6922
|
parameterType: "String"
|
|
@@ -6939,6 +6959,10 @@ const SPAN_FIELDS = {
|
|
|
6939
6959
|
sql: "s.durationMs",
|
|
6940
6960
|
parameterType: "Float64"
|
|
6941
6961
|
},
|
|
6962
|
+
timeToFirstChunkMs: {
|
|
6963
|
+
sql: "s.timeToFirstChunkMs",
|
|
6964
|
+
parameterType: "Float64"
|
|
6965
|
+
},
|
|
6942
6966
|
status: {
|
|
6943
6967
|
sql: "s.status",
|
|
6944
6968
|
parameterType: "String"
|
|
@@ -7150,6 +7174,7 @@ const TRACE_SELECT = `
|
|
|
7150
7174
|
r.entityName AS entityName,
|
|
7151
7175
|
r.entityType AS entityType,
|
|
7152
7176
|
r.environment AS environment,
|
|
7177
|
+
r.timeToFirstChunkMs AS timeToFirstChunkMs,
|
|
7153
7178
|
${TRACE_STATUS_SQL} AS status`;
|
|
7154
7179
|
var ParameterBuilder = class {
|
|
7155
7180
|
params = {};
|
|
@@ -7166,7 +7191,7 @@ function fieldDefinition(registry, field) {
|
|
|
7166
7191
|
return definition;
|
|
7167
7192
|
}
|
|
7168
7193
|
function resolveOrderField(field) {
|
|
7169
|
-
if (field === "startedAt" || field === "endedAt") return field;
|
|
7194
|
+
if (field === "startedAt" || field === "endedAt" || field === "timeToFirstChunkMs") return field;
|
|
7170
7195
|
throw new Error(`Unsupported trusted trace-query field: ${field}`);
|
|
7171
7196
|
}
|
|
7172
7197
|
function isMetadataField(field) {
|
|
@@ -7325,6 +7350,7 @@ function compileClickHouseTraceScope(selection, relationCollections, parameters,
|
|
|
7325
7350
|
startedAt,
|
|
7326
7351
|
endedAt,
|
|
7327
7352
|
${durationMsSql("startedAt", "endedAt")} AS durationMs,
|
|
7353
|
+
timeToFirstChunkMs,
|
|
7328
7354
|
if(isNotNull(error), 'error', 'success') AS status,
|
|
7329
7355
|
error,
|
|
7330
7356
|
entityType,
|
|
@@ -7443,9 +7469,11 @@ function traceOrder(plan) {
|
|
|
7443
7469
|
*/
|
|
7444
7470
|
function keysetCursorCondition(plan, order, parameters) {
|
|
7445
7471
|
if (plan.paginationMode !== "keyset" || !plan.cursor) return "";
|
|
7472
|
+
if (plan.cursor.sortValue === null) return `(isNull(sortValue) AND traceId > ${parameters.add(plan.cursor.traceId, "String")})`;
|
|
7473
|
+
const numeric = order.field === "timeToFirstChunkMs";
|
|
7446
7474
|
const comparison = order.direction === "ASC" ? ">" : "<";
|
|
7447
|
-
const sortValue = parameters.add(plan.cursor.sortValue, "DateTime64(3, 'UTC')");
|
|
7448
|
-
return `(sortValue ${comparison} ${sortValue} OR (sortValue = ${sortValue} AND traceId > ${parameters.add(plan.cursor.traceId, "String")}))`;
|
|
7475
|
+
const sortValue = parameters.add(plan.cursor.sortValue, numeric ? "Float64" : "DateTime64(3, 'UTC')");
|
|
7476
|
+
return `(sortValue ${comparison} ${sortValue} OR (sortValue = ${sortValue} AND traceId > ${parameters.add(plan.cursor.traceId, "String")})${numeric ? " OR isNull(sortValue)" : ""})`;
|
|
7449
7477
|
}
|
|
7450
7478
|
/**
|
|
7451
7479
|
* First statement of keyset and page mode: the page's traces (after the keyset cursor, `limit + 1`
|
|
@@ -7457,21 +7485,22 @@ function compileClickHouseTraceQueryKeys(plan) {
|
|
|
7457
7485
|
if (plan.paginationMode === "delta") throw new Error("Delta trace queries have no keys statement");
|
|
7458
7486
|
const parameters = new ParameterBuilder();
|
|
7459
7487
|
const order = traceOrder(plan);
|
|
7460
|
-
const ctes = compileClickHouseTraceCandidates(plan, "r.traceId AS traceId, r.dedupeKey AS dedupeKey, r.startedAt AS startedAt, r.endedAt AS endedAt", parameters, QUERY_TRACES_SCOPE);
|
|
7488
|
+
const ctes = compileClickHouseTraceCandidates(plan, "r.traceId AS traceId, r.dedupeKey AS dedupeKey, r.startedAt AS startedAt, r.endedAt AS endedAt, r.timeToFirstChunkMs AS timeToFirstChunkMs", parameters, QUERY_TRACES_SCOPE);
|
|
7461
7489
|
const cursor = keysetCursorCondition(plan, order, parameters);
|
|
7462
7490
|
const pick = order.direction === "DESC" ? "max" : "min";
|
|
7491
|
+
const rootOrder = order.field === "timeToFirstChunkMs" ? `ifNull(${order.field}, ${pick === "max" ? "-inf" : "inf"})` : order.field;
|
|
7463
7492
|
const limit = plan.paginationMode === "page" ? `LIMIT ${parameters.add(plan.perPage, "UInt64")} OFFSET ${parameters.add(plan.page * plan.perPage, "UInt64")}` : `LIMIT ${parameters.add(plan.limit + 1, "UInt64")}`;
|
|
7464
7493
|
return {
|
|
7465
7494
|
query: `WITH ${ctes.join(",\n")},
|
|
7466
7495
|
per_trace AS (
|
|
7467
7496
|
SELECT traceId, ${pick}(${order.field}) AS sortValue,
|
|
7468
|
-
${pick === "max" ? "argMax" : "argMin"}((dedupeKey, startedAt), ${
|
|
7497
|
+
${pick === "max" ? "argMax" : "argMin"}((dedupeKey, startedAt), ${rootOrder}) AS root
|
|
7469
7498
|
FROM candidates
|
|
7470
7499
|
GROUP BY traceId${cursor ? `\n HAVING ${cursor}` : ""}
|
|
7471
7500
|
)
|
|
7472
7501
|
SELECT traceId, root.1 AS dedupeKey, sortValue, root.2 AS rootStartedAt, count() OVER () AS __total
|
|
7473
7502
|
FROM per_trace
|
|
7474
|
-
ORDER BY sortValue ${order.direction}, traceId ASC
|
|
7503
|
+
ORDER BY sortValue ${order.direction} NULLS LAST, traceId ASC
|
|
7475
7504
|
${limit}`,
|
|
7476
7505
|
query_params: parameters.params,
|
|
7477
7506
|
sharedSnapshot: true
|
|
@@ -7489,16 +7518,16 @@ function compileClickHouseTraceRowsByKey(plan, keys) {
|
|
|
7489
7518
|
const ts = (value) => parameters.add(value, "DateTime64(3, 'UTC')");
|
|
7490
7519
|
const byEnd = order.field === "endedAt";
|
|
7491
7520
|
const tuples = keys.map((key) => {
|
|
7492
|
-
const started = ts(
|
|
7521
|
+
const started = ts(key.rootStartedAt);
|
|
7493
7522
|
const id = `${parameters.add(key.traceId, "String")}, ${parameters.add(key.dedupeKey, "String")}`;
|
|
7494
|
-
return byEnd ? `(${started}, ${id}, ${ts(key.sortValue)})` : `(${started}, ${id})`;
|
|
7523
|
+
return byEnd ? `(${started}, ${id}, ${ts(String(key.sortValue))})` : `(${started}, ${id})`;
|
|
7495
7524
|
});
|
|
7496
7525
|
const predicate = (plan.where ? (plan.where.type === "boolean" && plan.where.operator === "and" ? plan.where.args : [plan.where]).filter((conjunct) => collectRelationCollections(conjunct).size === 0) : []).map((conjunct) => `\n AND (${compilePredicate(conjunct, parameters)})`).join("");
|
|
7497
7526
|
return {
|
|
7498
7527
|
query: `SELECT ${TRACE_SELECT}
|
|
7499
7528
|
FROM ${TABLE_TRACE_ROOTS} r
|
|
7500
7529
|
WHERE (r.startedAt, r.traceId, r.dedupeKey${byEnd ? ", r.endedAt" : ""}) IN (${tuples.join(", ")})${compileTenantScope(plan.scope, parameters)}${predicate}
|
|
7501
|
-
ORDER BY ${order.field} ${order.direction}, traceId ASC
|
|
7530
|
+
ORDER BY ${order.field} ${order.direction} NULLS LAST, traceId ASC
|
|
7502
7531
|
LIMIT 1 BY traceId`,
|
|
7503
7532
|
query_params: parameters.params
|
|
7504
7533
|
};
|
|
@@ -7737,6 +7766,12 @@ async function runWithClickHouseTraceQueryTimeout(client, limits, compiled, quer
|
|
|
7737
7766
|
...CH_SETTINGS,
|
|
7738
7767
|
max_execution_time: resolvedTimeoutMs / 1e3,
|
|
7739
7768
|
...limits.memoryLimitBytes === void 0 ? {} : { max_memory_usage: String(limits.memoryLimitBytes) },
|
|
7769
|
+
...limits.maxThreads === void 0 ? {} : { max_threads: limits.maxThreads },
|
|
7770
|
+
...limits.maxBlockSize === void 0 ? {} : { max_block_size: String(limits.maxBlockSize) },
|
|
7771
|
+
...limits.spillThresholdBytes === void 0 ? {} : {
|
|
7772
|
+
max_bytes_before_external_sort: String(limits.spillThresholdBytes),
|
|
7773
|
+
max_bytes_before_external_group_by: String(limits.spillThresholdBytes)
|
|
7774
|
+
},
|
|
7740
7775
|
...compiled.sharedSnapshot ? { enable_shared_storage_snapshot_in_query: 1 } : {}
|
|
7741
7776
|
}
|
|
7742
7777
|
})).json();
|
|
@@ -7859,7 +7894,8 @@ async function queryTraces(client, plan, timeoutMs, strategy = null) {
|
|
|
7859
7894
|
entityName: row.entityName == null ? null : String(row.entityName),
|
|
7860
7895
|
entityType: row.entityType == null ? null : String(row.entityType),
|
|
7861
7896
|
environment: row.environment == null ? null : String(row.environment),
|
|
7862
|
-
status: row.status
|
|
7897
|
+
status: row.status,
|
|
7898
|
+
...timeToFirstChunkSupported && { timeToFirstChunkMs: row.timeToFirstChunkMs == null ? null : Number(row.timeToFirstChunkMs) }
|
|
7863
7899
|
});
|
|
7864
7900
|
if (plan.paginationMode === "delta") {
|
|
7865
7901
|
const rows = await run(compileClickHouseTraceQuery(plan, deltaHead));
|
|
@@ -7882,6 +7918,7 @@ async function queryTraces(client, plan, timeoutMs, strategy = null) {
|
|
|
7882
7918
|
});
|
|
7883
7919
|
}
|
|
7884
7920
|
const keys = await run(compileClickHouseTraceQueryKeys(plan));
|
|
7921
|
+
const sortValue = (value) => plan.orderBy.field !== "timeToFirstChunkMs" ? asIsoTimestamp(value) : value == null ? null : Number(value);
|
|
7885
7922
|
let total = Number(keys[0]?.__total ?? 0);
|
|
7886
7923
|
if (plan.paginationMode === "page" && keys.length === 0 && plan.page > 0) {
|
|
7887
7924
|
const [stats] = await run(compileClickHouseTraceQueryTotal(plan));
|
|
@@ -7891,7 +7928,7 @@ async function queryTraces(client, plan, timeoutMs, strategy = null) {
|
|
|
7891
7928
|
const rows = visible.length > 0 ? await run(compileClickHouseTraceRowsByKey(plan, visible.map((key) => ({
|
|
7892
7929
|
traceId: String(key.traceId),
|
|
7893
7930
|
dedupeKey: String(key.dedupeKey),
|
|
7894
|
-
sortValue:
|
|
7931
|
+
sortValue: sortValue(key.sortValue),
|
|
7895
7932
|
rootStartedAt: asIsoTimestamp(key.rootStartedAt)
|
|
7896
7933
|
})))) : [];
|
|
7897
7934
|
if (plan.paginationMode === "page") return coreStorage.traceQueryResponseSchema.parse({
|
|
@@ -7909,7 +7946,7 @@ async function queryTraces(client, plan, timeoutMs, strategy = null) {
|
|
|
7909
7946
|
traces: await toTraceRecords(rows),
|
|
7910
7947
|
page: { next: keys.length > plan.limit && lastKey ? coreStorage.encodeTraceQueryCursor(plan, {
|
|
7911
7948
|
result: "traces",
|
|
7912
|
-
sortValue:
|
|
7949
|
+
sortValue: sortValue(lastKey.sortValue),
|
|
7913
7950
|
traceId: String(lastKey.traceId)
|
|
7914
7951
|
}) : null }
|
|
7915
7952
|
});
|
|
@@ -7934,6 +7971,16 @@ function compileSpanQueryPredicate(predicate) {
|
|
|
7934
7971
|
params: parameters.params
|
|
7935
7972
|
};
|
|
7936
7973
|
}
|
|
7974
|
+
/** Thread filters see full-history representative values, never individual member traces. */
|
|
7975
|
+
function compileThreadQueryPredicate(predicate, parameters = new ParameterBuilder()) {
|
|
7976
|
+
return {
|
|
7977
|
+
sql: compileScalarPredicate(predicate, Object.fromEntries(Object.entries(coreStorage.THREAD_QUERY_FIELD_REGISTRY).map(([field, rule]) => [field, {
|
|
7978
|
+
sql: `s.${field}`,
|
|
7979
|
+
parameterType: rule.valueKind === "timestamp" ? "DateTime64(3, 'UTC')" : rule.valueKind === "number" ? "Float64" : "String"
|
|
7980
|
+
}])), parameters),
|
|
7981
|
+
params: parameters.params
|
|
7982
|
+
};
|
|
7983
|
+
}
|
|
7937
7984
|
//#endregion
|
|
7938
7985
|
//#region src/storage/domains/observability/v-next/span-query.ts
|
|
7939
7986
|
const identityFields = [
|
|
@@ -7974,14 +8021,15 @@ const displayFields = [
|
|
|
7974
8021
|
"model",
|
|
7975
8022
|
"provider",
|
|
7976
8023
|
"durationMs",
|
|
8024
|
+
"timeToFirstChunkMs",
|
|
7977
8025
|
"status"
|
|
7978
8026
|
];
|
|
7979
8027
|
var Parameters = class {
|
|
7980
8028
|
values = {};
|
|
7981
|
-
add(value,
|
|
8029
|
+
add(value, type = typeof value === "number" ? "UInt64" : "String") {
|
|
7982
8030
|
const name = `span_${Object.keys(this.values).length}`;
|
|
7983
|
-
this.values[name] =
|
|
7984
|
-
return `{${name}:${
|
|
8031
|
+
this.values[name] = type === "DateTime64(3, 'UTC')" ? new Date(value).toISOString().replace("T", " ").replace(/Z$/, "") : value;
|
|
8032
|
+
return `{${name}:${type}}`;
|
|
7985
8033
|
}
|
|
7986
8034
|
};
|
|
7987
8035
|
function identityValues(row) {
|
|
@@ -7994,13 +8042,20 @@ function identityValues(row) {
|
|
|
7994
8042
|
row.spanId
|
|
7995
8043
|
];
|
|
7996
8044
|
}
|
|
7997
|
-
function afterCursor(plan, parameters) {
|
|
8045
|
+
function afterCursor$1(plan, parameters) {
|
|
7998
8046
|
if (!plan.cursor) return "1";
|
|
7999
8047
|
const cursor = plan.cursor;
|
|
8000
|
-
const timestamp = parameters.add(cursor.sortValue, true);
|
|
8001
8048
|
const field = `s.${plan.orderBy.field}`;
|
|
8002
|
-
const terms = [
|
|
8003
|
-
const equal = [
|
|
8049
|
+
const terms = [];
|
|
8050
|
+
const equal = [];
|
|
8051
|
+
if (cursor.sortValue === null) equal.push(`isNull(${field})`);
|
|
8052
|
+
else {
|
|
8053
|
+
const numeric = plan.orderBy.field === "timeToFirstChunkMs";
|
|
8054
|
+
const value = parameters.add(cursor.sortValue, numeric ? "Float64" : "DateTime64(3, 'UTC')");
|
|
8055
|
+
terms.push(`${field} ${plan.orderBy.direction === "asc" ? ">" : "<"} ${value}`);
|
|
8056
|
+
if (numeric) terms.push(`isNull(${field})`);
|
|
8057
|
+
equal.push(`${field} = ${value}`);
|
|
8058
|
+
}
|
|
8004
8059
|
for (const key of identityFields) {
|
|
8005
8060
|
const value = cursor[key];
|
|
8006
8061
|
if (value === null) equal.push(`isNull(s.${key})`);
|
|
@@ -8012,10 +8067,10 @@ function afterCursor(plan, parameters) {
|
|
|
8012
8067
|
}
|
|
8013
8068
|
return `(${terms.join(" OR ")})`;
|
|
8014
8069
|
}
|
|
8015
|
-
function predicateFields(predicate, fields = /* @__PURE__ */ new Set()) {
|
|
8070
|
+
function predicateFields$1(predicate, fields = /* @__PURE__ */ new Set()) {
|
|
8016
8071
|
if (!predicate) return fields;
|
|
8017
|
-
if (predicate.type === "boolean") for (const arg of predicate.args) predicateFields(arg, fields);
|
|
8018
|
-
else if (predicate.type === "not") predicateFields(predicate.arg, fields);
|
|
8072
|
+
if (predicate.type === "boolean") for (const arg of predicate.args) predicateFields$1(arg, fields);
|
|
8073
|
+
else if (predicate.type === "not") predicateFields$1(predicate.arg, fields);
|
|
8019
8074
|
else fields.add(predicate.field);
|
|
8020
8075
|
return fields;
|
|
8021
8076
|
}
|
|
@@ -8028,8 +8083,8 @@ function fieldExpression(field) {
|
|
|
8028
8083
|
}
|
|
8029
8084
|
function compileClickHouseSpanQuery(plan) {
|
|
8030
8085
|
const parameters = new Parameters();
|
|
8031
|
-
const from = parameters.add(plan.timeRange.from,
|
|
8032
|
-
const to = parameters.add(plan.timeRange.to,
|
|
8086
|
+
const from = parameters.add(plan.timeRange.from, "DateTime64(3, 'UTC')");
|
|
8087
|
+
const to = parameters.add(plan.timeRange.to, "DateTime64(3, 'UTC')");
|
|
8033
8088
|
const scope = [];
|
|
8034
8089
|
if (plan.scope) {
|
|
8035
8090
|
scope.push(`organizationId = ${parameters.add(plan.scope.organizationId)}`);
|
|
@@ -8042,10 +8097,11 @@ function compileClickHouseSpanQuery(plan) {
|
|
|
8042
8097
|
const selectionFields = [.../* @__PURE__ */ new Set([
|
|
8043
8098
|
"startedAt",
|
|
8044
8099
|
"endedAt",
|
|
8045
|
-
|
|
8100
|
+
plan.orderBy.field,
|
|
8101
|
+
...predicateFields$1(plan.where)
|
|
8046
8102
|
])].filter((field) => !new Set(identityFields).has(field));
|
|
8047
8103
|
const expressions = selectionFields.map(fieldExpression);
|
|
8048
|
-
const after = afterCursor(plan, parameters);
|
|
8104
|
+
const after = afterCursor$1(plan, parameters);
|
|
8049
8105
|
return {
|
|
8050
8106
|
query: `WITH candidates AS (
|
|
8051
8107
|
SELECT ${identities}, ${selectionFields.map((field, index) => `${expressions[index]} AS ${field}`).join(", ")}
|
|
@@ -8055,13 +8111,14 @@ function compileClickHouseSpanQuery(plan) {
|
|
|
8055
8111
|
ORDER BY ${identities}, e.endedAt DESC
|
|
8056
8112
|
LIMIT 1 BY ${identities}
|
|
8057
8113
|
)
|
|
8058
|
-
SELECT ${[
|
|
8114
|
+
SELECT ${[.../* @__PURE__ */ new Set([
|
|
8059
8115
|
...identityFields,
|
|
8060
8116
|
"startedAt",
|
|
8061
|
-
"endedAt"
|
|
8062
|
-
|
|
8117
|
+
"endedAt",
|
|
8118
|
+
plan.orderBy.field
|
|
8119
|
+
])].map((field) => `s.${field}`).join(", ")}
|
|
8063
8120
|
FROM candidates s WHERE s.startedAt >= ${from} AND s.startedAt < ${to} AND (${predicate.sql}) AND ${after}
|
|
8064
|
-
ORDER BY s.${plan.orderBy.field} ${plan.orderBy.direction === "asc" ? "ASC" : "DESC"}, ${identityFields.map((field) => `s.${field} ASC NULLS LAST`).join(", ")}
|
|
8121
|
+
ORDER BY s.${plan.orderBy.field} ${plan.orderBy.direction === "asc" ? "ASC" : "DESC"} NULLS LAST, ${identityFields.map((field) => `s.${field} ASC NULLS LAST`).join(", ")}
|
|
8065
8122
|
LIMIT ${parameters.add(plan.limit + 1)}`,
|
|
8066
8123
|
query_params: {
|
|
8067
8124
|
...parameters.values,
|
|
@@ -8070,7 +8127,7 @@ function compileClickHouseSpanQuery(plan) {
|
|
|
8070
8127
|
};
|
|
8071
8128
|
}
|
|
8072
8129
|
const LEGACY_PAYLOAD_BATCH_ROWS = 32;
|
|
8073
|
-
function iso(value) {
|
|
8130
|
+
function iso$1(value) {
|
|
8074
8131
|
return new Date(value.includes("T") ? value : `${value.replace(" ", "T")}Z`).toISOString();
|
|
8075
8132
|
}
|
|
8076
8133
|
async function querySpans(client, plan, timeoutMs) {
|
|
@@ -8085,8 +8142,8 @@ async function querySpans(client, plan, timeoutMs) {
|
|
|
8085
8142
|
};
|
|
8086
8143
|
const selected = (await run(compileClickHouseSpanQuery(plan))).map((row) => ({
|
|
8087
8144
|
...row,
|
|
8088
|
-
startedAt: iso(row.startedAt),
|
|
8089
|
-
endedAt: iso(row.endedAt)
|
|
8145
|
+
startedAt: iso$1(row.startedAt),
|
|
8146
|
+
endedAt: iso$1(row.endedAt)
|
|
8090
8147
|
}));
|
|
8091
8148
|
const page = selected.slice(0, plan.limit);
|
|
8092
8149
|
if (!page.length) return {
|
|
@@ -8124,11 +8181,12 @@ async function querySpans(client, plan, timeoutMs) {
|
|
|
8124
8181
|
const hydrated = page.flatMap((identity) => {
|
|
8125
8182
|
const row = byIdentity.get(coreStorage.spanQueryIdentityKey(identity));
|
|
8126
8183
|
if (!row) return [];
|
|
8127
|
-
const { inputPreview: _input, outputPreview: _output, errorPreview: _error, ...metadata } = row;
|
|
8184
|
+
const { inputPreview: _input, outputPreview: _output, errorPreview: _error, timeToFirstChunkMs, ...metadata } = row;
|
|
8128
8185
|
return [{
|
|
8129
8186
|
...metadata,
|
|
8130
8187
|
...identity,
|
|
8131
|
-
durationMs: Number(row.durationMs)
|
|
8188
|
+
durationMs: Number(row.durationMs),
|
|
8189
|
+
...timeToFirstChunkSupported && { timeToFirstChunkMs: timeToFirstChunkMs == null ? null : Number(timeToFirstChunkMs) }
|
|
8132
8190
|
}];
|
|
8133
8191
|
});
|
|
8134
8192
|
let metrics = [];
|
|
@@ -8152,16 +8210,313 @@ async function querySpans(client, plan, timeoutMs) {
|
|
|
8152
8210
|
resourceId: last.resourceId,
|
|
8153
8211
|
traceId: last.traceId,
|
|
8154
8212
|
spanId: last.spanId,
|
|
8155
|
-
sortValue: last[plan.orderBy.field]
|
|
8213
|
+
sortValue: last[plan.orderBy.field] ?? null
|
|
8156
8214
|
}) : null;
|
|
8157
8215
|
return result;
|
|
8158
8216
|
}
|
|
8159
8217
|
//#endregion
|
|
8218
|
+
//#region src/storage/domains/observability/v-next/thread-query.ts
|
|
8219
|
+
const contextFields = [
|
|
8220
|
+
"resourceId",
|
|
8221
|
+
"userId",
|
|
8222
|
+
"entityType",
|
|
8223
|
+
"entityId",
|
|
8224
|
+
"entityName",
|
|
8225
|
+
"entityVersionId",
|
|
8226
|
+
"runId",
|
|
8227
|
+
"sessionId",
|
|
8228
|
+
"requestId",
|
|
8229
|
+
"serviceName",
|
|
8230
|
+
"experimentId",
|
|
8231
|
+
"environment",
|
|
8232
|
+
"executionSource"
|
|
8233
|
+
];
|
|
8234
|
+
function predicateFields(predicate, fields = /* @__PURE__ */ new Set()) {
|
|
8235
|
+
if (!predicate) return fields;
|
|
8236
|
+
if (predicate.type === "boolean") for (const arg of predicate.args) predicateFields(arg, fields);
|
|
8237
|
+
else if (predicate.type === "not") predicateFields(predicate.arg, fields);
|
|
8238
|
+
else fields.add(predicate.field);
|
|
8239
|
+
return fields;
|
|
8240
|
+
}
|
|
8241
|
+
function afterCursor(plan, parameters) {
|
|
8242
|
+
if (!plan.cursor) return "1";
|
|
8243
|
+
const { cursor } = plan;
|
|
8244
|
+
const stamp = parameters.add(cursor.sortValue, "DateTime64(3, 'UTC')");
|
|
8245
|
+
const field = `s.${plan.orderBy.field}`;
|
|
8246
|
+
const terms = [`${field} ${plan.orderBy.direction === "asc" ? ">" : "<"} ${stamp}`];
|
|
8247
|
+
const equal = [`${field} = ${stamp}`];
|
|
8248
|
+
for (const key of ["organizationId", "threadId"]) if (cursor[key] === null) equal.push(`isNull(s.${key})`);
|
|
8249
|
+
else {
|
|
8250
|
+
const value = parameters.add(cursor[key], "String");
|
|
8251
|
+
terms.push(`(${equal.join(" AND ")} AND (s.${key} > ${value} OR isNull(s.${key})))`);
|
|
8252
|
+
equal.push(`s.${key} = ${value}`);
|
|
8253
|
+
}
|
|
8254
|
+
return `(${terms.join(" OR ")})`;
|
|
8255
|
+
}
|
|
8256
|
+
const THREAD_QUERY_EXECUTION_LIMITS = {
|
|
8257
|
+
memoryLimitBytes: 512 * 1024 * 1024,
|
|
8258
|
+
maxThreads: 2,
|
|
8259
|
+
maxBlockSize: 4096,
|
|
8260
|
+
spillThresholdBytes: 32 * 1024 * 1024
|
|
8261
|
+
};
|
|
8262
|
+
const MAX_CANDIDATE_THREADS = 16384;
|
|
8263
|
+
const MAX_CANDIDATE_TRACES = 1e5;
|
|
8264
|
+
const MAX_CANDIDATE_ID_BYTES = 4 * 1024 * 1024;
|
|
8265
|
+
const MAX_FULL_HISTORY_ROOTS = 25e4;
|
|
8266
|
+
function canUseThreadCandidates(probe) {
|
|
8267
|
+
return Number(probe.threadCount) <= MAX_CANDIDATE_THREADS && Number(probe.traceCount) <= MAX_CANDIDATE_TRACES && Number(probe.threadBytes) + Number(probe.traceBytes) <= MAX_CANDIDATE_ID_BYTES;
|
|
8268
|
+
}
|
|
8269
|
+
const identity = (field) => `tuple(isNull(organizationId), ifNull(organizationId, ''), ${field})`;
|
|
8270
|
+
const candidateThreadsFilter = `${identity("threadId")} IN (SELECT ${identity("threadId")} FROM candidate_threads)`;
|
|
8271
|
+
const candidateTracesFilter = `${identity("traceId")} IN (SELECT ${identity("traceId")} FROM candidate_traces)`;
|
|
8272
|
+
function candidateCtes(scope, from, to, plan, probe = false) {
|
|
8273
|
+
const timestamp = plan.timeRange.mode === "threads" ? plan.timeRange.field : "startedAt";
|
|
8274
|
+
const scoped = scope.length ? `${scope.join(" AND ")} AND ` : "";
|
|
8275
|
+
return `candidate_threads AS (
|
|
8276
|
+
SELECT DISTINCT organizationId, threadId FROM ${TABLE_TRACE_ROOTS}
|
|
8277
|
+
WHERE ${scoped}threadId IS NOT NULL AND ${timestamp} >= ${from} AND ${timestamp} < ${to}
|
|
8278
|
+
${probe ? `LIMIT 16385` : ""}
|
|
8279
|
+
), candidate_traces AS (
|
|
8280
|
+
SELECT DISTINCT organizationId, traceId FROM ${TABLE_TRACE_ROOTS}
|
|
8281
|
+
WHERE ${scoped}${candidateThreadsFilter}
|
|
8282
|
+
${probe ? `LIMIT 100001` : ""}
|
|
8283
|
+
)`;
|
|
8284
|
+
}
|
|
8285
|
+
function compileClickHouseThreadRowsQuery(plan, strategy = "full") {
|
|
8286
|
+
const parameters = new ParameterBuilder();
|
|
8287
|
+
const from = parameters.add(plan.timeRange.from, "DateTime64(3, 'UTC')");
|
|
8288
|
+
const to = parameters.add(plan.timeRange.to, "DateTime64(3, 'UTC')");
|
|
8289
|
+
const scope = [];
|
|
8290
|
+
if (plan.scope) {
|
|
8291
|
+
scope.push(`organizationId = ${parameters.add(plan.scope.organizationId, "String")}`);
|
|
8292
|
+
if (plan.scope.resourceId !== void 0) scope.push(`resourceId = ${parameters.add(plan.scope.resourceId, "String")}`);
|
|
8293
|
+
}
|
|
8294
|
+
if (strategy === "historyBudget") return {
|
|
8295
|
+
query: `SELECT count() AS rootCount FROM (
|
|
8296
|
+
SELECT 1 FROM ${TABLE_TRACE_ROOTS} ${scope.length ? `WHERE ${scope.join(" AND ")}` : ""}
|
|
8297
|
+
LIMIT 250001
|
|
8298
|
+
)`,
|
|
8299
|
+
query_params: parameters.params
|
|
8300
|
+
};
|
|
8301
|
+
if (strategy === "probe") return {
|
|
8302
|
+
query: `WITH ${candidateCtes(scope, from, to, plan, true)}
|
|
8303
|
+
SELECT (SELECT count() FROM candidate_threads) AS threadCount,
|
|
8304
|
+
(SELECT sum(length(ifNull(organizationId, '')) + length(threadId)) FROM candidate_threads) AS threadBytes,
|
|
8305
|
+
count() AS traceCount, sum(length(ifNull(organizationId, '')) + length(traceId)) AS traceBytes
|
|
8306
|
+
FROM candidate_traces`,
|
|
8307
|
+
query_params: parameters.params
|
|
8308
|
+
};
|
|
8309
|
+
const predicate = plan.where ? compileThreadQueryPredicate(plan.where, parameters).sql : "1";
|
|
8310
|
+
const after = afterCursor(plan, parameters);
|
|
8311
|
+
const qualify = plan.timeRange.mode === "traces" ? "s.inWindow > 0" : `s.${plan.timeRange.field} >= ${from} AND s.${plan.timeRange.field} < ${to}`;
|
|
8312
|
+
const filtered = predicateFields(plan.where);
|
|
8313
|
+
const firstContext = [...contextFields, "timeToFirstChunkMs"].filter((field) => field === "resourceId" || filtered.has(field));
|
|
8314
|
+
const firstFields = [
|
|
8315
|
+
"startedAt",
|
|
8316
|
+
"traceId",
|
|
8317
|
+
"spanId",
|
|
8318
|
+
"endedAt",
|
|
8319
|
+
...firstContext
|
|
8320
|
+
];
|
|
8321
|
+
const lastFields = [
|
|
8322
|
+
"startedAt",
|
|
8323
|
+
"traceId",
|
|
8324
|
+
"spanId",
|
|
8325
|
+
"endedAt",
|
|
8326
|
+
"resourceId",
|
|
8327
|
+
...filtered.has("status") ? ["status"] : []
|
|
8328
|
+
];
|
|
8329
|
+
const rootDataFields = [
|
|
8330
|
+
"threadId",
|
|
8331
|
+
"spanId",
|
|
8332
|
+
"startedAt",
|
|
8333
|
+
"endedAt",
|
|
8334
|
+
...firstContext,
|
|
8335
|
+
...filtered.has("status") ? ["status"] : []
|
|
8336
|
+
];
|
|
8337
|
+
const rootExpression = (field) => field === "timeToFirstChunkMs" ? "if(spanType = 'agent_run', timeToFirstChunkMs, NULL)" : field === "status" ? "if(isNull(error), 'success', 'error')" : field;
|
|
8338
|
+
const sortedRoots = `roots AS (
|
|
8339
|
+
SELECT organizationId, traceId, ${rootDataFields.map((field) => `${rootExpression(field)} AS ${field}`).join(", ")}
|
|
8340
|
+
FROM ${TABLE_TRACE_ROOTS} ${scope.length || strategy === "candidates" ? `WHERE ${[...scope, ...strategy === "candidates" ? [candidateTracesFilter] : []].join(" AND ")}` : ""}
|
|
8341
|
+
ORDER BY organizationId, traceId, startedAt ASC, spanId, endedAt DESC, isNotNull(threadId) DESC, dedupeKey
|
|
8342
|
+
LIMIT 1 BY organizationId, traceId
|
|
8343
|
+
)`;
|
|
8344
|
+
return {
|
|
8345
|
+
query: `WITH ${strategy === "candidates" ? `${candidateCtes(scope, from, to, plan)}, ` : ""}${sortedRoots}, grouped AS (
|
|
8346
|
+
SELECT organizationId, threadId, min(startedAt) AS firstActivity, max(endedAt) AS lastActivity,
|
|
8347
|
+
count() AS traceCount, countIf(startedAt >= ${from} AND startedAt < ${to}) AS inWindow,
|
|
8348
|
+
min(tuple(${firstFields.join(", ")})) AS first,
|
|
8349
|
+
max(tuple(${lastFields.join(", ")})) AS last
|
|
8350
|
+
FROM roots WHERE threadId IS NOT NULL ${strategy === "candidates" ? `AND ${candidateThreadsFilter}` : ""} GROUP BY organizationId, threadId
|
|
8351
|
+
), rows AS (
|
|
8352
|
+
SELECT organizationId, threadId, firstActivity AS startedAt, lastActivity AS endedAt, traceCount, inWindow,
|
|
8353
|
+
${firstFields.map((field, i) => `first.${i + 1} AS ${i < 4 ? `first${field[0].toUpperCase()}${field.slice(1)}` : field}`).join(", ")},
|
|
8354
|
+
${lastFields.map((field, i) => `last.${i + 1} AS ${field === "status" ? "status" : `last${field[0].toUpperCase()}${field.slice(1)}`}`).join(", ")}
|
|
8355
|
+
FROM grouped
|
|
8356
|
+
) SELECT * FROM rows s WHERE ${qualify} AND (${predicate}) AND ${after}
|
|
8357
|
+
ORDER BY s.${plan.orderBy.field} ${plan.orderBy.direction === "asc" ? "ASC" : "DESC"}, s.organizationId ASC NULLS LAST, s.threadId ASC
|
|
8358
|
+
LIMIT ${parameters.add(plan.limit + 1, "UInt64")}`,
|
|
8359
|
+
query_params: parameters.params
|
|
8360
|
+
};
|
|
8361
|
+
}
|
|
8362
|
+
const rootKey = (row) => JSON.stringify([
|
|
8363
|
+
row.organizationId,
|
|
8364
|
+
row.traceId,
|
|
8365
|
+
row.spanId
|
|
8366
|
+
]);
|
|
8367
|
+
const iso = (value) => new Date(value.includes("T") ? value : `${value.replace(" ", "T")}Z`).toISOString();
|
|
8368
|
+
/** Bound HTTP form parameter size as well as payload rows. Long IDs must not break large pages. */
|
|
8369
|
+
function* batches(rows, maxRows = 256) {
|
|
8370
|
+
let batch = [];
|
|
8371
|
+
let bytes = 0;
|
|
8372
|
+
for (const row of rows) {
|
|
8373
|
+
const size = Buffer.byteLength(JSON.stringify(row), "utf8") * 2;
|
|
8374
|
+
if (batch.length && (batch.length >= maxRows || bytes + size > 48 * 1024)) {
|
|
8375
|
+
yield batch;
|
|
8376
|
+
batch = [];
|
|
8377
|
+
bytes = 0;
|
|
8378
|
+
}
|
|
8379
|
+
batch.push(row);
|
|
8380
|
+
bytes += size;
|
|
8381
|
+
}
|
|
8382
|
+
if (batch.length) yield batch;
|
|
8383
|
+
}
|
|
8384
|
+
async function queryThreadRows(client, plan, timeoutMs, strategy = "auto") {
|
|
8385
|
+
const deadline = performance.now() + coreStorage.resolveTraceQueryTimeoutMs(timeoutMs);
|
|
8386
|
+
const run = async (compiled) => {
|
|
8387
|
+
const remaining = Math.floor(deadline - performance.now());
|
|
8388
|
+
if (remaining <= 0) throw new coreStorage.TraceQueryExecutionError();
|
|
8389
|
+
return await runWithClickHouseTraceQueryTimeout(client, {
|
|
8390
|
+
timeoutMs: remaining,
|
|
8391
|
+
...THREAD_QUERY_EXECUTION_LIMITS
|
|
8392
|
+
}, compiled);
|
|
8393
|
+
};
|
|
8394
|
+
if (strategy === "auto") {
|
|
8395
|
+
const [probe] = await run(compileClickHouseThreadRowsQuery(plan, "probe"));
|
|
8396
|
+
if (Number(probe.threadCount) === 0) return {
|
|
8397
|
+
threads: [],
|
|
8398
|
+
page: { next: null }
|
|
8399
|
+
};
|
|
8400
|
+
strategy = canUseThreadCandidates(probe) ? "candidates" : "full";
|
|
8401
|
+
}
|
|
8402
|
+
if (strategy === "full") {
|
|
8403
|
+
const [budget] = await run(compileClickHouseThreadRowsQuery(plan, "historyBudget"));
|
|
8404
|
+
if (Number(budget.rootCount) > MAX_FULL_HISTORY_ROOTS) throw new coreStorage.TraceQueryResourceLimitError();
|
|
8405
|
+
}
|
|
8406
|
+
const selected = await run(compileClickHouseThreadRowsQuery(plan, strategy));
|
|
8407
|
+
const page = selected.slice(0, plan.limit);
|
|
8408
|
+
if (!page.length) return {
|
|
8409
|
+
threads: [],
|
|
8410
|
+
page: { next: null }
|
|
8411
|
+
};
|
|
8412
|
+
const roots = /* @__PURE__ */ new Map();
|
|
8413
|
+
const representative = (row, which) => ({
|
|
8414
|
+
organizationId: row.organizationId,
|
|
8415
|
+
resourceId: which === "first" ? row.resourceId : row.lastResourceId,
|
|
8416
|
+
traceId: row[`${which}TraceId`],
|
|
8417
|
+
spanId: row[`${which}SpanId`],
|
|
8418
|
+
startedAt: row[`${which}StartedAt`],
|
|
8419
|
+
endedAt: row[`${which}EndedAt`]
|
|
8420
|
+
});
|
|
8421
|
+
for (const row of page) for (const which of ["first", "last"]) {
|
|
8422
|
+
const root = representative(row, which);
|
|
8423
|
+
roots.set(rootKey(root), root);
|
|
8424
|
+
}
|
|
8425
|
+
const keys = (batch) => batch.map((root) => new TupleParam([
|
|
8426
|
+
root.organizationId === null ? 1 : 0,
|
|
8427
|
+
root.organizationId ?? "",
|
|
8428
|
+
root.resourceId === null ? 1 : 0,
|
|
8429
|
+
root.resourceId ?? "",
|
|
8430
|
+
root.traceId,
|
|
8431
|
+
root.spanId,
|
|
8432
|
+
iso(root.startedAt).replace("T", " ").replace(/Z$/, ""),
|
|
8433
|
+
iso(root.endedAt).replace("T", " ").replace(/Z$/, "")
|
|
8434
|
+
]));
|
|
8435
|
+
const rootWhere = `tuple(isNull(organizationId), ifNull(organizationId, ''), isNull(resourceId), ifNull(resourceId, ''),
|
|
8436
|
+
traceId, spanId, startedAt, endedAt) IN {roots:Array(Tuple(UInt8, String, UInt8, String, String, String, DateTime64(3, 'UTC'), DateTime64(3, 'UTC')))}`;
|
|
8437
|
+
const stored = [];
|
|
8438
|
+
for (const batch of batches([...roots.values()])) stored.push(...await run({
|
|
8439
|
+
query: `SELECT organizationId, traceId, spanId, startedAt, endedAt, spanType,
|
|
8440
|
+
${contextFields.join(", ")}, if(spanType = 'agent_run', timeToFirstChunkMs, NULL) AS timeToFirstChunkMs,
|
|
8441
|
+
if(isNull(error), 'success', 'error') AS status, ${PREVIEW_COLUMNS.join(", ")}
|
|
8442
|
+
FROM ${TABLE_TRACE_ROOTS} WHERE ${rootWhere}
|
|
8443
|
+
ORDER BY isNotNull(threadId) DESC LIMIT 1 BY organizationId, traceId, spanId`,
|
|
8444
|
+
query_params: { roots: keys(batch) }
|
|
8445
|
+
}));
|
|
8446
|
+
const byRoot = new Map(stored.map((row) => [rootKey(row), row]));
|
|
8447
|
+
const missing = stored.filter(needsPreviewPayloads);
|
|
8448
|
+
for (const batch of batches(missing, 32)) {
|
|
8449
|
+
const payloads = await run({
|
|
8450
|
+
query: `SELECT organizationId, traceId, spanId, input, output, error FROM ${TABLE_TRACE_ROOTS}
|
|
8451
|
+
WHERE ${rootWhere} ORDER BY isNotNull(threadId) DESC LIMIT 1 BY organizationId, traceId, spanId`,
|
|
8452
|
+
query_params: { roots: keys(batch) }
|
|
8453
|
+
});
|
|
8454
|
+
for (const payload of payloads) {
|
|
8455
|
+
const row = byRoot.get(rootKey(payload));
|
|
8456
|
+
if (row) Object.assign(row, resolveStoredPreviews(row, payload));
|
|
8457
|
+
}
|
|
8458
|
+
}
|
|
8459
|
+
const firstRoots = page.map((row) => representative(row, "first")).filter((root) => byRoot.get(rootKey(root))?.spanType === "agent_run");
|
|
8460
|
+
const models = [];
|
|
8461
|
+
for (const batch of batches(firstRoots)) models.push(...await run({
|
|
8462
|
+
query: `SELECT organizationId, traceId,
|
|
8463
|
+
if(JSONType(attributes, 'model') = 'String', JSONExtractString(attributes, 'model'), NULL) AS model,
|
|
8464
|
+
if(JSONType(attributes, 'provider') = 'String', JSONExtractString(attributes, 'provider'), NULL) AS provider
|
|
8465
|
+
FROM ${TABLE_SPAN_EVENTS} WHERE spanType = 'model_generation' AND
|
|
8466
|
+
tuple(isNull(organizationId), ifNull(organizationId, ''), isNull(resourceId), ifNull(resourceId, ''), traceId, parentSpanId)
|
|
8467
|
+
IN {agents:Array(Tuple(UInt8, String, UInt8, String, String, String))}
|
|
8468
|
+
ORDER BY organizationId, traceId, startedAt, spanId LIMIT 1 BY organizationId, traceId`,
|
|
8469
|
+
query_params: { agents: batch.map((root) => new TupleParam([
|
|
8470
|
+
root.organizationId === null ? 1 : 0,
|
|
8471
|
+
root.organizationId ?? "",
|
|
8472
|
+
root.resourceId === null ? 1 : 0,
|
|
8473
|
+
root.resourceId ?? "",
|
|
8474
|
+
root.traceId,
|
|
8475
|
+
root.spanId
|
|
8476
|
+
])) }
|
|
8477
|
+
}));
|
|
8478
|
+
const byTrace = new Map(models.map((row) => [JSON.stringify([row.organizationId, row.traceId]), row]));
|
|
8479
|
+
const threads = page.flatMap((row) => {
|
|
8480
|
+
const first = byRoot.get(rootKey(representative(row, "first")));
|
|
8481
|
+
const last = byRoot.get(rootKey(representative(row, "last")));
|
|
8482
|
+
if (!first || !last) return [];
|
|
8483
|
+
const model = byTrace.get(JSON.stringify([row.organizationId, row.firstTraceId]));
|
|
8484
|
+
return [coreStorage.threadQueryRowSchema.parse({
|
|
8485
|
+
organizationId: row.organizationId,
|
|
8486
|
+
threadId: row.threadId,
|
|
8487
|
+
firstTraceId: row.firstTraceId,
|
|
8488
|
+
lastTraceId: row.lastTraceId,
|
|
8489
|
+
...Object.fromEntries(contextFields.map((field) => [field, first[field]])),
|
|
8490
|
+
status: last.status,
|
|
8491
|
+
startedAt: iso(row.startedAt),
|
|
8492
|
+
endedAt: iso(row.endedAt),
|
|
8493
|
+
traceCount: Number(row.traceCount),
|
|
8494
|
+
timeToFirstChunkMs: first.timeToFirstChunkMs,
|
|
8495
|
+
model: model?.model ?? null,
|
|
8496
|
+
provider: model?.provider ?? null,
|
|
8497
|
+
inputPreview: resolveStoredPreviews(first).inputPreview,
|
|
8498
|
+
outputPreview: resolveStoredPreviews(last).outputPreview,
|
|
8499
|
+
errorPreview: resolveStoredPreviews(last).errorPreview
|
|
8500
|
+
})];
|
|
8501
|
+
});
|
|
8502
|
+
const last = page.at(-1);
|
|
8503
|
+
return {
|
|
8504
|
+
threads,
|
|
8505
|
+
page: { next: selected.length > plan.limit ? coreStorage.encodeThreadQueryCursor(plan, {
|
|
8506
|
+
organizationId: last.organizationId,
|
|
8507
|
+
threadId: last.threadId,
|
|
8508
|
+
sortValue: iso(last[plan.orderBy.field])
|
|
8509
|
+
}) : null }
|
|
8510
|
+
};
|
|
8511
|
+
}
|
|
8512
|
+
//#endregion
|
|
8160
8513
|
//#region src/storage/domains/observability/v-next/trace-aggregate.ts
|
|
8161
8514
|
/** Trace-root column expressions (alias `r`) for each groupable dimension; same values `where` sees. */
|
|
8162
8515
|
const DIMENSION_SQL = {
|
|
8163
8516
|
entityType: "r.entityType",
|
|
8164
8517
|
entityName: "r.entityName",
|
|
8518
|
+
name: "r.name",
|
|
8519
|
+
spanType: "r.spanType",
|
|
8165
8520
|
environment: "r.environment",
|
|
8166
8521
|
status: TRACE_STATUS_SQL,
|
|
8167
8522
|
serviceName: "r.serviceName",
|
|
@@ -9305,6 +9660,7 @@ function buildBranchCursor(row) {
|
|
|
9305
9660
|
//#endregion
|
|
9306
9661
|
//#region src/storage/domains/observability/v-next/index.ts
|
|
9307
9662
|
const spanQueryFeatures = typeof coreStorage.planSpanQuery === "function" ? ["span-query", "span-query-context-fields"] : [];
|
|
9663
|
+
const threadQueryFeatures = typeof coreStorage.planThreadRowsQuery === "function" ? ["thread-query-rows"] : [];
|
|
9308
9664
|
function buildSignalMigrationRequiredMessage(args) {
|
|
9309
9665
|
const tableList = args.tables.map((table) => ` - ${table.table} (${table.engine})`).join("\n");
|
|
9310
9666
|
return `
|
|
@@ -9742,8 +10098,11 @@ var ObservabilityStorageClickhouseVNext = class extends ObservabilityStorage {
|
|
|
9742
10098
|
"trace-query-tenant-scope",
|
|
9743
10099
|
"feedback",
|
|
9744
10100
|
"trace-query-context-ids",
|
|
10101
|
+
...timeToFirstChunkFeatures,
|
|
10102
|
+
"trace-query-root-span-fields",
|
|
9745
10103
|
"trace-aggregate",
|
|
9746
|
-
...spanQueryFeatures
|
|
10104
|
+
...spanQueryFeatures,
|
|
10105
|
+
...threadQueryFeatures
|
|
9747
10106
|
];
|
|
9748
10107
|
return [
|
|
9749
10108
|
"metrics",
|
|
@@ -9762,8 +10121,11 @@ var ObservabilityStorageClickhouseVNext = class extends ObservabilityStorage {
|
|
|
9762
10121
|
"trace-query-tenant-scope",
|
|
9763
10122
|
"feedback",
|
|
9764
10123
|
"trace-query-context-ids",
|
|
10124
|
+
...timeToFirstChunkFeatures,
|
|
10125
|
+
"trace-query-root-span-fields",
|
|
9765
10126
|
"trace-aggregate",
|
|
9766
|
-
...spanQueryFeatures
|
|
10127
|
+
...spanQueryFeatures,
|
|
10128
|
+
...threadQueryFeatures
|
|
9767
10129
|
];
|
|
9768
10130
|
}
|
|
9769
10131
|
async createSpan(args) {
|
|
@@ -9940,6 +10302,7 @@ var ObservabilityStorageClickhouseVNext = class extends ObservabilityStorage {
|
|
|
9940
10302
|
}
|
|
9941
10303
|
async queryThreads(plan) {
|
|
9942
10304
|
try {
|
|
10305
|
+
if (plan.result === "threadRows") return await queryThreadRows(this.#client, plan, this.#traceQueryTimeoutMs);
|
|
9943
10306
|
return await queryThreads(this.#client, plan, this.#traceQueryTimeoutMs);
|
|
9944
10307
|
} catch (error) {
|
|
9945
10308
|
if (error instanceof MastraError || error instanceof coreStorage.TraceQueryExecutionError || error instanceof coreStorage.TraceQueryResourceLimitError) throw error;
|