func copierGeneratorFactory(ctx context.Context) integration.GeneratorFactory {
	var tlsCfg *tls.Config
	if *skipHTTPSVerify {
		glog.Warning("Skipping HTTPS connection verification")
		tlsCfg = &tls.Config{InsecureSkipVerify: *skipHTTPSVerify}
	}
	httpClient := &http.Client{
		Timeout: 60 * time.Second,
		Transport: &http.Transport{
			TLSHandshakeTimeout:   30 * time.Second,
			ResponseHeaderTimeout: 30 * time.Second,
			MaxIdleConnsPerHost:   10,
			DisableKeepAlives:     false,
			MaxIdleConns:          100,
			IdleConnTimeout:       90 * time.Second,
			ExpectContinueTimeout: 1 * time.Second,
			TLSClientConfig:       tlsCfg,
		},
	}
	uri := *srcLogURI
	var opts jsonclient.Options
	if *srcPubKey != "" {
		pubkey, err := ioutil.ReadFile(*srcPubKey)
		if err != nil {
			glog.Exit(err)
		}
		opts.PublicKey = string(pubkey)
	}
	if len(*srcLogName) > 0 {
		llData, err := x509util.ReadFileOrURL(*logList, httpClient)
		if err != nil {
			glog.Exitf("Failed to read log list: %v", err)
		}
		ll, err := loglist.NewFromJSON(llData)
		if err != nil {
			glog.Exitf("Failed to build log list: %v", err)
		}
		logs := ll.FindLogByName(*srcLogName)
		if len(logs) == 0 {
			glog.Exitf("No log with name like %q found in loglist %q", *srcLogName, *logList)
		}
		if len(logs) > 1 {
			logNames := make([]string, len(logs))
			for i, log := range logs {
				logNames[i] = fmt.Sprintf("%q", log.Description)
			}
			glog.Exitf("Multiple logs with name like %q found in loglist: %s", *srcLogName, strings.Join(logNames, ","))
		}
		uri = "https://" + logs[0].URL
		if opts.PublicKey == "" {
			opts.PublicKeyDER = logs[0].Key
		}
	}
	logClient, err := client.New(uri, httpClient, opts)
	if err != nil {
		glog.Exitf("Failed to create client for %q: %v", uri, err)
	}
	glog.Infof("Testing with certs copied from log at %s starting at index %d", uri, *startIndex)
	genOpts := integration.CopyChainOptions{
		StartIndex:    *startIndex,
		BufSize:       *chainBufSize,
		BatchSize:     *batchSize,
		ParallelFetch: *parallelFetch,
	}
	return func(c *configpb.LogConfig) (integration.ChainGenerator, error) {
		return integration.NewCopyChainGeneratorFromOpts(ctx, logClient, c, genOpts)
	}
}
copierGeneratorFactory returns a function that creates per-Log ChainGenerator instances
that are based off a source CT log specified by the command line arguments.
package main
import (
	"bytes"
	"compress/gzip"
	"context"
	"crypto/tls"
	"encoding/base64"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"math/rand"
	"net/http"
	"os"
	"strings"
	"sync"
	"time"
	"github.com/golang/glog"
	"github.com/google/certificate-transparency-go/client"
	"github.com/google/certificate-transparency-go/fixchain/ratelimiter"
	"github.com/google/certificate-transparency-go/jsonclient"
	"github.com/google/certificate-transparency-go/loglist"
	"github.com/google/certificate-transparency-go/trillian/ctfe"
	"github.com/google/certificate-transparency-go/trillian/ctfe/configpb"
	"github.com/google/certificate-transparency-go/trillian/integration"
	"github.com/google/certificate-transparency-go/x509util"
	"github.com/google/trillian/monitoring"
	"github.com/google/trillian/monitoring/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
	banner      = flag.Bool("banner", true, "Display intro")
	httpServers = flag.String("ct_http_servers", "localhost:8092", "Comma-separated list of (assumed interchangeable) servers, each as address:port")
	testDir      = flag.String("testdata_dir", "testdata", "Name of directory with test data")
	leafNotAfter = flag.String("leaf_not_after", "", "Not-After date to use for leaf certs, RFC3339/ISO-8601 format (e.g. 2017-11-26T12:29:19Z)")
	srcLogURI       = flag.String("src_log_uri", "", "URI for source log to copy certificates from")
	srcPubKey       = flag.String("src_pub_key", "", "Name of file containing source log's public key")
	srcLogName      = flag.String("src_log_name", "", "Name of source log to copy certificate from  (from --log_list)")
	logList         = flag.String("log_list", loglist.AllLogListURL, "Location of master log list (URL or filename)")
	skipHTTPSVerify = flag.Bool("skip_https_verify", false, "Skip verification of HTTPS transport connection to source log")
	chainBufSize    = flag.Int("buffered_chains", 100, "Number of buffered certificate chains to hold")
	startIndex      = flag.Int64("start_index", 0, "Index of start point in source log to scan from (-1 for random start index)")
	batchSize       = flag.Int("batch_size", 500, "Max number of entries to request at per call to get-entries")
	parallelFetch   = flag.Int("parallel_fetch", 2, "Number of concurrent GetEntries fetches")
	metricsEndpoint     = flag.String("metrics_endpoint", "", "Endpoint for serving metrics; if left empty, metrics will not be exposed")
	seed                = flag.Int64("seed", -1, "Seed for random number generation")
	logConfig           = flag.String("log_config", "", "File holding log config in JSON")
	mmd                 = flag.Duration("mmd", 2*time.Minute, "Default MMD for logs")
	operations          = flag.Uint64("operations", ^uint64(0), "Number of operations to perform")
	minGetEntries       = flag.Int("min_get_entries", 1, "Minimum get-entries request size")
	maxGetEntries       = flag.Int("max_get_entries", 500, "Maximum get-entries request size")
	oversizedGetEntries = flag.Bool("oversized_get_entries", false, "Whether get-entries requests can go beyond log size")
	maxParallelChains   = flag.Int("max_parallel_chains", 2, "Maximum number of chains to add in parallel (will always add at least 1 chain)")
	limit               = flag.Int("rate_limit", 0, "Maximum rate of requests to an individual log; 0 for no rate limit")
	ignoreErrors        = flag.Bool("ignore_errors", false, "Whether to ignore errors and retry the operation")
	maxRetry            = flag.Duration("max_retry", 60*time.Second, "How long to keep retrying when ignore_errors is set")
	reqDeadline         = flag.Duration("req_deadline", 10*time.Second, "Deadline to set on individual requests")
)
var (
	addChainBias             = flag.Int("add_chain", 20, "Bias for add-chain operations")
	addPreChainBias          = flag.Int("add_pre_chain", 20, "Bias for add-pre-chain operations")
	getSTHBias               = flag.Int("get_sth", 2, "Bias for get-sth operations")
	getSTHConsistencyBias    = flag.Int("get_sth_consistency", 2, "Bias for get-sth-consistency operations")
	getProofByHashBias       = flag.Int("get_proof_by_hash", 2, "Bias for get-proof-by-hash operations")
	getEntriesBias           = flag.Int("get_entries", 2, "Bias for get-entries operations")
	getRootsBias             = flag.Int("get_roots", 1, "Bias for get-roots operations")
	getEntryAndProofBias     = flag.Int("get_entry_and_proof", 0, "Bias for get-entry-and-proof operations")
	invalidChance            = flag.Int("invalid_chance", 10, "Chance of generating an invalid operation, as the N in 1-in-N (0 for never)")
	dupeChance               = flag.Int("duplicate_chance", 10, "Chance of generating a duplicate submission, as the N in 1-in-N (0 for never)")
	strictSTHConsistencySize = flag.Bool("strict_sth_consistency_size", true, "If set to true, hammer will use only tree sizes from STHs it's seen for consistency proofs, otherwise it'll choose a random size for the smaller tree")
)
func newLimiter(rate int) integration.Limiter {
	if rate <= 0 {
		return nil
	}
	return ratelimiter.NewLimiter(rate)
}