mirror of https://github.com/etcd-io/dbtester.git
Merge pull request #200 from gyuho/new-test-results
*: add working in progress results, update psn, add tablewriter
This commit is contained in:
commit
3f03528820
126
README.md
126
README.md
|
|
@ -19,8 +19,7 @@ For etcd, we also recommend [etcd benchmark tool](https://github.com/coreos/etcd
|
||||||
All logs and results can be found at https://console.cloud.google.com/storage/browser/dbtester-results
|
All logs and results can be found at https://console.cloud.google.com/storage/browser/dbtester-results
|
||||||
|
|
||||||
- Google Cloud Compute Engine
|
- Google Cloud Compute Engine
|
||||||
- 3 machines of 8 vCPUs + 1 6GB Memory + 50 GB SSD
|
- 4 machines of 16 vCPUs + 30 GB Memory + 150 GB SSD (1 for client)
|
||||||
- 1 machine(client) of 16 vCPUs + 30 GB Memory + 50 GB SSD
|
|
||||||
- Ubuntu 16.10
|
- Ubuntu 16.10
|
||||||
- etcd v3.1 (Go 1.7.4)
|
- etcd v3.1 (Go 1.7.4)
|
||||||
- Zookeeper r3.4.9
|
- Zookeeper r3.4.9
|
||||||
|
|
@ -32,103 +31,60 @@ All logs and results can be found at https://console.cloud.google.com/storage/br
|
||||||
- zetcd v3.1 (Go 1.7.4)
|
- zetcd v3.1 (Go 1.7.4)
|
||||||
- cetcd v3.1 (Go 1.7.4)
|
- cetcd v3.1 (Go 1.7.4)
|
||||||
|
|
||||||
<br><br>
|
|
||||||
|
|
||||||
|
|
||||||
Below is latency distribution.
|
|
||||||
|
|
||||||
| Write 2M | etcd | Zookeeper | Consul |
|
|
||||||
|:-:|:-:|:-:|:-:|:-:|
|
|
||||||
| Total | 49.9517 sec | 57.648 sec | 196.5391 sec |
|
|
||||||
| Slowest latency | 219.4978 ms | 3673.511 ms | 3456.8632 ms |
|
|
||||||
| Fastest latency | 2.4053 ms | 1.3334 ms | 11.6683 ms |
|
|
||||||
| Average latency | 24.9106 ms | 25.6085 ms | 98.0761 ms |
|
|
||||||
| 10th percentile | 15.252641 ms | 8.694673 ms | 58.773931 ms |
|
|
||||||
| 90th percentile | 34.603153 ms | 21.260465 ms | 155.155478 ms |
|
|
||||||
| 95th percentile | 58.790464 ms | 38.915503 ms | 228.378322 ms |
|
|
||||||
| 99th percentile | 109.932998 ms | 249.609641 ms | 397.145186 ms |
|
|
||||||
| 99.9th percentile | 163.174532 ms | 1791.313948 ms | 2063.012564 ms |
|
|
||||||
|
|
||||||
| Write 2M, 1000QPS | etcd | Zookeeper | Consul |
|
|
||||||
|:-:|:-:|:-:|:-:|:-:|
|
|
||||||
| Total | 1999.0071 sec | 2001.5838 sec | 2136.9951 sec |
|
|
||||||
| Slowest latency | 266.4827 ms | 2391.7506 ms | 16507.9402 ms |
|
|
||||||
| Fastest latency | 1.1254 ms | 0.8987 ms | 3.2398 ms |
|
|
||||||
| Average latency | 2.9778 ms | 6.6063 ms | 209.3409 ms |
|
|
||||||
| 10th percentile | 1.673424 ms | 1.168258 ms | 8.883748 ms |
|
|
||||||
| 90th percentile | 4.249615 ms | 2.169022 ms | 192.123086 ms |
|
|
||||||
| 95th percentile | 4.869974 ms | 2.547155 ms | 203.922319 ms |
|
|
||||||
| 99th percentile | 6.493646 ms | 39.196783 ms | 4457.758814 ms |
|
|
||||||
| 99.9th percentile | 26.053565 ms | 1205.014692 ms | 12980.91952 ms |
|
|
||||||
|
|
||||||
##### Write 2M keys, 1000-client (etcd 100 TCP conns), 8-byte key, 256-byte value
|
|
||||||
|
|
||||||
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-02-etcd-zookeeper-consul/00-write-2M-keys/AVG-LATENCY-MS.svg" alt="2017Q1-02-etcd-zookeeper-consul/00-write-2M-keys/AVG-LATENCY-MS">
|
|
||||||
|
|
||||||
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-02-etcd-zookeeper-consul/00-write-2M-keys/AVG-THROUGHPUT.svg" alt="2017Q1-02-etcd-zookeeper-consul/00-write-2M-keys/AVG-THROUGHPUT">
|
|
||||||
|
|
||||||
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-02-etcd-zookeeper-consul/00-write-2M-keys/AVG-CPU.svg" alt="2017Q1-02-etcd-zookeeper-consul/00-write-2M-keys/AVG-CPU">
|
|
||||||
|
|
||||||
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-02-etcd-zookeeper-consul/00-write-2M-keys/AVG-VMRSS-MB.svg" alt="2017Q1-02-etcd-zookeeper-consul/00-write-2M-keys/AVG-VMRSS-MB">
|
|
||||||
|
|
||||||
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-02-etcd-zookeeper-consul/00-write-2M-keys/AVG-READS-COMPLETED-DELTA.svg" alt="2017Q1-02-etcd-zookeeper-consul/00-write-2M-keys/AVG-READS-COMPLETED-DELTA">
|
|
||||||
|
|
||||||
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-02-etcd-zookeeper-consul/00-write-2M-keys/AVG-SECTORS-READ-DELTA.svg" alt="2017Q1-02-etcd-zookeeper-consul/00-write-2M-keys/AVG-SECTORS-READ-DELTA">
|
|
||||||
|
|
||||||
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-02-etcd-zookeeper-consul/00-write-2M-keys/AVG-WRITES-COMPLETED-DELTA.svg" alt="2017Q1-02-etcd-zookeeper-consul/00-write-2M-keys/AVG-WRITES-COMPLETED-DELTA">
|
|
||||||
|
|
||||||
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-02-etcd-zookeeper-consul/00-write-2M-keys/AVG-SECTORS-WRITTEN-DELTA.svg" alt="2017Q1-02-etcd-zookeeper-consul/00-write-2M-keys/AVG-SECTORS-WRITTEN-DELTA">
|
|
||||||
|
|
||||||
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-02-etcd-zookeeper-consul/00-write-2M-keys/AVG-RECEIVE-BYTES-NUM-DELTA.svg" alt="2017Q1-02-etcd-zookeeper-consul/00-write-2M-keys/AVG-RECEIVE-BYTES-NUM-DELTA">
|
|
||||||
|
|
||||||
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-02-etcd-zookeeper-consul/00-write-2M-keys/AVG-TRANSMIT-BYTES-NUM-DELTA.svg" alt="2017Q1-02-etcd-zookeeper-consul/00-write-2M-keys/AVG-TRANSMIT-BYTES-NUM-DELTA">
|
|
||||||
|
|
||||||
|
|
||||||
<br><br>
|
<br><br>
|
||||||
##### Write 2M keys 1000QPS, 1000-client (etcd 100 TCP conns), 8-byte key, 256-byte value
|
##### Write 1M keys, 1000-client, 256-byte key, 1KB value
|
||||||
|
|
||||||
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-02-etcd-zookeeper-consul/02-write-2M-keys-rate-limited/AVG-LATENCY-MS.svg" alt="2017Q1-02-etcd-zookeeper-consul/02-write-2M-keys-rate-limited/AVG-LATENCY-MS">
|
```
|
||||||
|
+------------------------------+-------------------+------------------------+-----------------------+
|
||||||
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-02-etcd-zookeeper-consul/02-write-2M-keys-rate-limited/AVG-THROUGHPUT.svg" alt="2017Q1-02-etcd-zookeeper-consul/02-write-2M-keys-rate-limited/AVG-THROUGHPUT">
|
| | etcd-v3.1-go1.7.4 | zookeeper-r3.4.9-java8 | consul-v0.7.2-go1.7.4 |
|
||||||
|
+------------------------------+-------------------+------------------------+-----------------------+
|
||||||
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-02-etcd-zookeeper-consul/02-write-2M-keys-rate-limited/AVG-CPU.svg" alt="2017Q1-02-etcd-zookeeper-consul/02-write-2M-keys-rate-limited/AVG-CPU">
|
| READS-COMPLETED-DELTA | 6 | 6 | 15 |
|
||||||
|
| SECTORS-READS-DELTA-SUM | 0 | 0 | 0 |
|
||||||
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-02-etcd-zookeeper-consul/02-write-2M-keys-rate-limited/AVG-VMRSS-MB.svg" alt="2017Q1-02-etcd-zookeeper-consul/02-write-2M-keys-rate-limited/AVG-VMRSS-MB">
|
| WRITES-COMPLETED-DELTA-SUM | 96474 | 77628 | 940695 |
|
||||||
|
| SECTORS-WRITTEN-DELTA-SUM | 542512 | 9387436 | 41272068 |
|
||||||
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-02-etcd-zookeeper-consul/02-write-2M-keys-rate-limited/AVG-READS-COMPLETED-DELTA.svg" alt="2017Q1-02-etcd-zookeeper-consul/02-write-2M-keys-rate-limited/AVG-READS-COMPLETED-DELTA">
|
| RECEIVE-BYTES-SUM | 4.9 GB | 5.1 GB | 7.7 GB |
|
||||||
|
| RECEIVE-BYTES-NUM-DELTA-SUM | 4879653590 | 5061811231 | 7744263649 |
|
||||||
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-02-etcd-zookeeper-consul/02-write-2M-keys-rate-limited/AVG-SECTORS-READ-DELTA.svg" alt="2017Q1-02-etcd-zookeeper-consul/02-write-2M-keys-rate-limited/AVG-SECTORS-READ-DELTA">
|
| TRANSMIT-BYTES-SUM | 3.7 GB | 4.1 GB | 6.5 GB |
|
||||||
|
| TRANSMIT-BYTES-NUM-DELTA-SUM | 3749247743 | 4050548646 | 6518060316 |
|
||||||
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-02-etcd-zookeeper-consul/02-write-2M-keys-rate-limited/AVG-WRITES-COMPLETED-DELTA.svg" alt="2017Q1-02-etcd-zookeeper-consul/02-write-2M-keys-rate-limited/AVG-WRITES-COMPLETED-DELTA">
|
| TOTAL-SECONDS | 36.2024 s | 62.0373 s | 467.9311 s |
|
||||||
|
| AVG-THROUGHPUT | 27622.4453 | 15951.5555 | 2137.0667 |
|
||||||
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-02-etcd-zookeeper-consul/02-write-2M-keys-rate-limited/AVG-SECTORS-WRITTEN-DELTA.svg" alt="2017Q1-02-etcd-zookeeper-consul/02-write-2M-keys-rate-limited/AVG-SECTORS-WRITTEN-DELTA">
|
| SLOWEST-LATENCY | 246.4560 ms | 6650.0930 ms | 30388.9318 ms |
|
||||||
|
| FASTEST-LATENCY | 5.3413 ms | 1.7698 ms | 21.5605 ms |
|
||||||
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-02-etcd-zookeeper-consul/02-write-2M-keys-rate-limited/AVG-RECEIVE-BYTES-NUM-DELTA.svg" alt="2017Q1-02-etcd-zookeeper-consul/02-write-2M-keys-rate-limited/AVG-RECEIVE-BYTES-NUM-DELTA">
|
| AVG-LATENCY | 36.1057 ms | 37.7865 ms | 467.4253 ms |
|
||||||
|
| p10 | 13.712090 ms | 11.923543 ms | 65.910086 ms |
|
||||||
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-02-etcd-zookeeper-consul/02-write-2M-keys-rate-limited/AVG-TRANSMIT-BYTES-NUM-DELTA.svg" alt="2017Q1-02-etcd-zookeeper-consul/02-write-2M-keys-rate-limited/AVG-TRANSMIT-BYTES-NUM-DELTA">
|
| p25 | 16.625779 ms | 14.581663 ms | 77.221971 ms |
|
||||||
|
| p50 | 22.306160 ms | 19.217649 ms | 120.663354 ms |
|
||||||
|
| p75 | 40.376905 ms | 23.642903 ms | 716.373543 ms |
|
||||||
|
| p90 | 65.849751 ms | 28.756700 ms | 1068.038406 ms |
|
||||||
|
| p95 | 137.545464 ms | 59.868096 ms | 1080.751412 ms |
|
||||||
|
| p99 | 177.127309 ms | 544.858078 ms | 2686.919571 ms |
|
||||||
|
| p99.9 | 198.540415 ms | 2457.827147 ms | 19041.188919 ms |
|
||||||
|
+------------------------------+-------------------+------------------------+-----------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
<br><br>
|
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-01-etcd-zookeeper-consul/01-write-1M-keys/AVG-LATENCY-MS.svg" alt="2017Q1-01-etcd-zookeeper-consul/01-write-1M-keys/AVG-LATENCY-MS">
|
||||||
##### Write 2M keys, 8-byte key, 256-byte value
|
|
||||||
|
|
||||||
clients increase from 1 to 1000
|
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-01-etcd-zookeeper-consul/01-write-1M-keys/AVG-THROUGHPUT.svg" alt="2017Q1-01-etcd-zookeeper-consul/01-write-1M-keys/AVG-THROUGHPUT">
|
||||||
|
|
||||||
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-02-etcd-zookeeper-consul/01-write/AVG-LATENCY-MS.svg" alt="2017Q1-02-etcd-zookeeper-consul/01-write/AVG-LATENCY-MS">
|
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-01-etcd-zookeeper-consul/01-write-1M-keys/AVG-CPU.svg" alt="2017Q1-01-etcd-zookeeper-consul/01-write-1M-keys/AVG-CPU">
|
||||||
|
|
||||||
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-02-etcd-zookeeper-consul/01-write/AVG-THROUGHPUT.svg" alt="2017Q1-02-etcd-zookeeper-consul/01-write/AVG-THROUGHPUT">
|
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-01-etcd-zookeeper-consul/01-write-1M-keys/AVG-VOLUNTARY-CTXT-SWITCHES.svg" alt="2017Q1-01-etcd-zookeeper-consul/01-write-1M-keys/AVG-VOLUNTARY-CTXT-SWITCHES">
|
||||||
|
|
||||||
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-02-etcd-zookeeper-consul/01-write/AVG-CPU.svg" alt="2017Q1-02-etcd-zookeeper-consul/01-write/AVG-CPU">
|
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-01-etcd-zookeeper-consul/01-write-1M-keys/AVG-NON-VOLUNTARY-CTXT-SWITCHES.svg" alt="2017Q1-01-etcd-zookeeper-consul/01-write-1M-keys/AVG-NON-VOLUNTARY-CTXT-SWITCHES">
|
||||||
|
|
||||||
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-02-etcd-zookeeper-consul/01-write/AVG-VMRSS-MB.svg" alt="2017Q1-02-etcd-zookeeper-consul/01-write/AVG-VMRSS-MB">
|
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-01-etcd-zookeeper-consul/01-write-1M-keys/AVG-VMRSS-MB.svg" alt="2017Q1-01-etcd-zookeeper-consul/01-write-1M-keys/AVG-VMRSS-MB">
|
||||||
|
|
||||||
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-02-etcd-zookeeper-consul/01-write/AVG-READS-COMPLETED-DELTA.svg" alt="2017Q1-02-etcd-zookeeper-consul/01-write/AVG-READS-COMPLETED-DELTA">
|
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-01-etcd-zookeeper-consul/01-write-1M-keys/AVG-READS-COMPLETED-DELTA.svg" alt="2017Q1-01-etcd-zookeeper-consul/01-write-1M-keys/AVG-READS-COMPLETED-DELTA">
|
||||||
|
|
||||||
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-02-etcd-zookeeper-consul/01-write/AVG-SECTORS-READ-DELTA.svg" alt="2017Q1-02-etcd-zookeeper-consul/01-write/AVG-SECTORS-READ-DELTA">
|
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-01-etcd-zookeeper-consul/01-write-1M-keys/AVG-SECTORS-READ-DELTA.svg" alt="2017Q1-01-etcd-zookeeper-consul/01-write-1M-keys/AVG-SECTORS-READ-DELTA">
|
||||||
|
|
||||||
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-02-etcd-zookeeper-consul/01-write/AVG-WRITES-COMPLETED-DELTA.svg" alt="2017Q1-02-etcd-zookeeper-consul/01-write/AVG-WRITES-COMPLETED-DELTA">
|
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-01-etcd-zookeeper-consul/01-write-1M-keys/AVG-WRITES-COMPLETED-DELTA.svg" alt="2017Q1-01-etcd-zookeeper-consul/01-write-1M-keys/AVG-WRITES-COMPLETED-DELTA">
|
||||||
|
|
||||||
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-02-etcd-zookeeper-consul/01-write/AVG-SECTORS-WRITTEN-DELTA.svg" alt="2017Q1-02-etcd-zookeeper-consul/01-write/AVG-SECTORS-WRITTEN-DELTA">
|
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-01-etcd-zookeeper-consul/01-write-1M-keys/AVG-SECTORS-WRITTEN-DELTA.svg" alt="2017Q1-01-etcd-zookeeper-consul/01-write-1M-keys/AVG-SECTORS-WRITTEN-DELTA">
|
||||||
|
|
||||||
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-02-etcd-zookeeper-consul/01-write/AVG-RECEIVE-BYTES-NUM-DELTA.svg" alt="2017Q1-02-etcd-zookeeper-consul/01-write/AVG-RECEIVE-BYTES-NUM-DELTA">
|
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-01-etcd-zookeeper-consul/01-write-1M-keys/AVG-RECEIVE-BYTES-NUM-DELTA.svg" alt="2017Q1-01-etcd-zookeeper-consul/01-write-1M-keys/AVG-RECEIVE-BYTES-NUM-DELTA">
|
||||||
|
|
||||||
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-02-etcd-zookeeper-consul/01-write/AVG-TRANSMIT-BYTES-NUM-DELTA.svg" alt="2017Q1-02-etcd-zookeeper-consul/01-write/AVG-TRANSMIT-BYTES-NUM-DELTA">
|
<img src="https://storage.googleapis.com/dbtester-results/2017Q1-01-etcd-zookeeper-consul/01-write-1M-keys/AVG-TRANSMIT-BYTES-NUM-DELTA.svg" alt="2017Q1-01-etcd-zookeeper-consul/01-write-1M-keys/AVG-TRANSMIT-BYTES-NUM-DELTA">
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ type READMEConfig struct {
|
||||||
} `yaml:"results"`
|
} `yaml:"results"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeREADME(cfg READMEConfig) error {
|
func writeREADME(summary string, cfg READMEConfig) error {
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
|
|
||||||
buf.WriteString("\n\n")
|
buf.WriteString("\n\n")
|
||||||
|
|
@ -43,7 +43,9 @@ func writeREADME(cfg READMEConfig) error {
|
||||||
|
|
||||||
for _, result := range cfg.Results {
|
for _, result := range cfg.Results {
|
||||||
buf.WriteString(fmt.Sprintf("<br><br>\n##### %s", result.Title))
|
buf.WriteString(fmt.Sprintf("<br><br>\n##### %s", result.Title))
|
||||||
buf.WriteString("\n\n")
|
buf.WriteString("\n\n```\n")
|
||||||
|
buf.WriteString(summary)
|
||||||
|
buf.WriteString("```\n\n\n")
|
||||||
for _, img := range result.Images {
|
for _, img := range result.Images {
|
||||||
imgPath := ""
|
imgPath := ""
|
||||||
switch img.ImageType {
|
switch img.ImageType {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
package analyze
|
package analyze
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/csv"
|
"encoding/csv"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
@ -24,6 +25,7 @@ import (
|
||||||
|
|
||||||
humanize "github.com/dustin/go-humanize"
|
humanize "github.com/dustin/go-humanize"
|
||||||
"github.com/gyuho/dataframe"
|
"github.com/gyuho/dataframe"
|
||||||
|
"github.com/olekukonko/tablewriter"
|
||||||
)
|
)
|
||||||
|
|
||||||
type allAggregatedData struct {
|
type allAggregatedData struct {
|
||||||
|
|
@ -232,7 +234,7 @@ func do(configPath string) error {
|
||||||
for _, row := range rows {
|
for _, row := range rows {
|
||||||
switch row[0] {
|
switch row[0] {
|
||||||
case "TOTAL-SECONDS":
|
case "TOTAL-SECONDS":
|
||||||
row10TotalSeconds = append(row10TotalSeconds, row[1])
|
row10TotalSeconds = append(row10TotalSeconds, fmt.Sprintf("%s s", row[1]))
|
||||||
case "REQUESTS-PER-SECOND":
|
case "REQUESTS-PER-SECOND":
|
||||||
row11AverageThroughput = append(row11AverageThroughput, row[1])
|
row11AverageThroughput = append(row11AverageThroughput, row[1])
|
||||||
case "SLOWEST-LATENCY-MS":
|
case "SLOWEST-LATENCY-MS":
|
||||||
|
|
@ -293,14 +295,7 @@ func do(configPath string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
plog.Printf("saving data to %q", cfg.AllAggregatedPath)
|
aggRows := [][]string{
|
||||||
file, err := openToOverwrite(cfg.AllAggregatedPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
wr := csv.NewWriter(file)
|
|
||||||
if err := wr.WriteAll([][]string{
|
|
||||||
row1Header,
|
row1Header,
|
||||||
row2ReadsCompletedDeltaSum,
|
row2ReadsCompletedDeltaSum,
|
||||||
row3SectorsReadDeltaSum,
|
row3SectorsReadDeltaSum,
|
||||||
|
|
@ -323,7 +318,15 @@ func do(configPath string) error {
|
||||||
row20p95,
|
row20p95,
|
||||||
row21p99,
|
row21p99,
|
||||||
row22p999,
|
row22p999,
|
||||||
}); err != nil {
|
}
|
||||||
|
plog.Printf("saving data to %q", cfg.AllAggregatedPath)
|
||||||
|
file, err := openToOverwrite(cfg.AllAggregatedPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
wr := csv.NewWriter(file)
|
||||||
|
if err := wr.WriteAll(aggRows); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
wr.Flush()
|
wr.Flush()
|
||||||
|
|
@ -443,7 +446,24 @@ func do(configPath string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
tw := tablewriter.NewWriter(buf)
|
||||||
|
tw.SetHeader(aggRows[0])
|
||||||
|
for _, row := range aggRows[1:] {
|
||||||
|
tw.Append(row)
|
||||||
|
}
|
||||||
|
tw.SetAutoFormatHeaders(false)
|
||||||
|
tw.SetAlignment(tablewriter.ALIGN_CENTER)
|
||||||
|
tw.Render()
|
||||||
|
if err := toFile(buf.String(), changeExtToTxt(cfg.AllAggregatedPath)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
plog.Printf("writing README at %q", cfg.READMEConfig.OutputPath)
|
plog.Printf("writing README at %q", cfg.READMEConfig.OutputPath)
|
||||||
return writeREADME(cfg.READMEConfig)
|
return writeREADME(buf.String(), cfg.READMEConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func changeExtToTxt(fpath string) string {
|
||||||
|
ext := filepath.Ext(fpath)
|
||||||
|
return strings.Replace(fpath, ext, ".txt", -1)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
hash: fbfd7f9325aae2182e293f1acf2de3e5be3f322221e9a9ec0e3fcdbf801232de
|
hash: 96da2f975c12b9838bc2e467c400391f05df285e5586f7601d0a4f00090633c4
|
||||||
updated: 2017-01-23T13:32:56.468519546-08:00
|
updated: 2017-01-23T20:34:04.124178007-08:00
|
||||||
imports:
|
imports:
|
||||||
- name: bitbucket.org/zombiezen/gopdf
|
- name: bitbucket.org/zombiezen/gopdf
|
||||||
version: 1c63dc69751bc45441c2ce1f56b631c55294b4d5
|
version: 1c63dc69751bc45441c2ce1f56b631c55294b4d5
|
||||||
|
|
@ -97,7 +97,7 @@ imports:
|
||||||
- name: github.com/gyuho/dataframe
|
- name: github.com/gyuho/dataframe
|
||||||
version: 0654e1bff71a2c46f30d603f30b3621dd599ec50
|
version: 0654e1bff71a2c46f30d603f30b3621dd599ec50
|
||||||
- name: github.com/gyuho/psn
|
- name: github.com/gyuho/psn
|
||||||
version: 1828fc5fa67ec73c201469411b9a5e51a3ecd008
|
version: 5cd1061aaaf19d6cd8fa32239500b09516f67a14
|
||||||
subpackages:
|
subpackages:
|
||||||
- schema
|
- schema
|
||||||
- name: github.com/hashicorp/consul
|
- name: github.com/hashicorp/consul
|
||||||
|
|
@ -124,7 +124,7 @@ imports:
|
||||||
subpackages:
|
subpackages:
|
||||||
- pbutil
|
- pbutil
|
||||||
- name: github.com/olekukonko/tablewriter
|
- name: github.com/olekukonko/tablewriter
|
||||||
version: cca8bbc0798408af109aaaa239cbd2634846b340
|
version: a0225b3f23b5ce0cbec6d7a66a968f8a59eca9c4
|
||||||
- name: github.com/prometheus/client_golang
|
- name: github.com/prometheus/client_golang
|
||||||
version: c5b7fccd204277076155f10851dad72b76a49317
|
version: c5b7fccd204277076155f10851dad72b76a49317
|
||||||
subpackages:
|
subpackages:
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ import:
|
||||||
- package: github.com/gyuho/dataframe
|
- package: github.com/gyuho/dataframe
|
||||||
version: 0654e1bff71a2c46f30d603f30b3621dd599ec50
|
version: 0654e1bff71a2c46f30d603f30b3621dd599ec50
|
||||||
- package: github.com/gyuho/psn
|
- package: github.com/gyuho/psn
|
||||||
version: 1828fc5fa67ec73c201469411b9a5e51a3ecd008
|
version: 5cd1061aaaf19d6cd8fa32239500b09516f67a14
|
||||||
- package: github.com/hashicorp/consul
|
- package: github.com/hashicorp/consul
|
||||||
version: 8d57727ff0d113a97c89a992e4681680f95cbf03
|
version: 8d57727ff0d113a97c89a992e4681680f95cbf03
|
||||||
subpackages:
|
subpackages:
|
||||||
|
|
@ -106,3 +106,5 @@ import:
|
||||||
version: a4bde12657593d5e90d0533a3e4fd95e635124cb
|
version: a4bde12657593d5e90d0533a3e4fd95e635124cb
|
||||||
subpackages:
|
subpackages:
|
||||||
- rate
|
- rate
|
||||||
|
- package: github.com/olekukonko/tablewriter
|
||||||
|
version: a0225b3f23b5ce0cbec6d7a66a968f8a59eca9c4
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,8 @@ func StringDS(header []string, rows [][]string, topLimit int) string {
|
||||||
for _, row := range rows {
|
for _, row := range rows {
|
||||||
tw.Append(row[:columnsDSToShow:columnsDSToShow])
|
tw.Append(row[:columnsDSToShow:columnsDSToShow])
|
||||||
}
|
}
|
||||||
|
tw.SetAutoFormatHeaders(false)
|
||||||
|
tw.SetAlignment(tablewriter.ALIGN_CENTER)
|
||||||
tw.Render()
|
tw.Render()
|
||||||
|
|
||||||
return buf.String()
|
return buf.String()
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,8 @@ func StringNS(header []string, rows [][]string, topLimit int) string {
|
||||||
for _, row := range rows {
|
for _, row := range rows {
|
||||||
tw.Append(row[:columnsNSToShow:columnsNSToShow])
|
tw.Append(row[:columnsNSToShow:columnsNSToShow])
|
||||||
}
|
}
|
||||||
|
tw.SetAutoFormatHeaders(false)
|
||||||
|
tw.SetAlignment(tablewriter.ALIGN_CENTER)
|
||||||
tw.Render()
|
tw.Render()
|
||||||
|
|
||||||
return buf.String()
|
return buf.String()
|
||||||
|
|
|
||||||
|
|
@ -260,6 +260,8 @@ func StringPS(header []string, rows [][]string, topLimit int) string {
|
||||||
for _, row := range rows {
|
for _, row := range rows {
|
||||||
tw.Append(row[:columnsPSToShow:columnsPSToShow])
|
tw.Append(row[:columnsPSToShow:columnsPSToShow])
|
||||||
}
|
}
|
||||||
|
tw.SetAutoFormatHeaders(false)
|
||||||
|
tw.SetAlignment(tablewriter.ALIGN_CENTER)
|
||||||
tw.Render()
|
tw.Render()
|
||||||
|
|
||||||
return buf.String()
|
return buf.String()
|
||||||
|
|
|
||||||
|
|
@ -304,6 +304,8 @@ func StringSS(header []string, rows [][]string, topLimit int) string {
|
||||||
for _, row := range rows {
|
for _, row := range rows {
|
||||||
tw.Append(row[:columnsSSToShow:columnsSSToShow])
|
tw.Append(row[:columnsSSToShow:columnsSSToShow])
|
||||||
}
|
}
|
||||||
|
tw.SetAutoFormatHeaders(false)
|
||||||
|
tw.SetAlignment(tablewriter.ALIGN_CENTER)
|
||||||
tw.Render()
|
tw.Render()
|
||||||
|
|
||||||
return buf.String()
|
return buf.String()
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
package tablewriter
|
package tablewriter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
@ -20,45 +21,57 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
CENTRE = "+"
|
CENTER = "+"
|
||||||
ROW = "-"
|
ROW = "-"
|
||||||
COLUMN = "|"
|
COLUMN = "|"
|
||||||
SPACE = " "
|
SPACE = " "
|
||||||
|
NEWLINE = "\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ALIGN_DEFAULT = iota
|
ALIGN_DEFAULT = iota
|
||||||
ALIGN_CENTRE
|
ALIGN_CENTER
|
||||||
ALIGN_RIGHT
|
ALIGN_RIGHT
|
||||||
ALIGN_LEFT
|
ALIGN_LEFT
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
decimal = regexp.MustCompile(`^\d*\.?\d*$`)
|
decimal = regexp.MustCompile(`^-*\d*\.?\d*$`)
|
||||||
percent = regexp.MustCompile(`^\d*\.?\d*$%$`)
|
percent = regexp.MustCompile(`^-*\d*\.?\d*$%$`)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Border struct {
|
||||||
|
Left bool
|
||||||
|
Right bool
|
||||||
|
Top bool
|
||||||
|
Bottom bool
|
||||||
|
}
|
||||||
|
|
||||||
type Table struct {
|
type Table struct {
|
||||||
out io.Writer
|
out io.Writer
|
||||||
rows [][]string
|
rows [][]string
|
||||||
lines [][][]string
|
lines [][][]string
|
||||||
cs map[int]int
|
cs map[int]int
|
||||||
rs map[int]int
|
rs map[int]int
|
||||||
headers []string
|
headers []string
|
||||||
footers []string
|
footers []string
|
||||||
autoFmt bool
|
autoFmt bool
|
||||||
autoWrap bool
|
autoWrap bool
|
||||||
mW int
|
mW int
|
||||||
pCenter string
|
pCenter string
|
||||||
pRow string
|
pRow string
|
||||||
pColumn string
|
pColumn string
|
||||||
tColumn int
|
tColumn int
|
||||||
tRow int
|
tRow int
|
||||||
align int
|
hAlign int
|
||||||
rowLine bool
|
fAlign int
|
||||||
hdrLine bool
|
align int
|
||||||
border bool
|
newLine string
|
||||||
colSize int
|
rowLine bool
|
||||||
|
autoMergeCells bool
|
||||||
|
hdrLine bool
|
||||||
|
borders Border
|
||||||
|
colSize int
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start New Table
|
// Start New Table
|
||||||
|
|
@ -75,28 +88,35 @@ func NewWriter(writer io.Writer) *Table {
|
||||||
autoFmt: true,
|
autoFmt: true,
|
||||||
autoWrap: true,
|
autoWrap: true,
|
||||||
mW: MAX_ROW_WIDTH,
|
mW: MAX_ROW_WIDTH,
|
||||||
pCenter: CENTRE,
|
pCenter: CENTER,
|
||||||
pRow: ROW,
|
pRow: ROW,
|
||||||
pColumn: COLUMN,
|
pColumn: COLUMN,
|
||||||
tColumn: -1,
|
tColumn: -1,
|
||||||
tRow: -1,
|
tRow: -1,
|
||||||
|
hAlign: ALIGN_DEFAULT,
|
||||||
|
fAlign: ALIGN_DEFAULT,
|
||||||
align: ALIGN_DEFAULT,
|
align: ALIGN_DEFAULT,
|
||||||
|
newLine: NEWLINE,
|
||||||
rowLine: false,
|
rowLine: false,
|
||||||
hdrLine: true,
|
hdrLine: true,
|
||||||
border: true,
|
borders: Border{Left: true, Right: true, Bottom: true, Top: true},
|
||||||
colSize: -1}
|
colSize: -1}
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render table output
|
// Render table output
|
||||||
func (t Table) Render() {
|
func (t Table) Render() {
|
||||||
if t.border {
|
if t.borders.Top {
|
||||||
t.printLine(true)
|
t.printLine(true)
|
||||||
}
|
}
|
||||||
t.printHeading()
|
t.printHeading()
|
||||||
t.printRows()
|
if t.autoMergeCells {
|
||||||
|
t.printRowsMergeCells()
|
||||||
|
} else {
|
||||||
|
t.printRows()
|
||||||
|
}
|
||||||
|
|
||||||
if !t.rowLine && t.border {
|
if !t.rowLine && t.borders.Bottom {
|
||||||
t.printLine(true)
|
t.printLine(true)
|
||||||
}
|
}
|
||||||
t.printFooter()
|
t.printFooter()
|
||||||
|
|
@ -151,11 +171,26 @@ func (t *Table) SetCenterSeparator(sep string) {
|
||||||
t.pCenter = sep
|
t.pCenter = sep
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set Header Alignment
|
||||||
|
func (t *Table) SetHeaderAlignment(hAlign int) {
|
||||||
|
t.hAlign = hAlign
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set Footer Alignment
|
||||||
|
func (t *Table) SetFooterAlignment(fAlign int) {
|
||||||
|
t.fAlign = fAlign
|
||||||
|
}
|
||||||
|
|
||||||
// Set Table Alignment
|
// Set Table Alignment
|
||||||
func (t *Table) SetAlignment(align int) {
|
func (t *Table) SetAlignment(align int) {
|
||||||
t.align = align
|
t.align = align
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set New Line
|
||||||
|
func (t *Table) SetNewLine(nl string) {
|
||||||
|
t.newLine = nl
|
||||||
|
}
|
||||||
|
|
||||||
// Set Header Line
|
// Set Header Line
|
||||||
// This would enable / disable a line after the header
|
// This would enable / disable a line after the header
|
||||||
func (t *Table) SetHeaderLine(line bool) {
|
func (t *Table) SetHeaderLine(line bool) {
|
||||||
|
|
@ -168,10 +203,20 @@ func (t *Table) SetRowLine(line bool) {
|
||||||
t.rowLine = line
|
t.rowLine = line
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set Auto Merge Cells
|
||||||
|
// This would enable / disable the merge of cells with identical values
|
||||||
|
func (t *Table) SetAutoMergeCells(auto bool) {
|
||||||
|
t.autoMergeCells = auto
|
||||||
|
}
|
||||||
|
|
||||||
// Set Table Border
|
// Set Table Border
|
||||||
// This would enable / disable line around the table
|
// This would enable / disable line around the table
|
||||||
func (t *Table) SetBorder(border bool) {
|
func (t *Table) SetBorder(border bool) {
|
||||||
t.border = border
|
t.SetBorders(Border{border, border, border, border})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Table) SetBorders(border Border) {
|
||||||
|
t.borders = border
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append row to table
|
// Append row to table
|
||||||
|
|
@ -216,10 +261,47 @@ func (t Table) printLine(nl bool) {
|
||||||
t.pCenter)
|
t.pCenter)
|
||||||
}
|
}
|
||||||
if nl {
|
if nl {
|
||||||
fmt.Fprintln(t.out)
|
fmt.Fprint(t.out, t.newLine)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Print line based on row width with our without cell separator
|
||||||
|
func (t Table) printLineOptionalCellSeparators(nl bool, displayCellSeparator []bool) {
|
||||||
|
fmt.Fprint(t.out, t.pCenter)
|
||||||
|
for i := 0; i < len(t.cs); i++ {
|
||||||
|
v := t.cs[i]
|
||||||
|
if i > len(displayCellSeparator) || displayCellSeparator[i] {
|
||||||
|
// Display the cell separator
|
||||||
|
fmt.Fprintf(t.out, "%s%s%s%s",
|
||||||
|
t.pRow,
|
||||||
|
strings.Repeat(string(t.pRow), v),
|
||||||
|
t.pRow,
|
||||||
|
t.pCenter)
|
||||||
|
} else {
|
||||||
|
// Don't display the cell separator for this cell
|
||||||
|
fmt.Fprintf(t.out, "%s%s",
|
||||||
|
strings.Repeat(" ", v+2),
|
||||||
|
t.pCenter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if nl {
|
||||||
|
fmt.Fprint(t.out, t.newLine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the PadRight function if align is left, PadLeft if align is right,
|
||||||
|
// and Pad by default
|
||||||
|
func pad(align int) func(string, string, int) string {
|
||||||
|
padFunc := Pad
|
||||||
|
switch align {
|
||||||
|
case ALIGN_LEFT:
|
||||||
|
padFunc = PadRight
|
||||||
|
case ALIGN_RIGHT:
|
||||||
|
padFunc = PadLeft
|
||||||
|
}
|
||||||
|
return padFunc
|
||||||
|
}
|
||||||
|
|
||||||
// Print heading information
|
// Print heading information
|
||||||
func (t Table) printHeading() {
|
func (t Table) printHeading() {
|
||||||
// Check if headers is available
|
// Check if headers is available
|
||||||
|
|
@ -229,11 +311,14 @@ func (t Table) printHeading() {
|
||||||
|
|
||||||
// Check if border is set
|
// Check if border is set
|
||||||
// Replace with space if not set
|
// Replace with space if not set
|
||||||
fmt.Fprint(t.out, ConditionString(t.border, t.pColumn, SPACE))
|
fmt.Fprint(t.out, ConditionString(t.borders.Left, t.pColumn, SPACE))
|
||||||
|
|
||||||
// Identify last column
|
// Identify last column
|
||||||
end := len(t.cs) - 1
|
end := len(t.cs) - 1
|
||||||
|
|
||||||
|
// Get pad function
|
||||||
|
padFunc := pad(t.hAlign)
|
||||||
|
|
||||||
// Print Heading column
|
// Print Heading column
|
||||||
for i := 0; i <= end; i++ {
|
for i := 0; i <= end; i++ {
|
||||||
v := t.cs[i]
|
v := t.cs[i]
|
||||||
|
|
@ -241,13 +326,13 @@ func (t Table) printHeading() {
|
||||||
if t.autoFmt {
|
if t.autoFmt {
|
||||||
h = Title(h)
|
h = Title(h)
|
||||||
}
|
}
|
||||||
pad := ConditionString((i == end && !t.border), SPACE, t.pColumn)
|
pad := ConditionString((i == end && !t.borders.Left), SPACE, t.pColumn)
|
||||||
fmt.Fprintf(t.out, " %s %s",
|
fmt.Fprintf(t.out, " %s %s",
|
||||||
Pad(h, SPACE, v),
|
padFunc(h, SPACE, v),
|
||||||
pad)
|
pad)
|
||||||
}
|
}
|
||||||
// Next line
|
// Next line
|
||||||
fmt.Fprintln(t.out)
|
fmt.Fprint(t.out, t.newLine)
|
||||||
if t.hdrLine {
|
if t.hdrLine {
|
||||||
t.printLine(true)
|
t.printLine(true)
|
||||||
}
|
}
|
||||||
|
|
@ -261,16 +346,19 @@ func (t Table) printFooter() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only print line if border is not set
|
// Only print line if border is not set
|
||||||
if !t.border {
|
if !t.borders.Bottom {
|
||||||
t.printLine(true)
|
t.printLine(true)
|
||||||
}
|
}
|
||||||
// Check if border is set
|
// Check if border is set
|
||||||
// Replace with space if not set
|
// Replace with space if not set
|
||||||
fmt.Fprint(t.out, ConditionString(t.border, t.pColumn, SPACE))
|
fmt.Fprint(t.out, ConditionString(t.borders.Bottom, t.pColumn, SPACE))
|
||||||
|
|
||||||
// Identify last column
|
// Identify last column
|
||||||
end := len(t.cs) - 1
|
end := len(t.cs) - 1
|
||||||
|
|
||||||
|
// Get pad function
|
||||||
|
padFunc := pad(t.fAlign)
|
||||||
|
|
||||||
// Print Heading column
|
// Print Heading column
|
||||||
for i := 0; i <= end; i++ {
|
for i := 0; i <= end; i++ {
|
||||||
v := t.cs[i]
|
v := t.cs[i]
|
||||||
|
|
@ -278,17 +366,17 @@ func (t Table) printFooter() {
|
||||||
if t.autoFmt {
|
if t.autoFmt {
|
||||||
f = Title(f)
|
f = Title(f)
|
||||||
}
|
}
|
||||||
pad := ConditionString((i == end && !t.border), SPACE, t.pColumn)
|
pad := ConditionString((i == end && !t.borders.Top), SPACE, t.pColumn)
|
||||||
|
|
||||||
if len(t.footers[i]) == 0 {
|
if len(t.footers[i]) == 0 {
|
||||||
pad = SPACE
|
pad = SPACE
|
||||||
}
|
}
|
||||||
fmt.Fprintf(t.out, " %s %s",
|
fmt.Fprintf(t.out, " %s %s",
|
||||||
Pad(f, SPACE, v),
|
padFunc(f, SPACE, v),
|
||||||
pad)
|
pad)
|
||||||
}
|
}
|
||||||
// Next line
|
// Next line
|
||||||
fmt.Fprintln(t.out)
|
fmt.Fprint(t.out, t.newLine)
|
||||||
//t.printLine(true)
|
//t.printLine(true)
|
||||||
|
|
||||||
hasPrinted := false
|
hasPrinted := false
|
||||||
|
|
@ -304,7 +392,7 @@ func (t Table) printFooter() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set center to be space if length is 0
|
// Set center to be space if length is 0
|
||||||
if length == 0 && !t.border {
|
if length == 0 && !t.borders.Right {
|
||||||
center = SPACE
|
center = SPACE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -318,7 +406,7 @@ func (t Table) printFooter() {
|
||||||
pad = SPACE
|
pad = SPACE
|
||||||
}
|
}
|
||||||
// Ignore left space of it has printed before
|
// Ignore left space of it has printed before
|
||||||
if hasPrinted || t.border {
|
if hasPrinted || t.borders.Left {
|
||||||
pad = t.pRow
|
pad = t.pRow
|
||||||
center = t.pCenter
|
center = t.pCenter
|
||||||
}
|
}
|
||||||
|
|
@ -339,7 +427,7 @@ func (t Table) printFooter() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintln(t.out)
|
fmt.Fprint(t.out, t.newLine)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -383,7 +471,7 @@ func (t Table) printRow(columns [][]string, colKey int) {
|
||||||
for y := 0; y < total; y++ {
|
for y := 0; y < total; y++ {
|
||||||
|
|
||||||
// Check if border is set
|
// Check if border is set
|
||||||
fmt.Fprint(t.out, ConditionString((!t.border && y == 0), SPACE, t.pColumn))
|
fmt.Fprint(t.out, ConditionString((!t.borders.Left && y == 0), SPACE, t.pColumn))
|
||||||
|
|
||||||
fmt.Fprintf(t.out, SPACE)
|
fmt.Fprintf(t.out, SPACE)
|
||||||
str := columns[y][x]
|
str := columns[y][x]
|
||||||
|
|
@ -391,7 +479,7 @@ func (t Table) printRow(columns [][]string, colKey int) {
|
||||||
// This would print alignment
|
// This would print alignment
|
||||||
// Default alignment would use multiple configuration
|
// Default alignment would use multiple configuration
|
||||||
switch t.align {
|
switch t.align {
|
||||||
case ALIGN_CENTRE: //
|
case ALIGN_CENTER: //
|
||||||
fmt.Fprintf(t.out, "%s", Pad(str, SPACE, t.cs[y]))
|
fmt.Fprintf(t.out, "%s", Pad(str, SPACE, t.cs[y]))
|
||||||
case ALIGN_RIGHT:
|
case ALIGN_RIGHT:
|
||||||
fmt.Fprintf(t.out, "%s", PadLeft(str, SPACE, t.cs[y]))
|
fmt.Fprintf(t.out, "%s", PadLeft(str, SPACE, t.cs[y]))
|
||||||
|
|
@ -416,14 +504,111 @@ func (t Table) printRow(columns [][]string, colKey int) {
|
||||||
}
|
}
|
||||||
// Check if border is set
|
// Check if border is set
|
||||||
// Replace with space if not set
|
// Replace with space if not set
|
||||||
fmt.Fprint(t.out, ConditionString(t.border, t.pColumn, SPACE))
|
fmt.Fprint(t.out, ConditionString(t.borders.Left, t.pColumn, SPACE))
|
||||||
fmt.Fprintln(t.out)
|
fmt.Fprint(t.out, t.newLine)
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.rowLine {
|
if t.rowLine {
|
||||||
t.printLine(true)
|
t.printLine(true)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print the rows of the table and merge the cells that are identical
|
||||||
|
func (t Table) printRowsMergeCells() {
|
||||||
|
var previousLine []string
|
||||||
|
var displayCellBorder []bool
|
||||||
|
var tmpWriter bytes.Buffer
|
||||||
|
for i, lines := range t.lines {
|
||||||
|
// We store the display of the current line in a tmp writer, as we need to know which border needs to be print above
|
||||||
|
previousLine, displayCellBorder = t.printRowMergeCells(&tmpWriter, lines, i, previousLine)
|
||||||
|
if i > 0 { //We don't need to print borders above first line
|
||||||
|
if t.rowLine {
|
||||||
|
t.printLineOptionalCellSeparators(true, displayCellBorder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tmpWriter.WriteTo(t.out)
|
||||||
|
}
|
||||||
|
//Print the end of the table
|
||||||
|
if t.rowLine {
|
||||||
|
t.printLine(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print Row Information to a writer and merge identical cells.
|
||||||
|
// Adjust column alignment based on type
|
||||||
|
|
||||||
|
func (t Table) printRowMergeCells(writer io.Writer, columns [][]string, colKey int, previousLine []string) ([]string, []bool) {
|
||||||
|
// Get Maximum Height
|
||||||
|
max := t.rs[colKey]
|
||||||
|
total := len(columns)
|
||||||
|
|
||||||
|
// Pad Each Height
|
||||||
|
pads := []int{}
|
||||||
|
|
||||||
|
for i, line := range columns {
|
||||||
|
length := len(line)
|
||||||
|
pad := max - length
|
||||||
|
pads = append(pads, pad)
|
||||||
|
for n := 0; n < pad; n++ {
|
||||||
|
columns[i] = append(columns[i], " ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var displayCellBorder []bool
|
||||||
|
for x := 0; x < max; x++ {
|
||||||
|
for y := 0; y < total; y++ {
|
||||||
|
|
||||||
|
// Check if border is set
|
||||||
|
fmt.Fprint(writer, ConditionString((!t.borders.Left && y == 0), SPACE, t.pColumn))
|
||||||
|
|
||||||
|
fmt.Fprintf(writer, SPACE)
|
||||||
|
|
||||||
|
str := columns[y][x]
|
||||||
|
|
||||||
|
if t.autoMergeCells {
|
||||||
|
//Store the full line to merge mutli-lines cells
|
||||||
|
fullLine := strings.Join(columns[y], " ")
|
||||||
|
if len(previousLine) > y && fullLine == previousLine[y] && fullLine != "" {
|
||||||
|
// If this cell is identical to the one above but not empty, we don't display the border and keep the cell empty.
|
||||||
|
displayCellBorder = append(displayCellBorder, false)
|
||||||
|
str = ""
|
||||||
|
} else {
|
||||||
|
// First line or different content, keep the content and print the cell border
|
||||||
|
displayCellBorder = append(displayCellBorder, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This would print alignment
|
||||||
|
// Default alignment would use multiple configuration
|
||||||
|
switch t.align {
|
||||||
|
case ALIGN_CENTER: //
|
||||||
|
fmt.Fprintf(writer, "%s", Pad(str, SPACE, t.cs[y]))
|
||||||
|
case ALIGN_RIGHT:
|
||||||
|
fmt.Fprintf(writer, "%s", PadLeft(str, SPACE, t.cs[y]))
|
||||||
|
case ALIGN_LEFT:
|
||||||
|
fmt.Fprintf(writer, "%s", PadRight(str, SPACE, t.cs[y]))
|
||||||
|
default:
|
||||||
|
if decimal.MatchString(strings.TrimSpace(str)) || percent.MatchString(strings.TrimSpace(str)) {
|
||||||
|
fmt.Fprintf(writer, "%s", PadLeft(str, SPACE, t.cs[y]))
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(writer, "%s", PadRight(str, SPACE, t.cs[y]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Fprintf(writer, SPACE)
|
||||||
|
}
|
||||||
|
// Check if border is set
|
||||||
|
// Replace with space if not set
|
||||||
|
fmt.Fprint(writer, ConditionString(t.borders.Left, t.pColumn, SPACE))
|
||||||
|
fmt.Fprint(writer, t.newLine)
|
||||||
|
}
|
||||||
|
|
||||||
|
//The new previous line is the current one
|
||||||
|
previousLine = make([]string, total)
|
||||||
|
for y := 0; y < total; y++ {
|
||||||
|
previousLine[y] = strings.Join(columns[y], " ") //Store the full line for multi-lines cells
|
||||||
|
}
|
||||||
|
//Returns the newly added line and wether or not a border should be displayed above.
|
||||||
|
return previousLine, displayCellBorder
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Table) parseDimension(str string, colKey, rowKey int) []string {
|
func (t *Table) parseDimension(str string, colKey, rowKey int) []string {
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ const defaultPenalty = 1e5
|
||||||
// Wrap wraps s into a paragraph of lines of length lim, with minimal
|
// Wrap wraps s into a paragraph of lines of length lim, with minimal
|
||||||
// raggedness.
|
// raggedness.
|
||||||
func WrapString(s string, lim int) ([]string, int) {
|
func WrapString(s string, lim int) ([]string, int) {
|
||||||
words := strings.Split(strings.Replace(strings.TrimSpace(s), nl, sp, -1), sp)
|
words := strings.Split(strings.Replace(s, nl, sp, -1), sp)
|
||||||
var lines []string
|
var lines []string
|
||||||
max := 0
|
max := 0
|
||||||
for _, v := range words {
|
for _, v := range words {
|
||||||
|
|
@ -96,7 +96,7 @@ func WrapWords(words []string, spc, lim, pen int) [][]string {
|
||||||
func getLines(s string) []string {
|
func getLines(s string) []string {
|
||||||
var lines []string
|
var lines []string
|
||||||
|
|
||||||
for _, line := range strings.Split(strings.TrimSpace(s), nl) {
|
for _, line := range strings.Split(s, nl) {
|
||||||
lines = append(lines, line)
|
lines = append(lines, line)
|
||||||
}
|
}
|
||||||
return lines
|
return lines
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue