This commit is contained in:
Hadis-Fard 2017-07-20 17:09:58 -07:00
commit d9f6e3c6d7
31 changed files with 2264 additions and 80 deletions

View file

@ -0,0 +1,40 @@
## Setup Environment on a clean machine
### Windows
Install Visual Studio 2015 before running the following commands. Make sure C++ tools are enabled.
Run `cmd` as administrator.
powershell
Set-ExecutionPolicy Unrestricted
.\setup_env_windows.ps1 <PHP_VERSION - 7.x.x> <PHP_THREAD - ts or nts> <Absolute path to driver source folder> <ARCH - x86 or x64>
### Ubuntu 16
sudo env "PATH=$PATH" bash setup_env_unix.sh Ubuntu16 <PHP_VERSION - 7.x.x> <PHP_THREAD - ts or nts> <Absolute path to driver source folder>
### RedHat 7
sudo env "PATH=$PATH" bash setup_env_unix.sh RedHat7 <PHP_VERSION - 7.x.x> <PHP_THREAD - ts or nts> <Absolute path to driver source folder>
### Sierra
`brew` cannot be run with `sudo` on Sierra. Either enable passwordless `sudo` on the machine or enter the password when prompted.
bash setup_env_unix.sh Sierra <PHP_VERSION - 7.x.x> <PHP_THREAD - ts or nts> <Absolute path to driver source folder>
## Run benchmarks
PHPBench is used to run the benchmarks. Visit http://phpbench.readthedocs.io/en/latest/introduction.html to have an idea how the tool works.
### 1. Modify lib/connect.php with the test database credentials
### 2. Execute run-perf_tests.py.
### Windows
py.exe run-perf_tests.py -platform <PLATFORM> -iterations <ITERATIONS> -iterations-large <ITERATIONS_LARGE> -result-server <RESULT_SERVER> -result-db <RESULT_DB> -result-uid <RESULT_UID> -result-pwd <RESULT_PWD
### Linux and Mac
On Linux and Mac, the script must be executed with `sudo python3` because to enable pooling it needs to modify odbcinst.ini system file. As an improvement, the location of the odbcinst.ini file can be changed so that, sudo is not requiered.
python3 run-perf_tests.py -platform <PLATFORM> -iterations <ITERATIONS> -iterations-large <ITERATIONS_LARGE> -result-server <RESULT_SERVER> -result-db <RESULT_DB> -result-uid <RESULT_UID> -result-pwd <RESULT_PWD>
`-platform` - The platform that the tests are ran on. Must be one of the following: Windows10, WindowsServer2016 WindowsServer2012 Ubuntu16 RedHat7 Sierra
`-iterations` - The number of iterations for regular tests.
`-iterations-large` - The number of iterations for the tests that fetch large data. Usually set to 1.
`-result-server` - The server of result database. It is assumed that, the result database already setup before running the tests.
`-result-db` - Database name. With the current result database setup files, this should be set to `TestResults`
`-result-uid` - Result database username
`-result-pwd` Result database password

View file

@ -0,0 +1,31 @@
<?php
use PDOSqlsrvPerfTest\PDOSqlsrvUtil;
/**
* @BeforeMethods({"connect", "setTableName" })
* @AfterMethods({ "disconnect"})
*/
class PDOFetchLargeBench{
private $conn;
private $tableName;
public function setTableName(){
//Assumes the table is already populated with data
$this->tableName = "LargeDB.dbo.datatypes";
}
public function connect(){
$this->conn = PDOSqlsrvUtil::connect();
}
/*
* Each iteration calls prepare, execute and fetch APIs to fetch the already populdated data
*/
public function benchFetchWithPrepare(){
PDOSqlsrvUtil::fetchWithPrepare( $this->conn, $this->tableName );
}
public function disconnect(){
PDOSqlsrvUtil::disconnect( $this->conn );
}
}

View file

@ -0,0 +1,14 @@
<?php
use PDOSqlsrvPerfTest\PDOSqlsrvUtil;
class PDOConnectionBench{
/*
* Opens a connection and closes it immediately
*/
public function benchConnectAndDisconnect(){
$conn = PDOSqlsrvUtil::connect();
PDOSqlsrvUtil::disconnect( $conn );
}
}
?>

View file

@ -0,0 +1,30 @@
<?php
use PDOSqlsrvPerfTest\PDOSqlsrvUtil;
/**
* @BeforeMethods({"connect"})
* @AfterMethods({"disconnect"})
*/
class PDOCreateDbTableProcBench{
private $conn;
public function connect(){
$this->conn = PDOSqlsrvUtil::connect();
}
/*
* Each iteration creates a database, a table and a stored procedure in that database and drops the database at the end.
* Note that, execDirect function are used to execute all the queries.
*/
public function benchCreateDbTableProc(){
$randomNum = rand();
$databaseName = "test_db_$randomNum";
$tableName = "test_table_$randomNum";
$procName = "test_proc_$randomNum";
PDOSqlsrvUtil::createDbTableProc( $this->conn, $databaseName, $tableName, $procName );
PDOSqlsrvUtil::dropDatabase( $this->conn, $databaseName );
}
public function disconnect(){
PDOSqlsrvUtil::disconnect( $this->conn );
}
}

View file

@ -0,0 +1,53 @@
<?php
use PDOSqlsrvPerfTest\PDOSqlsrvUtil;
/**
* @BeforeMethods({"connect", "setTableName", "createTable", "generateInsertValues", "insertWithPrepare"})
* @AfterMethods({ "dropTable","disconnect"})
*/
class PDODeleteBench{
private $conn;
private $tableName;
private $insertValues;
public function setTableName(){
$this->tableName = "datatypes_".rand();
}
public function connect(){
$this->conn = PDOSqlsrvUtil::connect();
}
public function createTable(){
PDOSqlsrvUtil::createCRUDTable( $this->conn, $this->tableName );
}
public function generateInsertValues(){
$this->insertValues = PDOSqlsrvUtil::generateInsertValues();
}
public function insertWithPrepare(){
for( $i=0; $i<1000; $i++ ){
PDOSqlsrvUtil::insertWithPrepare( $this->conn, $this->tableName, $this->insertValues );
}
}
/**
* Each iteration inserts 1000 rows into the table, benchDelete deletes top row from the table 1000 times.
* Note that, every delete calls prepare and execute APIs.
*/
public function benchDelete(){
for( $i=0; $i<1000; $i++ ){
PDOSqlsrvUtil::deleteWithPrepare( $this->conn, $this->tableName );
}
}
public function dropTable(){
PDOSqlsrvUtil::dropTable( $this->conn, $this->tableName );
}
public function disconnect(){
PDOSqlsrvUtil::disconnect( $this->conn );
}
}

View file

@ -0,0 +1,50 @@
<?php
use PDOSqlsrvPerfTest\PDOSqlsrvUtil;
/**
* @BeforeMethods({"connect", "setTableName", "createTable", "generateInsertValues", "insertWithPrepare"})
* @AfterMethods({"dropTable", "disconnect"})
*/
class PDOFetchBench{
private $conn;
private $tableName;
private $insertValues;
public function setTableName(){
$this->tableName = "datatypes_".rand();
}
public function connect(){
$this->conn = PDOSqlsrvUtil::connect();
}
public function createTable(){
PDOSqlsrvUtil::createCRUDTable( $this->conn, $this->tableName );
}
public function generateInsertValues(){
$this->insertValues = PDOSqlsrvUtil::generateInsertValues();
}
public function insertWithPrepare(){
PDOSqlsrvUtil::insertWithPrepare( $this->conn, $this->tableName, $this->insertValues );
}
/**
* Each iteration inserts a row into the table, benchFetchWithPrepare() fetches that row 1000 times.
* Note that, every fetch calls prepare, execute and fetch APIs.
*/
public function benchFetchWithPrepare(){
for( $i=0; $i<1000; $i++){
PDOSqlsrvUtil::fetchWithPrepare( $this->conn, $this->tableName );
}
}
public function dropTable(){
PDOSqlsrvUtil::dropTable( $this->conn, $this->tableName );
}
public function disconnect(){
PDOSqlsrvUtil::disconnect( $this->conn );
}
}

View file

@ -0,0 +1,45 @@
<?php
use PDOSqlsrvPerfTest\PDOSqlsrvUtil;
/**
* @BeforeMethods({"connect", "setTableName", "createTable", "generateInsertValues"})
* @AfterMethods({ "dropTable", "disconnect"})
*/
class PDOInsertBench{
private $conn;
private $tableName;
private $insertValues;
public function setTableName(){
$this->tableName = "datatypes_".rand();
}
public function connect(){
$this->conn = PDOSqlsrvUtil::connect();
}
public function createTable(){
PDOSqlsrvUtil::createCRUDTable( $this->conn, $this->tableName );
}
public function generateInsertValues(){
$this->insertValues = PDOSqlsrvUtil::generateInsertValues();
}
/**
* Each iteration inserts 1000 rows into the table.
* Note that, every insertion calls prepare, bindParam and execute APIs.
*/
public function benchInsertWithPrepare(){
for ( $i=0; $i<1000; $i++){
PDOSqlsrvUtil::insertWithPrepare( $this->conn, $this->tableName, $this->insertValues );
}
}
public function dropTable(){
PDOSqlsrvUtil::dropTable( $this->conn, $this->tableName );
}
public function disconnect(){
PDOSqlsrvUtil::disconnect( $this->conn );
}
}

View file

@ -0,0 +1,25 @@
<?php
use PDOSqlsrvPerfTest\PDOSqlsrvUtil;
/**
* @BeforeMethods({"connect"})
* @AfterMethods({"disconnect"})
*/
class PDOSelectVersionBench{
private $conn;
public function connect(){
$this->conn = PDOSqlsrvUtil::connect();
}
/*
* Each iteration calls execDirect API to fetch @@Version
*/
public function benchSelectVersion(){
$version = PDOSqlsrvUtil::selectVersion( $this->conn );
}
public function disconnect(){
PDOSqlsrvUtil::disconnect( $this->conn );
}
}

View file

@ -0,0 +1,61 @@
<?php
use PDOSqlsrvPerfTest\PDOSqlsrvUtil;
/**
* @BeforeMethods({"connect", "setTableName", "createTable", "generateInsertValues", "insertWithPrepare", "generateUpdateValues", "generateUpdateParams"})
* @AfterMethods({"dropTable", "disconnect"})
*/
class PDOUpdateBench{
private $conn;
private $tableName;
private $insertValues;
private $updateValues;
private $updateParams;
public function setTableName(){
$this->tableName = "datatypes_".rand();
}
public function connect(){
$this->conn = PDOSqlsrvUtil::connect();
}
public function createTable(){
PDOSqlsrvUtil::createCRUDTable( $this->conn, $this->tableName );
}
public function generateInsertValues(){
$this->insertValues = PDOSqlsrvUtil::generateInsertValues();
}
public function insertWithPrepare(){
PDOSqlsrvUtil::insertWithPrepare( $this->conn, $this->tableName, $this->insertValues );
}
public function generateUpdateValues(){
$this->updateValues = PDOSqlsrvUtil::generateUpdateValues();
}
public function generateUpdateParams(){
$this->updateParams = PDOSqlsrvUtil::generateUpdateParams();
}
/**
* Each iteration inserts a row into the table, updateWithPrepare() updates that row 1000 times.
* Note that, every update calls prepare, bindParam and execute APIs.
*/
public function benchUpdateWithPrepare(){
for( $i=0; $i<1000; $i++ ){
$stmt = PDOSqlsrvUtil::updateWithPrepare( $this->conn, $this->tableName, $this->updateValues, $this->updateParams );
}
}
public function dropTable(){
PDOSqlsrvUtil::dropTable( $this->conn, $this->tableName );
}
public function disconnect(){
PDOSqlsrvUtil::disconnect( $this->conn );
}
}

