Skip to content

instar measure — predict file size for a target format

instar measure predicts the file size required to convert a source image (or a hypothetical empty image of a given size) to a target format. It is the safe, sandboxed equivalent of qemu-img measure, with byte-for-byte output parity for the targets qemu-img supports plus instar-specific support for vmdk, vpc (VHD), and vhdx targets that qemu-img cannot measure.

Synopsis

instar measure [OPTIONS] [INPUT]

Exactly one of --size SIZE or a positional INPUT file path is required.

Common options:

  -O, --target-format <FMT>       raw | qcow2 | vmdk | vpc | vhdx
                                  Default: raw
  -s, --size <SIZE>               Hypothetical image size with K/M/G/T suffix
  -f, --format <FMT>              Source format override (usually auto-detected)
      --output <FORMAT>           human (default) | json
  -o, --options <KEY=VALUE,...>   qemu-img-style options (repeatable)

Per-target option flags (alternatives to -o):

  qcow2: --cluster-size, --refcount-bits, --extended-l2,
         --lazy-refcounts, --compat, --preallocation, --compress
  vmdk:  --subformat, --grain-size
  vpc:   --subformat, --block-size
  vhdx:  --subformat, --block-size

The full flag surface is reported by instar measure --help.

Target formats

Target Source-image mode --size mode qemu-img parity
raw Yes Yes byte-identical
qcow2 Yes Yes byte-identical
vmdk Yes Yes instar-only
vpc Yes Yes instar-only
vhdx Yes Yes instar-only

For raw and qcow2 targets, instar's --output=human and --output=json output matches qemu-img measure exactly across every qemu-img version from 6.0.0 through 10.2.0 (verified by the cross-version baseline matrix in instar-testdata).

Output format

Human (default):

required size: 327680
fully allocated size: 1376256

JSON (--output=json):

{
    "required": 327680,
    "fully-allocated": 1376256
}

Note the hyphen in fully-allocated. Four-space indent, no trailing comma after the last field — these match qemu-img exactly.

For qcow2 v3 sources targeting qcow2, instar emits an additional bitmaps field (count of persistent qcow2 bitmaps the conversion would preserve, currently always 0). qemu-img has the same rule. The gate uses a 4+4 byte peek of magic and version; see docs/quirks.md for details.

-o key=value,... reference

Repeatable; later -o invocations override earlier ones. Individual flag values are overridden by matching -o keys when both are specified.

qcow2

Honoured: - cluster_size, compat (0.10|1.1), refcount_bits - extended_l2 (on|off), lazy_refcounts (on|off) - compression_type (zlib|zstd), preallocation (off|metadata|falloc|full)

Rejected (future work): - backing_file, backing_fmt, data_file, data_file_raw, encrypt.*

vmdk

