Import changes for goma server

  - f94fd59331f0a08e04fadd87f57e45645a45619e Remove oppressive language
  - 705de0b783ac80773b599e9d98bf9615842014a6 rootRel ignores case for Windows path.
  - d5a03991dd3066e4c4d9cee750444256ffaebf5a remoteexec: add os-family and docker-runtime tag for rbe ...
  - 4fb8d0bf2803331e7c96543e22f7e8dcd108e88e remoteexec: export latency metrics
  - d6190334f40f171b612626002bf451a1b11a368f remoteexec: release blobs in gomaInputSource
  - 080a7f2aa574a36d59f286dd9fd1b427d0909512 Ignore case to detect common inputs on Windows.
  - 3cbca9e3a7baf68d3aca935d4af9e9f485451e2b roll cloud.google.com/go/pubsub v1.6.0 to v1.6.1
  - db2a9f565a4a2760941f01c1e4f2ef2f2f61b97c remove workaround for *.thinlto.bc
  - 78812dc5c8119dd5ee4e670a411f766136414ccf Fix /showIncludes arg support for clang-cl
  - 4d8ca76aa627be90da5764c20a8c078c18f7601f roll cloud.google.com/go/pubsub v1.5.0 to v1.6.0
  - 7a48e01a4a1924cd98c92d6af49f148a56cb8403 remoteexec: don't make FILE_META for just 2MiB.
  - 86cdb70a44908700510ada0c93d244de3c746673 remoteexec: set short timeout in each stage in Exec request
  - 8775e082bc88b4901318590313d0d6557aa09a51 fix digest cache metrics
  - 91b6886c0e6ea18306b37e7943d13355478bfc77 roll github.com/google/go-cmp v0.5.0 to v0.5.1
  - d6ca1f1339907f32addf4bcb6a4498f2eb1c3436 digest: need to export fileExt tag in view
  - 8e9279c6c7c325625e91f049bed6c9baa8d0b886 digest: count diget cache usage per file ext.
  - 63373c5fdd1cc0a6bdad3f6d16e5ddd32397d7fc Set exec call max retry count
  - 206814d1c610c101c29a1657528cce3f3babd050 remoteexec: set short timeout for cas download
  - ff4661038d624605a3260328bb0a72b5a75ce0e0 remoteexec: limit buffer for gomaInputReader
  - 0e8a5925e4a191114fcaa95723506dc433cbd207 use go 1.14.6
  - 35f990fccecc3afa201f791cab5ab9b5c242293e Limit the number of files reported as missing inputs.
  - db6cfd62b40b88516df9db98305d1d99c017050c make more flags relocatable
  - f43b4e6a64dfec4e7ff2ae275952706dcad5d5de execlog: raise max message size for LogService
  - ff78635fcd063f2e9adb8ff4b1d729d0cee84b8b Limit max retries for RESOURCE_EXHAUSTED to 5
  - 062970a35a0d7b1e2cad7edd89f040bbf862f992 handle -mllvm -basic-aa-recphi=0
  - 5f5f4a475d5a3100265293b37a33f42e776bd0d7 handle -include<file> --include<file> --include=<arg>
  - a8bf0d17646a86d0f30e6b3b69023e4e7921cb26 retry slower for RESOURCE_EXHAUSTED
  - faef9c8f8fa3b843a6389bd377ce0128351b786f use go 1.14.5
  - 31215806700785c31df2aa612a275c5e69749d07 make "-mllvm", "-instcombine-*" relocatable.
  - 8d49153bf56544fb9a2fb04c17e24985e713ece4 roll cloud.google.com/go v0.60.0 to v0.61.0
  - c0138a6eeced9a322914565982748514cee72388 raise max digest cache entries to 2M.
  - 9d3d9f28622f3ad288f2e58b33ac5b4ae6a2b3d4 digest: measure digest cache stats
  - 06b1fb83893d4c1a0c68b4acdfd95c55da321cc4 auth: log group id for auth error
  - 95c3a41098c13a08b8c490ec4ecc040f0879d501 remoteexec: fix logging
  - 7d92046c6989aa78a69c8248f2ed1053b90e13e3 roll google.golang.org/api v0.28.0 to v0.29.0
  - 7ce952609abb49b29be5f6dd90659314eeeea142 roll cloud.google.com/go/pubsub v1.4.0 to v1.5.0
  - 8d8edd431bc10d3098174a942479501bc272f9a4 digest cache: limit max entries
  - 3947635af7fcb3f6a14351b5b301da115a9d8ee7 workaround for *thinlto.bc
  - c5744e4a46dfa5426580cc62c0292ff72c3c7b19 Add 'idirafter' to gccRelocatableReq
  - 46e46a33b06bd783e85286fa7c810350f83b0eaa start more slowly and backing off more quickly for RESOUR...
  - 116eac06fc66757d5f03cd30a53551f9e0e6ecff roll contrib.go.opencensus.io/exporter/stackdriver v0.13....
  - 7da28764733edc653a402ef249f0b4add7fe2216 roll cloud.google.com/go v0.59.0 to v0.60.0

TBR=yyanagisawa@google.com, yekuang@google.com
GitOrigin-RevId: f94fd59331f0a08e04fadd87f57e45645a45619e
Change-Id: I743e4ff7e8bce8e40ab888863a40cc8153596313
diff --git a/auth/acl/checker.go b/auth/acl/checker.go
index 3c85bd8..6d787cf 100644
--- a/auth/acl/checker.go
+++ b/auth/acl/checker.go
@@ -99,7 +99,7 @@
 		logger.Debugf("in group:%s", g.Id)
 		if g.Reject {
 			logger.Errorf("group:%s rejected", g.Id)
-			return "", nil, grpc.Errorf(codes.PermissionDenied, "access rejected")
+			return g.Id, nil, grpc.Errorf(codes.PermissionDenied, "access rejected")
 		}
 		if g.ServiceAccount == "" {
 			logger.Debugf("group:%s use EUC", g.Id)
@@ -109,12 +109,12 @@
 		sa := c.accounts[g.ServiceAccount]
 		if sa == nil {
 			logger.Errorf("group:%s service account not found: %s", g.Id, g.ServiceAccount)
-			return "", nil, grpc.Errorf(codes.Internal, "service account not found: %s", g.ServiceAccount)
+			return g.Id, nil, grpc.Errorf(codes.Internal, "service account not found: %s", g.ServiceAccount)
 		}
 		saToken, err := sa.Token(ctx)
 		if err != nil {
 			logger.Errorf("group:%s service account:%s error:%v", g.Id, g.ServiceAccount, err)
-			return "", nil, grpc.Errorf(codes.Internal, "service account:%s error:%v", g.ServiceAccount, err)
+			return g.Id, nil, grpc.Errorf(codes.Internal, "service account:%s error:%v", g.ServiceAccount, err)
 		}
 		logger.Debugf("group:%s use service account:%s", g.Id, g.ServiceAccount)
 		return g.Id, saToken, nil
diff --git a/auth/service.go b/auth/service.go
index 3ed2cf9..7464a34 100644
--- a/auth/service.go
+++ b/auth/service.go
@@ -145,12 +145,12 @@
 		te = v.(*tokenCacheEntry)
 	}
 	if te.TokenInfo == nil {
-		return nil, grpc.Errorf(codes.Internal, "nil TokenInfo is given")
+		return nil, grpc.Errorf(codes.Internal, "nil TokenInfo is given for %q", te.Group)
 	}
 
 	expires, err := ptypes.TimestampProto(te.TokenInfo.ExpiresAt)
 	if err != nil {
-		return nil, grpc.Errorf(codes.OutOfRange, "bad ExpiresAt %s: %v", te.TokenInfo.ExpiresAt, err)
+		return nil, grpc.Errorf(codes.OutOfRange, "bad ExpiresAt %s for %q: %v", te.TokenInfo.ExpiresAt, te.Group, err)
 	}
 
 	var errorDescription string
