Skip to content

Commit 06fb4c1

Browse files
craig[bot]rharding6373abarganierMarius Posta
committed
106552: tests: support float approximation in roachtest query comparison utils r=rharding6373 a=rharding6373 tests, logictest, floatcmp: refactor comparison test util functions This commit moves some float comparison test util functions from logictest into the floatcmp package. It also moves a query result comparison function from the tlp file to query_comparison_util in the tests package. This commit also marks roachtests as testonly targets. Epic: none Release note: None tests: support float approximation in roachtest query comparison utils Before this change unoptimized query oracle tests would compare results using simple string comparison. However, due to floating point precision limitations, it's possible for results with floating point to diverge during the course of normal computation. This results in test failures that are difficult to reproduce or determine whether they are expected behavior. This change utilizes existing floating point comparison functions used by logic tests to match float values only to a specific precision. Like the logic tests, we also have special handling for floats and decimals under the s390x architecture (see cockroachdb#63244). In order to avoid costly comparisons, we only check floating point precision if the naiive string comparison approach fails and there are float or decimal types in the result. Epic: None Fixes: cockroachdb#95665 Release note: None 106607: pkg/util/log: introduce `fluent.sink.conn.errors` metric to log package r=knz a=abarganier Logging is a critical subsystem within CRDB, but as things are today, we have very little observability into logging itself. For starters, we have no metrics in the logging package at all! This makes it difficult to observe things within the log package. For example, if a fluent-server log sink fails to connect to FluentBit, how can we tell? We get some STDOUT message, but that's about it. It's time to get some metrics into the log package. Doing so is a bit of a tricky dance, because pretty much every package in CRDB imports the logging package, meaning you almost always get a circular dependency when trying to make use of any library within pkg/util/log. Therefore, this PR injects metrics functionality into the logging package. It does so via a new interface called `LogMetrics` with functions that enable its users to modify metrics. The implementation of the interface can live elsewhere, such as the metrics package itself, whe circular dependencies aren't such a pain. We can then inject the implementation into the log package. With all that plumbing done, the PR also introduces a new metric, `fluent.sink.conn.errors` representing fluentbit connection errors whenever a fluent-server log sink fails to establish a connection. We can see the metric represented below in a test, where no fluent-server was running for a moment, before starting it. Note that the connection errors level off once the server was started: <img width="1018" alt="Screenshot 2023-07-11 at 1 32 57 PM" src="https://github.com/cockroachdb/cockroach/assets/8194877/ccacf84e-e585-4a76-98af-ed145629f9ef"> Release note (ops change): This patch introduces the counter metric `fluent.sink.conn.errors` to the CockroachDB tsdb, which is exposed to `/_status/vars` clients as `fluent_sink_conn_errors`. The metric is incremented whenever a `fluent-server` log sink fails to establish a connection to the log sink pointed to by the `address` for the sink in the provided log config. Epic: CC-9681 Addresses: cockroachdb#102753 107453: Revert "github-pull-request-make: temporary workaround" r=postamar a=postamar This reverts commit 2bd61c0. Relates to cockroachdb#106920. Co-authored-by: rharding6373 <[email protected]> Co-authored-by: Alex Barganier <[email protected]> Co-authored-by: Marius Posta <[email protected]>
4 parents 771d3f7 + c9999ae + abd9c99 + 13f1ab0 commit 06fb4c1

27 files changed

+745
-169
lines changed

