Revert "[named-fiddles] Remove named-fiddles."
Then remove the web UI to make the revert easier.
This reverts commit 6e3ae4324790799d05acf4113e1d2eb0b818463b.
Reason for revert: Still needed to copy example fiddles into prod.
Original change's description:
> [named-fiddles] Remove named-fiddles.
>
> All named fiddles are be checked into https://github.com/google/skia/tree/master/docs/examples.
>
> Change-Id: I988042b15869329616f34ee6d017f746da2082f6
> Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/291836
> Commit-Queue: Joe Gregorio <jcgregorio@google.com>
> Reviewed-by: Ravi Mistry <rmistry@google.com>
TBR=rmistry@google.com,jcgregorio@google.com
Change-Id: I92c4b2a8631a1b53226e185a9f7e9dcecdd8a99d
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/302616
Reviewed-by: Joe Gregorio <jcgregorio@google.com>
Commit-Queue: Joe Gregorio <jcgregorio@google.com>
diff --git a/named-fiddles/Makefile b/named-fiddles/Makefile
new file mode 100644
index 0000000..5053dcb
--- /dev/null
+++ b/named-fiddles/Makefile
@@ -0,0 +1,12 @@
+build:
+ go install ./go/named-fiddles
+
+release: build
+ CGO_ENABLED=0 GOOS=linux go install -a ./go/named-fiddles
+ ./build_release
+
+push: release
+ pushk named-fiddles
+
+testci:
+ go test ./go/...
\ No newline at end of file
diff --git a/named-fiddles/README.md b/named-fiddles/README.md
new file mode 100644
index 0000000..2ebb8aa
--- /dev/null
+++ b/named-fiddles/README.md
@@ -0,0 +1,3 @@
+# named-fiddles
+
+A background app that copies examples out of the Skia repo into fiddle.
diff --git a/named-fiddles/go/named-fiddles/main.go b/named-fiddles/go/named-fiddles/main.go
new file mode 100644
index 0000000..39f6286
--- /dev/null
+++ b/named-fiddles/go/named-fiddles/main.go
@@ -0,0 +1,183 @@
+package main
+
+import (
+ "context"
+ "encoding/json"
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+ "time"
+
+ "go.skia.org/infra/fiddlek/go/client"
+ "go.skia.org/infra/fiddlek/go/store"
+ "go.skia.org/infra/fiddlek/go/types"
+ "go.skia.org/infra/go/auth"
+ "go.skia.org/infra/go/common"
+ "go.skia.org/infra/go/git/gitinfo"
+ "go.skia.org/infra/go/gitauth"
+ "go.skia.org/infra/go/metrics2"
+ "go.skia.org/infra/go/sklog"
+ "go.skia.org/infra/named-fiddles/go/parse"
+)
+
+// flags
+var (
+ local = flag.Bool("local", false, "Running locally if true. As opposed to in production.")
+ period = flag.Duration("period", time.Hour, "How often to check if the named fiddles are valid.")
+ promPort = flag.String("prom_port", ":20000", "Metrics service address (e.g., ':10110')")
+ repoURL = flag.String("repo_url", "https://skia.googlesource.com/skia", "Repo url")
+ repoDir = flag.String("repo_dir", "/tmp/skia_named_fiddles", "Directory the repo is checked out into.")
+)
+
+// Server is the state of the server.
+type Server struct {
+ store *store.Store
+ repo *gitinfo.GitInfo
+
+ livenessExamples metrics2.Liveness // liveness of the naming the Skia examples.
+ errorsInExamplesRun metrics2.Counter // errorsInExamplesRun is the number of errors in a single examples run.
+ numInvalidExamples metrics2.Int64Metric // numInvalidExamples is the number of examples that are currently invalid.
+}
+
+// New creates a new Server.
+func New() (*Server, error) {
+ st, err := store.New(*local)
+ if err != nil {
+ return nil, fmt.Errorf("Failed to create client for GCS: %s", err)
+ }
+
+ if !*local {
+ ts, err := auth.NewDefaultTokenSource(false, auth.SCOPE_USERINFO_EMAIL, auth.SCOPE_GERRIT)
+ if err != nil {
+ sklog.Fatalf("Failed authentication: %s", err)
+ }
+ // Use the gitcookie created by the gitauth package.
+ if _, err := gitauth.New(ts, "/tmp/gitcookies", true, ""); err != nil {
+ sklog.Fatalf("Failed to create git cookie updater: %s", err)
+ }
+ sklog.Infof("Git authentication set up successfully.")
+ }
+
+ repo, err := gitinfo.CloneOrUpdate(context.Background(), *repoURL, *repoDir, false)
+ if err != nil {
+ return nil, fmt.Errorf("Failed to create git repo: %s", err)
+ }
+
+ srv := &Server{
+ store: st,
+ repo: repo,
+
+ livenessExamples: metrics2.NewLiveness("named_fiddles_examples"),
+ errorsInExamplesRun: metrics2.GetCounter("named_fiddles_errors_in_examples_run", nil),
+ numInvalidExamples: metrics2.GetInt64Metric("named_fiddles_examples_total_invalid"),
+ }
+ go srv.nameExamples()
+ return srv, nil
+}
+
+// errorsInResults returns an empty string if there are no errors, either
+// compile or runtime, found in the results. If there are errors then a string
+// describing the error is returned.
+func errorsInResults(runResults *types.RunResults, success bool) string {
+ status := ""
+ if runResults == nil {
+ status = "Failed to run."
+ } else if len(runResults.CompileErrors) > 0 || runResults.RunTimeError != "" {
+ // update validity
+ status = fmt.Sprintf("%v %s", runResults.CompileErrors, runResults.RunTimeError)
+ if len(status) > 100 {
+ status = status[:100]
+ }
+ }
+ return status
+}
+
+// exampleStep is a single run through naming all the examples.
+func (srv *Server) exampleStep() {
+ srv.errorsInExamplesRun.Reset()
+ sklog.Info("Starting exampleStep")
+ if err := srv.repo.Update(context.Background(), true, false); err != nil {
+ sklog.Errorf("Failed to sync git repo.")
+ return
+ }
+
+ var numInvalid int64
+ // Get a list of all examples.
+ dir := filepath.Join(*repoDir, "docs", "examples")
+ err := filepath.Walk(dir+"/", func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return fmt.Errorf("Failed to open %q: %s", path, err)
+ }
+ if info.IsDir() {
+ return nil
+ }
+ name := filepath.Base(info.Name())
+ if !strings.HasSuffix(name, ".cpp") {
+ return nil
+ }
+ name = name[0 : len(name)-4]
+ b, err := ioutil.ReadFile(filepath.Join(dir, info.Name()))
+ fc, err := parse.ParseCpp(string(b))
+ if err == parse.ErrorInactiveExample {
+ sklog.Infof("Inactive sample: %q", info.Name())
+ return nil
+ } else if err != nil {
+ sklog.Infof("Invalid sample: %q", info.Name())
+ numInvalid += 1
+ return nil
+ }
+ // Now run it.
+ sklog.Infof("About to run: %s", name)
+ b, err = json.Marshal(fc)
+ if err != nil {
+ sklog.Errorf("Failed to encode example to JSON: %s", err)
+ return nil
+ }
+
+ runResults, success := client.Do(b, false, "https://fiddle.skia.org", func(*types.RunResults) bool {
+ return true
+ })
+ if !success {
+ sklog.Errorf("Failed to run")
+ srv.errorsInExamplesRun.Inc(1)
+ return nil
+ }
+ status := errorsInResults(runResults, success)
+ if err := srv.store.WriteName(name, runResults.FiddleHash, "Skia example", status); err != nil {
+ sklog.Errorf("Failed to write status for %s: %s", name, err)
+ srv.errorsInExamplesRun.Inc(1)
+ }
+ return nil
+ })
+ if err != nil {
+ sklog.Errorf("Error walking the path %q: %v\n", dir, err)
+ return
+ }
+
+ srv.numInvalidExamples.Update(numInvalid)
+ srv.livenessExamples.Reset()
+}
+
+// nameExamples runs each Skia example and gives it a name.
+func (srv *Server) nameExamples() {
+ srv.exampleStep()
+ for range time.Tick(time.Minute) {
+ srv.exampleStep()
+ }
+}
+
+func main() {
+ common.InitWithMust(
+ "named-fiddles",
+ common.PrometheusOpt(promPort),
+ )
+
+ _, err := New()
+ if err != nil {
+ sklog.Fatalf("Failed to create Server: %s", err)
+ }
+ select {}
+}
diff --git a/named-fiddles/go/parse/parse.go b/named-fiddles/go/parse/parse.go
new file mode 100644
index 0000000..fdfe932
--- /dev/null
+++ b/named-fiddles/go/parse/parse.go
@@ -0,0 +1,92 @@
+package parse
+
+import (
+ "errors"
+ "fmt"
+ "regexp"
+ "strconv"
+ "strings"
+
+ "go.skia.org/infra/fiddlek/go/types"
+)
+
+var (
+ ErrorInactiveExample = errors.New("Inactive example (ifdef'd out)")
+
+ // re is used to parse the REG_FIDDLE macro found in the sample code.
+ re = regexp.MustCompile(`REG_FIDDLE\((?P<name>\w+),\s+(?P<width>\w+),\s+(?P<height>\w+),\s+(?P<textonly>\w+),\s+(?P<source>\w+)\)`)
+)
+
+const (
+ // The indices into the capture groups in the 're' regexp.
+ NAME = 1
+ WIDTH = 2
+ HEIGHT = 3
+ TEXTONLY = 4
+ SOURCE = 5
+)
+
+// parseCpp parses a Skia example and returns a FiddleContext that's ready to run.
+//
+// Returns ErrorInactiveExample is the example is ifdef'd out. Other errors
+// indicate a failure to parse the code or options.
+func ParseCpp(body string) (*types.FiddleContext, error) {
+ if body[:3] == "#if" {
+ return nil, ErrorInactiveExample
+ }
+
+ // Parse up the REG_FIDDLE macro values.
+ match := re.FindStringSubmatch(body)
+ if len(match) != 6 {
+ return nil, fmt.Errorf("Failed to find REG_FIDDLE macro.")
+ }
+ width, err := strconv.Atoi(match[WIDTH])
+ if err != nil {
+ return nil, fmt.Errorf("Failed to parse width: %s", err)
+ }
+ height, err := strconv.Atoi(match[HEIGHT])
+ if err != nil {
+ return nil, fmt.Errorf("Failed to parse height: %s", err)
+ }
+ source, err := strconv.Atoi(match[SOURCE])
+ if err != nil {
+ return nil, fmt.Errorf("Failed to parse source: %s", err)
+ }
+ textonly := match[TEXTONLY] == "true"
+
+ // Extract the code.
+ lines := strings.Split(body, "\n")
+
+ code := []string{}
+ foundREG := false
+ foundEnd := false
+ for _, line := range lines {
+ if !foundREG {
+ if strings.HasPrefix(line, "REG_FIDDLE(") {
+ foundREG = true
+ }
+ continue
+ }
+ if strings.Contains(line, "END FIDDLE") {
+ foundEnd = true
+ break
+ }
+ code = append(code, line)
+ }
+
+ if !foundEnd {
+ return nil, fmt.Errorf("Failed to find END FIDDLE.")
+ }
+
+ ret := &types.FiddleContext{
+ Name: match[NAME],
+ Code: strings.Join(code, "\n"),
+ Options: types.Options{
+ Width: width,
+ Height: height,
+ Source: source,
+ TextOnly: textonly,
+ },
+ }
+ return ret, nil
+}
diff --git a/named-fiddles/go/parse/parse_test.go b/named-fiddles/go/parse/parse_test.go
new file mode 100644
index 0000000..f972d41
--- /dev/null
+++ b/named-fiddles/go/parse/parse_test.go
@@ -0,0 +1,135 @@
+package parse
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "go.skia.org/infra/go/testutils/unittest"
+)
+
+const good_sample = `// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=bc9c7ea424d10bbcd1e5a88770d4794e
+REG_FIDDLE(Alpha_Constants_a, 256, 128, false, 1) {
+void draw(SkCanvas* canvas) {
+ std::vector<int32_t> srcPixels;
+ srcPixels.resize(source.height() * source.rowBytes());
+ SkPixmap pixmap(SkImageInfo::MakeN32Premul(source.width(), source.height()),
+ &srcPixels.front(), source.rowBytes());
+ source.readPixels(pixmap, 0, 0);
+ for (int y = 0; y < 16; ++y) {
+ for (int x = 0; x < 16; ++x) {
+ int32_t* blockStart = &srcPixels.front() + y * source.width() * 16 + x * 16;
+ size_t transparentCount = 0;
+ for (int fillY = 0; fillY < source.height() / 16; ++fillY) {
+ for (int fillX = 0; fillX < source.width() / 16; ++fillX) {
+ const SkColor color = SkUnPreMultiply::PMColorToColor(blockStart[fillX]);
+ transparentCount += SkColorGetA(color) == SK_AlphaTRANSPARENT;
+ }
+ blockStart += source.width();
+ }
+ if (transparentCount > 200) {
+ blockStart = &srcPixels.front() + y * source.width() * 16 + x * 16;
+ for (int fillY = 0; fillY < source.height() / 16; ++fillY) {
+ for (int fillX = 0; fillX < source.width() / 16; ++fillX) {
+ blockStart[fillX] = SK_ColorRED;
+ }
+ blockStart += source.width();
+ }
+ }
+ }
+ }
+ SkBitmap bitmap;
+ bitmap.installPixels(pixmap);
+ canvas->drawBitmap(bitmap, 0, 0);
+}
+} // END FIDDLE`
+
+const short_sample = `// Copyright 2019 Google LLC.
+REG_FIDDLE(Alpha_Constants_a, 256, 128, false, 1) {
+void draw(SkCanvas* canvas) {
+}
+} // END FIDDLE`
+
+const missing_end_sample = `// Copyright 2019 Google LLC.
+REG_FIDDLE(Alpha_Constants_a, 256, 128, false, 1) {
+void draw(SkCanvas* canvas) {
+}
+}`
+
+const inactive_sample = `#if 0 // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=f0e584aec20eaee7a5bfed62aa885eee
+REG_FIDDLE(TextBlobBuilder_allocRun, 256, 60, false, 0) {
+void draw(SkCanvas* canvas) {
+ SkTextBlobBuilder builder;
+ SkFont font;
+ SkPaint paint;
+ const SkTextBlobBuilder::RunBuffer& run = builder.allocRun(font, 5, 20, 20);
+ paint.textToGlyphs("hello", 5, run.glyphs);
+ canvas->drawRect({20, 20, 30, 30}, paint);
+ canvas->drawTextBlob(builder.make(), 20, 20, paint);
+}
+} // END FIDDLE
+#endif // Disabled until updated to use current API.`
+
+const missing_reg_sample = `
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+void draw(SkCanvas* canvas) {
+ SkTextBlobBuilder builder;
+ SkFont font;
+ SkPaint paint;
+ const SkTextBlobBuilder::RunBuffer& run = builder.allocRun(font, 5, 20, 20);
+ paint.textToGlyphs("hello", 5, run.glyphs);
+ canvas->drawRect({20, 20, 30, 30}, paint);
+ canvas->drawTextBlob(builder.make(), 20, 20, paint);
+}
+} // END FIDDLE`
+
+const bad_macro = `REG_FIDDLE(TextBlobBuilder_allocRun, foo, 60, false, 0) {`
+
+const textonly_sample = `// Copyright 2019 Google LLC.
+REG_FIDDLE(Alpha_Constants_a, 256, 128, true, 0) {
+void draw(SkCanvas* canvas) {
+}
+} // END FIDDLE`
+
+func TestParse(t *testing.T) {
+ unittest.SmallTest(t)
+
+ fc, err := ParseCpp(good_sample)
+ assert.NoError(t, err)
+
+ fc, err = ParseCpp(short_sample)
+ assert.NoError(t, err)
+ assert.Equal(t, "void draw(SkCanvas* canvas) {\n}", fc.Code)
+ assert.Equal(t, 256, fc.Options.Width)
+ assert.Equal(t, 128, fc.Options.Height)
+ assert.Equal(t, 1, fc.Options.Source)
+ assert.False(t, fc.Options.TextOnly)
+
+ fc, err = ParseCpp(missing_end_sample)
+ assert.Error(t, err)
+
+ fc, err = ParseCpp(inactive_sample)
+ assert.Equal(t, err, ErrorInactiveExample)
+ assert.Nil(t, fc)
+
+ fc, err = ParseCpp(missing_reg_sample)
+ assert.Error(t, err)
+
+ fc, err = ParseCpp(bad_macro)
+ assert.Error(t, err)
+
+ fc, err = ParseCpp(textonly_sample)
+ assert.NoError(t, err)
+ assert.Equal(t, "void draw(SkCanvas* canvas) {\n}", fc.Code)
+ assert.Equal(t, 256, fc.Options.Width)
+ assert.Equal(t, 128, fc.Options.Height)
+ assert.Equal(t, 0, fc.Options.Source)
+ assert.True(t, fc.Options.TextOnly)
+}
diff --git a/named-fiddles/images/named-fiddles/Dockerfile b/named-fiddles/images/named-fiddles/Dockerfile
new file mode 100644
index 0000000..d58390a
--- /dev/null
+++ b/named-fiddles/images/named-fiddles/Dockerfile
@@ -0,0 +1,13 @@
+FROM gcr.io/skia-public/basealpine:3.8
+
+USER root
+
+RUN apk update && \
+ apk add --no-cache git ca-certificates tzdata
+
+COPY . /
+
+USER skia
+
+ENTRYPOINT ["/usr/local/bin/named-fiddles"]
+CMD ["--logtostderr", "--prom_port=:20000", "--resources_dir=/usr/local/share/named-fiddles/"]
diff --git a/named-fiddles/images/named-fiddles/build_release b/named-fiddles/images/named-fiddles/build_release
new file mode 100755
index 0000000..a954bd0
--- /dev/null
+++ b/named-fiddles/images/named-fiddles/build_release
@@ -0,0 +1,15 @@
+#!/bin/bash
+APPNAME=named-fiddles
+
+set -x -e
+
+# Copy files into the right locations in ${ROOT}.
+copy_release_files()
+{
+INSTALL="install -D --verbose --backup=none"
+INSTALL_DIR="install -d --verbose --backup=none"
+${INSTALL} --mode=644 -T ${APPNAME}/Dockerfile ${ROOT}/Dockerfile
+${INSTALL} --mode=755 -T ${GOPATH}/bin/${APPNAME} ${ROOT}/usr/local/bin/${APPNAME}
+}
+
+source ../bash/docker_build.sh
diff --git a/promk/prometheus/alerts_public.yml b/promk/prometheus/alerts_public.yml
index 7e8e91a..9f85f15 100644
--- a/promk/prometheus/alerts_public.yml
+++ b/promk/prometheus/alerts_public.yml
@@ -78,6 +78,16 @@
annotations:
description: 'Fiddle is experiencing heavy load and has insufficient idle fiddler pods. https://skia.googlesource.com/buildbot/%2B/master/fiddlek/PROD.md#fiddler_pods'
+ - alert: NamedFiddlesFailing
+ expr: named_fiddles_total_invalid > 0
+ for: 15m
+ labels:
+ category: infra
+ severity: warning
+ owner: jcgregorio@google.com
+ annotations:
+ description: 'Some named fiddles are failing. Visit https://named-fiddles.skia.org to see which ones.'
+
- alert: FiddlerPodCommunicationErrors
expr: rate(run_exhaustion[20m]) * 20 * 60 > 5
for: 5m
diff --git a/run_unittests.go b/run_unittests.go
index c886445..ba17066 100644
--- a/run_unittests.go
+++ b/run_unittests.go
@@ -430,6 +430,7 @@
tests = append(tests, cmdTest([]string{"go", "run", "infra/bots/gen_tasks.go", "--test"}, ".", "gen_tasks.go --test", unittest.SMALL_TEST))
tests = append(tests, cmdTest([]string{"python", "go/testutils/unittest/uncategorized_tests.py"}, ".", "uncategorized tests", unittest.SMALL_TEST))
if runtime.GOOS == "linux" {
+ tests = append(tests, cmdTest([]string{"make", "testci"}, "named-fiddles", "named-fiddles elements", unittest.MEDIUM_TEST))
tests = append(tests, cmdTest([]string{"make", "test-frontend-ci"}, ".", "front-end tests", unittest.MEDIUM_TEST))
tests = append(tests, cmdTest([]string{"make", "validate"}, "proberk", "validate probers", unittest.SMALL_TEST))
tests = append(tests, cmdTest([]string{"make"}, "licenses", "check go package licenses", unittest.MEDIUM_TEST))