summaryrefslogtreecommitdiffstats
path: root/scripts/userfaultfd-wrlat.py
blob: 0684be4e04487e8a39aad8cc0e4531dd3d8cbb1c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#!/usr/bin/python3
#
# userfaultfd-wrlat Summarize userfaultfd write fault latencies.
#                   Events are continuously accumulated for the
#                   run, while latency distribution histogram is
#                   dumped each 'interval' seconds.
#
#                   For Linux, uses BCC, eBPF.
#
# USAGE: userfaultfd-lat [interval [count]]
#
# Copyright Virtuozzo GmbH, 2020
#
# Authors:
#   Andrey Gruzdev   <andrey.gruzdev@virtuozzo.com>
#
# This work is licensed under the terms of the GNU GPL, version 2 or
# later.  See the COPYING file in the top-level directory.

from __future__ import print_function
from bcc import BPF
from ctypes import c_ushort, c_int, c_ulonglong
from time import sleep
from sys import argv

def usage():
    print("USAGE: %s [interval [count]]" % argv[0])
    exit()

# define BPF program
bpf_text = """
#include <uapi/linux/ptrace.h>
#include <linux/mm.h>

BPF_HASH(ev_start, u32, u64);
BPF_HISTOGRAM(ev_delta_hist, u64);

/* Trace UFFD page fault start event. */
static void do_event_start()
{
    /* Using "(u32)" to drop group ID which is upper 32 bits */
    u32 tid = (u32) bpf_get_current_pid_tgid();
    u64 ts = bpf_ktime_get_ns();

    ev_start.update(&tid, &ts);
}

/* Trace UFFD page fault end event. */
static void do_event_end()
{
    /* Using "(u32)" to drop group ID which is upper 32 bits */
    u32 tid = (u32) bpf_get_current_pid_tgid();
    u64 ts = bpf_ktime_get_ns();
    u64 *tsp;

    tsp = ev_start.lookup(&tid);
    if (tsp) {
        u64 delta = ts - (*tsp);
        /* Transform time delta to milliseconds */
        ev_delta_hist.increment(bpf_log2l(delta / 1000000));
        ev_start.delete(&tid);
    }
}

/* KPROBE for handle_userfault(). */
int probe_handle_userfault(struct pt_regs *ctx, struct vm_fault *vmf,
        unsigned long reason)
{
    /* Trace only UFFD write faults. */
    if (reason & VM_UFFD_WP) {
        do_event_start();
    }
    return 0;
}

/* KRETPROBE for handle_userfault(). */
int retprobe_handle_userfault(struct pt_regs *ctx)
{
    do_event_end();
    return 0;
}
"""

# arguments
interval = 10
count = -1
if len(argv) > 1:
    try:
        interval = int(argv[1])
        if interval == 0:
            raise
        if len(argv) > 2:
            count = int(argv[2])
    except:    # also catches -h, --help
        usage()

# load BPF program
b = BPF(text=bpf_text)
# attach KRPOBEs
b.attach_kprobe(event="handle_userfault", fn_name="probe_handle_userfault")
b.attach_kretprobe(event="handle_userfault", fn_name="retprobe_handle_userfault")

# header
print("Tracing UFFD-WP write fault latency... Hit Ctrl-C to end.")

# output
loop = 0
do_exit = 0
while (1):
    if count > 0:
        loop += 1
        if loop > count:
            exit()
    try:
        sleep(interval)
    except KeyboardInterrupt:
        pass; do_exit = 1

    print()
    b["ev_delta_hist"].print_log2_hist("msecs")
    if do_exit:
        exit()