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