Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions rust/contracts/rust-core-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,7 @@ or removing a command fails until the table changes in the same commit.
| `processLifecycle.resolve` | Decide how each process or resource of one Lithe instance ends |
| `processLifecycle.inventory` | Group a process snapshot of one Lithe instance into terminals, Agent tasks and background services |
| `processLifecycle.settle` | Turn confirmation answers into what ends, what is kept and the updated settings |
| `processMemory.summarize` | Sum a process table's memory by part of one Lithe instance: the host, language servers, plugin services, system helpers and other child processes |
| `markdown.render` | Renders and sanitizes shared Markdown |
| `markdown.outline` | Outlines the headings of shared Markdown as a document-symbol tree |
| `markdown.resolveReference` | Decides what a link or an image of a rendered Markdown document points at |
Expand Down Expand Up @@ -5079,6 +5080,45 @@ scope-token ownership. Fixtures live in

Fixtures live in `rust/fixtures/process-lifecycle/settle-v1.json`.

`processMemory.summarize` accepts `{ hostProcessId, processes, roles, entryLimit }`
and attributes every process the host could read to one part of the instance,
so every host shows the same numbers for the same table. Each process row is
`{ processId, parentProcessId, responsibleProcessId?, startTime?, name, bytes }`.
`bytes` is the memory the host charges to the process; every row of one request
uses the same measure. `responsibleProcessId` is the process the operating system
holds responsible, which macOS reports for XPC services. `startTime` is in
microseconds since 1970; a reported parent that started after its child is a
reused process ID and is not followed. `roles` are
`{ processId, kind: "languageServer" | "pluginService", name }` for the processes
the host started for that role; a role naming the host is ignored, and the first
role listed for a process ID is used. `entryLimit` defaults to 20 and is capped
at 200.

Every process counts at most once:

- The host is the `host` part.
- A process whose parent chain reaches a role process before the host belongs to
that role's part. A language server a plugin service started is therefore
counted as a language server, with the processes below it.
- A process whose parent chain reaches the host first is a `childProcess`,
grouped under the host's direct child it descends from.
- A process outside the host's tree whose `responsibleProcessId` is the host,
such as a WebKit process of a web view, is a `systemHelper`, grouped under the
topmost such process. When the host was started from a terminal, macOS holds
the terminal responsible, so these helpers are not the host's.
- Other processes are left out. The processes of a cycle of parent links are
decided as if none of them had a parent.

It returns `{ totalBytes, categories, entries, omitted }`. `categories` lists
`host`, `languageServer`, `pluginService`, `systemHelper` and `childProcess` in
that order, each `{ kind, bytes, processCount }`, empty ones included. `entries`
are the groups other than the host, `{ kind, name, processId, bytes,
processCount }`, largest first, with ties ordered by part, name and process ID.
`name` is the role name, else the process name, shortened to 80 characters.
`omitted` is `{ count, bytes }` of the groups past the limit. A table without the
host or with a repeated process ID is `invalid_request`. Fixtures live in
`rust/fixtures/process-lifecycle/memory-summary-v1.json`.

## Local IDE capability broker

`ideHost.control` accepts `{action, arguments}` and delegates to the native
Expand Down
7 changes: 7 additions & 0 deletions rust/crates/core/src/protocol/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ pub enum CoreCommand {
ProcessLifecycleInventory,
/// Turns a plan and the user's answers into what ends (`processLifecycle.settle`).
ProcessLifecycleSettle,
/// Sums a process table's memory by part of the instance (`processMemory.summarize`).
ProcessMemorySummarize,
/// Renders and sanitizes shared Markdown (`markdown.render`).
MarkdownRender,
/// Outlines the headings of shared Markdown as document symbols (`markdown.outline`).
Expand Down Expand Up @@ -1193,6 +1195,7 @@ impl CoreCommand {
"processLifecycle.resolve" => Some(Self::ProcessLifecycleResolve),
"processLifecycle.inventory" => Some(Self::ProcessLifecycleInventory),
"processLifecycle.settle" => Some(Self::ProcessLifecycleSettle),
"processMemory.summarize" => Some(Self::ProcessMemorySummarize),
"markdown.render" => Some(Self::MarkdownRender),
"markdown.outline" => Some(Self::MarkdownOutline),
"markdown.resolveReference" => Some(Self::MarkdownResolveReference),
Expand Down Expand Up @@ -1623,6 +1626,10 @@ mod tests {
CoreCommand::parse("processLifecycle.settle"),
Some(CoreCommand::ProcessLifecycleSettle)
));
assert!(matches!(
CoreCommand::parse("processMemory.summarize"),
Some(CoreCommand::ProcessMemorySummarize)
));
}

