We’re comparing performance of the apps.mellanox.connectx driver between two systems:
AMD EPYC 7443P, Mellanox Technologies MT28800 Family [ConnectX-5 Ex]
Intel Xeon Silver 4116 @ 2.10GHz, Mellanox Technologies MT27800 Family [ConnectX-5]
BIOS settings unknown are at this point.
The configuration is one 2x100G port NIC, with both ports wired to each other. Packetblaster will transmit packets one one port in both test cases. In the Receive performance test case packets are received in the other port.
The code under test can be found here: eugeneia/mellanox-benchmark
We run benchmarks on a matrix of parameters including
- packet size (including 4-byte CRC)
- number of workers (cpu cores) provided to the application
- number of hardware send/receive queues per worker
The benchmark is run three times for every parameter configuration and we show the minimum, maximum, and average of the results.
We also overlay 100G linerate (grey dashed line) to put the results into perspective.
Gbps <- function(mpps,pktsize) {
mpps*(12+8+pktsize)*8/1000
}
Linerate <- function(G, pktsize) {
G*1e9 / ((12+8+pktsize)*8)
}
Packetblaster performance
Packetblaster is a optimized TX routine for our Connect-X driver. It should demonstrate the maximum transmit rate supported by the NIC.
This should reproduce the results in ConnectX: Review N*SQ 64B transmit performance mellanox (Rev 2) from 2016. It quite doesn’t though, and is not the same code. Have to investigate what’s the difference.
packetblaster <- (mellanox.tx.only.queues.sizes.intel.100e6.coarse %>%
mutate(system="Intel Xeon Silver 4116 @ 2.10GHz")) %>%
bind_rows((mellanox.tx.only.queues.sizes.epyc.100e6.coarse %>%
mutate(system="AMD EPYC 7443P"))) %>%
mutate(workers=sprintf("%d workers (cores)", workers),
queues=sprintf("%d queues", queues)) %>%
group_by(system, workers, queues, pktsize) %>%
summarise(min_mpps=min(rate), avg_mpps=mean(rate), max_mpps=max(rate),
min_loss=(min(drop+error)), min_loss=(mean(drop+error)), max_loss=(max(drop+error))) %>%
ungroup() %>%
mutate(Gbps=Gbps(max_mpps-max_loss, pktsize))
`summarise()` has grouped output by 'system', 'workers', 'queues'. You can override using the `.groups` argument.
ggplot(packetblaster, aes(x=pktsize, color=queues)) +
facet_grid(system ~ workers) +
geom_line(aes(y=max_mpps, linetype="0_tx")) +
geom_line(aes(y=max_loss, linetype="1_loss")) +
geom_point(aes(y=avg_mpps, shape="avg"), alpha=0.5) +
geom_point(aes(y=max_mpps, shape="max"), alpha=0.5) +
geom_point(aes(y=min_mpps, shape="min"), alpha=0.5) +
geom_line(aes(y=Linerate(100, pktsize)/1e6, linetype="2_linerate"), color='grey') +
coord_cartesian(ylim=c(NA, max(packetblaster$max_mpps))) +
ggtitle("Multi core performance by number of queues per worker and packet size",
subtitle="apps.mellanox.connectx: Packetblaster rate MPPS (TX only)")

