Skip to content

feat(metrics): show network storage usage - #164

Merged
pjcdawkins merged 9 commits into
mainfrom
claude/storage-metrics-disk-names-3b40fa
Sep 24, 2026
Merged

pjcdawkins merged 9 commits into
mainfrom
claude/storage-metrics-disk-names-3b40fa

Conversation

@pjcdawkins

@pjcdawkins pjcdawkins commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

The metrics API reports network storage volumes (used by storage mounts) under a storage mountpoint, but metrics:all and metrics:disk-usage only read /mnt and /tmp. On projects using storage mounts, storage usage was not visible in the CLI, and the Disk allocation in resources:get had no matching metrics column (disk_* refers to /mnt).

Changes:

  • metrics:all: add storage_used, storage_limit, storage_percent and storage_inodes_* columns. storage_percent and storage_inodes_percent are default columns.
  • metrics:disk-usage: add storage_used, storage_limit, storage_percent and storage_i* columns. storage_used, storage_limit, storage_percent and storage_ipercent are default columns.
  • In tables, the default storage columns are shown only if the deployment has storage mounts or a service reports storage. Machine-readable formats always include them (after the existing columns), for stable output.
  • Existing disk_* columns are not renamed.
  • A new api.metrics_storage config key (default true) turns this off.

🤖 Generated with Claude Code

The metrics API reports network storage volumes (used by "storage"
mounts) under the "storage" mountpoint, but metrics:all and
metrics:disk-usage only read "/mnt" and "/tmp". On projects using
storage mounts, the only disk the customer sizes could not be
monitored from the CLI, and resources:get "Disk" could not be compared
to any metrics column.

- metrics:all: add storage_used, storage_limit, storage_percent and
  storage_inodes_* columns.
- metrics:disk-usage: add storage_used, storage_limit, storage_percent
  and storage_i* columns, and a --storage report option.
- storage_percent is a default column only when a service reports
  storage, so output is unchanged for other projects.
- Existing disk_* columns are not renamed.
- The api.metrics_storage config key (default true) turns this off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 23, 2026 19:12

Copilot AI left a comment

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.

Copilot review overview

🟡 Changes recommended

Boolean environment parsing must be fixed so api.metrics_storage=0 actually disables storage metrics.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 High severity

Open (1)
What changed in this PR

Adds network storage usage metrics to legacy metrics commands, including configurable output and integration coverage.

Changes:

  • Adds storage usage and inode columns to metrics:all and metrics:disk-usage.
  • Adds the --storage report option.
  • Adds the api.metrics_storage configuration flag.
  • Adds integration tests for storage output and disabling behavior.

The environment override currently fails to disable the feature because "0" is cast to true; this requires correction.

File Summary
legacy/​src/​Command/​Metrics/​MetricsCommandBase.php Storage detection and configuration handling
legacy/​src/​Command/​Metrics/​DiskUsageCommand.php Storage columns and --storage option
legacy/​src/​Command/​Metrics/​AllMetricsCommand.php Storage metrics in combined output
legacy/​config-defaults.yaml Enables storage metrics by default
integration-tests/​metrics_test.go Tests storage output and configuration disabling

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread integration-tests/metrics_test.go Outdated

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

Warning

Changes suggested — 🟡 1 warning · 🔵 2 minor points · ⚪ 1 nitpick

🔍 Full review · 5 files reviewed

⚪ Nitpick

  • legacy/src/Command/Metrics/DiskUsageCommand.php:183 — storageFields() is duplicated verbatim in DiskUsageCommand and AllMetricsCommand (AllMetricsCommand.php:245), differing only in the key names (storage_iused vs storage_inodes_used). Both construct the same six SourceField/SourceFieldPercentage pairs over self::STORAGE_MOUNTPOINT; a future change (e.g. a different aggregation) has to be made twice or the two commands silently diverge.
Verification
  • $fields += $this->storageFields($bytes) cannot clobber existing entries: no storage_* key collides with the existing disk_/tmp_/inodes_ keys in either command.
  • The new api.metrics_storage: true lives in legacy/config-defaults.yaml, which Config always merges under the user config, so getBool('api.metrics_storage') cannot throw 'Configuration not defined'.
  • TEST_CLI_API_METRICS_STORAGE=0 is picked up by Config::applyEnvironmentOverrides' generic second-level scalar map and cast to boolean false, so the disabled test really exercises the disabled path.
  • Expected test values match the code: 920/1000 and 30/100 render as 92.0% and 30.0% via Format::Percent, and -B renders storage_used as 920 via Format::Rounded.
  • --tmp and --storage are rejected together before either sets columns, so the two report column lists can never be merged.

