blob: 3a43c04bdde257aea8145e9b71be2c8499760460 [file] [log] [blame] [view]
# Module: /
### High-Level Overview
Pinpoint is a service designed for automated performance and functional
bisection on Chromium. Its primary objective is to identify the specific commit
that introduced a regression by orchestrating a series of builds and tests
across a range of commits. The system manages complex, long-running analysis
jobs, integrating with external services like build systems, test schedulers,
and data stores to provide a fully automated workflow from job submission to
culprit identification.
### Project-Specific Terminology
- **Bisection:** An analysis workflow that iteratively narrows down a range of
commits to find a single culprit. It works by testing a commit in the middle
of the range, comparing its performance to a known-good baseline, and then
deciding which half of the range to continue searching in.
- **Pairwise:** An analysis workflow that runs tests on two specific commits
and statistically compares their performance results to determine if there
is a significant difference. This is often used to verify a potential
regression or confirm a culprit found by a bisection.
- **CombinedCommit:** A core data abstraction that represents a specific state
of the source code. It goes beyond a simple git hash by also including
potential modifications from dependency rolls (`DEPS` file changes) and
in-flight Gerrit patches. This allows Pinpoint to accurately reproduce and
test complex code states that are not represented by a single repository
commit.
- **Temporal:** A durable workflow orchestration system used as the backbone
of Pinpoint's execution engine. It allows Pinpoint to model long-running
bisection jobs as stateful, fault-tolerant processes, managing retries,
state persistence, and timeouts externally to the core application logic.
- **PGAdapter:** A proxy that translates the standard PostgreSQL wire protocol
into API calls for a different backend database, such as Google Cloud
Spanner. It enables applications written with standard PostgreSQL drivers to
connect to and interact with non-PostgreSQL databases without code changes.
- **Buganizer:** Google's internal issue tracking system. Pinpoint integrates
with Buganizer to automatically create, update, and comment on issues
related to performance regressions.
### Overall Architecture
Pinpoint is architected as a distributed system with a clear separation of
concerns between the user interface, the API gateway, the core workflow engine,
and its external integrations.
#### UI (`/ui`)
The user interface is a single-page application (SPA) built with LitElement and
TypeScript. Its design is centered on modularity and a predictable,
unidirectional data flow.
- **Why LitElement & Component-Based Structure:** The UI is composed of
reusable web components. The architecture uses a "shell and component"
model, where minimal HTML pages (`/pages`) act as entry points to load root
components. This delegates all rendering and logic to the components
themselves, promoting reusability and separation of concerns.
- **How it Manages Data Flow:** Components are divided into two types to
enforce a one-way data flow.
1. **Controller Components:** These are stateful components responsible for
a specific view (e.g., the job list or results page). They fetch data
from the backend, manage the view's state, and pass data down to child
components via properties.
2. **Presentational Components:** These are stateless components that are
only responsible for rendering data they receive as properties. They
communicate user interactions back to their parent controller by
dispatching custom events. This "data down, events up" pattern makes the
application's state management easier to reason about.
- **How it Communicates with the Backend:** All backend communication is
centralized in a single API service module (`/services/api.ts`). This
service exports `async` functions that encapsulate `fetch` calls and defines
TypeScript interfaces that mirror the backend's data structures. This
approach decouples UI components from the implementation details of API
requests and provides a type-safe contract between the frontend and backend.
#### Frontend Service (`/go/frontend`)
This Go service acts as the primary API gateway and web server for the Pinpoint
system. It is the component run by the `jobsserver` container.
- **Why it Exists:** It serves a dual purpose: it serves the static assets for
the SPA and provides the JSON API that the UI consumes. It also acts as a
bridge, forwarding requests for complex operations (like scheduling a new
job) to the core Pinpoint gRPC service.
- **How it Manages Configuration:** The service relies on a `benchmarks.json`
file as its single source of truth for all available benchmarks, the bots
that can run them, and associated test stories. On startup, this file is
parsed and its entire contents are loaded into an in-memory cache. This
design ensures that API requests for configuration data (e.g., to populate
UI dropdowns) are served with extremely low latency, as they do not require
disk or database access.
- **How it Connects to the Database:** The frontend requires a
PostgreSQL-compatible database to store and retrieve job metadata. It
achieves this by connecting through **PGAdapter**. This proxy allows the Go
service to use a standard PostgreSQL driver while the underlying data store
can be a different system, such as Google Cloud Spanner. This decouples the
service from a specific database technology, simplifying ddevelopment and
deployment.
#### Core Backend (`/go`)
The Go backend is the heart of Pinpoint, containing the business logic for
scheduling, executing, and analyzing bisection jobs.
- **API Layer (`/proto`, `/go/service`):** The public interface of the backend
is defined using Protocol Buffers (`service.proto`). This provides a
strongly-typed, language-agnostic API contract. The API is exposed via both
gRPC for high-performance internal communication and a RESTful JSON
interface (auto-generated via gRPC-Gateway) for consumption by the UI. The
API design favors explicit, job-specific RPCs (e.g., `ScheduleBisection`,
`SchedulePairwise`) to ensure type safety and clarity of intent.
- **Orchestration Layer (`/go/workflows`):** The core execution logic is
implemented as **Temporal workflows**.
- **Why Temporal:** Performance bisections are long-running, stateful
processes that can take hours and are susceptible to transient failures
in external systems. Using Temporal offloads the responsibility for
durability, state management, fault tolerance, and retries from the
application code. A Pinpoint job is modeled as a durable workflow
execution that can survive process restarts and continue from where it
left off.
- **How it Works:** Workflows define the high-level sequence of steps in a
job (e.g., find midpoint, build, test, compare, repeat). Each
individual, short-lived task (e.g., an API call to an external service)
is implemented as a Temporal "activity." A dedicated `worker` process
polls the Temporal cluster for tasks and executes this workflow and
activity logic.
- **Execution & Analysis Logic:** The workflows orchestrate calls to several
key modules that contain specialized business logic.
- `/go/midpoint`: This module is the bisection strategist. Its key
responsibility is to intelligently select the next commit to test. It
handles not only linear commit histories but also recursively bisects
through dependency (`DEPS`) changes, which is critical for finding
regressions caused by dependency rolls.
- `/go/compare`: This is the statistical analysis engine. After a test
run, this module receives two sets of performance measurements and
determines if they are statistically different. It uses a combination of
statistical tests (e.g., Mann-Whitney U) and a dual-threshold system to
classify the comparison as definitively `Same`, definitively
`Different`, or `Unknown`, triggering more test runs if necessary. This
automates the critical decision-making step in a bisection.
- `/go/backends`: This module centralizes all communication with external
infrastructure. It contains a collection of low-level clients that
abstract the APIs for services like Buildbucket (for compiling
Chromium), Swarming (for running tests), and Gitiles (for querying git
history). This design decouples the core workflow logic from the
implementation details of external systems.
- **Persistence (`/go/sql`):** While Temporal manages the state of _active_
workflows, a PostgreSQL-compatible SQL database provides long-term storage
for job history, metadata, and results. The `/go/sql` module defines the
database schema and provides a data access layer used by the `/go/frontend`
to serve job information via its API.
#### Buganizer Integration
Pinpoint integrates with Google's IssueTracker (Buganizer) to automate bug
management for performance regressions.
- **Why it's Needed:** This integration allows Pinpoint to automatically
create or comment on bugs when a regression is found, linking the analysis
directly to the engineering workflow and eliminating the need for manual
intervention.
- **How it Works:** The integration is facilitated by a dedicated service
account. This account is granted specific permissions to access internal
resources and secrets, such as an IssueTracker API key stored in Secret
Manager. To modify issues, the service account must be added as a
collaborator to the relevant Buganizer components. This service account is
then associated with the Temporal workers, ensuring that any actions
performed on Buganizer are done under this specific, audited identity.
### Core Workflows
#### A Performance Bisection Job
1. A user schedules a bisection job through the UI, which sends a request to
the Pinpoint Frontend service.
2. The Frontend forwards this request to the core gRPC Service (`/go/service`).
3. The gRPC service validates the request and initiates a `BisectWorkflow`
execution on the Temporal cluster.
4. A Pinpoint Worker process picks up the task and begins executing the
workflow logic.
5. **Inside the workflow loop:** a. An activity calls the `/go/midpoint` module
to determine the next commit to test. b. Activities then use clients from
`/go/backends` to request a build from Buildbucket and schedule tests on
Swarming for that commit. c. Once tests complete, another activity fetches
the performance metric values. d. These values are passed to an activity
that uses the `/go/compare` module to statistically determine if the
midpoint commit is better or worse than the baseline. e.g. Based on the
comparison verdict, the workflow narrows the commit range and the loop
continues.
6. Throughout the process, the worker executes activities that update the job's
status in the SQL database via the `/go/sql` layer. The Frontend service can
then query this database to display real-time progress in the UI.
7. Once the range is narrowed to a single commit, the workflow completes,
marking the commit as the culprit.
#### Culprit Finder Workflow
The `ScheduleCulpritFinder` RPC initiates a composite workflow designed for
high-confidence culprit identification. It proceeds in three distinct stages:
1. **Regression Verification:** It first runs a `Pairwise` job between the
provided start and end commits to statistically confirm that a regression
actually exists within the range. If no regression is found, the workflow
ends early.
2. **Bisection:** If a regression is confirmed, it proceeds to run a standard
`Bisection` job on the commit range to identify one or more potential
culprit commits.
3. **Culprit Verification:** For each potential culprit found by the bisection,
it runs a final `Pairwise` job comparing the culprit against its immediate
parent commit. This step verifies that the specific commit is, by itself,
the source of the regression. The workflow only reports culprits that pass
this final verification step.
# Module: /docs
The `/docs` module serves as a repository for operational and integration
documentation relevant to various services within the project, particularly
those supporting the Pinpoint ecosystem. Its primary goal is to provide guidance
on how services are configured, interact with external platforms, and manage
their data and workflows.
## Pinpoint Frontend
The Pinpoint Frontend service (`/go/frontend`) is the primary user interface and
API gateway for the Pinpoint performance regression analysis system. Its core
purpose is to provide users with a comprehensive view of Pinpoint jobs, enable
them to initiate and manage these jobs, and access crucial configuration data
necessary for defining performance experiments. It acts as the critical bridge
between user requests and the underlying Pinpoint services, databases, and
workflow orchestration.
The service's responsibilities include:
- Providing HTTP endpoints for listing and viewing Pinpoint jobs.
- Serving the Pinpoint UI (landing and results pages).
- Providing configuration data such as available benchmarks, bots, and
stories.
- Forwarding relevant requests to an underlying Pinpoint gRPC service for core
functionalities like scheduling new jobs.
### Key Components and Design Decisions
The Pinpoint Frontend service is designed with several key architectural
components to achieve its goals:
- **User Interface & API Endpoints:** The service serves both a web-based user
interface and a set of RESTful JSON API endpoints. These endpoints manage
job lifecycle (listing, viewing), and provide dynamic configuration data
(e.g., `/benchmarks`, `/bots`, `/stories`). It also acts as a proxy,
forwarding requests to the core Pinpoint gRPC service for operations like
job scheduling.
- **Database Integration with PGAdapter:** The Pinpoint Frontend is a Go
application that requires a PostgreSQL-compatible database connection.
However, it does not directly connect to a PostgreSQL database. Instead, it
leverages **PGAdapter**.
- **Why PGAdapter:** PGAdapter acts as an intelligent proxy, translating
standard PostgreSQL wire protocol requests into calls for a specific
backend database, such as Google Cloud Spanner. This design choice
decouples the frontend service from direct database-specific client
libraries. It allows the Go service to interact with the database as if
it were a standard PostgreSQL instance, significantly simplifying
development, testing, and deployment by using familiar database
patterns.
- **How it Works:** PGAdapter runs as a separate Java process, typically
on the same host or network location as the frontend. It requires
specific JAR files and configuration (e.g., instance ID, database name,
authentication credentials) to establish and maintain the connection
between the frontend and the underlying datastore.
- **Workflow Orchestration with Temporal:** For complex, long-running
operations such as initiating Pairwise bisect jobs or canceling ongoing
ones, the frontend delegates to a **Temporal workflow system**.
- **Why Temporal:** Temporal provides robust and durable workflow
orchestration. This ensures that complex jobs are executed reliably,
automatically retried on failures, and their state is consistently
maintained across potentially long durations. The frontend itself does
not directly perform these complex, stateful operations; instead, it
sends signals or initiates workflows on the Temporal cluster.
- **How it Works:** The Pinpoint gRPC service (which the frontend forwards
requests to) interacts with a Temporal client. This client communicates
with a Temporal cluster, which then dispatches tasks to dedicated
**Temporal workers**. These workers are responsible for executing the
actual job logic (e.g., fetching data, running bisects). Workers listen
on specific namespaces and task queues (e.g., `perf-internal`,
`perf.perf-chrome-public.bisect`).
- **Configuration Management via `benchmarks.json`:** The service relies on
`benchmarks.json` as its single source of truth for critical Pinpoint
operational parameters.
- **Why `benchmarks.json`:** This file centrally defines all available
performance benchmarks, the bots capable of running them, and the
specific test stories associated with each benchmark. This centralized
configuration ensures consistency across the system, provides dynamic
data for UI elements (like dropdowns), and enables the API endpoints to
serve up-to-date configuration.
- **How it Works:** Upon service startup, `benchmarks.json` is parsed and
its contents are loaded entirely into an in-memory cache. This approach
allows for extremely fast access to configuration data, as subsequent
requests to the `/benchmarks`, `/bots`, and `/stories` API endpoints can
retrieve information directly from memory without incurring repeated
disk I/O, ensuring high performance for frequently queried data.
### Configuration Workflow with `benchmarks.json`
```mermaid
graph TD
A[Service Startup] --> B[Read benchmarks.json from disk]
B --> C[Parse JSON & Load into In-Memory Cache]
C --> D{User/Client sends:}; D -- GET /benchmarks -->
E[Service retrieves data from In-Memory Cache]
D -- GET /bots?benchmark=<name> --> E
D -- GET /stories?benchmark=<name> --> E
E --> F[Respond to Client with configuration data]
```
### Pinpoint Job Initiation Workflow with Temporal
```mermaid
graph TD
A[User initiates new Pinpoint job via Frontend UI] --> B{HTTP Request to /pinpoint/new}
B --> C[Pinpoint Frontend Service]
C --> D{Internal call to Pinpoint gRPC Service}
D --> E[Pinpoint gRPC Service]
E --> F{Calls Temporal Client to start Workflow}
F --> G[Temporal Cluster]
G -- Schedules tasks on appropriate queue --> H[Temporal Worker]
H --> I[Updates database & Notifies Frontend of job status changes]
```
## Buganizer Integration
Automated systems, including Pinpoint, require the ability to interact with
Google's IssueTracker (**Buganizer**) for managing issues. This integration is
crucial for workflows that automatically create, update, or comment on bugs in
response to detected performance regressions or other significant events,
ensuring efficient tracking without manual intervention.
The integration is primarily facilitated through a dedicated **service account**
that acts on behalf of the automated system. This service account requires
specific, carefully managed permissions to securely access internal resources
and interact with Buganizer.
- **Service Account Creation:** A new service account is provisioned with a
clear and descriptive name, avoiding misleading terms. This is typically
managed through infrastructure-as-code changes.
- **Permission Grants:** The service account is granted several critical
roles:
- Read access to CAS (Credential Access System) for retrieving necessary
secrets.
- The `roles/cloudtrace.agent` for distributed tracing and observability.
- Read access to internal code repositories, often involving a separate
approval process.
- **IssueTracker API Key Access:** To interact with the IssueTracker API, the
service account needs to read the IssueTracker secret key, which is stored
in Secret Manager. This is a sensitive operation requiring an administrator
to temporarily elevate their own permissions using **breakglass** to grant
the service account the "Secret Manager Secret Accessor" role specifically
for the IssueTracker key. This ensures tight control over access to
sensitive credentials.
- **Ticket Collaboration:** For the service account to be able to modify
tickets (e.g., add comments, change status), it must either be added as a
**collaborator** on the relevant Buganizer components or be granted the
"issue editor" role by a Component Admin. This provides the automated system
with the necessary authority to update issues as required by workflow logic.
- **Deployment:** The configured service account is then associated with the
Pinpoint workers, specifically for Temporal workflows, ensuring these
workers operate under the appropriate identity when initiating or updating
Buganizer issues.
# Module: /go
This module contains the complete Go-based backend for Pinpoint, a service for
performance and functional bisection on Chromium. It orchestrates complex,
long-running analysis jobs by integrating with various external services like
build systems, test schedulers, and data stores.
### Core Principles
- **Durable Orchestration with Temporal**: The system's core logic is
implemented as Temporal workflows. This design choice provides fault
tolerance, state persistence, and observability for long-running bisection
jobs, offloading complex state management and retry logic from the
application code.
- **Separation of Concerns**: The architecture distinctly separates the public
API, the workflow orchestration logic, and low-level clients for external
services. This modularity simplifies maintenance and testing.
- **Configuration as Code**: Bot and test configurations are embedded directly
into the binary. This simplifies deployment and guarantees that the
application and its configuration are always in sync.
### Key Modules and Responsibilities
- **/go/service**: The public API layer. It exposes a gRPC service (and a JSON
equivalent via grpc-gateway) for clients to schedule, query, and cancel
jobs. Its primary responsibility is to validate incoming requests and
translate them into Temporal workflow executions, acting as the main entry
point into the system.
- **/go/workflows**: The heart of Pinpoint's execution engine. This module
defines the Temporal workflows and activities that implement the business
logic for bisection, pairwise testing, and culprit finding.
- **Why**: It models a Pinpoint job as a durable, stateful process. A
`BisectWorkflow`, for example, orchestrates the entire process of
finding a midpoint, building, testing, and comparing results, with
Temporal ensuring the process can survive failures and continue where it
left off.
- **How**: Workflows define the high-level steps, while activities execute
the short-lived, potentially failing tasks like making an API call to an
external service. A dedicated `worker` process executes this logic.
- **/go/backends**: A collection of low-level clients for all external
services.
- **Why**: To centralize and abstract interactions with external
infrastructure like Buildbucket (for builds), Swarming (for tests),
Gitiles (for git history), and BigQuery (for data storage). This
decouples the core workflow logic from the implementation details of
external APIs.
- **How**: Each submodule provides a dedicated client (e.g.,
`buildbucket.go`, `swarming.go`) that handles authentication, request
formation, and response parsing for a specific service.
- **/go/midpoint**: The bisection strategist.
- **Why**: To intelligently select the next commit to test. The core
challenge is handling not just linear commit history but also changes
rolled in through dependencies (DEPS).
- **How**: When bisecting a commit range, it finds the median commit. If
the range is already narrowed to two adjacent commits, it parses their
`DEPS` files, identifies the first modified dependency, and recursively
continues the bisection within that dependency's repository.
- **/go/compare**: The statistical analysis engine.
- **Why**: To provide automated, data-driven decisions on whether a change
introduced a regression. This is the critical decision point in each
bisection step.
- **How**: It uses a combination of statistical tests (Kolmogorov-Smirnov,
Mann-Whitney U) and a dual-threshold system. This allows it to quickly
classify two sets of measurements as definitively `Same`, definitively
`Different`, or `Unknown` (requiring more test runs), optimizing the
speed and accuracy of the bisection.
- **/go/bot_configs**: The central configuration provider.
- **Why**: To manage the vast and complex configurations for different
bots, benchmarks, and tests without hardcoding them in the business
logic.
- **How**: It loads configuration from embedded JSON and YAML files. It
provides lookup functions that resolve the correct build parameters and
test targets for any given bot and benchmark combination, including
handling aliases and regex-based matching.
- **/go/sql** and **/go/frontend**: The persistence and presentation layers.
- **Why**: To store and display job metadata. While Temporal manages
active workflow state, the SQL database provides long-term storage for
job history and results.
- **How**: `/go/sql` defines the database schema using Go structs and
provides a `jobs_store` for data access. `/go/frontend` is a lightweight
web service that queries this store to provide a simple UI and JSON API
for viewing job status and results.
### Core Workflow: A Performance Bisection Job
The following diagram illustrates how the key modules interact to execute a
typical bisection job.
```mermaid
graph TD
A["Client"] -- "1. ScheduleBisect" --> B["gRPC Service (/go/service)"]
B -- "2. Starts Workflow" --> C["Temporal Server"]
C -- "3. Dispatches Task" --> D["Pinpoint Worker (/go/workflows/worker)"]
subgraph "Pinpoint Worker Logic"
subgraph "Bisection Workflow"
D -- Starts --> E{"Loop: While range > 1"}
E -- "4. Find Midpoint" --> F["Activity: Midpoint Logic (/go/midpoint)"]
F --> G["Activity: Build & Test"]
G -- "5. Trigger Build/Test" --> H["Clients (/go/backends)"]
H --> I["Buildbucket & Swarming"]
I -- "6. Results (CAS digests)" --> H
H --> G
G -- "7. Values" --> J["Activity: Read & Compare"]
J -- "Fetches data from CAS" --> H
J -- "8. Compares samples" --> K["Statistical Logic (/go/compare)"]
K -- "9. Verdict (Same/Different)" --> J
J -- "Narrows commit range" --> E
end
subgraph "Job Status Persistence (Parallel)"
D -- "Updates Job Status" --> M["Activity: Update Datastore"]
M --> N["Job Store (/go/sql)"]
O["Frontend (/go/frontend)"] -- "Reads from" --> N
end
end
E -- "Culprit Found" --> L["Workflow Complete"]
L -- "10. Returns Result" --> C
C -- "11. Result available to" --> A
```
# Module: /go/backends
This module consolidates clients and logic for interacting with various external
services critical to Pinpoint's bisection workflow. It provides a standardized
and abstracted layer for tasks such as triggering builds, managing test
execution, fetching commit information, storing data, and reporting results,
minimizing direct external API dependencies across the Pinpoint codebase.
### Key Components and Responsibilities
- **`bigquery.go`**: Manages interactions with Google BigQuery. It abstracts
data ingestion and table management, enabling Pinpoint to store bisection
results, telemetry, or other structured data efficiently.
- **`buildbucket.go`**: Provides the primary interface for Buildbucket, the
system for building Chrome. Its responsibilities include:
- **Triggering and Managing Builds**: Scheduling new Chrome builds for
specific commits, with optional DEPS overrides or Gerrit patches, and
cancelling ongoing builds.
- **Build Status and Artifacts**: Querying build status and retrieving CAS
(Content Addressable Storage) references for build outputs. This is
crucial for passing artifacts to Swarming for testing.
- **Build Reuse**: Intelligently searching for and identifying existing
builds (Pinpoint `try` builds or CI `waterfall` builds) that match
specific criteria (commit, DEPS, patches) to avoid redundant work. This
is achieved via `GetSingleBuild`, `GetBuildWithDeps`,
`GetBuildWithPatches`, and `GetBuildFromWaterfall`, leveraging the
`PinpointWaterfall` mapping (defined in `waterfall_map.go`).
- **`crrev.go`**: Facilitates conversion between various Chromium commit
identifiers (e.g., commit positions, Git hashes) and canonical Git hashes,
ensuring consistent commit references across different backend systems.
- **`gitiles.go`**: Offers a client for Gitiles, primarily used to access Git
repository information (e.g., `chromium/src`). It provides an authenticated
interface for fetching repository details and commit history, essential for
bisection range determination.
- **`issuetracker.go`**: Integrates with Google IssueTracker (Buganizer). Its
core responsibility is to automate the reporting of bisection results (e.g.,
identified culprits) as comments on bug reports. It uses embedded templates
(`culprit_detected.tmpl`) for structured, informative messages.
- **`swarming.go`**: Manages interactions with Swarming, the distributed task
execution system for running tests. Key functions include:
- **Task Triggering**: Scheduling Swarming tasks, often using CAS
references from Buildbucket builds to execute performance benchmarks or
other tests.
- **Task Monitoring**: Retrieving task status, start times, and CAS output
references (for results or subsequent tasks).
- **Bot Management**: Querying available bots based on specific builder
configurations to ensure tasks run on appropriate hardware.
- **Task Interruption Detection**: Identifying if other tasks ran on a bot
between two specific bisection tasks (`GetBotTasksBetweenTwoTasks`) to
detect potential interference.
### Core Workflow (Build-and-Test Bisection Example)
```mermaid
graph TD
A["Pinpoint Bisection Request"] --> B{"Commit Range, Builder, Test"}
B --> C["Crrev: Resolve Commit Hashes"]
C --> D{"For each candidate commit in range"}
D --> E["Buildbucket: Search for Existing Build (incl. Waterfall)"]
E --> F{"Build Found?"}
F -- Yes --> I["Get CAS Reference"]
F -- No --> G["Buildbucket: Start New Build"]
G --> H{"Build Completed?"}
H -- No --> H
H -- Yes --> I["Get CAS Reference"]
I --> J["Swarming: Trigger Test Task with CAS"]
J --> K{"Swarming Task Completed?"}
K -- No --> K
K -- Yes --> L["Swarming: Get Task Results"]
L --> M["Analyze Test Results"]
M --> N{"Bisection Complete?"}
N -- No --> D
N -- Yes --> O["Identify Culprit(s)"]
O --> P["IssueTracker: Report Culprit(s)"]
O --> Q["BigQuery: Log Bisection Data"]
```
# Module: /go/backends/mocks
`go/backends/mocks`
This module provides generated mock clients for Pinpoint's external backend
services. It facilitates unit and integration testing by decoupling tested
components from actual external dependencies.
Mocks are generated from interfaces defined in
`go.skia.org/infra/pinpoint/go/backends` using the `mockery` tool. This approach
ensures mock consistency with interface contracts and enables robust, isolated
testing without live service reliance or API call overhead.
Each file, such as `BigQueryClient.go` or `BuildbucketClient.go`, contains a
mock implementation for a specific backend client interface. These mocks
leverage `github.com/stretchr/testify/mock` to define expected method calls,
return values, and side effects, simplifying test setup and enabling precise
control over simulated external service behavior.
# Module: /go/bot_configs
This module centralizes and manages configuration data for Pinpoint bots,
dictating how Chrome builds are specified in LUCI and how performance tests are
executed on Swarming. It also maps bots and benchmarks to specific build targets
(isolates).
### Bot Configurations
Bot configurations define parameters for building Chrome and running tests.
- **`bot_configs.go`**: Defines the `BotConfig` structure and provides the
primary interface (`GetBotConfig`) for retrieving bot configurations. It
also includes validation logic to ensure data integrity.
- **Separation of `external.json` and `internal.json`**: Public
configurations (`external.json`) are separated from internal or
experimental ones (`internal.json`). `internal.json` augments or
overrides `external.json` entries.
- **Embedded JSON**: Configurations are embedded directly into the Go
binary (`_embed` directive), ensuring runtime availability and
simplifying deployment.
- **Lazy Loading**: Configurations are unmarshaled from JSON only once
(`sync.Once`) upon first access, optimizing performance.
- **Aliases**: The `BotConfig.Alias` field allows bots with identical
configurations to refer to a common alias, reducing duplication.
`GetBotConfig` transparently resolves these aliases.
- **Validation**: The `validate()` function enforces data integrity,
checking for duplicate bot definitions, nested aliases, and completeness
of configuration fields.
### Isolate Targets
Isolate targets specify the executable artifact to run for performance tests on
Swarming.
- **`isolate_targets.go`**: Maps bot names and benchmark names to specific
Swarming isolate targets.
- **Embedded YAML**: Isolate target mappings are embedded from
`isolate_targets.yaml` into the binary.
- **Tiered Lookup**: `GetIsolateTarget` resolves targets using a priority
order:
* Benchmark-specific overrides.
* Exact bot name matches.
* Regex pattern matches within the bot name.
* A default `performance_test_suite` if no other match is found. This
hierarchy provides flexible control over target selection.
### Key Workflow: Retrieving a Bot Configuration
```mermaid
graph TD
A["GetBotConfig(bot, externalOnly)"] --> B{externalOnly?}
B -- Yes --> C[Load external.json]
B -- No --> D[Load external.json + internal.json]
C --> E[Check 'bot' in loaded configs]
D --> E
E -- Not Found --> F[Return error]
E -- Found --> G{BotConfig has Alias?}
G -- Yes --> H[Resolve Alias to final BotConfig]
G -- No --> I[Return BotConfig]
H --> I
```
### Key Workflow: Retrieving an Isolate Target
```mermaid
graph TD
A["GetIsolateTarget(bot, benchmark)"] --> B["Validate 'bot' exists in bot configs"]
B -- Error --> C["Return error"]
B -- OK --> D["Load isolate_targets.yaml"]
D --> E{"BenchmarkTargets[benchmark] exists?"}
E -- Yes --> F["Return Benchmark Target"]
E -- No --> G{"BotExactTargets[bot] exists?"}
G -- Yes --> H["Return Exact Bot Target"]
G -- No --> I{"BotRegexTargets matches bot?"}
I -- Yes --> J["Return Regex Match Target"]
I -- No --> K["Return default performance_test_suite"]
```
# Module: /go/cbb
The `/go/cbb` module provides essential utilities for the Continuous Browser
Benchmarking (CBB) infrastructure. It automates the distribution of Safari
Technology Preview (STP) releases for CBB devices and facilitates the ingestion
of historical CBB v3 performance data into the modern CBB v4 system (Skia Perf
dashboard).
### `download_stp`
This utility ensures CBB devices always have access to the latest Safari
Technology Preview (STP) releases for benchmarking. Apple does not provide a
direct API for STP downloads. Therefore, `download_stp` parses Apple's Safari
resources page to dynamically discover the latest STP builds and extract their
`.dmg` installer URLs.
Downloaded `.dmg` installers are then packaged and uploaded to CIPD
(`infra/chromeperf/cbb/safari_technology_preview`) for reliable and centralized
distribution across the CBB infrastructure. Packages are tagged with specific
CIPD references (e.g., `stable`, `canary`, `latest`,
`[release]-macos[version]`), enabling controlled rollouts and precise version
targeting for CBB devices. The process is idempotent, checking for existing
packages and references before performing redundant uploads.
```mermaid
graph TD
A["Start"] --> B{"Fetch & Parse Safari Resources Page"}
B --> C{"Extract Latest Release Number & Download Links"}
C --> D{"Is STP [Release]-macos15 in CIPD?"}
D -- Yes --> E["Exit"]
D -- No --> F["Download STP .dmg (Sequoia)"]
F --> G["Create CIPD Package (Sequoia)"]
G --> H["Apply Refs: stable, canary, latest, Release-macos15"]
H --> I["Download STP .dmg (Tahoe)"]
I --> J["Create CIPD Package (Tahoe)"]
J --> K["Apply Ref: Release-macos26"]
K --> L["End"]
```
### `upload_v3_data`
This utility backfills historical CBB v3 performance metrics, sourced from a
specific Google3 CSV export, into the Skia Perf dashboard. This enables modern
CBB v4 analyses to include a comprehensive historical context from legacy data.
The conversion process, orchestrated by `main.go`, consumes a single
`cbb_v3_data.csv` file and produces multiple Skia Perf JSON files. Key
responsibilities include:
- **Data Ingestion and Normalization:** Reads the CSV and normalizes
inconsistent Chrome version strings (e.g., `137.0.7151.070` to
`137.0.7151.70`) to conform to Skia Perf's expected formats.
- **Commit Position Mapping:** A critical step that translates CBB v3 browser
versions (Chrome, Safari, Edge across channels and devices) into equivalent
Skia Perf "commit positions." This mapping, leveraging hardcoded
configurations for known versions and a heuristic fallback for others, is
essential for correctly plotting historical data along the Skia Perf x-axis.
- **Skia Perf Format Generation:** Transforms CBB v3 data points into
individual Skia Perf `format.Format` JSON structures. This involves mapping
CBB v3 identifiers (device, browser, benchmark) to Skia Perf bot, benchmark,
and test trace keys, and explicitly marking the data as "Legacy Data."
- **Output Management:** Writes the generated JSON files, structured for
subsequent bulk upload to the `chrome-perf-non-public` GCS bucket, to
`~/cbb`.
```mermaid
graph TD
subgraph Google3 Environment
A[Original CBB v3 Data] --> B(Blaze tool: download_f1);
B -- Exports as --> C(cbb_v3_data.csv in ~/cbb);
end
subgraph Skia Infra Toolchain
C --> D(upload_v3_data tool);
D -- Reads, Normalizes & Maps --> E{Browser Version to Commit Position};
E -- Transforms to --> F(Skia Perf JSON Format);
F -- Generates multiple --> G(Output .json files in ~/cbb);
end
subgraph Skia Perf System
G --> H(gsutil: Upload to GCS bucket);
H -- Ingested by --> I(Skia Perf Dashboard);
I -- Visualizes legacy data as --> J(CBB v4 Historical Traces);
end
```
# Module: /go/cbb/download_stp
This module automates the distribution of Safari Technology Preview (STP)
releases for Continuous Browser Benchmarking (CBB). It ensures CBB devices
always have access to the latest STP versions by packaging them into CIPD.
**Design and Implementation:**
- **STP Discovery:** Apple does not provide a direct API for STP downloads.
The tool parses the
[Safari Technology Preview resources page](https://developer.apple.com/safari/resources/)
to extract the latest release number and download URLs for various macOS
versions. This approach is necessary to dynamically discover new STP builds.
- **CIPD Packaging:** STP `.dmg` installers are downloaded and uploaded as
CIPD packages (`infra/chromeperf/cbb/safari_technology_preview`). CIPD is
used for reliable and centralized distribution of artifacts across the
infrastructure.
- **Reference Management:** Packages are tagged with specific CIPD references
(e.g., `stable`, `canary`, `latest`, `[release]-macos[version]`). These
references allow CBB devices to consume specific STP versions, enabling
controlled rollouts and easy version targeting.
- **Idempotency:** Before creating new CIPD packages, the tool checks if the
current STP release (identified by its version and macOS) already exists in
CIPD with the necessary references. This prevents redundant uploads and
ensures efficiency.
**Key Workflow:**
```mermaid
graph TD
A["Start"] --> B{"Fetch & Parse Safari Resources Page"}
B --> C{"Extract Latest Release Number & Download Links"}
C --> D{"Is STP [Release]-macos15 in CIPD?"}
D -- Yes --> E["Exit"]
D -- No --> F["Download STP .dmg (Sequoia)"]
F --> G["Create CIPD Package (Sequoia)"]
G --> H["Apply Refs: stable, canary, latest, Release-macos15"]
H --> I["Download STP .dmg (Tahoe)"]
I --> J["Create CIPD Package (Tahoe)"]
J --> K["Apply Ref: Release-macos26"]
K --> L["End"]
```
# Module: /go/cbb/upload_v3_data
Converts legacy Cross-Browser Benchmarking (CBB) v3 data, sourced from a
specific Google3 CSV export, into the Skia Perf dashboard's JSON ingestion
format. This enables backfilling historical CBB v3 performance metrics into the
modern CBB v4 system.
### Design and Implementation
The utility operates as a one-shot conversion process, consuming a single CSV
file and producing multiple Skia Perf JSON files.
### Key Components and Responsibilities
- **`main.go`**: Orchestrates the entire conversion.
- **Data Ingestion and Normalization**: Reads `cbb_v3_data.csv` from the
user's `~/cbb` directory. It normalizes inconsistent Chrome version
strings (e.g., `137.0.7151.070` to `137.0.7151.70`) to match standard
formats expected by Skia Perf.
- **Commit Position Mapping**: Translates CBB v3 browser versions (Chrome,
Safari, Edge across stable/dev channels and devices) into equivalent
Skia Perf "commit positions." This mapping is crucial for correctly
plotting historical data on the Skia Perf x-axis. A set of hardcoded
maps (`chromeStableCP`, `safariTPCP`, etc.) provides precise mappings
for known versions. A heuristic fallback calculates approximate commit
positions for Chrome versions not explicitly listed, catering to the
historical nature of the data.
- **Skia Perf Format Generation**: Transforms raw CBB v3 data points into
individual Skia Perf `format.Format` JSON structures. This includes
mapping CBB v3 device, browser, and benchmark names to their
corresponding Skia Perf bot, benchmark, and test trace keys, and
explicitly marking them as "Legacy Data."
- **Output Management**: Writes the generated JSON files to `~/cbb`,
structuring them for subsequent bulk upload to the
`chrome-perf-non-public` GCS bucket.
```mermaid
graph TD
subgraph Google3 Environment
A[Original CBB v3 Data] --> B(Blaze tool: download_f1);
B -- Exports as --> C(cbb_v3_data.csv in ~/cbb);
end
subgraph Skia Infra Toolchain
C --> D(upload_v3_data tool);
D -- Reads, Normalizes & Maps --> E{Browser Version to Commit Position};
E -- Transforms to --> F(Skia Perf JSON Format);
F -- Generates multiple --> G(Output .json files in ~/cbb);
end
subgraph Skia Perf System
G --> H(gsutil: Upload to GCS bucket);
H -- Ingested by --> I(Skia Perf Dashboard);
I -- Visualizes legacy data as --> J(CBB v4 Historical Traces);
end
```
# Module: /go/clients
The `/go/clients` module provides abstracted interfaces for interacting with
external services essential to Pinpoint, such as build systems and data storage.
It centralizes and simplifies communication, ensuring Pinpoint's core logic
remains decoupled from specific service APIs and configurations.
### Design Principles and Implementation Choices
1. **Service Abstraction**:
- **Why**: To decouple Pinpoint's business logic from the specifics of
underlying external services. This allows for easier maintenance,
potential future integration of alternative services, and a consistent
API for Pinpoint workflows.
- **How**: Generic Go interfaces (e.g., `BuildClient`, `UploadClient`)
define common operations for distinct service types.
2. **Project-Specific Implementations**:
- **Why**: External services often have unique requirements,
configurations, or operational heuristics specific to a project (e.g.,
Chrome/Chromium). Abstracting these details allows the generic interface
to remain clean.
- **How**: Concrete client implementations (e.g., `buildChromeClient`,
`uploadChromeDataClient`) translate generic Pinpoint requests into
service-specific API calls and data structures, including complex
project-specific logic or heuristics where necessary.
3. **Delegation to Backend Clients**:
- **Why**: To separate the logic of _what_ operations to perform and _how_
to map parameters (handled by `clients`) from the low-level mechanics of
making authenticated API requests and managing data
serialization/deserialization.
- **How**: `clients` implementations embed and utilize clients from the
`go/backends` module, which are responsible for direct, authenticated
interactions with service APIs (e.g., Buildbucket, BigQuery).
### Key Components
- **`build` submodule**: Offers the `BuildClient` interface for managing
Chrome/Chromium builds. The `buildChromeClient` implementation encapsulates
the detailed logic for interacting with Buildbucket, including builder
resolution, constructing request properties, and specific build search
heuristics (e.g., differentiating "try" vs. "waterfall" builds).
- **`upload` submodule**: Provides the `UploadClient` interface for ingesting
data into storage systems. The `uploadChromeDataClient` implementation
handles operations for Google BigQuery, including deriving table schemas
from Go structs and inserting data rows.
# Module: /go/clients/build
The `/go/clients/build` module provides an abstraction layer for interacting
with build systems, primarily Buildbucket, to manage Chrome/Chromium builds
within Pinpoint. It enables other Pinpoint components to perform build-related
operations (find, start, get status, retrieve artifacts, cancel) without direct
knowledge of the underlying build system's API or project-specific
configurations.
### Design Principles and Implementation Choices
1. **Build System Agnostic Interface (`build_client.go`)**:
- **Why**: To decouple Pinpoint's core logic from specific build platform
details (e.g., Buildbucket, other potential future systems). A unified
interface simplifies interaction and allows for future expansion.
- **How**: The `BuildClient` interface defines generic operations. A
factory function (`NewBuildClient`) provides authenticated client
instances; currently, it exclusively returns a Chrome-specific client
but is designed for future multi-project support.
2. **Chrome/Chromium Specific Implementation (`build_chrome.go`)**:
- **Why**: Chromium builds have unique requirements: specific builders,
buckets (`try`, `ci`), custom request properties (e.g., `git_repo`,
`deps_revision_overrides`), and nuanced logic for finding existing
builds (e.g., handling patches or DEPS rolls differently for "try" vs.
"waterfall" builds).
- **How**: `buildChromeClient` encapsulates this logic. It maps Pinpoint's
generic `workflows.BuildParams` to Buildbucket-specific requests,
leveraging `bot_configs` for device-to-builder translation. Its
`FindBuild` method implements a precise search strategy: first for
Pinpoint-initiated "try" builds, then conditionally falling back to
"waterfall" (CI) builds if no patches or DEPS rolls are present.
3. **Generic Request/Response Wrappers (`types.go`)**:
- **Why**: To maintain the `BuildClient` interface's independence from
concrete Buildbucket protobufs or other API-specific data structures.
- **How**: Simple structs like `FindBuildRequest` and `StartBuildRequest`
use `any` to hold the actual, system-specific request/response payloads.
Concrete client implementations then perform type assertions.
4. **Delegation to Backend Client**:
- **Why**: To separate the module's logic (mapping Pinpoint concepts to
build system operations) from the low-level concerns of making API
calls.
- **How**: `buildChromeClient` embeds `backends.BuildbucketClient` to
handle the actual HTTP requests and protobuf serialization for
Buildbucket.
### Key Components
- **`build_client.go`**: Defines the `BuildClient` interface, which is the
primary API contract for build operations. It also contains `NewBuildClient`
for creating authenticated instances.
- **`build_chrome.go`**: Provides the concrete `buildChromeClient`
implementation for Chrome/Chromium projects. This file contains the detailed
logic for constructing Buildbucket requests, translating Pinpoint
parameters, and implementing the specific build search heuristics.
- **`types.go`**: Contains lightweight, generic request and response structs
that wrap system-specific payloads, enabling the `BuildClient` interface to
remain generic.
### Build Request Workflow
```mermaid
graph TD
A["Pinpoint Workflow needs a build"] --> B{"Call BuildClient.FindBuild or
StartBuild"}
subgraph "Find Build"
B --> C{"build_chrome.go: Create ChromeFindBuildRequest"}
C --> D{"Resolve Device to Buildbucket Builder"}
D --> E{"Attempt BuildbucketClient.GetSingleBuild
<br>(Pinpoint-tracked builds)"}
E -- "No Build & No Patches/DEPS" -->
F{"Attempt BuildbucketClient.GetBuildFromWaterfall<br>(CI builds)"}
E -- "Build Found or Waterfall Found" --> G["Return Build Info"]
F -- "No Build Found" --> H["Return No Build Found"]
E -- "No Build & Has Patches/DEPS" --> H
end
subgraph "Start Build"
B --> I{"build_chrome.go: Create Buildbucket ScheduleBuildRequest"}
I --> J{"Inject Chrome-specific Properties<br>(e.g., git_repo, revision,
deps_overrides)"}
J --> K{"Inject Pinpoint Tags"}
K --> L{"Call BuildbucketClient.StartBuild"}
L --> M["Return New Build Info"]
end
```
# Module: /go/clients/upload
This module offers a unified client for uploading data to backend storage,
specifically Google BigQuery for Chrome performance data.
The `UploadClient` interface (`upload_client.go`) abstracts the underlying
storage technology, decoupling consumers from specific backends (e.g., BigQuery,
Cloud SQL). This design enables future flexibility for different storage
solutions.
### Key Components
- **`upload_client.go`**: Defines the module's public API.
- `UploadClient` interface: Specifies `CreateTableFromStruct` (to define
table schema from a Go struct using tags) and `Insert` (to upload data
rows). This is the primary interaction point.
- `NewUploadClient` factory: Returns a concrete `UploadClient`
implementation, currently `uploadChromeDataClient`.
- **`upload_chrome_data.go`**: Implements `UploadClient` for BigQuery-specific
operations.
- `uploadChromeDataClient`: Manages BigQuery interactions for a given
project, dataset, and table. It leverages `go/backends.BigQueryClient`
to perform actual BigQuery API calls.
- **How it works**: `NewUploadClient` instantiates
`uploadChromeDataClient`, which establishes a connection to BigQuery.
Subsequent calls to `CreateTableFromStruct` or `Insert` translate
generic requests into BigQuery-specific operations, utilizing Go struct
tags (e.g., `bigquery:"column_name"`) for schema mapping and data
insertion.
### Workflow
```mermaid
graph TD
A["Application"] --> B{"Configure UploadClientConfig"}
B --> C["Call NewUploadClient(config)"]
C --> D{"Get UploadClient instance"}
D -- "(Optional) Create Table Schema" -->
E["Call CreateTableFromStruct(DefinitionStruct)"]
E -- "Delegates to" --> F["go/backends.BigQueryClient.CreateTable"]
D -- "Upload Data Rows" --> G["Call Insert(DataRowsStructs)"]
G -- "Delegates to" --> H["go/backends.BigQueryClient.Insert"]
```
# Module: /go/common
The `/go/common` module defines core data structures and utilities for
`CombinedCommit` objects. These objects encapsulate a main repository commit
alongside overridden dependency commits, crucial for defining specific test
environments where a main project is tested against particular versions of its
dependencies.
### Design Decisions & Implementation Choices
The `CombinedCommit` type extends the `pinpoint_proto.CombinedCommit` protobuf
message, adding domain-specific logic and helper methods. This keeps the
protobuf definition pure for serialization while providing rich functionality
for business operations.
- **Dependency Management:** The `ModifiedDeps` field (a slice of
`pinpoint_proto.Commit`) allows flexible specification of multiple
dependency overrides. This enables testing a main project (e.g., Chromium)
with specific versions of its transitive dependencies (e.g., V8, Skia),
offering granular control over the build environment.
- **Identity and Hashing:** The `Key()` method generates a unique FNV hash for
a `CombinedCommit`. This serves as a lightweight, non-cryptographic
identifier, suitable for caching and map indexing, facilitating efficient
tracking and comparison of build requests.
- **Dynamic Updates:** The `UpsertModifiedDep()` method efficiently adds or
updates dependency commits. Despite its linear time complexity, this design
is pragmatic, as the number of modified dependencies is expected to remain
small, preventing performance bottlenecks.
- **Convenience Constructors:** Functions like `NewChromiumCommit()` and
`NewCommit()` simplify `pinpoint_proto.Commit` instance creation,
abstracting common setup patterns.
### Key Components & Responsibilities
The `combined_commit.go` file implements the `CombinedCommit` type and its
methods. It is responsible for:
- **Representing Complex Commits:** Encapsulating a main repository commit
with its specific dependency overrides.
- **Commit Manipulation:** Providing methods to add, update, or retrieve
dependency information (`UpsertModifiedDep`, `DepsToMap`,
`GetLatestModifiedDep`).
- **Cloning and Identity:** Offering `Clone()` for creating independent copies
and `Key()` for generating unique identifiers.
### Workflow: Building a Combined Commit
```mermaid
graph TD
A[Start with Main Repository Commit] --> B{Add/Update a specific Dependency?};
B -- Yes --> C[Specify Dependency Repository and GitHash];
C --> D[Upsert Dependency to CombinedCommit];
B -- No / More Dependencies? --> B;
B -- Done --> E[Resulting CombinedCommit Object];
E --> F[Can be used for Bisection/Testing];
```
# Module: /go/compare
The `compare` module provides statistical methods to determine if two sets of
measurements are significantly different, similar, or inconclusive. It is
crucial for automated systems like Pinpoint to identify performance regressions
or functional failures efficiently.
### Design and Implementation Choices
- **Robust Statistical Testing**: To account for varying data characteristics
and sensitivity requirements, the module employs both the Kolmogorov-Smirnov
(KS) test, which is effective for detecting differences in distribution
shapes, and the Mann-Whitney U (MWU) test, which is robust for detecting
shifts in medians. The minimum p-value from these two tests is used as the
overall significance measure.
- **Optimized Decision-Making with Dual Thresholds**: Instead of a single
statistical significance level, the module utilizes a dual-threshold system
(provided by `pinpoint/go/compare/thresholds`). A `LowThreshold` identifies
statistically `Different` samples, while a `HighThreshold` allows for early
classification of `Same` samples, thereby conserving computational resources
in iterative bisection processes. P-values falling between these thresholds
result in an `Unknown` verdict, suggesting the need for more data.
- **Contextual Comparison Logic**: The comparison logic adapts based on the
type of data being analyzed:
- **Performance Bisections**: Handle rational, non-negative numbers
typical of benchmark results. They incorporate a "small difference"
check to avoid bisecting on trivial changes and normalize magnitudes
based on the interquartile range (IQR) for threshold calculation.
- **Functional Bisections**: Focus on failure rates (represented as 0s and
1s). They leverage specialized thresholds tailored for binary data,
generally assuming an "improvement" means a decrease in failure rate.
- **Pairwise Comparison for Related Samples**: For comparisons involving
paired observations (e.g., before-and-after measurements), the
`ComparePairwise` function is used. This delegates to the Wilcoxon
Signed-Rank Test (provided by `pinpoint/go/compare/stats`), which supports
data transformations (e.g., logarithmic) and provides confidence intervals
relevant for percentage changes, making it suitable for analysis of relative
effects.
### Key Components
- **`compare.go`**: This file defines the core comparison orchestration. It
introduces `Verdict` (e.g., `Same`, `Different`, `Unknown`) and
`ImprovementDir` (e.g., `Up`, `Down`, `UnknownDir`) enums. It exposes
high-level functions like `CompareFunctional`, `ComparePerformance`, and
`ComparePairwise` which encapsulate the specific comparison logic, threshold
application, and data pre-processing necessary for each context.
- **`kolmogorov_smirnov.go`**: Implements the 2-sample Kolmogorov-Smirnov (KS)
test. It calculates the maximum difference between the empirical cumulative
distribution functions (CDFs) of two independent samples to determine their
statistical similarity.
- **`mann_whitney_u.go`**: Implements the Mann-Whitney U (MWU) test. This
non-parametric test determines if two independent samples are drawn from the
same distribution by comparing their medians through rank analysis, robustly
handling tied values.
- **`pinpoint/go/compare/stats`**: This submodule provides the Wilcoxon
Signed-Rank Test, primarily used for `ComparePairwise`. It supports exact
and asymptotic calculations, data transformations (`LogTransform`,
`NormalizeResult`), and confidence interval computation.
- **`pinpoint/go/compare/thresholds`**: This submodule manages the
dual-threshold system (`LowThreshold`, `HighThreshold`). It provides
pre-computed high thresholds that dynamically adjust based on sample size
and expected magnitude, optimizing the bisection process by enabling quicker
decisions.
### Main Comparison Workflow
```mermaid
graph TD
A["Input: valuesA, valuesB, Context (Performance/Functional,
Direction, Magnitude)"] --> B{"Determine if small difference
(Performance only)?"}
B -- Yes --> C["Verdict: Same (IsTooSmall)"]
B -- No --> D{"Calculate p-values for KS and MWU tests"}
D --> E["PValue = min(PValueKS, PValueMWU)"]
E --> F{"Retrieve LowThreshold and context-specific HighThreshold"}
F --> G{"PValue <= LowThreshold?"}
G -- Yes --> H["Verdict: Different"]
G -- No --> I{"PValue <= HighThreshold?"}
I -- Yes --> J["Verdict: Unknown (collect more data)"]
I -- No --> K["Verdict: Same"]
H --> L["Return CompareResults"]
J --> L
K --> L
C --> L
```
# Module: /go/compare/stats
This module provides a Go implementation of the Wilcoxon Signed-Rank Test,
primarily for pairwise statistical analysis in performance benchmarking, as used
in projects like Pinpoint. It enables non-parametric comparison of related
samples, offering precise p-values and confidence intervals.
### Key Components and Responsibilities
- **`wilcoxon_signed_rank.go`**: Core implementation of the Wilcoxon
Signed-Rank Test.
- `WilcoxonSignedRankedTest`: General function for one-sample or
paired-sample tests.
- `PairwiseWilcoxonSignedRankedTest`: Specialized function for Pinpoint's
pairwise job context, providing `LogTransform`, `NormalizeResult`, and
`OriginalResult` options for data preprocessing and result
interpretation (e.g., percentage change).
- **`sign_rank.go`**: Provides the exact probability distribution functions
(`pSignRank` for CDF, `qSignRank` for quantile) for the Wilcoxon Signed-Rank
statistic, crucial for small sample sizes.
- **`zeroin.go`**: Implements Brent's root-finding algorithm (`zeroin`). This
is used by `wilcoxon_signed_rank.go` to numerically compute confidence
interval boundaries when the normal approximation is applied.
### Design and Implementation Choices
- **Wilcoxon Signed-Rank Test**: Chosen for its robustness in comparing
related samples without assuming normal distribution, which is suitable for
many performance metrics. The implementation is a Go rewrite of an R
equivalent, ensuring quantitative consistency.
- **Exact vs. Asymptotic Methods**: The module dynamically switches between
exact calculations (for sample sizes less than 50, providing higher
precision) and normal approximation (for larger samples, enhancing
computational efficiency).
- **Data Transformation Support**: Offers `LogTransform` (for metrics with
multiplicative effects or skewed distributions), `NormalizeResult` (for
expressing changes as percentages relative to a baseline), and
`OriginalResult` (for raw differences). This adapts the test to various data
characteristics encountered in performance analysis.
- **Confidence Intervals**: Calculations include confidence intervals (CI),
providing an estimated range of plausible values for the true median
difference, which complements p-values for a more complete statistical
inference.
- **Handling Ties and Zero Differences**: The `rank` function and subsequent
logic correctly account for tied values and zero differences within the
samples, maintaining statistical validity.
### Workflow: WilcoxonSignedRankedTest
```mermaid
graph TD
A["WilcoxonSignedRankedTest(x, y, alt)"] --> B{"Calculate Differences: d = x - y"}
B --> C{"Filter non-zero differences: d_non_zero"}
C --> D{"Is Sample Size < 50 AND no Ties/Zeroes?"}
D -- Yes --> E["Compute Exact P-Value (sign_rank.go: newWilcoxonDistribution, pSignRank)"]
E --> F["Compute Exact CI (getDiffs, sign_rank.go: qSignRank)"]
D -- No (Normal Approx) --> G["Compute Z-statistic & Sigma (accounting for ties)"]
G --> H["Compute Asymptotic P-Value (Standard Normal CDF)"]
H --> I["Compute Asymptotic CI (zeroin.go: zeroin for asymptoticW)"]
F --> J["Return WilcoxonSignedRankedTestResult"]
I --> J
```
# Module: /go/compare/thresholds
The `thresholds` module optimizes hypothesis testing for iterative systems like
Pinpoint bisection. It provides a dual-threshold system to efficiently determine
if two data samples are statistically different, similar, or require more data,
thereby minimizing computational resources.
### Design and Implementation
- **Dual Threshold Approach**: Beyond the standard `LowThreshold` (0.05) for
detecting significant differences, a dynamic `HighThreshold` is introduced.
This high threshold enables early conclusions of "no significant difference"
when the p-value is sufficiently high, accelerating decisions and reducing
sampling costs in bisection processes.
- **Pre-computed Lookups**: High thresholds are stored in static tables
(`highThresholdsFunctional`, `highThresholdsPerformance`) instead of being
calculated on-the-fly. This design ensures rapid, predictable lookups,
offloading computationally intensive statistical derivations to external
generation scripts.
- **Contextual Adaptation**: Thresholds adjust based on `normalized_magnitude`
and `sample_size`. Different magnitude normalizations are provided for
functional (failure rate) and performance (interquartile range) comparisons,
ensuring appropriate statistical sensitivity across diverse measurement
types.
### Key Components
- **`thresholds.go`**: Defines the `LowThreshold` constant and provides
`HighThresholdPerformance` and `HighThresholdFunctional` functions. These
functions query the pre-calculated tables to return the relevant high
threshold for a given magnitude and sample size.
### Decision Workflow
```mermaid
graph TD
A["Hypothesis Test P-value"] --> B{"P-value < LowThreshold?"}
B -- Yes --> C["Result: Different Samples (Reject Null)"]
B -- No --> D{"P-value > HighThreshold?"}
D -- Yes --> E["Result: Similar Samples (Reject Alternative)"]
D -- No --> F["Result: Inconclusive (Need More Data)"]
```
# Module: /go/frontend
The `/go/frontend` module is the public-facing HTTP service for the Pinpoint job
management system. It provides a web-based user interface and a set of HTTP APIs
for managing Pinpoint jobs, retrieving benchmark configurations, and initiating
job executions via an internal Pinpoint core service. It orchestrates various
backend components (database, core Pinpoint service) to deliver a cohesive user
experience.
### Design Principles
- **Delegated Core Logic:** Actual Pinpoint job execution and management are
delegated to an internal `pinpoint_service`. This abstracts workflow
complexities, allowing `frontend` to focus on presentation, data
aggregation, and user interaction.
- **Hybrid Data Sourcing:** Job metadata is stored and retrieved from a
`jobs_store` (PostgreSQL) for persistence and querying. Static
configurations (benchmarks, stories, bots) are embedded in
`benchmarks.json`, simplifying deployment and ensuring consistent
configuration.
- **Separation of Concerns:** Application bootstrapping, infrastructure setup
(e.g., database connectivity, HTTP server), and static asset serving are
isolated in `/go/frontend/cmd`. Core business logic, API definition, and UI
rendering are handled by `/go/frontend/service`, promoting modularity and
maintainability.
- **Templated UI:** Basic HTML templates are served directly by the Go
application, providing a functional user interface without requiring a
separate frontend application framework.
### Responsibilities and Key Components
**`/go/frontend/cmd`** This module is the executable entry point, responsible
for bootstrapping the application and launching the HTTP server.
- **`main.go`**:
- **Database Connectivity**: Establishes and manages a PostgreSQL
connection pool using `pgxpool` for efficient, centralized database
access.
- **Service Initialization**: Instantiates `jobs_store` for data
persistence and initializes the `/go/frontend/service` module, which
encapsulates the application's core logic and registers its HTTP API
handlers.
- **HTTP Server Setup**: Configures the `chi` router, registers API and
static asset routes, and starts the HTTP server, making the Pinpoint
frontend accessible.
**`/go/frontend/service`** This module provides the HTTP API and user interface
for the Pinpoint system, orchestrating data display and core service
interactions.
- **`Service` struct (`jobs.go`):** The central orchestrator, holding
references to the `jobs_store`, HTML templates, benchmark configurations,
and the `pinpoint_service`'s HTTP handler. Its `RegisterHandlers` method
defines all public API and UI endpoints.
- **HTTP Handlers (`jobs.go`):**
- **Job Management:** `ListJobsHandler` and `GetJobHandler` provide API
endpoints for querying and retrieving detailed job information from the
`jobs_store`, including validation for pagination and date ranges.
- **Configuration Discovery:** `ListBenchmarksHandler`,
`ListBotConfigurationsHandler`, and `ListStoriesHandler` expose the
embedded `benchmarks.json` data as JSON APIs, supporting dynamic UI
population and input validation.
- **UI Pages:** Dedicated `templateHandler` instances render
`landing-page.html` and `results-page.html` for user interaction.
- **Pinpoint Core Proxy:** The `pinpointHandler` forwards requests to
`/pinpoint/*` directly to an HTTP adapter for the internal
`pinpoint_service`, allowing the frontend to trigger and monitor actual
Temporal-backed Pinpoint workflow executions.
- **`benchmarks.json`:** An embedded static JSON file defining available
benchmarks, their stories, and supported bots, used for configuration APIs
and UI validation.
### Workflows
#### Application Initialization
```mermaid
graph TD
A["Start Application (main.go)"] --> B{"Parse Flags & Context"}
B --> C["Configure & Connect to PostgreSQL (pgxpool)"]
C --> D["Initialize JobStore (jobs_store)"]
C --> D["Initialize JobStore (jobs_store)"]
D --> E["Initialize Frontend Service (frontend/service)"]
E --> F["Register HTTP Handlers (chi.Router)"]
F --> G["Serve Static Assets"]
G --> H["Start HTTP Server (ListenAndServe)"]
```
#### Request Handling
```mermaid
graph TD
A["Client/User Interface"] -->|HTTP Request| B("Pinpoint Frontend Service")
subgraph "Pinpoint Frontend Service"
B -- "HTTP GET /json/jobs/list" --> D("Retrieve Jobs")
D --> E["JobStore (SQL)"]
B -- "HTTP GET /benchmarks" --> F("Retrieve Benchmarks")
F --> G["Embedded benchmarks.json"]
B -- "HTTP POST /pinpoint/job/create" --> H("Proxy to Core Service")
H --> I["Pinpoint Core Service (Temporal)"]
B -- "Render HTML page" --> J["HTML Templates"]
end
E -- "Job data" --> D
G -- "Benchmark Config" --> F
I -- "Workflow response" --> H
D -- "JSON Response" --> A
F -- "JSON Response" --> A
H -- "JSON Response" --> A
J -- "HTML page" --> A
```
# Module: /go/frontend/cmd
The `/go/frontend/cmd` module is the executable entry point for the Pinpoint
frontend service. It bootstraps the application, establishes database
connectivity, initializes core services, and starts the HTTP server to handle
API requests and serve static web assets.
This module acts as the primary orchestrator, assembling various backend
components into a cohesive web service. Its design separates application
bootstrapping and infrastructure concerns from the core business logic,
promoting modularity. It ensures the application is correctly configured with
database access and that the business logic from `frontend/service` is exposed
via HTTP.
### Responsibilities and Key Components
- **`main.go`**: The application's starting point.
- **Database Connectivity**: Parses the connection string and establishes
a PostgreSQL connection pool using `pgxpool`. This centralizes database
setup and optimizes connection management.
- **Service Initialization**: Instantiates the `jobs_store` for data
persistence and initializes the `frontend/service`. The
`frontend/service` encapsulates core business logic and registers its
HTTP API handlers.
- **HTTP Server Setup**: Configures and starts the HTTP server with `chi`
for routing. It also handles serving static UI assets from a specified
production directory, making the web interface accessible alongside API
endpoints.
```mermaid
graph TD
A["Start Application (main.go)"] --> B{"Parse Flags & Context"}
B --> C["Configure & Connect to PostgreSQL (pgxpool)"]
C --> D["Initialize JobStore (jobs_store)"]
D --> E["Initialize Frontend Service (frontend/service)"]
E --> F["Register HTTP Handlers (chi.Router)"]
F --> G["Serve Static Assets"]
G --> H["Start HTTP Server (ListenAndServe)"]
```
# Module: /go/frontend/service
This module provides the HTTP API and user interface for the Pinpoint job
management system. It acts as a gateway, exposing job metadata, benchmark
configurations, and routing core job creation/management requests to an internal
Pinpoint service.
### Design Principles and Implementation Choices
This service is designed for clear separation of concerns, aggregating data from
various sources to serve both programmatic clients and human users:
- **Delegated Core Logic:** Actual Pinpoint job execution and management is
delegated to an internal `pinpoint_service`. This underlying service,
primarily gRPC-based and interacting with Temporal workflows, is exposed via
an HTTP/JSON handler. This ensures the frontend module remains focused on
presentation and data aggregation, abstracting the complexities of workflow
orchestration.
- **Hybrid Data Sourcing:**
- **Persistent Job Data:** Pinpoint job metadata (e.g., status, details,
history) is stored and retrieved from a `jobs_store` (SQL database).
This enables scalable querying, filtering, and pagination of job data
through dedicated API endpoints.
- **Embedded Static Configuration:** Benchmark definitions, available
stories, and supported bots are loaded from an embedded
`benchmarks.json` file. Embedding this static data ensures it's always
present with the application binary, simplifying deployment and
providing consistent configuration for UI elements and input validation.
- **Templated UI:** HTML templates (`landing-page.html`, `results-page.html`)
are loaded and served dynamically, providing a basic web-based user
interface for interacting with the Pinpoint system without requiring a
separate frontend application framework.
### Key Components
- **`Service` struct (`jobs.go`):** The central component, orchestrating all
functionality. It holds references to the `jobs_store`, HTML templates,
benchmark configurations, and the `pinpoint_service`'s HTTP handler. Its
`RegisterHandlers` method defines all public API and UI endpoints.
- **HTTP Handlers (`jobs.go`):**
- **Job Management:** `ListJobsHandler` and `GetJobHandler` provide API
endpoints for querying and retrieving detailed information about
Pinpoint jobs from the `jobs_store`. These handlers include robust
validation for pagination and date range parameters.
- **Configuration Discovery:** `ListBenchmarksHandler`,
`ListBotConfigurationsHandler`, and `ListStoriesHandler` expose the
embedded benchmark configuration data as JSON APIs. These are critical
for dynamically populating user interfaces with valid options for
creating new Pinpoint jobs.
- **UI Pages:** Dedicated `templateHandler` instances serve the static
HTML pages, providing the user-facing interface.
- **Pinpoint Core Proxy:** Requests to `/pinpoint/*` are forwarded to the
`pinpointHandler`, which is an HTTP adapter for the internal
`pinpoint_service` instance. This allows the frontend to trigger and
monitor actual Temporal-backed Pinpoint workflow executions.
- **`benchmarks.json`:** An embedded JSON file that provides static
configuration for available benchmarks, their associated stories, and
supported bots. This data underpins the frontend's configuration APIs and
guides UI input validation.
### Workflow
```mermaid
graph TD
A["Client/User Interface"] -->|HTTP Request| B("Pinpoint Frontend Service")
subgraph "Pinpoint Frontend Service"
B -- "HTTP GET /json/jobs/list" --> D("Retrieve Jobs")
D --> E["JobStore (SQL)"]
B -- "HTTP GET /benchmarks" --> F("Retrieve Benchmarks")
F --> G["Embedded benchmarks.json"]
B -- "HTTP POST /pinpoint/job/create" --> H("Proxy to Core Service")
H --> I["Pinpoint Core Service (Temporal)"]
B -- "Render HTML page" --> J["HTML Templates"]
end
E -- "Job data" --> D
G -- "Benchmark Config" --> F
I -- "Workflow response" --> H
D -- "JSON Response" --> A
F -- "JSON Response" --> A
H -- "JSON Response" --> A
J -- "HTML page" --> A
```
# Module: /go/midpoint
The `/go/midpoint` module provides the core logic for identifying the next
candidate commit in a bisection process. Pinpoint uses bisection to efficiently
locate a regression's root cause by repeatedly narrowing down a range of
commits. This module's central role is to calculate the "midpoint" within a
given commit range.
### Responsibilities and Key Components
The `midpoint` module's primary responsibility is to determine the next commit
to test, handling both direct repository changes and changes introduced via
dependency rolls (DEPS).
- **`MidpointHandler`**: This is the main orchestrator for midpoint
calculations. It manages Gitiles clients for interacting with various
repositories and coordinates the recursive search for the midpoint.
- **`FindMidCombinedCommit`**: The module's public entry point. It takes two
`common.CombinedCommit` objects (which represent a main repository commit
combined with its modified dependencies) and returns the next midpoint. This
function is designed to:
- **Bisect the main repository**: If no modified dependencies are
involved, it finds the median commit directly within the main
repository's history.
- **Handle DEPS rolls**: If the two main commits are adjacent, it assumes
a DEPS roll. It then parses the DEPS files for both commits, identifies
the first differing git-based dependency, and recursively bisects within
that dependency's history.
- **Manage `CombinedCommit` state**: It ensures consistent comparison of
`CombinedCommit` objects by dynamically filling in implied dependency
hashes through `fillModifiedDeps`. This is crucial when one
`CombinedCommit` explicitly references a dependency change, and the
other implicitly uses its parent's version of that dependency.
### Design and Implementation Choices
The module's design revolves around the `common.CombinedCommit` structure, which
allows bisection to traverse across different repositories - starting with a
main repository (like Chromium) and then "diving" into its git-based
dependencies when a DEPS roll is detected.
- **Recursive search for culprits**: When two commits are found to be
adjacent, the system's design anticipates a DEPS roll. Instead of
terminating, it shifts the bisection focus to the dependencies defined in
the `DEPS` file. This allows Pinpoint to pinpoint regressions caused by
changes in upstream libraries.
- **`fillModifiedDeps` for state consistency**: Bisection paths can involve
`CombinedCommit` objects with varying levels of explicit dependency
information. `fillModifiedDeps` ensures that both ends of a comparison have
their `ModifiedDeps` arrays consistently populated from their respective
DEPS files. This normalization is vital for accurate comparison and
subsequent bisection steps, allowing `{C@1}` to be correctly compared
against `{C@1, V8@4}` by implicitly defining `V8@3` for `C@1`.
- **Support for git-based DEPS only**: The implementation currently focuses on
git-based dependencies, filtering out CIPD-based dependencies during DEPS
parsing. This simplifies the initial scope but means CIPD-related
regressions cannot be bisected this way.
- **Single-layer DEPS roll focus**: A current limitation is that the module
only identifies and bisects the _first_ differing git-based dependency in a
DEPS roll. It does not handle scenarios where multiple dependencies are
rolled simultaneously or where a rolled dependency itself contains another
roll that needs further bisection when adjacent. This is a pragmatic choice
to simplify the initial implementation.
### Workflow: Finding a Midpoint
```mermaid
graph TD
A["Start: FindMidCombinedCommit(startCC, endCC)"] --> B{"startCC == endCC?"}
B -- Yes --> C["Error: Identical commits"]
B -- No --> D{"Any ModifiedDeps?"}
D -- Yes --> E["Fill ModifiedDeps for consistency"]
E --> F["Identify latest differing ModifiedDep: startDep, endDep"]
F --> G{"startDep.GitHash == endDep.GitHash?"}
G -- Yes --> H["Handle edge cases: DEPS roll boundaries"]
G -- No --> I["Find midpoint within ModifiedDep: findMidCommit(startDep, endDep)"]
I --> J["Return new CombinedCommit with updated ModifiedDep"]
D -- No --> K["No ModifiedDeps: Bisect Main commit"]
K --> L["Find midpoint in Main: findMidCommit(startCC.Main, endCC.Main)"]
L --> M{"Midpoint is in Main?"}
M -- Yes --> N["Return new CombinedCommit with updated Main"]
M -- No --> O["Midpoint is a new dep: Add to ModifiedDeps"]
O --> J
subgraph "findMidCommit(startCommit, endCommit)"
P["Try findMidpoint(startCommit, endCommit)"] --> Q{"Result is startCommit (adjacent)?"}
Q -- No --> R["Return found midpoint"]
Q -- Yes --> S["Assume DEPS roll: Call findMidCommitInDEPS(startCommit, endCommit)"]
S --> T{"DEPS roll midpoint found?"}
T -- No --> U["Return startCommit (no further mid)"]
T -- Yes --> V["Return DEPS midpoint"]
end
```
# Module: /go/read_values
The `read_values` module provides an API to retrieve and process performance
benchmark results stored in RBE Content-Addressable Storage (CAS) for Pinpoint
workflows.
### Core Responsibility
This module is responsible for fetching raw performance sample values and
applying optional data aggregations (e.g., mean, min) for specified benchmarks
and charts from a list of CAS digests.
### Design and Implementation
- **CAS Abstraction:** The `CASProvider` interface decouples the core logic
from specific CAS backend implementations. This design promotes testability
and allows for future extensions beyond RBE without modifying the
`perfCASClient`.
- **RBE Integration:** `rbeProvider` implements `CASProvider` for RBE-CAS,
utilizing `cabe/go/backends` for efficient data retrieval. `DialRBECAS`
centralizes the connection to RBE, accommodating Pinpoint's use of multiple
Swarming instances to store CAS results. This ensures correct CAS client
selection for specific data.
- **Data Processing:** `perfCASClient` encapsulates the logic to fetch raw
`perfresults.PerfResults` from CAS digests, filter them by benchmark and
chart, and apply specified aggregation methods. This transforms low-level
CAS content into structured performance metrics required by Pinpoint.
- **Aggregation:** Integration with `perfresults.AggregationMapping` allows
users to select statistical aggregation methods. This enables the module to
return summarized performance data, which is vital for analysis, trend
detection, and reducing data volume, instead of large sets of raw samples.
### Key Components
- `read_values.go`: Implements the `CASProvider` interface, the
`perfCASClient` responsible for fetching and processing performance data,
and public methods for reading values by chart or for all charts, including
aggregation logic.
- `read_values_test.go`: Contains unit tests that validate the data fetching,
filtering, and aggregation functionalities using a mocked `CASProvider` to
ensure correctness and isolation.
### Workflow
```mermaid
graph TD
A[Request Benchmark Data] --> B{Call DialRBECAS};
B --> C[Initialize perfCASClient for specific Swarming instance];
C --> D{Call ReadValuesByChart or ReadValuesForAllCharts};
D --> E[For each CAS Digest in request];
E --> F[Fetch raw PerfResults from CAS via rbeProvider];
F -- On Success --> G[Filter results by Benchmark/Chart];
G --> H{Aggregation Method Specified?};
H -- Yes --> I[Apply aggregation to samples];
H -- No --> J[Collect all raw samples];
I --> K[Append processed values];
J --> K;
K --> E;
E -- All Digests Processed --> L[Return TestResults];
F -- On Error --> M[Return Error];
D -- On Error --> M;
B -- On Error --> M;
```
# Module: /go/run_benchmark
The `run_benchmark` module orchestrates performance benchmark executions as
Swarming tasks for Pinpoint. It dynamically constructs benchmark commands based
on specified parameters, schedules these tasks on Swarming, and offers utilities
to monitor and interpret task statuses.
### Core Components and Responsibilities
- **`run_benchmark.go`**: Manages the lifecycle of Swarming tasks for
benchmark execution.
- **How**: The `Run` function is the primary entry point, initiating
Swarming tasks. It leverages the `BenchmarkTest` interface to decouple
task scheduling from the specifics of command generation.
- **Why**: The `State` type abstracts raw Swarming statuses into
meaningful states (e.g., `IsTaskSuccessful`, `IsTaskTerminalFailure`),
simplifying status interpretation and providing a consistent API for
downstream services.
- **`benchmark_test_factory.go`**: Dynamically creates the appropriate
benchmark test executor.
- **How**: The `NewBenchmarkTest` function acts as a factory, using
`bot_configs` to determine the specific isolate target (e.g.,
`performance_test_suite`) for a given benchmark and bot. Based on this,
it returns an implementation of the `BenchmarkTest` interface.
- **Why**: This design choice promotes extensibility; new benchmark types
can be integrated by implementing the `BenchmarkTest` interface without
altering the core task scheduling logic in `run_benchmark.go`.
- **`telemetry.go`**: Implements the `BenchmarkTest` interface for Telemetry
and Crossbench performance tests.
- **How**: The `GetCommand` method constructs the precise command-line
arguments needed to execute a Telemetry or Crossbench test on a Swarming
bot, including handling different types of benchmarks (gtests, standard
telemetry benchmarks, crossbench) and various parameters like story,
story tags, and extra arguments.
- **Why**: Centralizes the complex logic of converting Pinpoint's abstract
benchmark parameters into concrete executable commands compatible with
Chromium's performance testing infrastructure. Maps and specific
argument generation functions ensure alignment with Chromium's perf
waterfall.
- **`swarming_helpers.go`**: Utility functions for building Swarming API
requests.
- **How**: Contains functions like `createSwarmingRequest`,
`generateProperties`, `convertDimensions`, and `generateTags` to
assemble a complete `apipb.NewTaskRequest` object, populating all
necessary fields for a Pinpoint-initiated Swarming task.
- **Why**: Decouples the low-level details of Swarming request
construction from the higher-level orchestration in `run_benchmark.go`,
improving clarity and maintainability. It also centralizes
Swarming-specific configurations like timeouts and service accounts.
### Workflow
```mermaid
graph TD
A[Run Benchmark Request] --> B{Resolve Bot Config & Isolate Target};
B --> C{Factory: Create BenchmarkTest Instance};
C -- GetCommand() --> D[Generate Benchmark Execution Command];
D --> E[Build Swarming Task Request];
E --> F[Trigger Swarming Task];
F --> G[Receive Swarming Task IDs];
subgraph "Benchmark Command Details (telemetry.go)"
C -- (e.g., TelemetryTest) --> D1{Is it a Waterfall GTest?};
D1 -- Yes --> D2[Add GTest-specific args];
D1 -- No, Is it Crossbench? --> D3[Add Crossbench-specific args];
D3 -- No --> D4[Add Telemetry args];
D2 & D3 & D4 --> D5[Include Story/StoryTag filters];
D5 --> D;
end
subgraph "Swarming Request Details (swarming_helpers.go)"
E --> E1[Convert Dimensions];
E --> E2[Generate Task Properties];
E --> E3[Generate Tags];
E1 & E2 & E3 --> E;
end
```
# Module: /go/service
The `/go/service` module provides the gRPC API for Pinpoint, enabling users to
schedule and query performance analysis jobs. It acts as the orchestration
layer, translating user requests into Temporal workflow executions.
## Design and Implementation Rationale
The module's core design relies on **Temporal workflows** for executing
long-running, stateful performance analysis jobs. This approach leverages
Temporal's fault tolerance, retry mechanisms, and state management capabilities,
offloading complex job lifecycle management from the service. The
`tpr_client.TemporalProvider` (`go.skia.org/infra/temporal/go/client`) abstracts
the Temporal client, ensuring testability and adaptability to different Temporal
environments.
The service uses **gRPC for efficient communication** and `grpc-gateway`
(`github.com/grpc-ecosystem/grpc-gateway/v2`) to **automatically generate a
RESTful JSON API**. This provides flexibility, allowing clients to interact with
Pinpoint via either gRPC or standard HTTP/JSON.
A **local rate limiter** (`golang.org/x/time/rate`) is integrated to temporarily
control the influx of new job requests. This is a pragmatic decision to manage
system load, particularly during migration phases or to prevent resource
exhaustion, acting as a temporary safeguard.
Workflow execution options consistently set `MaximumAttempts: 1` for all
scheduled jobs. This design assumes that once a Pinpoint job workflow begins,
any non-recoverable failure should not trigger a top-level workflow retry.
Instead, retries and error handling are expected to occur within the individual
activities composing the workflow.
**Input validation** (`validation.go`) is a critical "fail-fast" mechanism.
Requests are thoroughly checked before initiating any Temporal workflows,
preventing invalid jobs from consuming resources and providing immediate, clear
feedback to users.
## Key Components
### `service_impl.go`
Implements the Pinpoint gRPC API, defining methods for scheduling and querying
jobs, and for job cancellation.
- **`server` struct**: Encapsulates the `rate.Limiter` for request throttling
and the `tpr_client.TemporalProvider` for Temporal interactions.
- **`New` function**: The constructor, injecting the Temporal client provider
and rate limiter.
- **`NewJSONHandler`**: Exposes the gRPC service over HTTP/JSON using
`grpc-gateway`.
- **RPC Methods (e.g., `ScheduleBisection`, `ScheduleCulpritFinder`,
`SchedulePairwise`, `QueryPairwise`, `CancelJob`)**:
- Apply rate limiting.
- Perform initial request validation.
- Connect to Temporal via `tpr_client.TemporalProvider`.
- Generate unique workflow IDs.
- Configure and execute Temporal workflows with specific timeouts and
retry policies.
- Convert Temporal workflow results or status into gRPC responses.
### `validation.go`
Houses the logic for validating incoming gRPC requests.
- **`updateFieldsForCatapult`, `updateCulpritFinderFieldsForCatapult`**:
Temporary functions for migration compatibility, translating legacy
`Statistic` fields to `AggregationMethod`.
- **`validateBisectRequest`, `validateCulpritFinderRequest`,
`validatePairwiseRequest`, `validateQueryPairwiseRequest`**: Contains
specific validation rules for each job type, ensuring all required
parameters are present, valid, and conform to expected formats (e.g.,
non-empty git hashes, supported aggregation methods, valid UUIDs for job
IDs).
## Key Workflow: Scheduling a Job
```mermaid
graph TD
A[Client Sends Schedule Request] --> B{Is Request Rate-Limited?};
B -- Yes --> C[Return Resource Exhausted Error];
B -- No --> D{Validate Request Parameters?};
D -- No (Invalid) --> E[Return Invalid Argument Error];
D -- Yes --> F[Connect to Temporal Service];
F -- Error --> G[Return Internal Server Error];
F -- Success --> H[Start New Temporal Workflow];
H -- Error --> G;
H -- Success --> I[Return Job ID to Client];
```
## Key Workflow: Querying a Job
```mermaid
graph TD
A[Client Sends Query Request] --> B{Validate Request Parameters?};
B -- No (Invalid) --> C[Return Invalid Argument Error];
B -- Yes --> D[Connect to Temporal Service];
D -- Error --> E[Return Internal Server Error];
D -- Success --> F[Describe Temporal Workflow Execution];
F -- Error --> E;
F -- Success --> G{What is Workflow Status?};
G -- COMPLETED --> H[Fetch Workflow Results];
G -- FAILED/TIMED_OUT/TERMINATED --> I[Return FAILED Status];
G -- CANCELED --> J[Return CANCELED Status];
G -- RUNNING/CONTINUED_AS_NEW --> K[Return RUNNING Status];
G -- Other --> E;
H -- Error --> E;
H -- Success --> L[Return Detailed Results];
```
# Module: /go/sql
This module manages Pinpoint's persistent job data, defining its database
schema, generating Spanner SQL DDL, and providing a data access layer for
interacting with job records.
### Why and How
- **Schema as Go Structs**: The database schema is defined as Go structs (in
`schema`) annotated with `sql` tags. This provides type safety, ensures
consistency between application code and the database, and enhances
maintainability compared to raw SQL DDL.
- **Automated DDL Generation**: The `tosql` module programmatically converts
these Go struct definitions into Spanner-compatible `CREATE TABLE` DDL. This
guarantees schema alignment, centralizes management, and simplifies
controlled evolution by embedding the generated DDL directly into
`schema/spanner/spanner.go` at compile-time.
- **Flexible Data Storage**: Complex, evolving job parameters and results are
stored in `JSONB` columns within the `Jobs` table. This design choice
provides schema flexibility, minimizing the need for frequent database
migrations when new fields or data types are introduced in job definitions
or results.
- **Decoupled Data Access**: The `jobs_store` module provides a standardized
data access layer, decoupling job execution logic from the underlying
Spanner database. This ensures job states, parameters, and results are
reliably recorded and accessible, supporting the complete lifecycle of a
performance experiment.
### Responsibilities and Key Components
- **`schema`**: Defines the authoritative Go-native data structures, primarily
`JobSchema`, that model the `Jobs` Spanner table. It uses `sql` tags to
specify column types and constraints, including flexible `JSONB` fields for
dynamic data. The `spanner` submodule within `schema` contains the
_generated_ Spanner DDL.
- **`tosql`**: An executable responsible for generating the Spanner SQL DDL.
It reads the Go struct definitions from `schema`, utilizes the
`go/sql/exporter` utility to translate them into DDL, and writes the output
to `schema/spanner/spanner.go`.
- **`jobs_store`**: Manages all persistence and retrieval operations for
Pinpoint performance analysis jobs. Its responsibilities include initial job
creation (`AddInitialJob`), status updates (`UpdateJobStatus`), detailed run
data storage (`AddCommitRuns`), analysis results persistence (`AddResults`),
error reporting (`SetErrors`), and flexible job retrieval for dashboards
(`GetJob`, `ListJobs` with filtering and pagination).
### Workflows
#### Schema Definition and Generation
```mermaid
graph TD
A["Go Structs in schema/schema.go (with 'sql' tags)"] --> B{"tosql executable"}
B --> C["go/sql/exporter library"]
C --> D["Generated Spanner DDL (written to schema/spanner/spanner.go)"]
D --> E["Pinpoint Spanner Database (Schema applied)"]
```
#### Job Lifecycle Persistence
```mermaid
graph TD
A["User Schedules Job"] --> B{"jobs_store.AddInitialJob"}
B --> C["Job Stored: Pending"]
C --> D["Job Execution Workflow"]
D -- "Updates Status" --> E{"jobs_store.UpdateJobStatus"}
D -- "Stores Run Data" --> F{"jobs_store.AddCommitRuns"}
D -- "Stores Analysis Results" --> G{"jobs_store.AddResults"}
D -- "Records Errors" --> H{"jobs_store.SetErrors"}
E & F & G & H --> I["Job Data Persisted"]
I --> J{"jobs_store.GetJob / jobs_store.ListJobs"}
J --> K["Dashboard / Client Retrieval"]
```
# Module: /go/sql/jobs_store
The `jobs_store` module manages the persistence and retrieval of Pinpoint
performance analysis jobs. It provides a robust, standardized interface for
other Pinpoint components to interact with job data, decoupling job execution
logic from the underlying storage mechanism. This design ensures job states,
parameters, and results are reliably recorded and accessible, supporting the
complete lifecycle of a performance experiment.
### Responsibilities and Key Components
- **Job Data Persistence (`jobs_store.go`)**: The core `JobStore` interface
and its `jobStoreImpl` implementation handle all database operations related
to jobs.
- **Initial Job Creation**: `AddInitialJob` stores the complete
`SchedulePairwiseRequest` as the foundational record for a new job. This
captures all user-defined parameters at the job's inception.
- **Job State Management**: `UpdateJobStatus` tracks the progression of a
job through its lifecycle, updating its status and, upon completion,
recording its total execution duration.
- **Detailed Run Data Storage**: `AddCommitRuns` stores comprehensive
information about the builds and test runs executed for both the start
and end commits of a pairwise comparison. This data is critical for
understanding the experimental context.
- **Analysis Result Storage**: `AddResults` persists the final statistical
analysis results, such as Wilcoxon test outcomes, providing the
quantitative summary of the job.
- **Error Reporting**: `SetErrors` captures and stores any errors
encountered during job execution, aiding in debugging and user
communication.
- **Job Retrieval**: `GetJob` fetches all details for a specific job,
while `ListJobs` supports dashboard views by allowing filtered and
paginated retrieval of job summaries.
- **Why structured JSON columns?** The module utilizes flexible JSONB
columns (e.g., `additional_request_parameters`, `metric_summary`) to
store complex, evolving data structures (like
`schema.AdditionalRequestParametersSchema` and
`pinpointpb.PairwiseExecution_WilcoxonResult`). This approach avoids
frequent schema migrations when new fields or data types are introduced
in the job definition or results.
- **Dashboard Integration (`jobs_store.go`)**: `DashboardJob` and
`ListJobsOptions` are tailored data structures to efficiently support a
user-facing dashboard. `ListJobsOptions` allows for flexible filtering by
criteria such as job name, benchmark, date range, bot, and user, along with
pagination, directly addressing the "how" of presenting a manageable list of
jobs to users.
### Job Lifecycle Workflow
```mermaid
graph TD
A["User Schedules Job"] --> B{"AddInitialJob"}
B --> C["Job Stored: Pending"]
C --> D{"Job Execution Workflow"}
D -- "Updates Status" --> E{"UpdateJobStatus"}
D -- "Stores Commit & Run Data" --> F{"AddCommitRuns"}
D -- "Stores Analysis Results" --> G{"AddResults"}
D -- "Records Errors" --> H{"SetErrors"}
E --> I["Job Status: Running/Completed/Failed"]
F --> J["Additional Parameters (CommitRuns) Updated"]
G --> K["Metric Summary Updated"]
H --> L["Error Message Updated"]
I & J & K & L --> M{"GetJob / ListJobs"}
M --> N["Dashboard / Client"]
```
# Module: /go/sql/schema
### Overview
This module defines the Go-native data structures that represent the schema for
Pinpoint's persistent job data, primarily for the `Jobs` Spanner table. It acts
as the programmatic interface for interacting with job records in the database.
### Design and Implementation
The module's core design revolves around providing a type-safe, declarative
representation of the database schema in Go.
- **Go Structs as Schema Definition**: `schema.go` uses Go structs (e.g.,
`JobSchema`, `AdditionalRequestParametersSchema`) with `sql:"..."` tags to
define the database schema. This ensures the application's data models align
directly with the database schema, enabling compile-time type checking and
reducing runtime mismatches.
- **Flexible Data Storage with JSONB**: Critical job parameters and results
(e.g., `AdditionalRequestParameters`, `MetricSummary`) are stored as `JSONB`
fields in the database. This design choice provides flexibility, allowing
the addition of new parameters or changes to result formats without
requiring schema migrations on the main `Jobs` table.
- **Integration with Core Types**: The schema integrates types from other
Pinpoint modules, such as `go/workflows` (for build/test run data) and
`pinpoint/proto/v1` (for metric summary results). This ensures data
consistency and a unified data model across the Pinpoint ecosystem.
- **Automated DDL Generation**: The actual Spanner DDL (found in the `spanner`
submodule) is automatically generated from these Go structs by a dedicated
exporter tool. This centralizes schema management, guarantees consistency,
and simplifies controlled schema evolution by preventing manual DDL errors.
### Key Components
- **`schema.go`**:
- **`JobSchema`**: The primary Go struct modeling the `Jobs` table. It
defines core job metadata like `JobID` (primary key), `JobName`,
`JobStatus`, `SubmittedBy`, `Benchmark`, and `BotName`, alongside nested
`JSONB` fields for flexible data.
- **`AdditionalRequestParametersSchema`**: Encapsulates diverse parameters
required for a job, such as commit hashes, story details, and
aggregation methods. This struct is serialized into a `JSONB` column
within `JobSchema`.
- **`CommitRunData` / `CommitRuns`**: Nested structures within
`AdditionalRequestParametersSchema` to store detailed build and test run
information for comparative analyses.
- **`spanner` (Submodule)**: This submodule contains the declarative Spanner
SQL DDL for the `Jobs` table, specifically the `Schema` constant within
`spanner.go`. This DDL is generated from the Go structs defined in
`schema.go` and includes a TTL policy for data retention.
### Workflow: Schema Definition and Deployment
```mermaid
graph TD
A["Go Structs in schema.go"] --> B{"Annotated with 'sql' tags"}
B --> C["go/sql/exporter/ Tool"]
C --> D["go/sql/schema/spanner/spanner.go (Generated CREATE TABLE DDL)"]
D --> E["Pinpoint Spanner Database (Schema Applied)"]
```
# Module: /go/sql/schema/spanner
This module provides the declarative Spanner SQL schema for the `Jobs` table.
The schema is automatically generated by `//go/sql/exporter/`, centralizing
schema management and ensuring consistency across the system by avoiding manual
definition errors. This also facilitates controlled schema evolution.
The `Jobs` table is central to Pinpoint, storing comprehensive details about
each performance testing job. This includes critical metadata like `job_id`,
`job_status`, `submitted_by`, and `benchmark`. `JSONB` fields
(`additional_request_parameters`, `metric_summary`) are used to store flexible,
unstructured data, accommodating diverse job configurations and results without
rigid schema changes. A `TTL` (Time-To-Live) policy is applied to automatically
purge old job records (after 1095 days), managing data volume and retention.
The sole key component is the `Schema` constant within `spanner.go`, which
contains the complete `CREATE TABLE IF NOT EXISTS Jobs` DDL statement.
```mermaid
graph TD
A["Schema Definition Source (e.g., ORM models)"] --> B["go/sql/exporter/ tool"]
B --> C["go/sql/schema/spanner/spanner.go (Generated 'Schema' constant)"]
C --> D["Pinpoint Spanner Database (Schema Applied)"]
```
# Module: /go/sql/tosql
This module generates Spanner SQL DDL from Go struct definitions, ensuring
consistency between application data models and the database.
**Why and How:** The database schema's source of truth is defined as Go structs,
specifically `pinpoint/go/sql/schema.JobSchema`. This approach provides several
benefits over directly writing raw SQL DDL:
- **Type Safety:** Leverages Go's type system for schema definitions.
- **Consistency:** The same Go structs can be used for ORM, validation, and
schema generation, eliminating divergence.
- **Maintainability:** Easier for Go developers to read, understand, and
modify.
The `tosql` executable uses the `go/sql/exporter` library to introspect these Go
structs and convert them into Spanner-compatible DDL. This DDL is then embedded
as a string into a generated Go file (`schema/spanner/spanner.go`). This makes
the DDL available at compile time, simplifying deployment and ensuring the
application always uses the correct schema.
**Responsibilities:**
- **Schema Generation:** Programmatically creates Spanner DDL from Go struct
definitions.
- **Schema Consistency:** Ensures the database schema is directly derived from
and aligned with the application's Go data models.
**Key Components:**
- `main.go`: The executable that identifies the Go structs to be exported
(e.g., `pinpoint/go/sql/schema.JobSchema`), invokes the `go/sql/exporter`
library, and writes the generated Spanner DDL to
`schema/spanner/spanner.go`.
- `go/sql/exporter`: A generic library responsible for converting Go struct
definitions into SQL DDL for various database backends, used here for
Spanner schema generation.
**Workflow:**
```mermaid
graph LR
A["pinpoint/go/sql/schema.JobSchema Go Struct"] --> B("tosql executable (main.go)")
B --> C{"go/sql/exporter library"}
C --> D["Spanner DDL as Go String"]
D --> E["schema/spanner/spanner.go file"]
```
# Module: /go/workflows
This module is the central hub for defining and implementing Pinpoint's backend
logic using the Temporal workflow engine. It provides durable, fault-tolerant,
and observable execution for complex, long-running tasks like performance
bisection and A/B testing. The design separates the public interface of
workflows from their underlying implementation and execution environment.
### Key Components
- **`workflows.go`**: Defines the public contract for all Pinpoint workflows.
- **Why**: To decouple clients from the implementation. A client only
needs to know a workflow's name (e.g., `perf.bisect`) and its parameter
struct (`BisectParams`) to start a job, without importing the
implementation code.
- **How**: It provides constants for workflow names and Go structs for
parameters and results. These structs ensure type safety and include
helper methods for safely parsing and sanitizing user-provided inputs
(e.g., `GetMagnitude`, `GetInitialAttempt`).
- **`internal` submodule**: Contains the core business logic and
implementation of all primary workflows.
- **Why**: This module encapsulates the complex orchestration of building
Chrome, running benchmarks, and analyzing results.
- **How**: It implements key workflows like `BisectWorkflow`, which uses a
concurrent strategy to analyze multiple commit ranges in parallel, and
`PairwiseCommitsRunnerWorkflow`, which runs tests back-to-back on the
same bot to minimize environmental noise. It interacts with external
services like Swarming and Buildbucket through dedicated activities.
- **`catapult` submodule**: A compatibility layer to support the legacy
Catapult UI.
- **Why**: Ensures a smooth transition from the old Catapult backend by
allowing the legacy frontend to work with the modern, Temporal-based
system. It is designed for eventual deprecation.
- **How**: It acts as an adapter. Workflows in this module, like
`CatapultBisectWorkflow`, orchestrate the core `internal` workflows and
then transform their results into the legacy data format expected by
Catapult's datastore and UI.
- **`worker` submodule**: The executable that runs the workflow and activity
logic.
- **Why**: To separate the business logic from the runtime environment.
The same workflow code can be executed by different worker
configurations for development, staging, or production.
- **How**: The `worker` binary connects to the Temporal server, registers
all the workflow and activity functions defined in `internal` and
`catapult`, and polls a task queue for jobs to execute.
- **`sample` and `experiment` submodules**: Command-line tools for invoking
workflows.
- **Why**: To provide developers with a direct way to trigger and debug
workflows, and to run specialized experiments without needing the full
Pinpoint UI.
- **How**: These are simple client applications that use the Temporal Go
SDK and the public contracts from `workflows.go` to start workflows with
specific parameters from the command line.
### High-Level Workflow Architecture
The diagram below illustrates how a client's request is processed. The client
uses the public definitions to start a workflow, which is then picked up and
executed by a `worker` process.
```mermaid
graph TD
subgraph "Client"
A["Pinpoint API / `sample` CLI"]
end
subgraph "Temporal_Server"
B["Task Queue"]
end
subgraph "Worker_Process"
C["Pinpoint Worker"]
end
A -- "1. ExecuteWorkflow (uses `workflows.go` definitions)" --> B
C -- "2. Polls for tasks" --> B
B -- "3. Dispatches task" --> C
C -- "4. Executes implementation (from `internal`, `catapult`)" -->
D["External Services <br/> (Swarming, Buildbucket)"]
```
# Module: /go/workflows/catapult
The `/go/workflows/catapult` module acts as a compatibility layer, enabling
Pinpoint's modern, Skia-based bisection and culprit-finding logic to integrate
with the legacy Catapult UI and datastore. This ensures continuity for existing
Catapult users during the transition to a new Pinpoint backend, with the module
designed for eventual deprecation.
The module's design focuses on:
- **Backwards Compatibility**: Bridging the data model and API differences
between modern Pinpoint and legacy Catapult.
- **Orchestration**: Utilizing Temporal workflows to manage the multi-step
process of bisection, data transformation, and persistence.
- **Data Transformation**: Converting complex Pinpoint bisection results into
the specific `pinpoint_proto.LegacyJobResponse` format expected by
Catapult's frontend.
- **Regression Verification**: Implementing a "sandwich verification" pattern
to confirm regressions and accurately identify culprits, minimizing false
positives.
### Key Components
- **`CatapultBisectWorkflow` (in `catapult_bisect.go`)**: Orchestrates the
execution of a Pinpoint bisection for Catapult. It invokes the core
`internal.BisectWorkflow` for bisection logic, then delegates to
`ConvertToCatapultResponseWorkflow` for data transformation, and finally
uses `WriteBisectToCatapultActivity` to persist results to the Catapult API.
- **`ConvertToCatapultResponseWorkflow` (in `catapult_bisect.go`)**: A child
workflow that maps modern Pinpoint bisection results and request parameters
into the legacy `pinpoint_proto.LegacyJobResponse` structure. This includes
structuring "states," "attempts," and "comparisons" as required by the
Catapult UI.
- **`CulpritFinderWorkflow` (in `culprit_finder.go`)**: Implements the
"sandwich verification" process. It first uses `PairwiseWorkflow` to confirm
an initial regression, then `CatapultBisectWorkflow` to find potential
culprits, and finally re-verifies each culprit with `PairwiseWorkflow` to
confirm its significance. Optionally, it can trigger external culprit
processing.
- **`parsers.go`**: Contains comprehensive logic for translating data between
Pinpoint's internal models and Catapult's legacy proto. This includes
extracting and formatting commit details (author, message, commit position),
run data (values, bot IDs, CAS references), and comparison results.
- **`activites.go`**: Provides Temporal activities for interacting with
external systems:
- `FetchCommitActivity`: Retrieves rich commit metadata from Gitiles.
- `FetchTaskActivity`: Gathers detailed Swarming task information.
- `WriteBisectToCatapultActivity`: Executes the final write operation to
the legacy Catapult API.
- **`write.go`**: Defines the `CatapultClient` and its `WriteBisectToCatapult`
method, responsible for authenticated HTTP POST requests to the legacy
Catapult API endpoints to store bisection results.
### Core Workflow: Catapult Bisection
```mermaid
graph TD
A["CatapultBisectWorkflow Start"] --> B{"Generate Workflow ID"}
B --> C["Execute Child Workflow: internal.BisectWorkflow<br/>
(Perform Skia-based Bisection)"]
C -- "BisectExecution" --> D["Execute Child Workflow:
ConvertToCatapultResponseWorkflow<br/>
(Transform to Legacy Catapult Format)"]
D -- "LegacyJobResponse" --> E["Execute Activity:
WriteBisectToCatapultActivity<br/>(Persist to Catapult Datastore)"]
E -- "DatastoreResponse" --> F["Log Datastore Response"]
F --> G["CatapultBisectWorkflow End"]
```
### Core Workflow: Culprit Finder (Sandwich Verification)
```mermaid
graph TD
A["CulpritFinderWorkflow Start"] --> B{"Execute Child Workflow:
PairwiseWorkflow<br/>(Verify Initial Regression)"}
B -- "PairwiseExecution (initial regression)" --> C{"Is Regression
Significant?"}
C -- No --> D["End (No Regression Detected)"]
C -- Yes --> E["Execute Child Workflow: CatapultBisectWorkflow<br/>
(Find Potential Culprits)"]
E -- "BisectExecution (potential culprits)" --> F{"Are Culprits Found?"}
F -- No --> G["End (Regression Detected, No Culprits Found)"]
F -- Yes --> H["Parallel: For Each Culprit:<br/>Run Culprit Verification
(PairwiseWorkflow)"]
H -- "Verified Culprits" --> I["Invoke Culprit Processing Workflow
(Optional)"]
I --> J["End (Regression Detected, Culprits Verified)"]
```
# Module: /go/workflows/experiment
The `experiment` module is a command-line interface for initiating Chrome
performance experiments through Temporal. It abstracts experiment setup and
execution by delegating to Temporal workflows, ensuring durable and observable
execution.
Its primary role is to translate user-defined experiment parameters (benchmark,
commit, bot, iterations) into a request for the `workflows.TestAndExport`
Temporal workflow. The `TestAndExport` workflow then orchestrates Chrome builds,
triggers Swarming tasks, and exports task IDs to BigQuery. This design separates
experiment initiation from durable, fault-tolerant execution.
### Key Component:
- **`main.go`**: Contains the executable logic: parses flags, connects to
Temporal, constructs `internal.TestAndExportParams`, and invokes the
`workflows.TestAndExport` workflow. The client initiates the workflow with a
unique ID and a single-attempt retry policy, meaning it won't auto-retry
_starting_ the workflow if the initial call fails.
### Workflow:
```mermaid
graph TD
A["User executes `experiment` CLI"] --> B{"Parses Flags"}
B --> C["Connects to Temporal Service"]
C --> D["Constructs TestAndExportParams"]
D --> E["Triggers workflows.TestAndExport Temporal Workflow"]
E -- "Exports Swarming Task IDs to BQ" --> F["External System (e.g.,
`collectResults`) for result collection"]
```
# Module: /go/workflows/internal
This module contains the core implementation of Pinpoint's Temporal workflows.
It orchestrates complex tasks such as performance bisection, A/B testing
(pairwise), and continuous benchmarking for the Chrome for Benchmarking (CBB)
project. The design separates concerns into distinct workflows and activities
for building Chrome, running tests, analyzing results, and interacting with
external services like Swarming, Buildbucket, and Gerrit.
### Bisection Workflows
The primary goal is to automatically find the single commit that introduced a
performance regression within a given commit range.
- **`bisect.go`**: Implements the main `BisectWorkflow`. It employs a
concurrent bisection strategy using a `workflow.Selector` to manage multiple
benchmark runs and comparisons simultaneously. This non-blocking approach
allows the workflow to efficiently explore different sub-ranges of the
original commit range in parallel. If a comparison between two commits is
statistically inconclusive, the workflow dynamically increases the number of
test runs to gather more data before making a decision.
- **`bisect_run.go`**: Provides the `BisectRun` struct, which abstracts the
state of benchmark runs for a single commit. This is crucial because a
commit's runs may be initiated by different bisection sub-problems or
increased in number over time. It decouples the management of test runs from
the main bisection logic.
- **`midpoint.go`**: Contains the `FindMidCommitActivity`, which is essential
for the bisection algorithm. Its logic is more complex than a simple
`(start+end)/2` because it must correctly handle dependency rolls within the
Chromium `DEPS` file, ensuring the true midpoint is found even when the
regression is in a dependency like V8 or Skia.
```mermaid
graph TD
A[Start Bisect] --> B{Initial Pair Runs};
B --> C{Compare Results};
C -- Different --> D{Find Midpoint};
C -- Same --> E[No Culprit Found];
C -- Unknown --> F{Schedule More Runs};
F --> C;
D --> G{Is Range a Single Commit?};
G -- Yes --> H[Report Culprit];
G -- No --> I{Create New Sub-Ranges};
I --> J[Run Benchmark on Midpoint];
J --> C;
```
### Pairwise Comparison Workflows
These workflows provide a statistically rigorous A/B comparison between two
specific commits or builds. The design prioritizes minimizing environmental
noise.
- **`pairwise.go`**: The main `PairwiseWorkflow` orchestrates the comparison,
persists job status and results to a database via `database_activities.go`,
and handles the overall lifecycle of a pairwise job.
- **`pairwise_runner.go`**: Implements the `PairwiseCommitsRunnerWorkflow`.
Its key design choice is to execute benchmark pairs back-to-back on the
_same physical bot_, alternating the run order (A then B, B then A). This
minimizes noise from machine-to-machine variations. The workflow also
includes logic to balance the dataset by discarding pairs if one of the runs
fails, preserving the statistical integrity of the analysis.
- **`compare.go`**: Defines activities that perform the statistical analysis.
It strategically separates functional analysis (e.g., crash rates) from
performance analysis. A significant functional difference often indicates a
clear regression and can conclude the analysis early without needing to
evaluate performance metrics.
### Benchmark Execution and Data Collection
These are the foundational components that perform the actual work of building
and testing.
- **`commits_runner.go`**: The `SingleCommitRunner` workflow is a reusable
unit that orchestrates the entire process for one commit: building, running
a set of benchmarks, and collecting all results. It is the primary building
block for both bisection and pairwise workflows.
- **`build_workflow.go`**: Encapsulates all interaction with the build system
(Buildbucket). It intelligently finds existing builds to reuse or triggers
new ones, waits for completion, and fetches the resulting build artifact
(CAS reference).
- **`run_benchmark.go`**: Manages the execution of a single benchmark test on
Swarming. It includes robust polling and retry logic, especially for
`NO_RESOURCE` errors, which is critical for reliability. The
`RunBenchmarkPairwiseWorkflow` specifically handles scheduling two tasks
back-to-back on the same bot.
- **`fetch_and_process.go`**: Provides a data pipeline to post-process
results. It can query a BigQuery log of all Swarming tasks for a workflow,
retrieve detailed sample values for each task, and upload the processed data
to a separate BQ table for analysis.
### Chrome for Benchmarking (CBB) Workflows
This is a specialized system for continuously monitoring the performance of
major browsers.
- **`cbb_new_release_detector.go`**: The top-level workflow that periodically
checks for new official releases of Chrome, Edge, and Safari.
- **`chrome_releases_info.go`**: Fetches the latest browser version
information (e.g., from Chromium Dash). It compares this with historical
data stored in GCS (`gcs_store.go`) to detect new versions. Upon detecting a
new release, it commits updated metadata files into the Chromium repository
using the `git.go` activities.
- **`cbb_runner.go`**: Triggered after a new browser release is committed. It
runs a standard suite of CBB benchmarks (e.g., Speedometer3, Jetstream2). A
key responsibility is to format the raw benchmark output into the standard
Perf ingestion format and upload it to a GCS bucket, where it can be picked
up by the Skia Perf dashboard.
- **`stp_downloader.go`**: A utility workflow to automatically download new
versions of Safari Technology Preview and package them for deployment to
test devices via CIPD.
### Supporting Components
- **`database_activities.go`**: Provides a clean interface for workflows to
interact with a SQL database for persisting job state, parameters, and
results. This supports the Pinpoint UI.
- **`bug_update.go`**: Contains activities to interact with an issue tracker,
enabling the system to automatically post comments on bugs with the results
of a bisection or pairwise run.
- **`options.go`**: Centralizes all Temporal retry policies and timeouts. This
separation makes it easy to configure and maintain the reliability and
execution constraints for different types of workflows and activities (e.g.,
short local activities vs. long-running builds).
- **`testdata`**: Contains data for reproducible tests of the complex
bisection logic, including `DEPS` files with specific commit hashes and a
recorded Temporal event history.
# Module: /go/workflows/internal/testdata
This module contains sample data and configurations essential for testing the
`go/workflows` bisection logic.
`DEPS` files (e.g., `LatestChromiumDEPS`, `NMinusOneV8DEPS`) define specific,
fixed commit hashes for external dependencies like V8 and Skia. These are used
to create reproducible test scenarios for the bisection algorithm, representing
"good" and "bad" states or distinct points in a commit range. This ensures that
the bisection process can be consistently evaluated against known commit
references.
`bisect_event_history_20240627.json` captures a complete `perf.bisect` workflow
execution as a Temporal event history. It serves as a golden test case to
validate the workflow's intricate logic and state transitions, demonstrating how
the system iteratively narrows down a problematic commit. This file is crucial
for verifying that the bisection correctly orchestrates child workflows and
activities to pinpoint performance regressions.
The bisection workflow iteratively tests commit ranges:
```mermaid
graph TD
A["perf.bisect Workflow Started"] --> B{"FindAvailableBots"}
B --> C1["Run Single Commit (Lower Bound)"]
B --> C2["Run Single Commit (Higher Bound)"]
C1_RES("Lower Results") --> D{"Collect Data for Comparison"}
C2_RES("Higher Results") --> D
D --> E{"CompareActivity"}
E -- "Significant Difference" --> F{"FindMidCommit"}
F --> G{"Check If Range Is Single Commit"}
G -- "Not Single Commit" --> H["Run Single Commit (Mid Commit)"]
H --> H_RES("Mid Results")
H_RES --> D
G -- "Single Commit" --> I["Report Bisected Commit"]
```
# Module: /go/workflows/sample
# `sample`: A CLI for Triggering Pinpoint Workflows
The `sample` module is a command-line tool for developers to manually trigger
and test various Pinpoint workflows. It serves as a sample client
implementation, demonstrating how to programmatically start workflows like
bisection, culprit finding, and pairwise runs using the Temporal Go SDK. Its
primary use is for development and debugging.
### Design and Implementation
The tool is designed as a simple, flag-driven application for ease of use during
development.
- **Why:** This approach provides a straightforward way to test individual
workflows with specific parameters without requiring the full Pinpoint
frontend or API. It consolidates example invocations for all major workflows
into a single, executable file.
- **How:** The `main` function parses command-line flags. A dedicated boolean
flag (e.g., `--bisect`, `--single-commit`) selects which workflow to run.
The application establishes a connection to the Temporal server, and based
on the selected flag, calls a corresponding `trigger...` function.
### Key Components
**`main.go`** is the single file containing all logic.
- **Workflow Invocation:** Its primary responsibility is to translate
command-line arguments into a specific workflow execution request. Each
`trigger...` function (e.g., `triggerBisectWorkflow`,
`triggerCulpritFinderWorkflow`) is tailored to a single workflow. It
populates the workflow's parameter struct from the command-line flags and
uses the Temporal client's `ExecuteWorkflow` method to start it.
- **Client Connection:** It initializes and manages the Temporal client
connection (`client.Dial`), which is necessary to communicate with the
Temporal server.
- **Result Handling:** After triggering a workflow, it synchronously waits for
the result using the `Get` method on the workflow handle and prints the
outcome to the console for immediate feedback.
### Workflow Execution Process
The following diagram illustrates the typical flow when a user runs the `sample`
tool.
```mermaid
graph TD
A["User runs ./sample with flags, e.g., --bisect"] --> B{"Parse Flags"}
B --> C["Connect to Temporal Server"]
C --> D{"Select trigger function based on flag"}
D -- "e.g., --bisect" --> E["triggerBisectWorkflow"]
subgraph E ["triggerBisectWorkflow"]
direction LR
E1["Build BisectParams from flags"] --> E2["client.ExecuteWorkflow"]
E2 --> E3["Wait for result"]
end
E3 --> F["Print workflow result to console"]
```
# Module: /go/workflows/worker
The Pinpoint Worker is the executable that runs the business logic for all
Pinpoint jobs, such as performance bisections and pairwise analyses. It operates
as a [Temporal worker](https://docs.temporal.io/workers), connecting to a
Temporal cluster to poll for and execute tasks.
### Design and Implementation
The worker is designed to be a scalable and resilient processor for
long-running, stateful operations. Using the Temporal framework abstracts away
the complexities of distributed systems, allowing developers to focus on the
core business logic of a job.
The worker's lifecycle and operational model is as follows:
1. **Initialization**: On startup, it parses command-line flags to configure
its connection to the Temporal server (`--hostPort`, `--namespace`) and the
specific task queue it will listen to (`--taskQueue`). This flag-based
configuration allows the same binary to be used in different environments
(local, staging, production).
2. **Registration**: It registers all the workflow and activity functions it is
capable of executing. This acts as a contract with the Temporal server,
indicating which tasks this worker can handle. For example, it registers
`internal.BisectWorkflow` with the name `workflows.Bisect`.
3. **Execution**: The worker enters a polling loop, continuously asking the
Temporal server for tasks from its designated queue. When it receives a
task, it executes the corresponding registered function.
A key design choice is the use of the `--databaseWriteback` feature flag. This
conditionally registers a set of activities that write job results to a new
PostgreSQL database. This enables incremental migration and testing of a new
data backend without disrupting existing functionality.
```mermaid
graph TD
A[Start ./worker] --> B[Connect to Temporal Server];
B --> C[Listen on a specific Task Queue];
C --> D[Register all Workflow and Activity implementations];
D --> E{Poll for tasks};
E -- Receives Task --> F[Execute corresponding Workflow/Activity code];
F -- Task Complete --> E;
```
### Key Components
- **`main.go`**: This is the application's entry point. Its primary
responsibility is to initialize and configure the Temporal worker. It brings
together all the disparate workflow and activity implementations from
sub-packages (e.g., `internal`, `catapult`) and registers them with the core
worker engine. It also handles the setup of essential services like tracing
and metrics.
- **`BUILD.bazel`**: Defines the rules to build the `worker` Go binary and
package it into a `skia_app_container` named `bisect_workflow`. This
container is the deployable artifact for the Pinpoint backend.
# Module: /proto
This module defines the Pinpoint service's public API using Protocol Buffers. It
provides strongly-typed data structures for performance analysis workflows like
bisection and pairwise comparison. The API is accessible via both gRPC and a
RESTful JSON interface (via gRPC-Gateway).
### API Design
The API is defined in a single source of truth, `service.proto`, from which
server stubs, client libraries, and the REST gateway are auto-generated. This
approach ensures consistency across all interfaces.
The design favors explicit, job-specific RPCs (`ScheduleBisection`,
`SchedulePairwise`) over a generic one. This improves type safety and makes the
API's intent clear, as each request message contains only parameters relevant to
its specific workflow.
A key abstraction is the `CombinedCommit` message. It models a specific code
state not just by a git hash, but also by including potential DEPS modifications
and Gerrit patches. This is crucial for accurately reproducing and testing
complex changes, such as dependency rolls or in-flight CLs.
To ensure a smooth migration from the previous system, `LegacyJobRequest` and
`LegacyJobResponse` mirror the legacy Catapult Pinpoint API, providing backward
compatibility for existing clients.
### Key Files
- **`service.proto`**: The core API definition. It contains all RPC service
methods, message structures, and the HTTP annotations that map gRPC services
to RESTful endpoints.
- **`service.pb.go`**, **`service_grpc.pb.go`**, **`service.pb.gw.go`**:
Auto-generated Go language bindings for the protobuf messages, gRPC
interfaces, and REST gateway handlers. These files should not be modified
directly.
- **`generate.go`**: Uses `//go:generate` directives to document and automate
the commands for regenerating Go files from the `.proto` definition,
ensuring a repeatable process.
### Culprit Finder Workflow
The `ScheduleCulpritFinder` RPC initiates a composite workflow to identify the
specific commit that caused a performance regression.
```mermaid
graph TD
A["Client sends ScheduleCulpritFinderRequest"] --> B{"Pinpoint Service"}
B --> C("1. Regression Verification <br><i>Pairwise job between start/end
commits</i>")
C -- "Regression Confirmed" --> D("2. Bisection <br><i>Bisect job to find
potential culprits</i>")
C -- "No Regression" --> E["Workflow Ends: No culprits"]
D -- "Culprit(s) Found" --> F("3. Culprit Verification
<br><i>Pairwise job between each culprit and its parent</i>")
D -- "No Culprit Found" --> E
F -- "Verified" --> G["Workflow Ends: Culprit(s) reported"]
F -- "Not Verified" --> H["Workflow Ends: Bisection error or false positive"]
```
# Module: /proto/v1
This module defines the public API for the Pinpoint service using Protocol
Buffers. It provides strongly-typed data structures and service endpoints for
performance analysis tasks like bisection and pairwise comparison. The API is
designed to be accessed via both gRPC and a RESTful JSON interface, which is
enabled by gRPC-Gateway.
### API Design
The API is defined in `service.proto` and serves as the single source of truth.
This choice enables auto-generation of server stubs, client libraries, and the
REST gateway, ensuring consistency across different interfaces.
The design favors explicit, job-specific RPCs (`ScheduleBisection`,
`SchedulePairwise`, `ScheduleCulpritFinder`) over a single generic endpoint.
This improves type safety and makes the API's intent clear, as each request
message contains only the parameters relevant to its specific workflow.
A key abstraction is the `CombinedCommit` message. It models a specific state of
the code not just by a git hash, but also by including potential DEPS
modifications (`modified_deps`) and Gerrit patches (`patch`). This is crucial
for accurately reproducing and testing complex changes, such as those involving
dependency rolls or in-flight CLs.
To ensure a smooth migration from the previous system, the API includes
`LegacyJobRequest` and `LegacyJobResponse`. These messages mirror the structure
of the legacy Catapult Pinpoint API, providing backward compatibility for
existing clients like the Pinpoint UI.
### Key Files
- **`service.proto`**: The core API definition file. It contains all RPC
service methods, message structures (requests, responses, and data models
like `Commit`), and enums (`SwarmingStatus`, `PairwiseJobStatus`). It also
includes HTTP annotations that map gRPC services to RESTful endpoints.
- **`service.pb.go`**, **`service_grpc.pb.go`**, **`service.pb.gw.go`**: These
files are auto-generated from `service.proto`. They provide the Go language
bindings for the protobuf messages, the gRPC client and server interfaces,
and the reverse-proxy handlers for the RESTful gateway, respectively.
Application code should import and use these generated packages, not modify
them directly.
- **`generate.go`**: This file uses `//go:generate` directives to document and
automate the commands needed to regenerate the Go files from the
`service.proto` definition. This ensures the generation process is
repeatable and consistent.
### Culprit Finder Workflow
The `ScheduleCulpritFinder` RPC initiates an end-to-end workflow to identify the
specific commit that caused a performance regression between two points in
history. This is a composite workflow that internally leverages other Pinpoint
capabilities.
```mermaid
graph TD
A["Client sends ScheduleCulpritFinderRequest"] --> B{"Pinpoint Service"}
B --> C("1. Regression Verification <br><i>Pairwise job between start/end
commits</i>")
C -- "Regression Confirmed" --> D("2. Bisection <br><i>Bisect job to find
potential culprits</i>")
C -- "No Regression" --> E["Workflow Ends: No culprits"]
D -- "Culprit(s) Found" --> F("3. Culprit Verification
<br><i>Pairwise job between each culprit and its parent</i>")
D -- "No Culprit Found" --> E
F -- "Verified" --> G["Workflow Ends: Culprit(s) reported"]
F -- "Not Verified" --> H["Workflow Ends: Bisection error or false
positive"]
```
# Module: /ui
### Overview
The Pinpoint UI is a single-page application built with LitElement and
TypeScript. Its architecture is designed for modularity and a clear separation
of concerns, organized into three primary areas:
- **`pages`**: Minimal HTML files that serve as entry points for different
application views.
- **`modules`**: A collection of reusable LitElement web components that form
the building blocks of the UI.
- **`services`**: A centralized API client for all backend communication.
### Architectural Principles
The UI is built on a few key design principles to ensure maintainability and a
predictable data flow.
#### Component-Based Structure
The application follows a "shell and component" model. Each file in `/pages` is
a minimal HTML shell whose sole purpose is to load a single root web component
from `/modules`. This design delegates all rendering, state management, and user
interaction logic to the components, keeping the page entry points clean and
simple.
#### Unidirectional Data Flow
Components are divided into two main categories to enforce a clear, one-way data
flow:
1. **Controller Components** (e.g., `pinpoint-landing-page-sk`,
`pinpoint-results-page-sk`): These are stateful components that manage a
view. They are responsible for fetching data from the API service, managing
the view's state (e.g., filters, pagination), and passing data down to child
components via properties.
2. **Presentational Components** (e.g., `jobs-table-sk`,
`commit-run-overview-sk`): These are stateless ("dumb") components focused
solely on rendering data. They receive data as properties and communicate
user interactions (like a button click or sort request) back up to their
parent controller by dispatching custom events. They contain no business
logic.
This pattern makes components more reusable and the application easier to reason
about, as data flows down and events flow up.
#### Centralized API Service
All communication with the Pinpoint backend is consolidated in
`/services/api.ts`. This file serves as the single source of truth for API
interactions.
- **Why**: Centralizing the API client decouples UI components from the
specifics of HTTP requests. Any changes to backend endpoints only need to be
updated in this one file.
- **How**: It exports `async` functions (e.g., `listJobs`, `getJob`) that
handle making `fetch` calls. It also defines TypeScript interfaces (`Job`,
`Commit`) that mirror the Go structs on the backend, creating a strong,
type-safe contract between the frontend and backend.
### Key Workflows and Components
#### Job List and Filtering
The landing page demonstrates the core unidirectional data flow pattern. The
`pinpoint-scaffold-sk` provides the global UI, including filter controls. When a
user applies a filter, the scaffold dispatches an event. The
`pinpoint-landing-page-sk` (the controller) listens for this event, updates its
state and the URL, fetches the filtered data using the API service, and passes
the new list of jobs down to the `jobs-table-sk` for rendering.
```mermaid
sequenceDiagram
actor User
participant Scaffold as pinpoint-scaffold-sk
participant LandingPage as pinpoint-landing-page-sk
participant PinpointAPI as services/api.ts
participant JobsTable as jobs-table-sk
User->>Scaffold: Changes filter and clicks "Apply"
Scaffold->>LandingPage: Dispatches 'filters-changed' event
LandingPage->>LandingPage: Updates internal state & browser URL
LandingPage->>PinpointAPI: listJobs(new filter state)
PinpointAPI-->>LandingPage: Returns filtered jobs
LandingPage->>JobsTable: Passes new 'jobs' property
JobsTable-->>User: Renders updated table
```
#### Creating a New Job
The `pinpoint-new-job-sk` component encapsulates the entire workflow for
scheduling a new job. It is a modal dialog that contains the form, validation
logic, and the call to the `schedulePairwise` function in the API service. This
keeps the complexity of job creation isolated within a single component.
#### Viewing Job Results
The `pinpoint-results-page-sk` acts as a controller for the results view. It
reads the job ID from the URL, fetches the corresponding job data, and composes
various presentational components (`commit-run-overview-sk`,
`wilcoxon-results-sk`) to display different aspects of the results. It
conditionally renders components based on the job's type and status.
# Module: /ui/modules
### Overview
The `/ui/modules` directory contains the LitElement-based web components that
constitute the Pinpoint user interface. The architecture is designed around a
clear separation between stateful "page" (controller) components and stateless
"presentational" components.
This design promotes reusability and a unidirectional data flow. Page components
manage application state, fetch data from the API, and orchestrate the UI.
Presentational components receive data via properties and communicate user
interactions back to their parent via custom events, without containing any
business logic themselves.
### Component Architecture
#### Page and Controller Components
These components manage the primary views and application state.
- **`pinpoint-landing-page-sk`**: The main entry point for the jobs list. Its
core responsibility is managing state, including filters, sorting, and
pagination, and synchronizing it with the browser's URL. It fetches data and
passes it down to presentational components like `jobs-table-sk`.
- **`pinpoint-results-page-sk`**: Displays the detailed results of a single
Pinpoint job. It fetches all necessary data based on the Job ID in the URL
and composes other components (`commit-run-overview-sk`,
`wilcoxon-results-sk`) to render different aspects of the job result. It
conditionally displays content based on the job's status and type.
#### Structural and Modal Components
These components provide the overall application structure and on-demand
dialogs.
- **`pinpoint-scaffold-sk`**: Provides the main application shell, including a
consistent header with global actions like search and filtering. It uses a
`<slot>` to allow page components to inject their content. It is designed to
be decoupled from the page's content, communicating user actions (e.g.,
filter changes) via custom events.
- **`pinpoint-new-job-sk`**: A modal dialog for scheduling new jobs. It
encapsulates the form logic, client-side validation, and API calls needed to
create a job. It offers both a simplified and a detailed view to cater to
different user needs.
- **`job-overview-sk`**: A simple modal used to display the formatted
configuration parameters of a job.
#### Presentational Components
These are "dumb" components that focus solely on rendering data.
- **`jobs-table-sk`**: Renders a list of jobs in a table. It is stateless
regarding data fetching. User actions, such as clicking a column header to
sort, trigger custom events (`sort-changed`). The parent component is
responsible for handling these events, re-sorting the data, and passing it
back down.
- **`commit-run-overview-sk`**: Provides a compact, visual summary of test
runs for a single commit. Its key design decision is to define a
"successful" run as one that produces metric data, visually marking runs
with no values as failures (red).
- **`wilcoxon-results-sk`**: Renders the statistical results from a completed
pairwise job. It translates the raw statistical data into a color-coded
table, making it easy to interpret whether a change caused a significant
performance improvement or regression.
### Core Workflow: Viewing and Filtering Jobs
The interaction between the scaffold, landing page, and table demonstrates the
application's event-driven architecture.
```mermaid
sequenceDiagram
actor User
participant Scaffold as pinpoint-scaffold-sk
participant LandingPage as pinpoint-landing-page-sk
participant JobsTable as jobs-table-sk
participant PinpointAPI
User->>Scaffold: Changes filter and clicks "Apply"
Scaffold->>LandingPage: Dispatches 'filters-changed' event
LandingPage->>LandingPage: Updates state & URL
LandingPage->>PinpointAPI: listJobs(new filter state)
PinpointAPI-->>LandingPage: Returns filtered jobs
LandingPage->>JobsTable: Passes new 'jobs' property
JobsTable-->>User: Renders updated table
```
# Module: /ui/modules/commit-run-overview-sk
The `commit-run-overview-sk` module provides a UI component to visualize the
results of a build and its associated test runs for a single commit.
### Design and Implementation
The primary goal of this component is to offer a compact, at-a-glance summary of
a performance test execution for a given commit. This is particularly useful in
contexts like bisection, where users need to quickly assess the outcome at each
step.
It is implemented as a self-contained LitElement (`commit-run-overview-sk.ts`),
making it a reusable presentation component. It's designed to be stateless
regarding data fetching; it receives the necessary `CommitRunData` and
`JobSchema` objects via properties. This separation of concerns keeps the
component focused solely on rendering.
A key implementation detail is how it determines the success or failure of a
test run. Instead of relying on a status field, the component's `isEmptyValues`
method checks for the _presence of metric values_. A run that completes but
produces no metric data is visually marked as a "failure" (a red box). This
design choice reflects the fact that for performance testing, a run without
results is not a successful run.
To maintain a clean and uncluttered main view, detailed information for each
test run is presented on-demand in a modal dialog (`md-dialog`).
### Key Components
- **`commit-run-overview-sk.ts`**: This file contains the complete
implementation of the `CommitRunOverviewSk` web component. Its
responsibilities include:
- Rendering the high-level build information, such as the commit hash and
a link to the build log.
- Displaying a grid of small, colored squares, where each square
represents a single test iteration (`TestRun`). The square's color
(green for success, red for failure) is determined by the logic
described above.
- Handling user interaction. A click on any run's square triggers a dialog
that shows detailed metadata for that specific run, including links to
the Swarming task and CAS output.
### Workflow
The component follows a simple interactive workflow for displaying detailed run
information.
```mermaid
graph TD
A["Component receives CommitRunData"] --> B{"Render View"}
B --> C["Display build info"]
B --> D["Display grid of colored run boxes"]
D -- "Click a run box" --> E{"showRunDetails()"}
E --> F["Update state with selected run"]
F --> G["Show md-dialog with run details"]
G -- "Click 'Close'" --> H["Hide dialog"]
```
# Module: /ui/modules/job-overview-sk
### Module: `job-overview-sk`
This module provides a UI component, `<job-overview-sk>`, designed to display
the configuration parameters of a Pinpoint job in a modal dialog. Its purpose is
to offer users a clear, formatted, and easily accessible summary of a job's
inputs without cluttering the primary interface.
#### Design and Implementation
The component is implemented as a LitElement that wraps a Material Web
`<md-dialog>`. This choice provides a consistent, modern UI for presenting
contextual information on-demand.
The core responsibility of `job-overview-sk` is to transform a raw `JobSchema`
object into a human-readable format. This is achieved through several key design
choices:
- **Data Aggregation**: It combines top-level properties (e.g., `Benchmark`,
`BotName`) with nested parameters from `AdditionalRequestParameters` into a
single, unified list. This provides a comprehensive view of all relevant job
arguments.
- **Selective Filtering**: To maintain clarity, certain parameters deemed
internal or less relevant for an overview (like `commit_runs` and
`duration`) are explicitly filtered out.
- **User-Friendly Formatting**:
- Technical keys (e.g., `start_commit_githash`) are mapped to more
descriptive labels (e.g., "Start Commit") via the `formatKey` method.
This makes the information more accessible to a wider audience.
- Complex values, such as nested JSON objects, are automatically
pretty-printed using `renderParameterValue`, ensuring they are readable
within the dialog.
#### Key Components
- **`job-overview-sk.ts`**: The LitElement component definition. It accepts a
`job` property of type `JobSchema`. The component remains hidden until its
public `show()` method is invoked, which then displays the dialog with the
formatted job data.
#### Workflow
The component is designed to be controlled by a parent element. The parent is
responsible for passing the job data and triggering the dialog's visibility.
```mermaid
sequenceDiagram
participant Parent as Parent Component
participant User
participant E as job-overview-sk
Parent->>E: Sets [job] property with JobSchema data
User->>Parent: Clicks a "View Arguments" button
Parent->>E: Calls show() method
E-->>User: Displays dialog with formatted job parameters
User->>E: Clicks "Close" button
E-->>User: Dialog closes
```
# Module: /ui/modules/jobs-table-sk
### Module: `/ui/modules/jobs-table-sk`
#### Overview
`jobs-table-sk` is a presentational web component that displays a list of
Pinpoint jobs in a sortable, interactive table. Its primary purpose is to render
job data provided to it, without managing any application state or data fetching
itself.
#### Design and Implementation
The component is designed to be "stateless" or "dumb". It receives all necessary
data, including the list of `jobs` and the current sorting state (`sortBy`,
`sortDir`), via properties. This design decouples the component from business
logic, making it highly reusable and straightforward to test.
Instead of directly modifying data or calling APIs, the component communicates
user actions to its parent via custom events.
- **Sorting**: When a user clicks a column header, `jobs-table-sk` emits a
`sort-changed` event. It does not re-sort the data internally. The parent
component is responsible for listening to this event, re-fetching or
re-sorting the job list, and then passing the updated data back down to the
table.
- **Cancellation**: A "Cancel" button is conditionally rendered for jobs that
are 'Pending' or 'Running'. Clicking it fires a `cancel-job-clicked` event,
delegating the responsibility of making the API call to cancel the job to
the parent.
This event-driven, unidirectional data flow simplifies the component's
responsibility to just rendering and user input translation.
#### Key Components
- `jobs-table-sk.ts`: This file contains the complete definition of the
`JobsTableSk` Lit element. It defines the component's properties (`jobs`,
`sortBy`, `sortDir`), renders the table structure, handles click events on
headers to dispatch sorting events, and conditionally displays action
buttons.
#### Workflow: Sorting Jobs
The following diagram illustrates the interaction between the user, the
`jobs-table-sk` component, and its parent when sorting the table.
```mermaid
sequenceDiagram
participant User
participant jobs-table-sk
participant Parent Component
User->>jobs-table-sk: Clicks a column header
jobs-table-sk->>jobs-table-sk: Determines new sort key and direction
jobs-table-sk-->>Parent Component: Dispatches 'sort-changed' event
Parent Component->>Parent Component: Updates state, fetches/sorts data
Parent Component-->>jobs-table-sk: Passes new 'jobs', 'sortBy', and 'sortDir' properties
jobs-table-sk->>jobs-table-sk: Re-renders with sorted data
```
# Module: /ui/modules/pinpoint-landing-page-sk
### Overview
The `pinpoint-landing-page-sk` element is the main entry point for the Pinpoint
application. It orchestrates the display of a paginated, filterable, and
sortable list of Pinpoint jobs. Its primary design goal is to manage the
application's state and reflect it in the URL, allowing users to share and
bookmark specific views of the job list.
### Design and Implementation
The component's state, including filters, search terms, sorting preferences, and
the current page, is managed using `stateReflector`. This synchronizes the
component's internal state with the browser's URL query parameters. When the
page loads, it initializes its state from the URL; conversely, any change to the
state (e.g., applying a filter) updates the URL.
This component acts as a controller, delegating rendering and specific UI
interactions to child components:
- **`pinpoint-scaffold-sk`**: Provides the overall page structure, header, and
user controls for searching and filtering. When a user applies a filter,
this component emits an event which `pinpoint-landing-page-sk` handles by
re-fetching the job list with the new filter parameters.
- **`jobs-table-sk`**: Responsible for rendering the list of jobs in a table.
It emits events when a user clicks a column header to sort the data or
clicks a button to cancel a job.
### Key Responsibilities and Workflows
- **Data Fetching**: On initialization or when filters change, the component
calls the `listJobs` API endpoint to retrieve a page of jobs. It handles
loading and error states during this process.
- **Pagination**: It fetches a fixed number of jobs per page. The "Next"
button is enabled if the API returns a full page of results, which is a
simple heuristic for determining if more data is available. Navigation
updates the page number in the state and triggers a new API call with the
appropriate offset.
- **Sorting**: Sorting is performed **client-side**. When the `jobs-table-sk`
emits a `sort-changed` event, this component updates its sorting state and
re-sorts only the currently visible page of jobs before re-rendering. The
sorting parameters are not sent to the backend API.
- **Job Cancellation**: When a user initiates a job cancellation from the
`jobs-table-sk`, this component displays a confirmation dialog. Upon
submission with a required reason, it calls the `cancelJob` API and
refreshes the job list.
```mermaid
sequenceDiagram
participant User
participant URL
participant pinpoint-landing-page-sk as LandingPage
participant pinpoint-scaffold-sk as Scaffold
participant jobs-table-sk as JobsTable
participant PinpointAPI as API
User->>URL: Navigates to page (or refreshes)
URL-->>LandingPage: stateReflector reads initial state
LandingPage->>API: listJobs(state)
API-->>LandingPage: Returns list of jobs
LandingPage->>Scaffold: Renders with filter values
LandingPage->>JobsTable: Renders with job list
User->>Scaffold: Changes a filter
Scaffold->>LandingPage: Emits 'filters-changed' event
LandingPage->>URL: Updates URL via stateReflector
LandingPage->>API: listJobs(new filter state)
API-->>LandingPage: Returns filtered jobs
LandingPage->>JobsTable: Re-renders with new jobs
User->>JobsTable: Clicks column header to sort
JobsTable->>LandingPage: Emits 'sort-changed' event
LandingPage->>URL: Updates URL via stateReflector
Note over LandingPage: Performs client-side sort on current page of jobs
LandingPage->>JobsTable: Re-renders with sorted jobs
```
# Module: /ui/modules/pinpoint-new-job-sk
### `pinpoint-new-job-sk` Module
The `pinpoint-new-job-sk` module provides a modal dialog for scheduling new
Pinpoint performance testing jobs. Pinpoint is used to diagnose performance
regressions or compare performance between two Chrome commits (a "try job").
#### Design and Implementation
The component is designed to accommodate both novice and expert users by
offering two distinct views: "Simplified" and "Detailed".
- **Simplified View**: This view streamlines job creation for common use
cases. It pre-populates the form with a standard test configuration (e.g.,
`speedometer3.crossbench` on `mac-m1-pro-perf`) and only requires the user
to input the start and end commit hashes. This reduces complexity for users
who need a quick, standard performance check.
- **Detailed View**: This provides full control over all job parameters,
including the specific benchmark, device (bot), story, iteration count, and
an optional bug ID for tracking. This view is for users with specific
testing needs.
The form's options are populated dynamically. When a user selects a benchmark in
the detailed view, the component fetches the corresponding lists of compatible
devices and stories from the Pinpoint API. This ensures that users can only
select valid configurations, preventing backend errors.
Client-side validation is performed before submission to provide immediate
feedback on required fields and valid value ranges, such as the iteration count.
User feedback on the job submission status is provided via a non-blocking
`toast-sk` element, which on success includes a direct link to the newly created
job.
#### Key Components
- `pinpoint-new-job-sk.ts`: The core LitElement that defines the modal's UI
and behavior. It manages the component's state, handles user input,
orchestrates API calls to fetch data and schedule jobs, and renders either
the detailed or simplified view.
- `pinpoint-new-job-sk` relies on the `//pinpoint/ui/services:api_ts_lib`
module for all communication with the Pinpoint backend. This includes
fetching benchmarks, bots, and stories, as well as submitting the final job
request via the `schedulePairwise` function.
#### Workflows
**New Job Creation Workflow**
The following diagram illustrates the process of creating a new Pinpoint job
using this component.
```mermaid
sequenceDiagram
participant User
participant PinpointNewJobSk as Modal Component
participant PinpointAPI as API Service
User->>+Modal Component: Opens modal (show())
Modal Component->>+PinpointAPI: listBenchmarks()
PinpointAPI-->>-Modal Component: Returns benchmarks
Modal Component->>+PinpointAPI: listBots()
PinpointAPI-->>-Modal Component: Returns bots
Note over User,Modal Component: User fills in job details
User->>+Modal Component: Clicks "Start"
Modal Component->>Modal Component: Validates form input
alt Form is valid
Modal Component->>+PinpointAPI: schedulePairwise(request)
PinpointAPI-->>-Modal Component: Success (jobId)
Modal Component->>User: Shows success toast with job link
Modal Component->>Modal Component: Dispatches 'pinpoint-job-started' event
Modal Component->>Modal Component: close()
else Form is invalid
Modal Component->>User: Shows error toast
end
```
# Module: /ui/modules/pinpoint-results-page-sk
### Module: `pinpoint-results-page-sk`
#### Overview
`pinpoint-results-page-sk` is a full-page Lit element responsible for displaying
the detailed results of a single Pinpoint job. It serves as the primary view for
users to analyze job outcomes, compare performance between commits, and access
statistical analysis. The component is designed to be self-contained; it fetches
all necessary data based on a Job ID provided in the URL.
#### Design
The core design is centered around providing a comprehensive, state-aware view
of a job.
- **Data-Driven Rendering:** The component's lifecycle begins by parsing the
Job ID from the URL. It then fetches the corresponding job data from the
API. The entire UI is reactive to the state of this fetch operation,
displaying loading, error, or data-rich views accordingly.
- **Component Composition:** Instead of implementing all UI elements within
this module, it composes several specialized child components. It uses
`commit-run-overview-sk` to display details for the base and experimental
commits side-by-side, and `job-overview-sk` to show the job's full
configuration parameters in a dialog. This separation of concerns keeps the
results page focused on layout and state management.
- **Conditional Logic for Results:** A key design choice is how it handles
different job types and statuses. The statistical analysis, rendered by
`wilcoxon-result-sk`, is only shown for completed `Pairwise` jobs. For other
statuses (e.g., `Running`, `Failed`, `Canceled`), the component renders
distinct status boxes. This provides clear, immediate feedback to the user
about the job's state without cluttering the UI with irrelevant or
unavailable information.
#### Key Components
- **`pinpoint-results-page-sk.ts`**: The main element. Its primary
responsibilities include:
- Extracting the Job ID from the browser's URL.
- Orchestrating the API call to fetch job data.
- Managing the page's state (e.g., `loading`, `error`, `job`).
- Rendering the overall page layout, including the header with job
metadata and the main content area.
- Composing and passing data down to child components
(`commit-run-overview-sk`, `wilcoxon-result-sk`, `job-overview-sk`).
#### Workflows
The primary workflow is fetching and displaying job data based on the current
URL.
```mermaid
graph TD
A["Page Loads"] --> B{"Extract Job ID from URL"}
B --> C{"Job ID exists?"}
C -- "No" --> D["Show 'No Job ID' error"]
C -- "Yes" --> E["Fetch job data via API"]
E --> F{"Fetch successful?"}
F -- "No" --> G["Show fetch error"]
F -- "Yes" --> H["Render job header & commit details"]
H --> I{"Job is 'Pairwise' and 'Completed'?"}
I -- "Yes" --> J["Render 'wilcoxon-result-sk' with statistics"]
I -- "No" --> K["Render relevant status box (e.g., Pending, Failed)"]
```
# Module: /ui/modules/pinpoint-scaffold-sk
The `pinpoint-scaffold-sk` module provides the main layout and user interface
shell for the Pinpoint application. It creates a consistent header with global
actions like searching, filtering, and creating new jobs, while allowing
different page content to be displayed within its main body.
### Design and Implementation
The primary design goal is to decouple the application's overall structure (the
scaffold) from the specific content it displays (e.g., a list of jobs).
This is achieved through two main web component patterns:
1. **Content Projection:** The scaffold uses a `<slot>` element. This allows
parent components to insert their own content into the main area of the
scaffold, making the scaffold a reusable container.
2. **Event-Driven Communication:** Instead of directly manipulating its child
content, the scaffold communicates user actions via custom events. When a
user types in the search bar or applies filters, the component dispatches
`search-changed` and `filters-changed` events, respectively. A parent
component listens for these events and is responsible for updating the
content shown in the `<slot>`. This keeps the scaffold unaware of the
specific data being displayed.
Upon initialization, the component fetches the available benchmarks and bots
from the Pinpoint API to populate the filter dropdown menus.
### Key Components
- **`pinpoint-scaffold-sk.ts`**: This is the core file defining the
`<pinpoint-scaffold-sk>` Lit element. It contains the HTML template for the
header and main content area, the logic for handling user input in the
search and filter menus, and the code for dispatching events to parent
components. It also includes the logic for showing the
`<pinpoint-new-job-sk>` modal.
### Workflows
#### Filtering Content
The following diagram illustrates how the scaffold interacts with a parent
component to filter content.
```mermaid
sequenceDiagram
actor User
participant Scaffold as pinpoint-scaffold-sk
participant ParentComponent as e.g., Job List Page
User->>Scaffold: Clicks the filter icon
Scaffold-->>User: Shows filter menu
User->>Scaffold: Changes filter values and clicks "Apply"
Scaffold->>ParentComponent: Dispatches "filters-changed" event with new
filter parameters
ParentComponent->>ParentComponent: Receives event, fetches new data, and
updates its content inside the scaffold's <slot>
```
# Module: /ui/modules/wilcoxon-results-sk
### Overview
The `wilcoxon-results-sk` module is a UI component designed to present the
statistical results from a pairwise Wilcoxon signed-rank test. It visualizes the
performance comparison between a "control" and a "treatment" group from a
Pinpoint job, making it easy to determine if a change resulted in a
statistically significant improvement or regression.
### Design and Implementation
The component is implemented as a LitElement, `wilcoxon-result-sk`. Its primary
design goal is to translate complex statistical data from a `JobSchema` object
into a concise and easily interpretable table.
It operates by processing the `MetricSummary` field of a given Pinpoint job. For
each metric found, it calculates and displays:
- **Median Difference**: The percentage change between the control and
treatment medians.
- **Confidence Interval**: The 95% confidence interval for the median
difference, providing a range for the true effect size.
- **P-value**: The probability value to assess statistical significance.
A key feature is the use of color-coding to provide at-a-glance interpretation.
Cells are colored based on the outcome:
- **Green (`improvement`)**: A statistically significant change in the desired
direction (e.g., latency going down).
- **Red (`regression`)**: A statistically significant change in the opposite
of the desired direction.
- **Grey (`neutral`)**: The change is not statistically significant.
This logic depends on the metric's "improvement direction" (whether higher or
lower values are better). Currently, this is a known limitation as the direction
is hardcoded to 'UP' (higher is better) and awaits a backend implementation.
### Key Components
- **`wilcoxon-results-sk.ts`**: Defines the `WilcoxonResultSk` custom element.
It receives a `JobSchema` object as a property. Its main responsibility is
to render the results table. It contains the logic for formatting the
statistical values into human-readable strings (e.g., percentages) and
applying the appropriate CSS classes (`improvement`, `regression`,
`neutral`) to visually communicate the test's outcome.
# Module: /ui/pages
The `pages` module provides the top-level HTML entry points for the Pinpoint web
application. Each page corresponds to a distinct user view or workflow.
### Design Philosophy
The design prioritizes a clean separation between page structure and application
logic. Each page is a minimal HTML shell that loads a single root custom
element. The corresponding TypeScript file's sole responsibility is to import
this element.
This "shell and component" approach delegates all complex UI rendering, state
management, and user interaction to the custom elements (defined in other
modules). This keeps the page definitions simple and focused on bootstrapping
the correct user experience.
### Key Pages
- **`landing-page`**: The main entry point for the application. It loads the
`<pinpoint-landing-page-sk>` element, which contains the interface for
configuring and starting new Pinpoint jobs.
- **`results-page`**: Displays the outcome of a specific job. It loads the
`<pinpoint-results-page-sk>` element, which fetches and renders the detailed
results for a given job ID.
# Module: /ui/services
This module serves as the API client for the Pinpoint frontend. It abstracts all
communication with the backend, providing a centralized and type-safe way for UI
components to fetch data and trigger actions.
### Design and Implementation
- **Centralization:** All backend interactions are consolidated here to
simplify maintenance and ensure consistency. Changes to backend API
endpoints only require updates in this module, not across various UI
components.
- **Type Safety:** The module uses TypeScript interfaces (e.g., `Job`,
`JobSchema`, `Commit`) that mirror the Go structs in the backend. This
creates a strong data contract between the frontend and backend, preventing
data-related bugs at compile time.
- **API Interaction:** Functions like `schedulePairwise`, `listJobs`, and
`getJob` use the standard `fetch` API to make asynchronous HTTP requests.
They handle request formatting (URL parameters, JSON bodies) and basic error
handling.
### Key Components
- **`api.ts`**: This file is the single source of truth for frontend-backend
communication.
- **Data Models**: Defines TypeScript interfaces (`Job`, `JobSchema`,
`Commit`, etc.) that match the backend's data structures. This ensures
that data received from or sent to the API conforms to an expected
shape.
- **API Functions**: Exposes a set of `async` functions
(`schedulePairwise`, `listJobs`, `getJob`, etc.) that wrap `fetch` calls
to specific backend endpoints. Each function is responsible for
constructing the correct request and parsing the response.
### Example Workflow: Scheduling a Job
The following diagram shows how a UI component uses this service to schedule a
new pairwise job.
```mermaid
sequenceDiagram
participant UI Component
participant services/api.ts
participant Pinpoint Backend
UI Component->>services/api.ts: Calls schedulePairwise(request)
services/api.ts->>Pinpoint Backend: POST /pinpoint/v1/pairwise with JSON body
Pinpoint Backend-->>services/api.ts: Returns job execution details (JSON)
services/api.ts-->>UI Component: Returns Promise<PairwiseExecution>
```