Skip to content

Commit a66e21b

Browse files
committed
Snapshot using perf ctlfd
Signed-off-by: Ilana Brooks <[email protected]>
1 parent d36c159 commit a66e21b

File tree

5 files changed

+168
-29
lines changed

5 files changed

+168
-29
lines changed

src/perf_capabilities.ml

+6
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ let kcore = bit 2
88
let snapshot_on_exit = bit 3
99
let last_branch_record = bit 4
1010
let dlfilter = bit 5
11+
let ctlfd = bit 6
1112

1213
include Flags.Make (struct
1314
let allow_intersecting = false
@@ -20,6 +21,7 @@ include Flags.Make (struct
2021
; kcore, "kcore"
2122
; last_branch_record, "last_branch_record"
2223
; dlfilter, "dlfilter"
24+
; ctlfd, "ctlfd"
2325
]
2426
;;
2527
end)
@@ -96,6 +98,9 @@ let supports_kcore = kernel_version_at_least ~major:5 ~minor:5
9698
(* Added in kernel commit ce7b0e4, which made it into 5.4. *)
9799
let supports_snapshot_on_exit = kernel_version_at_least ~major:5 ~minor:4
98100

101+
(* Added in kernel commit d20aff1, which made it into 5.10. *)
102+
let supports_ctlfd = kernel_version_at_least ~major:5 ~minor:10
103+
99104
(* Added in kernel commit 291961f, which made it into 5.14. *)
100105
let supports_dlfilter = kernel_version_at_least ~major:5 ~minor:14
101106

@@ -113,4 +118,5 @@ let detect_exn () =
113118
|> set_if (supports_snapshot_on_exit version) snapshot_on_exit
114119
|> set_if (supports_last_branch_record ()) last_branch_record
115120
|> set_if (supports_dlfilter version) dlfilter
121+
|> set_if (supports_ctlfd version) ctlfd
116122
;;

src/perf_capabilities.mli

+1
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ val kcore : t
1111
val snapshot_on_exit : t
1212
val last_branch_record : t
1313
val dlfilter : t
14+
val ctlfd : t
1415
val detect_exn : unit -> t Deferred.t