View file

@ -0,0 +1,31 @@
<?php
use SqlsrvPerfTest\SqlsrvUtil;
/**
* @BeforeMethods({"connect", "setTableName" })
* @AfterMethods({ "disconnect"})
*/
class SqlsrvFetchLargeBench{
private $conn;
private $tableName;
public function setTableName(){
//Assumes the table is already populated with data
$this->tableName = "LargeDB.dbo.datatypes";
}
public function connect(){
$this->conn = SqlsrvUtil::connect();
}
/*
* Each iteration calls prepare, execute and fetch APIs to fetch the already populdated data
*/
public function benchFetchWithPrepare(){
SqlsrvUtil::fetchWithPrepare( $this->conn, $this->tableName );
}
public function disconnect(){
SqlsrvUtil::disconnect( $this->conn );
}
}

View file

@ -0,0 +1,13 @@
<?php
use SqlsrvPerfTest\SqlsrvUtil;
class SqlsrvConnectionBench{
/*
* Opens a connection and closes it immediately
*/
public function benchConnectAndDisconnect(){
$conn = SqlsrvUtil::connect();
SqlsrvUtil::disconnect( $conn );
}
}

View file

@ -0,0 +1,31 @@
<?php
use SqlsrvPerfTest\SqlsrvUtil;
/**
* @BeforeMethods({"connect"})
* @AfterMethods({"disconnect"})
*/
class SqlsrvCreateDbTableProcBench{
private $conn;
public function connect(){
$this->conn = SqlsrvUtil::connect();
}
/*
* Each iteration creates a database, a table and a stored procedure in that database and drops the database at the end.
* Note that, ODBC SQLExecDirect function are used to execute all the queries.
*/
public function benchCreateDbTableProc(){
$randomNum = rand();
$databaseName = "test_db_$randomNum";
$tableName = "test_table_$randomNum";
$procName = "test_proc_$randomNum";
SqlsrvUtil::createDbTableProc( $this->conn, $databaseName, $tableName, $procName );
SqlsrvUtil::dropDatabase( $this->conn, $databaseName );
}
public function disconnect(){
SqlsrvUtil::disconnect( $this->conn );
}
}

View file

@ -0,0 +1,53 @@
<?php
use SqlsrvPerfTest\SqlsrvUtil;
/**
* @BeforeMethods({"connect", "setTableName", "createTable", "generateInsertValues", "insertWithPrepare"})
* @AfterMethods({ "dropTable", "disconnect"})
*/
class SqlsrvDeleteBench{
private $conn;
private $tableName;
private $insertValues;
public function setTableName(){
$this->tableName = "datatypes_".rand();
}
public function connect(){
$this->conn = SqlsrvUtil::connect();
}
public function createTable(){
SqlsrvUtil::createCRUDTable( $this->conn, $this->tableName );
}
public function generateInsertValues(){
$this->insertValues = SqlsrvUtil::generateInsertValues();
}
public function insertWithPrepare(){
for ( $i=0; $i<1000; $i++ ){
SqlsrvUtil::insertWithPrepare( $this->conn, $this->tableName, $this->insertValues );
}
}
/**
* Each iteration inserts 1000 rows into the table, benchDelete deletes top row from the table 1000 times.
* Note that, every delete calls prepare and execute APIs.
*/
public function benchDelete(){
for( $i=0; $i<1000; $i++ ){
SqlsrvUtil::delete( $this->conn, $this->tableName );
}
}
public function dropTable(){
SqlsrvUtil::dropTable( $this->conn, $this->tableName );
}
public function disconnect(){
SqlsrvUtil::disconnect( $this->conn );
}
}

View file

@ -0,0 +1,53 @@
<?php
use SqlsrvPerfTest\SqlsrvUtil;
/**
* @BeforeMethods({"connect", "setTableName", "createTable", "generateInsertValues", "insertWithPrepare"})
* @AfterMethods({ "dropTable", "disconnect"})
*/
class SqlsrvFetchBench{
private $conn;
private $tableName;
private $insertValues;
public function setTableName(){
$this->tableName = "datatypes_".rand();
}
public function connect(){
$this->conn = SqlsrvUtil::connect();
}
public function createTable(){
SqlsrvUtil::createCRUDTable( $this->conn, $this->tableName );
}
public function generateInsertValues(){
$this->insertValues = SqlsrvUtil::generateInsertValues();
}
public function insertWithPrepare(){
SqlsrvUtil::insertWithPrepare( $this->conn, $this->tableName, $this->insertValues );
}
/**
* Each iteration inserts a row into the table, benchFetchWithPrepare() fetches that row 1000 times.
* Note that, every fetch calls prepare, execute and fetch APIs.
*/
public function benchFetchWithPrepare(){
for( $i=0; $i<1000; $i++){
SqlsrvUtil::fetchWithPrepare( $this->conn, $this->tableName );
}
}
public function dropTable(){
SqlsrvUtil::dropTable( $this->conn, $this->tableName );
}
public function disconnect(){
SqlsrvUtil::disconnect( $this->conn );
}
}

View file

@ -0,0 +1,46 @@
<?php
use SqlsrvPerfTest\SqlsrvUtil;
/**
* @BeforeMethods({"connect", "setTableName", "createTable", "generateInsertValues"})
* @AfterMethods({"dropTable","disconnect"})
*/
class SqlsrvInsertBench{
private $conn;
private $tableName;
private $insertValues;
public function setTableName(){
$this->tableName = "datatypes_".rand();
}
public function connect(){
$this->conn = SqlsrvUtil::connect();
}
public function createTable(){
SqlsrvUtil::createCRUDTable( $this->conn, $this->tableName );
}
public function generateInsertValues(){
$this->insertValues = SqlsrvUtil::generateInsertValues();
}
/**
* Each iteration inserts 1000 rows into the table.
* Note that, every insertion calls prepare, bindParam and execute APIs.
*/
public function benchInsertWithPrepare(){
for( $i=0; $i<1000; $i++ ){
SqlsrvUtil::insertWithPrepare( $this->conn, $this->tableName, $this->insertValues );
}
}
public function dropTable(){
SqlsrvUtil::dropTable( $this->conn, $this->tableName );
}
public function disconnect(){
SqlsrvUtil::disconnect( $this->conn );
}
}

View file

@ -0,0 +1,25 @@
<?php
use SqlsrvPerfTest\SqlsrvUtil;
/**
* @BeforeMethods({"connect"})
* @AfterMethods({"disconnect"})
*/
class SqlsrvSelectVersionBench{
private $conn;
public function connect(){
$this->conn = SqlsrvUtil::connect();
}
/*
* Each iteration calls execDirect API to fetch @@Version
*/
public function benchSelectVersion(){
$version = SqlsrvUtil::selectVersion( $this->conn );
}
public function disconnect(){
SqlsrvUtil::disconnect( $this->conn );
}
}

View file

@ -0,0 +1,61 @@
<?php
use SqlsrvPerfTest\SqlsrvUtil;
/**
* @BeforeMethods({"connect", "setTableName", "createTable", "generateInsertValues", "insertWithPrepare", "generateUpdateValues", "generateUpdateParams"})
* @AfterMethods({ "dropTable", "disconnect"})
*/
class SqlsrvUpdateBench{
private $conn;
private $tableName;
private $insertValues;
private $updateValues;
private $updateParams;
public function setTableName(){
$this->tableName = "datatypes_".rand();
}
public function connect(){
$this->conn = SqlsrvUtil::connect();
}
public function createTable(){
SqlsrvUtil::createCRUDTable( $this->conn, $this->tableName );
}
public function generateInsertValues(){
$this->insertValues = SqlsrvUtil::generateInsertValues();
}
public function insertWithPrepare(){
SqlsrvUtil::insertWithPrepare( $this->conn, $this->tableName, $this->insertValues );
}
public function generateUpdateValues(){
$this->updateValues = SqlsrvUtil::generateUpdateValues();
}
public function generateUpdateParams(){
$this->updateParams = SqlsrvUtil::generateUpdateParams();
}
/**
* Each iteration inserts a row into the table, updateWithPrepare() updates that row 1000 times.
* Note that, every update calls prepare, bindParam and execute APIs.
*/
public function benchUpdateWithPrepare(){
for( $i=0; $i<1000; $i++ ){
$stmt = SqlsrvUtil::updateWithPrepare( $this->conn, $this->tableName, $this->updateValues, $this->updateParams );
}
}
public function dropTable(){
SqlsrvUtil::dropTable( $this->conn, $this->tableName );
}
public function disconnect(){
SqlsrvUtil::disconnect( $this->conn );
}
}

View file

@ -0,0 +1,3 @@
set options=%2
set options=%options:"=%
C:\php-sdk\bin\phpsdk_setvars.bat && "c:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" %1 && .\buildconf --force && .\configure %options% && nmake && nmake install

View file

@ -0,0 +1,12 @@
{
"name": "Benchmark - PHP Drivers for SQL Server",
"require-dev": {
"phpbench/phpbench": "^1.0@dev"
},
"autoload": {
"psr-4": {
"SqlsrvPerfTest\\": "lib",
"PDOSqlsrvPerfTest\\": "lib"
}
}
}

View file

@ -0,0 +1,228 @@
<?php
namespace PDOSqlsrvPerfTest;
use PDO;
class PDOSqlsrvUtil{
public static function connect(){
require dirname(__FILE__).DIRECTORY_SEPARATOR.'connect.php';
try{
$conn = new PDO( "sqlsrv:Server=$server; Database=$database; ConnectionPooling=$pooling; MultipleActiveResultSets=$mars" , $uid, $pwd );
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return $conn;
}
catch( PDOException $e ){
var_dump( $e );
exit;
}
}
public static function disconnect( $conn ){
$conn = null;
}
public static function selectVersion( $conn ){
$sql = "SELECT @@Version";
$stmt = self::query( $conn, $sql );
return self::fetch( $stmt );
}
public static function createDbTableProc( $conn, $databaseName, $tableName, $procName ){
$tableParams = "id INTEGER, name VARCHAR(32), value INTEGER, start_date DATE, time_added TIMESTAMP, set_time TIME(7)";
$procParams = "@id INTEGER, @name VARCHAR(32)";
$procTextBase = "SET NOCOUNT ON; SELECT id, name, value FROM $databaseName.$tableName WHERE id = @id AND name = @name";
self::createDatabase( $conn, $databaseName );
self::useDatabase( $conn, $databaseName );
self::createTable( $conn, $tableName, $tableParams );
self::createStoredProc( $conn, $procName, $procParams, $procTextBase );
}
public static function generateInsertValues(){
$vcharVal = "test string";
$nvcharVal = "wstring";
$intVal = 3;
$dateTimeVal = '2016-10-31 01:39:39.7341976';
$charVal = "fixedstr";
$ncharVal = "fixed w string";
$realVal = 14.2;
$binVal = 0x0123456789ABCDE;
$vbinVal = 13;
$dateTimeOffsetVal = '7032-12-17 02:32:18.5210310+00:00';
$values = array( $vcharVal, $nvcharVal, $intVal, $dateTimeVal, $charVal, $ncharVal, $realVal, $binVal, $vbinVal, $dateTimeOffsetVal );
return $values;
}
public static function generateUpdateValues(){
$vcharVal = "test string updated";
$nvcharVal = "wstring updated";
$intVal = 5;
$dateTimeVal = '2005-10-31 01:20:39.7341976';
$charVal = "fixedstr updated";
$ncharVal = "fixed w string updated";
$realVal = 19.2;
$binVal = 0x01345789ABCDE;
$vbinVal = 18;
$dateTimeOffsetVal = '1032-12-17 02:42:18.5210310+00:00';
$updatedValues = array( $vcharVal, $nvcharVal, $intVal, $dateTimeVal, $charVal, $ncharVal, $realVal, $binVal, $vbinVal, $dateTimeOffsetVal );
return $updatedValues;
}
public static function generateUpdateParams(){
$fieldNames = array(
"vstring",
"nvstring",
"num",
"dttwo",
"string",
"nstring",
"real",
"bin",
"vbin",
"dtoffset");
$params = "";
foreach( $fieldNames as $fieldName ){
$params = $params.$fieldName."=?,";
}
$params = rtrim($params,", ");
return $params;
}
public static function insertWithPrepare( $conn, $tableName, $values ){
$sql = "INSERT INTO $tableName VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
$stmt = self::prepare( $conn, $sql );
self::bindParams( $stmt, $values );
self::execute( $stmt );
}
public static function fetchWithPrepare( $conn, $tableName ){
$sql = "SELECT * FROM $tableName";
$stmt = self::prepare( $conn, $sql );
self::execute( $stmt );
while ( $row = self::fetch( $stmt )){}
}
public static function deleteWithPrepare( $conn, $tableName ){
$sql = "DELETE TOP (1) FROM $tableName";
$stmt = self::prepare( $conn, $sql );
self::execute( $stmt );
}
public static function updateWithPrepare( $conn, $tableName, $updateValues, $params ){
$sql = "UPDATE $tableName SET ".$params;
$stmt = self::prepare( $conn, $sql );
self::bindParams( $stmt, $updateValues );
self::execute( $stmt );
}
private function bindParams( $stmt, $values ){
//This functionn assumes the fields are from createCRUDTable()
self::bindParam( $stmt, 1, $values[0], PDO::PARAM_STR);
self::bindParam( $stmt, 2, $values[1], PDO::PARAM_STR);
self::bindParam( $stmt, 3, $values[2], PDO::PARAM_INT);
self::bindParam( $stmt, 4, $values[3], PDO::PARAM_STR);
self::bindParam( $stmt, 5, $values[4], PDO::PARAM_STR);
self::bindParam( $stmt, 6, $values[5], PDO::PARAM_STR);
self::bindParam( $stmt, 7, $values[6], PDO::PARAM_STR);
self::bindParam( $stmt, 8, $values[7], PDO::PARAM_LOB);
self::bindParam( $stmt, 9, $values[8], PDO::PARAM_LOB);
self::bindParam( $stmt, 10, $values[9], PDO::PARAM_STR);
}
public static function createCRUDTable( $conn, $tableName ){
$fields = array(
"vstring" => "VARCHAR(64)",
"nvstring" => "NVARCHAR(64)",
"num" => "int",
"dttwo" => "DATETIME2",
"string" => "CHAR(64)",
"nstring" => "NCHAR(64)",
"real" => "NUMERIC",
"bin" => "BINARY(16)",
"vbin" => "VARBINARY",
"dtoffset" => "DATETIMEOFFSET");
$params = "";
foreach( $fields as $fieldName => $type ){
$params .= $fieldName." ".$type.",";
}
$params = rtrim($params,", ");
self::createTable( $conn, $tableName, $params );
}
private function createDatabase( $conn, $dbName ){
$sql = "CREATE DATABASE $dbName";
$conn->exec( $sql );
}
public static function dropDatabase( $conn, $dbName ){
$sql = "USE MASTER;DROP DATABASE $dbName";
$conn->exec( $sql );
}
public static function createTable( $conn, $tableName, $params ){
$sql = "CREATE TABLE $tableName ($params)";
$conn->exec( $sql );
}
public static function dropTable( $conn, $tableName ){
$sql = "DROP TABLE $tableName";
$conn->exec( $sql );
}
private function useDatabase( $conn, $dbName ){
$sql = "USE $dbName";
$conn->exec( $sql );
}
private function createStoredProc( $conn, $procName, $params, $text ){
$sql = "CREATE PROCEDURE $procName $params AS $text";
$conn->exec( $sql );
}
private function dropStoredProc( $conn, $procName ){
$sql = "DROP PROCEDURE $procName";
$conn->exec( $sql );
}
private function query( $conn, $sql ){
try{
return $conn->query( $sql );
}
catch( PDOException $e ){
var_dump( $e );
exit;
}
}
private function fetch( $stmt ){
return $stmt->fetch();
}
private function prepare( $conn, $sql ){
try{
$stmt = $conn->prepare( $sql );
if( $stmt === false ){
die( "Failed to prepare\n");
}
return $stmt;
}
catch( PDOException $e ){
var_dump( $e );
exit;
}
}
private function execute( $stmt ){
$ret = $stmt->execute();
if( $ret === false ){
die( "Failed to execute\n" );
}
}
private function bindParam( $stmt, $index, $value, $type ){
$ret = $stmt->bindParam( $index, $value, $type );
if ( $ret === false){
die( "Faild to bind\n");
}
}
}

View file

@ -0,0 +1,231 @@
<?php
namespace SqlsrvPerfTest;
class SqlsrvUtil{
public static function connect(){
require dirname(__FILE__).DIRECTORY_SEPARATOR.'connect.php';
$options = array( "Database"=>$database, "UID"=>$uid, "PWD"=>$pwd, "ConnectionPooling"=>$pooling, "MultipleActiveResultSets"=>$mars );
$conn = sqlsrv_connect( $server, $options );
if ( $conn === false ){
die( print_r( sqlsrv_errors(), true));
}
return $conn;
}
public static function disconnect( $conn ){
if ( $conn === false || $conn === null ){
die( print_r( "Invalid connection resource\n"));
}
$ret = sqlsrv_close( $conn );
if ( $ret === false ){
die( print_r( sqlsrv_errors(), true));
}
}
public static function selectVersion( $conn ){
$sql = "SELECT @@Version";
$stmt = self::query( $conn, $sql );
return self::fetchArray( $stmt );
}
public static function createDbTableProc( $conn, $databaseName, $tableName, $procName ){
$tableParams = "id INTEGER, name VARCHAR(32), value INTEGER, start_date DATE, time_added TIMESTAMP, set_time TIME(7)";
$procParams = "@id INTEGER, @name VARCHAR(32)";
$procTextBase = "SET NOCOUNT ON; SELECT id, name, value FROM $databaseName.$tableName WHERE id = @id AND name = @name";
self::createDatabase( $conn, $databaseName );
self::useDatabase( $conn, $databaseName );
self::createTable( $conn, $tableName, $tableParams );
self::createStoredProc( $conn, $procName, $procParams, $procTextBase );
}
public static function generateInsertValues(){
$vcharVal = "test string";
$nvcharVal = "wstring";
$intVal = 3;
$dateTimeVal = '2016-10-31 01:39:39.7341976';
$charVal = "fixedstr";
$ncharVal = "fixed w string";
$realVal = 14.2;
$binVal = 0x0123456789ABCDE;
$vbinVal = 13;
$dateTimeOffsetVal = '7032-12-17 02:32:18.5210310+00:00';
$values = array( $vcharVal, $nvcharVal, $intVal, $dateTimeVal, $charVal, $ncharVal, $realVal, $binVal, $vbinVal, $dateTimeOffsetVal );
return $values;
}
public static function generateUpdateValues(){
$vcharVal = "test string updated";
$nvcharVal = "wstring updated";
$intVal = 5;
$dateTimeVal = '2005-10-31 01:20:39.7341976';
$charVal = "fixedstr updated";
$ncharVal = "fixed w string updated";
$realVal = 19.2;
$binVal = 0x01345789ABCDE;
$vbinVal = 18;
$dateTimeOffsetVal = '1032-12-17 02:42:18.5210310+00:00';
$updatedValues = array( $vcharVal, $nvcharVal, $intVal, $dateTimeVal, $charVal, $ncharVal, $realVal, $binVal, $vbinVal, $dateTimeOffsetVal );
return $updatedValues;
}
public static function generateUpdateParams(){
$fieldNames = array(
"vstring",
"nvstring",
"num",
"dttwo",
"string",
"nstring",
"real",
"bin",
"vbin",
"dtoffset");
$params = "";
foreach( $fieldNames as $fieldName ){
$params = $params.$fieldName."=?,";
}
$params = rtrim($params,", ");
return $params;
}
public static function insertWithPrepare( $conn, $tableName, $values ){
$sql = "INSERT INTO $tableName VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
$stmt = self::prepare( $conn, $sql, $values );
self::execute( $stmt );
}
public static function updateWithPrepare( $conn, $tableName, $updateValues, $params ){
$sql = "UPDATE $tableName SET ".$params;
$stmt = self::prepare( $conn, $sql, $updateValues );
self::execute( $stmt );
}
public static function fetchWithPrepare( $conn, $tableName ){
$sql = "SELECT * FROM $tableName";
$stmt = self::prepare( $conn, $sql, array());
self::execute( $stmt );
while( $row = self::fetchArray( $stmt ) ) {}
}
public static function createCRUDTable( $conn, $tableName ){
$fields = array(
"vstring" => "VARCHAR(64)",
"nvstring" => "NVARCHAR(64)",
"num" => "int",
"dttwo" => "DATETIME2",
"string" => "CHAR(64)",
"nstring" => "NCHAR(64)",
"real" => "NUMERIC",
"bin" => "BINARY(16)",
"vbin" => "VARBINARY",
"dtoffset" => "DATETIMEOFFSET");
$params = "";
foreach( $fields as $fieldName => $type ){
$params .= $fieldName." ".$type.",";
}
$params = rtrim($params,", ");
self::createTable( $conn, $tableName, $params );
}
public static function query( $conn, $sql ){
$stmt = sqlsrv_query( $conn, $sql );
if( $stmt === false ){
die( print_r( sqlsrv_errors(), true));
}
return $stmt;
}
public static function fetch( $stmt ){
$ret = sqlsrv_fetch( $stmt );
if( $ret === false ){
die( print_r( sqlsrv_errors(), true));
}
return $ret;
}
public static function fetchArray( $stmt ){
$row = sqlsrv_fetch_array( $stmt );
if ( $row === false ){
die( print_r( sqlsrv_errors(), true));
}
return $row;
}
public static function getField( $stmt, $index ){
return sqlsrv_get_field( $stmt, $index );
}
private function createDatabase( $conn, $dbName ){
$sql = "CREATE DATABASE $dbName";
self::query( $conn, $sql );
}
public static function dropDatabase( $conn, $dbName ){
$sql = "USE MASTER;DROP DATABASE $dbName";
self::query( $conn, $sql );
}
public static function createTable( $conn, $tableName, $params ){
$sql = "CREATE TABLE $tableName ($params)";
self::query( $conn, $sql );
}
public static function dropTable( $conn, $tableName ){
$sql = "DROP TABLE $tableName";
self::query( $conn, $sql );
}
private function useDatabase( $conn, $dbName ){
$sql = "USE $dbName";
self::query( $conn, $sql );
}
private function createStoredProc( $conn, $procName, $params, $text ){
$sql = "CREATE PROCEDURE $procName $params AS $text";
self::query( $conn, $sql );
}
private function dropStoredProc( $conn, $procName ){
$sql = "DROP PROCEDURE $procName";
self::query( $conn, $sql );
}
private function insert( $conn, $tableName, $values ){
$sql = "INSERT INTO $tableName values ($values)";
self::query( $conn, $sql );
}
private function update( $conn, $tableName, $params, $condition ){
$sql = "UPDATE $tableName SET $params WHERE $condition";
self::query( $sql );
}
public function delete( $conn, $tableName){
$sql = "DELETE TOP (1) FROM $tableName";
self::query( $conn, $sql );
}
public function deleteWithPrepare( $conn, $tableName ){
$sql = "DELETE TOP (1) FROM $tableName";
$stmt = self::prepare( $conn, $sql, array() );
self::execute( $stmt );
}
private function prepare( $conn, $sql, $params ){
$stmt = sqlsrv_prepare( $conn, $sql, $params );
if( $stmt === false ){
die( print_r( sqlsrv_errors(), true));
}
return $stmt;
}
public function execute( $stmt ){
$ret = sqlsrv_execute( $stmt );
if ( $ret === false ){
die( print_r( sqlsrv_errors(), true));
}
}
}

