1 files changed, 196 insertions(+), 0 deletions(-)

A => main.go
A => main.go +196 -0
@@ 0,0 1,196 @@ 
+// Run some code on play.golang.org and display the result
+package main
+
+import (
+	"bufio"
+	"encoding/csv"
+	"io"
+	"log"
+	"os"
+	"sync"
+	"time"
+
+	"github.com/jessevdk/go-flags"
+	"github.com/tebeka/selenium"
+)
+
+var logger *log.Logger
+
+func init() {
+	logger = log.New(os.Stderr, "", 0)
+}
+
+type Result struct {
+	URL      string
+	Duration time.Duration
+	Error    error
+}
+
+type Client struct {
+	Capabilities selenium.Capabilities
+	URL          string
+	driver       selenium.WebDriver
+	count        uint
+}
+
+func (c *Client) reset() {
+	if c.driver != nil {
+		c.driver.Quit()
+		c.driver = nil
+	}
+	c.count = 0
+}
+
+func (c *Client) Connect() selenium.WebDriver {
+	if c.driver == nil {
+		// Try to connect to the endpoint
+		for {
+			var err error
+
+			c.driver, err = selenium.NewRemote(c.Capabilities, c.URL)
+			if err != nil {
+				logger.Println("Error connection to WD", c.URL, err)
+				time.Sleep(30 * time.Second)
+			} else {
+				logger.Println("connected")
+				c.count = 0
+				break
+			}
+		}
+	}
+
+	return c.driver
+}
+
+//
+// Download url re-using the previous Selenium session if count < 100
+//
+func (c *Client) Get(url string) (duration time.Duration, err error) {
+	if c.driver != nil && c.count > 100 {
+		c.reset()
+	}
+
+	var driver = c.Connect()
+
+	var start = time.Now()
+	err = driver.Get(url)
+	duration = time.Since(start)
+
+	if err != nil {
+		// There was an error loading the URL, destroy the web driver just
+		// in case and report the error.
+		c.reset()
+	}
+
+	return
+}
+
+func worker(
+	caps selenium.Capabilities,
+	seleniumUrl string,
+	waitGroup *sync.WaitGroup,
+	input <-chan string,
+	output chan<- Result,
+) {
+	defer waitGroup.Done()
+
+	var client = Client{Capabilities: caps, URL: seleniumUrl}
+
+	for url := range input {
+		var duration, err = client.Get(url)
+		output <- Result{
+			URL:      url,
+			Duration: duration,
+			Error:    err,
+		}
+	}
+
+	client.reset()
+}
+
+//
+// Read input line by line and send it to the returned channel. Once there's
+// nothing left to read closes the channel.
+//
+func ScanLines(input io.Reader) <-chan string {
+	var c = make(chan string)
+
+	go func() {
+		var scanner = bufio.NewScanner(input)
+
+		for scanner.Scan() {
+			c <- scanner.Text()
+		}
+
+		if err := scanner.Err(); err != nil {
+			logger.Println("reading input:", err)
+		}
+		close(c)
+	}()
+
+	return c
+}
+
+//
+// All the element sent to the returned channel will be written to `output`
+//
+func CSVWriter(output io.Writer) chan<- Result {
+	var c = make(chan Result)
+
+	go func() {
+		w := csv.NewWriter(output)
+
+		for o := range c {
+			var e = ""
+			if o.Error != nil {
+				logger.Println("Error for", o.URL, ":", o.Error)
+				e = o.Error.Error()
+			}
+			w.Write([]string{o.URL, e, o.Duration.String()})
+			w.Flush()
+		}
+	}()
+
+	return c
+}
+
+func main() {
+	var args struct {
+		Workers uint   `long:"workers" default:"4" description:"Number of worker routines"`
+		Arg struct {
+			URL     string `description:"URL to the Selenium server"`
+		} `positional-args:"yes" required:"yes"`
+	}
+	parser := flags.NewParser(&args, flags.Default)
+	extra, err := parser.ParseArgs(os.Args[1:])
+
+    if err != nil {
+        // FIXME go-flags outputs the error in stderr in some cases, check it
+        // does it for all errors
+        os.Exit(1)
+    }
+    if len(extra) != 0 {
+        logger.Fatalln("Extra arguments:", extra)
+    }
+
+	caps := selenium.Capabilities{
+		"browserName": "chrome",
+		"platform":    "Linux",
+		"version":     "48.0",
+	}
+	var input = ScanLines(os.Stdin)
+	var output = CSVWriter(os.Stdout)
+	var workers = int(args.Workers)
+	var url = args.Arg.URL
+	var wg sync.WaitGroup
+
+	logger.Println("Using", workers, "workers with", url)
+
+	wg.Add(int(workers))
+	for i := 0; i < workers; i++ {
+		go worker(caps, url, &wg, input, output)
+	}
+
+	wg.Wait()
+	close(output)
+}