Skip to content

Commit c4852d7

Browse files
craig[bot]kev-caoangles-n-daemons
committed
149372: roachtest: create backup fixtures for azure r=msbutler a=kev-cao This commit adds support for backup fixtures on Azure blob storage buckets. Epic: CRDB-52076 Fixes: #149370 149704: db-console: add analytics to hot ranges page r=angles-n-daemons a=angles-n-daemons db-console: add analytics to hot ranges page So that we can better understand when the hot ranges page is being used, we want analytics on filters, sort and pagination state anytime the page is rendered. This PR achieves this, by deduplicating with a useEffect. Fixes: #144395 Epic: none Release note: none 149713: db-console: change the name of the "Hot Ranges" page to "Top Ranges" r=angles-n-daemons a=angles-n-daemons Previously, the UI referred to high-activity ranges as "hot ranges", which implied they were necessarily experiencing high load or activity. This was inadequate because ranges shown may simply be the highest by some measure (QPS, CPU usage, etc.) without being truly "hot". To address this, this patch renames the "hot ranges" page and related UI components to "top ranges" to more accurately reflect that these are the top-ranked ranges by various metrics rather than necessarily experiencing high activity. Release note (ui change): The "Hot Ranges" page in the DB Console has been renamed to "Top Ranges" to better reflect that it shows the highest-ranked ranges by various metrics, not necessarily ranges experiencing high activity. Fixes: #147330 Epic: CRDB-43150 Release note (ui change): Changes the name of the "Hot Ranges" page to "Top Ranges". Co-authored-by: Kevin Cao <[email protected]> Co-authored-by: Brian Dillmann <[email protected]>
4 parents 2b6fc50 + 5f075cf + a53d302 + 8c8962b commit c4852d7

File tree

8 files changed

+148
-20
lines changed

8 files changed

+148
-20
lines changed

