github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/telemetry_test.go (about) 1 // Copyright 2020 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package sql_test 12 13 import ( 14 "bytes" 15 "context" 16 "fmt" 17 "regexp" 18 "sort" 19 "strings" 20 "testing" 21 "text/tabwriter" 22 23 "github.com/cockroachdb/cockroach/pkg/base" 24 "github.com/cockroachdb/cockroach/pkg/roachpb" 25 "github.com/cockroachdb/cockroach/pkg/server" 26 "github.com/cockroachdb/cockroach/pkg/server/diagnosticspb" 27 "github.com/cockroachdb/cockroach/pkg/sql" 28 "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" 29 "github.com/cockroachdb/cockroach/pkg/testutils/diagutils" 30 "github.com/cockroachdb/cockroach/pkg/testutils/serverutils" 31 "github.com/cockroachdb/cockroach/pkg/testutils/sqlutils" 32 "github.com/cockroachdb/cockroach/pkg/util/cloudinfo" 33 "github.com/cockroachdb/cockroach/pkg/util/leaktest" 34 "github.com/cockroachdb/cockroach/pkg/util/treeprinter" 35 "github.com/cockroachdb/datadriven" 36 "github.com/cockroachdb/errors" 37 ) 38 39 // TestTelemetry runs the datadriven telemetry tests. The test sets up a 40 // database and a testing diagnostics reporting server. The test implements the 41 // following data-driven commands: 42 // 43 // - exec 44 // 45 // Executes SQL statements against the database. Outputs no results on 46 // success. In case of error, outputs the error message. 47 // 48 // - feature-whitelist 49 // 50 // The input for this command is not SQL, but a list of regular expressions. 51 // Tests that follow (until the next feature-whitelist command) will only 52 // output counters that match a regexp in this white list. 53 // 54 // - feature-usage, feature-counters 55 // 56 // Executes SQL statements and then outputs the feature counters from the 57 // white list that have been reported to the diagnostic server. The first 58 // variant outputs only the names of the counters that changed; the second 59 // variant outputs the counts as well. It is necessary to use 60 // feature-whitelist before these commands to avoid test flakes (e.g. because 61 // of counters that are changed by looking up descriptors) 62 // 63 // - schema 64 // 65 // Outputs reported schema information. 66 // 67 // - sql-stats 68 // 69 // Executes SQL statements and then outputs information about reported sql 70 // statement statistics. 71 // 72 func TestTelemetry(t *testing.T) { 73 defer leaktest.AfterTest(t)() 74 75 ctx := context.Background() 76 // Note: these tests cannot be run in parallel (with each other or with other 77 // tests) because telemetry counters are global. 78 datadriven.Walk(t, "testdata/telemetry", func(t *testing.T, path string) { 79 // Disable cloud info reporting (it would make these tests really slow). 80 defer cloudinfo.Disable()() 81 82 diagSrv := diagutils.NewServer() 83 defer diagSrv.Close() 84 85 diagSrvURL := diagSrv.URL() 86 params := base.TestServerArgs{ 87 Knobs: base.TestingKnobs{ 88 Server: &server.TestingKnobs{ 89 DiagnosticsTestingKnobs: diagnosticspb.TestingKnobs{ 90 OverrideReportingURL: &diagSrvURL, 91 }, 92 }, 93 }, 94 } 95 s, sqlConn, _ := serverutils.StartServer(t, params) 96 defer s.Stopper().Stop(ctx) 97 98 runner := sqlutils.MakeSQLRunner(sqlConn) 99 // Disable automatic reporting so it doesn't interfere with the test. 100 runner.Exec(t, "SET CLUSTER SETTING diagnostics.reporting.enabled = false") 101 runner.Exec(t, "SET CLUSTER SETTING diagnostics.reporting.send_crash_reports = false") 102 // Disable plan caching to get accurate counts if the same statement is 103 // issued multiple times. 104 runner.Exec(t, "SET CLUSTER SETTING sql.query_cache.enabled = false") 105 106 var whitelist featureWhitelist 107 datadriven.RunTest(t, path, func(t *testing.T, td *datadriven.TestData) string { 108 switch td.Cmd { 109 case "exec": 110 _, err := sqlConn.Exec(td.Input) 111 if err != nil { 112 if errors.HasAssertionFailure(err) { 113 td.Fatalf(t, "%+v", err) 114 } 115 return fmt.Sprintf("error: %v\n", err) 116 } 117 return "" 118 119 case "schema": 120 s.ReportDiagnostics(ctx) 121 last := diagSrv.LastRequestData() 122 var buf bytes.Buffer 123 for i := range last.Schema { 124 buf.WriteString(formatTableDescriptor(&last.Schema[i])) 125 } 126 return buf.String() 127 128 case "feature-whitelist": 129 var err error 130 whitelist, err = makeWhitelist(strings.Split(td.Input, "\n")) 131 if err != nil { 132 td.Fatalf(t, "error parsing feature regex: %s", err) 133 } 134 return "" 135 136 case "feature-usage", "feature-counters": 137 // Report diagnostics once to reset the counters. 138 s.ReportDiagnostics(ctx) 139 _, err := sqlConn.Exec(td.Input) 140 var buf bytes.Buffer 141 if err != nil { 142 fmt.Fprintf(&buf, "error: %v\n", err) 143 } 144 s.ReportDiagnostics(ctx) 145 last := diagSrv.LastRequestData() 146 usage := last.FeatureUsage 147 keys := make([]string, 0, len(usage)) 148 for k, v := range usage { 149 if v == 0 { 150 // Ignore zero values (shouldn't happen in practice) 151 continue 152 } 153 if !whitelist.Match(k) { 154 // Feature key not in whitelist. 155 continue 156 } 157 keys = append(keys, k) 158 } 159 sort.Strings(keys) 160 tw := tabwriter.NewWriter(&buf, 2, 1, 2, ' ', 0) 161 for _, k := range keys { 162 // Report either just the key or the key and the count. 163 if td.Cmd == "feature-counters" { 164 fmt.Fprintf(tw, "%s\t%d\n", k, usage[k]) 165 } else { 166 fmt.Fprintf(tw, "%s\n", k) 167 } 168 } 169 _ = tw.Flush() 170 return buf.String() 171 172 case "sql-stats": 173 // Report diagnostics once to reset the stats. 174 s.SQLServer().(*sql.Server).ResetSQLStats(ctx) 175 s.ReportDiagnostics(ctx) 176 177 _, err := sqlConn.Exec(td.Input) 178 var buf bytes.Buffer 179 if err != nil { 180 fmt.Fprintf(&buf, "error: %v\n", err) 181 } 182 s.SQLServer().(*sql.Server).ResetSQLStats(ctx) 183 s.ReportDiagnostics(ctx) 184 last := diagSrv.LastRequestData() 185 buf.WriteString(formatSQLStats(last.SqlStats)) 186 return buf.String() 187 188 default: 189 td.Fatalf(t, "unknown command %s", td.Cmd) 190 return "" 191 } 192 }) 193 }) 194 } 195 196 type featureWhitelist []*regexp.Regexp 197 198 func makeWhitelist(strings []string) (featureWhitelist, error) { 199 w := make(featureWhitelist, len(strings)) 200 for i := range strings { 201 var err error 202 w[i], err = regexp.Compile("^" + strings[i] + "$") 203 if err != nil { 204 return nil, err 205 } 206 } 207 return w, nil 208 } 209 210 func (w featureWhitelist) Match(feature string) bool { 211 if w == nil { 212 // Unset whitelist matches all counters. 213 return true 214 } 215 for _, r := range w { 216 if r.MatchString(feature) { 217 return true 218 } 219 } 220 return false 221 } 222 223 func formatTableDescriptor(desc *sqlbase.TableDescriptor) string { 224 tp := treeprinter.New() 225 n := tp.Childf("table:%s", desc.Name) 226 cols := n.Child("columns") 227 for _, col := range desc.Columns { 228 var colBuf bytes.Buffer 229 fmt.Fprintf(&colBuf, "%s:%s", col.Name, col.Type.String()) 230 if col.DefaultExpr != nil { 231 fmt.Fprintf(&colBuf, " default: %s", *col.DefaultExpr) 232 } 233 if col.ComputeExpr != nil { 234 fmt.Fprintf(&colBuf, " computed: %s", *col.ComputeExpr) 235 } 236 cols.Child(colBuf.String()) 237 } 238 if len(desc.Checks) > 0 { 239 checks := n.Child("checks") 240 for _, chk := range desc.Checks { 241 checks.Childf("%s: %s", chk.Name, chk.Expr) 242 } 243 } 244 return tp.String() 245 } 246 247 func formatSQLStats(stats []roachpb.CollectedStatementStatistics) string { 248 bucketByApp := make(map[string][]roachpb.CollectedStatementStatistics) 249 for i := range stats { 250 s := &stats[i] 251 252 if strings.HasPrefix(s.Key.App, sqlbase.InternalAppNamePrefix) { 253 // Let's ignore all internal queries for this test. 254 continue 255 } 256 bucketByApp[s.Key.App] = append(bucketByApp[s.Key.App], *s) 257 } 258 var apps []string 259 for app, s := range bucketByApp { 260 apps = append(apps, app) 261 sort.Slice(s, func(i, j int) bool { 262 return s[i].Key.Query < s[j].Key.Query 263 }) 264 bucketByApp[app] = s 265 } 266 sort.Strings(apps) 267 tp := treeprinter.New() 268 n := tp.Child("sql-stats") 269 270 for _, app := range apps { 271 nodeApp := n.Child(app) 272 for _, s := range bucketByApp[app] { 273 var flags []string 274 if s.Key.Failed { 275 flags = append(flags, "failed") 276 } 277 if !s.Key.DistSQL { 278 flags = append(flags, "nodist") 279 } 280 var buf bytes.Buffer 281 if len(flags) > 0 { 282 buf.WriteString("[") 283 for i := range flags { 284 if i > 0 { 285 buf.WriteByte(',') 286 } 287 buf.WriteString(flags[i]) 288 } 289 buf.WriteString("] ") 290 } 291 buf.WriteString(s.Key.Query) 292 nodeApp.Child(buf.String()) 293 } 294 } 295 return tp.String() 296 }