Skip to content

Commit 9e5b56a

Browse files
mtkairplaytestmtkairplaytest
authored andcommitted
feat(telemetry): add LOC statistics to OpenTelemetry spans
1 parent 4c56216 commit 9e5b56a

5 files changed

Lines changed: 118 additions & 1 deletion

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export { TelemetryConfig } from "./config"
22
export { TelemetryProvider } from "./provider"
3+
export { LocTelemetry } from "./loc"
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { TelemetryProvider } from "./provider"
2+
import { SpanKind, SpanStatusCode } from "@opentelemetry/api"
3+
4+
export namespace LocTelemetry {
5+
export interface FileChange {
6+
filePath: string
7+
additions: number
8+
deletions: number
9+
toolName: string
10+
changeType?: "add" | "update" | "delete" | "move"
11+
}
12+
13+
export function recordFileChange(change: FileChange, sessionID: string): void {
14+
const tracer = TelemetryProvider.getTracer()
15+
if (!tracer) return
16+
17+
const span = tracer.startSpan("file.change", {
18+
kind: SpanKind.INTERNAL,
19+
attributes: {
20+
"opencode.session.id": sessionID,
21+
"opencode.tool.name": change.toolName,
22+
"opencode.file.path": change.filePath,
23+
"opencode.loc.additions": change.additions,
24+
"opencode.loc.deletions": change.deletions,
25+
"opencode.loc.net_change": change.additions - change.deletions,
26+
},
27+
})
28+
29+
if (change.changeType) {
30+
span.setAttribute("opencode.file.change_type", change.changeType)
31+
}
32+
33+
span.setStatus({ code: SpanStatusCode.OK })
34+
span.end()
35+
}
36+
37+
export function recordBatchChanges(changes: FileChange[], sessionID: string, toolName: string): void {
38+
const tracer = TelemetryProvider.getTracer()
39+
if (!tracer) return
40+
41+
if (changes.length === 0) return
42+
43+
let totalAdditions = 0
44+
let totalDeletions = 0
45+
const fileDetails = changes.map((change) => {
46+
totalAdditions += change.additions
47+
totalDeletions += change.deletions
48+
return {
49+
path: change.filePath,
50+
additions: change.additions,
51+
deletions: change.deletions,
52+
type: change.changeType,
53+
}
54+
})
55+
56+
const span = tracer.startSpan("file.batch_change", {
57+
kind: SpanKind.INTERNAL,
58+
attributes: {
59+
"opencode.session.id": sessionID,
60+
"opencode.tool.name": toolName,
61+
"opencode.batch.file_count": changes.length,
62+
"opencode.batch.total_additions": totalAdditions,
63+
"opencode.batch.total_deletions": totalDeletions,
64+
"opencode.batch.net_change": totalAdditions - totalDeletions,
65+
"opencode.batch.files": JSON.stringify(fileDetails),
66+
},
67+
})
68+
69+
span.setStatus({ code: SpanStatusCode.OK })
70+
span.end()
71+
}
72+
}

packages/opencode/src/tool/apply_patch.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { LSP } from "../lsp"
1313
import { Filesystem } from "../util/filesystem"
1414
import DESCRIPTION from "./apply_patch.txt"
1515
import { File } from "../file"
16+
import { LocTelemetry } from "@/telemetry"
1617

1718
const PatchParams = z.object({
1819
patchText: z.string().describe("The full patch text that describes all changes to be made"),
@@ -230,6 +231,18 @@ export const ApplyPatchTool = Tool.define("apply_patch", {
230231
await Bus.publish(FileWatcher.Event.Updated, { file: filePath, event: "change" })
231232
}
232233

234+
LocTelemetry.recordBatchChanges(
235+
fileChanges.map((change) => ({
236+
filePath: change.movePath ?? change.filePath,
237+
additions: change.additions,
238+
deletions: change.deletions,
239+
toolName: "apply_patch",
240+
changeType: change.type,
241+
})),
242+
ctx.sessionID,
243+
"apply_patch",
244+
)
245+
233246
// Notify LSP of file changes and collect diagnostics
234247
for (const change of fileChanges) {
235248
if (change.type === "delete") continue

packages/opencode/src/tool/edit.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { Filesystem } from "../util/filesystem"
1616
import { Instance } from "../project/instance"
1717
import { Snapshot } from "@/snapshot"
1818
import { assertExternalDirectory } from "./external-directory"
19+
import { LocTelemetry } from "@/telemetry"
1920

2021
const MAX_DIAGNOSTICS_PER_FILE = 20
2122

@@ -111,6 +112,17 @@ export const EditTool = Tool.define("edit", {
111112
if (change.removed) filediff.deletions += change.count || 0
112113
}
113114

115+
LocTelemetry.recordFileChange(
116+
{
117+
filePath: filediff.file,
118+
additions: filediff.additions,
119+
deletions: filediff.deletions,
120+
toolName: "edit",
121+
changeType: contentOld === "" ? "add" : "update",
122+
},
123+
ctx.sessionID,
124+
)
125+
114126
ctx.metadata({
115127
metadata: {
116128
diff,

packages/opencode/src/tool/write.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import z from "zod"
22
import * as path from "path"
33
import { Tool } from "./tool"
44
import { LSP } from "../lsp"
5-
import { createTwoFilesPatch } from "diff"
5+
import { createTwoFilesPatch, diffLines } from "diff"
66
import DESCRIPTION from "./write.txt"
7+
import { LocTelemetry } from "@/telemetry"
78
import { Bus } from "../bus"
89
import { File } from "../file"
910
import { FileTime } from "../file/time"
@@ -47,6 +48,24 @@ export const WriteTool = Tool.define("write", {
4748
})
4849
FileTime.read(ctx.sessionID, filepath)
4950

51+
let additions = 0
52+
let deletions = 0
53+
for (const change of diffLines(contentOld, params.content)) {
54+
if (change.added) additions += change.count || 0
55+
if (change.removed) deletions += change.count || 0
56+
}
57+
58+
LocTelemetry.recordFileChange(
59+
{
60+
filePath: filepath,
61+
additions,
62+
deletions,
63+
toolName: "write",
64+
changeType: exists ? "update" : "add",
65+
},
66+
ctx.sessionID,
67+
)
68+
5069
let output = "Wrote file successfully."
5170
await LSP.touchFile(filepath, true)
5271
const diagnostics = await LSP.diagnostics()

0 commit comments

Comments
 (0)