| 1 | #!/usr/bin/env bash |
| 2 | # get-events.sh -- fetch events of interest from agent-events. |
| 3 | # |
| 4 | # Index-friendly defaults: 24h time, multi-value selections, |
| 5 | # auto version filter (latest stable + latest 3 nightlies). |
| 6 | # |
| 7 | # Output: JSON dump under |
| 8 | # <repo>/.local/audits/query-agent-events/<timestamp>.json |
| 9 | |
| 10 | set -euo pipefail |
| 11 | |
| 12 | usage() { |
| 13 | cat <<'EOF' |
| 14 | get-events.sh [options] |
| 15 | |
| 16 | Transport: |
| 17 | --via cloud|agent (default: cloud) |
| 18 | |
| 19 | Time window: |
| 20 | --since '<n>h ago'|'<n>d ago'|<seconds> (default: 24h ago) |
| 21 | --before now|<seconds> (default: now) |
| 22 | |
| 23 | Filters (all are AND'd; values within a flag are OR'd): |
| 24 | --health <classes> comma-separated; common: all, crash, healthy |
| 25 | (default: all) |
| 26 | "crash" -> crash-first,crash-loop,crash-repeated,crash-entered |
| 27 | "healthy" -> healthy-first,healthy-loop,healthy-repeated,healthy-recovered |
| 28 | --exit-cause <causes> comma-separated; common: all, fatal, signal, graceful |
| 29 | (default: all) |
| 30 | "fatal" -> deliberate-exit class (OOM, disk full, etc.) |
| 31 | "signal" -> killed-hard variants |
| 32 | "graceful"-> exit instructed/updated/shutdown/no last status |
| 33 | --signal <values> comma-separated AE_FATAL_SIGNAL_CODE values |
| 34 | example: SIGSEGV/SEGV_MAPERR,SIGBUS/BUS_OBJERR |
| 35 | --function <names> comma-separated AE_FATAL_FUNCTION values |
| 36 | --version <spec> "auto" (default), "all", or a regex |
| 37 | auto -> latest stable + latest 3 nightlies (computed) |
| 38 | all -> no version filter |
| 39 | else -> regex applied client-side after fetch |
| 40 | (multi-value selections require explicit values; |
| 41 | use --versions for that) |
| 42 | --versions <list> explicit comma-separated AE_AGENT_VERSION values |
| 43 | (overrides --version's auto/all/regex) |
| 44 | --arch <values> comma-separated AE_HOST_ARCHITECTURE values |
| 45 | --os-family <values> comma-separated AE_OS_FAMILY values |
| 46 | --query <fts> residual FTS narrower (after structured filters) |
| 47 | --facets <names> comma-separated; included in response for grouping |
| 48 | |
| 49 | Response control: |
| 50 | --last N page size, default 500 |
| 51 | --histogram FIELD add a histogram bucket on FIELD |
| 52 | |
| 53 | Output: |
| 54 | --output PATH path to write the JSON dump (default: auto under .local/audits/...) |
| 55 | |
| 56 | Other: |
| 57 | -h, --help this message |
| 58 | -v, --verbose show the constructed payload before fetching |
| 59 | EOF |
| 60 | } |
| 61 | |
| 62 | # --------------------------------------------------------------- |
| 63 | # Argument parsing. |
| 64 | |
| 65 | VIA=cloud |
| 66 | SINCE='24h ago' |
| 67 | BEFORE=now |
| 68 | HEALTH=all |
| 69 | EXIT_CAUSE=all |
| 70 | SIGNAL= |
| 71 | FUNCTION= |
| 72 | VERSION=auto |
| 73 | VERSIONS_EXPLICIT= |
| 74 | ARCH= |
| 75 | OS_FAMILY= |
| 76 | QUERY= |
| 77 | FACETS= |
| 78 | LAST=500 |
| 79 | HISTOGRAM= |
| 80 | OUTPUT= |
| 81 | VERBOSE=0 |
| 82 | |
| 83 | while [ $# -gt 0 ]; do |
| 84 | case "$1" in |
| 85 | --via) VIA="$2"; shift 2 ;; |
| 86 | --since) SINCE="$2"; shift 2 ;; |
| 87 | --before) BEFORE="$2"; shift 2 ;; |
| 88 | --health) HEALTH="$2"; shift 2 ;; |
| 89 | --exit-cause) EXIT_CAUSE="$2"; shift 2 ;; |
| 90 | --signal) SIGNAL="$2"; shift 2 ;; |
| 91 | --function) FUNCTION="$2"; shift 2 ;; |
| 92 | --version) VERSION="$2"; shift 2 ;; |
| 93 | --versions) VERSIONS_EXPLICIT="$2"; shift 2 ;; |
| 94 | --arch) ARCH="$2"; shift 2 ;; |
| 95 | --os-family) OS_FAMILY="$2"; shift 2 ;; |
| 96 | --query) QUERY="$2"; shift 2 ;; |
| 97 | --facets) FACETS="$2"; shift 2 ;; |
| 98 | --last) LAST="$2"; shift 2 ;; |
| 99 | --histogram) HISTOGRAM="$2"; shift 2 ;; |
| 100 | --output) OUTPUT="$2"; shift 2 ;; |
| 101 | -v|--verbose) VERBOSE=1; shift ;; |
| 102 | -h|--help) usage; exit 0 ;; |
| 103 | *) echo "Unknown option: $1" >&2; usage >&2; exit 2 ;; |
| 104 | esac |
| 105 | done |
| 106 | |
| 107 | # --------------------------------------------------------------- |
| 108 | # Lib + env. |
| 109 | |
| 110 | # shellcheck source=SCRIPTDIR/_lib.sh disable=SC1091 |
| 111 | source "$(cd "$(dirname "$0")" && pwd)/_lib.sh" |
| 112 | agentevents_load_env |
| 113 | |
| 114 | # --------------------------------------------------------------- |
| 115 | # Time spec -> relative seconds. |
| 116 | |
| 117 | parse_time() { |
| 118 | local s="$1" |
| 119 | case "$s" in |
| 120 | now) echo 0 ;; |
| 121 | *' ago') # "24h ago", "7d ago", "30m ago" |
| 122 | local body="${s% ago}" |
| 123 | case "$body" in |
| 124 | *h) printf -- '-%d' "$(( ${body%h} * 3600 ))" ;; |
| 125 | *d) printf -- '-%d' "$(( ${body%d} * 86400 ))" ;; |
| 126 | *m) printf -- '-%d' "$(( ${body%m} * 60 ))" ;; |
| 127 | *) printf -- '-%d' "${body}" ;; |
| 128 | esac |
| 129 | ;; |
| 130 | -*|0*|[1-9]*) echo "$s" ;; |
| 131 | *) echo "Invalid time spec: $s" >&2; exit 2 ;; |
| 132 | esac |
| 133 | } |
| 134 | |
| 135 | AFTER=$(parse_time "$SINCE") |
| 136 | BEFORE_PARSED=$(parse_time "$BEFORE") |
| 137 | |
| 138 | # --------------------------------------------------------------- |
| 139 | # Build selections. |
| 140 | |
| 141 | declare -a SELECTION_KEYS=() |
| 142 | declare -A SELECTION_VALUES=() |
| 143 | |
| 144 | set_selection() { |
| 145 | local key="$1" |
| 146 | local csv="$2" |
| 147 | [ -z "$csv" ] && return |
| 148 | SELECTION_KEYS+=("$key") |
| 149 | SELECTION_VALUES[$key]="$csv" |
| 150 | } |
| 151 | |
| 152 | case "$HEALTH" in |
| 153 | all|"") ;; |
| 154 | crash) set_selection AE_AGENT_HEALTH "crash-first,crash-loop,crash-repeated,crash-entered" ;; |
| 155 | healthy) set_selection AE_AGENT_HEALTH "healthy-first,healthy-loop,healthy-repeated,healthy-recovered" ;; |
| 156 | *) set_selection AE_AGENT_HEALTH "$HEALTH" ;; |
| 157 | esac |
| 158 | |
| 159 | case "$EXIT_CAUSE" in |
| 160 | all|"") ;; |
| 161 | fatal) |
| 162 | set_selection AE_EXIT_CAUSE \ |
| 163 | "no last status,out of memory,disk full,disk almost full,disk read-only,already running,fatal on start,fatal on exit,fatal and exit,exit timeout" |
| 164 | ;; |
| 165 | signal) |
| 166 | set_selection AE_EXIT_CAUSE \ |
| 167 | "deadly signal,deadly signal on start,deadly signal on exit,deadly signal and exit,killed hard,killed hard on start,killed hard on shutdown,killed hard on exit,killed hard on update,killed hard low ram,killed fatal" |
| 168 | ;; |
| 169 | graceful) |
| 170 | set_selection AE_EXIT_CAUSE \ |
| 171 | "exit instructed,exit and updated,exit on system shutdown,exit to update,exit no reason,no last status" |
| 172 | ;; |
| 173 | *) |
| 174 | set_selection AE_EXIT_CAUSE "$EXIT_CAUSE" |
| 175 | ;; |
| 176 | esac |
| 177 | |
| 178 | [ -n "$SIGNAL" ] && set_selection AE_FATAL_SIGNAL_CODE "$SIGNAL" |
| 179 | [ -n "$FUNCTION" ] && set_selection AE_FATAL_FUNCTION "$FUNCTION" |
| 180 | [ -n "$ARCH" ] && set_selection AE_HOST_ARCHITECTURE "$ARCH" |
| 181 | [ -n "$OS_FAMILY" ] && set_selection AE_OS_FAMILY "$OS_FAMILY" |
| 182 | |
| 183 | # Version handling. |
| 184 | if [ -n "$VERSIONS_EXPLICIT" ]; then |
| 185 | set_selection AE_AGENT_VERSION "$VERSIONS_EXPLICIT" |
| 186 | elif [ "$VERSION" = "auto" ]; then |
| 187 | echo "[get-events] computing default version filter (latest stable + latest 3 nightlies)..." >&2 |
| 188 | versions_json="$(agentevents_compute_default_versions "$VIA" "${AFTER#-}")" |
| 189 | if [ "$(echo "$versions_json" | jq 'length')" -gt 0 ]; then |
| 190 | versions_csv="$(echo "$versions_json" | jq -r 'join(",")')" |
| 191 | echo "[get-events] auto versions: $versions_csv" >&2 |
| 192 | set_selection AE_AGENT_VERSION "$versions_csv" |
| 193 | else |
| 194 | echo "[get-events] auto version detection found no versions; proceeding without version filter" >&2 |
| 195 | fi |
| 196 | elif [ "$VERSION" = "all" ]; then |
| 197 | : # no filter |
| 198 | else |
| 199 | # Pattern -- best-effort: not multi-value selection, leave it |
| 200 | # to the caller to client-side-filter the dump after fetch. |
| 201 | echo "[get-events] --version <regex> is not pushed to the server; filter the resulting JSON with jq" >&2 |
| 202 | fi |
| 203 | |
| 204 | # --------------------------------------------------------------- |
| 205 | # Compose payload. |
| 206 | |
| 207 | ns="$(agentevents_namespace)" |
| 208 | |
| 209 | # Build selections object using jq. |
| 210 | SELECTIONS_JSON='{}' |
| 211 | for key in "${SELECTION_KEYS[@]}"; do |
| 212 | csv="${SELECTION_VALUES[$key]}" |
| 213 | SELECTIONS_JSON=$(echo "$SELECTIONS_JSON" | jq --arg k "$key" --arg v "$csv" \ |
| 214 | '.[$k] = ($v | split(","))') |
| 215 | done |
| 216 | |
| 217 | # Build the full payload. |
| 218 | PAYLOAD=$(jq -nc \ |
| 219 | --argjson after "$AFTER" \ |
| 220 | --argjson before "$BEFORE_PARSED" \ |
| 221 | --argjson last "$LAST" \ |
| 222 | --arg ns "$ns" \ |
| 223 | --argjson selections "$SELECTIONS_JSON" \ |
| 224 | --arg query "$QUERY" \ |
| 225 | --arg facets_csv "$FACETS" \ |
| 226 | --arg histogram "$HISTOGRAM" \ |
| 227 | ' |
| 228 | { |
| 229 | "after": $after, |
| 230 | "before": $before, |
| 231 | "last": $last, |
| 232 | "direction": "backward", |
| 233 | "__logs_sources": $ns |
| 234 | } |
| 235 | | (if ($selections | length) > 0 then .selections = $selections else . end) |
| 236 | | (if ($query | length) > 0 then .query = $query else . end) |
| 237 | | (if ($facets_csv | length) > 0 then .facets = ($facets_csv | split(",")) else . end) |
| 238 | | (if ($histogram | length) > 0 then .histogram = $histogram else . end) |
| 239 | ') |
| 240 | |
| 241 | if [ "$VERBOSE" -eq 1 ]; then |
| 242 | echo "[get-events] payload:" >&2 |
| 243 | echo "$PAYLOAD" | jq . >&2 |
| 244 | fi |
| 245 | |
| 246 | # --------------------------------------------------------------- |
| 247 | # Output path. |
| 248 | |
| 249 | if [ -z "$OUTPUT" ]; then |
| 250 | audit_dir="$(agentevents_audit_dir)" |
| 251 | OUTPUT="$audit_dir/$(date -u +%Y%m%dT%H%M%SZ).json" |
| 252 | fi |
| 253 | |
| 254 | # --------------------------------------------------------------- |
| 255 | # Fetch. |
| 256 | |
| 257 | echo "[get-events] fetching via $VIA (output: $OUTPUT)..." >&2 |
| 258 | agentevents_query_function "$VIA" "$PAYLOAD" > "$OUTPUT" |
| 259 | |
| 260 | rows="$(jq '.data | length // 0' "$OUTPUT" 2>/dev/null || echo 0)" |
| 261 | echo "[get-events] wrote $rows row(s) to $OUTPUT" >&2 |
| 262 | |
| 263 | # Print path on stdout for piping. |
| 264 | echo "$OUTPUT" |