@@ -161,18 +161,18 @@
 		switch st.Code() {
 		case codes.OK:
 			quota = -1 // TODO: -1 is unlimited.
-			logger.Infof("token info error non-nil, but ok?: %v", st.Message())
+			logger.Infof("token info %q error non-nil, but ok?: %v", te.Group, st.Message())
 		case codes.PermissionDenied:
 			errorDescription = st.Message()
-			logger.Errorf("token permission denied: %v", te.TokenInfo.Err)
+			logger.Errorf("token info %q permission denied: %v", te.Group, te.TokenInfo.Err)
 		default:
 			errorDescription = "internal error"
-			logger.Errorf("token info error: %v", te.TokenInfo.Err)
+			logger.Errorf("token info %q error: %v", te.Group, te.TokenInfo.Err)
 		}
 	} else {
 		// non grpc error.
 		errorDescription = "internal error"
-		logger.Errorf("token info error: %v", te.TokenInfo.Err)
+		logger.Errorf("token info %q error: %v", te.Group, te.TokenInfo.Err)
 	}
 
 	resp := &authpb.AuthResp{
diff --git a/cipd_manifest.txt b/cipd_manifest.txt
index f8ce1b8..49f3268 100644
--- a/cipd_manifest.txt
+++ b/cipd_manifest.txt
@@ -13,7 +13,7 @@
 # https://chrome-infra-packages.appspot.com/
 
 # go
-infra/3pp/tools/go/${platform} version:1.14.4
+infra/3pp/tools/go/${platform} version:1.14.6
 
 # protoc
 # If the version you want is missing, please follow the instruction in:
diff --git a/cipd_manifest.versions b/cipd_manifest.versions
index 16f4be0..1868fd0 100644
--- a/cipd_manifest.versions
+++ b/cipd_manifest.versions
@@ -2,8 +2,8 @@
 # Do not modify manually. All changes will be overwritten.
 
 infra/3pp/tools/go/linux-amd64
-	version:1.14.4
-	bFsw4kD0Jqg_LkxQHOaeVG8GwbUFAfQ0T3x2ybpeu5IC
+	version:1.14.6
+	cBVTh3Z2ZrAWAVJcGGpkmtNdZ9zd09sba_mBThu3E9AC
 
 infra/tools/protoc/linux-amd64
 	protobuf_version:v3.12.3
diff --git a/cmd/auth_server/main.go b/cmd/auth_server/main.go
index 7c70a50..75bf898 100644
--- a/cmd/auth_server/main.go
+++ b/cmd/auth_server/main.go
@@ -225,7 +225,7 @@
 		checkToken = func(ctx context.Context, token *oauth2.Token, tokenInfo *auth.TokenInfo) (string, *oauth2.Token, error) {
 			account, token, err := a.CheckToken(ctx, token, tokenInfo)
 			if err != nil {
-				return "", nil, err
+				return account, nil, err
 			}
 			if rbeCheckToken != nil {
 				_, token, err = rbeCheckToken(ctx, token, tokenInfo)
diff --git a/cmd/exec_server/main.go b/cmd/exec_server/main.go
index c1e8122..2fd0d9e 100644
--- a/cmd/exec_server/main.go
+++ b/cmd/exec_server/main.go
@@ -49,6 +49,7 @@
 	filepb "go.chromium.org/goma/server/proto/file"
 	"go.chromium.org/goma/server/remoteexec"
 	"go.chromium.org/goma/server/remoteexec/digest"
+	"go.chromium.org/goma/server/rpc"
 	"go.chromium.org/goma/server/server"
 )
 
@@ -67,12 +68,22 @@
 
 	remoteexecAddr       = flag.String("remoteexec-addr", "", "use remoteexec API endpoint")
 	remoteInstancePrefix = flag.String("remote-instance-prefix", "", "remote instance name path prefix.")
-	cmdFilesBucket       = flag.String("cmd-files-bucket", "", "cloud storage bucket for command binary files")
-	fetchConfigParallel  = flag.Bool("fetch-config-parallel", true, "fetch toolchain configs in parallel")
+
+	// http://b/141901653
+	execMaxRetryCount = flag.Int("exec-max-retry-count", 5, "max retry count for exec call. 0 is unlimited count, but bound to ctx timtout. Use small number for powerful clients to run local fallback quickly. Use large number for powerless clients to use remote more than local.")
+
+	cmdFilesBucket      = flag.String("cmd-files-bucket", "", "cloud storage bucket for command binary files")
+	fetchConfigParallel = flag.Bool("fetch-config-parallel", true, "fetch toolchain configs in parallel")
 
 	// Needed for b/120582303, but will be deprecated by b/80508682.
 	fileLookupConcurrency = flag.Int("file-lookup-concurrency", 20, "concurrency to look up files from file-server")
 
+	// chromium code as of July 2020 (*.c*, *.h) = 230k
+	// also chromium clobber bulids has ~60k gomacc invocation.
+	// thinlto would upload *.o and *.thinlto.
+	// rbe-staging1 uses 2.2M keys (< 512MB memory usage in redis).
+	maxDigestCacheEntries = flag.Int("max-digest-cache-entries", 2e6, "maximum entries in in-memory digest cache. 0 means unimited")
+
 	experimentHardeningRatio = flag.Float64("experiment-hardening-ratio", 0, "Ratio [0,1] to enable hardening. 0=no hardening. 1=all hardening.")
 )
 
@@ -267,10 +278,10 @@
 	addr, err := redis.AddrFromEnv()
 	if err != nil {
 		logger.Warnf("redis disabled for gomafile-digest: %v", err)
-		return digest.NewCache(nil)
+		return digest.NewCache(nil, *maxDigestCacheEntries)
 	}
 	logger.Infof("redis enabled for gomafile-digest: %v", addr)
-	return digest.NewCache(redis.NewClient(ctx, addr, "gomafile-digest:"))
+	return digest.NewCache(redis.NewClient(ctx, addr, "gomafile-digest:"), *maxDigestCacheEntries)
 }
 
 func main() {
@@ -312,6 +323,10 @@
 	if err != nil {
 		logger.Fatal(err)
 	}
+	err = view.Register(digest.DefaultViews...)
+	if err != nil {
+		logger.Fatal(err)
+	}
 	trace.ApplyConfig(trace.Config{
 		DefaultSampler: server.NewLimitedSampler(server.DefaultTraceFraction, server.DefaultTraceQPS),
 	})
@@ -369,6 +384,9 @@
 		ExecTimeout:    15 * time.Minute,
 		Client: remoteexec.Client{
 			ClientConn: reConn,
+			Retry: rpc.Retry{
+				MaxRetry: *execMaxRetryCount,
+			},
 		},
 		GomaFile:    filepb.NewFileServiceClient(fileConn),
 		DigestCache: newDigestCache(ctx),
diff --git a/cmd/remoteexec_proxy/main.go b/cmd/remoteexec_proxy/main.go
index d79db8d..55f018e 100644
--- a/cmd/remoteexec_proxy/main.go
+++ b/cmd/remoteexec_proxy/main.go
@@ -68,11 +68,14 @@
 	platformContainerImage = flag.String("platform-container-image", "", "docker uri of platform container image")
 	insecureRemoteexec     = flag.Bool("insecure-remoteexec", false, "insecure grpc for remoteexec API")
 	insecureSkipVerify     = flag.Bool("insecure-skip-verify", false, "insecure skip verifying the server certificate")
+	execMaxRetryCount      = flag.Int("exec-max-retry-count", 5, "max retry count for exec call. 0 is unlimited count, but bound to ctx timtout. Use small number for powerful clients to run local fallback quickly. Use large number for powerless clients to use remote more than local.")
 
 	fileCacheBucket = flag.String("file-cache-bucket", "", "file cache bucking store bucket")
 
 	execConfigFile = flag.String("exec-config-file", "", "exec inventory config file")
 
+	maxDigestCacheEntries = flag.Int("max-digest-cache-entries", 2e6, "maximum entries in in-memory digest cache")
+
 	traceProjectID = flag.String("trace-project-id", "", "project id for cloud tracing")
 	traceFraction  = flag.Float64("trace-sampling-fraction", 1.0, "sampling fraction for stackdriver trace")
 	traceQPS       = flag.Float64("trace-sampling-qps-limit", 1.0, "sampling qps limit for stackdriver trace")
@@ -370,10 +373,10 @@
 	redisAddr, err := redis.AddrFromEnv()
 	if err != nil {
 		logger.Warnf("redis disabled for gomafile-digest: %v", err)
-		digestCache = digest.NewCache(nil)
+		digestCache = digest.NewCache(nil, *maxDigestCacheEntries)
 	} else {
 		logger.Infof("redis enabled for gomafile-digest: %v", redisAddr)
-		digestCache = digest.NewCache(redis.NewClient(ctx, redisAddr, "gomafile-digest:"))
+		digestCache = digest.NewCache(redis.NewClient(ctx, redisAddr, "gomafile-digest:"), *maxDigestCacheEntries)
 	}
 
 	re := &remoteexec.Adapter{
@@ -381,6 +384,9 @@
 		ExecTimeout:    15 * time.Minute,
 		Client: remoteexec.Client{
 			ClientConn: reConn,
+			Retry: rpc.Retry{
+				MaxRetry: *execMaxRetryCount,
+			},
 		},
 		InsecureClient: *insecureRemoteexec,
 		GomaFile:       fileServiceClient,
diff --git a/execlog/service.go b/execlog/service.go
index 2df3620..6ee4ba1 100644
--- a/execlog/service.go
+++ b/execlog/service.go
@@ -11,9 +11,9 @@
 )
 
 // DefaultMaxReqMsgSize is max request message size for execlog service.
-// execlog server may recives > 6MB.
+// execlog server may receives > 8MB.
 // grpc's default is 4MB.
-const DefaultMaxReqMsgSize = 8 * 1024 * 1024
+const DefaultMaxReqMsgSize = 10 * 1024 * 1024
 
 // Service represents goma execlog service.
 type Service struct {
diff --git a/go.mod b/go.mod
index c0b2526..5b4e067 100644
--- a/go.mod
+++ b/go.mod
@@ -3,17 +3,17 @@
 go 1.12
 
 require (
-	cloud.google.com/go v0.59.0
-	cloud.google.com/go/pubsub v1.4.0
+	cloud.google.com/go v0.61.0
+	cloud.google.com/go/pubsub v1.6.1
 	cloud.google.com/go/storage v1.10.0
-	contrib.go.opencensus.io/exporter/stackdriver v0.13.1
+	contrib.go.opencensus.io/exporter/stackdriver v0.13.2
 	github.com/bazelbuild/remote-apis v0.0.0-20191104140458-e77c4eb2ca48
 	github.com/bazelbuild/remote-apis-sdks v0.0.0-20200519141236-d78b5eaf5b8f
 	github.com/fsnotify/fsnotify v1.4.7
 	github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e
 	github.com/golang/protobuf v1.4.2
 	github.com/gomodule/redigo v2.0.0+incompatible
-	github.com/google/go-cmp v0.5.0
+	github.com/google/go-cmp v0.5.1
 	github.com/google/uuid v1.1.1
 	github.com/googleapis/gax-go/v2 v2.0.5
 	github.com/googleapis/google-cloud-go-testing v0.0.0-20190904031503-2d24dde44ba5
@@ -21,11 +21,11 @@
 	go.opencensus.io v0.22.4
 	go.uber.org/zap v1.10.0
 	golang.org/x/build v0.0.0-20191031202223-0706ea4fce0c
-	golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2
+	golang.org/x/net v0.0.0-20200707034311-ab3426394381
 	golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
-	golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
-	google.golang.org/api v0.28.0
-	google.golang.org/genproto v0.0.0-20200623002339-fbb79eadd5eb
+	golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208
+	google.golang.org/api v0.29.0
+	google.golang.org/genproto v0.0.0-20200726014623-da3ae01ef02d
 	google.golang.org/grpc v1.30.0
 	google.golang.org/protobuf v1.25.0
 )
diff --git a/go.sum b/go.sum
index 798e781..70e687f 100644
--- a/go.sum
+++ b/go.sum
@@ -22,8 +22,8 @@
 cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
 cloud.google.com/go v0.57.0 h1:EpMNVUorLiZIELdMZbCYX/ByTFCdoYopYAGxaGVz9ms=
 cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
-cloud.google.com/go v0.59.0 h1:BM3svUDU3itpc2m5cu5wCyThIYNDlFlts9GASw31GW8=
-cloud.google.com/go v0.59.0/go.mod h1:qJxNOVCRTxHfwLhvDxxSI9vQc1zI59b9pEglp1Iv60E=
+cloud.google.com/go v0.61.0 h1:NLQf5e1OMspfNT1RAHOB3ublr1TW3YTXO8OiWwVjK2U=
+cloud.google.com/go v0.61.0/go.mod h1:XukKJg4Y7QsUu0Hxg3qQKUWR4VuWivmyMK2+rUyxAqw=
 cloud.google.com/go/bigquery v1.0.1 h1:hL+ycaJpVE9M7nLoiXb/Pn10ENE2u+oddxbD8uu0ZVU=
 cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
 cloud.google.com/go/bigquery v1.3.0 h1:sAbMqjY1PEQKZBWfbu6Y6bsupJ9c4QdHnzg/VvYTLcE=
@@ -48,8 +48,8 @@
 cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
 cloud.google.com/go/pubsub v1.3.1 h1:ukjixP1wl0LpnZ6LWtZJ0mX5tBmjp1f8Sqer8Z2OMUU=
 cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
-cloud.google.com/go/pubsub v1.4.0 h1:76oR7VBOkL7ivoIrFKyW0k7YDCRelrlxktIzQiIUGgg=
-cloud.google.com/go/pubsub v1.4.0/go.mod h1:LFrqilwgdw4X2cJS9ALgzYmMu+ULyrUN6IHV3CPK4TM=
+cloud.google.com/go/pubsub v1.6.1 h1:lhCQrTgu7f5SjWm5yJO0geSsPORQ2OAD+Eq1AMyBW8Y=
+cloud.google.com/go/pubsub v1.6.1/go.mod h1:kvW9rcn9OLEx6eTIzMBbWbpB8YsK3vu9jxgPolVz+p4=
 cloud.google.com/go/storage v1.0.0 h1:VV2nUM3wwLLGh9lSABFgZMjInyUbJeaRSE64WuAIQ+4=
 cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
 cloud.google.com/go/storage v1.5.0 h1:RPUcBvDeYgQFMfQu1eBMq6piD1SXmLH+vK3qjewZPus=
@@ -60,8 +60,8 @@
 cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
 cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA=
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
-contrib.go.opencensus.io/exporter/stackdriver v0.13.1 h1:RX9W6FelAqTVnBi/bRXJLXr9n18v4QkQwZYIdnNS51I=
-contrib.go.opencensus.io/exporter/stackdriver v0.13.1/go.mod h1:z2tyTZtPmQ2HvWH4cOmVDgtY+1lomfKdbLnkJvZdc8c=
+contrib.go.opencensus.io/exporter/stackdriver v0.13.2 h1:5lKLBwUuq4S6pTbYaBtWmnay3eJfKNS3qL8M8HM5fM4=
+contrib.go.opencensus.io/exporter/stackdriver v0.13.2/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
@@ -189,10 +189,14 @@
 github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
 github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
+github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
 github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
 github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/martian/v3 v3.0.0 h1:pMen7vLs8nvgEYhywH3KDWJIJTeEr2ULsVWHWYHQyBs=
+github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
 github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57 h1:eqyIo2HjKhKe/mJzTG8n4VqvLXIOEG+SLdDqX7xGtkY=
 github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/pprof v0.0.0-20190515194954-54271f7e092f h1:Jnx61latede7zDD3DiiP4gmNz33uK0U5HDUaF0a/HVQ=
@@ -206,8 +210,8 @@
 github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d h1:iaAPcMIY2f+gpk8tKf0BMW5sLrlhaASiYAnFmvVG5e0=
 github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200507031123-427632fa3b1c h1:lIC98ZUNah83ky7d9EXktLFe4H7Nwus59dTOLXr8xAI=
-github.com/google/pprof v0.0.0-20200507031123-427632fa3b1c/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99 h1:Ak8CrdlwwXwAZxzS66vgPt4U8yUZX7JwLvVR58FN5jM=
+github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
@@ -284,6 +288,7 @@
 github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
 github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4=
@@ -311,6 +316,7 @@
 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522 h1:OeRHuibLsmZkFj773W4LcfAGsSxJgfPONhr8cmO+eLA=
@@ -371,6 +377,7 @@
 golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191101175033-0deb6923b6d9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -393,6 +400,10 @@
 golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2 h1:eDrdRpKgkcCqKZQwyZRyeFZgfqt37SL7Kv3tok06cKE=
 golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4=
+golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
+golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -415,6 +426,8 @@
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
 golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA=
+golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
@@ -460,10 +473,12 @@
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -516,12 +531,12 @@
 golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88 h1:4j84u0sokprDu3IdSYHJMmou+YSLflMz8p7yAx/QI4g=
 golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200527183253-8e7acdbce89d h1:SR+e35rACZFBohNb4Om1ibX6N3iO0FtdbwqGSuD9dBU=
-golang.org/x/tools v0.0.0-20200527183253-8e7acdbce89d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2 h1:FD4wDsP+CQUqh2V12OBOt90pLHVToe58P++fUu3ggV4=
 golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200622203043-20e05c1c8ffa h1:mMXQKlWCw9mIWgVLLfiycDZjMHMMYqiuakI4E/l2xcA=
-golang.org/x/tools v0.0.0-20200622203043-20e05c1c8ffa/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200713011307-fd294ab11aed h1:+qzWo37K31KxduIYaBeMqJ8MUOyTayOQKpH9aDPLMSY=
+golang.org/x/tools v0.0.0-20200713011307-fd294ab11aed/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200725200936-102e7d357031 h1:VtIxiVHWPhnny2ZTi4f9/2diZKqyLaq3FUTuud5+khA=
+golang.org/x/tools v0.0.0-20200725200936-102e7d357031/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
@@ -548,10 +563,10 @@
 google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
 google.golang.org/api v0.24.0 h1:cG03eaksBzhfSIk7JRGctfp3lanklcOM/mTGvow7BbQ=
 google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
-google.golang.org/api v0.25.0 h1:LodzhlzZEUfhXzNUMIfVlf9Gr6Ua5MMtoFWh7+f47qA=
-google.golang.org/api v0.25.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
 google.golang.org/api v0.28.0 h1:jMF5hhVfMkTZwHW1SDpKq5CkgWLXOb31Foaca9Zr3oM=
 google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.29.0 h1:BaiDisFir8O4IJxvAabCGGkQ6yCJegNQqSVoYUNAnbk=
+google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -608,12 +623,12 @@
 google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
-google.golang.org/genproto v0.0.0-20200528110217-3d3490e7e671 h1:kXfS50QRTECiLeGhluyBEvWeuvKfyKK+Id/ud02Rs5Y=
-google.golang.org/genproto v0.0.0-20200528110217-3d3490e7e671/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
 google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790 h1:FGjyjrQGURdc98leD1P65IdQD9Zlr4McvRcqIlV6OSs=
 google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
-google.golang.org/genproto v0.0.0-20200623002339-fbb79eadd5eb h1:PUcq6RTy8Gp9xukBme8m2+2Z8pQCmJ7TbPpQd6xNDvk=
-google.golang.org/genproto v0.0.0-20200623002339-fbb79eadd5eb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200711021454-869866162049 h1:YFTFpQhgvrLrmxtiIncJxFXeCyq84ixuKWVCaCAi9Oc=
+google.golang.org/genproto v0.0.0-20200711021454-869866162049/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200726014623-da3ae01ef02d h1:HJaAqDnKreMkv+AQyf1Mcw0jEmL9kKBNL07RDJu1N/k=
+google.golang.org/genproto v0.0.0-20200726014623-da3ae01ef02d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/grpc v1.19.0 h1:cfg4PD8YEdSFnm7qLV4++93WcmhH2nIUhMjhdCvl3j8=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.20.1 h1:Hz2g2wirWK7H0qIIhGIqRGTuMwTE8HEKFnDZZ7lm9NU=
diff --git a/proto/api/goma_data.pb.go b/proto/api/goma_data.pb.go
index 23e7f49..7a1dcd8 100644
--- a/proto/api/goma_data.pb.go
+++ b/proto/api/goma_data.pb.go
@@ -351,7 +351,7 @@
 const (
 	// The reason is unknown (or not BAD_REQUEST)
 	ExecResp_UNKNOWN ExecResp_BadRequestReasonCode = 0
-	// The request contains unsupported (e.g. blacklisted) compiler flags.
+	// The request contains unsupported compiler flags.
 	ExecResp_UNSUPPORTED_COMPILER_FLAGS ExecResp_BadRequestReasonCode = 1
 )
 
diff --git a/proto/api/goma_data.proto b/proto/api/goma_data.proto
index 69eff3d..594fd68 100644
--- a/proto/api/goma_data.proto
+++ b/proto/api/goma_data.proto
@@ -325,7 +325,7 @@
   enum BadRequestReasonCode {
     // The reason is unknown (or not BAD_REQUEST)
     UNKNOWN = 0;
-    // The request contains unsupported (e.g. blacklisted) compiler flags.
+    // The request contains unsupported compiler flags.
     UNSUPPORTED_COMPILER_FLAGS = 1;
   };
   enum CacheSource {
diff --git a/proto/auth/acl.pb.go b/proto/auth/acl.pb.go
index 14a5b08..90b870f 100644
--- a/proto/auth/acl.pb.go
+++ b/proto/auth/acl.pb.go
@@ -51,7 +51,6 @@
 	// If service_account is empty, pass EUC as is.
 	ServiceAccount string `protobuf:"bytes,6,opt,name=service_account,json=serviceAccount,proto3" json:"service_account,omitempty"`
 	// If reject is true, deny access from this group.
-	// Used for blacklisting.
 	Reject bool `protobuf:"varint,7,opt,name=reject,proto3" json:"reject,omitempty"`
 }
 
diff --git a/proto/auth/acl.proto b/proto/auth/acl.proto
index 965738a..d9b22fa 100644
--- a/proto/auth/acl.proto
+++ b/proto/auth/acl.proto
@@ -35,7 +35,6 @@
   string service_account = 6;
 
   // If reject is true, deny access from this group.
-  // Used for blacklisting.
   bool reject = 7;
 }
 
diff --git a/remoteexec/adapter.go b/remoteexec/adapter.go
index adb3d23..9ff6f6c 100644
--- a/remoteexec/adapter.go
+++ b/remoteexec/adapter.go
@@ -20,6 +20,7 @@
 
 	"github.com/golang/protobuf/proto"
 	"github.com/golang/protobuf/ptypes"
+	"go.opencensus.io/stats"
 	"go.opencensus.io/trace"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/credentials/oauth"
@@ -227,6 +228,10 @@
 			},
 		},
 		digestStore: gs,
