Skip to content

Commit

Permalink
pass profile parameters in JSON body
Browse files Browse the repository at this point in the history
  • Loading branch information
d-netto committed May 16, 2024
1 parent c91c118 commit 3bccf98
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 36 deletions.
1 change: 1 addition & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ version = "1.2.0"

[deps]
HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
PProf = "e4faabce-9ead-11e9-39d9-4379958e3056"
Profile = "9abbd945-dff8-562f-b5e8-e1ebf5ef1b79"
Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
Expand Down
85 changes: 53 additions & 32 deletions src/ProfileEndpoints.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module ProfileEndpoints

import HTTP
import JSON3
import Profile
import PProf

Expand Down Expand Up @@ -73,29 +74,32 @@ function cpu_profile_endpoint(req::HTTP.Request)
@info "TODO: interactive HTML input page"
return HTTP.Response(400, cpu_profile_error_message())
end
return handle_cpu_profile(qp)
n = convert(Int, parse(Float64, get(qp, "n", default_n())))
delay = parse(Float64, get(qp, "delay", default_delay()))
duration = parse(Float64, get(qp, "duration", default_duration()))
with_pprof = parse(Bool, get(qp, "pprof", default_pprof()))
return handle_cpu_profile(n, delay, duration, with_pprof)
end

function cpu_profile_start_endpoint(req::HTTP.Request)
uri = HTTP.URI(req.target)
qp = HTTP.queryparams(uri)
return handle_cpu_profile_start(qp)
n = convert(Int, parse(Float64, get(qp, "n", default_n())))
delay = parse(Float64, get(qp, "delay", default_delay()))
return handle_cpu_profile_start(n, delay)
end

function cpu_profile_stop_endpoint(req::HTTP.Request)
Profile.stop_timer()
@info "Stopping CPU Profiling from ProfileEndpoints"
uri = HTTP.URI(req.target)
qp = HTTP.queryparams(uri)
return handle_cpu_profile_stop(qp)
with_pprof = parse(Bool, get(qp, "pprof", default_pprof()))
return handle_cpu_profile_stop(with_pprof)
end

function handle_cpu_profile(qp, stage_path = nothing)
function handle_cpu_profile(n, delay, duration, with_pprof, stage_path = nothing)
# Run the profile
n = convert(Int, parse(Float64, get(qp, "n", default_n())))
delay = parse(Float64, get(qp, "delay", default_delay()))
duration = parse(Float64, get(qp, "duration", default_duration()))
with_pprof = parse(Bool, get(qp, "pprof", default_pprof()))
return _do_cpu_profile(n, delay, duration, with_pprof, stage_path)
end

Expand All @@ -114,10 +118,8 @@ function _do_cpu_profile(n, delay, duration, with_pprof, stage_path = nothing)
return fetch(Threads.@spawn _cpu_profile_get_response_and_write_to_file($path; with_pprof=$with_pprof))
end

function handle_cpu_profile_start(qp)
function handle_cpu_profile_start(n, delay)
# Run the profile
n = convert(Int, parse(Float64, get(qp, "n", default_n())))
delay = parse(Float64, get(qp, "delay", default_delay()))
return _start_cpu_profile(n, delay)
end

Expand All @@ -130,8 +132,7 @@ function _start_cpu_profile(n, delay)
return resp
end

function handle_cpu_profile_stop(qp, stage_path = nothing)
with_pprof = parse(Bool, get(qp, "pprof", default_pprof()))
function handle_cpu_profile_stop(with_pprof, stage_path = nothing)
path = "cpu_profile"
if stage_path === nothing
# Defer the potentially expensive profile symbolication to a non-interactive thread
Expand Down Expand Up @@ -292,7 +293,7 @@ end # if isdefined
### Debug super-endpoint
###

debug_super_endpoint_error_message() = """Need to provide at least a `profile_type` query param"""
debug_super_endpoint_error_message() = """Need to provide at least a `profile_type` argument in the JSON body"""

function debug_profile_endpoint_with_stage_path(stage_path = nothing)
# If a stage has not been assigned, create a temporary directory to avoid
Expand All @@ -301,22 +302,36 @@ function debug_profile_endpoint_with_stage_path(stage_path = nothing)
stage_path = tempdir()
end
function debug_profile_endpoint(req::HTTP.Request)
uri = HTTP.URI(req.target)
qp = HTTP.queryparams(uri)
if isempty(qp) || !haskey(qp, "profile_type")
@info "TODO: interactive HTML input page"
@info "Debugging profile endpoint"
json_body = HTTP.body(req)
if isempty(json_body)
return HTTP.Response(400, debug_super_endpoint_error_message())
end
body = JSON3.read(json_body)
if !haskey(body, "profile_type")
return HTTP.Response(400, debug_super_endpoint_error_message())
end
req_type = qp["profile_type"]
@info "Debugging endpoint hit with type: $req_type"
if req_type == "cpu_profile"
return handle_cpu_profile(qp, stage_path)
elseif req_type == "cpu_profile_start"
return handle_cpu_profile_start(qp)
elseif req_type == "cpu_profile_stop"
return handle_cpu_profile_stop(qp, stage_path)
profile_type = body["profile_type"]
if profile_type == "cpu_profile"
return handle_cpu_profile(
convert(Int, parse(Float64, get(body, "n", default_n()))),
parse(Float64, get(body, "delay", default_delay())),
parse(Float64, get(body, "duration", default_duration())),
parse(Bool, get(body, "pprof", default_pprof())),
stage_path
)
elseif profile_type == "cpu_profile_start"
return handle_cpu_profile_start(
convert(Int, parse(Float64, get(body, "n", default_n()))),
parse(Float64, get(body, "delay", default_delay()))
)
elseif profile_type == "cpu_profile_stop"
return handle_cpu_profile_stop(
parse(Bool, get(body, "pprof", default_pprof())),
stage_path
)
else
error("Unknown profile_type: $req_type")
return HTTP.Response(400, "Unknown profile_type: $profile_type")
end
end
return debug_profile_endpoint
Expand All @@ -326,9 +341,8 @@ end
### Server
###

