elonmuskceo commited on
Commit
577d5c4
·
1 Parent(s): 6fd80fb

Upload 3 files

Browse files
Files changed (3) hide show
  1. Dockerfile +22 -0
  2. app.py +232 -0
  3. requirements.txt +8 -0
Dockerfile ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM ubuntu:kinetic
2
+
3
+ # Doesn't usually have an "upgrade"
4
+ RUN apt-get update \
5
+ && DEBIAN_FRONTEND=noninteractive \
6
+ apt-get install --no-install-recommends --assume-yes \
7
+ build-essential \
8
+ python3 \
9
+ python3-dev \
10
+ python3-pip
11
+
12
+ COPY requirements.txt .
13
+
14
+ RUN pip install -r requirements.txt
15
+
16
+ COPY . .
17
+
18
+ ENTRYPOINT ["/bin/sh", "-c"]
19
+
20
+ EXPOSE 7860
21
+
22
+ CMD ["shiny run --port 7860 --host 0.0.0.0 app.py"]
app.py ADDED
@@ -0,0 +1,232 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+
3
+ from psutil import cpu_count, cpu_percent
4
+
5
+ from math import ceil
6
+
7
+ import matplotlib
8
+ import matplotlib.pyplot as plt
9
+ import numpy as np
10
+ import pandas as pd
11
+ from shiny import App, Inputs, Outputs, Session, reactive, render, ui
12
+
13
+ shinylive_message = ""
14
+
15
+ # The agg matplotlib backend seems to be a little more efficient than the default when
16
+ # running on macOS, and also gives more consistent results across operating systems
17
+ matplotlib.use("agg")
18
+
19
+ # max number of samples to retain
20
+ MAX_SAMPLES = 1000
21
+ # secs between samples
22
+ SAMPLE_PERIOD = 1
23
+
24
+
25
+ ncpu = cpu_count(logical=True)
26
+
27
+ app_ui = ui.page_fluid(
28
+ ui.tags.style(
29
+ """
30
+ /* Don't apply fade effect, it's constantly recalculating */
31
+ .recalculating {
32
+ opacity: 1;
33
+ }
34
+ tbody > tr:last-child {
35
+ /*border: 3px solid var(--bs-dark);*/
36
+ box-shadow:
37
+ 0 0 2px 1px #fff, /* inner white */
38
+ 0 0 4px 2px #0ff, /* middle cyan */
39
+ 0 0 5px 3px #00f; /* outer blue */
40
+ }
41
+ #table table {
42
+ table-layout: fixed;
43
+ width: %s;
44
+ font-size: 0.8em;
45
+ }
46
+ th, td {
47
+ text-align: center;
48
+ }
49
+ """
50
+ % f"{ncpu*4}em"
51
+ ),
52
+ ui.h3("CPU Usage %", class_="mt-2"),
53
+ ui.layout_sidebar(
54
+ ui.panel_sidebar(
55
+ ui.input_select(
56
+ "cmap",
57
+ "Colormap",
58
+ {
59
+ "inferno": "inferno",
60
+ "viridis": "viridis",
61
+ "copper": "copper",
62
+ "prism": "prism (not recommended)",
63
+ },
64
+ ),
65
+ ui.p(ui.input_action_button("reset", "Clear history", class_="btn-sm")),
66
+ ui.input_switch("hold", "Freeze output", value=False),
67
+ shinylive_message,
68
+ class_="mb-3",
69
+ ),
70
+ ui.panel_main(
71
+ ui.div(
72
+ {"class": "card mb-3"},
73
+ ui.div(
74
+ {"class": "card-body"},
75
+ ui.h5({"class": "card-title mt-0"}, "Graphs"),
76
+ ui.output_plot("plot", height=f"{ncpu * 40}px"),
77
+ ),
78
+ ui.div(
79
+ {"class": "card-footer"},
80
+ ui.input_numeric("sample_count", "Number of samples per graph", 50),
81
+ ),
82
+ ),
83
+ ui.div(
84
+ {"class": "card"},
85
+ ui.div(
86
+ {"class": "card-body"},
87
+ ui.h5({"class": "card-title m-0"}, "Heatmap"),
88
+ ),
89
+ ui.div(
90
+ {"class": "card-body overflow-auto pt-0"},
91
+ ui.output_table("table"),
92
+ ),
93
+ ui.div(
94
+ {"class": "card-footer"},
95
+ ui.input_numeric("table_rows", "Rows to display", 5),
96
+ ),
97
+ ),
98
+ ),
99
+ ),
100
+ )
101
+
102
+
103
+ @reactive.Calc
104
+ def cpu_current():
105
+ reactive.invalidate_later(SAMPLE_PERIOD)
106
+ return cpu_percent(percpu=True)
107
+
108
+
109
+ def server(input: Inputs, output: Outputs, session: Session):
110
+ cpu_history = reactive.Value(None)
111
+
112
+ @reactive.Calc
113
+ def cpu_history_with_hold():
114
+ # If "hold" is on, grab an isolated snapshot of cpu_history; if not, then do a
115
+ # regular read
116
+ if not input.hold():
117
+ return cpu_history()
118
+ else:
119
+ # Even if frozen, we still want to respond to input.reset()
120
+ input.reset()
121
+ with reactive.isolate():
122
+ return cpu_history()
123
+
124
+ @reactive.Effect
125
+ def collect_cpu_samples():
126
+ """cpu_percent() reports just the current CPU usage sample; this Effect gathers
127
+ them up and stores them in the cpu_history reactive value, in a numpy 2D array
128
+ (rows are CPUs, columns are time)."""
129
+
130
+ new_data = np.vstack(cpu_current())
131
+ with reactive.isolate():
132
+ if cpu_history() is None:
133
+ cpu_history.set(new_data)
134
+ else:
135
+ combined_data = np.hstack([cpu_history(), new_data])
136
+ # Throw away extra data so we don't consume unbounded amounts of memory
137
+ if combined_data.shape[1] > MAX_SAMPLES:
138
+ combined_data = combined_data[:, -MAX_SAMPLES:]
139
+ cpu_history.set(combined_data)
140
+
141
+ @reactive.Effect(priority=100)
142
+ @reactive.event(input.reset)
143
+ def reset_history():
144
+ cpu_history.set(None)
145
+
146
+ @output
147
+ @render.plot
148
+ def plot():
149
+ history = cpu_history_with_hold()
150
+
151
+ if history is None:
152
+ history = np.array([])
153
+ history.shape = (ncpu, 0)
154
+
155
+ nsamples = input.sample_count()
156
+
157
+ # Throw away samples too old to fit on the plot
158
+ if history.shape[1] > nsamples:
159
+ history = history[:, -nsamples:]
160
+
161
+ ncols = 2
162
+ nrows = int(ceil(ncpu / ncols))
163
+ fig, axeses = plt.subplots(
164
+ nrows=nrows,
165
+ ncols=ncols,
166
+ squeeze=False,
167
+ )
168
+ for i in range(0, ncols * nrows):
169
+ row = i // ncols
170
+ col = i % ncols
171
+ axes = axeses[row, col]
172
+ if i >= len(history):
173
+ axes.set_visible(False)
174
+ continue
175
+ data = history[i]
176
+ axes.yaxis.set_label_position("right")
177
+ axes.yaxis.tick_right()
178
+ axes.set_xlim(-(nsamples - 1), 0)
179
+ axes.set_ylim(0, 100)
180
+
181
+ assert len(data) <= nsamples
182
+
183
+ # Set up an array of x-values that will right-align the data relative to the
184
+ # plotting area
185
+ x = np.arange(0, len(data))
186
+ x = np.flip(-x)
187
+
188
+ # Color bars by cmap
189
+ color = plt.get_cmap(input.cmap())(data / 100)
190
+ axes.bar(x, data, color=color, linewidth=0, width=1.0)
191
+
192
+ axes.set_yticks([25, 50, 75])
193
+ for ytl in axes.get_yticklabels():
194
+ if col == ncols - 1 or i == ncpu - 1 or True:
195
+ ytl.set_fontsize(7)
196
+ else:
197
+ ytl.set_visible(False)
198
+ hide_ticks(axes.yaxis)
199
+ for xtl in axes.get_xticklabels():
200
+ xtl.set_visible(False)
201
+ hide_ticks(axes.xaxis)
202
+ axes.grid(True, linewidth=0.25)
203
+
204
+ return fig
205
+
206
+ @output
207
+ @render.table
208
+ def table():
209
+ history = cpu_history_with_hold()
210
+ latest = pd.DataFrame(history).transpose().tail(input.table_rows())
211
+ if latest.shape[0] == 0:
212
+ return latest
213
+ return (
214
+ latest.style.format(precision=0)
215
+ .hide(axis="index")
216
+ .set_table_attributes(
217
+ 'class="dataframe shiny-table table table-borderless font-monospace"'
218
+ )
219
+ .background_gradient(cmap=input.cmap(), vmin=0, vmax=100)
220
+ )
221
+
222
+
223
+ def hide_ticks(axis):
224
+ for ticks in [axis.get_major_ticks(), axis.get_minor_ticks()]:
225
+ for tick in ticks:
226
+ tick.tick1line.set_visible(False)
227
+ tick.tick2line.set_visible(False)
228
+ tick.label1.set_visible(False)
229
+ tick.label2.set_visible(False)
230
+
231
+
232
+ app = App(app_ui, server)
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ # Pandas needs Jinja2 for table styling, but it doesn't (yet) load automatically
2
+ # in Pyodide, so we need to explicitly list it here.
3
+ Jinja2
4
+ matplotlib
5
+ pandas
6
+ psutil
7
+ numpy
8
+ shiny