mirror of
https://github.com/hcengineering/platform.git
synced 2025-01-03 17:05:16 +03:00
UBERF-6747 Expose collaborator statistics (#5483)
Signed-off-by: Alexander Onnikov <Alexander.Onnikov@xored.com>
This commit is contained in:
parent
ae1b3eb062
commit
935788cd6b
@ -49,15 +49,26 @@
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
async function fetchCollabStats (): Promise<void> {
|
||||
const collaborator = getMetadata(presentation.metadata.CollaboratorApiUrl)
|
||||
await fetch(collaborator + `/api/v1/statistics?token=${token}`, {})
|
||||
.then(async (json) => {
|
||||
dataCollab = await json.json()
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
|
||||
let data: any
|
||||
let dataFront: any
|
||||
let dataCollab: any
|
||||
let admin = false
|
||||
onDestroy(
|
||||
ticker.subscribe(() => {
|
||||
void fetchStats()
|
||||
|
||||
void fetchUIStats()
|
||||
void fetchCollabStats()
|
||||
})
|
||||
)
|
||||
const tabs: TabItem[] = [
|
||||
@ -73,6 +84,10 @@
|
||||
id: 'statistics-front',
|
||||
labelIntl: getEmbeddedLabel('Front')
|
||||
},
|
||||
{
|
||||
id: 'statistics-collab',
|
||||
labelIntl: getEmbeddedLabel('Collaborator')
|
||||
},
|
||||
{
|
||||
id: 'users',
|
||||
labelIntl: getEmbeddedLabel('Users')
|
||||
@ -121,6 +136,8 @@
|
||||
|
||||
$: metricsDataFront = dataFront?.metrics as Metrics | undefined
|
||||
|
||||
$: metricsDataCollab = dataCollab?.metrics as Metrics | undefined
|
||||
|
||||
$: totalStats = Array.from(Object.entries(activeSessions).values()).reduce(
|
||||
(cur, it) => {
|
||||
const totalFind = it[1].sessions.reduce((it, itm) => itm.current.find + it, 0)
|
||||
@ -330,6 +347,12 @@
|
||||
<MetricsInfo metrics={metricsDataFront} />
|
||||
{/if}
|
||||
</div>
|
||||
{:else if selectedTab === 'statistics-collab'}
|
||||
<div class="flex-column p-3 h-full" style:overflow="auto">
|
||||
{#if metricsDataCollab !== undefined}
|
||||
<MetricsInfo metrics={metricsDataCollab} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<Loading />
|
||||
|
@ -13,5 +13,9 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { MeasureMetricsContext, newMetrics } from '@hcengineering/core'
|
||||
import { startCollaborator } from '@hcengineering/collaborator'
|
||||
void startCollaborator()
|
||||
|
||||
const ctx = new MeasureMetricsContext('collaborator', {}, {}, newMetrics())
|
||||
|
||||
void startCollaborator(ctx)
|
||||
|
@ -49,6 +49,7 @@
|
||||
"@types/jest": "^29.5.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/analytics": "^0.6.0",
|
||||
"@hcengineering/core": "^0.6.28",
|
||||
"@hcengineering/account": "^0.6.0",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
|
@ -14,5 +14,9 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { MeasureMetricsContext, newMetrics } from '@hcengineering/core'
|
||||
import { startCollaborator } from './starter'
|
||||
void startCollaborator()
|
||||
|
||||
const ctx = new MeasureMetricsContext('collaborator', {}, {}, newMetrics())
|
||||
|
||||
void startCollaborator(ctx)
|
||||
|
@ -50,9 +50,7 @@ export class StorageExtension implements Extension {
|
||||
|
||||
async onLoadDocument ({ context, documentName }: withContext<onLoadDocumentPayload>): Promise<any> {
|
||||
this.configuration.ctx.info('load document', { documentName })
|
||||
return await this.configuration.ctx.with('load-document', {}, async () => {
|
||||
return await this.loadDocument(documentName as DocumentId, context)
|
||||
})
|
||||
return await this.loadDocument(documentName as DocumentId, context)
|
||||
}
|
||||
|
||||
async onStoreDocument ({ context, documentName, document }: withContext<onStoreDocumentPayload>): Promise<void> {
|
||||
@ -67,9 +65,7 @@ export class StorageExtension implements Extension {
|
||||
}
|
||||
|
||||
this.collaborators.delete(documentName)
|
||||
await ctx.with('store-document', {}, async () => {
|
||||
await this.storeDocument(documentName as DocumentId, document, context)
|
||||
})
|
||||
await this.storeDocument(documentName as DocumentId, document, context)
|
||||
}
|
||||
|
||||
async onConnect ({ context, documentName, instance }: withContext<onConnectPayload>): Promise<any> {
|
||||
@ -92,9 +88,7 @@ export class StorageExtension implements Extension {
|
||||
}
|
||||
|
||||
this.collaborators.delete(documentName)
|
||||
await ctx.with('store-document', {}, async () => {
|
||||
await this.storeDocument(documentName as DocumentId, document, context)
|
||||
})
|
||||
await this.storeDocument(documentName as DocumentId, document, context)
|
||||
}
|
||||
|
||||
async afterUnloadDocument ({ documentName }: afterUnloadDocumentPayload): Promise<any> {
|
||||
|
@ -1,36 +0,0 @@
|
||||
import { MeasureMetricsContext, metricsToString, newMetrics } from '@hcengineering/core'
|
||||
import { writeFile } from 'fs/promises'
|
||||
|
||||
const metricsFile = process.env.METRICS_FILE
|
||||
const metricsConsole = (process.env.METRICS_CONSOLE ?? 'false') === 'true'
|
||||
|
||||
const METRICS_UPDATE_INTERVAL = !metricsConsole ? 1000 : 30000
|
||||
|
||||
const metrics = newMetrics()
|
||||
export const metricsContext = new MeasureMetricsContext('System', {}, metrics)
|
||||
|
||||
if (metricsFile !== undefined || metricsConsole) {
|
||||
console.info('storing measurements into local file', metricsFile)
|
||||
let oldMetricsValue = ''
|
||||
|
||||
const intTimer = setInterval(() => {
|
||||
const val = metricsToString(metrics, 'System', 140)
|
||||
if (val !== oldMetricsValue) {
|
||||
oldMetricsValue = val
|
||||
if (metricsFile !== undefined) {
|
||||
writeFile(metricsFile, val).catch((err) => {
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
if (metricsConsole) {
|
||||
console.info('METRICS:\n', val)
|
||||
}
|
||||
}
|
||||
}, METRICS_UPDATE_INTERVAL)
|
||||
|
||||
const closeTimer = (): void => {
|
||||
clearInterval(intTimer)
|
||||
}
|
||||
process.on('SIGINT', closeTimer)
|
||||
process.on('SIGTERM', closeTimer)
|
||||
}
|
@ -13,7 +13,8 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { MeasureContext, generateId } from '@hcengineering/core'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
import { MeasureContext, generateId, metricsAggregate } from '@hcengineering/core'
|
||||
import { MinioService } from '@hcengineering/minio'
|
||||
import { Token, decodeToken } from '@hcengineering/server-token'
|
||||
import { ServerKit } from '@hcengineering/text'
|
||||
@ -146,6 +147,31 @@ export async function start (
|
||||
}
|
||||
}
|
||||
|
||||
app.get('/api/v1/statistics', (req, res) => {
|
||||
try {
|
||||
const token = req.query.token as string
|
||||
const payload = decodeToken(token)
|
||||
const admin = payload.extra?.admin === 'true'
|
||||
res.status(200)
|
||||
res.setHeader('Content-Type', 'application/json')
|
||||
res.setHeader('Cache-Control', 'public, no-store, no-cache, must-revalidate, max-age=0')
|
||||
|
||||
const json = JSON.stringify({
|
||||
metrics: metricsAggregate((ctx as any).metrics),
|
||||
statistics: {
|
||||
activeSessions: {}
|
||||
},
|
||||
admin
|
||||
})
|
||||
res.end(json)
|
||||
} catch (err: any) {
|
||||
ctx.error('statistics error', { err })
|
||||
Analytics.handleError(err)
|
||||
res.writeHead(404, {})
|
||||
res.end()
|
||||
}
|
||||
})
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
app.post('/rpc', async (req, res) => {
|
||||
const authHeader = req.headers.authorization
|
||||
|
@ -14,16 +14,16 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { MeasureContext } from '@hcengineering/core'
|
||||
import { MinioService } from '@hcengineering/minio'
|
||||
import { setMetadata } from '@hcengineering/platform'
|
||||
import serverToken from '@hcengineering/server-token'
|
||||
import { MongoClient } from 'mongodb'
|
||||
|
||||
import config from './config'
|
||||
import { metricsContext } from './metrics'
|
||||
import { start } from './server'
|
||||
|
||||
export async function startCollaborator (): Promise<void> {
|
||||
export async function startCollaborator (ctx: MeasureContext, onClose?: () => void): Promise<void> {
|
||||
setMetadata(serverToken.metadata.Secret, config.Secret)
|
||||
|
||||
let minioPort = 9000
|
||||
@ -44,21 +44,21 @@ export async function startCollaborator (): Promise<void> {
|
||||
|
||||
const mongoClient = await MongoClient.connect(config.MongoUrl)
|
||||
|
||||
const shutdown = await start(metricsContext, config, minioClient, mongoClient)
|
||||
const shutdown = await start(ctx, config, minioClient, mongoClient)
|
||||
|
||||
const close = (): void => {
|
||||
void shutdown().then(() => {
|
||||
void mongoClient.close()
|
||||
})
|
||||
metricsContext.info('closed')
|
||||
onClose?.()
|
||||
}
|
||||
|
||||
process.on('uncaughtException', (e) => {
|
||||
metricsContext.error('UncaughtException', { error: e })
|
||||
ctx.error('UncaughtException', { error: e })
|
||||
})
|
||||
|
||||
process.on('unhandledRejection', (reason, promise) => {
|
||||
metricsContext.error('Unhandled Rejection at:', { promise, reason })
|
||||
ctx.error('Unhandled Rejection at:', { promise, reason })
|
||||
})
|
||||
|
||||
process.on('SIGINT', close)
|
||||
|
Loading…
Reference in New Issue
Block a user