UBERF-4319: Performance changes (#4474)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev
2024-01-30 18:07:34 +07:00
committed by GitHub
parent d02e88737d
commit e6a35d2a03
37 changed files with 775 additions and 436 deletions
+23 -1
View File
@@ -38,7 +38,8 @@ import core, {
generateId,
SearchQuery,
SearchOptions,
SearchResult
SearchResult,
MeasureDoneOperation
} from '@hcengineering/core'
import { PlatformError, UNAUTHORIZED, broadcastEvent, getMetadata, unknownError } from '@hcengineering/platform'
@@ -376,6 +377,27 @@ class Connection implements ClientConnection {
return await promise.promise
}
async measure (operationName: string): Promise<MeasureDoneOperation> {
const dateNow = Date.now()
// Send measure-start
const mid = await this.sendRequest({
method: 'measure',
params: [operationName]
})
return async () => {
const serverTime: number = await this.sendRequest({
method: 'measure-done',
params: [operationName, mid]
})
return {
time: Date.now() - dateNow,
serverTime
}
}
}
async loadModel (last: Timestamp, hash?: string): Promise<Tx[] | LoadModelResponse> {
return await this.sendRequest({ method: 'loadModel', params: [last, hash] })
}
-3
View File
@@ -16,9 +16,6 @@
import type { AccountClient, ClientConnectEvent } from '@hcengineering/core'
import type { Plugin, Resource } from '@hcengineering/platform'
import { Metadata, plugin } from '@hcengineering/platform'
// import type { LiveQuery } from '@hcengineering/query'
// export type Connection = Client & LiveQuery & TxOperations
/**
* @public
+6 -1
View File
@@ -30,7 +30,8 @@ import core, {
type WithLookup,
type SearchQuery,
type SearchOptions,
type SearchResult
type SearchResult,
type MeasureDoneOperation
} from '@hcengineering/core'
import { devModelId } from '@hcengineering/devmodel'
import { Builder } from '@hcengineering/model'
@@ -68,6 +69,10 @@ class ModelClient implements AccountClient {
}
}
async measure (operationName: string): Promise<MeasureDoneOperation> {
return await this.client.measure(operationName)
}
notify?: (tx: Tx) => void
getHierarchy (): Hierarchy {
@@ -352,97 +352,105 @@
return
}
const operations = client.apply(_id)
// TODO: We need a measure client and mark all operations with it as measure under one root,
// to prevent other operations to infer our measurement.
const doneOp = await getClient().measure('tracker.createIssue')
const lastOne = await client.findOne<Issue>(tracker.class.Issue, {}, { sort: { rank: SortingOrder.Descending } })
const incResult = await client.updateDoc(
tracker.class.Project,
core.space.Space,
_space,
{
$inc: { sequence: 1 }
},
true
)
try {
const operations = client.apply(_id)
const value: DocData<Issue> = {
title: getTitle(object.title),
description: object.description,
assignee: object.assignee,
component: object.component,
milestone: object.milestone,
number: (incResult as any).object.sequence,
status: object.status,
priority: object.priority,
rank: calcRank(lastOne, undefined),
comments: 0,
subIssues: 0,
dueDate: object.dueDate,
parents:
parentIssue != null
? [
{ parentId: parentIssue._id, parentTitle: parentIssue.title, space: parentIssue.space },
...parentIssue.parents
]
: [],
reportedTime: 0,
remainingTime: 0,
estimation: object.estimation,
reports: 0,
relations: relatedTo !== undefined ? [{ _id: relatedTo._id, _class: relatedTo._class }] : [],
childInfo: [],
kind
}
const lastOne = await client.findOne<Issue>(tracker.class.Issue, {}, { sort: { rank: SortingOrder.Descending } })
const incResult = await client.updateDoc(
tracker.class.Project,
core.space.Space,
_space,
{
$inc: { sequence: 1 }
},
true
)
await docCreateManager.commit(operations, _id, _space, value)
const value: DocData<Issue> = {
title: getTitle(object.title),
description: object.description,
assignee: object.assignee,
component: object.component,
milestone: object.milestone,
number: (incResult as any).object.sequence,
status: object.status,
priority: object.priority,
rank: calcRank(lastOne, undefined),
comments: 0,
subIssues: 0,
dueDate: object.dueDate,
parents:
parentIssue != null
? [
{ parentId: parentIssue._id, parentTitle: parentIssue.title, space: parentIssue.space },
...parentIssue.parents
]
: [],
reportedTime: 0,
remainingTime: 0,
estimation: object.estimation,
reports: 0,
relations: relatedTo !== undefined ? [{ _id: relatedTo._id, _class: relatedTo._class }] : [],
childInfo: [],
kind
}
await operations.addCollection(
tracker.class.Issue,
_space,
parentIssue?._id ?? tracker.ids.NoParent,
parentIssue?._class ?? tracker.class.Issue,
'subIssues',
value,
_id
)
for (const label of object.labels) {
await operations.addCollection(label._class, label.space, _id, tracker.class.Issue, 'labels', {
title: label.title,
color: label.color,
tag: label.tag
})
}
await docCreateManager.commit(operations, _id, _space, value)
if (relatedTo !== undefined) {
const doc = await client.findOne(tracker.class.Issue, { _id })
if (doc !== undefined) {
if (client.getHierarchy().isDerived(relatedTo._class, tracker.class.Issue)) {
await updateIssueRelation(operations, relatedTo as Issue, doc, 'relations', '$push')
} else {
const update = await getResource(chunter.backreference.Update)
await update(doc, 'relations', [relatedTo], tracker.string.AddedReference)
await operations.addCollection(
tracker.class.Issue,
_space,
parentIssue?._id ?? tracker.ids.NoParent,
parentIssue?._class ?? tracker.class.Issue,
'subIssues',
value,
_id
)
for (const label of object.labels) {
await operations.addCollection(label._class, label.space, _id, tracker.class.Issue, 'labels', {
title: label.title,
color: label.color,
tag: label.tag
})
}
if (relatedTo !== undefined) {
const doc = await client.findOne(tracker.class.Issue, { _id })
if (doc !== undefined) {
if (client.getHierarchy().isDerived(relatedTo._class, tracker.class.Issue)) {
await updateIssueRelation(operations, relatedTo as Issue, doc, 'relations', '$push')
} else {
const update = await getResource(chunter.backreference.Update)
await update(doc, 'relations', [relatedTo], tracker.string.AddedReference)
}
}
}
await operations.commit()
await descriptionBox.createAttachments(_id)
addNotification(
await translate(tracker.string.IssueCreated, {}, $themeStore.language),
getTitle(object.title),
IssueNotification,
{
issueId: _id,
subTitlePostfix: (await translate(tracker.string.CreatedOne, {}, $themeStore.language)).toLowerCase(),
issueUrl: currentProject != null && generateIssueShortLink(getIssueId(currentProject, value as Issue))
}
)
console.log('createIssue measure', await doneOp())
draftController.remove()
descriptionBox?.removeDraft(false)
isAssigneeTouched = false
} catch (err: any) {
console.error(err)
await doneOp() // Complete in case of error
}
await operations.commit()
await descriptionBox.createAttachments(_id)
addNotification(
await translate(tracker.string.IssueCreated, {}, $themeStore.language),
getTitle(object.title),
IssueNotification,
{
issueId: _id,
subTitlePostfix: (await translate(tracker.string.CreatedOne, {}, $themeStore.language)).toLowerCase(),
issueUrl: currentProject != null && generateIssueShortLink(getIssueId(currentProject, value as Issue))
}
)
draftController.remove()
descriptionBox?.removeDraft(false)
isAssigneeTouched = false
}
async function setParentIssue (): Promise<void> {
@@ -157,7 +157,9 @@
async function handleSelection (evt: Event, selection: number): Promise<void> {
const item = items[selection]
if (item == null) {
return
}
if (item.item !== undefined) {
const doc = item.item.doc
void client.findOne(doc._class, { _id: doc._id }).then((value) => {
@@ -1,6 +1,6 @@
<script lang="ts">
import contact, { PersonAccount } from '@hcengineering/contact'
import { metricsToRows } from '@hcengineering/core'
import { Metrics } from '@hcengineering/core'
import login from '@hcengineering/login'
import { getEmbeddedLabel, getMetadata } from '@hcengineering/platform'
import presentation, { createQuery } from '@hcengineering/presentation'
@@ -9,10 +9,8 @@
IconArrowRight,
Loading,
Panel,
Scroller,
TabItem,
TabList,
closePopup,
fetchMetadataLocalStorage,
ticker
} from '@hcengineering/ui'
@@ -20,6 +18,7 @@
import Expandable from '@hcengineering/ui/src/components/Expandable.svelte'
import { ObjectPresenter } from '@hcengineering/view-resources'
import { onDestroy } from 'svelte'
import MetricsInfo from './statistics/MetricsInfo.svelte'
const _endpoint: string = fetchMetadataLocalStorage(login.metadata.LoginEndpoint) ?? ''
const token: string = getMetadata(presentation.metadata.Token) ?? ''
@@ -33,12 +32,9 @@
let admin = false
onDestroy(
ticker.subscribe(() => {
fetch(endpoint + `/api/v1/statistics?token=${token}`, {}).then(async (json) => {
void fetch(endpoint + `/api/v1/statistics?token=${token}`, {}).then(async (json) => {
data = await json.json()
admin = data?.admin ?? false
if (!admin) {
closePopup()
}
})
})
)
@@ -86,15 +82,34 @@
}
employees = emp
})
const toNum = (value: any) => value as number
let warningTimeout = 15
$: metricsData = data?.metrics as Metrics | undefined
$: totalStats = Array.from(Object.entries(activeSessions).values()).reduce(
(cur, it) => {
const totalFind = it[1].reduce((it, itm) => itm.current.find + it, 0)
const totalTx = it[1].reduce((it, itm) => itm.current.tx + it, 0)
return {
find: cur.find + totalFind,
tx: cur.tx + totalTx
}
},
{ find: 0, tx: 0 }
)
</script>
<Panel on:close isFullSize useMaxWidth={true}>
<svelte:fragment slot="header">
{#if data}
Mem: {data.statistics.memoryUsed} / {data.statistics.memoryTotal} CPU: {data.statistics.cpuUsage}
<div class="flex-col">
<span>
Mem: {data.statistics.memoryUsed} / {data.statistics.memoryTotal} CPU: {data.statistics.cpuUsage}
</span>
<span>
TotalFind: {totalStats.find} / Total Tx: {totalStats.tx}
</span>
</div>
{/if}
</svelte:fragment>
<svelte:fragment slot="title">
@@ -118,7 +133,7 @@
icon={IconArrowRight}
label={getEmbeddedLabel('Set maintenance warning')}
on:click={() => {
fetch(endpoint + `/api/v1/manage?token=${token}&operation=maintenance&timeout=${warningTimeout}`, {
void fetch(endpoint + `/api/v1/manage?token=${token}&operation=maintenance&timeout=${warningTimeout}`, {
method: 'PUT'
})
}}
@@ -136,7 +151,7 @@
icon={IconArrowRight}
label={getEmbeddedLabel('Reboot server')}
on:click={() => {
fetch(endpoint + `/api/v1/manage?token=${token}&operation=reboot`, {
void fetch(endpoint + `/api/v1/manage?token=${token}&operation=reboot`, {
method: 'PUT'
})
}}
@@ -151,92 +166,74 @@
{@const totalTx = act[1].reduce((it, itm) => itm.current.tx + it, 0)}
{@const employeeGroups = Array.from(new Set(act[1].map((it) => it.userId)))}
<span class="flex-col">
<div class="fs-title">
Workspace: {act[0]}: {act[1].length} current 5 mins => {totalFind}/{totalTx}
</div>
<div class="flex-col">
{#each employeeGroups as employeeId}
{@const employee = employees.get(employeeId)}
{@const connections = act[1].filter((it) => it.userId === employeeId)}
{@const find = connections.reduce((it, itm) => itm.current.find + it, 0)}
{@const txes = connections.reduce((it, itm) => itm.current.tx + it, 0)}
<div class="p-1 flex-col">
<Expandable>
<svelte:fragment slot="title">
<div class="flex-row-center p-1">
{#if employee}
<ObjectPresenter
_class={contact.mixin.Employee}
objectId={employee.person}
props={{ shouldShowAvatar: true }}
/>
{:else}
{employeeId}
{/if}
: {connections.length}
<div class="ml-4">
<div class="ml-1">{find}/{txes}</div>
</div>
</div>
</svelte:fragment>
{#each connections as user, i}
<div class="flex-row-center ml-10">
#{i}
{user.userId}
<div class="p-1">
Total: {user.total.find}/{user.total.tx}
</div>
<div class="p-1">
Previous 5 mins: {user.mins5.find}/{user.mins5.tx}
</div>
<div class="p-1">
Current 5 mins: {user.current.find}/{user.current.tx}
</div>
</div>
<div class="p-1 flex-col ml-10">
{#each Object.entries(user.data ?? {}) as [k, v]}
<div class="p-1">
{k}: {JSON.stringify(v)}
</div>
{/each}
</div>
{/each}
</Expandable>
<Expandable contentColor expanded={false} expandable={true} bordered>
<svelte:fragment slot="title">
<div class="fs-title">
Workspace: {act[0]}: {act[1].length} current 5 mins => {totalFind}/{totalTx}
</div>
{/each}
</div>
</svelte:fragment>
<div class="flex-col">
{#each employeeGroups as employeeId}
{@const employee = employees.get(employeeId)}
{@const connections = act[1].filter((it) => it.userId === employeeId)}
{@const find = connections.reduce((it, itm) => itm.current.find + it, 0)}
{@const txes = connections.reduce((it, itm) => itm.current.tx + it, 0)}
<div class="p-1 flex-col ml-4">
<Expandable>
<svelte:fragment slot="title">
<div class="flex-row-center p-1">
{#if employee}
<ObjectPresenter
_class={contact.mixin.Employee}
objectId={employee.person}
props={{ shouldShowAvatar: true, disabled: true }}
/>
{:else}
{employeeId}
{/if}
: {connections.length}
<div class="ml-4">
<div class="ml-1">{find}/{txes}</div>
</div>
</div>
</svelte:fragment>
{#each connections as user, i}
<div class="flex-row-center ml-10">
#{i}
{user.userId}
<div class="p-1">
Total: {user.total.find}/{user.total.tx}
</div>
<div class="p-1">
Previous 5 mins: {user.mins5.find}/{user.mins5.tx}
</div>
<div class="p-1">
Current 5 mins: {user.current.find}/{user.current.tx}
</div>
</div>
<div class="p-1 flex-col ml-10">
{#each Object.entries(user.data ?? {}) as [k, v]}
<div class="p-1">
{k}: {JSON.stringify(v)}
</div>
{/each}
</div>
{/each}
</Expandable>
</div>
{/each}
</div>
</Expandable>
</span>
{/each}
</div>
{:else if selectedTab === 'statistics'}
<Scroller>
<table class="antiTable" class:highlightRows={true}>
<thead class="scroller-thead">
<tr>
<th><div class="p-1">Name</div> </th>
<th>Average</th>
<th>Total</th>
<th>Ops</th>
</tr>
</thead>
<tbody>
{#each metricsToRows(data.metrics, 'System') as row}
<tr class="antiTable-body__row">
<td>
<span style={`padding-left: ${toNum(row[0]) + 0.5}rem;`}>
{row[1]}
</span>
</td>
<td>{row[2]}</td>
<td>{row[3]}</td>
<td>{row[4]}</td>
</tr>
{/each}
</tbody>
</table>
</Scroller>
<div class="flex-column p-3 h-full" style:overflow="auto">
{#if metricsData !== undefined}
<MetricsInfo metrics={metricsData} />
{/if}
</div>
{/if}
{:else}
<Loading />
@@ -0,0 +1,83 @@
<script lang="ts">
import { Metrics } from '@hcengineering/core'
import { Expandable } from '@hcengineering/ui'
import { FixedColumn } from '@hcengineering/view-resources'
export let metrics: Metrics
export let level = 0
export let name: string = 'System'
$: haschilds = Object.keys(metrics.measurements).length > 0 || Object.keys(metrics.params).length > 0
function showAvg (name: string, time: number, ops: number): string {
if (name.startsWith('#')) {
return `➿ ${time}`
}
if (ops === 0) {
return `⏱️ ${time}`
}
return `${Math.floor((time / ops) * 100) / 100}`
}
</script>
<Expandable
expanded={level === 0}
expandable={level !== 0 && haschilds}
bordered
showChevron={haschilds && level !== 0}
contentColor
>
<svelte:fragment slot="title">
<div class="flex-row-center flex-between flex-grow ml-2">
{name}
</div>
</svelte:fragment>
<svelte:fragment slot="tools">
<FixedColumn key="row">
<div class="flex-row-center flex-between">
<FixedColumn key="ops">
<span class="p-1">
{metrics.operations}
</span>
</FixedColumn>
<FixedColumn key="time">
<span class="p-1">
{showAvg(name, metrics.value, metrics.operations)}
</span>
</FixedColumn>
<FixedColumn key="time-full">
<span class="p-1">
{metrics.value}
</span>
</FixedColumn>
</div>
</FixedColumn>
</svelte:fragment>
{#each Object.entries(metrics.measurements) as [k, v], i}
<div style:margin-left={`${level * 0.5}rem`}>
<svelte:self metrics={v} name="{i}. {k}" level={level + 1} />
</div>
{/each}
{#each Object.entries(metrics.params) as [k, v], i}
<div style:margin-left={`${level * 0.5}rem`}>
{#each Object.entries(v).toSorted((a, b) => b[1].value / (b[1].operations + 1) - a[1].value / (a[1].operations + 1)) as [kk, vv]}
<Expandable expandable={false} bordered showChevron={false} contentColor>
<svelte:fragment slot="title">
<div class="flex-row-center flex-between flex-grow">
# {k} = {kk}
</div>
</svelte:fragment>
<svelte:fragment slot="tools">
<FixedColumn key="row">
<div class="flex-row-center flex-between">
<FixedColumn key="ops">{vv.operations}</FixedColumn>
<FixedColumn key="time">{showAvg(kk, vv.value, vv.operations)}</FixedColumn>
<FixedColumn key="time-full">{vv.value}</FixedColumn>
</div>
</FixedColumn>
</svelte:fragment>
</Expandable>
{/each}
</div>
{/each}
</Expandable>