mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-08-05 19:55:37 +00:00
• Fix up various references of 'tint' to 'dawn' post-migration. • Round the CPU speed to 100MHz. This appears to fluctuate on reboots for some machines. • Fix git.Log() - the Count was trimming from the wrong end, leading to perfmon spamming the github repo with repeat results. • Instead of using google-benchmark's `--benchmark_repetitions` flag for repeating benchmarks, calculate averages by re-running the benchmark executable. Use '--benchmark_enable_random_interleaving' to randomize the order in which benchmarks are run, which greatly helps reduce noise in the averaged results. • If the host machine supports CPU temperature sensors, wait for thermals to stabilise before running the benchmarks. Further helps reduce result noise. • Breakout of the historic benchmarking loop every 15 minutes to check for new Gerrit changes to benchmark. • When idle, attempt to re-benchmark historic results that are 'spiky' to reduce noise in the graphs. • Specify more CMake flags to avoid building non-benchmark executables. • Update the default base change for historic results. Attempting to use the old hash, which was prior to the tint -> dawn merge makes, the git log go back to T0 on the dawn branch. We don't want to benchmark nearly 3k changes. Change-Id: I8e59c7838720eb8bd11f217e9bd3104ba1eb51c3 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/87642 Kokoro: Kokoro <noreply+kokoro@google.com> Reviewed-by: Antonio Maiorano <amaiorano@google.com> Commit-Queue: Ben Clayton <bclayton@google.com>
418 lines
10 KiB
Go
418 lines
10 KiB
Go
// Copyright 2022 The Tint Authors.
|
||
//
|
||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
// you may not use this file except in compliance with the License.
|
||
// You may obtain a copy of the License at
|
||
//
|
||
// http://www.apache.org/licenses/LICENSE-2.0
|
||
//
|
||
// Unless required by applicable law or agreed to in writing, software
|
||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
// See the License for the specific language governing permissions and
|
||
// limitations under the License.
|
||
|
||
// Package git provides helpers for interfacing with the git tool
|
||
package git
|
||
|
||
import (
|
||
"context"
|
||
"encoding/hex"
|
||
"errors"
|
||
"fmt"
|
||
"net/url"
|
||
"os"
|
||
"os/exec"
|
||
"path/filepath"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
// Hash is a 20 byte, git object hash.
|
||
type Hash [20]byte
|
||
|
||
func (h Hash) String() string { return hex.EncodeToString(h[:]) }
|
||
|
||
// IsZero returns true if the hash h is all zeros
|
||
func (h Hash) IsZero() bool {
|
||
zero := Hash{}
|
||
return h == zero
|
||
}
|
||
|
||
// ParseHash returns a Hash from a hexadecimal string.
|
||
func ParseHash(s string) (Hash, error) {
|
||
b, err := hex.DecodeString(s)
|
||
if err != nil {
|
||
return Hash{}, fmt.Errorf("failed to parse hash '%v':\n %w", s, err)
|
||
}
|
||
h := Hash{}
|
||
copy(h[:], b)
|
||
return h, nil
|
||
}
|
||
|
||
// The timeout for git operations if no other timeout is specified
|
||
var DefaultTimeout = time.Minute
|
||
|
||
// Git wraps the 'git' executable
|
||
type Git struct {
|
||
// Path to the git executable
|
||
exe string
|
||
// Debug flag to print all command to the `git` executable
|
||
LogAllActions bool
|
||
}
|
||
|
||
// New returns a new Git instance
|
||
func New(exe string) (*Git, error) {
|
||
if _, err := os.Stat(exe); err != nil {
|
||
return nil, err
|
||
}
|
||
return &Git{exe: exe}, nil
|
||
}
|
||
|
||
// Credentials holds the user name and password used to perform git operations.
|
||
type Credentials struct {
|
||
Username string
|
||
Password string
|
||
}
|
||
|
||
// Empty return true if there's no username or password for authentication
|
||
func (a Credentials) Empty() bool {
|
||
return a.Username == "" && a.Password == ""
|
||
}
|
||
|
||
// addToURL returns the url with the credentials appended
|
||
func (c Credentials) addToURL(u string) (string, error) {
|
||
if !c.Empty() {
|
||
modified, err := url.Parse(u)
|
||
if err != nil {
|
||
return "", fmt.Errorf("failed to parse url '%v': %v", u, err)
|
||
}
|
||
modified.User = url.UserPassword(c.Username, c.Password)
|
||
u = modified.String()
|
||
}
|
||
return u, nil
|
||
}
|
||
|
||
// ErrRepositoryDoesNotExist indicates that a repository does not exist
|
||
var ErrRepositoryDoesNotExist = errors.New("repository does not exist")
|
||
|
||
// Open opens an existing git repo at path. If the repository does not exist at
|
||
// path then ErrRepositoryDoesNotExist is returned.
|
||
func (g Git) Open(path string) (*Repository, error) {
|
||
info, err := os.Stat(filepath.Join(path, ".git"))
|
||
if err != nil || !info.IsDir() {
|
||
return nil, ErrRepositoryDoesNotExist
|
||
}
|
||
return &Repository{g, path}, nil
|
||
}
|
||
|
||
// Optional settings for Git.Clone
|
||
type CloneOptions struct {
|
||
// If specified then the given branch will be cloned instead of the default
|
||
Branch string
|
||
// Timeout for the operation
|
||
Timeout time.Duration
|
||
// Authentication for the clone
|
||
Credentials Credentials
|
||
}
|
||
|
||
// Clone performs a clone of the repository at url to path.
|
||
func (g Git) Clone(path, url string, opt *CloneOptions) (*Repository, error) {
|
||
if err := os.MkdirAll(path, 0777); err != nil {
|
||
return nil, err
|
||
}
|
||
if opt == nil {
|
||
opt = &CloneOptions{}
|
||
}
|
||
url, err := opt.Credentials.addToURL(url)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
r := &Repository{g, path}
|
||
args := []string{"clone", url, "."}
|
||
if opt.Branch != "" {
|
||
args = append(args, "--branch", opt.Branch)
|
||
}
|
||
if _, err := r.run(nil, opt.Timeout, args...); err != nil {
|
||
return nil, err
|
||
}
|
||
return r, nil
|
||
}
|
||
|
||
// Repository points to a git repository
|
||
type Repository struct {
|
||
// Path to the 'git' executable
|
||
Git Git
|
||
// Repo directory
|
||
Path string
|
||
}
|
||
|
||
// Optional settings for Repository.Fetch
|
||
type FetchOptions struct {
|
||
// The remote name. Defaults to 'origin'
|
||
Remote string
|
||
// Timeout for the operation
|
||
Timeout time.Duration
|
||
// Git authentication for the remote
|
||
Credentials Credentials
|
||
}
|
||
|
||
// Fetch performs a fetch of a reference from the remote, returning the Hash of
|
||
// the fetched reference.
|
||
func (r Repository) Fetch(ref string, opt *FetchOptions) (Hash, error) {
|
||
if opt == nil {
|
||
opt = &FetchOptions{}
|
||
}
|
||
if opt.Remote == "" {
|
||
opt.Remote = "origin"
|
||
}
|
||
if _, err := r.run(nil, opt.Timeout, "fetch", opt.Remote, ref); err != nil {
|
||
return Hash{}, err
|
||
}
|
||
out, err := r.run(nil, 0, "rev-parse", "FETCH_HEAD")
|
||
if err != nil {
|
||
return Hash{}, err
|
||
}
|
||
return ParseHash(out)
|
||
}
|
||
|
||
// Optional settings for Repository.Push
|
||
type PushOptions struct {
|
||
// The remote name. Defaults to 'origin'
|
||
Remote string
|
||
// Timeout for the operation
|
||
Timeout time.Duration
|
||
// Git authentication for the remote
|
||
Credentials Credentials
|
||
}
|
||
|
||
// Push performs a push of the local reference to the remote reference.
|
||
func (r Repository) Push(localRef, remoteRef string, opt *PushOptions) error {
|
||
if opt == nil {
|
||
opt = &PushOptions{}
|
||
}
|
||
if opt.Remote == "" {
|
||
opt.Remote = "origin"
|
||
}
|
||
url, err := r.run(nil, opt.Timeout, "remote", "get-url", opt.Remote)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
url, err = opt.Credentials.addToURL(url)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if _, err := r.run(nil, opt.Timeout, "push", url, localRef+":"+remoteRef); err != nil {
|
||
return err
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// Optional settings for Repository.Add
|
||
type AddOptions struct {
|
||
// Timeout for the operation
|
||
Timeout time.Duration
|
||
// Git authentication for the remote
|
||
Credentials Credentials
|
||
}
|
||
|
||
// Add stages the listed files
|
||
func (r Repository) Add(path string, opt *AddOptions) error {
|
||
if opt == nil {
|
||
opt = &AddOptions{}
|
||
}
|
||
if _, err := r.run(nil, opt.Timeout, "add", path); err != nil {
|
||
return err
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// Optional settings for Repository.Commit
|
||
type CommitOptions struct {
|
||
// Timeout for the operation
|
||
Timeout time.Duration
|
||
// Author name
|
||
AuthorName string
|
||
// Author email address
|
||
AuthorEmail string
|
||
// Amend last commit?
|
||
Amend bool
|
||
}
|
||
|
||
// Commit commits the staged files with the given message, returning the hash of
|
||
// commit
|
||
func (r Repository) Commit(msg string, opt *CommitOptions) (Hash, error) {
|
||
if opt == nil {
|
||
opt = &CommitOptions{}
|
||
}
|
||
|
||
args := []string{"commit"}
|
||
if opt.Amend {
|
||
args = append(args, "--amend")
|
||
} else {
|
||
args = append(args, "-m", msg)
|
||
}
|
||
|
||
var env []string
|
||
if opt.AuthorName != "" || opt.AuthorEmail != "" {
|
||
env = []string{
|
||
fmt.Sprintf("GIT_AUTHOR_NAME=%v", opt.AuthorName),
|
||
fmt.Sprintf("GIT_AUTHOR_EMAIL=%v", opt.AuthorEmail),
|
||
fmt.Sprintf("GIT_COMMITTER_NAME=%v", opt.AuthorName),
|
||
fmt.Sprintf("GIT_COMMITTER_EMAIL=%v", opt.AuthorEmail),
|
||
}
|
||
}
|
||
if _, err := r.run(env, opt.Timeout, "commit", "-m", msg); err != nil {
|
||
return Hash{}, err
|
||
}
|
||
out, err := r.run(nil, 0, "rev-parse", "HEAD")
|
||
if err != nil {
|
||
return Hash{}, err
|
||
}
|
||
return ParseHash(out)
|
||
}
|
||
|
||
// Optional settings for Repository.Checkout
|
||
type CheckoutOptions struct {
|
||
// Timeout for the operation
|
||
Timeout time.Duration
|
||
}
|
||
|
||
// Checkout performs a checkout of a reference.
|
||
func (r Repository) Checkout(ref string, opt *CheckoutOptions) error {
|
||
if opt == nil {
|
||
opt = &CheckoutOptions{}
|
||
}
|
||
if _, err := r.run(nil, opt.Timeout, "checkout", ref); err != nil {
|
||
return err
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// Optional settings for Repository.Log
|
||
type LogOptions struct {
|
||
// The git reference to the oldest commit in the range to query.
|
||
From string
|
||
// The git reference to the newest commit in the range to query.
|
||
To string
|
||
// Timeout for the operation
|
||
Timeout time.Duration
|
||
}
|
||
|
||
// CommitInfo describes a single git commit
|
||
type CommitInfo struct {
|
||
Hash Hash
|
||
Date time.Time
|
||
Author string
|
||
Subject string
|
||
Description string
|
||
}
|
||
|
||
// Log returns the list of commits between two references (inclusive).
|
||
// The first returned commit is the most recent.
|
||
func (r Repository) Log(opt *LogOptions) ([]CommitInfo, error) {
|
||
if opt == nil {
|
||
opt = &LogOptions{}
|
||
}
|
||
args := []string{"log"}
|
||
rng := "HEAD"
|
||
if opt.To != "" {
|
||
rng = opt.To
|
||
}
|
||
if opt.From != "" {
|
||
rng = opt.From + "^.." + rng
|
||
}
|
||
args = append(args, rng, "--pretty=format:ǁ%Hǀ%cIǀ%an <%ae>ǀ%sǀ%b")
|
||
out, err := r.run(nil, opt.Timeout, args...)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return parseLog(out)
|
||
}
|
||
|
||
// Optional settings for Repository.ConfigOptions
|
||
type ConfigOptions struct {
|
||
// Timeout for the operation
|
||
Timeout time.Duration
|
||
}
|
||
|
||
// Config returns the git configuration values for the repo
|
||
func (r Repository) Config(opt *ConfigOptions) (map[string]string, error) {
|
||
if opt == nil {
|
||
opt = &ConfigOptions{}
|
||
}
|
||
text, err := r.run(nil, opt.Timeout, "config", "-l")
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
lines := strings.Split(text, "\n")
|
||
out := make(map[string]string, len(lines))
|
||
for _, line := range lines {
|
||
idx := strings.Index(line, "=")
|
||
if idx > 0 {
|
||
key, value := line[:idx], line[idx+1:]
|
||
out[key] = value
|
||
}
|
||
}
|
||
return out, nil
|
||
}
|
||
|
||
func (r Repository) run(env []string, timeout time.Duration, args ...string) (string, error) {
|
||
return r.Git.run(r.Path, env, timeout, args...)
|
||
}
|
||
|
||
func (g Git) run(dir string, env []string, timeout time.Duration, args ...string) (string, error) {
|
||
if timeout == 0 {
|
||
timeout = DefaultTimeout
|
||
}
|
||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||
defer cancel()
|
||
cmd := exec.CommandContext(ctx, g.exe, args...)
|
||
cmd.Dir = dir
|
||
if env != nil {
|
||
// Godocs for exec.Cmd.Env:
|
||
// "If Env contains duplicate environment keys, only the last value in
|
||
// the slice for each duplicate key is used.
|
||
cmd.Env = append(os.Environ(), env...)
|
||
}
|
||
if g.LogAllActions {
|
||
fmt.Printf("%v> %v %v\n", dir, g.exe, strings.Join(args, " "))
|
||
}
|
||
out, err := cmd.CombinedOutput()
|
||
if g.LogAllActions {
|
||
fmt.Println(string(out))
|
||
}
|
||
if err != nil {
|
||
return string(out), fmt.Errorf("%v> %v %v failed:\n %w\n%v",
|
||
dir, g.exe, strings.Join(args, " "), err, string(out))
|
||
}
|
||
return strings.TrimSpace(string(out)), nil
|
||
}
|
||
|
||
func parseLog(str string) ([]CommitInfo, error) {
|
||
msgs := strings.Split(str, "ǁ")
|
||
cls := make([]CommitInfo, 0, len(msgs))
|
||
for _, s := range msgs {
|
||
if parts := strings.Split(s, "ǀ"); len(parts) == 5 {
|
||
hash, err := ParseHash(parts[0])
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
date, err := time.Parse(time.RFC3339, parts[1])
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
cl := CommitInfo{
|
||
Hash: hash,
|
||
Date: date,
|
||
Author: strings.TrimSpace(parts[2]),
|
||
Subject: strings.TrimSpace(parts[3]),
|
||
Description: strings.TrimSpace(parts[4]),
|
||
}
|
||
|
||
cls = append(cls, cl)
|
||
}
|
||
}
|
||
return cls, nil
|
||
}
|