Honoured: - subformat (monolithicSparse|streamOptimized|monolithicFlat), grain_size - preallocation (off|falloc|full) — accepted for vmdk, vpc and vhdx alike, and the answer is the one off gives. preallocation=metadata is refused (measure: preallocation=metadata is not supported for FORMAT), through -o and through --preallocation. qemu-img refuses metadata preallocation for these formats when it creates them (handle_aiocb_truncate() in qemu 7.0.0's block/file-posix.c), and measure predicts what instar's own writer produces. qemu-img cannot measure these formats at all, so there is no qemu-img answer to compare with.

Accepted but no size effect: - adapter_type, hwversion, toolsversion, zeroed_grain

vpc (VHD)

Honoured: - subformat (dynamic|fixed)

Accepted but no size effect: - force_size, force_size_calc

vhdx

Honoured: - subformat (dynamic), block_size

The virtual size is at most 64 TiB (70368744177664 bytes), the largest VHDX qemu-img creates. A larger --size is refused before the guest runs (measure: virtual size N exceeds the VHDX maximum of 64 TiB (70368744177664 bytes)). A larger source is read, and then refused with measure failed: source virtual size exceeds the VHDX maximum of 64 TiB (70368744177664 bytes) (MeasureResult::ERROR_TOO_LARGE), not as an unsupported source format. A size that is not a multiple of 512 is measured rounded up to one, as convert and create record it.

Accepted but no size effect: - log_size, block_state_zero

Rejected (future work): - subformat=fixed

raw

No options accepted. Any -o key with -O raw is rejected with an error.

Known divergences from qemu-img

For raw and qcow2 targets, instar measure matches qemu-img exactly on the cross-version baseline matrix. A handful of source-image cases exhibit small numeric divergences because instar's parser scanners are simpler than qemu-img's:

  • Raw sources with on-disk sparse extents: instar over-reports required because the raw scanner does not use SEEK_HOLE/SEEK_DATA. qemu-img does.
  • QCOW2 sources for some real-world images: instar's scanner counts allocated bytes slightly differently. Likely a compressed-cluster or extended-L2 subcluster edge case under investigation.
  • QCOW2 sources with backing chains: instar reports the top layer's allocations only. qemu-img consults the chain. Tracked as #642; the same plumbing closes this for a VHD or VHDX chain, whichever format the chain is in.
  • VHDX sources: instar treats every BAT block as fully allocated. qemu-img returns the actual block-state distribution.
  • VMDK multi-extent source layouts: instar's extent map propagation differs from qemu-img for some non-trivial layouts.
  • VHD legacy CHS-only sources: instar's reported virtual_size differs by approximately 2 MiB.

The canonical list of test cases skipped due to these divergences is KNOWN_SOURCE_SCANNER_DIVERGENCES in tests/test_measure.py. Each entry names the divergence category.

Malformed block tables

measure's VHDX allocation scan refuses a payload block whose BAT entry names a byte range that is the image's own structure, rather than counting it as allocated: file offset zero, where the file identifier and headers live, or a range overlapping any region the image's region table declares. This is the same test the guest chain walker already applies when reading such a block for convert, dd, compare, bench and rebase; measure's own whole-BAT scan did not apply it until recently, so it could count a block one of those operations already refused to read:

measure: source block table names a block inside the image's own
structure (at file offset zero, or overlapping a region the image
declares); the format is recognised, the image is malformed

Exit 1, the same as any other refused source.

The format is recognised and the image parsed far enough to walk its BAT, so the message deliberately does not say "unsupported format" — that would send a user hunting a format problem that is not there. A block declared immediately past the last region in the table is unaffected and still measures normally. See docs/format-internals.md § vhdx for the predicate both the readers and this scan share.

Future work

  • SEEK_HOLE detection in the raw scanner.
  • VHDX scanner partial-block-state walk.
  • VMDK multi-extent sparse propagation.
  • QCOW2 scanner backing-chain composition.
  • QCOW2 compressed-cluster / extended-L2 subcluster overcount investigation.
  • encrypt.format=luks aware sizing.
  • -l SNAPSHOT snapshot-targeted measurement.
  • --image-opts parsing.
  • -o help listing.
  • subformat=fixed for vhdx target.

For per-format size formulas implemented today, see src/crates/measure/src/lib.rs.

Examples

Predict size for a 1 GiB hypothetical qcow2 with the default cluster size:

instar measure --size 1G -O qcow2

Same but with a 4 KiB cluster size:

instar measure --size 1G -O qcow2 -o cluster_size=4k

Predict size for converting a qcow2 image to raw:

instar measure -O raw mydisk.qcow2

JSON output for scripting:

instar measure -O qcow2 --output=json mydisk.vmdk

Predict an instar-only target (qemu-img cannot do this):

instar measure -O vhdx mydisk.qcow2

Differencing images

A differencing VHD (footer disk type 4) or VHDX (HasParent set) stores only the sectors that differ from a parent image. measure reads an image on its own rather than composing a parent chain into it, so it refuses such a source by name and exits 1 rather than reporting a required-size estimate that ignores the parent's data:

measure: source is a differencing <VHD|VHDX> image, and measure reads an
image on its own rather than composing a parent into it, so the sectors
it leaves to its parent could not be composed

instar info is the exception — it reports the parent as a backing file instead of refusing. convert, dd, compare, bench and rebase do compose a differencing source against its parent; see the "VHD/VHDX differencing" section of quirks.md for the per-operation record.

This refusal is deliberate, not merely unimplemented: measure reports on one image, and a differencing child's own blocks are almost always intact while the required size they describe only covers part of the image. Whether that should change anyway is tracked as #643, open and not settled either way.

📝 Report an issue with this page