Documentation
¶
Overview ¶
Package scale is the reproducible scale acceptance harness. It generates deterministic fixtures, builds the real pdfconcat executable, runs it on large plans, measures wall time, peak resident memory, open descriptors and scratch disk, and then checks the output with qpdf and Poppler.
Run it with
export PDFCONCAT_SCALE=1 PDFCONCAT_REQUIRE_QA_TOOLS=1 go run ./tools/qualitygate test -run ^TestScaleAcceptance$ -require TestScaleAcceptance -timeout 30m ./test/scale
Without PDFCONCAT_SCALE=1 the acceptance test skips, saying so. With it set, a missing prerequisite (the executable does not build, qpdf or Poppler is missing) fails the test, so a dedicated CI job cannot pass by skipping. PDFCONCAT_SCALE_RESULTS names a file that receives one JSON object per run.
Cases: 5,000 distinct one-page PDFs interleaved with 5,000 generated pages as a plan file and on standard input, with one repeated and with all-distinct blank styles; a 15,000-page job with repeated source occurrences, grouped paths, compact blank counts and linked sources; and a resource-heavy corpus of image-rich and multi-page PDFs; and 3,000 distinct generated pages with 10,000-character texts via file and stdin. Long-text cases verify complete extracted text and report their envelope, without claiming that their workload has the light-case budgets. The light cases are gated at 1 GiB peak resident memory, 64 open descriptors at --jobs 4 and 60 seconds; the other cases are measured and reported.
Measurement covers the executable from launch to exit. Fixture generation and the independent verification (qpdf, pdftotext, pdftoppm) are timed separately and excluded. Peak memory comes from the kernel's accounting of the child (rusage on Unix, the peak working set on Windows); descriptors are sampled while the process runs. Capped Unix cases additionally verify a 64/64 kernel limit on the same owned PID through a private native launcher. It records unprivileged identity, an actual refused hard-limit raise, executable identity and configured inheritance hygiene; the launched Go-runtime controls independently verify the limit and inherited-object closure. A native C compiler is required for capped Unix cases; source, compiler/SDK and executable identities are recorded, and helper compilation is excluded from measured wall time. The frozen executable/input directories must have no concurrent writer or privileged external limit mutation. The ceiling applies to the product process after successful exec, not aggregate descriptors in arbitrary descendants or an unconstrained production peak. Windows peak RSS is a sampled lower bound, while Unix RSS uses launch-through-exit kernel accounting. Each result records valid/unavailable/error states, successful/attempted descriptor samples and the sampled temporal span as a fraction of wall time; that fraction does not prove every transient maximum was observed. Enabled acceptance rejects missing or failed required readings in uncapped cases. Verified Unix capped cases keep a separate resource-bound result and observation quality: partial/error readings remain visible, and 64 is never inserted into the sampled maximum. At least one actual descriptor reading is still required to record descriptor cost; no successful readings means unavailable observation. Missing/contradictory cap evidence, failed product execution, sampler cleanup, RSS, scratch and independent-oracle failures still fail acceptance. Windows retains sampled native handle checks.
Index ¶
- func DirectorySize(dir string) (int64, error)
- func FileSize(path string) (int64, error)
- func WriteFile(path string, data []byte) error
- type Appearance
- type BlankStyles
- type DescriptorCeiling
- type DescriptorCollectorIdentity
- type Measurement
- type NativeCompilerIdentity
- type ObserverRetryEvidence
- type ReadingFailure
- type RunSpec
- type Verification
- type VisualSource
- type Workload
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func DirectorySize ¶
DirectorySize totals the regular files under dir, for input and output byte accounting.
Types ¶
type Appearance ¶
Appearance is a rendered check of one generated page: the pixel at (X, Y) must be Color, within a small tolerance, on output page Page (1-based).
type BlankStyles ¶
type BlankStyles int
BlankStyles says how the generated pages of a light workload are styled.
const ( SharedBlankStyle BlankStyles = 0 // DistinctBlankStyles gives every generated page of a light workload its own text. DistinctBlankStyles BlankStyles = 1 // MixedPages is the output size of MixedWorkload. MixedPages = 15000 )
type DescriptorCeiling ¶ added in v0.2.0
type DescriptorCeiling struct {
NativeCompilerIdentity
SourceSHA256 string `json:"source_sha256"`
LauncherSHA256 string `json:"launcher_sha256"`
BinarySHA256 string `json:"binary_sha256"`
InvocationSHA256 string `json:"invocation_sha256"`
Token string `json:"token"`
Hygiene string `json:"hygiene"`
PID int `json:"pid"`
UID int `json:"uid"`
EUID int `json:"euid"`
GID int `json:"gid"`
EGID int `json:"egid"`
Soft uint64 `json:"soft"`
Hard uint64 `json:"hard"`
Device uint64 `json:"device"`
Inode uint64 `json:"inode"`
Bytes int64 `json:"bytes"`
Mode uint64 `json:"mode"`
RaiseErrno int `json:"raise_errno"`
ControlCloseOnExec bool `json:"control_cloexec"`
HygieneConfigured bool `json:"hygiene_configured"`
}
DescriptorCeiling records native cap preparation; execution and sample quality are separate facts.
type DescriptorCollectorIdentity ¶ added in v0.2.0
type DescriptorCollectorIdentity struct {
NativeCompilerIdentity
ReadinessStarted time.Time `json:"readiness_started"`
ReadinessFinished time.Time `json:"readiness_finished"`
ReadinessFrame string `json:"readiness_frame"`
SourceSHA256 string `json:"source_sha256"`
BinarySHA256 string `json:"binary_sha256"`
ReadinessPID int `json:"readiness_pid"`
}
DescriptorCollectorIdentity identifies a compiled test-only descriptor query. Its observed counts are sampled lower bounds, independent of kernel ceilings.
type Measurement ¶
type Measurement struct {
DescriptorCollector *DescriptorCollectorIdentity `json:"descriptor_collector,omitempty"`
DescriptorCeiling *DescriptorCeiling `json:"descriptor_ceiling,omitempty"`
ObserverRetries ObserverRetryEvidence
Stdout string
Stderr string
// RSSState is valid, unavailable, or error; an error includes any failed intermediate RSS sample.
RSSState string
// DescriptorState is valid only when every attempted live-process sample succeeds.
DescriptorState string
ReadingFailures []ReadingFailure
Wall time.Duration
PeakRSSBytes int64
MaxDescriptors int64
// DescriptorSamples counts successful readings. SampleAttempts also includes failures.
TerminalSamples int64
DescriptorTerminalSamples int64
DescriptorSamples int64
SampleAttempts int64
// DescriptorCoverage is the observed temporal span divided by launch-to-exit wall time, not exhaustive coverage.
DescriptorCoverage float64
PeakScratchBytes int64
ExitCode int
}
Measurement is what one execution cost.
func Measure ¶
func Measure(ctx context.Context, spec RunSpec) (Measurement, error)
Measure runs the command and measures it. A nonzero exit status is reported in the Measurement, not as an error. Returned errors identify process, scratch-inspection or collector-cleanup failures.
func (*Measurement) DescriptorBoundVerified ¶ added in v0.2.0
func (m *Measurement) DescriptorBoundVerified() bool
DescriptorBoundVerified distinguishes the completed process's checked ceiling from sample quality. Its facts are produced by the owned native launch boundary, not by a requested RunSpec limit.
type NativeCompilerIdentity ¶ added in v0.2.0
type NativeCompilerIdentity struct {
Compiler string `json:"compiler"`
CompilerPath string `json:"compiler_path"`
CompilerSHA256 string `json:"compiler_sha256"`
SDK string `json:"sdk,omitempty"`
}
NativeCompilerIdentity identifies the compiler and selected SDK for a test-only native tool.
type ObserverRetryEvidence ¶
type ObserverRetryEvidence struct {
First time.Time `json:"first,omitzero"`
Last time.Time `json:"last,omitzero"`
Cause string `json:"cause,omitempty"`
Count int64 `json:"count"`
}
ObserverRetryEvidence records bounded evidence of interrupted native exit-observer calls.
type ReadingFailure ¶
type ReadingFailure struct {
ProofWaitStarted time.Time `json:"proof_wait_started,omitzero"`
ProofWaitFinished time.Time `json:"proof_wait_finished,omitzero"`
ReadStarted time.Time `json:"read_started"`
ReadFinished time.Time `json:"read_finished"`
Metric string `json:"metric"`
Phase string `json:"phase"`
Cause string `json:"cause"`
Stderr string `json:"stderr,omitempty"`
Stdout string `json:"stdout,omitempty"`
Probe string `json:"probe,omitempty"`
ExitCode int `json:"exit_code"`
NativeCode uint64 `json:"native_code,omitempty"`
}
ReadingFailure preserves the cause and phase of an unsuccessful process observation.
type RunSpec ¶
type RunSpec struct {
// Binary is the executable to run.
Binary string
// Dir is the working directory.
Dir string
// ScratchDir is a directory whose total file size is sampled while the process runs; the executable
// puts its private workspace beside the output, so this is the output directory.
ScratchDir string
// Stdin, when not nil, is the process's standard input.
Stdin io.Reader
// Env is the process environment; nil inherits this process's.
Env []string
// OnSample, when not nil, is called from the sampling goroutine each time a reading of the running
// process, and of ScratchDir, is complete. A caller that keeps the process alive until it has been
// read counts the calls instead of guessing how long a reading takes.
OnSample func()
// Args are the command-line arguments after the executable's name.
Args []string
// DescriptorLimit, when not zero, caps the process's open descriptors on Unix, where exceeding it
// makes its opens fail; Windows only samples handles.
DescriptorLimit uint64
}
RunSpec describes one measured process execution.
type Verification ¶
type Verification struct {
// Findings are the failed oracle checks and appearance mismatches; empty means the output is right.
Findings []string
// Elapsed is how long qpdf, pdftotext and pdftoppm took; it is excluded from the run's measurement.
Elapsed time.Duration
// Pages and Objects are the output's page and object counts as qpdf sees them.
Pages, Objects int
}
Verification is the independent judgement of one output file.
type VisualSource ¶
VisualSource binds an original source raster to its output occurrence.
type Workload ¶
type Workload struct {
// Name identifies the case in results.
Name string
// Dir is the directory holding the plan's sources; the executable runs with it as working directory.
Dir string
// Plan is the plan JSON.
Plan []byte
// Expectation is what the oracle must find in the output.
Expectation pdforacle.Expectation
// Appearances lists generated-page appearances to verify by rendering a sample page of each.
Appearances []Appearance
// VisualSources samples original image-bearing source pages in the assembled output.
VisualSources []VisualSource
// FullTexts verifies long text after whitespace normalization of independent extraction.
FullTexts map[int]string
// Pages is the expected number of output pages.
Pages int
// SourceFiles and InputBytes describe the input corpus, filled in by Write.
SourceFiles int
InputBytes int64
// contains filtered or unexported fields
}
Workload is one scale case: the plan the executable reads, the files it refers to, and everything an independent checker needs to judge the output without trusting the executable.
func HeavyWorkload ¶
HeavyWorkload is a resource-heavy corpus at lower counts: image-rich pages of incompressible data and multi-page documents, interleaved with generated pages.
func LightWorkload ¶
func LightWorkload(dir string, count int, styles BlankStyles) (*Workload, error)
LightWorkload is count distinct one-page sources interleaved with count generated pages. With DistinctBlankStyles every generated page has its own text; otherwise all share one text and style.
func LongTextWorkload ¶
LongTextWorkload is 3,000 distinct 10,000-character pages, a shaped-text lifetime stress case.
func MixedWorkload ¶
MixedWorkload is the 15,000-page case: repeated source occurrences (every source is used once per round, rounds visit the groups in opposite directions), leading and trailing blanks, grouped directories, compact blank counts with distinct text and backgrounds, and linked sources whose links must stay inside their own occurrence.
func (*Workload) FixtureSignature ¶
FixtureSignature is a digest-free fingerprint of the fixture set for determinism tests: the sorted relative paths with byte sizes.