1#!/usr/local/bin/recon 2require 'strict' 3 4local benchrun = require 'benchrun' 5local perfdata = require 'perfdata' 6local csv = require 'csv' 7local sysctl = require 'sysctl' 8local os = require 'os' 9 10local kDefaultDuration = 30 11 12local benchmark = benchrun.new { 13 name = 'xnu.zero_fill_fault_throughput', 14 version = 1, 15 arg = arg, 16 modify_argparser = function(parser) 17 parser:option{ 18 name = '--cpu-workers', 19 description = 'Number of cpu workers' 20 } 21 parser:flag{ 22 name = '--through-max-workers', 23 description = 'Run benchmark for [1..n] cpu workers' 24 } 25 parser:flag{ 26 name = '--through-max-workers-fast', 27 description = 'Run benchmark for [1..2] and each power of four value in [4..n] cpu workers' 28 } 29 parser:option{ 30 name = '--path', 31 description = 'Path to fault throughput binary' 32 } 33 parser:option{ 34 name = '--duration', 35 description = 'How long, in seconds, to run each iteration', 36 default = kDefaultDuration 37 } 38 parser:option{ 39 name = '--variant', 40 description = 'Which benchmark variant to run (sparate-objects or share-objects)', 41 default = 'separate-objects' 42 } 43 parser:option{ 44 name = '--first-cpu', 45 description = 'Enable thread pinning starting from this CPU ID. "enable_skstb=1" boot-arg required', 46 default = -1 47 } 48 end 49} 50 51assert(benchmark.opt.path, "No path supplied for fault throughput binary") 52assert(benchmark.opt.variant == "separate-objects" or 53 benchmark.opt.variant == "share-objects", "Unsupported benchmark variant") 54 55local ncpus, err = sysctl('hw.logicalcpu_max') 56assert(ncpus > 0, 'invalid number of logical cpus') 57local cpu_workers = tonumber(benchmark.opt.cpu_workers) or ncpus 58 59assert(tonumber(benchmark.opt.first_cpu) < ncpus, "invalid first CPU") 60 61local unit = perfdata.unit.custom('pages/sec') 62local tests = {} 63 64function QueueTest(num_cores) 65 table.insert(tests, { 66 path = benchmark.opt.path, 67 num_cores = num_cores, 68 }) 69end 70 71if benchmark.opt.through_max_workers then 72 for i = 1, cpu_workers do 73 QueueTest(i) 74 end 75elseif benchmark.opt.through_max_workers_fast then 76 local i = 1 77 while i <= cpu_workers do 78 QueueTest(i) 79 -- Always do a run with two threads to see what the first part of 80 -- the scaling curve looks like 81 -- (and to measure perf on dual core systems). 82 if i == 1 and cpu_workers >= 2 then 83 QueueTest(i + 1) 84 end 85 i = i * 4 86 end 87else 88 QueueTest(cpu_workers) 89end 90 91for _, test in ipairs(tests) do 92 local args = {test.path, "-v", benchmark.opt.variant, benchmark.opt.duration, test.num_cores, 93 echo = true} 94 if benchmark.opt.first_cpu ~= -1 then 95 args[#args + 1] = benchmark.opt.first_cpu 96 end 97 for out in benchmark:run(args) do 98 local result = out:match("-----Results-----\n(.*)") 99 benchmark:assert(result, "Unable to find result data in output") 100 local data = csv.openstring(result, {header = true}) 101 for field in data:lines() do 102 for k, v in pairs(field) do 103 benchmark.writer:add_value(k, unit, tonumber(v), { 104 [perfdata.larger_better] = true, 105 threads = test.num_cores, 106 variant = benchmark.opt.variant 107 }) 108 end 109 end 110 end 111end 112 113benchmark:finish() 114