Skip to content

Commit

Permalink
Add -xcpu support for general providers
Browse files Browse the repository at this point in the history
Add code to dt_cg_tramp_prologue() to filter on the desired cpu.
Then call the new dt_cg_tramp_prologue_cpu() function, which
assumes a cpu has already been selected, if necessary.

Providers that handle -xcpu specially can bypass that filtering
by calling the new dt_cg_tramp_prologue_cpu() function.

The new function simply calls
dt_cg_tramp_prologue_act(pcb, DT_ACTIVITY_ACTIVE).  The _act
function is called directly by only one provider: dtrace.

Signed-off-by: Eugene Loh <eugene.loh@oracle.com>
Reviewed-by: Kris Van Hees <kris.van.hees@oracle.com>
  • Loading branch information
euloh authored and kvanhees committed Jan 23, 2024
1 parent 6588ea5 commit 65c7605
Show file tree
Hide file tree
Showing 6 changed files with 337 additions and 7 deletions.
28 changes: 25 additions & 3 deletions libdtrace/dt_cg.c
Original file line number Diff line number Diff line change
Expand Up @@ -391,10 +391,32 @@ dt_cg_tramp_prologue_act(dt_pcb_t *pcb, dt_activity_t act)
}
}

void
dt_cg_tramp_prologue_cpu(dt_pcb_t *pcb) {
dt_cg_tramp_prologue_act(pcb, DT_ACTIVITY_ACTIVE);
}

void
dt_cg_tramp_prologue(dt_pcb_t *pcb)
{
dt_cg_tramp_prologue_act(pcb, DT_ACTIVITY_ACTIVE);
dtrace_hdl_t *dtp = pcb->pcb_hdl;

/* Check if we are on the specified CPU (if any). */
if (dtp->dt_options[DTRACEOPT_CPU] != DTRACEOPT_UNSET) {
dt_irlist_t *dlp = &pcb->pcb_ir;

emit(dlp, BPF_MOV_REG(BPF_REG_8, BPF_REG_1));

emit(dlp, BPF_CALL_HELPER(BPF_FUNC_get_smp_processor_id));
emit(dlp, BPF_BRANCH_IMM(BPF_JNE, BPF_REG_0,
dtp->dt_options[DTRACEOPT_CPU],
pcb->pcb_exitlbl));

emit(dlp, BPF_MOV_REG(BPF_REG_1, BPF_REG_8));
}

/* Call the rest of the prologue code generation. */
dt_cg_tramp_prologue_cpu(pcb);
}

/*
Expand Down Expand Up @@ -541,8 +563,8 @@ dt_cg_tramp_copy_args_from_regs(dt_pcb_t *pcb, int called)
* So put the PC in both arg0 and arg1, test the PC, and then zero out
* either arg0 or arg1, as apropriate.
*
* The caller must ensure that %r7 and %r8 contain the values set by
* the dt_cg_tramp_prologue*() functions.
* The caller must ensure that %r7 and %r8 contain the values set by the
* dt_cg_tramp_prologue*() functions.
*/
void
dt_cg_tramp_copy_pc_from_regs(dt_pcb_t *pcb)
Expand Down
1 change: 1 addition & 0 deletions libdtrace/dt_cg.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ extern void dt_cg(dt_pcb_t *, dt_node_t *);
extern void dt_cg_xsetx(dt_irlist_t *, dt_ident_t *, uint_t, int, uint64_t);
extern dt_irnode_t *dt_cg_node_alloc(uint_t, struct bpf_insn);
extern void dt_cg_tramp_prologue_act(dt_pcb_t *pcb, dt_activity_t act);
extern void dt_cg_tramp_prologue_cpu(dt_pcb_t *pcb);
extern void dt_cg_tramp_prologue(dt_pcb_t *pcb);
extern void dt_cg_tramp_clear_regs(dt_pcb_t *pcb);
extern void dt_cg_tramp_copy_regs(dt_pcb_t *pcb);
Expand Down
4 changes: 2 additions & 2 deletions libdtrace/dt_prov_cpc.c
Original file line number Diff line number Diff line change
Expand Up @@ -397,10 +397,10 @@ static int trampoline(dt_pcb_t *pcb, uint_t exitlbl)
int i;
dt_irlist_t *dlp = &pcb->pcb_ir;

