source upload
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
/// minimal REST server for a list of Persons stored in SQlite3
|
||||
program RESTBenchmark;
|
||||
|
||||
{
|
||||
run the Server executable then e.g.
|
||||
- ab -n 1000 -c 100 http://localhost:8888/root/abc
|
||||
for latency measure (return the current timestamp as a few bytes)
|
||||
- ab -n 1000 -c 100 http://localhost:8888/root/xyz
|
||||
for bandwidth measure (returns some ORM query as 77KB of JSON)
|
||||
}
|
||||
{$ifndef UNIX}
|
||||
{$APPTYPE CONSOLE}
|
||||
{$endif}
|
||||
|
||||
{$I Synopse.inc} // LINUXNOTBSD
|
||||
|
||||
uses
|
||||
{$I SynDprUses.inc} // use FastMM4 on older Delphi, or set FPC threads
|
||||
SysUtils,
|
||||
SynCommons, // framework core
|
||||
SynTable,
|
||||
SynCrtSock, // direct access to HTTP server
|
||||
SynLog, // logging features
|
||||
mORMot, // RESTful server & ORM
|
||||
mORMotSQLite3, // SQLite3 engine as ORM core
|
||||
SynSQLite3Static, // staticaly linked SQLite3 engine
|
||||
{$ifdef UNIX}
|
||||
BaseUnix,
|
||||
{$endif}
|
||||
{$ifdef LINUXNOTBSD}
|
||||
SynSystemd,
|
||||
mORMotService,
|
||||
{$endif}
|
||||
mORMotHttpServer; // HTTP server for RESTful server
|
||||
|
||||
type
|
||||
TSQLPerson = class(TSQLRecordNoCase)
|
||||
private
|
||||
fFirstName: RawUTF8;
|
||||
fName: RawUTF8;
|
||||
fBirth: TDateTime;
|
||||
published
|
||||
property Name: RawUTF8 read fName write fName;
|
||||
property FirstName: RawUTF8 read fFirstName write fFirstName;
|
||||
property Birth: TDateTime read fBirth write fBirth;
|
||||
end;
|
||||
|
||||
TMyServices = class(TSynPersistent)
|
||||
protected
|
||||
fDb: TSQLRestServer;
|
||||
procedure PopulateWithRandom;
|
||||
public
|
||||
constructor Create(aDB: TSQLRestServer); reintroduce;
|
||||
published
|
||||
procedure abc(ctxt: TSQLRestServerURIContext);
|
||||
procedure xyz(ctxt: TSQLRestServerURIContext);
|
||||
end;
|
||||
|
||||
{ TMyServices }
|
||||
|
||||
constructor TMyServices.Create(aDB: TSQLRestServer);
|
||||
begin
|
||||
inherited Create;
|
||||
fDB := aDB;
|
||||
fDB.ServiceMethodRegisterPublishedMethods('', self); // root/abc and root/xyz
|
||||
if not fDb.TableHasRows(TSQLPerson) then
|
||||
PopulateWithRandom;
|
||||
end;
|
||||
|
||||
procedure TMyServices.PopulateWithRandom;
|
||||
var
|
||||
aBatch: TSQLRestBatch;
|
||||
aPerson: TSQLPerson;
|
||||
aTimer: TPrecisionTimer;
|
||||
i: integer;
|
||||
begin
|
||||
aTimer.Start;
|
||||
aPerson := TSQLPerson.Create;
|
||||
try
|
||||
aBatch := TSQLRestBatch.Create(fDB, TSQLPerson, 10000);
|
||||
try
|
||||
for i := 1 to 1000 do begin
|
||||
aPerson.Name := FormatUTF8('% name', [CardinalToHexShort(i * 777777)]);
|
||||
aPerson.FirstName := FormatUTF8('first %', [i]);
|
||||
aPerson.Birth := i + 40000;
|
||||
aBatch.Add(aPerson, true);
|
||||
end;
|
||||
fDb.BatchSend(aBatch);
|
||||
finally
|
||||
aBatch.Free;
|
||||
end;
|
||||
finally
|
||||
aPerson.Free;
|
||||
end;
|
||||
writeln('Created 1000 entries in ', aTimer.Stop);
|
||||
end;
|
||||
|
||||
procedure TMyServices.xyz(ctxt: TSQLRestServerURIContext);
|
||||
var
|
||||
s: RawUTF8;
|
||||
begin
|
||||
FormatUTF8('xyz %', [NowUTCToString], s);
|
||||
ctxt.Results([s]);
|
||||
end;
|
||||
|
||||
procedure TMyServices.abc(ctxt: TSQLRestServerURIContext);
|
||||
var
|
||||
s: RawUTF8;
|
||||
begin
|
||||
s := fDB.RetrieveListJSON(TSQLPerson, '', '', true);
|
||||
ctxt.Returns(s);
|
||||
end;
|
||||
|
||||
var
|
||||
url: AnsiString;
|
||||
keepAlive: boolean;
|
||||
aRestServer: TSQLRestServer;
|
||||
lastReadCount: TSynMonitorCount64;
|
||||
|
||||
procedure DoTest(const url: AnsiString; keepAlive: boolean);
|
||||
var
|
||||
aHttpServer: TSQLHttpServer;
|
||||
aServices: TMyServices;
|
||||
begin
|
||||
// create the main mORMot server
|
||||
aRestServer := TSQLRestServerDB.CreateWithOwnModel([TSQLPerson],'test.db',False,'root'); // authentication=false
|
||||
try
|
||||
// create tables or fields if missing
|
||||
aRestServer.CreateMissingTables;
|
||||
// initialize the services implementation class
|
||||
aServices := TMyServices.Create(aRestServer);
|
||||
try
|
||||
// serve aRestServer data over HTTP
|
||||
aHttpServer := TSQLHttpServer.Create(url,[aRestServer]);
|
||||
if not keepAlive and (aHttpServer.HttpServer is THttpServer) then
|
||||
THttpServer(aHttpServer.HttpServer).ServerKeepAliveTimeOut := 0;
|
||||
try
|
||||
aHttpServer.AccessControlAllowOrigin := '*'; // allow cross-site AJAX queries
|
||||
write('Background server is running on ', url, ' keepAlive ');
|
||||
if (keepAlive) then
|
||||
writeLn('is enabled') else
|
||||
writeLn('is disabled');
|
||||
{$ifdef LINUX}
|
||||
SynDaemonIntercept(nil);
|
||||
writeln('Press [Ctrl+C] or send SIGINT/SIGTERM to close the server.');
|
||||
fpPause;
|
||||
{$else}
|
||||
write('Press [Enter] to close the server.');
|
||||
readln;
|
||||
{$endif}
|
||||
finally
|
||||
aHttpServer.Free;
|
||||
end;
|
||||
finally
|
||||
aServices.Free;
|
||||
end;
|
||||
finally
|
||||
FreeAndNil(aRestServer);
|
||||
end;
|
||||
end;
|
||||
|
||||
const
|
||||
UNIX_SOCK_PATH = '/tmp/rest-bench.socket';
|
||||
|
||||
{$ifdef LINUX}
|
||||
/// killa process after X second without GEt requests
|
||||
function inactivityWatchdog(p: pointer): ptrint;
|
||||
var currentRC: TSynMonitorCount64;
|
||||
begin
|
||||
repeat
|
||||
sleep(10000); /// once per 10 second
|
||||
if aRestServer = nil then // not initialized
|
||||
continue;
|
||||
currentRC := aRestServer.Stats.Read;
|
||||
if (currentRC - lastReadCount) <= 0 then begin
|
||||
SQLite3Log.Add.Log(sllServer, 'Terminating due to inactivity..');
|
||||
FpKill(GetProcessID, SIGTERM);
|
||||
break;
|
||||
end;
|
||||
lastReadCount := currentRC;
|
||||
until false;
|
||||
Result := 0;
|
||||
end;
|
||||
{$endif}
|
||||
begin
|
||||
// set logging abilities
|
||||
SQLite3Log.Family.Level := LOG_VERBOSE;
|
||||
//SQLite3Log.Family.EchoToConsole := LOG_VERBOSE;
|
||||
SQLite3Log.Family.PerThreadLog := ptIdentifiedInOnFile;
|
||||
SQLite3Log.Family.NoFile := true; // do not create log files for benchmark
|
||||
{$ifdef UNIX}
|
||||
{$ifdef LINUXNOTBSD}
|
||||
if SynSystemd.ProcessIsStartedBySystemd then begin
|
||||
SQLite3Log.Family.EchoToConsole := SQLite3Log.Family.Level;
|
||||
SQLite3Log.Family.EchoToConsoleUseJournal := true;
|
||||
if sd.listen_fds(0) = 1 then
|
||||
url := '' // force to use socket passed by systemd
|
||||
else
|
||||
url := '8888';
|
||||
// set a wachdog to kill our process after 10 sec of inactivity
|
||||
// just for demo - in real life verifiing only read operations is not enought
|
||||
lastReadCount := 0;
|
||||
BeginThread(@inactivityWatchdog, nil);
|
||||
end else
|
||||
{$endif}
|
||||
if (ParamCount>0) and (ParamStr(1)='unix') then begin
|
||||
url := 'unix:' + UNIX_SOCK_PATH;
|
||||
if FileExists(UNIX_SOCK_PATH) then
|
||||
DeleteFile(UNIX_SOCK_PATH); // remove socket file
|
||||
end else
|
||||
{$endif}
|
||||
url := '8888';
|
||||
if (ParamCount>1) and (ParamStr(2)='false') then
|
||||
keepAlive := false else
|
||||
keepAlive := true;
|
||||
DoTest(url, keepAlive);
|
||||
end.
|
||||
|
Reference in New Issue
Block a user