library(ggplot2)
library(dplyr)
We’re comparing performance of the apps.mellanox.connectx driver between two
AMD EPYC 7443P, Mellanox Technologies MT28800 Family [ConnectX-5 Ex] (PCIeGen4, Speed 16GT/s, Width x16)
Intel Xeon Silver 4116 @ 2.10GHz, Mellanox Technologies MT27800 Family [ConnectX-5] (PCIeGen3, Speed 8GT/s, Width x16)
On the AMD EPYC 7443P, where the NIC is on IO bus 81, we also compare two BIOS settings:
Preferred IO Bus->81
Preferred IO->Auto
See [https://www.supermicro.com/support/faqs/faq.cfm?faq=33731]
Preferred IO Device
Advanced->NB Configuration->Preferred IO->Manual
Advanced->NB Configuration->Preferred IO Bus->81
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)
}
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())) %>%
bind_rows((mellanox.tx.rx.queues.sizes.epyc.100e6.coarse.bios2 %>%
mutate(system="AMD EPYC 7443P (Pref. IO bus 81)") %>% 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))) +
scale_x_continuous(breaks=c(64,256,512,512+256,1024)) +
scale_y_continuous(n.breaks = 10) +
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")

ggplot(txrx, aes(x=pktsize, color=queues)) +
facet_grid(system ~ workers) +
geom_line(aes(y=rxGbps, linetype="0_rx")) +
geom_line(aes(y=Gbps, linetype="1_tx")) +
geom_point(aes(y=rxGbps, shape="rx"), alpha=0.5) +
geom_point(aes(y=Gbps, shape="tx"), alpha=0.5) +
geom_line(aes(y=100, linetype="2_linerate"), color='grey') +
coord_cartesian(ylim=c(NA, max(txrx$rxGbps))) +
scale_x_continuous(breaks=c(64,256,512,512+256,1024)) +
scale_y_continuous(n.breaks = 10) +
ggtitle("Multi core performance by number of queues per worker and packet size",
subtitle="apps.mellanox.connectx: RX throughput of combined receive queues in Gbps")

Forwarding performance
Single node
Packetblaster transmits on one port, packets are received on the other port and forwarded back to the Packetblaster port (with src/dst MAC addresses swapped). 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/forward.
txfwd <- (mellanox.tx.fwd.queues.sizes.intel.100e6 %>%
mutate(system="Intel Xeon Silver 4116 @ 2.10GHz")) %>%
bind_rows((mellanox.tx.fwd.queues.sizes.epyc.100e6.coarse.bios2 %>%
mutate(system="AMD EPYC 7443P (Pref. IO bus 81)") %>% na.omit())) %>%
mutate(workers=sprintf("%d workers (cores)", workers),
queues=sprintf("%d queues", queues)) %>%
mutate(fwd_mpps=fwrate-(fwdrop+fwerror)) %>%
group_by(system, workers, queues, pktsize) %>%
summarise(min_mpps=min(txrate),
avg_mpps=mean(txrate),
max_mpps=max(txrate),
min_fwd_mpps=(min(fwd_mpps)),
avg_fwd_mpps=(mean(fwd_mpps)),
max_fwd_mpps=(max(fwd_mpps))) %>%
ungroup() %>%
mutate(fwdGbps=Gbps(max_fwd_mpps, pktsize), Gbps=Gbps(max_mpps, pktsize))
`summarise()` has grouped output by 'system', 'workers', 'queues'. You can override using the `.groups` argument.
ggplot(txfwd, aes(x=pktsize, color=queues)) +
facet_grid(system ~ workers) +
geom_line(aes(y=max_fwd_mpps, linetype="0_fwd")) +
geom_line(aes(y=max_mpps, linetype="1_tx")) +
geom_point(aes(y=avg_fwd_mpps, shape="avg"), alpha=0.5) +
geom_point(aes(y=max_fwd_mpps, shape="max"), alpha=0.5) +
geom_point(aes(y=min_fwd_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(txfwd$max_fwd_mpps))) +
scale_x_continuous(breaks=c(64,256,512,512+256,1024)) +
scale_y_continuous(n.breaks = 10) +
ggtitle("Multi core performance by number of queues per worker and packet size",
subtitle="apps.mellanox.connectx: Forwarding rate of combined receive queues in MPPS")

ggplot(txfwd, aes(x=pktsize, color=queues)) +
facet_grid(system ~ workers) +
geom_line(aes(y=fwdGbps, linetype="0_fwd")) +
geom_line(aes(y=Gbps, linetype="1_tx")) +
geom_point(aes(y=fwdGbps, shape="fwd"), alpha=0.5) +
geom_point(aes(y=Gbps, shape="tx"), alpha=0.5) +
geom_line(aes(y=100, linetype="2_linerate"), color='grey') +
scale_x_continuous(breaks=c(64,256,512,512+256,1024)) +
scale_y_continuous(n.breaks = 10) +
ggtitle("Multi core performance by number of queues per worker and packet size",
subtitle="apps.mellanox.connectx: Forwarding throughput of combined receive queues in Gbps")

Forwarding between systems
Here we test forwarding performance between our two systems. Each system uses one port of a 2x100G Connect-X card.
Note that test traffic is generated by the system:
Intel Xeon Silver 4116 @ 2.10GHz, Mellanox Technologies MT27800 Family [ConnectX-5] (PCIeGen3, Speed 8GT/s, Width x16)
As such it can not exceed the TX rate measured in “Packet sizes”, which is overlayed as a dashed grey line (“txlimit”) in the plots below.
The traffic generator uses one worker/core with 16 transmit queues.
The Epyc system receives the generated test traffic and forwards it back to the load generator over the same port using N workers/cores with one queue pair each.
The test traffic is split across two pairs of MACs and two vlans.
fwd_b2b_macvlan <- tx.fwd.b2b.coarse.nofc.macvlan.fine.n3 %>%
mutate(workers=as.factor(workers)) %>%
mutate(queues=as.factor(queues)) %>%
group_by(pktsize, workers, queues) %>%
summarise(fwrate=max(fwrate)) %>% ungroup() %>%
left_join(filter(packetblaster_sizes, system=="Intel Xeon Silver 4116 (pcie gen3)"),
by=c("pktsize" = "pktsize")) %>%
mutate(Gbps=Gbps(fwrate, pktsize), MaxGbps=Gbps(maxrate, pktsize))
`summarise()` has grouped output by 'pktsize', 'workers'. You can override using the `.groups` argument.
ggplot(fwd_b2b_macvlan, aes(x=pktsize, color=workers)) +
geom_line(aes(y=fwrate)) +
geom_point(aes(y=fwrate)) +
geom_line(aes(y=Linerate(100, pktsize)/1e6, linetype="linerate"), color='grey') +
geom_line(aes(y=maxrate, linetype="txlimit"), color='grey') +
coord_cartesian(ylim=c(NA, max(fwd_b2b_macvlan$fwrate))) +
ggtitle("Forwarding performance between two servers",
subtitle="two macs, two vlans, rate in Mpps")
Warning: Removed 28 row(s) containing missing values (geom_path).

