Skip to content

Speed up CA spawn by batching pki-server CLI calls. - #5372

Open
agaragna77 wants to merge 1 commit into
dogtagpki:masterfrom
agaragna77:StartupOptimization
Open

Speed up CA spawn by batching pki-server CLI calls.#5372
agaragna77 wants to merge 1 commit into
dogtagpki:masterfrom
agaragna77:StartupOptimization

Conversation

@agaragna77

@agaragna77 agaragna77 commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Batch group-member adds and fold cert request/create/import into a single ca-cert-create per cert; fix Java CLI routing for hyphenated subcommand names like add-batch.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added batch group member addition functionality for assigning a single member to multiple groups simultaneously.
  • Improvements

    • Enhanced CLI command routing to correctly resolve commands with overlapping module names to the longest match.
    • Optimized certificate request handling during deployment operations.
  • Documentation

    • Added performance benchmarking tools and analysis results documenting optimization improvements to deployment startup times.

Batch group-member adds and fold cert request/create/import into a
single ca-cert-create per cert; fix Java CLI routing for hyphenated
subcommand names like add-batch.

Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR optimizes PKI spawn performance through CLI routing improvements, batch group member operations, and refactored certificate creation flows for DS-enabled deployments. It includes benchmark tooling to measure and validate these improvements.

Changes

Core Features: CLI, Batch Operations, Deployment

Layer / File(s) Summary
CLI Longest-Match Module Routing
base/common/src/main/java/org/dogtagpki/cli/CLI.java
CLI.findModules() now tracks the longest matching command-name segment sequence via a matchedJ cursor instead of breaking on first match, enabling proper disambiguation of overlapping module prefixes like "add-batch" vs "add".
Python Batch Group Member Addition
base/server/python/pki/server/cli/__init__.py, base/server/python/pki/server/cli/group.py
JAVA_COMMANDS regex recognizes *-group-member-add-batch as a Java command. GroupMemberCLI registers GroupMemberAddBatchCLI subcommand with argparse parser for member_id and group_ids arguments and corresponding help text.
Java Batch Group Member Command
base/server/src/main/java/org/dogtagpki/server/cli/SubsystemGroupMemberAddBatchCLI.java, base/server/src/main/java/org/dogtagpki/server/cli/SubsystemGroupMemberCLI.java
SubsystemGroupMemberAddBatchCLI implements execute() to validate arguments, load LDAP/engine config, open UGSubsystem session, iterate group IDs to look up and add the member to each group, and persist modifications with guaranteed session shutdown.
Certificate Request-ID and CSR Creation Refactoring
base/server/python/pki/server/deployment/__init__.py
PKIDeployer.prepare_cert_request_id() centralizes request-ID logic (legacy via SystemConfigClient or deferred). ensure_csr_file() persists CSR to subsystem location. When pki_ds_setup is enabled, create_cert() constructs CSR import parameters and conditionally passes request metadata (type, DNS names, adjust-validity) to subsystem.create_cert(). Admin and system certificate setup paths now call prepare_cert_request_id() before certificate operations.
Bulk Group Operations and Extended CA Cert API
base/server/python/pki/server/subsystem.py
PKISubsystem.add_group_members() batch-adds a member to multiple groups via single pki-server invocation. CASubsystem.create_cert() signature expanded with optional csr_path, csr_format, request_type, dns_names, adjust_validity, and import_cert parameters; command line conditionally appends corresponding flags (--csr, --request-type, --dns-names, --adjust-validity, --import-cert) when set. PKIDeployer now calls add_group_members() instead of iterating per-group membership calls.

Benchmark Analysis and Measurement

