unit PerfTestCases; interface {$I Synopse.inc} // enable/disable third-party libraries {.$define USENEXUSDB} {.$define USEBDE} {.$define USEUNIDAC} {.$define USEZEOS} {.$define USEFIREDAC} // enable/disable database engines {.$define USEJET} {.$define USEFIREBIRDEMB} {.$define ODBCSQLITEFIREBIRD} {.$define USELOCALMSSQLEXPRESS} // SQL Server 2008 R2 Express locally {.$define USELOCALDBMSSQLEXPRESS} // SQL Server 2012 LocalDB edition {.$define USELOCALDB2} {.$define USELOCALPOSTGRESQL} {.$define USELOCALMYSQL} {.$define USEMONGODB} {$ifdef CPU64} {$undef USENEXUSDB} // official NexusDB is not yet 64 bit ready :( {$undef USEJET} // MS Access / JET is not available under Win64 {$endif} // if defined, will create two "stored false" properties, to test UNIQUE columns {.$define UNIK} uses SysUtils, SynCommons, SynTable, SynLog, SynTests, mORMot, SynSQLite3, SynSQLite3Static, SynDB, SynDBSQLite3, SynDBOracle, SynDBPostgres, SynDBRemote, mORMotSQLite3, {$ifdef MSWINDOWS} SynOleDB, SynDBDataSet, {$endif} SynDBODBC, {$ifdef USENEXUSDB} SynDBNexusDB, {$endif} {$ifdef USEBDE} SynDBBDE, {$endif} {$ifdef USEUNIDAC} SynDBUniDAC, SQLiteUniProvider, InterbaseUniProvider, OracleUniProvider, DB2UniProvider, SQLServerUniProvider, PostgreSQLUniProvider, MySqlUniProvider, {$endif} {$ifdef USEZEOS} SynDBZeos, {$endif} {$ifdef USEFIREDAC} SynDBFireDAC, {$ifdef ISDELPHIXE5} FireDAC.Phys.Oracle, FireDAC.Phys.MSAcc, FireDAC.Phys.MSSQL, FireDAC.Phys.MySQL, FireDAC.Phys.SQLite, FireDAC.Phys.IB, FireDAC.Phys.PG, FireDAC.Phys.DB2, {$else} uADPhysOracle, uADPhysMSAcc, uADPhysMSSQL, uADPhysSQLite, uADPhysIB, uADPhysPG, uADPhysDB2, uADPhysMySQL, {$endif} {$endif} {$ifdef USEMONGODB} SynMongoDB, mORMotMongoDB, {$endif} mORMotDB; type TStat = class(TSynPersistent) private fCreateTable: RawUTF8; fNumberOfElements: integer; fInsertTime: RawUTF8; fEngine: RawUTF8; fClientCloseTime: RawUTF8; fInsertRate: integer; fReadOneByOneTime: RawUTF8; fReadOneByOneRate: integer; fInsertBatchTransactionRate: integer; fInsertTransactionRate: integer; fInsertBatchRate: integer; fInsertBatchTransactionTime: RawUTF8; fInsertTransactionTime: RawUTF8; fInsertBatchTime: RawUTF8; fReadAllVirtualRate: integer; fReadAllDirectRate: integer; fReadAllDirectTime: RawUTF8; fReadAllVirtualTime: RawUTF8; {$ifdef UNIK} fReadOneByNameRate: integer; fReadOneByNameTime: RawUTF8; {$endif} published property Engine: RawUTF8 read fEngine; property CreateTableTime: RawUTF8 read fCreateTable write fCreateTable; property NumberOfElements: integer read fNumberOfElements write fNumberOfElements; property InsertTime: RawUTF8 read fInsertTime; property InsertRate: integer read fInsertRate; property InsertBatchTime: RawUTF8 read fInsertBatchTime; property InsertBatchRate: integer read fInsertBatchRate; property InsertTransactionTime: RawUTF8 read fInsertTransactionTime; property InsertTransactionRate: integer read fInsertTransactionRate; property InsertBatchTransactionTime: RawUTF8 read fInsertBatchTransactionTime; property InsertBatchTransactionRate: integer read fInsertBatchTransactionRate; property ReadOneByOneTime: RawUTF8 read fReadOneByOneTime; property ReadOneByOneRate: integer read fReadOneByOneRate; {$ifdef UNIK} property ReadOneByNameTime: RawUTF8 read fReadOneByNameTime; property ReadOneByNameRate: integer read fReadOneByNameRate; {$endif} property ReadAllVirtualTime: RawUTF8 read fReadAllVirtualTime; property ReadAllVirtualRate: integer read fReadAllVirtualRate; property ReadAllDirectTime: RawUTF8 read fReadAllDirectTime; property ReadAllDirectRate: integer read fReadAllDirectRate; property ClientCloseTime: RawUTF8 read fClientCloseTime; end; TSQLRecordSample = class(TSQLRecord) private fFirstName: RawUTF8; fLastName: RawUTF8; fAmount: Currency; fBirthDate: TDateTime; fLastChange: TModTime; fCreatedAt: TCreateTime; published property FirstName: RawUTF8 index 40 read fFirstName write fFirstName {$ifdef UNIK}stored AS_UNIQUE{$endif}; property LastName: RawUTF8 index 40 read fLastName write fLastName {$ifdef UNIK}stored AS_UNIQUE{$endif}; property Amount: Currency read fAmount write fAmount; property BirthDate: TDateTime read fBirthDate write fBirthDate; property LastChange: TModTime read fLastChange write fLastChange; property CreatedAt: TCreateTime read fCreatedAt write fCreatedAt; end; TTestDatabaseBenchmark = class(TSynTestsLogged) public Ini: RawUTF8; Stats: TSynObjectList; constructor Create(const Ident: string = ''); override; destructor Destroy; override; procedure SaveStats; published procedure DirectDatabaseAccess; procedure ExternalDatabaseAccess; end; TTestDatabaseAbstract = class(TSynTestCase) protected Main: TTestDatabaseBenchmark; Value: TSQLRecordSample; Stat: TStat; Namee, Num: RawUTF8; ChangeStart: TTimeLog; RunTimer: TPrecisionTimer; SQlite3Mode: TSQLSynchronousMode; SQlite3Lock: TSQLLockingMode; Client: TSQLRestClientDB; DBFileName: TFileName; DBPassword: RawUTF8; ValueLastName, ValueFirstName: TRawUTF8DynArray; Res: TIDDynArray; Flags: set of (dbIsFile, dbInMemory, dbInMemoryVirtual, dbPropIsMemory, dbPropUntouched, dbDropTable); procedure Setup; override; procedure Cleanup; override; procedure MethodSetup; override; procedure MethodCleanup; override; procedure RunTests; virtual; function ModelCreate: TSQLModel; virtual; procedure ClientCreate; virtual; procedure ClientFree; virtual; procedure RunWrites(UseTransactions, UseBatch: boolean); virtual; procedure ValueCheck(i: PtrInt); procedure RunModeLock(Mode: TSQLSynchronousMode; Lock: TSQLLockingMode); virtual; end; TTestDirectSqliteEngine = class(TTestDatabaseAbstract) protected procedure RunModeLock(Mode: TSQLSynchronousMode; Lock: TSQLLockingMode); override; published procedure SqliteFileFull; procedure SqliteFileOff; procedure SqliteFileOffExc; procedure SqliteFileOffExcAes; procedure SqliteInMemory; end; TTestInMemoryEngine = class(TTestDatabaseAbstract) protected function ModelCreate: TSQLModel; override; procedure ClientCreate; override; published procedure InMemoryStatic; procedure InMemoryVirtual; end; TTestDatabaseExternalAbstract = class(TTestDatabaseAbstract) protected Props: TSQLDBConnectionProperties; function ModelCreate: TSQLModel; override; procedure ClientCreate; override; procedure ClientFree; override; procedure RunExternal(P: TSQLDBConnectionProperties); virtual; public end; TTestSqliteExternal = class(TTestDatabaseExternalAbstract) protected procedure ClientCreate; override; published procedure ExternalSqliteFileFull; procedure ExternalSqliteFileOff; procedure ExternalSqliteFileOffExc; procedure ExternalSqliteInMemory; end; TTestSqliteRemote = class(TTestDatabaseExternalAbstract) protected RemoteProps: TSQLDBSQLite3ConnectionProperties; RemoteServer: TSQLDBServerRemote; RemoteClient: TSQLDBConnectionPropertiesClass; procedure ClientCreate; override; procedure ClientFree; override; published {$ifdef MSWINDOWS} procedure RemoteSqliteWinHTTP; {$endif} procedure RemoteSqliteSocket; end; {$ifdef USELOCALPOSTGRESQL} TTestPostgresql = class(TTestDatabaseExternalAbstract) published procedure _SynDBPostgres; {$ifdef USEZEOS} procedure ZeosPostgres; {$endif} end; {$endif USELOCALPOSTGRESQL} implementation { TTestDatabaseAbstract } procedure TTestDatabaseAbstract.Setup; begin EnsureDirectoryExists(ExeVersion.ProgramFilePath + 'db'); Main := Owner as TTestDatabaseBenchmark; Value := TSQLRecordSample.Create; Namee := 'Name/ '; UniqueRawUTF8(Namee); // FPC does not call it PWord(@Namee[4])^ := $a9c3; // some 'e'acute to test UTF-8 encoding SQlite3Mode := smOff; // fastest mode by default SQlite3Lock := lmExclusive; end; procedure TTestDatabaseAbstract.Cleanup; begin // warning: this method could be called several times after a single Setup FreeAndNil(Value); end; procedure TTestDatabaseAbstract.MethodSetup; begin Flags := []; Stat := TStat.Create; Stat.fEngine := ToUTF8(Owner.CurrentMethodInfo^.TestName); end; procedure TTestDatabaseAbstract.MethodCleanup; begin if Stat.Engine <> '' then Main.Stats.Add(Stat) else FreeAndNil(Stat); ClientFree; end; procedure TTestDatabaseAbstract.RunTests; const _ID: array['1'..'4'] of RawUTF8 = ('', ' batch', ' trans', ' batch trans'); var trans, batch, UseDirect: boolean; time: RawUTF8; rate: QWord; i: PtrInt; log: ISynLog; begin // Insertion tests Num := '1'; for trans := false to true do for batch := false to true do begin log := TSynLog.Enter('% Insert%',[Owner.CurrentMethodInfo^.IdentTestName, _ID[Num[1]]],self); RunWrites(trans, batch); NotifyTestSpeed('insert%',[_ID[Num[1]]],Stat.NumberOfElements,0,@RunTimer); time := RunTimer.LastTime; rate := RunTimer.PerSec(Stat.NumberOfElements); log := nil; case Num[1] of '1': begin Stat.fInsertTime := time; Stat.fInsertRate := rate; end; '2': begin Stat.fInsertBatchTime := time; Stat.fInsertBatchRate := rate; end; '3': begin Stat.fInsertTransactionTime := time; Stat.fInsertTransactionRate := rate; end; '4': begin Stat.fInsertBatchTransactionTime := time; Stat.fInsertBatchTransactionRate := rate; break; // still need Client for Read tests end; end; inc(Num[1]); ClientFree; end; // Read tests log := TSynLog.Enter('% Read One',[Owner.CurrentMethodInfo^.IdentTestName],self); Value.ClearProperties; RunTimer.Start; for i := 0 to Stat.NumberOfElements-1 do begin Client.Retrieve(Res[i],Value); ValueCheck(i); end; NotifyTestSpeed('read one',Stat.NumberOfElements,0,@RunTimer); Stat.fReadOneByOneTime := RunTimer.LastTime; Stat.fReadOneByOneRate := RunTimer.PerSec(Stat.NumberOfElements); log := nil; {$ifdef UNIK} // one by one retrieve values using Name property log := TSynLog.Enter('% Read Unique',[Owner.CurrentMethodInfo^.IdentTestName],self); RunTimer.Start; for i := 0 to Stat.NumberOfElements-1 do begin Client.Retrieve('LastName=?',[],[ValueLastName[i]],Value); Check((Value.IDValue=Res[i]) and (PInt64(@Value.Amount)^=(i+1)*100) and (Value.LastChange>=ChangeStart)); end; NotifyTestSpeed('read unique',Stat.NumberOfElements,0,@RunTimer); Stat.fReadOneByNameTime := RunTimer.LastTime; Stat.fReadOneByNameRate := RunTimer.PerSec(Stat.NumberOfElements); {$endif UNIK} // retrieve all rows with or without the virtual module for UseDirect := false to true do begin log := nil; log := TSynLog.Enter('% Read Direct=%', [Owner.CurrentMethodInfo^.IdentTestName, BOOL_STR[UseDirect]],self); with Client.Server do begin Cache.Flush; // fair benchmark DB.CacheFlush; // fair benchmark (16100 rows/s->456000 with cache!) StaticVirtualTableDirect := UseDirect; end; RunTimer.Start; Value.ClearProperties; Check(Value.FillPrepare(Client,'order by RowId'), 'FillPrepare'); //FileFromString((Value.FillTable as TSQLTableJSON).PrivateInternalCopy,Stat.Engine+'.json'); i := 0; while Value.FillOne do begin ValueCheck(i); inc(i); end; CheckEqual(i,Stat.NumberOfElements,'FillOne'); if UseDirect then begin NotifyTestSpeed('read direct',Stat.NumberOfElements,0,@RunTimer); Stat.fReadAllDirectTime := RunTimer.LastTime; Stat.fReadAllDirectRate := RunTimer.PerSec(Stat.NumberOfElements); end else begin NotifyTestSpeed('read virtual',Stat.NumberOfElements,0,@RunTimer); Stat.fReadAllVirtualTime := RunTimer.LastTime; Stat.fReadAllVirtualRate := RunTimer.PerSec(Stat.NumberOfElements); end; end; end; function TTestDatabaseAbstract.ModelCreate: TSQLModel; begin result := TSQLModel.Create([TSQLRecordSample]); end; procedure TTestDatabaseAbstract.ClientCreate; var fn: TFileName; begin if dbIsFile in Flags then fn := DBFileName else fn := SQLITE_MEMORY_DATABASE_NAME; Client := TSQLRestClientDB.Create(ModelCreate, nil, fn, TSQLRestServerDB, {auth=}false, DBPassword); Client.Model.Owner := Client; Client.Server.DB.Synchronous := SQlite3Mode; Client.Server.DB.LockingMode := SQlite3Lock; end; procedure TTestDatabaseAbstract.ClientFree; begin FreeAndNil(Client); end; procedure TTestDatabaseAbstract.RunWrites(UseTransactions, UseBatch: boolean); var i: PtrInt; forceID: boolean; begin DBFileName := FormatString('%db%%.%.db',[ExeVersion.ProgramFilePath, PathDelim, Owner.CurrentMethodInfo^.MethodName, Num]); if FileExists(DBFileName) then DeleteFile(DBFileName); RunTimer.Start; ClientCreate; if CheckFailed(Client <> nil,'Client?') then exit; // avoid GPF Client.Server.CreateMissingTables; ChangeStart := Client.ServerTimestamp; // use by ValueCheck if Stat.CreateTableTime='' then Stat.CreateTableTime := RunTimer.Stop; if (SQlite3Mode = smFull) and not UseTransactions then // full synch is slow Stat.NumberOfElements := 500 else Stat.NumberOfElements := 5000; SetLength(ValueLastName, Stat.NumberOfElements); SetLength(ValueFirstName, Stat.NumberOfElements); for i := 0 to Stat.NumberOfElements - 1 do if ValueLastName[i] = '' then begin UInt32ToUtf8(i + 1, ValueLastName[i]); {$ifndef UNIK} if i <> 100 then // test https://synopse.info/fossil/info/e8c211062e {$endif} ValueFirstName[i] := Namee + ValueLastName[i]; end; RunTimer.Start; if UseTransactions then Client.TransactionBegin(TSQLRecordSample); if UseBatch then Client.BatchStart(TSQLRecordSample) else if length(Res)=0) else begin Res[i] := Client.Add(Value,true,forceID); Check(Res[i]>0,'Add'); end; end; if UseBatch then Check(Client.BatchSend(Res)=HTTP_SUCCESS); if UseTransactions then Client.Commit; Value.ClearProperties; Check(Client.Retrieve(Res[1],Value),'One Retrieve after Add'); ValueCheck(1); end; procedure TTestDatabaseAbstract.ValueCheck(i: PtrInt); begin CheckEqual(Value.IDValue, Res[i], 'ID'); CheckEqual(PInt64(@Value.Amount)^, (i+1)*100, 'Amount'); Check(Value.LastChange >= ChangeStart, 'LastChange'); CheckEqual(Value.FirstName, ValueFirstName[i], 'FirstName'); Value.IDValue := 0; Value.Amount := 0; Value.FirstName := ''; Value.LastChange := 0; end; procedure TTestDatabaseAbstract.RunModeLock(Mode: TSQLSynchronousMode; Lock: TSQLLockingMode); begin SQlite3Mode := Mode; SQlite3Lock := Lock; RunTests; end; { TTestDirectSqliteEngine } procedure TTestDirectSqliteEngine.RunModeLock(Mode: TSQLSynchronousMode; Lock: TSQLLockingMode); begin Flags := [dbIsFile]; inherited RunModeLock(Mode, Lock); end; procedure TTestDirectSqliteEngine.SqliteFileFull; begin RunModeLock(smFull, lmNormal); end; procedure TTestDirectSqliteEngine.SqliteFileOff; begin RunModeLock(smOff, lmNormal); end; procedure TTestDirectSqliteEngine.SqliteFileOffExc; begin RunModeLock(smOff, lmExclusive); end; procedure TTestDirectSqliteEngine.SqliteFileOffExcAes; begin DBPassword := 'password'; RunModeLock(smOff, lmExclusive); DBPassword := ''; end; procedure TTestDirectSqliteEngine.SqliteInMemory; begin Flags := []; RunTests; end; { TTestInMemoryEngine } function TTestInMemoryEngine.ModelCreate: TSQLModel; begin result := inherited ModelCreate; // registration should be done BEFORE Client is initialized if dbInMemoryVirtual in Flags then result.VirtualTableRegister(TSQLRecordSample, TSQLVirtualTableBinary) end; procedure TTestInMemoryEngine.ClientCreate; begin inherited ClientCreate; if not (dbInMemoryVirtual in Flags) then Client.Server.StaticDataCreate(TSQLRecordSample, DBFileName, {binary=}true); end; procedure TTestInMemoryEngine.InMemoryStatic; begin Flags := [dbInMemory]; RunTests; end; procedure TTestInMemoryEngine.InMemoryVirtual; begin Flags := [dbInMemory, dbInMemoryVirtual]; RunTests; end; { TTestDatabaseExternalAbstract } function TTestDatabaseExternalAbstract.ModelCreate: TSQLModel; begin result := inherited ModelCreate; // registration should be done BEFORE Client is initialized VirtualTableExternalRegister(result, TSQLRecordSample, Props, 'SampleRecord'); end; procedure TTestDatabaseExternalAbstract.ClientCreate; begin if (Props <> nil) and (dbDropTable in Flags) then Props.ExecuteNoResult('drop table SAMPLERECORD', []); inherited ClientCreate; end; procedure TTestDatabaseExternalAbstract.ClientFree; begin inherited ClientFree; if not(dbPropUntouched in Flags) then FreeAndNil(Props); end; procedure TTestDatabaseExternalAbstract.RunExternal(P: TSQLDBConnectionProperties); begin Flags := [dbPropUntouched, dbDropTable]; Props := P; try Props.ThreadSafeConnection.Connect; Check(Props.ThreadSafeConnection.Connected, 'connected'); RunTests; finally FreeAndNil(Props); end; end; { TTestSqliteExternal } procedure TTestSqliteExternal.ClientCreate; begin if dbPropIsMemory in Flags then DBFileName := SQLITE_MEMORY_DATABASE_NAME; if not (dbPropUntouched in Flags) then Props := TSQLDBSQLite3ConnectionProperties.Create(DBFileName, '', '', ''); with TSQLDBSQLite3Connection(Props.MainConnection) do begin Synchronous := SQlite3Mode; LockingMode := SQlite3Lock; end; inherited ClientCreate; end; procedure TTestSqliteExternal.ExternalSqliteFileFull; begin RunModeLock(smFull, lmNormal); end; procedure TTestSqliteExternal.ExternalSqliteFileOff; begin RunModeLock(smOff, lmNormal); end; procedure TTestSqliteExternal.ExternalSqliteFileOffExc; begin RunModeLock(smOff, lmExclusive); end; procedure TTestSqliteExternal.ExternalSqliteInMemory; begin Flags := [dbPropIsMemory]; RunTests; end; { TTestSqliteRemote } procedure TTestSqliteRemote.ClientCreate; begin RemoteProps := TSQLDBSQLite3ConnectionProperties.Create(SQLITE_MEMORY_DATABASE_NAME, '', '', ''); RemoteProps.MainSQLite3DB.Synchronous := SQlite3Mode; RemoteProps.MainSQLite3DB.LockingMode := SQlite3Lock; RemoteServer := TSQLDBServerRemote.Create(RemoteProps, 'root', '8888', 'user', 'password'); Props := RemoteClient.Create('localhost:8888', 'root', 'user', 'password'); inherited ClientCreate; end; procedure TTestSqliteRemote.ClientFree; begin inherited ClientFree; RemoteServer.Free; RemoteProps.Free; end; procedure TTestSqliteRemote.RemoteSqliteSocket; begin RemoteClient := TSQLDBSocketConnectionProperties; RunTests; end; {$ifdef MSWINDOWS} procedure TTestSqliteRemote.RemoteSqliteWinHTTP; begin RemoteClient := TSQLDBWinHTTPConnectionProperties; RunTests; end; {$endif MSWINDOWS} {$ifdef USELOCALPOSTGRESQL} { TTestPostgresql } procedure TTestPostgresql._SynDBPostgres; begin RunExternal(TSQLDBPostgresConnectionProperties.Create( 'localhost','postgres','postgres','docker')); end; {$ifdef USEZEOS} procedure TTestPostgresql.ZeosPostgres; begin RunExternal(TSQLDBZEOSConnectionProperties.Create(TSQLDBZEOSConnectionProperties.URI( dPostgreSQL,'localhost','libpq.so.5',false),'postgres','postgres','docker')); end; {$endif} {$endif USELOCALPOSTGRESQL} { TTestDatabaseBenchmark } constructor TTestDatabaseBenchmark.Create(const Ident: string); var fn: TFileName; begin Stats := TSynObjectList.Create; fn := ChangeFileExt(ExeVersion.ProgramFileName, '.ini'); if FileExists(fn) then Ini := StringFromFile(fn) else FileFromString('', fn); inherited Create(Ident); end; destructor TTestDatabaseBenchmark.Destroy; begin inherited Destroy; SaveStats; Stats.Free; end; procedure TTestDatabaseBenchmark.SaveStats; type TStatArray = array[0..1000] of TStat; var Stat: ^TStatArray; mode,s,txt: RawUTF8; m,nCat, col1len: integer; max,Cat1,Cat2,Eng1,Eng2,Eng: RawUTF8; Rows: TRawUTF8DynArray; Doc, Cons: RawUTF8; procedure SetCategories(const Title: RawUTF8; const Cat: array of RawUTF8); var i: integer; begin mode := UrlEncode(Title); s := s+'

