libdpf/test/profile/party_profile.cpp

479 lines
16 KiB
C++
Raw Permalink Normal View History

/// @file test/profile/party_profile.cpp
/// @brief Workload for the (2+1) party protocols.
///
/// Spawns p0/p1/p2 the same way party_bench does. Each role reports CPU time
/// and the bytes and frames of the flow itself. The repeat barrier is not
/// included in those counters. `wall_ms` includes process startup.
///
/// profile_party --list
/// profile_party --suite core --repeat 5 --warmup 1
/// profile_party --suite extreme
/// profile_party --suite gadget --repeat 3 --warmup 1
/// profile_party --tag "beaver,bench" --repeat 3
/// profile_party --case beaver_dot_n32
///
/// Profile-guided build. The party binaries must be rebuilt with the same
/// mode, because the protocol code lives in those processes:
/// cmake -S test -B build-pgo -DLIBDPF_PGO=generate -DCMAKE_BUILD_TYPE=Release
/// cmake --build build-pgo --target profile_party p0 p1 p2
/// build-pgo/bin/profile_party --suite all --repeat 4 --warmup 1
/// cmake -S test -B build-pgo -DLIBDPF_PGO=use -DCMAKE_BUILD_TYPE=Release
/// cmake --build build-pgo --target profile_party p0 p1 p2
#include "cases.hpp"
#include "registry.hpp"
#include "spawn.hpp"
#include <algorithm>
#include <cstdint>
#include <cstdlib>
#include <iostream>
#include <string>
#include <map>
#include <sstream>
#include <vector>
namespace
{
const char * const kCore[] = {
"beaver_dot_n32",
"beaver_product_100_200",
"beaver_stream_n128",
"beaver_horner_d4",
"beaver_batch_one_round",
"beaver_xor_mux",
"dcf_gt",
"dpf_point_2a_7",
"geneval_point",
"geneval_arith_point",
"blocked_dcf_point",
"ds_key_agrees",
"grotto_prefix_horner",
"verifiable_honest_point",
};
const char * const kExtreme[] = {
"beaver_dot_n128",
"beaver_stream_n2048",
"dcf_dense_gt",
"dcf_blocked_interval_ip",
"grotto_signed_prefix_dense",
"grotto_offset_horner_d3",
"grotto_offset_horner_multipiece",
"grotto_geneval_offset_horner",
"geneval_interval_dense",
"geneval_cmp_dense_gt",
"ds_cmp_many_points",
"recent_offset_poly",
"recent_offset_jet",
"recent_offset_twist",
"recent_offset_repr",
"recent_dist_dpf3_point",
"wildcard_single_leaf",
};
const char * const kGadget[] = {
"arith_proj_m5",
"arith_proj_m17",
"arith_proj_m64",
"arith_mul_p5",
"arith_mul_p7",
"arith_mul_p11",
"arith_thresh_b8",
"arith_thresh_b16",
"arith_chain_mul4",
"yao_if_4_2",
"yao_if_16_8",
"yao_onehot_k4",
"yao_onehot_k8",
"flute_d2",
"flute_d4",
"flute_d8",
"flute_d4_o8",
"shuffle_n16",
"shuffle_n64",
"shuffle_n256",
};
const char kUsage[] =
"profile_party [--list] [--slice S ...] [--tier std|heavy|all|smoke]\n"
" [--suite core|extreme|gadget|all] [--tag TAGS] [--case NAME ...]\n"
" [--repeat N=3] [--warmup W=1]\n"
"Default run is the core suite. --suite gadget is the word-garbling,\n"
"stacked-Yao, FLUTE, and hidden-shuffle battery on the party mesh.\n"
"--slice/--tier select the cost-matrix catalog instead.\n"
"--list with no filter prints every bench flow.\n"
"heavy: wildcard_single_leaf, beaver_stream_n512/n2048, dcf_full_*, *_domain.\n"
"smoke: flows that are not benchable (rejects and negative tests).\n";
bool starts_with(const std::string & s, const char * prefix)
{
const auto n = std::char_traits<char>::length(prefix);
return s.size() >= n && s.compare(0, n, prefix) == 0;
}
std::string slice_of(const std::string & name)
{
if (starts_with(name, "arith_"))
return "arith-garble";
if (starts_with(name, "yao_"))
return "yao-stack";
if (starts_with(name, "flute_"))
return "flute";
if (starts_with(name, "shuffle_"))
return "shuffle";
if (starts_with(name, "beaver_dot"))
return "beaver-dot";
if (starts_with(name, "beaver_stream"))
return "beaver-stream";
if (starts_with(name, "beaver_scale"))
return "beaver-scale";
if (starts_with(name, "beaver_horner") || name == "beaver_sign_horner")
return "beaver-horner";
if (starts_with(name, "beaver_product") || starts_with(name, "beaver_mul")
|| starts_with(name, "beaver_chained"))
return "beaver-product";
if (starts_with(name, "beaver_poly") || name == "beaver_like_terms"
|| starts_with(name, "beaver_nested") || starts_with(name, "beaver_tuple")
|| starts_with(name, "beaver_mixed") || starts_with(name, "beaver_factored"))
return "beaver-poly";
if (starts_with(name, "beaver_"))
return "beaver-misc";
if (starts_with(name, "dcf_full"))
return "dcf-full";
if (starts_with(name, "dcf_dense"))
return "dcf-dense";
if (starts_with(name, "dcf_blocked") || starts_with(name, "blocked_"))
return "blocked";
if (starts_with(name, "dcf_"))
return "dcf";
if (starts_with(name, "geneval_cmp"))
return "geneval-cmp";
if (starts_with(name, "geneval_"))
return "geneval";
if (starts_with(name, "grotto_"))
return "grotto-net";
if (starts_with(name, "ds_"))
return "ds";
if (starts_with(name, "recent_offset") || starts_with(name, "recent_ring")
|| starts_with(name, "recent_closed") || starts_with(name, "recent_exact"))
return "recent-grotto";
if (starts_with(name, "recent_dpf3") || starts_with(name, "recent_dist_dpf3"))
return "dpf3";
if (starts_with(name, "recent_"))
return "recent";
if (starts_with(name, "wildcard_"))
return "wildcard";
if (starts_with(name, "verifiable_"))
return "verifiable";
if (starts_with(name, "dpf_"))
return "dpf-point";
if (starts_with(name, "carry_") || starts_with(name, "extractable_"))
return "auth";
if (starts_with(name, "cov_paint") || starts_with(name, "cov_idcf")
|| name == "cov_eq_at" || name == "cov_ccmp")
return "coverage-cmp";
if (starts_with(name, "cov_geneval") || name == "cov_idpf_at"
|| name == "cov_eval_inner_product")
return "coverage-eval";
if (starts_with(name, "cov_"))
return "coverage";
return "other";
}
std::string tier_of(const std::string & name, bool)
{
const bool domain = name.size() >= 7
&& name.compare(name.size() - 7, 7, "_domain") == 0;
const bool dpf3_walk = name.find("dpf3") != std::string::npos
&& (name.find("update") != std::string::npos
|| name.find("proof") != std::string::npos
|| name.find("blocked") != std::string::npos);
if (name == "wildcard_single_leaf"
|| name == "beaver_stream_n512"
|| name == "beaver_stream_n2048"
|| starts_with(name, "dcf_full_")
|| domain
|| dpf3_walk
|| starts_with(name, "recent_dist_dpf3"))
return "heavy";
const bool negative = name.find("fail") != std::string::npos
|| name.find("tamper") != std::string::npos
|| name.find("reject") != std::string::npos
|| name.find("wrong") != std::string::npos
|| name.find("bad_use") != std::string::npos;
if (negative)
return "smoke";
return "std";
}
std::map<std::string, std::string> fields_of(const std::string & line)
{
std::map<std::string, std::string> out;
std::istringstream in(line);
std::string tok;
while (in >> tok)
{
const auto eq = tok.find('=');
if (eq == std::string::npos)
continue;
out.emplace(tok.substr(0, eq), tok.substr(eq + 1));
}
return out;
}
std::string field(const std::map<std::string, std::string> & m, const char * key)
{
const auto it = m.find(key);
return it == m.end() ? "0" : it->second;
}
} // namespace
int main(int argc, char ** argv)
{
dpf::party::register_all_flows();
std::string suite = "core";
std::string tag;
std::string tier;
std::vector<std::string> slices;
std::vector<std::string> case_names;
std::uint64_t repeat = 3;
std::uint64_t warmup = 1;
bool list_only = false;
bool suite_set = false;
bool tier_set = false;
for (int i = 1; i < argc; ++i)
{
const std::string a = argv[i];
auto need = [&](const char * flag) {
if (i + 1 >= argc)
{
std::cerr << "missing value for " << flag << "\n";
std::exit(2);
}
return std::string(argv[++i]);
};
if (a == "--list")
list_only = true;
else if (a == "--suite")
{
suite = need("--suite");
suite_set = true;
}
else if (a == "--tag")
tag = need("--tag");
else if (a == "--case")
case_names.push_back(need("--case"));
else if (a == "--slice")
slices.push_back(need("--slice"));
else if (a == "--tier")
{
tier = need("--tier");
tier_set = true;
}
else if (a == "--repeat")
repeat = std::stoull(need("--repeat"));
else if (a == "--warmup")
warmup = std::stoull(need("--warmup"));
else if (a == "--help" || a == "-h")
{
std::cout << kUsage;
return 0;
}
else
{
std::cerr << "unknown argument: " << a << "\n" << kUsage;
return 2;
}
}
if (repeat == 0)
repeat = 1;
const bool catalog_mode = tier_set || !slices.empty()
|| (list_only && !suite_set && tag.empty() && case_names.empty());
struct picked
{
std::string name;
std::string slice;
std::string tier;
};
std::vector<picked> picked_flows;
auto take_catalog = [&](const std::string & want_tier) {
for (const auto * f : dpf::party::select_flows(""))
{
const std::string slice = slice_of(f->name);
const std::string flow_tier = tier_of(f->name, f->bench);
if (want_tier == "std" && flow_tier != "std")
continue;
if (want_tier == "heavy" && flow_tier != "heavy")
continue;
if (want_tier == "smoke" && flow_tier != "smoke")
continue;
if (want_tier == "all" && flow_tier == "smoke")
continue;
if (!slices.empty()
&& std::find(slices.begin(), slices.end(), slice) == slices.end())
continue;
if (!case_names.empty()
&& std::find(case_names.begin(), case_names.end(), f->name) == case_names.end())
continue;
picked_flows.push_back(picked{f->name, slice, flow_tier});
}
};
if (catalog_mode)
{
const std::string want = tier_set ? tier : (list_only ? "all" : "std");
if (want != "std" && want != "heavy" && want != "all" && want != "smoke")
{
std::cerr << "unknown tier: " << want << "\n" << kUsage;
return 2;
}
take_catalog(want);
for (const auto & name : case_names)
{
bool found = false;
for (const auto & row : picked_flows)
found = found || row.name == name;
if (!found)
{
std::cerr << "unknown flow: " << name << "\n";
return 2;
}
}
if (picked_flows.empty())
{
std::cerr << "no flows match the requested slice/tier/case\n";
return 2;
}
}
else
{
std::vector<std::string> names;
if (!case_names.empty())
names = std::move(case_names);
else if (!tag.empty())
{
for (const auto * f : dpf::party::select_flows(tag))
names.emplace_back(f->name);
if (names.empty())
{
std::cerr << "no flows match tag: " << tag << "\n";
return 2;
}
}
else if (suite == "core" || suite == "all")
{
for (const char * n : kCore)
names.emplace_back(n);
if (suite == "all")
{
for (const char * n : kExtreme)
names.emplace_back(n);
for (const char * n : kGadget)
names.emplace_back(n);
}
}
else if (suite == "extreme")
{
for (const char * n : kExtreme)
names.emplace_back(n);
}
else if (suite == "gadget")
{
for (const char * n : kGadget)
names.emplace_back(n);
}
else
{
std::cerr << "unknown suite: " << suite << "\n" << kUsage;
return 2;
}
for (const auto & name : names)
{
const auto * f = dpf::party::find_flow(name);
if (!f)
{
std::cerr << "unknown flow: " << name << "\n";
return 2;
}
picked_flows.push_back(picked{name, slice_of(name), tier_of(name, f->bench)});
}
}
if (list_only)
{
std::cout << "family\tslice\tcase\titems\ttier\n";
for (const auto & row : picked_flows)
std::cout << "party\t" << row.slice << '\t' << row.name
<< "\t1\t" << row.tier << '\n';
return 0;
}
dpf::party::spawn_opts opts;
opts.repeat = repeat;
opts.warmup = warmup;
opts.metrics = true;
std::cout
<< "family\tslice\ttier\tflow\trole\twall_ms\tavg_ns\tmin_ns\tmax_ns\t"
<< "bytes_sent\tbytes_recv\tframes_sent\tframes_recv\t"
<< "bytes_recv_from_p2\tbytes_recv_from_peer\t"
<< "bytes_sent_to_p2\tbytes_sent_to_peer\t"
<< "payload_sent\tpayload_recv\twire_overhead\trounds\tprg_evals\t"
<< "avg_bytes_sent\tavg_frames_sent\trc\n";
int fails = 0;
for (const auto & row : picked_flows)
{
const auto result = dpf::party::spawn_trio_flow(row.name, opts);
if (result.metrics_lines.empty())
{
std::cout << "party\t" << row.slice << '\t' << row.tier << '\t'
<< row.name << "\t-\t" << result.wall_ms
<< "\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t"
<< result.rc[0] << "," << result.rc[1] << "," << result.rc[2]
<< "\n";
}
for (const auto & line : result.metrics_lines)
{
const auto f = fields_of(line);
const auto role = field(f, "role");
const int rc = role == "p0" ? result.rc[0]
: role == "p1" ? result.rc[1]
: role == "p2" ? result.rc[2] : 0;
std::cout << "party\t" << row.slice << '\t' << row.tier << '\t'
<< row.name << '\t' << role << '\t' << result.wall_ms << '\t'
<< field(f, "avg_ns") << '\t'
<< field(f, "min_ns") << '\t'
<< field(f, "max_ns") << '\t'
<< field(f, "bytes_sent") << '\t'
<< field(f, "bytes_recv") << '\t'
<< field(f, "frames_sent") << '\t'
<< field(f, "frames_recv") << '\t'
<< field(f, "bytes_recv_from_p2") << '\t'
<< field(f, "bytes_recv_from_peer") << '\t'
<< field(f, "bytes_sent_to_p2") << '\t'
<< field(f, "bytes_sent_to_peer") << '\t'
<< field(f, "payload_sent") << '\t'
<< field(f, "payload_recv") << '\t'
<< field(f, "wire_overhead") << '\t'
<< field(f, "rounds") << '\t'
<< field(f, "prg_evals") << '\t'
<< field(f, "avg_bytes_sent") << '\t'
<< field(f, "avg_frames_sent") << '\t'
<< rc << '\n';
}
if (result.rc[0] || result.rc[1] || result.rc[2])
++fails;
}
return fails ? 1 : 0;
}