adb-toolkit/proprietary/backups/logcatpatterns.ts
2026-06-30 12:47:36 +10:00

136 lines
5.1 KiB
TypeScript

import type { LogcatLine } from './types'
export type RefKind =
| 'activity' // launching/focusing an activity in a package
| 'spawn' // process start
| 'death' // process death / kill / LMK
| 'crash' // app crash / FATAL
| 'anr' // application not responding
| 'signal' // signal sent to a pid
| 'gfx' // surface/window/composition reference
| 'mention' // generic package reference found in the message
export interface LogRef {
kind: RefKind
target: string // entity id payload (package, component, or pid)
targetKind: 'package' | 'component' | 'pid'
}
// Severity weight per kind — used by the engine to colour/size the edge.
export const REF_SEVERITY: Record<RefKind, number> = {
crash: 5, anr: 5, death: 3, signal: 3, spawn: 2, activity: 1, gfx: 1, mention: 0,
}
// A dotted token that looks like an Android package/class (≥2 segments, starts
// with a lowercase domain-ish chunk). Greedy but effective; the engine de-dupes.
const PKG = /\b([a-z][a-z0-9_]*(?:\.[a-z0-9_]+){2,})\b/g
// component: com.foo/.Bar or com.foo/com.foo.Bar
const COMPONENT = /\b([a-z][a-z0-9_.]+)\/([a-z0-9_.$]+)/i
// CSV payload of an event-log line: "[u0,12345,com.foo,...]" or "[12345,...]"
const EVENT_CSV = /\[([^\]]*)\]/
function firstPackage(s: string): string | null {
PKG.lastIndex = 0
const m = PKG.exec(s)
return m ? m[1] : null
}
// Pull the package field out of an event-log CSV payload (first dotted token).
function eventPackage(msg: string): string | null {
const csv = EVENT_CSV.exec(msg)
if (!csv) return firstPackage(msg)
for (const f of csv[1].split(',')) {
const t = f.trim()
if (/^[a-z][a-z0-9_]*(\.[a-z0-9_]+){2,}$/.test(t)) return t
}
return firstPackage(msg)
}
/**
* Extract the relationships a single log line implies. Returns [] for the vast
* majority of lines (which only contribute co-occurrence + their own node).
*/
export function extractRefs(line: LogcatLine): LogRef[] {
const tag = line.tag
const msg = line.message || ''
const refs: LogRef[] = []
const push = (kind: RefKind, target: string | null, targetKind: LogRef['targetKind']) => {
if (target) refs.push({ kind, target, targetKind })
}
// ---- binary event-log tags (events buffer) -----------------------------
switch (tag) {
case 'am_proc_start':
case 'am_proc_bound':
push('spawn', eventPackage(msg), 'package'); return refs
case 'am_proc_died':
case 'am_kill':
case 'am_low_memory':
push('death', eventPackage(msg), 'package'); return refs
case 'am_crash':
push('crash', eventPackage(msg), 'package'); return refs
case 'am_anr':
push('anr', eventPackage(msg), 'package'); return refs
case 'am_activity_launch_time':
case 'am_focused_activity':
case 'am_resume_activity':
case 'am_pause_activity':
case 'wm_focused_window': {
const c = COMPONENT.exec(msg)
push('activity', c ? `${c[1]}/${c[2]}` : eventPackage(msg), c ? 'component' : 'package')
return refs
}
}
// ---- framework text logs (main/system buffers) -------------------------
if (tag === 'ActivityManager' || tag === 'ActivityTaskManager') {
if (/\bANR in\b/.test(msg)) push('anr', firstPackage(msg), 'package')
if (/\bStart proc\b/.test(msg)) push('spawn', firstPackage(msg), 'package')
if (/\bKilling\b|\bhas died\b|\bdied\b/.test(msg)) push('death', firstPackage(msg), 'package')
const sig = /Sending signal\.\s*PID:\s*(\d+)/.exec(msg)
if (sig) push('signal', sig[1], 'pid')
if (/\bSTART u\d+|\bDisplayed\b|\bmoveTaskTo/.test(msg)) {
const c = COMPONENT.exec(msg)
push('activity', c ? `${c[1]}/${c[2]}` : firstPackage(msg), c ? 'component' : 'package')
}
if (refs.length) return refs
}
if (tag === 'AndroidRuntime' || /FATAL EXCEPTION/.test(msg)) {
const p = /Process:\s*([a-z][a-z0-9_.]+)/i.exec(msg)
push('crash', p ? p[1] : firstPackage(msg), 'package')
if (refs.length) return refs
}
if (tag === 'lowmemorykiller' || tag === 'lmkd') {
push('death', firstPackage(msg), 'package'); if (refs.length) return refs
}
if (/^(SurfaceFlinger|WindowManager|ViewRootImpl|Choreographer|gralloc|OpenGLRenderer)/.test(tag)) {
const c = COMPONENT.exec(msg)
if (c) { push('gfx', `${c[1]}/${c[2]}`, 'component'); return refs }
}
return refs // caller may still apply the generic "mention" fallback if enabled
}
// Generic fallback: any package-looking token (used when "parsed mentions" is on
// in settings — maximises connections for "see everything", at the cost of noise).
export function extractMentions(line: LogcatLine, max = 3): LogRef[] {
const msg = line.message || ''
const out: LogRef[] = []
const seen = new Set<string>()
PKG.lastIndex = 0
let m: RegExpExecArray | null
while ((m = PKG.exec(msg)) && out.length < max) {
const t = m[1]
// Skip obvious noise: java.*, android.* internal classes are too common.
if (seen.has(t)) continue
seen.add(t)
if (/^(java|javax|sun|kotlin|android|androidx|dalvik)\./.test(t)) continue
out.push({ kind: 'mention', target: t, targetKind: 'package' })
}
return out
}