source upload

This commit is contained in:
Razor12911
2022-01-17 22:16:47 +02:00
parent 12936d065b
commit 098e8c48de
1778 changed files with 1206749 additions and 0 deletions

View File

@@ -0,0 +1,113 @@
# Simple mORMot server for REST benchmark
## Socket based server
- compile and run RESTBenchmark
- test it with browser:
- http://localhost:8888/root/abc
- http://localhost:8888/root/xyz
- test it with Apache Bench
```
ab -n 10000 -c 1000 http://localhost:8888/root/abc
```
## Keep alive
By default mROMot HTTP server runs in KeepAlive mode.
To disable KeepAlive run `RESTBenchmark` with secont parameter `false`
```
./RESTBenchmark 8888 false
```
Disabling KeepAlive make sence in case mORMotserver is behind the reverse proxy.
In this case reverse proxy cares about KeepAlive connection pool and mormot can
operate with fixed thread pool size.
## Unix Domain Socket (Linux)
### When to use
In case mORMot is behind a local reverse proxy on the environment with a
huge number of incoming connection it's make sence to use a UDS to minimize
unnecessary TCP handshakes between mORMot and reverse proxy.
To emulate such environment on the syntetic test we can disable keep alive
in RESTBEnchmark by passing `false` to then second parameter
```
./RESTBenchmark unix false
./RESTBenchmark 8888 false
```
### How to run
- compile a program and run with `unix` parameter
```
./RESTBenchmark unix
```
Benchmark will listen on Unix Domain Socket `/tmp/rest-bench.socket`
- test it with curl
```
curl --unix-socket /tmp/rest-bench.socket http://localhost/root/abc
```
- setup nginx as a reverse proxy
```
sudo ln -s "$(pwd)/mormot-rest-nginx.conf" /etc/nginx/sites-available
sudo ln -s /etc/nginx/sites-available/mormot-rest-nginx.conf /etc/nginx/sites-enabled
sudo nginx -s reload
```
- test it using ab (or better - wrk)
```
wrk http://localhost:8888/root/abc
```
## systemd integration
- socket activation
- journald logging (can be exported for LogView program)
- auto shutdown on inactivity
With combination of [Systemd Template Unit](https://fedoramagazine.org/systemd-template-unit-files/), `* A` DNS zone and
nginx virtual hosts we can build a "cloud solution" using mORMot services with per-customer process level isolation.
### Install Unit
Add our service to systemd (should be executed once).
This command activate `rest_benchmark` socket on port 8889
```bash
sudo ./installSystemSocket.sh
```
### Socket activation, auto shutdown and logs
Kill possible instances of RESTBenchmark program
```bash
killall -TERM RESTBenchmark
```
Now program is **NOT running**, lets send an HTTP request to a port configured in `rest_benchmark.socket` unit (8889)
```bash
curl http://localhost:8889/root/xyz
```
Magic - we got a response :) This is how systemd socket activation works.
Inside our demo program, in case it executed by systemd it:
- log all activity into journald (see EchoToConsoleUseJournal)
- stops after 10 seconds without GET requests (see inactivityWatchdog in RESTBenchmark.dpr)
Open new terminal window and run a command to watch logs from `rest_benchmark` services
```bash
journalctl -u rest_benchmark.service -f
```
Now wait for 10 second - service should shut down itself.
### journald and LogView (Samples/11 - Exception logging)
Logs from journald can be exported to format LogView understand. Example:
```bash
journalctl -u rest_benchmark.service --no-hostname -o short-iso-precise --since today | grep "RESTBenchmark\[.*\]: . " > todaysLog.log
```
better to grep by program PID `"RESTBenchmark\[12345]: . "` to include only one start/stop circle (on 2020-06 LogView do not allow to filter on PIDs)

View File

@@ -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.

View File

