# Analytics The Editor server reports how the pipeline is used through Unity's editor analytics (`EditorAnalytics.SendAnalytic`). The data answers questions the package has no other way to answer: whether sessions are interactive or automated, which commands clients actually reach for, how often they fail, how long they take, and how much traffic is raw `eval` rather than a concrete command. Three things bound what is collected: - **Editor only.** A server running in a development Player reports nothing — `EditorAnalytics` does not exist there. - **The editor's own opt-out applies.** With analytics disabled in Unity preferences, `SendAnalytic` does nothing and no event leaves the machine. There is no separate pipeline switch to turn off. - **Nothing about a command's content is collected.** Not its parameters, not the code passed to `eval`, not paths, not results. - **Nothing a project declared about its own commands is collected.** Only commands shipped in the package report their name and tags; anything a project declared itself reports `` and no tags — see [What is and is not collected](#what-is-and-is-not-collected). ## Events ### `Pipeline_SessionStarted` Sent once when a client executes its first command, which is the moment the pipeline actually started handling work — not when the Editor launched or the server bound its port. | Field | Meaning | |-------|---------| | `batchmode` | The Editor was launched with `-batchmode`. | | `automated` | The Editor was launched with `-automated`. | | `browserAllowed` | The server accepts sandboxed browser clients (the limited-CORS path). Off unless enabled in the Pipeline settings. | | `dynamicPortAssignation` | The port was auto-assigned from the server's range rather than pinned in the settings asset. | | `hotReloadWatcherEnabled` | The `.cs` change watcher is running. | A session spans a Unity **process**. The latch lives in `SessionState`, which survives domain reloads, so a script recompile does not open a second session. ### `Pipeline_CommandExecuted` Sent once per command a client executes. | Field | Meaning | |-------|---------| | `commandName` | The registered command name — but only for commands that ship with the package. A project-declared command reports the literal `` instead. | | `commandTags` | The command's tags, in declaration order — but only for commands that ship with the package. Empty for an untagged command, and always empty for a project-declared one. | | `commandSuccess` | Whether the command succeeded. False both when it threw and when it returned a result carrying its own failure — `eval`, `reload_file`, `run_script` and `run_tests` all report failure the second way. | | `commandDuration` | Milliseconds spent inside the command, excluding the wait for the one-command-at-a-time gate. | | `isUserDefinedCommand` | The command was declared by the project rather than shipped in the package. | | `isEval` | The command is a code evaluation (tagged `scripts/eval`: `eval`, `eval_file`, `run_script`). | | `isEvalWithExistingCommandAvailable` | Whether the evaluated snippet touches an API that a shipped command already covers — the "you did not need eval for this" signal. Same judgement `report_evals` makes, applied to one invocation: see [How the eval-coverage flag is decided](#how-the-eval-coverage-flag-is-decided). | What counts as one execution: - One event per `/api/exec` call, including a `job: true` submission — reported when the job actually **finishes**, not when its job handle is returned. - A `batch` reports one event, for `batch` itself. Its sub-operations are not reported individually. - A request rejected before anything ran (oversized body, malformed JSON, unknown command, settling host, full job queue) reports nothing: no command executed. - The internal executions behind `/api/editor_status` and `/api/test-status` report nothing. Clients poll those endpoints, and the polling would swamp everything else. ### `Pipeline_SessionStopped` Sent when the Editor quits, and only if `Pipeline_SessionStarted` was sent — an Editor that never served a command closes no session. | Field | Meaning | |-------|---------| | `sessionDuration` | Milliseconds between the session starting and the Editor quitting. | ## How the eval-coverage flag is decided `isEvalWithExistingCommandAvailable` reuses the eval instrumentation from AUTHAPI-29 rather than inventing a second opinion. Per invocation: 1. `EvalUsageFingerprinter.Analyze` turns the snippet into API fingerprints — dotted member-access paths, with no literals or raw source in them. 2. Each fingerprint is looked up in `EvalCoverageMap.Default`, a small curated hint map from API to the command that would cover it. 3. The flag is true only if a mapped command is **actually in the live catalog**. A mapping to a command that does not exist yet (`AssetDatabase.Refresh` → `refresh_assets`) is a gap for `report_evals` to surface, not an avoidable eval. This is deliberately the same rule as `report_evals`' `covered` list, so the two can never disagree. Two consequences worth knowing when reading the data: - **It is a floor, not a measure.** The map has a handful of entries and matching is on the whole fingerprint, so `PlayerSettings.GetScriptingBackend(...)` matches while `UnityEditor.PlayerSettings.GetScriptingBackend(...)` and `PlayerSettings.GetScriptingBackend(...).ToString()` do not — they fingerprint as different paths. A `false` means "no mapped-and-present command matched", never "no command could cover this". - **`run_script` always reports `false`.** It runs a project file through a named entry point rather than a snippet, and the fingerprinter parses script-kind bodies, so it is not fingerprinted. `eval` (inline body) and `eval_file` (body read back from its file) are judged. ## What is and is not collected A command's name and tags are text the project's own authors wrote, so either can name an unannounced feature or an internal system. Tags are constrained in shape (`^[a-z0-9_]+(/[a-z0-9_]+)*$`) and shipped commands must use the documented taxonomy, but nothing holds a project to it, so a tag carries a project-chosen word exactly as a name does. So both are withheld. `commandName` and `commandTags` describe only commands that ship in the package (`Unity.Pipeline`, `Unity.Pipeline.Editor`); every other `[CliCommand]` reports the literal `` and an empty tag array. The remaining fields — success, duration, `isEval` — are facts about the execution, not about what the project called it, so they are reported for every command. What this costs: project-declared traffic is countable but not breakable down by area. That is the deliberate trade — `isUserDefinedCommand` answers how much of the surface is project-declared, which is the question worth asking, and nothing identifying is needed to answer it. Command **arguments** are never collected. Neither is anything a command reads or writes. ## Adding post-command work Analytics is one consumer of a single seam rather than a special case. When a command finishes, `BasePipelineServer.OnCommandDone` receives a `CommandExecutionInfo` describing both the raw transaction and the execution. It runs on the HTTP thread, so `EditorPipelineServer` splits by what each consumer needs: the transaction log is appended there directly (thread-safe file I/O by design), and anything needing Unity APIs is handed to the server dispatcher with `Dispatcher.Post`, which runs it in `OnCommandDoneMainThread` on the next main-thread pump. New post-command work goes in one of those two, depending on which thread it needs. `Post` is the general fire-and-forget counterpart to `Dispatcher.Invoke`: it returns immediately instead of parking the request thread until the next pump, and swallows-and-logs failures because there is no caller left to throw to. Work still queued when the server stops is dropped, so a report can be lost in the frame the Editor quits or reloads its domain. ## See also - [Creating commands](creating-commands.md) — where a command's name and tags come from. - [Connectivity](connectivity.md) — the port and CORS settings the session event reports. - [Code reload](code-reload.md) — the watcher whose state the session event reports.