69a075c890
Каждый вызов пишется строкой 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.
119 lines
5.5 KiB
PowerShell
119 lines
5.5 KiB
PowerShell
#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 ', ') }
|
||
}
|