+		input: &gomaInput{
+			gomaFile:    f.GomaFile,
+			digestCache: f.DigestCache,
+		},
 		action: &rpb.Action{
 			Timeout:    ptypes.DurationProto(timeout),
 			DoNotCache: doNotCache(gomaReq),
@@ -236,6 +241,53 @@
 	return r
 }
 
+// Use this to collect all timestamps and then print on one line,
+// regardless of where this function returns.
+type execSpan struct {
+	t0         time.Time
+	req        *request
+	timestamps []string
+}
+
+var spanMeasures = map[string]*stats.Float64Measure{
+	"inventory":     execInventoryTime,
+	"input tree":    execInputTreeTime,
+	"setup":         execSetupTime,
+	"check cache":   execCheckCacheTime,
+	"check missing": execCheckMissingTime,
+	"upload blobs":  execUploadBlobsTime,
+	"execute":       execExecuteTime,
+	"response":      execResponseTime,
+}
+
+func (s *execSpan) Close(ctx context.Context) {
+	s.timestamps = append(s.timestamps, fmt.Sprintf("total: %s", time.Since(s.t0).Truncate(time.Millisecond)))
+	logger := log.FromContext(ctx)
+	logger.Infof("%s", strings.Join(s.timestamps, ", "))
+}
+
+func (s *execSpan) Do(ctx context.Context, desc string, d time.Duration, f func(ctx context.Context)) time.Duration {
+	t := time.Now()
+	if d != 0 {
+		var cancel context.CancelFunc
+		ctx, cancel = context.WithTimeout(ctx, d)
+		defer cancel()
+	}
+	f(ctx)
+	duration := time.Since(t)
+	if ctx.Err() != nil {
+		logger := log.FromContext(ctx)
+		logger.Errorf("exec %s %s: timed-out %s: %s: %v", s.req.ID(), desc, d, duration, ctx.Err())
+	}
+	s.timestamps = append(s.timestamps, fmt.Sprintf("%s: %s", desc, duration.Truncate(time.Millisecond)))
+
+	if m, ok := spanMeasures[desc]; ok {
+		stats.Record(ctx, m.M(float64(duration.Nanoseconds())/1e6))
+	}
+
+	return duration
+}
+
 // Exec handles goma Exec requests with remoteexec backend.
 //
 //  1. compute input tree and Action.
@@ -269,60 +321,58 @@
 		}
 	}()
 
-	t0 := time.Now()
+	// Use this to collect all timestamps and then print on one line,
+	// regardless of where this function returns.
+	espan := &execSpan{t0: time.Now()}
+	defer espan.Close(ctx)
+
 	adjustExecReq(req)
 	ctx = f.outgoingContext(ctx, req.GetRequesterInfo())
 	f.ensureCapabilities(ctx)
 
 	r := f.newRequest(ctx, req)
+	defer r.Close()
+	espan.req = r
 
-	// Use this to collect all timestamps and then print on one line, regardless of where
-	// this function returns.
-	var timestamps []string
-	defer func() {
-		logger.Infof("%s", strings.Join(timestamps, ", "))
-	}()
-	addTimestamp := func(desc string, duration time.Duration) {
-		timestamps = append(timestamps, fmt.Sprintf("%s: %s", desc, duration.Truncate(time.Millisecond)))
-	}
-
-	t := time.Now()
-	resp = r.getInventoryData(ctx)
-	addTimestamp("inventory", time.Since(t))
+	dur := espan.Do(ctx, "inventory", 1*time.Second, func(ctx context.Context) {
+		resp = r.getInventoryData(ctx)
+	})
 	if resp != nil {
-		logger.Infof("fail fast in inventory lookup: %s", time.Since(t))
+		logger.Infof("fail fast in inventory lookup: %s", dur)
 		return resp, nil
 	}
 
-	t = time.Now()
-	resp = r.newInputTree(ctx)
-	addTimestamp("input tree", time.Since(t))
+	dur = espan.Do(ctx, "input tree", 30*time.Second, func(ctx context.Context) {
+		resp = r.newInputTree(ctx)
+	})
 	if resp != nil {
-		logger.Infof("fail fast in input tree: %s", time.Since(t))
+		logger.Infof("fail fast in input tree: %s", dur)
 		return resp, nil
 	}
 
-	t = time.Now()
-	r.setupNewAction(ctx)
-	addTimestamp("setup", time.Since(t))
+	espan.Do(ctx, "setup", 1*time.Second, func(ctx context.Context) {
+		r.setupNewAction(ctx)
+	})
 
 	eresp := &rpb.ExecuteResponse{}
 	var cached bool
-	t = time.Now()
-	eresp.Result, cached = r.checkCache(ctx)
-	addTimestamp("check cache", time.Since(t))
+	espan.Do(ctx, "check cache", 1*time.Second, func(ctx context.Context) {
+		eresp.Result, cached = r.checkCache(ctx)
+	})
 	if !cached {
-		t = time.Now()
-		blobs, err := r.missingBlobs(ctx)
-		addTimestamp("check missing", time.Since(t))
+		var blobs []*rpb.Digest
+		var err error
+		espan.Do(ctx, "check missing", 10*time.Second, func(ctx context.Context) {
+			blobs, err = r.missingBlobs(ctx)
+		})
 		if err != nil {
 			logger.Errorf("error in check missing blobs: %v", err)
 			return nil, err
 		}
 
-		t = time.Now()
-		resp, err = r.uploadBlobs(ctx, blobs)
-		addTimestamp("upload blobs", time.Since(t))
+		espan.Do(ctx, "upload blobs", 30*time.Second, func(ctx context.Context) {
+			resp, err = r.uploadBlobs(ctx, blobs)
+		})
 		if err != nil {
 			logger.Errorf("error in upload blobs: %v", err)
 			return nil, err
@@ -332,17 +382,16 @@
 			return resp, nil
 		}
 
-		t = time.Now()
-		eresp, err = r.executeAction(ctx)
-		addTimestamp("execute", time.Since(t))
+		espan.Do(ctx, "execute", 0, func(ctx context.Context) {
+			eresp, err = r.executeAction(ctx)
+		})
 		if err != nil {
 			logger.Infof("execute err=%v", err)
 			return nil, err
 		}
 	}