pkg/cmd/roachtest/tests/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,7 @@ go_library(
350350
"@com_github_prometheus_common//model",
351351
"@com_github_stretchr_testify//assert",
352352
"@com_github_stretchr_testify//require",
353+
"@in_gopkg_yaml_v3//:yaml_v3",
353354
"@org_golang_google_protobuf//proto",
354355
"@org_golang_x_exp//maps",
355356
"@org_golang_x_oauth2//clientcredentials",

pkg/cmd/roachtest/tests/backup.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ const (
6565
AssumeRoleGCSCredentials = "GOOGLE_CREDENTIALS_ASSUME_ROLE"
6666
AssumeRoleGCSServiceAccount = "GOOGLE_SERVICE_ACCOUNT"
6767

68+
AzureClientIDEnvVar = "AZURE_CLIENT_ID"
69+
AzureClientSecretEnvVar = "AZURE_CLIENT_SECRET"
70+
AzureTenantIDEnvVar = "AZURE_TENANT_ID"
71+
6872
// rows2TiB is the number of rows to import to load 2TB of data (when
6973
// replicated).
7074
rows2TiB = 65_104_166

pkg/cmd/roachtest/tests/backup_fixtures.go

Lines changed: 76 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
gosql "database/sql"
1111
"fmt"
1212
"net/url"
13+
"os"
1314
"path"
1415
"time"
1516

@@ -33,8 +34,21 @@ import (
3334
"github.com/cockroachdb/cockroach/pkg/util/timeutil"
3435
"github.com/cockroachdb/errors"
3536
"github.com/stretchr/testify/require"
37+
"gopkg.in/yaml.v3"
3638
)
3739

40+
// At the moment, Azure VMs do not have managed identities set up yet.
41+
// Therefore, in order to use implicit authentication, we need to put a
42+
// credentials file on each VM and point the
43+
// `COCKROACH_AZURE_APPLICATION_CREDENTIALS_FILE` environment variable at the
44+
// file.
45+
// Currently, the only set of credentials that have write access to the storage
46+
// buckets are the Teamcity credentials, so the Azure fixture roachtests cannot
47+
// be run locally until those managed identities are set up.
48+
// TODO (kev-cao): Once managed identities are set up, we can remove this file
49+
// and rely on the managed identity to authenticate with Azure Blob Storage.
50+
const azureCredentialsFilePath = "/home/ubuntu/azure-credentials.yaml"
51+
3852
type BackupFixture interface {
3953
Kind() string
4054
// The database that is backed up.
@@ -159,13 +173,20 @@ type backupDriver struct {
159173
}
160174

161175
func (bd *backupDriver) prepareCluster(ctx context.Context) {
162-
bd.c.Start(ctx, bd.t.L(), option.NewStartOpts(option.NoBackupSchedule), install.MakeClusterSettings(install.ClusterSettingsOption{
163-
// Large imports can run into a death spiral where splits fail because
164-
// there is a snapshot backlog, which makes the snapshot backlog worse
165-
// because add sst causes ranges to fall behind and need recovery snapshots
166-
// to catch up.
167-
"kv.snapshot_rebalance.max_rate": "256 MiB",
168-
}))
176+
bd.c.Start(
177+
ctx, bd.t.L(), option.NewStartOpts(option.NoBackupSchedule),
178+
install.MakeClusterSettings(
179+
install.ClusterSettingsOption{
180+
// Large imports can run into a death spiral where splits fail because
181+
// there is a snapshot backlog, which makes the snapshot backlog worse
182+
// because add sst causes ranges to fall behind and need recovery snapshots
183+
// to catch up.
184+
"kv.snapshot_rebalance.max_rate": "256 MiB",
185+
},
186+
install.EnvOption{
187+
fmt.Sprintf("COCKROACH_AZURE_APPLICATION_CREDENTIALS_FILE=%s", azureCredentialsFilePath),
188+
},
189+
))
169190
}
170191

171192
func (bd *backupDriver) initWorkload(ctx context.Context) {
@@ -513,6 +534,12 @@ func GetFixtureRegistry(ctx context.Context, t test.Test, cloud spec.Cloud) *blo
513534
Host: "cockroach-fixtures-us-east-2",
514535
RawQuery: "AUTH=implicit",
515536
}
537+
case spec.Azure:
538+
uri = url.URL{
539+
Scheme: "azure-blob",
540+
Host: "cockroachdb-fixtures-eastus",
541+
RawQuery: "AUTH=implicit&AZURE_ACCOUNT_NAME=roachtest",
542+
}
516543
case spec.GCE, spec.Local:
517544
account, err := vm.Providers["gce"].FindActiveAccount(t.L())
518545
require.NoError(t, err)
@@ -544,7 +571,7 @@ func registerBackupFixtures(r registry.Registry) {
544571
}),
545572
timeout: 30 * time.Minute,
546573
suites: registry.Suites(registry.Nightly),
547-
clouds: []spec.Cloud{spec.AWS, spec.GCE, spec.Local},
574+
clouds: []spec.Cloud{spec.AWS, spec.Azure, spec.GCE, spec.Local},
548575
},
549576
{
550577
fixture: SmallFixture,
@@ -555,7 +582,7 @@ func registerBackupFixtures(r registry.Registry) {
555582
// fixture on top of the allocated 2 hours for the test.
556583
timeout: 3 * time.Hour,
557584
suites: registry.Suites(registry.Nightly),
558-
clouds: []spec.Cloud{spec.AWS, spec.GCE},
585+
clouds: []spec.Cloud{spec.AWS, spec.Azure, spec.GCE},
559586
},
560587
{
561588
fixture: MediumFixture,
@@ -566,7 +593,7 @@ func registerBackupFixtures(r registry.Registry) {
566593
}),
567594
timeout: 12 * time.Hour,
568595
suites: registry.Suites(registry.Weekly),
569-
clouds: []spec.Cloud{spec.AWS, spec.GCE},
596+
clouds: []spec.Cloud{spec.AWS, spec.Azure, spec.GCE},
570597
// The fixture takes an estimated 3.5 hours to fingerprint, so we skip it.
571598
skipFingerprint: true,
572599
},
@@ -604,6 +631,7 @@ func registerBackupFixtures(r registry.Registry) {
604631
Suites: bf.suites,
605632
Skip: bf.skip,
606633
Run: func(ctx context.Context, t test.Test, c cluster.Cluster) {
634+
require.NoError(t, maybePutAzureCredentialsFile(ctx, c, azureCredentialsFilePath))
607635
registry := GetFixtureRegistry(ctx, t, c.Cloud())
608636

609637
handle, err := registry.Create(ctx, bf.fixture.Name, t.L())
@@ -638,6 +666,44 @@ func registerBackupFixtures(r registry.Registry) {
638666
}
639667
}
640668

669+
func maybePutAzureCredentialsFile(ctx context.Context, c cluster.Cluster, path string) error {
670+
if c.Cloud() != spec.Azure {
671+
return nil
672+
}
673+
674+
type azureCreds struct {
675+
TenantID string `yaml:"azure_tenant_id"`
676+
ClientID string `yaml:"azure_client_id"`
677+
ClientSecret string `yaml:"azure_client_secret"`
678+
}
679+
680+
azureEnvVars := []string{AzureTenantIDEnvVar, AzureClientIDEnvVar, AzureClientSecretEnvVar}
681+
azureEnvValues := make(map[string]string)
682+
for _, envVar := range azureEnvVars {
683+
val := os.Getenv(envVar)
684+
if val == "" {
685+
return errors.Newf("environment variable %s is not set", envVar)
686+
}
687+
azureEnvValues[envVar] = val
688+
}
689+
690+
creds := azureCreds{
691+
TenantID: azureEnvValues[AzureTenantIDEnvVar],
692+
ClientID: azureEnvValues[AzureClientIDEnvVar],
693+
ClientSecret: azureEnvValues[AzureClientSecretEnvVar],
694+
}
695+
696+
credsYaml, err := yaml.Marshal(creds)
697+
if err != nil {
698+
return errors.Wrapf(err, "failed to marshal Azure credentials to YAML")
699+
}
700+
701+
return errors.Wrap(
702+
c.PutString(ctx, string(credsYaml), path, 0700),
703+
"failed to put Azure credentials file in cluster",
704+
)
705+
}
706+
641707
func registerBlobFixtureGC(r registry.Registry) {
642708
r.Add(registry.TestSpec{
643709
Name: "blobfixture/gc",

pkg/roachprod/blobfixture/registry.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ type Registry struct {
5757
// for all fixture data and metadata. See the comment on the uri field for the
5858
// structure of a fixture directory.
5959
func NewRegistry(ctx context.Context, uri url.URL) (*Registry, error) {
60-
supportedSchemes := map[string]bool{"gs": true, "s3": true, "azure": true}
60+
supportedSchemes := map[string]bool{"gs": true, "s3": true, "azure-blob": true}
6161
if !supportedSchemes[uri.Scheme] {
6262
return nil, errors.Errorf("unsupported scheme %q", uri.Scheme)
6363
}

pkg/ui/workspaces/db-console/src/app.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -473,10 +473,15 @@ export const App: React.FC<AppProps> = (props: AppProps) => {
473473
{/* hot ranges */}
474474
<Route
475475
exact
476-
path={`/hotranges`}
476+
path={`/topranges`}
477477
component={HotRangesPage}
478478
/>
479479
{/* old route redirects */}
480+
<Route
481+
exact
482+
path={`/hotranges`}
483+
component={HotRangesPage}
484+
/>
480485
<Redirect
481486
exact
482487
from="/cluster"

pkg/ui/workspaces/db-console/src/views/app/components/layoutSidebar/index.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ export class Sidebar extends React.Component<SidebarProps> {
5252
isHidden: () => this.props.isSingleNodeCluster,
5353
},
5454
{
55-
path: "/hotranges",
56-
text: "Hot Ranges",
57-
activeFor: ["/hotranges", "/reports/range"],
55+
path: "/topranges",
56+
text: "Top Ranges",
57+
activeFor: ["/hotranges", "/hotranges", "/reports/range"],
5858
},
5959
{ path: "/jobs", text: "Jobs", activeFor: [] },
6060
{ path: "/schedules", text: "Schedules", activeFor: [] },

pkg/ui/workspaces/db-console/src/views/hotRanges/hotRangesTable.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@ import {
1212
Anchor,
1313
EmptyTable,
1414
util,
15+
ISortedTablePagination,
1516
} from "@cockroachlabs/cluster-ui";
1617
import { Tooltip } from "antd";
1718
import classNames from "classnames/bind";
1819
import round from "lodash/round";
19-
import React from "react";
20+
import React, { useEffect } from "react";
2021
import { connect } from "react-redux";
2122
import { Link } from "react-router-dom";
2223

@@ -47,6 +48,10 @@ interface HotRangesTableProps {
4748
clearFilterContainer: React.ReactNode;
4849
sortSetting?: SortSetting;
4950
onSortChange?: (ss: SortSetting) => void;
51+
onViewPropertiesChange?: (vp: {
52+
sortSetting: SortSetting;
53+
pagination: ISortedTablePagination;
54+
}) => void;
5055
emptyMessage?: EmptyMessage;
5156
}
5257

@@ -57,6 +62,7 @@ const HotRangesTable = ({
5762
clearFilterContainer,
5863
sortSetting,
5964
onSortChange,
65+
onViewPropertiesChange,
6066
emptyMessage,
6167
}: HotRangesTableProps) => {
6268
const [pagination, updatePagination] = util.usePagination(1, PAGE_SIZE);
@@ -278,6 +284,13 @@ const HotRangesTable = ({
278284
},
279285
];
280286

287+
useEffect(() => {
288+
onViewPropertiesChange?.({
289+
sortSetting,
290+
pagination,
291+
});
292+
}, [sortSetting, pagination, onViewPropertiesChange]);
293+
281294
return (
282295
<div className="section">
283296
<div className={cx("hotranges-heading-container")}>

pkg/ui/workspaces/db-console/src/views/hotRanges/index.tsx

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,24 @@
33
// Use of this software is governed by the CockroachDB Software License
44
// included in the /LICENSE file.
55

6+
import { createHash } from "crypto";
7+
68
import {
79
Loading,
810
Text,
911
Anchor,
1012
util,
1113
TimezoneContext,
14+
SortSetting,
15+
ISortedTablePagination,
1216
} from "@cockroachlabs/cluster-ui";
1317
import classNames from "classnames/bind";
14-
import React, { useRef, useMemo, useEffect, useContext } from "react";
18+
import React, { useRef, useMemo, useEffect, useContext, useState } from "react";
1519
import { Helmet } from "react-helmet";
1620
import { useDispatch, useSelector } from "react-redux";
1721

1822
import { cockroach } from "src/js/protos";
23+
import { analytics } from "src/redux/analytics";
1924
import {
2025
refreshHotRanges,
2126
clearHotRanges,
@@ -63,6 +68,8 @@ const HotRangesPage = () => {
6368
const timezone = useContext(TimezoneContext);
6469

6570
const { filters, applyFilters } = useFilters();
71+
const [sortSetting, setSortSetting] = useState<SortSetting>(null);
72+
const [pagination, setPagination] = useState<ISortedTablePagination>(null);
6673

6774
// dispatch hot ranges call whenever the filters change and are not empty
6875
useEffect(() => {
@@ -77,6 +84,28 @@ const HotRangesPage = () => {
7784
}
7885
}, [filters.nodeIds, dispatch]);
7986

87+
// track analytics on filters, pagination and sort.
88+
const analyticsKey = createHash("md5")
89+
.update(JSON.stringify([filters, sortSetting, pagination]))
90+
.digest("hex");
91+
useEffect(() => {
92+
if (!filters.nodeIds.length || !pagination || !sortSetting) {
93+
return;
94+
}
95+
analytics.track({
96+
event: "Hot Ranges Page Load",
97+
properties: {
98+
filters,
99+
pagination,
100+
sortSetting,
101+
},
102+
});
103+
// this is keyed on a hash of the contents for filters, pagination and sortSetting
104+
// as opposed to the references themselves, as we don't want to re-run this effect
105+
// when the contents haven't changed, but the references have.
106+
// eslint-disable-next-line react-hooks/exhaustive-deps
107+
}, [analyticsKey]);
108+
80109
// load the databases if possible.
81110
useEffect(() => {
82111
dispatch(refreshDatabases());
@@ -97,10 +126,10 @@ const HotRangesPage = () => {
97126

98127
return (
99128
<React.Fragment>
100-
<Helmet title="Hot Ranges" />
101-
<h3 className="base-heading">Hot Ranges</h3>
129+
<Helmet title="Top Ranges" />
130+
<h3 className="base-heading">Top Ranges</h3>
102131
<Text className={cx("hotranges-description")}>
103-
The Hot Ranges table shows ranges receiving a high number of reads or
132+
The Top Ranges table shows ranges receiving a high number of reads or
104133
writes. By default, the table is sorted by ranges with the highest QPS
105134
(queries per second). <br />
106135
Use this information to
@@ -129,6 +158,16 @@ const HotRangesPage = () => {
129158
nodeIdToLocalityMap={nodeIdToLocalityMap}
130159
clearFilterContainer={<span ref={clearButtonRef} />}
131160
emptyMessage={emptyMessage}
161+
onViewPropertiesChange={({
162+
sortSetting,
163+
pagination,
164+
}: {
165+
sortSetting: SortSetting;
166+
pagination: ISortedTablePagination;
167+
}) => {
168+
setSortSetting(sortSetting);
169+
setPagination(pagination);
170+
}}
132171
/>
133172
)}
134173
page={undefined}

0 commit comments

Comments
 (0)