Ver Fonte

Merge branch 'TechEmpower:master' into master

Jeremy Kuhn há 1 ano atrás
pai
commit
8d543cdaec
100 ficheiros alterados com 2546 adições e 532 exclusões
  1. 1 1
      .github/PULL_REQUEST_TEMPLATE.md
  2. 42 19
      .github/workflows/build.yml
  3. 37 0
      .github/workflows/get-maintainers.yml
  4. 1 1
      .github/workflows/label-failing-pr.yml
  5. 49 0
      .github/workflows/ping-maintainers.yml
  6. 7 3
      .gitignore
  7. 51 17
      Dockerfile
  8. 2 1
      README.md
  9. 7 0
      entrypoint.sh
  10. 2 1
      frameworks/C++/cpoll_cppsp/benchmark_config.json
  11. 0 2
      frameworks/C++/cppcms/benchmark_config.json
  12. 2 1
      frameworks/C++/cppcms/config-nginx-mysql.json
  13. 2 1
      frameworks/C++/cppcms/config-nginx-postgresql.json
  14. 6 15
      frameworks/C++/cppcms/nginx.conf
  15. 27 25
      frameworks/C++/cutelyst/benchmark_config.json
  16. 14 13
      frameworks/C++/cutelyst/build.sh
  17. 1 1
      frameworks/C++/cutelyst/cutelyst-nginx-my.dockerfile
  18. 1 1
      frameworks/C++/cutelyst/cutelyst-nginx-pg.dockerfile
  19. 1 1
      frameworks/C++/cutelyst/cutelyst-nginx.dockerfile
  20. 1 1
      frameworks/C++/cutelyst/cutelyst-pf-apg-batch.dockerfile
  21. 1 1
      frameworks/C++/cutelyst/cutelyst-pf-apg.dockerfile
  22. 1 1
      frameworks/C++/cutelyst/cutelyst-pf-my.dockerfile
  23. 1 1
      frameworks/C++/cutelyst/cutelyst-pf-pg.dockerfile
  24. 1 1
      frameworks/C++/cutelyst/cutelyst-t-apg-cutelee.dockerfile
  25. 1 1
      frameworks/C++/cutelyst/cutelyst-thread-apg-batch.dockerfile
  26. 1 1
      frameworks/C++/cutelyst/cutelyst-thread-apg.dockerfile
  27. 1 1
      frameworks/C++/cutelyst/cutelyst-thread-my-cutelee.dockerfile
  28. 1 1
      frameworks/C++/cutelyst/cutelyst-thread-my.dockerfile
  29. 1 1
      frameworks/C++/cutelyst/cutelyst-thread-pg-cutelee.dockerfile
  30. 1 1
      frameworks/C++/cutelyst/cutelyst-thread-pg.dockerfile
  31. 1 1
      frameworks/C++/cutelyst/cutelyst-thread-pico.dockerfile
  32. 1 1
      frameworks/C++/cutelyst/cutelyst-thread.dockerfile
  33. 1 1
      frameworks/C++/cutelyst/cutelyst.dockerfile
  34. 16 14
      frameworks/C++/cutelyst/src/CMakeLists.txt
  35. 70 0
      frameworks/C++/cutelyst/src/cachedqueries.cpp
  36. 19 0
      frameworks/C++/cutelyst/src/cachedqueries.h
  37. 31 27
      frameworks/C++/cutelyst/src/cutelyst-benchmarks.cpp
  38. 55 54
      frameworks/C++/cutelyst/src/databaseupdatestest.cpp
  39. 4 4
      frameworks/C++/cutelyst/src/databaseupdatestest.h
  40. 29 27
      frameworks/C++/cutelyst/src/fortunetest.cpp
  41. 6 6
      frameworks/C++/cutelyst/src/fortunetest.h
  42. 1 1
      frameworks/C++/cutelyst/src/jsontest.cpp
  43. 18 16
      frameworks/C++/cutelyst/src/multipledatabasequeriestest.cpp
  44. 3 3
      frameworks/C++/cutelyst/src/multipledatabasequeriestest.h
  45. 2 2
      frameworks/C++/cutelyst/src/plaintexttest.cpp
  46. 0 37
      frameworks/C++/cutelyst/src/root.cpp
  47. 0 22
      frameworks/C++/cutelyst/src/root.h
  48. 46 13
      frameworks/C++/cutelyst/src/singledatabasequerytest.cpp
  49. 7 4
      frameworks/C++/cutelyst/src/singledatabasequerytest.h
  50. 14 18
      frameworks/C++/drogon/drogon-core.dockerfile
  51. 14 18
      frameworks/C++/drogon/drogon.dockerfile
  52. 2 1
      frameworks/C++/drogon/drogon_benchmark/config-core.json
  53. 3 2
      frameworks/C++/drogon/drogon_benchmark/config.json
  54. 1 2
      frameworks/C++/drogon/drogon_benchmark/plugins/SyncPlugin.cc
  55. 7 7
      frameworks/C++/ffead-cpp/benchmark_config.json
  56. 38 0
      frameworks/C++/just-boost/README.md
  57. 27 0
      frameworks/C++/just-boost/benchmark_config.json
  58. 25 0
      frameworks/C++/just-boost/just-boost.dockerfile
  59. 401 0
      frameworks/C++/just-boost/main.cpp
  60. 0 48
      frameworks/C++/lithium/benchmark_config.json
  61. 3 1
      frameworks/C++/poco/poco.dockerfile
  62. 9 0
      frameworks/C++/reactor/README.md
  63. 23 0
      frameworks/C++/reactor/benchmark_config.json
  64. 3 5
      frameworks/C++/reactor/config.toml
  65. 37 0
      frameworks/C++/reactor/reactor.dockerfile
  66. 120 0
      frameworks/C++/reactor/techempower.cpp
  67. 6 6
      frameworks/C++/suil/benchmark/src/main.cpp
  68. 1 1
      frameworks/C++/suil/suil.dockerfile
  69. 0 4
      frameworks/C++/treefrog/benchmark_config.json
  70. 47 15
      frameworks/C++/treefrog/config/application.ini
  71. 10 0
      frameworks/C++/treefrog/config/cache.ini
  72. 34 2
      frameworks/C++/treefrog/controllers/worldcontroller.cpp
  73. 10 10
      frameworks/C++/treefrog/treefrog-epoll.dockerfile
  74. 9 9
      frameworks/C++/treefrog/treefrog-mongodb.dockerfile
  75. 9 9
      frameworks/C++/treefrog/treefrog-mysql.dockerfile
  76. 9 9
      frameworks/C++/treefrog/treefrog.dockerfile
  77. 16 8
      frameworks/C++/ulib/benchmark_config.json
  78. 42 0
      frameworks/C++/userver/README.md
  79. 55 0
      frameworks/C++/userver/benchmark_config.json
  80. 14 10
      frameworks/C++/userver/config.toml
  81. 27 0
      frameworks/C++/userver/userver-bare.dockerfile
  82. 27 0
      frameworks/C++/userver/userver.dockerfile
  83. 3 0
      frameworks/C++/userver/userver_benchmark/.clang-format
  84. 18 0
      frameworks/C++/userver/userver_benchmark/CMakeLists.txt
  85. 205 0
      frameworks/C++/userver/userver_benchmark/bare/simple_connection.cpp
  86. 26 0
      frameworks/C++/userver/userver_benchmark/bare/simple_connection.hpp
  87. 12 0
      frameworks/C++/userver/userver_benchmark/bare/simple_response.hpp
  88. 90 0
      frameworks/C++/userver/userver_benchmark/bare/simple_router.cpp
  89. 46 0
      frameworks/C++/userver/userver_benchmark/bare/simple_router.hpp
  90. 28 0
      frameworks/C++/userver/userver_benchmark/bare/simple_server.cpp
  91. 34 0
      frameworks/C++/userver/userver_benchmark/bare/simple_server.hpp
  92. 80 0
      frameworks/C++/userver/userver_benchmark/common/db_helpers.cpp
  93. 50 0
      frameworks/C++/userver/userver_benchmark/common/db_helpers.hpp
  94. 37 0
      frameworks/C++/userver/userver_benchmark/controllers/cached_queries/handler.cpp
  95. 29 0
      frameworks/C++/userver/userver_benchmark/controllers/cached_queries/handler.hpp
  96. 21 0
      frameworks/C++/userver/userver_benchmark/controllers/cached_queries/world_cache_component.cpp
  97. 36 0
      frameworks/C++/userver/userver_benchmark/controllers/cached_queries/world_cache_component.hpp
  98. 166 0
      frameworks/C++/userver/userver_benchmark/controllers/fortunes/handler.cpp
  99. 32 0
      frameworks/C++/userver/userver_benchmark/controllers/fortunes/handler.hpp
  100. 16 0
      frameworks/C++/userver/userver_benchmark/controllers/json/handler.cpp

+ 1 - 1
.github/PULL_REQUEST_TEMPLATE.md

@@ -1,7 +1,7 @@
 <!--
 Thank you for submitting to the TechEmpower Framework Benchmarks!
 
-If you are submitting a new framework, please make sure that you add the appropriate line in the `.github/workflows/build.yml` file for proper integration testing. Also please make sure that an appropriate `README.md` is added in your framework directory with information about the framework and a link to its homepage and documentation.
+If you are submitting a new framework, please make sure that an appropriate `README.md` is added in your framework directory with information about the framework and a link to its homepage and documentation.
 
 For new frameworks, please do not include source code that isn't required for the benchmarks.
 

+ 42 - 19
.github/workflows/build.yml

@@ -2,7 +2,7 @@ name: build
 on: [ push, pull_request ]
 jobs:
   setup:
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-22.04
     steps:
       # Required for workflow triggers like the auto-label for failing PRs
       - name: Save PR number
@@ -20,7 +20,7 @@ jobs:
       #
       # We need to fetch more than one commit to be able to access HEAD^2 in case
       # of a pull request
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
         with:
           fetch-depth: 10
       # In case of a push event, the commit we care about is simply HEAD.
@@ -46,9 +46,9 @@ jobs:
           echo "$(git log --format=%B -n 1 HEAD^2)" >> $GITHUB_ENV
           echo "EOF" >> $GITHUB_ENV
           echo "PREVIOUS_COMMIT=$(git log --format=%H -n 1 HEAD^2~1)" >> $GITHUB_ENV
-      - uses: actions/setup-python@v2
+      - uses: actions/setup-python@v4
         with:
-          python-version: '2.7'
+          python-version: '3.10'
           architecture: 'x64'
       - name: Get all changes vs master
         env:
@@ -65,14 +65,16 @@ jobs:
       - id: event_out
         name: Write event outputs
         run: |
-          # Escape the multiline string for Github Actions, see https://github.community/t/set-output-truncates-multiline-strings/16852/3
-          COMMIT_MESSAGE="${COMMIT_MESSAGE//'%'/'%25'}"
-          COMMIT_MESSAGE="${COMMIT_MESSAGE//$'\n'/'%0A'}"
-          COMMIT_MESSAGE="${COMMIT_MESSAGE//$'\r'/'%0D'}"
-          echo "::set-output name=commit_message::$COMMIT_MESSAGE"
-          echo "::set-output name=branch_name::$BRANCH_NAME"
-          echo "::set-output name=target_branch_name::$TARGET_BRANCH_NAME"
-          echo "::set-output name=previous_commit::$PREVIOUS_COMMIT"
+          # Escape the multiline string for Github Actions, see https://lab.amalitsky.com/posts/2022/github-actions-set-output-migration/#migration-from-set-output-to-github-output
+          delimiter="$(openssl rand -hex 8)"
+              {
+                echo "commit_message<<${delimiter}"
+                echo $COMMIT_MESSAGE
+                echo "${delimiter}"
+              } >> $GITHUB_OUTPUT
+          echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT
+          echo "target_branch_name=$TARGET_BRANCH_NAME" >> $GITHUB_OUTPUT
+          echo "previous_commit=$PREVIOUS_COMMIT" >> $GITHUB_OUTPUT
       - id: verify_out
         name: Write verify job matrix
         run: |
@@ -82,7 +84,7 @@ jobs:
           #   - TESTLANG: {lang}
           # with a TESTLANG object in the include array for each language under frameworks
           VERIFY_MATRIX=$(ls -1 frameworks | jq -Rc '.+"/" | select(inside(env.RUN_TESTS))  | rtrimstr("/")' | jq -sc '{include: map({TESTLANG: .})}')
-          echo "::set-output name=verify_matrix::$VERIFY_MATRIX"
+          echo "verify_matrix=$VERIFY_MATRIX" >> $GITHUB_OUTPUT
     outputs:
       commit_message: ${{ steps.event_out.outputs.commit_message }}
       branch_name: ${{ steps.event_out.outputs.branch_name }}
@@ -93,7 +95,7 @@ jobs:
     needs: setup
     # The matrix check is necessary because an empty job matrix is otherwise considered a workflow failure
     if: ${{ !contains(needs.setup.outputs.commit_message, '[ci skip]') && contains(needs.setup.outputs.verify_matrix, 'TESTLANG') }}
-    runs-on: ubuntu-18.04
+    runs-on: ubuntu-22.04
     strategy:
       matrix: ${{ fromJSON(needs.setup.outputs.verify_matrix) }}
       # Disable fail-fast to allow all failing frameworks/etc to fail in a
@@ -108,12 +110,12 @@ jobs:
       PREVIOUS_COMMIT: ${{ needs.setup.outputs.previous_commit }}
       PR_NUMBER: ${{ github.event.pull_request.number }}
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
         with:
           fetch-depth: 10
-      - uses: actions/setup-python@v2
+      - uses: actions/setup-python@v4
         with:
-          python-version: '2.7'
+          python-version: '3.10'
           architecture: 'x64'
       - name: Get all changes vs master
         # Runs github_actions_diff, with the the output accessible in later steps
@@ -138,7 +140,10 @@ jobs:
           if [ "$RUN_TESTS" ]; then echo "Proceeding to run tests."; else echo 'Skipping test verification.'; fi
       - name: Build tfb dockerfile
         if: ${{ env.RUN_TESTS }}
-        run: docker build -t techempower/tfb - < ./Dockerfile;
+        uses: mattes/cached-docker-build-action@v1
+        with:
+          args: " --file ./Dockerfile --build-arg USER_ID=$(id -u) --build-arg GROUP_ID=$(id -g) --tag techempower/tfb ."
+          cache_key: "${{ hashFiles('./Dockerfile') }}"
       - name: Stop services
         # Stop services that would claim ports we may need
         run: |
@@ -150,4 +155,22 @@ jobs:
           # run-ci.py runs the diffing to see if github actions needs to test this framework. Ideally/eventually,
           # we'd like to try and do the diffing before github_actions_clean & setup.
           # This will run the tests exactly as you would in your own vm:
-          docker network create tfb > /dev/null 2>&1 && docker run --network=tfb -v /var/run/docker.sock:/var/run/docker.sock --mount type=bind,source=`pwd`,target=/FrameworkBenchmarks techempower/tfb --mode verify --test-dir $RUN_TESTS --results-environment Github-Actions;
+          docker network create tfb > /dev/null 2>&1 && docker run --network=tfb -e USER_ID=$(id -u) -v /var/run/docker.sock:/var/run/docker.sock --mount type=bind,source=`pwd`,target=/FrameworkBenchmarks techempower/tfb --mode verify --test-dir $RUN_TESTS --results-environment Github-Actions;
+  dependabot:
+    needs: verify
+    runs-on: ubuntu-latest
+    permissions:
+      pull-requests: write
+      contents: write
+    if: ${{ github.actor == 'dependabot[bot]' }}
+    steps:
+      - name: Dependabot metadata
+        id: metadata
+        uses: dependabot/[email protected]
+        with:
+          github-token: "${{ secrets.GITHUB_TOKEN }}"
+      - name: Enable auto-merge for Dependabot PRs
+        run: gh pr merge --auto --merge "$PR_URL"
+        env:
+          PR_URL: ${{github.event.pull_request.html_url}}
+          GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}

+ 37 - 0
.github/workflows/get-maintainers.yml

@@ -0,0 +1,37 @@
+name: get-maintainers
+on: 
+  pull_request:
+    types: [opened, reopened]
+permissions:
+  pull-requests: write
+jobs:
+  get_maintainers:
+    runs-on: ubuntu-22.04
+    steps:
+      - uses: actions/checkout@v3
+        with:
+          fetch-depth: 10
+      - name: Get commit branch and commit message from PR
+        run: |
+          echo "BRANCH_NAME=$GITHUB_HEAD_REF" >> $GITHUB_ENV
+          echo "TARGET_BRANCH_NAME=$(echo ${GITHUB_BASE_REF##*/})" >> $GITHUB_ENV
+          echo "COMMIT_MESSAGE<<EOF" >> $GITHUB_ENV
+          echo "$(git log --format=%B -n 1 HEAD^2)" >> $GITHUB_ENV
+          echo "EOF" >> $GITHUB_ENV
+          echo "PREVIOUS_COMMIT=$(git log --format=%H -n 1 HEAD^2~1)" >> $GITHUB_ENV
+      - uses: actions/setup-python@v4
+        with:
+          python-version: '3.10'
+          architecture: 'x64'
+      - name: Save PR number
+        run: |
+          mkdir -p ./maintainers
+          echo ${{ github.event.number }} > ./maintainers/NR
+      - name: Get Maintainers
+        run: |
+          python ./toolset/github_actions/get_maintainers.py > ./maintainers/maintainers.md
+      - name: Save Maintainers
+        uses: actions/upload-artifact@v3
+        with:
+          name: maintainers
+          path: maintainers/

+ 1 - 1
.github/workflows/label-failing-pr.yml

@@ -7,7 +7,7 @@ on:
 jobs:
   apply_label:
     if: ${{ github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'failure' }}
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-22.04
     steps:
       - name: 'Download artifact'
         uses: actions/[email protected]

+ 49 - 0
.github/workflows/ping-maintainers.yml

@@ -0,0 +1,49 @@
+name: Ping Maintainers
+on:
+  workflow_run:
+    workflows: [ "get-maintainers"]
+    types:
+      - completed
+permissions:
+  pull-requests: write
+jobs:
+  ping_maintainers:
+    runs-on: ubuntu-22.04
+    steps:
+      - name: 'Download maintainers artifact'
+        uses: actions/github-script@v6
+        with:
+          script: |
+            let artifacts = await github.rest.actions.listWorkflowRunArtifacts({
+               owner: context.repo.owner,
+               repo: context.repo.repo,
+               run_id: ${{github.event.workflow_run.id }},
+            });
+            let matchArtifact = artifacts.data.artifacts.filter((artifact) => {
+              return artifact.name == "maintainers"
+            })[0];
+            let download = await github.rest.actions.downloadArtifact({
+               owner: context.repo.owner,
+               repo: context.repo.repo,
+               artifact_id: matchArtifact.id,
+               archive_format: 'zip',
+            });
+            let fs = require('fs');
+            fs.writeFileSync('${{github.workspace}}/maintainers.zip', Buffer.from(download.data));
+      - run: unzip maintainers.zip
+      - name: Ping maintainers
+        uses: actions/github-script@v6
+        with:
+          github-token: ${{ secrets.GITHUB_TOKEN }}
+          script: |
+            let fs = require('fs');
+            let issue_number = Number(fs.readFileSync('./NR'));
+            let maintainers_comment = fs.readFileSync('./maintainers.md', 'utf8');
+            if (maintainers_comment) {
+              await github.rest.issues.createComment({
+                issue_number: issue_number,
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                body: maintainers_comment
+              });
+            }

+ 7 - 3
.gitignore

@@ -1,6 +1,4 @@
-.DS_Store
 installs
-*.log
 node_modules/
 .travis.bak
 
@@ -35,6 +33,7 @@ build/
 *.patch
 */bin/
 
+
 # intellij
 *.iml
 *.ipr
@@ -59,9 +58,14 @@ benchmark.cfg
 
 # Visual Studio Code
 .vscode
+.history/
+.devcontainer
 
 # vim
 .*.sw[a-p]
 
 # merge tooling
-*.orig
+*.orig
+
+# python
+.venv/

+ 51 - 17
Dockerfile

@@ -1,26 +1,60 @@
-FROM buildpack-deps:bionic
+FROM ubuntu:22.04
 
+ARG DEBIAN_FRONTEND=noninteractive
+# WARNING: DON'T PUT A SPACE AFTER ANY BACKSLASH OR APT WILL BREAK
 # One -q produces output suitable for logging (mostly hides
 # progress indicators)
-RUN apt-get -yqq update
+RUN apt-get -yqq update && \
+    apt-get -yqq install \
+      -o Dpkg::Options::="--force-confdef" \
+      -o Dpkg::Options::="--force-confold" \
+      cloc \
+      curl \
+      gcc \
+      git-core \
+      gosu \
+      # Needed for mysqlclient
+      libmysqlclient-dev \
+      libpq-dev \
+      pkg-config \
+      python3 \
+      python3-dev \
+      python3-pip \
+      siege \
+      software-properties-common
 
-# WARNING: DONT PUT A SPACE AFTER ANY BACKSLASH OR APT WILL BREAK
-RUN apt-get -yqq install -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" \
-  git-core \
-  cloc dstat                    `# Collect resource usage statistics` \
-  python-dev \
-  python-pip \
-  software-properties-common \
-  libmysqlclient-dev            `# Needed for MySQL-python`
+RUN pip3 install \
+      colorama==0.3.1 \
+      docker==4.0.2 \
+      mysqlclient \
+      psutil \
+      psycopg2-binary \
+      pymongo==3.13.0 \
+      # urllib3 incompatibility:
+      # https://github.com/docker/docker-py/issues/3113#issuecomment-1525500104
+      requests==2.28.1
 
-RUN pip install colorama==0.3.1 requests MySQL-python psycopg2-binary pymongo docker==4.0.2 psutil
+# Collect resource usage statistics
+ARG DOOL_VERSION=v1.2.0
 
-RUN apt-get install -yqq siege
+WORKDIR /tmp
+RUN curl -LSs "https://github.com/scottchiefbaker/dool/archive/${DOOL_VERSION}.tar.gz" | \
+      tar --strip-components=1 -xz && \
+    ./install.py
 
-# Fix for docker-py trying to import one package from the wrong location
-RUN cp -r /usr/local/lib/python2.7/dist-packages/backports/ssl_match_hostname/ /usr/lib/python2.7/dist-packages/backports
+# Check if the group ID is already created
+ARG GROUP_ID
+RUN if ! getent group "$GROUP_ID"; then \
+      addgroup --gid "$GROUP_ID" user; \
+    fi
 
-ENV PYTHONPATH /FrameworkBenchmarks
-ENV FWROOT /FrameworkBenchmarks
+# Check if the user ID is already created
+ARG USER_ID
+RUN if ! getent passwd "$USER_ID"; then \
+      adduser --disabled-password --gecos '' --gid "$GROUP_ID" --uid "$USER_ID" user; \
+    fi
 
-ENTRYPOINT ["python", "/FrameworkBenchmarks/toolset/run-tests.py"]
+ENV FWROOT=/FrameworkBenchmarks USER_ID="$USER_ID"
+ENV PYTHONPATH="$FWROOT"
+
+ENTRYPOINT ["/FrameworkBenchmarks/entrypoint.sh"]

+ 2 - 1
README.md

@@ -89,7 +89,8 @@ Results of continuous benchmarking runs are available in real time [here](https:
 
 ### Data Visualization
 
-If you have a `results.json` file that you would like to visualize, you can [do that here](https://tfb-status.techempower.com/share). You can also attach a `runid` parameter to that url where `runid` is a run listed on [tfb-status](https://tfb-status.techempower.com) like so: https://www.techempower.com/benchmarks/#section=test&runid=fd07b64e-47ce-411e-8b9b-b13368e988c6
+If you have a `results.json` file that you would like to visualize, you can [do that here](https://tfb-status.techempower.com/share). You can also attach a `runid` parameter to that url where `runid` is a run listed on [tfb-status](https://tfb-status.techempower.com) like so: https://www.techempower.com/benchmarks/#section=test&runid=fd07b64e-47ce-411e-8b9b-b13368e988c6.
+If you want to visualize them or compare different results files on bash, here is an unofficial [plaintext results parser](https://github.com/joeyleeeeeee97/PlainTextResultsParser)
 
 ## Contributing
 

+ 7 - 0
entrypoint.sh

@@ -0,0 +1,7 @@
+#!/bin/bash
+
+set -eo pipefail -u
+
+chown -LR "$USER_ID" /var/run
+# Drop permissions of user to match those of the host system
+gosu "$USER_ID" python3 "${FWROOT}/toolset/run-tests.py" "$@"

+ 2 - 1
frameworks/C++/cpoll_cppsp/benchmark_config.json

@@ -38,7 +38,8 @@
       "database_os": "Linux",
       "display_name": "cpoll-cppsp-raw",
       "notes": "",
-      "versus": "cpoll_cppsp"
+      "versus": "cpoll_cppsp",
+      "tags": ["broken"]
     },
     "postgres-raw": {
       "db_url": "/db_pg_async",

+ 0 - 2
frameworks/C++/cppcms/benchmark_config.json

@@ -24,10 +24,8 @@
       "notes": ""
     },
     "postgres": {
-      "json_url": "/json",
       "db_url": "/db",
       "fortune_url": "/fortunes",
-      "plaintext_url": "/plaintext",
       "cached_query_url": "/cached-worlds/",
       "port": 8080,
       "approach": "Realistic",

+ 2 - 1
frameworks/C++/cppcms/config-nginx-mysql.json

@@ -4,7 +4,8 @@
     },
     "service": {
         "api": "fastcgi",
-        "socket": "/var/tmp/cppcms.sock"
+        "socket": "/var/tmp/cppcms.sock",
+        "backlog": 65535
     },
     "http": {
         "script": "/"

+ 2 - 1
frameworks/C++/cppcms/config-nginx-postgresql.json

@@ -4,7 +4,8 @@
     },
     "service": {
         "api": "fastcgi",
-        "socket": "/var/tmp/cppcms.sock"
+        "socket": "/var/tmp/cppcms.sock",
+        "backlog": 65535
     },
     "http": {
         "script": "/"

+ 6 - 15
frameworks/C++/cppcms/nginx.conf

@@ -7,32 +7,23 @@ error_log stderr error;
 events {
     # This needed to be increased because the nginx error log said so.
     # http://nginx.org/en/docs/ngx_core_module.html#worker_connections
-    worker_connections  65535;
+    worker_connections 65535;
     multi_accept on;
 }
 
 http {
     default_type  application/octet-stream;
     client_body_temp_path      /tmp;
-
-    # turn off request logging for performance
     access_log off;
+    server_tokens off;
 
-    # I think these only options affect static file serving
-    sendfile        on;
-    tcp_nopush      on;
+    sendfile on;
+    tcp_nopush on;
 
-    # Allow many HTTP Keep-Alive requests in a single TCP connection before
-    # closing it (the default is 100). This will minimize the total number
-    # of TCP connections opened/closed. The problem is that this may cause
-    # some worker processes to be handling too connections relative to the
-    # other workers based on an initial imbalance, so this is disabled for
-    # now.
+    keepalive_timeout 65;
+    keepalive_disable none;
     keepalive_requests 1000;
 
-    #keepalive_timeout  0;
-    keepalive_timeout  65;
-
     server {
         # For information on deferred, see:
         # http://nginx.org/en/docs/http/ngx_http_core_module.html#listen

+ 27 - 25
frameworks/C++/cutelyst/benchmark_config.json

@@ -20,10 +20,11 @@
                 "versus": ""
             },
             "pf-apg": {
-                "db_url": "/dbp",
-                "query_url": "/queriesp?queries=",
+                "db_url": "/pg",
+                "query_url": "/querAPG?queries=",
                 "update_url": "/updatep?queries=",
-                "fortune_url": "/fortunes_raw_p",
+                "fortune_url": "/f_RW_APG",
+                "cached_query_url": "/cached_queries?count=",
                 "port": 8080,
                 "approach": "Realistic",
                 "classification": "Fullstack",
@@ -57,10 +58,10 @@
                 "versus": ""
             },
             "pf-pg": {
-                "db_url": "/db_postgres",
-                "query_url": "/query_postgres?queries=",
-                "update_url": "/updates_postgres?queries=",
-                "fortune_url": "/fortunes_raw_postgres",
+                "db_url": "/PG",
+                "query_url": "/queryPG?queries=",
+                "update_url": "/ups_QPG?queries=",
+                "fortune_url": "/f_RW_QPG",
                 "port": 8080,
                 "approach": "Realistic",
                 "classification": "Fullstack",
@@ -77,10 +78,10 @@
                 "versus": ""
             },
             "pf-my": {
-                "db_url": "/db_mysql",
-                "query_url": "/query_mysql?queries=",
-                "update_url": "/updates_mysql?queries=",
-                "fortune_url": "/fortunes_raw_mysql",
+                "db_url": "/MY",
+                "query_url": "/queryMY?queries=",
+                "update_url": "/ups_QMY?queries=",
+                "fortune_url": "/f_RW_QMY",
                 "port": 8080,
                 "approach": "Realistic",
                 "classification": "Fullstack",
@@ -132,10 +133,11 @@
                 "versus": ""
             },
             "thread-apg": {
-                "db_url": "/dbp",
-                "query_url": "/queriesp?queries=",
+                "db_url": "/pg",
+                "query_url": "/querAPG?queries=",
                 "update_url": "/updatep?queries=",
-                "fortune_url": "/fortunes_raw_p",
+                "fortune_url": "/f_RW_APG",
+                "cached_query_url": "/cached_queries?count=",
                 "port": 8080,
                 "approach": "Realistic",
                 "classification": "Fullstack",
@@ -169,10 +171,10 @@
                 "versus": ""
             },
             "thread-pg": {
-                "db_url": "/db_postgres",
-                "query_url": "/query_postgres?queries=",
-                "update_url": "/updates_postgres?queries=",
-                "fortune_url": "/fortunes_raw_postgres",
+                "db_url": "/PG",
+                "query_url": "/queryPG?queries=",
+                "update_url": "/ups_QPG?queries=",
+                "fortune_url": "/f_RW_QPG",
                 "port": 8080,
                 "approach": "Realistic",
                 "classification": "Fullstack",
@@ -189,7 +191,7 @@
                 "versus": ""
             },
             "thread-pg-cutelee": {
-                "fortune_url": "/fortunes_cutelee_postgres",
+                "fortune_url": "/f_CL_QPG",
                 "port": 8080,
                 "approach": "Realistic",
                 "classification": "Fullstack",
@@ -206,7 +208,7 @@
                 "versus": ""
             },
             "t-apg-cutelee": {
-                "fortune_url": "/fortunes_c_p",
+                "fortune_url": "/f_CL_APG",
                 "port": 8080,
                 "approach": "Realistic",
                 "classification": "Fullstack",
@@ -223,10 +225,10 @@
                 "versus": ""
             },
             "thread-my": {
-                "db_url": "/db_mysql",
-                "query_url": "/query_mysql?queries=",
-                "update_url": "/updates_mysql?queries=",
-                "fortune_url": "/fortunes_raw_mysql",
+                "db_url": "/MY",
+                "query_url": "/queryMY?queries=",
+                "update_url": "/ups_QMY?queries=",
+                "fortune_url": "/f_RW_QMY",
                 "port": 8080,
                 "approach": "Realistic",
                 "classification": "Fullstack",
@@ -243,7 +245,7 @@
                 "versus": ""
             },
             "thread-my-cutelee": {
-                "fortune_url": "/fortunes_cutelee_mysql",
+                "fortune_url": "/f_CL_QMY",
                 "port": 8080,
                 "approach": "Realistic",
                 "classification": "Fullstack",

+ 14 - 13
frameworks/C++/cutelyst/build.sh

@@ -1,28 +1,29 @@
 #!/bin/bash
 
-export ASQL_VER=0.46.0
-export CUTELEE_VER=6.0.0
-export CUTELYST_VER=3.1.0
+export ASQL_VER=0.84.0
+export CUTELEE_VER=6.1.0
+export CUTELYST_TAG=v4.0.0-alpha1
+export CUTELYST_VER=4.0.0
 
 apt update -qq && \
     apt install -yqq --no-install-recommends \
     cmake \
     git \
     pkg-config \
-    qtbase5-dev \
-    libqt5sql5-mysql \
-    libqt5sql5-psql \
-    qtdeclarative5-dev \
+    qt6-base-dev \
+    libqt6sql6-mysql \
+    libqt6sql6-psql \
+    libegl1-mesa-dev \
     postgresql-server-dev-all
 
-wget -q https://github.com/cutelyst/cutelee/releases/download/v${CUTELEE_VER}/cutelee_${CUTELEE_VER}_amd64.deb && \
-    apt install -yqq ./cutelee_${CUTELEE_VER}_amd64.deb
+wget -q https://github.com/cutelyst/cutelee/releases/download/v${CUTELEE_VER}/cutelee6-qt6_${CUTELEE_VER}_amd64.deb && \
+    apt install -yqq ./cutelee6-qt6_${CUTELEE_VER}_amd64.deb
 
-wget -q https://github.com/cutelyst/asql/releases/download/v${ASQL_VER}/libasql_${ASQL_VER}_amd64.deb && \
-    apt install -yqq ./libasql_${ASQL_VER}_amd64.deb
+wget -q https://github.com/cutelyst/asql/releases/download/v${ASQL_VER}/libasql0-qt6_${ASQL_VER}_amd64.deb && \
+    apt install -yqq ./libasql0-qt6_${ASQL_VER}_amd64.deb
 
-wget -q https://github.com/cutelyst/cutelyst/releases/download/v${CUTELYST_VER}/cutelyst_${CUTELYST_VER}_amd64.deb && \
-    apt install -yqq ./cutelyst_${CUTELYST_VER}_amd64.deb
+wget -q https://github.com/cutelyst/cutelyst/releases/download/${CUTELYST_TAG}/cutelyst4-qt6_${CUTELYST_VER}_amd64.deb && \
+    apt install -yqq ./cutelyst4-qt6_${CUTELYST_VER}_amd64.deb
 
 cd ${TROOT} && \
     mkdir -p build && \

+ 1 - 1
frameworks/C++/cutelyst/cutelyst-nginx-my.dockerfile

@@ -1,4 +1,4 @@
-FROM ubuntu:20.04
+FROM ubuntu:22.04
 
 RUN apt-get update -qq && \
     apt-get install -yqq locales wget build-essential

+ 1 - 1
frameworks/C++/cutelyst/cutelyst-nginx-pg.dockerfile

@@ -1,4 +1,4 @@
-FROM ubuntu:20.04
+FROM ubuntu:22.04
 
 RUN apt-get update -qq && \
     apt-get install -yqq locales wget build-essential

+ 1 - 1
frameworks/C++/cutelyst/cutelyst-nginx.dockerfile

@@ -1,4 +1,4 @@
-FROM ubuntu:20.04
+FROM ubuntu:22.04
 
 RUN apt-get update -qq && \
     apt-get install -yqq locales wget build-essential

+ 1 - 1
frameworks/C++/cutelyst/cutelyst-pf-apg-batch.dockerfile

@@ -1,4 +1,4 @@
-FROM ubuntu:20.04
+FROM ubuntu:22.04
 
 RUN apt-get update -qq && \
     apt-get install -yqq locales wget build-essential

+ 1 - 1
frameworks/C++/cutelyst/cutelyst-pf-apg.dockerfile

@@ -1,4 +1,4 @@
-FROM ubuntu:20.04
+FROM ubuntu:22.04
 
 RUN apt-get update -qq && \
     apt-get install -yqq locales wget build-essential

+ 1 - 1
frameworks/C++/cutelyst/cutelyst-pf-my.dockerfile

@@ -1,4 +1,4 @@
-FROM ubuntu:20.04
+FROM ubuntu:22.04
 
 RUN apt-get update -qq && \
     apt-get install -yqq locales wget build-essential

+ 1 - 1
frameworks/C++/cutelyst/cutelyst-pf-pg.dockerfile

@@ -1,4 +1,4 @@
-FROM ubuntu:20.04
+FROM ubuntu:22.04
 
 RUN apt-get update -qq && \
     apt-get install -yqq locales wget build-essential

+ 1 - 1
frameworks/C++/cutelyst/cutelyst-t-apg-cutelee.dockerfile

@@ -1,4 +1,4 @@
-FROM ubuntu:20.04
+FROM ubuntu:22.04
 
 RUN apt-get update -qq && \
     apt-get install -yqq locales wget build-essential

+ 1 - 1
frameworks/C++/cutelyst/cutelyst-thread-apg-batch.dockerfile

@@ -1,4 +1,4 @@
-FROM ubuntu:20.04
+FROM ubuntu:22.04
 
 RUN apt-get update -qq && \
     apt-get install -yqq locales wget build-essential

+ 1 - 1
frameworks/C++/cutelyst/cutelyst-thread-apg.dockerfile

@@ -1,4 +1,4 @@
-FROM ubuntu:20.04
+FROM ubuntu:22.04
 
 RUN apt-get update -qq && \
     apt-get install -yqq locales wget build-essential

+ 1 - 1
frameworks/C++/cutelyst/cutelyst-thread-my-cutelee.dockerfile

@@ -1,4 +1,4 @@
-FROM ubuntu:20.04
+FROM ubuntu:22.04
 
 RUN apt-get update -qq && \
     apt-get install -yqq locales wget build-essential

+ 1 - 1
frameworks/C++/cutelyst/cutelyst-thread-my.dockerfile

@@ -1,4 +1,4 @@
-FROM ubuntu:20.04
+FROM ubuntu:22.04
 
 RUN apt-get update -qq && \
     apt-get install -yqq locales wget build-essential

+ 1 - 1
frameworks/C++/cutelyst/cutelyst-thread-pg-cutelee.dockerfile

@@ -1,4 +1,4 @@
-FROM ubuntu:20.04
+FROM ubuntu:22.04
 
 RUN apt-get update -qq && \
     apt-get install -yqq locales wget build-essential

+ 1 - 1
frameworks/C++/cutelyst/cutelyst-thread-pg.dockerfile

@@ -1,4 +1,4 @@
-FROM ubuntu:20.04
+FROM ubuntu:22.04
 
 RUN apt-get update -qq && \
     apt-get install -yqq locales wget build-essential

+ 1 - 1
frameworks/C++/cutelyst/cutelyst-thread-pico.dockerfile

@@ -1,4 +1,4 @@
-FROM ubuntu:20.04
+FROM ubuntu:22.04
 
 RUN apt-get update -qq && \
     apt-get install -yqq locales wget build-essential

+ 1 - 1
frameworks/C++/cutelyst/cutelyst-thread.dockerfile

@@ -1,4 +1,4 @@
-FROM ubuntu:20.04
+FROM ubuntu:22.04
 
 RUN apt-get update -qq && \
     apt-get install -yqq locales wget build-essential

+ 1 - 1
frameworks/C++/cutelyst/cutelyst.dockerfile

@@ -1,4 +1,4 @@
-FROM ubuntu:20.04
+FROM ubuntu:22.04
 
 RUN apt-get update -qq && \
     apt-get install -yqq locales wget build-essential

+ 16 - 14
frameworks/C++/cutelyst/src/CMakeLists.txt

@@ -13,10 +13,10 @@ FetchContent_Declare(
 )
 FetchContent_MakeAvailable(mimalloc)
 
-find_package(Qt5 5.6.0 REQUIRED COMPONENTS Core Network Sql)
-find_package(ASqlQt5 0.43.0 REQUIRED)
-find_package(Cutelyst3Qt5 3.1 REQUIRED)
-find_package(Cutelee6Qt5 6.0.0 REQUIRED)
+find_package(Qt6 6.2.0 REQUIRED COMPONENTS Core Network Sql)
+find_package(ASqlQt6 0.84 REQUIRED)
+find_package(Cutelyst4Qt6 3.6 REQUIRED)
+find_package(Cutelee6Qt6 6.1.0 REQUIRED)
 find_package(PostgreSQL REQUIRED)
 
 # Auto generate moc files
@@ -44,10 +44,10 @@ set(cutelyst_benchmarks_SRCS
     multipledatabasequeriestest.h
     plaintexttest.cpp
     plaintexttest.h
-    root.cpp
-    root.h
     singledatabasequerytest.cpp
     singledatabasequerytest.h
+    cachedqueries.h
+    cachedqueries.cpp
     ${TEMPLATES_SRC}
 )
 
@@ -60,10 +60,11 @@ target_link_libraries(cutelyst_benchmarks
     Cutelyst::Core
     Cutelyst::Utils::Sql
     Cutelyst::View::Cutelee
-    Qt5::Core
-    Qt5::Network
-    Qt5::Sql
-    ASqlQt5::Core
+    Qt::Core
+    Qt::Network
+    Qt::Sql
+    ASql::Core
+    ASql::Pg
 )
 
 add_executable(cutelyst-benchmarks ${cutelyst_benchmarks_SRCS} main.cpp)
@@ -73,10 +74,11 @@ target_link_libraries(cutelyst-benchmarks
     Cutelyst::Server
     Cutelyst::Utils::Sql
     Cutelyst::View::Cutelee
-    Qt5::Core
-    Qt5::Network
-    Qt5::Sql
-    ASqlQt5::Core
+    Qt::Core
+    Qt::Network
+    Qt::Sql
+    ASql::Core
+    ASql::Pg
     mimalloc
 )
 if (mimalloc_FOUND)

+ 70 - 0
frameworks/C++/cutelyst/src/cachedqueries.cpp

@@ -0,0 +1,70 @@
+#include "cachedqueries.h"
+
+#include <apool.h>
+#include <aresult.h>
+#include <apreparedquery.h>
+
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QJsonArray>
+
+#include <QCache>
+
+using namespace ASql;
+
+CachedQueries::CachedQueries(QObject *parent)
+    : Controller{parent}
+{
+
+}
+
+void CachedQueries::cached_queries(Context *c)
+{
+    int queries = c->request()->queryParam(u"count"_qs).toInt();
+    if (queries < 1) {
+        queries = 1;
+    } else if (queries > 500) {
+        queries = 500;
+    }
+
+    static thread_local QCache<int, QJsonObject> cache(1024);
+
+    auto array = std::shared_ptr<QJsonArray>(new QJsonArray);
+
+    ASync async(c);
+    static thread_local auto db = APool::database();
+    for (int i = 0; i < queries; ++i) {
+        const int id = (rand() % 10000) + 1;
+
+        QJsonObject *obj = cache[id];
+        if (obj) {
+            array->append(*obj);
+            continue;
+        }
+
+        db.exec(APreparedQueryLiteral(u8"SELECT id, randomNumber FROM world WHERE id=$1"),
+                               {id}, c, [c, async, i, queries, array] (AResult &result) {
+            if (Q_LIKELY(!result.error() && result.size())) {
+                auto it = result.begin();
+                int id = it[0].toInt();
+                auto obj = new QJsonObject({
+                                               {u"id"_qs, id},
+                                               {u"randomNumber"_qs, it[1].toInt()}
+                                           });
+                array->append(*obj);
+                cache.insert(id, obj, 1);
+
+                if (array->size() == queries) {
+                    c->response()->setJsonArrayBody(*array);
+                }
+                return;
+            }
+
+            c->res()->setStatus(Response::InternalServerError);
+        });
+    }
+
+    if (array->size() == queries) {
+        c->response()->setJsonArrayBody(*array);
+    }
+}

+ 19 - 0
frameworks/C++/cutelyst/src/cachedqueries.h

@@ -0,0 +1,19 @@
+#ifndef CACHEDQUERIES_H
+#define CACHEDQUERIES_H
+
+#include <Cutelyst/Controller>
+
+using namespace Cutelyst;
+
+class CachedQueries : public Controller
+{
+    Q_OBJECT
+    C_NAMESPACE("")
+public:
+    explicit CachedQueries(QObject *parent = nullptr);
+
+    C_ATTR(cached_queries, :Local :AutoArgs)
+    void cached_queries(Context *c);
+};
+
+#endif // CACHEDQUERIES_H

+ 31 - 27
frameworks/C++/cutelyst/src/cutelyst-benchmarks.cpp

@@ -13,6 +13,7 @@
 #include <QDir>
 
 #include <apool.h>
+#include <apg.h>
 
 #include "jsontest.h"
 #include "singledatabasequerytest.h"
@@ -20,14 +21,17 @@
 #include "databaseupdatestest.h"
 #include "fortunetest.h"
 #include "plaintexttest.h"
+#include "cachedqueries.h"
 
 using namespace Cutelyst;
+using namespace ASql;
 
 static QMutex mutex;
 
 cutelyst_benchmarks::cutelyst_benchmarks(QObject *parent) : Application(parent)
 {
-    qsrand(QDateTime::currentMSecsSinceEpoch());
+    static std::once_flag once;
+    std::call_once(once, []() { srand(time(NULL)); });
 }
 
 cutelyst_benchmarks::~cutelyst_benchmarks()
@@ -36,7 +40,7 @@ cutelyst_benchmarks::~cutelyst_benchmarks()
 
 bool cutelyst_benchmarks::init()
 {
-    if (config(QStringLiteral("SendDate")).value<bool>()) {
+    if (config(u"SendDate"_qs).value<bool>()) {
         qDebug() << "Manually send date";
         auto dateT = new QTimer(this);
         dateT->setInterval(1000);
@@ -56,11 +60,12 @@ bool cutelyst_benchmarks::init()
     new DatabaseUpdatesTest(this);
     new FortuneTest(this);
     new PlaintextTest(this);
+    new CachedQueries(this);
 
     if (defaultHeaders().server().isEmpty()) {
-        defaultHeaders().setServer(QStringLiteral("Cutelyst"));
+        defaultHeaders().setServer("Cutelyst"_qba);
     }
-    defaultHeaders().removeHeader(QStringLiteral("X-Cutelyst"));
+    defaultHeaders().removeHeader("X-Cutelyst");
 
     return true;
 }
@@ -70,42 +75,41 @@ bool cutelyst_benchmarks::postFork()
     QMutexLocker locker(&mutex); // QSqlDatabase::addDatabase is not thread-safe
 
     QSqlDatabase db;
-    const auto driver = config(QStringLiteral("Driver")).toString();
-    if (driver == QLatin1String("QPSQL")) {
-        db = QSqlDatabase::addDatabase(driver, Sql::databaseNameThread(QStringLiteral("postgres")));
-        db.setDatabaseName(QStringLiteral("hello_world"));
-        db.setUserName(QStringLiteral("benchmarkdbuser"));
-        db.setPassword(QStringLiteral("benchmarkdbpass"));
-        db.setHostName(config(QStringLiteral("DatabaseHostName")).toString());
+    const auto driver = config(u"Driver"_qs).toString();
+    if (driver == u"QPSQL") {
+        db = QSqlDatabase::addDatabase(driver, Sql::databaseNameThread(u"postgres"_qs));
+        db.setDatabaseName(u"hello_world"_qs);
+        db.setUserName(u"benchmarkdbuser"_qs);
+        db.setPassword(u"benchmarkdbpass"_qs);
+        db.setHostName(config(u"DatabaseHostName"_qs).toString());
         if (!db.open()) {
             qDebug() << "Error opening PostgreSQL db:" << db << db.connectionName() << db.lastError().databaseText();
             return false;
         }
-    } else if (driver == QLatin1String("QMYSQL")) {
-        db = QSqlDatabase::addDatabase(driver, Sql::databaseNameThread(QStringLiteral("mysql")));
-        db.setDatabaseName(QStringLiteral("hello_world"));
-        db.setUserName(QStringLiteral("benchmarkdbuser"));
-        db.setPassword(QStringLiteral("benchmarkdbpass"));
-        db.setHostName(config(QStringLiteral("DatabaseHostName")).toString());
+    } else if (driver == u"QMYSQL") {
+        db = QSqlDatabase::addDatabase(driver, Sql::databaseNameThread(u"mysql"_qs));
+        db.setDatabaseName(u"hello_world"_qs);
+        db.setUserName(u"benchmarkdbuser"_qs);
+        db.setPassword(u"benchmarkdbpass"_qs);
+        db.setHostName(config(u"DatabaseHostName"_qs).toString());
         if (!db.open()) {
             qDebug() << "Error opening MySQL db:" << db << db.connectionName() << db.lastError().databaseText();
             return false;
         }
-    } else if (driver == QLatin1String("postgres")) {
-        QUrl uri(QStringLiteral("postgresql://benchmarkdbuser:benchmarkdbpass@server/hello_world"));
-        uri.setHost(config(QStringLiteral("DatabaseHostName")).toString());
+    } else if (driver == u"postgres") {
+        QUrl uri(u"postgresql://benchmarkdbuser:benchmarkdbpass@server/hello_world"_qs);
+        uri.setHost(config(u"DatabaseHostName"_qs).toString());
         qDebug() << "ASql URI:" << uri.toString();
 
-        APool::addDatabase(uri.toString());
-        APool::setDatabaseMaxIdleConnections(128);
+        APool::create(ASql::APg::factory(uri.toString()));
+        APool::setMaxIdleConnections(128);
+        APool::setSetupCallback([](ADatabase db) {
+            // Enable Pipeline mode
+            db.enterPipelineMode(500);
+        });
     }
 
     qDebug() << "Connections" << QCoreApplication::applicationPid() << QThread::currentThread() << QSqlDatabase::connectionNames();
-//    db = QSqlDatabase::addDatabase(QLatin1String("QSQLITE"), QLatin1String("sqlite"));
-//    if (!db.open()) {
-//        qDebug() << "Error opening db:" << db << db.lastError().databaseText();
-//        return false;
-//    }
 
     return true;
 }

+ 55 - 54
frameworks/C++/cutelyst/src/databaseupdatestest.cpp

@@ -16,6 +16,8 @@
 
 #include "picojson.h"
 
+using namespace ASql;
+
 DatabaseUpdatesTest::DatabaseUpdatesTest(QObject *parent) : Controller(parent)
 {
 
@@ -23,7 +25,7 @@ DatabaseUpdatesTest::DatabaseUpdatesTest(QObject *parent) : Controller(parent)
 
 void DatabaseUpdatesTest::updatep(Context *c)
 {
-    int queries = c->request()->queryParam(QStringLiteral("queries"), QStringLiteral("1")).toInt();
+    int queries = c->request()->queryParam(u"queries"_qs, u"1"_qs).toInt();
     if (queries < 1) {
         queries = 1;
     } else if (queries > 500) {
@@ -34,29 +36,29 @@ void DatabaseUpdatesTest::updatep(Context *c)
     ASync async(c);
     static thread_local auto db = APool::database();
     for (int i = 0; i < queries; ++i) {
-        int id = (qrand() % 10000) + 1;
+        int id = (rand() % 10000) + 1;
 
-        int randomNumber = (qrand() % 10000) + 1;
+        int randomNumber = (rand() % 10000) + 1;
 
         array.emplace_back(picojson::object({
                             {"id", picojson::value(double(id))},
                             {"randomNumber", picojson::value(double(randomNumber))}
                         }));
 
-        db.exec(APreparedQueryLiteral(u"SELECT randomNumber, id FROM world WHERE id=$1"),
-                               {id}, [c, async] (AResult &result) {
+        db.exec(APreparedQueryLiteral(u8"SELECT randomNumber, id FROM world WHERE id=$1"),
+                               {id}, c, [c, async] (AResult &result) {
             if (Q_UNLIKELY(result.error() || !result.size())) {
                 c->res()->setStatus(Response::InternalServerError);
                 return;
             }
-        }, c);
-        db.exec(APreparedQueryLiteral(u"UPDATE world SET randomNumber=$1 WHERE id=$2"),
-                               {randomNumber, id}, [c, async] (AResult &result) {
+        });
+        db.exec(APreparedQueryLiteral(u8"UPDATE world SET randomNumber=$1 WHERE id=$2"),
+                               {randomNumber, id}, c, [c, async] (AResult &result) {
             if (Q_UNLIKELY(result.error())) {
                 c->res()->setStatus(Response::InternalServerError);
                 return;
             }
-        }, c);
+        });
     }
 
     c->response()->setJsonBody(QByteArray::fromStdString(picojson::value(array).serialize()));
@@ -64,7 +66,7 @@ void DatabaseUpdatesTest::updatep(Context *c)
 
 void DatabaseUpdatesTest::updateb(Context *c)
 {
-    int queries = c->request()->queryParam(QStringLiteral("queries"), QStringLiteral("1")).toInt();
+    int queries = c->request()->queryParam(u"queries"_qs, u"1"_qs).toInt();
     if (queries < 1) {
         queries = 1;
     } else if (queries > 500) {
@@ -78,9 +80,9 @@ void DatabaseUpdatesTest::updateb(Context *c)
     ASync async(c);
     static thread_local auto db = APool::database();
     for (int i = 0; i < queries; ++i) {
-        int id = (qrand() % 10000) + 1;
+        int id = (rand() % 10000) + 1;
 
-        int randomNumber = (qrand() % 10000) + 1;
+        int randomNumber = (rand() % 10000) + 1;
 
         argsIds.append(id);
         args.append(id);
@@ -91,23 +93,23 @@ void DatabaseUpdatesTest::updateb(Context *c)
                             {"randomNumber", picojson::value(double(randomNumber))}
                         }));
 
-        db.exec(APreparedQueryLiteral(u"SELECT randomNumber, id FROM world WHERE id=$1"),
-                               {id}, [c, async] (AResult &result) {
+        db.exec(APreparedQueryLiteral(u8"SELECT randomNumber, id FROM world WHERE id=$1"),
+                               {id}, c, [c, async] (AResult &result) {
             if (Q_UNLIKELY(result.error() || !result.size())) {
                 c->res()->setStatus(Response::InternalServerError);
                 return;
             }
-        }, c);
+        });
     }
     args.append(argsIds);
 
     const APreparedQuery pq = getSql(queries);
-    db.exec(pq, args, [c, async] (AResult &result) {
+    db.exec(pq, args, c, [c, async] (AResult &result) {
         if (Q_UNLIKELY(result.error())) {
             c->res()->setStatus(Response::InternalServerError);
             return;
         }
-    }, c);
+    });
 
     c->response()->setJsonBody(QByteArray::fromStdString(picojson::value(array).serialize()));
 }
@@ -115,22 +117,22 @@ void DatabaseUpdatesTest::updateb(Context *c)
 void DatabaseUpdatesTest::updates_postgres(Context *c)
 {
     QSqlQuery query = CPreparedSqlQueryThreadForDB(
-                QLatin1String("SELECT id, randomNumber FROM world WHERE id = :id"),
-                QStringLiteral("postgres"));
+                u"SELECT id, randomNumber FROM world WHERE id = :id"_qs,
+                u"postgres"_qs);
     QSqlQuery updateQuery = CPreparedSqlQueryThreadForDB(
-                QLatin1String("UPDATE world SET randomNumber = :randomNumber WHERE id = :id"),
-                QStringLiteral("postgres"));
+                u"UPDATE world SET randomNumber = :randomNumber WHERE id = :id"_qs,
+                u"postgres"_qs);
     processQuery(c, query, updateQuery);
 }
 
 void DatabaseUpdatesTest::updates_mysql(Context *c)
 {
     QSqlQuery query = CPreparedSqlQueryThreadForDB(
-                QLatin1String("SELECT randomNumber, id FROM world WHERE id = :id"),
-                QStringLiteral("mysql"));
+                u"SELECT randomNumber, id FROM world WHERE id = :id"_qs,
+                u"mysql"_qs);
     QSqlQuery updateQuery = CPreparedSqlQueryThreadForDB(
-                QLatin1String("UPDATE world SET randomNumber = :randomNumber WHERE id = :id"),
-                QStringLiteral("mysql"));
+                u"UPDATE world SET randomNumber = :randomNumber WHERE id = :id"_qs,
+                u"mysql"_qs);
     processQuery(c, query, updateQuery);
 }
 
@@ -138,7 +140,7 @@ void DatabaseUpdatesTest::processQuery(Context *c, QSqlQuery &query, QSqlQuery &
 {
     QJsonArray array;
 
-    int queries = c->request()->queryParam(QStringLiteral("queries"), QStringLiteral("1")).toInt();
+    int queries = c->request()->queryParam(u"queries"_qs, u"1"_qs).toInt();
     if (queries < 1) {
         queries = 1;
     } else if (queries > 500) {
@@ -149,26 +151,26 @@ void DatabaseUpdatesTest::processQuery(Context *c, QSqlQuery &query, QSqlQuery &
     ids.reserve(queries);
     randomNumbers.reserve(queries);
     for (int i = 0; i < queries; ++i) {
-        int id = (qrand() % 10000) + 1;
+        int id = (rand() % 10000) + 1;
 
-        query.bindValue(QStringLiteral(":id"), id);
+        query.bindValue(u":id"_qs, id);
         if (Q_UNLIKELY(!query.exec() || !query.next())) {
             c->res()->setStatus(Response::InternalServerError);
             return;
         }
 
-        int randomNumber = (qrand() % 10000) + 1;
+        int randomNumber = (rand() % 10000) + 1;
         ids.append(id);
         randomNumbers.append(randomNumber);
 
         array.append(QJsonObject{
-                         {QStringLiteral("id"), id},
-                         {QStringLiteral("randomNumber"), randomNumber}
+                         {u"id"_qs, id},
+                         {u"randomNumber"_qs, randomNumber}
                      });
     }
 
-    updateQuery.bindValue(QStringLiteral(":id"), ids);
-    updateQuery.bindValue(QStringLiteral(":randomNumber"), randomNumbers);
+    updateQuery.bindValue(u":id"_qs, ids);
+    updateQuery.bindValue(u":randomNumber"_qs, randomNumbers);
     if (Q_LIKELY(updateQuery.execBatch())) {
         c->response()->setJsonArrayBody(array);
     } else {
@@ -179,29 +181,28 @@ void DatabaseUpdatesTest::processQuery(Context *c, QSqlQuery &query, QSqlQuery &
 APreparedQuery DatabaseUpdatesTest::getSql(int count)
 {
     auto iter = m_sqlMap.find(count);
-    if (iter != m_sqlMap.end())
-    {
-        return iter.value();
-    }
-    QString sql = QStringLiteral("UPDATE WORLD SET randomnumber=CASE id ");
-    sql.reserve(80 + count * 25);
-    int placeholdersCounter = 1;
-    for (int i = 0; i < count; i++) {
-        sql.append(QStringLiteral("WHEN $%1 THEN $%2 ").arg(placeholdersCounter).arg(placeholdersCounter + 1));
-        placeholdersCounter += 2;
-    }
-    sql.append(QStringLiteral("ELSE randomnumber END WHERE id IN ("));
+    if (Q_UNLIKELY(iter == m_sqlMap.end())) {
+        QString sql = u"UPDATE WORLD SET randomnumber=CASE id "_qs;
+        sql.reserve(80 + count * 25);
+        int placeholdersCounter = 1;
+        for (int i = 0; i < count; i++) {
+            sql.append(u"WHEN $%1 THEN $%2 "_qs.arg(placeholdersCounter).arg(placeholdersCounter + 1));
+            placeholdersCounter += 2;
+        }
+        sql.append(u"ELSE randomnumber END WHERE id IN (");
 
-    for (int i = 0; i < count; i++) {
-        sql.append(QLatin1Char('$') + QString::number(placeholdersCounter) + QLatin1Char(','));
-        ++placeholdersCounter;
-    }
+        for (int i = 0; i < count; i++) {
+            sql.append(u'$' + QString::number(placeholdersCounter) + u',');
+            ++placeholdersCounter;
+        }
+
+        if (count) {
+            sql.remove(sql.size() - 1, 1);
+        }
+        sql.append(u')');
 
-    if (count) {
-        sql.remove(sql.size() - 1, 1);
+        iter = m_sqlMap.insert(count, APreparedQuery(sql));
     }
-    sql.append(QLatin1Char(')'));
-    m_sqlMap.insert(count, sql);
 
-    return sql;
+    return iter.value();
 }

+ 4 - 4
frameworks/C++/cutelyst/src/databaseupdatestest.h

@@ -20,17 +20,17 @@ public:
     C_ATTR(updateb, :Local :AutoArgs)
     void updateb(Context *c);
 
-    C_ATTR(updates_postgres, :Local :AutoArgs)
+    C_ATTR(updates_postgres, :Path('ups_QPG') :AutoArgs)
     void updates_postgres(Context *c);
 
-    C_ATTR(updates_mysql, :Local :AutoArgs)
+    C_ATTR(updates_mysql, :Path('ups_QMY') :AutoArgs)
     void updates_mysql(Context *c);
 
 private:
     inline void processQuery(Context *c, QSqlQuery &query, QSqlQuery &updateQuery);
-    inline APreparedQuery getSql(int count);
+    inline ASql::APreparedQuery getSql(int count);
 
-    QMap<int, APreparedQuery> m_sqlMap;
+    QMap<int, ASql::APreparedQuery> m_sqlMap;
 };
 
 #endif // DATABASEUPDATESTEST_H

+ 29 - 27
frameworks/C++/cutelyst/src/fortunetest.cpp

@@ -9,6 +9,8 @@
 
 #include <QSqlQuery>
 
+using namespace ASql;
+
 FortuneTest::FortuneTest(QObject *parent) : Controller(parent)
 {
 
@@ -18,7 +20,7 @@ void FortuneTest::fortunes_raw_p(Context *c)
 {
     ASync async(c);
     static thread_local auto db = APool::database();
-    db.exec(APreparedQueryLiteral(u"SELECT id, message FROM fortune"), [c, async, this] (AResult &result) {
+    db.exec(APreparedQueryLiteral(u8"SELECT id, message FROM fortune"), c, [c, async, this] (AResult &result) {
         if (Q_UNLIKELY(result.error() && !result.size())) {
             c->res()->setStatus(Response::InternalServerError);
             return;
@@ -31,21 +33,21 @@ void FortuneTest::fortunes_raw_p(Context *c)
             fortunes.emplace_back(Fortune{it[0].toInt(), it[1].toString()});
             ++it;
         }
-        fortunes.emplace_back(Fortune{0, QStringLiteral("Additional fortune added at request time.")});
+        fortunes.emplace_back(Fortune{0, u"Additional fortune added at request time."_qs});
 
         std::sort(fortunes.begin(), fortunes.end(), [] (const Fortune &a1, const Fortune &a2) {
             return a1.message < a2.message;
         });
 
         renderRaw(c, fortunes);
-    }, c);
+    });
 }
 
 void FortuneTest::fortunes_raw_postgres(Context *c)
 {
     QSqlQuery query = CPreparedSqlQueryThreadForDB(
-                QLatin1String("SELECT id, message FROM fortune"),
-                QStringLiteral("postgres"));
+                u"SELECT id, message FROM fortune"_qs,
+                u"postgres"_qs);
     auto fortunes = processQuery(c, query);
     renderRaw(c, fortunes);
 }
@@ -53,8 +55,8 @@ void FortuneTest::fortunes_raw_postgres(Context *c)
 void FortuneTest::fortunes_raw_mysql(Context *c)
 {
     QSqlQuery query = CPreparedSqlQueryThreadForDB(
-                QLatin1String("SELECT id, message FROM fortune"),
-                QStringLiteral("mysql"));
+                u"SELECT id, message FROM fortune"_qs,
+                u"mysql"_qs);
     auto fortunes = processQuery(c, query);
     renderRaw(c, fortunes);
 }
@@ -63,7 +65,7 @@ void FortuneTest::fortunes_c_p(Context *c)
 {
     ASync async(c);
     static thread_local auto db = APool::database();
-    db.exec(APreparedQueryLiteral(u"SELECT id, message FROM fortune"), [c, async] (AResult &result) {
+    db.exec(APreparedQueryLiteral(u8"SELECT id, message FROM fortune"), c, [c, async] (AResult &result) {
         if (Q_UNLIKELY(result.error() && !result.size())) {
             c->res()->setStatus(Response::InternalServerError);
             return;
@@ -80,59 +82,59 @@ void FortuneTest::fortunes_c_p(Context *c)
         }
 
         fortunes.append(QVariant::fromValue(QVariantList{
-                            {0, QStringLiteral("Additional fortune added at request time.")},
+                            {0, u"Additional fortune added at request time."_qs},
                         }));
         std::sort(fortunes.begin(), fortunes.end(), [] (const QVariant &a1, const QVariant &a2) {
             return a1.toList()[1].toString() < a2.toList()[1].toString();
         });
 
-        c->setStash(QStringLiteral("template"), QStringLiteral("fortunes.html"));
-        c->setStash(QStringLiteral("fortunes"), fortunes);
+        c->setStash(u"template"_qs, u"fortunes.html"_qs);
+        c->setStash(u"fortunes"_qs, fortunes);
         static thread_local View *view = c->view();
         view->execute(c);
-        c->response()->setContentType(QStringLiteral("text/html; charset=UTF-8"));
-    }, c);
+        c->response()->setContentType("text/html; charset=UTF-8"_qba);
+    });
 }
 
 void FortuneTest::fortunes_cutelee_postgres(Context *c)
 {
     QSqlQuery query = CPreparedSqlQueryThreadForDB(
-                QLatin1String("SELECT id, message FROM fortune"),
-                QStringLiteral("postgres"));
+                u"SELECT id, message FROM fortune"_qs,
+                u"postgres"_qs);
     if (query.exec()) {
         QVariantList fortunes = Sql::queryToList(query);
         fortunes.append(QVariant::fromValue(QVariantList{
-                            {0, QStringLiteral("Additional fortune added at request time.")},
+                            {0, u"Additional fortune added at request time."_qs},
                         }));
         std::sort(fortunes.begin(), fortunes.end(), [] (const QVariant &a1, const QVariant &a2) {
             return a1.toList()[1].toString() < a2.toList()[1].toString();
         });
-        c->setStash(QStringLiteral("template"), QStringLiteral("fortunes.html"));
-        c->setStash(QStringLiteral("fortunes"), fortunes);
+        c->setStash(u"template"_qs, u"fortunes.html"_qs);
+        c->setStash(u"fortunes"_qs, fortunes);
         static thread_local View *view = c->view();
         view->execute(c);
-        c->response()->setContentType(QStringLiteral("text/html; charset=UTF-8"));
+        c->response()->setContentType("text/html; charset=UTF-8"_qba);
     }
 }
 
 void FortuneTest::fortunes_cutelee_mysql(Context *c)
 {
     QSqlQuery query = CPreparedSqlQueryThreadForDB(
-                QLatin1String("SELECT id, message FROM fortune"),
-                QStringLiteral("mysql"));
+                u"SELECT id, message FROM fortune"_qs,
+                u"mysql"_qs);
     if (query.exec()) {
         QVariantList fortunes = Sql::queryToList(query);
         fortunes.append(QVariant::fromValue(QVariantList{
-                            {0, QStringLiteral("Additional fortune added at request time.")},
+                            {0, u"Additional fortune added at request time."_qs},
                         }));
         std::sort(fortunes.begin(), fortunes.end(), [] (const QVariant &a1, const QVariant &a2) {
             return a1.toList()[1].toString() < a2.toList()[1].toString();
         });
-        c->setStash(QStringLiteral("template"), QStringLiteral("fortunes.html"));
-        c->setStash(QStringLiteral("fortunes"), fortunes);
+        c->setStash(u"template"_qs, u"fortunes.html"_qs);
+        c->setStash(u"fortunes"_qs, fortunes);
         static thread_local View *view = c->view();
         view->execute(c);
-        c->response()->setContentType(QStringLiteral("text/html; charset=UTF-8"));
+        c->response()->setContentType("text/html; charset=UTF-8"_qba);
     }
 }
 
@@ -149,7 +151,7 @@ FortuneList FortuneTest::processQuery(Context *c, QSqlQuery &query)
     while (query.next()) {
         fortunes.push_back({query.value(0).toInt(), query.value(1).toString()});
     }
-    fortunes.push_back({0, QStringLiteral("Additional fortune added at request time.")});
+    fortunes.push_back({0, u"Additional fortune added at request time."_qs});
 
     std::sort(fortunes.begin(), fortunes.end(), [] (const Fortune &a1, const Fortune &a2) {
         return a1.message < a2.message;
@@ -181,5 +183,5 @@ void FortuneTest::renderRaw(Context *c, const FortuneList &fortunes) const
 
     auto response = c->response();
     response->setBody(out);
-    response->setContentType(QStringLiteral("text/html; charset=UTF-8"));
+    response->setContentType("text/html; charset=UTF-8"_qba);
 }

+ 6 - 6
frameworks/C++/cutelyst/src/fortunetest.h

@@ -19,22 +19,22 @@ class FortuneTest : public Controller
 public:
     explicit FortuneTest(QObject *parent = 0);
 
-    C_ATTR(fortunes_raw_p, :Local :AutoArgs)
+    C_ATTR(fortunes_raw_p, :Path('f_RW_APG') :AutoArgs)
     void fortunes_raw_p(Context *c);
 
-    C_ATTR(fortunes_raw_postgres, :Local :AutoArgs)
+    C_ATTR(fortunes_raw_postgres, :Path('f_RW_QPG') :AutoArgs)
     void fortunes_raw_postgres(Context *c);
 
-    C_ATTR(fortunes_raw_mysql, :Local :AutoArgs)
+    C_ATTR(fortunes_raw_mysql, :Path('f_RW_QMY') :AutoArgs)
     void fortunes_raw_mysql(Context *c);
 
-    C_ATTR(fortunes_c_p, :Local :AutoArgs)
+    C_ATTR(fortunes_c_p, :Path('f_CL_APG') :AutoArgs)
     void fortunes_c_p(Context *c);
 
-    C_ATTR(fortunes_cutelee_postgres, :Local :AutoArgs)
+    C_ATTR(fortunes_cutelee_postgres, :Path('f_CL_QPG') :AutoArgs)
     void fortunes_cutelee_postgres(Context *c);
 
-    C_ATTR(fortunes_cutelee_mysql, :Local :AutoArgs)
+    C_ATTR(fortunes_cutelee_mysql, :Path('f_CL_QMY') :AutoArgs)
     void fortunes_cutelee_mysql(Context *c);
 
 private:

+ 1 - 1
frameworks/C++/cutelyst/src/jsontest.cpp

@@ -12,7 +12,7 @@ JsonTest::JsonTest(QObject *parent) : Controller(parent)
 
 void JsonTest::json(Context *c)
 {
-    c->response()->setJsonObjectBody({ {QStringLiteral("message"), QStringLiteral("Hello, World!")} });
+    c->response()->setJsonObjectBody({ {u"message"_qs, u"Hello, World!"_qs} });
 }
 
 void JsonTest::pson(Context *c)

+ 18 - 16
frameworks/C++/cutelyst/src/multipledatabasequeriestest.cpp

@@ -12,6 +12,8 @@
 #include <QJsonObject>
 #include <QJsonArray>
 
+using namespace ASql;
+
 MultipleDatabaseQueriesTest::MultipleDatabaseQueriesTest(QObject *parent) : Controller(parent)
 {
 
@@ -19,7 +21,7 @@ MultipleDatabaseQueriesTest::MultipleDatabaseQueriesTest(QObject *parent) : Cont
 
 void MultipleDatabaseQueriesTest::queriesp(Context *c)
 {
-    int queries = c->request()->queryParam(QStringLiteral("queries")).toInt();
+    int queries = c->request()->queryParam(u"queries"_qs).toInt();
     if (queries < 1) {
         queries = 1;
     } else if (queries > 500) {
@@ -30,15 +32,15 @@ void MultipleDatabaseQueriesTest::queriesp(Context *c)
     ASync async(c);
     static thread_local auto db = APool::database();
     for (int i = 0; i < queries; ++i) {
-        const int id = (qrand() % 10000) + 1;
+        const int id = (rand() % 10000) + 1;
 
-        db.exec(APreparedQueryLiteral(u"SELECT id, randomNumber FROM world WHERE id=$1"),
-                               {id}, [c, async, i, queries, array] (AResult &result) {
+        db.exec(APreparedQueryLiteral(u8"SELECT id, randomNumber FROM world WHERE id=$1"),
+                               {id}, c, [c, async, i, queries, array] (AResult &result) {
             if (Q_LIKELY(!result.error() && result.size())) {
                 auto it = result.begin();
                 array->append(QJsonObject{
-                                  {QStringLiteral("id"), it[0].toInt()},
-                                  {QStringLiteral("randomNumber"), it[1].toInt()}
+                                  {u"id"_qs, it[0].toInt()},
+                                  {u"randomNumber"_qs, it[1].toInt()}
                               });
 
                 if (i + 1 == queries) {
@@ -48,23 +50,23 @@ void MultipleDatabaseQueriesTest::queriesp(Context *c)
             }
 
             c->res()->setStatus(Response::InternalServerError);
-        }, c);
+        });
     }
 }
 
 void MultipleDatabaseQueriesTest::query_postgres(Context *c)
 {
     QSqlQuery query = CPreparedSqlQueryThreadForDB(
-                QLatin1String("SELECT id, randomNumber FROM world WHERE id = :id"),
-                QStringLiteral("postgres"));
+                u"SELECT id, randomNumber FROM world WHERE id = :id"_qs,
+                u"postgres"_qs);
     processQuery(c, query);
 }
 
 void MultipleDatabaseQueriesTest::query_mysql(Context *c)
 {
     QSqlQuery query = CPreparedSqlQueryThreadForDB(
-                QLatin1String("SELECT id, randomNumber FROM world WHERE id = :id"),
-                QStringLiteral("mysql"));
+                u"SELECT id, randomNumber FROM world WHERE id = :id"_qs,
+                u"mysql"_qs);
     processQuery(c, query);
 }
 
@@ -72,7 +74,7 @@ void MultipleDatabaseQueriesTest::processQuery(Context *c, QSqlQuery &query)
 {
     QJsonArray array;
 
-    int queries = c->request()->queryParam(QStringLiteral("queries")).toInt();
+    int queries = c->request()->queryParam(u"queries"_qs).toInt();
     if (queries < 1) {
         queries = 1;
     } else if (queries > 500) {
@@ -80,13 +82,13 @@ void MultipleDatabaseQueriesTest::processQuery(Context *c, QSqlQuery &query)
     }
 
     for (int i = 0; i < queries; ++i) {
-        const int id = (qrand() % 10000) + 1;
+        const int id = (rand() % 10000) + 1;
 
-        query.bindValue(QStringLiteral(":id"), id);
+        query.bindValue(u":id"_qs, id);
         if (Q_LIKELY(query.exec() && query.next())) {
             array.append(QJsonObject{
-                             {QStringLiteral("id"), query.value(0).toInt()},
-                             {QStringLiteral("randomNumber"), query.value(1).toInt()}
+                             {u"id"_qs, query.value(0).toInt()},
+                             {u"randomNumber"_qs, query.value(1).toInt()}
                          });
         } else {
             c->res()->setStatus(Response::InternalServerError);

+ 3 - 3
frameworks/C++/cutelyst/src/multipledatabasequeriestest.h

@@ -13,13 +13,13 @@ class MultipleDatabaseQueriesTest : public Controller
 public:
     explicit MultipleDatabaseQueriesTest(QObject *parent = 0);
 
-    C_ATTR(queriesp, :Local :AutoArgs)
+    C_ATTR(queriesp, :Path('querAPG') :AutoArgs)
     void queriesp(Context *c);
 
-    C_ATTR(query_postgres, :Local :AutoArgs)
+    C_ATTR(query_postgres, :Path('queryPG') :AutoArgs)
     void query_postgres(Context *c);
 
-    C_ATTR(query_mysql, :Local :AutoArgs)
+    C_ATTR(query_mysql, :Path('queryMY') :AutoArgs)
     void query_mysql(Context *c);
 
 private:

+ 2 - 2
frameworks/C++/cutelyst/src/plaintexttest.cpp

@@ -8,6 +8,6 @@ PlaintextTest::PlaintextTest(QObject *parent) : Controller(parent)
 void PlaintextTest::plaintext(Context *c)
 {
     Response *res = c->response();
-    res->setBody(QByteArrayLiteral("Hello, World!"));
-    res->setContentType(QStringLiteral("text/plain"));
+    res->setBody("Hello, World!"_qba);
+    res->setContentType("text/plain"_qba);
 }

+ 0 - 37
frameworks/C++/cutelyst/src/root.cpp

@@ -1,37 +0,0 @@
-#include "root.h"
-
-#include <QElapsedTimer>
-
-using namespace Cutelyst;
-
-Root::Root(QObject *parent) : Controller(parent)
-{
-}
-
-Root::~Root()
-{
-}
-
-QElapsedTimer timerSetup(Context *c)
-{
-    QElapsedTimer timer;
-    timer.start();
-    return timer;
-}
-
-QString setupHeader(Context *c)
-{
-    return c->response()->headers().setDateWithDateTime(QDateTime::currentDateTimeUtc());
-}
-
-void Root::End(Context *c)
-{
-    static thread_local QString lastDate = setupHeader(c);
-    static thread_local QElapsedTimer timer = timerSetup(c);
-    if (timer.hasExpired(1000)) {
-        lastDate = setupHeader(c);
-        timer.restart();
-    } else {
-        c->response()->setHeader(QStringLiteral("date"), lastDate);
-    }
-}

+ 0 - 22
frameworks/C++/cutelyst/src/root.h

@@ -1,22 +0,0 @@
-#ifndef ROOT_H
-#define ROOT_H
-
-#include <Cutelyst/Controller>
-
-using namespace Cutelyst;
-
-class Root : public Controller
-{
-    Q_OBJECT
-    C_NAMESPACE("")
-public:
-    explicit Root(QObject *parent = 0);
-    ~Root();
-
-private:
-    C_ATTR(End, :AutoArgs)
-    void End(Context *c);
-};
-
-#endif //ROOT_H
-

+ 46 - 13
frameworks/C++/cutelyst/src/singledatabasequerytest.cpp

@@ -10,22 +10,55 @@
 
 #include <QJsonDocument>
 #include <QJsonObject>
+#include <QTimer>
 
 #include "picojson.h"
 
+using namespace ASql;
+
 SingleDatabaseQueryTest::SingleDatabaseQueryTest(QObject *parent) : Controller(parent)
 {
 
 }
 
-void SingleDatabaseQueryTest::dbp(Context *c)
+void SingleDatabaseQueryTest::db_asql_pg(Context *c)
 {
-    const int id = (qrand() % 10000) + 1;
+    const int id = (rand() % 10000) + 1;
 
     ASync async(c);
     static thread_local auto db = APool::database();
-    db.exec(APreparedQueryLiteral(u"SELECT id, randomNumber FROM world WHERE id=$1"),
-                           {id}, [c, async] (AResult &result) {
+
+    db.exec(APreparedQueryLiteral(u8"SELECT id, randomNumber FROM world WHERE id=$1"),
+                           {id}, c, [c, async] (AResult &result) {
+        if (Q_LIKELY(!result.error() && result.size())) {
+            auto it = result.begin();
+            c->response()->setJsonBody(QByteArray::fromStdString(
+                            picojson::value(picojson::object({
+                                                {"id", picojson::value(double(it[0].toInt()))},
+                                                {"randomNumber", picojson::value(double(it[1].toInt()))}
+                                            })).serialize()));
+            return;
+        }
+
+        c->res()->setStatus(Response::InternalServerError);
+    });
+}
+
+ADatabase getPipelineEnabledDatabase()
+{
+    auto db = APool::database();
+    db.enterPipelineMode(300);
+    return db;
+}
+
+void SingleDatabaseQueryTest::db_asql_pipeline_pg(Context *c)
+{
+    const int id = (rand() % 10000) + 1;
+
+    ASync async(c);
+    static thread_local auto db = getPipelineEnabledDatabase();
+    db.exec(APreparedQueryLiteral(u8"SELECT id, randomNumber FROM world WHERE id=$1"),
+                           {id}, c, [c, async] (AResult &result) {
         if (Q_LIKELY(!result.error() && result.size())) {
             auto it = result.begin();
             c->response()->setJsonBody(QByteArray::fromStdString(
@@ -37,37 +70,37 @@ void SingleDatabaseQueryTest::dbp(Context *c)
         }
 
         c->res()->setStatus(Response::InternalServerError);
-    }, c);
+    });
 }
 
 void SingleDatabaseQueryTest::db_postgres(Context *c)
 {
     QSqlQuery query = CPreparedSqlQueryThreadForDB(
-                QLatin1String("SELECT id, randomNumber FROM world WHERE id = :id"),
-                QStringLiteral("postgres"));
+                u"SELECT id, randomNumber FROM world WHERE id = :id"_qs,
+                u"postgres"_qs);
     processQuery(c, query);
 }
 
 void SingleDatabaseQueryTest::db_mysql(Context *c)
 {
     QSqlQuery query = CPreparedSqlQueryThreadForDB(
-                QLatin1String("SELECT id, randomNumber FROM world WHERE id = :id"),
-                QStringLiteral("mysql"));
+                u"SELECT id, randomNumber FROM world WHERE id = :id"_qs,
+                u"mysql"_qs);
     processQuery(c, query);
 }
 
 void SingleDatabaseQueryTest::processQuery(Context *c, QSqlQuery &query)
 {
-    int id = (qrand() % 10000) + 1;
+    int id = (rand() % 10000) + 1;
 
-    query.bindValue(QStringLiteral(":id"), id);
+    query.bindValue(u":id"_qs, id);
     if (Q_UNLIKELY(!query.exec() || !query.next())) {
         c->res()->setStatus(Response::InternalServerError);
         return;
     }
 
     c->response()->setJsonObjectBody({
-                                         {QStringLiteral("id"), query.value(0).toInt()},
-                                         {QStringLiteral("randomNumber"), query.value(1).toInt()}
+                                         {u"id"_qs, query.value(0).toInt()},
+                                         {u"randomNumber"_qs, query.value(1).toInt()}
                                      });
 }

+ 7 - 4
frameworks/C++/cutelyst/src/singledatabasequerytest.h

@@ -13,13 +13,16 @@ class SingleDatabaseQueryTest : public Controller
 public:
     explicit SingleDatabaseQueryTest(QObject *parent = 0);
 
-    C_ATTR(dbp, :Local :AutoArgs)
-    void dbp(Context *c);
+    C_ATTR(db_asql_pg, :Path('pg') :AutoArgs)
+    void db_asql_pg(Context *c);
 
-    C_ATTR(db_postgres, :Local :AutoArgs)
+    C_ATTR(db_asql_pipeline_pg, :Path('Pg') :AutoArgs)
+    void db_asql_pipeline_pg(Context *c);
+
+    C_ATTR(db_postgres, :Path('PG') :AutoArgs)
     void db_postgres(Context *c);
 
-    C_ATTR(db_mysql, :Local :AutoArgs)
+    C_ATTR(db_mysql, :Path('MY') :AutoArgs)
     void db_mysql(Context *c);
 
 private:

+ 14 - 18
frameworks/C++/drogon/drogon-core.dockerfile

@@ -1,4 +1,4 @@
-FROM ubuntu:18.04
+FROM ubuntu:20.04
 
 COPY ./ ./
 
@@ -11,7 +11,7 @@ RUN  apt-get update -yqq && \
      zlib1g-dev && \
      add-apt-repository ppa:ubuntu-toolchain-r/test -y && \
 	 apt-get update -yqq && \
-	 apt-get install -yqq gcc-8 g++-8
+	 apt-get install -yqq gcc-10 g++-10
 
 RUN locale-gen en_US.UTF-8
 
@@ -19,10 +19,10 @@ ENV LANG en_US.UTF-8
 ENV LANGUAGE en_US:en
 ENV LC_ALL en_US.UTF-8
 
-ENV CC=gcc-8
-ENV CXX=g++-8
-ENV AR=gcc-ar-8
-ENV RANLIB=gcc-ranlib-8
+ENV CC=gcc-10
+ENV CXX=g++-10
+ENV AR=gcc-ar-10
+ENV RANLIB=gcc-ranlib-10
 
 ENV IROOT=/install
 ENV DROGON_ROOT=$IROOT/drogon
@@ -32,26 +32,22 @@ ENV TEST_PATH=/drogon_benchmark/build
 
 WORKDIR $IROOT
 
-RUN wget https://github.com/an-tao/postgres/archive/batch_mode_ubuntu.tar.gz
-RUN tar -xvzf batch_mode_ubuntu.tar.gz
-WORKDIR $PG_ROOT
-
-RUN ./configure --prefix=/usr CFLAGS='-O2 -pipe'
-RUN make && make install
-
-WORKDIR $IROOT
+RUN sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
+RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
+RUN sudo apt -y update
+RUN sudo apt -y install postgresql-server-dev-all
 
 RUN git clone https://github.com/an-tao/drogon
 
 WORKDIR $DROGON_ROOT
 
-RUN git checkout fb17efe765d162be01680b05a3a387c7a182a4c5
+RUN git checkout ebf87d69d7bb45dfa478ba364ef9374d9be25092
 RUN git submodule update --init
 RUN mkdir build
 
 WORKDIR $DROGON_ROOT/build
 
-RUN cmake -DCMAKE_BUILD_TYPE=release ..
+RUN cmake -DCMAKE_BUILD_TYPE=release -DCMAKE_CXX_FLAGS=-flto ..
 RUN make && make install
 
 WORKDIR $IROOT
@@ -62,12 +58,12 @@ WORKDIR $MIMALLOC_ROOT
 RUN git checkout v1.6.7 -b v1.6.7
 RUN mkdir -p out/release
 WORKDIR $MIMALLOC_ROOT/out/release
-RUN cmake ../..
+RUN cmake -DCMAKE_BUILD_TYPE=release -DCMAKE_CXX_FLAGS=-flto ../..
 RUN make && make install
 
 WORKDIR $TEST_PATH
 
-RUN cmake -DCMAKE_BUILD_TYPE=release ..
+RUN cmake -DCMAKE_BUILD_TYPE=release -DCMAKE_CXX_FLAGS=-flto ..
 RUN make
 
 EXPOSE 8080

+ 14 - 18
frameworks/C++/drogon/drogon.dockerfile

@@ -1,4 +1,4 @@
-FROM ubuntu:18.04
+FROM ubuntu:20.04
 
 COPY ./ ./
 
@@ -11,7 +11,7 @@ RUN  apt-get update -yqq && \
      zlib1g-dev && \
      add-apt-repository ppa:ubuntu-toolchain-r/test -y && \
 	 apt-get update -yqq && \
-	 apt-get install -yqq gcc-8 g++-8
+	 apt-get install -yqq gcc-10 g++-10
 
 RUN locale-gen en_US.UTF-8
 
@@ -19,10 +19,10 @@ ENV LANG en_US.UTF-8
 ENV LANGUAGE en_US:en
 ENV LC_ALL en_US.UTF-8
 
-ENV CC=gcc-8
-ENV CXX=g++-8
-ENV AR=gcc-ar-8
-ENV RANLIB=gcc-ranlib-8
+ENV CC=gcc-10
+ENV CXX=g++-10
+ENV AR=gcc-ar-10
+ENV RANLIB=gcc-ranlib-10
 
 ENV IROOT=/install
 ENV DROGON_ROOT=$IROOT/drogon
@@ -32,26 +32,22 @@ ENV TEST_PATH=/drogon_benchmark/build
 
 WORKDIR $IROOT
 
-RUN wget https://github.com/an-tao/postgres/archive/batch_mode_ubuntu.tar.gz
-RUN tar -xvzf batch_mode_ubuntu.tar.gz
-WORKDIR $PG_ROOT
-
-RUN ./configure --prefix=/usr CFLAGS='-O2 -pipe'
-RUN make && make install
-
-WORKDIR $IROOT
+RUN sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
+RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
+RUN sudo apt -y update
+RUN sudo apt -y install postgresql-server-dev-all
 
 RUN git clone https://github.com/an-tao/drogon
 
 WORKDIR $DROGON_ROOT
 
-RUN git checkout fb17efe765d162be01680b05a3a387c7a182a4c5
+RUN git checkout ebf87d69d7bb45dfa478ba364ef9374d9be25092
 RUN git submodule update --init
 RUN mkdir build
 
 WORKDIR $DROGON_ROOT/build
 
-RUN cmake -DCMAKE_BUILD_TYPE=release ..
+RUN cmake -DCMAKE_BUILD_TYPE=release -DCMAKE_CXX_FLAGS=-flto ..
 RUN make && make install
 
 WORKDIR $IROOT
@@ -62,12 +58,12 @@ WORKDIR $MIMALLOC_ROOT
 RUN git checkout v1.6.7 -b v1.6.7
 RUN mkdir -p out/release
 WORKDIR $MIMALLOC_ROOT/out/release
-RUN cmake ../..
+RUN cmake -DCMAKE_BUILD_TYPE=release -DCMAKE_CXX_FLAGS=-flto ../..
 RUN make && make install
 
 WORKDIR $TEST_PATH
 
-RUN cmake -DCMAKE_BUILD_TYPE=release ..
+RUN cmake -DCMAKE_BUILD_TYPE=release -DCMAKE_CXX_FLAGS=-flto ..
 RUN make
 
 EXPOSE 8080

+ 2 - 1
frameworks/C++/drogon/drogon_benchmark/config-core.json

@@ -34,7 +34,8 @@
         //any synchronous interface of it.
         "is_fast": true,
         //connection_number:1 by default
-        "connection_number": 1
+        "connection_number": 1,
+        "auto_batch": false
     }],
     "app": {
         //threads_num:the number of IO threads,1 by default, if the value is set to 0, the number of threads

+ 3 - 2
frameworks/C++/drogon/drogon_benchmark/config.json

@@ -34,7 +34,8 @@
         //any synchronous interface of it.
         "is_fast": true,
         //connection_number:1 by default
-        "connection_number": 1
+        "connection_number": 1,
+        "auto_batch": false
     }],
     "app": {
         //threads_num:the number of IO threads,1 by default, if the value is set to 0, the number of threads
@@ -185,4 +186,4 @@
     }],
     //custom_config: custom configuration for users. This object can be get by the app().getCustomConfig() method. 
     "custom_config": {}
-}
+}

+ 1 - 2
frameworks/C++/drogon/drogon_benchmark/plugins/SyncPlugin.cc

@@ -34,8 +34,7 @@ void SyncPlugin::initAndStart(const Json::Value &config)
                     {
                         auto resp = HttpResponse::newHttpResponse();
                         resp->setBody("Hello, World!");
-                        resp->setContentTypeCodeAndCustomString(
-                            CT_TEXT_PLAIN, "Content-Type: text/plain\r\n");
+                        resp->setContentTypeCode(CT_TEXT_PLAIN);
                         return resp;
                     }
                     break;

+ 7 - 7
frameworks/C++/ffead-cpp/benchmark_config.json

@@ -23,7 +23,7 @@
 			"display_name": "ffead-cpp-mongo",
 			"notes": "mongodb redis",
 			"versus": "",
-			"tags": []
+			"tags": ["broken"]
 		},
 		"v-picov": {
 			"json_url": "/t3/j",
@@ -95,7 +95,7 @@
 			"display_name": "ffead-cpp [v-prof-b]",
 			"notes": "",
 			"versus": "",
-			"tags": []
+			"tags": ["broken"]
 		},
 		"postgresql-raw-profiled": {
 			"db_url": "/t3/d",
@@ -137,7 +137,7 @@
 			"display_name": "ffead-cpp [pg-raw-prof-b]",
 			"notes": "memory libpq batch patch profiled",
 			"versus": "",
-			"tags": []
+			"tags": ["broken"]
 		},
 		"postgresql-raw-async-profiled": {
 			"db_url": "/t4/d",
@@ -219,7 +219,7 @@
 			"display_name": "ffead-cpp [pg-raw-async-prof-b]",
 			"notes": "async memory libpq batch patch profiled",
 			"versus": "",
-			"tags": []
+			"tags": ["broken"]
 		},
 		"postgresql-raw-async-clibpqb-pool-profiled": {
 			"db_url": "/t4/d",
@@ -240,7 +240,7 @@
 			"display_name": "ffead-cpp [pg-raw-async-prof-b-pool]",
 			"notes": "async memory libpq batch patch profiled",
 			"versus": "",
-			"tags": []
+			"tags": ["broken"]
 		},
 		"postgresql-raw-async-qw-profiled": {
 			"db_url": "/t5/d",
@@ -282,7 +282,7 @@
 			"display_name": "ffead-cpp [pg-raw-async-qw-prof-b]",
 			"notes": "async memory libpq batch patch profiled",
 			"versus": "",
-			"tags": []
+			"tags": ["broken"]
 		},
 		"postgresql-raw-async-qw-pool-profiled-m": {
 			"query_url": "/t5/quem?queries=",
@@ -304,4 +304,4 @@
 			"tags": []
 		}
 	}]
-}
+}

+ 38 - 0
frameworks/C++/just-boost/README.md

@@ -0,0 +1,38 @@
+# Just.Boost Benchmarking Test
+
+## Description
+
+Backend using just C++(20) and Boost.
+
+## Run Test
+
+    cd FrameworkBenchmarks/
+    ./tfb --mode verify --test just-boost
+
+## Software Versions
+
+- [Alpine 3.18](https://hub.docker.com/_/alpine)
+- [gcc](https://gcc.gnu.org/)
+- [c++20](https://en.cppreference.com/w/cpp/20)
+- [Boost](https://www.boost.org/)
+	- [Beast](https://www.boost.org/doc/libs/1_83_0/libs/beast/doc/html/index.html) ([HTTP Server with C++ 20 coroutine](https://www.boost.org/doc/libs/1_83_0/libs/beast/example/http/server/awaitable/http_server_awaitable.cpp))
+	- [JSON](https://www.boost.org/doc/libs/1_83_0/libs/json/doc/html/index.html)
+- [libpq — C Library](https://www.postgresql.org/docs/current/libpq.html) (PostgreSQL client)
+
+## Test URLs
+
+### Test 1: JSON Encoding
+
+    http://localhost:8000/json
+
+### Test 2: Single Row Query
+
+    http://localhost:8000/db
+
+### Test 3: Multi Row Query
+
+    http://localhost:8000/queries/{count}
+
+### Test 6: Plaintext
+
+    http://localhost:8000/plaintext

+ 27 - 0
frameworks/C++/just-boost/benchmark_config.json

@@ -0,0 +1,27 @@
+{
+  "framework": "just-boost",
+  "tests": [
+    {
+      "default": {
+        "json_url": "/json",
+        "plaintext_url": "/plaintext",
+        "db_url": "/db",
+        "query_url": "/queries/",
+        "port": 8000,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "database": "Postgres",
+        "framework": "Boost",
+        "language": "C++",
+        "orm": "Raw",
+        "platform": "None",
+        "webserver": "None",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "just-boost",
+        "notes": "",
+        "versus": ""
+      }
+    }
+  ]
+}

+ 25 - 0
frameworks/C++/just-boost/just-boost.dockerfile

@@ -0,0 +1,25 @@
+# docker build --progress=plain --build-arg CXXFLAGS="-Wall" -t just-boost -f just-boost.dockerfile .
+# docker run --rm --name just-boost -p 8000:8000 -d just-boost
+# docker container stop just-boost
+FROM alpine:3.19
+
+ARG APP=just-boost
+ARG CXXFLAGS=-O3
+
+ENV LANG=C.UTF-8 LC_ALL=C.UTF-8
+ENV BCPP_PG_CONN_STR="postgres://benchmarkdbuser:benchmarkdbpass@tfb-database/hello_world"
+#ENV BCPP_N_THREADS=0 # default 0 : number of cores
+
+WORKDIR /usr/src/${APP}
+
+RUN apk add --no-cache build-base boost-dev libpq-dev
+COPY *.cpp ./
+RUN g++ ${CXXFLAGS} -std=c++20 \
+        -I$(pg_config --includedir) \
+        -o main main.cpp \
+        -L$(pg_config --libdir) -lpq \
+        && rm *.cpp
+
+EXPOSE 8000
+
+CMD ["./main"]

+ 401 - 0
frameworks/C++/just-boost/main.cpp

@@ -0,0 +1,401 @@
+#include <boost/beast/core.hpp>
+#include <boost/beast/http.hpp>
+#include <boost/beast/version.hpp>
+#include <boost/asio/ip/tcp.hpp>
+#include <boost/asio/awaitable.hpp>
+#include <boost/asio/co_spawn.hpp>
+#include <boost/asio/use_awaitable.hpp>
+#include <boost/config.hpp>
+#include <boost/json/src.hpp>
+
+#include <algorithm>
+#include <cstdlib>
+#include <ctime>
+#include <iostream>
+#include <memory>
+#include <string>
+#include <thread>
+#include <vector>
+#include <random>
+
+#if defined(BOOST_ASIO_HAS_CO_AWAIT)
+
+#include <libpq-fe.h>
+
+namespace beast = boost::beast;   // from <boost/beast.hpp>
+namespace http = beast::http;     // from <boost/beast/http.hpp>
+namespace net = boost::asio;      // from <boost/asio.hpp>
+using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
+namespace json = boost::json;     // from <boost/json.hpp>
+
+using tcp_stream = typename beast::tcp_stream::rebind_executor<
+		net::use_awaitable_t<>::executor_with_default<net::any_io_executor>>::other;
+
+namespace becpp
+{
+
+using result_ptr = std::unique_ptr<PGresult, decltype(&PQclear)>;
+
+//https://gist.github.com/ictlyh/12fe787ec265b33fd7e4b0bd08bc27cb
+result_ptr prepare(PGconn* conn,
+                   const char* stmtName,
+                   const char* command,
+                   uint8_t nParams)
+{
+	auto res = PQprepare(conn, stmtName, command, nParams, nullptr);
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		std::cerr << "PQprepare failed: " << PQresultErrorMessage(res)
+			        << std::endl;
+		PQclear(res);
+		return {nullptr, nullptr};
+	}
+
+	return {res, &PQclear};
+}
+
+result_ptr execute(PGconn* conn,
+                   const char* stmtName,
+                   uint8_t nParams,
+                   const char* const* paramValues,
+                   const int* paramLengths = nullptr,
+                   const int* paramFormats = nullptr,
+                   int resultFormat = 0)
+{
+	auto res = PQexecPrepared(conn, stmtName, nParams, paramValues, paramLengths,
+	                          paramFormats, resultFormat);
+	const auto status = PQresultStatus(res);
+	if (status != PGRES_COMMAND_OK &&
+	    status != PGRES_TUPLES_OK  &&
+	    status != PGRES_SINGLE_TUPLE)
+	{
+		std::cerr << "PQexecPrepared failed: " << PQresultErrorMessage(res)
+		          << std::endl;
+		PQclear(res);
+		return {nullptr, nullptr};
+	}
+
+	return {res, &PQclear};
+}
+
+const char* env(const char* env_var, const char* default_value)
+{
+	if (const char* env_p = std::getenv(env_var))
+		return env_p;
+
+	return default_value;
+}
+
+std::string now_string()
+{
+	std::time_t time = std::time(nullptr);
+	char timeString[std::size("Wed, 17 Apr 2013 12:00:00 GMT")];
+	std::strftime(std::data(timeString), std::size(timeString), "%a, %d %b %Y %X %Z", std::localtime(&time));
+	return timeString;
+}
+
+template <class Body, class Allocator>
+http::message_generator
+handle_error(
+	http::request<Body, http::basic_fields<Allocator>>&& req,
+	http::status status,
+	beast::string_view msg)
+{
+	http::response<http::string_body> res{status, req.version()};
+	res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
+	res.set(http::field::content_type, "text/html");
+	res.set(http::field::date, now_string());
+	res.keep_alive(req.keep_alive());
+	res.body() = std::string(msg);
+	res.prepare_payload();
+	return res;
+}
+
+template <class Body, class Allocator>
+http::message_generator
+handle_target(
+	http::request<Body, http::basic_fields<Allocator>>&& req,
+	PGconn* conn = nullptr)
+{
+	//std::cout << "handle_target: " << req.target() << std::endl;
+	http::response<http::string_body> res{http::status::ok, req.version()};
+	res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
+	res.set(http::field::content_type, "application/json");
+	res.set(http::field::date, now_string());
+	res.keep_alive(req.keep_alive());
+	
+	if (req.target() == "/json")
+	{
+		// {"message":"Hello, World!"}
+		json::object obj;
+		obj["message"] = "Hello, World!";
+		res.body() = json::serialize(obj);
+	}
+	else if (req.target() == "/plaintext")
+	{
+		res.set(http::field::content_type, "text/plain");
+		res.body() = "Hello, World!";
+	}
+	else if (req.target() == "/db" || req.target().starts_with("/queries/"))
+	{
+		static std::random_device rd;
+		static std::minstd_rand gen(rd());
+		static std::uniform_int_distribution<> distrib(1, 10000);
+		static const char* word_query = "SELECT randomNumber FROM world WHERE id=$1";
+		thread_local auto stmt = prepare(conn, "word_query_stmt", word_query, 1);
+
+		if (req.target() == "/db")
+		{
+			const unsigned uint_id = distrib(gen);
+			const auto str_id =  std::to_string(uint_id);
+			const char* char_ptr_id = str_id.c_str();
+			if (auto rs = execute(conn, "word_query_stmt", 1, &char_ptr_id))
+			{
+				// {"id":3217,"randomNumber":2149}
+				json::object obj;
+				obj["id"] = uint_id;
+				obj["randomNumber"] = std::atoi(PQgetvalue(rs.get(), 0, 0));
+				res.body() = json::serialize(obj);
+				//std::cout << "res.body(): " << res.body() << std::endl;
+			}
+			else
+				return handle_error(std::move(req),
+				                    http::status::internal_server_error,
+				                    "internal_server_error");
+		}
+		else if (req.target().starts_with("/queries/"))
+		{
+			int n_queries = 1;
+			try
+			{
+				const int n = std::stoi(req.target().substr(req.target().find_last_of('/')+1));
+				if (n > 1) n_queries = n;
+			} catch(...) {}
+			if (n_queries > 500) n_queries = 500;
+			json::array objs;
+			for (auto i = 0; i < n_queries; ++i)
+			{
+				const unsigned uint_id = distrib(gen);
+				const auto str_id =  std::to_string(uint_id);
+				const char* char_ptr_id = str_id.c_str();
+				if (auto rs = execute(conn, "word_query_stmt", 1, &char_ptr_id))
+				{
+					// {"id":3217,"randomNumber":2149}
+					json::object obj;
+					obj["id"] = uint_id;
+					obj["randomNumber"] = std::atoi(PQgetvalue(rs.get(), 0, 0));
+					objs.push_back(obj);
+				}
+				else
+					return handle_error(std::move(req),
+					                    http::status::internal_server_error,
+					                    "internal_server_error");
+			}
+			res.body() = json::serialize(objs);
+			//std::cout << "res.body(): " << res.body() << std::endl;
+		}
+	}
+	else
+	{
+		return handle_error(std::move(req),
+		                    http::status::not_found,
+		                    "Unhandled target: '" + std::string(req.target()) + "'");
+	}
+
+	res.prepare_payload();
+	return res;
+}
+
+// Return a response for the given request.
+//
+// The concrete type of the response message (which depends on the
+// request), is type-erased in message_generator.
+template <class Body, class Allocator>
+http::message_generator
+handle_request(
+	http::request<Body, http::basic_fields<Allocator>>&& req)
+{
+	// Make sure we can handle the method
+	if (req.method() != http::verb::get)
+		return handle_error(std::move(req),
+		                    http::status::not_found,
+		                    "Unhandled method: '" + std::string(req.method_string()) + "'");
+
+	if (req.target() == "/json" || req.target() == "/plaintext")
+		return handle_target(std::move(req));
+
+	thread_local std::unique_ptr<PGconn, decltype(&PQfinish)> conn
+	{
+		PQconnectdb(env("BCPP_PG_CONN_STR", "")),
+		&PQfinish
+	};
+	if (PQstatus(conn.get()) != CONNECTION_OK)
+	{
+		auto msg = PQerrorMessage(conn.get());
+		std::cerr << "PQerrorMessage: " << msg << std::endl;
+		return handle_error(std::move(req),
+		                    http::status::internal_server_error,
+		                    msg);
+	}
+	return handle_target(std::move(req), conn.get());
+}
+
+}
+//------------------------------------------------------------------------------
+
+
+// Handles an HTTP server connection
+net::awaitable<void>
+do_session(tcp_stream stream)
+{
+	// This buffer is required to persist across reads
+	beast::flat_buffer buffer;
+
+	// This lambda is used to send messages
+	try
+	{
+		for(;;)
+		{
+			// Set the timeout.
+			stream.expires_after(std::chrono::seconds(30));
+
+			// Read a request
+			http::request<http::string_body> req;
+			co_await http::async_read(stream, buffer, req);
+
+			// Handle the request
+			http::message_generator msg =
+				becpp::handle_request(std::move(req));
+
+			// Determine if we should close the connection
+			bool keep_alive = msg.keep_alive();
+
+			// Send the response
+			co_await beast::async_write(stream, std::move(msg), net::use_awaitable);
+
+			if(! keep_alive)
+			{
+				// This means we should close the connection, usually because
+				// the response indicated the "Connection: close" semantic.
+				break;
+			}
+		}
+	}
+	catch (boost::system::system_error & se)
+	{
+		if (se.code() != http::error::end_of_stream )
+			throw ;
+	}
+
+	// Send a TCP shutdown
+	beast::error_code ec;
+	stream.socket().shutdown(tcp::socket::shutdown_send, ec);
+
+	// At this point the connection is closed gracefully
+	// we ignore the error because the client might have
+	// dropped the connection already.
+}
+
+//------------------------------------------------------------------------------
+
+// Accepts incoming connections and launches the sessions
+net::awaitable<void>
+do_listen(tcp::endpoint endpoint)
+{
+	// Open the acceptor
+	auto acceptor = net::use_awaitable.as_default_on(tcp::acceptor(co_await net::this_coro::executor));
+	acceptor.open(endpoint.protocol());
+
+	// Allow address reuse
+	acceptor.set_option(net::socket_base::reuse_address(true));
+
+	// Bind to the server address
+	acceptor.bind(endpoint);
+
+	// Start listening for connections
+	acceptor.listen(net::socket_base::max_listen_connections);
+
+	for(;;)
+		boost::asio::co_spawn(
+			acceptor.get_executor(),
+				do_session(tcp_stream(co_await acceptor.async_accept())),
+				[](std::exception_ptr e)
+				{
+					if (e)
+						try
+						{
+							std::rethrow_exception(e);
+						}
+						catch (std::exception&){}
+//						catch (std::exception &e) {
+//							std::cerr << "Error in session: " << e.what() << "\n";
+//						}
+				});
+
+}
+
+int main(int argc, char* argv[])
+{
+	auto const address = net::ip::make_address(becpp::env("BCPP_ADDRESS", "0.0.0.0"));
+	auto const port = static_cast<unsigned short>(std::atoi(becpp::env("BCPP_PORT", "8000")));
+	auto env_threads = std::atoi(becpp::env("BCPP_N_THREADS", "0"));
+	if (env_threads == 0)
+	{
+		env_threads = std::thread::hardware_concurrency();
+		std::cout << "Using number of cores: " << env_threads << '\n';
+	}
+	auto const threads = std::max<int>(1, env_threads);
+
+	std::cout << "__GNUG__=" << __GNUG__ << '\n';
+	std::cout << "__cplusplus=" << __cplusplus << '\n';
+	std::cout << "__TIMESTAMP__=" << __TIMESTAMP__ << '\n';
+	std::cout << "__GNUC_EXECUTION_CHARSET_NAME=" << __GNUC_EXECUTION_CHARSET_NAME << '\n';
+	std::cout << "Listening " << address << ':' << port << " threads=" << threads << std::endl;
+
+	// The io_context is required for all I/O
+	net::io_context ioc{threads};
+
+	// Spawn a listening port
+	boost::asio::co_spawn(ioc,
+						  do_listen(tcp::endpoint{address, port}),
+						  [](std::exception_ptr e)
+						  {
+							  if (e)
+								  try
+								  {
+									  std::rethrow_exception(e);
+								  }
+								  catch(std::exception & e)
+								  {
+									  std::cerr << "Error in acceptor: " << e.what() << "\n";
+								  }
+						  });
+
+	// Run the I/O service on the requested number of threads
+	try {
+	std::vector<std::thread> v;
+	v.reserve(threads - 1);
+	for(auto i = threads - 1; i > 0; --i)
+		v.emplace_back(
+		[&ioc]
+		{
+			ioc.run();
+		});
+	ioc.run();
+	} catch (std::exception& e)
+	{
+		std::cerr << "Error in main: " << e.what() << "\n";
+	}
+
+	return EXIT_SUCCESS;
+}
+
+#else
+
+int main(int, char * [])
+{
+	std::printf("awaitables require C++20\n");
+	return 1;
+}
+
+#endif

+ 0 - 48
frameworks/C++/lithium/benchmark_config.json

@@ -27,12 +27,10 @@
         "versus": "None"
       },
       "postgres": {
-        "json_url"       : "/json",
         "db_url"         : "/db",
         "query_url"      : "/queries?N=",
         "fortune_url"    : "/fortunes",
         "update_url"     : "/updates?N=",
-        "plaintext_url"  : "/plaintext",
         "cached_query_url": "/cached-worlds?N=",
         "port": 8080,
         "approach": "Realistic",
@@ -51,12 +49,10 @@
         "versus": "None"
       },
       "postgres-beta": {
-        "json_url"       : "/json",
         "db_url"         : "/db",
         "query_url"      : "/queries?N=",
         "fortune_url"    : "/fortunes",
         "update_url"     : "/updates?N=",
-        "plaintext_url"  : "/plaintext",
         "cached_query_url": "/cached-worlds?N=",
         "port": 8080,
         "approach": "Realistic",
@@ -73,51 +69,7 @@
         "display_name": "Lithium-postgres-beta",
         "notes": "",
         "versus": "None"
-      },
-      "postgres-batch": {
-        "db_url"         : "/db",
-        "query_url"      : "/queries?N=",
-        "fortune_url"    : "/fortunes",
-        "update_url"     : "/updates?N=",
-        "port": 8080,
-        "approach": "Realistic",
-        "classification": "Micro",
-        "database": "Postgres",
-        "framework": "Lithium",
-        "language": "C++",
-        "flavor": "None",
-        "orm": "Full",
-        "platform": "None",
-        "webserver": "None",
-        "os": "Linux",
-        "database_os": "Linux",
-        "display_name": "Lithium-postgres-batch",
-        "notes": "",
-        "versus": "None"
-      },
-
-      "postgres-batch-beta": {
-        "db_url"         : "/db",
-        "query_url"      : "/queries?N=",
-        "fortune_url"    : "/fortunes",
-        "update_url"     : "/updates?N=",
-        "port": 8080,
-        "approach": "Realistic",
-        "classification": "Micro",
-        "database": "Postgres",
-        "framework": "Lithium",
-        "language": "C++",
-        "flavor": "None",
-        "orm": "Full",
-        "platform": "None",
-        "webserver": "None",
-        "os": "Linux",
-        "database_os": "Linux",
-        "display_name": "Lithium-postgres-batch-beta",
-        "notes": "",
-        "versus": "None"
       }
-
     }
   ]
 }

+ 3 - 1
frameworks/C++/poco/poco.dockerfile

@@ -9,7 +9,9 @@ ENV POCO_VERSION 1.6.1
 ENV POCO_HOME /poco
 
 WORKDIR ${POCO_HOME}
-RUN curl -sL http://pocoproject.org/releases/poco-${POCO_VERSION}/poco-${POCO_VERSION}-all.tar.gz | tar xz --strip-components=1
+RUN wget https://pocoproject.org/releases/poco-${POCO_VERSION}/poco-${POCO_VERSION}-all.zip
+RUN unzip poco-${POCO_VERSION}-all.zip
+RUN mv ./poco-${POCO_VERSION}-all/* ./
 
 RUN ./configure --no-tests --no-samples
 RUN make --quiet PageCompiler-libexec XML-libexec JSON-libexec

+ 9 - 0
frameworks/C++/reactor/README.md

@@ -0,0 +1,9 @@
+# [reactor](https://github.com/shaovie/reactor) (c++) Benchmarking Test
+
+This is the go portion of a [benchmarking test suite](https://www.techempower.com/benchmarks/) comparing a variety of web development platforms.
+
+"reactor is a high-performance, lightweight, i/o event-driven network framework in c++."
+
+## Test URLs
+
+    http://localhost:8080/plaintext

+ 23 - 0
frameworks/C++/reactor/benchmark_config.json

@@ -0,0 +1,23 @@
+{
+  "framework": "reactor",
+  "tests": [{
+    "default": {
+      "plaintext_url": "/plaintext",
+      "port": 8080,
+      "approach": "Stripped",
+      "classification": "Platform",
+      "database": "None",
+      "framework": "reactor",
+      "language": "C++",
+      "flavor": "None",
+      "orm": "Raw",
+      "platform": "None",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "Reactor",
+      "notes": "",
+      "versus": "c++"
+    }
+  }]
+}

+ 3 - 5
frameworks/Erlang/elli/config.toml → frameworks/C++/reactor/config.toml

@@ -1,16 +1,14 @@
 [framework]
-name = "elli"
+name = "reactor"
 
 [main]
 urls.plaintext = "/plaintext"
-urls.json = "/json"
-urls.db = "/db"
 approach = "Stripped"
 classification = "Platform"
-database = "MySQL"
+database = "None"
 database_os = "Linux"
 os = "Linux"
 orm = "Raw"
 platform = "None"
 webserver = "None"
-versus = ""
+versus = "c++"

+ 37 - 0
frameworks/C++/reactor/reactor.dockerfile

@@ -0,0 +1,37 @@
+FROM ubuntu:22.04
+MAINTAINER [email protected]
+
+RUN apt-get update -yqq
+RUN apt-get install -yqq g++-11 gcc-11 make git
+
+RUN   update-alternatives --quiet --remove-all gcc \
+    ; update-alternatives --quiet --remove-all g++ \
+    ; update-alternatives --quiet --remove-all cc \
+    ; update-alternatives --quiet --remove-all cpp \
+    ; update-alternatives --quiet --install /usr/bin/gcc gcc /usr/bin/gcc-11 20 \
+    ; update-alternatives --quiet --install /usr/bin/cc cc /usr/bin/gcc-11 20 \
+    ; update-alternatives --quiet --install /usr/bin/g++ g++ /usr/bin/g++-11 20 \
+    ; update-alternatives --quiet --install /usr/bin/cpp cpp /usr/bin/g++-11 20 \
+    ; update-alternatives --quiet --config gcc \
+    ; update-alternatives --quiet --config cc \
+    ; update-alternatives --quiet --config g++ \
+    ; update-alternatives --quiet --config cpp
+
+
+WORKDIR /reactor-bench
+
+RUN git clone https://github.com/shaovie/reactor.git
+
+
+RUN cd reactor/ && make clean all
+COPY ./techempower.cpp /reactor-bench/reactor
+
+WORKDIR /reactor-bench/reactor
+RUN g++ techempower.cpp -O2 -std=c++11 -lreactor -L./bin -lpthread -o app
+
+ENV LD_LIBRARY_PATH=./bin:$LD_LIBRARY_PATH
+
+EXPOSE 8080
+
+RUN ulimit -n 100000
+CMD ./app

+ 120 - 0
frameworks/C++/reactor/techempower.cpp

@@ -0,0 +1,120 @@
+#include "src/reactor.h"
+#include "src/io_handle.h"
+#include "src/acceptor.h"
+#include "src/options.h"
+#include "src/poll_sync_opt.h"
+
+#include <string>
+#include <signal.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <thread>
+
+reactor *conn_reactor = nullptr;
+const char httpheaders1[] = "HTTP/1.1 200 OK\r\nConnection: keep-alive\r\nServer: goev\r\nContent-Type: text/plain\r\nDate: ";
+const char httpheaders2[] = "\r\nContent-Length: 13\r\n\r\nHello, World!";
+
+const int pcache_data_t = 1;
+
+class http : public io_handle {
+public:
+    virtual bool on_open() {
+        // add_ev_handler 尽量放在最后, (on_open 和o_read可能不在一个线程)
+        if (conn_reactor->add_ev_handler(this, this->get_fd(), ev_handler::ev_read) != 0) {
+            fprintf(stderr, "add new conn to poller fail! %s\n", strerror(errno));
+            return false;
+        }
+        return true;
+    }
+    virtual bool on_read() {
+        char *buf = nullptr;
+        int ret = this->recv(buf);
+        if (ret == 0) // closed
+            return false;
+        else if (ret < 0)
+            return true;
+
+        if (::strstr(buf, "\r\n\r\n") == nullptr) {
+            // invliad msg
+            return false;
+        }
+
+        int writen = 0;
+        ::memcpy(buf, httpheaders1, sizeof(httpheaders1)-1);
+        writen += sizeof(httpheaders1)-1;
+
+        char *date = (char *)this->poll_cache_get(pcache_data_t);
+        ret = ::strlen(date);
+        ::memcpy(buf + writen, date, ret);
+        writen += ret;
+
+        ret = sizeof(httpheaders2)-1;
+        ::memcpy(buf + writen, httpheaders2, ret);
+        writen += ret;
+
+        this->send(buf, writen);
+        return true;
+    }
+    virtual void on_close() {
+        this->destroy();
+    }
+};
+ev_handler *gen_http() {
+    return new http();
+}
+void release_dates(void *p) {
+    delete[] (char *)p;
+}
+void sync_date(bool init) {
+    struct timeval now;
+    gettimeofday(&now, NULL);
+    struct tm tmv;
+    ::localtime_r(&(now.tv_sec), &tmv);
+    char dates[32] = {0};
+    ::strftime(dates, 32, "%a, %d %b %Y %H:%M:%S GMT", &tmv);
+    void **args = new void *[conn_reactor->get_poller_num()];
+    for (int i = 0; i < conn_reactor->get_poller_num(); ++i) {
+        char *ds = new char[32]{0};
+        ::strcpy(ds, dates);
+        poll_sync_opt::sync_cache *arg = new poll_sync_opt::sync_cache();
+        arg->id = pcache_data_t;
+        arg->value = ds;
+        arg->free_func = release_dates;
+        args[i] = arg;
+    }
+    if (init)
+        conn_reactor->init_poll_sync_opt(poll_sync_opt::sync_cache_t, args);
+    else
+        conn_reactor->poll_sync_opt(poll_sync_opt::sync_cache_t, args);
+}
+void sync_date_timing() {
+    while (true) {
+        std::this_thread::sleep_for(std::chrono::seconds(1));
+        sync_date(false);
+    }
+}
+int main (int argc, char *argv[]) {
+    options opt;
+    opt.set_cpu_affinity = true;
+    if (argc > 1)
+        opt.poller_num = atoi(argv[1]);
+
+    signal(SIGPIPE ,SIG_IGN);
+
+    conn_reactor = new reactor();
+    if (conn_reactor->open(opt) != 0)
+        ::exit(1);
+
+    sync_date(true);
+
+    std::thread thr(sync_date_timing);
+    thr.detach();
+
+    opt.reuse_addr = true;
+    acceptor acc(conn_reactor, gen_http);
+    if (acc.open(":8080", opt) != 0)
+        ::exit(1);
+    conn_reactor->run();
+}

+ 6 - 6
frameworks/C++/suil/benchmark/src/main.cpp

@@ -58,19 +58,19 @@ int main(int argc, char *argv[])
 
     Endpoint<SystemAttrs, PgSqlMiddleware> ep{"/",
           opt(serverConfig, std::move(config)),
-          opt(numberOfWorkers, 0)   /* Will run with number of available cores */
+          opt(connectionTimeout, 60_sec)
     };
 
     ep.middleware<PgSqlMiddleware>().setup(
             suil::env("POSTGRES_CONN", DEFAULT_POSTGRES_CONN),
-            opt(ASYNC,   true),   // connections are async
-            opt(TIMEOUT, 5_sec),  // timeout on db transactions
-            opt(EXPIRES, 30_sec)  // connections are cached for 30 seconds
+            opt(ASYNC,   true),    // connections are async
+            opt(TIMEOUT, 10_sec),  // timeout on db transactions
+            opt(EXPIRES, 30_sec)   // connections are cached for 30 seconds
     );
 
 #if SUIL_BENCH_DEV == 1
     {
-        scoped(conn, ep.middleware<PgSqlMiddleware>().conn());
+        scoped(conn, ep.middleware<PgSqlMiddleware>().conn(false));
         seedDatabase(conn);
     }
 #endif
@@ -152,4 +152,4 @@ int main(int argc, char *argv[])
     });
 
     return ep.start();
-}
+}

+ 1 - 1
frameworks/C++/suil/suil.dockerfile

@@ -2,7 +2,7 @@ FROM suilteam/base:alpine
 
 COPY ./ suil-bench
 
-ENV SUIL_VERSION=0.1.0
+ENV SUIL_VERSION=0.1.1
 ENV SUIL_VERSION_TAG=alpha
 ENV SUIL_CONFIGURATION=Release
 

+ 0 - 4
frameworks/C++/treefrog/benchmark_config.json

@@ -26,12 +26,10 @@
       "versus": ""
     },
     "mysql": {
-      "json_url": "/json/json",
       "db_url": "/world/random",
       "query_url": "/world/queries/",
       "fortune_url": "/fortune/index",
       "update_url": "/world/updates/",
-      "plaintext_url": "/world/plain",
       "cached_query_url": "/world/cached_queries/",
       "port": 8080,
       "approach": "Realistic",
@@ -73,12 +71,10 @@
       "versus": ""
     },
     "mongodb": {
-      "json_url": "/json/json",
       "db_url": "/world/mrandom",
       "query_url": "/world/mqueries/",
       "fortune_url": "/fortune/mindex",
       "update_url": "/world/mupdates/",
-      "plaintext_url": "/world/plain",
       "cached_query_url": "/world/cached_mqueries/",
       "port": 8080,
       "approach": "Realistic",

+ 47 - 15
frameworks/C++/treefrog/config/application.ini

@@ -34,12 +34,18 @@ UploadTemporaryDirectory=tmp
 # Specify setting files for SQL databases.
 SqlDatabaseSettingsFiles=database.ini
 
-# Specify the setting file for MongoDB, mongodb.ini.
+# Specify the setting file for MongoDB.
+# To access MongoDB server, uncomment the following line.
 MongoDbSettingsFile=mongodb.ini
 
-# Specify the setting file for Redis, redis.ini.
+# Specify the setting file for Redis.
+# To access Redis server, uncomment the following line.
 RedisSettingsFile=
 
+# Specify the setting file for Memcached.
+# To access Memcached server, uncomment the following line.
+MemcachedSettingsFile=
+
 # Specify the directory path to store SQL query files.
 SqlQueriesStoredDirectory=sql/
 
@@ -51,7 +57,24 @@ DirectViewRenderMode=false
 # Specify a file path for SQL query log.
 # If it's empty or the line is commented out, output to SQL query log
 # is disabled.
-SqlQueryLogFile=
+SqlQueryLog.FilePath=
+
+# Specify the layout of SQL query log.
+#  %d : date-time
+#  %p : priority (lowercase)
+#  %P : priority (uppercase)
+#  %t : thread ID (dec)
+#  %T : thread ID (hex)
+#  %i : PID (dec)
+#  %I : PID (hex)
+#  %e : elapsed processing time in milliseconds
+#  %m : log message
+#  %n : newline code
+SqlQueryLog.Layout="%d [%t] (%e) %m%n"
+
+# Specify the date-time format of SQL query log, see also QDateTime
+# class reference.
+SqlQueryLog.DateTimeFormat="yyyy-MM-dd hh:mm:ss"
 
 # Determines whether the application aborts (to create a core dump
 # on Unix systems) or not when it output a fatal message by tFatal()
@@ -95,6 +118,7 @@ Session.Name=TFSESSION
 # For 'sqlobject', the settings specified in SqlDatabaseSettingsFiles are used.
 # For 'mongodb', the settings specified in MongoDbSettingsFile are used.
 # For 'redis', the settings specified in RedisSettingsFile are used.
+# For 'memcached', the settings specified in MemcachedSettingsFile are used.
 Session.StoreType=cookie
 
 # Replaces the session ID with a new one each time one connects, and
@@ -111,6 +135,10 @@ Session.CookieDomain=
 # Specifies a path attribute to set in the session cookie. Defaults to /.
 Session.CookiePath=/
 
+# Specifies a value to assert that a cookie must not be sent with cross-origin
+# requests; Strict, Lax or None.
+Session.CookieSameSite=Lax
+
 # Probability that the garbage collection starts.
 # If 100 specified, the GC of sessions starts at the rate of once per 100
 # accesses. If 0 specified, the GC never starts.
@@ -138,7 +166,7 @@ MPM.thread.MaxAppServers=
 # Maximum number of action threads allowed to start simultaneously
 # per server process. Set max_connections parameter of the DBMS
 # to (MaxAppServers * MaxThreadsPerAppServer) or more.
-MPM.thread.MaxThreadsPerAppServer=100
+MPM.thread.MaxThreadsPerAppServer=128
 
 ##
 ## MPM epoll section
@@ -182,6 +210,7 @@ AccessLog.FilePath=
 #  %r : First line of request
 #  %s : Status code
 #  %O : Bytes sent, including headers, cannot be zero
+#  %e : elapsed processing time in milliseconds
 #  %n : Newline code
 AccessLog.Layout="%h %d \"%r\" %s %O%n"
 
@@ -210,13 +239,13 @@ ActionMailer.smtp.HostName=
 # Specify the connection's port number.
 ActionMailer.smtp.Port=
 
-# Enables STARTTLS extension if true.
-ActionMailer.smtp.EnableSTARTTLS=false
-
 # Enables SMTP authentication if true; disables SMTP
 # authentication if false.
 ActionMailer.smtp.Authentication=false
 
+# Enables STARTTLS extension if true.
+ActionMailer.smtp.EnableSTARTTLS=false
+
 # Specify the user name for SMTP authentication.
 ActionMailer.smtp.UserName=
 
@@ -237,18 +266,21 @@ ActionMailer.smtp.DelayedDelivery=false
 ## Cache section
 ##
 
-# Specify the settings file to enable the cache module.
-# Comment out the following line.
+# Specify the settings file to enable the cache module,
+# which can be used through Tf::cache() function.
+# See https://api-reference.treefrogframework.org/classTCache.html.
+# Uncomment the following line and write connection information
+# to the file.
 Cache.SettingsFile=cache.ini
 
-# Specify the cache backend, such as 'sqlite', 'mongodb'
-# or 'redis'.
-Cache.Backend=sqlite
+# Specify the cache backend, such as 'sqlite', 'mongodb', 'redis',
+# 'memcached' or 'memory'.
+Cache.Backend=memory
 
 # Probability of starting garbage collection (GC) for cache.
-# If 100 is specified, GC will be started at a rate of once per 100
+# If 1000 is specified, GC will be started at a rate of once per 1000
 # sets. If 0 is specified, the GC never starts.
-Cache.GcProbability=10000000
+Cache.GcProbability=100000000
 
 # If true, enable LZ4 compression when storing data.
-Cache.EnableCompression=no
+Cache.EnableCompression=false

+ 10 - 0
frameworks/C++/treefrog/config/cache.ini

@@ -28,3 +28,13 @@ UserName=
 Password=
 ConnectOptions=
 PostOpenStatements=
+
+[memory]
+DatabaseName=tfcache.shm
+HostName=
+Port=
+UserName=
+Password=
+ConnectOptions=MEMORY_SIZE=1G
+PostOpenStatements=
+

+ 34 - 2
frameworks/C++/treefrog/controllers/worldcontroller.cpp

@@ -3,6 +3,7 @@
 #include "pworld.h"
 #include "mngworld.h"
 #include <TCache>
+#include <TSqlQuery>
 
 
 void WorldController::index()
@@ -220,7 +221,7 @@ void WorldController::cached_pqueries()
 
 void WorldController::cached_pqueries(const QString &num)
 {
-    constexpr int SECONDS = 60 * 10;  // cache time
+    constexpr int SECONDS = 60 * 30;  // cache time
     QVariantList worlds;
     QVariantMap world;
     int d = std::min(std::max(num.toInt(), 1), 500);
@@ -246,17 +247,48 @@ void WorldController::cached_pqueries(const QString &num)
 
 void WorldController::pupdates(const QString &num)
 {
+    const QString statement("UPDATE world SET randomnumber = CASE id");
     QVariantList worlds;
+    QString ids;
+    QString q = statement;
+    q.reserve(4096);
     int d = std::min(std::max(num.toInt(), 1), 500);
     PWorld world;
 
+    auto blkupdate = [&q, &ids, &statement]() {
+        if (!ids.isEmpty()) {
+            ids.chop(1);
+            q += QStringLiteral(" END WHERE id IN (%1)").arg(ids);
+            TSqlQuery query;
+            query.exec(q);
+            ids.clear();
+            q = statement;
+        }
+    };
+
     for (int i = 0; i < d; ++i) {
         int id = Tf::random(1, 10000);
         world = PWorld::get(id);
         world.setRandomNumber( Tf::random(1, 10000) );
-        world.update();
+        q += QLatin1String(" WHEN ");
+        q += QString::number(world.id());
+        q += QLatin1String(" THEN ");
+        q += QString::number(world.randomNumber());
+        ids += QString::number(world.id());
+        ids += ',';
         worlds << world.toVariantMap();
+
+        if (!((i + 1) % 200)) {
+            blkupdate();
+        }
     }
+
+    if (d == 1) {
+        world.update();
+    } else {
+        blkupdate();
+    }
+
     renderJson(worlds);
 }
 

+ 10 - 10
frameworks/C++/treefrog/treefrog-epoll.dockerfile

@@ -1,21 +1,21 @@
-FROM buildpack-deps:focal
+FROM buildpack-deps:jammy
 
 ENV DEBIAN_FRONTEND noninteractive
 ENV DEBCONF_NOWARNINGS yes
-ENV TFVER=2.2.0
+ENV TFVER=2.7.1
 
-RUN apt-get update -yqq && apt-get upgrade -yq && apt-get install -yqq --no-install-recommends \
-    software-properties-common unzip wget make cmake gcc clang libjemalloc-dev qt5-qmake qt5-default qtbase5-dev \
-    qtbase5-dev-tools libqt5sql5 libqt5sql5-mysql libqt5sql5-psql libqt5qml5 libqt5xml5 \
-    qtdeclarative5-dev libqt5quick5 libqt5quickparticles5 libqt5gui5 libqt5printsupport5 \
-    libqt5widgets5 libqt5opengl5-dev libqt5quicktest5 libqt5sql5-sqlite libsqlite3-dev libmongoc-dev libbson-dev \
-    redis-server
+RUN apt-get update -yqq && apt-get upgrade -yq && \
+    apt-get install -yqq --no-install-recommends software-properties-common unzip wget libjemalloc-dev \
+    qmake6 qt6-base-dev qt6-base-dev-tools qt6-tools-dev-tools qt6-declarative-dev libqt6sql6-mysql \
+    libqt6sql6-psql libqt6sql6-odbc libqt6sql6-sqlite libqt6core6 libqt6qml6 libqt6xml6 libpq5 libodbc1 \
+    libmongoc-dev libbson-dev gcc g++ clang make cmake pkg-config
+RUN rm -f /usr/bin/qmake; ln -sf /usr/bin/qmake6 /usr/bin/qmake
 
 WORKDIR /usr/src
 RUN wget -q https://github.com/treefrogframework/treefrog-framework/archive/v${TFVER}.tar.gz
 RUN tar xf v${TFVER}.tar.gz
 RUN cd treefrog-framework-${TFVER} && \
-    ./configure --spec=linux-clang && \
+    ./configure --enable-shared-mongoc --spec=linux-clang && \
     cd src && \
     make -j4 && \
     make install && \
@@ -37,4 +37,4 @@ RUN sed -i 's|MultiProcessingModule=.*|MultiProcessingModule=epoll|g' config/app
 EXPOSE 8080
 
 # 3. Start TreeFrog
-CMD treefrog /workspace
+CMD treefrog /workspace

+ 9 - 9
frameworks/C++/treefrog/treefrog-mongodb.dockerfile

@@ -1,21 +1,21 @@
-FROM buildpack-deps:focal
+FROM buildpack-deps:jammy
 
 ENV DEBIAN_FRONTEND noninteractive
 ENV DEBCONF_NOWARNINGS yes
-ENV TFVER=2.2.0
+ENV TFVER=2.7.1
 
-RUN apt-get update -yqq && apt-get upgrade -yq && apt-get install -yqq --no-install-recommends \
-    software-properties-common unzip wget make cmake gcc clang libjemalloc-dev qt5-qmake qt5-default qtbase5-dev \
-    qtbase5-dev-tools libqt5sql5 libqt5sql5-mysql libqt5sql5-psql libqt5qml5 libqt5xml5 \
-    qtdeclarative5-dev libqt5quick5 libqt5quickparticles5 libqt5gui5 libqt5printsupport5 \
-    libqt5widgets5 libqt5opengl5-dev libqt5quicktest5 libqt5sql5-sqlite libsqlite3-dev libmongoc-dev libbson-dev \
-    redis-server
+RUN apt-get update -yqq && apt-get upgrade -yq && \
+    apt-get install -yqq --no-install-recommends software-properties-common unzip wget libjemalloc-dev \
+    qmake6 qt6-base-dev qt6-base-dev-tools qt6-tools-dev-tools qt6-declarative-dev libqt6sql6-mysql \
+    libqt6sql6-psql libqt6sql6-odbc libqt6sql6-sqlite libqt6core6 libqt6qml6 libqt6xml6 libpq5 libodbc1 \
+    libmongoc-dev libbson-dev gcc g++ clang make cmake pkg-config
+RUN rm -f /usr/bin/qmake; ln -sf /usr/bin/qmake6 /usr/bin/qmake
 
 WORKDIR /usr/src
 RUN wget -q https://github.com/treefrogframework/treefrog-framework/archive/v${TFVER}.tar.gz
 RUN tar xf v${TFVER}.tar.gz
 RUN cd treefrog-framework-${TFVER} && \
-    ./configure --spec=linux-clang && \
+    ./configure --enable-shared-mongoc --spec=linux-clang && \
     cd src && \
     make -j4 && \
     make install && \

+ 9 - 9
frameworks/C++/treefrog/treefrog-mysql.dockerfile

@@ -1,21 +1,21 @@
-FROM buildpack-deps:focal
+FROM buildpack-deps:jammy
 
 ENV DEBIAN_FRONTEND noninteractive
 ENV DEBCONF_NOWARNINGS yes
-ENV TFVER=2.2.0
+ENV TFVER=2.7.1
 
-RUN apt-get update -yqq && apt-get upgrade -yq && apt-get install -yqq --no-install-recommends \
-    software-properties-common unzip wget make cmake gcc clang libjemalloc-dev qt5-qmake qt5-default qtbase5-dev \
-    qtbase5-dev-tools libqt5sql5 libqt5sql5-mysql libqt5sql5-psql libqt5qml5 libqt5xml5 \
-    qtdeclarative5-dev libqt5quick5 libqt5quickparticles5 libqt5gui5 libqt5printsupport5 \
-    libqt5widgets5 libqt5opengl5-dev libqt5quicktest5 libqt5sql5-sqlite libsqlite3-dev libmongoc-dev libbson-dev \
-    redis-server
+RUN apt-get update -yqq && apt-get upgrade -yq && \
+    apt-get install -yqq --no-install-recommends software-properties-common unzip wget libjemalloc-dev \
+    qmake6 qt6-base-dev qt6-base-dev-tools qt6-tools-dev-tools qt6-declarative-dev libqt6sql6-mysql \
+    libqt6sql6-psql libqt6sql6-odbc libqt6sql6-sqlite libqt6core6 libqt6qml6 libqt6xml6 libpq5 libodbc1 \
+    libmongoc-dev libbson-dev gcc g++ clang make cmake pkg-config
+RUN rm -f /usr/bin/qmake; ln -sf /usr/bin/qmake6 /usr/bin/qmake
 
 WORKDIR /usr/src
 RUN wget -q https://github.com/treefrogframework/treefrog-framework/archive/v${TFVER}.tar.gz
 RUN tar xf v${TFVER}.tar.gz
 RUN cd treefrog-framework-${TFVER} && \
-    ./configure --spec=linux-clang && \
+    ./configure --enable-shared-mongoc --spec=linux-clang && \
     cd src && \
     make -j4 && \
     make install && \

+ 9 - 9
frameworks/C++/treefrog/treefrog.dockerfile

@@ -1,21 +1,21 @@
-FROM buildpack-deps:focal
+FROM buildpack-deps:jammy
 
 ENV DEBIAN_FRONTEND noninteractive
 ENV DEBCONF_NOWARNINGS yes
-ENV TFVER=2.2.0
+ENV TFVER=2.7.1
 
-RUN apt-get update -yqq && apt-get upgrade -yq && apt-get install -yqq --no-install-recommends \
-    software-properties-common unzip wget make cmake gcc clang libjemalloc-dev qt5-qmake qt5-default qtbase5-dev \
-    qtbase5-dev-tools libqt5sql5 libqt5sql5-mysql libqt5sql5-psql libqt5qml5 libqt5xml5 \
-    qtdeclarative5-dev libqt5quick5 libqt5quickparticles5 libqt5gui5 libqt5printsupport5 \
-    libqt5widgets5 libqt5opengl5-dev libqt5quicktest5 libqt5sql5-sqlite libsqlite3-dev libmongoc-dev libbson-dev \
-    redis-server
+RUN apt-get update -yqq && apt-get upgrade -yq && \
+    apt-get install -yqq --no-install-recommends software-properties-common unzip wget libjemalloc-dev \
+    qmake6 qt6-base-dev qt6-base-dev-tools qt6-tools-dev-tools qt6-declarative-dev libqt6sql6-mysql \
+    libqt6sql6-psql libqt6sql6-odbc libqt6sql6-sqlite libqt6core6 libqt6qml6 libqt6xml6 libpq5 libodbc1 \
+    libmongoc-dev libbson-dev gcc g++ clang make cmake pkg-config
+RUN rm -f /usr/bin/qmake; ln -sf /usr/bin/qmake6 /usr/bin/qmake
 
 WORKDIR /usr/src
 RUN wget -q https://github.com/treefrogframework/treefrog-framework/archive/v${TFVER}.tar.gz
 RUN tar xf v${TFVER}.tar.gz
 RUN cd treefrog-framework-${TFVER} && \
-    ./configure --spec=linux-clang && \
+    ./configure --enable-shared-mongoc --spec=linux-clang && \
     cd src && \
     make -j4 && \
     make install && \

+ 16 - 8
frameworks/C++/ulib/benchmark_config.json

@@ -16,7 +16,8 @@
       "database_os": "Linux",
       "display_name": "ULib",
       "notes": "",
-      "versus": ""
+      "versus": "",
+      "tags": ["broken"]
     },
     "plaintext_fit": {
       "plaintext_url": "/plaintext",
@@ -33,7 +34,8 @@
       "database_os": "Linux",
       "display_name": "ULib-fit",
       "notes": "",
-      "versus": ""
+      "versus": "",
+      "tags": ["broken"]
     },
     "json": {
       "json_url": "/json",
@@ -50,7 +52,8 @@
       "database_os": "Linux",
       "display_name": "ULib",
       "notes": "",
-      "versus": ""
+      "versus": "",
+      "tags": ["broken"]
     },
     "json_fit": {
       "json_url": "/json",
@@ -67,7 +70,8 @@
       "database_os": "Linux",
       "display_name": "ULib-fit",
       "notes": "",
-      "versus": ""
+      "versus": "",
+      "tags": ["broken"]
     },
     "mysql": {
       "db_url": "/db",
@@ -87,7 +91,8 @@
       "database_os": "Linux",
       "display_name": "ULib-mysql",
       "notes": "",
-      "versus": ""
+      "versus": "",
+      "tags": ["broken"]
     },
     "postgres": {
       "db_url": "/db",
@@ -107,7 +112,8 @@
       "database_os": "Linux",
       "display_name": "ULib-postgres",
       "notes": "",
-      "versus": ""
+      "versus": "",
+      "tags": ["broken"]
     },
     "postgres_fit": {
       "db_url": "/db",
@@ -125,7 +131,8 @@
       "database_os": "Linux",
       "display_name": "ULib-fit",
       "notes": "",
-      "versus": ""
+      "versus": "",
+      "tags": ["broken"]
     },
     "mongodb": {
       "setup_file": "setup_mongodb",
@@ -146,7 +153,8 @@
       "database_os": "Linux",
       "display_name": "ULib-mongodb",
       "notes": "",
-      "versus": ""
+      "versus": "",
+      "tags": ["broken"]
     }
   }]
 }

