chore: update skills (#3406)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
openclaw-barnacle[bot]
2026-08-11 10:51:38 -07:00
committed by GitHub
co-authored by github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
parent d9157142e9
commit 29ee5126de
17 changed files with 523 additions and 132 deletions
@@ -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`
+1 -1
View File
@@ -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
+52 -13
View File
@@ -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"
+108 -29
View File
@@ -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"
+1 -1
View File
@@ -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
View File
@@ -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",