mirror of
https://github.com/denoland/deno.git
synced 2025-03-04 09:57:11 -05:00
feat(http): add otel metrics (#28034)
Signed-off-by: Leo Kettmeir <crowlkats@toaxl.com> Co-authored-by: Luca Casonato <hello@lcas.dev>
This commit is contained in:
parent
f62fc9e81f
commit
bf79971c95
13 changed files with 1579 additions and 245 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -2045,6 +2045,7 @@ dependencies = [
|
|||
"deno_core",
|
||||
"deno_error",
|
||||
"deno_net",
|
||||
"deno_telemetry",
|
||||
"deno_websocket",
|
||||
"flate2",
|
||||
"http 0.2.12",
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
op_http_close_after_finish,
|
||||
op_http_get_request_headers,
|
||||
op_http_get_request_method_and_url,
|
||||
op_http_metric_handle_otel_error,
|
||||
op_http_read_request_body,
|
||||
op_http_request_on_cancel,
|
||||
op_http_serve,
|
||||
|
@ -89,6 +90,7 @@ import { SymbolAsyncDispose } from "ext:deno_web/00_infra.js";
|
|||
import {
|
||||
builtinTracer,
|
||||
enterSpan,
|
||||
METRICS_ENABLED,
|
||||
TRACING_ENABLED,
|
||||
} from "ext:deno_telemetry/telemetry.ts";
|
||||
import {
|
||||
|
@ -573,6 +575,9 @@ function mapToCallback(context, callback, onError) {
|
|||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (METRICS_ENABLED) {
|
||||
op_http_metric_handle_otel_error(req);
|
||||
}
|
||||
// deno-lint-ignore no-console
|
||||
console.error("Exception in onError while handling exception", error);
|
||||
response = internalServerError();
|
||||
|
|
|
@ -30,6 +30,7 @@ cache_control.workspace = true
|
|||
deno_core.workspace = true
|
||||
deno_error.workspace = true
|
||||
deno_net.workspace = true
|
||||
deno_telemetry.workspace = true
|
||||
deno_websocket.workspace = true
|
||||
flate2.workspace = true
|
||||
http.workspace = true
|
||||
|
|
|
@ -270,8 +270,12 @@ pub async fn op_http_upgrade_websocket_next(
|
|||
// Stage 1: set the response to 101 Switching Protocols and send it
|
||||
let upgrade = http.upgrade()?;
|
||||
{
|
||||
{
|
||||
http.otel_info_set_status(StatusCode::SWITCHING_PROTOCOLS.as_u16());
|
||||
}
|
||||
let mut response_parts = http.response_parts();
|
||||
response_parts.status = StatusCode::SWITCHING_PROTOCOLS;
|
||||
|
||||
for (name, value) in headers {
|
||||
response_parts.headers.append(
|
||||
HeaderName::from_bytes(&name).unwrap(),
|
||||
|
@ -305,7 +309,10 @@ fn set_promise_complete(http: Rc<HttpRecord>, status: u16) {
|
|||
// The Javascript code should never provide a status that is invalid here (see 23_response.js), so we
|
||||
// will quietly ignore invalid values.
|
||||
if let Ok(code) = StatusCode::from_u16(status) {
|
||||
http.response_parts().status = code;
|
||||
{
|
||||
http.response_parts().status = code;
|
||||
}
|
||||
http.otel_info_set_status(status);
|
||||
}
|
||||
http.complete();
|
||||
}
|
||||
|
@ -713,7 +720,10 @@ fn set_response(
|
|||
// The Javascript code should never provide a status that is invalid here (see 23_response.js), so we
|
||||
// will quietly ignore invalid values.
|
||||
if let Ok(code) = StatusCode::from_u16(status) {
|
||||
http.response_parts().status = code;
|
||||
{
|
||||
http.response_parts().status = code;
|
||||
}
|
||||
http.otel_info_set_status(status);
|
||||
}
|
||||
} else if force_instantiate_body {
|
||||
response_fn(Compression::None).abort();
|
||||
|
@ -1404,3 +1414,12 @@ pub async fn op_raw_write_vectored(
|
|||
let nwritten = resource.write_vectored(&buf1, &buf2).await?;
|
||||
Ok(nwritten)
|
||||
}
|
||||
|
||||
#[op2(fast)]
|
||||
pub fn op_http_metric_handle_otel_error(external: *const c_void) {
|
||||
let http =
|
||||
// SAFETY: external is deleted before calling this op.
|
||||
unsafe { take_external!(external, "op_http_metric_handle_otel_error") };
|
||||
|
||||
http.otel_info_set_error("user");
|
||||
}
|
||||
|
|
369
ext/http/lib.rs
369
ext/http/lib.rs
|
@ -53,6 +53,10 @@ use deno_core::ResourceId;
|
|||
use deno_core::StringOrBuffer;
|
||||
use deno_error::JsErrorBox;
|
||||
use deno_net::raw::NetworkStream;
|
||||
use deno_telemetry::Histogram;
|
||||
use deno_telemetry::MeterProvider;
|
||||
use deno_telemetry::UpDownCounter;
|
||||
use deno_telemetry::OTEL_GLOBALS;
|
||||
use deno_websocket::ws_create_server_stream;
|
||||
use flate2::write::GzEncoder;
|
||||
use flate2::Compression;
|
||||
|
@ -70,6 +74,7 @@ use hyper_v014::Body;
|
|||
use hyper_v014::HeaderMap;
|
||||
use hyper_v014::Request;
|
||||
use hyper_v014::Response;
|
||||
use once_cell::sync::OnceCell;
|
||||
use serde::Serialize;
|
||||
use tokio::io::AsyncRead;
|
||||
use tokio::io::AsyncWrite;
|
||||
|
@ -100,6 +105,15 @@ pub use request_properties::HttpRequestProperties;
|
|||
pub use service::UpgradeUnavailableError;
|
||||
pub use websocket_upgrade::WebSocketUpgradeError;
|
||||
|
||||
struct OtelCollectors {
|
||||
duration: Histogram<f64>,
|
||||
active_requests: UpDownCounter<i64>,
|
||||
request_size: Histogram<u64>,
|
||||
response_size: Histogram<u64>,
|
||||
}
|
||||
|
||||
static OTEL_COLLECTORS: OnceCell<OtelCollectors> = OnceCell::new();
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
pub struct Options {
|
||||
/// By passing a hook function, the caller can customize various configuration
|
||||
|
@ -156,6 +170,7 @@ deno_core::extension!(
|
|||
http_next::op_http_wait,
|
||||
http_next::op_http_close,
|
||||
http_next::op_http_cancel,
|
||||
http_next::op_http_metric_handle_otel_error,
|
||||
],
|
||||
esm = ["00_serve.ts", "01_http.js", "02_websocket.ts"],
|
||||
options = {
|
||||
|
@ -231,6 +246,287 @@ impl From<tokio::net::unix::SocketAddr> for HttpSocketAddr {
|
|||
}
|
||||
}
|
||||
|
||||
struct OtelInfo {
|
||||
attributes: OtelInfoAttributes,
|
||||
duration: Option<std::time::Instant>,
|
||||
request_size: Option<u64>,
|
||||
response_size: Option<u64>,
|
||||
}
|
||||
|
||||
struct OtelInfoAttributes {
|
||||
http_request_method: Cow<'static, str>,
|
||||
network_protocol_version: &'static str,
|
||||
url_scheme: Cow<'static, str>,
|
||||
server_address: Option<String>,
|
||||
server_port: Option<i64>,
|
||||
error_type: Option<&'static str>,
|
||||
http_response_status_code: Option<i64>,
|
||||
}
|
||||
|
||||
impl OtelInfoAttributes {
|
||||
fn method(method: &http::method::Method) -> Cow<'static, str> {
|
||||
use http::method::Method;
|
||||
|
||||
match *method {
|
||||
Method::GET => Cow::Borrowed("GET"),
|
||||
Method::POST => Cow::Borrowed("POST"),
|
||||
Method::PUT => Cow::Borrowed("PUT"),
|
||||
Method::DELETE => Cow::Borrowed("DELETE"),
|
||||
Method::HEAD => Cow::Borrowed("HEAD"),
|
||||
Method::OPTIONS => Cow::Borrowed("OPTIONS"),
|
||||
Method::CONNECT => Cow::Borrowed("CONNECT"),
|
||||
Method::PATCH => Cow::Borrowed("PATCH"),
|
||||
Method::TRACE => Cow::Borrowed("TRACE"),
|
||||
_ => Cow::Owned(method.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
fn method_v02(method: &http_v02::method::Method) -> Cow<'static, str> {
|
||||
use http_v02::method::Method;
|
||||
|
||||
match *method {
|
||||
Method::GET => Cow::Borrowed("GET"),
|
||||
Method::POST => Cow::Borrowed("POST"),
|
||||
Method::PUT => Cow::Borrowed("PUT"),
|
||||
Method::DELETE => Cow::Borrowed("DELETE"),
|
||||
Method::HEAD => Cow::Borrowed("HEAD"),
|
||||
Method::OPTIONS => Cow::Borrowed("OPTIONS"),
|
||||
Method::CONNECT => Cow::Borrowed("CONNECT"),
|
||||
Method::PATCH => Cow::Borrowed("PATCH"),
|
||||
Method::TRACE => Cow::Borrowed("TRACE"),
|
||||
_ => Cow::Owned(method.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
fn version(version: http::Version) -> &'static str {
|
||||
use http::Version;
|
||||
|
||||
match version {
|
||||
Version::HTTP_09 => "0.9",
|
||||
Version::HTTP_10 => "1.0",
|
||||
Version::HTTP_11 => "1.1",
|
||||
Version::HTTP_2 => "2",
|
||||
Version::HTTP_3 => "3",
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn version_v02(version: http_v02::Version) -> &'static str {
|
||||
use http_v02::Version;
|
||||
|
||||
match version {
|
||||
Version::HTTP_09 => "0.9",
|
||||
Version::HTTP_10 => "1.0",
|
||||
Version::HTTP_11 => "1.1",
|
||||
Version::HTTP_2 => "2",
|
||||
Version::HTTP_3 => "3",
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn for_counter(&self) -> Vec<deno_telemetry::KeyValue> {
|
||||
let mut attributes = vec![
|
||||
deno_telemetry::KeyValue::new(
|
||||
"http.request.method",
|
||||
self.http_request_method.clone(),
|
||||
),
|
||||
deno_telemetry::KeyValue::new("url.scheme", self.url_scheme.clone()),
|
||||
];
|
||||
|
||||
if let Some(address) = self.server_address.clone() {
|
||||
attributes.push(deno_telemetry::KeyValue::new("server.address", address));
|
||||
}
|
||||
if let Some(port) = self.server_port {
|
||||
attributes.push(deno_telemetry::KeyValue::new("server.port", port));
|
||||
}
|
||||
|
||||
attributes
|
||||
}
|
||||
|
||||
fn for_histogram(&self) -> Vec<deno_telemetry::KeyValue> {
|
||||
let mut histogram_attributes = vec![
|
||||
deno_telemetry::KeyValue::new(
|
||||
"http.request.method",
|
||||
self.http_request_method.clone(),
|
||||
),
|
||||
deno_telemetry::KeyValue::new("url.scheme", self.url_scheme.clone()),
|
||||
deno_telemetry::KeyValue::new(
|
||||
"network.protocol.version",
|
||||
self.network_protocol_version,
|
||||
),
|
||||
];
|
||||
|
||||
if let Some(address) = self.server_address.clone() {
|
||||
histogram_attributes
|
||||
.push(deno_telemetry::KeyValue::new("server.address", address));
|
||||
}
|
||||
if let Some(port) = self.server_port {
|
||||
histogram_attributes
|
||||
.push(deno_telemetry::KeyValue::new("server.port", port));
|
||||
}
|
||||
if let Some(status_code) = self.http_response_status_code {
|
||||
histogram_attributes.push(deno_telemetry::KeyValue::new(
|
||||
"http.response.status_code",
|
||||
status_code,
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(error) = self.error_type {
|
||||
histogram_attributes
|
||||
.push(deno_telemetry::KeyValue::new("error.type", error));
|
||||
}
|
||||
|
||||
histogram_attributes
|
||||
}
|
||||
}
|
||||
|
||||
impl OtelInfo {
|
||||
fn new(
|
||||
instant: std::time::Instant,
|
||||
request_size: u64,
|
||||
attributes: OtelInfoAttributes,
|
||||
) -> Self {
|
||||
let otel = OTEL_GLOBALS.get().unwrap();
|
||||
let collectors = OTEL_COLLECTORS.get_or_init(|| {
|
||||
let meter = otel
|
||||
.meter_provider
|
||||
.meter_with_scope(otel.builtin_instrumentation_scope.clone());
|
||||
|
||||
let duration = meter
|
||||
.f64_histogram("http.server.request.duration")
|
||||
.with_unit("s")
|
||||
.with_description("Duration of HTTP server requests.")
|
||||
.with_boundaries(vec![
|
||||
0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1.0, 2.5, 5.0,
|
||||
7.5, 10.0,
|
||||
])
|
||||
.build();
|
||||
|
||||
let active_requests = meter
|
||||
.i64_up_down_counter("http.server.active_requests")
|
||||
.with_unit("{request}")
|
||||
.with_description("Number of active HTTP server requests.")
|
||||
.build();
|
||||
|
||||
let request_size = meter
|
||||
.u64_histogram("http.server.request.body.size")
|
||||
.with_unit("By")
|
||||
.with_description("Size of HTTP server request bodies.")
|
||||
.with_boundaries(vec![
|
||||
0.0,
|
||||
100.0,
|
||||
1000.0,
|
||||
10000.0,
|
||||
100000.0,
|
||||
1000000.0,
|
||||
10000000.0,
|
||||
100000000.0,
|
||||
1000000000.0,
|
||||
])
|
||||
.build();
|
||||
|
||||
let response_size = meter
|
||||
.u64_histogram("http.server.response.body.size")
|
||||
.with_unit("By")
|
||||
.with_description("Size of HTTP server response bodies.")
|
||||
.with_boundaries(vec![
|
||||
0.0,
|
||||
100.0,
|
||||
1000.0,
|
||||
10000.0,
|
||||
100000.0,
|
||||
1000000.0,
|
||||
10000000.0,
|
||||
100000000.0,
|
||||
1000000000.0,
|
||||
])
|
||||
.build();
|
||||
|
||||
OtelCollectors {
|
||||
duration,
|
||||
active_requests,
|
||||
request_size,
|
||||
response_size,
|
||||
}
|
||||
});
|
||||
|
||||
collectors.active_requests.add(1, &attributes.for_counter());
|
||||
|
||||
Self {
|
||||
attributes,
|
||||
duration: Some(instant),
|
||||
request_size: Some(request_size),
|
||||
response_size: Some(0),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_duration_and_request_size(&mut self) {
|
||||
let collectors = OTEL_COLLECTORS.get().unwrap();
|
||||
let attributes = self.attributes.for_histogram();
|
||||
|
||||
if let Some(duration) = self.duration.take() {
|
||||
let duration = duration.elapsed();
|
||||
collectors
|
||||
.duration
|
||||
.record(duration.as_secs_f64(), &attributes);
|
||||
}
|
||||
|
||||
if let Some(request_size) = self.request_size.take() {
|
||||
let collectors = OTEL_COLLECTORS.get().unwrap();
|
||||
collectors.request_size.record(request_size, &attributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for OtelInfo {
|
||||
fn drop(&mut self) {
|
||||
let collectors = OTEL_COLLECTORS.get().unwrap();
|
||||
|
||||
self.handle_duration_and_request_size();
|
||||
|
||||
collectors
|
||||
.active_requests
|
||||
.add(-1, &self.attributes.for_counter());
|
||||
|
||||
if let Some(response_size) = self.response_size {
|
||||
collectors
|
||||
.response_size
|
||||
.record(response_size, &self.attributes.for_histogram());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_error_otel(
|
||||
otel: &Option<Rc<RefCell<Option<OtelInfo>>>>,
|
||||
error: &HttpError,
|
||||
) {
|
||||
if let Some(otel) = otel.as_ref() {
|
||||
let mut maybe_otel_info = otel.borrow_mut();
|
||||
if let Some(otel_info) = maybe_otel_info.as_mut() {
|
||||
otel_info.attributes.error_type = Some(match error {
|
||||
HttpError::Resource(_) => "resource",
|
||||
HttpError::Canceled(_) => "canceled",
|
||||
HttpError::HyperV014(_) => "hyper",
|
||||
HttpError::InvalidHeaderName(_) => "invalid header name",
|
||||
HttpError::InvalidHeaderValue(_) => "invalid header value",
|
||||
HttpError::Http(_) => "http",
|
||||
HttpError::ResponseHeadersAlreadySent => {
|
||||
"response headers already sent"
|
||||
}
|
||||
HttpError::ConnectionClosedWhileSendingResponse => {
|
||||
"connection closed while sending response"
|
||||
}
|
||||
HttpError::AlreadyInUse => "already in use",
|
||||
HttpError::Io(_) => "io",
|
||||
HttpError::NoResponseHeaders => "no response headers",
|
||||
HttpError::ResponseAlreadyCompleted => "response already completed",
|
||||
HttpError::UpgradeBodyUsed => "upgrade body used",
|
||||
HttpError::Other(_) => "unknown",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct HttpConnResource {
|
||||
addr: HttpSocketAddr,
|
||||
scheme: &'static str,
|
||||
|
@ -300,6 +596,8 @@ impl HttpConnResource {
|
|||
let (request_tx, request_rx) = oneshot::channel();
|
||||
let (response_tx, response_rx) = oneshot::channel();
|
||||
|
||||
let otel_instant = OTEL_GLOBALS.get().map(|_| std::time::Instant::now());
|
||||
|
||||
let acceptor = HttpAcceptor::new(request_tx, response_rx);
|
||||
self.acceptors_tx.unbounded_send(acceptor).ok()?;
|
||||
|
||||
|
@ -317,11 +615,37 @@ impl HttpConnResource {
|
|||
.unwrap_or(Encoding::Identity)
|
||||
};
|
||||
|
||||
let otel_info = OTEL_GLOBALS.get().map(|_| {
|
||||
let size_hint = request.size_hint();
|
||||
Rc::new(RefCell::new(Some(OtelInfo::new(
|
||||
otel_instant.unwrap(),
|
||||
size_hint.upper().unwrap_or(size_hint.lower()),
|
||||
OtelInfoAttributes {
|
||||
http_request_method: OtelInfoAttributes::method_v02(
|
||||
request.method(),
|
||||
),
|
||||
url_scheme: Cow::Borrowed(self.scheme),
|
||||
network_protocol_version: OtelInfoAttributes::version_v02(
|
||||
request.version(),
|
||||
),
|
||||
server_address: request.uri().host().map(|host| host.to_string()),
|
||||
server_port: request.uri().port_u16().map(|port| port as i64),
|
||||
error_type: Default::default(),
|
||||
http_response_status_code: Default::default(),
|
||||
},
|
||||
))))
|
||||
});
|
||||
|
||||
let method = request.method().to_string();
|
||||
let url = req_url(&request, self.scheme, &self.addr);
|
||||
let read_stream = HttpStreamReadResource::new(self, request);
|
||||
let write_stream =
|
||||
HttpStreamWriteResource::new(self, response_tx, accept_encoding);
|
||||
let read_stream =
|
||||
HttpStreamReadResource::new(self, request, otel_info.clone());
|
||||
let write_stream = HttpStreamWriteResource::new(
|
||||
self,
|
||||
response_tx,
|
||||
accept_encoding,
|
||||
otel_info,
|
||||
);
|
||||
Some((read_stream, write_stream, method, url))
|
||||
};
|
||||
|
||||
|
@ -438,22 +762,29 @@ pub struct HttpStreamReadResource {
|
|||
pub rd: AsyncRefCell<HttpRequestReader>,
|
||||
cancel_handle: CancelHandle,
|
||||
size: SizeHint,
|
||||
otel_info: Option<Rc<RefCell<Option<OtelInfo>>>>,
|
||||
}
|
||||
|
||||
pub struct HttpStreamWriteResource {
|
||||
conn: Rc<HttpConnResource>,
|
||||
wr: AsyncRefCell<HttpResponseWriter>,
|
||||
accept_encoding: Encoding,
|
||||
otel_info: Option<Rc<RefCell<Option<OtelInfo>>>>,
|
||||
}
|
||||
|
||||
impl HttpStreamReadResource {
|
||||
fn new(conn: &Rc<HttpConnResource>, request: Request<Body>) -> Self {
|
||||
fn new(
|
||||
conn: &Rc<HttpConnResource>,
|
||||
request: Request<Body>,
|
||||
otel_info: Option<Rc<RefCell<Option<OtelInfo>>>>,
|
||||
) -> Self {
|
||||
let size = request.body().size_hint();
|
||||
Self {
|
||||
_conn: conn.clone(),
|
||||
rd: HttpRequestReader::Headers(request).into(),
|
||||
size,
|
||||
cancel_handle: CancelHandle::new(),
|
||||
otel_info,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -530,11 +861,13 @@ impl HttpStreamWriteResource {
|
|||
conn: &Rc<HttpConnResource>,
|
||||
response_tx: oneshot::Sender<Response<Body>>,
|
||||
accept_encoding: Encoding,
|
||||
otel_info: Option<Rc<RefCell<Option<OtelInfo>>>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
conn: conn.clone(),
|
||||
wr: HttpResponseWriter::Headers(response_tx).into(),
|
||||
accept_encoding,
|
||||
otel_info,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -786,6 +1119,14 @@ async fn op_http_write_headers(
|
|||
let (new_wr, body) = http_response(data, compressing, encoding)?;
|
||||
let body = builder.status(status).body(body)?;
|
||||
|
||||
if let Some(otel) = stream.otel_info.as_ref() {
|
||||
let mut otel = otel.borrow_mut();
|
||||
if let Some(otel_info) = otel.as_mut() {
|
||||
otel_info.attributes.http_response_status_code = Some(status as _);
|
||||
otel_info.handle_duration_and_request_size();
|
||||
}
|
||||
}
|
||||
|
||||
let mut old_wr = RcRef::map(&stream, |r| &r.wr).borrow_mut().await;
|
||||
let response_tx = match replace(&mut *old_wr, new_wr) {
|
||||
HttpResponseWriter::Headers(response_tx) => response_tx,
|
||||
|
@ -810,7 +1151,8 @@ fn op_http_headers(
|
|||
let stream = state.resource_table.get::<HttpStreamReadResource>(rid)?;
|
||||
let rd = RcRef::map(&stream, |r| &r.rd)
|
||||
.try_borrow()
|
||||
.ok_or(HttpError::AlreadyInUse)?;
|
||||
.ok_or(HttpError::AlreadyInUse)
|
||||
.inspect_err(|e| handle_error_otel(&stream.otel_info, e))?;
|
||||
match &*rd {
|
||||
HttpRequestReader::Headers(request) => Ok(req_headers(request.headers())),
|
||||
HttpRequestReader::Body(headers, _) => Ok(req_headers(headers)),
|
||||
|
@ -1025,6 +1367,15 @@ async fn op_http_write(
|
|||
.get::<HttpStreamWriteResource>(rid)?;
|
||||
let mut wr = RcRef::map(&stream, |r| &r.wr).borrow_mut().await;
|
||||
|
||||
if let Some(otel) = stream.otel_info.as_ref() {
|
||||
let mut maybe_otel_info = otel.borrow_mut();
|
||||
if let Some(otel_info) = maybe_otel_info.as_mut() {
|
||||
if let Some(response_size) = otel_info.response_size.as_mut() {
|
||||
*response_size += buf.len() as u64;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match &mut *wr {
|
||||
HttpResponseWriter::Headers(_) => Err(HttpError::NoResponseHeaders),
|
||||
HttpResponseWriter::Closed => Err(HttpError::ResponseAlreadyCompleted),
|
||||
|
@ -1125,13 +1476,17 @@ async fn op_http_upgrade_websocket(
|
|||
|
||||
let request = match &mut *rd {
|
||||
HttpRequestReader::Headers(request) => request,
|
||||
_ => return Err(HttpError::UpgradeBodyUsed),
|
||||
_ => {
|
||||
return Err(HttpError::UpgradeBodyUsed)
|
||||
.inspect_err(|e| handle_error_otel(&stream.otel_info, e))
|
||||
}
|
||||
};
|
||||
|
||||
let (transport, bytes) = extract_network_stream(
|
||||
hyper_v014::upgrade::on(request)
|
||||
.await
|
||||
.map_err(|err| HttpError::HyperV014(Arc::new(err)))?,
|
||||
.map_err(|err| HttpError::HyperV014(Arc::new(err)))
|
||||
.inspect_err(|e| handle_error_otel(&stream.otel_info, e))?,
|
||||
);
|
||||
Ok(ws_create_server_stream(
|
||||
&mut state.borrow_mut(),
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
// Copyright 2018-2025 the Deno authors. MIT license.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::cell::Cell;
|
||||
use std::cell::Ref;
|
||||
use std::cell::RefCell;
|
||||
|
@ -30,6 +32,8 @@ use tokio::sync::oneshot;
|
|||
use crate::request_properties::HttpConnectionProperties;
|
||||
use crate::response_body::ResponseBytesInner;
|
||||
use crate::response_body::ResponseStreamResult;
|
||||
use crate::OtelInfo;
|
||||
use crate::OtelInfoAttributes;
|
||||
|
||||
pub type Request = hyper::Request<Incoming>;
|
||||
pub type Response = hyper::Response<HttpRecordResponse>;
|
||||
|
@ -184,12 +188,38 @@ pub(crate) async fn handle_request(
|
|||
server_state: SignallingRc<HttpServerState>, // Keep server alive for duration of this future.
|
||||
tx: tokio::sync::mpsc::Sender<Rc<HttpRecord>>,
|
||||
) -> Result<Response, hyper_v014::Error> {
|
||||
let otel_info = if deno_telemetry::OTEL_GLOBALS.get().is_some() {
|
||||
let instant = std::time::Instant::now();
|
||||
let size_hint = request.size_hint();
|
||||
Some(OtelInfo::new(
|
||||
instant,
|
||||
size_hint.upper().unwrap_or(size_hint.lower()),
|
||||
OtelInfoAttributes {
|
||||
http_request_method: OtelInfoAttributes::method(request.method()),
|
||||
url_scheme: request
|
||||
.uri()
|
||||
.scheme_str()
|
||||
.map(|s| Cow::Owned(s.to_string()))
|
||||
.unwrap_or_else(|| Cow::Borrowed("http")),
|
||||
network_protocol_version: OtelInfoAttributes::version(
|
||||
request.version(),
|
||||
),
|
||||
server_address: request.uri().host().map(|host| host.to_string()),
|
||||
server_port: request.uri().port_u16().map(|port| port as i64),
|
||||
error_type: Default::default(),
|
||||
http_response_status_code: Default::default(),
|
||||
},
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// If the underlying TCP connection is closed, this future will be dropped
|
||||
// and execution could stop at any await point.
|
||||
// The HttpRecord must live until JavaScript is done processing so is wrapped
|
||||
// in an Rc. The guard ensures unneeded resources are freed at cancellation.
|
||||
let guarded_record = guard(
|
||||
HttpRecord::new(request, request_info, server_state),
|
||||
HttpRecord::new(request, request_info, server_state, otel_info),
|
||||
HttpRecord::cancel,
|
||||
);
|
||||
|
||||
|
@ -228,6 +258,7 @@ struct HttpRecordInner {
|
|||
been_dropped: bool,
|
||||
finished: bool,
|
||||
needs_close_after_finish: bool,
|
||||
otel_info: Option<OtelInfo>,
|
||||
}
|
||||
|
||||
pub struct HttpRecord(RefCell<Option<HttpRecordInner>>);
|
||||
|
@ -248,6 +279,7 @@ impl HttpRecord {
|
|||
request: Request,
|
||||
request_info: HttpConnectionProperties,
|
||||
server_state: SignallingRc<HttpServerState>,
|
||||
otel_info: Option<OtelInfo>,
|
||||
) -> Rc<Self> {
|
||||
let (request_parts, request_body) = request.into_parts();
|
||||
let request_body = Some(request_body.into());
|
||||
|
@ -284,6 +316,7 @@ impl HttpRecord {
|
|||
been_dropped: false,
|
||||
finished: false,
|
||||
needs_close_after_finish: false,
|
||||
otel_info,
|
||||
});
|
||||
record
|
||||
}
|
||||
|
@ -487,6 +520,7 @@ impl HttpRecord {
|
|||
) -> Poll<Self::Output> {
|
||||
let mut mut_self = self.0.self_mut();
|
||||
if mut_self.response_ready {
|
||||
mut_self.otel_info.take();
|
||||
return Poll::Ready(());
|
||||
}
|
||||
mut_self.response_waker = Some(cx.waker().clone());
|
||||
|
@ -523,6 +557,22 @@ impl HttpRecord {
|
|||
|
||||
HttpRecordFinished(self)
|
||||
}
|
||||
|
||||
pub fn otel_info_set_status(&self, status: u16) {
|
||||
let mut inner = self.self_mut();
|
||||
if let Some(info) = inner.otel_info.as_mut() {
|
||||
info.attributes.http_response_status_code = Some(status as _);
|
||||
info.handle_duration_and_request_size();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn otel_info_set_error(&self, error: &'static str) {
|
||||
let mut inner = self.self_mut();
|
||||
if let Some(info) = inner.otel_info.as_mut() {
|
||||
info.attributes.error_type = Some(error);
|
||||
info.handle_duration_and_request_size();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
|
@ -579,6 +629,16 @@ impl Body for HttpRecordResponse {
|
|||
}
|
||||
record.take_response_body();
|
||||
}
|
||||
|
||||
if let ResponseStreamResult::NonEmptyBuf(buf) = &res {
|
||||
let mut http = self.0 .0.borrow_mut();
|
||||
if let Some(otel_info) = &mut http.as_mut().unwrap().otel_info {
|
||||
if let Some(response_size) = &mut otel_info.response_size {
|
||||
*response_size += buf.len() as u64;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Poll::Ready(res.into())
|
||||
}
|
||||
|
||||
|
|
|
@ -38,8 +38,10 @@ use opentelemetry::logs::AnyValue;
|
|||
use opentelemetry::logs::LogRecord as LogRecordTrait;
|
||||
use opentelemetry::logs::Severity;
|
||||
use opentelemetry::metrics::AsyncInstrumentBuilder;
|
||||
pub use opentelemetry::metrics::Histogram;
|
||||
use opentelemetry::metrics::InstrumentBuilder;
|
||||
use opentelemetry::metrics::MeterProvider as _;
|
||||
pub use opentelemetry::metrics::MeterProvider;
|
||||
pub use opentelemetry::metrics::UpDownCounter;
|
||||
use opentelemetry::otel_debug;
|
||||
use opentelemetry::otel_error;
|
||||
use opentelemetry::trace::Link;
|
||||
|
@ -51,10 +53,10 @@ use opentelemetry::trace::TraceFlags;
|
|||
use opentelemetry::trace::TraceId;
|
||||
use opentelemetry::trace::TraceState;
|
||||
use opentelemetry::InstrumentationScope;
|
||||
use opentelemetry::Key;
|
||||
use opentelemetry::KeyValue;
|
||||
use opentelemetry::StringValue;
|
||||
use opentelemetry::Value;
|
||||
pub use opentelemetry::Key;
|
||||
pub use opentelemetry::KeyValue;
|
||||
pub use opentelemetry::StringValue;
|
||||
pub use opentelemetry::Value;
|
||||
use opentelemetry_otlp::HttpExporterBuilder;
|
||||
use opentelemetry_otlp::Protocol;
|
||||
use opentelemetry_otlp::WithExportConfig;
|
||||
|
@ -196,7 +198,7 @@ fn otel_create_shared_runtime() -> UnboundedSender<BoxFuture<'static, ()>> {
|
|||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct OtelSharedRuntime;
|
||||
pub struct OtelSharedRuntime;
|
||||
|
||||
impl hyper::rt::Executor<BoxFuture<'static, ()>> for OtelSharedRuntime {
|
||||
fn execute(&self, fut: BoxFuture<'static, ()>) {
|
||||
|
@ -586,15 +588,16 @@ mod hyper_client {
|
|||
}
|
||||
}
|
||||
|
||||
struct OtelGlobals {
|
||||
span_processor: BatchSpanProcessor<OtelSharedRuntime>,
|
||||
log_processor: BatchLogProcessor<OtelSharedRuntime>,
|
||||
id_generator: DenoIdGenerator,
|
||||
meter_provider: SdkMeterProvider,
|
||||
builtin_instrumentation_scope: InstrumentationScope,
|
||||
#[derive(Debug)]
|
||||
pub struct OtelGlobals {
|
||||
pub span_processor: BatchSpanProcessor<OtelSharedRuntime>,
|
||||
pub log_processor: BatchLogProcessor<OtelSharedRuntime>,
|
||||
pub id_generator: DenoIdGenerator,
|
||||
pub meter_provider: SdkMeterProvider,
|
||||
pub builtin_instrumentation_scope: InstrumentationScope,
|
||||
}
|
||||
|
||||
static OTEL_GLOBALS: OnceCell<OtelGlobals> = OnceCell::new();
|
||||
pub static OTEL_GLOBALS: OnceCell<OtelGlobals> = OnceCell::new();
|
||||
|
||||
pub fn init(
|
||||
rt_config: OtelRuntimeConfig,
|
||||
|
@ -808,7 +811,7 @@ pub fn handle_log(record: &log::Record) {
|
|||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum DenoIdGenerator {
|
||||
pub enum DenoIdGenerator {
|
||||
Random(RandomIdGenerator),
|
||||
Deterministic {
|
||||
next_trace_id: AtomicU64,
|
||||
|
@ -1708,9 +1711,9 @@ impl OtelMeter {
|
|||
|
||||
enum Instrument {
|
||||
Counter(opentelemetry::metrics::Counter<f64>),
|
||||
UpDownCounter(opentelemetry::metrics::UpDownCounter<f64>),
|
||||
UpDownCounter(UpDownCounter<f64>),
|
||||
Gauge(opentelemetry::metrics::Gauge<f64>),
|
||||
Histogram(opentelemetry::metrics::Histogram<f64>),
|
||||
Histogram(Histogram<f64>),
|
||||
Observable(Arc<Mutex<HashMap<Vec<KeyValue>, f64>>>),
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,13 @@
|
|||
"args": "run -A main.ts metric.ts",
|
||||
"output": "metric.out"
|
||||
},
|
||||
"http_metric": {
|
||||
"envs": {
|
||||
"OTEL_METRIC_EXPORT_INTERVAL": "1000"
|
||||
},
|
||||
"args": "run -A main.ts http_metric.ts",
|
||||
"output": "http_metric.out"
|
||||
},
|
||||
"links": {
|
||||
"args": "run -A main.ts links.ts",
|
||||
"output": "links.out"
|
||||
|
|
|
@ -189,5 +189,259 @@
|
|||
"spanId": "0000000000000004"
|
||||
}
|
||||
],
|
||||
"metrics": []
|
||||
"metrics": [
|
||||
{
|
||||
"name": "http.server.active_requests",
|
||||
"description": "Number of active HTTP server requests.",
|
||||
"unit": "{request}",
|
||||
"metadata": [],
|
||||
"sum": {
|
||||
"dataPoints": [
|
||||
{
|
||||
"attributes": [
|
||||
{
|
||||
"key": "http.request.method",
|
||||
"value": {
|
||||
"stringValue": "GET"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "url.scheme",
|
||||
"value": {
|
||||
"stringValue": "http"
|
||||
}
|
||||
}
|
||||
],
|
||||
"startTimeUnixNano": "[WILDCARD]",
|
||||
"timeUnixNano": "[WILDCARD]",
|
||||
"exemplars": [],
|
||||
"flags": 0,
|
||||
"asInt": 0
|
||||
}
|
||||
],
|
||||
"aggregationTemporality": 2,
|
||||
"isMonotonic": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "http.server.request.body.size",
|
||||
"description": "Size of HTTP server request bodies.",
|
||||
"unit": "By",
|
||||
"metadata": [],
|
||||
"histogram": {
|
||||
"dataPoints": [
|
||||
{
|
||||
"attributes": [
|
||||
{
|
||||
"key": "http.request.method",
|
||||
"value": {
|
||||
"stringValue": "GET"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "http.response.status_code",
|
||||
"value": {
|
||||
"intValue": "200"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "network.protocol.version",
|
||||
"value": {
|
||||
"stringValue": "1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "url.scheme",
|
||||
"value": {
|
||||
"stringValue": "http"
|
||||
}
|
||||
}
|
||||
],
|
||||
"startTimeUnixNano": "[WILDCARD]",
|
||||
"timeUnixNano": "[WILDCARD]",
|
||||
"count": 1,
|
||||
"sum": 0,
|
||||
"bucketCounts": [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"explicitBounds": [
|
||||
0,
|
||||
100,
|
||||
1000,
|
||||
10000,
|
||||
100000,
|
||||
1000000,
|
||||
10000000,
|
||||
100000000,
|
||||
1000000000
|
||||
],
|
||||
"exemplars": [],
|
||||
"flags": 0,
|
||||
"min": 0,
|
||||
"max": 0
|
||||
}
|
||||
],
|
||||
"aggregationTemporality": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "http.server.request.duration",
|
||||
"description": "Duration of HTTP server requests.",
|
||||
"unit": "s",
|
||||
"metadata": [],
|
||||
"histogram": {
|
||||
"dataPoints": [
|
||||
{
|
||||
"attributes": [
|
||||
{
|
||||
"key": "http.request.method",
|
||||
"value": {
|
||||
"stringValue": "GET"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "http.response.status_code",
|
||||
"value": {
|
||||
"intValue": "200"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "network.protocol.version",
|
||||
"value": {
|
||||
"stringValue": "1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "url.scheme",
|
||||
"value": {
|
||||
"stringValue": "http"
|
||||
}
|
||||
}
|
||||
],
|
||||
"startTimeUnixNano": "[WILDCARD]",
|
||||
"timeUnixNano": "[WILDCARD]",
|
||||
"count": 1,
|
||||
"sum": [WILDCARD],
|
||||
"bucketCounts": [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"explicitBounds": [
|
||||
0.005,
|
||||
0.01,
|
||||
0.025,
|
||||
0.05,
|
||||
0.075,
|
||||
0.1,
|
||||
0.25,
|
||||
0.5,
|
||||
0.75,
|
||||
1,
|
||||
2.5,
|
||||
5,
|
||||
7.5,
|
||||
10
|
||||
],
|
||||
"exemplars": [],
|
||||
"flags": 0,
|
||||
"min": [WILDCARD],
|
||||
"max": [WILDCARD]
|
||||
}
|
||||
],
|
||||
"aggregationTemporality": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "http.server.response.body.size",
|
||||
"description": "Size of HTTP server response bodies.",
|
||||
"unit": "By",
|
||||
"metadata": [],
|
||||
"histogram": {
|
||||
"dataPoints": [
|
||||
{
|
||||
"attributes": [
|
||||
{
|
||||
"key": "http.request.method",
|
||||
"value": {
|
||||
"stringValue": "GET"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "http.response.status_code",
|
||||
"value": {
|
||||
"intValue": "200"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "network.protocol.version",
|
||||
"value": {
|
||||
"stringValue": "1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "url.scheme",
|
||||
"value": {
|
||||
"stringValue": "http"
|
||||
}
|
||||
}
|
||||
],
|
||||
"startTimeUnixNano": "[WILDCARD]",
|
||||
"timeUnixNano": "[WILDCARD]",
|
||||
"count": 1,
|
||||
"sum": 0,
|
||||
"bucketCounts": [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"explicitBounds": [
|
||||
0,
|
||||
100,
|
||||
1000,
|
||||
10000,
|
||||
100000,
|
||||
1000000,
|
||||
10000000,
|
||||
100000000,
|
||||
1000000000
|
||||
],
|
||||
"exemplars": [],
|
||||
"flags": 0,
|
||||
"min": 0,
|
||||
"max": 0
|
||||
}
|
||||
],
|
||||
"aggregationTemporality": 2
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
608
tests/specs/cli/otel_basic/http_metric.out
Normal file
608
tests/specs/cli/otel_basic/http_metric.out
Normal file
|
@ -0,0 +1,608 @@
|
|||
{
|
||||
"spans": [
|
||||
{
|
||||
"traceId": "00000000000000000000000000000001",
|
||||
"spanId": "0000000000000001",
|
||||
"traceState": "",
|
||||
"parentSpanId": "",
|
||||
"flags": 1,
|
||||
"name": "GET",
|
||||
"kind": 3,
|
||||
"startTimeUnixNano": "[WILDCARD]",
|
||||
"endTimeUnixNano": "[WILDCARD]",
|
||||
"attributes": [
|
||||
{
|
||||
"key": "http.request.method",
|
||||
"value": {
|
||||
"stringValue": "GET"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "url.full",
|
||||
"value": {
|
||||
"stringValue": "http://localhost:8080/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "url.scheme",
|
||||
"value": {
|
||||
"stringValue": "http"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "url.path",
|
||||
"value": {
|
||||
"stringValue": "/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "url.query",
|
||||
"value": {
|
||||
"stringValue": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "http.response.status_code",
|
||||
"value": {
|
||||
"stringValue": "200"
|
||||
}
|
||||
}
|
||||
],
|
||||
"droppedAttributesCount": 0,
|
||||
"events": [],
|
||||
"droppedEventsCount": 0,
|
||||
"links": [],
|
||||
"droppedLinksCount": 0,
|
||||
"status": {
|
||||
"message": "",
|
||||
"code": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"traceId": "00000000000000000000000000000002",
|
||||
"spanId": "0000000000000002",
|
||||
"traceState": "",
|
||||
"parentSpanId": "",
|
||||
"flags": 1,
|
||||
"name": "GET",
|
||||
"kind": 2,
|
||||
"startTimeUnixNano": "[WILDCARD]",
|
||||
"endTimeUnixNano": "[WILDCARD]",
|
||||
"attributes": [
|
||||
{
|
||||
"key": "http.request.method",
|
||||
"value": {
|
||||
"stringValue": "GET"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "url.full",
|
||||
"value": {
|
||||
"stringValue": "http://localhost:8080/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "url.scheme",
|
||||
"value": {
|
||||
"stringValue": "http"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "url.path",
|
||||
"value": {
|
||||
"stringValue": "/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "url.query",
|
||||
"value": {
|
||||
"stringValue": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "http.response.status_code",
|
||||
"value": {
|
||||
"stringValue": "200"
|
||||
}
|
||||
}
|
||||
],
|
||||
"droppedAttributesCount": 0,
|
||||
"events": [],
|
||||
"droppedEventsCount": 0,
|
||||
"links": [],
|
||||
"droppedLinksCount": 0,
|
||||
"status": {
|
||||
"message": "",
|
||||
"code": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"traceId": "00000000000000000000000000000001",
|
||||
"spanId": "0000000000000003",
|
||||
"traceState": "",
|
||||
"parentSpanId": "0000000000000001",
|
||||
"flags": 1,
|
||||
"name": "GET",
|
||||
"kind": 3,
|
||||
"startTimeUnixNano": "[WILDCARD]",
|
||||
"endTimeUnixNano": "[WILDCARD]",
|
||||
"attributes": [
|
||||
{
|
||||
"key": "http.request.method",
|
||||
"value": {
|
||||
"stringValue": "GET"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "url.full",
|
||||
"value": {
|
||||
"stringValue": "http://localhost:8080/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "url.scheme",
|
||||
"value": {
|
||||
"stringValue": "http"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "url.path",
|
||||
"value": {
|
||||
"stringValue": "/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "url.query",
|
||||
"value": {
|
||||
"stringValue": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "http.response.status_code",
|
||||
"value": {
|
||||
"stringValue": "200"
|
||||
}
|
||||
}
|
||||
],
|
||||
"droppedAttributesCount": 0,
|
||||
"events": [],
|
||||
"droppedEventsCount": 0,
|
||||
"links": [],
|
||||
"droppedLinksCount": 0,
|
||||
"status": {
|
||||
"message": "",
|
||||
"code": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"traceId": "00000000000000000000000000000003",
|
||||
"spanId": "0000000000000004",
|
||||
"traceState": "",
|
||||
"parentSpanId": "",
|
||||
"flags": 1,
|
||||
"name": "GET",
|
||||
"kind": 2,
|
||||
"startTimeUnixNano": "[WILDCARD]",
|
||||
"endTimeUnixNano": "[WILDCARD]",
|
||||
"attributes": [
|
||||
{
|
||||
"key": "http.request.method",
|
||||
"value": {
|
||||
"stringValue": "GET"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "url.full",
|
||||
"value": {
|
||||
"stringValue": "http://localhost:8080/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "url.scheme",
|
||||
"value": {
|
||||
"stringValue": "http"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "url.path",
|
||||
"value": {
|
||||
"stringValue": "/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "url.query",
|
||||
"value": {
|
||||
"stringValue": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "http.response.status_code",
|
||||
"value": {
|
||||
"stringValue": "200"
|
||||
}
|
||||
}
|
||||
],
|
||||
"droppedAttributesCount": 0,
|
||||
"events": [],
|
||||
"droppedEventsCount": 0,
|
||||
"links": [],
|
||||
"droppedLinksCount": 0,
|
||||
"status": {
|
||||
"message": "",
|
||||
"code": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"traceId": "00000000000000000000000000000001",
|
||||
"spanId": "0000000000000005",
|
||||
"traceState": "",
|
||||
"parentSpanId": "0000000000000001",
|
||||
"flags": 1,
|
||||
"name": "GET",
|
||||
"kind": 3,
|
||||
"startTimeUnixNano": "[WILDCARD]",
|
||||
"endTimeUnixNano": "[WILDCARD]",
|
||||
"attributes": [
|
||||
{
|
||||
"key": "http.request.method",
|
||||
"value": {
|
||||
"stringValue": "GET"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "url.full",
|
||||
"value": {
|
||||
"stringValue": "http://localhost:8080/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "url.scheme",
|
||||
"value": {
|
||||
"stringValue": "http"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "url.path",
|
||||
"value": {
|
||||
"stringValue": "/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "url.query",
|
||||
"value": {
|
||||
"stringValue": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "http.response.status_code",
|
||||
"value": {
|
||||
"stringValue": "200"
|
||||
}
|
||||
}
|
||||
],
|
||||
"droppedAttributesCount": 0,
|
||||
"events": [],
|
||||
"droppedEventsCount": 0,
|
||||
"links": [],
|
||||
"droppedLinksCount": 0,
|
||||
"status": {
|
||||
"message": "",
|
||||
"code": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"traceId": "00000000000000000000000000000004",
|
||||
"spanId": "0000000000000006",
|
||||
"traceState": "",
|
||||
"parentSpanId": "",
|
||||
"flags": 1,
|
||||
"name": "GET",
|
||||
"kind": 2,
|
||||
"startTimeUnixNano": "[WILDCARD]",
|
||||
"endTimeUnixNano": "[WILDCARD]",
|
||||
"attributes": [
|
||||
{
|
||||
"key": "http.request.method",
|
||||
"value": {
|
||||
"stringValue": "GET"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "url.full",
|
||||
"value": {
|
||||
"stringValue": "http://localhost:8080/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "url.scheme",
|
||||
"value": {
|
||||
"stringValue": "http"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "url.path",
|
||||
"value": {
|
||||
"stringValue": "/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "url.query",
|
||||
"value": {
|
||||
"stringValue": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "http.response.status_code",
|
||||
"value": {
|
||||
"stringValue": "200"
|
||||
}
|
||||
}
|
||||
],
|
||||
"droppedAttributesCount": 0,
|
||||
"events": [],
|
||||
"droppedEventsCount": 0,
|
||||
"links": [],
|
||||
"droppedLinksCount": 0,
|
||||
"status": {
|
||||
"message": "",
|
||||
"code": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"logs": [],
|
||||
"metrics": [
|
||||
{
|
||||
"name": "http.server.active_requests",
|
||||
"description": "Number of active HTTP server requests.",
|
||||
"unit": "{request}",
|
||||
"metadata": [],
|
||||
"sum": {
|
||||
"dataPoints": [
|
||||
{
|
||||
"attributes": [
|
||||
{
|
||||
"key": "http.request.method",
|
||||
"value": {
|
||||
"stringValue": "GET"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "url.scheme",
|
||||
"value": {
|
||||
"stringValue": "http"
|
||||
}
|
||||
}
|
||||
],
|
||||
"startTimeUnixNano": "[WILDCARD]",
|
||||
"timeUnixNano": "[WILDCARD]",
|
||||
"exemplars": [],
|
||||
"flags": 0,
|
||||
"asInt": 0
|
||||
}
|
||||
],
|
||||
"aggregationTemporality": 2,
|
||||
"isMonotonic": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "http.server.request.body.size",
|
||||
"description": "Size of HTTP server request bodies.",
|
||||
"unit": "By",
|
||||
"metadata": [],
|
||||
"histogram": {
|
||||
"dataPoints": [
|
||||
{
|
||||
"attributes": [
|
||||
{
|
||||
"key": "http.request.method",
|
||||
"value": {
|
||||
"stringValue": "GET"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "http.response.status_code",
|
||||
"value": {
|
||||
"intValue": "200"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "network.protocol.version",
|
||||
"value": {
|
||||
"stringValue": "1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "url.scheme",
|
||||
"value": {
|
||||
"stringValue": "http"
|
||||
}
|
||||
}
|
||||
],
|
||||
"startTimeUnixNano": "[WILDCARD]",
|
||||
"timeUnixNano": "[WILDCARD]",
|
||||
"count": 3,
|
||||
"sum": 0,
|
||||
"bucketCounts": [
|
||||
3,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"explicitBounds": [
|
||||
0,
|
||||
100,
|
||||
1000,
|
||||
10000,
|
||||
100000,
|
||||
1000000,
|
||||
10000000,
|
||||
100000000,
|
||||
1000000000
|
||||
],
|
||||
"exemplars": [],
|
||||
"flags": 0,
|
||||
"min": 0,
|
||||
"max": 0
|
||||
}
|
||||
],
|
||||
"aggregationTemporality": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "http.server.request.duration",
|
||||
"description": "Duration of HTTP server requests.",
|
||||
"unit": "s",
|
||||
"metadata": [],
|
||||
"histogram": {
|
||||
"dataPoints": [
|
||||
{
|
||||
"attributes": [
|
||||
{
|
||||
"key": "http.request.method",
|
||||
"value": {
|
||||
"stringValue": "GET"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "http.response.status_code",
|
||||
"value": {
|
||||
"intValue": "200"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "network.protocol.version",
|
||||
"value": {
|
||||
"stringValue": "1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "url.scheme",
|
||||
"value": {
|
||||
"stringValue": "http"
|
||||
}
|
||||
}
|
||||
],
|
||||
"startTimeUnixNano": "[WILDCARD]",
|
||||
"timeUnixNano": "[WILDCARD]",
|
||||
"count": 3,
|
||||
"sum": [WILDCARD],
|
||||
"bucketCounts": [
|
||||
3,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"explicitBounds": [
|
||||
0.005,
|
||||
0.01,
|
||||
0.025,
|
||||
0.05,
|
||||
0.075,
|
||||
0.1,
|
||||
0.25,
|
||||
0.5,
|
||||
0.75,
|
||||
1,
|
||||
2.5,
|
||||
5,
|
||||
7.5,
|
||||
10
|
||||
],
|
||||
"exemplars": [],
|
||||
"flags": 0,
|
||||
"min": [WILDCARD],
|
||||
"max": [WILDCARD]
|
||||
}
|
||||
],
|
||||
"aggregationTemporality": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "http.server.response.body.size",
|
||||
"description": "Size of HTTP server response bodies.",
|
||||
"unit": "By",
|
||||
"metadata": [],
|
||||
"histogram": {
|
||||
"dataPoints": [
|
||||
{
|
||||
"attributes": [
|
||||
{
|
||||
"key": "http.request.method",
|
||||
"value": {
|
||||
"stringValue": "GET"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "http.response.status_code",
|
||||
"value": {
|
||||
"intValue": "200"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "network.protocol.version",
|
||||
"value": {
|
||||
"stringValue": "1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "url.scheme",
|
||||
"value": {
|
||||
"stringValue": "http"
|
||||
}
|
||||
}
|
||||
],
|
||||
"startTimeUnixNano": "[WILDCARD]",
|
||||
"timeUnixNano": "[WILDCARD]",
|
||||
"count": 3,
|
||||
"sum": 0,
|
||||
"bucketCounts": [
|
||||
3,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"explicitBounds": [
|
||||
0,
|
||||
100,
|
||||
1000,
|
||||
10000,
|
||||
100000,
|
||||
1000000,
|
||||
10000000,
|
||||
100000000,
|
||||
1000000000
|
||||
],
|
||||
"exemplars": [],
|
||||
"flags": 0,
|
||||
"min": 0,
|
||||
"max": 0
|
||||
}
|
||||
],
|
||||
"aggregationTemporality": 2
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
10
tests/specs/cli/otel_basic/http_metric.ts
Normal file
10
tests/specs/cli/otel_basic/http_metric.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
const server = Deno.serve(
|
||||
{ port: 8080, onListen: () => {} },
|
||||
() => new Response("foo"),
|
||||
);
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
await fetch(`http://localhost:8080`);
|
||||
}
|
||||
|
||||
await server.shutdown();
|
|
@ -40,6 +40,17 @@ const server = Deno.serve(
|
|||
data.spans.sort((a, b) =>
|
||||
Number(BigInt(`0x${a.spanId}`) - BigInt(`0x${b.spanId}`))
|
||||
);
|
||||
data.metrics.sort((a, b) => a.name.localeCompare(b.name));
|
||||
for (const metric of data.metrics) {
|
||||
if ("histogram" in metric) {
|
||||
for (const dataPoint of metric.histogram.dataPoints) {
|
||||
dataPoint.attributes.sort((a, b) => {
|
||||
return a.key.localeCompare(b.key);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(data, null, 2));
|
||||
});
|
||||
},
|
||||
|
|
|
@ -18,8 +18,8 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"startTimeUnixNano": "[WILDCARD]",
|
||||
"timeUnixNano": "[WILDCARD]",
|
||||
"startTimeUnixNano": [WILDCARD],
|
||||
"timeUnixNano": [WILDCARD],
|
||||
"exemplars": [],
|
||||
"flags": 0,
|
||||
"asDouble": 1
|
||||
|
@ -29,180 +29,6 @@
|
|||
"isMonotonic": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "up_down_counter",
|
||||
"description": "Example of a UpDownCounter",
|
||||
"unit": "",
|
||||
"metadata": [],
|
||||
"sum": {
|
||||
"dataPoints": [
|
||||
{
|
||||
"attributes": [
|
||||
{
|
||||
"key": "attribute",
|
||||
"value": {
|
||||
"doubleValue": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"startTimeUnixNano": "[WILDCARD]",
|
||||
"timeUnixNano": "[WILDCARD]",
|
||||
"exemplars": [],
|
||||
"flags": 0,
|
||||
"asDouble": -1
|
||||
}
|
||||
],
|
||||
"aggregationTemporality": 2,
|
||||
"isMonotonic": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "gauge",
|
||||
"description": "Example of a Gauge",
|
||||
"unit": "",
|
||||
"metadata": [],
|
||||
"gauge": {
|
||||
"dataPoints": [
|
||||
{
|
||||
"attributes": [
|
||||
{
|
||||
"key": "attribute",
|
||||
"value": {
|
||||
"doubleValue": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"startTimeUnixNano": "[WILDCARD]",
|
||||
"timeUnixNano": "[WILDCARD]",
|
||||
"exemplars": [],
|
||||
"flags": 0,
|
||||
"asDouble": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "histogram",
|
||||
"description": "Example of a Histogram",
|
||||
"unit": "",
|
||||
"metadata": [],
|
||||
"histogram": {
|
||||
"dataPoints": [
|
||||
{
|
||||
"attributes": [
|
||||
{
|
||||
"key": "attribute",
|
||||
"value": {
|
||||
"doubleValue": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"startTimeUnixNano": "[WILDCARD]",
|
||||
"timeUnixNano": "[WILDCARD]",
|
||||
"count": 1,
|
||||
"sum": 1,
|
||||
"bucketCounts": [
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"explicitBounds": [
|
||||
0,
|
||||
5,
|
||||
10,
|
||||
25,
|
||||
50,
|
||||
75,
|
||||
100,
|
||||
250,
|
||||
500,
|
||||
750,
|
||||
1000,
|
||||
2500,
|
||||
5000,
|
||||
7500,
|
||||
10000
|
||||
],
|
||||
"exemplars": [],
|
||||
"flags": 0,
|
||||
"min": 1,
|
||||
"max": 1
|
||||
}
|
||||
],
|
||||
"aggregationTemporality": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "observable_counter",
|
||||
"description": "Example of a ObservableCounter",
|
||||
"unit": "",
|
||||
"metadata": [],
|
||||
"sum": {
|
||||
"dataPoints": [
|
||||
{
|
||||
"attributes": [],
|
||||
"startTimeUnixNano": "[WILDCARD]",
|
||||
"timeUnixNano": "[WILDCARD]",
|
||||
"exemplars": [],
|
||||
"flags": 0,
|
||||
"asDouble": 1
|
||||
}
|
||||
],
|
||||
"aggregationTemporality": 2,
|
||||
"isMonotonic": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "observable_up_down_counter",
|
||||
"description": "Example of a ObservableUpDownCounter",
|
||||
"unit": "",
|
||||
"metadata": [],
|
||||
"sum": {
|
||||
"dataPoints": [
|
||||
{
|
||||
"attributes": [],
|
||||
"startTimeUnixNano": "[WILDCARD]",
|
||||
"timeUnixNano": "[WILDCARD]",
|
||||
"exemplars": [],
|
||||
"flags": 0,
|
||||
"asDouble": 1
|
||||
}
|
||||
],
|
||||
"aggregationTemporality": 2,
|
||||
"isMonotonic": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "observable_gauge",
|
||||
"description": "Example of a ObservableGauge",
|
||||
"unit": "",
|
||||
"metadata": [],
|
||||
"gauge": {
|
||||
"dataPoints": [
|
||||
{
|
||||
"attributes": [],
|
||||
"startTimeUnixNano": "[WILDCARD]",
|
||||
"timeUnixNano": "[WILDCARD]",
|
||||
"exemplars": [],
|
||||
"flags": 0,
|
||||
"asDouble": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "counter",
|
||||
"description": "Example of a Counter",
|
||||
|
@ -219,8 +45,8 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"startTimeUnixNano": "[WILDCARD]",
|
||||
"timeUnixNano": "[WILDCARD]",
|
||||
"startTimeUnixNano": [WILDCARD],
|
||||
"timeUnixNano": [WILDCARD],
|
||||
"exemplars": [],
|
||||
"flags": 0,
|
||||
"asDouble": 1
|
||||
|
@ -230,33 +56,6 @@
|
|||
"isMonotonic": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "up_down_counter",
|
||||
"description": "Example of a UpDownCounter",
|
||||
"unit": "",
|
||||
"metadata": [],
|
||||
"sum": {
|
||||
"dataPoints": [
|
||||
{
|
||||
"attributes": [
|
||||
{
|
||||
"key": "attribute",
|
||||
"value": {
|
||||
"doubleValue": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"startTimeUnixNano": "[WILDCARD]",
|
||||
"timeUnixNano": "[WILDCARD]",
|
||||
"exemplars": [],
|
||||
"flags": 0,
|
||||
"asDouble": -1
|
||||
}
|
||||
],
|
||||
"aggregationTemporality": 2,
|
||||
"isMonotonic": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "gauge",
|
||||
"description": "Example of a Gauge",
|
||||
|
@ -273,8 +72,33 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"startTimeUnixNano": "[WILDCARD]",
|
||||
"timeUnixNano": "[WILDCARD]",
|
||||
"startTimeUnixNano": [WILDCARD],
|
||||
"timeUnixNano": [WILDCARD],
|
||||
"exemplars": [],
|
||||
"flags": 0,
|
||||
"asDouble": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "gauge",
|
||||
"description": "Example of a Gauge",
|
||||
"unit": "",
|
||||
"metadata": [],
|
||||
"gauge": {
|
||||
"dataPoints": [
|
||||
{
|
||||
"attributes": [
|
||||
{
|
||||
"key": "attribute",
|
||||
"value": {
|
||||
"doubleValue": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"startTimeUnixNano": [WILDCARD],
|
||||
"timeUnixNano": [WILDCARD],
|
||||
"exemplars": [],
|
||||
"flags": 0,
|
||||
"asDouble": 1
|
||||
|
@ -298,8 +122,72 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"startTimeUnixNano": "[WILDCARD]",
|
||||
"timeUnixNano": "[WILDCARD]",
|
||||
"startTimeUnixNano": [WILDCARD],
|
||||
"timeUnixNano": [WILDCARD],
|
||||
"count": 1,
|
||||
"sum": 1,
|
||||
"bucketCounts": [
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"explicitBounds": [
|
||||
0,
|
||||
5,
|
||||
10,
|
||||
25,
|
||||
50,
|
||||
75,
|
||||
100,
|
||||
250,
|
||||
500,
|
||||
750,
|
||||
1000,
|
||||
2500,
|
||||
5000,
|
||||
7500,
|
||||
10000
|
||||
],
|
||||
"exemplars": [],
|
||||
"flags": 0,
|
||||
"min": 1,
|
||||
"max": 1
|
||||
}
|
||||
],
|
||||
"aggregationTemporality": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "histogram",
|
||||
"description": "Example of a Histogram",
|
||||
"unit": "",
|
||||
"metadata": [],
|
||||
"histogram": {
|
||||
"dataPoints": [
|
||||
{
|
||||
"attributes": [
|
||||
{
|
||||
"key": "attribute",
|
||||
"value": {
|
||||
"doubleValue": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"startTimeUnixNano": [WILDCARD],
|
||||
"timeUnixNano": [WILDCARD],
|
||||
"count": 1,
|
||||
"sum": 1,
|
||||
"bucketCounts": [
|
||||
|
@ -355,8 +243,8 @@
|
|||
"dataPoints": [
|
||||
{
|
||||
"attributes": [],
|
||||
"startTimeUnixNano": "[WILDCARD]",
|
||||
"timeUnixNano": "[WILDCARD]",
|
||||
"startTimeUnixNano": [WILDCARD],
|
||||
"timeUnixNano": [WILDCARD],
|
||||
"exemplars": [],
|
||||
"flags": 0,
|
||||
"asDouble": 1
|
||||
|
@ -367,23 +255,23 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"name": "observable_up_down_counter",
|
||||
"description": "Example of a ObservableUpDownCounter",
|
||||
"name": "observable_counter",
|
||||
"description": "Example of a ObservableCounter",
|
||||
"unit": "",
|
||||
"metadata": [],
|
||||
"sum": {
|
||||
"dataPoints": [
|
||||
{
|
||||
"attributes": [],
|
||||
"startTimeUnixNano": "[WILDCARD]",
|
||||
"timeUnixNano": "[WILDCARD]",
|
||||
"startTimeUnixNano": [WILDCARD],
|
||||
"timeUnixNano": [WILDCARD],
|
||||
"exemplars": [],
|
||||
"flags": 0,
|
||||
"asDouble": 1
|
||||
}
|
||||
],
|
||||
"aggregationTemporality": 2,
|
||||
"isMonotonic": false
|
||||
"isMonotonic": true
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -395,14 +283,126 @@
|
|||
"dataPoints": [
|
||||
{
|
||||
"attributes": [],
|
||||
"startTimeUnixNano": "[WILDCARD]",
|
||||
"timeUnixNano": "[WILDCARD]",
|
||||
"startTimeUnixNano": [WILDCARD],
|
||||
"timeUnixNano": [WILDCARD],
|
||||
"exemplars": [],
|
||||
"flags": 0,
|
||||
"asDouble": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "observable_gauge",
|
||||
"description": "Example of a ObservableGauge",
|
||||
"unit": "",
|
||||
"metadata": [],
|
||||
"gauge": {
|
||||
"dataPoints": [
|
||||
{
|
||||
"attributes": [],
|
||||
"startTimeUnixNano": [WILDCARD],
|
||||
"timeUnixNano": [WILDCARD],
|
||||
"exemplars": [],
|
||||
"flags": 0,
|
||||
"asDouble": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "observable_up_down_counter",
|
||||
"description": "Example of a ObservableUpDownCounter",
|
||||
"unit": "",
|
||||
"metadata": [],
|
||||
"sum": {
|
||||
"dataPoints": [
|
||||
{
|
||||
"attributes": [],
|
||||
"startTimeUnixNano": [WILDCARD],
|
||||
"timeUnixNano": [WILDCARD],
|
||||
"exemplars": [],
|
||||
"flags": 0,
|
||||
"asDouble": 1
|
||||
}
|
||||
],
|
||||
"aggregationTemporality": 2,
|
||||
"isMonotonic": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "observable_up_down_counter",
|
||||
"description": "Example of a ObservableUpDownCounter",
|
||||
"unit": "",
|
||||
"metadata": [],
|
||||
"sum": {
|
||||
"dataPoints": [
|
||||
{
|
||||
"attributes": [],
|
||||
"startTimeUnixNano": [WILDCARD],
|
||||
"timeUnixNano": [WILDCARD],
|
||||
"exemplars": [],
|
||||
"flags": 0,
|
||||
"asDouble": 1
|
||||
}
|
||||
],
|
||||
"aggregationTemporality": 2,
|
||||
"isMonotonic": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "up_down_counter",
|
||||
"description": "Example of a UpDownCounter",
|
||||
"unit": "",
|
||||
"metadata": [],
|
||||
"sum": {
|
||||
"dataPoints": [
|
||||
{
|
||||
"attributes": [
|
||||
{
|
||||
"key": "attribute",
|
||||
"value": {
|
||||
"doubleValue": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"startTimeUnixNano": [WILDCARD],
|
||||
"timeUnixNano": [WILDCARD],
|
||||
"exemplars": [],
|
||||
"flags": 0,
|
||||
"asDouble": -1
|
||||
}
|
||||
],
|
||||
"aggregationTemporality": 2,
|
||||
"isMonotonic": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "up_down_counter",
|
||||
"description": "Example of a UpDownCounter",
|
||||
"unit": "",
|
||||
"metadata": [],
|
||||
"sum": {
|
||||
"dataPoints": [
|
||||
{
|
||||
"attributes": [
|
||||
{
|
||||
"key": "attribute",
|
||||
"value": {
|
||||
"doubleValue": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"startTimeUnixNano": [WILDCARD],
|
||||
"timeUnixNano": [WILDCARD],
|
||||
"exemplars": [],
|
||||
"flags": 0,
|
||||
"asDouble": -1
|
||||
}
|
||||
],
|
||||
"aggregationTemporality": 2,
|
||||
"isMonotonic": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue