public static void main(String[] args) throws Exception {
    OptionParser parser = new OptionParser();

    ArgumentAcceptingOptionSpec<String> propsFilepathOpt = parser.accepts("props", "Path to property file \n"
            + "All the argument can be provided through a property file with key=value at each line.\n"
            + "If an parameter both exist in the property file and the command line argument, the value in the command line argument would override the value in the property file")
        .withRequiredArg()
        .describedAs("props_file")
        .ofType(String.class);

    // database connection configuration
    ArgumentAcceptingOptionSpec<String> dbUsernameOpt = parser.accepts(DB_USERNAME, "Username to database")
        .withRequiredArg()
        .describedAs("db_username")
        .ofType(String.class);

    ArgumentAcceptingOptionSpec<String> dbDatacenterOpt = parser.accepts(DB_DATACENTER, "Datacenter of the database")
        .withRequiredArg()
        .describedAs("datacenter")
        .ofType(String.class);

    ArgumentAcceptingOptionSpec<String> dbNameOpt =
        parser.accepts(DB_NAME, "Database name").withRequiredArg().describedAs("db_name").ofType(String.class);

    ArgumentAcceptingOptionSpec<String> dbHostOpt =
        parser.accepts(DB_HOST, "Database host").withRequiredArg().describedAs("db_host").ofType(String.class);

    ArgumentAcceptingOptionSpec<Integer> parallelismOpt = parser.accepts(PARALLELISM, "Number of thread to execute sql statement")
        .withRequiredArg()
        .describedAs("parallelism")
        .ofType(Integer.class);

    ArgumentAcceptingOptionSpec<Integer> targetMRowsOpt = parser.accepts(TARGET_ROWS,
            "Number of rows to insert so the total database rows would reach this target." + "Notice that this target is in in millions. If the value is 1, this command would make sure database would have 1 million rows.")
        .withRequiredArg()
        .describedAs("target_rows")
        .ofType(Integer.class);

    OptionSpec<Void> includeListTestOpt = parser.accepts(INCLUDE_LIST, "Including list operation in the performance tests.");

    OptionSet options = parser.parse(args);
    Properties props = new Properties();
    if (options.has(propsFilepathOpt)) {
      String propFilepath = options.valueOf(propsFilepathOpt);
      System.out.println("Loading properties from file " + propFilepath);
      try (FileInputStream fis = new FileInputStream(propFilepath)) {
        props.load(fis);
      }
      System.out.println("Getting properties: " + props.stringPropertyNames());
    }
    if (options.has(dbUsernameOpt)) {
      props.setProperty(DB_USERNAME, options.valueOf(dbUsernameOpt));
    }
    if (options.has(dbDatacenterOpt)) {
      props.setProperty(DB_DATACENTER, options.valueOf(dbDatacenterOpt));
    }
    if (options.has(dbNameOpt)) {
      props.setProperty(DB_NAME, options.valueOf(dbNameOpt));
    }
    if (options.has(dbHostOpt)) {
      props.setProperty(DB_HOST, options.valueOf(dbHostOpt));
    }
    if (options.has(parallelismOpt)) {
      props.setProperty(PARALLELISM, String.valueOf(options.valueOf(parallelismOpt)));
    }
    if (options.has(targetMRowsOpt)) {
      props.setProperty(TARGET_ROWS, String.valueOf(options.valueOf(targetMRowsOpt)));
    }
    if (options.has(includeListTestOpt)) {
      props.setProperty(INCLUDE_LIST, "true");
    }
    if (!props.containsKey(INCLUDE_LIST)) {
      props.setProperty(INCLUDE_LIST, "false");
    }

    List<String> requiredArguments = Arrays.asList(DB_USERNAME, DB_DATACENTER, DB_NAME, DB_HOST, PARALLELISM, TARGET_ROWS, INCLUDE_LIST);
    for (String requiredArgument : requiredArguments) {
      if (!props.containsKey(requiredArgument)) {
        System.err.println("Missing " + requiredArgument + "! Please provide it through property file or the command line argument");
        parser.printHelpOn(System.err);
        System.exit(1);
      }
    }

    // Now ask for password if it's not provided in the propFile
    if (!props.containsKey(DB_PASSWORD)) {
      String password = ToolUtils.passwordInput("Please input database password for user " + props.getProperty(DB_USERNAME) + ": ");
      props.setProperty(DB_PASSWORD, password);
    }

    // Now create a mysql named blob data accessor
    Properties newProperties = new Properties();
    String dbUrl = "jdbc:mysql://" + props.getProperty(DB_HOST) + "/" + props.getProperty(DB_NAME) + "?serverTimezone=UTC";
    MySqlUtils.DbEndpoint dbEndpoint =
        new MySqlUtils.DbEndpoint(dbUrl, props.getProperty(DB_DATACENTER), true, props.getProperty(DB_USERNAME), props.getProperty(DB_PASSWORD));
    JSONArray jsonArray = new JSONArray();
    jsonArray.put(dbEndpoint.toJson());
    System.out.println("DB_INFO: " + jsonArray);
    newProperties.setProperty(MySqlNamedBlobDbConfig.DB_INFO, jsonArray.toString());
    newProperties.setProperty(MySqlNamedBlobDbConfig.DB_RELY_ON_NEW_TABLE, "true");
    newProperties.setProperty(MySqlNamedBlobDbConfig.LOCAL_POOL_SIZE, String.valueOf(2 * Integer.valueOf(props.getProperty(PARALLELISM))));
    newProperties.setProperty(ClusterMapConfig.CLUSTERMAP_DATACENTER_NAME, props.getProperty(DB_DATACENTER));

    int numThreads = Integer.valueOf(props.getProperty(PARALLELISM));
    ScheduledExecutorService executor = Utils.newScheduler(numThreads + 1, "workers-", false);
    MetricRegistry registry = new MetricRegistry();
    // Mock an account service
    AccountService accountService = createInMemoryAccountService();
    MySqlNamedBlobDbFactory factory =
        new MySqlNamedBlobDbFactory(new VerifiableProperties(newProperties), registry, accountService, SystemTime.getInstance());
    DataSource dataSource = factory.buildDataSource(dbEndpoint);
    NamedBlobDb namedBlobDb = factory.getNamedBlobDb();

    // First, fill the database with target number of rows
    long targetRows = Long.valueOf(props.getProperty(TARGET_ROWS)) * 1000000L;
    long existingRows = getNumberOfRowsInDatabase(dataSource);
    if (existingRows >= targetRows) {
      System.out.println("Existing number of rows: " + existingRows + ", more than target number of rows: " + targetRows
          + ", skip filling database rows");
    } else {
      fillDatabase(registry, namedBlobDb, executor, accountService, existingRows, targetRows, numThreads);
    }

    // Then starting doing performance tests
    // For performance testing, we are expecting each database query's average latency to be 2ms. So each worker can achieve
    // 500 QPS. We are trying to achieve 80K QPS for each query, which means we at least need 160 threads.
    boolean includeList = props.getProperty(INCLUDE_LIST).equals("true");
    runPerformanceTest(registry, namedBlobDb, executor, accountService, numThreads, includeList);

    Utils.shutDownExecutorService(executor, 10, TimeUnit.SECONDS);
    namedBlobDb.close();
  }
Tool to do performance testing on mysql database with named blob.

This tool would insert number of target rows specified in the argument or property file and then do performance testing
with several named blob operations.

To run this tool.
First compile ambry.jar with ./gradelw allJar
Then copy target/ambry.jar to a host that has access to mysql database if mysql database has host based permission checking.
Then run
 > java -cp "*" com.github.ambry.tools.perf.NamedBlobMysqlDatabasePerf
 >      --db_username database_username
 >      --db_datacenter datacenter_name
 >      --db_name database_name
 >      --db_host database_host
 >      --parallelism 10 // number of connections to create
 >      --target_row 10  // number of millions of rows to create before performance test
 >      --include_list false // true of false to include list operations in the performance test

 Or you can provide a property file to include all the arguments in the above command, for example:
 > cat named_blob.props
 db_username=database_username
 db_password=databse_pasword // yes, you can also provide database password in the prop file
 db_datacenter=datacenter_name
 db_name=database_name
 db_host=database_hostname
 parallelism=10
 target_rows=10
 include_list=false
 > java -cp "*" com.github.ambry.tools.perf.NamedBlobMysqlDatabasePerf --props named_blob.props
ambry-tools/src/main/java/com/github/ambry/tools/perf/NamedBlobMysqlDatabasePerf.java