/** * The budget run: one run of a Datadog Standard indexing budget held month * to date. X is the whole account's Standard bill. * * Each run: * 1. reads what Standard indexing counted this month for the whole account * (`datadog.estimated_usage.logs.ingested_events`, indexed events only, * by index) and what the Receiver itself sent to Standard (its * `routeState` series, by container), so lines outside the Receiver are * known; * 2. holds each service with its own number (`service_budgets`) to that * number, and sets the rate for the rest of the Receiver's services to * (X - their numbers - spent - what lines outside the Receiver will * add) / hours left; * 3. reads every statement's events an hour from the Receiver's series and * gives each service its rate, noisiest statements held first; * 4. divides each service's limit and total by the nodes its pods run on * (discovered on this run, or `nodes_by_container` in policy.yaml); * 5. writes caps.csv, actions.csv and totals.csv (a `*` row for every * service without a row of its own) and config-generation.csv, to the * policy ConfigMap or the gitops repo. * * Units are events: Datadog bills Standard per event, and the Receiver is * installed with `rateReceiverUnit events` (engine 1.1.146). * * A failed read changes nothing: the files from the last run stay in force, * and the run says so and exits non-zero. */ import { type DatadogMetricsCreds, type DdPointSeries } from './datadog-metrics-read.js'; import type { DollarBudget } from './policy-loader.js'; import type { K8sApi } from './k8s-in-cluster.js'; /** Indexed events by service and index, hourly (the series the spike audit reads). */ export declare const STANDARD_USAGE_QUERY = "sum:datadog.estimated_usage.logs.ingested_events{datadog_is_excluded:false} by {service,datadog_index}.as_count().rollup(sum, 3600)"; /** * Engine 1.1.146 posts its counters as distributions under `log10x.` * (config `pipelines/run/output/metric/datadog/config.yaml`, metricPrefix). */ export declare const DD_ALL_EVENTS_DISTRIBUTION = "log10x.all_events_summaryVolume"; export declare const DD_EMITTED_EVENTS_DISTRIBUTION = "log10x.emitted_events_summaryVolume"; /** * Every statement's events, hourly, by hash, name, container and level. A * series is at most 1,500 points (docs.datadoghq.com/dashboards/functions/ * rollup/); a 24-hour lookback is 25. Hourly buckets, summed over the span * they cover: Datadog aligns a rollup bucket to its interval, so one bucket * the size of the lookback would start before the window and be lost. */ export declare function patternEventsQuery(envId?: string): string; /** Events an hour from a series of hourly buckets: their sum over the hours they span, whatever their alignment. */ export declare function hourlyRate(points: Array<[number, number]>): number; /** Lines the Receiver emitted, hourly, by where each went and by container. */ export declare function receiverRouteQuery(envId?: string): string; export type BudgetAction = 'tier_down' | 'offload' | 'pass'; export interface PatternRate { container: string; pattern: string; /** The statement's name (message_pattern), the mute file's key. */ name?: string; severity: string; eventsPerHour: number; } export interface BudgetInput { budget: DollarBudget; nowSec: number; /** Indexed events this month, by service and datadog_index. */ usage: DdPointSeries[]; /** The Receiver's emitted lines this month, hourly, by routestate. */ routes: DdPointSeries[]; /** Every statement's events an hour over the lookback. */ patterns: PatternRate[]; /** Nodes each container's pods run on. */ instances: Record; /** Receivers in the fleet, for the `*` row. */ nodesTotal: number; /** Where the node counts came from, stated in the report. */ instanceBasis: 'discovery' | 'policy'; exceptions: string[]; targetServices: string[]; /** * Statements a manual mute protects (sample rate 1): they always pass and * count toward their service's total, so the other lines give way first. */ protectedPatterns?: ReadonlySet; /** Lines the Receiver moved off Standard this month, by route; default from `routes` through the last usage hour. */ movedEvents?: { tier_down: number; offload: number; }; } export interface ServiceRow { container: string; action: BudgetAction; instances: number; /** Fleet events an hour the service sends now. */ eventsPerHour: number; /** Fleet events an hour Standard keeps from it under this run: its total and what passes past it. */ allowancePerHour: number; /** Fleet events an hour its total admits. */ totalPerHour: number; /** Per-statement limit, events per window per node; 0 exempts the service. */ cap: number; /** Service total, events per window per node. */ total: number; /** Fleet events an hour from statements a mute protects. */ protectedPerHour: number; /** A service with its own number: dollars a month, spent so far, and whether it is used up. */ ownBudget?: { usdMonthly: number; spentUsd: number; exhausted: boolean; datadogService: string; }; } export interface BudgetPlan { monthStartSec: number; monthEndSec: number; /** End of the last hour Datadog's usage metric returned. */ dataThroughSec: number; hoursLeft: number; budgetEvents: number; spentEvents: number; spentUsd: number; outsideEvents: number; outsideProjectedMonthEvents: number; /** Fleet events an hour left for the Receiver's budgeted services. */ receiverAllowancePerHour: number; /** Fleet events an hour each statement keeps when the budget binds; undefined under it. */ statementLevelPerHour?: number; /** Fleet events an hour the held services keep whatever their limits: the sample and keep-count lines past their totals. */ floorPerHour: number; /** True when that floor alone exceeds what the budget leaves. */ floorExceedsBudget: boolean; exhausted: boolean; overBudget: boolean; rows: ServiceRow[]; star: { action: BudgetAction; instances: number; cap: number; total: number; allowancePerHour: number; }; /** The one line: Standard held at X, and what moved this month priced where it went. */ heldLine: string; lines: string[]; warnings: string[]; } /** A fleet rate (events an hour) as events per regulator window per node, at least 1. */ export declare function perWindowPerNode(eventsPerHour: number, instances: number): number; export declare function planBudget(input: BudgetInput): BudgetPlan; /** * Standard held at X, and what the Receiver moved off it this month, priced * where it went: Flex at the list storage rate (compute unpriced, left out), * S3 off the Datadog bill. Each also at the Standard rate it did not pay. */ export declare function ceilingReportLine(input: { budgetUsd: number; spentUsd: number; standardUsdPerMillion: number; flexEvents: number; s3Events: number; }): string; export interface BudgetFiles { 'caps.csv': string; 'actions.csv': string; 'totals.csv': string; 'config-generation.csv': string; } /** The four files the Receiver reads. Rows: `,::`; `*` last. */ export declare function renderBudgetFiles(plan: { rows: ReadonlyArray>; star: Pick; }): BudgetFiles; export declare function renderBudgetReport(plan: BudgetPlan, budget: DollarBudget, nowSec: number, extra?: string[]): string; export type DdQuery = (creds: DatadogMetricsCreds, query: string, fromSec: number, toSec: number) => Promise; export interface BudgetReadDeps { creds?: DatadogMetricsCreds | null; query?: DdQuery; } export interface BudgetReads { usage: DdPointSeries[]; routes: DdPointSeries[]; patterns: PatternRate[]; } /** The three Datadog reads. Throws on a missing key or a failed query: the caller keeps the last files. */ export declare function readBudgetInputs(nowSec: number, lookbackHours: number, envId: string | undefined, deps?: BudgetReadDeps): Promise; /** * Statements a manual mute protects: rows of the mute file * (`,[:][:]`, header * `fieldSet,value`) with a sample rate of 1 that have not expired. */ export declare function protectedPatterns(mutesCsv: string | undefined, nowSec: number): Set; /** * Node counts from the cluster, for a DaemonSet Receiver: the Receiver's * nodes are those running a pod in its namespace whose env names the policy * ConfigMap (`K8S_CONFIGMAP`, written on every Receiver install); a service's * count is the distinct nodes running both it and a Receiver. */ export declare function discoverInstances(k8s: K8sApi, namespace: string, configMap: string): Promise<{ instances: Record; nodesTotal: number; }>;