mirror of
https://github.com/openclaw/clawhub.git
synced 2026-08-14 00:47:57 +00:00
chore: update skills (#3406)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
parent
d9157142e9
commit
29ee5126de
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"repository": "https://github.com/axiomhq/skills",
|
"repository": "https://github.com/axiomhq/skills",
|
||||||
"resolvedCommit": "0e98ebaeec76a70c8fda9a7737605800c2f1245d",
|
"resolvedCommit": "7f29f9a97ffd71bf2ad375e035ba6f3ba30dcc8b",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"skills": {
|
"skills": {
|
||||||
"axiom-alerting": {
|
"axiom-alerting": {
|
||||||
|
|||||||
@@ -195,13 +195,28 @@ unit_fields_other() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Newline before each pipeline `|` so stored queries read one stage per line.
|
||||||
|
# The split only tracks plain '...'/"..." literals, so it bails out and stores
|
||||||
|
# the query untouched when it holds a construct whose string boundaries it
|
||||||
|
# cannot follow: a backslash escape, an @-verbatim literal (where `\` is not an
|
||||||
|
# escape), or a // comment. Formatting is cosmetic, silently rewriting a query
|
||||||
|
# is not, so anything ambiguous stays on one line.
|
||||||
|
format_pipeline() {
|
||||||
|
if [[ "$1" == *\\* || "$1" == *"@'"* || "$1" == *'@"'* || "$1" == *"//"* ]]; then
|
||||||
|
printf '%s' "$1"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
jq -rn --arg apl "$1" \
|
||||||
|
'$apl | gsub("(?<s>\"[^\"]*\"|'\''[^'\'']*'\'')|(?<p> \\| )"; if .s then .s else "\n| " end)'
|
||||||
|
}
|
||||||
|
|
||||||
# Build the query object. Both APL and MPL land in `query.apl` (shared API
|
# Build the query object. Both APL and MPL land in `query.apl` (shared API
|
||||||
# field); MPL also gets `query.metricsDataset`.
|
# field); MPL also gets `query.metricsDataset`.
|
||||||
build_query() {
|
build_query() {
|
||||||
if [[ -n "$MPL" ]]; then
|
if [[ -n "$MPL" ]]; then
|
||||||
jq -n --arg apl "$MPL" --arg ds "$DATASET" '{apl: $apl, metricsDataset: $ds}'
|
jq -n --arg apl "$(format_pipeline "$MPL")" --arg ds "$DATASET" '{apl: $apl, metricsDataset: $ds}'
|
||||||
else
|
else
|
||||||
jq -n --arg apl "$APL" '{apl: $apl}'
|
jq -n --arg apl "$(format_pipeline "$APL")" '{apl: $apl}'
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
#
|
#
|
||||||
# Reads credentials from ~/.axiom.toml (shared with axiom-sre)
|
# Reads credentials from ~/.axiom.toml (shared with axiom-sre)
|
||||||
# Set AXIOM_URL_OVERRIDE to route requests to a specific edge deployment endpoint.
|
# Set AXIOM_URL_OVERRIDE to route requests to a specific edge deployment endpoint.
|
||||||
|
# Set AXIOM_CONNECT_TIMEOUT / AXIOM_MAX_TIME (seconds) to override the default
|
||||||
|
# connection (10s) and total request (120s) timeouts.
|
||||||
#
|
#
|
||||||
# Examples:
|
# Examples:
|
||||||
# axiom-api prod GET /v1/datasets
|
# axiom-api prod GET /v1/datasets
|
||||||
@@ -54,6 +56,8 @@ fi
|
|||||||
|
|
||||||
CURL_ARGS=(
|
CURL_ARGS=(
|
||||||
-s
|
-s
|
||||||
|
--connect-timeout "${AXIOM_CONNECT_TIMEOUT:-10}"
|
||||||
|
--max-time "${AXIOM_MAX_TIME:-120}"
|
||||||
-w '\n%{http_code}'
|
-w '\n%{http_code}'
|
||||||
-X "$METHOD"
|
-X "$METHOD"
|
||||||
-H "Authorization: Bearer $TOKEN"
|
-H "Authorization: Bearer $TOKEN"
|
||||||
|
|||||||
@@ -47,7 +47,12 @@
|
|||||||
# specific entity name (service, host, device) to find which metrics carry it.
|
# specific entity name (service, host, device) to find which metrics carry it.
|
||||||
# To list metric names, use the `metrics` subcommand instead.
|
# To list metric names, use the `metrics` subcommand instead.
|
||||||
#
|
#
|
||||||
# --start and --end default to the last 24 hours if omitted.
|
# --start and --end accept RFC3339 (offsets allowed, e.g. 2025-06-01T00:00:00+02:00)
|
||||||
|
# or relative now / now-<N><unit> with <unit> in s/m/h/d/w, resolved to RFC3339 UTC
|
||||||
|
# client-side because the info endpoints only parse RFC3339. This is narrower than
|
||||||
|
# metrics-query, which forwards times to the server unparsed and also accepts forms
|
||||||
|
# like now-1y; here anything outside now / now-<N>[smhdw] must already be RFC3339.
|
||||||
|
# Defaults: last 24 hours.
|
||||||
# For sparse metrics (sensors, batch jobs), try --start with a wider range (e.g. 7 days).
|
# For sparse metrics (sensors, batch jobs), try --start with a wider range (e.g. 7 days).
|
||||||
#
|
#
|
||||||
# Examples:
|
# Examples:
|
||||||
@@ -67,6 +72,53 @@ set -euo pipefail
|
|||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
# Percent-encode one URL component (path segment or query value). Dataset,
|
||||||
|
# metric, and tag names are user/OTel-controlled and may contain characters
|
||||||
|
# that are reserved in URLs (/ % + space); times may carry a `+02:00` offset
|
||||||
|
# whose `+` would otherwise decode as a space server-side.
|
||||||
|
urlencode() {
|
||||||
|
jq -rn --arg v "$1" '$v|@uri'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Normalize a time argument to RFC3339 UTC. RFC3339 input passes through
|
||||||
|
# verbatim; the relative forms `now` and `now-<N><unit>` (unit in s/m/h/d/w)
|
||||||
|
# are resolved client-side because the info endpoints only parse RFC3339.
|
||||||
|
# Note: metrics-query forwards times to the server unparsed, so it accepts a
|
||||||
|
# broader set (e.g. now-1y); those forms are NOT handled here and, if passed,
|
||||||
|
# fall through to the RFC3339-only endpoint and fail.
|
||||||
|
normalize_time() {
|
||||||
|
local t="$1"
|
||||||
|
if [[ "$t" == "now" ]]; then
|
||||||
|
date -u '+%Y-%m-%dT%H:%M:%SZ'
|
||||||
|
elif [[ "$t" =~ ^now-([0-9]+)([smhdw])$ ]]; then
|
||||||
|
local n="${BASH_REMATCH[1]}" u="${BASH_REMATCH[2]}"
|
||||||
|
if date --version &>/dev/null; then
|
||||||
|
local word
|
||||||
|
case "$u" in
|
||||||
|
s) word="seconds" ;;
|
||||||
|
m) word="minutes" ;;
|
||||||
|
h) word="hours" ;;
|
||||||
|
d) word="days" ;;
|
||||||
|
w) word="weeks" ;;
|
||||||
|
esac
|
||||||
|
date -u -d "$n $word ago" '+%Y-%m-%dT%H:%M:%SZ'
|
||||||
|
else
|
||||||
|
# BSD date: -v units are case-sensitive (M = minute, m = month).
|
||||||
|
local unit
|
||||||
|
case "$u" in
|
||||||
|
s) unit="S" ;;
|
||||||
|
m) unit="M" ;;
|
||||||
|
h) unit="H" ;;
|
||||||
|
d) unit="d" ;;
|
||||||
|
w) unit="w" ;;
|
||||||
|
esac
|
||||||
|
date -u -v "-${n}${unit}" '+%Y-%m-%dT%H:%M:%SZ'
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
printf '%s\n' "$t"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
show_usage() {
|
show_usage() {
|
||||||
echo "Usage:" >&2
|
echo "Usage:" >&2
|
||||||
echo " metrics-info <deploy> <dataset> metrics [--by-type] [--type T]..." >&2
|
echo " metrics-info <deploy> <dataset> metrics [--by-type] [--type T]..." >&2
|
||||||
@@ -80,8 +132,8 @@ show_usage() {
|
|||||||
echo " metrics-info <deploy> <dataset> find-metrics <search-value> (searches tag values, not metric names)" >&2
|
echo " metrics-info <deploy> <dataset> find-metrics <search-value> (searches tag values, not metric names)" >&2
|
||||||
echo "" >&2
|
echo "" >&2
|
||||||
echo "Options:" >&2
|
echo "Options:" >&2
|
||||||
echo " --start T Start time (RFC3339). Default: 24h ago" >&2
|
echo " --start T Start time (RFC3339 or relative, e.g. now-7d). Default: 24h ago" >&2
|
||||||
echo " --end T End time (RFC3339). Default: now" >&2
|
echo " --end T End time (RFC3339 or relative, e.g. now). Default: now" >&2
|
||||||
echo " --by-type (metrics listing) Group entries by metric type" >&2
|
echo " --by-type (metrics listing) Group entries by metric type" >&2
|
||||||
echo " --type T (metrics listing) Filter to type T. Repeatable." >&2
|
echo " --type T (metrics listing) Filter to type T. Repeatable." >&2
|
||||||
echo " --no-values (describe) Return tag names only" >&2
|
echo " --no-values (describe) Return tag names only" >&2
|
||||||
@@ -118,20 +170,12 @@ while [[ $# -gt 0 ]]; do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
# Default time range: last 24 hours
|
# Default time range: last 24 hours. Relative forms are resolved to RFC3339 UTC.
|
||||||
if [[ -z "$START" ]]; then
|
START=$(normalize_time "${START:-now-24h}")
|
||||||
if date --version &>/dev/null 2>&1; then
|
END=$(normalize_time "${END:-now}")
|
||||||
START=$(date -u -d '24 hours ago' '+%Y-%m-%dT%H:%M:%SZ')
|
|
||||||
else
|
|
||||||
START=$(date -u -v-24H '+%Y-%m-%dT%H:%M:%SZ')
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
if [[ -z "$END" ]]; then
|
|
||||||
END=$(date -u '+%Y-%m-%dT%H:%M:%SZ')
|
|
||||||
fi
|
|
||||||
|
|
||||||
TIME_PARAMS="start=${START}&end=${END}"
|
TIME_PARAMS="start=$(urlencode "$START")&end=$(urlencode "$END")"
|
||||||
BASE="/v1/query/metrics/info/datasets/${DATASET}"
|
BASE="/v1/query/metrics/info/datasets/$(urlencode "$DATASET")"
|
||||||
|
|
||||||
# Resolve the regional edge URL for this dataset
|
# Resolve the regional edge URL for this dataset
|
||||||
RESOLVED_URL=$("$SCRIPT_DIR/resolve-url" "$DEPLOYMENT" "$DATASET" 2>/dev/null || true)
|
RESOLVED_URL=$("$SCRIPT_DIR/resolve-url" "$DEPLOYMENT" "$DATASET" 2>/dev/null || true)
|
||||||
@@ -185,34 +229,62 @@ case "${POSITIONAL[0]}" in
|
|||||||
# the typical 1+1+N round trips an agent would make to characterise
|
# the typical 1+1+N round trips an agent would make to characterise
|
||||||
# an unfamiliar metric.
|
# an unfamiliar metric.
|
||||||
METRIC="${POSITIONAL[1]}"
|
METRIC="${POSITIONAL[1]}"
|
||||||
|
METRIC_ENC=$(urlencode "$METRIC")
|
||||||
RAW=$(fetch_metrics_listing)
|
RAW=$(fetch_metrics_listing)
|
||||||
META=$(printf '%s' "$RAW" | jq -e --arg m "$METRIC" '.[$m] // error("metric not found in listing for the given time range: " + $m)')
|
META=$(printf '%s' "$RAW" | jq -e --arg m "$METRIC" '.[$m] // error("metric not found in listing for the given time range: " + $m)')
|
||||||
TAGS_JSON=$("$SCRIPT_DIR/axiom-api" "$DEPLOYMENT" GET "${BASE}/metrics/${METRIC}/tags?${TIME_PARAMS}")
|
TAGS_JSON=$("$SCRIPT_DIR/axiom-api" "$DEPLOYMENT" GET "${BASE}/metrics/${METRIC_ENC}/tags?${TIME_PARAMS}")
|
||||||
if [[ "$NO_VALUES" -eq 1 ]]; then
|
if [[ "$NO_VALUES" -eq 1 ]]; then
|
||||||
# tags as flat array of names
|
# tags as flat array of names
|
||||||
jq -n --argjson m "$META" --argjson tags "$TAGS_JSON" '$m + {tags: $tags}'
|
jq -n --argjson m "$META" --argjson tags "$TAGS_JSON" '$m + {tags: $tags}'
|
||||||
else
|
else
|
||||||
# tags as object: { tag_name: [values…] }
|
# tags as object: { tag_name: [values…] }. Per-tag value fetches
|
||||||
VALUES_OBJ='{}'
|
# are independent, so run them concurrently; tag counts are small
|
||||||
|
# (rarely more than a few dozen), so no concurrency cap is needed.
|
||||||
|
TAG_NAMES=()
|
||||||
while IFS= read -r tag; do
|
while IFS= read -r tag; do
|
||||||
[[ -z "$tag" ]] && continue
|
[[ -z "$tag" ]] && continue
|
||||||
VALUES=$("$SCRIPT_DIR/axiom-api" "$DEPLOYMENT" GET "${BASE}/metrics/${METRIC}/tags/${tag}/values?${TIME_PARAMS}")
|
TAG_NAMES+=("$tag")
|
||||||
if [[ "$VALUES_LIMIT" -gt 0 ]]; then
|
|
||||||
VALUES=$(printf '%s' "$VALUES" | jq --argjson n "$VALUES_LIMIT" '.[:$n]')
|
|
||||||
fi
|
|
||||||
VALUES_OBJ=$(jq -n --argjson o "$VALUES_OBJ" --arg t "$tag" --argjson v "$VALUES" '$o + {($t): $v}')
|
|
||||||
done < <(printf '%s' "$TAGS_JSON" | jq -r '.[]?')
|
done < <(printf '%s' "$TAGS_JSON" | jq -r '.[]?')
|
||||||
|
VALUES_OBJ='{}'
|
||||||
|
if [[ ${#TAG_NAMES[@]} -gt 0 ]]; then
|
||||||
|
TMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/metrics-info.XXXXXX")
|
||||||
|
trap 'rm -rf "$TMP_DIR"' EXIT
|
||||||
|
PIDS=()
|
||||||
|
for i in "${!TAG_NAMES[@]}"; do
|
||||||
|
"$SCRIPT_DIR/axiom-api" "$DEPLOYMENT" GET \
|
||||||
|
"${BASE}/metrics/${METRIC_ENC}/tags/$(urlencode "${TAG_NAMES[$i]}")/values?${TIME_PARAMS}" \
|
||||||
|
> "$TMP_DIR/$i.json" &
|
||||||
|
PIDS+=($!)
|
||||||
|
done
|
||||||
|
FETCH_FAILED=0
|
||||||
|
for i in "${!PIDS[@]}"; do
|
||||||
|
if ! wait "${PIDS[$i]}"; then
|
||||||
|
echo "Error: failed to fetch values for tag '${TAG_NAMES[$i]}'" >&2
|
||||||
|
FETCH_FAILED=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [[ "$FETCH_FAILED" -eq 1 ]]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
for i in "${!TAG_NAMES[@]}"; do
|
||||||
|
VALUES=$(cat "$TMP_DIR/$i.json")
|
||||||
|
if [[ "$VALUES_LIMIT" -gt 0 ]]; then
|
||||||
|
VALUES=$(printf '%s' "$VALUES" | jq --argjson n "$VALUES_LIMIT" '.[:$n]')
|
||||||
|
fi
|
||||||
|
VALUES_OBJ=$(jq -n --argjson o "$VALUES_OBJ" --arg t "${TAG_NAMES[$i]}" --argjson v "$VALUES" '$o + {($t): $v}')
|
||||||
|
done
|
||||||
|
fi
|
||||||
jq -n --argjson m "$META" --argjson tags "$VALUES_OBJ" '$m + {tags: $tags}'
|
jq -n --argjson m "$META" --argjson tags "$VALUES_OBJ" '$m + {tags: $tags}'
|
||||||
fi
|
fi
|
||||||
elif [[ ${#POSITIONAL[@]} -eq 3 && "${POSITIONAL[2]}" == "tags" ]]; then
|
elif [[ ${#POSITIONAL[@]} -eq 3 && "${POSITIONAL[2]}" == "tags" ]]; then
|
||||||
# List tags for a metric
|
# List tags for a metric
|
||||||
METRIC="${POSITIONAL[1]}"
|
METRIC="${POSITIONAL[1]}"
|
||||||
"$SCRIPT_DIR/axiom-api" "$DEPLOYMENT" GET "${BASE}/metrics/${METRIC}/tags?${TIME_PARAMS}"
|
"$SCRIPT_DIR/axiom-api" "$DEPLOYMENT" GET "${BASE}/metrics/$(urlencode "$METRIC")/tags?${TIME_PARAMS}"
|
||||||
elif [[ ${#POSITIONAL[@]} -eq 5 && "${POSITIONAL[2]}" == "tags" && "${POSITIONAL[4]}" == "values" ]]; then
|
elif [[ ${#POSITIONAL[@]} -eq 5 && "${POSITIONAL[2]}" == "tags" && "${POSITIONAL[4]}" == "values" ]]; then
|
||||||
# List tag values for a metric+tag
|
# List tag values for a metric+tag
|
||||||
METRIC="${POSITIONAL[1]}"
|
METRIC="${POSITIONAL[1]}"
|
||||||
TAG="${POSITIONAL[3]}"
|
TAG="${POSITIONAL[3]}"
|
||||||
"$SCRIPT_DIR/axiom-api" "$DEPLOYMENT" GET "${BASE}/metrics/${METRIC}/tags/${TAG}/values?${TIME_PARAMS}"
|
"$SCRIPT_DIR/axiom-api" "$DEPLOYMENT" GET "${BASE}/metrics/$(urlencode "$METRIC")/tags/$(urlencode "$TAG")/values?${TIME_PARAMS}"
|
||||||
elif [[ ${#POSITIONAL[@]} -eq 5 && "${POSITIONAL[2]}" == "tags" && "${POSITIONAL[4]}" == "type" ]]; then
|
elif [[ ${#POSITIONAL[@]} -eq 5 && "${POSITIONAL[2]}" == "tags" && "${POSITIONAL[4]}" == "type" ]]; then
|
||||||
# Probe the typing of a metric+tag by running `metrics-query` with
|
# Probe the typing of a metric+tag by running `metrics-query` with
|
||||||
# `filter <tag> is <T>` for each candidate type. The type(s) that
|
# `filter <tag> is <T>` for each candidate type. The type(s) that
|
||||||
@@ -226,8 +298,15 @@ case "${POSITIONAL[0]}" in
|
|||||||
# `<dataset>`:`<metric>` | filter `<tag>` is <T> | align to 5m using sum
|
# `<dataset>`:`<metric>` | filter `<tag>` is <T> | align to 5m using sum
|
||||||
# If <tag> is <T> matches no rows, the response has empty `series`.
|
# If <tag> is <T> matches no rows, the response has empty `series`.
|
||||||
PROBE_QUERY='`'"$DATASET"'`:`'"$METRIC"'` | filter `'"$TAG"'` is '"$t"' | align to 5m using sum'
|
PROBE_QUERY='`'"$DATASET"'`:`'"$METRIC"'` | filter `'"$TAG"'` is '"$t"' | align to 5m using sum'
|
||||||
RESPONSE=$("$SCRIPT_DIR/metrics-query" "$DEPLOYMENT" "$PROBE_QUERY" "$START" "$END" 2>/dev/null || echo '{}')
|
# Propagate probe failures instead of swallowing them: a failed
|
||||||
COUNT=$(printf '%s' "$RESPONSE" | jq -r '(.series // []) | length' 2>/dev/null || echo 0)
|
# query (bad dataset, auth, network) must not be reported as the
|
||||||
|
# tag being "absent" — that would be a confident wrong answer.
|
||||||
|
if ! RESPONSE=$("$SCRIPT_DIR/metrics-query" "$DEPLOYMENT" "$PROBE_QUERY" "$START" "$END" 2>&1); then
|
||||||
|
echo "Error: type probe query failed (tag '$TAG' is $t):" >&2
|
||||||
|
printf '%s\n' "$RESPONSE" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
COUNT=$(printf '%s' "$RESPONSE" | jq -r '(.series // []) | length')
|
||||||
if [[ "$COUNT" -gt 0 ]]; then
|
if [[ "$COUNT" -gt 0 ]]; then
|
||||||
PRESENT_JSON=$(printf '%s' "$PRESENT_JSON" | jq --arg t "$t" '. + [$t]')
|
PRESENT_JSON=$(printf '%s' "$PRESENT_JSON" | jq --arg t "$t" '. + [$t]')
|
||||||
fi
|
fi
|
||||||
@@ -253,7 +332,7 @@ case "${POSITIONAL[0]}" in
|
|||||||
elif [[ ${#POSITIONAL[@]} -eq 3 && "${POSITIONAL[2]}" == "values" ]]; then
|
elif [[ ${#POSITIONAL[@]} -eq 3 && "${POSITIONAL[2]}" == "values" ]]; then
|
||||||
# List values for a tag
|
# List values for a tag
|
||||||
TAG="${POSITIONAL[1]}"
|
TAG="${POSITIONAL[1]}"
|
||||||
"$SCRIPT_DIR/axiom-api" "$DEPLOYMENT" GET "${BASE}/tags/${TAG}/values?${TIME_PARAMS}"
|
"$SCRIPT_DIR/axiom-api" "$DEPLOYMENT" GET "${BASE}/tags/$(urlencode "$TAG")/values?${TIME_PARAMS}"
|
||||||
else
|
else
|
||||||
show_usage
|
show_usage
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -1,10 +1,23 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# metrics-query: Execute a metrics query against Axiom MetricsDB
|
# metrics-query: Execute a metrics query against Axiom MetricsDB
|
||||||
#
|
#
|
||||||
# Usage: metrics-query [-p name=value]... <deployment> <mpl> <startTime> <endTime>
|
# Usage: metrics-query [-p name=value]... [-w pixels] [--pixel-per-point n] \
|
||||||
|
# <deployment> <mpl> <startTime> <endTime>
|
||||||
#
|
#
|
||||||
# Times: RFC3339 (e.g. 2025-01-01T00:00:00Z) or relative (e.g. now-1h, now-1d).
|
# Times: RFC3339 (e.g. 2025-01-01T00:00:00Z) or relative (e.g. now-1h, now-1d).
|
||||||
#
|
#
|
||||||
|
# Adaptive resolution ($__interval):
|
||||||
|
# Reference $__interval anywhere a Duration is expected (e.g.
|
||||||
|
# `align to $__interval using avg`, `bucket to $__interval ...`) and the
|
||||||
|
# server resolves it to a "nice" step computed from the query time range and
|
||||||
|
# the target chart width. No `param $__interval` declaration is needed -- the
|
||||||
|
# metrics service registers it automatically. Tune the density with:
|
||||||
|
# -w / --chart-width <pixels> target chart width; the server aims for
|
||||||
|
# ~chart-width/pixel-per-point buckets
|
||||||
|
# (default ~500 buckets when -w is omitted).
|
||||||
|
# --pixel-per-point <n> pixels per data point (server default 10).
|
||||||
|
# Both are forwarded under the request body's queryOptions object.
|
||||||
|
#
|
||||||
# Parameter values (-p / --param name=value, repeatable):
|
# Parameter values (-p / --param name=value, repeatable):
|
||||||
# For each MPL parameter declared in the query (e.g. `param $svc: string;`),
|
# For each MPL parameter declared in the query (e.g. `param $svc: string;`),
|
||||||
# pass the variable name without the leading `$` and an MPL literal as the
|
# pass the variable name without the leading `$` and an MPL literal as the
|
||||||
@@ -32,6 +45,8 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|||||||
|
|
||||||
PARAMS=()
|
PARAMS=()
|
||||||
POSITIONAL=()
|
POSITIONAL=()
|
||||||
|
CHART_WIDTH=""
|
||||||
|
PIXEL_PER_POINT=""
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
-p|--param)
|
-p|--param)
|
||||||
@@ -46,6 +61,30 @@ while [[ $# -gt 0 ]]; do
|
|||||||
PARAMS+=("${1#--param=}")
|
PARAMS+=("${1#--param=}")
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
|
-w|--chart-width)
|
||||||
|
if [[ $# -lt 2 ]]; then
|
||||||
|
echo "Error: $1 requires a pixel-width argument" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
CHART_WIDTH="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--chart-width=*)
|
||||||
|
CHART_WIDTH="${1#--chart-width=}"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--pixel-per-point)
|
||||||
|
if [[ $# -lt 2 ]]; then
|
||||||
|
echo "Error: $1 requires an integer argument" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
PIXEL_PER_POINT="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--pixel-per-point=*)
|
||||||
|
PIXEL_PER_POINT="${1#--pixel-per-point=}"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
--)
|
--)
|
||||||
shift
|
shift
|
||||||
while [[ $# -gt 0 ]]; do POSITIONAL+=("$1"); shift; done
|
while [[ $# -gt 0 ]]; do POSITIONAL+=("$1"); shift; done
|
||||||
@@ -63,13 +102,17 @@ START_TIME="${POSITIONAL[2]:-}"
|
|||||||
END_TIME="${POSITIONAL[3]:-}"
|
END_TIME="${POSITIONAL[3]:-}"
|
||||||
|
|
||||||
if [[ -z "$DEPLOYMENT" || -z "$MPL" || -z "$START_TIME" || -z "$END_TIME" ]]; then
|
if [[ -z "$DEPLOYMENT" || -z "$MPL" || -z "$START_TIME" || -z "$END_TIME" ]]; then
|
||||||
echo "Usage: metrics-query [-p name=value]... <deployment> <mpl> <startTime> <endTime>" >&2
|
echo "Usage: metrics-query [-p name=value]... [-w pixels] [--pixel-per-point n] <deployment> <mpl> <startTime> <endTime>" >&2
|
||||||
echo "" >&2
|
echo "" >&2
|
||||||
echo "Times: RFC3339 (e.g. 2025-01-01T00:00:00Z) or relative (e.g. now-1h, now-1d)." >&2
|
echo "Times: RFC3339 (e.g. 2025-01-01T00:00:00Z) or relative (e.g. now-1h, now-1d)." >&2
|
||||||
echo "" >&2
|
echo "" >&2
|
||||||
echo "-p / --param name=value (repeatable): supply an MPL parameter value." >&2
|
echo "-p / --param name=value (repeatable): supply an MPL parameter value." >&2
|
||||||
echo " name - variable name without the leading \$ (e.g. 'svc' for \$svc)." >&2
|
echo " name - variable name without the leading \$ (e.g. 'svc' for \$svc)." >&2
|
||||||
echo " value - MPL literal, forwarded verbatim under params.param__<name>." >&2
|
echo " value - MPL literal, forwarded verbatim under params.param__<name>." >&2
|
||||||
|
echo "" >&2
|
||||||
|
echo "-w / --chart-width <pixels> target chart width; lets the server resolve" >&2
|
||||||
|
echo " \$__interval to a nice step (queryOptions)." >&2
|
||||||
|
echo "--pixel-per-point <n> pixels per data point (server default 10)." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -97,6 +140,17 @@ if [[ ${#PARAMS[@]} -gt 0 ]]; then
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Validate the optional chart-sizing options. They must be positive integers;
|
||||||
|
# they are forwarded under queryOptions so the server can resolve $__interval.
|
||||||
|
if [[ -n "$CHART_WIDTH" && ! "$CHART_WIDTH" =~ ^[1-9][0-9]*$ ]]; then
|
||||||
|
echo "Error: --chart-width must be a positive integer (got: $CHART_WIDTH)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ -n "$PIXEL_PER_POINT" && ! "$PIXEL_PER_POINT" =~ ^[1-9][0-9]*$ ]]; then
|
||||||
|
echo "Error: --pixel-per-point must be a positive integer (got: $PIXEL_PER_POINT)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# Extract dataset name from MPL: `dataset`:`metric` ... or dataset:`metric` ...
|
# Extract dataset name from MPL: `dataset`:`metric` ... or dataset:`metric` ...
|
||||||
# Strip leading `param <name>: <type>;` declarations first so their `:` doesn't
|
# Strip leading `param <name>: <type>;` declarations first so their `:` doesn't
|
||||||
# get mistaken for the dataset:metric separator.
|
# get mistaken for the dataset:metric separator.
|
||||||
@@ -141,6 +195,23 @@ if [[ ${#PARAM_NAMES[@]} -gt 0 ]]; then
|
|||||||
JQ_EXPR="$JQ_EXPR + {params: ($PARAMS_EXPR)}"
|
JQ_EXPR="$JQ_EXPR + {params: ($PARAMS_EXPR)}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Forward chart-sizing hints under queryOptions. The edge translates these into
|
||||||
|
# the x-axiom-chart-width / x-axiom-pixel-per-point headers, which the metrics
|
||||||
|
# service uses to resolve $__interval. Values are JSON numbers (--argjson).
|
||||||
|
if [[ -n "$CHART_WIDTH" || -n "$PIXEL_PER_POINT" ]]; then
|
||||||
|
QO_EXPR=""
|
||||||
|
if [[ -n "$CHART_WIDTH" ]]; then
|
||||||
|
JQ_ARGS+=(--argjson chartWidth "$CHART_WIDTH")
|
||||||
|
QO_EXPR="{\"chart-width\": \$chartWidth}"
|
||||||
|
fi
|
||||||
|
if [[ -n "$PIXEL_PER_POINT" ]]; then
|
||||||
|
JQ_ARGS+=(--argjson pixelPerPoint "$PIXEL_PER_POINT")
|
||||||
|
if [[ -n "$QO_EXPR" ]]; then QO_EXPR+=" + "; fi
|
||||||
|
QO_EXPR+="{\"pixel-per-point\": \$pixelPerPoint}"
|
||||||
|
fi
|
||||||
|
JQ_EXPR="$JQ_EXPR + {queryOptions: ($QO_EXPR)}"
|
||||||
|
fi
|
||||||
|
|
||||||
BODY=$(jq -n "${JQ_ARGS[@]}" "$JQ_EXPR")
|
BODY=$(jq -n "${JQ_ARGS[@]}" "$JQ_EXPR")
|
||||||
|
|
||||||
AXIOM_ACCEPT="application/json+metrics.v2" "$SCRIPT_DIR/axiom-api" "$DEPLOYMENT" POST "/v1/query/_mpl" "$BODY"
|
AXIOM_ACCEPT="application/json+metrics.v2" "$SCRIPT_DIR/axiom-api" "$DEPLOYMENT" POST "/v1/query/_mpl" "$BODY"
|
||||||
|
|||||||
@@ -1,31 +1,18 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# metrics-spec: Fetch the metrics query specification from Axiom
|
# metrics-spec: Fetch the MPL metrics query specification from Axiom
|
||||||
#
|
#
|
||||||
# Usage: metrics-spec <deployment> <dataset>
|
# Usage: metrics-spec
|
||||||
#
|
#
|
||||||
# Calls OPTIONS /v1/query/_mpl to retrieve the complete metrics query
|
# Retrieves the complete MPL query spec with syntax, operators, and examples.
|
||||||
# spec with syntax, operators, and examples. Read this before composing queries.
|
# Read this before composing queries.
|
||||||
#
|
|
||||||
# The dataset is needed to resolve the correct edge deployment URL.
|
|
||||||
#
|
|
||||||
# Example:
|
|
||||||
# metrics-spec prod my-metrics-dataset
|
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SPEC_URL="https://us-east-1.aws.edge.axiom.co/v1/query/_mpl"
|
||||||
|
|
||||||
DEPLOYMENT="${1:-}"
|
# Match the timeout convention used by axiom-api so a stalled edge can't hang
|
||||||
DATASET="${2:-}"
|
# the caller indefinitely. Override via AXIOM_CONNECT_TIMEOUT / AXIOM_MAX_TIME.
|
||||||
|
curl -sS -X OPTIONS -H "Accept: text/markdown" \
|
||||||
if [[ -z "$DEPLOYMENT" || -z "$DATASET" ]]; then
|
--connect-timeout "${AXIOM_CONNECT_TIMEOUT:-10}" \
|
||||||
echo "Usage: metrics-spec <deployment> <dataset>" >&2
|
--max-time "${AXIOM_MAX_TIME:-120}" \
|
||||||
exit 1
|
"$SPEC_URL"
|
||||||
fi
|
|
||||||
|
|
||||||
RESOLVED_URL=$("$SCRIPT_DIR/resolve-url" "$DEPLOYMENT" "$DATASET" 2>/dev/null || true)
|
|
||||||
if [[ -n "$RESOLVED_URL" ]]; then
|
|
||||||
export AXIOM_URL_OVERRIDE="$RESOLVED_URL"
|
|
||||||
fi
|
|
||||||
|
|
||||||
AXIOM_ACCEPT="text/markdown" "$SCRIPT_DIR/axiom-api" "$DEPLOYMENT" OPTIONS "/v1/query/_mpl"
|
|
||||||
|
|||||||
@@ -125,6 +125,39 @@ else
|
|||||||
fail "dashboard-chart-patch outputs valid JSON only" "got: $patch_out"
|
fail "dashboard-chart-patch outputs valid JSON only" "got: $patch_out"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
apl_fmt=$("$SCRIPTS_DIR/chart-add" --type Statistic --id t --name T \
|
||||||
|
--apl "['logs'] | where a=='x' | summarize c=count()" | jq -r '.query.apl')
|
||||||
|
if [[ "$(printf '%s' "$apl_fmt" | grep -c '^| ')" == "2" && "$apl_fmt" != *" | "* ]]; then
|
||||||
|
ok "chart-add breaks each pipeline stage onto its own line"
|
||||||
|
else
|
||||||
|
fail "chart-add breaks each pipeline stage onto its own line" "got: $apl_fmt"
|
||||||
|
fi
|
||||||
|
|
||||||
|
apl_str=$("$SCRIPTS_DIR/chart-add" --type Statistic --id t --name T \
|
||||||
|
--apl "['logs'] | where msg=='a | b'" | jq -r '.query.apl')
|
||||||
|
if [[ "$apl_str" == *"msg=='a | b'"* ]]; then
|
||||||
|
ok "chart-add leaves a pipe inside a string literal untouched"
|
||||||
|
else
|
||||||
|
fail "chart-add leaves a pipe inside a string literal untouched" "got: $apl_str"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Constructs whose string boundaries the split cannot follow must round-trip
|
||||||
|
# byte-for-byte rather than risk a newline landing inside a literal.
|
||||||
|
check_verbatim() {
|
||||||
|
local label="$1" input="$2" got
|
||||||
|
got=$("$SCRIPTS_DIR/chart-add" --type Statistic --id t --name T --apl "$input" | jq -r '.query.apl')
|
||||||
|
if [[ "$got" == "$input" ]]; then
|
||||||
|
ok "chart-add stores $label untouched"
|
||||||
|
else
|
||||||
|
fail "chart-add stores $label untouched" "got: $got"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_verbatim "a backslash-escaped quote" '["logs"] | where msg == "a \" b | c" | project msg'
|
||||||
|
check_verbatim "an @-verbatim literal" '["logs"] | where p == @"c:\x | y" | project p'
|
||||||
|
check_verbatim "a // comment" '["logs"] // note | here
|
||||||
|
| count'
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "======================"
|
echo "======================"
|
||||||
echo "Passed: $passed | Failed: $failed"
|
echo "Passed: $passed | Failed: $failed"
|
||||||
|
|||||||
@@ -11,9 +11,16 @@ exports when the consumer must control reset and adapter order.
|
|||||||
| Semantic | `--oc-bg-*`, `--oc-text-*`, `--oc-accent-*` | Theme-aware UI intent |
|
| Semantic | `--oc-bg-*`, `--oc-text-*`, `--oc-accent-*` | Theme-aware UI intent |
|
||||||
| Scale | `--oc-space-*`, `--oc-font-size-*`, `--oc-radius-*` | Shared dimensions |
|
| Scale | `--oc-space-*`, `--oc-font-size-*`, `--oc-radius-*` | Shared dimensions |
|
||||||
| Motion | `--oc-duration-*`, `--oc-ease-*` | Shared interaction timing |
|
| Motion | `--oc-duration-*`, `--oc-ease-*` | Shared interaction timing |
|
||||||
|
| Layer | `--oc-layer-*` | Popover and non-modal notification stacking roles |
|
||||||
| Product | `--oc-status-*`, `--oc-input-*`, `--oc-diff-*` | Opt-in operational UI |
|
| Product | `--oc-status-*`, `--oc-input-*`, `--oc-diff-*` | Opt-in operational UI |
|
||||||
| Consumer alias | Unprefixed legacy names | Migration compatibility only |
|
| Consumer alias | Unprefixed legacy names | Migration compatibility only |
|
||||||
|
|
||||||
|
Component styles consume semantic and product roles in their owning
|
||||||
|
stylesheet; Carapace does not define a global component-token namespace. When
|
||||||
|
a component genuinely needs a local custom property, scope it to the component
|
||||||
|
root and document the override point beside that component rather than turning
|
||||||
|
it into a second palette.
|
||||||
|
|
||||||
## Semantic Choices
|
## Semantic Choices
|
||||||
|
|
||||||
- Page background: `--oc-bg-page`
|
- Page background: `--oc-bg-page`
|
||||||
@@ -27,6 +34,10 @@ exports when the consumer must control reset and adapter order.
|
|||||||
`--oc-accent-primary-hover`
|
`--oc-accent-primary-hover`
|
||||||
- Secondary accent: `--oc-accent-secondary`
|
- Secondary accent: `--oc-accent-secondary`
|
||||||
- Neutral control backgrounds: `--oc-control-bg`, `--oc-control-bg-hover`
|
- Neutral control backgrounds: `--oc-control-bg`, `--oc-control-bg-hover`
|
||||||
|
- Modal isolation: `--oc-surface-modal-backdrop`; ordinary translucent
|
||||||
|
surfaces continue to use `--oc-surface-overlay`
|
||||||
|
- Product fields: `--oc-input-*`; status feedback: paired `--oc-status-*-bg`
|
||||||
|
and `--oc-status-*-fg` roles
|
||||||
- Subtle, strong, and accent borders: `--oc-border-subtle`,
|
- Subtle, strong, and accent borders: `--oc-border-subtle`,
|
||||||
`--oc-border-strong`, `--oc-border-accent`
|
`--oc-border-strong`, `--oc-border-accent`
|
||||||
- Focus: `--oc-focus-ring`
|
- Focus: `--oc-focus-ring`
|
||||||
|
|||||||
@@ -13,9 +13,16 @@ to `@openclaw/carapace`.
|
|||||||
| Semantic | `--oc-bg-*`, `--oc-text-*`, `--oc-accent-*` | Theme-aware UI intent |
|
| Semantic | `--oc-bg-*`, `--oc-text-*`, `--oc-accent-*` | Theme-aware UI intent |
|
||||||
| Scale | `--oc-space-*`, `--oc-font-size-*`, `--oc-radius-*` | Shared dimensions |
|
| Scale | `--oc-space-*`, `--oc-font-size-*`, `--oc-radius-*` | Shared dimensions |
|
||||||
| Motion | `--oc-duration-*`, `--oc-ease-*` | Shared interaction timing |
|
| Motion | `--oc-duration-*`, `--oc-ease-*` | Shared interaction timing |
|
||||||
|
| Layer | `--oc-layer-*` | Popover and non-modal notification stacking roles |
|
||||||
| Product | `--oc-status-*`, `--oc-input-*`, `--oc-diff-*` | Opt-in operational UI |
|
| Product | `--oc-status-*`, `--oc-input-*`, `--oc-diff-*` | Opt-in operational UI |
|
||||||
| Consumer alias | Unprefixed legacy names | Migration compatibility only |
|
| Consumer alias | Unprefixed legacy names | Migration compatibility only |
|
||||||
|
|
||||||
|
Component styles consume semantic and product roles in their owning
|
||||||
|
stylesheet; Carapace does not define a global component-token namespace. When
|
||||||
|
a component genuinely needs a local custom property, scope it to the component
|
||||||
|
root and document the override point beside that component rather than turning
|
||||||
|
it into a second palette.
|
||||||
|
|
||||||
## Semantic Choices
|
## Semantic Choices
|
||||||
|
|
||||||
- Page background: `--oc-bg-page`
|
- Page background: `--oc-bg-page`
|
||||||
@@ -29,6 +36,10 @@ to `@openclaw/carapace`.
|
|||||||
`--oc-accent-primary-hover`
|
`--oc-accent-primary-hover`
|
||||||
- Secondary accent: `--oc-accent-secondary`
|
- Secondary accent: `--oc-accent-secondary`
|
||||||
- Neutral control backgrounds: `--oc-control-bg`, `--oc-control-bg-hover`
|
- Neutral control backgrounds: `--oc-control-bg`, `--oc-control-bg-hover`
|
||||||
|
- Modal isolation: `--oc-surface-modal-backdrop`; ordinary translucent
|
||||||
|
surfaces continue to use `--oc-surface-overlay`
|
||||||
|
- Product fields: `--oc-input-*`; status feedback: paired `--oc-status-*-bg`
|
||||||
|
and `--oc-status-*-fg` roles
|
||||||
- Subtle, strong, and accent borders: `--oc-border-subtle`,
|
- Subtle, strong, and accent borders: `--oc-border-subtle`,
|
||||||
`--oc-border-strong`, `--oc-border-accent`
|
`--oc-border-strong`, `--oc-border-accent`
|
||||||
- Focus: `--oc-focus-ring`
|
- Focus: `--oc-focus-ring`
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ scripts/datasets prod
|
|||||||
scripts/datasets prod --kind otel:metrics:v1
|
scripts/datasets prod --kind otel:metrics:v1
|
||||||
|
|
||||||
# Fetch the metrics query spec
|
# Fetch the metrics query spec
|
||||||
scripts/metrics-spec prod
|
scripts/metrics-spec
|
||||||
|
|
||||||
# List available metrics in a dataset
|
# List available metrics in a dataset
|
||||||
scripts/metrics-info prod my-dataset metrics
|
scripts/metrics-info prod my-dataset metrics
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ Setup, prerequisites, and `~/.axiom.toml` configuration: see `README.md`. Edge-d
|
|||||||
## Workflow
|
## Workflow
|
||||||
|
|
||||||
1. `scripts/datasets <deploy> --kind otel:metrics:v1` — list metrics datasets.
|
1. `scripts/datasets <deploy> --kind otel:metrics:v1` — list metrics datasets.
|
||||||
2. `scripts/metrics-spec <deploy> <dataset>` — **required** before composing any query. MPL evolves; the spec is the source of truth.
|
2. `scripts/metrics-spec` — **required** before composing any query. MPL evolves; the spec is the source of truth. Also use it to answer general MPL/metrics questions.
|
||||||
3. `scripts/metrics-info <deploy> <dataset> metrics` — list metrics with `{type, temporality, unit}` metadata. Read this before writing the query (see [Choosing a Query Shape](#choosing-a-query-shape)).
|
3. `scripts/metrics-info <deploy> <dataset> metrics` — list metrics with `{type, temporality, unit}` metadata. Read this before writing the query (see [Choosing a Query Shape](#choosing-a-query-shape)).
|
||||||
4. `scripts/metrics-info <deploy> <dataset> tags [<tag> values]` — explore filter dimensions.
|
4. `scripts/metrics-info <deploy> <dataset> tags [<tag> values]` — explore filter dimensions.
|
||||||
5. `scripts/metrics-query <deploy> '<MPL>' <start> <end>` — execute. Iterate.
|
5. `scripts/metrics-query <deploy> '<MPL>' <start> <end>` — execute. Iterate.
|
||||||
@@ -35,7 +35,7 @@ Rules per type (consult `metrics-spec` for exact operator names — they evolve)
|
|||||||
- **CounterMonotonic + Cumulative** — running total (resets aside). The raw values are rarely what you want. Convert to a per-second rate first, **then** align/aggregate.
|
- **CounterMonotonic + Cumulative** — running total (resets aside). The raw values are rarely what you want. Convert to a per-second rate first, **then** align/aggregate.
|
||||||
- **CounterMonotonic + Delta** — already per-interval. Sum/align without a rate step.
|
- **CounterMonotonic + Delta** — already per-interval. Sum/align without a rate step.
|
||||||
- **CounterNonMonotonic** — can go up or down (queue depth, balance). Intent is ambiguous: rate, delta, or current value all make sense for different questions. **Ask the user** before picking one.
|
- **CounterNonMonotonic** — can go up or down (queue depth, balance). Intent is ambiguous: rate, delta, or current value all make sense for different questions. **Ask the user** before picking one.
|
||||||
- **Histogram** — not a scalar. `align using avg` produces nonsense. Use the bucket/quantile operators from `metrics-spec`.
|
- **Histogram** — not a scalar. `align using avg` produces nonsense. Use `bucket … using` with the histogram functions from `metrics-spec`; quantiles are float specs to those functions, and `temporality` selects the variant (`Cumulative` vs `Delta` interpolation). Consult `metrics-spec` for the exact signatures.
|
||||||
- **`temporality: null`** — "not applicable for this instrument type" (the norm for Gauges), not "missing data".
|
- **`temporality: null`** — "not applicable for this instrument type" (the norm for Gauges), not "missing data".
|
||||||
|
|
||||||
When surfacing numbers, attach the `unit` (treat `null` as unitless). If you combine metrics with mismatched units in arithmetic, warn rather than silently producing a meaningless number.
|
When surfacing numbers, attach the `unit` (treat `null` as unitless). If you combine metrics with mismatched units in arithmetic, warn rather than silently producing a meaningless number.
|
||||||
@@ -43,7 +43,7 @@ When surfacing numbers, attach the `unit` (treat `null` as unitless). If you com
|
|||||||
## Query Metrics
|
## Query Metrics
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scripts/metrics-query <deploy> '<MPL>' <start> <end>
|
scripts/metrics-query [-w pixels] [--pixel-per-point n] <deploy> '<MPL>' <start> <end>
|
||||||
```
|
```
|
||||||
|
|
||||||
| Parameter | Notes |
|
| Parameter | Notes |
|
||||||
@@ -51,22 +51,57 @@ scripts/metrics-query <deploy> '<MPL>' <start> <end>
|
|||||||
| `deploy` | Name from `~/.axiom.toml` (e.g. `prod`). |
|
| `deploy` | Name from `~/.axiom.toml` (e.g. `prod`). |
|
||||||
| `MPL` | Pipeline string. Dataset is parsed from the MPL itself. |
|
| `MPL` | Pipeline string. Dataset is parsed from the MPL itself. |
|
||||||
| `start` / `end` | RFC3339 (`2025-01-01T00:00:00Z`) or relative (`now-1h`, `now`). |
|
| `start` / `end` | RFC3339 (`2025-01-01T00:00:00Z`) or relative (`now-1h`, `now`). |
|
||||||
|
| `-w` / `--chart-width <px>` | Optional. Target chart width in pixels; lets the server resolve `$__interval`. |
|
||||||
|
| `--pixel-per-point <n>` | Optional. Pixels per point (server default 10); with `-w` sets the bucket count. |
|
||||||
|
|
||||||
|
**Always single-quote the MPL string in the shell.** MPL is full of backticks; inside double quotes the shell executes them as command substitution, silently mangling the query (or running whatever the identifier names).
|
||||||
|
|
||||||
|
**Bound the output before grouping.** `group by <tag>` returns one series per tag value with no cap — on a high-cardinality tag this floods the output. Check cardinality first (`describe`, or `tags <tag> values`) and prefer plain `group using <agg>` while exploring.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scripts/metrics-query prod \
|
scripts/metrics-query prod -w 1200 \
|
||||||
'`my-dataset`:`http.server.duration` | align to 5m using avg' \
|
'`my-dataset`:`http.server.duration` | align to $__interval using avg' \
|
||||||
now-1h now
|
now-1h now
|
||||||
|
|
||||||
scripts/metrics-query prod \
|
scripts/metrics-query prod -w 1200 \
|
||||||
'`my-dataset`:`http.server.duration`
|
'`my-dataset`:`http.server.duration`
|
||||||
| where `service.name` == "frontend" and method == "GET"
|
| where `service.name` == "frontend" and method == "GET"
|
||||||
| align to 5m using avg
|
| align to $__interval using avg
|
||||||
| group by status_code using sum' \
|
| group by status_code using sum' \
|
||||||
now-1d now
|
now-1d now
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Adaptive resolution (`$__interval`)
|
||||||
|
|
||||||
|
Hardcoding a step (`align to 5m`) makes charts look wrong at other zoom
|
||||||
|
levels — too sparse zoomed in, too dense zoomed out. Prefer the system
|
||||||
|
parameter `$__interval` wherever a `Duration` is expected, and pass the chart
|
||||||
|
width so the server picks the step:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scripts/metrics-query prod -w 1200 \
|
||||||
|
'`my-dataset`:`http.server.duration` | align to $__interval using avg' \
|
||||||
|
now-7d now
|
||||||
|
```
|
||||||
|
|
||||||
|
The metrics service computes `$__interval` from the query's time range and the
|
||||||
|
target chart width, then snaps it **up** to a nice resolution from the ladder
|
||||||
|
`1s, 5s, 10s, 15s, 30s, 1m, 5m, 10m, 15m, 30m, 1h, 12h, 1d, 1w, 1M, 1Y`. It
|
||||||
|
never drops below a metric's stored resolution.
|
||||||
|
|
||||||
|
- **No declaration needed** — the server auto-registers `$__interval`; do *not*
|
||||||
|
add `param $__interval: Duration;` (the edge forwards the query verbatim and
|
||||||
|
the metrics service injects the parameter).
|
||||||
|
- **Bucket count** ≈ `chart-width / pixel-per-point` (`pixel-per-point` default
|
||||||
|
10). Omit `-w` and the server targets ~500 buckets.
|
||||||
|
- Works anywhere a `Duration` is valid, e.g. `bucket to $__interval using
|
||||||
|
histogram(0.5, 0.95)`.
|
||||||
|
- Set `-w` to your render width (e.g. the `metrics-chart` skill's plot width)
|
||||||
|
so one bucket ≈ one pixel column. The value is forwarded under the request
|
||||||
|
body's `queryOptions` (`chart-width`, `pixel-per-point`).
|
||||||
|
|
||||||
### Parameters
|
### Parameters
|
||||||
|
|
||||||
MPL can declare parameters (`param $svc: string;`). Pass values with repeated `-p name=value`. The script applies the API's `param__` prefix; values are forwarded verbatim as MPL literals (string literals include their quotes).
|
MPL can declare parameters (`param $svc: string;`). Pass values with repeated `-p name=value`. The script applies the API's `param__` prefix; values are forwarded verbatim as MPL literals (string literals include their quotes).
|
||||||
@@ -96,7 +131,7 @@ Literal syntax per type lives in `metrics-spec`.
|
|||||||
|
|
||||||
## Discovery (`metrics-info`)
|
## Discovery (`metrics-info`)
|
||||||
|
|
||||||
Time range defaults to the last 24h; override with `--start` / `--end`.
|
Time range defaults to the last 24h; override with `--start` / `--end`. Both accept RFC3339 (offsets allowed) or relative `now` / `now-<N><unit>` with `<unit>` in `s m h d w`, resolved to RFC3339 UTC client-side. This is **narrower** than `metrics-query`, which forwards times to the server unparsed and so also accepts forms like `now-1y`; in `metrics-info` anything outside `now` / `now-<N>[smhdw]` must already be RFC3339 or the request 400s.
|
||||||
|
|
||||||
| Command | Returns |
|
| Command | Returns |
|
||||||
|---|---|
|
|---|---|
|
||||||
@@ -114,21 +149,25 @@ Time range defaults to the last 24h; override with `--start` / `--end`.
|
|||||||
|
|
||||||
## Error Handling
|
## Error Handling
|
||||||
|
|
||||||
HTTP errors return JSON with `message`, `code`, and optional `detail`:
|
HTTP errors return JSON with `code` and `message`; some include a `detail` object:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{"message": "...", "code": 400, "detail": {"errorType": 1, "message": "raw error"}}
|
{"code": 400, "message": "MPL syntax error: …"}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Syntax errors (400) include an annotated source pointer listing the valid operators at the failure position — read it, it usually names the fix.
|
||||||
|
|
||||||
| Code | Cause |
|
| Code | Cause |
|
||||||
|---|---|
|
|---|---|
|
||||||
| 400 | Invalid query syntax or bad dataset name |
|
| 400 | Invalid query syntax or bad dataset name |
|
||||||
| 401 | Missing/invalid auth |
|
| 401 | Missing/invalid auth |
|
||||||
| 403 | No permission |
|
| 403 | No permission |
|
||||||
| 404 | Dataset not found |
|
| 404 | Dataset not found |
|
||||||
| 429 | Rate limited |
|
| 429 | Rate limited — back off and retry; don't tight-loop |
|
||||||
| 500 | Internal error |
|
| 500 | Internal error |
|
||||||
|
|
||||||
|
Requests time out client-side after 120s (`AXIOM_MAX_TIME` to override; `AXIOM_CONNECT_TIMEOUT` for the 10s connect timeout).
|
||||||
|
|
||||||
On 500, re-run with `curl -v` to capture the `traceparent` / `x-axiom-trace-id` header and report it — the trace ID is what the backend team needs to debug.
|
On 500, re-run with `curl -v` to capture the `traceparent` / `x-axiom-trace-id` header and report it — the trace ID is what the backend team needs to debug.
|
||||||
|
|
||||||
## Scripts
|
## Scripts
|
||||||
@@ -137,8 +176,8 @@ On 500, re-run with `curl -v` to capture the `traceparent` / `x-axiom-trace-id`
|
|||||||
|---|---|
|
|---|---|
|
||||||
| `scripts/setup` | Check requirements and config. |
|
| `scripts/setup` | Check requirements and config. |
|
||||||
| `scripts/datasets <deploy> [--kind <kind>]` | List datasets with edge deployment. |
|
| `scripts/datasets <deploy> [--kind <kind>]` | List datasets with edge deployment. |
|
||||||
| `scripts/metrics-spec <deploy> <dataset>` | Fetch the MPL query spec. |
|
| `scripts/metrics-spec` | Fetch the MPL query spec. |
|
||||||
| `scripts/metrics-query <deploy> <mpl> <start> <end>` | Execute a query. |
|
| `scripts/metrics-query [-w px] [--pixel-per-point n] <deploy> <mpl> <start> <end>` | Execute a query; use `$__interval` + `-w` for adaptive resolution. |
|
||||||
| `scripts/metrics-info <deploy> <dataset> ...` | Discover metrics, tags, values. |
|
| `scripts/metrics-info <deploy> <dataset> ...` | Discover metrics, tags, values. |
|
||||||
| `scripts/axiom-api <deploy> <method> <path> [body]` | Low-level API calls. |
|
| `scripts/axiom-api <deploy> <method> <path> [body]` | Low-level API calls. |
|
||||||
| `scripts/resolve-url <deploy> <dataset>` | Resolve to the edge deployment URL. |
|
| `scripts/resolve-url <deploy> <dataset>` | Resolve to the edge deployment URL. |
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
#
|
#
|
||||||
# Reads credentials from ~/.axiom.toml (shared with axiom-sre)
|
# Reads credentials from ~/.axiom.toml (shared with axiom-sre)
|
||||||
# Set AXIOM_URL_OVERRIDE to route requests to a specific edge deployment endpoint.
|
# Set AXIOM_URL_OVERRIDE to route requests to a specific edge deployment endpoint.
|
||||||
|
# Set AXIOM_CONNECT_TIMEOUT / AXIOM_MAX_TIME (seconds) to override the default
|
||||||
|
# connection (10s) and total request (120s) timeouts.
|
||||||
#
|
#
|
||||||
# Examples:
|
# Examples:
|
||||||
# axiom-api prod GET /v1/datasets
|
# axiom-api prod GET /v1/datasets
|
||||||
@@ -51,6 +53,8 @@ fi
|
|||||||
|
|
||||||
CURL_ARGS=(
|
CURL_ARGS=(
|
||||||
-s
|
-s
|
||||||
|
--connect-timeout "${AXIOM_CONNECT_TIMEOUT:-10}"
|
||||||
|
--max-time "${AXIOM_MAX_TIME:-120}"
|
||||||
-w '\n%{http_code}'
|
-w '\n%{http_code}'
|
||||||
-X "$METHOD"
|
-X "$METHOD"
|
||||||
-H "Authorization: Bearer $TOKEN"
|
-H "Authorization: Bearer $TOKEN"
|
||||||
|
|||||||
@@ -47,7 +47,12 @@
|
|||||||
# specific entity name (service, host, device) to find which metrics carry it.
|
# specific entity name (service, host, device) to find which metrics carry it.
|
||||||
# To list metric names, use the `metrics` subcommand instead.
|
# To list metric names, use the `metrics` subcommand instead.
|
||||||
#
|
#
|
||||||
# --start and --end default to the last 24 hours if omitted.
|
# --start and --end accept RFC3339 (offsets allowed, e.g. 2025-06-01T00:00:00+02:00)
|
||||||
|
# or relative now / now-<N><unit> with <unit> in s/m/h/d/w, resolved to RFC3339 UTC
|
||||||
|
# client-side because the info endpoints only parse RFC3339. This is narrower than
|
||||||
|
# metrics-query, which forwards times to the server unparsed and also accepts forms
|
||||||
|
# like now-1y; here anything outside now / now-<N>[smhdw] must already be RFC3339.
|
||||||
|
# Defaults: last 24 hours.
|
||||||
# For sparse metrics (sensors, batch jobs), try --start with a wider range (e.g. 7 days).
|
# For sparse metrics (sensors, batch jobs), try --start with a wider range (e.g. 7 days).
|
||||||
#
|
#
|
||||||
# Examples:
|
# Examples:
|
||||||
@@ -67,6 +72,53 @@ set -euo pipefail
|
|||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
# Percent-encode one URL component (path segment or query value). Dataset,
|
||||||
|
# metric, and tag names are user/OTel-controlled and may contain characters
|
||||||
|
# that are reserved in URLs (/ % + space); times may carry a `+02:00` offset
|
||||||
|
# whose `+` would otherwise decode as a space server-side.
|
||||||
|
urlencode() {
|
||||||
|
jq -rn --arg v "$1" '$v|@uri'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Normalize a time argument to RFC3339 UTC. RFC3339 input passes through
|
||||||
|
# verbatim; the relative forms `now` and `now-<N><unit>` (unit in s/m/h/d/w)
|
||||||
|
# are resolved client-side because the info endpoints only parse RFC3339.
|
||||||
|
# Note: metrics-query forwards times to the server unparsed, so it accepts a
|
||||||
|
# broader set (e.g. now-1y); those forms are NOT handled here and, if passed,
|
||||||
|
# fall through to the RFC3339-only endpoint and fail.
|
||||||
|
normalize_time() {
|
||||||
|
local t="$1"
|
||||||
|
if [[ "$t" == "now" ]]; then
|
||||||
|
date -u '+%Y-%m-%dT%H:%M:%SZ'
|
||||||
|
elif [[ "$t" =~ ^now-([0-9]+)([smhdw])$ ]]; then
|
||||||
|
local n="${BASH_REMATCH[1]}" u="${BASH_REMATCH[2]}"
|
||||||
|
if date --version &>/dev/null; then
|
||||||
|
local word
|
||||||
|
case "$u" in
|
||||||
|
s) word="seconds" ;;
|
||||||
|
m) word="minutes" ;;
|
||||||
|
h) word="hours" ;;
|
||||||
|
d) word="days" ;;
|
||||||
|
w) word="weeks" ;;
|
||||||
|
esac
|
||||||
|
date -u -d "$n $word ago" '+%Y-%m-%dT%H:%M:%SZ'
|
||||||
|
else
|
||||||
|
# BSD date: -v units are case-sensitive (M = minute, m = month).
|
||||||
|
local unit
|
||||||
|
case "$u" in
|
||||||
|
s) unit="S" ;;
|
||||||
|
m) unit="M" ;;
|
||||||
|
h) unit="H" ;;
|
||||||
|
d) unit="d" ;;
|
||||||
|
w) unit="w" ;;
|
||||||
|
esac
|
||||||
|
date -u -v "-${n}${unit}" '+%Y-%m-%dT%H:%M:%SZ'
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
printf '%s\n' "$t"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
show_usage() {
|
show_usage() {
|
||||||
echo "Usage:" >&2
|
echo "Usage:" >&2
|
||||||
echo " metrics-info <deploy> <dataset> metrics [--by-type] [--type T]..." >&2
|
echo " metrics-info <deploy> <dataset> metrics [--by-type] [--type T]..." >&2
|
||||||
@@ -80,8 +132,8 @@ show_usage() {
|
|||||||
echo " metrics-info <deploy> <dataset> find-metrics <search-value> (searches tag values, not metric names)" >&2
|
echo " metrics-info <deploy> <dataset> find-metrics <search-value> (searches tag values, not metric names)" >&2
|
||||||
echo "" >&2
|
echo "" >&2
|
||||||
echo "Options:" >&2
|
echo "Options:" >&2
|
||||||
echo " --start T Start time (RFC3339). Default: 24h ago" >&2
|
echo " --start T Start time (RFC3339 or relative, e.g. now-7d). Default: 24h ago" >&2
|
||||||
echo " --end T End time (RFC3339). Default: now" >&2
|
echo " --end T End time (RFC3339 or relative, e.g. now). Default: now" >&2
|
||||||
echo " --by-type (metrics listing) Group entries by metric type" >&2
|
echo " --by-type (metrics listing) Group entries by metric type" >&2
|
||||||
echo " --type T (metrics listing) Filter to type T. Repeatable." >&2
|
echo " --type T (metrics listing) Filter to type T. Repeatable." >&2
|
||||||
echo " --no-values (describe) Return tag names only" >&2
|
echo " --no-values (describe) Return tag names only" >&2
|
||||||
@@ -118,20 +170,12 @@ while [[ $# -gt 0 ]]; do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
# Default time range: last 24 hours
|
# Default time range: last 24 hours. Relative forms are resolved to RFC3339 UTC.
|
||||||
if [[ -z "$START" ]]; then
|
START=$(normalize_time "${START:-now-24h}")
|
||||||
if date --version &>/dev/null 2>&1; then
|
END=$(normalize_time "${END:-now}")
|
||||||
START=$(date -u -d '24 hours ago' '+%Y-%m-%dT%H:%M:%SZ')
|
|
||||||
else
|
|
||||||
START=$(date -u -v-24H '+%Y-%m-%dT%H:%M:%SZ')
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
if [[ -z "$END" ]]; then
|
|
||||||
END=$(date -u '+%Y-%m-%dT%H:%M:%SZ')
|
|
||||||
fi
|
|
||||||
|
|
||||||
TIME_PARAMS="start=${START}&end=${END}"
|
TIME_PARAMS="start=$(urlencode "$START")&end=$(urlencode "$END")"
|
||||||
BASE="/v1/query/metrics/info/datasets/${DATASET}"
|
BASE="/v1/query/metrics/info/datasets/$(urlencode "$DATASET")"
|
||||||
|
|
||||||
# Resolve the regional edge URL for this dataset
|
# Resolve the regional edge URL for this dataset
|
||||||
RESOLVED_URL=$("$SCRIPT_DIR/resolve-url" "$DEPLOYMENT" "$DATASET" 2>/dev/null || true)
|
RESOLVED_URL=$("$SCRIPT_DIR/resolve-url" "$DEPLOYMENT" "$DATASET" 2>/dev/null || true)
|
||||||
@@ -185,34 +229,62 @@ case "${POSITIONAL[0]}" in
|
|||||||
# the typical 1+1+N round trips an agent would make to characterise
|
# the typical 1+1+N round trips an agent would make to characterise
|
||||||
# an unfamiliar metric.
|
# an unfamiliar metric.
|
||||||
METRIC="${POSITIONAL[1]}"
|
METRIC="${POSITIONAL[1]}"
|
||||||
|
METRIC_ENC=$(urlencode "$METRIC")
|
||||||
RAW=$(fetch_metrics_listing)
|
RAW=$(fetch_metrics_listing)
|
||||||
META=$(printf '%s' "$RAW" | jq -e --arg m "$METRIC" '.[$m] // error("metric not found in listing for the given time range: " + $m)')
|
META=$(printf '%s' "$RAW" | jq -e --arg m "$METRIC" '.[$m] // error("metric not found in listing for the given time range: " + $m)')
|
||||||
TAGS_JSON=$("$SCRIPT_DIR/axiom-api" "$DEPLOYMENT" GET "${BASE}/metrics/${METRIC}/tags?${TIME_PARAMS}")
|
TAGS_JSON=$("$SCRIPT_DIR/axiom-api" "$DEPLOYMENT" GET "${BASE}/metrics/${METRIC_ENC}/tags?${TIME_PARAMS}")
|
||||||
if [[ "$NO_VALUES" -eq 1 ]]; then
|
if [[ "$NO_VALUES" -eq 1 ]]; then
|
||||||
# tags as flat array of names
|
# tags as flat array of names
|
||||||
jq -n --argjson m "$META" --argjson tags "$TAGS_JSON" '$m + {tags: $tags}'
|
jq -n --argjson m "$META" --argjson tags "$TAGS_JSON" '$m + {tags: $tags}'
|
||||||
else
|
else
|
||||||
# tags as object: { tag_name: [values…] }
|
# tags as object: { tag_name: [values…] }. Per-tag value fetches
|
||||||
VALUES_OBJ='{}'
|
# are independent, so run them concurrently; tag counts are small
|
||||||
|
# (rarely more than a few dozen), so no concurrency cap is needed.
|
||||||
|
TAG_NAMES=()
|
||||||
while IFS= read -r tag; do
|
while IFS= read -r tag; do
|
||||||
[[ -z "$tag" ]] && continue
|
[[ -z "$tag" ]] && continue
|
||||||
VALUES=$("$SCRIPT_DIR/axiom-api" "$DEPLOYMENT" GET "${BASE}/metrics/${METRIC}/tags/${tag}/values?${TIME_PARAMS}")
|
TAG_NAMES+=("$tag")
|
||||||
if [[ "$VALUES_LIMIT" -gt 0 ]]; then
|
|
||||||
VALUES=$(printf '%s' "$VALUES" | jq --argjson n "$VALUES_LIMIT" '.[:$n]')
|
|
||||||
fi
|
|
||||||
VALUES_OBJ=$(jq -n --argjson o "$VALUES_OBJ" --arg t "$tag" --argjson v "$VALUES" '$o + {($t): $v}')
|
|
||||||
done < <(printf '%s' "$TAGS_JSON" | jq -r '.[]?')
|
done < <(printf '%s' "$TAGS_JSON" | jq -r '.[]?')
|
||||||
|
VALUES_OBJ='{}'
|
||||||
|
if [[ ${#TAG_NAMES[@]} -gt 0 ]]; then
|
||||||
|
TMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/metrics-info.XXXXXX")
|
||||||
|
trap 'rm -rf "$TMP_DIR"' EXIT
|
||||||
|
PIDS=()
|
||||||
|
for i in "${!TAG_NAMES[@]}"; do
|
||||||
|
"$SCRIPT_DIR/axiom-api" "$DEPLOYMENT" GET \
|
||||||
|
"${BASE}/metrics/${METRIC_ENC}/tags/$(urlencode "${TAG_NAMES[$i]}")/values?${TIME_PARAMS}" \
|
||||||
|
> "$TMP_DIR/$i.json" &
|
||||||
|
PIDS+=($!)
|
||||||
|
done
|
||||||
|
FETCH_FAILED=0
|
||||||
|
for i in "${!PIDS[@]}"; do
|
||||||
|
if ! wait "${PIDS[$i]}"; then
|
||||||
|
echo "Error: failed to fetch values for tag '${TAG_NAMES[$i]}'" >&2
|
||||||
|
FETCH_FAILED=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [[ "$FETCH_FAILED" -eq 1 ]]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
for i in "${!TAG_NAMES[@]}"; do
|
||||||
|
VALUES=$(cat "$TMP_DIR/$i.json")
|
||||||
|
if [[ "$VALUES_LIMIT" -gt 0 ]]; then
|
||||||
|
VALUES=$(printf '%s' "$VALUES" | jq --argjson n "$VALUES_LIMIT" '.[:$n]')
|
||||||
|
fi
|
||||||
|
VALUES_OBJ=$(jq -n --argjson o "$VALUES_OBJ" --arg t "${TAG_NAMES[$i]}" --argjson v "$VALUES" '$o + {($t): $v}')
|
||||||
|
done
|
||||||
|
fi
|
||||||
jq -n --argjson m "$META" --argjson tags "$VALUES_OBJ" '$m + {tags: $tags}'
|
jq -n --argjson m "$META" --argjson tags "$VALUES_OBJ" '$m + {tags: $tags}'
|
||||||
fi
|
fi
|
||||||
elif [[ ${#POSITIONAL[@]} -eq 3 && "${POSITIONAL[2]}" == "tags" ]]; then
|
elif [[ ${#POSITIONAL[@]} -eq 3 && "${POSITIONAL[2]}" == "tags" ]]; then
|
||||||
# List tags for a metric
|
# List tags for a metric
|
||||||
METRIC="${POSITIONAL[1]}"
|
METRIC="${POSITIONAL[1]}"
|
||||||
"$SCRIPT_DIR/axiom-api" "$DEPLOYMENT" GET "${BASE}/metrics/${METRIC}/tags?${TIME_PARAMS}"
|
"$SCRIPT_DIR/axiom-api" "$DEPLOYMENT" GET "${BASE}/metrics/$(urlencode "$METRIC")/tags?${TIME_PARAMS}"
|
||||||
elif [[ ${#POSITIONAL[@]} -eq 5 && "${POSITIONAL[2]}" == "tags" && "${POSITIONAL[4]}" == "values" ]]; then
|
elif [[ ${#POSITIONAL[@]} -eq 5 && "${POSITIONAL[2]}" == "tags" && "${POSITIONAL[4]}" == "values" ]]; then
|
||||||
# List tag values for a metric+tag
|
# List tag values for a metric+tag
|
||||||
METRIC="${POSITIONAL[1]}"
|
METRIC="${POSITIONAL[1]}"
|
||||||
TAG="${POSITIONAL[3]}"
|
TAG="${POSITIONAL[3]}"
|
||||||
"$SCRIPT_DIR/axiom-api" "$DEPLOYMENT" GET "${BASE}/metrics/${METRIC}/tags/${TAG}/values?${TIME_PARAMS}"
|
"$SCRIPT_DIR/axiom-api" "$DEPLOYMENT" GET "${BASE}/metrics/$(urlencode "$METRIC")/tags/$(urlencode "$TAG")/values?${TIME_PARAMS}"
|
||||||
elif [[ ${#POSITIONAL[@]} -eq 5 && "${POSITIONAL[2]}" == "tags" && "${POSITIONAL[4]}" == "type" ]]; then
|
elif [[ ${#POSITIONAL[@]} -eq 5 && "${POSITIONAL[2]}" == "tags" && "${POSITIONAL[4]}" == "type" ]]; then
|
||||||
# Probe the typing of a metric+tag by running `metrics-query` with
|
# Probe the typing of a metric+tag by running `metrics-query` with
|
||||||
# `filter <tag> is <T>` for each candidate type. The type(s) that
|
# `filter <tag> is <T>` for each candidate type. The type(s) that
|
||||||
@@ -226,8 +298,15 @@ case "${POSITIONAL[0]}" in
|
|||||||
# `<dataset>`:`<metric>` | filter `<tag>` is <T> | align to 5m using sum
|
# `<dataset>`:`<metric>` | filter `<tag>` is <T> | align to 5m using sum
|
||||||
# If <tag> is <T> matches no rows, the response has empty `series`.
|
# If <tag> is <T> matches no rows, the response has empty `series`.
|
||||||
PROBE_QUERY='`'"$DATASET"'`:`'"$METRIC"'` | filter `'"$TAG"'` is '"$t"' | align to 5m using sum'
|
PROBE_QUERY='`'"$DATASET"'`:`'"$METRIC"'` | filter `'"$TAG"'` is '"$t"' | align to 5m using sum'
|
||||||
RESPONSE=$("$SCRIPT_DIR/metrics-query" "$DEPLOYMENT" "$PROBE_QUERY" "$START" "$END" 2>/dev/null || echo '{}')
|
# Propagate probe failures instead of swallowing them: a failed
|
||||||
COUNT=$(printf '%s' "$RESPONSE" | jq -r '(.series // []) | length' 2>/dev/null || echo 0)
|
# query (bad dataset, auth, network) must not be reported as the
|
||||||
|
# tag being "absent" — that would be a confident wrong answer.
|
||||||
|
if ! RESPONSE=$("$SCRIPT_DIR/metrics-query" "$DEPLOYMENT" "$PROBE_QUERY" "$START" "$END" 2>&1); then
|
||||||
|
echo "Error: type probe query failed (tag '$TAG' is $t):" >&2
|
||||||
|
printf '%s\n' "$RESPONSE" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
COUNT=$(printf '%s' "$RESPONSE" | jq -r '(.series // []) | length')
|
||||||
if [[ "$COUNT" -gt 0 ]]; then
|
if [[ "$COUNT" -gt 0 ]]; then
|
||||||
PRESENT_JSON=$(printf '%s' "$PRESENT_JSON" | jq --arg t "$t" '. + [$t]')
|
PRESENT_JSON=$(printf '%s' "$PRESENT_JSON" | jq --arg t "$t" '. + [$t]')
|
||||||
fi
|
fi
|
||||||
@@ -253,7 +332,7 @@ case "${POSITIONAL[0]}" in
|
|||||||
elif [[ ${#POSITIONAL[@]} -eq 3 && "${POSITIONAL[2]}" == "values" ]]; then
|
elif [[ ${#POSITIONAL[@]} -eq 3 && "${POSITIONAL[2]}" == "values" ]]; then
|
||||||
# List values for a tag
|
# List values for a tag
|
||||||
TAG="${POSITIONAL[1]}"
|
TAG="${POSITIONAL[1]}"
|
||||||
"$SCRIPT_DIR/axiom-api" "$DEPLOYMENT" GET "${BASE}/tags/${TAG}/values?${TIME_PARAMS}"
|
"$SCRIPT_DIR/axiom-api" "$DEPLOYMENT" GET "${BASE}/tags/$(urlencode "$TAG")/values?${TIME_PARAMS}"
|
||||||
else
|
else
|
||||||
show_usage
|
show_usage
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -1,10 +1,23 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# metrics-query: Execute a metrics query against Axiom MetricsDB
|
# metrics-query: Execute a metrics query against Axiom MetricsDB
|
||||||
#
|
#
|
||||||
# Usage: metrics-query [-p name=value]... <deployment> <mpl> <startTime> <endTime>
|
# Usage: metrics-query [-p name=value]... [-w pixels] [--pixel-per-point n] \
|
||||||
|
# <deployment> <mpl> <startTime> <endTime>
|
||||||
#
|
#
|
||||||
# Times: RFC3339 (e.g. 2025-01-01T00:00:00Z) or relative (e.g. now-1h, now-1d).
|
# Times: RFC3339 (e.g. 2025-01-01T00:00:00Z) or relative (e.g. now-1h, now-1d).
|
||||||
#
|
#
|
||||||
|
# Adaptive resolution ($__interval):
|
||||||
|
# Reference $__interval anywhere a Duration is expected (e.g.
|
||||||
|
# `align to $__interval using avg`, `bucket to $__interval ...`) and the
|
||||||
|
# server resolves it to a "nice" step computed from the query time range and
|
||||||
|
# the target chart width. No `param $__interval` declaration is needed -- the
|
||||||
|
# metrics service registers it automatically. Tune the density with:
|
||||||
|
# -w / --chart-width <pixels> target chart width; the server aims for
|
||||||
|
# ~chart-width/pixel-per-point buckets
|
||||||
|
# (default ~500 buckets when -w is omitted).
|
||||||
|
# --pixel-per-point <n> pixels per data point (server default 10).
|
||||||
|
# Both are forwarded under the request body's queryOptions object.
|
||||||
|
#
|
||||||
# Parameter values (-p / --param name=value, repeatable):
|
# Parameter values (-p / --param name=value, repeatable):
|
||||||
# For each MPL parameter declared in the query (e.g. `param $svc: string;`),
|
# For each MPL parameter declared in the query (e.g. `param $svc: string;`),
|
||||||
# pass the variable name without the leading `$` and an MPL literal as the
|
# pass the variable name without the leading `$` and an MPL literal as the
|
||||||
@@ -32,6 +45,8 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|||||||
|
|
||||||
PARAMS=()
|
PARAMS=()
|
||||||
POSITIONAL=()
|
POSITIONAL=()
|
||||||
|
CHART_WIDTH=""
|
||||||
|
PIXEL_PER_POINT=""
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
-p|--param)
|
-p|--param)
|
||||||
@@ -46,6 +61,30 @@ while [[ $# -gt 0 ]]; do
|
|||||||
PARAMS+=("${1#--param=}")
|
PARAMS+=("${1#--param=}")
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
|
-w|--chart-width)
|
||||||
|
if [[ $# -lt 2 ]]; then
|
||||||
|
echo "Error: $1 requires a pixel-width argument" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
CHART_WIDTH="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--chart-width=*)
|
||||||
|
CHART_WIDTH="${1#--chart-width=}"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--pixel-per-point)
|
||||||
|
if [[ $# -lt 2 ]]; then
|
||||||
|
echo "Error: $1 requires an integer argument" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
PIXEL_PER_POINT="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--pixel-per-point=*)
|
||||||
|
PIXEL_PER_POINT="${1#--pixel-per-point=}"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
--)
|
--)
|
||||||
shift
|
shift
|
||||||
while [[ $# -gt 0 ]]; do POSITIONAL+=("$1"); shift; done
|
while [[ $# -gt 0 ]]; do POSITIONAL+=("$1"); shift; done
|
||||||
@@ -63,13 +102,17 @@ START_TIME="${POSITIONAL[2]:-}"
|
|||||||
END_TIME="${POSITIONAL[3]:-}"
|
END_TIME="${POSITIONAL[3]:-}"
|
||||||
|
|
||||||
if [[ -z "$DEPLOYMENT" || -z "$MPL" || -z "$START_TIME" || -z "$END_TIME" ]]; then
|
if [[ -z "$DEPLOYMENT" || -z "$MPL" || -z "$START_TIME" || -z "$END_TIME" ]]; then
|
||||||
echo "Usage: metrics-query [-p name=value]... <deployment> <mpl> <startTime> <endTime>" >&2
|
echo "Usage: metrics-query [-p name=value]... [-w pixels] [--pixel-per-point n] <deployment> <mpl> <startTime> <endTime>" >&2
|
||||||
echo "" >&2
|
echo "" >&2
|
||||||
echo "Times: RFC3339 (e.g. 2025-01-01T00:00:00Z) or relative (e.g. now-1h, now-1d)." >&2
|
echo "Times: RFC3339 (e.g. 2025-01-01T00:00:00Z) or relative (e.g. now-1h, now-1d)." >&2
|
||||||
echo "" >&2
|
echo "" >&2
|
||||||
echo "-p / --param name=value (repeatable): supply an MPL parameter value." >&2
|
echo "-p / --param name=value (repeatable): supply an MPL parameter value." >&2
|
||||||
echo " name - variable name without the leading \$ (e.g. 'svc' for \$svc)." >&2
|
echo " name - variable name without the leading \$ (e.g. 'svc' for \$svc)." >&2
|
||||||
echo " value - MPL literal, forwarded verbatim under params.param__<name>." >&2
|
echo " value - MPL literal, forwarded verbatim under params.param__<name>." >&2
|
||||||
|
echo "" >&2
|
||||||
|
echo "-w / --chart-width <pixels> target chart width; lets the server resolve" >&2
|
||||||
|
echo " \$__interval to a nice step (queryOptions)." >&2
|
||||||
|
echo "--pixel-per-point <n> pixels per data point (server default 10)." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -97,6 +140,17 @@ if [[ ${#PARAMS[@]} -gt 0 ]]; then
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Validate the optional chart-sizing options. They must be positive integers;
|
||||||
|
# they are forwarded under queryOptions so the server can resolve $__interval.
|
||||||
|
if [[ -n "$CHART_WIDTH" && ! "$CHART_WIDTH" =~ ^[1-9][0-9]*$ ]]; then
|
||||||
|
echo "Error: --chart-width must be a positive integer (got: $CHART_WIDTH)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ -n "$PIXEL_PER_POINT" && ! "$PIXEL_PER_POINT" =~ ^[1-9][0-9]*$ ]]; then
|
||||||
|
echo "Error: --pixel-per-point must be a positive integer (got: $PIXEL_PER_POINT)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# Extract dataset name from MPL: `dataset`:`metric` ... or dataset:`metric` ...
|
# Extract dataset name from MPL: `dataset`:`metric` ... or dataset:`metric` ...
|
||||||
# Strip leading `param <name>: <type>;` declarations first so their `:` doesn't
|
# Strip leading `param <name>: <type>;` declarations first so their `:` doesn't
|
||||||
# get mistaken for the dataset:metric separator.
|
# get mistaken for the dataset:metric separator.
|
||||||
@@ -141,6 +195,23 @@ if [[ ${#PARAM_NAMES[@]} -gt 0 ]]; then
|
|||||||
JQ_EXPR="$JQ_EXPR + {params: ($PARAMS_EXPR)}"
|
JQ_EXPR="$JQ_EXPR + {params: ($PARAMS_EXPR)}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Forward chart-sizing hints under queryOptions. The edge translates these into
|
||||||
|
# the x-axiom-chart-width / x-axiom-pixel-per-point headers, which the metrics
|
||||||
|
# service uses to resolve $__interval. Values are JSON numbers (--argjson).
|
||||||
|
if [[ -n "$CHART_WIDTH" || -n "$PIXEL_PER_POINT" ]]; then
|
||||||
|
QO_EXPR=""
|
||||||
|
if [[ -n "$CHART_WIDTH" ]]; then
|
||||||
|
JQ_ARGS+=(--argjson chartWidth "$CHART_WIDTH")
|
||||||
|
QO_EXPR="{\"chart-width\": \$chartWidth}"
|
||||||
|
fi
|
||||||
|
if [[ -n "$PIXEL_PER_POINT" ]]; then
|
||||||
|
JQ_ARGS+=(--argjson pixelPerPoint "$PIXEL_PER_POINT")
|
||||||
|
if [[ -n "$QO_EXPR" ]]; then QO_EXPR+=" + "; fi
|
||||||
|
QO_EXPR+="{\"pixel-per-point\": \$pixelPerPoint}"
|
||||||
|
fi
|
||||||
|
JQ_EXPR="$JQ_EXPR + {queryOptions: ($QO_EXPR)}"
|
||||||
|
fi
|
||||||
|
|
||||||
BODY=$(jq -n "${JQ_ARGS[@]}" "$JQ_EXPR")
|
BODY=$(jq -n "${JQ_ARGS[@]}" "$JQ_EXPR")
|
||||||
|
|
||||||
AXIOM_ACCEPT="application/json+metrics.v2" "$SCRIPT_DIR/axiom-api" "$DEPLOYMENT" POST "/v1/query/_mpl" "$BODY"
|
AXIOM_ACCEPT="application/json+metrics.v2" "$SCRIPT_DIR/axiom-api" "$DEPLOYMENT" POST "/v1/query/_mpl" "$BODY"
|
||||||
|
|||||||
@@ -1,31 +1,18 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# metrics-spec: Fetch the metrics query specification from Axiom
|
# metrics-spec: Fetch the MPL metrics query specification from Axiom
|
||||||
#
|
#
|
||||||
# Usage: metrics-spec <deployment> <dataset>
|
# Usage: metrics-spec
|
||||||
#
|
#
|
||||||
# Calls OPTIONS /v1/query/_mpl to retrieve the complete metrics query
|
# Retrieves the complete MPL query spec with syntax, operators, and examples.
|
||||||
# spec with syntax, operators, and examples. Read this before composing queries.
|
# Read this before composing queries.
|
||||||
#
|
|
||||||
# The dataset is needed to resolve the correct edge deployment URL.
|
|
||||||
#
|
|
||||||
# Example:
|
|
||||||
# metrics-spec prod my-metrics-dataset
|
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SPEC_URL="https://us-east-1.aws.edge.axiom.co/v1/query/_mpl"
|
||||||
|
|
||||||
DEPLOYMENT="${1:-}"
|
# Match the timeout convention used by axiom-api so a stalled edge can't hang
|
||||||
DATASET="${2:-}"
|
# the caller indefinitely. Override via AXIOM_CONNECT_TIMEOUT / AXIOM_MAX_TIME.
|
||||||
|
curl -sS -X OPTIONS -H "Accept: text/markdown" \
|
||||||
if [[ -z "$DEPLOYMENT" || -z "$DATASET" ]]; then
|
--connect-timeout "${AXIOM_CONNECT_TIMEOUT:-10}" \
|
||||||
echo "Usage: metrics-spec <deployment> <dataset>" >&2
|
--max-time "${AXIOM_MAX_TIME:-120}" \
|
||||||
exit 1
|
"$SPEC_URL"
|
||||||
fi
|
|
||||||
|
|
||||||
RESOLVED_URL=$("$SCRIPT_DIR/resolve-url" "$DEPLOYMENT" "$DATASET" 2>/dev/null || true)
|
|
||||||
if [[ -n "$RESOLVED_URL" ]]; then
|
|
||||||
export AXIOM_URL_OVERRIDE="$RESOLVED_URL"
|
|
||||||
fi
|
|
||||||
|
|
||||||
AXIOM_ACCEPT="text/markdown" "$SCRIPT_DIR/axiom-api" "$DEPLOYMENT" OPTIONS "/v1/query/_mpl"
|
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ echo ""
|
|||||||
echo "Usage:"
|
echo "Usage:"
|
||||||
echo " scripts/datasets prod # List datasets"
|
echo " scripts/datasets prod # List datasets"
|
||||||
echo " scripts/datasets prod --kind otel:metrics:v1 # List metrics datasets"
|
echo " scripts/datasets prod --kind otel:metrics:v1 # List metrics datasets"
|
||||||
echo " scripts/metrics-spec prod <dataset> # Fetch query spec"
|
echo " scripts/metrics-spec # Fetch query spec"
|
||||||
echo " scripts/metrics-info prod <dataset> metrics # List metrics"
|
echo " scripts/metrics-info prod <dataset> metrics # List metrics"
|
||||||
echo " scripts/metrics-info prod <dataset> tags # List tags"
|
echo " scripts/metrics-info prod <dataset> tags # List tags"
|
||||||
echo " scripts/metrics-query prod '<mpl>' '<start>' '<end>' # Run query"
|
echo " scripts/metrics-query prod '<mpl>' '<start>' '<end>' # Run query"
|
||||||
|
|||||||
+4
-4
@@ -17,7 +17,7 @@
|
|||||||
"source": "axiomhq/skills",
|
"source": "axiomhq/skills",
|
||||||
"sourceType": "github",
|
"sourceType": "github",
|
||||||
"skillPath": "skills/building-dashboards/SKILL.md",
|
"skillPath": "skills/building-dashboards/SKILL.md",
|
||||||
"computedHash": "331e80b2aa6d7a8a9d5ec69c50d5a843f125534ed01d93b9cc916a5a92ca9552"
|
"computedHash": "84a130aab324297fe3fe1203b56743359732d4386dff6f0d065726d175b6e413"
|
||||||
},
|
},
|
||||||
"controlling-costs": {
|
"controlling-costs": {
|
||||||
"source": "axiomhq/skills",
|
"source": "axiomhq/skills",
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
"source": "openclaw/carapace",
|
"source": "openclaw/carapace",
|
||||||
"sourceType": "github",
|
"sourceType": "github",
|
||||||
"skillPath": "openclaw-carapace/SKILL.md",
|
"skillPath": "openclaw-carapace/SKILL.md",
|
||||||
"computedHash": "6d7a33003875107ab35d4ac978753cc95b38b38aaaa306ae3db54f935d5451f7"
|
"computedHash": "84d8602ecdf8e1ad39cd4c5ae8f1165da571619746ab705a471d6fdfe7f4ef3e"
|
||||||
},
|
},
|
||||||
"openclaw-design": {
|
"openclaw-design": {
|
||||||
"source": "openclaw/carapace",
|
"source": "openclaw/carapace",
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
"source": "openclaw/carapace",
|
"source": "openclaw/carapace",
|
||||||
"sourceType": "github",
|
"sourceType": "github",
|
||||||
"skillPath": "openclaw-design-system/SKILL.md",
|
"skillPath": "openclaw-design-system/SKILL.md",
|
||||||
"computedHash": "6d94e825c58fc5650a532e4ed8bc96208ede8a166089598d1924393cc46f3275"
|
"computedHash": "e7ff42933430aaf28bd2ca7a9e861c62e4aadddc7604d777118b7fec3304b042"
|
||||||
},
|
},
|
||||||
"openclaw-marketing-pages": {
|
"openclaw-marketing-pages": {
|
||||||
"source": "openclaw/carapace",
|
"source": "openclaw/carapace",
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
"source": "axiomhq/skills",
|
"source": "axiomhq/skills",
|
||||||
"sourceType": "github",
|
"sourceType": "github",
|
||||||
"skillPath": "skills/query-metrics/SKILL.md",
|
"skillPath": "skills/query-metrics/SKILL.md",
|
||||||
"computedHash": "3da9845936d9cb99f8fa6fdcdced06cc25eb9f16aa401e4838991d7f039fc177"
|
"computedHash": "955a35d22ca2244c14abafe10c57b8314ddbbc5e9ce1040ddc91edf6cfb05f8e"
|
||||||
},
|
},
|
||||||
"sentry-fix-issues": {
|
"sentry-fix-issues": {
|
||||||
"source": "getsentry/sentry-for-ai",
|
"source": "getsentry/sentry-for-ai",
|
||||||
|
|||||||
Reference in New Issue
Block a user