dt_cg_tramp_prologue(pcb);
dt_cg_tramp_prologue_cpu(pcb);

/*
* After the dt_cg_tramp_prologue() call, we have:
* After the dt_cg_tramp_prologue_cpu() call, we have:
* // (%r7 = dctx->mst)
* // (%r8 = dctx->ctx)
*/
Expand Down
4 changes: 2 additions & 2 deletions libdtrace/dt_prov_profile.c
Original file line number Diff line number Diff line change
Expand Up @@ -220,10 +220,10 @@ static int trampoline(dt_pcb_t *pcb, uint_t exitlbl)
int i;
dt_irlist_t *dlp = &pcb->pcb_ir;

dt_cg_tramp_prologue(pcb);
dt_cg_tramp_prologue_cpu(pcb);

/*
* After the dt_cg_tramp_prologue() call, we have:
* After the dt_cg_tramp_prologue_cpu() call, we have:
* // (%r7 = dctx->mst)
* // (%r8 = dctx->ctx)
*/
Expand Down
151 changes: 151 additions & 0 deletions test/stress/options/tst.cpu-syscall.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
#!/bin/bash
#
# Oracle Linux DTrace.
# Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
# Licensed under the Universal Permissive License v 1.0 as shown at
# http://oss.oracle.com/licenses/upl.
#

dtrace=$1

DIRNAME="$tmpdir/cpu-syscall.$$.$RANDOM"
mkdir -p $DIRNAME
cd $DIRNAME

#
# Make trigger code and compile it.
#

cat << EOF > main.c
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
int ncpus;
cpu_set_t *mask;
size_t size;
/*
* Grow the CPU mask as needed.
*
* Other ways of determining the number of CPUs available on the system:
* - inspecting the contents of /proc/cpuinfo
* - using sysconf(3) to obtain _SC_NPROCESSORS_CONF and _SC_NPROCESSORS_ONLN
* - inspecting the list of CPU directories under /sys/devices/system/cpu/
*/
int grow_mask() {
ncpus = 1024;
while ((mask = CPU_ALLOC(ncpus)) != NULL &&
sched_getaffinity(0, CPU_ALLOC_SIZE(ncpus), mask) != 0 &&
errno == EINVAL) {
CPU_FREE(mask);
errno = 0;
ncpus *= 2;
}
if (mask == NULL || (errno != 0 && errno != EINVAL))
return 1;
size = CPU_ALLOC_SIZE(ncpus);
return 0;
}
int bind_to_cpu(size_t cpu) {
CPU_ZERO_S(size, mask);
CPU_SET_S(cpu, size, mask);
if (sched_setaffinity(0, size, mask) != 0)
return 1;
return 0;
}
int main(int c, char **v) {
int i, fd = open("/dev/null", O_WRONLY);
if (fd == -1)
return 1;
if (grow_mask() == -1)
return 1;
/* Loop over CPUs (in argv[]). */
for (i = 1; i < c; i++) {
/* Bind to CPU. */
if (bind_to_cpu(atol(v[i])) == -1)
return 1;
/* Call write() from CPU. */
write(fd, &i, sizeof(i));
}
close(fd);
CPU_FREE(mask);
return 0;
}
EOF

gcc main.c
if [ $? -ne 0 ]; then
echo ERROR compilation failed
exit 1
fi

#
# Get CPU list and form expected-results file.
#

cpulist=`awk '/^processor[ ]: [0-9]*$/ { print $3 }' /proc/cpuinfo`
echo $cpulist

echo > expect.txt
for cpu in $cpulist; do
echo $cpu >> expect.txt
done

#
# Run DTrace (without -xcpu). Check that all CPUs appear.
#

$dtrace $dt_flags \
-qn 'syscall::write:entry { printf("%d\n", cpu); }' \
-c "./a.out $cpulist" | sort -n | uniq > actual.txt
if [ $? -ne 0 ]; then
echo ERROR dtrace failed
exit 1
fi

if ! diff -q expect.txt actual.txt > /dev/null; then
echo ERROR did not see expected CPUs in baseline case
echo expect $cpulist
echo actual:
cat actual.txt
exit 1
fi

#
# Run DTrace (with -xcpu). Check that only the specified CPU appears.
#