Layer / File(s) Summary
Spawn Debug Log Analysis Script
benchmarks/analyze-spawn-debug.py
New Python script parses ISO-like timestamps from pkispawn --debug output, extracts DEBUG: Command payloads, detects phase markers, counts command types and pki-server subcommands, generates phase timeline with duration deltas, and computes per-invocation timing statistics via consecutive-timestamp gap analysis with min/max/avg/median aggregation.
Startup Baseline Measurement Script
benchmarks/run-startup-baseline.sh
New Bash script runs timed pki-create and pki-spawn steps via /usr/bin/time, logs output with TIME_RESULT metrics to benchmarks/startup-baseline.log, and prints instructions for updating benchmark markdown with captured timings.
Benchmark Result Documentation
benchmarks/startup-baseline.md, benchmarks/spawn-validation-results.md, benchmarks/spawn-optimized-results.md, benchmarks/spawn-debug-analysis.txt, benchmarks/spawn-debug-analysis-optimized.txt
Benchmark reports document PKI startup/spawn timing baseline, end-to-end validation wall times (~137–138s), phase breakdowns (DB setup, CA init, cert/admin LDAP, Tomcat), subprocess/subcommand counts, per-invocation micro-benchmarks, and prioritized optimization targets (e.g., batching group-member-add, reducing system-cert round-trips, minimizing DB rebuild, deferring profile import).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • fmarco76

Poem

🐰 Through the CLI's longest path we hop,
Batch-adding members in a single stop,
Certificates deferred, requests prepared with care,
Spawn times tumble down through optimized air!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main optimization objective: batching pki-server CLI calls to speed up CA spawn. It aligns with the primary change across multiple files (batching group member adds, consolidating certificate operations, and fixing CLI routing).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces optimizations to the PKI deployment and CLI routing, notably adding a batch group member addition feature (add-batch) to reduce JVM invocations and refactoring certificate creation to support direct CSR import. Benchmark scripts and baseline results are also included to document these performance gains. The review feedback highlights several key improvements: resolving a potential NullPointerException in CLI.java command routing, adding defensive checks for member_id in Python, handling string-type dns_names to prevent character-splitting bugs, ensuring safe binary writes for CSR files, and validating all groups beforehand in the batch CLI to prevent partial updates.

// repeat for the remaining parts
current = module.getCLI();
i = j + 1;
i = matchedJ + 1;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If no matching module is found during the command routing, module will remain null. This will lead to a NullPointerException when calling module.getCLI() on line 181.

Consider adding a null check before invoking module.getCLI() to throw a descriptive CLIException instead:

if (module == null) {
    throw new CLIException("Unknown command: " + command);
}

Comment on lines +2069 to +2070
if not group_ids:
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Add a defensive check to ensure member_id is not None or empty before constructing the command list. This prevents a cryptic TypeError when calling subprocess.check_call with a None value.

        if not group_ids:
            return

        if not member_id:
            raise ValueError("member_id must be specified")

Comment on lines +2721 to +2722
if dns_names:
cmd.extend(['--dns-names', ','.join(dns_names)])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If dns_names is passed as a single string instead of a list of strings, calling ','.join(dns_names) will split the string by characters (e.g., 'example.com' becomes 'e,x,a,m,p,l,e,.,c,o,m'). Adding a check for isinstance(dns_names, str) prevents this common bug.

Suggested change
if dns_names:
cmd.extend(['--dns-names', ','.join(dns_names)])
if dns_names:
if isinstance(dns_names, str):
cmd.extend(['--dns-names', dns_names])
else:
cmd.extend(['--dns-names', ','.join(dns_names)])

Comment on lines +3436 to +3437
with open(csr_path, 'w', encoding='utf-8') as f:
f.write(request_pem)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent a potential TypeError if pki.nssdb.convert_csr returns bytes instead of str (or vice versa depending on the Python 3 environment/library version), write the file in binary mode ('wb') and encode the string if necessary.

Suggested change
with open(csr_path, 'w', encoding='utf-8') as f:
f.write(request_pem)
with open(csr_path, 'wb') as f:
if isinstance(request_pem, str):
f.write(request_pem.encode('utf-8'))
else:
f.write(request_pem)

