diff --git a/etherlink/bin_node/lib_dev/rollup_services.ml b/etherlink/bin_node/lib_dev/rollup_services.ml index 0a0a1c5f3df34ead3f4f78bd82f0eae856c8c322..e326bdf8bb0a1fd5489ed622556240824e3a7231 100644 --- a/etherlink/bin_node/lib_dev/rollup_services.ml +++ b/etherlink/bin_node/lib_dev/rollup_services.ml @@ -281,7 +281,7 @@ let retry_connection (f : Uri.t -> 'a tzresult Lwt.t) endpoint : let call_service ~base ?(media_types = Media_type.all_media_types) rpc b c input = let open Lwt_result_syntax in - Octez_telemetry.RPC_client.trace_call ~media_types base rpc b c @@ fun _ -> + Octez_telemetry.HTTP_client.trace_call ~media_types base rpc b c @@ fun _ -> let*! res = Tezos_rpc_http_client_unix.RPC_client_unix.call_service media_types diff --git a/etherlink/bin_node/lib_dev/rpc_server.ml b/etherlink/bin_node/lib_dev/rpc_server.ml index 8b5e83c72c75692c289a7755cb397f3d24f7b6aa..fc1d31dbae380cdd042d221b4a87bbdffafbdcab 100644 --- a/etherlink/bin_node/lib_dev/rpc_server.ml +++ b/etherlink/bin_node/lib_dev/rpc_server.ml @@ -18,7 +18,7 @@ type evm_services_methods = { type block_production = [`Single_node | `Disabled] module Resto = struct - let callback server Evm_directory.{dir; extra} = + let callback ~port server Evm_directory.{dir; extra} = let open Cohttp in let open Lwt_syntax in let callback_log conn req body = @@ -31,7 +31,8 @@ module Resto = struct let meth = req |> Request.meth |> Code.string_of_method in let* body_str = body |> Cohttp_lwt.Body.to_string in let* () = Events.callback_log ~uri ~meth ~body:body_str in - Tezos_rpc_http_server.RPC_server.resto_callback + Octez_telemetry.HTTP_server.resto_callback + ~port server conn req @@ -89,7 +90,7 @@ module Resto = struct ~max_active_connections ~host server - ~callback:(callback server directory) + ~callback:(callback ~port server directory) ~conn_closed node in diff --git a/manifest/product_octez.ml b/manifest/product_octez.ml index b804bea6aa70c7f841d9fcfafda2a5d2f02764f1..63ae2dda8ea40d57834f011a8a2d08345ef71027 100644 --- a/manifest/product_octez.ml +++ b/manifest/product_octez.ml @@ -4515,6 +4515,22 @@ let _octez_rpc_http_server_tests = alcotezt; ] +let octez_telemetry = + octez_lib + "octez-telemetry" + ~internal_name:"Octez_telemetry" + ~path:"src/lib_telemetry" + ~synopsis:"Convenient wrappers to trace Octez libraries with Opentelemetry" + ~deps: + [ + octez_base |> open_ ~m:"TzPervasives" |> open_; + opentelemetry; + opentelemetry_lwt; + resto; + octez_rpc_http; + octez_rpc_http_server; + ] + let _bip39_generator = private_exe "bip39_generator" @@ -5551,6 +5567,7 @@ let octez_smart_rollup_node_lib = opentelemetry_lwt; opentelemetry_client_cohttp_lwt; opentelemetry_ambient_context_lwt; + octez_telemetry; ] let wasm_helpers_intf_modules = ["wasm_utils_intf"] @@ -9222,21 +9239,6 @@ let _octez_scoru_wasm_debugger = octez_scoru_wasm_debugger_lib |> open_; ] -let octez_telemetry = - octez_lib - "octez-telemetry" - ~internal_name:"Octez_telemetry" - ~path:"src/lib_telemetry" - ~synopsis:"Convenient wrappers to trace Octez libraries with Opentelemetry" - ~deps: - [ - octez_base |> open_ ~m:"TzPervasives" |> open_; - opentelemetry; - opentelemetry_lwt; - resto; - octez_rpc_http; - ] - let _octez_scoru_wasm_regressions = tezt ["tezos_scoru_wasm_regressions"] diff --git a/src/lib_smart_rollup_node/dune b/src/lib_smart_rollup_node/dune index c16a7c505a40863c3cf3143f90df44d97e446704..ed3a48c24cd2393e39fe8e8becb2bc33b07b3a87 100644 --- a/src/lib_smart_rollup_node/dune +++ b/src/lib_smart_rollup_node/dune @@ -50,7 +50,8 @@ octez-l2-libs.scoru-wasm-fast opentelemetry-lwt octez-libs.opentelemetry-client-cohttp-lwt - opentelemetry.ambient-context.lwt) + opentelemetry.ambient-context.lwt + octez-libs.octez-telemetry) (flags (:standard) -open Tezos_base.TzPervasives diff --git a/src/lib_smart_rollup_node/rpc_directory_helpers.ml b/src/lib_smart_rollup_node/rpc_directory_helpers.ml index 0e5faac135f4db7c5bc6e4e7ade3c3b39c10a5a2..4a28e12a5c0a560b92a1430561e307f2936c9dec 100644 --- a/src/lib_smart_rollup_node/rpc_directory_helpers.ml +++ b/src/lib_smart_rollup_node/rpc_directory_helpers.ml @@ -59,22 +59,8 @@ module Make_sub_directory (S : PARAM) = struct ~kind:Span_kind_server ~service_name trace_name - @@ fun scope -> - Opentelemetry.Scope.add_attrs scope (fun () -> - let req = Tezos_rpc.Service.forge_partial_request service a q in - let path, query = - match String.split_on_char '?' (Uri.path_and_query req.uri) with - | [] -> ("", `None) - | [p] -> (p, `None) - | p :: q :: _ -> (p, `String q) - in - [ - ("http.request.method", `String meth); - ("http.route", `String route); - ("url.path", `String path); - ("url.query", query); - ]) ; - f a q i + ~attrs:[("http.route", `String route)] + @@ fun _scope -> f a q i in directory := Tezos_rpc.Directory.register !directory service f diff --git a/src/lib_smart_rollup_node/rpc_server.ml b/src/lib_smart_rollup_node/rpc_server.ml index 75bb005108c42432dc211e341bd4c49d61b3e92e..4f7252fde2290365a2e1be135df23f987c44044d 100644 --- a/src/lib_smart_rollup_node/rpc_server.ml +++ b/src/lib_smart_rollup_node/rpc_server.ml @@ -83,7 +83,8 @@ let start ~rpc_addr ~rpc_port ~acl ~cors dir = RPC_server.launch ~host server - ~callback:(RPC_server.resto_callback server) + ~callback: + (Octez_telemetry.HTTP_server.resto_callback ~port:rpc_port server) node in return {server; host; node; acl} diff --git a/src/lib_telemetry/RPC_client.ml b/src/lib_telemetry/HTTP_client.ml similarity index 100% rename from src/lib_telemetry/RPC_client.ml rename to src/lib_telemetry/HTTP_client.ml diff --git a/src/lib_telemetry/RPC_client.mli b/src/lib_telemetry/HTTP_client.mli similarity index 100% rename from src/lib_telemetry/RPC_client.mli rename to src/lib_telemetry/HTTP_client.mli diff --git a/src/lib_telemetry/HTTP_server.ml b/src/lib_telemetry/HTTP_server.ml new file mode 100644 index 0000000000000000000000000000000000000000..1361fd0ad37ead20afbc1f16e159287a170db58d --- /dev/null +++ b/src/lib_telemetry/HTTP_server.ml @@ -0,0 +1,97 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2025 Nomadic Labs *) +(* Copyright (c) 2025 Functori *) +(* *) +(*****************************************************************************) + +let attributes ~port ~meth io req () = + let uri = Cohttp.Request.uri req in + let scheme = + match Uri.scheme uri with None -> `None | Some scheme -> `String scheme + in + let host = + match Uri.host uri with None -> `None | Some host -> `String host + in + let path, query = + match String.split_on_char '?' (Uri.path_and_query uri) with + | [] -> ("", `None) + | [p] -> (p, `None) + | p :: q :: _ -> (p, `String q) + in + let transport, client_addr, peer_addr, peer_port = + match io with + | Conduit_lwt_unix.TCP {ip; port; _} -> + let ip = Ipaddr.to_string ip in + (`String "tcp", `String ip, `String ip, `Int port) + | Conduit_lwt_unix.Tunnel (t, _, _) -> + (`String "tunnel", `None, `String t, `None) + | Conduit_lwt_unix.Domain_socket {path; _} -> + (`String "unix socket", `None, `String path, `None) + | Conduit_lwt_unix.Vchan {domid; port} -> + ( `String "vchan", + `None, + `String (Format.sprintf "dom%d" domid), + `String port ) + in + let headers_attrs = + List.map + (fun (header, v) -> ("http.request.header." ^ header, `String v)) + (Cohttp.Header.to_list req.headers) + in + [ + ("http.request.method", `String meth); + ("url.scheme", scheme); + ("url.path", `String path); + ("url.query", query); + ("network.protocol.name", `String "http"); + ("server.port", `Int port); + ("client.address", client_addr); + ("network.peer.address", peer_addr); + ("network.peer.port", peer_port); + ( "network.protocol.version", + `String (Cohttp.Code.string_of_version req.version) ); + ("network.transport", transport); + ("server.address", host); + ] + @ headers_attrs + +let result_attributes scope action () = + match action with + | `Expert (resp, _) | `Response (resp, _) -> + let headers_attrs = + List.map + (fun (header, v) -> ("http.response.header." ^ header, `String v)) + (Cohttp.Header.to_list (Cohttp_lwt.Response.headers resp)) + in + let status = Cohttp_lwt.Response.status resp in + let status_code = Cohttp.Code.code_of_status status in + Opentelemetry.Scope.set_status + scope + (let code = + if status_code >= 300 then + Opentelemetry.Span_status.Status_code_error + else Status_code_ok + in + Opentelemetry.Span_status.make + ~message:(Cohttp.Code.string_of_status status) + ~code) ; + [("http.response.status_code", `Int status_code)] @ headers_attrs + +let resto_callback ~port server ((io, _conn) as iconn) (req : Cohttp.Request.t) + body = + let open Lwt_syntax in + let meth = Cohttp.Code.string_of_method req.meth in + let trace_name = String.concat " " [meth; req.resource] in + Opentelemetry_lwt.Trace.with_ + ~kind:Span_kind_server + ~service_name:"HTTP_server" + trace_name + @@ fun scope -> + Opentelemetry.Scope.add_attrs scope (attributes ~port ~meth io req) ; + let* action = + Tezos_rpc_http_server.RPC_server.resto_callback server iconn req body + in + Opentelemetry.Scope.add_attrs scope (result_attributes scope action) ; + return action diff --git a/src/lib_telemetry/HTTP_server.mli b/src/lib_telemetry/HTTP_server.mli new file mode 100644 index 0000000000000000000000000000000000000000..33907a6fe50e306e3f76bbdca4eceeb62282ed04 --- /dev/null +++ b/src/lib_telemetry/HTTP_server.mli @@ -0,0 +1,32 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2025 Nomadic Labs *) +(* Copyright (c) 2025 Functori *) +(* *) +(*****************************************************************************) + +(** Module providing OpenTelemetry tracing capabilities for Resto-based RPC + services. This allows for profiling of RPC requests and responses. *) + +(** [resto_callback ~port server conn request body] is a wrapper around the + standard Resto callback that adds OpenTelemetry tracing information to RPC + calls. + + This function intercepts RPC requests, creates spans with appropriate + attributes (method, path, status code), and measures the duration of request + processing. + + @param port The port number on which the RPC server is listening + @param server The Resto RPC server instance + @param conn The connection information from the HTTP server + @param request The HTTP request being processed + @param body The request body + @return The HTTP response action to be taken by the server *) +val resto_callback : + port:int -> + Tezos_rpc_http_server.RPC_server.server -> + Conduit_lwt_unix.flow * Cohttp.Connection.t -> + Cohttp.Request.t -> + Cohttp_lwt.Body.t -> + Cohttp_lwt_unix.Server.response_action Lwt.t diff --git a/src/lib_telemetry/dune b/src/lib_telemetry/dune index 6a49854fc37c6c07f370e7b18cf181f5ec3e8dcc..7168f14767b93d7315c44268d7848a48b0b344a4 100644 --- a/src/lib_telemetry/dune +++ b/src/lib_telemetry/dune @@ -10,7 +10,8 @@ opentelemetry opentelemetry-lwt octez-libs.resto - octez-libs.rpc-http) + octez-libs.rpc-http + octez-libs.rpc-http-server) (flags (:standard) -open Tezos_base.TzPervasives