Receive performance
Packetblaster transmits on one port, and packets are received on the other port. Each side has a dedicated CPU core for each worker. I.e., “6 workers” means six cores used for transmit, and six distinct cores are used for receive.
txrx <- (mellanox.tx.rx.queues.sizes.intel.100e6.coarse %>%
mutate(system="Intel Xeon Silver 4116 @ 2.10GHz")) %>%
bind_rows((mellanox.tx.rx.queues.sizes.epyc.100e6.coarse %>%
mutate(system="AMD EPYC 7443P") %>% na.omit())) %>%
mutate(workers=sprintf("%d workers (cores)", workers),
queues=sprintf("%d queues", queues)) %>%
mutate(rx_mpps=rxrate-(rxdrop+rxerror)) %>%
group_by(system, workers, queues, pktsize) %>%
summarise(min_mpps=min(txrate),
avg_mpps=mean(txrate),
max_mpps=max(txrate),
min_rx_mpps=(min(rx_mpps)),
avg_rx_mpps=(mean(rx_mpps)),
max_rx_mpps=(max(rx_mpps))) %>%
ungroup() %>%
mutate(rxGbps=Gbps(max_rx_mpps, pktsize), Gbps=Gbps(max_mpps, pktsize))
`summarise()` has grouped output by 'system', 'workers', 'queues'. You can override using the `.groups` argument.
ggplot(txrx, aes(x=pktsize, color=queues)) +
facet_grid(system ~ workers) +
geom_line(aes(y=max_rx_mpps, linetype="0_rx")) +
geom_line(aes(y=max_mpps, linetype="1_tx")) +
geom_point(aes(y=avg_rx_mpps, shape="avg"), alpha=0.5) +
geom_point(aes(y=max_rx_mpps, shape="max"), alpha=0.5) +
geom_point(aes(y=min_rx_mpps, shape="min"), alpha=0.5) +
geom_line(aes(y=Linerate(100, pktsize)/1e6, linetype="2_linerate"), color='grey') +
coord_cartesian(ylim=c(NA, max(txrx$max_mpps))) +
ggtitle("Multi core performance by number of queues per worker and packet size",
subtitle="apps.mellanox.connectx: RX rate of combined receive queues in MPPS")

Zoom into results on EPYC
Packetblaster
packetblaster_epyc <- filter(packetblaster, system=="AMD EPYC 7443P")
ggplot(packetblaster_epyc, aes(x=pktsize, color=queues)) +
facet_grid(system ~ workers) +
geom_line(aes(y=max_mpps, linetype="0_tx")) +
geom_line(aes(y=max_loss, linetype="1_loss")) +
geom_point(aes(y=avg_mpps, shape="avg"), alpha=0.5) +
geom_point(aes(y=max_mpps, shape="max"), alpha=0.5) +
geom_point(aes(y=min_mpps, shape="min"), alpha=0.5) +
geom_line(aes(y=Linerate(100, pktsize)/1e6, linetype="2_linerate"), color='grey') +
coord_cartesian(ylim=c(NA, max(packetblaster_epyc$max_mpps))) +
ggtitle("Multi core performance by number of queues per worker and packet size",
subtitle="apps.mellanox.connectx: Packetblaster rate MPPS (TX only)")

Receive
txrx_epyc <- filter(txrx, system=="AMD EPYC 7443P")
ggplot(txrx_epyc, aes(x=pktsize, color=queues)) +
facet_grid(system ~ workers) +
geom_line(aes(y=max_rx_mpps, linetype="0_rx")) +
geom_line(aes(y=max_mpps, linetype="1_tx")) +
geom_point(aes(y=avg_rx_mpps, shape="avg"), alpha=0.5) +
geom_point(aes(y=max_rx_mpps, shape="max"), alpha=0.5) +
geom_point(aes(y=min_rx_mpps, shape="min"), alpha=0.5) +
geom_line(aes(y=Linerate(100, pktsize)/1e6, linetype="2_linerate"), color='grey') +
coord_cartesian(ylim=c(NA, max(txrx_epyc$max_mpps))) +
ggtitle("Multi core performance by number of queues per worker and packet size",
subtitle="apps.mellanox.connectx: RX rate of combined receive queues in MPPS")

RX Drops
ggplot(txrx, aes(x=pktsize, color=queues)) +
facet_grid(system ~ workers) +
geom_line(aes(y=max_mpps-max_rx_mpps, linetype="0_drop")) +
geom_line(aes(y=max_mpps, linetype="1_tx")) +
geom_point(aes(y=avg_mpps-avg_rx_mpps, shape="avg"), alpha=0.5) +
geom_point(aes(y=max_mpps-max_rx_mpps, shape="max"), alpha=0.5) +
geom_point(aes(y=min_mpps-min_rx_mpps, shape="min"), alpha=0.5) +
geom_line(aes(y=Linerate(100, pktsize)/1e6, linetype="2_linerate"), color='grey') +
coord_cartesian(ylim=c(NA, max(txrx$max_mpps))) +
ggtitle("Multi core performance by number of queues per worker and packet size",
subtitle="apps.mellanox.connectx: Loss rate of combined receive queues in MPPS")

