/ lib / ocaml / Prometheus.res
Prometheus.res
  1  // SPDX-License-Identifier: MIT
  2  // SPDX-FileCopyrightText: 2025 Jonathan D.A. Jewell
  3  
  4  /// Prometheus metrics adapter
  5  /// Connects to Prometheus HTTP API for metrics queries
  6  
  7  open Adapter
  8  
  9  let baseUrl = ref(Deno.Env.getWithDefault("PROMETHEUS_URL", "http://localhost:9090"))
 10  let connected = ref(false)
 11  
 12  let name = "prometheus"
 13  let description = "Prometheus metrics server adapter"
 14  
 15  let connect = async () => {
 16    // Test connection to Prometheus
 17    let url = baseUrl.contents ++ "/api/v1/status/buildinfo"
 18    switch await Deno.Fetch.get(url) {
 19    | Ok(_) => connected := true
 20    | Error(e) => Exn.raiseError(`Failed to connect to Prometheus: ${e}`)
 21    }
 22  }
 23  
 24  let disconnect = async () => {
 25    connected := false
 26  }
 27  
 28  let isConnected = async () => connected.contents
 29  
 30  // Query instant metrics
 31  let queryHandler = async (args: dict<JSON.t>): JSON.t => {
 32    let query = switch Dict.get(args, "query") {
 33    | Some(JSON.String(q)) => q
 34    | _ => Exn.raiseError("query parameter is required")
 35    }
 36    let time = switch Dict.get(args, "time") {
 37    | Some(JSON.String(t)) => Some(t)
 38    | _ => None
 39    }
 40  
 41    let url = switch time {
 42    | Some(t) => `${baseUrl.contents}/api/v1/query?query=${query}&time=${t}`
 43    | None => `${baseUrl.contents}/api/v1/query?query=${query}`
 44    }
 45  
 46    switch await Deno.Fetch.get(url) {
 47    | Ok(data) => data
 48    | Error(e) => Exn.raiseError(e)
 49    }
 50  }
 51  
 52  // Query range metrics
 53  let queryRangeHandler = async (args: dict<JSON.t>): JSON.t => {
 54    let query = switch Dict.get(args, "query") {
 55    | Some(JSON.String(q)) => q
 56    | _ => Exn.raiseError("query parameter is required")
 57    }
 58    let start = switch Dict.get(args, "start") {
 59    | Some(JSON.String(s)) => s
 60    | _ => Exn.raiseError("start parameter is required")
 61    }
 62    let end_ = switch Dict.get(args, "end") {
 63    | Some(JSON.String(e)) => e
 64    | _ => Exn.raiseError("end parameter is required")
 65    }
 66    let step = switch Dict.get(args, "step") {
 67    | Some(JSON.String(s)) => s
 68    | _ => "15s"
 69    }
 70  
 71    let url = `${baseUrl.contents}/api/v1/query_range?query=${query}&start=${start}&end=${end_}&step=${step}`
 72  
 73    switch await Deno.Fetch.get(url) {
 74    | Ok(data) => data
 75    | Error(e) => Exn.raiseError(e)
 76    }
 77  }
 78  
 79  // List all series matching a selector
 80  let seriesHandler = async (args: dict<JSON.t>): JSON.t => {
 81    let match_ = switch Dict.get(args, "match") {
 82    | Some(JSON.String(m)) => m
 83    | _ => Exn.raiseError("match parameter is required")
 84    }
 85  
 86    let url = `${baseUrl.contents}/api/v1/series?match[]=${match_}`
 87  
 88    switch await Deno.Fetch.get(url) {
 89    | Ok(data) => data
 90    | Error(e) => Exn.raiseError(e)
 91    }
 92  }
 93  
 94  // List all label names
 95  let labelsHandler = async (_args: dict<JSON.t>): JSON.t => {
 96    let url = `${baseUrl.contents}/api/v1/labels`
 97  
 98    switch await Deno.Fetch.get(url) {
 99    | Ok(data) => data
100    | Error(e) => Exn.raiseError(e)
101    }
102  }
103  
104  // List label values for a specific label
105  let labelValuesHandler = async (args: dict<JSON.t>): JSON.t => {
106    let label = switch Dict.get(args, "label") {
107    | Some(JSON.String(l)) => l
108    | _ => Exn.raiseError("label parameter is required")
109    }
110  
111    let url = `${baseUrl.contents}/api/v1/label/${label}/values`
112  
113    switch await Deno.Fetch.get(url) {
114    | Ok(data) => data
115    | Error(e) => Exn.raiseError(e)
116    }
117  }
118  
119  // Get current targets
120  let targetsHandler = async (_args: dict<JSON.t>): JSON.t => {
121    let url = `${baseUrl.contents}/api/v1/targets`
122  
123    switch await Deno.Fetch.get(url) {
124    | Ok(data) => data
125    | Error(e) => Exn.raiseError(e)
126    }
127  }
128  
129  // Get current alerts
130  let alertsHandler = async (_args: dict<JSON.t>): JSON.t => {
131    let url = `${baseUrl.contents}/api/v1/alerts`
132  
133    switch await Deno.Fetch.get(url) {
134    | Ok(data) => data
135    | Error(e) => Exn.raiseError(e)
136    }
137  }
138  
139  // Get alerting rules
140  let rulesHandler = async (_args: dict<JSON.t>): JSON.t => {
141    let url = `${baseUrl.contents}/api/v1/rules`
142  
143    switch await Deno.Fetch.get(url) {
144    | Ok(data) => data
145    | Error(e) => Exn.raiseError(e)
146    }
147  }
148  
149  let tools: dict<toolDef> = Dict.fromArray([
150    ("prometheus_query", {
151      description: "Execute an instant PromQL query",
152      params: Dict.fromArray([
153        ("query", stringParam(~description="PromQL query expression")),
154        ("time", stringParam(~description="Evaluation timestamp (RFC3339 or Unix timestamp)")),
155      ]),
156      handler: queryHandler,
157    }),
158    ("prometheus_query_range", {
159      description: "Execute a range PromQL query",
160      params: Dict.fromArray([
161        ("query", stringParam(~description="PromQL query expression")),
162        ("start", stringParam(~description="Start timestamp (RFC3339 or Unix)")),
163        ("end", stringParam(~description="End timestamp (RFC3339 or Unix)")),
164        ("step", stringParam(~description="Query step (e.g., 15s, 1m)")),
165      ]),
166      handler: queryRangeHandler,
167    }),
168    ("prometheus_series", {
169      description: "List time series matching a selector",
170      params: Dict.fromArray([
171        ("match", stringParam(~description="Series selector (e.g., up{job=\"prometheus\"})")),
172      ]),
173      handler: seriesHandler,
174    }),
175    ("prometheus_labels", {
176      description: "List all label names",
177      params: Dict.make(),
178      handler: labelsHandler,
179    }),
180    ("prometheus_label_values", {
181      description: "List values for a specific label",
182      params: Dict.fromArray([
183        ("label", stringParam(~description="Label name")),
184      ]),
185      handler: labelValuesHandler,
186    }),
187    ("prometheus_targets", {
188      description: "Get current scrape targets and their status",
189      params: Dict.make(),
190      handler: targetsHandler,
191    }),
192    ("prometheus_alerts", {
193      description: "Get current active alerts",
194      params: Dict.make(),
195      handler: alertsHandler,
196    }),
197    ("prometheus_rules", {
198      description: "Get alerting and recording rules",
199      params: Dict.make(),
200      handler: rulesHandler,
201    }),
202  ])