benchstat: avoid reading whole inputs

For very large benchmark inputs it makes sense to avoid
reading the entire file into memory.

Change-Id: Ib7f1b13c1b7b4659323a912aee1c4a5a3d6a49a5
Reviewed-on: https://go-review.googlesource.com/c/159057
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Austin Clements <austin@google.com>
Reviewed-by: Ian Rogers <irogers@google.com>
diff --git a/benchstat/data.go b/benchstat/data.go
index 3c3a08b..6332950 100644
--- a/benchstat/data.go
+++ b/benchstat/data.go
@@ -7,6 +7,7 @@
 import (
 	"bytes"
 	"fmt"
+	"io"
 	"strconv"
 	"strings"
 
@@ -159,19 +160,29 @@
 
 // AddConfig adds the benchmark results in the formatted data
 // to the named configuration.
+// If the input is large, AddFile should be preferred,
+// since it avoids the need to read a copy of the entire raw input
+// into memory.
 func (c *Collection) AddConfig(config string, data []byte) {
-	c.Configs = append(c.Configs, config)
-	key := Key{Config: config}
-	br := benchfmt.NewReader(bytes.NewReader(data))
-	for br.Next() {
-		c.addResult(key, br.Result())
-	}
-	if err := br.Err(); err != nil {
+	err := c.AddFile(config, bytes.NewReader(data))
+	if err != nil {
 		// bytes.Reader never returns errors
 		panic(err)
 	}
 }
 
+// AddFile adds the benchmark results in the formatted data
+// (read from the reader r) to the named configuration.
+func (c *Collection) AddFile(config string, r io.Reader) error {
+	c.Configs = append(c.Configs, config)
+	key := Key{Config: config}
+	br := benchfmt.NewReader(r)
+	for br.Next() {
+		c.addResult(key, br.Result())
+	}
+	return br.Err()
+}
+
 // AddResults adds the benchmark results to the named configuration.
 func (c *Collection) AddResults(config string, results []*benchfmt.Result) {
 	c.Configs = append(c.Configs, config)
diff --git a/cmd/benchstat/main.go b/cmd/benchstat/main.go
index 7d87625..4b27d7f 100644
--- a/cmd/benchstat/main.go
+++ b/cmd/benchstat/main.go
@@ -98,7 +98,6 @@
 	"bytes"
 	"flag"
 	"fmt"
-	"io/ioutil"
 	"log"
 	"os"
 	"strings"
@@ -172,11 +171,14 @@
 		c.Order = order
 	}
 	for _, file := range flag.Args() {
-		data, err := ioutil.ReadFile(file)
+		f, err := os.Open(file)
 		if err != nil {
 			log.Fatal(err)
 		}
-		c.AddConfig(file, data)
+		if err := c.AddFile(file, f); err != nil {
+			log.Fatal(err)
+		}
+		f.Close()
 	}
 
 	tables := c.Tables()