-	t = time.Now()
-	resp, err = r.newResp(ctx, eresp, cached)
-	addTimestamp("response", time.Since(t))
-	addTimestamp("total", time.Since(t0))
+	espan.Do(ctx, "response", 30*time.Second, func(ctx context.Context) {
+		resp, err = r.newResp(ctx, eresp, cached)
+	})
 	return resp, err
 }
diff --git a/remoteexec/adapter_test.go b/remoteexec/adapter_test.go
index 9e1828c..8c17438 100644
--- a/remoteexec/adapter_test.go
+++ b/remoteexec/adapter_test.go
@@ -237,7 +237,7 @@
 	}
 
 	t.Logf("clear in-memory digest cache, but still in redis.")
-	cluster.adapter.DigestCache = digest.NewCache(&cluster.redis)
+	cluster.adapter.DigestCache = digest.NewCache(&cluster.redis, 1e6)
 
 	req = &gomapb.ExecReq{
 		CommandSpec: clang.CommandSpec("clang", "bin/clang"),
diff --git a/remoteexec/clang-cl.go b/remoteexec/clang-cl.go
index 91ea5f0..8cd7bef 100644
--- a/remoteexec/clang-cl.go
+++ b/remoteexec/clang-cl.go
@@ -153,6 +153,8 @@
 			subArgs["cpp"] = append(subArgs["cpp"], strings.Split(arg[len("-Wp,"):], ",")...)
 		case arg == "-Xclang":
 			subCmd = "clang"
+		case arg == "-mllvm":
+			subCmd = "llvm"
 
 		case strings.HasPrefix(arg, "-w"): // inhibit all warnings
 		case strings.HasPrefix(arg, "-W"): // warning
@@ -208,6 +210,7 @@
 		case arg == "/Gw", arg == "/Gw-": // Optimize global data
 		case arg == "/GF": // Enables string pooling
 		case arg == "/c": // Compile without linking
+		case arg == "/showIncludes": // List include files
 		case strings.HasPrefix(arg, "/D"): // Preprocessor
 		case strings.HasPrefix(arg, "/EH"): // Specify error handling
 		case strings.HasPrefix(arg, "/Zc:"): // Specify compiler behavior
@@ -238,6 +241,11 @@
 				if err != nil {
 					return err
 				}
+			case "llvm":
+				err := llvmArgRelocatable(filepath, args)
+				if err != nil {
+					return err
+				}
 			default:
 				return fmt.Errorf("unsupported subcommand args %s: %s", cmd, args)
 			}
diff --git a/remoteexec/clang-cl_test.go b/remoteexec/clang-cl_test.go
index f381ee7..890f6cf 100644
--- a/remoteexec/clang-cl_test.go
+++ b/remoteexec/clang-cl_test.go
@@ -191,6 +191,12 @@
 			unknownFlag: true,
 		},
 		{
+			desc:        "non-scoped showIncludes",
+			args:        modifyArgs(baseReleaseArgs, "/showIncludes:user", "/showIncludes"),
+			relocatable: true,
+			unknownFlag: false,
+		},
+		{
 			desc:        "full chromium release build args",
 			args:        fullChromiumReleaseBuildArgs,
 			relocatable: true,
@@ -205,6 +211,18 @@
 			args:        fullLLVMReleaseBuildArgs,
 			relocatable: false,
 		},