'+copy(Title,1,posEx(' (',Title)-1)+'

'#13#10; max := Int32ToUtf8(m); nCat := length(Cat); Cat1 := ''; Cat2 := ''; SetLength(Rows,Stats.Count+1); Rows[0] := ' '; cons := cons+#13#10+Title+#13#10+StringOfChar(' ',col1len+2); for i := 0 to high(Cat) do begin Rows[0] := Rows[0]+''+Cat[i]+''; Cat1 := Cat1+UrlEncode(Cat[i])+'|'; Cat2 := Cat2+UrlEncode(Cat[high(Cat)-i])+'|'; cons := cons+Cat[i]; if i<>high(Cat) then cons := cons+StringOfChar(' ',12-length(Cat[i])); end; cons := cons+#13#10; SetLength(Cat1,length(Cat1)-1); SetLength(Cat2,length(Cat2)-1); Eng1 := ''; Eng2 := ''; for i := 0 to Stats.Count-1 do begin Eng := Stat[i].Engine; { j := PosEx(' ',Eng); if j>0 then begin Delete(Eng,j,1); insert('
',Eng,j); end;} Rows[i+1] := ''+Eng+''; Eng1 := Eng1+UrlEncode(Stat[i].Engine)+'|'; Eng2 := Eng2+UrlEncode(Stat[Stats.Count-1-i].Engine)+'|'; end; SetLength(Eng1,length(Eng1)-1); SetLength(Eng2,length(Eng2)-1); end; procedure Pic1(const Leg: RawUTF8; n: integer); var i: integer; begin txt := 'http://chart.apis.google.com/chart?chtt='+mode+'&chxl=1:|'+Leg+ '&chxt=x,y&chbh=a&chs=600x500&cht=bhg&chco='; // for i := 1 to 5 do txt := txt+IntToHex($309F30+i*$010101,3)+','; // txt[length(txt)] := '&'; // '3D7930,3D8930,309F30,6070F0,5070E0,40C355,65D055,80C1A2,F05050,F0A280'+ txt := txt+'3D7930,3D8930,309F30,40C355&';//,6070F0,5070E0,65D055,80C1A2,3D7930,3D8930,F05050,F04050,F04040,F01040,F0A280&'; txt := txt+'chxr=0,0,'+max+'&chds='; for i := 1 to n do txt := txt+'0,'+max+','; txt[length(txt)] := '&'; txt := txt+'chd=t:'; end; procedure PicEnd(const Legend: RawUTF8); begin txt[length(txt)] := '&'; s := s+'

'#13#10; txt := ''; end; procedure SetValues(var Rows: RawUTF8; const eng: RawUTF8; const v: array of const); var j: integer; fmt,s: RawUTF8; begin for j := 2 to length(v) do fmt := fmt+'%,'; fmt := fmt+'%|'; txt := txt+FormatUTF8(fmt,v); fmt := ''; for j := 1 to length(v) do fmt := fmt+'%'; Rows := Rows+FormatUTF8(fmt,v); fmt := eng+StringOfChar(' ',col1len-length(eng)+2); for j := 0 to high(v) do begin VarRecToUTF8(v[j],s); if j<>high(v) then s := s+StringOfChar(' ',12-length(s)); fmt := fmt+s; end; cons := cons+fmt+#13#10; end; procedure Table; var i: integer; begin s := s+'

'; for i := 0 to High(Rows) do s := s+''+Rows[i]+''#13#10; s := s+'

'; Doc := Doc+'|%30'; for i := 1 to nCat do Doc := Doc+'%15'; Doc := Doc+#13#10; for i := 0 to High(Rows) do begin Doc := Doc+StringReplaceAll(Rows[i], ['','', '','', '','', '','}', '','', '','{\b ', '','|', ' ',''])+#13#10; end; Doc := Doc+'|%'#13#10; end; var i,j: integer; begin // introducting text Stat := pointer(Stats.List); s := FormatUTF8('Running tests using Synopse mORMot framework %, '+ 'compiled with %, against SQLite %, on %, at %.', [SYNOPSE_FRAMEWORK_VERSION, GetDelphiCompilerVersion, SQLite3.libversion, OSVersionText, NowToString]); cons := '[code]'#13#10+s+#13#10#13#10; s := '

'+s+'

'; // compute max Insertion rate value for charts m := 0; col1len := 0; for i := 0 to Stats.Count-1 do with Stat[i] do begin if InsertRate>m then m := InsertRate; if InsertBatchRate>m then m := InsertBatchRate; if InsertTransactionRate>m then m := InsertTransactionRate; if InsertBatchTransactionRate>m then m := InsertBatchTransactionRate; j := length(Engine); if j>col1len then col1len := j; end; // Insertion Categories SetCategories('Insertion speed (rows/second)',['Direct','Batch','Trans','Batch Trans']); // Insertion per-Engine Values and Chart Pic1(Cat2,5); for i := 0 to Stats.Count-1 do with Stat[i] do SetValues(Rows[i+1], Engine, [InsertRate,InsertBatchRate,InsertTransactionRate,InsertBatchTransactionRate]); Table; PicEnd(Eng1); // Insertion per-Category Chart Pic1(Eng2,Stats.Count); for i := 0 to Stats.Count-1 do txt := txt+Int32ToUtf8(Stat[i].InsertRate)+','; txt[length(txt)] := '|'; for i := 0 to Stats.Count-1 do txt := txt+Int32ToUtf8(Stat[i].InsertBatchRate)+','; txt[length(txt)] := '|'; for i := 0 to Stats.Count-1 do txt := txt+Int32ToUtf8(Stat[i].InsertTransactionRate)+','; txt[length(txt)] := '|'; for i := 0 to Stats.Count-1 do txt := txt+Int32ToUtf8(Stat[i].InsertBatchTransactionRate)+','; PicEnd(Cat1); // compute max Reading rate value for charts m := 0; for i := 0 to Stats.Count-1 do with Stat[i] do begin if ReadOneByOneRate>m then m := ReadOneByOneRate; {$ifdef UNIK} if ReadOneByNameRate>m then m := ReadOneByNameRate; {$endif} if ReadAllVirtualRate>m then m := ReadAllVirtualRate; if ReadAllDirectRate>m then m := ReadAllDirectRate; end; // Reading Categories SetCategories('Read speed (rows/second)',['By one', {$ifdef UNIK}'By name',{$endif}'All Virtual','All Direct']); // Reading per-Engine Values and Chart Pic1(Cat2,{$ifdef UNIK}4{$else}3{$endif}); for i := 0 to Stats.Count-1 do with Stat[i] do SetValues(Rows[i+1], Engine, [ReadOneByOneRate,{$ifdef UNIK}ReadOneByNameRate,{$endif} ReadAllVirtualRate,ReadAllDirectRate]); Table; PicEnd(Eng1); // Reading per-Category Chart Pic1(Eng2,Stats.Count); for i := 0 to Stats.Count-1 do txt := txt+Int32ToUtf8(Stat[i].ReadOneByOneRate)+','; txt[length(txt)] := '|'; {$ifdef UNIK} for i := 0 to Stats.Count-1 do txt := txt+Int32ToUtf8(Stat[i].ReadOneByNameRate)+','; txt[length(txt)] := '|'; {$endif} for i := 0 to Stats.Count-1 do txt := txt+Int32ToUtf8(Stat[i].ReadAllVirtualRate)+','; txt[length(txt)] := '|'; for i := 0 to Stats.Count-1 do txt := txt+Int32ToUtf8(Stat[i].ReadAllDirectRate)+','; PicEnd(Cat1); // save to local files FileFromString(Doc,ChangeFileExt(ExeVersion.ProgramFileName,'.doc')); FileFromString(cons+'[/code]',ChangeFileExt(ExeVersion.ProgramFileName,'.txt')); FileFromString(''#13#10+s,ChangeFileExt(ExeVersion.ProgramFileName,'.htm')); end; procedure TTestDatabaseBenchmark.DirectDatabaseAccess; begin //exit; AddCase(TTestDirectSqliteEngine); AddCase(TTestInMemoryEngine); end; procedure TTestDatabaseBenchmark.ExternalDatabaseAccess; begin //exit; AddCase(TTestSqliteExternal); AddCase(TTestSqliteRemote); {$ifdef USELOCALPOSTGRESQL} AddCase(TTestPostgresql); {$endif} end; end.