Revamp Explorer section for Kino (#879)

Pong and custom Kinos chapters are still pending.
This commit is contained in:
José Valim 2022-01-17 17:14:25 +01:00 committed by GitHub
parent fe0bf660a2
commit 4d79706c01
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 405 additions and 273 deletions

View file

@ -86,24 +86,25 @@ defmodule Livebook.Notebook.Explore do
# cover_url: "/images/axon.png"
# }
# },
%{
path: Path.join(__DIR__, "explore/vm_introspection.livemd"),
details: %{
description: "Extract and visualize information about a remote running node.",
cover_url: "/images/vm_introspection.png"
}
},
%{
ref: :kino_intro,
path: Path.join(__DIR__, "explore/kino/intro_to_kino.livemd")
},
%{
ref: :kino_vm_introspection,
path: Path.join(__DIR__, "explore/kino/vm_introspection.livemd")
},
%{
ref: :kino_chat_app,
path: Path.join(__DIR__, "explore/kino/chat_app.livemd")
},
%{
ref: :kino_pong,
path: Path.join(__DIR__, "explore/kino/pong.livemd")
},
%{
ref: :kino_custom_widgets,
path: Path.join(__DIR__, "explore/kino/creating_custom_widgets.livemd")
ref: :kino_custom_kinos,
path: Path.join(__DIR__, "explore/kino/custom_kinos.livemd")
}
]
@ -202,9 +203,15 @@ defmodule Livebook.Notebook.Explore do
%{
title: "Interactions with Kino",
description:
"Kino is an Elixir package that allows for displaying and controlling rich, interactieve widgets in Livebook. Learn how to make your notebooks more engaging with inputs, plots, tables, and much more!",
"Kino is an Elixir package for displaying and controlling rich, interactive widgets in Livebook. Learn how to make your notebooks more engaging with inputs, plots, tables, and much more!",
cover_url: "/images/kino.png",
notebook_refs: [:kino_intro, :kino_pong, :kino_custom_widgets]
notebook_refs: [
:kino_intro,
:kino_vm_introspection,
:kino_chat_app,
:kino_pong,
:kino_custom_kinos
]
}
]

View file