The diff adds integration-tests/metrics_test.go covering the storage columns, the --storage report and the api.metrics_storage=0 path; it runs in the CI integration-test job (make integration-test, after building the phar), while the legacy-php job runs php-cs-fixer, phpstan (level 8) and PHPUnit over the changed PHP. No PHP unit test covers MetricsCommandBase::storageColumns() directly, and nothing covers a storage mountpoint that omits inode metrics.

Review details
  • Commit: 0efbcec
  • Model: claude-opus-5

Review 1 of 10 for this pull request · View the full run

Comment thread legacy/src/Command/Metrics/AllMetricsCommand.php Outdated
Comment thread legacy/src/Command/Metrics/DiskUsageCommand.php Outdated
Comment thread legacy/src/Command/Metrics/MetricsCommandBase.php Outdated
- Remove the disk --storage option. Instead, disk shows storage_used,
  storage_limit and storage_percent by default (metrics:all shows
  storage_percent).
- In tables, storage columns are shown only if a service reports
  storage. Machine-readable formats always include them, for stable
  output.
- Return empty values instead of failing when the storage mountpoint
  lacks a metric (e.g. inodes).
- Share storageFields() between both commands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@upsun-dispatch

upsun-dispatch Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

📋 PR Summary

Adds network storage usage (the storage mountpoint reported by the metrics API) to the CLI metrics commands. metrics:all and metrics:disk-usage gain storage used/limit/percent and storage inode columns, shown by default in tables only when the environment actually has storage mounts or reports storage data, and always appended in machine-readable formats for stable output. The behaviour can be disabled via a new api.metrics_storage config key (default true), and existing disk_* columns are unchanged.

Changes
Layer / File(s) Summary
Metrics commands
legacy/src/Command/Metrics/MetricsCommandBase.php Adds shared helpers to build storage fields and to decide which storage columns appear in the header and default column set.
legacy/src/Command/Metrics/AllMetricsCommand.php Adds storage_used/limit/percent and storage inode columns; both storage_percent and storage_inodes_percent are now default columns.
legacy/src/Command/Metrics/DiskUsageCommand.php Adds storage used/limit/percent and storage inode columns, with the main storage columns shown by default.
Configuration
legacy/config-defaults.yaml Introduces the api.metrics_storage setting, defaulting to true.
Tests
integration-tests/metrics_storage_test.go New integration tests covering storage columns in table and machine-readable output; the CSV expectation now also includes the storage inodes percent column.

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

Warning

Changes suggested — 🟡 1 warning · 🔵 2 minor points

🔁 Incremental · 4 files reviewed

Verification
  • extractValue()'s storage early return makes the db fixture's inode-less storage mountpoint yield blank cells instead of the previous RuntimeException.
  • storageFields($bytes, $inodesPrefix) produces storage_inodes_* for metrics:all and storage_i* for disk, matching each command's TABLE_HEADER keys.
  • $fields += $this->storageFields(...) adds no key that collides with an existing field in either command.
  • The disabled branch of storageColumns() filters storage_* out of the header while the fields array is also skipped, so no row key is rendered without a header.

Covered by the rewritten integration-tests/metrics_test.go table test (storage present/absent, csv determinism, TEST_CLI_API_METRICS_STORAGE=0), run by the integration-test job in .github/workflows/ci.yml via make integration-test; no test covers a storage mountpoint that reports a limit without the matching used value.

Review details

Review 2 of 10 for this pull request · View the full run

Comment thread legacy/src/Command/Metrics/MetricsCommandBase.php
Comment thread legacy/src/Command/Metrics/MetricsCommandBase.php
Comment thread legacy/src/Command/Metrics/MetricsCommandBase.php
A limit without a matching used value was shown as 0%.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

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

Note

Reviewed — No new issues found · 1 still open

🔁 Incremental · 2 files reviewed

Outstanding from earlier reviews:

  • 🔵 #4086400234 — legacy/src/Command/Metrics/MetricsCommandBase.php:326: Column-count change silently breaks positional parsers of csv/tsv output. — Code unchanged: storageColumns() still appends the storage columns for machine-readable formats regardless of whether any service reports a storage mountpoint; author states this is intentional for stable column sets.
Verification
  • getValueFromSource now returns null when the percentage numerator is missing, so a storage volume reporting only disk_limit renders blank instead of 0.0%.
  • The mirror case (used present, limit absent) still returns null via $limit > 0, so both halves of the partial-data case now render identically blank.
  • Format::format/formatPercent return '' for null, so the new null propagation renders an empty cell rather than erroring.
  • The updated fixture (db storage with inodes_limit but no inodes_used) exercises the new branch, matching the expected trailing-blank column in 'all storage columns' and 'disk-usage storage columns'.
  • extractValue's storage-mountpoint branch returns null rather than throwing, so the added inodes_limit-only fixture cannot abort buildRows.