Comment on lines +71 to +82
for (int i = 1; i < cmdArgs.length; i++) {
String groupID = cmdArgs[i];

Group group = ugSubsystem.getGroupFromName(groupID);

if (group == null) {
throw new GroupNotFoundException(groupID);
}

group.addMemberName(memberID);
ugSubsystem.modifyGroup(group);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent partial updates and ensure atomicity where possible, validate that all specified groups exist before performing any modifications. Currently, if a group in the middle of the list does not exist, the previous groups will have already been modified, leaving the system in a partially configured state.

            java.util.List<Group> groups = new java.util.ArrayList<>();
            for (int i = 1; i < cmdArgs.length; i++) {
                String groupID = cmdArgs[i];
                Group group = ugSubsystem.getGroupFromName(groupID);
                if (group == null) {
                    throw new GroupNotFoundException(groupID);
                }
                groups.add(group);
            }

            for (Group group : groups) {
                group.addMemberName(memberID);
                ugSubsystem.modifyGroup(group);
            }

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (2)
benchmarks/run-startup-baseline.sh (1)

9-13: ⚡ Quick win

Add preflight checks for required scripts and container availability.

Failing early with clear errors avoids partial logs and ambiguous failures during timed runs.

Proposed fix
 CREATE=${CREATE:-../IDM-CI/bash/pki-create.sh}
 SPAWN=${SPAWN:-../IDM-CI/bash/pki-spawn.sh}
 CFG=${CFG:-/usr/share/pki/server/examples/installation/ca.cfg}
 SKIP_BUILD=${SKIP_BUILD:-}
+
+[[ -x "$CREATE" ]] || { echo "Missing or non-executable CREATE script: $CREATE" >&2; exit 1; }
+[[ -x "$SPAWN" ]] || { echo "Missing or non-executable SPAWN script: $SPAWN" >&2; exit 1; }
+docker inspect pki >/dev/null 2>&1 || { echo "Container 'pki' is not available." >&2; exit 1; }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@benchmarks/run-startup-baseline.sh` around lines 9 - 13, Add preflight checks
at the top of run-startup-baseline.sh to fail fast with clear errors: verify
that the files referenced by CREATE and SPAWN exist and are executable and that
CFG exists/readable (use the CREATE, SPAWN, CFG variables), and short-circuit if
SKIP_BUILD is set. Also check that the container runtime is available (e.g.,
docker or podman command exists and `docker ps`/`podman ps` succeeds) and print
explicit error messages and exit non-zero if any check fails. Ensure messages
name the failing variable (CREATE/SPAWN/CFG) and include suggested fixes so
timed runs stop early with clear logs.
base/common/src/main/java/org/dogtagpki/cli/CLI.java (1)

149-182: ⚡ Quick win

Add a regression test for longest-match routing.

This changes a core dispatch rule; please lock it down with coverage for overlapping names like add vs add-batch so future CLI additions do not silently regress routing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@base/common/src/main/java/org/dogtagpki/cli/CLI.java` around lines 149 - 182,
Add a regression test that verifies the CLI dispatch chooses the longest
matching module name when names overlap (e.g., "add" vs "add-batch"); exercise
the parsing/dispatch logic that builds moduleName and uses
current.getModule(...) (the loop using variables moduleName, matchedJ, j and
assigning module via CLIModule m) and assert that the returned module is the
longer match (add-batch) and that i/remaining dispatch behave correctly;
implement the test in the CLI test suite to invoke the CLI entry point with both
"add" and "add-batch" forms and fail if the shorter module is chosen.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@base/server/src/main/java/org/dogtagpki/server/cli/SubsystemGroupMemberAddBatchCLI.java`:
- Around line 71-81: The loop in SubsystemGroupMemberAddBatchCLI currently calls
ugSubsystem.getGroupFromName, group.addMemberName and ugSubsystem.modifyGroup
for each group as it iterates, causing partial updates if a later group is
invalid or modifyGroup fails; change the logic to first validate all target
groupIDs by calling ugSubsystem.getGroupFromName for each cmdArgs[i] and abort
if any are null, then perform the modifications; implement the update phase to
either (a) apply changes only after validation (calling group.addMemberName and
ugSubsystem.modifyGroup in a second loop) or (b) if possible use a transactional
API on ugSubsystem, and if modifyGroup can still fail ensure you revert
previously modified groups by calling group.removeMemberName on successful ones
in a catch block to avoid partial application (refer to
SubsystemGroupMemberAddBatchCLI, groupID, getGroupFromName, Group.addMemberName,
Group.removeMemberName, and ugSubsystem.modifyGroup).

In `@benchmarks/analyze-spawn-debug.py`:
- Around line 79-80: The current computation sets t0/t1 from the first/last raw
lines which can be None if header/footer lack timestamps; instead, map
parse_line_ts over all lines (using the same parse_line_ts function), filter out
None results, then set t0 to the minimum timestamp and t1 to the maximum
timestamp from that parsed list (fall back to None if no parsed timestamps
exist) so wall-clock bounds come from actual parsed timestamps rather than raw
first/last lines.
- Line 192: The current median calculation uses med = sorted(durs)[len(durs) //
2], which selects the upper middle element for even-length lists; change it to
compute the true median by sorting durs and, when len(durs) is even, averaging
the two middle elements (or simply use statistics.median on durs) so med
reflects the true median; update the assignment where med is computed in
benchmarks/analyze-spawn-debug.py (the sorted(durs) / med logic).

In `@benchmarks/spawn-debug-analysis.txt`:
- Line 1: The benchmark artifact contains an absolute local path
"/home/agaragna/Projects/pki/benchmarks/spawn-debug-timestamped.log"; update the
artifact generation or the committed file (benchmarks/spawn-debug-analysis.txt)
to use a repo-relative path (e.g., "benchmarks/spawn-debug-timestamped.log") or
a placeholder variable, ensuring any tooling that writes this log uses
repository-root-relative paths instead of absolute user-specific paths; modify
the producer of this entry (the script or tool that writes the log name) to
compute the relative path before writing.

In `@benchmarks/spawn-optimized-results.md`:
- Line 98: The ts timestamp format currently uses the integer epoch token (%s)
after seconds which does not produce sub-second fractions; in the pipeline line
that pipes to ts (the "--debug 2>&1 | ts ..." invocation) replace the "%S.%s"
pattern with the dotted fractional variant of seconds (use "%.S" so seconds +
fraction are emitted correctly) to ensure true sub-second timestamps are
produced.

---

Nitpick comments:
In `@base/common/src/main/java/org/dogtagpki/cli/CLI.java`:
- Around line 149-182: Add a regression test that verifies the CLI dispatch
chooses the longest matching module name when names overlap (e.g., "add" vs
"add-batch"); exercise the parsing/dispatch logic that builds moduleName and
uses current.getModule(...) (the loop using variables moduleName, matchedJ, j
and assigning module via CLIModule m) and assert that the returned module is the
longer match (add-batch) and that i/remaining dispatch behave correctly;
implement the test in the CLI test suite to invoke the CLI entry point with both
"add" and "add-batch" forms and fail if the shorter module is chosen.

In `@benchmarks/run-startup-baseline.sh`:
- Around line 9-13: Add preflight checks at the top of run-startup-baseline.sh
to fail fast with clear errors: verify that the files referenced by CREATE and
SPAWN exist and are executable and that CFG exists/readable (use the CREATE,
SPAWN, CFG variables), and short-circuit if SKIP_BUILD is set. Also check that
the container runtime is available (e.g., docker or podman command exists and
`docker ps`/`podman ps` succeeds) and print explicit error messages and exit
non-zero if any check fails. Ensure messages name the failing variable
(CREATE/SPAWN/CFG) and include suggested fixes so timed runs stop early with
clear logs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 86ab2ce9-a164-41aa-99d9-2823b04097d1

📥 Commits

Reviewing files that changed from the base of the PR and between 60dc2ac and e630c52.

📒 Files selected for processing (14)
  • base/common/src/main/java/org/dogtagpki/cli/CLI.java
  • base/server/python/pki/server/cli/__init__.py
  • base/server/python/pki/server/cli/group.py
  • base/server/python/pki/server/deployment/__init__.py
  • base/server/python/pki/server/subsystem.py
  • base/server/src/main/java/org/dogtagpki/server/cli/SubsystemGroupMemberAddBatchCLI.java
  • base/server/src/main/java/org/dogtagpki/server/cli/SubsystemGroupMemberCLI.java
  • benchmarks/analyze-spawn-debug.py
  • benchmarks/run-startup-baseline.sh
  • benchmarks/spawn-debug-analysis-optimized.txt
  • benchmarks/spawn-debug-analysis.txt
  • benchmarks/spawn-optimized-results.md
  • benchmarks/spawn-validation-results.md
  • benchmarks/startup-baseline.md

Comment on lines +71 to +81
for (int i = 1; i < cmdArgs.length; i++) {
String groupID = cmdArgs[i];

Group group = ugSubsystem.getGroupFromName(groupID);

if (group == null) {
throw new GroupNotFoundException(groupID);
}

group.addMemberName(memberID);
ugSubsystem.modifyGroup(group);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid partial group updates on later failures.

This persists each membership change as you iterate. If one of the later groupIDs is invalid or modifyGroup() fails, the earlier groups stay updated and the batch leaves the subsystem in a partially applied state.

Suggested direction
-            for (int i = 1; i < cmdArgs.length; i++) {
-                String groupID = cmdArgs[i];
-
-                Group group = ugSubsystem.getGroupFromName(groupID);
-
-                if (group == null) {
-                    throw new GroupNotFoundException(groupID);
-                }
-
-                group.addMemberName(memberID);
-                ugSubsystem.modifyGroup(group);
-            }
+            java.util.List<Group> groups = new java.util.ArrayList<>();
+            for (int i = 1; i < cmdArgs.length; i++) {
+                String groupID = cmdArgs[i];
+                Group group = ugSubsystem.getGroupFromName(groupID);
+                if (group == null) {
+                    throw new GroupNotFoundException(groupID);
+                }
+                groups.add(group);
+            }
+
+            for (Group group : groups) {
+                group.addMemberName(memberID);
+                ugSubsystem.modifyGroup(group);
+            }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for (int i = 1; i < cmdArgs.length; i++) {
String groupID = cmdArgs[i];
Group group = ugSubsystem.getGroupFromName(groupID);
if (group == null) {
throw new GroupNotFoundException(groupID);
}
group.addMemberName(memberID);
ugSubsystem.modifyGroup(group);
java.util.List<Group> groups = new java.util.ArrayList<>();
for (int i = 1; i < cmdArgs.length; i++) {
String groupID = cmdArgs[i];
Group group = ugSubsystem.getGroupFromName(groupID);
if (group == null) {
throw new GroupNotFoundException(groupID);
}
groups.add(group);
}
for (Group group : groups) {
group.addMemberName(memberID);
ugSubsystem.modifyGroup(group);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@base/server/src/main/java/org/dogtagpki/server/cli/SubsystemGroupMemberAddBatchCLI.java`
around lines 71 - 81, The loop in SubsystemGroupMemberAddBatchCLI currently
calls ugSubsystem.getGroupFromName, group.addMemberName and
ugSubsystem.modifyGroup for each group as it iterates, causing partial updates
if a later group is invalid or modifyGroup fails; change the logic to first
validate all target groupIDs by calling ugSubsystem.getGroupFromName for each
cmdArgs[i] and abort if any are null, then perform the modifications; implement
the update phase to either (a) apply changes only after validation (calling
group.addMemberName and ugSubsystem.modifyGroup in a second loop) or (b) if
possible use a transactional API on ugSubsystem, and if modifyGroup can still
fail ensure you revert previously modified groups by calling
group.removeMemberName on successful ones in a catch block to avoid partial
application (refer to SubsystemGroupMemberAddBatchCLI, groupID,
getGroupFromName, Group.addMemberName, Group.removeMemberName, and
ugSubsystem.modifyGroup).

Comment on lines +79 to +80
t0 = parse_line_ts(lines[0]) if lines else None
t1 = parse_line_ts(lines[-1]) if lines else None

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Compute wall-clock bounds from parsed timestamps, not first/last raw lines.

If header/footer lines lack timestamps, t0/t1 becomes None or inaccurate despite valid data in the body.

Proposed fix
-    t0 = parse_line_ts(lines[0]) if lines else None
-    t1 = parse_line_ts(lines[-1]) if lines else None
+    parsed_ts = [parse_line_ts(line) for line in lines]
+    parsed_ts = [ts for ts in parsed_ts if ts is not None]
+    t0 = parsed_ts[0] if parsed_ts else None
+    t1 = parsed_ts[-1] if parsed_ts else None
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
t0 = parse_line_ts(lines[0]) if lines else None
t1 = parse_line_ts(lines[-1]) if lines else None
parsed_ts = [parse_line_ts(line) for line in lines]
parsed_ts = [ts for ts in parsed_ts if ts is not None]
t0 = parsed_ts[0] if parsed_ts else None
t1 = parsed_ts[-1] if parsed_ts else None
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@benchmarks/analyze-spawn-debug.py` around lines 79 - 80, The current
computation sets t0/t1 from the first/last raw lines which can be None if
header/footer lack timestamps; instead, map parse_line_ts over all lines (using
the same parse_line_ts function), filter out None results, then set t0 to the
minimum timestamp and t1 to the maximum timestamp from that parsed list (fall
back to None if no parsed timestamps exist) so wall-clock bounds come from
actual parsed timestamps rather than raw first/last lines.

durs = inv_durations[sub]
s = sum(durs)
total_ps_time += s
med = sorted(durs)[len(durs) // 2]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use a true median for even-sized duration lists.

Current median logic picks the upper middle element for even n, which skews reported medians.

Proposed fix
+import statistics
...
-        med = sorted(durs)[len(durs) // 2]
+        med = statistics.median(durs)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
med = sorted(durs)[len(durs) // 2]
med = statistics.median(durs)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@benchmarks/analyze-spawn-debug.py` at line 192, The current median
calculation uses med = sorted(durs)[len(durs) // 2], which selects the upper
middle element for even-length lists; change it to compute the true median by
sorting durs and, when len(durs) is even, averaging the two middle elements (or
simply use statistics.median on durs) so med reflects the true median; update
the assignment where med is computed in benchmarks/analyze-spawn-debug.py (the
sorted(durs) / med logic).

@@ -0,0 +1,65 @@
Log: /home/agaragna/Projects/pki/benchmarks/spawn-debug-timestamped.log

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid committing absolute local paths in benchmark artifacts.

Use repo-relative paths to keep results portable and avoid exposing local machine details.

Proposed fix
-Log: /home/agaragna/Projects/pki/benchmarks/spawn-debug-timestamped.log
+Log: benchmarks/spawn-debug-timestamped.log
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Log: /home/agaragna/Projects/pki/benchmarks/spawn-debug-timestamped.log
Log: benchmarks/spawn-debug-timestamped.log
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@benchmarks/spawn-debug-analysis.txt` at line 1, The benchmark artifact
contains an absolute local path
"/home/agaragna/Projects/pki/benchmarks/spawn-debug-timestamped.log"; update the
artifact generation or the committed file (benchmarks/spawn-debug-analysis.txt)
to use a repo-relative path (e.g., "benchmarks/spawn-debug-timestamped.log") or
a placeholder variable, ensuring any tooling that writes this log uses
repository-root-relative paths instead of absolute user-specific paths; modify
the producer of this entry (the script or tool that writes the log name) to
compute the relative path before writing.

-s CA \
-D pki_audit_signing_nickname= \
-D pki_ds_url=ldap://ds.example.com:3389 \
--debug 2>&1 | ts '%Y-%m-%dT%H:%M:%S.%s' \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In moreutils ts, what do %sand%Nrepresent, and what format should be used to emit ISO timestamp with true sub-second precision (e.g.,YYYY-MM-DDTHH:MM:SS.)?

💡 Result:

In the moreutils ts utility, the standard strftime(3) format specifiers apply [1][2]. However, to achieve sub-second resolution, moreutils provides extended versions of certain specifiers [1][3]. %s and %N: - %s represents the number of seconds since the Unix Epoch (seconds since 1970-01-01 00:00:00 UTC) [1][3]. - %N is a standard strftime specifier for nanoseconds [1], though it is not explicitly mentioned as an extended feature of ts. More critically, ts provides the extended specifier %.s, which represents seconds since the epoch with sub-second (microsecond) resolution [1][3]. To emit an ISO 8601 timestamp with true sub-second precision, you should use the extended specifier %.S (which adds the fractional second to the standard %S specifier) [1][2]. The recommended format string is: '%Y-%m-%dT%H:%M:%.S' Example usage: command | ts '%Y-%m-%dT%H:%M:%.S' This will produce output in the format YYYY-MM-DDTHH:MM:SS. [1][3]. The %.S specifier provides microsecond resolution [2][3].

Citations:


🌐 Web query:

moreutils ts format specifier %N and %s; what specifier emits fractional seconds/sub-second precision (e.g., uses %.S or %.s)?

💡 Result:

In moreutils ts, fractional/sub-second output is produced by the dotted variants:

  • %.S = like %S (seconds), but with a fractional part (sub-second resolution)
  • %.s = like %s (Unix epoch seconds), but with a fractional part
  • %.T = like %T (%H:%M:%S), but with a fractional part (e.g. 1:15:30.00001) [1]

%N (nanoseconds) is not a ts format specifier (it’s a GNU date extension and won’t work in ts); use %.S/%.s/%.T instead. [2]

Sources: [1] ts(1) man page (Debian) (manpages.debian.org), [2] Unix & Linux SE discussion of ts and %N (unix.stackexchange.com)

Citations:


Fix moreutils ts fractional timestamp format (%s isn’t sub-second)

moreutils ts uses %s for Unix epoch seconds (integer), so ...:%S.%s does not emit a true sub-second fraction. Use the dotted fractional variants instead (e.g., %.S for %S with fractional part).

Suggested adjustment
-  --debug 2>&1 | ts '%Y-%m-%dT%H:%M:%S.%s' \
+  --debug 2>&1 | ts '%Y-%m-%dT%H:%M:%.S' \
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
--debug 2>&1 | ts '%Y-%m-%dT%H:%M:%S.%s' \
--debug 2>&1 | ts '%Y-%m-%dT%H:%M:%.S' \
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@benchmarks/spawn-optimized-results.md` at line 98, The ts timestamp format
currently uses the integer epoch token (%s) after seconds which does not produce
sub-second fractions; in the pipeline line that pipes to ts (the "--debug 2>&1 |
ts ..." invocation) replace the "%S.%s" pattern with the dotted fractional
variant of seconds (use "%.S" so seconds + fraction are emitted correctly) to
ensure true sub-second timestamps are produced.

Comment on lines +251 to +252
print('Usage: pki-server %s-group-member-add-batch [OPTIONS] <member ID> <group ID>...'
% self.parent.parent.parent.name)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pki-server <subsystem>-group-member-add-batch <member ID> <group ID> doesn't really go well with existing commands:

$ pki-server <subsystem>-group-add <group ID>
$ pki-server <subsystem>-group-member-find <group ID>
$ pki-server <subsystem>-group-member-add <group ID> <member ID>
$ pki-server <subsystem>-group-member-del <group ID> <member ID>

The pki-server <subsystem>-group-* commands are meant for groups, so the first param is always the <group ID>.

Since we want to add a user to multiple groups (i.e. roles) I'd suggest to update the following command instead to accept multiple groups/roles:

$ pki-server <subsystem>-user-role-add <user ID> <role ID> [<additional role IDs>...]

Note: The command is defined in both Python UserRoleAddCLI class and Java SubsystemUserRoleAddCLI class. The Python class is needed to add it as a subcommand of pki-server, but the actual implementation is in the Java class.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants