go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/client/cmd/cas/casimpl/output.go (about) 1 // Copyright 2021 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package casimpl 16 17 import ( 18 "encoding/json" 19 "os" 20 "sort" 21 22 "go.chromium.org/luci/common/data/packedintset" 23 "go.chromium.org/luci/common/errors" 24 ) 25 26 type StatusCode int 27 28 // possible exit errors 29 const ( 30 // Bad command line arguments were provided 31 ArgumentsInvalid StatusCode = iota 32 // Error authenticating with RBE-CAS server 33 AuthenticationError 34 // Error instantiating a new RBE client 35 ClientError 36 // The provided digest is bad and the file/dir cannot be found 37 DigestInvalid 38 // Disk I/O issues 39 IOError 40 // Issue with RPC to RBE-CAS server 41 RPCError 42 // Any uncategorised error 43 Unknown 44 ) 45 46 // writeExitResult writes the status msg 47 func writeExitResult(path string, statusCode StatusCode, digest string) error { 48 if path == "" { 49 return nil 50 } 51 52 var toString = map[StatusCode]string{ 53 ArgumentsInvalid: "arguments_invalid", 54 AuthenticationError: "authentication_error", 55 ClientError: "client_error", 56 DigestInvalid: "digest_invalid", 57 IOError: "io_error", 58 RPCError: "rpc_error", 59 Unknown: "unknown", 60 } 61 62 type ErrorDetails struct { 63 Digest string `json:"digest,omitempty"` 64 } 65 66 var body struct { 67 Result string `json:"result"` 68 ErrorDetails ErrorDetails `json:"error_details,omitempty"` 69 } 70 71 body.Result = toString[statusCode] 72 if digest != "" { 73 body.ErrorDetails = ErrorDetails{Digest: digest} 74 } 75 76 out, err := json.Marshal(body) 77 if err != nil { 78 return errors.Annotate(err, "failed to marshal json").Err() 79 } 80 if err := os.WriteFile(path, out, 0600); err != nil { 81 return errors.Annotate(err, "failed to write json").Err() 82 } 83 return nil 84 } 85 86 // writeStats writes cache stats in packed format. 87 func writeStats(path string, hot, cold []int64) error { 88 // Copy before sort. 89 cold = append([]int64{}, cold...) 90 hot = append([]int64{}, hot...) 91 92 sort.Slice(cold, func(i, j int) bool { return cold[i] < cold[j] }) 93 sort.Slice(hot, func(i, j int) bool { return hot[i] < hot[j] }) 94 95 var sizeCold, sizeHot int64 96 for _, fileSize := range cold { 97 sizeCold += fileSize 98 } 99 for _, fileSize := range hot { 100 sizeHot += fileSize 101 } 102 103 packedCold, err := packedintset.Pack(cold) 104 if err != nil { 105 return errors.Annotate(err, "failed to pack uploaded items").Err() 106 } 107 108 packedHot, err := packedintset.Pack(hot) 109 if err != nil { 110 return errors.Annotate(err, "failed to pack not uploaded items").Err() 111 } 112 113 statsJSON, err := json.Marshal(struct { 114 ItemsCold []byte `json:"items_cold"` 115 SizeCold int64 `json:"size_cold"` 116 ItemsHot []byte `json:"items_hot"` 117 SizeHot int64 `json:"size_hot"` 118 Result string `json:"result"` 119 }{ 120 ItemsCold: packedCold, 121 SizeCold: sizeCold, 122 ItemsHot: packedHot, 123 SizeHot: sizeHot, 124 Result: "success", 125 }) 126 if err != nil { 127 return errors.Annotate(err, "failed to marshal json").Err() 128 } 129 if err := os.WriteFile(path, statsJSON, 0600); err != nil { 130 return errors.Annotate(err, "failed to write json").Err() 131 } 132 133 return nil 134 }