#[test]
Expand Down
17 changes: 17 additions & 0 deletions rust/crates/core/src/runtime/dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,23 @@ fn execute_parsed(mut parsed: CoreRequest, document: Option<&str>) -> CoreRespon
),
}
}
CoreCommand::ProcessMemorySummarize => {
match serde_json::from_value::<lithe_process::memory::MemoryRequest>(parsed.payload)
.map_err(|error| error.to_string())
.and_then(|request| {
lithe_process::memory::summarize(&request).map_err(|error| error.to_string())
}) {
Ok(summary) => CoreResponse::success(
id,
serde_json::to_value(summary).expect("process memory summary should encode"),
),
Err(details) => CoreResponse::failure(
id,
CoreError::new(ErrorCode::InvalidRequest, "Invalid process memory request")
.with_details(details),
),
}
}
CoreCommand::Editor(command) => editor::dispatch(command, id, parsed.payload, document),
CoreCommand::WorkbenchChromeLayoutReduce => {
rules_response(id, "workbench.chromeLayout.reduce", parsed.payload)
Expand Down
3 changes: 3 additions & 0 deletions rust/crates/core/src/runtime/request_trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,8 @@ pub(crate) fn is_hot(method: &str) -> bool {
method,
"workspace.filterChanges" | "lsp.workspaceFilesChanged" | "git.classifyMetadataChanges"
)
// The status bar sums the instance's memory every few seconds while it is shown.
|| method == "processMemory.summarize"
}

/// Explicit host provenance wins; Git provenance covers hosts that send only that.
Expand Down Expand Up @@ -270,6 +272,7 @@ mod tests {
"workspace.classifyVisibility",
"git.classifyMetadataChanges",
"debug.transport.handle",
"processMemory.summarize",
] {
assert!(is_hot(method), "{method}");
}
Expand Down
1 change: 1 addition & 0 deletions rust/crates/core/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ mod marketplace_plugins;
mod plugin_editor_resources;
mod plugin_run_debug;
mod plugin_settings_documents;
mod process_memory;
mod project;
mod project_generators;
mod project_tree;
Expand Down
33 changes: 33 additions & 0 deletions rust/crates/core/src/tests/process_memory.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//! `processMemory.summarize` against the shared memory summary fixture.

use serde_json::{json, Value};

fn run(request: &Value) -> Value {
serde_json::from_str(&crate::execute_json(
&json!({"command": "processMemory.summarize", "payload": request}).to_string(),
))
.unwrap()
}

#[test]
fn shared_memory_summary_fixture_runs_every_case() {
let fixture: Value = serde_json::from_str(include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/../../fixtures/process-lifecycle/memory-summary-v1.json"
)))
.unwrap();
for case in fixture["cases"].as_array().unwrap() {
let response = run(&case["request"]);
assert_eq!(response["ok"], true, "case {}: {response}", case["name"]);
assert_eq!(response["data"], case["expected"], "case {}", case["name"]);
}
for case in fixture["invalid"].as_array().unwrap() {
let response = run(&case["request"]);
assert_eq!(response["ok"], false, "case {}: {response}", case["name"]);
assert_eq!(
response["error"]["code"], "invalid_request",
"case {}",
case["name"]
);
}
}
2 changes: 1 addition & 1 deletion rust/crates/process/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "lithe-process"
version = "0.1.0"
edition = "2021"
license = "Apache-2.0"
description = "Child-process lifecycle and inventory decisions shared by every Lithe host"
description = "Child-process lifecycle, inventory and memory attribution decisions shared by every Lithe host"

[dependencies]
serde = { version = "1.0", features = ["derive"] }
Expand Down
5 changes: 3 additions & 2 deletions rust/crates/process/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Child-process lifecycle decisions and inventory classification shared by
//! the platform process supervisors.
//! Child-process lifecycle decisions, inventory classification and memory
//! attribution shared by the platform process supervisors.

pub mod inventory;
pub mod lifecycle;
pub mod memory;
Loading
Loading