Home | History | Annotate | Download | only in perf
      1 // Copyright 2018 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 package main
      6 
      7 // This server runs along side the karma tests and listens for POST requests
      8 // when any test case reports it has output for Perf. See perfReporter.js
      9 // for the browser side part.
     10 
     11 // Unlike the gold ingester, the perf ingester allows multiple reports
     12 // of the same benchmark and will output the average of these results
     13 // on a call to dump
     14 
     15 import (
     16 	"encoding/json"
     17 	"flag"
     18 	"fmt"
     19 	"io/ioutil"
     20 	"log"
     21 	"net/http"
     22 	"os"
     23 	"path"
     24 	"strconv"
     25 	"strings"
     26 
     27 	"github.com/google/uuid"
     28 	"go.skia.org/infra/perf/go/ingestcommon"
     29 )
     30 
     31 // upload_nano_results looks for anything*.json
     32 // We add the random UUID to avoid name clashes when uploading to
     33 // the perf bucket (which uploads to folders based on Month/Day/Hour, which can
     34 // easily have duplication if multiple perf tasks run in an hour.)
     35 var JSON_FILENAME = fmt.Sprintf("%s_browser_bench.json", uuid.New().String())
     36 
     37 var (
     38 	outDir = flag.String("out_dir", "/OUT/", "location to dump the Perf JSON")
     39 	port   = flag.String("port", "8081", "Port to listen on.")
     40 
     41 	botId            = flag.String("bot_id", "", "swarming bot id")
     42 	browser          = flag.String("browser", "Chrome", "Browser Key")
     43 	buildBucketID    = flag.Int64("buildbucket_build_id", 0, "Buildbucket build id key")
     44 	builder          = flag.String("builder", "", "Builder, like 'Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit'")
     45 	compiledLanguage = flag.String("compiled_language", "wasm", "wasm or asm.js")
     46 	config           = flag.String("config", "Release", "Configuration (e.g. Debug/Release) key")
     47 	gitHash          = flag.String("git_hash", "-", "The git commit hash of the version being tested")
     48 	hostOS           = flag.String("host_os", "Debian9", "OS Key")
     49 	issue            = flag.Int64("issue", 0, "issue (if tryjob)")
     50 	patch_storage    = flag.String("patch_storage", "", "patch storage (if tryjob)")
     51 	patchset         = flag.Int64("patchset", 0, "patchset (if tryjob)")
     52 	taskId           = flag.String("task_id", "", "swarming task id")
     53 	sourceType       = flag.String("source_type", "pathkit", "Gold Source type, like pathkit,canvaskit")
     54 )
     55 
     56 // Received from the JS side.
     57 type reportBody struct {
     58 	// a name describing the benchmark. Should be unique enough to allow use of grep.
     59 	BenchName string `json:"bench_name"`
     60 	// The number of microseconds of the task.
     61 	TimeMicroSeconds float64 `json:"time_us"`
     62 }
     63 
     64 // The keys to be used at the top level for all Results.
     65 var defaultKeys map[string]string
     66 
     67 // contains all the results reported in through report_perf_data
     68 var results map[string][]reportBody
     69 
     70 type BenchData struct {
     71 	Hash         string                               `json:"gitHash"`
     72 	Issue        string                               `json:"issue"`
     73 	PatchSet     string                               `json:"patchset"`
     74 	Key          map[string]string                    `json:"key"`
     75 	Options      map[string]string                    `json:"options,omitempty"`
     76 	Results      map[string]ingestcommon.BenchResults `json:"results"`
     77 	PatchStorage string                               `json:"patch_storage,omitempty"`
     78 
     79 	SwarmingTaskID string `json:"swarming_task_id,omitempty"`
     80 	SwarmingBotID  string `json:"swarming_bot_id,omitempty"`
     81 }
     82 
     83 func main() {
     84 	flag.Parse()
     85 
     86 	cpuGPU := "CPU"
     87 	if strings.Index(*builder, "-GPU-") != -1 {
     88 		cpuGPU = "GPU"
     89 	}
     90 	defaultKeys = map[string]string{
     91 		"arch":              "WASM",
     92 		"browser":           *browser,
     93 		"compiled_language": *compiledLanguage,
     94 		"compiler":          "emsdk",
     95 		"configuration":     *config,
     96 		"cpu_or_gpu":        cpuGPU,
     97 		"cpu_or_gpu_value":  "Browser",
     98 		"os":                *hostOS,
     99 		"source_type":       *sourceType,
    100 	}
    101 
    102 	results = make(map[string][]reportBody)
    103 
    104 	http.HandleFunc("/report_perf_data", reporter)
    105 	http.HandleFunc("/dump_json", dumpJSON)
    106 
    107 	fmt.Printf("Waiting for perf ingestion on port %s\n", *port)
    108 
    109 	log.Fatal(http.ListenAndServe(":"+*port, nil))
    110 }
    111 
    112 // reporter handles when the client reports a test has a benchmark.
    113 func reporter(w http.ResponseWriter, r *http.Request) {
    114 	if r.Method != "POST" {
    115 		http.Error(w, "Only POST accepted", 400)
    116 		return
    117 	}
    118 	defer r.Body.Close()
    119 
    120 	body, err := ioutil.ReadAll(r.Body)
    121 	if err != nil {
    122 		http.Error(w, "Malformed body", 400)
    123 		return
    124 	}
    125 
    126 	benchOutput := reportBody{}
    127 	if err := json.Unmarshal(body, &benchOutput); err != nil {
    128 		fmt.Println(err)
    129 		http.Error(w, "Could not unmarshal JSON", 400)
    130 		return
    131 	}
    132 
    133 	if _, err := w.Write([]byte("Accepted")); err != nil {
    134 		fmt.Printf("Could not write response: %s\n", err)
    135 		return
    136 	}
    137 
    138 	results[benchOutput.BenchName] = append(results[benchOutput.BenchName], benchOutput)
    139 }
    140 
    141 // createOutputFile creates a file and set permissions correctly.
    142 func createOutputFile(p string) (*os.File, error) {
    143 	outputFile, err := os.Create(p)
    144 	if err != nil {
    145 		return nil, fmt.Errorf("Could not open file %s on disk: %s", p, err)
    146 	}
    147 	// Make this accessible (and deletable) by all users
    148 	if err = outputFile.Chmod(0666); err != nil {
    149 		return nil, fmt.Errorf("Could not change permissions of file %s: %s", p, err)
    150 	}
    151 	return outputFile, nil
    152 }
    153 
    154 // dumpJSON writes out a JSON file with all the results, typically at the end of
    155 // all the tests. If there is more than one result per benchmark, we report the average.
    156 func dumpJSON(w http.ResponseWriter, r *http.Request) {
    157 	if r.Method != "POST" {
    158 		http.Error(w, "Only POST accepted", 400)
    159 		return
    160 	}
    161 
    162 	p := path.Join(*outDir, JSON_FILENAME)
    163 	outputFile, err := createOutputFile(p)
    164 	defer outputFile.Close()
    165 	if err != nil {
    166 		fmt.Println(err)
    167 		http.Error(w, "Could not open json file on disk", 500)
    168 		return
    169 	}
    170 
    171 	benchData := BenchData{
    172 		Hash:           *gitHash,
    173 		Issue:          strconv.FormatInt(*issue, 10),
    174 		PatchStorage:   *patch_storage,
    175 		PatchSet:       strconv.FormatInt(*patchset, 10),
    176 		Key:            defaultKeys,
    177 		SwarmingBotID:  *botId,
    178 		SwarmingTaskID: *taskId,
    179 	}
    180 
    181 	allResults := make(map[string]ingestcommon.BenchResults)
    182 	for name, benches := range results {
    183 		samples := []float64{}
    184 		total := float64(0)
    185 		for _, t := range benches {
    186 			samples = append(samples, t.TimeMicroSeconds)
    187 			total += t.TimeMicroSeconds
    188 		}
    189 		allResults[name] = map[string]ingestcommon.BenchResult{
    190 			"default": map[string]interface{}{
    191 				"average_us": total / float64(len(benches)),
    192 				"samples":    samples,
    193 			},
    194 		}
    195 	}
    196 	benchData.Results = allResults
    197 
    198 	enc := json.NewEncoder(outputFile)
    199 	enc.SetIndent("", "  ") // Make it human readable.
    200 	if err := enc.Encode(&benchData); err != nil {
    201 		fmt.Println(err)
    202 		http.Error(w, "Could not write json to disk", 500)
    203 		return
    204 	}
    205 	fmt.Println("JSON Written")
    206 }
    207