Skip to content

Commit eeb9bb0

Browse files
committed
fix(TopShards): switch between history and immediate data
1 parent 33fc57e commit eeb9bb0

File tree

11 files changed

+171
-40
lines changed

11 files changed

+171
-40
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.top-shards {
2+
&__filters {
3+
display: flex;
4+
flex-wrap: wrap;
5+
align-items: baseline;
6+
gap: 16px;
7+
}
8+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import {RadioButton} from '@gravity-ui/uikit';
2+
3+
import {DateRange, DateRangeValues} from '../../../../../components/DateRange';
4+
5+
import {
6+
EShardsWorkloadMode,
7+
IShardsWorkloadFilters,
8+
} from '../../../../../types/store/shardsWorkload';
9+
10+
import {isEnumMember} from '../../../../../utils/typecheckers';
11+
12+
import i18n from '../i18n';
13+
import {b} from '../TopShards';
14+
15+
import './Filters.scss';
16+
17+
interface FiltersProps {
18+
value: IShardsWorkloadFilters;
19+
onChange: (value: Partial<IShardsWorkloadFilters>) => void;
20+
className?: string;
21+
}
22+
23+
export const Filters = ({value, onChange, className}: FiltersProps) => {
24+
const handleModeChange = (mode: string) => {
25+
if (!isEnumMember(EShardsWorkloadMode, mode)) {
26+
const values = Object.values(EShardsWorkloadMode).join(', ');
27+
throw new Error(`Unexpected TopShards mode "${mode}". Should be one of: ${values}`);
28+
}
29+
30+
onChange({mode});
31+
};
32+
33+
const handleDateRangeChange = (dateRange: DateRangeValues) => {
34+
onChange({
35+
mode: EShardsWorkloadMode.History,
36+
...dateRange,
37+
});
38+
};
39+
40+
const from = value.mode === EShardsWorkloadMode.Immediate ? undefined : value.from;
41+
const to = value.mode === EShardsWorkloadMode.Immediate ? undefined : value.to;
42+
43+
return (
44+
<div className={b('filters', className)}>
45+
<RadioButton value={value.mode} onUpdate={handleModeChange}>
46+
<RadioButton.Option value={EShardsWorkloadMode.Immediate}>
47+
{i18n('filters.mode.immediate')}
48+
</RadioButton.Option>
49+
<RadioButton.Option value={EShardsWorkloadMode.History}>
50+
{i18n('filters.mode.history')}
51+
</RadioButton.Option>
52+
</RadioButton>
53+
<DateRange from={from} to={to} onChange={handleDateRangeChange} />
54+
</div>
55+
);
56+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './Filters';

src/containers/Tenant/Diagnostics/TopShards/TopShards.scss

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
.top-shards {
22
display: flex;
33
flex-direction: column;
4+
gap: 10px;
45

56
height: 100%;
67

@@ -11,15 +12,6 @@
1112
justify-content: center;
1213
}
1314

14-
&__controls {
15-
display: flex;
16-
flex-wrap: wrap;
17-
align-items: baseline;
18-
gap: 16px;
19-
20-
margin-bottom: 10px;
21-
}
22-
2315
&__table {
2416
overflow: auto;
2517
flex-grow: 1;

src/containers/Tenant/Diagnostics/TopShards/TopShards.tsx

Lines changed: 57 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import cn from 'bem-cn-lite';
55
import DataTable, {Column, Settings, SortOrder} from '@gravity-ui/react-data-table';
66
import {Loader} from '@gravity-ui/uikit';
77

8-
import {DateRange, DateRangeValues} from '../../../../components/DateRange';
98
import {InternalLink} from '../../../../components/InternalLink';
109

1110
import HistoryContext from '../../../../contexts/HistoryContext';
@@ -18,7 +17,7 @@ import {
1817
setShardsQueryFilters,
1918
} from '../../../../store/reducers/shardsWorkload';
2019
import {setCurrentSchemaPath, getSchema} from '../../../../store/reducers/schema';
21-
import type {IShardsWorkloadFilters} from '../../../../types/store/shardsWorkload';
20+
import {EShardsWorkloadMode, IShardsWorkloadFilters} from '../../../../types/store/shardsWorkload';
2221

2322
import type {EPathType} from '../../../../types/api/schema';
2423

@@ -31,10 +30,12 @@ import {getDefaultNodePath} from '../../../Node/NodePages';
3130

3231
import {isColumnEntityType} from '../../utils/schema';
3332

33+
import {Filters} from './Filters';
34+
3435
import i18n from './i18n';
3536
import './TopShards.scss';
3637

37-
const b = cn('top-shards');
38+
export const b = cn('top-shards');
3839
const bLink = cn('yc-link');
3940

4041
const TABLE_SETTINGS: Settings = {
@@ -83,6 +84,12 @@ function dataTableToStringSortOrder(value: SortOrder | SortOrder[] = []) {
8384
return sortOrders.map(({columnId}) => columnId).join(',');
8485
}
8586

87+
function fillDateRangeFor(value: IShardsWorkloadFilters) {
88+
value.to = Date.now();
89+
value.from = value.to - HOUR_IN_SECONDS * 1000;
90+
return value;
91+
}
92+
8693
interface TopShardsProps {
8794
tenantPath: string;
8895
type?: EPathType;
@@ -101,17 +108,20 @@ export const TopShards = ({tenantPath, type}: TopShardsProps) => {
101108
wasLoaded,
102109
} = useTypedSelector((state) => state.shardsWorkload);
103110

104-
// default date range should be the last hour, but shouldn't propagate into URL until user interacts with the control
111+
// default filters shouldn't propagate into URL until user interacts with the control
105112
// redux initial value can't be used, as it synchronizes with URL
106113
const [filters, setFilters] = useState<IShardsWorkloadFilters>(() => {
107-
if (!storeFilters?.from && !storeFilters?.to) {
108-
return {
109-
from: Date.now() - HOUR_IN_SECONDS * 1000,
110-
to: Date.now(),
111-
};
114+
const defaultValue = {...storeFilters};
115+
116+
if (!defaultValue.mode) {
117+
defaultValue.mode = EShardsWorkloadMode.Immediate;
118+
}
119+
120+
if (!defaultValue.from && !defaultValue.to) {
121+
fillDateRangeFor(defaultValue);
112122
}
113123

114-
return storeFilters;
124+
return defaultValue;
115125
});
116126

117127
const [sortOrder, setSortOrder] = useState(tableColumnsNames.CPUCores);
@@ -150,12 +160,28 @@ export const TopShards = ({tenantPath, type}: TopShardsProps) => {
150160
setSortOrder(dataTableToStringSortOrder(newSortOrder));
151161
};
152162

153-
const handleDateRangeChange = (value: DateRangeValues) => {
163+
const handleFiltersChange = (value: Partial<IShardsWorkloadFilters>) => {
164+
const newStateValue = {...value};
165+
const isDateRangePristine =
166+
!storeFilters.from && !storeFilters.to && !value.from && !value.to;
167+
168+
if (isDateRangePristine) {
169+
switch (value.mode) {
170+
case EShardsWorkloadMode.Immediate:
171+
newStateValue.from = newStateValue.to = undefined;
172+
break;
173+
case EShardsWorkloadMode.History:
174+
// should default to the current datetime every time history mode activates
175+
fillDateRangeFor(newStateValue);
176+
break;
177+
}
178+
}
179+
154180
dispatch(setShardsQueryFilters(value));
155-
setFilters(value);
181+
setFilters((state) => ({...state, ...newStateValue}));
156182
};
157183

158-
const tableColumns: Column<any>[] = useMemo(() => {
184+
const tableColumns = useMemo(() => {
159185
const onSchemaClick = (schemaPath: string) => {
160186
return () => {
161187
dispatch(setCurrentSchemaPath(schemaPath));
@@ -164,7 +190,7 @@ export const TopShards = ({tenantPath, type}: TopShardsProps) => {
164190
};
165191
};
166192

167-
return [
193+
const columns: Column<any>[] = [
168194
{
169195
name: tableColumnsNames.Path,
170196
render: ({value: relativeNodePath}) => {
@@ -217,23 +243,29 @@ export const TopShards = ({tenantPath, type}: TopShardsProps) => {
217243
align: DataTable.RIGHT,
218244
sortable: false,
219245
},
220-
{
221-
name: tableColumnsNames.PeakTime,
222-
render: ({value}) => formatDateTime(new Date(value as string).valueOf()),
223-
sortable: false,
224-
},
225246
{
226247
name: tableColumnsNames.InFlightTxCount,
227248
render: ({value}) => formatNumber(value as number),
228249
align: DataTable.RIGHT,
229250
sortable: false,
230251
},
231-
{
252+
];
253+
254+
if (filters.mode === EShardsWorkloadMode.History) {
255+
// after NodeId
256+
columns.splice(5, 0, {
257+
name: tableColumnsNames.PeakTime,
258+
render: ({value}) => formatDateTime(new Date(value as string).valueOf()),
259+
sortable: false,
260+
});
261+
columns.push({
232262
name: tableColumnsNames.IntervalEnd,
233263
render: ({value}) => formatDateTime(new Date(value as string).getTime()),
234-
}
235-
];
236-
}, [dispatch, history, tenantPath]);
264+
});
265+
}
266+
267+
return columns;
268+
}, [dispatch, filters.mode, history, tenantPath]);
237269

238270
const renderLoader = () => {
239271
return (
@@ -272,10 +304,8 @@ export const TopShards = ({tenantPath, type}: TopShardsProps) => {
272304

273305
return (
274306
<div className={b()}>
275-
<div className={b('controls')}>
276-
{i18n('description')}
277-
<DateRange from={filters.from} to={filters.to} onChange={handleDateRangeChange} />
278-
</div>
307+
<Filters value={filters} onChange={handleFiltersChange} />
308+
{filters.mode === EShardsWorkloadMode.History && <div>{i18n('description')}</div>}
279309
{renderContent()}
280310
</div>
281311
);
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
{
22
"no-data": "No data",
3-
"description": "Shards with CPU load over 70% are listed"
3+
"filters.mode.immediate": "Immediate",
4+
"filters.mode.history": "Historical",
5+
"description": "Historical data only tracks shards with CPU load over 70%"
46
}
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
{
22
"no-data": "Нет данных",
3-
"description": "Отображаются шарды с загрузкой CPU выше 70%"
3+
"filters.mode.immediate": "Мгновенные",
4+
"filters.mode.history": "Исторические",
5+
"description": "Исторические данные хранятся только о шардах с загрузкой CPU выше 70%"
46
}

src/store/reducers/shardsWorkload.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type {
66
IShardsWorkloadFilters,
77
IShardsWorkloadState,
88
} from '../../types/store/shardsWorkload';
9+
import {EShardsWorkloadMode} from '../../types/store/shardsWorkload';
910

1011
import {parseQueryAPIExecuteResponse} from '../../utils/query';
1112

@@ -51,7 +52,7 @@ function getFiltersConditions(filters?: IShardsWorkloadFilters) {
5152
return conditions.join(' AND ');
5253
}
5354

54-
function createShardQuery(
55+
function createShardQueryHistorical(
5556
path: string,
5657
filters?: IShardsWorkloadFilters,
5758
sortOrder?: SortOrder[],
@@ -85,6 +86,28 @@ ${orderBy}
8586
LIMIT 20`;
8687
}
8788

89+
function createShardQueryImmediate(path: string, sortOrder?: SortOrder[], tenantName?: string) {
90+
const pathSelect = tenantName
91+
? `CAST(SUBSTRING(CAST(Path AS String), ${tenantName.length}) AS Utf8) AS Path`
92+
: 'Path';
93+
94+
const orderBy = sortOrder ? `ORDER BY ${sortOrder.map(formatSortOrder).join(', ')}` : '';
95+
96+
return `SELECT
97+
${pathSelect},
98+
TabletId,
99+
CPUCores,
100+
DataSize,
101+
NodeId,
102+
InFlightTxCount
103+
FROM \`.sys/partition_stats\`
104+
WHERE
105+
Path='${path}'
106+
OR Path LIKE '${path}/%'
107+
${orderBy}
108+
LIMIT 20`;
109+
}
110+
88111
const queryAction = 'execute-scan';
89112

90113
const shardsWorkload: Reducer<IShardsWorkloadState, IShardsWorkloadAction> = (
@@ -147,7 +170,10 @@ export const sendShardQuery = ({database, path = '', sortOrder, filters}: SendSh
147170
request: window.api.sendQuery(
148171
{
149172
schema: 'modern',
150-
query: createShardQuery(path, filters, sortOrder, database),
173+
query:
174+
filters?.mode === EShardsWorkloadMode.Immediate
175+
? createShardQueryImmediate(path, sortOrder, database)
176+
: createShardQueryHistorical(path, filters, sortOrder, database),
151177
database,
152178
action: queryAction,
153179
},

src/store/state-url-mapping.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ const paramSetup = {
4848
generalTab: {
4949
stateKey: 'tenant.diagnosticsTab',
5050
},
51+
shardsMode: {
52+
stateKey: 'shardsWorkload.filters.mode',
53+
},
5154
shardsDateFrom: {
5255
stateKey: 'shardsWorkload.filters.from',
5356
type: 'number',

src/types/store/shardsWorkload.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,17 @@ import type {ApiRequestAction} from '../../store/utils';
33
import type {IResponseError} from '../api/error';
44
import type {IQueryResult} from './query';
55

6+
export enum EShardsWorkloadMode {
7+
Immediate = 'immediate',
8+
History = 'history',
9+
}
10+
611
export interface IShardsWorkloadFilters {
712
/** ms from epoch */
813
from?: number;
914
/** ms from epoch */
1015
to?: number;
16+
mode?: EShardsWorkloadMode;
1117
}
1218

1319
export interface IShardsWorkloadState {

0 commit comments

Comments
 (0)