View file

@ -0,0 +1,8 @@
<?php
$server = 'server';
$database = 'testdb';
$uid = 'usr';
$pwd = 'pwd';
$pooling=false;
$mars=false;
?>

View file

@ -0,0 +1,3 @@
{
"bootstrap": "vendor/autoload.php"
}

View file

@ -0,0 +1,24 @@
--The script can be run to read the results. You can filter out the results ran on a certain date by changing the date at the end.
select t1.ResultId, Test, Client, Server, Driver, Duration, Memory, Success, Team, StartTime from
(
select pr.ResultId, pr.Success, pt.TestName as Test, cl.HostName as Client, srv.HostName as Server,
tm.TeamName as Team, st.value as Driver, bi.value as Duration, bi2.value as Memory, dt.value as StartTime from
KeyValueTableBigInt bi,
KeyValueTableBigInt bi2,
KeyValueTableString st,
KeyValueTableDate dt,
PerformanceResults pr,
Clients cl,
PerformanceTests pt,
Teams tm,
Servers srv
where bi.name = 'duration' and bi.ResultId = pr.ResultId
and bi2.name = 'memory' and bi2.ResultId = pr.ResultId
and dt.name = 'startTime' and dt.ResultId = pr.ResultId
and st.name = 'driver' and st.ResultId = pr.ResultId
and cl.ClientId = pr.ClientId
and pt.TestId = pr.TestId
and tm.TeamId = pr.TeamId
and srv.ServerId = pr.ServerId
) t1 where StartTime like '%2017-06-23%'

View file

@ -0,0 +1,793 @@
#!/usr/bin/python3
"""
Description: This script intended to run the Performance Tests on Windows, Linux and Mac.
Requirements:
Run setup_env_unix.sh( Linux and Mac ) or setup_env_windows.ps1( Windows ) before invoking this script.
modify lib/connect.php with the credentials to connect to the test database.
"""
import shutil
from shutil import copyfile
import os
import sys
import argparse
import subprocess
import fileinput
import subprocess
from subprocess import call
import xml.etree.ElementTree as ET
import pyodbc
import platform
import re
import datetime
import time
from time import strftime
import hashlib
"""
Paths to current benchmarks. These constants should be modified if there are any changes in folder structure of the project. "regular" folder contains the benchmarks that can be run for any iterations. "large" folder contains the benchmarks ( currently the benchmark that fetches large amount of data ) that take long time to run and meant to have less number of iterations than the regular ones.
"""
sqlsrv_regular_path = "benchmark" + os.sep + "sqlsrv" + os.sep + "regular"
sqlsrv_large_path = "benchmark" + os.sep + "sqlsrv" + os.sep + "large"
pdo_regular_path = "benchmark" + os.sep + "pdo_sqlsrv" + os.sep + "regular"
pdo_large_path = "benchmark" + os.sep + "pdo_sqlsrv" + os.sep + "large"
"""
Path to the connect.php file that contains test database credentials. Note that, the benchmarks are run against this database and it is different from Result database.
"""
connect_file = "lib" + os.sep + "connect.php"
connect_file_bak = connect_file + ".bak"
"""
Global data format used across the script
"""
fmt = "%Y-%m-%d %H:%M:%S.0000000"
def validate_platform( platform_name ):
"""
This module validates the platform name passed in to the script as an argument.
If no match, the script will stop the execution.
Args:
platform_name (str): Platform name to validate
Returns:
N/A
"""
platforms = [
"Windows10"
, "WindowsServer2016"
, "WindowsServer2012"
, "Ubuntu16"
, "RedHat7"
, "Sierra"]
if platform_name not in platforms:
print ( "Platform must be one of the following:" )
print( platforms )
exit( 1 )
class DB( object ):
"""
A class to keep database credentials
Attributes:
server_name (str): The name or the IP address of the server.
database_name (str): The name of the database
username (str): Database username
password (str): Database password for username
"""
def __init__ ( self
, server_name = None
, database_name = None
, username = None
, password = None):
self.server_name = server_name
self.database_name = database_name
self.username = username
self.password = password
class XMLResult( object ):
"""
A class to keep a result set of a benchmark generated by PHPBench as an XML file.
Attributes:
benchmark_name (str): The name or the benchmark.
success (int): 0 or 1. 0 if the benchmark failed to execute, 1 if the execution was successful.
duration (int,optional): In case of success, time taken to run the benchmark.
memory (int, optional): In case of success, memory peak when executing the benchmark.
iterations(int, optional): In case of success, number of iterations the benchmark was run for.
error_message(str, optional): In case of failure, descriptive error message.
"""
def __init__ ( self
, benchmark_name = None
, success = None
, duration = None
, memory = None
, iterations = None
, error_message = None ):
self.benchmark_name = benchmark_name
self.success = success
self.duration = duration
self.memory = memory
self.iterations = iterations
self.error_message = error_message
def get_test_name( name ):
"""
This module maps PHPBench benchmark names to the names that are used accross the teams.
Args:
name (str): Name of the benchmark
Returns:
The mapped name
Raises:
KeyError when the name passed in does not match any of the keys
"""
test_name_dict = {
'SqlsrvConnectionBench': 'connection'
, 'SqlsrvCreateDbTableProcBench': 'create'
, 'SqlsrvInsertBench': 'crud-create'
, 'SqlsrvFetchBench': 'crud-retrieve'
, 'SqlsrvUpdateBench': 'crud-update'
, 'SqlsrvDeleteBench': 'crud-delete'
, 'SqlsrvFetchLargeBench': 'large'
, 'SqlsrvSelectVersionBench': 'version'
, 'PDOConnectionBench': 'connection'
, 'PDOCreateDbTableProcBench': 'create'
, 'PDOInsertBench': 'crud-create'
, 'PDOFetchBench': 'crud-retrieve'
, 'PDOUpdateBench': 'crud-update'
, 'PDODeleteBench': 'crud-delete'
, 'PDOFetchLargeBench': 'large'
, 'PDOSelectVersionBench': 'version'
}
return test_name_dict[ name ]
def get_run_command( path_to_tests, iterations, dump_file ):
"""
This module returns the command to run the tests
Args:
path_to_tests (str): The folder that contains the tests to be run
iterations (str): Number of iterations
dump_file (str): The name of the XML file to output the results
Returns:
The command to run the tests
"""
command = "vendor" + os.sep + "bin" + os.sep + "phpbench run {0} --iterations {1} --dump-file={2}"
return command.format( path_to_tests, iterations, dump_file )
def get_id( conn, id_field, table, name_field, value ):
"""
This module returns id of an entry when value is a string
Args:
conn (obj): A connection to the result database
id_field (str): The name of the id column
table (str): The name of the table that contains the entry
name_field (str): The name of the field to compare the value against
value (str): The value that its id is requested
Returns:
id of the value if the value exists in the table, None otherwise
"""
query = "SELECT {0} FROM {1} WHERE {2}='{3}'"
cursor = conn.cursor()
cursor.execute( query.format( id_field, table, name_field, value ))
id = cursor.fetchone()
cursor.close()
if id is not None:
return id[0]
return id
def get_id_no_quote( conn, id_field, table, name_field, value ):
"""
This module returns id of an entry when value is not a string.
@TODO This is a hack, could not get binary binding working with pyodbc.
This module should be removed and get_id should use binding parameters.
Args:
conn (obj): A connection to the result database
id_field (str): The name of the id column
table (str): The name of the table that contains the entry
name_field (str): The name of the field to compare the value against
value (bin): The value that its id is requested
Returns:
id of the value if the value exists in the table, None otherwise
"""
query = "SELECT {0} FROM {1} WHERE {2}={3}"
cursor = conn.cursor()
cursor.execute( query.format( id_field, table, name_field, value ))
id = cursor.fetchone()
cursor.close()
if id is not None:
return id[0]
return id
def get_test_database():
"""
This module reads test database details from connect.php and stores them into an instance of DB class
Returns:
A DB object that contains database credentials
"""
test_db = DB()
for line in open( connect_file ):
if "server" in line:
test_db.server_name = line.split("=")[1].strip()[1:-2]
elif "database" in line:
test_db.database_name = line.split("=")[1].strip()[1:-2]
elif "uid" in line:
test_db.username = line.split("=")[1].strip()[1:-2]
elif "pwd" in line:
test_db.password = line.split("=")[1].strip()[1:-2]
return test_db
def connect( db ):
"""
This module creates a connection to the given database
Args:
db (obj): database object
Returns:
A connection object to the given database
"""
return pyodbc.connect(
driver="{ODBC Driver 13 for SQL Server}"
, host=db.server_name
, database=db.database_name
, user=db.username
, password=db.password
, autocommit = True)
def get_server_version( server ):
"""
This module returns the version of the given server
Args:
server (obj): Server object to connect to
Returns:
The output of @@Version
"""
conn = connect( server )
cursor = conn.cursor()
cursor.execute( "SELECT @@VERSION")
version = cursor.fetchone()[0]
cursor.close()
return version
def get_sha1_file( filename ):
"""
This module generates sha1sum for the given file
Args:
filename (str): Full path to the file
Returns:
sha1sum hash of the file
"""
hash_size = 256
sha1 = hashlib.sha1()
with open( filename, 'rb' ) as f:
while True:
data = f.read( hash_size )
if not data:
break
sha1.update( data )
return "0x" + sha1.hexdigest()
def insert_server_entry( conn, server_name, server_version ):
"""
This module inserts a new entry into Servers table
Args:
conn (obj): Connection object to the Results database
server_name (str): Name of the Test Server that the tests are run against
server_version (str): @@Version of the Test Server
Returns:
N/A
"""
query = "INSERT INTO Servers ( HostName, Version ) VALUES ( '{0}', '{1}' )"
cursor = conn.cursor()
cursor.execute( query.format( server_name, server_version ))
cursor.close()
def insert_client_entry ( conn, name ):
"""
This module inserts a new entry into Clients table
Args:
conn (obj): Connection object to the Results database
name (str): Name of the Client machine that the tests are run on
Returns:
N/A
"""
query = "INSERT INTO Clients ( HostName ) VALUES( '{0}' )"
cursor = conn.cursor()
cursor.execute( query.format( name ))
cursor.close()
def insert_team_entry ( conn, name ):
"""
This module inserts a new entry into Teams table
Args:
conn (obj): Connection object to the Results database
name (str): Team name
Returns:
N/A
"""
query = "INSERT INTO Teams ( TeamName ) VALUES( '{0}' )"
cursor = conn.cursor()
cursor.execute( query.format( name ))
cursor.close()
def insert_test_entry( conn, name ):
"""
This module inserts a new entry into PerformanceTests table
Args:
conn (obj): Connection object to the Results database
name (str): Test name
Returns:
N/A
"""
#TO-DO Remove unnecessary columns from the table and fix the query string. Amd64 and 0 are used to bypass not null
query = "INSERT INTO PerformanceTests ( TestName, Arch, HashVer ) VALUES( '{0}', 'Amd64', 0 )"
cursor = conn.cursor()
cursor.execute( query.format( name ))
cursor.close()
def insert_driver_entry( conn, driver_path, driver_hash ):
"""
This module inserts a new entry into Drivers table
Args:
conn (obj): Connection object to the Results database
name (str): Full path to the driver
driver_hash (bin): sha1sum hash of the driver
Returns:
N/A
"""
file_date = time.strftime( fmt, time.gmtime( os.path.getmtime( driver_path )))
query = "INSERT INTO Drivers ( Arch, FileDate, SHA1, HashVer ) VALUES ( ?, ?, {0}, 1 )"
cursor = conn.cursor()
cursor.execute( query.format(driver_hash), ( get_php_arch(), file_date ))
cursor.close()
def get_server_id( conn, test_db ):
"""
This module retrieves the id of a Server entry. If the given server does not exist in Servers table,
the module inserts it into the Servers table and retrieves its id.
Args:
conn (obj): Connection object to the Results database
test_db (obj): An object that contains Test Server details
Returns:
id of the given server
"""
server_id = get_id( conn, "ServerId", "Servers", "HostName", test_db.server_name )
if server_id is None:
insert_server_entry( conn, test_db.server_name, get_server_version( test_db ))
server_id = get_id( conn, "ServerId", "Servers", "HostName", test_db.server_name )
return server_id
def get_client_id( conn ):
"""
This module retrieves the id of a Client entry. If the given client does not exist in Clients table,
the module inserts it into the Clients table and retrieves its id.
Args:
conn (obj): Connection object to the Results database
Returns:
id of the client
"""
client_name = platform.node()
client_id = get_id( conn, "ClientId", "Clients", "HostName", client_name )
if client_id is None:
insert_client_entry( conn, client_name )
client_id = get_id( conn, "ClientId", "Clients", "HostName", client_name )
return client_id
def get_team_id( conn ):
"""
This module retrieves the id of a Team entry. If the given team name - PHP does not exist in Teams table,
the module inserts it into the Teams table and retrieves its id.
Args:
conn (obj): Connection object to the Results database
Returns:
id of the team
"""
team_name = "PHP"
team_id = get_id( conn, "TeamId", "Teams", "TeamName", team_name)
if team_id is None:
insert_team_entry( conn, team_name )
team_id = get_id( conn, "TeamId", "Teams", "TeamName", team_name)
return team_id
def get_test_id( conn, test_name ):
"""
This module retrieves the id of a Test entry. If the given test does not exists in PerformanceTests table,
the module inserts it into the PerformanceTests table and retrieves its id.
Args:
conn (obj): Connection object to the Results database
test_name (str): The name of the test that the id is requested for
Returns:
id of the test
"""
test_id = get_id( conn, "TestId", "PerformanceTests", "TestName", test_name )
if test_id is None:
insert_test_entry( conn, test_name )
test_id = get_id( conn, "TestId", "PerformanceTests", "TestName", test_name )
return test_id
def get_driver_id( conn, driver_name ):
"""
This module retrieves the id of a Driver entry. If the given driver does not exists in Drivers table,
the module inserts it into the Drivers table and retrieves its id.
Args:
conn (obj): Connection object to the Results database
driver_name (str): The name of the driver that the id is requested for
Returns:
id of the driver
"""
driver_path = get_path_to_driver( driver_name )
driver_hash = get_sha1_file( driver_path )
driver_id = get_id_no_quote( conn, "DriverId", "Drivers", "SHA1", driver_hash )
if driver_id is None:
insert_driver_entry( conn, driver_path, driver_hash )
driver_id = get_id_no_quote( conn, "DriverId", "Drivers", "SHA1", driver_hash )
return driver_id
def insert_result_entry_and_get_id( conn, test_id, client_id, driver_id, server_id, team_id, success ):
"""
This module inserts a new result entry into PerformanceResults table and retrieves its id.
Args:
conn (obj): Connection object to the Results database
test_id (int): The id of the test
client_id (int): The id of the client that the test was run on
driver_id (int): The id of the driver that the test was run against
server_id (int): The id of the server that the test was run against
team_id (int): The id of the team that the test belongs to
success (int): 0 if the test failed, 1 otherwise
Returns:
id of the result
"""
query = "INSERT INTO PerformanceResults( TestId, ClientId, DriverId, ServerId, TeamId, Success ) OUTPUT INSERTED.ResultId VALUES( {0}, {1}, {2}, {3}, {4}, {5} )"
cursor = conn.cursor()
cursor.execute( query.format( test_id, client_id, driver_id, server_id, team_id, success ))
result_id = cursor.fetchone()
cursor.close()
if result_id is not None:
return result_id[0]
return id
def insert_result_key_value( conn, table_name, result_id, key, value ):
"""
This module inserts a new entry into a key-value table.
Args:
conn (obj): Connection object to the Results database
table_name (string): The name of the table. Current possible values: KeyValueTableBigInt, KeyValueTableDate, KeyValueTableString
result_id (int): The result id that is associated with the key-value table
key (str): name of the property
value (int, date, string): The value of the key
Returns:
N/A
"""
query = "INSERT INTO {0} ( ResultId, name, value ) VALUES( ?, ?, ? )"
cursor = conn.cursor()
cursor.execute( query.format( table_name ), ( result_id, key, value ) )
cursor.close()
def get_php_arch():
"""
This module determines the architecture of the default php of the system
Args:
N/A
Returns
x86 or x64
"""
p = subprocess.Popen( "php -r \"echo PHP_INT_SIZE;\"", stdout=subprocess.PIPE, shell = True )
out, err = p.communicate()
if out.decode('ascii') == "8":
return "x64"
elif out.decode('ascii') == "4":
return "x86"
def get_php_version():
"""
This module determines the version of the default php of the system
Args:
N/A
Returns:
php version
"""
p = subprocess.Popen( "php -r \"echo phpversion();\"", stdout=subprocess.PIPE, shell = True )
out, err = p.communicate()
return out.decode('ascii')
def get_php_thread():
"""
This module determines the thread safety of the default php of the system
Args:
N/A
Returns:
nts or ts
"""
if os.name == 'nt':
command = "php -i | findstr \"Thread\""
else:
command = "php -i | grep 'Thread'"
p = subprocess.Popen( command, stdout=subprocess.PIPE, shell = True )
out, err = p.communicate()
if out.decode('ascii').split()[3].strip() == 'disabled':
return "nts"
else:
return "ts"
def get_driver_version( driver_name ):
"""
This module determines the version of the given php driver.
Args:
driver_name (str): Name of the driver. Possible values sqlsrv and pdo_sqlsrv
Returns:
The version of the given driver
"""
command = "php -r \"echo phpversion('{0}');\""
p = subprocess.Popen( command.format( driver_name ), stdout=subprocess.PIPE, shell = True )
out, err = p.communicate()
return out.decode('ascii')
def get_msodbcsql_version( test_db ):
"""
This module determines the version of MSODBCSQL using the sqlsrv driver.
Args:
test_db (obj): An object that contains Test Server details
Returns:
MSODBCSQL version
"""
command = "php -r \"echo sqlsrv_client_info( sqlsrv_connect( '{0}', array( 'UID'=>'{1}', 'PWD'=>'{2}')))['DriverVer'];\""
p = subprocess.Popen( command.format( test_db.server_name, test_db.username, test_db.password ), stdout=subprocess.PIPE, shell = True )
out, err = p.communicate()
return out.decode('ascii')
def get_path_to_driver( driver_name ):
"""
This module returns the full path to the given php driver
Args:
driver_name (str): Name of the driver. Possible values sqlsrv and pdo_sqlsrv
Returns:
Full path to the given driver
"""
p = subprocess.Popen( "php -r \"echo ini_get('extension_dir');\"", stdout=subprocess.PIPE, shell = True )
out, err = p.communicate()
extension_dir = out.decode('ascii')
if os.name == 'nt':
return extension_dir + os.sep + "php_" + driver_name + ".dll"
else:
return extension_dir + os.sep + driver_name + ".so"
def enable_mars():
"""
This module enables MARS by modifying connect.php file
"""
print( "Enabling MARS...")
with fileinput.FileInput( connect_file, inplace=True, backup='.bak') as file:
for line in file:
print( line.replace( "$mars=false;", "$mars=true;" ), end='')
def disable_mars():
"""
This module disables MARS by modifying connect.php file
"""
print( "Disabling MARS...")
os.remove( connect_file )
copyfile( connect_file_bak, connect_file )
def enable_pooling():
"""
This module enables Connection Pooling.
On Windows, this is done by modifying connect.php file.
On Linux and Mac, odbcinst.ini file needs to be modified.
@TO-DO: Currently modifying odbcinst.ini requires root permissions.
Copy the MSODBCSQL to a location which does not require sudo.
"""
print( "Enabling Pooling...")
if os.name == 'nt':
with fileinput.FileInput( connect_file, inplace=True, backup='.bak') as file:
for line in file:
print( line.replace( "$pooling=false;", "$pooling=true;" ), end='')
else:
# Get the location of odbcinst.ini
odbcinst = os.popen( "odbcinst -j" ).read().splitlines()[1].split()[1]
odbcinst_bak = odbcinst + ".bak"
# Create a copy of odbcinst.ini
copyfile( odbcinst, odbcinst_bak )
# Lines to enable Connection pooling
lines_to_append="CPTimeout=5\n[ODBC]\nPooling=Yes\n"
with open( odbcinst, "a" ) as f:
f.write( lines_to_append )
def disable_pooling():
"""
This module disables Connection Pooling.
On Windows, this is done by modifying connect.php file.
On Linux and Mac, odbcinst.ini file needs to be modified.
@TO-DO: Currently modifying odbcinst.ini requires root permissions.
Copy the MSODBCSQL to a location which does not require sudo.
"""
print("Disabling Pooling...")
if os.name == 'nt':
os.remove( connect_file )
copyfile( connect_file_bak, connect_file )
else:
# Get the location of odbcinst.ini
odbcinst = os.popen( "odbcinst -j" ).read().splitlines()[1].split()[1]
odbcinst_bak = odbcinst + ".bak"
os.remove( odbcinst )
copyfile( odbcinst_bak, odbcinst )
os.remove( odbcinst_bak )
def run_tests( iterations, iterations_large ):
"""
This module runs the tests using PHPBench
Args:
iterations (int): Number of iterations the tests in "regular" folder are run for
iterations_large (int): Number of iterations the tests in "large" folder are run for
Returns:
N/A
"""
print("Running the tests...")
call( get_run_command( sqlsrv_regular_path, iterations, "sqlsrv-regular.xml" ), shell=True )
call( get_run_command( sqlsrv_large_path, iterations_large, "sqlsrv-large.xml" ), shell=True )
call( get_run_command( pdo_regular_path, iterations, "pdo_sqlsrv-regular.xml" ), shell=True )
call( get_run_command( pdo_large_path, iterations_large, "pdo_sqlsrv-large.xml" ), shell=True )
def parse_results( dump_file ):
"""
This module parses the .xml files generated by PHPBench
@TO-DO: Currently only limited detailes are parsed, such as duration and peak memory.
PHPBench reports a lot more information that can be helpful.
Consider looking at the xml files.
Args:
dump_file (str): The name of the XML file to be parsed.
Returns:
An array of XMLResult objects, where each object contains benchmark information, such as duration and memory.
"""
xml_results = []
tree = ET.parse( dump_file )
root = tree.getroot()
# The following lines assume a certain xml structure.
# Get all the benchmarks in a list
benchmarks = root[0].findall( 'benchmark' )
for benchmark in benchmarks:
xml_result = XMLResult()
#Get the benchmark name and remove the leasing backslash
xml_result.benchmark_name = benchmark.get( 'class' )[1:]
errors = benchmark[0][0].find( 'errors' )
# Store the error message and mark the benchmark as failed if something went wrong when running the benchmark.
if( errors is not None ):
xml_result.success = 0
xml_result.error_message = errors[0].text
# If the bechmark was run successfully, parse the results. This is where you would add code to parse more details about the benchmark.
else:
xml_result.success = 1
xml_result.duration = int( round( int( benchmark[0][0].find( 'stats' ).get( 'sum' )) / 1000000 ))
iterations = benchmark[0][0].findall( 'iteration' )
xml_result.iterations = len( iterations )
# Memory peak is an iteration specific, so going through all the iterations and capturing the highest.
# Memory peak is usually the same for all iterations.
memory_peak = 0
for iteration in iterations:
iter_memory_peak = int( iteration.get( 'mem-peak' ))
if iter_memory_peak > memory_peak:
memory_peak = iter_memory_peak
xml_result.memory = memory_peak
xml_results.append( xml_result )
return xml_results
def parse_and_store_results( dump_file, test_db, result_db, platform, driver, start_time, mars, pooling ):
"""
This module parses the given xml file and stores the results into Result Database.
Args:
dump_file (str): Name of the xml file that containst the results from PHPBench
test_db (obj): An object that contains Test Database details
result_db (obj): An object that contains Result Database details
platform (str): The platform name that the tests are run on
driver (str): Name of the driver, sqlsrv or pdo_sqlsrv
start_time (date): Time when the script was run
mars (int): 0 to turn MARS off, 1 otherwise
pooling (int): 0 to turn Connection Pooling off, 1 otherwise
Returns:
N/A
"""
# Connect to the Result Database
conn = connect( result_db )
server_id = get_server_id( conn, test_db )
client_id = get_client_id( conn )
team_id = get_team_id( conn )
driver_id = get_driver_id( conn, driver )
php_arch = get_php_arch()
php_thread = get_php_thread()
php_version = get_php_version()
driver_version = get_driver_version( driver )
msodbcsql_version = get_msodbcsql_version( test_db )
cursor = conn.cursor()
#parse the results from xml file
results = parse_results( dump_file )
# Store every result into the Result Database
for result in results:
test_name = get_test_name( result.benchmark_name )
test_id = get_test_id( conn, test_name )
result_id = insert_result_entry_and_get_id( conn, test_id, client_id, driver_id, server_id, team_id, result.success )
if result.success:
insert_result_key_value( conn, "KeyValueTableBigInt", result_id, "duration", result.duration )
insert_result_key_value( conn, "KeyValueTableBigInt", result_id, "memory", result.memory )
insert_result_key_value( conn, "KeyValueTableBigInt", result_id, "iterations", result.iterations)
else:
insert_result_key_value( conn, "KeyValueTableString", result_id, "error", result.error_message )
insert_result_key_value( conn, "KeyValueTableDate" , result_id, "startTime" , start_time )
insert_result_key_value( conn, "KeyValueTableBigInt", result_id, "mars" , mars )
insert_result_key_value( conn, "KeyValueTableBigInt", result_id, "pooling" , pooling )
insert_result_key_value( conn, "KeyValueTableString", result_id, "driver" , driver )
insert_result_key_value( conn, "KeyValueTableString", result_id, "php_arch" , php_arch )
insert_result_key_value( conn, "KeyValueTableString", result_id, "os" , platform )
insert_result_key_value( conn, "KeyValueTableString", result_id, "php_thread" , php_thread )
insert_result_key_value( conn, "KeyValueTableString", result_id, "php_version" , php_version )
insert_result_key_value( conn, "KeyValueTableString", result_id, "msodbcsql" , msodbcsql_version )
insert_result_key_value( conn, "KeyValueTableString", result_id, "driver_version" , driver_version )
def parse_and_store_results_all( test_db, result_db, platform, start_time, mars, pooling ):
"""
This module parses the given sqlsrv-regular.xml, sqlsrv-large.xml,pdo_sqlsrv-regular.xml, pdo_sqlsrv-large.xml and stores the results into Result Database.
Args:
test_db (obj): An object that contains Test Database details
result_db (obj): An object that contains Result Database details
platform (str): The platform name that the tests are run on
start_time (date): Time when the script was run
mars (int): 0 to turn MARS off, 1 otherwise
pooling (int): 0 to turn Connection Pooling off, 1 otherwise
Returns:
N/A
"""
print("Parsing and storing the results...")
parse_and_store_results( "sqlsrv-regular.xml", test_db, result_db, platform, "sqlsrv", start_time, mars, pooling )
parse_and_store_results( "sqlsrv-large.xml", test_db, result_db, platform, "sqlsrv", start_time, mars, pooling )
parse_and_store_results( "pdo_sqlsrv-regular.xml", test_db, result_db, platform, "pdo_sqlsrv", start_time, mars, pooling )
parse_and_store_results( "pdo_sqlsrv-large.xml", test_db, result_db, platform, "pdo_sqlsrv", start_time, mars, pooling )
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument( '-platform', '--PLATFORM', required=True, help='The name of the platform the tests run on' )
parser.add_argument( '-iterations', '--ITERATIONS', required=True, help='Number of iterations for regular tests', type=int )
parser.add_argument( '-iterations-large', '--ITERATIONS_LARGE', required=True, help='Number of iterations for regular tests', type=int )
parser.add_argument( '-result-server', '--RESULT_SERVER', required=True, help='IP address of the Result Server' )
parser.add_argument( '-result-db', '--RESULT_DB', required=True, help='Name of the Result Database' )
parser.add_argument( '-result-uid', '--RESULT_UID', required=True, help='Username to connect to the Result Database' )
parser.add_argument( '-result-pwd', '--RESULT_PWD', required=True, help='Password to connect to the Result Database' )
args = parser.parse_args()
# Start time is recorded only in the beginning of this script execution. So it is not benchmark specific.
# Start time can be used to group the results
start_time = datetime.datetime.now().strftime( fmt )
print( "Start time: " + start_time )
validate_platform( args.PLATFORM )
result_db = DB( args.RESULT_SERVER, args.RESULT_DB, args.RESULT_UID, args.RESULT_PWD )
test_db = get_test_database()
print("Running the tests with default settings...")
run_tests( args.ITERATIONS, args.ITERATIONS_LARGE )
parse_and_store_results_all( test_db, result_db, args.PLATFORM, start_time, 0, 0 )
"""
The following lines are commented out, because it already takes a long time to run the tests with the default settings.
Echo block can be uncommented and run separately.
print("Running the tests with MARS ON...")
enable_mars()
run_tests( args.ITERATIONS, args.ITERATIONS_LARGE )
parse_and_store_results_all( test_db, result_db, args.PLATFORM, start_time, 1, 0 )
disable_mars()
print("Running the tests with Pooling ON...")
enable_pooling()
run_tests( args.ITERATIONS, args.ITERATIONS_LARGE )
parse_and_store_results_all( test_db, result_db, args.PLATFORM, start_time, 0, 1 )
disable_pooling()
"""
exit()