integration-tests/metrics_test.go covers the changed branch via the db fixture with partial inode metrics (TestMetricsStorage), run by the integration-test job in .github/workflows/ci.yml (make integration-test); no PHPUnit test in the diff covers getValueFromSource directly, though the legacy-php job runs phpstan/php-cs-fixer over the changed file.

Review details

Review 3 of 10 for this pull request · View the full run

Resolve conflicts with the --latest fix (#165) and the observability
entrypoint lookup (#118):

- MetricsCommandBase: keep both STORAGE_MOUNTPOINT and
  LATEST_SETTLE_TIME.
- Move the storage tests to metrics_storage_test.go, next to the
  --latest tests in metrics_test.go.
- Serve the observability entrypoint (_links.resources_overview) in both
  tests' mocks, as the resources overview URL is now discovered there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

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

Note

Reviewed — No blocking findings · 🔵 2 minor points

🔍 Full review · 6 files reviewed

🔵 Minor points

  • legacy/src/Command/Metrics/MetricsCommandBase.php:362 — In table (non-machine-readable) output the storage columns are shown only if some point in $values['data'] has a mountpoints.storage key. With --latest the data has already been reduced to a single point in processQuery(), so on a project that does use network storage, a latest point in which no service has yet reported its storage mountpoint makes metrics:all -1 / disk -1 drop the Storage columns entirely — the user sees a table with no storage information and no indication that the data was incomplete, rather than the blank cells the PR description predicts. Running the same command without -1 shows them again, so the column set flickers between invocations.
  • legacy/src/Command/Metrics/DiskUsageCommand.php:154 — disk treats the two volumes asymmetrically: the /mnt defaults include ipercent (inode usage %), but the storage default set passed to storageColumns() is ['storage_used', 'storage_limit', 'storage_percent'] and omits storage_ipercent, even though the command fetches inodes metrics for the storage mountpoint and the header defines the column. A network storage volume that is running out of inodes therefore shows nothing in the default table, while the same condition on /mnt is visible; the user must know to pass -c storage_ipercent.
Verification
  • Both commands' inode prefixes ('storage_inodes_' and 'storage_i') produce keys that exactly match the storage entries added to their TABLE_HEADER constants.
  • extractValue()'s new storage branch returns null instead of throwing when the storage mountpoint omits inodes_used, matching the partial 'db' fixture that expects a blank cell.
  • getValueFromSource()'s $value !== null && $limit > 0 guard makes a limit-only storage entry render blank rather than 0.0%, and leaves the limit-missing case unchanged.
  • storageColumns()'s array_filter with ARRAY_FILTER_USE_KEY strips every storage_* key (including storage_inodes_*) from both headers when api.metrics_storage is false.
  • Config::__construct merges config-defaults.yaml under the wrapper-written config.yaml, so getBool('api.metrics_storage') cannot throw 'Configuration not defined' for existing configs.

New integration tests in integration-tests/metrics_storage_test.go cover both commands, the enabled/disabled config, csv stability and the partial-inode-data case, and metrics_test.go was adapted to the merged-in observability entrypoint lookup; they run in the CI 'integration-test' job (builds the phar, then make test on integration-tests). No PHPUnit test covers storageColumns()/storageFields()/extractValue() directly; the 'legacy-php' job only runs phpstan, php-cs-fixer and the existing unit suite.

Review details
  • Commit: 5a9cfe2
  • Model: claude-opus-5

Review 4 of 10 for this pull request · View the full run

Matches the /mnt defaults, which include inode usage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@pjcdawkins

Copy link
Copy Markdown
Contributor Author

Re the latest review:

  • --latest dropping storage columns: no change. Since fix(metrics): show the latest complete data point with --latest #165, --latest picks the latest complete point, and storage is reported within each service's own data point, so a point containing the service but missing its storage mountpoint is not expected.
  • disk inode asymmetry: added storage_ipercent to the default columns in b28edb6.

🤖 Addressed by Claude Code

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

Note

Reviewed — No new issues found · 1 still open

🔁 Incremental · 2 files reviewed

Outstanding from earlier reviews:

  • 🔵 legacy/src/Command/Metrics/MetricsCommandBase.php:362: Storage usage silently vanishes from the default table on partial data points. — The table branch still decides from $values['data'], so with --latest a point lacking a storage mountpoint drops the columns silently. (first raised)
Verification
  • storage_ipercent is a real key: storageFields($bytes, 'storage_i') emits storage_iused/ilimit/ipercent, and DiskUsageCommand::TABLE_HEADER declares storage_ipercent.
  • Adding storage_ipercent to the machine-readable default set keeps CSV order ... tmp_percent, storage_used, storage_limit, storage_percent, storage_ipercent, matching the new test's expected header.
  • With api.metrics_storage=false, storageColumns() strips all storage_* keys from the header and leaves defaultColumns untouched, so the extra default name cannot reach columnsToDisplay().
  • With --tmp, columns is set explicitly to tmpReportColumns, so specifiedColumns() wins and the new storage default column does not appear.

The diff adds an integration test case ('disk-usage csv always includes storage') in integration-tests/metrics_storage_test.go, run by the integration-test CI job (make integration-test, .github/workflows/ci.yml:140); no PHP-side unit test covers storageColumns() directly.

Review details

Review 5 of 10 for this pull request · View the full run

Tables previously showed storage columns only if a data point reported
the storage mountpoint. Also check for mounts with "source: storage" in
the current deployment, so the columns do not depend on the data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

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

Warning

Changes suggested — 🟡 1 warning · 🔵 1 minor point

🔁 Incremental · 4 files reviewed

Verification
  • storageColumns() now receives $environment from both call sites (AllMetricsCommand.php:219, DiskUsageCommand.php:154), matching the new required parameter.
  • Environment is already imported in MetricsCommandBase (use Platformsh\Client\Model\Environment), so the new type hint resolves.
  • usesStorage() adds no extra API round-trip: Api::getCurrentDeployment() is memoised in self::$deploymentsCache and is already called by getServices() during processQuery().
  • ($mount['source'] ?? null) is safe for legacy string mount definitions ('shared:files/x'), since ?? suppresses the non-numeric string-offset error and yields null.
  • The machine-readable short-circuit in storageColumns() means usesStorage() is never reached for csv/json output.

Verified by the Go integration tests in integration-tests/metrics_storage_test.go, including the new 'all table shows storage when the deployment has storage mounts' case, run by the make test step of the CI workflow's Go job; no PHPUnit test covers usesStorage(), and no test exercises a deployment whose apps lack a mounts property.

Review details

Review 6 of 10 for this pull request · View the full run

Comment thread legacy/src/Command/Metrics/MetricsCommandBase.php Outdated
Comment thread legacy/src/Command/Metrics/MetricsCommandBase.php Outdated
pjcdawkins and others added 2 commits September 24, 2026 11:23
- Ignore apps and workers excluded by --service or --type, so filtered
  tables do not gain an always-empty storage column.
- Tolerate a missing or null "mounts" deployment property.
- Filter by services[] in the test mock, as the API does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Take main's TestMetricsLatest (#167), which fixes the same missing
observability entrypoint mock as this branch did.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

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

Note

Reviewed — No blocking findings · 🔵 1 minor point

🔍 Full review · 5 files reviewed

🔵 Minor point

  • legacy/src/Command/Metrics/AllMetricsCommand.php:219 — metrics:all adds only storage_percent to the default table columns, while the /mnt and /tmp volumes each contribute both a disk-percent and an inode-percent default column (inodes_percent, tmp_inodes_percent). The command does fetch MetricKind::API_TYPE_INODES for the storage mountpoint and defines storage_inodes_percent in TABLE_HEADER, so a network-storage volume exhausting its inodes shows nothing in the default metrics:all table, whereas the same condition on /mnt or /tmp is visible. metrics:disk-usage already passes storage_ipercent in its default set (DiskUsageCommand.php:154), so the two commands disagree about the same volume.
Verification
  • storageFields() key names line up with both headers: 'storage_inodes_' exists in AllMetricsCommand::TABLE_HEADER and 'storage_i' in DiskUsageCommand::TABLE_HEADER.
  • The new extractValue() storage branch returns null instead of throwing, so the test's partial db storage entry (no inodes_used) renders blank rather than aborting.
  • getValueFromSource()'s added $value !== null guard makes a missing used-value render blank instead of 0.0%, and still shows 0.0% for a genuine zero.
  • usesStorage() reads mounts via getProperty('mounts', false) ?: [], so an app whose deployment payload omits or nulls mounts does not throw.
  • storageColumns() strips only storage_-prefixed header keys when api.metrics_storage is false, and neither command's $defaultColumns contains such a key, so no default column becomes unresolvable.

Verified by the new Go integration test integration-tests/metrics_storage_test.go (table/CSV column sets, partial storage data, the disabled flag), run by the integration-test CI job; no PHP unit test covers storageColumns()/usesStorage() directly, though the legacy-php job runs phpstan, php-cs-fixer and PHPUnit over the changed classes.

Review details
  • Commit: e036946
  • Model: claude-opus-5

Review 8 of 10 for this pull request · View the full run

Matches the /mnt and /tmp defaults, and the disk command.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@pjcdawkins

Copy link
Copy Markdown
Contributor Author

Added storage_inodes_percent to the metrics:all default columns in 17f2a29, matching /mnt, /tmp and the disk command.

🤖 Addressed by Claude Code

@pjcdawkins
pjcdawkins merged commit 0583bbd into main Sep 24, 2026
6 checks passed
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