library(ggplot2)
library(dplyr)

We’re comparing performance of the apps.mellanox.connectx driver between two

On the AMD EPYC 7443P, where the NIC is on IO bus 81, we also compare two BIOS settings:

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

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.

Single-core

Testing single-core performance of Packetblaster at 64B packets while comparing number of queues and queue size.

This should reproduce the results in ConnectX: Review N*SQ 64B transmit performance mellanox (Rev 2) from 2016. The EPYC Preferred IO bus->81 run seems to do so partially. On the Intel system the curve seems to match, however it seems to be offset by reduced overall throughput.

packetblaster_single <- (mellanox.tx.rx.queues.qsize.intel.100e6 %>%
                          mutate(system="Intel Xeon Silver 4116 @ 2.10GHz")) %>%
  bind_rows((mellanox.tx.rx.queues.qsize.epyc.100e6 %>%
              mutate(system="AMD EPYC 7443P (Pref. IO bus 81)"))) %>%
  mutate(qsize=as.factor(qsize)) %>%
  group_by(queues, qsize, system) %>% 
  summarise(minrate=min(rate), maxrate=max(rate), avgrate=mean(rate)) %>%
  ungroup()
`summarise()` has grouped output by 'queues', 'qsize'. You can override using the `.groups` argument.
ggplot(packetblaster_single, aes(x=queues, color=qsize)) +
  facet_wrap(~ system) +
  geom_line(aes(y=maxrate)) +
  geom_point(aes(y=maxrate)) + 
  ggtitle("Packetblaster performance with 64B Ethernet packets",
          subtitle="Single core, rate in Mpps")

Packet sizes

Testing Packetblaster single-core performance with 16 transmit queues comparing performance between packet sizes.

packetblaster_sizes <- (mellanox.tx.only.sizes.intel.100e6.all %>%
           mutate(system="Intel Xeon Silver 4116 (pcie gen3)")) %>%
  bind_rows((mellanox.tx.only.sizes.epyc.100e6.all.bios2 %>%
               mutate(system="AMD EPYC 7443P (pcie gen4)"))) %>%
  mutate(queues=as.factor(queues)) %>%
  group_by(pktsize, queues, system) %>% 
  summarise(minrate=min(rate), maxrate=max(rate), avgrate=mean(rate)) %>%
  ungroup()
`summarise()` has grouped output by 'pktsize', 'queues'. You can override using the `.groups` argument.
ggplot(packetblaster_sizes, aes(x=pktsize, color=system)) +
  geom_step(aes(y=avgrate, linetype="avg")) +
  geom_step(aes(y=minrate, linetype="min")) +
  geom_step(aes(y=maxrate, linetype="max")) +
  geom_line(aes(y=Linerate(100, pktsize)/1e6, linetype="linerate"), color='grey') +
  coord_cartesian(ylim=c(NA, max(packetblaster_sizes$maxrate))) +
  scale_x_continuous(n.breaks = 20) + 
  ggtitle("Packetblaster performance with different sizes of Ethernet packets",
          subtitle="Single core, 16 queues, rate in Mpps (TX only)")

Multi-core

Testing Packetblaster multi-core performance, comparing number of workers, queues, and packet size. Queue size is the default (1024).

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"))) %>%
  bind_rows((mellanox.tx.only.queues.sizes.epyc.100e6.coarse.bios2 %>%
               mutate(system="AMD EPYC 7443P (Pref. IO bus 81)"))) %>%
  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))) +
  scale_x_continuous(breaks=c(64,256,512,512+256,1024)) +
  scale_y_continuous(n.breaks = 8) + 
  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())) %>%
  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==