Files
kompas3d-mcp/tools/dev/oplog-report.ps1
T
mikhail 69a075c890 Журнал операций: история вызовов инструментов в JSONL
Каждый вызов пишется строкой JSON из фильтра в Program.cs: инструмент,
аргументы, длительность, ok, полный текст ошибки либо начало ответа.
Снимок модели в журнал не идёт — только размер картинки.

Включается только явным путём: KOMPAS_MCP_OPLOG_PATH на старте или
set_operation_log(enabled, path?) в сессии; KOMPAS_MCP_OPLOG=0 глушит,
не стирая путь. Сбой записи операцию не роняет, причина видна в
kompas_status.

tools/dev/oplog-report.ps1 разбирает накопленное: частота и медианное
время по инструментам, доля отказов, топ ошибок, ни разу не вызванные
инструменты — материал для решений, что из каталога убрать и что
переделать.

Заодно поправлены устаревшие счётчики в README: 60 инструментов
(было указано 57 при 59 фактических), 567 тестов вместо 427.
2026-07-31 21:36:28 +03:00

119 lines
5.5 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#requires -Version 7.0
<#
.SYNOPSIS
Сводка по журналу операций MCP-сервера (JSONL): чем пользуются, что падает, что тормозит.
.DESCRIPTION
Журнал накапливает вызовы инструментов (см. set_operation_log и KOMPAS_MCP_OPLOG_PATH). Этот
отчёт — то, ради чего он ведётся: по нему видно, какие инструменты каталога реально зовут,
какие стабильно отказывают (кандидаты на доработку) и какие не зовут никогда (кандидаты на
удаление). Инструменты, ни разу не встретившиеся в журнале, перечисляются отдельно — их список
берётся из README (каталог инструментов там источник истины).
.EXAMPLE
pwsh -NoProfile -File tools/dev/oplog-report.ps1
.EXAMPLE
pwsh -NoProfile -File tools/dev/oplog-report.ps1 -Path D:\logs\kompas-operations.jsonl -Errors 5
#>
[CmdletBinding()]
param(
# Файл журнала или каталог с ним. По умолчанию — KOMPAS_MCP_OPLOG_PATH.
[string] $Path = $env:KOMPAS_MCP_OPLOG_PATH,
# Сколько текстов ошибок показать в разделе «частые отказы».
[int] $Errors = 10
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
if ([string]::IsNullOrWhiteSpace($Path)) {
throw "Путь к журналу не задан: укажите -Path или переменную окружения KOMPAS_MCP_OPLOG_PATH."
}
if (Test-Path -LiteralPath $Path -PathType Container) {
$Path = Join-Path $Path 'kompas-operations.jsonl'
}
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
throw "Журнал не найден: $Path"
}
# Битая строка (сервер убит посреди записи) не должна ронять отчёт.
$broken = 0
$records = foreach ($line in (Get-Content -LiteralPath $Path -Encoding utf8)) {
if ([string]::IsNullOrWhiteSpace($line)) { continue }
try { $line | ConvertFrom-Json } catch { $broken++ }
}
$records = @($records)
if ($records.Count -eq 0) {
Write-Host "Журнал пуст: $Path"
return
}
$sessions = @($records | Where-Object { $_.PSObject.Properties.Name -contains 'session' } |
Select-Object -ExpandProperty session -Unique)
$failed = @($records | Where-Object { -not $_.ok })
Write-Host "Журнал: $Path"
Write-Host ("Вызовов: {0} отказов: {1} ({2:P1}) период: {3} … {4}" -f
$records.Count, $failed.Count, ($failed.Count / $records.Count),
$records[0].ts, $records[-1].ts)
if ($sessions.Count -gt 0) { Write-Host "Сессий: $($sessions.Count)" }
if ($broken -gt 0) { Write-Host "Нечитаемых строк: $broken" -ForegroundColor DarkYellow }
Write-Host "`n=== Инструменты ==="
$byTool = $records | Group-Object tool | ForEach-Object {
$calls = @($_.Group)
$bad = @($calls | Where-Object { -not $_.ok })
$ms = @($calls | ForEach-Object { [double] $_.ms } | Sort-Object)
[pscustomobject]@{
Инструмент = $_.Name
Вызовов = $calls.Count
Отказов = $bad.Count
'Отказы %' = [int] [math]::Round(100 * $bad.Count / $calls.Count)
'мс med' = [int] $ms[[math]::Floor($ms.Count / 2)]
'мс max' = [int] ($ms | Select-Object -Last 1)
}
}
$byTool | Sort-Object Вызовов -Descending | Format-Table -AutoSize
$suspects = @($byTool | Where-Object { $_.Отказов -gt 0 } | Sort-Object 'Отказы %' -Descending)
if ($suspects.Count -gt 0) {
Write-Host "=== Кандидаты на доработку (есть отказы) ==="
$suspects | Format-Table -AutoSize
}
if ($failed.Count -gt 0) {
Write-Host "=== Частые отказы ==="
$failed |
Group-Object { "$($_.tool): " + ($_.error -replace '\s+', ' ') } |
Sort-Object Count -Descending |
Select-Object -First $Errors |
ForEach-Object {
$text = $_.Name
if ($text.Length -gt 160) { $text = $text.Substring(0, 160) + '…' }
Write-Host ("{0,4}x {1}" -f $_.Count, $text)
}
}
# Не звали ни разу. Каталог берётся из исходников (атрибуты McpServerTool), а не из README:
# имя инструмента там объявлено буквально, без риска поймать в шаблон параметр или слово из текста.
$toolsDir = Join-Path (Split-Path -Parent (Split-Path -Parent $PSScriptRoot)) 'src\Kompas.Mcp.Host\Tools'
if (Test-Path -LiteralPath $toolsDir) {
$catalog = [System.Collections.Generic.HashSet[string]]::new()
foreach ($file in (Get-ChildItem -LiteralPath $toolsDir -Filter '*.cs')) {
$text = Get-Content -LiteralPath $file.FullName -Raw -Encoding utf8
foreach ($m in [regex]::Matches($text, 'McpServerTool\(Name\s*=\s*"([^"]+)"')) {
[void] $catalog.Add($m.Groups[1].Value)
}
}
$known = $catalog.Count
foreach ($tool in ($records | Select-Object -ExpandProperty tool -Unique)) { [void] $catalog.Remove($tool) }
Write-Host "`n=== Ни разу не встречались в журнале: $($catalog.Count) из $known ==="
if ($catalog.Count -gt 0) { Write-Host (($catalog | Sort-Object) -join ', ') }
}