Skip to content

Commit 333eaa1

Browse files
committed
Update docs and plugin guide for v1.8.5 release
Bump version to 1.8.5 in mix.exs, app.src, and README. Expand plugin documentation with clearer instructions, option references, and custom handler details. Improve README with new tips, updated system panel descriptions, and OTP 27+ process label info. Refine error message for missing plugins in observer_cli_plugin.erl.
1 parent 5e6a1fa commit 333eaa1

File tree

5 files changed

+156
-148
lines changed

5 files changed

+156
-148
lines changed

README.md

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ Observer CLI is a library to be dropped into any beam nodes, to be used to assis
2828
%% rebar.config
2929
{deps, [observer_cli]}
3030
%% erlang.mk
31-
dep_observer_cli = hex 1.8.3
31+
dep_observer_cli = hex 1.8.5
3232
```
3333

3434
### Elixir
@@ -81,6 +81,9 @@ iex(1)> :observer_cli.start(:'target@host', :'magic_cookie')
8181
> #### exclamation {: .info}
8282
> **ensure observer_cli application been loaded on target node.**
8383
84+
> #### tip {: .tip}
85+
> Pass `{interval, 3000}` (Erlang) or `interval: 3000` (Elixir) to sample every 3 seconds. The minimum refresh interval is 1000 ms.
86+
8487
### Escriptize
8588

8689
1. cd path/to/observer_cli/
@@ -109,12 +112,14 @@ The Home panel provides a comprehensive overview of your Erlang node:
109112
* **port_limit**: `erl +Q Number` sets the maximum number of simultaneously existing ports for this system if a Number is passed as value. Valid range for Number is [1024-134217727]. The default value used is normally 65536. However, if the runtime system is able to determine maximum amount of file descriptors that it is allowed to open and this value is larger than 65536, the chosen value will increased to a value larger or equal to the maximum amount of file descriptors that can be opened.
110113
* **atom_limit**: `erl +t size` sets the maximum number of atoms the virtual machine can handle. Defaults to 1,048,576.
111114

112-
[`PS`](https://man7.org/linux/man-pages/man1/ps.1.html) report a snapshot of the beam process.
115+
[`ps`](https://man7.org/linux/man-pages/man1/ps.1.html) reports a snapshot of the BEAM OS process and feeds the system panel. Observer CLI samples four columns:
113116

114-
| Command/Flag | Description |
115-
|--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
116-
| ps -o pcpu | cpu utilization of the process in "##.#" format. Currently, it is the CPU time used divided by the time the process has been running (cputime/realtime ratio), expressed as a percentage. It will not add up to 100% unless you are lucky. |
117-
| ps -o pmem | ratio of the process's resident set size to the physical memory on the machine, expressed as a percentage. |
117+
| Command/Flag | Description |
118+
|--------------|-------------|
119+
| `ps -o pcpu` | CPU utilization of the BEAM OS process expressed as percentage of a single core. Calculated from cumulative scheduler time / wall clock time, so it may exceed what top reports on multi-core systems. |
120+
| `ps -o pmem` | Percentage of physical memory used by the BEAM OS process (resident set size / total RAM). |
121+
| `ps -o rss` | Resident set size in kilobytes, useful for spotting long-lived memory growth. |
122+
| `ps -o vsz` | Virtual memory size in kilobytes, highlighting total address space reservations (code, heap, and mapped binaries). |
118123

119124
[`erlang:memory/0`](http://erlang.org/doc/man/erlang.html#memory-0) Returns a list with information about memory dynamically allocated by the Erlang emulator.
120125

@@ -144,7 +149,7 @@ Scheduler utilization by [`erlang:statistics(scheduler_wall_time)`](http://erlan
144149

145150
### Process
146151

147-
When looking for high memory usage, for example it's interesting to be able to list all of a node's processes and find the top N consumers. Enter `m` then press `Enter` will use the `recon:proc_count(memory, N)` function, we can get:
152+
When looking for high memory usage, for example it's interesting to be able to list all of a node's processes and find the top N consumers. Enter `m` then press `Enter` will use the `recon:proc_count(memory, N)` function, and you will get output like the following. On OTP 27+ nodes, process rows also display any label set through [`proc_lib:set_label/1`](https://www.erlang.org/doc/apps/stdlib/proc_lib.html#set_label/1), which helps correlate supervised jobs with their metrics.
148153

149154
![Top](https://user-images.githubusercontent.com/3116225/96717499-25539e00-13d9-11eb-85af-fdde633da098.jpg)
150155

@@ -217,6 +222,7 @@ When find out who is slowly but surely eating up all your bandwidth, enter the s
217222
* **System Info**: [`erlang:system_info/1`](http://erlang.org/doc/man/erlang.html#system_info-1) returns various information about the allocators of the current system (emulator).
218223
* **Allocator Info**: [`recon_alloc:average_block_sizes(current|max)`](https://ferd.github.io/recon/recon_alloc.html#average_block_sizes-1) check all allocators in `allocator` and returns the average block sizes being used for mbcs and sbcs. This value is interesting to use because it will tell us how large most blocks are. This can be related to the VM's largest multiblock carrier size (lmbcs) and smallest multiblock carrier size (smbcs) to specify allocation strategies regarding the carrier sizes to be used.
219224
* **Cache Hit Rate**: [`recon_alloc:cache_hit_rates()`](https://ferd.github.io/recon/recon_alloc.html#cache_hit_rates-0) Cache can be tweaked using three VM flags: `+MMmcs`, `+MMrmcbf`, and `+MMamcbf`.
225+
* **Distribution buffers**: Uses [`erlang:dist_get_stat/1`](https://www.erlang.org/doc/man/erlang.html#dist_get_stat-1) on OTP 24+ to track per-node distribution queue sizes and the configured `dist_buf_busy_limit`.
220226

221227
### ETS
222228

docs/plugin.md

Lines changed: 138 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -1,191 +1,193 @@
11
# How to write your own plugin?
22

3-
If you need to customize some of your internal metrics and integrate it into observer_ci,
4-
you only need to write a `observer_cli_plugin` behaviour in a few simple steps to get a nice presentation.
3+
Observer CLI exposes a small behaviour (`observer_cli_plugin`) that lets you present custom metrics alongside the built-in views. This guide walks through the required configuration and each callback so you can build your own panels quickly.
54

6-
1. Configure observer_cli,tell observer_cli how to find your plugin.
5+
## 1. Register the plugin module
76

8-
```erlang
9-
%% module - Specific module implements plugin behavior. It's mandatory.
10-
%% title - Menu title. It's mandatory.
11-
%% shortcut - Switch plugin by shortcut. It's mandatory.
12-
%% interval - Refresh interval ms. It's optional. default is 1500ms.
13-
%% sort_column - Sort the sheet by this index. It's optional default is 2.
7+
Add a `plugins` entry to the Observer CLI environment (for example in `mix.exs` or `observer_cli.app.src`):
148