@ -194,35 +194,11 @@ Date.from_iso8601("2020-02-30")
Now, what happens if we want our code to behave differently depending
if the date is valid or not? We can use `case` to pattern match on
the different tuples. This is also a good opportunity to use Livebook's
inputs to pass different values to our code. To render inputs, we need to
install the [Kino](https://github.com/livebook-dev/kino) library:
the different tuples:
```elixir
Mix.install(
[
{:kino, github: "livebook-dev/kino"}
],
consolidate_protocols: false
)
```
> Note: the `consolidate_protocols: false` option is not usually
> given, but it will be handy when we discuss protocols later
> in this notebook.
Kino allows our code notebooks to control Livebook itself. Let's render
an input by evaluating the cell below:
```elixir
date_input = Kino.Input.text("Date")
```
Now we can read its value and parse it:
```elixir
# Read the date input, which returns something like "2020-02-30"
input = Kino.Input.read(date_input)
# Give a random date as input
input = "2020-02-30"
# And then match on the return value
case Date.from_iso8601(input) do
@ -234,12 +210,11 @@ case Date.from_iso8601(input) do
end
```
Now try adding a date to the input above, such as `2020-02-30` and
reevaluate the cell accordingly. In this example, we are using `case`
to pattern match on the different outcomes of the `Date.from_iso8601`
function. We say the `case` above has two clauses, one matching on
`{:ok, date}` and another on `{:error, reason}`. Try changing the input
and re-executing the cell to see how the outcome changes.
In this example, we are using `case` to pattern match on the different
outcomes of the `Date.from_iso8601`. We say the `case` above has two
clauses, one matching on `{:ok, date}` and another on `{:error, reason}`.
Now try changing the `input` variable above and reevaluate the cell
accordingly. What happens when you give it an invalid date?
Finally, we can also pattern match on maps. This is used to extract the
values for the given keys:

View file

@ -9,9 +9,11 @@ and more.
If you are not familiar with Elixir, there is a fast paced
introduction to the language in the [Distributed portals with
Elixir](/explore/notebooks/distributed-portals-with-elixir)
notebook.
notebook. For a more structured introduction to the language,
see [Elixir's Getting Started guide](https://elixir-lang.org/getting-started/introduction.html)
and [the many learning resources available](https://elixir-lang.org/learning.html).
Let's move on.
Let's move forward.
## Autocompletion
@ -70,10 +72,10 @@ data = [
Kino.DataTable.new(data)
```
See the [Interactions with Kino](/explore/notebooks/intro-to-kino) notebook
to learn all the ways you can interact with Livebook from Kino.
There is much more to `Kino` and we have [a series of Kino guides
in the Explore section to teach you more](/explore).
It is a good idea to specify versions of the installed packages,
Note that it is a good idea to specify versions of the installed packages,
so that the notebook is easily reproducible later on. The install
command goes beyond simply installing dependencies, it also caches
them, consolidates protocols, and more. Check

View file

@ -86,29 +86,18 @@ Process.sleep(300_000)
Having this cell running, feel free to insert another Elixir cell
in the section below and see it evaluates immediately.
## Notebook files
## Saving notebooks
By default notebooks are kept in memory, which is fine for interactive hacking,
but oftentimes you will want to save your work for later. Fortunately, notebooks
can be persisted by clicking on the "Disk" icon (<i class="ri-livebook-save"></i>)
in the bottom-right corner and selecting the file location.
Notebooks are stored in **live markdown** format, which is essentially the markdown you know,
Notebooks are stored in **live markdown** format, which is the Markdown you know,
with just a few assumptions on how particular elements are represented. Thanks to this
approach you can easily keep notebooks under version control and get readable diffs.
You can also easily preview those files, reuse for blog posts, and even edit in a text editor.
## Math
Livebook uses $\TeX$ syntax for math.
It supports both inline math like $e^{\pi i} + 1 = 0$, as well as display math:
$$
S(x) = \frac{1}{1 + e^{-x}} = \frac{e^{x}}{e^{x} + 1}
$$
You can explore all supported expressions [here](https://katex.org/docs/supported.html).
## Stepping up your workflow
Once you start using notebooks more, it's gonna be beneficial
@ -118,6 +107,35 @@ Make sure to check out the shortcuts by clicking the "Keyboard" icon
(<i class="ri-livebook-shortcuts"></i>) in the sidebar or
by pressing <kbd>?</kbd>.
## Markdown extensions
Livebook also include supports for Math expressions and Mermaid diagrams.
### Math expressions
Livebook uses $\TeX$ syntax for math inside your Markdown cells.
It supports both inline math, like $e^{\pi i} + 1 = 0$, as well as display math:
$$
S(x) = \frac{1}{1 + e^{-x}} = \frac{e^{x}}{e^{x} + 1}
$$
You can explore all supported expressions [here](https://katex.org/docs/supported.html).
### Mermaid diagrams
[Mermaid](https://mermaid-js.github.io/) is a library for creating diagrams
and visualizations using text and code. You can define those diagrams in
your Markdown cells via ```` ```mermaid ```` blocks. Let's see an example:
```mermaid
graph TD;
A-->B;
A-->C;
B-->D;
C-->D;
```
## Next steps
That's our quick intro to Livebook! Where to go next?

View file

@ -2,11 +2,15 @@
## Setup
To render graphs in Livebook, we need the
[`vega_lite`](https://github.com/elixir-nx/vega_lite) package
for defining our graph specification and
[`kino`](https://github.com/elixir-nx/kino). We won't use Kino
directly, but it is required to render VegaLite:
We need two libraries for plotting in Livebook:
* The [`vega_lite`](https://github.com/elixir-nx/vega_lite)
package allows us to define our graph specifications
* The [`kino`](https://github.com/elixir-nx/kino) package
renders our specifications
Let's install them:
```elixir
Mix.install([

View file

@ -0,0 +1,153 @@
# Building a chat app with Kino.Control
## Setup
In this guide, we will build a chat application using
[`kino`](https://github.com/livebook-dev/kino). Let's
install it and get started:
```elixir
Mix.install([
{:kino, github: "livebook-dev/kino"}
])
```
## Kino.Control
In our [introduction to Kino](/explore/notebooks/intro_to_kino),
we learned about inputs and several different outputs, such as
tables, frames, and more. In particular, we learned how to use
inputs to capture values directly into our notebooks:
```elixir
name = Kino.Input.text("Your name")
```
and use them to print something back:
```elixir
IO.puts("Hello, #{Kino.Input.read(name)}!")
```
Inputs have one special power: they are shared across all users
accessing the notebook. For example, if you copy and paste the
URL of this notebook into another tab, the input will share the
same value. This plays into Livebook's strengths of being an
interactive and collaborative tool.
Sometimes, however, you don't want the input to be shared.
You want each different user to get their own inputs and perform
individual actions. That's exactly how
[`Kino.Control`](https://hexdocs.pm/kino/Kino.Control.html) works.
Each control is specific to each user on the page. You then receive
each user interaction as a message.
## The button control
The simplest control is `Kino.Control.button/1`. Let's give it a try:
```elixir
click_me = Kino.Control.button("Click me!")
```
Execute the cell above and the button will be rendered. You can click
it, but nothing will happen. Luckily, we can subscribe to the button
events:
```elixir
Kino.Control.subscribe(click_me, :click_me)
```
Now that we have subscribed, every time the button is clicked, we will
receive a message tagged with `:click_me`. Let's print all messages
in our inbox:
```elixir
Process.info(self(), :messages)
```
Now execute the cell above, click the button a couple times, and
re-execute the cell above. For each click, there is a new message in
our inbox. There are several ways we can consume this message.
Let's see a different one in the next example.
<!-- livebook:{"branch_parent_index":0} -->
## The form control
Whenever we want to submit multiple inputs at once, we can use
`Kino.Control.form/2`.
```elixir
inputs = [
first_name: Kino.Input.text("First name"),
last_name: Kino.Input.text("Last name")
]
form = Kino.Control.form(inputs, submit: "Greet")
```
Execute the cell above and you will see a form rendered.
You can now fill in the form and press the submit button.
Each submission will trigger a new event. Let's consume
them as a stream. Elixir streams are lazy collections that
are consumed as they happen:
```elixir
for event <- Kino.Control.stream(form) do
IO.inspect(event)
end
```
Now, as you submit the form, you should see a new event
printed. However, there is a downside: we are now stuck
inside this infinite loop of events. Luckily, we started
this particular section as a branched section, which means
the main execution flow will not be interrupted. But it
is something you should keep in mind in the future. You
can also stop it by pressing the "Stop" button above the
Elixir cell.
<!-- livebook:{"branch_parent_index":0} -->
## The chat application
We are now equipped with all knowledge necessary to build
our chat application. First, we will need a frame. Every
time a new message is received, we will append it to the
frame:
```elixir
frame = Kino.Frame.new()
```
Now we need a form with the user name and their message:
```elixir
inputs = [
name: Kino.Input.text("Name"),
message: Kino.Input.text("Message")
]
form = Kino.Control.form(inputs, submit: "Send", reset_on_submit: [:message])
```
Notice we used a new option, called `:reset_on_submit`,
that automatically clears the input once submitted.
Finally, let's stream the form events and post each
message to the frame:
```elixir
for %{data: %{name: name, message: message}} <- Kino.Control.stream(form) do
content = Kino.Markdown.new("**#{name}**: #{message}")
Kino.Frame.append(frame, content)
end
```
Execute the cell above and your chat app should be
fully operational. Open up this same notebook across
on different tabs and each different user can post
their messages.
In the next guide we will go one step further and
[develop a multiplayer pong game](/explore/notebooks/pong)!

View file

@ -1,9 +1,9 @@
# Creating custom widgets
# Custom kinos with JavaScript
## Introduction
The `Kino.JS` and `Kino.JS.Live` docs outline the API that enables
developing custom JavaScript powered widgets. The examples discussed
developing custom JavaScript powered kinos. The examples discussed
there are kept minimal to introduce the basic concepts without much
overhead. In this notebook we take things a bit further and showcase
a couple more elaborate use cases.
@ -14,7 +14,7 @@ Mix.install([
])
```
## Diagrams with Mermaid
## HTML rendering
As a quick recap let's define a widget for rendering diagrams
from text specification using [Mermaid](https://mermaid-js.github.io/mermaid/#/).

View file

@ -6,92 +6,118 @@ In this notebook we will explore the possibilities that
[`kino`](https://github.com/elixir-nx/kino) brings
into your notebooks. Kino can be thought of as Livebook's
friend that instructs it how to render certain widgets
and interact with them.
and interact with them. Let's install it:
```elixir
Mix.install([
{:kino, github: "livebook-dev/kino"},
{:vega_lite, "~> 0.1.2"}
{:kino, github: "livebook-dev/kino"}
])
```
```elixir
alias VegaLite, as: Vl
```
<!-- livebook:{"branch_parent_index":0} -->
## Kino.VegaLite
## Kino.Input
In the [Plotting with VegaLite](/explore/notebooks/intro-to-vega-lite) notebook we show
numerous ways in which you can visualize your data. However, all of the plots
there are static.
Using Kino, we can dynamically stream data to the plot, so that it keeps updating!
To do that, all you need is a regular VegaLite specification that you then pass
to `Kino.VegaLite.new/1`. You don't have to specify any data up-front.
The [`Kino.Input`](https://hexdocs.pm/kino/Kino.Input.html)
module contains the most common kinos you will use. They are
used to define inputs in one cell, which you can read in a
future cell:
```elixir
widget =
Vl.new(width: 400, height: 400)
|> Vl.mark(:line)
|> Vl.encode_field(:x, "x", type: :quantitative)
|> Vl.encode_field(:y, "y", type: :quantitative)
|> Kino.VegaLite.new()
name = Kino.Input.text("Your name")
```
Then you can push data to the plot widget at any point and see it update dynamically:
and now we can greet the user back:
```elixir
for i <- 1..300 do
point = %{x: i / 10, y: :math.sin(i / 10)}
# The :window option ensures we only show the latest
# 100 data points on the plot
Kino.VegaLite.push(widget, point, window: 100)
Process.sleep(25)
end
IO.puts("Hello, #{Kino.Input.read(name)}!")
```
You can also explicitly clear the data:
There are multiple types of inputs, such as text areas,
color dialogs, selects, and more. Feel free to explore them.
<!-- livebook:{"branch_parent_index":0} -->
## Kino.Markdown
Given our notebooks already know how to render Markdown,
you won't be surprised to find we can also render Markdown
directly from our Elixir cells. This is done by wrapping
the Markdown contents in [`Kino.Markdown.new/1`](https://hexdocs.pm/kino/Kino.Markdown.html):
````elixir
Kino.Markdown.new("""
# Example
A regular Markdown file.
## Code
```elixir
Kino.VegaLite.clear(widget)
"Elixir" |> String.graphemes() |> Enum.frequencies()
```
### Periodical updates
## Table
You may want to have a plot running forever and updating in the background.
There is a dedicated `Kino.VegaLite.periodically/4` function that allows you do do just that!
You just need to specify the interval and the reducer callback like this,
then you interact with the widget as usually.
| ID | Name | Website |
| -- | ------ | ----------------------- |
| 1 | Elixir | https://elixir-lang.org |
| 2 | Erlang | https://www.erlang.org |
""")
````
The way it works is that Livebook automatically detects
the output is a kino and renders it in Markdown. That's
the first of many kinos we will learn today. Let's move
forward.
<!-- livebook:{"branch_parent_index":0} -->
## Kino.DataTable
You can render arbitrary tabular data using [`Kino.DataTable.new/1`](https://hexdocs.pm/kino/Kino.DataTable.html), let's have a look:
```elixir
widget =
Vl.new(width: 400, height: 400)
|> Vl.mark(:line)
|> Vl.encode_field(:x, "x", type: :quantitative)
|> Vl.encode_field(:y, "y", type: :quantitative)
|> Kino.VegaLite.new()
|> Kino.render()
data = [
%{id: 1, name: "Elixir", website: "https://elixir-lang.org"},
%{id: 2, name: "Erlang", website: "https://www.erlang.org"}
]
# Add a callback to run every 25ms
Kino.VegaLite.periodically(widget, 25, 0, fn i ->
point = %{x: i / 10, y: :math.sin(i / 10)}
# Interacting with the widget is as usual
Kino.VegaLite.push(widget, point, window: 100)
# Continue with the new accumulator value
{:cont, i + 1}
end)
Kino.DataTable.new(data)
```
The data must be an enumerable, with records being maps or
keyword lists.
Now, let's get some more realistic data. Whenever you run
Elixir code, you have several lightweight processes running
side-by-side. We can actually gather information about these
processes and render it as a table:
```elixir
processes = Process.list() |> Enum.map(&Process.info/1)
```
We can pick the data keys that are relevant for us:
```elixir
Kino.DataTable.new(
processes,
keys: [:registered_name, :initial_call, :reductions, :stack_size]
)
```
Now you can use the table above to sort by the number of
reductions and identify the most busy processes!
<!-- livebook:{"branch_parent_index":0} -->
## Kino.ETS
You can use `Kino.ETS.new/1` to render ETS tables and easily
browse their contents. Let's first create our own table:
Kino supports multiple other data structures to be rendered
as tables. For example, you can use [`Kino.ETS`](https://hexdocs.pm/kino/Kino.ETS.html)
to render ETS tables and easily browse their contents.
Let's first create our own table:
```elixir
tid = :ets.new(:users, [:set, :public])
@ -116,73 +142,7 @@ end
Having the rows inserted, click on the "Refetch" icon in the table output
above to see them.
<!-- livebook:{"branch_parent_index":0} -->
## Kino.DataTable
When it comes to tables, we are not limited to ETS! You can render
arbitrary tabular data using `Kino.DataTable.new/1`, let's have
a look:
```elixir
data = [
%{id: 1, name: "Elixir", website: "https://elixir-lang.org"},
%{id: 2, name: "Erlang", website: "https://www.erlang.org"}
]
Kino.DataTable.new(data)
```
The data must be an enumerable, with records being maps,
keyword lists or tuples.
Now, let's get some more realistic data:
```elixir
processes = Process.list() |> Enum.map(&Process.info/1)
```
We can easily pick only the data keys that are relevant
for us:
```elixir
Kino.DataTable.new(
processes,
keys: [:registered_name, :initial_call, :reductions, :stack_size]
)
```
We can sort by the number of reductions to identify the
most busy processes!
<!-- livebook:{"branch_parent_index":0} -->
## Kino.Markdown
Sometimes you may want to render arbitrary content as rich-text,
that's when `Kino.Markdown.new/1` comes into play:
````elixir
"""
# Example
A regular Markdown file.
## Code
```elixir
"Elixir" |> String.graphemes() |> Enum.frequencies()
```
## Table
| ID | Name | Website |
| -- | ------ | ----------------------- |
| 1 | Elixir | https://elixir-lang.org |
| 2 | Erlang | https://www.erlang.org |
"""
|> Kino.Markdown.new()
````
Similar functionality is available for database queries via [Ecto](https://github.com/elixir-ecto/ecto) and the [`Kino.Ecto`](https://hexdocs.pm/kino/Kino.Ecto.html) module.
<!-- livebook:{"branch_parent_index":0} -->
@ -191,56 +151,53 @@ A regular Markdown file.
As we saw, Livebook automatically recognises widgets returned
from each cell and renders them accordingly. However, sometimes
it's useful to explicitly render a widget in the middle of the cell,
similarly to `IO.puts/1` and that's exactly what `Kino.render/1`
similarly to `IO.puts/1`, and that's exactly what `Kino.render/1`
does! It works with any type and tells Livebook to render the value
in its special manner.
```elixir
# Arbitrary data structures
Kino.render([%{name: "Ada Lovelace"}, %{name: "Alan Turing"}])
# Static plots
vl =
Vl.new(width: 400, height: 400)
|> Vl.data_from_series(x: 1..100, y: 1..100)
|> Vl.mark(:line)
|> Vl.encode_field(:x, "x", type: :quantitative)
|> Vl.encode_field(:y, "y", type: :quantitative)
Kino.render(vl)
Kino.render(vl)
Kino.render("Plain text")
# Some kinos
Kino.render(Kino.Markdown.new("**Hello world**"))
"Cell result 🚀"
```
Before we saw how you can render and stream data to the plot
from a separate cell, the same could be rewritten in one go
like this:
```elixir
widget =
Vl.new(width: 400, height: 400)
|> Vl.mark(:line)
|> Vl.encode_field(:x, "x", type: :quantitative)
|> Vl.encode_field(:y, "y", type: :quantitative)
|> Kino.VegaLite.new()
|> Kino.render()
for i <- 1..300 do
point = %{x: i / 10, y: :math.sin(i / 10)}
Kino.VegaLite.push(widget, point, window: 100)
Process.sleep(25)
end
```
<!-- livebook:{"branch_parent_index":0} -->
## Kino.animate/3
## Kino.Frame and animations
If you want to continuously update the output as time passes,
you can use `Kino.animate/3`:
`Kino.Frame` allows us to render an empty frame and update it
as we progress. Let's render an empty frame:
```elixir
frame = Kino.Frame.new()
```
Now, let's render a random number between 1 and 100 directly
in the frame:
```elixir
Kino.Frame.render(frame, "Got: #{Enum.random(1..100)}")
```
Notice how every time you reevaluate the cell above it updates
the frame. You can also use `Kino.Frame.append/2` to append to
the frame:
```elixir
Kino.Frame.append(frame, "Got: #{Enum.random(1..100)}")
```
Appending multiple times will always add new contents. The content
can be reset by calling `Kino.Frame.render/2` or `Kino.Frame.clear/1`.
By using loops, you can use `Kino.Frame` to dynamically add contents
or animate your livebooks. In fact, there is a convenience function
called `Kino.animate/3` to be used exactly for this purpose:
```elixir
Kino.animate(100, 0, fn i ->
@ -251,6 +208,9 @@ end)
The above example renders new Markdown output every 100ms.
You can use the same approach to render regular output
or images too! Also note some elements may have specific
functions for periodic updates, such as `Kino.VegaLite.periodically/4`
seen in previous sections.
or images too!
With this, we finished our introduction to Kino. Now we are
ready to bring two concepts we have already learned together:
`Kino` and `VegaLite`. [Let's use them to introspect the Elixir
runtime your livebooks run on](/explore/notebooks/vm-introspection).

View file

@ -1,4 +1,4 @@
# Building multiplayer Pong
# Multiplayer pong game from scratch
## Introduction

View file

@ -1,15 +1,15 @@
# Fun with VM introspection
# Runtime introspection with VegaLite
## Introduction
In this notebook we manually establish connection to a running node,
and then we try to retrieve and plot some interesting information
about the system.
In this chapter we will use `Kino` and `VegaLite`
to introspect and plot how our system behaves over
time. If you are not familiar with VegaLite, [read
our introductory chapter](/explore/notebooks/intro-to-vega-lite).
## Setup
We are definitely gonna plot some data in this notebook,
so let's add `:vega_lite` and `:kino` for that.
Let's add `:vega_lite` and `:kino` as dependencies:
```elixir
Mix.install([
@ -18,46 +18,53 @@ Mix.install([
])
```
Let's also define a convenience shortcut for the
VegaLite module:
```elixir
alias VegaLite, as: Vl
```
## Connecting to a remote node
The first thing we need is a separate Elixir node. In practice,
you would start an external Elixir system, such as by running the
following in your production app:
Our goal is to introspect an Elixir node. The code we will
write in this notebook can be used to introspect any running
Elixir node. It can be a development environment that you would
start with:
```
iex --name my_app@IP -S mix TASK
```
Or by connecting to a production node assembled via
Or a production node assembled via
[`mix release`](https://hexdocs.pm/mix/Mix.Tasks.Release.html).
For convenience, however, you can simply start [a new notebook](/explore/notebooks/new),
since Livebook automatically starts each notebook as a remote node.
Once you start a new notebook, you can find its node name and
cookie by running the following inside an Elixir cell:
<!-- livebook:{"force_markdown":true} -->
In order to connect two nodes, we need to know their node name
and their cookie. We can get this information for the Livebook
runtime like this:
```elixir
IO.puts node()
IO.puts Node.get_cookie()
```
Now render the inputs below:
We will capture this information using Kino inputs. However,
for convenience, we will use the node and cookie of the current
notebook as default values. This means that, if you don't have
a separate Elixir, the runtime will connect and introspect itself.
Let's render the inputs:
```elixir
node_input = Kino.Input.text("Node")
cookie_input = Kino.Input.text("Cookie")
node_input = Kino.Input.text("Node", default: node())
cookie_input = Kino.Input.text("Cookie", default: Node.get_cookie())
Kino.render(node_input)
Kino.render(cookie_input)
:ok
```
And paste the node name and the cookie value from the other node inside.
Now let's read the inputs, configure the cookie, and connect to the other notebook:
Now let's read the inputs, configure the cookie, and connect to the
other node:
```elixir
node =
@ -83,9 +90,6 @@ Node.spawn(node, fn ->
end)
```
From the result of `node/1` it's clear that the function was evaluated
remotely, but note that we still get the standard output back.
## Inspecting processes
Now we are going to extract some information from the running node on our own!
@ -127,7 +131,8 @@ processes =
end)
```
Having all that data, we can now visualize it on a scatter plot!
Having all that data, we can now visualize it on a scatter plot
using VegaLite:
```elixir
Vl.new(width: 600, height: 400)
@ -144,17 +149,22 @@ and take the most memory.
## Tracking memory usage
So far we have used VegaLite to draw static plots. However, we can
Kino to dynamically push data to VegaLite. Let's use them together
to plot the runtime memory usage over time.
There's a very simple way to determine current memory usage in the VM:
```elixir
:erlang.memory()
```
We can use `Kino.VegaLite.periodically/4` to create a self-updating
plot of memory usage over time on the remote node!
Now let's build a dynamic VegaLite graph. Instead of returning the
VegaLite specification as is, we will wrap it in `Kino.VegaLite.new/1`
to make it dynamic:
```elixir
widget =
memory_plot =
Vl.new(width: 600, height: 400, padding: 20)
|> Vl.repeat(
[layer: ["total", "processes", "atom", "binary", "code", "ets"]],
@ -165,38 +175,38 @@ widget =
|> Vl.encode(:color, datum: [repeat: :layer], type: :nominal)
)
|> Kino.VegaLite.new()
|> Kino.render()
```
Kino.VegaLite.periodically(widget, 200, 1, fn i ->
Now we can use `Kino.VegaLite.periodically/4` to create a self-updating
plot of memory usage over time on the remote node:
```elixir
Kino.VegaLite.periodically(memory_plot, 200, 1, fn i ->
point =
:rpc.call(node, :erlang, :memory, [])
|> Enum.map(fn {type, bytes} -> {type, bytes / 1_000_000} end)
|> Map.new()
|> Map.put(:iter, i)
Kino.VegaLite.push(widget, point, window: 1000)
Kino.VegaLite.push(memory_plot, point, window: 1000)
{:cont, i + 1}
end)
```
Unless you connected to a production node, the memory usage
most likely doesn't change, so to emulate some spikes you can
run the following code in the remote node:
run the following code:
**Binary usage**
<!-- livebook:{"force_markdown":true} -->
```elixir
x = Enum.reduce(1..10_000, [], fn i, acc ->
[String.duplicate("cat", i) | acc]
end)
for i <- 1..10_000 do
String.duplicate("cat", i)
end
```
**ETS usage**
<!-- livebook:{"force_markdown":true} -->
```elixir
tid = :ets.new(:users, [:set, :public])
@ -204,3 +214,6 @@ for i <- 1..1_000_000 do
:ets.insert(tid, {i, "User #{i}"})
end
```
In the next chapter, we will learn [how to use `Kino.Control`
to build a chat app](/explore/notebooks/chat-app)!

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB