mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Valera V Harseko
yesterday 5ec57539279164d5be9797a4975f2e9bdaf0263c
Benchmark: make summary.sh reusable, move notes into the workflow

- Parametrize summary.sh: the server name, version and image are now arguments
per server (`<name> <statistics.json> <version> <image>` x2), replacing the
hardcoded "OpenLDAP"/"OpenDJ". The report title, tables and chart
legends/labels are all driven by the passed names, so the script can compare
any two LDAP servers.
- Move the benchmark-specific Notes out of the script into the workflow's
"Build job summary" step (appended to $GITHUB_STEP_SUMMARY after the generic
report).
2 files modified
100 ■■■■ changed files
.github/benchmark/summary.sh 80 ●●●● patch | view | raw | blame | history
.github/workflows/benchmark.yml 20 ●●●● patch | view | raw | blame | history
.github/benchmark/summary.sh
@@ -13,23 +13,30 @@
#
# Copyright 2026 3A Systems, LLC.
#
# Render an OpenDJ-vs-OpenLDAP LDAP benchmark report (versions + comparison table +
# comparative Mermaid charts) to stdout โ€” intended to be appended to $GITHUB_STEP_SUMMARY.
# Render an LDAP benchmark comparison report (versions + per-operation table + QuickChart charts)
# for two servers A and B to stdout โ€” intended to be appended to $GITHUB_STEP_SUMMARY. The report
# is generic: server names, versions and images are all parameters, and benchmark-specific notes
# are NOT included here (append them separately, e.g. `cat notes.md >> $GITHUB_STEP_SUMMARY`).
#
# Usage:
#   summary.sh <openldap_statistics.json> <opendj_statistics.json> \
#              <openldap_version> <opendj_version> [openldap_image] [opendj_image]
#   summary.sh <A_name> <A_statistics.json> <A_version> <A_image> \
#              <B_name> <B_statistics.json> <B_version> <B_image>
#
# The admin connection bind is labelled ADMIN_CONNECT in the plan and is intentionally
# skipped here, so it never pollutes the per-operation comparison.
set -euo pipefail
OL_JSON="${1:?openldap statistics.json required}"
DJ_JSON="${2:?opendj statistics.json required}"
OL_VER="${3:-unknown}"
DJ_VER="${4:-unknown}"
OL_IMG="${5:-}"
DJ_IMG="${6:-}"
A_NAME="${1:?server A name required}"
A_JSON="${2:?server A statistics.json required}"
A_VER="${3:-unknown}"
A_IMG="${4:-}"
B_NAME="${5:?server B name required}"
B_JSON="${6:?server B statistics.json required}"
B_VER="${7:-unknown}"
B_IMG="${8:-}"
A_COLOR="#4e79a7"   # blue   = server A
B_COLOR="#f28e2b"   # orange = server B
# Operations to compare, in workflow order. ADMIN_CONNECT and Total are excluded.
OPS=("ADD" "SEARCH" "COMPARE" "MODIFY" "BIND" "DELETE" "READD")
@@ -39,7 +46,7 @@
# mi <file> <label> <field> -> integer value (0 if absent).
mi() { jq -r --arg l "$2" --arg f "$3" '((.[$l][$f]) // 0) | round' "$1"; }
echo "## ๐Ÿ”ฌ Benchmark: OpenDJ vs OpenLDAP"
echo "## ๐Ÿ”ฌ Benchmark: ${A_NAME} vs ${B_NAME}"
echo ""
# ---------------------------------------------------------------- Versions
@@ -47,47 +54,45 @@
echo ""
echo "| Server | Version | Image |"
echo "|---|---|---|"
echo "| **OpenLDAP** | \`${OL_VER}\` | \`${OL_IMG:-n/a}\` |"
echo "| **OpenDJ** | \`${DJ_VER}\` | \`${DJ_IMG:-n/a}\` |"
echo "| **${A_NAME}** | \`${A_VER}\` | \`${A_IMG:-n/a}\` |"
echo "| **${B_NAME}** | \`${B_VER}\` | \`${B_IMG:-n/a}\` |"
echo ""
# ---------------------------------------------------------------- Totals
ol_tot_tp=$(m  "$OL_JSON" Total throughput)
dj_tot_tp=$(m  "$DJ_JSON" Total throughput)
ol_tot_n=$(mi  "$OL_JSON" Total sampleCount)
dj_tot_n=$(mi  "$DJ_JSON" Total sampleCount)
ol_tot_e=$(mi  "$OL_JSON" Total errorCount)
dj_tot_e=$(mi  "$DJ_JSON" Total errorCount)
ol_tot_mean=$(m "$OL_JSON" Total meanResTime)
dj_tot_mean=$(m "$DJ_JSON" Total meanResTime)
a_tot_tp=$(m  "$A_JSON" Total throughput)
b_tot_tp=$(m  "$B_JSON" Total throughput)
a_tot_n=$(mi  "$A_JSON" Total sampleCount)
b_tot_n=$(mi  "$B_JSON" Total sampleCount)
a_tot_e=$(mi  "$A_JSON" Total errorCount)
b_tot_e=$(mi  "$B_JSON" Total errorCount)
a_tot_mean=$(m "$A_JSON" Total meanResTime)
b_tot_mean=$(m "$B_JSON" Total meanResTime)
echo "### Totals (all operations, ADMIN_CONNECT excluded by the plan label)"
echo ""
echo "| Server | Throughput (ops/s) | Mean (ms) | Samples | Errors |"
echo "|---|--:|--:|--:|--:|"
echo "| **OpenLDAP** | ${ol_tot_tp} | ${ol_tot_mean} | ${ol_tot_n} | ${ol_tot_e} |"
echo "| **OpenDJ** | ${dj_tot_tp} | ${dj_tot_mean} | ${dj_tot_n} | ${dj_tot_e} |"
echo "| **${A_NAME}** | ${a_tot_tp} | ${a_tot_mean} | ${a_tot_n} | ${a_tot_e} |"
echo "| **${B_NAME}** | ${b_tot_tp} | ${b_tot_mean} | ${b_tot_n} | ${b_tot_e} |"
echo ""
# ---------------------------------------------------------------- Per-op table
echo "### Per-operation latency"
echo ""
echo "| Operation | mean ms OpenLDAP | mean ms OpenDJ | p99 ms OpenLDAP | p99 ms OpenDJ | err OL | err DJ |"
echo "| Operation | mean ms ${A_NAME} | mean ms ${B_NAME} | p99 ms ${A_NAME} | p99 ms ${B_NAME} | err ${A_NAME} | err ${B_NAME} |"
echo "|---|--:|--:|--:|--:|--:|--:|"
for op in "${OPS[@]}"; do
  printf '| %s | %s | %s | %s | %s | %s | %s |\n' \
    "$op" \
    "$(m  "$OL_JSON" "$op" meanResTime)"  "$(m  "$DJ_JSON" "$op" meanResTime)" \
    "$(mi "$OL_JSON" "$op" pct3ResTime)"  "$(mi "$DJ_JSON" "$op" pct3ResTime)" \
    "$(mi "$OL_JSON" "$op" errorCount)"   "$(mi "$DJ_JSON" "$op" errorCount)"
    "$(m  "$A_JSON" "$op" meanResTime)"  "$(m  "$B_JSON" "$op" meanResTime)" \
    "$(mi "$A_JSON" "$op" pct3ResTime)"  "$(mi "$B_JSON" "$op" pct3ResTime)" \
    "$(mi "$A_JSON" "$op" errorCount)"   "$(mi "$B_JSON" "$op" errorCount)"