+		{
+			desc: "-mllvm -instcombine-lower-dbg-declare=0",
+			args: append(append([]string{}, baseReleaseArgs...),
+				"-mllvm", "-instcombine-lower-dbg-declare=0"),
+			relocatable: true,
+		},
+		{
+			desc: "-mllvm -basic-aa-recphi=0",
+			args: append(append([]string{}, baseReleaseArgs...),
+				"-mllvm", "-basic-aa-recphi=0"),
+			relocatable: true,
+		},
 	} {
 		t.Run(tc.desc, func(t *testing.T) {
 			err := clangclRelocatableReq(winpath.FilePath{}, tc.args, tc.envs)
diff --git a/remoteexec/client.go b/remoteexec/client.go
index 381e4e9..a4ebf8d 100644
--- a/remoteexec/client.go
+++ b/remoteexec/client.go
@@ -35,6 +35,7 @@
 type Client struct {
 	*grpc.ClientConn
 	CallOptions []grpc.CallOption
+	Retry       rpc.Retry
 }
 
 func (c Client) callOptions(opts ...grpc.CallOption) []grpc.CallOption {
@@ -159,7 +160,7 @@
 		Recv() (*lpb.Operation, error)
 	}
 	pctx := ctx
-	err := rpc.Retry{}.Do(ctx, func() error {
+	err := c.Retry.Do(ctx, func() error {
 		ctx, cancel := context.WithTimeout(pctx, 1*time.Minute)
 		defer cancel()
 		var stream responseStream
@@ -238,7 +239,11 @@
 	// Other error would be non retriable.
 	switch codes.Code(st.GetCode()) {
 	case codes.OK:
-	case codes.FailedPrecondition, codes.ResourceExhausted, codes.Internal:
+	case codes.ResourceExhausted:
+		logger.Warnf("execute response: status=%s", st)
+		return status.FromProto(st).Err()
+
+	case codes.FailedPrecondition, codes.Internal:
 		logger.Warnf("execute response: status=%s", st)
 		fallthrough
 	case codes.Unavailable:
diff --git a/remoteexec/digest/digest_cache.go b/remoteexec/digest/digest_cache.go
index 2e28afc..008aca3 100644
--- a/remoteexec/digest/digest_cache.go
+++ b/remoteexec/digest/digest_cache.go
@@ -6,31 +6,67 @@
 	"context"
 	"errors"
 	"fmt"
+	"path/filepath"
 	"strings"
 	"sync"
 	"time"
 
 	rpb "github.com/bazelbuild/remote-apis/build/bazel/remote/execution/v2"
-
+	"github.com/golang/groupcache/lru"
 	"github.com/golang/protobuf/proto"
+	"go.opencensus.io/stats"
+	"go.opencensus.io/stats/view"
+	"go.opencensus.io/tag"
 
 	"go.chromium.org/goma/server/log"
 	cachepb "go.chromium.org/goma/server/proto/cache"
 )
 
+var (
+	cacheStats = stats.Int64(
+		"go.chromium.org/goma/server/remoteexec/digest.cache",
+		"digest cache operations",
+		stats.UnitDimensionless)
+
+	opKey      = tag.MustNewKey("op")
+	fileExtKey = tag.MustNewKey("file_ext")
+
+	DefaultViews = []*view.View{
+		{
+			Name:        "go.chromium.org/goma/server/remoteexec/digest.cache-entries",
+			Description: `number of digest cache entries`,
+			Measure:     cacheStats,
+			Aggregation: view.Sum(),
+		},
+		{
+			Name:        "go.chromium.org/goma/server/remoteexec/digest.cache-ops",
+			Description: `digest cache operations`,
+			Measure:     cacheStats,
+			TagKeys: []tag.Key{
+				opKey,
+				fileExtKey,
+			},
+			Aggregation: view.Count(),
+		},
+	}
+)
+
 // Cache caches file's digest data.
 type Cache struct {
-	c  cachepb.CacheServiceClient
-	mu sync.RWMutex
-	m  map[string]Data
+	c cachepb.CacheServiceClient
+
+	mu  sync.Mutex
+	lru lru.Cache
 }
 
 // NewCache creates new cache for digest data.
-func NewCache(c cachepb.CacheServiceClient) *Cache {
-	return &Cache{
+func NewCache(c cachepb.CacheServiceClient, maxEntries int) *Cache {
+	cache := &Cache{
 		c: c,
-		m: make(map[string]Data),
 	}
+	cache.lru.MaxEntries = maxEntries
+	cache.lru.OnEvicted = cache.onEvicted
+	return cache
 }
 
 var errNoCacheClient = errors.New("no cache client")
@@ -72,12 +108,25 @@
 
 // Get gets source's digest.
 func (c *Cache) Get(ctx context.Context, key string, src Source) (Data, error) {
+	var fileExt string
+	if gi, ok := src.(interface {
+		Filename() string
+	}); ok {
+		fileExt = filepathExt(gi.Filename())
+	}
+
 	if c != nil {
-		c.mu.RLock()
-		d, ok := c.m[key]
-		c.mu.RUnlock()
+		c.mu.Lock()
+		data, ok := c.lru.Get(lru.Key(key))
+		c.mu.Unlock()
 		if ok {
-			return d, nil
+			stats.RecordWithTags(ctx, []tag.Mutator{
+				tag.Upsert(opKey, "hit"),
+				tag.Upsert(fileExtKey, fileExt),
+			}, cacheStats.M(0))
+			// stochastically put it to cache client
+			// to make lru/lfu work?
+			return data.(Data), nil
 		}
 	}
 	var keystr string
@@ -95,12 +144,20 @@
 		d := New(src, dk)
 		if c != nil {
 			c.mu.Lock()
-			c.m[key] = d
+			c.lru.Add(lru.Key(key), d)
 			c.mu.Unlock()
+			stats.RecordWithTags(ctx, []tag.Mutator{
+				tag.Upsert(opKey, "cache-get"),
+				tag.Upsert(fileExtKey, fileExt),
+			}, cacheStats.M(1))
 		}
 		return d, nil
 	}
 	logger.Infof("digest cache miss %s %v: %s", keystr, err, time.Since(start))
+	stats.RecordWithTags(ctx, []tag.Mutator{
+		tag.Upsert(opKey, "miss"),
+		tag.Upsert(fileExtKey, fileExt),
+	}, cacheStats.M(0))
 	d, err := FromSource(ctx, src)
 	if err != nil {
 		logger.Warnf("digest from source %s %v: %s", keystr, err, time.Since(start))
@@ -108,13 +165,49 @@
 	}
 	if c != nil {
 		c.mu.Lock()
-		c.m[key] = d
+		c.lru.Add(lru.Key(key), d)
 		c.mu.Unlock()
 		logger.Infof("digest cache set %s => %v: %s", keystr, d, time.Since(start))
 		err = c.cacheSet(ctx, key, d.Digest())
 		if err != nil {
 			logger.Warnf("digest cache set fail %s => %v: %v: %s", keystr, d, err, time.Since(start))
 		}
+		stats.RecordWithTags(ctx, []tag.Mutator{
+			tag.Upsert(opKey, "cache-set"),
+			tag.Upsert(fileExtKey, fileExt),
+		}, cacheStats.M(1))
 	}
 	return d, nil
 }
+
+func (c *Cache) onEvicted(k lru.Key, value interface{}) {
+	ctx := context.Background()
+	logger := log.FromContext(ctx)
+	key := k.(string)
+	var filename, fileExt string
+	d := value.(Data)
+	if dd, ok := d.(data); ok {
+		src := dd.source
+		if gi, ok := src.(interface {
+			Filename() string
+		}); ok {
+			filename = gi.Filename()
+			fileExt = filepathExt(gi.Filename())
+		}
+	}
+	logger.Infof("digest cache evict %s %s %q", key, d.Digest(), filename)
+	stats.RecordWithTags(ctx, []tag.Mutator{
+		tag.Upsert(opKey, "evict"),
+		tag.Upsert(fileExtKey, fileExt),
+	}, cacheStats.M(-1))
+}
+
+func filepathExt(fname string) string {
+	ext := filepath.Ext(fname)
+	if strings.ContainsAny(ext, `/\`) {
+		// ext should not have path separtor.
+		// if it has, it would be in directory part.
+		ext = ""
+	}
+	return ext
+}
diff --git a/remoteexec/digest/digest_cache_test.go b/remoteexec/digest/digest_cache_test.go
index 254ee0a..61f5fcb 100644
--- a/remoteexec/digest/digest_cache_test.go
+++ b/remoteexec/digest/digest_cache_test.go
@@ -18,7 +18,7 @@
 	}
 	dc := NewCache(cache.LocalClient{
 		CacheServiceServer: c,
-	})
+	}, 1000)
 
 	ctx := context.Background()
 
diff --git a/remoteexec/exec.go b/remoteexec/exec.go
index 534ba9e..8533800 100644
--- a/remoteexec/exec.go
+++ b/remoteexec/exec.go
@@ -53,6 +53,7 @@
 
 	digestStore *digest.Store
 	tree        *merkletree.MerkleTree
+	input       gomaInputInterface
 
 	filepath clientFilePath
 
@@ -70,6 +71,10 @@
 	err error
 }
 
+func (r *request) Close() {
+	r.input.Close()
+}
+
 type clientFilePath interface {
 	IsAbs(path string) bool
 	Base(path string) string
@@ -133,6 +138,9 @@
 
 // ID returns compiler proxy id of the request.
 func (r *request) ID() string {
+	if r == nil {
+		return "<unknown>"
+	}
 	return r.gomaReq.GetRequesterInfo().GetCompilerProxyId()
 }
 
@@ -245,6 +253,7 @@
 type gomaInputInterface interface {
 	toDigest(context.Context, *gomapb.ExecReq_Input) (digest.Data, error)
 	upload(context.Context, []*gomapb.FileBlob) ([]string, error)
+	Close()
 }
 
 func uploadInputFiles(ctx context.Context, inputs []*gomapb.ExecReq_Input, gi gomaInputInterface, sema chan struct{}) error {
@@ -462,11 +471,7 @@
 	cleanCWD := r.filepath.Clean(r.gomaReq.GetCwd())
 	cleanRootDir := r.filepath.Clean(r.tree.RootDir())
 
-	gi := gomaInput{
-		gomaFile:    r.f.GomaFile,
-		digestCache: r.f.DigestCache,
-	}
-	results := inputFiles(ctx, r.gomaReq.Input, gi, func(filename string) (string, error) {
+	results := inputFiles(ctx, r.gomaReq.Input, r.input, func(filename string) (string, error) {
 		return rootRel(r.filepath, filename, cleanCWD, cleanRootDir)
 	}, executableInputs, r.f.FileLookupSema)
 	for _, result := range results {
@@ -484,7 +489,7 @@
 		}
 	}
 
-	err = uploadInputFiles(ctx, uploads, gi, r.f.FileLookupSema)
+	err = uploadInputFiles(ctx, uploads, r.input, r.f.FileLookupSema)
 	if err != nil {
 		r.err = err
 		return nil
@@ -509,6 +514,7 @@
 	if len(missingInputs) > 0 {
 		r.gomaResp.MissingInput = missingInputs
 		r.gomaResp.MissingReason = missingReason
+		thinOutMissing(r.gomaResp, missingInputLimit)
 		sortMissing(r.gomaReq.Input, r.gomaResp)
 		logFileList(logger, "missing inputs", r.gomaResp.MissingInput)
 		return r.gomaResp
@@ -1092,6 +1098,21 @@
 	})
 }
 
+// The server does not report more than this size as missing inputs to avoid DoS from Goma client.
+const missingInputLimit = 100
+
+// thinOutMissing thins out missint inputs if it is more than limit.
+// Note: sortMissing should be called after this to preserve the file name order.
+func thinOutMissing(resp *gomapb.ExecResp, limit int) {
+	if len(resp.MissingInput) < limit { // no need to thin out.
+		return
+	}
+	rand.Shuffle(len(resp.MissingInput), func(i, j int) {
+		resp.MissingInput[i], resp.MissingInput[j] = resp.MissingInput[j], resp.MissingInput[i]
+	})
+	resp.MissingInput = resp.MissingInput[:limit]
+}
+
 func logFileList(logger log.Logger, msg string, files []string) {
 	s := fmt.Sprintf("%q", files)
 	const logLineThreshold = 95 * 1024
@@ -1109,7 +1130,7 @@
 		s, files = files[0], files[1:]
 		fmt.Fprintf(&b, "%q", s)
 		if b.Len() > logLineThreshold {
-			logger.Infof("%s %d: [%s]", msg, i, b)
+			logger.Infof("%s %d: [%s]", msg, i, b.String())
 			i++
 			b.Reset()
 		}
@@ -1142,6 +1163,7 @@
 			if len(missingInputs) > 0 {
 				r.gomaResp.MissingInput = missingInputs
 				r.gomaResp.MissingReason = missingReason
+				thinOutMissing(r.gomaResp, missingInputLimit)
 				sortMissing(r.gomaReq.Input, r.gomaResp)
 				logFileList(logger, "missing inputs", r.gomaResp.MissingInput)
 				return r.gomaResp, nil
@@ -1217,15 +1239,36 @@
 		return r.gomaResp, nil
 	}
 	md := eresp.Result.GetExecutionMetadata()
-	logger.Infof("exit=%d cache=%s : exec on %q queue=%s worker=%s input=%s exec=%s output=%s",
+	queueTime := timestampSub(ctx, md.GetWorkerStartTimestamp(), md.GetQueuedTimestamp())
+	workerTime := timestampSub(ctx, md.GetWorkerCompletedTimestamp(), md.GetWorkerStartTimestamp())
+	inputTime := timestampSub(ctx, md.GetInputFetchCompletedTimestamp(), md.GetInputFetchStartTimestamp())
+	execTime := timestampSub(ctx, md.GetExecutionCompletedTimestamp(), md.GetExecutionStartTimestamp())
+	outputTime := timestampSub(ctx, md.GetOutputUploadCompletedTimestamp(), md.GetOutputUploadStartTimestamp())
+	osFamily := platformOSFamily(r.platform)
+	dockerRuntime := platformDockerRuntime(r.platform)
+	logger.Infof("exit=%d cache=%s : exec on %q[%s, %s] queue=%s worker=%s input=%s exec=%s output=%s",
 		eresp.Result.GetExitCode(),
 		r.gomaResp.GetCacheHit(),
 		md.GetWorker(),
-		timestampSub(ctx, md.GetWorkerStartTimestamp(), md.GetQueuedTimestamp()),
-		timestampSub(ctx, md.GetWorkerCompletedTimestamp(), md.GetWorkerStartTimestamp()),
-		timestampSub(ctx, md.GetInputFetchCompletedTimestamp(), md.GetInputFetchStartTimestamp()),
-		timestampSub(ctx, md.GetExecutionCompletedTimestamp(), md.GetExecutionStartTimestamp()),
-		timestampSub(ctx, md.GetOutputUploadCompletedTimestamp(), md.GetOutputUploadStartTimestamp()))
+		osFamily,
+		dockerRuntime,
+		queueTime,
+		workerTime,
+		inputTime,
+		execTime,
+		outputTime)
+	tags := []tag.Mutator{
+		tag.Upsert(rbeExitKey, fmt.Sprintf("%d", eresp.Result.GetExitCode())),
+		tag.Upsert(rbeCacheKey, r.gomaResp.GetCacheHit().String()),
+		tag.Upsert(rbePlatformOSFamilyKey, osFamily),
+		tag.Upsert(rbePlatformDockerRuntimeKey, dockerRuntime),
+	}
+	stats.RecordWithTags(ctx, tags, rbeQueueTime.M(float64(queueTime.Nanoseconds())/1e6))
+	stats.RecordWithTags(ctx, tags, rbeWorkerTime.M(float64(workerTime.Nanoseconds())/1e6))
+	stats.RecordWithTags(ctx, tags, rbeInputTime.M(float64(inputTime.Nanoseconds())/1e6))
+	stats.RecordWithTags(ctx, tags, rbeExecTime.M(float64(execTime.Nanoseconds())/1e6))
+	stats.RecordWithTags(ctx, tags, rbeOutputTime.M(float64(outputTime.Nanoseconds())/1e6))
+
 	r.gomaResp.ExecutionStats = &gomapb.ExecutionStats{
 		ExecutionStartTimestamp:     md.GetExecutionStartTimestamp(),
 		ExecutionCompletedTimestamp: md.GetExecutionCompletedTimestamp(),
@@ -1311,6 +1354,29 @@
 	return r.gomaResp, r.Err()
 }
 
+func platformOSFamily(p *rpb.Platform) string {
+	for _, p := range p.Properties {
+		if p.Name == "OSFamily" {
+			return p.Value
+		}
+	}
+	return "unspecified"
+}
+
+func platformDockerRuntime(p *rpb.Platform) string {
+	for _, p := range p.Properties {
+		switch p.Name {
+		case "dockerRuntime":
+			return p.Value
+		case "dockerPrivileged":
+			if p.Value == "true" {
+				return "nsjail"
+			}
+		}
+	}
+	return "default"
+}
+
 func shortLogMsg(msg []byte) string {
 	if len(msg) <= 1024 {
 		return string(msg)
diff --git a/remoteexec/exec_test.go b/remoteexec/exec_test.go
index acfb868..805ae10 100644
--- a/remoteexec/exec_test.go
+++ b/remoteexec/exec_test.go
@@ -219,6 +219,8 @@
 	return hashes, nil
 }
 
+func (f *fakeGomaInput) Close() {}
+
 func makeInput(tb testing.TB, content, filename string) *gomapb.ExecReq_Input {
 	tb.Helper()
 	blob := &gomapb.FileBlob{
diff --git a/remoteexec/fake_cluster_for_test.go b/remoteexec/fake_cluster_for_test.go
index d973e02..9d403e9 100644
--- a/remoteexec/fake_cluster_for_test.go
+++ b/remoteexec/fake_cluster_for_test.go
@@ -130,7 +130,7 @@
 		ExecTimeout:       10 * time.Second,
 		Client:            Client{ClientConn: f.conn},
 		GomaFile:          fpb.NewFileServiceClient(f.fconn),
-		DigestCache:       digest.NewCache(&f.redis),
+		DigestCache:       digest.NewCache(&f.redis, 1e6),
 		CmdStorage:        &f.cmdStorage,
 		ToolDetails:       &rpb.ToolDetails{},
 		FileLookupSema:    make(chan struct{}, 2),
diff --git a/remoteexec/gcc.go b/remoteexec/gcc.go
index 6f3d519..cb78be8 100644
--- a/remoteexec/gcc.go
+++ b/remoteexec/gcc.go
@@ -18,7 +18,10 @@
 	"-fprofile-instr-use=",
 	"-fprofile-sample-use=",
 	"-fsanitize-blacklist=",
+	"--include=",
+	"--include",
 	"-include=",
+	"-include",
 	"-isystem",
 	"-o",
 	"-resource-dir=",
@@ -157,6 +160,7 @@
 		case arg == "-integrated-as":
 		case arg == "-pedantic":
 		case arg == "-pipe":
+		case arg == "-pie":
 		case arg == "-pthread":
 		case arg == "-c":
 		case strings.HasPrefix(arg, "-std"):
@@ -174,6 +178,8 @@
 			pathFlag = true
 		case arg == "-isysroot":
 			pathFlag = true
+		case arg == "-idirafter":
+			pathFlag = true
 
 		case strings.HasPrefix(arg, "-"): // unknown flag?
 			return fmt.Errorf("unknown flag: %s", arg)
@@ -242,6 +248,7 @@
 			skipFlag = true
 		case arg == "-load":
 			pathFlag = true
+		case strings.HasPrefix(arg, "-debug-info-kind="):
 		default:
 			return fmt.Errorf("clang unknown arg: %s", arg)
 		}
@@ -270,6 +277,20 @@
 			// -mllvm -pbqp-coalescing
 			// https://github.com/llvm-mirror/llvm/blob/114087caa6f95b526861c3af94b3093d9444c57b/lib/CodeGen/RegAllocPBQP.cpp
 
+		case strings.HasPrefix(arg, "-instcombine-"):
+			// https://b/issues/161304121
+			// -mllvm -instcombine-lower-dbg-declare=0
+			// -instcombine-* defined in
+			// https://github.com/llvm/llvm-project/blob/8e9a505139fbef7d2e6e9d0adfe1efc87326f9ef/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
+
+		case strings.HasPrefix(arg, "-basic-aa"):
+			// -mllvm -basic-aa-recphi=0
+			// https://github.com/llvm/llvm-project/blob/694ded37b9d70e385addfc482d298b054073ebe1/llvm/lib/Analysis/BasicAliasAnalysis.cpp
+
+		case strings.HasPrefix(arg, "-sanitizer-coverage-"):
+			// -mllvm -sanitizer-coverage-prune-blocks=1
+			// https://github.com/llvm/llvm-project/blob/93ec6cd684265161623b4ea67836f022cd18c224/llvm/lib/Transforms/Instrumentation/SanitizerCoverage.cpp
+
 		default:
 			return fmt.Errorf("llvm unknown arg: %s", arg)
 		}
diff --git a/remoteexec/gcc_test.go b/remoteexec/gcc_test.go
index 4e6a3a0..3275951 100644
--- a/remoteexec/gcc_test.go
+++ b/remoteexec/gcc_test.go
@@ -281,6 +281,84 @@
 				"-static-libgcc"),
 			relocatable: true,
 		},
+		{
+			desc: "idirafter relative",
+			args: append(append([]string{}, baseReleaseArgs...),
+				"-idirafter", "../../system/ulib/include"),
+			relocatable: true,
+		},
+		{
+			desc: "idirafter abs",
+			args: append(append([]string{}, baseReleaseArgs...),
+				"-idirafter", "/b/c/system/ulib/include"),
+			relocatable: false,
+		},
+		{
+			desc: "-mllvm -instcombine-lower-dbg-declare=0",
+			args: append(append([]string{}, baseReleaseArgs...),
+				"-mllvm", "-instcombine-lower-dbg-declare=0"),
+			relocatable: true,
+		},
+		{
+			desc: "-includeBuildConfig.h",
+			args: append(append([]string{}, baseReleaseArgs...),
+				"-includeBuildConfig.h"),
+			relocatable: true,
+		},
+		{
+			desc: "-include/usr/include/BuildConfig.h",
+			args: append(append([]string{}, baseReleaseArgs...),
+				"-include/usr/include/BuildConfig.h"),
+			relocatable: false,
+		},
+		{
+			desc: "--includeBuildConfig.h",
+			args: append(append([]string{}, baseReleaseArgs...),
+				"--includeBuildConfig.h"),
+			relocatable: true,
+		},
+		{
+			desc: "--include/usr/include/BuildConfig.h",
+			args: append(append([]string{}, baseReleaseArgs...),
+				"--include/usr/include/BuildConfig.h"),
+			relocatable: false,
+		},
+		{
+			desc: "--include=BuildConfig.h",
+			args: append(append([]string{}, baseReleaseArgs...),
+				"--include=BuildConfig.h"),
+			relocatable: true,
+		},
+		{
+			desc: "--include=/usr/include/BuildConfig.h",
+			args: append(append([]string{}, baseReleaseArgs...),
+				"--include=/usr/include/BuildConfig.h"),
+			relocatable: false,
+		},
+		{
+			desc: "-mllvm -basic-aa-recphi=0",
+			args: append(append([]string{}, baseReleaseArgs...),
+				"-mllvm", "-basic-aa-recphi=0"),
+			relocatable: true,
+		},
+		{
+			desc: "-pie",
+			args: append(append([]string{}, baseReleaseArgs...),
+				"-pie"),
+			relocatable: true,
+		},
+		{
+			desc: "-Xclang -debug-info-kind=constructor",
+			args: append(append([]string{}, baseReleaseArgs...),
+				"-Xclang", "-debug-info-kind=constructor"),
+			relocatable: true,
+		},
+		{
+			desc: "-mllvm -sanitizer-coverage-prune-blocks=1",
+			args: append(append([]string{}, baseReleaseArgs...),
+				"-mllvm", "-sanitizer-coverage-prune-blocks=1"),
+			relocatable: true,
+		},
 	} {
 		t.Run(tc.desc, func(t *testing.T) {
 			err := gccRelocatableReq(posixpath.FilePath{}, tc.args, tc.envs)
diff --git a/remoteexec/gomainput.go b/remoteexec/gomainput.go
index 299767f..4852a0c 100644
--- a/remoteexec/gomainput.go
+++ b/remoteexec/gomainput.go
@@ -10,13 +10,18 @@
 	"fmt"
 	"io"
 	"io/ioutil"
+	"sync"
+	"time"
 
+	"go.opencensus.io/stats"
+	"go.opencensus.io/tag"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/status"
 
 	"go.chromium.org/goma/server/file"
 	"go.chromium.org/goma/server/hash"
+	"go.chromium.org/goma/server/log"
 	gomapb "go.chromium.org/goma/server/proto/api"
 	fpb "go.chromium.org/goma/server/proto/file"
 	"go.chromium.org/goma/server/remoteexec/digest"
@@ -35,14 +40,26 @@
 
 	// key: goma file hash -> value: digest.Data
 	digestCache DigestCache
+
+	mu   sync.Mutex
+	srcs []*gomaInputSource
+}
+
+func (gi *gomaInput) Close() {
+	gi.mu.Lock()
+	defer gi.mu.Unlock()
+	for _, src := range gi.srcs {
+		src.resetBlob()
+	}
 }
 
 // gomaInput converts goma input file to remoteexec digest.
-func (gi gomaInput) toDigest(ctx context.Context, input *gomapb.ExecReq_Input) (digest.Data, error) {
+func (gi *gomaInput) toDigest(ctx context.Context, input *gomapb.ExecReq_Input) (digest.Data, error) {
 	hashKey := input.GetHashKey()
 	// TODO: if input has size bytes, use it as digest.
 	// if it has inlined content, put it in digest.Data.
 
+	// client usually sets hashKey, but compute hashKey if not set.
 	if hashKey == "" && input.GetContent() != nil {
 		var err error
 		hashKey, err = hash.SHA256Proto(input.GetContent())
@@ -50,15 +67,20 @@
 			return nil, err
 		}
 	}
-	src := gomaInputSource{
+	src := &gomaInputSource{
 		lookupClient: gi.gomaFile,
 		hashKey:      hashKey,
+		filename:     input.GetFilename(),
 		blob:         input.GetContent(),
 	}
+	gi.mu.Lock()
+	gi.srcs = append(gi.srcs, src)
+	gi.mu.Unlock()
+
 	return gi.digestCache.Get(ctx, hashKey, src)
 }
 
-func (gi gomaInput) upload(ctx context.Context, content []*gomapb.FileBlob) ([]string, error) {
+func (gi *gomaInput) upload(ctx context.Context, content []*gomapb.FileBlob) ([]string, error) {
 	if len(content) == 0 {
 		return nil, status.Error(codes.FailedPrecondition, "upload: contents must not be empty.")
 	}
@@ -122,24 +144,48 @@
 type gomaInputSource struct {
 	lookupClient lookupClient
 	hashKey      string
-	blob         *gomapb.FileBlob
+	filename     string
+
+	mu   sync.Mutex
+	blob *gomapb.FileBlob
 }
 
-func (g gomaInputSource) String() string {
-	return fmt.Sprintf("goma-input:%s %p", g.hashKey, g.blob)
-}
-
-func (g gomaInputSource) Open(ctx context.Context) (io.ReadCloser, error) {
+func (g *gomaInputSource) String() string {
+	g.mu.Lock()
 	blob := g.blob
-	// TODO: release g.blob
+	g.mu.Unlock()
+	return fmt.Sprintf("goma-input:%s %s %p", g.hashKey, g.filename, blob)
+}
+
+func (g *gomaInputSource) Filename() string {
+	return g.filename
+}
+
+func (g *gomaInputSource) getBlob(ctx context.Context) (*gomapb.FileBlob, error) {
+	g.mu.Lock()
+	blob := g.blob
+	g.mu.Unlock()
 	if blob == nil {
-		var err error
 		blobs, err := lookup(ctx, g.lookupClient, []string{g.hashKey})
 		if err != nil {
 			return nil, err
 		}
 		blob = blobs[0]
 	}
+	return blob, nil
+}
+
+func (g *gomaInputSource) resetBlob() {
+	g.mu.Lock()
+	defer g.mu.Unlock()
+	g.blob = nil
+}
+
+func (g *gomaInputSource) Open(ctx context.Context) (io.ReadCloser, error) {
+	blob, err := g.getBlob(ctx)
+	if err != nil {
+		return nil, err
+	}
 	switch blob.GetBlobType() {
 	case gomapb.FileBlob_FILE:
 		r := bytes.NewReader(blob.Content)
@@ -151,7 +197,6 @@
 			lookupClient: g.lookupClient,
 			hashKey:      g.hashKey,
 			meta:         blob,
-			allocated:    make([]byte, gomaInputBatchSize*file.FileChunkSize),
 		}, nil
 
 	case gomapb.FileBlob_FILE_UNSPECIFIED:
@@ -162,6 +207,46 @@
 
 const gomaInputBatchSize = 5
 
+// one allocation at most 10MiB (5 * 2MB)
+// limit at most 1GB (5 * 2MiB * 100) for input buffers.
+const maxConcurrentInputBuffers = 100
+
+type gomaInputBufferPool struct {
+	sema chan bool
+	// use sync.Pool?
+}
+
+var inputBufferPool = &gomaInputBufferPool{
+	sema: make(chan bool, maxConcurrentInputBuffers),
+}
+
+func (p *gomaInputBufferPool) allocate(ctx context.Context, size int64) ([]byte, error) {
+	if size > gomaInputBatchSize*file.FileChunkSize {
+		size = gomaInputBatchSize * file.FileChunkSize
+	}
+	ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
+	defer cancel()
+	start := time.Now()
+	select {
+	case <-ctx.Done():
+		logger := log.FromContext(ctx)
+		logger.Errorf("goma input allocate buffer timed-out size=%d, %s", size, time.Since(start))
+		stats.RecordWithTags(ctx, []tag.Mutator{
+			tag.Upsert(allocStatusKey, "fail"),
+		}, inputBufferAllocSize.M(size))
+		return nil, ctx.Err()
+	case p.sema <- true:
+		stats.RecordWithTags(ctx, []tag.Mutator{
+			tag.Upsert(allocStatusKey, "ok"),
+		}, inputBufferAllocSize.M(size))
+		return make([]byte, size), nil
+	}
+}
+
+func (p *gomaInputBufferPool) release(buf []byte) {
+	<-p.sema
+}
+
 type gomaInputReader struct {
 	ctx          context.Context
 	lookupClient lookupClient
@@ -175,6 +260,10 @@
 func (r *gomaInputReader) Read(buf []byte) (int, error) {
 	if len(r.buf) == 0 {
 		if len(r.meta.HashKey) == r.i {
+			// release buffer once all contents has been processed.
+			inputBufferPool.release(r.allocated)
+			r.buf = nil
+			r.allocated = nil
 			return 0, io.EOF
 		}
 		j := r.i + gomaInputBatchSize
@@ -190,6 +279,12 @@
 				return 0, status.Errorf(codes.Internal, "lookup chunk in FILE_META %s %d %s: not FILE_CHUNK %v", r.hashKey, r.i+i, r.meta.HashKey[r.i+i], blob.GetBlobType())
 			}
 		}
+		if len(r.allocated) == 0 {
+			r.allocated, err = inputBufferPool.allocate(r.ctx, r.meta.GetFileSize())
+			if err != nil {
+				return 0, status.Errorf(codes.ResourceExhausted, "allocate buffer for FILE_META %s size=%d: %v", r.hashKey, r.meta.GetFileSize(), err)
+			}
+		}
 		b := r.allocated
 		pos := 0
 		i0 := r.i
@@ -209,5 +304,11 @@
 }
 
 func (r *gomaInputReader) Close() error {
+	// release buffer if it has not yet been released.
+	if len(r.allocated) > 0 {
+		inputBufferPool.release(r.allocated)
+		r.buf = nil
+		r.allocated = nil
+	}
 	return nil
 }
diff --git a/remoteexec/gomaoutput.go b/remoteexec/gomaoutput.go
index bb5dd68..7c3f57b 100644
--- a/remoteexec/gomaoutput.go
+++ b/remoteexec/gomaoutput.go
@@ -12,6 +12,7 @@
 	"io"
 	"os"
 	"sync"
+	"time"
 
 	rpb "github.com/bazelbuild/remote-apis/build/bazel/remote/execution/v2"
 
@@ -40,6 +41,19 @@
 	gomaFile fpb.FileServiceClient
 }
 
+func retryCAS(ctx context.Context, f func(ctx context.Context) error) error {
+	timeout := 1 * time.Second
+	n := 0
+	return rpc.Retry{}.Do(ctx, func() error {
+		n++
+		timeout *= time.Duration(n)
+		ctx, cancel := context.WithTimeout(ctx, timeout)
+		defer cancel()
+		err := f(ctx)
+		return fixRBEInternalError(err)
+	})
+}
+
 func (g gomaOutput) stdoutData(ctx context.Context, eresp *rpb.ExecuteResponse) {
 	if len(eresp.Result.StdoutRaw) > 0 {
 		g.gomaResp.Result.StdoutBuffer = eresp.Result.StdoutRaw
@@ -49,9 +63,8 @@
 		return
 	}
 	var buf bytes.Buffer
-	err := rpc.Retry{}.Do(ctx, func() error {
-		err := cas.DownloadDigest(ctx, g.bs, &buf, g.instance, eresp.Result.StdoutDigest)
-		return fixRBEInternalError(err)
+	err := retryCAS(ctx, func(ctx context.Context) error {
+		return cas.DownloadDigest(ctx, g.bs, &buf, g.instance, eresp.Result.StdoutDigest)
 	})
 	if err != nil {
 		logger := log.FromContext(ctx)
@@ -71,9 +84,8 @@
 		return
 	}
 	var buf bytes.Buffer
-	err := rpc.Retry{}.Do(ctx, func() error {
-		err := cas.DownloadDigest(ctx, g.bs, &buf, g.instance, eresp.Result.StderrDigest)
-		return fixRBEInternalError(err)
+	err := retryCAS(ctx, func(ctx context.Context) error {
+		return cas.DownloadDigest(ctx, g.bs, &buf, g.instance, eresp.Result.StderrDigest)
 	})
 	if err != nil {
 		logger := log.FromContext(ctx)
@@ -86,10 +98,10 @@
 
 func (g gomaOutput) outputFileHelper(ctx context.Context, fname string, output *rpb.OutputFile) (*gomapb.ExecResult_Output, error) {
 	var blob *gomapb.FileBlob
-	err := rpc.Retry{}.Do(ctx, func() error {
+	err := retryCAS(ctx, func(ctx context.Context) error {
 		var err error
 		blob, err = g.toFileBlob(ctx, output)
-		return fixRBEInternalError(err)
+		return err
 	})
 	if err != nil {
 		logger := log.FromContext(ctx)
@@ -216,7 +228,7 @@
 
 	logger := log.FromContext(ctx)
 
-	if output.Digest.SizeBytes < file.LargeFileThreshold {
+	if output.Digest.SizeBytes <= file.LargeFileThreshold {
 		// for single FileBlob.
 		var buf bytes.Buffer
 		err := cas.DownloadDigest(ctx, g.bs, &buf, g.instance, output.Digest)
@@ -299,9 +311,8 @@
 		return
 	}
 	var buf bytes.Buffer
-	err := rpc.Retry{}.Do(ctx, func() error {
-		err := cas.DownloadDigest(ctx, g.bs, &buf, g.instance, output.TreeDigest)
-		return fixRBEInternalError(err)
+	err := retryCAS(ctx, func(ctx context.Context) error {
+		return cas.DownloadDigest(ctx, g.bs, &buf, g.instance, output.TreeDigest)
 	})
 	if err != nil {
 		logger.Errorf("failed to download tree %s: %v", dname, err)
diff --git a/remoteexec/inputroot.go b/remoteexec/inputroot.go
index e10c42b..c3e282a 100644
--- a/remoteexec/inputroot.go
+++ b/remoteexec/inputroot.go
@@ -14,13 +14,20 @@
 	gomapb "go.chromium.org/goma/server/proto/api"
 )
 
-func samePathElement(a, b []string) []string {
+func samePathElement(filepath clientFilePath, a, b []string) []string {
 	for i, p := range a {
 		if i >= len(b) {
 			return a[:i]
 		}
-		if p != b[i] {
-			return a[:i]
+		switch filepath.(type) {
+		case winpath.FilePath:
+			if strings.ToLower(p) != strings.ToLower(b[i]) {
+				return a[:i]
+			}
+		default:
+			if p != b[i] {
+				return a[:i]
+			}
 		}
 	}
 	return a
@@ -32,7 +39,7 @@
 	}
 	path := filepath.SplitElem(paths[0])
 	for _, p := range paths[1:] {
-		path = samePathElement(path, filepath.SplitElem(p))
+		path = samePathElement(filepath, path, filepath.SplitElem(p))
 	}
 	return filepath.Join(path...)
 }
@@ -85,8 +92,11 @@
 			return false
 		}
 	case winpath.FilePath:
-		// TODO: check for win path.
-		return true
+		// The drive should be considered as root on Win.
+		// e.g. c:\ will return false.
+		if len(dir) == 3 && dir[1:] == `:\` {
+			return false
+		}
 
 	default:
 		// unknown filepath?
@@ -193,10 +203,9 @@
 	if len(fileElems) < len(rootElems) {
 		return "", errOutOfRoot
 	}
-	for i, elem := range rootElems {
-		if fileElems[i] != elem {
-			return "", errOutOfRoot
-		}
+	commonElems := samePathElement(filepath, rootElems, fileElems)
+	if len(commonElems) != len(rootElems) {
+		return "", errOutOfRoot
 	}
 	fileElems = fileElems[len(rootElems):]
 	relname := filepath.Join(fileElems...)
diff --git a/remoteexec/inputroot_test.go b/remoteexec/inputroot_test.go
index a189373..609b921 100644
--- a/remoteexec/inputroot_test.go
+++ b/remoteexec/inputroot_test.go
@@ -5,16 +5,17 @@
 package remoteexec
 
 import (
-	"reflect"
 	"testing"
 
 	"github.com/golang/protobuf/proto"
+	"github.com/google/go-cmp/cmp"
 
 	"go.chromium.org/goma/server/command/descriptor/posixpath"
+	"go.chromium.org/goma/server/command/descriptor/winpath"
 	gomapb "go.chromium.org/goma/server/proto/api"
 )
 
-func TestGetPathsWithNoCommonDir(t *testing.T) {
+func TestGetPathsWithNoCommonDirPosix(t *testing.T) {
 	for _, tc := range []struct {
 		desc  string
 		paths []string
@@ -82,8 +83,73 @@
 		},
 	} {
 		got := getPathsWithNoCommonDir(posixpath.FilePath{}, tc.paths)
-		if !reflect.DeepEqual(got, tc.want) {
-			t.Errorf("test case %s: getPathsWithNoCommonDir(paths=%v)=%v; want %v", tc.desc, tc.paths, got, tc.want)
+		if !cmp.Equal(got, tc.want) {
+			t.Errorf("test case %s: getPathsWithNoCommonDirPosix(paths=%v)=%v; want %v", tc.desc, tc.paths, got, tc.want)
+		}
+	}
+}
+
+func TestGetPathsWithNoCommonDirWin(t *testing.T) {
+	for _, tc := range []struct {
+		desc  string
+		paths []string
+		want  []string
+	}{
+		{
+			desc: `empty`,
+			// paths: nil,
+			// want:  nil,
+		},
+		{
+			desc:  `single`,
+			paths: []string{`/foo`},
+			// want:  nil,
+		},
+		{
+			desc: `has common dir`,
+			paths: []string{
+				`c:\foo\bar1`,
+				`c:\foo\local\bar2`,
+				`c:\foo\bar3`,
+			},
+			// want: nil,
+		},
+		{
+			desc: `has common dir with differnt pathsep`,
+			paths: []string{
+				`c:/foo/bar1`,
+				`c:\foo\local/bar2`,
+				`c:\foo\bar3`,
+			},
+			// want: nil,
+		},
+		{
+			desc: `has common dir with different case`,
+			paths: []string{
+				`C:\FOO\BAR1`,
+				`C:\Foo\Local\Bar2`,
+				`c:\foo\bar3`,
+			},
+			// want: nil,
+		},
+		{
+			desc: `no common dir`,
+			paths: []string{
+				`c:\foo`,
+				`c:\goo\local\baz`,
+				`c:\goo\`,
+				`c:\foo\local\bar2`,
+				`c:\foo\bar3`,
+			},
+			want: []string{
+				`c:\foo`,
+				`c:\goo\local\baz`,
+			},
+		},
+	} {
+		got := getPathsWithNoCommonDir(winpath.FilePath{}, tc.paths)
+		if !cmp.Equal(got, tc.want) {
+			t.Errorf(`test case %s: getPathsWithNoCommonDirWin(paths=%v)=%v; want %v`, tc.desc, tc.paths, got, tc.want)
 		}
 	}
 }
@@ -291,7 +357,7 @@
 	}
 }
 
-func TestRootRel(t *testing.T) {
+func TestRootRelPosix(t *testing.T) {
 	for _, tc := range []struct {
 		fname, cwd, rootDir string
 		want                string
@@ -366,6 +432,51 @@
 	}
 }
 
+func TestRootRelWin(t *testing.T) {
+	for _, tc := range []struct {
+		fname, cwd, rootDir string
+		want                string
+		wantErr             bool
+	}{
+		{
+			fname:   `..\..\base\foo.cc`,
+			cwd:     `c:\Users\foo\src\chromium\src\out\Release`,
+			rootDir: `c:\Users\foo\src\chromium\src`,
+			want:    `out\Release\..\..\base\foo.cc`,
+		},
+		{ // should ignore case to get relative path but preserve original case.
+			fname:   `..\..\Base\Foo.cc`,
+			cwd:     `C:\Users\Foo\Src\Chromium\Src\Out\Release`,
+			rootDir: `c:\users\foo\src\chromium\src`,
+			want:    `Out\Release\..\..\Base\Foo.cc`,
+		},
+		{
+			fname:   `c:\Users\foo\src\chromium\src\third_party\depot_tools\win_toolchain\vs_files\hash\win_sdk\bin\..\..\VC\Tols\MSVC\14.x.x\include\limit.h`,
+			cwd:     `c:\Users\foo\src\chromium\src\out\Release`,
+			rootDir: `c:\Users\foo\src\chromium\src`,
+			want:    `third_party\depot_tools\win_toolchain\vs_files\hash\win_sdk\bin\..\..\VC\Tols\MSVC\14.x.x\include\limit.h`,
+		},
+		{
+			fname:   `..\..\..\base\out-of-root.h`,
+			cwd:     `c:\Users\foo\src\chromium\src\out\Release`,
+			rootDir: `c:\Users\foo\src\chromium\src`,
+			wantErr: true,
+		},
+	} {
+		var filepath winpath.FilePath
+		got, err := rootRel(filepath, tc.fname, filepath.Clean(tc.cwd), filepath.Clean(tc.rootDir))
+		if tc.wantErr {
+			if err == nil {
+				t.Errorf("rootRel(winpath.FilePath, %q, %q, %q)=%v, nil; want error", tc.fname, tc.cwd, tc.rootDir, got)
+			}
+			continue
+		}
+		if err != nil || got != tc.want {
+			t.Errorf("rootRel(winpath.FilePath, %q, %q, %q)=%q, %v; want %q, nil", tc.fname, tc.cwd, tc.rootDir, got, err, tc.want)
+		}
+	}
+}
+
 func BenchmarkRootRel(b *testing.B) {
 	for i := 0; i < b.N; i++ {
 		rootRel(posixpath.FilePath{}, "../../base/foo.cc", "/home/foo/src/chromium/src/out/Release", "/home/foo/src/chromium/src")
diff --git a/remoteexec/stats.go b/remoteexec/stats.go
index cc3a0b2..b9bb3d3 100644
--- a/remoteexec/stats.go
+++ b/remoteexec/stats.go
@@ -25,6 +25,82 @@
 
 	wrapperTypeKey = tag.MustNewKey("wrapper")
 
+	inputBufferAllocSize = stats.Int64(
+		"go.chromium.org/goma/server/remoteexec.input-buffer-alloc",
+		"Size to allocate buffer for input files",
+		stats.UnitBytes)
+
+	allocStatusKey = tag.MustNewKey("status")
+
+	execInventoryTime = stats.Float64(
+		"go.chromium.org/goma/server/remoteexec.exec-inventory",
+		"Time in inventory check",
+		stats.UnitMilliseconds)
+	execInputTreeTime = stats.Float64(
+		"go.chromium.org/goma/server/remoteexec.exec-input-tree",
+		"Time in input tree construction",
+		stats.UnitMilliseconds)
+	execSetupTime = stats.Float64(
+		"go.chromium.org/goma/server/remoteexec.exec-setup",
+		"Time in setup",
+		stats.UnitMilliseconds)
+	execCheckCacheTime = stats.Float64(
+		"go.chromium.org/goma/server/remoteexec.exec-check-cache",
+		"Time to check cache",
+		stats.UnitMilliseconds)
+	execCheckMissingTime = stats.Float64(
+		"go.chromium.org/goma/server/remoteexec.exec-check-missing",
+		"Time to check missing",
+		stats.UnitMilliseconds)
+	execUploadBlobsTime = stats.Float64(
+		"go.chromium.org/goma/server/remoteexec.exec-upload-blobs",
+		"Time to upload blobs",
+		stats.UnitMilliseconds)
+	execExecuteTime = stats.Float64(
+		"go.chromium.org/goma/server/remoteexec.exec-execute",
+		"Time to execute",
+		stats.UnitMilliseconds)
+	execResponseTime = stats.Float64(
+		"go.chromium.org/goma/server/remoteexec.exec-response",
+		"Time in response",
+		stats.UnitMilliseconds)
+
+	rbeQueueTime = stats.Float64(
+		"go.chromium.org/goma/server/remoteexec.rbe-queue",
+		"Time in RBE queue",
+		stats.UnitMilliseconds)
+	rbeWorkerTime = stats.Float64(
+		"go.chromium.org/goma/server/remoteexec.rbe-worker",
+		"Time in RBE worker",
+		stats.UnitMilliseconds)
+	rbeInputTime = stats.Float64(
+		"go.chromium.org/goma/server/remoteexec.rbe-input",
+		"Time in RBE input",
+		stats.UnitMilliseconds)
+	rbeExecTime = stats.Float64(
+		"go.chromium.org/goma/server/remoteexec.rbe-exec",
+		"TIme in RBE exec",
+		stats.UnitMilliseconds)
+	rbeOutputTime = stats.Float64(
+		"go.chromium.org/goma/server/remoteexec.rbe-output",
+		"Time in RBE output",
+		stats.UnitMilliseconds)
+
+	rbeExitKey                  = tag.MustNewKey("exit")
+	rbeCacheKey                 = tag.MustNewKey("cache")
+	rbePlatformOSFamilyKey      = tag.MustNewKey("os-family")
+	rbePlatformDockerRuntimeKey = tag.MustNewKey("docker-runtime")
+	// wrapper?
+
+	rbeTagKeys = []tag.Key{
+		rbeExitKey,
+		rbeCacheKey,
+		rbePlatformOSFamilyKey,
+		rbePlatformDockerRuntimeKey,
+	}
+
+	defaultLatencyDistribution = view.Distribution(1, 2, 3, 4, 5, 6, 8, 10, 13, 16, 20, 25, 30, 40, 50, 65, 80, 100, 130, 160, 200, 250, 300, 400, 500, 650, 800, 1000, 2000, 5000, 10000, 20000, 50000, 100000, 200000, 500000)
+
 	DefaultViews = []*view.View{
 		{
 			Description: `Number of current running exec operations`,
@@ -39,6 +115,84 @@
 			Measure:     wrapperCount,
 			Aggregation: view.Count(),
 		},
+		{
+			Description: "Size to allocate buffer for input files",
+			TagKeys: []tag.Key{
+				allocStatusKey,
+			},
+			Measure:     inputBufferAllocSize,
+			Aggregation: view.Sum(),
+		},
+		{
+			Description: "Time in inventory check",
+			Measure:     execInventoryTime,
+			Aggregation: defaultLatencyDistribution,
+		},
+		{
+			Description: "Time in input tree construction",
+			Measure:     execInputTreeTime,
+			Aggregation: defaultLatencyDistribution,
+		},
+		{
+			Description: "Time in setup",
+			Measure:     execSetupTime,
+			Aggregation: defaultLatencyDistribution,
+		},
+		{
+			Description: "Time to check cache",
+			Measure:     execCheckCacheTime,
+			Aggregation: defaultLatencyDistribution,
+		},
+		{
+			Description: "Time to check missing",
+			Measure:     execCheckMissingTime,
+			Aggregation: defaultLatencyDistribution,
+		},
+		{
+			Description: "Time to upload blobs",
+			Measure:     execUploadBlobsTime,
+			Aggregation: defaultLatencyDistribution,
+		},
+		{
+			Description: "Time to execute",
+			Measure:     execExecuteTime,
+			Aggregation: defaultLatencyDistribution,
+		},
+		{
+			Description: "Time in response",
+			Measure:     execResponseTime,
+			Aggregation: defaultLatencyDistribution,
+		},
+		{
+			Description: "Time in RBE queue",
+			Measure:     rbeQueueTime,
+			TagKeys:     rbeTagKeys,
+			Aggregation: defaultLatencyDistribution,
+		},
+		{
+			Description: "Time in RBE worker",
+			Measure:     rbeWorkerTime,
+			TagKeys:     rbeTagKeys,
+			Aggregation: defaultLatencyDistribution,
+		},
+		{
+			Description: "Time in RBE input",
+			Measure:     rbeInputTime,
+			TagKeys:     rbeTagKeys,
+			Aggregation: defaultLatencyDistribution,
+		},
+		{
+			Description: "Time in RBE exec",
+			Measure:     rbeExecTime,
+			TagKeys:     rbeTagKeys,
+			Aggregation: defaultLatencyDistribution,
+		},
+		{
+			Description: "Time in RBE output",
+			Measure:     rbeOutputTime,
+			TagKeys:     rbeTagKeys,
+			Aggregation: defaultLatencyDistribution,
+		},
 	}
 )
 
diff --git a/rpc/retry.go b/rpc/retry.go
index cb41c94..cbec300 100644
--- a/rpc/retry.go
+++ b/rpc/retry.go
@@ -33,6 +33,9 @@
 	MaxRetry  int
 	BaseDelay time.Duration
 	MaxDelay  time.Duration
+
+	// backoff factor. default is 1.6
+	Factor float64
 }
 
 func (r Retry) retry() int {
@@ -56,7 +59,13 @@
 	return r.MaxDelay
 }
 
-func (r Retry) factor() float64 { return 1.6 }
+func (r Retry) factor() float64 {
+	if r.Factor == 0 {
+		return 1.6
+	}
+	return r.Factor
+}
+
 func (r Retry) jitter() float64 { return 0.2 }
 
 func (r Retry) backoff(n int) time.Duration {
@@ -80,8 +89,10 @@
 
 // RetriableError represents retriable error in Retry.Do.
 type RetriableError struct {
-	Err   error
-	Delay time.Duration
+	Err    error
+	Max    int
+	Delay  time.Duration
+	Factor float64
 }
 
 func (e RetriableError) Error() string {
@@ -112,8 +123,17 @@
 	if !ok {
 		return nil
 	}
+	var retryMax int
+	var retryDelay time.Duration
+	var retryFactor float64
 	switch st.Code() {
-	case codes.Unavailable, codes.ResourceExhausted:
+	case codes.ResourceExhausted:
+		// start slowly and backing off more quickly.
+		retryMax = 5
+		retryDelay = 1 * time.Second
+		retryFactor = 2.0
+
+	case codes.Unavailable:
 
 	case codes.DeadlineExceeded:
 		if ctx.Err() == nil {
@@ -163,17 +183,23 @@
 			dur := ri.GetRetryDelay()
 			d, err := ptypes.Duration(dur)
 			return RetriableError{
-				Err:   err,
-				Delay: d,
+				Err:    err,
+				Max:    retryMax,
+				Delay:  d,
+				Factor: retryFactor,
 			}
 		}
 	}
 	return RetriableError{
-		Err:   errors.New("no errdetails.RetryInfo in status"),
-		Delay: 0,
+		Err:    errors.New("no errdetails.RetryInfo in status"),
+		Max:    retryMax,
+		Delay:  retryDelay,
+		Factor: retryFactor,
 	}
 }
 
+var timeAfter = time.After
+
 // Do calls f with retry, while f returns RetriableError, codes.Unavailable or
 // codes.ResourceExhausted.
 // It returns codes.DeadlineExceeded if ctx is cancelled.
@@ -226,21 +252,29 @@
 		if rerr.Err != nil {
 			span.Annotatef(nil, "retryInfo.err: %v", rerr.Err)
 		}
+		if (r.MaxRetry <= 0 && rerr.Max != r.MaxRetry) || (rerr.Max > 0 && rerr.Max < r.MaxRetry) {
+			r.MaxRetry = rerr.Max
+			span.Annotatef(nil, "retryInfo.delay=%s", rerr.Delay)
+		}
 		if rerr.Delay > r.BaseDelay {
 			r.BaseDelay = rerr.Delay
 			span.Annotatef(nil, "retryInfo.delay=%s", rerr.Delay)
 		}
+		if rerr.Factor != 0 {
+			r.Factor = rerr.Factor
+			span.Annotatef(nil, "retryInfo.factor=%v", rerr.Factor)
+		}
 
 		delay := r.backoff(i)
 		span.Annotatef([]trace.Attribute{
 			trace.Int64Attribute("retry", int64(i)),
 			trace.Int64Attribute("backoff_msec", delay.Nanoseconds()/1e6),
 		}, "retry backoff %s for err:%v", delay, err)
-		logger.Warnf("retry %d backoff %s for err:%v", i, delay, err)
+		logger.Warnf("retry %d backoff %s for err:%v retryInfo=%#v", i, delay, err, r)
 		select {
 		case <-ctx.Done():
 			return grpc.Errorf(codes.DeadlineExceeded, "%v: %v: %v", ctx.Err(), d, errs)
-		case <-time.After(delay):
+		case <-timeAfter(delay):
 			d = append(d, delay)
 		}
 	}
diff --git a/rpc/retry_test.go b/rpc/retry_test.go
index 6ff4943..163c4fb 100644
--- a/rpc/retry_test.go
+++ b/rpc/retry_test.go
@@ -30,6 +30,15 @@
 }
 
 func TestRetry(t *testing.T) {
+	origTimeAfter := timeAfter
+	timeAfter = func(d time.Duration) <-chan time.Time {
+		ch := make(chan time.Time)
+		close(ch)
+		return ch
+	}
+	defer func() {
+		timeAfter = origTimeAfter
+	}()
 	for _, tc := range []struct {
 		desc    string
 		retry   Retry
@@ -156,6 +165,25 @@
 			},
 			wantN: 3,
 		},
+		{
+			desc: "retry with RESOURCE_EXHAUSTED",
+			f: &retrySpy{
+				errs: []error{
+					status.Error(codes.ResourceExhausted, "resource exhausted"),
+					status.Error(codes.ResourceExhausted, "resource exhausted"),
+					status.Error(codes.ResourceExhausted, "resource exhausted"),
+					status.Error(codes.ResourceExhausted, "resource exhausted"),
+					status.Error(codes.ResourceExhausted, "resource exhausted"),
+					status.Error(codes.ResourceExhausted, "resource exhausted"),
+					status.Error(codes.ResourceExhausted, "resource exhausted"),
+					status.Error(codes.ResourceExhausted, "resource exhausted"),
+					status.Error(codes.ResourceExhausted, "resource exhausted"),
+					status.Error(codes.ResourceExhausted, "resource exhausted"),
+				},
+			},
+			wantN:   5,
+			wantErr: true,
+		},
 	} {
 		t.Run(tc.desc, func(t *testing.T) {
 			ctx := context.Background()