Help
This is an R Markdown Notebook. When you execute code within the notebook, the results appear beneath the code.
Try executing this chunk by clicking the Run button within the chunk or by placing your cursor inside it and pressing Ctrl+Shift+Enter.
Add a new chunk by clicking the Insert Chunk button on the toolbar or by pressing Ctrl+Alt+I.
When you save the notebook, an HTML file containing the code and output will be saved alongside it (click the Preview button or press Ctrl+Shift+K to preview the HTML file).
The preview shows you a rendered HTML copy of the contents of the editor. Consequently, unlike Knit, Preview does not run any R code chunks. Instead, the output of the chunk when it was last run in the editor is displayed.
LS0tDQp0aXRsZTogImFwcHMubWVsbGFub3guY29ubmVjdHg6IFBhY2tldGJsYXN0ZXIgYW5kIFJlY2VpdmUgcGVyZm9ybWFuY2UiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpXZeKAmXJlIGNvbXBhcmluZyBwZXJmb3JtYW5jZSBvZiB0aGUgYGFwcHMubWVsbGFub3guY29ubmVjdHhgIGRyaXZlciBiZXR3ZWVuIHR3bw0Kc3lzdGVtczoNCg0KIC0gYEFNRCBFUFlDIDc0NDNQYCwgYE1lbGxhbm94IFRlY2hub2xvZ2llcyBNVDI4ODAwIEZhbWlseSBbQ29ubmVjdFgtNSBFeF1gDQogLSBgSW50ZWwgWGVvbiBTaWx2ZXIgNDExNiBAIDIuMTBHSHpgLCBgTWVsbGFub3ggVGVjaG5vbG9naWVzIE1UMjc4MDAgRmFtaWx5IFtDb25uZWN0WC01XWANCg0KQklPUyBzZXR0aW5ncyB1bmtub3duIGFyZSBhdCB0aGlzIHBvaW50Lg0KDQpUaGUgY29uZmlndXJhdGlvbiBpcyBvbmUgMngxMDBHIHBvcnQgTklDLCB3aXRoIGJvdGggcG9ydHMgd2lyZWQgdG8gZWFjaCBvdGhlci4NClBhY2tldGJsYXN0ZXIgd2lsbCB0cmFuc21pdCBwYWNrZXRzIG9uZSBvbmUgcG9ydCBpbiBib3RoIHRlc3QgY2FzZXMuDQpJbiB0aGUgKlJlY2VpdmUgcGVyZm9ybWFuY2UqIHRlc3QgY2FzZSBwYWNrZXRzIGFyZSByZWNlaXZlZCBpbiB0aGUgb3RoZXIgcG9ydC4NCg0KVGhlIGNvZGUgdW5kZXIgdGVzdCBjYW4gYmUgZm91bmQgaGVyZToNCltldWdlbmVpYS9tZWxsYW5veC1iZW5jaG1hcmtdKGh0dHBzOi8vZ2l0aHViLmNvbS9ldWdlbmVpYS9zbmFiYi9jb21taXRzL21lbGxhbm94LWJlbmNobWFyaykNCg0KV2UgcnVuIGJlbmNobWFya3Mgb24gYSBtYXRyaXggb2YgcGFyYW1ldGVycyBpbmNsdWRpbmcNCg0KLSBwYWNrZXQgc2l6ZSAoaW5jbHVkaW5nIDQtYnl0ZSBDUkMpDQotIG51bWJlciBvZiB3b3JrZXJzIChjcHUgY29yZXMpIHByb3ZpZGVkIHRvIHRoZSBhcHBsaWNhdGlvbg0KLSBudW1iZXIgb2YgaGFyZHdhcmUgc2VuZC9yZWNlaXZlIHF1ZXVlcyBwZXIgd29ya2VyDQoNClRoZSBiZW5jaG1hcmsgaXMgcnVuIHRocmVlIHRpbWVzIGZvciBldmVyeSBwYXJhbWV0ZXIgY29uZmlndXJhdGlvbg0KYW5kIHdlIHNob3cgdGhlIG1pbmltdW0sIG1heGltdW0sIGFuZCBhdmVyYWdlIG9mIHRoZSByZXN1bHRzLg0KDQpXZSBhbHNvIG92ZXJsYXkgMTAwRyBsaW5lcmF0ZSAoZ3JleSBkYXNoZWQgbGluZSkgdG8gcHV0IHRoZSByZXN1bHRzIGludG8NCnBlcnNwZWN0aXZlLg0KDQpgYGB7cn0NCkdicHMgPC0gZnVuY3Rpb24obXBwcyxwa3RzaXplKSB7DQogIG1wcHMqKDEyKzgrcGt0c2l6ZSkqOC8xMDAwDQp9DQoNCkxpbmVyYXRlIDwtIGZ1bmN0aW9uKEcsIHBrdHNpemUpIHsNCiAgRyoxZTkgLyAoKDEyKzgrcGt0c2l6ZSkqOCkNCn0NCmBgYA0KDQojIFBhY2tldGJsYXN0ZXIgcGVyZm9ybWFuY2UNCg0KUGFja2V0Ymxhc3RlciBpcyBhIG9wdGltaXplZCBUWCByb3V0aW5lIGZvciBvdXIgQ29ubmVjdC1YIGRyaXZlci4NCkl0IHNob3VsZCBkZW1vbnN0cmF0ZSB0aGUgbWF4aW11bSB0cmFuc21pdCByYXRlIHN1cHBvcnRlZCBieSB0aGUgTklDLg0KDQpUaGlzIHNob3VsZCByZXByb2R1Y2UgdGhlIHJlc3VsdHMgaW4gW0Nvbm5lY3RYOiBSZXZpZXcgTipTUSA2NEIgdHJhbnNtaXQgcGVyZm9ybWFuY2UgbWVsbGFub3ggKFJldiAyKV0oaHR0cHM6Ly9naXRodWIuY29tL3NuYWJiY28vc25hYmIvaXNzdWVzLzEwMDcpDQpmcm9tIDIwMTYuIEl0IHF1aXRlIGRvZXNu4oCZdCB0aG91Z2gsIGFuZCBpcyBub3QgdGhlIHNhbWUgY29kZS4gSGF2ZSB0byBpbnZlc3RpZ2F0ZQ0Kd2hhdOKAmXMgdGhlIGRpZmZlcmVuY2UuDQoNCmBgYHtyfQ0KcGFja2V0Ymxhc3RlciA8LSAobWVsbGFub3gudHgub25seS5xdWV1ZXMuc2l6ZXMuaW50ZWwuMTAwZTYuY29hcnNlICU+JQ0KICAgICAgICAgICBtdXRhdGUoc3lzdGVtPSJJbnRlbCBYZW9uIFNpbHZlciA0MTE2IEAgMi4xMEdIeiIpKSAlPiUNCiAgYmluZF9yb3dzKChtZWxsYW5veC50eC5vbmx5LnF1ZXVlcy5zaXplcy5lcHljLjEwMGU2LmNvYXJzZSAlPiUNCiAgICAgICAgICAgICAgIG11dGF0ZShzeXN0ZW09IkFNRCBFUFlDIDc0NDNQIikpKSAlPiUNCiAgbXV0YXRlKHdvcmtlcnM9c3ByaW50ZigiJWQgd29ya2VycyAoY29yZXMpIiwgd29ya2VycyksDQogICAgICAgICBxdWV1ZXM9c3ByaW50ZigiJWQgcXVldWVzIiwgcXVldWVzKSkgJT4lDQogIGdyb3VwX2J5KHN5c3RlbSwgd29ya2VycywgcXVldWVzLCBwa3RzaXplKSAlPiUgDQogIHN1bW1hcmlzZShtaW5fbXBwcz1taW4ocmF0ZSksIGF2Z19tcHBzPW1lYW4ocmF0ZSksIG1heF9tcHBzPW1heChyYXRlKSwNCiAgICAgICAgICAgIG1pbl9sb3NzPShtaW4oZHJvcCtlcnJvcikpLCBtaW5fbG9zcz0obWVhbihkcm9wK2Vycm9yKSksIG1heF9sb3NzPShtYXgoZHJvcCtlcnJvcikpKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBtdXRhdGUoR2Jwcz1HYnBzKG1heF9tcHBzLW1heF9sb3NzLCBwa3RzaXplKSkNCmBgYA0KDQpgYGB7ciBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD0xMH0NCmdncGxvdChwYWNrZXRibGFzdGVyLCBhZXMoeD1wa3RzaXplLCBjb2xvcj1xdWV1ZXMpKSArDQogIGZhY2V0X2dyaWQoc3lzdGVtIH4gd29ya2VycykgKyANCiAgZ2VvbV9saW5lKGFlcyh5PW1heF9tcHBzLCBsaW5ldHlwZT0iMF90eCIpKSArDQogIGdlb21fbGluZShhZXMoeT1tYXhfbG9zcywgbGluZXR5cGU9IjFfbG9zcyIpKSArIA0KICBnZW9tX3BvaW50KGFlcyh5PWF2Z19tcHBzLCBzaGFwZT0iYXZnIiksIGFscGhhPTAuNSkgKw0KICBnZW9tX3BvaW50KGFlcyh5PW1heF9tcHBzLCBzaGFwZT0ibWF4IiksIGFscGhhPTAuNSkgKw0KICBnZW9tX3BvaW50KGFlcyh5PW1pbl9tcHBzLCBzaGFwZT0ibWluIiksIGFscGhhPTAuNSkgKw0KICBnZW9tX2xpbmUoYWVzKHk9TGluZXJhdGUoMTAwLCBwa3RzaXplKS8xZTYsIGxpbmV0eXBlPSIyX2xpbmVyYXRlIiksIGNvbG9yPSdncmV5JykgKw0KICBjb29yZF9jYXJ0ZXNpYW4oeWxpbT1jKE5BLCBtYXgocGFja2V0Ymxhc3RlciRtYXhfbXBwcykpKSArDQogIGdndGl0bGUoIk11bHRpIGNvcmUgcGVyZm9ybWFuY2UgYnkgbnVtYmVyIG9mIHF1ZXVlcyBwZXIgd29ya2VyIGFuZCBwYWNrZXQgc2l6ZSIsDQogICAgICAgICAgc3VidGl0bGU9ImFwcHMubWVsbGFub3guY29ubmVjdHg6IFBhY2tldGJsYXN0ZXIgcmF0ZSBNUFBTIChUWCBvbmx5KSIpDQpgYGANCg0KIyBSZWNlaXZlIHBlcmZvcm1hbmNlDQoNClBhY2tldGJsYXN0ZXIgdHJhbnNtaXRzIG9uIG9uZSBwb3J0LCBhbmQgcGFja2V0cyBhcmUgcmVjZWl2ZWQgb24gdGhlIG90aGVyIHBvcnQuDQpFYWNoIHNpZGUgaGFzIGEgZGVkaWNhdGVkIENQVSBjb3JlIGZvciBlYWNoIHdvcmtlci4NCkkuZS4sICI2IHdvcmtlcnMiIG1lYW5zIHNpeCBjb3JlcyB1c2VkIGZvciB0cmFuc21pdCwNCmFuZCBzaXggZGlzdGluY3QgY29yZXMgYXJlIHVzZWQgZm9yIHJlY2VpdmUuDQoNCmBgYHtyfQ0KdHhyeCA8LSAobWVsbGFub3gudHgucngucXVldWVzLnNpemVzLmludGVsLjEwMGU2LmNvYXJzZSAlPiUNCiAgICAgICAgICBtdXRhdGUoc3lzdGVtPSJJbnRlbCBYZW9uIFNpbHZlciA0MTE2IEAgMi4xMEdIeiIpKSAlPiUNCiAgYmluZF9yb3dzKChtZWxsYW5veC50eC5yeC5xdWV1ZXMuc2l6ZXMuZXB5Yy4xMDBlNi5jb2Fyc2UgJT4lDQogICAgICAgICAgICAgICBtdXRhdGUoc3lzdGVtPSJBTUQgRVBZQyA3NDQzUCIpICU+JSBuYS5vbWl0KCkpKSAlPiUNCiAgbXV0YXRlKHdvcmtlcnM9c3ByaW50ZigiJWQgd29ya2VycyAoY29yZXMpIiwgd29ya2VycyksDQogICAgICAgICBxdWV1ZXM9c3ByaW50ZigiJWQgcXVldWVzIiwgcXVldWVzKSkgJT4lDQogIG11dGF0ZShyeF9tcHBzPXJ4cmF0ZS0ocnhkcm9wK3J4ZXJyb3IpKSAlPiUNCiAgZ3JvdXBfYnkoc3lzdGVtLCB3b3JrZXJzLCBxdWV1ZXMsIHBrdHNpemUpICU+JSANCiAgc3VtbWFyaXNlKG1pbl9tcHBzPW1pbih0eHJhdGUpLA0KICAgICAgICAgICAgYXZnX21wcHM9bWVhbih0eHJhdGUpLA0KICAgICAgICAgICAgbWF4X21wcHM9bWF4KHR4cmF0ZSksDQogICAgICAgICAgICBtaW5fcnhfbXBwcz0obWluKHJ4X21wcHMpKSwNCiAgICAgICAgICAgIGF2Z19yeF9tcHBzPShtZWFuKHJ4X21wcHMpKSwNCiAgICAgICAgICAgIG1heF9yeF9tcHBzPShtYXgocnhfbXBwcykpKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBtdXRhdGUocnhHYnBzPUdicHMobWF4X3J4X21wcHMsIHBrdHNpemUpLCBHYnBzPUdicHMobWF4X21wcHMsIHBrdHNpemUpKQ0KYGBgDQoNCg0KYGBge3IgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9MTB9DQpnZ3Bsb3QodHhyeCwgYWVzKHg9cGt0c2l6ZSwgY29sb3I9cXVldWVzKSkgKw0KICBmYWNldF9ncmlkKHN5c3RlbSB+IHdvcmtlcnMpICsNCiAgZ2VvbV9saW5lKGFlcyh5PW1heF9yeF9tcHBzLCBsaW5ldHlwZT0iMF9yeCIpKSArDQogIGdlb21fbGluZShhZXMoeT1tYXhfbXBwcywgbGluZXR5cGU9IjFfdHgiKSkgKyANCiAgZ2VvbV9wb2ludChhZXMoeT1hdmdfcnhfbXBwcywgc2hhcGU9ImF2ZyIpLCBhbHBoYT0wLjUpICsNCiAgZ2VvbV9wb2ludChhZXMoeT1tYXhfcnhfbXBwcywgc2hhcGU9Im1heCIpLCBhbHBoYT0wLjUpICsNCiAgZ2VvbV9wb2ludChhZXMoeT1taW5fcnhfbXBwcywgc2hhcGU9Im1pbiIpLCBhbHBoYT0wLjUpICsNCiAgZ2VvbV9saW5lKGFlcyh5PUxpbmVyYXRlKDEwMCwgcGt0c2l6ZSkvMWU2LCBsaW5ldHlwZT0iMl9saW5lcmF0ZSIpLCBjb2xvcj0nZ3JleScpICsNCiAgY29vcmRfY2FydGVzaWFuKHlsaW09YyhOQSwgbWF4KHR4cngkbWF4X21wcHMpKSkgKw0KICBnZ3RpdGxlKCJNdWx0aSBjb3JlIHBlcmZvcm1hbmNlIGJ5IG51bWJlciBvZiBxdWV1ZXMgcGVyIHdvcmtlciBhbmQgcGFja2V0IHNpemUiLA0KICAgICAgICAgIHN1YnRpdGxlPSJhcHBzLm1lbGxhbm94LmNvbm5lY3R4OiBSWCByYXRlIG9mIGNvbWJpbmVkIHJlY2VpdmUgcXVldWVzIGluIE1QUFMiKQ0KYGBgDQoNCg0KIyBab29tIGludG8gcmVzdWx0cyBvbiBFUFlDDQoNCiMjIFBhY2tldGJsYXN0ZXINCg0KYGBge3IgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9MTB9DQpwYWNrZXRibGFzdGVyX2VweWMgPC0gZmlsdGVyKHBhY2tldGJsYXN0ZXIsIHN5c3RlbT09IkFNRCBFUFlDIDc0NDNQIikNCmdncGxvdChwYWNrZXRibGFzdGVyX2VweWMsIGFlcyh4PXBrdHNpemUsIGNvbG9yPXF1ZXVlcykpICsNCiAgZmFjZXRfZ3JpZChzeXN0ZW0gfiB3b3JrZXJzKSArIA0KICBnZW9tX2xpbmUoYWVzKHk9bWF4X21wcHMsIGxpbmV0eXBlPSIwX3R4IikpICsNCiAgZ2VvbV9saW5lKGFlcyh5PW1heF9sb3NzLCBsaW5ldHlwZT0iMV9sb3NzIikpICsgDQogIGdlb21fcG9pbnQoYWVzKHk9YXZnX21wcHMsIHNoYXBlPSJhdmciKSwgYWxwaGE9MC41KSArDQogIGdlb21fcG9pbnQoYWVzKHk9bWF4X21wcHMsIHNoYXBlPSJtYXgiKSwgYWxwaGE9MC41KSArDQogIGdlb21fcG9pbnQoYWVzKHk9bWluX21wcHMsIHNoYXBlPSJtaW4iKSwgYWxwaGE9MC41KSArDQogIGdlb21fbGluZShhZXMoeT1MaW5lcmF0ZSgxMDAsIHBrdHNpemUpLzFlNiwgbGluZXR5cGU9IjJfbGluZXJhdGUiKSwgY29sb3I9J2dyZXknKSArDQogIGNvb3JkX2NhcnRlc2lhbih5bGltPWMoTkEsIG1heChwYWNrZXRibGFzdGVyX2VweWMkbWF4X21wcHMpKSkgKw0KICBnZ3RpdGxlKCJNdWx0aSBjb3JlIHBlcmZvcm1hbmNlIGJ5IG51bWJlciBvZiBxdWV1ZXMgcGVyIHdvcmtlciBhbmQgcGFja2V0IHNpemUiLA0KICAgICAgICAgIHN1YnRpdGxlPSJhcHBzLm1lbGxhbm94LmNvbm5lY3R4OiBQYWNrZXRibGFzdGVyIHJhdGUgTVBQUyAoVFggb25seSkiKQ0KYGBgDQoNCiMjIFJlY2VpdmUNCg0KYGBge3IgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9MTB9DQp0eHJ4X2VweWMgPC0gZmlsdGVyKHR4cngsIHN5c3RlbT09IkFNRCBFUFlDIDc0NDNQIikNCmdncGxvdCh0eHJ4X2VweWMsIGFlcyh4PXBrdHNpemUsIGNvbG9yPXF1ZXVlcykpICsNCiAgZmFjZXRfZ3JpZChzeXN0ZW0gfiB3b3JrZXJzKSArDQogIGdlb21fbGluZShhZXMoeT1tYXhfcnhfbXBwcywgbGluZXR5cGU9IjBfcngiKSkgKw0KICBnZW9tX2xpbmUoYWVzKHk9bWF4X21wcHMsIGxpbmV0eXBlPSIxX3R4IikpICsgDQogIGdlb21fcG9pbnQoYWVzKHk9YXZnX3J4X21wcHMsIHNoYXBlPSJhdmciKSwgYWxwaGE9MC41KSArDQogIGdlb21fcG9pbnQoYWVzKHk9bWF4X3J4X21wcHMsIHNoYXBlPSJtYXgiKSwgYWxwaGE9MC41KSArDQogIGdlb21fcG9pbnQoYWVzKHk9bWluX3J4X21wcHMsIHNoYXBlPSJtaW4iKSwgYWxwaGE9MC41KSArDQogIGdlb21fbGluZShhZXMoeT1MaW5lcmF0ZSgxMDAsIHBrdHNpemUpLzFlNiwgbGluZXR5cGU9IjJfbGluZXJhdGUiKSwgY29sb3I9J2dyZXknKSArDQogIGNvb3JkX2NhcnRlc2lhbih5bGltPWMoTkEsIG1heCh0eHJ4X2VweWMkbWF4X21wcHMpKSkgKw0KICBnZ3RpdGxlKCJNdWx0aSBjb3JlIHBlcmZvcm1hbmNlIGJ5IG51bWJlciBvZiBxdWV1ZXMgcGVyIHdvcmtlciBhbmQgcGFja2V0IHNpemUiLA0KICAgICAgICAgIHN1YnRpdGxlPSJhcHBzLm1lbGxhbm94LmNvbm5lY3R4OiBSWCByYXRlIG9mIGNvbWJpbmVkIHJlY2VpdmUgcXVldWVzIGluIE1QUFMiKQ0KDQpgYGANCg0KDQojIFJYIERyb3BzDQoNCmBgYHtyIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTEwfQ0KZ2dwbG90KHR4cngsIGFlcyh4PXBrdHNpemUsIGNvbG9yPXF1ZXVlcykpICsNCiAgZmFjZXRfZ3JpZChzeXN0ZW0gfiB3b3JrZXJzKSArDQogIGdlb21fbGluZShhZXMoeT1tYXhfbXBwcy1tYXhfcnhfbXBwcywgbGluZXR5cGU9IjBfZHJvcCIpKSArDQogIGdlb21fbGluZShhZXMoeT1tYXhfbXBwcywgbGluZXR5cGU9IjFfdHgiKSkgKyANCiAgZ2VvbV9wb2ludChhZXMoeT1hdmdfbXBwcy1hdmdfcnhfbXBwcywgc2hhcGU9ImF2ZyIpLCBhbHBoYT0wLjUpICsNCiAgZ2VvbV9wb2ludChhZXMoeT1tYXhfbXBwcy1tYXhfcnhfbXBwcywgc2hhcGU9Im1heCIpLCBhbHBoYT0wLjUpICsNCiAgZ2VvbV9wb2ludChhZXMoeT1taW5fbXBwcy1taW5fcnhfbXBwcywgc2hhcGU9Im1pbiIpLCBhbHBoYT0wLjUpICsNCiAgZ2VvbV9saW5lKGFlcyh5PUxpbmVyYXRlKDEwMCwgcGt0c2l6ZSkvMWU2LCBsaW5ldHlwZT0iMl9saW5lcmF0ZSIpLCBjb2xvcj0nZ3JleScpICsNCiAgY29vcmRfY2FydGVzaWFuKHlsaW09YyhOQSwgbWF4KHR4cngkbWF4X21wcHMpKSkgKw0KICBnZ3RpdGxlKCJNdWx0aSBjb3JlIHBlcmZvcm1hbmNlIGJ5IG51bWJlciBvZiBxdWV1ZXMgcGVyIHdvcmtlciBhbmQgcGFja2V0IHNpemUiLA0KICAgICAgICAgIHN1YnRpdGxlPSJhcHBzLm1lbGxhbm94LmNvbm5lY3R4OiBMb3NzIHJhdGUgb2YgY29tYmluZWQgcmVjZWl2ZSBxdWV1ZXMgaW4gTVBQUyIpDQpgYGANCg0KIyBIZWxwDQoNClRoaXMgaXMgYW4gW1IgTWFya2Rvd25dKGh0dHA6Ly9ybWFya2Rvd24ucnN0dWRpby5jb20pIE5vdGVib29rLiBXaGVuIHlvdSBleGVjdXRlIGNvZGUgd2l0aGluIHRoZSBub3RlYm9vaywgdGhlIHJlc3VsdHMgYXBwZWFyIGJlbmVhdGggdGhlIGNvZGUuIA0KDQpUcnkgZXhlY3V0aW5nIHRoaXMgY2h1bmsgYnkgY2xpY2tpbmcgdGhlICpSdW4qIGJ1dHRvbiB3aXRoaW4gdGhlIGNodW5rIG9yIGJ5IHBsYWNpbmcgeW91ciBjdXJzb3IgaW5zaWRlIGl0IGFuZCBwcmVzc2luZyAqQ3RybCtTaGlmdCtFbnRlciouDQoNCkFkZCBhIG5ldyBjaHVuayBieSBjbGlja2luZyB0aGUgKkluc2VydCBDaHVuayogYnV0dG9uIG9uIHRoZSB0b29sYmFyIG9yIGJ5IHByZXNzaW5nICpDdHJsK0FsdCtJKi4NCg0KV2hlbiB5b3Ugc2F2ZSB0aGUgbm90ZWJvb2ssIGFuIEhUTUwgZmlsZSBjb250YWluaW5nIHRoZSBjb2RlIGFuZCBvdXRwdXQgd2lsbCBiZSBzYXZlZCBhbG9uZ3NpZGUgaXQgKGNsaWNrIHRoZSAqUHJldmlldyogYnV0dG9uIG9yIHByZXNzICpDdHJsK1NoaWZ0K0sqIHRvIHByZXZpZXcgdGhlIEhUTUwgZmlsZSkuDQoNClRoZSBwcmV2aWV3IHNob3dzIHlvdSBhIHJlbmRlcmVkIEhUTUwgY29weSBvZiB0aGUgY29udGVudHMgb2YgdGhlIGVkaXRvci4gQ29uc2VxdWVudGx5LCB1bmxpa2UgKktuaXQqLCAqUHJldmlldyogZG9lcyBub3QgcnVuIGFueSBSIGNvZGUgY2h1bmtzLiBJbnN0ZWFkLCB0aGUgb3V0cHV0IG9mIHRoZSBjaHVuayB3aGVuIGl0IHdhcyBsYXN0IHJ1biBpbiB0aGUgZWRpdG9yIGlzIGRpc3BsYXllZC4NCg==