done
echo ""
# ---------------------------------------------------------------- Chart helpers (QuickChart)
# Mermaid xychart-beta can't do grouped bars / a legend and crowds 14 x-labels, so render proper
# grouped bar charts via QuickChart (Chart.js) as images: https://quickchart.io/chart?c=<config>.
OL_COLOR="#4e79a7"   # blue   = OpenLDAP
DJ_COLOR="#f28e2b"   # orange = OpenDJ
# JSON array of the OPS labels: ["ADD","SEARCH",...].
labels_json() {
@@ -110,26 +115,15 @@
echo "_Per-operation throughput is not charted: every op runs once per loop iteration, so each"
echo "op's throughput just equals the loop rate. The meaningful throughput is the aggregate._"
echo ""
TP_CFG="{\"type\":\"bar\",\"data\":{\"labels\":[\"OpenLDAP\",\"OpenDJ\"],\"datasets\":[{\"label\":\"ops/s\",\"backgroundColor\":[\"$OL_COLOR\",\"$DJ_COLOR\"],\"data\":[${ol_tot_tp},${dj_tot_tp}]}]},\"options\":{\"legend\":{\"display\":false},\"title\":{\"display\":true,\"text\":\"Total throughput (ops/s)\"}}}"
TP_CFG="{\"type\":\"bar\",\"data\":{\"labels\":[\"${A_NAME}\",\"${B_NAME}\"],\"datasets\":[{\"label\":\"ops/s\",\"backgroundColor\":[\"$A_COLOR\",\"$B_COLOR\"],\"data\":[${a_tot_tp},${b_tot_tp}]}]},\"options\":{\"legend\":{\"display\":false},\"title\":{\"display\":true,\"text\":\"Total throughput (ops/s)\"}}}"
echo "![Total throughput (ops/s)]($(qc 500 320 "$TP_CFG"))"
echo ""
# ---------------------------------------------------------------- Latency chart (grouped bars)
echo "### p99 latency per operation (ms, lower is better)"
echo ""
echo "_๐ŸŸฆ OpenLDAP ยท ๐ŸŸง OpenDJ โ€” grouped bars per operation._"
echo "_๐ŸŸฆ ${A_NAME} ยท ๐ŸŸง ${B_NAME} โ€” grouped bars per operation._"
echo ""
LAT_CFG="{\"type\":\"bar\",\"data\":{\"labels\":$(labels_json),\"datasets\":[{\"label\":\"OpenLDAP\",\"backgroundColor\":\"$OL_COLOR\",\"data\":[$(vals mi "$OL_JSON" pct3ResTime)]},{\"label\":\"OpenDJ\",\"backgroundColor\":\"$DJ_COLOR\",\"data\":[$(vals mi "$DJ_JSON" pct3ResTime)]}]},\"options\":{\"title\":{\"display\":true,\"text\":\"p99 latency per operation (ms)\"}}}"
LAT_CFG="{\"type\":\"bar\",\"data\":{\"labels\":$(labels_json),\"datasets\":[{\"label\":\"${A_NAME}\",\"backgroundColor\":\"$A_COLOR\",\"data\":[$(vals mi "$A_JSON" pct3ResTime)]},{\"label\":\"${B_NAME}\",\"backgroundColor\":\"$B_COLOR\",\"data\":[$(vals mi "$B_JSON" pct3ResTime)]}]},\"options\":{\"title\":{\"display\":true,\"text\":\"p99 latency per operation (ms)\"}}}"
echo "![p99 latency per operation (ms)]($(qc 900 400 "$LAT_CFG"))"
echo ""
# ---------------------------------------------------------------- Caveats
echo "### Notes"
echo ""
echo "- \`BIND\` is the measured **user authentication** (\`test=sbind\`, single bind/unbind on its"
echo "  own connection) as \`cn=user_<n>,ou=People\` with the password set by \`MODIFY\`. The admin"
echo "  connection bind (\`ADMIN_CONNECT\`) is cached once per thread and excluded from these results."
echo "- MODIFY sends the password in cleartext; each server hashes it on write with the **same"
echo "  scheme (SSHA, Salted SHA-1)** โ€” OpenLDAP via the ppolicy hash-cleartext overlay, OpenDJ via"
echo "  its Salted SHA-1 default scheme โ€” so BIND authentication is compared on equal footing."
echo "- Full interactive JMeter HTML dashboards are attached as the \`jmeter-reports\` artifact."
.github/workflows/benchmark.yml
@@ -237,10 +237,24 @@
      # ---------------------------------------------------------------- Report
      - name: Build job summary
        run: |
          # summary.sh is generic: <name> <statistics.json> <version> <image> per server.
          bash .github/benchmark/summary.sh \
            openldap/statistics.json opendj/statistics.json \
            "$OPENLDAP_VER" "$OPENDJ_VER" \
            "$OPENLDAP_IMAGE" "$OPENDJ_IMAGE" >> "$GITHUB_STEP_SUMMARY"
            "OpenLDAP" openldap/statistics.json "$OPENLDAP_VER" "$OPENLDAP_IMAGE" \
            "OpenDJ"   opendj/statistics.json   "$OPENDJ_VER"   "$OPENDJ_IMAGE" \
            >> "$GITHUB_STEP_SUMMARY"
          # benchmark-specific notes (kept out of the reusable script)
          {
            echo ""
            echo "### Notes"
            echo ""
            echo '- `BIND` is the measured **user authentication** (`test=sbind`, single bind/unbind on its'
            echo '  own connection) as `mail=u_<n>,ou=People` with the password set by `MODIFY`. The admin'
            echo '  connection bind (`ADMIN_CONNECT`) is cached once per thread and excluded from these results.'
            echo '- `MODIFY` sends the password in cleartext; each server hashes it on write with the **same'
            echo '  scheme (`{SSHA}`, Salted SHA-1)** โ€” OpenLDAP via the ppolicy hash-cleartext overlay, OpenDJ'
            echo '  via its Salted SHA-1 default scheme โ€” so `BIND` authentication is compared on equal footing.'
            echo '- Full interactive JMeter HTML dashboards are attached as the `jmeter-reports` artifact.'
          } >> "$GITHUB_STEP_SUMMARY"
      - name: Upload JMeter reports
        if: always()