View file

@ -0,0 +1,140 @@
#!/bin/bash
set -e
if [[ ("$1" = "Ubuntu16" || "$1" = "RedHat7" || "$1" = "Sierra") ]]; then
PLATFORM=$1
else
echo "First argument must be one of Ubuntu16, RedHat7, Sierra. Exiting..."
exit 1
fi
if [[ "$2" != 7.*.* ]]; then
echo "Second argument must be PHP version in format of 7.x.x.Exiting..."
exit
else
PHP_VERSION=$2
fi
if [[ ("$3" != "nts" && "$3" != "ts") ]]; then
echo "Thrid argument must be either nts or ts. Exiting..."
exit 1
else
PHP_THREAD=$3
fi
if [[ (! -d "$4") || (! -d $4/sqlsrv) || (! -d $4/pdo_sqlsrv) || (! -d $4/shared) ]]; then
echo "Fourth argument must be path to source folder.Path not found.Exiting..."
exit 1
else
DRIVER_SOURCE_PATH=$4
fi
rm -rf env_setup.log
touch env_setup.log
if [ $PLATFORM = "Ubuntu16" ]; then
echo "Update..."
yes | sudo dpkg --configure -a >> env_setup.log 2>&1
yes | sudo apt-get update >> env_setup.log 2>&1
echo "Installing git, zip, curl, libxml, autoconf, openssl, python3, pip3..."
yes | sudo apt-get install git zip curl autoconf libxml2-dev libssl-dev pkg-config python3 python3-pip >> env_setup.log 2>&1
echo "OK"
echo "Installing MSODBCSQL..."
curl -s https://packages.microsoft.com/keys/microsoft.asc | apt-key add -
curl -s https://packages.microsoft.com/config/ubuntu/16.04/prod.list > /etc/apt/sources.list.d/mssql-release.list
yes | sudo apt-get update >> env_setup.log 2>&1
yes | sudo ACCEPT_EULA=Y apt-get install msodbcsql >> env_setup.log 2>&1
yes | sudo apt-get install -qq unixodbc-dev >> env_setup.log 2>&1
echo "Installing pyodbc"
pip3 install --upgrade pip >> env_setup.log 2>&1
pip3 install pyodbc >> env_setup.log 2>&1
echo "OK"
elif [ $PLATFORM = "RedHat7" ]; then
echo "Update..."
yes | sudo yum update >> env_setup.log 2>&1
echo "OK"
echo "Enabling EPEL repo..."
wget https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm >> env_setup.log 2>&1
yes | sudo yum install epel-release-latest-7.noarch.rpm >> env_setup.log 2>&1 || true
echo "OK"
echo "Installing python34-setuptools..."
yes | sudo yum install python34-setuptools -y >> env_setup.log 2>&1
echo "OK"
echo "Installing gcc, git, zip libxml, openssl, EPEL, python3, pip3..."
yes | sudo yum install -y gcc-c++ libxml2-devel git zip openssl-devel python34 python34-devel python34-pip >> env_setup.log 2>&1
echo "OK"
echo "Installing MSODBCSQL..."
curl -s https://packages.microsoft.com/config/rhel/7/prod.repo > /etc/yum.repos.d/mssql-release.repo
(yes | sudo ACCEPT_EULA=Y yum install -y msodbcsql >> env_setup.log 2>&1)
(yes | sudo yum install -y unixODBC-devel autoconf >> env_setup.log 2>&1)
echo "OK"
echo "Installing pyodbc"
pip3 install --upgrade pip >> env_setup.log 2>&1
pip3 install pyodbc >> env_setup.log 2>&1
echo "OK"
elif [ $PLATFORM = "Sierra" ]; then
echo "Installing homebrew..."
yes | /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" >> env_setup.log 2>&1
echo "OK"
echo "Installing wget..."
brew install wget >> env_setup.log 2>&1
echo "OK"
echo "Installing svn..."
brew install svn >> env_setup.log 2>&1
echo "OK"
echo "Installing openssl..."
brew install pkg-config >> env_setup.log 2>&1
brew install openssl >> env_setup.log 2>&1
echo "OK"
echo "Installing python3..."
brew install python3 >> env_setup.log 2>&1
echo "OK"
echo "Installing MSODBCSQL..."
brew tap microsoft/msodbcsql https://github.com/Microsoft/homebrew-msodbcsql >> env_setup.log 2>&1
brew update >> env_setup.log 2>&1
yes | ACCEPT_EULA=Y brew install msodbcsql >> env_setup.log 2>&1
echo "OK"
yes | brew install autoconf >> env_setup.log 2>&1
echo "Installing pyodbc..."
pip3 install pyodbc >> env_setup.log 2>&1
echo "OK"
fi
echo "Downloading PHP-$PHP_VERSION source tarball..."
wget http://ca1.php.net/get/php-$PHP_VERSION.tar.gz/from/this/mirror -O php-$PHP_VERSION.tar.gz >> env_setup.log 2>&1
echo "OK"
echo "Extracting PHP source tarball..."
rm -rf php-$PHP_VERSION
tar -xf php-$PHP_VERSION.tar.gz
echo "OK"
cd php-$PHP_VERSION
mkdir ext/sqlsrv ext/pdo_sqlsrv
cp -r $DRIVER_SOURCE_PATH/sqlsrv/* $DRIVER_SOURCE_PATH/shared ext/sqlsrv
cp -r $DRIVER_SOURCE_PATH/pdo_sqlsrv/* $DRIVER_SOURCE_PATH/shared ext/pdo_sqlsrv
./buildconf --force >> ../env_setup.log 2>&1
CONFIG_OPTIONS="--enable-cli --enable-cgi --enable-pdo --enable-sqlsrv=shared --with-pdo_sqlsrv=shared --with-odbcver=0x0380 --with-zlib --enable-mbstring --prefix=/usr/local"
[ "${PHP_THREAD}" == "ts" ] && CONFIG_OPTIONS=${CONFIG_OPTIONS}" --enable-maintainer-zts"
if [ $PLATFORM = "Sierra" ]; then
CONFIG_OPTIONS=$CONFIG_OPTIONS" --with-openssl=/usr/local/opt/openssl/"
else
CONFIG_OPTIONS=$CONFIG_OPTIONS" --with-openssl"
fi
echo "Configuring PHP..."
(./configure $CONFIG_OPTIONS >> ../env_setup.log 2>&1)
echo "OK"
echo "Compiling PHP and the drivers..."
make >> ../env_setup.log 2>&1
echo "OK"
sudo make install >> ../env_setup.log 2>&1
cp php.ini-production php.ini
echo "extension=sqlsrv.so" >> php.ini
echo "extension=pdo_sqlsrv.so" >> php.ini
sudo cp php.ini /usr/local/lib
cd ..
php -v
php --ri sqlsrv
php --ri pdo_sqlsrv
echo "Installing Composer..."
wget https://getcomposer.org/installer -O composer-setup.php >> env_setup.log 2>&1
php composer-setup.php >> env_setup.log 2>&1
echo "OK"
echo "Installing PHPBench..."
php composer.phar install >> env_setup.log 2>&1
echo "OK"
echo "Cleaning up..."
rm -rf php-$PHP_VERSION* compser-setup.php
echo "OK"
echo "Setup completed!"

View file

@ -0,0 +1,118 @@
Param(
[Parameter(Mandatory=$True,Position=1)]
[string]$PHP_VERSION,
[Parameter(Mandatory=$True,Position=2)]
[string]$PHP_THREAD,
[Parameter(Mandatory=$True,Position=3)]
[string]$DRIVER_SOURCE_PATH,
[Parameter(Mandatory=$True,Position=4)]
[string]$ARCH
)
IF($ARCH -ne "x64" -And $ARCH -ne "x86"){
Write-Host "ARCH must either x64 or x86"
Break
}
IF($PHP_THREAD -ne "nts" -And $PHP_THREAD -ne "ts"){
Write-Host "PHP_THREAD must either nts or ts"
Break
}
IF($PHP_VERSION -NotMatch "7.[0-1].[0-9]"){
Write-Host "PHP_VERSION must be in format of 7.x.x"
Break
}
$startingDir=$pwd.Path
$tempFolder=Join-Path $startingDir "temp"
Remove-Item temp -Recurse -Force -ErrorAction Ignore
New-Item -ItemType directory -Path temp
(New-Object System.Net.WebClient).DownloadFile("http://windows.php.net/downloads/releases/sha1sum.txt","$tempFolder\sha1sum.txt")
$PHP70_LATEST_VERSION=type $tempFolder\sha1sum.txt | where { $_ -match "php-(7.0\.\d+)-src" } | foreach { $matches[1] }
$PHP71_LATEST_VERSION=type $tempFolder\sha1sum.txt | where { $_ -match "php-(7.1\.\d+)-src" } | foreach { $matches[1] }
$PHP_VERSION_MINOR=$PHP_VERSION.split(".")[1]
Write-Host "Installing chocolatey..."
iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
Write-Host "Installing Git..."
choco install -y git
Set-Alias git 'C:\Program Files\Git\cmd\git.exe'
Write-Host "Installing Python3..."
choco install -y python3
RefreshEnv
Write-Host "Installing pyodbc..."
C:\Python*\Scripts\pip3.exe install pyodbc | Out-Null
Write-Host "Downloading MSODBCSQL..."
#This needs to be manually updated when there is a new release
(New-object System.Net.WebClient).DownloadFile('https://download.microsoft.com/download/D/5/E/D5EEF288-A277-45C8-855B-8E2CB7E25B96/x64/msodbcsql.msi', "$tempFolder\msodbcsql.msi")
Write-Host "Installing MSODBCSQL..."
msiexec /quiet /passive /qn /i $tempFolder\msodbcsql.msi IACCEPTMSODBCSQLLICENSETERMS=YES | Out-Null
Write-Host "Installing 7-Zip..."
choco install -y 7zip.install
Write-Host "Downloading PHP-SDK..."
(New-Object System.Net.WebClient).DownloadFile('http://windows.php.net/downloads/php-sdk/php-sdk-binary-tools-20110915.zip', "$tempFolder\binary_tools.zip")
Write-Host "Downloading PHP-$PHP_VERSION source..."
IF($PHP_VERSION -eq $PHP70_LATEST_VERSION -Or $PHP_VERSION -eq $PHP71_LATEST_VERSION){
(New-Object System.Net.WebClient).DownloadFile("http://windows.php.net/downloads/releases/php-$PHP_VERSION-src.zip", "$tempFolder\php-$PHP_VERSION-src.zip")
}
ELSE{
(New-Object System.Net.WebClient).DownloadFile("http://windows.php.net/downloads/releases/archives/php-$PHP_VERSION-src.zip", "$tempFolder\php-$PHP_VERSION-src.zip")
}
Write-Host "Downloading Dependencies..."
(New-Object System.Net.WebClient).DownloadFile("http://windows.php.net/downloads/php-sdk/deps-7.$PHP_VERSION_MINOR-vc14-$ARCH.7z", "$tempFolder\deps-7.$PHP_VERSION_MINOR-vc14-$ARCH.7z")
Add-Type -AssemblyName System.IO.Compression.FileSystem
Remove-Item C:\php-sdk -Recurse -Force -ErrorAction Ignore
New-Item -ItemType directory -Path C:\php-sdk
[System.IO.Compression.ZipFile]::ExtractToDirectory("$tempFolder\binary_tools.zip", "C:\php-sdk")
cd C:\php-sdk\
bin\phpsdk_buildtree.bat phpdev
New-Item -ItemType directory -Path .\phpdev\vc14
Copy-Item .\phpdev\vc9\* phpdev\vc14\ -recurse
[System.IO.Compression.ZipFile]::ExtractToDirectory("$tempFolder\php-$PHP_VERSION-src.zip", "C:\php-sdk\phpdev\vc14\$ARCH\")
7z.exe x $tempFolder\deps-7.$PHP_VERSION_MINOR-vc14-$ARCH.7z -oC:\php-sdk\phpdev\vc14\$ARCH\
bin\phpsdk_setvars.bat
cd C:\php-sdk\phpdev\vc14\$ARCH\php-$PHP_VERSION-src
New-Item -ItemType directory -Path .\ext\sqlsrv
New-Item -ItemType directory -Path .\ext\pdo_sqlsrv
Copy-Item $DRIVER_SOURCE_PATH\sqlsrv\* .\ext\sqlsrv\ -recurse
Copy-Item $DRIVER_SOURCE_PATH\shared\ .\ext\sqlsrv\ -recurse
Copy-Item $DRIVER_SOURCE_PATH\pdo_sqlsrv\* .\ext\pdo_sqlsrv\ -recurse
Copy-Item $DRIVER_SOURCE_PATH\shared\ .\ext\pdo_sqlsrv\ -recurse
$CONFIG_OPTIONS="--enable-cli --enable-cgi --enable-sqlsrv=shared --enable-pdo=shared --with-pdo-sqlsrv=shared --with-odbcver=0x0380 --enable-mbstring --with-openssl"
if ($PHP_THREAD -ceq "nts") {
$CONFIG_OPTIONS=$CONFIG_OPTIONS + " --disable-zts"
}
& $startingDir\compile_php.bat $ARCH $CONFIG_OPTIONS
Copy-Item php.ini-production php.ini
Add-Content php.ini "extension=C:\php\ext\php_sqlsrv.dll"
Add-Content php.ini "extension=C:\php\ext\php_pdo_sqlsrv.dll"
Add-Content php.ini "extension=C:\php\ext\php_openssl.dll"
Move-Item php.ini C:\Windows -force
Copy-Item C:\php-sdk\phpdev\vc14\$ARCH\deps\bin\ssleay32.dll C:\Windows -force
Copy-Item C:\php-sdk\phpdev\vc14\$ARCH\deps\bin\libeay32.dll C:\Windows -force
cd $startingDir
[Environment]::SetEnvironmentVariable("Path", $env:Path + ";C:\php\", [System.EnvironmentVariableTarget]::Machine)
$env:Path += ";C:\php\"
RefreshEnv
wget https://getcomposer.org/installer -O composer-setup.php
php composer-setup.php
php composer.phar install
Remove-Item temp -Recurse -Force -ErrorAction Ignore
Write-Host "Setup completed!"

View file

@ -1,63 +1,47 @@
--TEST--
PDO Connection Pooling Test on Unix
PDO_SQLSRV Connection Pooling Test on Unix
--DESCRIPTION--
This test assumes odbcinst.ini has not been modified.
This test also requires root privileges to modify odbcinst.ini file on Linux.
This test assumes the default odbcinst.ini has not been modified.
--SKIPIF--
<?php if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') die("Skipped: Test for Linux and Mac"); ?>
--FILE--
<?php
// On Bamboo we must use sudo to fiddle with odbcinst.ini
// On travis-ci we can't use sudo
$sudo = '';
$user = posix_getpwuid(posix_geteuid());
if (strtolower($user['name']) != 'root')
{
$sudo = 'sudo ';
}
$lines_to_add="CPTimeout=5\n[ODBC]\nPooling=Yes\n";
//get odbcinst.ini location
//get default odbcinst.ini location
$lines = explode("\n", shell_exec("odbcinst -j"));
$odbcinst_ini = explode(" ", $lines[1])[1];
$custom_odbcinst_ini = dirname(__FILE__)."/odbcinst.ini";
//back up the odbcinst.ini file
shell_exec($sudo."cp $odbcinst_ini $odbcinst_ini.bak");
//copy the default odbcinst.ini into the current folder
copy( $odbcinst_ini, $custom_odbcinst_ini);
//enable pooling by modifying the odbcinst.ini file
$current = file_get_contents($odbcinst_ini);
$current = file_get_contents($custom_odbcinst_ini);
$current.=$lines_to_add;
shell_exec("cp $odbcinst_ini .");
file_put_contents("odbcinst.ini", $current);
shell_exec($sudo."cp odbcinst.ini $odbcinst_ini");
file_put_contents($custom_odbcinst_ini, $current);
//Creating a new php process, because for changes in odbcinst.ini file to affect pooling, drivers must be reloaded.
print_r(shell_exec(PHP_BINARY." ".dirname(__FILE__)."/isPooled.php"));
//Also setting the odbcini path to the current folder for the same process.
//This will let us modify odbcinst.ini without root permissions
print_r(shell_exec("export ODBCSYSINI=".dirname(__FILE__)."&&".PHP_BINARY." ".dirname(__FILE__)."/isPooled.php"));
//disable pooling by modifying the odbcinst.ini file
$current = file_get_contents($odbcinst_ini);
$current = file_get_contents($custom_odbcinst_ini);
$current = str_replace($lines_to_add,'',$current);
file_put_contents("odbcinst.ini", $current);
shell_exec($sudo."cp odbcinst.ini $odbcinst_ini");
file_put_contents($custom_odbcinst_ini, $current);
print_r(shell_exec(PHP_BINARY." ".dirname(__FILE__)."/isPooled.php"));
print_r(shell_exec("export ODBCSYSINI=".dirname(__FILE__)."&&".PHP_BINARY." ".dirname(__FILE__)."/isPooled.php"));
?>
--CLEAN--
<?php
$sudo = '';
$user = posix_getpwuid(posix_geteuid());
if (strtolower($user['name']) == 'bamboo')
{
$sudo = 'sudo ';
}
$custom_odbcinst_ini = dirname(__FILE__)."/odbcinst.ini";
unlink($custom_odbcinst_ini);
//back to default odbcinst.ini
shell_exec("unset ODBCSYSINI");
$lines = explode("\n", shell_exec("odbcinst -j"));
$odbcinst_ini = explode(" ", $lines[1])[1];
shell_exec($sudo."cp $odbcinst_ini.bak $odbcinst_ini");
shell_exec($sudo."rm $odbcinst_ini.bak");
?>
--EXPECT--
Pooled
Not Pooled

View file

@ -165,7 +165,7 @@ function GetTempTableName($table = '', $temporary = true)
// dropped once the connection is closed. Otherwise, the caller
// should take care of dropping the temp table afterwards.
$timestamp = round(microtime(true)*1000);
$someNumber = rand(0, 1000);
$prefix = '';
if ($temporary)
@ -174,7 +174,7 @@ function GetTempTableName($table = '', $temporary = true)
if (strlen($table) == 0)
$table = 'php_test_table';
return $prefix . $table . '_' . $timestamp;
return $prefix . $table . '_' . $someNumber;
}
function GetTempProcName($proc = '', $temporary = true)
@ -183,7 +183,7 @@ function GetTempProcName($proc = '', $temporary = true)
// automatically dropped once the connection is closed. Otherwise,
// the caller should take care of dropping the temp procedure afterwards.
$timestamp = round(microtime(true)*1000);
$someNumber = rand(0, 1000);
$prefix = '';
if ($temporary)
@ -192,7 +192,7 @@ function GetTempProcName($proc = '', $temporary = true)
if (strlen($proc) == 0)
$proc = 'php_test_proc';
return $prefix . $proc . '_' . $timestamp;
return $prefix . $proc . '_' . $someNumber;
}
function ExecuteQuery($conn, $query)

View file

@ -1,63 +1,47 @@
--TEST--
SQLSRV Connection Pooling Test on Unix
--DESCRIPTION--
This test assumes odbcinst.ini has not been modified.
This test also requires root privileges to modify odbcinst.ini file on Linux.
This test assumes the default odbcinst.ini has not been modified.
--SKIPIF--
<?php if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') die("Skipped: Test for Linux and Mac"); ?>
--FILE--
<?php
// On Bamboo we must use sudo to fiddle with odbcinst.ini
// On travis-ci we can't use sudo
$sudo = '';
$user = posix_getpwuid(posix_geteuid());
if (strtolower($user['name']) != 'root')
{
$sudo = 'sudo ';
}
$lines_to_add="CPTimeout=5\n[ODBC]\nPooling=Yes\n";
//get odbcinst.ini location
//get default odbcinst.ini location
$lines = explode("\n", shell_exec("odbcinst -j"));
$odbcinst_ini = explode(" ", $lines[1])[1];
$custom_odbcinst_ini = dirname(__FILE__)."/odbcinst.ini";
//back up the odbcinst.ini file
shell_exec($sudo."cp $odbcinst_ini $odbcinst_ini.bak");
//copy the default odbcinst.ini into the current folder
copy( $odbcinst_ini, $custom_odbcinst_ini);
//enable pooling by modifying the odbcinst.ini file
$current = file_get_contents($odbcinst_ini);
$current = file_get_contents($custom_odbcinst_ini);
$current.=$lines_to_add;
shell_exec("cp $odbcinst_ini .");
file_put_contents("odbcinst.ini", $current);
shell_exec($sudo."cp odbcinst.ini $odbcinst_ini");
file_put_contents($custom_odbcinst_ini, $current);
//Creating a new php process, because for changes in odbcinst.ini file to affect pooling, drivers must be reloaded.
print_r(shell_exec(PHP_BINARY." ".dirname(__FILE__)."/isPooled.php"));
//Also setting the odbcini path to the current folder for the same process.
//This will let us modify odbcinst.ini without root permissions
print_r(shell_exec("export ODBCSYSINI=".dirname(__FILE__)."&&".PHP_BINARY." ".dirname(__FILE__)."/isPooled.php"));
//disable pooling by modifying the odbcinst.ini file
$current = file_get_contents($odbcinst_ini);
$current = file_get_contents($custom_odbcinst_ini);
$current = str_replace($lines_to_add,'',$current);
file_put_contents("odbcinst.ini", $current);
shell_exec($sudo."cp odbcinst.ini $odbcinst_ini");
file_put_contents($custom_odbcinst_ini, $current);
print_r(shell_exec(PHP_BINARY." ".dirname(__FILE__)."/isPooled.php"));
print_r(shell_exec("export ODBCSYSINI=".dirname(__FILE__)."&&".PHP_BINARY." ".dirname(__FILE__)."/isPooled.php"));
?>
--CLEAN--
<?php
$sudo = '';
$user = posix_getpwuid(posix_geteuid());
if (strtolower($user['name']) == 'bamboo')
{
$sudo = 'sudo ';
}
$custom_odbcinst_ini = dirname(__FILE__)."/odbcinst.ini";
unlink($custom_odbcinst_ini);
//back to default odbcinst.ini
shell_exec("unset ODBCSYSINI");
$lines = explode("\n", shell_exec("odbcinst -j"));
$odbcinst_ini = explode(" ", $lines[1])[1];
shell_exec($sudo."cp $odbcinst_ini.bak $odbcinst_ini");
shell_exec($sudo."rm $odbcinst_ini.bak");
?>
--EXPECT--
Pooled
Not Pooled

View file

@ -12,13 +12,7 @@ if( !$conn ) {
}
// Set database name
$dbUniqueName = "php_uniqueDB01";
// DROP database if exists
$stmt = sqlsrv_query($conn,"IF EXISTS(SELECT name FROM sys.databases WHERE name = '"
.$dbUniqueName."') DROP DATABASE ".$dbUniqueName);
if($stmt === false){ die( print_r( sqlsrv_errors(), true )); }
sqlsrv_free_stmt($stmt);
$dbUniqueName = "php_uniqueDB_" . rand(0, 1000);
// CREATE database
$stmt = sqlsrv_query($conn,"CREATE DATABASE ". $dbUniqueName);