summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore4
-rw-r--r--Makefile7
-rw-r--r--preemption_logger.cu158
-rw-r--r--task_host_utilities.cu384
-rw-r--r--testbench.h3
5 files changed, 554 insertions, 2 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e589da8
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
1preemption_logger
2constant_cycles_kernel
3copy_experiments/copy_contender
4copy_experiments/mon_cross_ctx_copies
diff --git a/Makefile b/Makefile
index 16e17dd..9f6da4d 100644
--- a/Makefile
+++ b/Makefile
@@ -1,13 +1,16 @@
1.PHONY: all copy_experiments clean 1.PHONY: all copy_experiments clean
2 2
3all: constant_cycles_kernel copy_experiments 3all: constant_cycles_kernel preemption_logger copy_experiments
4 4
5constant_cycles_kernel: constant_cycles_kernel.cu testbench.h 5constant_cycles_kernel: constant_cycles_kernel.cu testbench.h
6 nvcc constant_cycles_kernel.cu -o constant_cycles_kernel 6 nvcc constant_cycles_kernel.cu -o constant_cycles_kernel
7 7
8preemption_logger: task_host_utilities.cu preemption_logger.cu testbench.h
9 nvcc preemption_logger.cu -o preemption_logger -g
10
8copy_experiments: 11copy_experiments:
9 $(MAKE) -C $@ 12 $(MAKE) -C $@
10 13
11clean: 14clean:
12 rm -f constant_cycles_kernel 15 rm -f constant_cycles_kernel preemption_logger
13 $(MAKE) -C copy_experiments clean 16 $(MAKE) -C copy_experiments clean
diff --git a/preemption_logger.cu b/preemption_logger.cu
new file mode 100644
index 0000000..667c459
--- /dev/null
+++ b/preemption_logger.cu
@@ -0,0 +1,158 @@
1/* Copyright 2023 Joshua Bakita
2 * Tool that tracks preemptions as discontinuities in GPU time and logs them.
3 *
4 * The logged intervals are output to standard out as a CSV, with each row
5 * containing a single interval. The first column contains the interval start
6 * time in nanoseconds (ns), and the second column contains the interval end
7 * time in ns.
8 *
9 * All progress and error messages and printed to standard error.
10 *
11 * Assumptions:
12 * - The GPU clock continues to tick, regardless of if this is the current
13 * application running.
14 */
15#include <stdio.h>
16#include <stdint.h>
17#include <sys/types.h>
18#include <time.h>
19#include <unistd.h>
20
21#include "task_host_utilities.cu"
22#include "testbench.h"
23
24struct interval {
25 uint64_t start;
26 uint64_t end;
27};
28
29// Minimum time discontinuity which indicates a gap between intervals
30// One clock tick is about 1ns
31#define MIN_PREEMPT_TICKS 2*1000 // ~2us
32
33// Watch for discontinuities in the GPU clock, indicating intervals during
34// which we we were preempted and another context ran.
35// @param ivls Base address of array to log interval start and end times
36// @param num_ivls How many intervals should be logged before exiting?
37__global__ void loop_on_gpu(struct interval *ivls, int num_ivls) {
38 struct interval *curr_ivl = ivls;
39 uint64_t last_time = GlobalTimer64();
40 if (num_ivls <= 0)
41 return;
42 curr_ivl->start = last_time;
43 // While no preemptions are occuring, this repeatedly obtains the clock,
44 // storing the value in `last_time`. When we're switched away from and back
45 // to, there will appear to be a large gap between `now` and `last_time`.
46 // We record `last_time` as the end of the last interval, and `now` as the
47 // start of the next interval, then resume repeatedly obtaining the clock.
48
49 // Continue to log intervals until the pointer `curr_ivl` points to a
50 // location off the end of the `ivls` array.
51 while (curr_ivl < ivls + num_ivls) {
52 uint64_t now = GlobalTimer64();
53 if (now > last_time + MIN_PREEMPT_TICKS) {
54 curr_ivl->end = last_time;
55 curr_ivl++;
56 curr_ivl->start = now;
57 }
58 last_time = now;
59 }
60}
61
62static const char* usage_msg = "\
63Usage: %s NUM_INTERVALS [-r]\n\
64Spin on the GPU, logging intervals to stdout during which we are scheduled.\n\
65 NUM_INTERVALS Number of intervals of execution to log (logs one less than\n\
66 this number of preemptions).\n\
67 -r, --raw Print raw logged GPU times (skip conversion to CPU time).\n";
68
69int main(int argc, char **argv) {
70 cudaError_t err; // Needed for the SAFE() macro
71 struct interval *ivls_gpu, *ivls;
72 struct timespec start, end, end_ivls_only;
73 int num_ivls, skip_conversion;
74 uint64_t dev_ns = 0;
75 double d2h_scale = 1.0, host_s = 0;
76 pid_t pid = getpid();
77
78 // Argument parsing (could be improved)
79 if (argc != 2 && argc != 3) {
80 fprintf(stderr, usage_msg, argv[0]);
81 return 1;
82 }
83 num_ivls = atoi(argv[1]);
84 if (!num_ivls) {
85 fprintf(stderr, usage_msg, argv[0]);
86 return 1;
87 }
88 skip_conversion = argc == 3 &&
89 (!strcmp(argv[2], "-r") || !strcmp(argv[2], "--raw"));
90
91 // Synchronize the GPU and CPU clocks (if requested) using the utilities
92 // from task_host_utilities.cu.
93 if (!skip_conversion) {
94 // Note that this appears to almost always be 1.0 on recent GPUs
95 d2h_scale = GetGPUTimerScale(0);
96 // The following uses a subprocess for clock synchronization. Sketch?
97 GetHostDeviceTimeOffset(0, &host_s, &dev_ns);
98 }
99
100 // Initialize GPU and CPU memory to store the intervals of execution
101 // Note that we use GPU memory rather than pinned host memory in order to make
102 // the time-monitoring loop on-GPU as fast as possible. (Pinned host memory is
103 // slower to read/write from/to.)
104 SAFE(cudaMalloc(&ivls_gpu, num_ivls * sizeof(struct interval)));
105 ivls = (struct interval*)malloc(num_ivls * sizeof(struct interval));
106 if (!ivls) {
107 perror("While allocating memory");
108 return 1;
109 }
110
111 // Monitor execution intervals
112 fprintf(stderr, "(%d) Started monitoring execution for %d intervals...\n",
113 pid, num_ivls);
114 clock_gettime(CLOCK_MONOTONIC_RAW, &start);
115 loop_on_gpu<<<1,1>>>(ivls_gpu, num_ivls);
116 SAFE(cudaDeviceSynchronize());
117 clock_gettime(CLOCK_MONOTONIC_RAW, &end_ivls_only);
118
119 // Pull back results onto the CPU
120 SAFE(cudaMemcpy(ivls, ivls_gpu, num_ivls * sizeof(struct interval),
121 cudaMemcpyDeviceToHost));
122 clock_gettime(CLOCK_MONOTONIC_RAW, &end);
123
124 // Print out the intervals, the start and end times of our monitor, and some
125 // summary statistics
126 for (int i = 0; i < num_ivls; i++) {
127 if (skip_conversion) {
128 printf("%lu, %lu\n", ivls[i].start, ivls[i].end);
129 } else {
130 // Convert all the interval times into CPU time.
131 // First compute how far this interval's starting point is, in CPU seconds,
132 // from the point where the device and CPU clocks were synchronized
133 uint64_t start_time_since_sync = (ivls[i].start - dev_ns) * d2h_scale;
134 // Then convert into absolute CPU time via offset adjustment
135 uint64_t start = s2ns(host_s) + start_time_since_sync;
136 // Repeat the same process for interval end times
137 uint64_t end_time_since_sync = (ivls[i].end - dev_ns) * d2h_scale;
138 uint64_t end = s2ns(host_s) + end_time_since_sync;
139 printf("%lu, %lu\n", start, end);
140 }
141 }
142 fprintf(stderr, "(%d) Total monitor runtime: %ld ms\n", pid,
143 ns2ms(timediff(start, end)));
144 fprintf(stderr, "(%d) CPU logging start: %ld ns\n", pid, time2ns(start));
145 fprintf(stderr, "(%d) CPU logging end: %ld ns\n", pid, time2ns(end));
146 fprintf(stderr, "(%d) CPU copy out end: %ld ns\n", pid, time2ns(end));
147 if (!skip_conversion) {
148 // Difference between CPU time of the start of the first interval and launch time
149 int64_t launch_oh = s2ns(host_s) +
150 (ivls[0].start - dev_ns) * d2h_scale - time2ns(start);
151 fprintf(stderr, "(%d) Aprox launch overhead: %ld ns\n", pid, launch_oh);
152 fprintf(stderr, "(%d) CPU clock - GPU clock: %ld tick gap\n", pid,
153 (long)s2ns(host_s) - dev_ns);
154 fprintf(stderr, "(%d) 1 CPU tick/1 GPU tick: %.3f\n", pid, d2h_scale);
155 }
156
157 return 0;
158}
diff --git a/task_host_utilities.cu b/task_host_utilities.cu
new file mode 100644
index 0000000..e35292f
--- /dev/null
+++ b/task_host_utilities.cu
@@ -0,0 +1,384 @@
1// This file contains the implementation of the functions defined in
2// task_host_utilities.h--used by task_host_utilities.c to work with the GPU.
3#include <cuda_runtime.h>
4#include <errno.h>
5#include <stdint.h>
6#include <stdio.h>
7#include <string.h>
8#include <sys/mman.h>
9#include <sys/types.h>
10#include <sys/wait.h>
11#include <time.h>
12#include <unistd.h>
13
14// The number of GPU nanoseconds to spin for GetGPUTimerScale. Increasing this
15// will both increase the accuracy and the time the function takes to return.
16#define TIMER_SPIN_DURATION (2ull * 1000 * 1000 * 1000)
17
18// This macro takes a cudaError_t value. It prints an error message and returns
19// 0 if the cudaError_t isn't cudaSuccess. Otherwise, it returns nonzero.
20#define CheckCUDAError(val) (InternalCUDAErrorCheck((val), #val, __FILE__, __LINE__))
21
22// Prints an error message and returns 0 if the given CUDA result is an error.