2018-10-12 19:21:17 +00:00
|
|
|
// Copyright 2018 The Chromium Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
|
|
// found in the LICENSE file.
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
// This server runs along side the karma tests and listens for POST requests
|
|
|
|
// when any test case reports it has output for Perf. See perfReporter.js
|
|
|
|
// for the browser side part.
|
|
|
|
|
|
|
|
// Unlike the gold ingester, the perf ingester allows multiple reports
|
|
|
|
// of the same benchmark and will output the average of these results
|
|
|
|
// on a call to dump
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
"strconv"
|
2018-10-16 14:15:01 +00:00
|
|
|
"strings"
|
2018-10-12 19:21:17 +00:00
|
|
|
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"go.skia.org/infra/perf/go/ingestcommon"
|
|
|
|
)
|
|
|
|
|
|
|
|
// upload_nano_results looks for anything*.json
|
|
|
|
// We add the random UUID to avoid name clashes when uploading to
|
|
|
|
// the perf bucket (which uploads to folders based on Month/Day/Hour, which can
|
|
|
|
// easily have duplication if multiple perf tasks run in an hour.)
|
|
|
|
var JSON_FILENAME = fmt.Sprintf("%s_browser_bench.json", uuid.New().String())
|
|
|
|
|
|
|
|
var (
|
|
|
|
outDir = flag.String("out_dir", "/OUT/", "location to dump the Perf JSON")
|
|
|
|
port = flag.String("port", "8081", "Port to listen on.")
|
|
|
|
|
|
|
|
botId = flag.String("bot_id", "", "swarming bot id")
|
|
|
|
browser = flag.String("browser", "Chrome", "Browser Key")
|
|
|
|
buildBucketID = flag.Int64("buildbucket_build_id", 0, "Buildbucket build id key")
|
|
|
|
builder = flag.String("builder", "", "Builder, like 'Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit'")
|
|
|
|
compiledLanguage = flag.String("compiled_language", "wasm", "wasm or asm.js")
|
|
|
|
config = flag.String("config", "Release", "Configuration (e.g. Debug/Release) key")
|
|
|
|
gitHash = flag.String("git_hash", "-", "The git commit hash of the version being tested")
|
|
|
|
hostOS = flag.String("host_os", "Debian9", "OS Key")
|
|
|
|
issue = flag.Int64("issue", 0, "issue (if tryjob)")
|
|
|
|
patch_storage = flag.String("patch_storage", "", "patch storage (if tryjob)")
|
|
|
|
patchset = flag.Int64("patchset", 0, "patchset (if tryjob)")
|
|
|
|
taskId = flag.String("task_id", "", "swarming task id")
|
2018-10-16 14:15:01 +00:00
|
|
|
sourceType = flag.String("source_type", "pathkit", "Gold Source type, like pathkit,canvaskit")
|
2018-10-12 19:21:17 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Received from the JS side.
|
|
|
|
type reportBody struct {
|
|
|
|
// a name describing the benchmark. Should be unique enough to allow use of grep.
|
|
|
|
BenchName string `json:"bench_name"`
|
|
|
|
// The number of microseconds of the task.
|
|
|
|
TimeMicroSeconds float64 `json:"time_us"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// The keys to be used at the top level for all Results.
|
|
|
|
var defaultKeys map[string]string
|
|
|
|
|
|
|
|
// contains all the results reported in through report_perf_data
|
|
|
|
var results map[string][]reportBody
|
|
|
|
|
|
|
|
type BenchData struct {
|
|
|
|
Hash string `json:"gitHash"`
|
|
|
|
Issue string `json:"issue"`
|
|
|
|
PatchSet string `json:"patchset"`
|
|
|
|
Key map[string]string `json:"key"`
|
|
|
|
Options map[string]string `json:"options,omitempty"`
|
|
|
|
Results map[string]ingestcommon.BenchResults `json:"results"`
|
|
|
|
PatchStorage string `json:"patch_storage,omitempty"`
|
|
|
|
|
|
|
|
SwarmingTaskID string `json:"swarming_task_id,omitempty"`
|
|
|
|
SwarmingBotID string `json:"swarming_bot_id,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
flag.Parse()
|
|
|
|
|
2018-10-16 14:15:01 +00:00
|
|
|
cpuGPU := "CPU"
|
|
|
|
if strings.Index(*builder, "-GPU-") != -1 {
|
|
|
|
cpuGPU = "GPU"
|
|
|
|
}
|
2018-10-12 19:21:17 +00:00
|
|
|
defaultKeys = map[string]string{
|
|
|
|
"arch": "WASM",
|
|
|
|
"browser": *browser,
|
|
|
|
"compiled_language": *compiledLanguage,
|
|
|
|
"compiler": "emsdk",
|
|
|
|
"configuration": *config,
|
2018-10-16 14:15:01 +00:00
|
|
|
"cpu_or_gpu": cpuGPU,
|
2018-10-12 19:21:17 +00:00
|
|
|
"cpu_or_gpu_value": "Browser",
|
|
|
|
"os": *hostOS,
|
2018-10-16 14:15:01 +00:00
|
|
|
"source_type": *sourceType,
|
2018-10-12 19:21:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
results = make(map[string][]reportBody)
|
|
|
|
|
|
|
|
http.HandleFunc("/report_perf_data", reporter)
|
|
|
|
http.HandleFunc("/dump_json", dumpJSON)
|
|
|
|
|
|
|
|
fmt.Printf("Waiting for perf ingestion on port %s\n", *port)
|
|
|
|
|
|
|
|
log.Fatal(http.ListenAndServe(":"+*port, nil))
|
|
|
|
}
|
|
|
|
|
|
|
|
// reporter handles when the client reports a test has a benchmark.
|
|
|
|
func reporter(w http.ResponseWriter, r *http.Request) {
|
|
|
|
if r.Method != "POST" {
|
|
|
|
http.Error(w, "Only POST accepted", 400)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer r.Body.Close()
|
|
|
|
|
|
|
|
body, err := ioutil.ReadAll(r.Body)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "Malformed body", 400)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
benchOutput := reportBody{}
|
|
|
|
if err := json.Unmarshal(body, &benchOutput); err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
http.Error(w, "Could not unmarshal JSON", 400)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err := w.Write([]byte("Accepted")); err != nil {
|
|
|
|
fmt.Printf("Could not write response: %s\n", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
results[benchOutput.BenchName] = append(results[benchOutput.BenchName], benchOutput)
|
|
|
|
}
|
|
|
|
|
|
|
|
// createOutputFile creates a file and set permissions correctly.
|
|
|
|
func createOutputFile(p string) (*os.File, error) {
|
|
|
|
outputFile, err := os.Create(p)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Could not open file %s on disk: %s", p, err)
|
|
|
|
}
|
|
|
|
// Make this accessible (and deletable) by all users
|
|
|
|
if err = outputFile.Chmod(0666); err != nil {
|
|
|
|
return nil, fmt.Errorf("Could not change permissions of file %s: %s", p, err)
|
|
|
|
}
|
|
|
|
return outputFile, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// dumpJSON writes out a JSON file with all the results, typically at the end of
|
|
|
|
// all the tests. If there is more than one result per benchmark, we report the average.
|
|
|
|
func dumpJSON(w http.ResponseWriter, r *http.Request) {
|
|
|
|
if r.Method != "POST" {
|
|
|
|
http.Error(w, "Only POST accepted", 400)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
p := path.Join(*outDir, JSON_FILENAME)
|
|
|
|
outputFile, err := createOutputFile(p)
|
|
|
|
defer outputFile.Close()
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
http.Error(w, "Could not open json file on disk", 500)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
benchData := BenchData{
|
|
|
|
Hash: *gitHash,
|
|
|
|
Issue: strconv.FormatInt(*issue, 10),
|
|
|
|
PatchStorage: *patch_storage,
|
|
|
|
PatchSet: strconv.FormatInt(*patchset, 10),
|
|
|
|
Key: defaultKeys,
|
|
|
|
SwarmingBotID: *botId,
|
|
|
|
SwarmingTaskID: *taskId,
|
|
|
|
}
|
|
|
|
|
|
|
|
allResults := make(map[string]ingestcommon.BenchResults)
|
|
|
|
for name, benches := range results {
|
|
|
|
samples := []float64{}
|
|
|
|
total := float64(0)
|
|
|
|
for _, t := range benches {
|
|
|
|
samples = append(samples, t.TimeMicroSeconds)
|
|
|
|
total += t.TimeMicroSeconds
|
|
|
|
}
|
|
|
|
allResults[name] = map[string]ingestcommon.BenchResult{
|
|
|
|
"default": map[string]interface{}{
|
|
|
|
"average_us": total / float64(len(benches)),
|
|
|
|
"samples": samples,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
benchData.Results = allResults
|
|
|
|
|
|
|
|
enc := json.NewEncoder(outputFile)
|
|
|
|
enc.SetIndent("", " ") // Make it human readable.
|
|
|
|
if err := enc.Encode(&benchData); err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
http.Error(w, "Could not write json to disk", 500)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
fmt.Println("JSON Written")
|
|
|
|
}
|