src/perf_ctlfd.ml

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
open Core
2+
3+
module Command = struct
4+
type t = string
5+
6+
let snapshot = "snapshot"
7+
end
8+
9+
let ack_msg = "ack\n\000"
10+
let ack_timeout = Time_ns.Span.of_int_sec 1
11+
12+
type t =
13+
{ mutable ctl_rx : Core_unix.File_descr.t
14+
; ctl_tx : Core_unix.File_descr.t
15+
; ack_rx : Core_unix.File_descr.t
16+
; mutable ack_tx : Core_unix.File_descr.t
17+
; ack_buf : Bytes.t
18+
}
19+
20+
let create () =
21+
let ctl_rx, ctl_tx = Core_unix.pipe ~close_on_exec:false () in
22+
let ack_rx, ack_tx = Core_unix.pipe ~close_on_exec:false () in
23+
Core_unix.set_close_on_exec ctl_tx;
24+
Core_unix.set_close_on_exec ack_rx;
25+
{ ctl_rx; ctl_tx; ack_rx; ack_tx; ack_buf = Bytes.make (String.length ack_msg) '\000' }
26+
;;
27+
28+
let close_perf_side_fds t =
29+
Core_unix.close ~restart:true t.ctl_rx;
30+
t.ctl_rx <- Core_unix.File_descr.of_int (-1);
31+
Core_unix.close ~restart:true t.ack_tx;
32+
t.ack_tx <- Core_unix.File_descr.of_int (-1)
33+
;;
34+
35+
let control_opt ({ ctl_rx; ack_tx; _ } as t) =
36+
let p = Core_unix.File_descr.to_int in
37+
( [ [%string "--control=fd:%{p ctl_rx#Int},%{p ack_tx#Int}"] ]
38+
, fun () -> close_perf_side_fds t )
39+
;;
40+
41+
let block_read_ack t =
42+
let total_bytes_read = ref 0 in
43+
while
44+
match
45+
Core_unix.select
46+
~restart:true
47+
~read:[ t.ack_rx ]
48+
~write:[]
49+
~except:[]
50+
~timeout:(`After ack_timeout)
51+
()
52+
with
53+
| { read = []; _ } -> failwith "Perf didn't ack snapshot within timeout"
54+
| { read = [ _fd ]; _ } ->
55+
let bytes_read =
56+
Core_unix.read ~restart:true t.ack_rx ~buf:t.ack_buf ~pos:!total_bytes_read
57+
in
58+
if bytes_read = 0 then failwith "Perf unexpectedly closed ack fd";
59+
total_bytes_read := !total_bytes_read + bytes_read;
60+
!total_bytes_read < Bytes.length t.ack_buf
61+
| _ -> failwith "unreachable"
62+
do
63+
()
64+
done;
65+
if not
66+
(String.equal
67+
ack_msg
68+
(Bytes.unsafe_to_string ~no_mutation_while_string_reachable:t.ack_buf))
69+
then failwith "Receive malformed ack from perf";
70+
Bytes.fill t.ack_buf ~pos:0 ~len:(Bytes.length t.ack_buf) '\000'
71+
;;
72+
73+
let dispatch_and_block_for_ack t (command : Command.t) =
74+
(* Don't do an async write because we want to write immediately; we don't really
75+
care if we block for a bit *)
76+
if Core_unix.single_write_substring ~restart:true t.ctl_tx ~buf:command
77+
<> String.length command
78+
then failwith "Unexpected partial write to perf ctlfd"
79+
else block_read_ack t
80+
;;

src/perf_ctlfd.mli

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
open! Core
2+
3+
type t
4+
5+
val create : unit -> t
6+
7+
(** Returns the additional arguments to `perf record` to use these as control fds, and a
8+
callback to invoke after the fork. *)
9+
val control_opt : t -> string list * (unit -> unit)
10+
11+
module Command : sig
12+
type t
13+
14+
val snapshot : t
15+
end
16+
17+
val dispatch_and_block_for_ack : t -> Command.t -> unit

src/perf_tool_backend.ml

+64-29
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,43 @@ module Recording = struct
8989
type t = { callgraph_mode : Callgraph_mode.t option } [@@deriving sexp]
9090
end
9191

92+
module Snapshot_behavior = struct
93+
module At_exit = struct
94+
type t =
95+
| Sigint
96+
| Sigusr2
97+
end
98+
99+
module Function_call = struct
100+
type t =
101+
| Sigusr2
102+
| Ctlfd of Perf_ctlfd.t
103+
end
104+
105+
type t =
106+
| Never
107+
| At_exit of At_exit.t
108+
| Function_call of Function_call.t
109+
110+
let opt t =
111+
let snapshot_opt =
112+
match t with
113+
| Never -> []
114+
| At_exit Sigint -> [ "--snapshot=e" ]
115+
| Function_call (Ctlfd _ | Sigusr2) | At_exit Sigusr2 -> [ "--snapshot" ]
116+
in
117+
let control_opt, invoke_after_fork =
118+
match t with
119+
| Never | At_exit (Sigint | Sigusr2) | Function_call Sigusr2 -> [], Fn.id
120+
| Function_call (Ctlfd ctlfd) -> Perf_ctlfd.control_opt ctlfd
121+
in
122+
snapshot_opt @ control_opt, invoke_after_fork
123+
;;
124+
end
125+
92126
type t =
93127
{ pid : Pid.t
94-
; when_to_snapshot : [ `at_exit of [ `sigint | `sigusr2 ] | `function_call | `never ]
128+
; snapshot_behavior : Snapshot_behavior.t
95129
}
96130