@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<CONFIG>
<ProjectOptions>
<Version Value="11"/>
<General>
<Flags>
<MainUnitHasCreateFormStatements Value="False"/>
<MainUnitHasTitleStatement Value="False"/>
<MainUnitHasScaledStatement Value="False"/>
</Flags>
<SessionStorage Value="InProjectDir"/>
<MainUnit Value="0"/>
<Title Value="RESTBenchmark"/>
<UseAppBundle Value="False"/>
<ResourceType Value="res"/>
</General>
<BuildModes Count="1">
<Item1 Name="Default" Default="True"/>
</BuildModes>
<PublishOptions>
<Version Value="2"/>
<UseFileFilters Value="True"/>
</PublishOptions>
<RunParams>
<FormatVersion Value="2"/>
<Modes Count="0"/>
</RunParams>
<Units Count="1">
<Unit0>
<Filename Value="RESTBenchmark.dpr"/>
<IsPartOfProject Value="True"/>
</Unit0>
</Units>
</ProjectOptions>
<CompilerOptions>
<Version Value="11"/>
<Target>
<Filename Value="RESTBenchmark"/>
</Target>
<SearchPaths>
<IncludeFiles Value="../..;../../..;$(ProjOutDir;$(ProjOutDir)"/>
<Libraries Value="../../../static/$(TargetCPU)-$(TargetOS)"/>
<OtherUnitFiles Value="../..;../../.."/>
<UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
</SearchPaths>
</CompilerOptions>
<Debugging>
<Exceptions Count="1">
<Item1>
<Name Value="Unknown"/>
<Enabled Value="False"/>
</Item1>
</Exceptions>
</Debugging>
</CONFIG>

View File

@@ -0,0 +1,28 @@
unit RESTModel;
interface
uses
SynCommons,
SynTable, // for TSynValidateText
mORMot;
type
TPerson = class(TSQLRecord) // TSQLRecord has already ID: integer primary key
private
fName: RawUTF8;
published
/// ORM will create a NAME VARCHAR(80) column
property Name: RawUTF8 index 80 read fName write fName;
end;
const
SERVER_ROOT = 'root';
SERVER_PORT = '888';
implementation
end.

View File

@@ -0,0 +1,5 @@
ln -s "`pwd`/rest_benchmark.socket" /etc/systemd/system
ln -s "`pwd`/rest_benchmark.service" /etc/systemd/system
systemctl enable rest_benchmark.socket
systemctl start rest_benchmark.socket
systemctl status rest_benchmark.socket #output status to console

View File

@@ -0,0 +1,59 @@
upstream mormot-uds {
server unix:/tmp/rest-bench.socket;
keepalive 32;
}
upstream mormot-sock {
server localhost:8888;
keepalive 32;
}
server {
listen 8889;
server_name localhost;
access_log /dev/null;
# prevent nginx version exposing in Server header
server_tokens off;
proxy_set_header Host $host;
# Tell upstream real IP address of client
proxy_set_header X-Real-IP $realip_remote_addr;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Do not rewrite a URL while pass it to upstream
proxy_redirect off;
# Let's upstream handle errors
proxy_intercept_errors on;
tcp_nodelay on;
# proxy all requests to the beckend
location / {
proxy_pass http://mormot-sock;
}
}
server {
listen 8887;
server_name localhost;
access_log /dev/null;
# prevent nginx version exposing in Server header
server_tokens off;
proxy_set_header Host $host;
# Tell upstream real IP address of client
proxy_set_header X-Real-IP $realip_remote_addr;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Do not rewrite a URL while pass it to upstream
proxy_redirect off;
# Let's upstream handle errors
proxy_intercept_errors on;
tcp_nodelay on;
# proxy all requests to the beckend
location / {
proxy_pass http://mormot-uds;
}
}

View File

@@ -0,0 +1,12 @@
[Unit]
Description=mORMot socket activation demo
Documentation=https://synopse.info
RefuseManualStart=true
[Service]
Restart=on-failure
ExecStart="/home/pavelmash/dev/tmp/crypto_ssl/mORMot/SQLite3/Samples/36 - Simple REST Benchmark/RESTBenchmark"
# starting from systemd v244 is possible to configure log limits per service as below
# befote - only inside journald.conf
# LogRateLimitIntervalSec=1s
# LogRateLimitBurst=50000 # ~10 000 req / sec

View File

@@ -0,0 +1,7 @@
[Socket]
ListenStream=127.0.0.1:8889
NoDelay=true
KeepAlive=true
[Install]
WantedBy=sockets.target