/// @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 #include #include #include #include #include #include #include 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::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 fields_of(const std::string & line) { std::map 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 & 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 slices; std::vector 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_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 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; }