97131
let perf_selector_of_trace_scope : Trace_scope.t -> string = function
@@ -353,23 +387,23 @@ module Recording = struct
353387
[]
354388
| None, Intel_processor_trace _ | None, Stacktrace_sampling _ -> []
355389
in
356-
let when_to_snapshot =
390+
let snapshot_behavior : Snapshot_behavior.t =
357391
if full_execution
358-
then `never
392+
then Never
359393
else (
360394
match when_to_snapshot with
361395
| Magic_trace_or_the_application_terminates ->
362-
if perf_supports_snapshot_on_exit then `at_exit `sigint else `at_exit `sigusr2
363-
| Application_calls_a_function _ -> `function_call)
396+
if perf_supports_snapshot_on_exit then At_exit Sigint else At_exit Sigusr2
397+
| Application_calls_a_function _ ->
398+
Function_call
399+
(if Perf_capabilities.(do_intersect capabilities ctlfd)
400+
then Ctlfd (Perf_ctlfd.create ())
401+
else Sigusr2))
364402
in
365-
let snapshot_opt =
403+
let snapshot_opt, invoke_after_fork =
366404
match collection_mode with
367-
| Stacktrace_sampling _ -> []
368-
| Intel_processor_trace _ ->
369-
(match when_to_snapshot with
370-
| `never -> []
371-
| `at_exit `sigint -> [ "--snapshot=e" ]
372-
| `function_call | `at_exit `sigusr2 -> [ "--snapshot" ])
405+
| Stacktrace_sampling _ -> [], Fn.id
406+
| Intel_processor_trace _ -> Snapshot_behavior.opt snapshot_behavior
373407
in
374408
let overwrite_opts =
375409
match collection_mode, full_execution with
@@ -403,6 +437,7 @@ module Recording = struct
403437
session, it doesn't also send SIGINT to the perf process, allowing us to send it a
404438
SIGUSR2 first to get it to capture a snapshot before exiting. *)
405439
Core_unix.setpgid ~of_:perf_pid ~to_:perf_pid;
440+
invoke_after_fork ();
406441
let%map () = Async.Clock_ns.after (Time_ns.Span.of_ms 500.0) in
407442
(* Check that the process hasn't failed after waiting, because there's no point pausing
408443
to do recording if we've already failed. *)
@@ -412,35 +447,35 @@ module Recording = struct
412447
| Some (_, exit) -> perf_exit_to_or_error exit
413448
| _ -> Ok ()
414449
in
415-
( { pid = perf_pid; when_to_snapshot }
450+
( { pid = perf_pid; snapshot_behavior }
416451
, { Data.callgraph_mode = selected_callgraph_mode } )
417452
;;
418453

419454
let maybe_take_snapshot t ~source =
420-
let signal =
421-
match t.when_to_snapshot, source with
455+
let should_take_snapshot =
456+
match t.snapshot_behavior, source with
422457
(* [`never] only comes up in [-full-execution] mode. In that mode, perf always gives a
423458
complete trace; there's no snapshotting. *)
424-
| `never, _ -> None
459+
| Never, _ -> false
425460
(* Do not snapshot at the end of a program if the user has set up a trigger symbol. *)
426-
| `function_call, `ctrl_c -> None
461+
| Function_call _, `ctrl_c -> false
427462
(* This shouldn't happen unless there was a bug elsewhere. It would imply that a trigger
428463
symbol was hit when there is no trigger symbol configured. *)
429-
| `at_exit _, `function_call -> None
464+
| At_exit _, `function_call -> false
430465
(* Trigger symbol was hit, and we're configured to look for them. *)
431-
| `function_call, `function_call -> Some Signal.usr2
466+
| Function_call _, `function_call -> true
432467
(* Ctrl-C was hit, and we're configured to look for that. *)
433-
| `at_exit signal, `ctrl_c ->
434-
(* The actual signal to use varies depending on whether or not the user's version of perf
435-
supports snapshot-at-exit. *)
436-
Some
437-
(match signal with
438-
| `sigint -> Signal.int
439-
| `sigusr2 -> Signal.usr2)
468+
| At_exit _, `ctrl_c -> true
440469
in
441-
match signal with
442-
| None -> ()
443-
| Some signal -> Signal_unix.send_i signal (`Pid t.pid)
470+
if should_take_snapshot
471+
then (
472+
match t.snapshot_behavior with
473+
| Never -> failwith "unreachable"
474+
| At_exit Sigusr2 | Function_call Sigusr2 ->
475+
Signal_unix.send_i Signal.usr2 (`Pid t.pid)
476+
| At_exit Sigint -> Signal_unix.send_i Signal.int (`Pid t.pid)
477+
| Function_call (Ctlfd ctlfd) ->
478+
Perf_ctlfd.(dispatch_and_block_for_ack ctlfd Command.snapshot))
444479
;;
445480

446481
let finish_recording t =

0 commit comments

Comments
 (0)