nerr=0
for cpu0 in $cpulist; do
$dtrace $dt_flags -xcpu=$cpu0 \
-qn 'syscall::write:entry { printf("%d\n", cpu); }' \
-c "./a.out $cpulist" | sort -n | uniq > actual.txt
echo > expect.txt
echo $cpu0 >> expect.txt

if ! diff -q expect.txt actual.txt > /dev/null; then
echo ERROR did not see expected CPUs in cpu $cpu0 case, saw:
cat actual.txt
nerr=$(($nerr + 1))
fi
done

exit $nerr
156 changes: 156 additions & 0 deletions test/unittest/options/tst.cpu-syscall.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
#!/bin/bash
#
# Oracle Linux DTrace.
# Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
# Licensed under the Universal Permissive License v 1.0 as shown at
# http://oss.oracle.com/licenses/upl.
#

dtrace=$1

DIRNAME="$tmpdir/cpu-syscall.$$.$RANDOM"
mkdir -p $DIRNAME
cd $DIRNAME

#
# Make trigger code and compile it.
#

cat << EOF > main.c
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
int ncpus;
cpu_set_t *mask;
size_t size;
/*
* Grow the CPU mask as needed.
*
* Other ways of determining the number of CPUs available on the system:
* - inspecting the contents of /proc/cpuinfo
* - using sysconf(3) to obtain _SC_NPROCESSORS_CONF and _SC_NPROCESSORS_ONLN
* - inspecting the list of CPU directories under /sys/devices/system/cpu/
*/
int grow_mask() {
ncpus = 1024;
while ((mask = CPU_ALLOC(ncpus)) != NULL &&
sched_getaffinity(0, CPU_ALLOC_SIZE(ncpus), mask) != 0 &&
errno == EINVAL) {
CPU_FREE(mask);
errno = 0;
ncpus *= 2;
}
if (mask == NULL || (errno != 0 && errno != EINVAL))
return 1;
size = CPU_ALLOC_SIZE(ncpus);
return 0;
}
int bind_to_cpu(size_t cpu) {
CPU_ZERO_S(size, mask);
CPU_SET_S(cpu, size, mask);
if (sched_setaffinity(0, size, mask) != 0)
return 1;
return 0;
}
int main(int c, char **v) {
int i, fd = open("/dev/null", O_WRONLY);
if (fd == -1)
return 1;
if (grow_mask() == -1)
return 1;
/* Loop over CPUs (in argv[]). */
for (i = 1; i < c; i++) {
/* Bind to CPU. */
if (bind_to_cpu(atol(v[i])) == -1)
return 1;
/* Call write() from CPU. */
write(fd, &i, sizeof(i));
}
close(fd);
CPU_FREE(mask);
return 0;
}
EOF

gcc main.c
if [ $? -ne 0 ]; then
echo ERROR compilation failed
exit 1
fi

#
# Get CPU list and form expected-results file.
#

cpulist=`awk '/^processor[ ]: [0-9]*$/ { print $3 }' /proc/cpuinfo`
echo $cpulist

echo > expect.txt
for cpu in $cpulist; do
echo $cpu >> expect.txt
done

#
# Run DTrace (without -xcpu). Check that all CPUs appear.
#

$dtrace $dt_flags \
-qn 'syscall::write:entry { printf("%d\n", cpu); }' \
-c "./a.out $cpulist" | sort -n | uniq > actual.txt
if [ $? -ne 0 ]; then
echo ERROR dtrace failed
exit 1
fi

if ! diff -q expect.txt actual.txt > /dev/null; then
echo ERROR did not see expected CPUs in baseline case
echo expect $cpulist
echo actual:
cat actual.txt
exit 1
fi

#
# Pick a CPU at random.
#

cpus=( $cpulist )
ncpus=${#cpus[@]}
cpu0=${cpus[$((RANDOM % $ncpus))]}

#
# Run DTrace (with -xcpu). Check that only the specified CPU appears.
#

$dtrace $dt_flags -xcpu=$cpu0 \
-qn 'syscall::write:entry { printf("%d\n", cpu); }' \
-c "./a.out $cpulist" | sort -n | uniq > actual.txt
echo > expect.txt
echo $cpu0 >> expect.txt

if ! diff -q expect.txt actual.txt > /dev/null; then
echo ERROR did not see expected CPUs in cpu $cpu0 case, saw:
cat actual.txt
exit 1
fi

exit 0

0 comments on commit 65c7605

Please sign in to comment.