Skip to main content

netget/client/prometheus_remote_write/
actions.rs

1use crate::llm::actions::{
2    client_trait::{Client, ClientActionResult},
3    protocol_trait::Protocol,
4    ActionDefinition, ParameterDefinition, StartupExamples,
5};
6use crate::protocol::log_template::LogTemplate;
7use crate::protocol::{
8    metadata::{DevelopmentState, ProtocolMetadataV2},
9    ConnectContext, EventType,
10};
11use crate::server::prometheus_remote_write::{
12    actions::parameter,
13    codec::{self, WriteBatch, DEFAULT_PATH, DEFAULT_RETRY_429},
14};
15use crate::state::app_state::AppState;
16use anyhow::{bail, Context, Result};
17use serde_json::{json, Value};
18use std::sync::LazyLock;
19#[derive(Default)]
20pub struct PrometheusRemoteWriteClientProtocol;
21impl PrometheusRemoteWriteClientProtocol {
22    pub fn new() -> Self {
23        Self
24    }
25}
26pub fn write_action() -> ActionDefinition {
27    ActionDefinition { name:"write_remote_samples".into(), description:"Submit one typed published1.0 float-sample batch. Retry5xx/transport failures with backoff while connected;429 opt-in. Caller must preserve per-series order across batches and supply stale markers when appropriate.".into(), parameters:vec![parameter("batch","object","series[{labels:{name:nonempty UTF-8 value},samples:[{timestamp_ms:i64,value:finite number|nan|+inf|-inf|stale}]}]; empty series array is a v1 probe",true)], example:json!({"type":"write_remote_samples","batch":{"series":[{"labels":{"__name__":"example_temperature","site":"one"},"samples":[{"timestamp_ms":1700000000000i64,"value":21.5}]}]}}), log_template:Some(LogTemplate::new().with_info("Remote write action {type} queued")) }
28}
29fn disconnect() -> ActionDefinition {
30    ActionDefinition { name:"disconnect".into(),description:"Cancel logical session, pending exchange/backoff and parked handler; unsent volatile samples are lost".into(),parameters:vec![],example:json!({"type":"disconnect"}),log_template:Some(LogTemplate::new().with_info("Remote write action {type} queued")) }
31}
32pub static REMOTE_WRITE_CONNECTED_EVENT: LazyLock<EventType> = LazyLock::new(|| {
33    EventType::new(
34        "remote_write_connected",
35        "Logical HTTP origin ready; no TCP until a validated write",
36        write_action().example,
37    )
38    .with_parameters(vec![parameter(
39        "remote_addr",
40        "string",
41        "Collector HTTP origin address",
42        true,
43    )])
44    .with_actions(vec![write_action(), disconnect()])
45});
46pub static REMOTE_WRITE_RESPONSE_EVENT: LazyLock<EventType> = LazyLock::new(|| {
47    EventType::new("remote_write_response","Final HTTP outcome: any2xx accepted;4xx other than opted-in429 terminal.5xx retry until acceptance/cancellation. Response body ignored. No durability/exactly-once promise.",write_action().example).with_parameters(vec![parameter("series_count","number","Submitted series",true),parameter("sample_count","number","Submitted float samples",true),parameter("status","number","Terminal HTTP status",true),parameter("accepted","boolean","True for any terminal 2xx response; durability is unconfirmed",true),parameter("attempts","number","HTTP attempts including retries",true),parameter("durable_storage_confirmed","boolean","Always false; HTTP acceptance alone is insufficient",true)]).with_actions(vec![write_action(),disconnect()])
48});
49impl Protocol for PrometheusRemoteWriteClientProtocol {
50    fn protocol_name(&self) -> &'static str {
51        "PrometheusRemoteWrite"
52    }
53    fn stack_name(&self) -> &'static str {
54        "ETH>IP>TCP>HTTP>PrometheusRemoteWrite"
55    }
56    fn keywords(&self) -> Vec<&'static str> {
57        vec![
58            "prometheus-remote-write",
59            "remote-write",
60            "prometheus-write",
61        ]
62    }
63    fn group_name(&self) -> &'static str {
64        "Network Services"
65    }
66    fn description(&self) -> &'static str {
67        "Typed published remote write1.0 float-sample emitter with cancellable retry backoff"
68    }
69    fn example_prompt(&self) -> &'static str {
70        "Send remote write float samples to localhost9090"
71    }
72    fn get_sync_actions(&self) -> Vec<ActionDefinition> {
73        vec![]
74    }
75    fn get_async_actions(&self, _: &AppState) -> Vec<ActionDefinition> {
76        vec![write_action(), disconnect()]
77    }
78    fn get_event_types(&self) -> Vec<EventType> {
79        vec![
80            REMOTE_WRITE_CONNECTED_EVENT.clone(),
81            REMOTE_WRITE_RESPONSE_EVENT.clone(),
82        ]
83    }
84    fn get_startup_parameters(&self) -> Vec<ParameterDefinition> {
85        vec![
86            ParameterDefinition {
87                name: "auth_token".into(),
88                type_hint: "string".into(),
89                description: "Optional Bearer token<=1024 printable ASCII; excluded from handlers"
90                    .into(),
91                required: false,
92                example: json!("collector-secret"),
93                default: None,
94            },
95            ParameterDefinition {
96                name: "path".into(),
97                type_hint: "string".into(),
98                description: "Absolute HTTP write path<=1024 bytes, no query/fragment".into(),
99                required: false,
100                example: json!("/receive"),
101                default: Some(json!(DEFAULT_PATH)),
102            },
103            ParameterDefinition {
104                name: "retry_429".into(),
105                type_hint: "boolean".into(),
106                description: "Retry429 like5xx while connected; otherwise429 is terminal".into(),
107                required: false,
108                example: json!(true),
109                default: Some(json!(DEFAULT_RETRY_429)),
110            },
111        ]
112    }
113    fn metadata(&self) -> ProtocolMetadataV2 {
114        ProtocolMetadataV2::builder().state(DevelopmentState::Experimental).well_known_port(9090).implementation("Native bounded public1.0 protobuf/Snappy block encoding; owned Hyper HTTP/1.1 exchange and exponential backoff")
115            .llm_control("Connected/typed terminal-response events and common shared memory; injection/disconnect independent of parked handlers and retry IO")
116            .e2e_testing("Required pinned official Prometheus3.15.0 TSDB readback; actual independent Prometheus sender; literal codec, retry/errors and owned cancellation checks")
117            .notes("Published1.0 float-sample selected scope, not full Prometheus agent/conformance. Cleartext HTTP origin and configurable path(default/api/v1/write), optional Bearer; required headers fixed, no custom override.256KiB wire/decoded body,128series/2048samples/32labels,128byte legacy ASCII names/2048byte nonempty UTF-8 values. One in-flight batch,32queued events/actions,followup depth8,64KiB ignored response body,64/32KiB headers,10s each exchange.5xx and IO/framing failures retry indefinitely while connected with100ms..5s exponential backoff;429 optional, other HTTP statuses terminal,any2xx accepted,redirects not followed,Retry-After ignored. Identical retry body can duplicate remotely. Per-batch sample order validated; caller owns cross-batch order/stale lifecycle. Cancellation drops volatile samples; no WAL,dedup,storage,scraping/discovery,automatic stale detection,metadata/exemplars/native histograms,2.0,TLS/basic/cloud auth,queries,fuzz orpcap. Logical local address0.0.0.0:0;freshTCPperattempt.").build()
118    }
119    fn get_startup_examples(&self) -> StartupExamples {
120        StartupExamples::new(
121            json!({"type":"open_client","base_stack":"prometheus-remote-write","remote_addr":"http://localhost:9090","instruction":"Send one float batch"}),
122            json!({"type":"open_client","base_stack":"prometheus-remote-write","remote_addr":"http://localhost:9090","event_handlers":[{"event_pattern":"remote_write_connected","handler":{"type":"script","language":"python","code":"import json,sys\njson.load(sys.stdin)\nprint(json.dumps({'actions':[{'type':'disconnect'}]}))"}}]}),
123            json!({"type":"open_client","base_stack":"prometheus-remote-write","remote_addr":"http://localhost:9090","event_handlers":[{"event_pattern":"remote_write_connected","handler":{"type":"static","actions":[write_action().example]}}]}),
124        )
125    }
126}
127impl Client for PrometheusRemoteWriteClientProtocol {
128    fn connect(
129        &self,
130        ctx: ConnectContext,
131    ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<std::net::SocketAddr>> + Send>>
132    {
133        Box::pin(super::PrometheusRemoteWriteClient::connect(ctx))
134    }
135    fn execute_action(&self, action: Value) -> Result<ClientActionResult> {
136        match action["type"].as_str() {
137            Some("write_remote_samples") => {
138                let batch: WriteBatch =
139                    serde_json::from_value(action.get("batch").context("batch required")?.clone())?;
140                codec::encode_batch(&batch)?;
141                Ok(ClientActionResult::Custom {
142                    name: "write_remote_samples".into(),
143                    data: serde_json::to_value(batch)?,
144                })
145            }
146            Some("disconnect") => Ok(ClientActionResult::Disconnect),
147            _ => bail!("unknown remote write emitter action"),
148        }
149    }
150}