+ 42 - 0
frameworks/C++/userver/README.md

@@ -0,0 +1,42 @@
+# userver Benchmarking Test
+
+This is the [userver](https://github.com/userver-framework/userver) portion of a [benchmarking test suite](https://github.com/TechEmpower/FrameworkBenchmarks) comparing a variety of web development platforms.
+
+This benchmarks comes in two configurations: **userver** and **userver-bare**, where both configurations use exactly the same handlers code, but **userver-bare** replaces default http implementation of **userver** with custom one.  
+You see, **userver** being feature-rich framework widely used in production comes with a lot of useful functionality built-in (metrics, dynamic configuring, logging/tracing, congestion control etc...) none of which is of any use in benchmarks; although most of that can be disabled via configs, some parts remain, and these parts aren't free.  
+The aim of **userver-bare** is to explore practical limits of lower-level **userver** functionality when performance is an absolute must, while still being idiomatic userver code.
+
+### Test Type Implementation Source Code
+
+* [Plaintext](userver_benchmark/controllers/plaintext/handler.cpp)
+* [Json](userver_benchmark/controllers/json/handler.cpp)
+* [Single Database Query](userver_benchmark/controllers/single_query/handler.cpp)
+* [Multiple Database Queries](userver_benchmark/controllers/multiple_queries/handler.cpp)
+* [Database Updates](userver_benchmark/controllers/updates/handler.cpp)
+* [Cached Queries](userver_benchmark/controllers/cached_queries/handler.cpp)
+
+## Test URLs
+### Plaintext
+
+http://localhost:8080/plaintext
+
+### Json
+
+http://localhost:8080/json
+
+### Single Database Query
+
+http://localhost:8080/db
+
+### Multiple Database Queries
+
+http://localhost:8080/queries
+
+### Database Updates
+
+http://localhost:8080/updates
+
+### Cached Queries
+
+http://localhost:8080/cached-queries
+

+ 55 - 0
frameworks/C++/userver/benchmark_config.json

@@ -0,0 +1,55 @@
+{
+  "framework": "userver",
+  "tests": [
+    {
+      "default": {
+        "json_url": "/json",
+        "plaintext_url": "/plaintext",
+        "db_url": "/db",
+        "query_url": "/queries?queries=",
+        "update_url": "/updates?queries=",
+        "cached_query_url": "/cached-queries?count=",
+	"fortune_url": "/fortunes",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Fullstack",
+        "database": "postgres",
+        "framework": "userver",
+        "language": "C++",
+        "flavor": "None",
+        "orm": "Micro",
+        "platform": "None",
+        "webserver": "None",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "userver",
+        "notes": "",
+        "versus": "None"
+      },
+      "bare": {
+        "json_url": "/json",
+        "plaintext_url": "/plaintext",
+        "db_url": "/db",
+        "query_url": "/queries?queries=",
+        "update_url": "/updates?queries=",
+        "cached_query_url": "/cached-queries?count=",
+	"fortune_url": "/fortunes",
+        "port": 8081,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "database": "postgres",
+        "framework": "userver",
+        "language": "C++",
+        "flavor": "None",
+        "orm": "Micro",
+        "platform": "None",
+        "webserver": "None",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "userver[bare]",
+        "notes": "",
+        "versus": "None"
+      }
+    }
+  ]
+}

+ 14 - 10
frameworks/C++/wt/config.toml → frameworks/C++/userver/config.toml

@@ -1,5 +1,5 @@
 [framework]
-name = "wt"
+name = "userver"
 
 [main]
 urls.plaintext = "/plaintext"
@@ -7,28 +7,32 @@ urls.json = "/json"
 urls.db = "/db"
 urls.query = "/queries?queries="
 urls.update = "/updates?queries="
-urls.fortune = "/fortune"
+urls.cached_query = "/cached-queries?count="
+urls.fortune = "/fortunes"
 approach = "Realistic"
 classification = "Fullstack"
-database = "MySQL"
+database = "Postgres"
 database_os = "Linux"
 os = "Linux"
-orm = "Full"
+orm = "Micro"
 platform = "None"
 webserver = "None"
-versus = "wt"
+versus = "None"
 
-[postgres]
+[bare]
+urls.plaintext = "/plaintext"
+urls.json = "/json"
 urls.db = "/db"
 urls.query = "/queries?queries="
 urls.update = "/updates?queries="
-urls.fortune = "/fortune"
+urls.cached_query = "/cached-queries?count="
+urls.fortune = "/fortunes"
 approach = "Realistic"
-classification = "Fullstack"
+classification = "Micro"
 database = "Postgres"
 database_os = "Linux"
 os = "Linux"
-orm = "Full"
+orm = "Micro"
 platform = "None"
 webserver = "None"
-versus = "wt"
+versus = "None"

+ 27 - 0
frameworks/C++/userver/userver-bare.dockerfile

@@ -0,0 +1,27 @@
+FROM ghcr.io/userver-framework/ubuntu-userver-build-base:v1 AS builder
+
+RUN apt update && \
+    apt install -y lsb-release wget software-properties-common gnupg && \
+        wget https://apt.llvm.org/llvm.sh && chmod +x llvm.sh && ./llvm.sh 16
+
+WORKDIR /src
+RUN git clone https://github.com/userver-framework/userver.git && \
+    cd userver && git checkout b85d540d7022e344f6fcf9fd467c67b046c961fe
+COPY userver_benchmark/ ./
+RUN mkdir build && cd build && \
+    cmake -DUSERVER_IS_THE_ROOT_PROJECT=0 -DUSERVER_FEATURE_CRYPTOPP_BLAKE2=0 \
+          -DUSERVER_FEATURE_UTEST=0 \
+          -DUSERVER_FEATURE_POSTGRESQL=1 \
+          -DUSERVER_FEATURE_ERASE_LOG_WITH_LEVEL=warning \
+          -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-march=native" -DCMAKE_C_FLAGS="-march=native" \
+          -DCMAKE_CXX_COMPILER=clang++-16 -DCMAKE_C_COMPILER=clang-16 -DUSERVER_USE_LD=lld-16 -DUSERVER_LTO_CACHE=0 .. && \
+    make -j $(nproc)
+
+FROM builder AS runner
+WORKDIR /app
+COPY userver_configs/* ./
+COPY --from=builder /src/build/userver_techempower ./
+
+EXPOSE 8081
+CMD ./userver_techempower -c ./static_config.yaml
+

+ 27 - 0
frameworks/C++/userver/userver.dockerfile

@@ -0,0 +1,27 @@
+FROM ghcr.io/userver-framework/ubuntu-userver-build-base:v1 AS builder
+
+RUN apt update && \
+    apt install -y lsb-release wget software-properties-common gnupg && \
+        wget https://apt.llvm.org/llvm.sh && chmod +x llvm.sh && ./llvm.sh 16
+
+WORKDIR /src
+RUN git clone https://github.com/userver-framework/userver.git && \
+    cd userver && git checkout b85d540d7022e344f6fcf9fd467c67b046c961fe
+COPY userver_benchmark/ ./
+RUN mkdir build && cd build && \
+    cmake -DUSERVER_IS_THE_ROOT_PROJECT=0 -DUSERVER_FEATURE_CRYPTOPP_BLAKE2=0 \
+          -DUSERVER_FEATURE_UTEST=0 \
+          -DUSERVER_FEATURE_POSTGRESQL=1 \
+          -DUSERVER_FEATURE_ERASE_LOG_WITH_LEVEL=warning \
+          -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-march=native" -DCMAKE_C_FLAGS="-march=native" \
+          -DCMAKE_CXX_COMPILER=clang++-16 -DCMAKE_C_COMPILER=clang-16 -DUSERVER_USE_LD=lld-16 -DUSERVER_LTO_CACHE=0 .. && \
+    make -j $(nproc)
+
+FROM builder AS runner
+WORKDIR /app
+COPY userver_configs/* ./
+COPY --from=builder /src/build/userver_techempower ./
+
+EXPOSE 8080
+CMD ./userver_techempower -c ./static_config.yaml
+

+ 3 - 0
frameworks/C++/userver/userver_benchmark/.clang-format

@@ -0,0 +1,3 @@
+BasedOnStyle: google
+DerivePointerAlignment: false
+IncludeBlocks: Preserve

+ 18 - 0
frameworks/C++/userver/userver_benchmark/CMakeLists.txt

@@ -0,0 +1,18 @@
+cmake_minimum_required(VERSION 3.12)
+project(userver_techempower CXX)
+
+set(CMAKE_CXX_STANDARD 17)
+
+file(GLOB_RECURSE SOURCES
+  ${CMAKE_CURRENT_SOURCE_DIR}/controllers/*.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/common/*.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/bare/*.cpp
+)
+
+include(GNUInstallDirs)
+
+add_subdirectory(userver)
+userver_setup_environment()
+
+add_executable(${PROJECT_NAME} ${SOURCES} userver_techempower.cpp)
+target_link_libraries(${PROJECT_NAME} PRIVATE userver-core userver-postgresql)

+ 205 - 0
frameworks/C++/userver/userver_benchmark/bare/simple_connection.cpp

@@ -0,0 +1,205 @@
+#include "simple_connection.hpp"
+
+#include <array>
+
+#include <cctz/time_zone.h>
+#include <http_parser.h>
+#include <boost/container/small_vector.hpp>
+
+#include "simple_server.hpp"
+
+#include <userver/engine/async.hpp>
+#include <userver/utils/datetime/wall_coarse_clock.hpp>
+#include <userver/utils/scope_guard.hpp>
+#include <userver/utils/small_string.hpp>
+
+namespace userver_techempower::bare {
+
+namespace {
+
+struct HttpParser final {
+  http_parser parser{};
+  http_parser_settings parser_settings{};
+
+  std::function<void(std::string_view)> on_request_cb{};
+
+  userver::utils::SmallString<50> url;
+
+  explicit HttpParser(std::function<void(std::string_view)> on_request_cb)
+      : on_request_cb{std::move(on_request_cb)} {
+    http_parser_init(&parser, HTTP_REQUEST);
+    parser.data = this;
+
+    http_parser_settings_init(&parser_settings);
+    parser_settings.on_url = HttpOnUrl;
+    parser_settings.on_message_begin = HttpOnMessageBegin;
+    parser_settings.on_message_complete = HttpOnMessageComplete;
+  }
+
+  void Execute(const char* data, std::size_t length) {
+    http_parser_execute(&parser, &parser_settings, data, length);
+  }
+
+  static int HttpOnUrl(http_parser* parser, const char* data,
+                       std::size_t length) {
+    auto* self = static_cast<HttpParser*>(parser->data);
+    self->url.append(std::string_view{data, length});
+    return 0;
+  }
+
+  static int HttpOnMessageBegin(http_parser* parser) {
+    auto* self = static_cast<HttpParser*>(parser->data);
+    self->url.clear();
+    return 0;
+  }
+
+  static int HttpOnMessageComplete(http_parser* parser) {
+    auto* self = static_cast<HttpParser*>(parser->data);
+    self->on_request_cb(static_cast<std::string_view>(self->url));
+    return 0;
+  }
+};
+
+class ResponseBuffers final {
+ public:
+  using HeadersString = userver::utils::SmallString<200>;
+
+  HeadersString& Next(userver::engine::io::Socket& socket, std::string&& body) {
+    if (Size() == kMaxResponses) {
+      Send(socket);
+    }
+
+    auto& response = responses_.emplace_back();
+    response.body = std::move(body);
+    return response.headers;
+  }
+
+  void Send(userver::engine::io::Socket& socket) {
+    if (Size() == 0) {
+      return;
+    }
+
+    boost::container::small_vector<userver::engine::io::IoData,
+                                   kMaxResponses * 2>
+        iovec(Size() * 2);
+
+    std::size_t index = 0;
+    std::size_t total_size = 0;
+    for (const auto& response : responses_) {
+      iovec[index++] = {response.headers.data(), response.headers.size()};
+      iovec[index++] = {response.body.data(), response.body.size()};
+      total_size += response.headers.size() + response.body.size();
+    }
+
+    if (socket.SendAll(iovec.data(), iovec.size(), {}) != total_size) {
+      throw std::runtime_error{"Socket closed by remote"};
+    }
+
+    responses_.clear();
+  }
+
+ private:
+  static constexpr std::size_t kMaxResponses = 16;
+
+  [[nodiscard]] std::size_t Size() const { return responses_.size(); }
+
+  struct Response final {
+    HeadersString headers;
+    std::string body;
+  };
+
+  boost::container::small_vector<Response, kMaxResponses> responses_;
+};
+
+constexpr std::string_view kCommonHeaders{"HTTP/1.1 200 OK\r\nServer: us\r\n"};
+constexpr std::string_view kHeadersEnd{"\r\n\r\n"};
+
+std::string MakeHttpDate(std::chrono::system_clock::time_point date) {
+  static const std::string kFormatString = "%a, %d %b %Y %H:%M:%S %Z";
+  static const auto tz = cctz::utc_time_zone();
+
+  return cctz::format(kFormatString, date, tz);
+}
+
+std::string_view GetCachedDate() {
+  constexpr size_t kMaxDateHeaderLength = 128;
+
+  static thread_local std::chrono::seconds::rep last_second = 0;
+  static thread_local char last_time_string[kMaxDateHeaderLength]{};
+  static thread_local std::string_view result_view{};
+
+  const auto now = userver::utils::datetime::WallCoarseClock::now();
+  const auto now_seconds =
+      std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch())
+          .count();
+  if (now_seconds != last_second) {
+    last_second = now_seconds;
+
+    const auto time_str = MakeHttpDate(now);
+
+    std::memcpy(last_time_string, time_str.c_str(), time_str.size());
+    result_view = std::string_view{last_time_string, time_str.size()};
+  }
+
+  return result_view;
+}
+
+}  // namespace
+
+SimpleConnection::SimpleConnection(SimpleServer& server,
+                                   userver::engine::io::Socket&& socket)
+    : server_{server},
+      socket_{std::move(socket)},
+      processing_task_{userver::engine::AsyncNoSpan([this] { Process(); })} {}
+
+SimpleConnection::~SimpleConnection() { processing_task_.SyncCancel(); }
+
+void SimpleConnection::Process() {
+  constexpr std::size_t kBufferSize = 4096;
+  std::array<char, kBufferSize> buffer{};
+
+  userver::utils::ScopeGuard close_guard{[this] { socket_.Close(); }};
+
+  ResponseBuffers buffers{};
+  const auto handle_request = [this, &buffers](std::string_view url) {
+    auto response = server_.HandleRequest(url);
+    const auto content_length_str = std::to_string(response.body.size());
+    auto& headers = buffers.Next(socket_, std::move(response.body));
+
+    headers.append(kCommonHeaders);
+    headers.append("Content-Type: ");
+    headers.append(response.content_type);
+
+    headers.append("\r\nContent-Length: ");
+    headers.append(content_length_str);
+
+    headers.append("\r\nDate: ");
+    headers.append(GetCachedDate());
+
+    headers.append(kHeadersEnd);
+  };
+  HttpParser parser{handle_request};
+
+  std::size_t last_bytes_read = 0;
+  while (true) {
+    bool is_readable = true;
+    if (last_bytes_read < kBufferSize) {
+      is_readable = socket_.WaitReadable({});
+    }
+
+    last_bytes_read =
+        is_readable ? socket_.RecvSome(buffer.data(), kBufferSize, {}) : 0;
+    if (last_bytes_read == 0) {
+      break;
+    }
+
+    parser.Execute(buffer.data(), last_bytes_read);
+    if (parser.parser.http_errno != 0) {
+      break;
+    }
+
+    buffers.Send(socket_);
+  }
+}
+
+}  // namespace userver_techempower::bare

+ 26 - 0
frameworks/C++/userver/userver_benchmark/bare/simple_connection.hpp

@@ -0,0 +1,26 @@
+#pragma once
+
+#include <userver/engine/io/socket.hpp>
+#include <userver/engine/task/task_with_result.hpp>
+
+namespace userver_techempower::bare {
+
+class SimpleServer;
+
+class SimpleConnection final {
+ public:
+  explicit SimpleConnection(SimpleServer& server,
+                            userver::engine::io::Socket&& socket);
+  ~SimpleConnection();
+
+ private:
+  void Process();
+
+  const SimpleServer& server_;
+
+  userver::engine::io::Socket socket_;
+
+  userver::engine::TaskWithResult<void> processing_task_;
+};
+
+}  // namespace userver_techempower::bare

+ 12 - 0
frameworks/C++/userver/userver_benchmark/bare/simple_response.hpp

@@ -0,0 +1,12 @@
+#pragma once
+
+#include <string>
+
+namespace userver_techempower::bare {
+
+struct SimpleResponse final {
+  std::string body;
+  std::string content_type;
+};
+
+}  // namespace userver_techempower::bare

+ 90 - 0
frameworks/C++/userver/userver_benchmark/bare/simple_router.cpp

@@ -0,0 +1,90 @@
+#include "simple_router.hpp"
+
+#include <userver/components/component_context.hpp>
+
+#include "../controllers/cached_queries/handler.hpp"
+#include "../controllers/fortunes/handler.hpp"
+#include "../controllers/json/handler.hpp"
+#include "../controllers/multiple_queries/handler.hpp"
+#include "../controllers/plaintext/handler.hpp"
+#include "../controllers/single_query/handler.hpp"
+#include "../controllers/updates/handler.hpp"
+
+namespace userver_techempower::bare {
+
+namespace {
+
+constexpr std::string_view kPlainTextUrlPrefix{"/plaintext"};
+constexpr std::string_view kJsonUrlPrefix{"/json"};
+constexpr std::string_view kSingleQueryUrlPrefix{"/db"};
+constexpr std::string_view kMultipleQueriesUrlPrefix{"/queries"};
+constexpr std::string_view kUpdatesUrlPrefix{"/updates"};
+constexpr std::string_view kCachedQueriesUrlPrefix{"/cached-queries"};
+constexpr std::string_view kFortunesUrlPrefix{"/fortunes"};
+
+// NOLINTNEXTLINE
+const std::string kContentTypePlain{"text/plain"};
+// NOLINTNEXTLINE
+const std::string kContentTypeJson{"application/json"};
+// NOLINTNEXTLINE
+const std::string kContentTypeTextHtml{"text/html; charset=utf-8"};
+
+bool StartsWith(std::string_view source, std::string_view pattern) {
+  return source.substr(0, pattern.length()) == pattern;
+}
+
+}  // namespace
+
+SimpleRouter::SimpleRouter(const userver::components::ComponentConfig& config,
+                           const userver::components::ComponentContext& context)
+    : userver::components::LoggableComponentBase{config, context},
+      single_query_{context.FindComponent<single_query::Handler>()},
+      multiple_queries_{context.FindComponent<multiple_queries::Handler>()},
+      updates_{context.FindComponent<updates::Handler>()},
+      cached_queries_{context.FindComponent<cached_queries::Handler>()},
+      fortunes_{context.FindComponent<fortunes::Handler>()} {}
+
+SimpleRouter::~SimpleRouter() = default;
+
+SimpleResponse SimpleRouter::RouteRequest(std::string_view url) const {
+  if (StartsWith(url, kPlainTextUrlPrefix)) {
+    return {plaintext::Handler::GetResponse(), kContentTypePlain};
+  }
+
+  if (StartsWith(url, kJsonUrlPrefix)) {
+    return {ToString(json::Handler::GetResponse()), kContentTypeJson};
+  }
+
+  if (StartsWith(url, kSingleQueryUrlPrefix)) {
+    return {ToString(single_query_.GetResponse()), kContentTypeJson};
+  }
+
+  if (StartsWith(url, kMultipleQueriesUrlPrefix)) {
+    const auto queries = db_helpers::ParseParamFromQuery(
+        url.substr(kMultipleQueriesUrlPrefix.size()), "queries");
+
+    return {ToString(multiple_queries_.GetResponse(queries)), kContentTypeJson};
+  }
+
+  if (StartsWith(url, kUpdatesUrlPrefix)) {
+    const auto queries = db_helpers::ParseParamFromQuery(
+        url.substr(kMultipleQueriesUrlPrefix.size()), "queries");
+
+    return {ToString(updates_.GetResponse(queries)), kContentTypeJson};
+  }
+
+  if (StartsWith(url, kCachedQueriesUrlPrefix)) {
+    const auto count = db_helpers::ParseParamFromQuery(
+        url.substr(kCachedQueriesUrlPrefix.size()), "count");
+
+    return {ToString(cached_queries_.GetResponse(count)), kContentTypeJson};
+  }
+
+  if (StartsWith(url, kFortunesUrlPrefix)) {
+    return {fortunes_.GetResponse(), kContentTypeTextHtml};
+  }
+
+  throw std::runtime_error{"No handler found for url"};
+}
+
+}  // namespace userver_techempower::bare

+ 46 - 0
frameworks/C++/userver/userver_benchmark/bare/simple_router.hpp

@@ -0,0 +1,46 @@
+#pragma once
+
+#include <userver/components/loggable_component_base.hpp>
+
+#include "simple_response.hpp"
+
+namespace userver_techempower {
+
+namespace single_query {
+class Handler;
+}
+namespace multiple_queries {
+class Handler;
+}
+namespace updates {
+class Handler;
+}
+namespace cached_queries {
+class Handler;
+}
+namespace fortunes {
+class Handler;
+}
+
+namespace bare {
+
+class SimpleRouter final : public userver::components::LoggableComponentBase {
+ public:
+  static constexpr std::string_view kName{"simple-router"};
+
+  SimpleRouter(const userver::components::ComponentConfig& config,
+               const userver::components::ComponentContext& context);
+  ~SimpleRouter() final;
+
+  [[nodiscard]] SimpleResponse RouteRequest(std::string_view url) const;
+
+ private:
+  const single_query::Handler& single_query_;
+  const multiple_queries::Handler& multiple_queries_;
+  const updates::Handler& updates_;
+  const cached_queries::Handler& cached_queries_;
+  const fortunes::Handler& fortunes_;
+};
+
+}  // namespace bare
+}  // namespace userver_techempower

+ 28 - 0
frameworks/C++/userver/userver_benchmark/bare/simple_server.cpp

@@ -0,0 +1,28 @@
+#include "simple_server.hpp"
+
+#include <userver/components/component_context.hpp>
+#include <userver/engine/io/socket.hpp>
+
+#include "simple_connection.hpp"
+#include "simple_router.hpp"
+
+namespace userver_techempower::bare {
+
+SimpleServer::SimpleServer(const userver::components::ComponentConfig& config,
+                           const userver::components::ComponentContext& context)
+    : userver::components::TcpAcceptorBase(config, context),
+      router_{context.FindComponent<SimpleRouter>()} {}
+
+SimpleServer::~SimpleServer() = default;
+
+void SimpleServer::ProcessSocket(userver::engine::io::Socket&& socket) {
+  const auto fd = socket.Fd();
+  connections_[fd] =
+      std::make_unique<SimpleConnection>(*this, std::move(socket));
+}
+
+SimpleResponse SimpleServer::HandleRequest(std::string_view url) const {
+  return router_.RouteRequest(url);
+}
+
+}  // namespace userver_techempower::bare

+ 34 - 0
frameworks/C++/userver/userver_benchmark/bare/simple_server.hpp

@@ -0,0 +1,34 @@
+#pragma once
+
+#include <array>
+#include <memory>
+
+#include <userver/components/tcp_acceptor_base.hpp>
+
+#include "simple_response.hpp"
+
+namespace userver_techempower::bare {
+
+class SimpleConnection;
+class SimpleRouter;
+
+class SimpleServer final : public userver::components::TcpAcceptorBase {
+ public:
+  static constexpr std::string_view kName{"simple-server"};
+
+  SimpleServer(const userver::components::ComponentConfig& config,
+               const userver::components::ComponentContext& context);
+  ~SimpleServer() final;
+
+ private:
+  void ProcessSocket(userver::engine::io::Socket&& socket) final;
+
+  friend class SimpleConnection;
+  [[nodiscard]] SimpleResponse HandleRequest(std::string_view url) const;
+
+  const SimpleRouter& router_;
+
+  static constexpr std::size_t kMaxFd = 65536;
+  std::array<std::unique_ptr<SimpleConnection>, kMaxFd> connections_;
+};
+}  // namespace userver_techempower::bare

+ 80 - 0
frameworks/C++/userver/userver_benchmark/common/db_helpers.cpp

@@ -0,0 +1,80 @@
+#include "db_helpers.hpp"
+
+#include <cctype>
+#include <charconv>
+
+#include <userver/formats/json/inline.hpp>
+#include <userver/utils/rand.hpp>
+
+namespace userver_techempower::db_helpers {
+
+namespace {
+
+int ParseFromQueryVal(std::string_view query_val) {
+  if (query_val.empty()) {
+    return 1;
+  }
+
+  int parse_result{};
+  const auto [ptr, err] = std::from_chars(
+      query_val.data(), query_val.data() + query_val.size(), parse_result);
+  if (err != std::errc{} || ptr != query_val.data() + query_val.size()) {
+    return 1;
+  }
+
+  return std::min(500, std::max(1, parse_result));
+}
+
+}  // namespace
+
+userver::storages::postgres::Query CreateNonLoggingQuery(
+    std::string statement) {
+  return userver::storages::postgres::Query{
+      statement, std::nullopt /* name */,
+      userver::storages::postgres::Query::LogMode::kNameOnly};
+}
+
+int GenerateRandomId() {
+  return userver::utils::RandRange(1, kMaxWorldRows + 1);
+}
+
+int GenerateRandomValue() {
+  return userver::utils::RandRange(1, kMaxWorldRows + 1);
+}
+
+userver::formats::json::Value Serialize(
+    const WorldTableRow& value,
+    userver::formats::serialize::To<userver::formats::json::Value>) {
+  return userver::formats::json::MakeObject("id", value.id, "randomNumber",
+                                            value.random_number);
+}
+
+int ParseParamFromQuery(const userver::server::http::HttpRequest& request,
+                        const std::string& name) {
+  const auto& arg_str = request.GetArg(name);
+  return ParseFromQueryVal(arg_str);
+}
+
+int ParseParamFromQuery(std::string_view url, std::string_view name) {
+  auto pos = url.find(name);
+  if (pos == std::string_view::npos) {
+    return 1;
+  }
+  pos += name.size() + 1;  // +1 for '='
+
+  std::size_t len = 0;
+  while (pos + len < url.size() && std::isdigit(url[pos + len])) {
+    ++len;
+  }
+
+  return ParseFromQueryVal(url.substr(pos, len));
+}
+
+DatabasePoolSemaphore::DatabasePoolSemaphore(std::size_t initial_count)
+    : semaphore_{initial_count} {}
+
+userver::engine::SemaphoreLock DatabasePoolSemaphore::Acquire() const {
+  return userver::engine::SemaphoreLock{semaphore_};
+}
+
+}  // namespace userver_techempower::db_helpers

