@@ 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)
+}