tools/run-cts: Optimize coverage collection

Enable coverage collection when using the test server, which is substantially faster than running in separate, isolated processes.

Use clang's `__llvm_profile_*` APIs to reset the counters between each test case run.

Change-Id: I01f8d0c1b3f215f66cfa50ef0fd51f2522c2ea57
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/113880
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Ben Clayton <bclayton@google.com>
This commit is contained in:
Ben Clayton 2023-01-10 21:55:43 +00:00 committed by Dawn LUCI CQ
parent df2c1621bf
commit 54264d3037
3 changed files with 68 additions and 4 deletions

View File

@ -343,6 +343,7 @@ function(common_compile_options TARGET)
endif() endif()
if (DAWN_EMIT_COVERAGE) if (DAWN_EMIT_COVERAGE)
target_compile_definitions(${TARGET} PRIVATE "DAWN_EMIT_COVERAGE")
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") if(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
target_compile_options(${TARGET} PRIVATE "--coverage") target_compile_options(${TARGET} PRIVATE "--coverage")
target_link_options(${TARGET} PRIVATE "gcov") target_link_options(${TARGET} PRIVATE "gcov")

View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#include <filesystem>
#include <string> #include <string>
#include <tuple> #include <tuple>
#include <utility> #include <utility>
@ -22,6 +23,14 @@
#include "src/dawn/node/binding/GPU.h" #include "src/dawn/node/binding/GPU.h"
#include "tint/tint.h" #include "tint/tint.h"
#ifdef DAWN_EMIT_COVERAGE
extern "C" {
void __llvm_profile_reset_counters(void);
void __llvm_profile_set_filename(const char*);
int __llvm_profile_write_file(void);
}
#endif // DAWN_EMIT_COVERAGE
namespace { namespace {
Napi::Value CreateGPU(const Napi::CallbackInfo& info) { Napi::Value CreateGPU(const Napi::CallbackInfo& info) {
const auto& env = info.Env(); const auto& env = info.Env();
@ -50,6 +59,32 @@ Napi::Value CreateGPU(const Napi::CallbackInfo& info) {
return wgpu::interop::GPU::Create<wgpu::binding::GPU>(env, std::move(flags)); return wgpu::interop::GPU::Create<wgpu::binding::GPU>(env, std::move(flags));
} }
#ifdef DAWN_EMIT_COVERAGE
struct Coverage {
Coverage() : output_path_{GetOutputPath()} {
__llvm_profile_set_filename(output_path_.c_str());
}
~Coverage() { std::filesystem::remove(output_path_); }
static void Begin(const Napi::CallbackInfo& info) {
auto* coverage = static_cast<Coverage*>(info.Data());
std::filesystem::remove(coverage->output_path_);
__llvm_profile_reset_counters();
}
static Napi::Value End(const Napi::CallbackInfo& info) {
__llvm_profile_write_file();
auto* coverage = static_cast<Coverage*>(info.Data());
return Napi::String::New(info.Env(), coverage->output_path_.c_str());
}
private:
static std::filesystem::path GetOutputPath() { return std::tmpnam(nullptr); }
std::filesystem::path output_path_;
};
#endif // DAWN_EMIT_COVERAGE
} // namespace } // namespace
// Initialize() initializes the Dawn node module, registering all the WebGPU // Initialize() initializes the Dawn node module, registering all the WebGPU
@ -68,6 +103,15 @@ Napi::Object Initialize(Napi::Env env, Napi::Object exports) {
// Export function that creates and returns the wgpu::interop::GPU interface // Export function that creates and returns the wgpu::interop::GPU interface
exports.Set(Napi::String::New(env, "create"), Napi::Function::New<CreateGPU>(env)); exports.Set(Napi::String::New(env, "create"), Napi::Function::New<CreateGPU>(env));
#ifdef DAWN_EMIT_COVERAGE
Coverage* coverage = new Coverage();
auto coverage_provider = Napi::Object::New(env);
coverage_provider.Set("begin", Napi::Function::New(env, &Coverage::Begin, nullptr, coverage));
coverage_provider.Set("end", Napi::Function::New(env, &Coverage::End, nullptr, coverage));
coverage_provider.AddFinalizer([](const Napi::Env&, Coverage* c) { delete c; }, coverage);
exports.Set(Napi::String::New(env, "coverage"), coverage_provider);
#endif // DAWN_EMIT_COVERAGE
return exports; return exports;
} }

View File

@ -153,7 +153,7 @@ func run() error {
flag.StringVar(&adapterName, "adapter", "", "name (or substring) of the GPU adapter to use") flag.StringVar(&adapterName, "adapter", "", "name (or substring) of the GPU adapter to use")
flag.BoolVar(&dumpShaders, "dump-shaders", false, "dump WGSL shaders. Enables --verbose") flag.BoolVar(&dumpShaders, "dump-shaders", false, "dump WGSL shaders. Enables --verbose")
flag.BoolVar(&unrollConstEvalLoops, "unroll-const-eval-loops", unrollConstEvalLoopsDefault, "unroll loops in const-eval tests") flag.BoolVar(&unrollConstEvalLoops, "unroll-const-eval-loops", unrollConstEvalLoopsDefault, "unroll loops in const-eval tests")
flag.BoolVar(&genCoverage, "coverage", false, "displays coverage data. Enables --isolated") flag.BoolVar(&genCoverage, "coverage", false, "displays coverage data")
flag.StringVar(&coverageFile, "export-coverage", "", "write coverage data to the given path") flag.StringVar(&coverageFile, "export-coverage", "", "write coverage data to the given path")
flag.Parse() flag.Parse()
@ -258,7 +258,6 @@ func run() error {
} }
if genCoverage { if genCoverage {
isolated = true
llvmCov, err := exec.LookPath("llvm-cov") llvmCov, err := exec.LookPath("llvm-cov")
if err != nil { if err != nil {
return fmt.Errorf("failed to find LLVM, required for --coverage") return fmt.Errorf("failed to find LLVM, required for --coverage")
@ -657,6 +656,9 @@ func (r *runner) runServer(ctx context.Context, id int, caseIndices <-chan int,
if r.colors { if r.colors {
args = append(args, "--colors") args = append(args, "--colors")
} }
if r.covEnv != nil {
args = append(args, "--coverage")
}
if r.verbose { if r.verbose {
args = append(args, args = append(args,
"--verbose", "--verbose",
@ -720,8 +722,9 @@ func (r *runner) runServer(ctx context.Context, id int, caseIndices <-chan int,
res := result{index: idx, testcase: r.testcases[idx]} res := result{index: idx, testcase: r.testcases[idx]}
type Response struct { type Response struct {
Status string Status string
Message string Message string
CoverageData string
} }
postResp, err := http.Post(fmt.Sprintf("http://localhost:%v/run?%v", port, r.testcases[idx]), "", &bytes.Buffer{}) postResp, err := http.Post(fmt.Sprintf("http://localhost:%v/run?%v", port, r.testcases[idx]), "", &bytes.Buffer{})
if err != nil { if err != nil {
@ -758,6 +761,17 @@ func (r *runner) runServer(ctx context.Context, id int, caseIndices <-chan int,
res.status = fail res.status = fail
res.error = fmt.Errorf("unknown status: '%v'", resp.Status) res.error = fmt.Errorf("unknown status: '%v'", resp.Status)
} }
if resp.CoverageData != "" {
coverage, covErr := r.covEnv.Import(resp.CoverageData)
if covErr != nil {
if res.message != "" {
res.message += "\n"
}
res.message += fmt.Sprintf("could not import coverage data from '%v': %v", resp.CoverageData, covErr)
}
res.coverage = coverage
}
} else { } else {
msg, err := ioutil.ReadAll(postResp.Body) msg, err := ioutil.ReadAll(postResp.Body)
if err != nil { if err != nil {
@ -824,6 +838,11 @@ func (r *runner) runParallelIsolated(ctx context.Context) error {
// Once all the results have been printed, a summary will be printed and the // Once all the results have been printed, a summary will be printed and the
// function will return. // function will return.
func (r *runner) streamResults(ctx context.Context, wg *sync.WaitGroup, results chan result) error { func (r *runner) streamResults(ctx context.Context, wg *sync.WaitGroup, results chan result) error {
// If the context was already cancelled then just return
if err := ctx.Err(); err != nil {
return err
}
// Create another goroutine to close the results chan when all the runner // Create another goroutine to close the results chan when all the runner
// goroutines have finished. // goroutines have finished.
start := time.Now() start := time.Now()