+ 50 - 0
frameworks/C++/userver/userver_benchmark/common/db_helpers.hpp

@@ -0,0 +1,50 @@
+#pragma once
+
+#include <userver/engine/semaphore.hpp>
+#include <userver/formats/json/value.hpp>
+#include <userver/server/http/http_request.hpp>
+#include <userver/storages/postgres/cluster_types.hpp>
+#include <userver/storages/postgres/query.hpp>
+
+namespace userver_techempower::db_helpers {
+
+userver::storages::postgres::Query CreateNonLoggingQuery(std::string statement);
+
+constexpr int kMaxWorldRows = 10000;
+
+const userver::storages::postgres::Query kSelectRowQuery =
+    CreateNonLoggingQuery("SELECT id, randomNumber FROM World WHERE id = $1");
+
+constexpr auto kClusterHostType =
+    userver::storages::postgres::ClusterHostType::kMaster;
+
+constexpr std::string_view kDbComponentName = "hello-world-db";
+
+struct WorldTableRow final {
+  int id;
+  int random_number;
+};
+
+int GenerateRandomId();
+int GenerateRandomValue();
+
+userver::formats::json::Value Serialize(
+    const WorldTableRow& value,
+    userver::formats::serialize::To<userver::formats::json::Value>);
+
+int ParseParamFromQuery(const userver::server::http::HttpRequest& request,
+                        const std::string& name);
+
+int ParseParamFromQuery(std::string_view url, std::string_view name);
+
+class DatabasePoolSemaphore final {
+ public:
+  explicit DatabasePoolSemaphore(std::size_t initial_count);
+
+  userver::engine::SemaphoreLock Acquire() const;
+
+ private:
+  mutable userver::engine::Semaphore semaphore_;
+};
+
+}  // namespace userver_techempower::db_helpers

