xref: /xnu-11215/tests/vm/fault_throughput.lua (revision aca3beaa)
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