ggplot(fwd_b2b_macvlan, aes(x=pktsize, color=workers)) +
geom_line(aes(y=Gbps)) +
geom_point(aes(y=Gbps)) +
geom_line(aes(y=MaxGbps, linetype="txlimit"), color='grey') +
geom_line(aes(y=100, linetype="linerate"), color='grey') +
coord_cartesian(ylim=c(NA, max(fwd_b2b_macvlan$Gbps))) +
ggtitle("Forwarding performance between two servers",
subtitle="two macs, two vlans, rate in Gbps")
Warning: Removed 28 row(s) containing missing values (geom_path).

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.
LS0tDQp0aXRsZTogImFwcHMubWVsbGFub3guY29ubmVjdHg6IFBhY2tldGJsYXN0ZXIgYW5kIFJlY2VpdmUgcGVyZm9ybWFuY2UiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpgYGB7cn0NCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkoZHBseXIpDQpgYGANCg0KV2XigJlyZSBjb21wYXJpbmcgcGVyZm9ybWFuY2Ugb2YgdGhlIGBhcHBzLm1lbGxhbm94LmNvbm5lY3R4YCBkcml2ZXIgYmV0d2VlbiB0d28NCg0KDQogLSBgQU1EIEVQWUMgNzQ0M1BgLCBgTWVsbGFub3ggVGVjaG5vbG9naWVzIE1UMjg4MDAgRmFtaWx5IFtDb25uZWN0WC01IEV4XWANCiAgIChQQ0llR2VuNCwgU3BlZWQgMTZHVC9zLCBXaWR0aCB4MTYpDQogLSBgSW50ZWwgWGVvbiBTaWx2ZXIgNDExNiBAIDIuMTBHSHpgLCAgYE1lbGxhbm94IFRlY2hub2xvZ2llcyBNVDI3ODAwIEZhbWlseSBbQ29ubmVjdFgtNV1gDQogICAoUENJZUdlbjMsIFNwZWVkIDhHVC9zLCBXaWR0aCB4MTYpDQogDQogT24gdGhlIGBBTUQgRVBZQyA3NDQzUGAsIHdoZXJlIHRoZSBOSUMgaXMgb24gSU8gYnVzICo4MSosDQogd2UgYWxzbyBjb21wYXJlIHR3byBCSU9TIHNldHRpbmdzOg0KIA0KIC0gYFByZWZlcnJlZCBJTyBCdXMtPjgxYA0KIC0gYFByZWZlcnJlZCBJTy0+QXV0b2ANCiANClNlZSBbaHR0cHM6Ly93d3cuc3VwZXJtaWNyby5jb20vc3VwcG9ydC9mYXFzL2ZhcS5jZm0/ZmFxPTMzNzMxXQ0KDQo+IFByZWZlcnJlZCBJTyBEZXZpY2UNCj4gDQo+IEFkdmFuY2VkLT5OQiBDb25maWd1cmF0aW9uLT5QcmVmZXJyZWQgSU8tPk1hbnVhbA0KPg0KPiBBZHZhbmNlZC0+TkIgQ29uZmlndXJhdGlvbi0+UHJlZmVycmVkIElPIEJ1cy0+ODENCg0KVGhlIGNvbmZpZ3VyYXRpb24gaXMgb25lIDJ4MTAwRyBwb3J0IE5JQywgd2l0aCBib3RoIHBvcnRzIHdpcmVkIHRvIGVhY2ggb3RoZXIuDQpQYWNrZXRibGFzdGVyIHdpbGwgdHJhbnNtaXQgcGFja2V0cyBvbmUgb25lIHBvcnQgaW4gYm90aCB0ZXN0IGNhc2VzLg0KSW4gdGhlICpSZWNlaXZlIHBlcmZvcm1hbmNlKiB0ZXN0IGNhc2UgcGFja2V0cyBhcmUgcmVjZWl2ZWQgaW4gdGhlIG90aGVyIHBvcnQuDQoNClRoZSBjb2RlIHVuZGVyIHRlc3QgY2FuIGJlIGZvdW5kIGhlcmU6DQpbZXVnZW5laWEvbWVsbGFub3gtYmVuY2htYXJrXShodHRwczovL2dpdGh1Yi5jb20vZXVnZW5laWEvc25hYmIvY29tbWl0cy9tZWxsYW5veC1iZW5jaG1hcmspDQoNCldlIHJ1biBiZW5jaG1hcmtzIG9uIGEgbWF0cml4IG9mIHBhcmFtZXRlcnMgaW5jbHVkaW5nDQoNCi0gcGFja2V0IHNpemUgKGluY2x1ZGluZyA0LWJ5dGUgQ1JDKQ0KLSBudW1iZXIgb2Ygd29ya2VycyAoY3B1IGNvcmVzKSBwcm92aWRlZCB0byB0aGUgYXBwbGljYXRpb24NCi0gbnVtYmVyIG9mIGhhcmR3YXJlIHNlbmQvcmVjZWl2ZSBxdWV1ZXMgcGVyIHdvcmtlcg0KDQpUaGUgYmVuY2htYXJrIGlzIHJ1biB0aHJlZSB0aW1lcyBmb3IgZXZlcnkgcGFyYW1ldGVyIGNvbmZpZ3VyYXRpb24NCmFuZCB3ZSBzaG93IHRoZSBtaW5pbXVtLCBtYXhpbXVtLCBhbmQgYXZlcmFnZSBvZiB0aGUgcmVzdWx0cy4NCg0KV2UgYWxzbyBvdmVybGF5IDEwMEcgbGluZXJhdGUgKGdyZXkgZGFzaGVkIGxpbmUpIHRvIHB1dCB0aGUgcmVzdWx0cyBpbnRvDQpwZXJzcGVjdGl2ZS4NCg0KYGBge3J9DQpHYnBzIDwtIGZ1bmN0aW9uKG1wcHMscGt0c2l6ZSkgew0KICBtcHBzKigxMis4K3BrdHNpemUpKjgvMTAwMA0KfQ0KDQpMaW5lcmF0ZSA8LSBmdW5jdGlvbihHLCBwa3RzaXplKSB7DQogIEcqMWU5IC8gKCgxMis4K3BrdHNpemUpKjgpDQp9DQpgYGANCg0KIyBQYWNrZXRibGFzdGVyIHBlcmZvcm1hbmNlDQoNClBhY2tldGJsYXN0ZXIgaXMgYSBvcHRpbWl6ZWQgVFggcm91dGluZSBmb3Igb3VyIENvbm5lY3QtWCBkcml2ZXIuDQpJdCBzaG91bGQgZGVtb25zdHJhdGUgdGhlIG1heGltdW0gdHJhbnNtaXQgcmF0ZSBzdXBwb3J0ZWQgYnkgdGhlIE5JQy4NCg0KIyMgU2luZ2xlLWNvcmUNCg0KVGVzdGluZyBzaW5nbGUtY29yZSBwZXJmb3JtYW5jZSBvZiBQYWNrZXRibGFzdGVyIGF0IDY0QiBwYWNrZXRzIHdoaWxlIGNvbXBhcmluZw0KbnVtYmVyIG9mIHF1ZXVlcyBhbmQgcXVldWUgc2l6ZS4NCg0KVGhpcyBzaG91bGQgcmVwcm9kdWNlIHRoZSByZXN1bHRzIGluIFtDb25uZWN0WDogUmV2aWV3IE4qU1EgNjRCIHRyYW5zbWl0IHBlcmZvcm1hbmNlIG1lbGxhbm94IChSZXYgMildKGh0dHBzOi8vZ2l0aHViLmNvbS9zbmFiYmNvL3NuYWJiL2lzc3Vlcy8xMDA3KQ0KZnJvbSAyMDE2LiBUaGUgRVBZQyBgUHJlZmVycmVkIElPIGJ1cy0+ODFgIHJ1biBzZWVtcyB0byBkbyBzbyBwYXJ0aWFsbHkuIE9uDQp0aGUgSW50ZWwgc3lzdGVtIHRoZSBjdXJ2ZSBzZWVtcyB0byBtYXRjaCwgaG93ZXZlciBpdCBzZWVtcyB0byBiZSBvZmZzZXQgYnkNCnJlZHVjZWQgb3ZlcmFsbCB0aHJvdWdocHV0Lg0KDQpgYGB7cn0NCnBhY2tldGJsYXN0ZXJfc2luZ2xlIDwtIChtZWxsYW5veC50eC5yeC5xdWV1ZXMucXNpemUuaW50ZWwuMTAwZTYgJT4lDQogICAgICAgICAgICAgICAgICAgICAgICAgIG11dGF0ZShzeXN0ZW09IkludGVsIFhlb24gU2lsdmVyIDQxMTYgQCAyLjEwR0h6IikpICU+JQ0KICBiaW5kX3Jvd3MoKG1lbGxhbm94LnR4LnJ4LnF1ZXVlcy5xc2l6ZS5lcHljLjEwMGU2ICU+JQ0KICAgICAgICAgICAgICBtdXRhdGUoc3lzdGVtPSJBTUQgRVBZQyA3NDQzUCAoUHJlZi4gSU8gYnVzIDgxKSIpKSkgJT4lDQogIG11dGF0ZShxc2l6ZT1hcy5mYWN0b3IocXNpemUpKSAlPiUNCiAgZ3JvdXBfYnkocXVldWVzLCBxc2l6ZSwgc3lzdGVtKSAlPiUgDQogIHN1bW1hcmlzZShtaW5yYXRlPW1pbihyYXRlKSwgbWF4cmF0ZT1tYXgocmF0ZSksIGF2Z3JhdGU9bWVhbihyYXRlKSkgJT4lDQogIHVuZ3JvdXAoKQ0KYGBgDQogIA0KYGBge3IgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9MTB9DQpnZ3Bsb3QocGFja2V0Ymxhc3Rlcl9zaW5nbGUsIGFlcyh4PXF1ZXVlcywgY29sb3I9cXNpemUpKSArDQogIGZhY2V0X3dyYXAofiBzeXN0ZW0pICsNCiAgZ2VvbV9saW5lKGFlcyh5PW1heHJhdGUpKSArDQogIGdlb21fcG9pbnQoYWVzKHk9bWF4cmF0ZSkpICsgDQogIGdndGl0bGUoIlBhY2tldGJsYXN0ZXIgcGVyZm9ybWFuY2Ugd2l0aCA2NEIgRXRoZXJuZXQgcGFja2V0cyIsDQogICAgICAgICAgc3VidGl0bGU9IlNpbmdsZSBjb3JlLCByYXRlIGluIE1wcHMiKQ0KYGBgDQoNCiMjIyBQYWNrZXQgc2l6ZXMNCg0KVGVzdGluZyBQYWNrZXRibGFzdGVyIHNpbmdsZS1jb3JlIHBlcmZvcm1hbmNlIHdpdGggMTYgdHJhbnNtaXQgcXVldWVzIGNvbXBhcmluZw0KcGVyZm9ybWFuY2UgYmV0d2VlbiBwYWNrZXQgc2l6ZXMuDQoNCmBgYHtyfQ0KcGFja2V0Ymxhc3Rlcl9zaXplcyA8LSAobWVsbGFub3gudHgub25seS5zaXplcy5pbnRlbC4xMDBlNi5hbGwgJT4lDQogICAgICAgICAgIG11dGF0ZShzeXN0ZW09IkludGVsIFhlb24gU2lsdmVyIDQxMTYgKHBjaWUgZ2VuMykiKSkgJT4lDQogIGJpbmRfcm93cygobWVsbGFub3gudHgub25seS5zaXplcy5lcHljLjEwMGU2LmFsbC5iaW9zMiAlPiUNCiAgICAgICAgICAgICAgIG11dGF0ZShzeXN0ZW09IkFNRCBFUFlDIDc0NDNQIChwY2llIGdlbjQpIikpKSAlPiUNCiAgbXV0YXRlKHF1ZXVlcz1hcy5mYWN0b3IocXVldWVzKSkgJT4lDQogIGdyb3VwX2J5KHBrdHNpemUsIHF1ZXVlcywgc3lzdGVtKSAlPiUgDQogIHN1bW1hcmlzZShtaW5yYXRlPW1pbihyYXRlKSwgbWF4cmF0ZT1tYXgocmF0ZSksIGF2Z3JhdGU9bWVhbihyYXRlKSkgJT4lDQogIHVuZ3JvdXAoKQ0KYGBgDQoNCg0KYGBge3IgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9N30NCmdncGxvdChwYWNrZXRibGFzdGVyX3NpemVzLCBhZXMoeD1wa3RzaXplLCBjb2xvcj1zeXN0ZW0pKSArDQogIGdlb21fc3RlcChhZXMoeT1hdmdyYXRlLCBsaW5ldHlwZT0iYXZnIikpICsNCiAgZ2VvbV9zdGVwKGFlcyh5PW1pbnJhdGUsIGxpbmV0eXBlPSJtaW4iKSkgKw0KICBnZW9tX3N0ZXAoYWVzKHk9bWF4cmF0ZSwgbGluZXR5cGU9Im1heCIpKSArDQogIGdlb21fbGluZShhZXMoeT1MaW5lcmF0ZSgxMDAsIHBrdHNpemUpLzFlNiwgbGluZXR5cGU9ImxpbmVyYXRlIiksIGNvbG9yPSdncmV5JykgKw0KICBjb29yZF9jYXJ0ZXNpYW4oeWxpbT1jKE5BLCBtYXgocGFja2V0Ymxhc3Rlcl9zaXplcyRtYXhyYXRlKSkpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKG4uYnJlYWtzID0gMjApICsgDQogIGdndGl0bGUoIlBhY2tldGJsYXN0ZXIgcGVyZm9ybWFuY2Ugd2l0aCBkaWZmZXJlbnQgc2l6ZXMgb2YgRXRoZXJuZXQgcGFja2V0cyIsDQogICAgICAgICAgc3VidGl0bGU9IlNpbmdsZSBjb3JlLCAxNiBxdWV1ZXMsIHJhdGUgaW4gTXBwcyAoVFggb25seSkiKQ0KYGBgDQoNCg0KDQojIyBNdWx0aS1jb3JlDQoNClRlc3RpbmcgUGFja2V0Ymxhc3RlciBtdWx0aS1jb3JlIHBlcmZvcm1hbmNlLCBjb21wYXJpbmcgbnVtYmVyIG9mIHdvcmtlcnMsDQpxdWV1ZXMsIGFuZCBwYWNrZXQgc2l6ZS4gUXVldWUgc2l6ZSBpcyB0aGUgZGVmYXVsdCAoMTAyNCkuDQoNCg0KYGBge3J9DQpwYWNrZXRibGFzdGVyIDwtIChtZWxsYW5veC50eC5vbmx5LnF1ZXVlcy5zaXplcy5pbnRlbC4xMDBlNi5jb2Fyc2UgJT4lDQogICAgICAgICAgIG11dGF0ZShzeXN0ZW09IkludGVsIFhlb24gU2lsdmVyIDQxMTYgQCAyLjEwR0h6IikpICU+JQ0KICBiaW5kX3Jvd3MoKG1lbGxhbm94LnR4Lm9ubHkucXVldWVzLnNpemVzLmVweWMuMTAwZTYuY29hcnNlICU+JQ0KICAgICAgICAgICAgICAgbXV0YXRlKHN5c3RlbT0iQU1EIEVQWUMgNzQ0M1AiKSkpICU+JQ0KICBiaW5kX3Jvd3MoKG1lbGxhbm94LnR4Lm9ubHkucXVldWVzLnNpemVzLmVweWMuMTAwZTYuY29hcnNlLmJpb3MyICU+JQ0KICAgICAgICAgICAgICAgbXV0YXRlKHN5c3RlbT0iQU1EIEVQWUMgNzQ0M1AgKFByZWYuIElPIGJ1cyA4MSkiKSkpICU+JQ0KICBtdXRhdGUod29ya2Vycz1zcHJpbnRmKCIlZCB3b3JrZXJzIChjb3JlcykiLCB3b3JrZXJzKSwNCiAgICAgICAgIHF1ZXVlcz1zcHJpbnRmKCIlZCBxdWV1ZXMiLCBxdWV1ZXMpKSAlPiUNCiAgZ3JvdXBfYnkoc3lzdGVtLCB3b3JrZXJzLCBxdWV1ZXMsIHBrdHNpemUpICU+JSANCiAgc3VtbWFyaXNlKG1pbl9tcHBzPW1pbihyYXRlKSwgYXZnX21wcHM9bWVhbihyYXRlKSwgbWF4X21wcHM9bWF4KHJhdGUpLA0KICAgICAgICAgICAgbWluX2xvc3M9KG1pbihkcm9wK2Vycm9yKSksIG1pbl9sb3NzPShtZWFuKGRyb3ArZXJyb3IpKSwgbWF4X2xvc3M9KG1heChkcm9wK2Vycm9yKSkpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIG11dGF0ZShHYnBzPUdicHMobWF4X21wcHMtbWF4X2xvc3MsIHBrdHNpemUpKQ0KYGBgDQoNCmBgYHtyIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTEwfQ0KZ2dwbG90KHBhY2tldGJsYXN0ZXIsIGFlcyh4PXBrdHNpemUsIGNvbG9yPXF1ZXVlcykpICsNCiAgZmFjZXRfZ3JpZChzeXN0ZW0gfiB3b3JrZXJzKSArIA0KICBnZW9tX2xpbmUoYWVzKHk9bWF4X21wcHMsIGxpbmV0eXBlPSIwX3R4IikpICsNCiAgZ2VvbV9saW5lKGFlcyh5PW1heF9sb3NzLCBsaW5ldHlwZT0iMV9sb3NzIikpICsgDQogIGdlb21fcG9pbnQoYWVzKHk9YXZnX21wcHMsIHNoYXBlPSJhdmciKSwgYWxwaGE9MC41KSArDQogIGdlb21fcG9pbnQoYWVzKHk9bWF4X21wcHMsIHNoYXBlPSJtYXgiKSwgYWxwaGE9MC41KSArDQogIGdlb21fcG9pbnQoYWVzKHk9bWluX21wcHMsIHNoYXBlPSJtaW4iKSwgYWxwaGE9MC41KSArDQogIGdlb21fbGluZShhZXMoeT1MaW5lcmF0ZSgxMDAsIHBrdHNpemUpLzFlNiwgbGluZXR5cGU9IjJfbGluZXJhdGUiKSwgY29sb3I9J2dyZXknKSArDQogIGNvb3JkX2NhcnRlc2lhbih5bGltPWMoTkEsIG1heChwYWNrZXRibGFzdGVyJG1heF9tcHBzKSkpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcz1jKDY0LDI1Niw1MTIsNTEyKzI1NiwxMDI0KSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobi5icmVha3MgPSA4KSArIA0KICBnZ3RpdGxlKCJNdWx0aSBjb3JlIHBlcmZvcm1hbmNlIGJ5IG51bWJlciBvZiBxdWV1ZXMgcGVyIHdvcmtlciBhbmQgcGFja2V0IHNpemUiLA0KICAgICAgICAgIHN1YnRpdGxlPSJhcHBzLm1lbGxhbm94LmNvbm5lY3R4OiBQYWNrZXRibGFzdGVyIHJhdGUgTVBQUyAoVFggb25seSkiKQ0KYGBgDQoNCiMgUmVjZWl2ZSBwZXJmb3JtYW5jZQ0KDQpQYWNrZXRibGFzdGVyIHRyYW5zbWl0cyBvbiBvbmUgcG9ydCwgYW5kIHBhY2tldHMgYXJlIHJlY2VpdmVkIG9uIHRoZSBvdGhlciBwb3J0Lg0KRWFjaCBzaWRlIGhhcyBhIGRlZGljYXRlZCBDUFUgY29yZSBmb3IgZWFjaCB3b3JrZXIuDQpJLmUuLCAiNiB3b3JrZXJzIiBtZWFucyBzaXggY29yZXMgdXNlZCBmb3IgdHJhbnNtaXQsDQphbmQgc2l4IGRpc3RpbmN0IGNvcmVzIGFyZSB1c2VkIGZvciByZWNlaXZlLg0KDQpgYGB7cn0NCnR4cnggPC0gKG1lbGxhbm94LnR4LnJ4LnF1ZXVlcy5zaXplcy5pbnRlbC4xMDBlNi5jb2Fyc2UgJT4lDQogICAgICAgICAgbXV0YXRlKHN5c3RlbT0iSW50ZWwgWGVvbiBTaWx2ZXIgNDExNiBAIDIuMTBHSHoiKSkgJT4lDQogIGJpbmRfcm93cygobWVsbGFub3gudHgucngucXVldWVzLnNpemVzLmVweWMuMTAwZTYuY29hcnNlICU+JQ0KICAgICAgICAgICAgICAgbXV0YXRlKHN5c3RlbT0iQU1EIEVQWUMgNzQ0M1AiKSAlPiUgbmEub21pdCgpKSkgJT4lDQogIGJpbmRfcm93cygobWVsbGFub3gudHgucngucXVldWVzLnNpemVzLmVweWMuMTAwZTYuY29hcnNlLmJpb3MyICU+JQ0KICAgICAgICAgICAgICAgbXV0YXRlKHN5c3RlbT0iQU1EIEVQWUMgNzQ0M1AgKFByZWYuIElPIGJ1cyA4MSkiKSAlPiUgbmEub21pdCgpKSkgJT4lDQogIG11dGF0ZSh3b3JrZXJzPXNwcmludGYoIiVkIHdvcmtlcnMgKGNvcmVzKSIsIHdvcmtlcnMpLA0KICAgICAgICAgcXVldWVzPXNwcmludGYoIiVkIHF1ZXVlcyIsIHF1ZXVlcykpICU+JQ0KICBtdXRhdGUocnhfbXBwcz1yeHJhdGUtKHJ4ZHJvcCtyeGVycm9yKSkgJT4lDQogIGdyb3VwX2J5KHN5c3RlbSwgd29ya2VycywgcXVldWVzLCBwa3RzaXplKSAlPiUgDQogIHN1bW1hcmlzZShtaW5fbXBwcz1taW4odHhyYXRlKSwNCiAgICAgICAgICAgIGF2Z19tcHBzPW1lYW4odHhyYXRlKSwNCiAgICAgICAgICAgIG1heF9tcHBzPW1heCh0eHJhdGUpLA0KICAgICAgICAgICAgbWluX3J4X21wcHM9KG1pbihyeF9tcHBzKSksDQogICAgICAgICAgICBhdmdfcnhfbXBwcz0obWVhbihyeF9tcHBzKSksDQogICAgICAgICAgICBtYXhfcnhfbXBwcz0obWF4KHJ4X21wcHMpKSkgJT4lDQogIHVuZ3JvdXAoKSAlPiUNCiAgbXV0YXRlKHJ4R2Jwcz1HYnBzKG1heF9yeF9tcHBzLCBwa3RzaXplKSwgR2Jwcz1HYnBzKG1heF9tcHBzLCBwa3RzaXplKSkNCmBgYA0KDQoNCmBgYHtyIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTEwfQ0KZ2dwbG90KHR4cngsIGFlcyh4PXBrdHNpemUsIGNvbG9yPXF1ZXVlcykpICsNCiAgZmFjZXRfZ3JpZChzeXN0ZW0gfiB3b3JrZXJzKSArDQogIGdlb21fbGluZShhZXMoeT1tYXhfcnhfbXBwcywgbGluZXR5cGU9IjBfcngiKSkgKw0KICBnZW9tX2xpbmUoYWVzKHk9bWF4X21wcHMsIGxpbmV0eXBlPSIxX3R4IikpICsgDQogIGdlb21fcG9pbnQoYWVzKHk9YXZnX3J4X21wcHMsIHNoYXBlPSJhdmciKSwgYWxwaGE9MC41KSArDQogIGdlb21fcG9pbnQoYWVzKHk9bWF4X3J4X21wcHMsIHNoYXBlPSJtYXgiKSwgYWxwaGE9MC41KSArDQogIGdlb21fcG9pbnQoYWVzKHk9bWluX3J4X21wcHMsIHNoYXBlPSJtaW4iKSwgYWxwaGE9MC41KSArDQogIGdlb21fbGluZShhZXMoeT1MaW5lcmF0ZSgxMDAsIHBrdHNpemUpLzFlNiwgbGluZXR5cGU9IjJfbGluZXJhdGUiKSwgY29sb3I9J2dyZXknKSArDQogIGNvb3JkX2NhcnRlc2lhbih5bGltPWMoTkEsIG1heCh0eHJ4JG1heF9tcHBzKSkpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcz1jKDY0LDI1Niw1MTIsNTEyKzI1NiwxMDI0KSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobi5icmVha3MgPSAxMCkgKyANCiAgZ2d0aXRsZSgiTXVsdGkgY29yZSBwZXJmb3JtYW5jZSBieSBudW1iZXIgb2YgcXVldWVzIHBlciB3b3JrZXIgYW5kIHBhY2tldCBzaXplIiwNCiAgICAgICAgICBzdWJ0aXRsZT0iYXBwcy5tZWxsYW5veC5jb25uZWN0eDogUlggcmF0ZSBvZiBjb21iaW5lZCByZWNlaXZlIHF1ZXVlcyBpbiBNUFBTIikNCmBgYA0KDQpgYGB7ciBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD0xMH0NCmdncGxvdCh0eHJ4LCBhZXMoeD1wa3RzaXplLCBjb2xvcj1xdWV1ZXMpKSArDQogIGZhY2V0X2dyaWQoc3lzdGVtIH4gd29ya2VycykgKw0KICBnZW9tX2xpbmUoYWVzKHk9cnhHYnBzLCBsaW5ldHlwZT0iMF9yeCIpKSArDQogIGdlb21fbGluZShhZXMoeT1HYnBzLCBsaW5ldHlwZT0iMV90eCIpKSArIA0KICBnZW9tX3BvaW50KGFlcyh5PXJ4R2Jwcywgc2hhcGU9InJ4IiksIGFscGhhPTAuNSkgKw0KICBnZW9tX3BvaW50KGFlcyh5PUdicHMsIHNoYXBlPSJ0eCIpLCBhbHBoYT0wLjUpICsNCiAgZ2VvbV9saW5lKGFlcyh5PTEwMCwgbGluZXR5cGU9IjJfbGluZXJhdGUiKSwgY29sb3I9J2dyZXknKSArDQogIGNvb3JkX2NhcnRlc2lhbih5bGltPWMoTkEsIG1heCh0eHJ4JHJ4R2JwcykpKSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3M9Yyg2NCwyNTYsNTEyLDUxMisyNTYsMTAyNCkpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKG4uYnJlYWtzID0gMTApICsgDQogIGdndGl0bGUoIk11bHRpIGNvcmUgcGVyZm9ybWFuY2UgYnkgbnVtYmVyIG9mIHF1ZXVlcyBwZXIgd29ya2VyIGFuZCBwYWNrZXQgc2l6ZSIsDQogICAgICAgICAgc3VidGl0bGU9ImFwcHMubWVsbGFub3guY29ubmVjdHg6IFJYIHRocm91Z2hwdXQgb2YgY29tYmluZWQgcmVjZWl2ZSBxdWV1ZXMgaW4gR2JwcyIpDQpgYGANCg0KDQojIEZvcndhcmRpbmcgcGVyZm9ybWFuY2UNCg0KIyMgU2luZ2xlIG5vZGUNCg0KUGFja2V0Ymxhc3RlciB0cmFuc21pdHMgb24gb25lIHBvcnQsIHBhY2tldHMgYXJlIHJlY2VpdmVkIG9uIHRoZSBvdGhlciBwb3J0IGFuZA0KZm9yd2FyZGVkIGJhY2sgdG8gdGhlIFBhY2tldGJsYXN0ZXIgcG9ydCAod2l0aCBzcmMvZHN0IE1BQyBhZGRyZXNzZXMgc3dhcHBlZCkuDQpFYWNoIHNpZGUgaGFzIGEgZGVkaWNhdGVkIENQVSBjb3JlIGZvciBlYWNoIHdvcmtlci4NCkkuZS4sICI2IHdvcmtlcnMiIG1lYW5zIHNpeCBjb3JlcyB1c2VkIGZvciB0cmFuc21pdCwNCmFuZCBzaXggZGlzdGluY3QgY29yZXMgYXJlIHVzZWQgZm9yIHJlY2VpdmUvZm9yd2FyZC4NCg0KYGBge3J9DQp0eGZ3ZCA8LSAobWVsbGFub3gudHguZndkLnF1ZXVlcy5zaXplcy5pbnRlbC4xMDBlNiAlPiUNCiAgICAgICAgICBtdXRhdGUoc3lzdGVtPSJJbnRlbCBYZW9uIFNpbHZlciA0MTE2IEAgMi4xMEdIeiIpKSAlPiUNCiAgYmluZF9yb3dzKChtZWxsYW5veC50eC5md2QucXVldWVzLnNpemVzLmVweWMuMTAwZTYuY29hcnNlLmJpb3MyICU+JQ0KICAgICAgICAgICAgICAgbXV0YXRlKHN5c3RlbT0iQU1EIEVQWUMgNzQ0M1AgKFByZWYuIElPIGJ1cyA4MSkiKSAlPiUgbmEub21pdCgpKSkgJT4lDQogIG11dGF0ZSh3b3JrZXJzPXNwcmludGYoIiVkIHdvcmtlcnMgKGNvcmVzKSIsIHdvcmtlcnMpLA0KICAgICAgICAgcXVldWVzPXNwcmludGYoIiVkIHF1ZXVlcyIsIHF1ZXVlcykpICU+JQ0KICBtdXRhdGUoZndkX21wcHM9ZndyYXRlLShmd2Ryb3ArZndlcnJvcikpICU+JQ0KICBncm91cF9ieShzeXN0ZW0sIHdvcmtlcnMsIHF1ZXVlcywgcGt0c2l6ZSkgJT4lIA0KICBzdW1tYXJpc2UobWluX21wcHM9bWluKHR4cmF0ZSksDQogICAgICAgICAgICBhdmdfbXBwcz1tZWFuKHR4cmF0ZSksDQogICAgICAgICAgICBtYXhfbXBwcz1tYXgodHhyYXRlKSwNCiAgICAgICAgICAgIG1pbl9md2RfbXBwcz0obWluKGZ3ZF9tcHBzKSksDQogICAgICAgICAgICBhdmdfZndkX21wcHM9KG1lYW4oZndkX21wcHMpKSwNCiAgICAgICAgICAgIG1heF9md2RfbXBwcz0obWF4KGZ3ZF9tcHBzKSkpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIG11dGF0ZShmd2RHYnBzPUdicHMobWF4X2Z3ZF9tcHBzLCBwa3RzaXplKSwgR2Jwcz1HYnBzKG1heF9tcHBzLCBwa3RzaXplKSkNCmBgYA0KDQpgYGB7ciBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD0xMH0NCmdncGxvdCh0eGZ3ZCwgYWVzKHg9cGt0c2l6ZSwgY29sb3I9cXVldWVzKSkgKw0KICBmYWNldF9ncmlkKHN5c3RlbSB+IHdvcmtlcnMpICsNCiAgZ2VvbV9saW5lKGFlcyh5PW1heF9md2RfbXBwcywgbGluZXR5cGU9IjBfZndkIikpICsNCiAgZ2VvbV9saW5lKGFlcyh5PW1heF9tcHBzLCBsaW5ldHlwZT0iMV90eCIpKSArIA0KICBnZW9tX3BvaW50KGFlcyh5PWF2Z19md2RfbXBwcywgc2hhcGU9ImF2ZyIpLCBhbHBoYT0wLjUpICsNCiAgZ2VvbV9wb2ludChhZXMoeT1tYXhfZndkX21wcHMsIHNoYXBlPSJtYXgiKSwgYWxwaGE9MC41KSArDQogIGdlb21fcG9pbnQoYWVzKHk9bWluX2Z3ZF9tcHBzLCBzaGFwZT0ibWluIiksIGFscGhhPTAuNSkgKw0KICBnZW9tX2xpbmUoYWVzKHk9TGluZXJhdGUoMTAwLCBwa3RzaXplKS8xZTYsIGxpbmV0eXBlPSIyX2xpbmVyYXRlIiksIGNvbG9yPSdncmV5JykgKw0KICBjb29yZF9jYXJ0ZXNpYW4oeWxpbT1jKE5BLCBtYXgodHhmd2QkbWF4X2Z3ZF9tcHBzKSkpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcz1jKDY0LDI1Niw1MTIsNTEyKzI1NiwxMDI0KSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobi5icmVha3MgPSAxMCkgKyANCiAgZ2d0aXRsZSgiTXVsdGkgY29yZSBwZXJmb3JtYW5jZSBieSBudW1iZXIgb2YgcXVldWVzIHBlciB3b3JrZXIgYW5kIHBhY2tldCBzaXplIiwNCiAgICAgICAgICBzdWJ0aXRsZT0iYXBwcy5tZWxsYW5veC5jb25uZWN0eDogRm9yd2FyZGluZyByYXRlIG9mIGNvbWJpbmVkIHJlY2VpdmUgcXVldWVzIGluIE1QUFMiKQ0KYGBgDQoNCmBgYHtyIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTEwfQ0KZ2dwbG90KHR4ZndkLCBhZXMoeD1wa3RzaXplLCBjb2xvcj1xdWV1ZXMpKSArDQogIGZhY2V0X2dyaWQoc3lzdGVtIH4gd29ya2VycykgKw0KICBnZW9tX2xpbmUoYWVzKHk9ZndkR2JwcywgbGluZXR5cGU9IjBfZndkIikpICsNCiAgZ2VvbV9saW5lKGFlcyh5PUdicHMsIGxpbmV0eXBlPSIxX3R4IikpICsgDQogIGdlb21fcG9pbnQoYWVzKHk9ZndkR2Jwcywgc2hhcGU9ImZ3ZCIpLCBhbHBoYT0wLjUpICsNCiAgZ2VvbV9wb2ludChhZXMoeT1HYnBzLCBzaGFwZT0idHgiKSwgYWxwaGE9MC41KSArDQogIGdlb21fbGluZShhZXMoeT0xMDAsIGxpbmV0eXBlPSIyX2xpbmVyYXRlIiksIGNvbG9yPSdncmV5JykgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzPWMoNjQsMjU2LDUxMiw1MTIrMjU2LDEwMjQpKSArDQogIHNjYWxlX3lfY29udGludW91cyhuLmJyZWFrcyA9IDEwKSArIA0KICBnZ3RpdGxlKCJNdWx0aSBjb3JlIHBlcmZvcm1hbmNlIGJ5IG51bWJlciBvZiBxdWV1ZXMgcGVyIHdvcmtlciBhbmQgcGFja2V0IHNpemUiLA0KICAgICAgICAgIHN1YnRpdGxlPSJhcHBzLm1lbGxhbm94LmNvbm5lY3R4OiBGb3J3YXJkaW5nIHRocm91Z2hwdXQgb2YgY29tYmluZWQgcmVjZWl2ZSBxdWV1ZXMgaW4gR2JwcyIpDQpgYGANCg0KIyMgRm9yd2FyZGluZyBiZXR3ZWVuIHN5c3RlbXMNCg0KSGVyZSB3ZSB0ZXN0IGZvcndhcmRpbmcgcGVyZm9ybWFuY2UgYmV0d2VlbiBvdXIgdHdvIHN5c3RlbXMuDQpFYWNoIHN5c3RlbSB1c2VzIG9uZSBwb3J0IG9mIGEgMngxMDBHIENvbm5lY3QtWCBjYXJkLg0KDQpOb3RlIHRoYXQgdGVzdCB0cmFmZmljIGlzIGdlbmVyYXRlZCBieSB0aGUgc3lzdGVtOg0KDQogLSBgSW50ZWwgWGVvbiBTaWx2ZXIgNDExNiBAIDIuMTBHSHpgLCAgYE1lbGxhbm94IFRlY2hub2xvZ2llcyBNVDI3ODAwIEZhbWlseSBbQ29ubmVjdFgtNV1gDQogICAoUENJZUdlbjMsIFNwZWVkIDhHVC9zLCBXaWR0aCB4MTYpDQogICANCkFzIHN1Y2ggaXQgY2FuIG5vdCBleGNlZWQgdGhlIFRYIHJhdGUgbWVhc3VyZWQgaW4gKuKAnFBhY2tldCBzaXplc+KAnSosIHdoaWNoIGlzDQpvdmVybGF5ZWQgYXMgYSBkYXNoZWQgZ3JleSBsaW5lICjigJx0eGxpbWl04oCdKSBpbiB0aGUgcGxvdHMgYmVsb3cuDQoNClRoZSB0cmFmZmljIGdlbmVyYXRvciB1c2VzIG9uZSB3b3JrZXIvY29yZSB3aXRoIDE2IHRyYW5zbWl0IHF1ZXVlcy4NCg0KVGhlIEVweWMgc3lzdGVtIHJlY2VpdmVzIHRoZSBnZW5lcmF0ZWQgdGVzdCB0cmFmZmljIGFuZCBmb3J3YXJkcyBpdCBiYWNrIHRvIHRoZQ0KbG9hZCBnZW5lcmF0b3Igb3ZlciB0aGUgc2FtZSBwb3J0IHVzaW5nICpOKiB3b3JrZXJzL2NvcmVzIHdpdGggb25lIHF1ZXVlIHBhaXINCmVhY2guDQoNClRoZSB0ZXN0IHRyYWZmaWMgaXMgc3BsaXQgYWNyb3NzIHR3byBwYWlycyBvZiBNQUNzIGFuZCB0d28gdmxhbnMuDQoNCmBgYHtyfQ0KZndkX2IyYl9tYWN2bGFuIDwtIHR4LmZ3ZC5iMmIuY29hcnNlLm5vZmMubWFjdmxhbi5maW5lLm4zICU+JQ0KICBtdXRhdGUod29ya2Vycz1hcy5mYWN0b3Iod29ya2VycykpICU+JQ0KICBtdXRhdGUocXVldWVzPWFzLmZhY3RvcihxdWV1ZXMpKSAlPiUNCiAgZ3JvdXBfYnkocGt0c2l6ZSwgd29ya2VycywgcXVldWVzKSAlPiUNCiAgc3VtbWFyaXNlKGZ3cmF0ZT1tYXgoZndyYXRlKSkgJT4lIHVuZ3JvdXAoKSAlPiUNCiAgbGVmdF9qb2luKGZpbHRlcihwYWNrZXRibGFzdGVyX3NpemVzLCBzeXN0ZW09PSJJbnRlbCBYZW9uIFNpbHZlciA0MTE2IChwY2llIGdlbjMpIiksDQogICAgICAgICAgICBieT1jKCJwa3RzaXplIiA9ICJwa3RzaXplIikpICU+JQ0KICBtdXRhdGUoR2Jwcz1HYnBzKGZ3cmF0ZSwgcGt0c2l6ZSksIE1heEdicHM9R2JwcyhtYXhyYXRlLCBwa3RzaXplKSkNCmBgYA0KICANCg0KYGBge3IgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9N30NCmdncGxvdChmd2RfYjJiX21hY3ZsYW4sIGFlcyh4PXBrdHNpemUsIGNvbG9yPXdvcmtlcnMpKSArDQogIGdlb21fbGluZShhZXMoeT1md3JhdGUpKSArDQogIGdlb21fcG9pbnQoYWVzKHk9ZndyYXRlKSkgKyANCiAgZ2VvbV9saW5lKGFlcyh5PUxpbmVyYXRlKDEwMCwgcGt0c2l6ZSkvMWU2LCBsaW5ldHlwZT0ibGluZXJhdGUiKSwgY29sb3I9J2dyZXknKSArDQogIGdlb21fbGluZShhZXMoeT1tYXhyYXRlLCBsaW5ldHlwZT0idHhsaW1pdCIpLCBjb2xvcj0nZ3JleScpICsNCiAgY29vcmRfY2FydGVzaWFuKHlsaW09YyhOQSwgbWF4KGZ3ZF9iMmJfbWFjdmxhbiRmd3JhdGUpKSkgKw0KICBnZ3RpdGxlKCJGb3J3YXJkaW5nIHBlcmZvcm1hbmNlIGJldHdlZW4gdHdvIHNlcnZlcnMiLA0KICAgICAgICAgIHN1YnRpdGxlPSJ0d28gbWFjcywgdHdvIHZsYW5zLCByYXRlIGluIE1wcHMiKQ0KYGBgDQoNCmBgYHtyIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTd9DQpnZ3Bsb3QoZndkX2IyYl9tYWN2bGFuLCBhZXMoeD1wa3RzaXplLCBjb2xvcj13b3JrZXJzKSkgKw0KICBnZW9tX2xpbmUoYWVzKHk9R2JwcykpICsNCiAgZ2VvbV9wb2ludChhZXMoeT1HYnBzKSkgKyANCiAgZ2VvbV9saW5lKGFlcyh5PU1heEdicHMsIGxpbmV0eXBlPSJ0eGxpbWl0IiksIGNvbG9yPSdncmV5JykgKw0KICBnZW9tX2xpbmUoYWVzKHk9MTAwLCBsaW5ldHlwZT0ibGluZXJhdGUiKSwgY29sb3I9J2dyZXknKSArDQogIGNvb3JkX2NhcnRlc2lhbih5bGltPWMoTkEsIG1heChmd2RfYjJiX21hY3ZsYW4kR2JwcykpKSArDQogIGdndGl0bGUoIkZvcndhcmRpbmcgcGVyZm9ybWFuY2UgYmV0d2VlbiB0d28gc2VydmVycyIsDQogICAgICAgICAgc3VidGl0bGU9InR3byBtYWNzLCB0d28gdmxhbnMsIHJhdGUgaW4gR2JwcyIpDQpgYGANCg0KIyBIZWxwDQoNClRoaXMgaXMgYW4gW1IgTWFya2Rvd25dKGh0dHA6Ly9ybWFya2Rvd24ucnN0dWRpby5jb20pIE5vdGVib29rLiBXaGVuIHlvdSBleGVjdXRlIGNvZGUgd2l0aGluIHRoZSBub3RlYm9vaywgdGhlIHJlc3VsdHMgYXBwZWFyIGJlbmVhdGggdGhlIGNvZGUuIA0KDQpUcnkgZXhlY3V0aW5nIHRoaXMgY2h1bmsgYnkgY2xpY2tpbmcgdGhlICpSdW4qIGJ1dHRvbiB3aXRoaW4gdGhlIGNodW5rIG9yIGJ5IHBsYWNpbmcgeW91ciBjdXJzb3IgaW5zaWRlIGl0IGFuZCBwcmVzc2luZyAqQ3RybCtTaGlmdCtFbnRlciouDQoNCkFkZCBhIG5ldyBjaHVuayBieSBjbGlja2luZyB0aGUgKkluc2VydCBDaHVuayogYnV0dG9uIG9uIHRoZSB0b29sYmFyIG9yIGJ5IHByZXNzaW5nICpDdHJsK0FsdCtJKi4NCg0KV2hlbiB5b3Ugc2F2ZSB0aGUgbm90ZWJvb2ssIGFuIEhUTUwgZmlsZSBjb250YWluaW5nIHRoZSBjb2RlIGFuZCBvdXRwdXQgd2lsbCBiZSBzYXZlZCBhbG9uZ3NpZGUgaXQgKGNsaWNrIHRoZSAqUHJldmlldyogYnV0dG9uIG9yIHByZXNzICpDdHJsK1NoaWZ0K0sqIHRvIHByZXZpZXcgdGhlIEhUTUwgZmlsZSkuDQoNClRoZSBwcmV2aWV3IHNob3dzIHlvdSBhIHJlbmRlcmVkIEhUTUwgY29weSBvZiB0aGUgY29udGVudHMgb2YgdGhlIGVkaXRvci4gQ29uc2VxdWVudGx5LCB1bmxpa2UgKktuaXQqLCAqUHJldmlldyogZG9lcyBub3QgcnVuIGFueSBSIGNvZGUgY2h1bmtzLiBJbnN0ZWFkLCB0aGUgb3V0cHV0IG9mIHRoZSBjaHVuayB3aGVuIGl0IHdhcyBsYXN0IHJ1biBpbiB0aGUgZWRpdG9yIGlzIGRpc3BsYXllZC4NCg==