+ 37 - 0
frameworks/C++/userver/userver_benchmark/controllers/cached_queries/handler.cpp

@@ -0,0 +1,37 @@
+#include "handler.hpp"
+
+#include <userver/formats/serialize/common_containers.hpp>
+
+#include <boost/container/small_vector.hpp>
+
+namespace userver_techempower::cached_queries {
+
+Handler::Handler(const userver::components::ComponentConfig& config,
+                 const userver::components::ComponentContext& context)
+    : userver::server::handlers::HttpHandlerJsonBase{config, context},
+      cache_{context.FindComponent<WorldCacheComponent>()},
+      query_arg_name_{"count"} {}
+
+userver::formats::json::Value Handler::HandleRequestJsonThrow(
+    const userver::server::http::HttpRequest& request,
+    const userver::formats::json::Value&,
+    userver::server::request::RequestContext&) const {
+  const auto queries =
+      db_helpers::ParseParamFromQuery(request, query_arg_name_);
+
+  return GetResponse(queries);
+}
+
+userver::formats::json::Value Handler::GetResponse(int queries) const {
+  boost::container::small_vector<db_helpers::WorldTableRow, 500> result(
+      queries);
+
+  const auto cache_ptr = cache_.Get();
+  const auto& cache = *cache_ptr;
+  std::generate(result.begin(), result.end(),
+                [&cache] { return cache.at(db_helpers::GenerateRandomId()); });
+
+  return userver::formats::json::ValueBuilder{result}.ExtractValue();
+}
+
+}  // namespace userver_techempower::cached_queries