pkg/BUILD.bazel

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,7 @@ ALL_TESTS = [
655655
"//pkg/util/log/eventpb:eventpb_test",
656656
"//pkg/util/log/logconfig:logconfig_test",
657657
"//pkg/util/log/logcrash:logcrash_test",
658+
"//pkg/util/log/logmetrics:logmetrics_test",
658659
"//pkg/util/log/testshout:testshout_test",
659660
"//pkg/util/log:log_test",
660661
"//pkg/util/metric/aggmetric:aggmetric_test",
@@ -2320,6 +2321,8 @@ GO_TARGETS = [
23202321
"//pkg/util/log/logcrash:logcrash",
23212322
"//pkg/util/log/logcrash:logcrash_test",
23222323
"//pkg/util/log/logflags:logflags",
2324+
"//pkg/util/log/logmetrics:logmetrics",
2325+
"//pkg/util/log/logmetrics:logmetrics_test",
23232326
"//pkg/util/log/logpb:logpb",
23242327
"//pkg/util/log/logtestutils:logtestutils",
23252328
"//pkg/util/log/logutil:logutil",

pkg/cmd/github-pull-request-make/main.go

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -209,13 +209,6 @@ func main() {
209209

210210
if len(pkgs) > 0 {
211211
for name, pkg := range pkgs {
212-
// TODO(postamar): remove this temporary workaround
213-
// This hack was added to get #106743 over the finish line.
214-
if strings.HasPrefix(name, "pkg/ccl/schemachangerccl") ||
215-
strings.HasPrefix(name, "pkg/sql/schemachanger") {
216-
continue
217-
}
218-
219212
// 20 minutes total seems OK, but at least 2 minutes per test.
220213
// This should be reduced. See #46941.
221214
duration := (20 * time.Minute) / time.Duration(len(pkgs))

pkg/cmd/roachtest/BUILD.bazel

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library", "go_test")
22

33
go_library(
44
name = "roachtest_lib",
5+
testonly = 1,
56
srcs = [
67
"cluster.go",
78
"github.go",
@@ -62,13 +63,15 @@ go_library(
6263

6364
go_binary(
6465
name = "roachtest",
66+
testonly = 1,
6567
embed = [":roachtest_lib"],
6668
visibility = ["//visibility:public"],
6769
)
6870

6971
go_test(
7072
name = "roachtest_test",
7173
size = "small",
74+
testonly = 1,
7275
srcs = [
7376
"cluster_test.go",
7477
"github_test.go",

pkg/cmd/roachtest/tests/BUILD.bazel

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
33

44
go_library(
55
name = "tests",
6+
testonly = 1,
67
srcs = [
78
"acceptance.go",
89
"activerecord.go",
@@ -224,6 +225,7 @@ go_library(
224225
"//pkg/storage",
225226
"//pkg/storage/enginepb",
226227
"//pkg/testutils",
228+
"//pkg/testutils/floatcmp",
227229
"//pkg/testutils/jobutils",
228230
"//pkg/testutils/release",
229231
"//pkg/testutils/sqlutils",
@@ -286,6 +288,7 @@ go_test(
286288
srcs = [
287289
"blocklist_test.go",
288290
"drt_test.go",
291+
"query_comparison_util_test.go",
289292
"tpcc_test.go",
290293
"util_load_group_test.go",
291294
":mocks_drt", # keep
@@ -298,6 +301,7 @@ go_test(
298301
"//pkg/roachprod/logger",
299302
"//pkg/roachprod/prometheus",
300303
"//pkg/testutils/skip",
304+
"//pkg/util/leaktest",
301305
"//pkg/util/version",
302306
"@com_github_golang_mock//gomock",
303307
"@com_github_google_go_github//github",

pkg/cmd/roachtest/tests/costfuzz.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,11 @@ func runCostFuzzQuery(qgen queryGenerator, rnd *rand.Rand, h queryComparisonHelp
114114
return nil
115115
}
116116

117-
if diff := unsortedMatricesDiff(controlRows, perturbRows); diff != "" {
117+
diff, err := unsortedMatricesDiffWithFloatComp(controlRows, perturbRows, h.colTypes)
118+
if err != nil {
119+
return err
120+
}
121+
if diff != "" {
118122
// We have a mismatch in the perturbed vs control query outputs.
119123
h.logStatements()
120124
h.logVerboseOutput()

pkg/cmd/roachtest/tests/query_comparison_util.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import (
1818
"math/rand"
1919
"os"
2020
"path/filepath"
21+
"runtime"
22+
"sort"
2123
"strings"
2224
"time"
2325

@@ -28,10 +30,12 @@ import (
2830
"github.com/cockroachdb/cockroach/pkg/internal/sqlsmith"
2931
"github.com/cockroachdb/cockroach/pkg/internal/workloadreplay"
3032
"github.com/cockroachdb/cockroach/pkg/roachprod/install"
33+
"github.com/cockroachdb/cockroach/pkg/testutils/floatcmp"
3134
"github.com/cockroachdb/cockroach/pkg/testutils/sqlutils"
3235
"github.com/cockroachdb/cockroach/pkg/util/randutil"
3336
"github.com/cockroachdb/cockroach/pkg/util/timeutil"
3437
"github.com/cockroachdb/errors"
38+
"github.com/google/go-cmp/cmp"
3539
)
3640

3741
const (
@@ -424,6 +428,7 @@ type queryComparisonHelper struct {
424428

425429
statements []string
426430
statementsAndExplains []sqlAndOutput
431+
colTypes []string
427432
}
428433

429434
// runQuery runs the given query and returns the output. If the stmt doesn't
@@ -448,6 +453,14 @@ func (h *queryComparisonHelper) runQuery(stmt string) ([][]string, error) {
448453
return nil, err
449454
}
450455
defer rows.Close()
456+
cts, err := rows.ColumnTypes()
457+
if err != nil {
458+
return nil, err
459+
}
460+
h.colTypes = make([]string, len(cts))
461+
for i, ct := range cts {
462+
h.colTypes[i] = ct.DatabaseTypeName()
463+
}
451464
return sqlutils.RowsToStrMatrix(rows)
452465
}
453466

@@ -504,3 +517,107 @@ func (h *queryComparisonHelper) logVerboseOutput() {
504517
func (h *queryComparisonHelper) makeError(err error, msg string) error {
505518
return errors.Wrapf(err, "%s. %d statements run", msg, h.stmtNo)
506519
}
520+
521+
func joinAndSortRows(rowMatrix1, rowMatrix2 [][]string, sep string) (rows1, rows2 []string) {
522+
for _, row := range rowMatrix1 {
523+
rows1 = append(rows1, strings.Join(row[:], sep))
524+
}
525+
for _, row := range rowMatrix2 {
526+
rows2 = append(rows2, strings.Join(row[:], sep))
527+
}
528+
sort.Strings(rows1)
529+
sort.Strings(rows2)
530+
return rows1, rows2
531+
}
532+
533+
// unsortedMatricesDiffWithFloatComp sorts and compares the rows in rowMatrix1
534+
// to rowMatrix2 and outputs a diff or message related to the comparison. If a
535+
// string comparison of the rows fails, and they contain floats or decimals, it
536+
// performs an approximate comparison of the values.
537+
func unsortedMatricesDiffWithFloatComp(
538+
rowMatrix1, rowMatrix2 [][]string, colTypes []string,
539+
) (string, error) {
540+
rows1, rows2 := joinAndSortRows(rowMatrix1, rowMatrix2, ",")
541+
result := cmp.Diff(rows1, rows2)
542+
if result == "" {
543+
return result, nil
544+
}
545+
if len(rows1) != len(rows2) || len(colTypes) != len(rowMatrix1[0]) || len(colTypes) != len(rowMatrix2[0]) {
546+
return result, nil
547+
}
548+
var needApproxMatch bool
549+
for i := range colTypes {
550+
// On s390x, check that values for both float and decimal coltypes are
551+
// approximately equal to take into account platform differences in floating
552+
// point calculations. On other architectures, check float values only.
553+
if (runtime.GOARCH == "s390x" && colTypes[i] == "DECIMAL") ||
554+
colTypes[i] == "FLOAT4" || colTypes[i] == "FLOAT8" {
555+
needApproxMatch = true
556+
break
557+
}
558+
}
559+
if !needApproxMatch {
560+
return result, nil
561+
}
562+
// Use an unlikely string as a separator so that we can make a comparison
563+
// using sorted rows. We don't use the rows sorted above because splitting
564+
// the rows could be ambiguous.
565+
sep := ",unsortedMatricesDiffWithFloatComp separator,"
566+
rows1, rows2 = joinAndSortRows(rowMatrix1, rowMatrix2, sep)
567+
for i := range rows1 {
568+
// Split the sorted rows.
569+
row1 := strings.Split(rows1[i], sep)
570+
row2 := strings.Split(rows2[i], sep)
571+
572+
for j := range row1 {
573+
if runtime.GOARCH == "s390x" && colTypes[j] == "DECIMAL" {
574+
// On s390x, check that values for both float and decimal coltypes are
575+
// approximately equal to take into account platform differences in floating
576+
// point calculations. On other architectures, check float values only.
577+
match, err := floatcmp.FloatsMatchApprox(row1[j], row2[j])
578+
if err != nil {
579+
return "", err
580+
}
581+
if !match {
582+
return result, nil
583+
}
584+
} else if colTypes[j] == "FLOAT4" || colTypes[j] == "FLOAT8" {
585+
// Check that float values are approximately equal.
586+
var err error
587+
var match bool
588+
if runtime.GOARCH == "s390x" {
589+
match, err = floatcmp.FloatsMatchApprox(row1[j], row2[j])
590+
} else {
591+
match, err = floatcmp.FloatsMatch(row1[j], row2[j])
592+
}
593+
if err != nil {
594+
return "", err
595+
}
596+
if !match {
597+
return result, nil
598+
}
599+
} else {
600+
// Check that other columns are equal with a string comparison.
601+
if row1[j] != row2[j] {
602+
return result, nil
603+
}
604+
}
605+
}
606+
}
607+
return "", nil
608+
}
609+
610+
// unsortedMatricesDiff sorts and compares rows of data.
611+
func unsortedMatricesDiff(rowMatrix1, rowMatrix2 [][]string) string {
612+
var rows1 []string
613+
for _, row := range rowMatrix1 {
614+
rows1 = append(rows1, strings.Join(row[:], ","))
615+
}
616+
var rows2 []string
617+
for _, row := range rowMatrix2 {
618+
rows2 = append(rows2, strings.Join(row[:], ","))
619+
}
620+
sort.Strings(rows1)
621+
sort.Strings(rows2)
622+
return cmp.Diff(rows1, rows2)
623+
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
// Copyright 2023 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 tests
12+
13+
import (
14+
"testing"
15+
16+
"github.com/cockroachdb/cockroach/pkg/util/leaktest"
17+
)
18+
19+
// TestUnsortedMatricesDiff is a unit test for the
20+
// unsortedMatricesDiffWithFloatComp() and unsortedMatricesDiff() utility
21+
// functions.
22+
func TestUnsortedMatricesDiff(t *testing.T) {
23+
defer leaktest.AfterTest(t)()
24+
tcs := []struct {
25+
name string
26+
colTypes []string
27+
t1, t2 [][]string
28+
exactMatch bool
29+
approxMatch bool
30+
}{
31+
{
32+
name: "float exact match",
33+
colTypes: []string{"FLOAT8"},
34+
t1: [][]string{{"1.2345678901234567"}},
35+
t2: [][]string{{"1.2345678901234567"}},
36+
exactMatch: true,
37+
},
38+
{
39+
name: "float approx match",
40+
colTypes: []string{"FLOAT8"},
41+
t1: [][]string{{"1.2345678901234563"}},
42+
t2: [][]string{{"1.2345678901234564"}},
43+
exactMatch: false,
44+
approxMatch: true,
45+
},
46+
{
47+
name: "float no match",
48+
colTypes: []string{"FLOAT8"},
49+
t1: [][]string{{"1.234567890123"}},
50+
t2: [][]string{{"1.234567890124"}},
51+
exactMatch: false,
52+
approxMatch: false,
53+
},
54+
{
55+
name: "multi float approx match",
56+
colTypes: []string{"FLOAT8", "FLOAT8"},
57+
t1: [][]string{{"1.2345678901234567", "1.2345678901234567"}},
58+
t2: [][]string{{"1.2345678901234567", "1.2345678901234568"}},
59+
exactMatch: false,
60+
approxMatch: true,
61+
},
62+
{
63+
name: "string no match",
64+
colTypes: []string{"STRING"},
65+
t1: [][]string{{"hello"}},
66+
t2: [][]string{{"world"}},
67+
exactMatch: false,
68+
approxMatch: false,
69+
},
70+
{
71+
name: "mixed types match",
72+
colTypes: []string{"STRING", "FLOAT8"},
73+
t1: [][]string{{"hello", "1.2345678901234567"}},
74+
t2: [][]string{{"hello", "1.2345678901234567"}},
75+
exactMatch: true,
76+
},
77+
{
78+
name: "mixed types float approx match",
79+
colTypes: []string{"STRING", "FLOAT8"},
80+
t1: [][]string{{"hello", "1.23456789012345678"}},
81+
t2: [][]string{{"hello", "1.23456789012345679"}},
82+
exactMatch: false,
83+
approxMatch: true,
84+
},
85+
{
86+
name: "mixed types no match",
87+
colTypes: []string{"STRING", "FLOAT8"},
88+
t1: [][]string{{"hello", "1.2345678901234567"}},
89+
t2: [][]string{{"world", "1.2345678901234567"}},
90+
exactMatch: false,
91+
approxMatch: false,
92+
},
93+
{
94+
name: "different col count",
95+
colTypes: []string{"STRING"},
96+
t1: [][]string{{"hello", "1.2345678901234567"}},
97+
t2: [][]string{{"world", "1.2345678901234567"}},
98+
exactMatch: false,
99+
approxMatch: false,
100+
},
101+
{
102+
name: "different row count",
103+
colTypes: []string{"STRING", "FLOAT8"},
104+
t1: [][]string{{"hello", "1.2345678901234567"}, {"aloha", "2.345"}},
105+
t2: [][]string{{"world", "1.2345678901234567"}},
106+
exactMatch: false,
107+
approxMatch: false,
108+
},
109+
{
110+
name: "multi row unsorted",
111+
colTypes: []string{"STRING", "FLOAT8"},
112+
t1: [][]string{{"hello", "1.2345678901234567"}, {"world", "1.2345678901234560"}},
113+
t2: [][]string{{"world", "1.2345678901234560"}, {"hello", "1.2345678901234567"}},
114+
exactMatch: true,
115+
},
116+
}
117+
for _, tc := range tcs {
118+
t.Run(tc.name, func(t *testing.T) {
119+
match := unsortedMatricesDiff(tc.t1, tc.t2)
120+
if tc.exactMatch && match != "" {
121+
t.Fatalf("unsortedMatricesDiff: expected exact match, got diff: %s", match)
122+
} else if !tc.exactMatch && match == "" {
123+
t.Fatalf("unsortedMatricesDiff: expected no exact match, got no diff")
124+
}
125+
126+
var err error
127+
match, err = unsortedMatricesDiffWithFloatComp(tc.t1, tc.t2, tc.colTypes)
128+
if err != nil {
129+
t.Fatal(err)
130+
}
131+
if tc.exactMatch && match != "" {
132+
t.Fatalf("unsortedMatricesDiffWithFloatComp: expected exact match, got diff: %s", match)
133+
} else if !tc.exactMatch && tc.approxMatch && match != "" {
134+
t.Fatalf("unsortedMatricesDiffWithFloatComp: expected approx match, got diff: %s", match)
135+
} else if !tc.exactMatch && !tc.approxMatch && match == "" {
136+
t.Fatalf("unsortedMatricesDiffWithFloatComp: expected no approx match, got no diff")
137+
}
138+
})
139+
}
140+
}

0 commit comments

Comments
 (0)