function serve_profiling_server(;addr="127.0.0.1", port=16825, verbose=false, stage_path = nothing, kw...)
verbose >= 0 && @info "Starting HTTP profiling server on port $port"
router = HTTP.Router()
function register_endpoints(router; stage_path = nothing)
@info "Registering profiling endpoints"
HTTP.register!(router, "/profile", cpu_profile_endpoint)
HTTP.register!(router, "/profile_start", cpu_profile_start_endpoint)
HTTP.register!(router, "/profile_stop", cpu_profile_stop_endpoint)
Expand All @@ -338,8 +352,15 @@ function serve_profiling_server(;addr="127.0.0.1", port=16825, verbose=false, st
HTTP.register!(router, "/allocs_profile_stop", allocations_stop_endpoint)
debug_profile_endpoint = debug_profile_endpoint_with_stage_path(stage_path)
HTTP.register!(router, "/debug_engine", debug_profile_endpoint)
# HTTP.serve! returns listening/serving server object
return HTTP.serve!(router, addr, port; verbose, kw...)
end

function serve_profiling_server(;addr="127.0.0.1", port=16825, verbose=false, stage_path = nothing, kw...)
if verbose >= 0
@info "Starting profiling server on http://$addr:$port"
end
router = HTTP.Router()
register_endpoints(router; stage_path)
return HTTP.serve!(router, addr, port; verbose=verbose, kw...)
end

# Precompile the endpoints as much as possible, so that your /profile attempt doesn't end
Expand Down
1 change: 1 addition & 0 deletions test/Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[deps]
HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
PProf = "e4faabce-9ead-11e9-39d9-4379958e3056"
Profile = "9abbd945-dff8-562f-b5e8-e1ebf5ef1b79"
Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
Expand Down
16 changes: 12 additions & 4 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ using Test

import InteractiveUtils
import HTTP
import JSON3
import Profile
import PProf

Expand Down Expand Up @@ -77,7 +78,9 @@ const url = "http://127.0.0.1:$port"
@testset "debug endpoint cpu profile" begin
done[] = false
t = workload()
req = HTTP.get("$url/debug_engine?profile_type=cpu_profile&duration=3&pprof=false")
headers = ["Content-Type" => "application/json"]
payload = JSON3.write(Dict("profile_type" => "cpu_profile"))
req = HTTP.post("$url/debug_engine", headers, payload)
@test req.status == 200
fname = read(IOBuffer(req.body), String)
@info "filename: $fname"
Expand All @@ -87,13 +90,17 @@ const url = "http://127.0.0.1:$port"
@testset "debug endpoint cpu profile start/end" begin
done[] = false
t = workload()
req = HTTP.get("$url/debug_engine?profile_type=cpu_profile_start")
# JSON payload should contain profile_type
headers = ["Content-Type" => "application/json"]
payload = JSON3.write(Dict("profile_type" => "cpu_profile_start"))
req = HTTP.post("$url/debug_engine", headers, payload)
@test req.status == 200
@test String(req.body) == "CPU profiling started."

sleep(3) # Allow workload to run a while before we stop profiling.

req = HTTP.get("$url/debug_engine?profile_type=cpu_profile_stop&pprof=false")
payload = JSON3.write(Dict("profile_type" => "cpu_profile_stop"))
req = HTTP.post("$url/debug_engine", headers, payload)
@test req.status == 200
fname = read(IOBuffer(req.body), String)
@info "filename: $fname"
Expand All @@ -106,7 +113,8 @@ const url = "http://127.0.0.1:$port"
# We retrive data via PProf directly if `pprof=true`; make sure that path's tested.
# This second call to `profile_stop` should still return the profile, even though
# the profiler is already stopped, as it's `profile_start` that calls `clear()`.
req = HTTP.get("$url/debug_engine?profile_type=cpu_profile_stop&pprof=true")
payload = JSON3.write(Dict("profile_type" => "cpu_profile_stop", "pprof" => "true"))
req = HTTP.post("$url/debug_engine", headers, payload)
@test req.status == 200
# Test that there's something here
# TODO: actually parse the profile
Expand Down

0 comments on commit 3bccf98

Please sign in to comment.