+ 29 - 0
frameworks/C++/userver/userver_benchmark/controllers/cached_queries/handler.hpp

@@ -0,0 +1,29 @@
+#pragma once
+
+#include <userver/server/handlers/http_handler_json_base.hpp>
+
+#include "world_cache_component.hpp"
+
+namespace userver_techempower::cached_queries {
+
+class Handler final : public userver::server::handlers::HttpHandlerJsonBase {
+ public:
+  static constexpr std::string_view kName = "cached-queries-handler";
+
+  Handler(const userver::components::ComponentConfig& config,
+          const userver::components::ComponentContext& context);
+
+  userver::formats::json::Value HandleRequestJsonThrow(
+      const userver::server::http::HttpRequest& request,
+      const userver::formats::json::Value&,
+      userver::server::request::RequestContext&) const final;
+
+  userver::formats::json::Value GetResponse(int queries) const;
+
+ private:
+  const WorldCacheComponent& cache_;
+
+  const std::string query_arg_name_;
+};
+
+}  // namespace userver_techempower::cached_queries

+ 21 - 0
frameworks/C++/userver/userver_benchmark/controllers/cached_queries/world_cache_component.cpp

@@ -0,0 +1,21 @@
+#include "world_cache_component.hpp"
+
+namespace userver_techempower::cached_queries {
+
+WorldCache::WorldCache() { data_.reserve(db_helpers::kMaxWorldRows + 1); }
+
+std::size_t WorldCache::size() const { return data_.size(); }
+
+void WorldCache::insert_or_assign(int key, db_helpers::WorldTableRow&& row) {
+  if (size() <= static_cast<std::size_t>(key)) {
+    data_.resize(key + 1);
+  }
+
+  data_[key] = row;
+}
+
+const db_helpers::WorldTableRow& WorldCache::at(size_t ind) const {
+  return data_[ind];
+}
+
+}  // namespace userver_techempower::cached_queries

