From 4b25360cd620f00b816edf530d9714ea0ab24045 Mon Sep 17 00:00:00 2001 From: Pete Vilter Date: Thu, 22 Dec 2022 15:32:20 -0500 Subject: [PATCH 1/9] initial whack at type inference profile endpoints --- src/ProfileEndpoints.jl | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/ProfileEndpoints.jl b/src/ProfileEndpoints.jl index 54569da..62307f3 100644 --- a/src/ProfileEndpoints.jl +++ b/src/ProfileEndpoints.jl @@ -242,6 +242,24 @@ end end # if isdefined +### +### Type Inference +### + +function typeinf_start_endpoint() + Core.Compiler.__set_measure_typeinf(true) +end + +function typeinf_stop_endpoint() + Core.Compiler.__set_measure_typeinf(true) + timings = Core.Compiler.Timings.clear_and_fetch_timings() + flame_graph = SnoopCompile.to_flamegraph(timings) + prof_name = tempname() + PProf.from_flame_graph(out=prof_name, flame_graph) + prof_name = "$prof_name.pb.gz" + return _http_response(read(prof_name), "allocs_profile.pb.gz") +end + ### ### Server ### @@ -256,6 +274,8 @@ function serve_profiling_server(;addr="127.0.0.1", port=16825, verbose=false, kw HTTP.register!(router, "/allocs_profile", allocations_profile_endpoint) HTTP.register!(router, "/allocs_profile_start", allocations_start_endpoint) HTTP.register!(router, "/allocs_profile_stop", allocations_stop_endpoint) + HTTP.register!(router, "/typeinf_profile_start", typeinf_start_endpoint) + HTTP.register!(router, "/typeinf_profile_stop", typeinf_stop_endpoint) # HTTP.serve! returns listening/serving server object return HTTP.serve!(router, addr, port; verbose, kw...) end @@ -281,6 +301,9 @@ function __init__() precompile(_start_alloc_profile, (Float64,)) || error("precompilation of package functions is not supposed to fail") precompile(_stop_alloc_profile, ()) || error("precompilation of package functions is not supposed to fail") end + + precompile(typeinf_start_endpoint, (HTTP.Request,)) || error("precompilation of package functions is not supposed to fail") + precompile(typeinf_stop_endpoint, (HTTP.Request,)) || error("precompilation of package functions is not supposed to fail") end end # module ProfileEndpoints From ed375167a4902a4dd4f1dcd7f0f4082d2fede5cc Mon Sep 17 00:00:00 2001 From: Pete Vilter Date: Thu, 22 Dec 2022 21:30:44 -0500 Subject: [PATCH 2/9] add deps --- Project.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Project.toml b/Project.toml index e622d1c..2cf344c 100644 --- a/Project.toml +++ b/Project.toml @@ -4,10 +4,12 @@ authors = ["Nathan Daly ", "Dana Wilson version = "1.0.0" [deps] +FlameGraphs = "08572546-2f56-4bcf-ba4e-bab62c3a3f89" HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3" PProf = "e4faabce-9ead-11e9-39d9-4379958e3056" Profile = "9abbd945-dff8-562f-b5e8-e1ebf5ef1b79" Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b" +SnoopCompile = "aa65fe97-06da-5843-b5b1-d5d13cad87d2" [compat] HTTP = "1" From 2e23a43b31024ae3ac8bdbcd6dd2f93f37808045 Mon Sep 17 00:00:00 2001 From: Pete Vilter Date: Thu, 22 Dec 2022 21:30:49 -0500 Subject: [PATCH 3/9] get it working --- src/ProfileEndpoints.jl | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/ProfileEndpoints.jl b/src/ProfileEndpoints.jl index 62307f3..572a6ae 100644 --- a/src/ProfileEndpoints.jl +++ b/src/ProfileEndpoints.jl @@ -1,8 +1,10 @@ module ProfileEndpoints +using FlameGraphs import HTTP import Profile import PProf +using SnoopCompile: InferenceTimingNode using Serialization: serialize @@ -246,16 +248,22 @@ end # if isdefined ### Type Inference ### -function typeinf_start_endpoint() +function typeinf_start_endpoint(req::HTTP.Request) Core.Compiler.__set_measure_typeinf(true) + return HTTP.Response(200, "Type inference profiling started.") end -function typeinf_stop_endpoint() +function typeinf_stop_endpoint(req::HTTP.Request) Core.Compiler.__set_measure_typeinf(true) - timings = Core.Compiler.Timings.clear_and_fetch_timings() - flame_graph = SnoopCompile.to_flamegraph(timings) + timings = if isdefined(Core.Compiler.Timings, :clear_and_fetch_timings) + # TODO: make a root node out of this + Core.Compiler.Timings.clear_and_fetch_timings() + else + InferenceTimingNode(Core.Compiler.Timings._timings[1]) + end + flame_graph = flamegraph(timings) prof_name = tempname() - PProf.from_flame_graph(out=prof_name, flame_graph) + PProf.pprof(flame_graph; out=prof_name, web=false) prof_name = "$prof_name.pb.gz" return _http_response(read(prof_name), "allocs_profile.pb.gz") end From 34f506ac12dc1ba38b0714f5da22b19e402f7222 Mon Sep 17 00:00:00 2001 From: Pete Vilter Date: Thu, 22 Dec 2022 21:46:39 -0500 Subject: [PATCH 4/9] add tests --- src/ProfileEndpoints.jl | 3 ++- test/runtests.jl | 21 ++++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/ProfileEndpoints.jl b/src/ProfileEndpoints.jl index 572a6ae..04827d2 100644 --- a/src/ProfileEndpoints.jl +++ b/src/ProfileEndpoints.jl @@ -1,9 +1,10 @@ module ProfileEndpoints -using FlameGraphs import HTTP import Profile import PProf + +using FlameGraphs using SnoopCompile: InferenceTimingNode using Serialization: serialize diff --git a/test/runtests.jl b/test/runtests.jl index 0315627..22d245e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,8 +1,8 @@ module ProfileEndpointsTests using ProfileEndpoints -using Test using Serialization +using Test import InteractiveUtils import HTTP @@ -153,6 +153,25 @@ const url = "http://127.0.0.1:$port" wait(t) # handle errors end end + + @testset "Type inference profiling" begin + @testset "typeinf start/stop endpoints" begin + resp = HTTP.get("$url/typeinf_profile_start", retry=false, status_exception=false) + @test resp.status == 200 + @test String(resp.body) == "Type inference profiling started." + + # workload + @eval foo() = 2 + @eval foo() + + resp = HTTP.get("$url/typeinf_profile_stop", retry=false, status_exception=false) + @test resp.status == 200 + data = read(IOBuffer(resp.body), String) + # Test that there's something here + # TODO: actually parse the profile + @test length(data) > 100 + end + end @testset "error handling" begin let res = HTTP.get("$url/profile", status_exception=false) From 0f819772ada9469df2457162696d540bcc63e5d9 Mon Sep 17 00:00:00 2001 From: Nathan Daly Date: Thu, 5 Jan 2023 14:30:36 -0700 Subject: [PATCH 5/9] Simplify inference profiling code using SnoopCompile API itself --- Project.toml | 1 + src/ProfileEndpoints.jl | 19 +++++++++---------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Project.toml b/Project.toml index 2cf344c..362d32a 100644 --- a/Project.toml +++ b/Project.toml @@ -10,6 +10,7 @@ PProf = "e4faabce-9ead-11e9-39d9-4379958e3056" Profile = "9abbd945-dff8-562f-b5e8-e1ebf5ef1b79" Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b" SnoopCompile = "aa65fe97-06da-5843-b5b1-d5d13cad87d2" +SnoopCompileCore = "e2b509da-e806-4183-be48-004708413034" [compat] HTTP = "1" diff --git a/src/ProfileEndpoints.jl b/src/ProfileEndpoints.jl index 04827d2..92beded 100644 --- a/src/ProfileEndpoints.jl +++ b/src/ProfileEndpoints.jl @@ -5,7 +5,8 @@ import Profile import PProf using FlameGraphs -using SnoopCompile: InferenceTimingNode +using SnoopCompile +import SnoopCompileCore using Serialization: serialize @@ -249,19 +250,17 @@ end # if isdefined ### Type Inference ### +# WARNING: This is not thread-safe unless your julia has merged +# https://github.com/JuliaLang/julia/pull/47615. function typeinf_start_endpoint(req::HTTP.Request) - Core.Compiler.__set_measure_typeinf(true) + SnoopCompileCore.start_deep_timing() return HTTP.Response(200, "Type inference profiling started.") end function typeinf_stop_endpoint(req::HTTP.Request) - Core.Compiler.__set_measure_typeinf(true) - timings = if isdefined(Core.Compiler.Timings, :clear_and_fetch_timings) - # TODO: make a root node out of this - Core.Compiler.Timings.clear_and_fetch_timings() - else - InferenceTimingNode(Core.Compiler.Timings._timings[1]) - end + SnoopCompileCore.stop_deep_timing() + timings = SnoopCompileCore.finish_snoopi_deep() + flame_graph = flamegraph(timings) prof_name = tempname() PProf.pprof(flame_graph; out=prof_name, web=false) @@ -310,7 +309,7 @@ function __init__() precompile(_start_alloc_profile, (Float64,)) || error("precompilation of package functions is not supposed to fail") precompile(_stop_alloc_profile, ()) || error("precompilation of package functions is not supposed to fail") end - + precompile(typeinf_start_endpoint, (HTTP.Request,)) || error("precompilation of package functions is not supposed to fail") precompile(typeinf_stop_endpoint, (HTTP.Request,)) || error("precompilation of package functions is not supposed to fail") end From e982f956c3fa9f31f9b56990ddee434725459668 Mon Sep 17 00:00:00 2001 From: Nathan Daly Date: Thu, 5 Jan 2023 14:44:53 -0700 Subject: [PATCH 6/9] Fix profile name --- src/ProfileEndpoints.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ProfileEndpoints.jl b/src/ProfileEndpoints.jl index 92beded..8339439 100644 --- a/src/ProfileEndpoints.jl +++ b/src/ProfileEndpoints.jl @@ -265,7 +265,7 @@ function typeinf_stop_endpoint(req::HTTP.Request) prof_name = tempname() PProf.pprof(flame_graph; out=prof_name, web=false) prof_name = "$prof_name.pb.gz" - return _http_response(read(prof_name), "allocs_profile.pb.gz") + return _http_response(read(prof_name), "inference_profile.pb.gz") end ### From 857b42a517881dccd52ad1dcc2ece80584975e01 Mon Sep 17 00:00:00 2001 From: Nathan Daly Date: Thu, 5 Jan 2023 14:54:11 -0700 Subject: [PATCH 7/9] Add check for empty profile --- src/ProfileEndpoints.jl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/ProfileEndpoints.jl b/src/ProfileEndpoints.jl index 8339439..a61ab79 100644 --- a/src/ProfileEndpoints.jl +++ b/src/ProfileEndpoints.jl @@ -261,6 +261,13 @@ function typeinf_stop_endpoint(req::HTTP.Request) SnoopCompileCore.stop_deep_timing() timings = SnoopCompileCore.finish_snoopi_deep() + # Currently, SnoopCompile will throw an error if timings is empty.. + # Reported, here: https://github.com/timholy/SnoopCompile.jl/pull/212/files#r1062926193 + if isempty(timings.children) + # So just return an empty profile.. + return _http_response("", "inference_profile.pb.gz") + end + flame_graph = flamegraph(timings) prof_name = tempname() PProf.pprof(flame_graph; out=prof_name, web=false) From 101b9c9a9178e02c516a4b95a4038b37cfa29673 Mon Sep 17 00:00:00 2001 From: Nathan Daly Date: Thu, 5 Jan 2023 15:40:22 -0700 Subject: [PATCH 8/9] Send a 501 for typeinf profiling if it's not threadsafe to call from the server --- src/ProfileEndpoints.jl | 11 +++++++++-- test/runtests.jl | 10 +++++++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/ProfileEndpoints.jl b/src/ProfileEndpoints.jl index a61ab79..dbd811c 100644 --- a/src/ProfileEndpoints.jl +++ b/src/ProfileEndpoints.jl @@ -250,14 +250,21 @@ end # if isdefined ### Type Inference ### -# WARNING: This is not thread-safe unless your julia has merged -# https://github.com/JuliaLang/julia/pull/47615. function typeinf_start_endpoint(req::HTTP.Request) + if !isdefined(Core.Compiler.Timings, :clear_and_fetch_timings) + # See: https://github.com/JuliaLang/julia/pull/47615. + return HTTP.Response(501, "Type inference profiling isn't thread safe without Julia #47615.") + end SnoopCompileCore.start_deep_timing() return HTTP.Response(200, "Type inference profiling started.") end function typeinf_stop_endpoint(req::HTTP.Request) + if !isdefined(Core.Compiler.Timings, :clear_and_fetch_timings) + # See: https://github.com/JuliaLang/julia/pull/47615. + return HTTP.Response(501, "Type inference profiling isn't thread safe without Julia #47615.") + end + SnoopCompileCore.stop_deep_timing() timings = SnoopCompileCore.finish_snoopi_deep() diff --git a/test/runtests.jl b/test/runtests.jl index 22d245e..5c3810e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -153,17 +153,21 @@ const url = "http://127.0.0.1:$port" wait(t) # handle errors end end - + @testset "Type inference profiling" begin + if !isdefined(Core.Compiler.Timings, :clear_and_fetch_timings) + @test HTTP.get("$url/typeinf_profile_start", retry=false, status_exception=false).status == 501 + @test HTTP.get("$url/typeinf_profile_stop", retry=false, status_exception=false).status == 501 + end @testset "typeinf start/stop endpoints" begin resp = HTTP.get("$url/typeinf_profile_start", retry=false, status_exception=false) @test resp.status == 200 @test String(resp.body) == "Type inference profiling started." - + # workload @eval foo() = 2 @eval foo() - + resp = HTTP.get("$url/typeinf_profile_stop", retry=false, status_exception=false) @test resp.status == 200 data = read(IOBuffer(resp.body), String) From faeeccbd4bfc2f48848fd3714088a942fbbd109d Mon Sep 17 00:00:00 2001 From: Nathan Daly Date: Thu, 5 Jan 2023 15:48:16 -0700 Subject: [PATCH 9/9] Send a 501 for typeinf profiling if it's not threadsafe to call from the server --- test/runtests.jl | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 5c3810e..da34a3a 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -158,22 +158,23 @@ const url = "http://127.0.0.1:$port" if !isdefined(Core.Compiler.Timings, :clear_and_fetch_timings) @test HTTP.get("$url/typeinf_profile_start", retry=false, status_exception=false).status == 501 @test HTTP.get("$url/typeinf_profile_stop", retry=false, status_exception=false).status == 501 - end - @testset "typeinf start/stop endpoints" begin - resp = HTTP.get("$url/typeinf_profile_start", retry=false, status_exception=false) - @test resp.status == 200 - @test String(resp.body) == "Type inference profiling started." - - # workload - @eval foo() = 2 - @eval foo() - - resp = HTTP.get("$url/typeinf_profile_stop", retry=false, status_exception=false) - @test resp.status == 200 - data = read(IOBuffer(resp.body), String) - # Test that there's something here - # TODO: actually parse the profile - @test length(data) > 100 + else + @testset "typeinf start/stop endpoints" begin + resp = HTTP.get("$url/typeinf_profile_start", retry=false, status_exception=false) + @test resp.status == 200 + @test String(resp.body) == "Type inference profiling started." + + # workload + @eval foo() = 2 + @eval foo() + + resp = HTTP.get("$url/typeinf_profile_stop", retry=false, status_exception=false) + @test resp.status == 200 + data = read(IOBuffer(resp.body), String) + # Test that there's something here + # TODO: actually parse the profile + @test length(data) > 100 + end end end