| | |
| | | # |
| | | # 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") |
| | |
| | | # 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 |
| | |
| | | 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() { |
| | |
| | | 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 ")" |
| | | 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 ")" |
| | | 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." |