[perf][ui] Check v8 hashes to see if single commit
- Add Proxy Handler to fetch v8 requests.
- Only support v8 instance, ignores all others.
- Added overhead, but no other option.
- Set parent trace check
Change-Id: I6211a747bb10ede3519ff288347bfd0daae7ada5
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/1062685
Reviewed-by: Ashwin Verleker <ashwinpv@google.com>
Commit-Queue: Tony Seaward <seawardt@google.com>
diff --git a/perf/go/frontend/BUILD.bazel b/perf/go/frontend/BUILD.bazel
index 31337f5..b16b34d 100644
--- a/perf/go/frontend/BUILD.bazel
+++ b/perf/go/frontend/BUILD.bazel
@@ -3,7 +3,10 @@
go_library(
name = "frontend",
- srcs = ["frontend.go"],
+ srcs = [
+ "frontend.go",
+ "proxy.go",
+ ],
data = glob(["testdata/**"]),
embedsrcs = [
"googleanalytics.html",
diff --git a/perf/go/frontend/frontend.go b/perf/go/frontend/frontend.go
index 4a17fe2..f5ebbd6 100644
--- a/perf/go/frontend/frontend.go
+++ b/perf/go/frontend/frontend.go
@@ -1047,6 +1047,7 @@
router.Post("/_/fe_telemetry", f.feTelemetryHandler)
router.Get("/_/defaults", f.defaultsHandler)
router.Get("/_/revision", f.revisionHandler)
+ router.Get("/_/json/", Proxy_GetHandler)
return router
}
diff --git a/perf/go/frontend/proxy.go b/perf/go/frontend/proxy.go
new file mode 100644
index 0000000..2eeaf0b
--- /dev/null
+++ b/perf/go/frontend/proxy.go
@@ -0,0 +1,57 @@
+package frontend
+
+import (
+ "errors"
+ "io"
+ "net/http"
+
+ "go.skia.org/infra/go/httputils"
+ "go.skia.org/infra/go/sklog"
+)
+
+// Proxy_GetHandler proxies a GET request to the given url.
+//
+// Takes the URL to fetch in the "url" query parameter.
+//
+// It is intended to be used to work around CORS issues, where a browser can't
+// directly contact another server, e.g. googlesource.com.
+func Proxy_GetHandler(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json")
+ url := r.FormValue("url")
+ if url == "" {
+ httputils.ReportError(w, errors.New("Missing 'url' query parameter."), "Missing 'url' query parameter.", http.StatusBadRequest)
+ return
+ }
+
+ // We don't use the httputils.NewTimeoutClient() because we don't want to
+ // follow redirects, we want to proxy them.
+ client := &http.Client{}
+
+ req, err := http.NewRequest("GET", url, nil)
+ if err != nil {
+ httputils.ReportError(w, err, "Failed to create request.", http.StatusInternalServerError)
+ return
+ }
+ // Copy headers from the original request.
+ req.Header = r.Header
+ req.Header.Del("Referer")
+ req.Header.Del("Origin")
+ req.Header.Del("Accept-Encoding") // To avoid getting gzipped content we can't easily handle.
+
+ resp, err := client.Do(req)
+ if err != nil {
+ httputils.ReportError(w, err, "Failed to make request.", http.StatusInternalServerError)
+ return
+ }
+ defer resp.Body.Close()
+
+ // Copy headers from the proxied response.
+ for k, v := range resp.Header {
+ w.Header()[k] = v
+ }
+ w.WriteHeader(resp.StatusCode)
+ if _, err := io.Copy(w, resp.Body); err != nil {
+ // We can't call ReportError here because we've already written the header.
+ sklog.Errorf("Failed to write proxied response: %s", err)
+ }
+}
diff --git a/perf/modules/explore-simple-sk/explore-simple-sk.ts b/perf/modules/explore-simple-sk/explore-simple-sk.ts
index f7b73eb..685f5f2 100644
--- a/perf/modules/explore-simple-sk/explore-simple-sk.ts
+++ b/perf/modules/explore-simple-sk/explore-simple-sk.ts
@@ -489,6 +489,29 @@
private fromParamsKey: string = '';
+ private buildTestPath(params: any) {
+ const parts = [];
+ if (params.test) {
+ parts.push(params.test);
+ }
+ // Only add subtests if they are not the same as story, up to 4.
+ if (params.subtest_1 && params.subtest_1 !== this.story) {
+ parts.push(params.subtest_1);
+ }
+ if (params.subtest_2 && params.subtest_2 !== this.story) {
+ parts.push(params.subtest_2);
+ }
+ if (params.subtest_3 && params.subtest_3 !== this.story) {
+ parts.push(params.subtest_3);
+ }
+ if (params.subtest_4 && params.subtest_4 !== this.story) {
+ parts.push(params.subtest_4);
+ }
+
+ parts.push(this.story);
+ this.testPath = parts.join('/');
+ }
+
private testPath: string = '';
private startCommit: string = '';
@@ -3638,6 +3661,9 @@
case 'show_google_plot':
this._state.show_google_plot = paramValue;
break;
+ case 'disable_filter_parent_traces':
+ this._state.disable_filter_parent_traces = paramValue;
+ break;
default:
break;
}
diff --git a/perf/modules/point-links-sk/point-links-sk.ts b/perf/modules/point-links-sk/point-links-sk.ts
index 55e0ca6..9a48707 100644
--- a/perf/modules/point-links-sk/point-links-sk.ts
+++ b/perf/modules/point-links-sk/point-links-sk.ts
@@ -66,28 +66,48 @@
private fuchsiaBuildLogKey = 'Test stdio';
+ // _pointLinks stores the asynchronously rendered TemplateResult[] for the point links.
+ // This is necessary because `lit-html` cannot directly render an array of Promises.
+ private _pointLinks: TemplateResult[] = [];
+
private static template = (ele: PointLinksSk) =>
html`<div class="point-links" ?hidden=${Object.keys(ele.displayUrls || {}).length === 0}>
<ul class="table">
- ${ele.renderPointLinks()} ${ele.renderRevisionLink()}
+ ${ele._pointLinks} ${ele.renderRevisionLink()}
</ul>
</div>`;
connectedCallback(): void {
super.connectedCallback();
+ // Initial render is handled by the `load` and `reset` functions,
+ // which now call `renderPointLinks` to manage asynchronous updates.
this.render();
}
- renderPointLinks(): TemplateResult[] {
+ /**
+ * Asynchronously renders the point links and updates the `_pointLinks` property.
+ * This function is now `async` to `await` the `isRange` check and uses `Promise.all`
+ * to ensure all individual link rendering promises resolve before updating the DOM.
+ */
+ async renderPointLinks(): Promise<void> {
if (Object.keys(this.displayTexts).length === 0 && Object.keys(this.displayUrls).length === 0) {
- return [];
+ this._pointLinks = [];
+ this.render();
+ return;
}
const keys = Object.keys(this.displayUrls);
- const getHtml = (key: string): TemplateResult => {
+ const getHtml = async (key: string): Promise<TemplateResult> => {
const link = this.displayUrls![key];
// TODO(b/398878559): Strip after 'Git' string until json keys are ready.
const keyText: string = key.split(' Git')[0];
- const linkText = this.displayTexts![key] || 'Link';
+ let linkText = this.displayTexts![key] || 'Link';
+ // This is a specific change for just v8.
+ if (keyText === 'V8') {
+ const isRange = await this.isRange(link);
+ if (!isRange && linkText.includes(' - ')) {
+ linkText = linkText.split(' - ')[1];
+ }
+ }
const htmlUrl = html`<a
href="${link}"
title="${linkText}"
@@ -104,7 +124,9 @@
</md-icon-button>
</li>`;
};
- return keys.map(getHtml);
+ const htmlPromises = keys.map(getHtml);
+ this._pointLinks = await Promise.all(htmlPromises);
+ this.render();
}
renderRevisionLink() {
@@ -133,6 +155,65 @@
navigator.clipboard.writeText(text);
}
+ // isRange returns true if the given link refers to a range of commits, i.e.
+ // more than one commit.
+ //
+ // The ingested data always contains 2 commit hashes, but it is possible that
+ // there is only a single commit between the 2 hashes. We request a JSON
+ // formatted response from googlesource and check the response to see how many
+ // commits exist in the log.
+ private async isRange(link: string): Promise<boolean> {
+ // We can only inspect googlesource links that point to a commit.
+ if (!link.includes('googlesource.com')) {
+ // Not a googlesource commit link we can inspect, assume it's not a single
+ // commit, so we treat it as a range to be safe.
+ return true;
+ }
+
+ const url = new URL(link);
+ url.searchParams.set('format', 'JSON');
+
+ const proxyUrl = `/_/json//?url=${encodeURIComponent(url.toString())}`;
+
+ try {
+ const resp = await fetch(proxyUrl);
+ if (!resp.ok) {
+ // If the proxy fails or the underlying request fails, we can't determine.
+ // Let's log the error and assume it's a range.
+ const text = await resp.text();
+ console.error(
+ `Failed to fetch through proxy for ${url.toString()}: ${resp.status} ${text}`
+ );
+ return true;
+ }
+ const text = await resp.text();
+
+ // Handle googlesource's JSON prefix: )]}'
+ if (!text.startsWith(")]}'")) {
+ // Not the JSON format we expect.
+ return true;
+ }
+ const jsonStr = text.substring(4);
+ // It's possible to get an empty response body.
+ if (!jsonStr.trim()) {
+ return true;
+ }
+ const json = JSON.parse(jsonStr);
+
+ // A range is defined as having more than one commit in the log.
+ if (json && Array.isArray(json.log)) {
+ return json.log.length > 1;
+ }
+ // If we don't have a log array, we can't determine, so assume it's a range.
+ return true;
+ } catch (error) {
+ errorMessage(`Error while determining if link is a range: ${error}`);
+ // On any other error (e.g. network, JSON parsing), we can't be sure.
+ // Returning true is safer as it will display the full link text.
+ return true;
+ }
+ }
+
// load and display the links for the given commit and trace.
public async load(
commit_position: CommitNumber | null,
@@ -166,7 +247,7 @@
// Reuse the existing links
this.displayUrls = existingLink.displayUrls || {};
this.displayTexts = existingLink.displayTexts || {};
- this.render();
+ this.renderPointLinks();
return Promise.resolve(commitLinks);
}
}
@@ -247,6 +328,7 @@
this.displayTexts = displayTexts;
this.displayUrls = displayUrls;
+ this.renderPointLinks();
// Before adding a new commit link, check if it already exists in the array.
// This should not be necessary, but it is a safeguard due to async calls.
@@ -274,7 +356,7 @@
this.commitPosition = null;
this.displayUrls = {};
this.displayTexts = {};
- this.render();
+ this.renderPointLinks();
}
render(): void {