@jcit/signoz 0.1.3 → 0.2.1

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.
Files changed (2) hide show
  1. package/dist/cli.mjs +117 -8
  2. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -2,11 +2,19 @@
2
2
  import { createRequire } from 'node:module';
3
3
  import { formatOutput, addExamples, credential, installErrorHandler, defineCliApp } from '@jcit/core';
4
4
  import { c as createSignozClient } from './shared/signoz.DH9MeMIj.mjs';
5
+ import { Option } from 'commander';
5
6
  import { consola } from 'consola';
6
7
  import { readFileSync } from 'node:fs';
7
8
 
9
+ function addCommonOptions(cmd) {
10
+ return cmd.addOption(
11
+ new Option("--format <format>", "Output format: json | table | text").default("json")
12
+ ).addOption(new Option("--url <url>", "SigNoz API base URL").hideHelp()).addOption(new Option("--token <token>", "SigNoz API token").hideHelp());
13
+ }
14
+
8
15
  function registerAlerts(program) {
9
- const cmd = program.command("alerts").description("List all alert rules").option("--format <format>", "Output format: json | table | text", "text").option("--url <url>", "SigNoz API base URL").option("--token <token>", "SigNoz API token").action(async (opts) => {
16
+ const cmd = program.command("alerts").description("List all alert rules");
17
+ addCommonOptions(cmd).action(async (opts) => {
10
18
  const client = createSignozClient({ url: opts.url, token: opts.token });
11
19
  const alerts = await client("/api/v1/rules");
12
20
  formatOutput(alerts, opts.format);
@@ -35,6 +43,60 @@ function registerAuth(program) {
35
43
  });
36
44
  }
37
45
 
46
+ const LIST_SQL = `SELECT now() as ts,
47
+ metric_name, temporality, type, unit,
48
+ count(*) as value
49
+ FROM signoz_metrics.distributed_metadata
50
+ GROUP BY metric_name, temporality, type, unit
51
+ ORDER BY metric_name`;
52
+ function extractMetrics(result) {
53
+ const series = result.data?.data?.results?.[0]?.aggregations?.[0]?.series;
54
+ if (!series) return [];
55
+ return series.map((s) => {
56
+ const labels = {};
57
+ for (const l of s.labels ?? []) {
58
+ labels[l.key.name] = l.value;
59
+ }
60
+ const temporality = labels.temporality ?? "";
61
+ const promqlOk = temporality === "Cumulative" || temporality === "Unspecified";
62
+ return {
63
+ name: labels.metric_name ?? "",
64
+ temporality,
65
+ type: labels.type ?? "",
66
+ unit: labels.unit ?? "",
67
+ promql: promqlOk ? "yes" : "no"
68
+ };
69
+ });
70
+ }
71
+ function registerMetrics(program) {
72
+ const cmd = program.command("metrics").description("List available metrics with temporality and type");
73
+ addCommonOptions(cmd).action(async (opts) => {
74
+ const client = createSignozClient({ url: opts.url, token: opts.token });
75
+ const now = Date.now();
76
+ const result = await client("/api/v5/query_range", {
77
+ method: "POST",
78
+ body: {
79
+ schemaVersion: "v1",
80
+ start: now - 36e5,
81
+ end: now,
82
+ requestType: "time_series",
83
+ compositeQuery: {
84
+ queries: [
85
+ { type: "clickhouse_sql", spec: { name: "A", query: LIST_SQL, disabled: false } }
86
+ ]
87
+ }
88
+ }
89
+ });
90
+ const metrics = extractMetrics(result);
91
+ if (opts.format === "json") {
92
+ formatOutput(metrics, "json");
93
+ } else {
94
+ formatOutput(metrics, opts.format);
95
+ }
96
+ });
97
+ addExamples(cmd, ["signoz metrics", "signoz metrics --format table"]);
98
+ }
99
+
38
100
  const DURATION_RE = /^(\d+)(s|m|h|d)$/;
39
101
  const UNITS = {
40
102
  s: 1e3,
@@ -59,19 +121,30 @@ function parseUntil(input) {
59
121
  if (input === "now") return Date.now();
60
122
  return parseSince(input);
61
123
  }
124
+ function injectTimeVars(sql, startMs, endMs) {
125
+ const startS = Math.floor(startMs / 1e3);
126
+ const endS = Math.floor(endMs / 1e3);
127
+ const startNs = BigInt(startMs) * 1000000n;
128
+ const endNs = BigInt(endMs) * 1000000n;
129
+ return sql.replaceAll("{{start_ms}}", String(startMs)).replaceAll("{{end_ms}}", String(endMs)).replaceAll("{{start_s}}", String(startS)).replaceAll("{{end_s}}", String(endS)).replaceAll("{{start_ns}}", String(startNs)).replaceAll("{{end_ns}}", String(endNs));
130
+ }
62
131
 
63
132
  function buildPromqlRequest(query, start, end, step) {
64
133
  return {
134
+ schemaVersion: "v1",
65
135
  start,
66
136
  end,
67
137
  requestType: "time_series",
68
138
  compositeQuery: {
69
- queries: [{ type: "promql", spec: { name: "A", query, step, disabled: false } }]
139
+ queries: [
140
+ { type: "promql", spec: { name: "A", query, step, disabled: false, stats: false } }
141
+ ]
70
142
  }
71
143
  };
72
144
  }
73
145
  function buildSqlRequest(query, start, end) {
74
146
  return {
147
+ schemaVersion: "v1",
75
148
  start,
76
149
  end,
77
150
  requestType: "time_series",
@@ -80,8 +153,31 @@ function buildSqlRequest(query, start, end) {
80
153
  }
81
154
  };
82
155
  }
156
+ function isEmptyResult(res) {
157
+ const results = res.data?.data?.results;
158
+ if (!results) return true;
159
+ return results.every((r) => !r.aggregations?.some((a) => a.series && a.series.length > 0));
160
+ }
161
+ function flattenSeries(res) {
162
+ const rows = [];
163
+ for (const result of res.data?.data?.results ?? []) {
164
+ for (const agg of result.aggregations ?? []) {
165
+ for (const s of agg.series ?? []) {
166
+ const labels = {};
167
+ for (const l of s.labels ?? []) {
168
+ labels[l.key.name] = l.value;
169
+ }
170
+ for (const v of s.values ?? []) {
171
+ rows.push({ ts: new Date(v.timestamp).toISOString(), ...labels, value: v.value });
172
+ }
173
+ }
174
+ }
175
+ }
176
+ return rows;
177
+ }
83
178
  function registerQuery(program) {
84
- const cmd = program.command("query").description("Query traces, logs, and metrics via the unified query API").option("--promql <expr>", "PromQL expression").option("--sql <query>", "ClickHouse SQL query (must include timestamp WHERE clause)").option("-f, --file <path>", "Load query from JSON file (v5 query_range format)").option("--since <time>", "Start time: duration ago (1h, 30m, 7d) or ISO date", "1h").option("--until <time>", "End time: 'now', duration ago, or ISO date", "now").option("--step <seconds>", "Step interval in seconds (PromQL only)", "60").option("--format <format>", "Output format: json | table | text", "json").option("--url <url>", "SigNoz API base URL").option("--token <token>", "SigNoz API token").action(async (opts) => {
179
+ const cmd = program.command("query").description("Query traces, logs, and metrics via the unified query API").option("--promql <expr>", "PromQL expression").option("--sql <query>", "ClickHouse SQL query (use {{start_ms}} etc. for time injection)").option("-f, --file <path>", "Load query from JSON file (v5 query_range format)").option("--since <time>", "Start time: duration ago (1h, 30m, 7d) or ISO date", "1h").option("--until <time>", "End time: 'now', duration ago, or ISO date", "now").option("--step <seconds>", "Step interval in seconds (PromQL only)", "60");
180
+ addCommonOptions(cmd).action(async (opts) => {
85
181
  const modes = [opts.promql, opts.sql, opts.file].filter(Boolean);
86
182
  if (modes.length === 0) {
87
183
  cmd.error("specify one of --promql, --sql, or --file");
@@ -100,9 +196,10 @@ function registerQuery(program) {
100
196
  }
101
197
  body = buildPromqlRequest(opts.promql, start, end, step);
102
198
  } else if (opts.sql) {
103
- body = buildSqlRequest(opts.sql, start, end);
199
+ body = buildSqlRequest(injectTimeVars(opts.sql, start, end), start, end);
104
200
  } else {
105
201
  body = JSON.parse(readFileSync(opts.file, "utf-8"));
202
+ body.schemaVersion ??= "v1";
106
203
  body.start = start;
107
204
  body.end = end;
108
205
  }
@@ -110,18 +207,29 @@ function registerQuery(program) {
110
207
  method: "POST",
111
208
  body
112
209
  });
113
- formatOutput(result, opts.format);
210
+ if (opts.format === "table") {
211
+ const rows = flattenSeries(result);
212
+ formatOutput(rows.length > 0 ? rows : result, "table");
213
+ } else {
214
+ formatOutput(result, opts.format);
215
+ }
216
+ if (opts.promql && isEmptyResult(result)) {
217
+ consola.warn(
218
+ 'PromQL returned no data. Common causes:\n \u2022 Delta-temporality metrics (e.g. signoz_calls_total) are not queryable via PromQL \u2014 use --sql or the builder API instead\n \u2022 OTel dot-separated names require {__name__="metric.name"} syntax'
219
+ );
220
+ }
114
221
  });
115
222
  addExamples(cmd, [
223
+ `signoz query --promql '{__name__="http.client.request.duration.bucket"}' --since 1h`,
116
224
  "signoz query --promql 'rate(http_requests_total[5m])' --since 1h",
117
225
  "signoz query --sql 'SELECT count(*) FROM signoz_logs.distributed_logs_v2 WHERE timestamp >= ...'",
118
- "signoz query -f my-query.json --since 7d --until 1d",
119
- "signoz query --promql 'up' --format table --step 30"
226
+ "signoz query -f my-query.json --since 7d --until 1d"
120
227
  ]);
121
228
  }
122
229
 
123
230
  function registerServices(program) {
124
- const cmd = program.command("services").description("List all SigNoz services").option("--format <format>", "Output format: json | table | text", "text").option("--url <url>", "SigNoz API base URL").option("--token <token>", "SigNoz API token").action(async (opts) => {
231
+ const cmd = program.command("services").description("List all SigNoz services");
232
+ addCommonOptions(cmd).action(async (opts) => {
125
233
  const client = createSignozClient({ url: opts.url, token: opts.token });
126
234
  const services = await client("/api/v1/services/list");
127
235
  formatOutput(services, opts.format);
@@ -140,6 +248,7 @@ const program = defineCliApp({
140
248
  });
141
249
  registerAuth(program);
142
250
  registerQuery(program);
251
+ registerMetrics(program);
143
252
  registerAlerts(program);
144
253
  registerServices(program);
145
254
  program.parseAsync();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jcit/signoz",
3
- "version": "0.1.3",
3
+ "version": "0.2.1",
4
4
  "description": "CLI for SigNoz observability platform",
5
5
  "repository": {
6
6
  "type": "git",