@kylincloud/flamegraph 0.35.10 → 0.35.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +72 -0
- package/dist/ProfilerTable.d.ts.map +1 -1
- package/dist/Tooltip/FlamegraphTooltip.d.ts +2 -1
- package/dist/Tooltip/FlamegraphTooltip.d.ts.map +1 -1
- package/dist/Tooltip/TableTooltip.d.ts.map +1 -1
- package/dist/Tooltip/Tooltip.d.ts +4 -2
- package/dist/Tooltip/Tooltip.d.ts.map +1 -1
- package/dist/index.cjs.js +4 -4
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +4 -4
- package/dist/index.esm.js.map +1 -1
- package/dist/index.node.cjs.js +2 -2
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/index.node.esm.js +4 -4
- package/dist/index.node.esm.js.map +1 -1
- package/package.json +19 -14
- package/src/ProfilerTable.tsx +13 -25
- package/src/SharedQueryInput.tsx +1 -1
- package/src/Tooltip/FlamegraphTooltip.tsx +35 -18
- package/src/Tooltip/TableTooltip.tsx +18 -7
- package/src/Tooltip/Tooltip.tsx +286 -86
- package/src/i18n.tsx +4 -4
- package/src/sass/_common.scss +0 -24
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kylincloud/flamegraph",
|
|
3
|
-
"version": "0.35.
|
|
3
|
+
"version": "0.35.15",
|
|
4
4
|
"description": "KylinCloud flamegraph renderer (Pyroscope-based)",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "dist/index.node.cjs.js",
|
|
@@ -26,6 +26,20 @@
|
|
|
26
26
|
"LICENSE",
|
|
27
27
|
"package.json"
|
|
28
28
|
],
|
|
29
|
+
"scripts": {
|
|
30
|
+
"clean": "rm -rf dist",
|
|
31
|
+
"build": "pnpm run clean && pnpm run build:types && pnpm run build:js && pnpm run build:assets",
|
|
32
|
+
"build:types": "tsc -p tsconfig.build.json --emitDeclarationOnly",
|
|
33
|
+
"build:js": "rollup -c",
|
|
34
|
+
"build:assets": "cp src/logo-v3-small.svg dist/logo-v3-small.svg || true",
|
|
35
|
+
"type-check": "tsc -p tsconfig.build.json --noEmit",
|
|
36
|
+
"dev:types": "tsc -p tsconfig.build.json --emitDeclarationOnly --watch",
|
|
37
|
+
"dev:js": "rollup -c -w",
|
|
38
|
+
"dev": "pnpm run dev:js",
|
|
39
|
+
"lint": "eslint ./ --cache --fix",
|
|
40
|
+
"release": "bash -c 'source .env && export GITLAB_TOKEN=\"glpat-$RELEASE_TOKEN\" && release-it'",
|
|
41
|
+
"publish": "npm login --registry=https://registry.npmjs.org/ && pnpm publish --access public --registry=https://registry.npmjs.org/"
|
|
42
|
+
},
|
|
29
43
|
"peerDependencies": {
|
|
30
44
|
"graphviz-react": "^1.2.5",
|
|
31
45
|
"react": ">=16.14.0",
|
|
@@ -51,6 +65,7 @@
|
|
|
51
65
|
},
|
|
52
66
|
"devDependencies": {
|
|
53
67
|
"@fortawesome/fontawesome-common-types": "^7.1.0",
|
|
68
|
+
"@release-it/conventional-changelog": "^10.0.2",
|
|
54
69
|
"@rollup/plugin-commonjs": "^29.0.0",
|
|
55
70
|
"@rollup/plugin-json": "^6.1.0",
|
|
56
71
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
@@ -65,24 +80,14 @@
|
|
|
65
80
|
"@types/node": "^24.10.1",
|
|
66
81
|
"@types/react": "18.2.66",
|
|
67
82
|
"@types/react-dom": "18.2.22",
|
|
83
|
+
"compare-func": "^2.0.0",
|
|
68
84
|
"eslint": "^9.0.0",
|
|
69
85
|
"postcss": "^8.5.6",
|
|
86
|
+
"release-it": "^19.0.6",
|
|
70
87
|
"rollup": "^4.53.3",
|
|
71
88
|
"rollup-plugin-postcss": "^4.0.2",
|
|
72
89
|
"sass": "^1.94.2",
|
|
73
90
|
"tslib": "^2.8.1",
|
|
74
91
|
"typescript": "5.4.5"
|
|
75
|
-
},
|
|
76
|
-
"scripts": {
|
|
77
|
-
"clean": "rm -rf dist",
|
|
78
|
-
"build": "pnpm run clean && pnpm run build:types && pnpm run build:js && pnpm run build:assets",
|
|
79
|
-
"build:types": "tsc -p tsconfig.build.json --emitDeclarationOnly",
|
|
80
|
-
"build:js": "rollup -c",
|
|
81
|
-
"build:assets": "cp src/logo-v3-small.svg dist/logo-v3-small.svg || true",
|
|
82
|
-
"type-check": "tsc -p tsconfig.build.json --noEmit",
|
|
83
|
-
"dev:types": "tsc -p tsconfig.build.json --emitDeclarationOnly --watch",
|
|
84
|
-
"dev:js": "rollup -c -w",
|
|
85
|
-
"dev": "pnpm run dev:js",
|
|
86
|
-
"lint": "eslint ./ --cache --fix"
|
|
87
92
|
}
|
|
88
|
-
}
|
|
93
|
+
}
|
package/src/ProfilerTable.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/ProfilerTable.tsx
|
|
2
|
-
import React, { useRef, RefObject
|
|
2
|
+
import React, { useRef, RefObject } from 'react';
|
|
3
3
|
import type Color from 'color';
|
|
4
4
|
import cl from 'classnames';
|
|
5
5
|
import type { Maybe } from 'true-myth';
|
|
@@ -15,7 +15,6 @@ import TableTooltip from './Tooltip/TableTooltip';
|
|
|
15
15
|
import { getFormatter, ratioToPercent, diffPercent } from './format/format';
|
|
16
16
|
import {
|
|
17
17
|
colorBasedOnPackageName,
|
|
18
|
-
defaultColor,
|
|
19
18
|
getPackageNameFromStackTrace,
|
|
20
19
|
} from './FlameGraph/FlameGraphComponent/color';
|
|
21
20
|
import { fitIntoTableCell, FitModes } from './fitMode/fitMode';
|
|
@@ -200,7 +199,6 @@ interface GetTableBodyRowsProps
|
|
|
200
199
|
extends Omit<ProfilerTableProps, 'tableBodyRef'> {
|
|
201
200
|
sortBy: string;
|
|
202
201
|
sortByDirection: string;
|
|
203
|
-
isDoubles: boolean;
|
|
204
202
|
messages: FlamegraphMessages;
|
|
205
203
|
}
|
|
206
204
|
|
|
@@ -208,7 +206,6 @@ const getTableBody = ({
|
|
|
208
206
|
flamebearer,
|
|
209
207
|
sortBy,
|
|
210
208
|
sortByDirection,
|
|
211
|
-
isDoubles,
|
|
212
209
|
fitMode,
|
|
213
210
|
handleTableItemClick,
|
|
214
211
|
highlightQuery,
|
|
@@ -280,9 +277,8 @@ const getTableBody = ({
|
|
|
280
277
|
return false;
|
|
281
278
|
};
|
|
282
279
|
|
|
283
|
-
const nameCell = (x: { name: string }
|
|
280
|
+
const nameCell = (x: { name: string }) => (
|
|
284
281
|
<button className="table-item-button">
|
|
285
|
-
<span className="color-reference" style={style} />
|
|
286
282
|
<div className="symbol-name" style={fitIntoTableCell(fitMode)}>
|
|
287
283
|
{x.name}
|
|
288
284
|
</div>
|
|
@@ -291,14 +287,13 @@ const getTableBody = ({
|
|
|
291
287
|
|
|
292
288
|
const getSingleRow = (
|
|
293
289
|
x: SingleCell & { name: string },
|
|
294
|
-
color: InstanceType<typeof Color
|
|
295
|
-
style: CSSProperties
|
|
290
|
+
color: InstanceType<typeof Color>
|
|
296
291
|
): BodyRow => ({
|
|
297
292
|
'data-row': `${x.type};${x.name};${x.self};${x.total}`,
|
|
298
293
|
isRowSelected: isRowSelected(x.name),
|
|
299
294
|
onClick: () => handleTableItemClick(x),
|
|
300
295
|
cells: [
|
|
301
|
-
{ value: nameCell(x
|
|
296
|
+
{ value: nameCell(x) },
|
|
302
297
|
{
|
|
303
298
|
value: formatter.format(x.self, sampleRate),
|
|
304
299
|
style: backgroundImageStyle(x.self, maxSelf, color),
|
|
@@ -311,8 +306,7 @@ const getTableBody = ({
|
|
|
311
306
|
});
|
|
312
307
|
|
|
313
308
|
const getDoubleRow = (
|
|
314
|
-
x: DoubleCell & { name: string }
|
|
315
|
-
style: CSSProperties
|
|
309
|
+
x: DoubleCell & { name: string }
|
|
316
310
|
): BodyRow => {
|
|
317
311
|
const leftPercent = ratioToPercent(x.totalLeft / x.leftTicks);
|
|
318
312
|
const rghtPercent = ratioToPercent(x.totalRght / x.rightTicks);
|
|
@@ -334,9 +328,9 @@ const getTableBody = ({
|
|
|
334
328
|
// this function has been removed
|
|
335
329
|
diffValue = messages.diffRemoved;
|
|
336
330
|
} else if (totalDiff > 0) {
|
|
337
|
-
diffValue =
|
|
331
|
+
diffValue = `+${totalDiff.toFixed(2)}%`;
|
|
338
332
|
} else if (totalDiff < 0) {
|
|
339
|
-
diffValue =
|
|
333
|
+
diffValue = `${totalDiff.toFixed(2)}%`;
|
|
340
334
|
}
|
|
341
335
|
|
|
342
336
|
return {
|
|
@@ -344,7 +338,7 @@ const getTableBody = ({
|
|
|
344
338
|
isRowSelected: isRowSelected(x.name),
|
|
345
339
|
onClick: () => handleTableItemClick(x),
|
|
346
340
|
cells: [
|
|
347
|
-
{ value: nameCell(x
|
|
341
|
+
{ value: nameCell(x) },
|
|
348
342
|
{ value: `${leftPercent} %` },
|
|
349
343
|
{ value: `${rghtPercent} %` },
|
|
350
344
|
{
|
|
@@ -366,19 +360,14 @@ const getTableBody = ({
|
|
|
366
360
|
return isMatch(highlightQuery, x.name);
|
|
367
361
|
})
|
|
368
362
|
.map((x) => {
|
|
369
|
-
const pn = getPackageNameFromStackTrace(spyName, x.name);
|
|
370
|
-
const color = isDoubles
|
|
371
|
-
? defaultColor
|
|
372
|
-
: colorBasedOnPackageName(palette, pn);
|
|
373
|
-
const style = {
|
|
374
|
-
backgroundColor: color.rgb().toString(),
|
|
375
|
-
};
|
|
376
|
-
|
|
377
363
|
if (x.type === 'double') {
|
|
378
|
-
return getDoubleRow(x as DoubleCell & { name: string }
|
|
364
|
+
return getDoubleRow(x as DoubleCell & { name: string });
|
|
379
365
|
}
|
|
380
366
|
|
|
381
|
-
|
|
367
|
+
const pn = getPackageNameFromStackTrace(spyName, x.name);
|
|
368
|
+
const color = colorBasedOnPackageName(palette, pn);
|
|
369
|
+
|
|
370
|
+
return getSingleRow(x as SingleCell & { name: string }, color);
|
|
382
371
|
});
|
|
383
372
|
|
|
384
373
|
return rows.length > 0
|
|
@@ -468,7 +457,6 @@ function Table({
|
|
|
468
457
|
flamebearer,
|
|
469
458
|
sortBy: tableSortProps.sortBy,
|
|
470
459
|
sortByDirection: tableSortProps.sortByDirection,
|
|
471
|
-
isDoubles,
|
|
472
460
|
fitMode,
|
|
473
461
|
handleTableItemClick,
|
|
474
462
|
highlightQuery,
|
package/src/SharedQueryInput.tsx
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
// src/Tooltip/FlamegraphTooltip.tsx
|
|
2
|
+
import React, {
|
|
3
|
+
useCallback,
|
|
4
|
+
RefObject,
|
|
5
|
+
Dispatch,
|
|
6
|
+
SetStateAction,
|
|
7
|
+
} from 'react';
|
|
2
8
|
import { Maybe } from 'true-myth';
|
|
3
9
|
import type { Unwrapped } from 'true-myth/maybe';
|
|
4
10
|
import { Units } from '../models/units';
|
|
@@ -15,6 +21,10 @@ import {
|
|
|
15
21
|
} from '../FlameGraph/FlameGraphComponent/colorPalette';
|
|
16
22
|
|
|
17
23
|
import { Tooltip, TooltipData } from './Tooltip';
|
|
24
|
+
import {
|
|
25
|
+
useFlamegraphI18n,
|
|
26
|
+
type FlamegraphMessages,
|
|
27
|
+
} from '../i18n';
|
|
18
28
|
|
|
19
29
|
type xyToDataSingle = (
|
|
20
30
|
x: number,
|
|
@@ -43,14 +53,14 @@ export type FlamegraphTooltipProps = {
|
|
|
43
53
|
|
|
44
54
|
palette: FlamegraphPalette;
|
|
45
55
|
} & (
|
|
46
|
-
|
|
47
|
-
|
|
56
|
+
| { format: 'single'; xyToData: xyToDataSingle }
|
|
57
|
+
| {
|
|
48
58
|
format: 'double';
|
|
49
59
|
leftTicks: number;
|
|
50
60
|
rightTicks: number;
|
|
51
61
|
xyToData: xyToDataDouble;
|
|
52
62
|
}
|
|
53
|
-
);
|
|
63
|
+
);
|
|
54
64
|
|
|
55
65
|
export default function FlamegraphTooltip(props: FlamegraphTooltipProps) {
|
|
56
66
|
const {
|
|
@@ -64,6 +74,7 @@ export default function FlamegraphTooltip(props: FlamegraphTooltipProps) {
|
|
|
64
74
|
rightTicks,
|
|
65
75
|
palette,
|
|
66
76
|
} = props;
|
|
77
|
+
const i18n = useFlamegraphI18n();
|
|
67
78
|
|
|
68
79
|
const setTooltipContent = useCallback(
|
|
69
80
|
(
|
|
@@ -87,8 +98,7 @@ export default function FlamegraphTooltip(props: FlamegraphTooltipProps) {
|
|
|
87
98
|
|
|
88
99
|
let data: Unwrapped<typeof opt>;
|
|
89
100
|
|
|
90
|
-
//
|
|
91
|
-
// https://github.com/true-myth/true-myth/issues/279
|
|
101
|
+
// true-myth Maybe 解包
|
|
92
102
|
if (opt.isJust) {
|
|
93
103
|
data = opt.value;
|
|
94
104
|
} else {
|
|
@@ -96,13 +106,14 @@ export default function FlamegraphTooltip(props: FlamegraphTooltipProps) {
|
|
|
96
106
|
return;
|
|
97
107
|
}
|
|
98
108
|
|
|
99
|
-
// set the content for tooltip
|
|
100
109
|
switch (data.format) {
|
|
101
110
|
case 'single': {
|
|
102
111
|
const newLeftContent: TooltipData = {
|
|
103
112
|
percent: formatPercent(data.total / numTicks),
|
|
104
113
|
samples:
|
|
105
|
-
units === 'trace_samples'
|
|
114
|
+
units === 'trace_samples'
|
|
115
|
+
? ''
|
|
116
|
+
: numberWithCommas(data.total),
|
|
106
117
|
units,
|
|
107
118
|
formattedValue: formatter.format(data.total, sampleRate),
|
|
108
119
|
tooltipType: 'flamegraph',
|
|
@@ -139,7 +150,8 @@ export default function FlamegraphTooltip(props: FlamegraphTooltipProps) {
|
|
|
139
150
|
title: data.name,
|
|
140
151
|
units,
|
|
141
152
|
},
|
|
142
|
-
palette
|
|
153
|
+
palette,
|
|
154
|
+
i18n
|
|
143
155
|
);
|
|
144
156
|
|
|
145
157
|
setContent({
|
|
@@ -150,10 +162,10 @@ export default function FlamegraphTooltip(props: FlamegraphTooltipProps) {
|
|
|
150
162
|
break;
|
|
151
163
|
}
|
|
152
164
|
default:
|
|
153
|
-
throw new Error(`Unsupported format:'`);
|
|
165
|
+
throw new Error(`Unsupported format: '${(data as any).format}'`);
|
|
154
166
|
}
|
|
155
167
|
},
|
|
156
|
-
[numTicks, sampleRate, units, leftTicks, rightTicks, palette]
|
|
168
|
+
[numTicks, sampleRate, units, leftTicks, rightTicks, palette, xyToData, format, i18n]
|
|
157
169
|
);
|
|
158
170
|
|
|
159
171
|
return (
|
|
@@ -161,6 +173,7 @@ export default function FlamegraphTooltip(props: FlamegraphTooltipProps) {
|
|
|
161
173
|
dataSourceRef={canvasRef}
|
|
162
174
|
clickInfoSide="right"
|
|
163
175
|
setTooltipContent={setTooltipContent}
|
|
176
|
+
palette={palette}
|
|
164
177
|
/>
|
|
165
178
|
);
|
|
166
179
|
}
|
|
@@ -189,7 +202,8 @@ export function formatDouble(
|
|
|
189
202
|
title: string;
|
|
190
203
|
units: Units;
|
|
191
204
|
},
|
|
192
|
-
palette: FlamegraphPalette = DefaultPalette
|
|
205
|
+
palette: FlamegraphPalette = DefaultPalette,
|
|
206
|
+
i18n?: FlamegraphMessages
|
|
193
207
|
): {
|
|
194
208
|
tooltipData: TooltipData[];
|
|
195
209
|
title: {
|
|
@@ -231,17 +245,20 @@ export function formatDouble(
|
|
|
231
245
|
tooltipDiffColor = palette.goodColor.rgb().string();
|
|
232
246
|
}
|
|
233
247
|
|
|
248
|
+
// 这里同时处理:
|
|
249
|
+
// - new / removed 走 i18n,带安全兜底
|
|
250
|
+
// - 百分比 diff 不再带括号,直接 "+12.34%" / "-5.67%"
|
|
234
251
|
let tooltipDiffText = '';
|
|
235
252
|
if (!totalLeft) {
|
|
236
|
-
//
|
|
237
|
-
tooltipDiffText = '
|
|
253
|
+
// 新函数
|
|
254
|
+
tooltipDiffText = i18n?.diffNew ?? '新增';
|
|
238
255
|
} else if (!totalRight) {
|
|
239
|
-
//
|
|
240
|
-
tooltipDiffText = '
|
|
256
|
+
// 被移除的函数
|
|
257
|
+
tooltipDiffText = i18n?.diffRemoved ?? '移除';
|
|
241
258
|
} else if (totalDiff > 0) {
|
|
242
|
-
tooltipDiffText =
|
|
259
|
+
tooltipDiffText = `+${totalDiff.toFixed(2)}%`;
|
|
243
260
|
} else if (totalDiff < 0) {
|
|
244
|
-
tooltipDiffText =
|
|
261
|
+
tooltipDiffText = `${totalDiff.toFixed(2)}%`;
|
|
245
262
|
}
|
|
246
263
|
|
|
247
264
|
return {
|
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
// src/Tooltip/TableTooltip.tsx
|
|
2
|
+
import React, {
|
|
3
|
+
useCallback,
|
|
4
|
+
RefObject,
|
|
5
|
+
Dispatch,
|
|
6
|
+
SetStateAction,
|
|
7
|
+
} from 'react';
|
|
2
8
|
import { Units } from '../models/units';
|
|
3
9
|
|
|
4
10
|
import type { FlamegraphPalette } from '../FlameGraph/FlameGraphComponent/colorPalette';
|
|
5
11
|
import { Tooltip, TooltipData } from './Tooltip';
|
|
6
12
|
import { formatDouble } from './FlamegraphTooltip';
|
|
7
13
|
import { getFormatter } from '../format/format';
|
|
14
|
+
import { useFlamegraphI18n } from '../i18n';
|
|
8
15
|
|
|
9
16
|
export interface TableTooltipProps {
|
|
10
17
|
tableBodyRef: RefObject<HTMLTableSectionElement>;
|
|
@@ -23,6 +30,7 @@ export default function TableTooltip({
|
|
|
23
30
|
}: TableTooltipProps) {
|
|
24
31
|
const formatter = getFormatter(numTicks, sampleRate, units);
|
|
25
32
|
const totalFlamebearer = formatter.format(numTicks, sampleRate);
|
|
33
|
+
const i18n = useFlamegraphI18n();
|
|
26
34
|
|
|
27
35
|
const setTooltipContent = useCallback(
|
|
28
36
|
(
|
|
@@ -58,11 +66,12 @@ export default function TableTooltip({
|
|
|
58
66
|
parseInt(self, 10),
|
|
59
67
|
sampleRate
|
|
60
68
|
);
|
|
61
|
-
const
|
|
69
|
+
const totalFormatted = formatter.format(
|
|
62
70
|
parseInt(total, 10),
|
|
63
71
|
sampleRate
|
|
64
72
|
);
|
|
65
|
-
|
|
73
|
+
|
|
74
|
+
// 保持原有百分比计算逻辑
|
|
66
75
|
const totalFlamebearerSplitted = totalFlamebearer.split(' ');
|
|
67
76
|
const totalFlamebearerNoUnitsValue =
|
|
68
77
|
totalFlamebearerSplitted[0] === '<'
|
|
@@ -73,7 +82,7 @@ export default function TableTooltip({
|
|
|
73
82
|
const selfNoUnitsValue =
|
|
74
83
|
selfSplitted[0] === '<' ? selfSplitted[1] : selfSplitted[0];
|
|
75
84
|
|
|
76
|
-
const totalSplitted =
|
|
85
|
+
const totalSplitted = totalFormatted.split(' ');
|
|
77
86
|
const totalNoUnitsValue =
|
|
78
87
|
totalSplitted[0] === '<' ? totalSplitted[1] : totalSplitted[0];
|
|
79
88
|
|
|
@@ -84,7 +93,7 @@ export default function TableTooltip({
|
|
|
84
93
|
parseFloat(totalFlamebearerNoUnitsValue)) *
|
|
85
94
|
100
|
|
86
95
|
).toFixed(2)}%)`,
|
|
87
|
-
total: `${
|
|
96
|
+
total: `${totalFormatted}(${(
|
|
88
97
|
(parseFloat(totalNoUnitsValue) /
|
|
89
98
|
parseFloat(totalFlamebearerNoUnitsValue)) *
|
|
90
99
|
100
|
|
@@ -117,7 +126,8 @@ export default function TableTooltip({
|
|
|
117
126
|
title: functionName,
|
|
118
127
|
units,
|
|
119
128
|
},
|
|
120
|
-
palette
|
|
129
|
+
palette,
|
|
130
|
+
i18n
|
|
121
131
|
);
|
|
122
132
|
|
|
123
133
|
setContent({
|
|
@@ -131,7 +141,7 @@ export default function TableTooltip({
|
|
|
131
141
|
break;
|
|
132
142
|
}
|
|
133
143
|
},
|
|
134
|
-
[
|
|
144
|
+
[formatter, sampleRate, units, totalFlamebearer, palette, i18n]
|
|
135
145
|
);
|
|
136
146
|
|
|
137
147
|
return (
|
|
@@ -140,6 +150,7 @@ export default function TableTooltip({
|
|
|
140
150
|
shouldShowTitle={false}
|
|
141
151
|
clickInfoSide="left"
|
|
142
152
|
setTooltipContent={setTooltipContent}
|
|
153
|
+
palette={palette}
|
|
143
154
|
/>
|
|
144
155
|
);
|
|
145
156
|
}
|