9+
```erlang
1510
{plugins,
16-
[
17-
#{module => observer_cli_plug_behaviour_x, title => "XPlug",
18-
interval => 1600, shortcut => "X", sort_column => 3},
19-
#{module => observer_cli_plug_behaviour_y, title => "YPlug",
20-
interval =>2000, shortcut => "Y", sort_column => 3}
21-
]
22-
}
11+
[
12+
#{module => observer_cli_plug_behaviour_x,
13+
title => "XPlug",
14+
shortcut => "X",
15+
interval => 1600,
16+
sort_column => 3},
17+
#{module => observer_cli_plug_behaviour_y,
18+
title => "YPlug",
19+
shortcut => "Y",
20+
interval => 2000,
21+
sort_column => 3}
22+
]}.
2323
```
2424

25-
The main view is `HOME` by default(`observer_cli:start()`).
26-
If you want to plugin view as main view, DO:`your_cli:start().`
25+
**Option reference**
26+
27+
- `module` - module implementing the behaviour (required).
28+
- `title` - label rendered in the menu bar (required).
29+
- `shortcut` - single key used to jump to the plugin (required).
30+
- `interval` - refresh rate in milliseconds (optional, defaults to `1500`).
31+
- `sort_column` - index used when sorting the sheet (optional, defaults to `2`).
32+
- `handler` - tuple `{PredicateFun, Module}` for custom row handling (optional, see [Custom handlers](#4-custom-handlers)).
33+
34+
The default entry point is still the `HOME` view (`observer_cli:start()`). To boot straight into plugin mode expose a shim:
2735

2836
```erlang
29-
% your_cli.erl
30-
start() -> observer_cli:start_plugin().
37+
-module(your_cli).
38+
39+
start() ->
40+
observer_cli:start_plugin().
3141
```
3242

33-
2. Write observer_cli_plugin behaviour.
34-
observer_cli_plugin has 3 callbacks.
43+
## 2. Implement `observer_cli_plugin`
44+
45+
The behaviour defines three callbacks.
3546

36-
2.1 attributes.
47+
### `attributes/1`
3748

3849
```erlang
3950
-callback attributes(PrevState) -> {[Rows], NewState} when
40-
Rows :: #{content => string()|integer()|{byte, pos_integer()},
41-
width => pos_integer(), color => binary()}.
51+
Rows :: [
52+
#{content => string() | integer() | {byte, pos_integer()} | {percent, float()},
53+
width => pos_integer(),
54+
color => binary()}
55+
],
56+
NewState :: any().
4257
```
4358

44-
for example:
59+
This callback drives the banner directly under the menu. The structure is a list of rows; each row is a list of maps describing individual cells.
4560

4661
```erlang
4762
attributes(PrevState) ->
48-
Attrs = [
49-
[
50-
#{content => "XXX Ets Size", width => 15},
51-
#{content => 122, width => 10},
52-
#{content => "Memory Capcity", width => 15},
53-
#{content => {percent, 0.12}, width => 16},
54-
#{content => "XYZ1 Process Mem", width => 19},
55-
#{content => {byte, 1023 * 1203}, width => 19}
56-
],
57-
[
58-
#{content => "YYY Ets Size", width => 15},
59-
#{content => 43, width => 10},
60-
#{content => "Disk Capcity", width => 15},
61-
#{content => {percent, 0.23}, width => 16},
62-
#{content => "XYZ2 Process Mem", width => 19},
63-
#{content => {byte, 2034 * 220}, width => 19}
63+
Attrs = [
64+
[
65+
#{content => "XXX ETS Size", width => 15},
66+
#{content => 122, width => 10},
67+
#{content => "Memory Capacity", width => 16},
68+
#{content => {percent, 0.12}, width => 10},
69+
#{content => "XYZ1 Process Mem", width => 20},
70+
#{content => {byte, 1023 * 1203}, width => 14}
71+
],
72+
[
73+
#{content => "YYY ETS Size", width => 15},
74+
#{content => 43, width => 10},
75+
#{content => "Disk Capacity", width => 15},
76+
#{content => {percent, 0.23}, width => 10},
77+
#{content => "XYZ2 Process Mem", width => 20},
78+
#{content => {byte, 2034 * 220}, width => 14}
79+
]
6480
],
65-
[
66-
#{content => "ZZZ Ets Size", width => 15},
67-
#{content => 108, width => 10},
68-
#{content => "Volume Capcity", width => 15},
69-
#{content => {percent, 0.101}, width => 16},
70-
#{content => "XYZ3 Process Mem", width => 19},
71-
#{content => {byte, 12823}, width => 19}
72-
]
73-
],
74-
NewState = PrevState,
75-
{Attrs, NewState}.
81+
{Attrs, PrevState}.
7682
```
7783

78-
```markdown
84+
Rendered banner:
85+
86+
```
7987
|Home(H)|XPlug(X)|YPlug(Y)| | 0Days 3:34:50 |
80-
|XXX Ets Size | 122 | Memory Capcity | 12.00% | XYZ1 Process Mem | 1.1737 MB |
81-
|YYY Ets Size | 43 | Disk Capcity | 23.00% | XYZ2 Process Mem | 436.9922 KB |
82-
|ZZZ Ets Size | 108 | Volume Capcity | 10.10% | XYZ3 Process Mem | 12.5225 KB |
88+
|XXX ETS Size | 122 | Memory Capacity | 12.00% | XYZ1 Process Mem | 1.1737 MB |
89+
|YYY ETS Size | 43 | Disk Capacity | 23.00% | XYZ2 Process Mem | 436.9922 KB |
8390
```
8491

92+
### `sheet_header/0`
93+
8594
```erlang
8695
-callback sheet_header() -> [SheetHeader] when
87-
SheetHeader :: #{title => string(), width => pos_integer(), shortcut => string()}.
96+
SheetHeader :: #{title => string(),
97+
width => pos_integer(),
98+
shortcut => string()}.
8899
```
89100

90-
for example:
101+
Defines the tabular columns shown underneath the banner. Shortcuts let the user sort the sheet by pressing the letter.
91102

92103
```erlang
93104
sheet_header() ->
94-
[
95-
#{title => "Pid", width => 15},
96-
#{title => "Register", width => 20},
97-
#{title => "Memory", width => 20, shortcut => "S"},
98-
#{title => "Reductions", width => 23, shortcut => "R"},
99-
#{title => "Message Queue Len", width => 23, shortcut => "Q"}
100-
].
105+
[
106+
#{title => "Pid", width => 15},
107+
#{title => "Register", width => 20},
108+
#{title => "Memory", width => 20, shortcut => "S"},
109+
#{title => "Reductions", width => 23, shortcut => "R"},
110+
#{title => "Message Queue Len", width => 23, shortcut => "Q"}
111+
].
101112
```
102113

103-
```markdown
104-
|No |Pid |Register |Memory(S) |Reductions(R) |Message Queue Len(Q) |
114+
Result:
115+
116+
```
117+
|No |Pid |Register |Memory(S) |Reductions(R) |Message Queue Len(Q) |
105118
```
106119

107-
```erlang
108-
-callback sheet_body(PrevState) -> {[SheetBody], NewState} when
109-
PrevState :: any(),
110-
SheetBody :: list(),
111-
NewState :: any().
120+
### `sheet_body/1`
112121

122+
```erlang
123+
-callback sheet_body(PrevState) -> {[SheetBody], NewState}.
113124
```
114125

115-
for example:
126+
Return the table rows. Each row is a list; Observer CLI paginates automatically (PageDown/PageUp or `F/B` keys).
116127

117128
```erlang
118129
sheet_body(PrevState) ->
119-
Body = [
120-
begin
121-
Register =
122-
case erlang:process_info(Pid, registered_name) of
123-
[] -> [];
124-
{_, Name} -> Name
125-
end,
126-
[
127-
Pid,
128-
Register,
129-
{byte, element(2, erlang:process_info(Pid, memory))},
130-
element(2, erlang:process_info(Pid, reductions)),
131-
element(2, erlang:process_info(Pid, message_queue_len))
132-
]
133-
end
134-
|| Pid <- erlang:processes()
135-
],
136-
NewState = PrevState,
137-
{Body, NewState}.
138-
```
139-
140-
Support `{byte, 1024*10}` to ` 10.0000 KB`; `{percent, 0.12}` to `12.00%`.
141-
142-
```markdown
143-
|No |Pid |Register |Memory(S) |Reductions(R) |Message Queue Len(Q) |
144-
|1 |<0.242.0> | | 4.5020 MB | 26544288 | 0 |
145-
|2 | <0.206.0> | | 1.2824 MB | 13357885 | 0 |
146-
|3 | <0.10.0> | erl_prim_loader | 1.0634 MB | 10046775 | 0 |
147-
|4 | <0.434.0> | | 419.1719 KB | 10503690 | 0 |
148-
|5 | <0.44.0> | application_contro | 416.6250 KB | 153598 | 0 |
149-
|6 | <0.50.0> | code_server | 416.4219 KB | 301045 | 0 |
150-
|7 | <0.9.0> | rebar_agent | 136.7031 KB | 1337603 | 0 |
151-
|8 | <0.207.0> | | 99.3125 KB | 9629 | 0 |
152-
|9 | <0.58.0> | file_server_2 | 41.3359 KB | 34303 | 0 |
153-
|10 | <0.209.0> | | 27.3438 KB | 31210 | 0 |
154-
|11 | <0.0.0> | init | 25.8516 KB | 8485 | 0 |
155-
|refresh: 1600ms q(quit) Positive Number(set refresh interval time ms) F/B(forward/back) Current pages is 1 |
130+
Rows = [
131+
begin
132+
Register =
133+
case erlang:process_info(Pid, registered_name) of
134+
[] -> [];
135+
{_, Name} -> Name
136+
end,
137+
[
138+
Pid,
139+
Register,
140+
{byte, element(2, erlang:process_info(Pid, memory))},
141+
element(2, erlang:process_info(Pid, reductions)),
142+
element(2, erlang:process_info(Pid, message_queue_len))
143+
]
144+
end
145+
|| Pid <- erlang:processes()
146+
],
147+
{Rows, PrevState}.
156148
```
157149

158-
Support F/B to page up/down.
150+
Rendered sample:
159151

160-
[A more specific plugin](https://github.com/zhongwencool/os_stats) can collect linux system information such as kernel vsn, loadavg, disk, memory usage, cpu utilization, IO statistics.
152+
```
153+
|No |Pid |Register |Memory(S) |Reductions(R) |Message Queue Len(Q) |
154+
|1 |<0.242.0> | |4.5020 MB | 26544288 | 0 |
155+
|2 |<0.206.0> | |1.2824 MB | 13357885 | 0 |
156+
|3 |<0.10.0> |erl_prim_loader |1.0634 MB | 10046775 | 0 |
157+
...
158+
|refresh: 1600ms q(quit) Positive Number(set refresh interval time ms) F/B(forward/back) Current page is 1 |
159+
```
161160

162-
3. Handler: specific per-item behavior
161+
### Formatting helpers
163162

164-
By default, once you select a row, it will show the process information in `observer_cli_process` view. This is done
165-
by looking for a `pid` in the row, so the first one found will be used and passed to the `observer_cli_process` view.
163+
- `{byte, Value}` automatically renders human-readable byte units.
164+
- `{percent, Value}` outputs a percentage with two decimals.
165+
- `color` can be any ANSI color escape (e.g., `?RED_BG`) to highlight critical cells.
166166

167-
To customize this behavior, you can implement your own handler. The handler is a tuple with a function and a module.
168-
The function is a predicate that will be used to filter all row's items and, if the resulting list in not empty, the
169-
`Handler:start/3` function will be called. The signature is the same of `observer_cli_process:start/3`.
167+
## 3. Custom handlers
170168

171-
The new configuration will look like this:
169+
By default, selecting a row in your plugin opens the standard `observer_cli_process` view for the first `pid` found in that row. To override this, add a `handler` tuple to the plugin definition. The predicate receives every element in the row and should return true for the items you need. When the predicate matches, `HandlerModule:start/3` is invoked with the same contract as `observer_cli_process:start/3`.
172170

173171
```erlang
174-
%% module - Specific module implements plugin behavior. It's mandatory.
175-
%% title - Menu title. It's mandatory.
176-
%% shortcut - Switch plugin by shortcut. It's mandatory.
177-
%% interval - Refresh interval ms. It's optional. default is 1500ms.
178-
%% sort_column - Sort the sheet by this index. It's optional default is 2.
179-
%% handler - Specific handler implements per-item behavior. It's optional, default is `{fun is_pid/1,observer_cli_process}`.`
180-
181172
{plugins,
182-
[
183-
#{module => observer_cli_plug_behaviour_x, title => "XPlug",
184-
interval => 1600, shortcut => "X", sort_column => 3,
185-
handler => {fun is_pid/1, observer_cli_plug_item_behaviour_x}},
186-
#{module => observer_cli_plug_behaviour_y, title => "YPlug",
187-
interval =>2000, shortcut => "Y", sort_column => 3,
188-
handler => {fun is_binary/1, observer_cli_plug_item_behaviour_y}},
189-
]
190-
}
173+
[
174+
#{module => observer_cli_plug_behaviour_x,
175+
title => "XPlug",
176+
shortcut => "X",
177+
interval => 1600,
178+
sort_column => 3,
179+
handler => {fun is_pid/1, observer_cli_plug_item_behaviour_x}},
180+
#{module => observer_cli_plug_behaviour_y,
181+
title => "YPlug",
182+
shortcut => "Y",
183+
interval => 2000,
184+
sort_column => 3,
185+
handler => {fun is_binary/1, observer_cli_plug_item_behaviour_y}}
186+
]}.
191187
```
188+
189+
Use this when a row selection should drill into a custom detail view (for example, ETS metadata or OS metrics).
190+
191+
## 4. Example plugin
192+
193+
[`os_stats`](https://github.com/zhongwencool/os_stats) shows a complete implementation that surfaces Linux kernel information, load averages, disk usage, memory, CPU, and IO statistics via the same behaviour. Use it as inspiration for structuring larger dashboards.

mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ defmodule ObserverCli.MixProject do
44
def project do
55
[
66
app: :observer_cli,
7-
version: "1.8.4",
7+
version: "1.8.5",
88
language: :erlang,
99
description: "observer in shell",
1010
deps: [

0 commit comments

Comments
 (0)