@kinetica/admin-agent 0.2.2 → 0.2.3
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/README.md
CHANGED
|
@@ -332,14 +332,14 @@ The `--bundle` flag points the agent at an **extracted** support-bundle director
|
|
|
332
332
|
|
|
333
333
|
Available against an extracted `gpudb_sysinfo` support bundle (see [Offline Bundle Mode](#offline-bundle-mode)). All read-only; the search/timeline tools stream and bound their output so a large rank log (tens of MB, hundreds of thousands of lines) never blows up the context.
|
|
334
334
|
|
|
335
|
-
| Tool | Description
|
|
336
|
-
| ------------------------------ |
|
|
337
|
-
| `kinetica_load_bundle` | Attach an extracted bundle directory; without a path it opens a directory picker (a model-supplied path needs operator confirmation)
|
|
338
|
-
| `kinetica_bundle_list_files` | Inventory: detected version, ranks + services present, file counts/sizes by kind, plus a layout-match verdict + per-file confidence for off-shape bundles — call this first
|
|
339
|
-
| `kinetica_bundle_log_timeline` | Per-time-bucket severity counts across ranks (the incident shape) — call before searching
|
|
340
|
-
| `kinetica_bundle_search_logs` | Bounded log search by regex, min-severity, time window, and rank / host-manager / component (reads both rolling and Loki-export logs)
|
|
341
|
-
| `kinetica_bundle_read_config` | Read the bundle's real on-disk `gpudb.conf`, with optional section/key filter
|
|
342
|
-
| `kinetica_bundle_read_sysinfo` | OS/process/version diagnostic files (memory, CPU, disk, GPU, network, process args)
|
|
335
|
+
| Tool | Description |
|
|
336
|
+
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
337
|
+
| `kinetica_load_bundle` | Attach an extracted bundle directory; without a path it opens a directory picker (a model-supplied path needs operator confirmation) |
|
|
338
|
+
| `kinetica_bundle_list_files` | Inventory: detected version, ranks + services present, file counts/sizes by kind, plus a layout-match verdict + per-file confidence for off-shape bundles — call this first |
|
|
339
|
+
| `kinetica_bundle_log_timeline` | Per-time-bucket severity counts across ranks (the incident shape) — call before searching |
|
|
340
|
+
| `kinetica_bundle_search_logs` | Bounded log search by regex, min-severity, time window, and rank / host-manager / component (reads both rolling and Loki-export logs); `include_multiline` stitches a multi-line record — e.g. a full `Executing SQL:` query whose embedded newlines span many lines — back onto each match |
|
|
341
|
+
| `kinetica_bundle_read_config` | Read the bundle's real on-disk `gpudb.conf`, with optional section/key filter |
|
|
342
|
+
| `kinetica_bundle_read_sysinfo` | OS/process/version diagnostic files (memory, CPU, disk, GPU, network, process args) |
|
|
343
343
|
|
|
344
344
|
### Reporting
|
|
345
345
|
|
package/dist/admin-agent.js
CHANGED
|
@@ -4034,6 +4034,8 @@ function parseLogLine(line) {
|
|
|
4034
4034
|
|
|
4035
4035
|
// src/bundle/log-search.ts
|
|
4036
4036
|
var DEFAULT_MAX_MATCHES = 200;
|
|
4037
|
+
var MULTILINE_MAX_LINES = 300;
|
|
4038
|
+
var MULTILINE_MAX_CHARS = 2e4;
|
|
4037
4039
|
var REGEX_SCAN_MAX = 8192;
|
|
4038
4040
|
var GRANULARITY_LEN = {
|
|
4039
4041
|
day: 10,
|
|
@@ -4075,6 +4077,23 @@ function matchesFilters(parsed, query3, regex, minRank) {
|
|
|
4075
4077
|
return false;
|
|
4076
4078
|
return true;
|
|
4077
4079
|
}
|
|
4080
|
+
function buildMatch(lineNumber, parsed) {
|
|
4081
|
+
return {
|
|
4082
|
+
lineNumber,
|
|
4083
|
+
...parsed.timestamp !== void 0 ? { timestamp: parsed.timestamp } : {},
|
|
4084
|
+
...parsed.severity !== void 0 ? { severity: parsed.severity } : {},
|
|
4085
|
+
...parsed.rank !== void 0 ? { rank: parsed.rank } : {},
|
|
4086
|
+
message: parsed.message,
|
|
4087
|
+
raw: parsed.raw
|
|
4088
|
+
};
|
|
4089
|
+
}
|
|
4090
|
+
function finalizeMultiline(pending) {
|
|
4091
|
+
if (pending.extra.length === 0) return pending.base;
|
|
4092
|
+
const joined = pending.extra.join("\n");
|
|
4093
|
+
const suffix = pending.truncated ? "\n\u2026 [continuation truncated]" : "";
|
|
4094
|
+
return { ...pending.base, message: `${pending.base.message}
|
|
4095
|
+
${joined}${suffix}` };
|
|
4096
|
+
}
|
|
4078
4097
|
async function searchLogFile(filePath, query3) {
|
|
4079
4098
|
const maxMatches = query3.maxMatches ?? DEFAULT_MAX_MATCHES;
|
|
4080
4099
|
const minRank = query3.minSeverity !== void 0 ? severityRank(query3.minSeverity) : -Infinity;
|
|
@@ -4096,9 +4115,17 @@ async function searchLogFile(filePath, query3) {
|
|
|
4096
4115
|
...query3.fromTs !== void 0 ? { fromTs: floorTimestamp(query3.fromTs) } : {},
|
|
4097
4116
|
...query3.toTs !== void 0 ? { toTs: ceilTimestamp(query3.toTs) } : {}
|
|
4098
4117
|
};
|
|
4118
|
+
const coalesce = query3.coalesceMultiline === true;
|
|
4099
4119
|
const matches = [];
|
|
4100
4120
|
let totalMatched = 0;
|
|
4101
4121
|
let linesScanned = 0;
|
|
4122
|
+
let pending;
|
|
4123
|
+
const flushPending = () => {
|
|
4124
|
+
if (pending) {
|
|
4125
|
+
matches.push(finalizeMultiline(pending));
|
|
4126
|
+
pending = void 0;
|
|
4127
|
+
}
|
|
4128
|
+
};
|
|
4102
4129
|
try {
|
|
4103
4130
|
const rl = (0, import_node_readline.createInterface)({
|
|
4104
4131
|
input: (0, import_node_fs4.createReadStream)(filePath, { encoding: "utf-8" }),
|
|
@@ -4107,20 +4134,29 @@ async function searchLogFile(filePath, query3) {
|
|
|
4107
4134
|
for await (const line of rl) {
|
|
4108
4135
|
linesScanned++;
|
|
4109
4136
|
const parsed = parseLogLine(line);
|
|
4137
|
+
if (pending) {
|
|
4138
|
+
if (parsed.timestamp === void 0) {
|
|
4139
|
+
if (!pending.truncated && pending.extra.length < MULTILINE_MAX_LINES && pending.chars + line.length + 1 <= MULTILINE_MAX_CHARS) {
|
|
4140
|
+
pending.extra.push(line);
|
|
4141
|
+
pending.chars += line.length + 1;
|
|
4142
|
+
} else {
|
|
4143
|
+
pending.truncated = true;
|
|
4144
|
+
}
|
|
4145
|
+
continue;
|
|
4146
|
+
}
|
|
4147
|
+
flushPending();
|
|
4148
|
+
}
|
|
4110
4149
|
if (!matchesFilters(parsed, boundedQuery, regex, minRank)) continue;
|
|
4111
4150
|
totalMatched++;
|
|
4112
4151
|
if (matches.length < maxMatches) {
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
...parsed.severity !== void 0 ? { severity: parsed.severity } : {},
|
|
4117
|
-
...parsed.rank !== void 0 ? { rank: parsed.rank } : {},
|
|
4118
|
-
message: parsed.message,
|
|
4119
|
-
raw: parsed.raw
|
|
4120
|
-
});
|
|
4152
|
+
const base = buildMatch(linesScanned, parsed);
|
|
4153
|
+
if (coalesce) pending = { base, extra: [], chars: 0, truncated: false };
|
|
4154
|
+
else matches.push(base);
|
|
4121
4155
|
}
|
|
4122
4156
|
}
|
|
4157
|
+
flushPending();
|
|
4123
4158
|
} catch (err) {
|
|
4159
|
+
flushPending();
|
|
4124
4160
|
const message = err instanceof Error ? err.message : String(err);
|
|
4125
4161
|
return {
|
|
4126
4162
|
matches,
|
|
@@ -4508,7 +4544,8 @@ function toLineQuery(q) {
|
|
|
4508
4544
|
...q.minSeverity !== void 0 ? { minSeverity: q.minSeverity } : {},
|
|
4509
4545
|
...q.fromTs !== void 0 ? { fromTs: q.fromTs } : {},
|
|
4510
4546
|
...q.toTs !== void 0 ? { toTs: q.toTs } : {},
|
|
4511
|
-
...q.maxMatches !== void 0 ? { maxMatches: q.maxMatches } : {}
|
|
4547
|
+
...q.maxMatches !== void 0 ? { maxMatches: q.maxMatches } : {},
|
|
4548
|
+
...q.coalesceMultiline !== void 0 ? { coalesceMultiline: q.coalesceMultiline } : {}
|
|
4512
4549
|
};
|
|
4513
4550
|
}
|
|
4514
4551
|
function toTimelineLineQuery(q) {
|
|
@@ -4847,6 +4884,9 @@ var BundleSearchLogsSchema = import_zod20.z.object({
|
|
|
4847
4884
|
host_manager: import_zod20.z.boolean().describe("Search the host-manager (hm) log \u2014 a singleton service, not a rank.").optional(),
|
|
4848
4885
|
component: import_zod20.z.string().optional(),
|
|
4849
4886
|
include_components: import_zod20.z.boolean().optional(),
|
|
4887
|
+
include_multiline: import_zod20.z.boolean().describe(
|
|
4888
|
+
"Reconstruct multi-line log records: append continuation lines (those with no timestamp) to each match. Use this to capture a full SQL statement on an 'Executing SQL:' line \u2014 the query often spans many lines because the SQL has embedded newlines, and a plain match shows only its first line. Works on the rolling core logs (logs-local/); Loki per-rank tails (logs/rankN.log) keep only the statement's first line, so there are no continuation lines to stitch there."
|
|
4889
|
+
).optional(),
|
|
4850
4890
|
max_matches: import_zod20.z.number().int().min(1).max(1e3).optional()
|
|
4851
4891
|
});
|
|
4852
4892
|
async function bundleSearchLogs(source, args = {}) {
|
|
@@ -4859,6 +4899,7 @@ async function bundleSearchLogs(source, args = {}) {
|
|
|
4859
4899
|
...args.host_manager !== void 0 ? { hostManager: args.host_manager } : {},
|
|
4860
4900
|
...args.component !== void 0 ? { component: args.component } : {},
|
|
4861
4901
|
...args.include_components !== void 0 ? { includeComponents: args.include_components } : {},
|
|
4902
|
+
...args.include_multiline !== void 0 ? { coalesceMultiline: args.include_multiline } : {},
|
|
4862
4903
|
...args.max_matches !== void 0 ? { maxMatches: args.max_matches } : {}
|
|
4863
4904
|
};
|
|
4864
4905
|
const result = await source.searchLogs(query3);
|
|
@@ -20,6 +20,7 @@ Severity order for filtering is `WARN < UERR < ERROR < FATAL`, so `min_severity=
|
|
|
20
20
|
|
|
21
21
|
- The logs are large (a rank log can exceed 100k lines). NEVER ask for a whole file. Use `kinetica_bundle_log_timeline` to localize, then `kinetica_bundle_search_logs` with a tight time window + severity to extract only relevant lines. The match cap is shared across files — if you see "capped", narrow the query rather than asking for more.
|
|
22
22
|
- You can pass a timeline bucket label straight into `from_ts`/`to_ts` (e.g. `2026-06-11 15` searches that whole hour) — partial timestamps are widened to cover the full period.
|
|
23
|
+
- A single log record can span multiple physical lines when a logged value (notably a SQL statement) contains embedded newlines — the continuation lines have no timestamp. A plain search returns only the first line. Pass `include_multiline: true` to stitch the continuation lines back onto each match and recover the whole record. See "Finding a crash's triggering SQL".
|
|
23
24
|
- Timestamps are plain local strings without a timezone; compare them lexically and treat cross-rank timing cautiously.
|
|
24
25
|
- **Ranks vs. the host manager:** `rank` selects a numeric rank (`r0`, `r1`, …) only. The host manager (`core-gpudb-rolling-hm.log`) is a singleton service, NOT a rank — search or timeline it with `host_manager: true`, never `rank: "hm"`. By default both `log_timeline` and `search_logs` already cover the host manager along with the numeric ranks; `kinetica_bundle_list_files` lists it under `services_present`.
|
|
25
26
|
|
|
@@ -29,11 +30,13 @@ When a worker rank segfaults mid-query, that rank's log holds the **backtrace**
|
|
|
29
30
|
|
|
30
31
|
Workflow, given a `JobId` from a worker's crash stack:
|
|
31
32
|
|
|
32
|
-
1. `kinetica_bundle_search_logs` with `rank: "r0"
|
|
33
|
-
2.
|
|
34
|
-
3. **
|
|
33
|
+
1. `kinetica_bundle_search_logs` with `rank: "r0"`, `regex` = the JobId, **and `include_multiline: true`**. r0 logs the `/execute/sql` receipt (submitting user), the `Sql/SqlDriver.cpp … Executing SQL:` line, and per-operation endpoint lines.
|
|
34
|
+
2. **Read the full statement straight from the `Executing SQL:` line — do not reconstruct it.** Kinetica logs the SQL verbatim, so a real query spans MANY physical lines: `FROM …`, `JOIN …`, `WHERE …` each land on their own line with no timestamp prefix. Those continuation lines belong to the same log record; `include_multiline: true` stitches them back onto the match so you see the WHOLE query. WITHOUT it, a match is only the first physical line (e.g. `… Executing SQL: SELECT c."circuitId", c."circuitRef"`) and you would wrongly report the query as "truncated." Quote the statement verbatim from this single (now multi-line) match.
|
|
35
|
+
3. **Cache-hit fallback only:** if `Found plan for the SQL in cache` precedes the job, Kinetica logs `Executing SQL:` as just the statement keyword (e.g. a bare `SELECT` or `EXECUTE PROCEDURE …`) with no continuation lines to stitch. ONLY then fall back to the per-operation endpoint lines (`Endpoint_aggregate_group_by.cpp`, filter/join endpoints): they carry `table:`, `column_names:`/`aliases:` (the SELECT list), and `expr:` (the full WHERE predicate), whose values survive a cache hit. A `datetime()`/timestamp filter showing up here often _is_ the input that triggered a parser segfault.
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
**Where the full multi-line query actually lives:** `include_multiline` recovers the whole statement only from the **rolling core logs** (`logs-local/core-gpudb-rolling-r0.log`), where the SQL's embedded newlines are preserved as continuation lines. The **Loki per-rank tail** (`logs/rank0.log`) keeps only the statement's first physical line — promtail captures each line as its own record, so the `FROM`/`JOIN`/`WHERE` lines are simply not in that export, and nothing can stitch them. So this workflow depends on r0 being present under `logs-local/`. If r0 exists only as a Loki tail (rare for the coordinator, but possible for a Loki-only bundle), the complete query may not be in the bundle at all — say so rather than reporting the first line as the whole query, and fall back to step 3's endpoint lines.
|
|
38
|
+
|
|
39
|
+
See `rank-architecture.md` (Where queries are logged) for why this locality holds, and "Two log families" below for rolling-vs-Loki precedence.
|
|
37
40
|
|
|
38
41
|
### Files of interest
|
|
39
42
|
|