+ 36 - 0
frameworks/C++/userver/userver_benchmark/controllers/cached_queries/world_cache_component.hpp

@@ -0,0 +1,36 @@
+#pragma once
+
+#include <vector>
+
+#include "../../common/db_helpers.hpp"
+
+#include <userver/cache/base_postgres_cache.hpp>
+
+namespace userver_techempower::cached_queries {
+
+class WorldCache final {
+ public:
+  WorldCache();
+
+  std::size_t size() const;
+  void insert_or_assign(int key, db_helpers::WorldTableRow&& row);
+
+  const db_helpers::WorldTableRow& at(size_t ind) const;
+
+ private:
+  std::vector<db_helpers::WorldTableRow> data_;
+};
+
+struct WorldCachePolicy final {
+  static constexpr std::string_view kName = "world-pg-cache";
+  using ValueType = db_helpers::WorldTableRow;
+  using CacheContainer = WorldCache;
+  static constexpr auto kKeyMember = &db_helpers::WorldTableRow::id;
+  static constexpr const char* kQuery = "SELECT id, randomNumber FROM World";
+  static constexpr const char* kUpdatedField = "";
+  static constexpr auto kClusterHostType = db_helpers::kClusterHostType;
+};
+
+using WorldCacheComponent = userver::components::PostgreCache<WorldCachePolicy>;
+
+}  // namespace userver_techempower::cached_queries

+ 166 - 0
frameworks/C++/userver/userver_benchmark/controllers/fortunes/handler.cpp

@@ -0,0 +1,166 @@
+#include "handler.hpp"
+
+#include <vector>
+
+#include <userver/components/component_context.hpp>
+#include <userver/storages/postgres/postgres.hpp>
+
+namespace userver_techempower::fortunes {
+
+namespace {
+
+const std::string kContentTypeHeader{"Content-Type"};
+const std::string kContentTypeTextHtml{"text/html; charset=utf-8"};
+
+struct Fortune final {
+  int id;
+  std::string_view message;
+};
+
+constexpr std::string_view kResultingHtmlHeader{
+    "<!DOCTYPE "
+    "html><html><head><title>Fortunes</title></head><body><table><tr><th>id</"
+    "th><th>message</th></tr>"};
+constexpr std::string_view kResultingHtmlFooter{"</table></body></html>"};
+
+constexpr std::string_view kNewRowStart{"<tr><td>"};
+constexpr std::string_view kColumnsSeparator{"</td><td>"};
+constexpr std::string_view kNewRowEnd{"</td></tr>"};
+
+constexpr std::string_view kEscapedQuote{"&quot;"};
+constexpr std::string_view kEscapedAmpersand{"&amp;"};
+constexpr std::string_view kEscapedLessThanSign{"&lt;"};
+constexpr std::string_view kEscapedMoreThanSign{"&gt;"};
+
+void AppendFortune(std::string& result, const Fortune& fortune) {
+  {
+    auto old_size = result.size();
+    const auto fortune_id = std::to_string(fortune.id);
+
+    const auto first_step_size =
+        kNewRowStart.size() + fortune_id.size() + kColumnsSeparator.size();
+
+    result.resize(old_size + first_step_size);
+    char* append_position = result.data() + old_size;
+
+    // this is just faster than std::string::append if we know the resulting
+    // size upfront, because there are a lot of not inlined calls otherwise
+    const auto append = [&append_position](std::string_view what) {
+      std::memcpy(append_position, what.data(), what.size());
+      append_position += what.size();
+    };
+    append(kNewRowStart);
+    append(fortune_id);
+    append(kColumnsSeparator);
+  }
+
+  {
+    std::string_view message{fortune.message};
+
+    const auto do_append = [&result](std::string_view unescaped,
+                                     std::string_view escaped) {
+      const auto old_size = result.size();
+      const auto added_size = unescaped.size() + escaped.size();
+
+      result.resize(result.size() + added_size);
+      char* append_position = result.data() + old_size;
+      if (!unescaped.empty()) {
+        std::memcpy(append_position, unescaped.data(), unescaped.size());
+        append_position += unescaped.size();
+      }
+      std::memcpy(append_position, escaped.data(), escaped.size());
+    };
+
+    std::size_t unescaped_len = 0;
+    const auto append = [&unescaped_len, &message,
+                         &do_append](std::string_view escaped) {
+      do_append(message.substr(0, unescaped_len), escaped);
+      message = message.substr(std::exchange(unescaped_len, 0) + 1);
+    };
+
+    while (unescaped_len < message.size()) {
+      const auto c = message[unescaped_len];
+      switch (c) {
+        case '"': {
+          append(kEscapedQuote);
+          break;
+        }
+        case '&': {
+          append(kEscapedAmpersand);
+          break;
+        }
+        case '<': {
+          append(kEscapedLessThanSign);
+          break;
+        }
+        case '>': {
+          append(kEscapedMoreThanSign);
+          break;
+        }
+        default:
+          ++unescaped_len;
+      }
+    }
+    result.append(message);
+  }
+
+  { result.append(kNewRowEnd); }
+}
+
+std::string FormatFortunes(const std::vector<Fortune>& fortunes) {
+  std::string result{};
+  // Wild guess, seems reasonable. Could be the exact value needed, but that
+  // looks kinda cheating.
+  result.reserve(2048);
+
+  result.append(kResultingHtmlHeader);
+  for (const auto& fortune : fortunes) {
+    AppendFortune(result, fortune);
+  }
+  result.append(kResultingHtmlFooter);
+
+  return result;
+}
+
+constexpr std::size_t kBestConcurrencyWildGuess = 256;
+
+}  // namespace
+
+Handler::Handler(const userver::components::ComponentConfig& config,
+                 const userver::components::ComponentContext& context)
+    : userver::server::handlers::HttpHandlerBase{config, context},
+      pg_{context
+              .FindComponent<userver::components::Postgres>(
+                  db_helpers::kDbComponentName)
+              .GetCluster()},
+      select_all_fortunes_query_{
+          db_helpers::CreateNonLoggingQuery("SELECT id, message FROM Fortune")},
+      semaphore_{kBestConcurrencyWildGuess} {}
+
+std::string Handler::HandleRequestThrow(
+    const userver::server::http::HttpRequest& request,
+    userver::server::request::RequestContext&) const {
+  request.GetHttpResponse().SetHeader(kContentTypeHeader, kContentTypeTextHtml);
+  return GetResponse();
+}
+
+std::string Handler::GetResponse() const {
+  const auto pg_result = [this] {
+    const auto lock = semaphore_.Acquire();
+    return pg_->Execute(db_helpers::kClusterHostType,
+                        select_all_fortunes_query_);
+  }();
+
+  auto fortunes = pg_result.AsContainer<std::vector<Fortune>>(
+      userver::storages::postgres::kRowTag);
+  fortunes.push_back({0, "Additional fortune added at request time."});
+
+  std::sort(fortunes.begin(), fortunes.end(),
+            [](const auto& lhs, const auto& rhs) {
+              return lhs.message < rhs.message;
+            });
+
+  return FormatFortunes(fortunes);
+}
+
+}  // namespace userver_techempower::fortunes

+ 32 - 0
frameworks/C++/userver/userver_benchmark/controllers/fortunes/handler.hpp

@@ -0,0 +1,32 @@
+#pragma once
+
+#include "../../common/db_helpers.hpp"
+
+#include <userver/server/handlers/http_handler_base.hpp>
+
+#include <userver/storages/postgres/postgres_fwd.hpp>
+#include <userver/storages/postgres/query.hpp>
+
+namespace userver_techempower::fortunes {
+
+class Handler final : public userver::server::handlers::HttpHandlerBase {
+ public:
+  static constexpr std::string_view kName = "fortunes-handler";
+
+  Handler(const userver::components::ComponentConfig& config,
+          const userver::components::ComponentContext& context);
+
+  std::string HandleRequestThrow(
+      const userver::server::http::HttpRequest& request,
+      userver::server::request::RequestContext&) const final;
+
+  std::string GetResponse() const;
+
+ private:
+  const userver::storages::postgres::ClusterPtr pg_;
+  const userver::storages::postgres::Query select_all_fortunes_query_;
+
+  db_helpers::DatabasePoolSemaphore semaphore_;
+};
+
+}  // namespace userver_techempower::fortunes

+ 16 - 0
frameworks/C++/userver/userver_benchmark/controllers/json/handler.cpp

@@ -0,0 +1,16 @@
+#include "handler.hpp"
+
+namespace userver_techempower::json {
+
+userver::formats::json::Value Handler::HandleRequestJsonThrow(
+    const userver::server::http::HttpRequest&,
+    const userver::formats::json::Value&,
+    userver::server::request::RequestContext&) const {
+  return GetResponse();
+}
+
+userver::formats::json::Value Handler::GetResponse() {
+  return userver::formats::json::MakeObject("message", "Hello, World!");
+}
+
+}  // namespace userver_techempower::json

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff