Skip to content

Commit 25d20d1

Browse files
Add alloc profiling start/stop endpoints (#10)
* Add initial alloc profiling start/stop endpoints * Add tests
1 parent 5418faa commit 25d20d1

File tree

2 files changed

+84
-21
lines changed

2 files changed

+84
-21
lines changed

src/PerformanceProfilingHttpEndpoints.jl

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -123,27 +123,37 @@ end
123123

124124
@static if !(isdefined(Profile, :Allocs) && isdefined(PProf, :Allocs))
125125

126-
function allocations_profile_endpoint(::HTTP.Request)
127-
return HTTP.Response(501, "You must use a build of Julia (1.8+) and PProf that support Allocations profiling.")
126+
for f in (:allocations_profile_endpoint, :allocations_start_endpoint, :allocations_stop_endpoint)
127+
@eval function $f(::HTTP.Request)
128+
return HTTP.Response(501, "You must use a build of Julia (1.8+) and PProf that support Allocations profiling.")
129+
end
128130
end
129131

130132
else
131133

132134
function allocations_profile_endpoint(req::HTTP.Request)
133-
134135
uri = HTTP.URI(req.target)
135136
qp = HTTP.queryparams(uri)
136137
if isempty(qp)
137138
@info "TODO: interactive HTML input page"
138139
return HTTP.Response(400, allocs_profile_error_message())
139140
end
140-
141141
sample_rate = convert(Float64, parse(Float64, get(qp, "sample_rate", default_alloc_sample_rate())))
142142
duration = parse(Float64, get(qp, "duration", default_duration()))
143-
144143
return _do_alloc_profile(duration, sample_rate)
145144
end
146145

146+
function allocations_start_endpoint(req::HTTP.Request)
147+
uri = HTTP.URI(req.target)
148+
qp = HTTP.queryparams(uri)
149+
sample_rate = convert(Float64, parse(Float64, get(qp, "sample_rate", default_alloc_sample_rate())))
150+
return _start_alloc_profile(sample_rate)
151+
end
152+
153+
function allocations_stop_endpoint(req::HTTP.Request)
154+
return _stop_alloc_profile()
155+
end
156+
147157
function _do_alloc_profile(duration, sample_rate)
148158
@info "Starting allocation Profiling from PerformanceProfilingHttpEndpoints with configuration:" duration sample_rate
149159

@@ -158,13 +168,30 @@ function _do_alloc_profile(duration, sample_rate)
158168
"allocs_profile-duration=$duration&sample_rate=$sample_rate.pb.gz")
159169
end
160170

171+
function _start_alloc_profile(sample_rate)
172+
@info "Starting allocation Profiling from PerformanceProfilingHttpEndpoints with configuration:" sample_rate
173+
Profile.Allocs.clear()
174+
Profile.Allocs.start(; sample_rate)
175+
return HTTP.Response(200, "Allocation profiling started.")
176+
end
177+
178+
function _stop_alloc_profile()
179+
Profile.Allocs.stop()
180+
prof_name = tempname()
181+
PProf.Allocs.pprof(out=prof_name, web=false)
182+
prof_name = "$prof_name.pb.gz"
183+
return _http_response(read(prof_name), "allocs_profile.pb.gz")
184+
end
185+
161186
end # if isdefined
162187

163188
function serve_profiling_server(;addr="127.0.0.1", port=16825, verbose=false, kw...)
164189
verbose >= 0 && @info "Starting HTTP profiling server on port $port"
165190
router = HTTP.Router()
166191
HTTP.register!(router, "/profile", cpu_profile_endpoint)
167192
HTTP.register!(router, "/allocs_profile", allocations_profile_endpoint)
193+
HTTP.register!(router, "/allocs_profile_start", allocations_start_endpoint)
194+
HTTP.register!(router, "/allocs_profile_stop", allocations_stop_endpoint)
168195
# HTTP.serve! returns listening/serving server object
169196
return HTTP.serve!(router, addr, port; verbose, kw...)
170197
end
@@ -176,8 +203,12 @@ function __init__()
176203
precompile(cpu_profile_endpoint, (HTTP.Request,)) || error("precompilation of package functions is not supposed to fail")
177204
precompile(_do_cpu_profile, (Int,Float64,Float64,Bool)) || error("precompilation of package functions is not supposed to fail")
178205
precompile(allocations_profile_endpoint, (HTTP.Request,)) || error("precompilation of package functions is not supposed to fail")
206+
precompile(allocations_start_endpoint, (HTTP.Request,)) || error("precompilation of package functions is not supposed to fail")
207+
precompile(allocations_stop_endpoint, (HTTP.Request,)) || error("precompilation of package functions is not supposed to fail")
179208
if isdefined(Profile, :Allocs) && isdefined(PProf, :Allocs)
180209
precompile(_do_alloc_profile, (Float64,Float64,)) || error("precompilation of package functions is not supposed to fail")
210+
precompile(_start_alloc_profile, (Float64,)) || error("precompilation of package functions is not supposed to fail")
211+
precompile(_stop_alloc_profile, ()) || error("precompilation of package functions is not supposed to fail")
181212
end
182213
end
183214

test/runtests.jl

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -43,32 +43,64 @@ const url = "http://127.0.0.1:$port"
4343
@testset "Allocation profiling" begin
4444
done = Threads.Atomic{Bool}(false)
4545
# Schedule some work that's known to be expensive, to profile it
46-
t = @async begin
46+
workload() = @async begin
4747
for _ in 1:200
4848
if done[] return end
4949
global a = [[] for i in 1:1000]
5050
yield() # yield to allow the tests to run
5151
end
5252
end
5353

54-
req = HTTP.get("$url/allocs_profile?duration=3", retry=false, status_exception=false)
55-
if !(isdefined(Profile, :Allocs) && isdefined(PProf, :Allocs))
56-
# We should be tesing the Allocs profiling if we're on julia nightly.
57-
@assert VERSION < v"1.8.0-DEV.1346"
58-
@test req.status == 501 # not implemented
59-
else
60-
@test req.status == 200
61-
@test length(req.body) > 0
54+
@testset "allocs_profile endpoint" begin
55+
done[] = false
56+
t = workload()
57+
req = HTTP.get("$url/allocs_profile?duration=3", retry=false, status_exception=false)
58+
if !(isdefined(Profile, :Allocs) && isdefined(PProf, :Allocs))
59+
@assert VERSION < v"1.8.0-DEV.1346"
60+
@test req.status == 501 # not implemented
61+
else
62+
@test req.status == 200
63+
@test length(req.body) > 0
64+
65+
data = read(IOBuffer(req.body), String)
66+
# Test that there's something here
67+
# TODO: actually parse the profile
68+
@test length(data) > 100
69+
end
70+
@info "Finished `allocs_profile` tests, waiting for workload to finish."
71+
done[] = true
72+
wait(t) # handle errors
73+
end
6274

63-
data = read(IOBuffer(req.body), String)
64-
# Test that there's something here
65-
# TODO: actually parse the profile
66-
@test length(data) > 100
75+
@testset "allocs_profile_start/stop endpoints" begin
76+
done[] = false
77+
t = workload()
78+
req = HTTP.get("$url/allocs_profile_start", retry=false, status_exception=false)
79+
if !(isdefined(Profile, :Allocs) && isdefined(PProf, :Allocs))
80+
@assert VERSION < v"1.8.0-DEV.1346"
81+
@test req.status == 501 # not implemented
82+
else
83+
@test req.status == 200
84+
@test String(req.body) == "Allocation profiling started."
85+
end
6786

87+
sleep(3) # Allow workload to run a while before we stop profiling.
88+
89+
req = HTTP.get("$url/allocs_profile_stop", retry=false, status_exception=false)
90+
if !(isdefined(Profile, :Allocs) && isdefined(PProf, :Allocs))
91+
@assert VERSION < v"1.8.0-DEV.1346"
92+
@test req.status == 501 # not implemented
93+
else
94+
@test req.status == 200
95+
data = read(IOBuffer(req.body), String)
96+
# Test that there's something here
97+
# TODO: actually parse the profile
98+
@test length(data) > 100
99+
end
100+
@info "Finished `allocs_profile_stop` tests, waiting for workload to finish."
101+
done[] = true
102+
wait(t) # handle errors
68103
end
69-
@info "Finished tests, waiting for workload to finish."
70-
done[] = true
71-
wait(t) # handle errors
72104
end
73105

74106
@testset "error handling" begin

0 commit comments

Comments
 (0)