-
Notifications
You must be signed in to change notification settings - Fork 41
/
tidy_scaling.R
executable file
·332 lines (284 loc) · 11.2 KB
/
tidy_scaling.R
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
#!/usr/bin/env Rscript
# Copyright (c) 2018-2019 Intel Corporation
#
# SPDX-License-Identifier: Apache-2.0
# Show pod scaling data - memory use, boot time, CPU utilisation.
suppressMessages(suppressWarnings(library(ggplot2))) # ability to plot nicely.
# So we can plot multiple graphs
library(gridExtra) # together.
suppressMessages(suppressWarnings(library(ggpubr))) # for ggtexttable.
suppressMessages(library(jsonlite)) # to load the data.
suppressMessages(library(scales)) # For de-science notation of axis
library(tibble) # tibbles for tidy data
render_tidy_scaling <- function()
{
testnames=c(
"k8s-scaling.*"
)
bootdata=c() # Track per-launch data
nodedata=c() # Track node status data
memstats=c() # Statistics for memory usage
cpustats=c() # Statistics for cpu usage
bootstats=c() # Statistics for boot (launch) times
inodestats=c() # Statistics for inode usage
# iterate over every set of results (test run)
for (currentdir in resultdirs) {
# For every results file we are interested in evaluating
for (testname in testnames) {
matchdir=paste(inputdir, currentdir, sep="")
matchfile=paste(testname, '\\.json', sep="")
files=list.files(matchdir, pattern=matchfile)
if ( length(files) == 0 ) {
#warning(paste("Pattern [", matchdir, "/", matchfile, "] matched nothing"))
}
# For every matching results file
for (ffound in files) {
fname=paste(inputdir, currentdir, ffound, sep="")
if ( !file.exists(fname)) {
warning(paste("Skipping non-existent file: ", fname))
next
}
# Derive the name from the test result dirname
datasetname=basename(currentdir)
# Import the data
fdata=fromJSON(fname)
# De-nest the test name specific data
shortname=substr(ffound, 1, nchar(ffound)-nchar(".json"))
fdata=fdata[[shortname]]
testname=datasetname
# Most of the data we are looking for comes in BootResults, so pick it out to make
# referencing easier
br=fdata$BootResults
# The launched pods is a list of data frames when imported. It is much nicer
# for us to work with it as a single data frame, so convert it...
lp=do.call("rbind", br$launched_pods)
########################################################
#### Now extract all the pod launch boot data items ####
########################################################
local_bootdata=tibble(launch_time=br$launch_time$Result)
local_bootdata=cbind(local_bootdata, n_pods=br$n_pods$Result)
local_bootdata=cbind(local_bootdata, node=lp$node)
local_bootdata=cbind(local_bootdata, testname=rep(testname, length(local_bootdata$node)))
########################################################
#### Now extract all node performance information ######
########################################################
nu=br$node_util
# We need to associate a pod count with each result, but you
# get one result per-node, and the JSON does not carry the pod
# count in that table. Walk the node util structure, assigning the
# n_pods value from the boot results over to the list of node util
# entries associated with it - creating a new 'n_pods' field in the
# node util dataframe.
for (n in seq(length(br$n_pods$Result))) {
nu[[n]]$n_pods = br$n_pods$Result[[n]]
}
# node_util is a list of nested data frames. I'm sure there is some better R'ish
# way of extracting this data maybe with dplyr, map, select or melt, but I can't
# work it out right now, and at least this is semi-readable...
#
# Basically, we are de-listing and flattening the lists of dataframes into a
# singly 'tidy' dataframe...
nodes=do.call("rbind", lapply(nu, "[", "node"))
noschedule=do.call("rbind", lapply(nu, "[", "noschedule"))
n_pods=do.call("rbind", lapply(nu, "[", "n_pods"))
idle=lapply(nu, "[", "cpu_idle")
idle_df=do.call("rbind", lapply(idle, "[[", "cpu_idle"))
free=lapply(nu, "[", "mem_free")
free_df=do.call("rbind", lapply(free, "[[", "mem_free"))
used=lapply(nu, "[", "mem_used")
used_df=do.call("rbind", lapply(used, "[[", "mem_used"))
ifree=lapply(nu, "[", "inode_free")
ifree_df=do.call("rbind", lapply(ifree, "[[", "inode_free"))
iused=lapply(nu, "[", "inode_used")
iused_df=do.call("rbind", lapply(iused, "[[", "inode_used"))
# and build our rows
local_nodedata=tibble(node=nodes$node)
local_nodedata=cbind(local_nodedata, n_pods=n_pods)
local_nodedata=cbind(local_nodedata, noschedule=noschedule)
local_nodedata=cbind(local_nodedata, idle=idle_df$Result)
local_nodedata=cbind(local_nodedata, mem_free=free_df$Result)
local_nodedata=cbind(local_nodedata, mem_used=used_df$Result)
local_nodedata=cbind(local_nodedata, inode_free=ifree_df$Result)
local_nodedata=cbind(local_nodedata, inode_used=iused_df$Result)
local_nodedata=cbind(local_nodedata, testname=rep(testname, length(local_nodedata$node)))
# Now Calculate some stats. This gets more complicated as we may have n-nodes,
# and we want to show a 'pod average', so we try to assess for all nodes. If
# we have different 'size' nodes in a cluster, that could throw out the result,
# but the only other option would be to try and show every node separately in the
# table.
# Get a list of all the nodes
nodes=unique(local_nodedata$node)
memtotal=0
cputotal=0
inodetotal=0
# Calculate per-node totals, and tot them up to a global total.
for (n in nodes) {
# Make a frame with just that nodes data in
thisnode=subset(local_nodedata, node %in% c(n))
# Do not use the master (non-schedulable) nodes to calculate
# launched pod metrics
if(thisnode[1,]$noschedule == "true") {
next
}
memtotal = memtotal + thisnode[nrow(thisnode),]$mem_used
cpuused = thisnode[1,]$idle - thisnode[nrow(thisnode),]$idle
cputotal = cputotal + cpuused
inodetotal = inodetotal + thisnode[nrow(thisnode),]$inode_used
}
num_pods = local_bootdata$n_pods[length(local_bootdata$n_pods)]
# We get data in Kb, but want the graphs in Gb.
memtotal = memtotal / (1024*1024)
gb_per_pod = memtotal/num_pods
pod_per_gb = 1/gb_per_pod
# Memory usage stats.
local_mems = c(
"Test"=testname,
"n"=num_pods,
"Tot_Gb"=round(memtotal, 3),
"avg_Gb"=round(gb_per_pod, 4),
"n_per_Gb"=round(pod_per_gb, 2)
)
memstats=rbind(memstats, local_mems)
# cpu usage stats
local_cpus = c(
"Test"=testname,
"n"=num_pods,
"Tot_CPU"=round(cputotal, 3),
"avg_CPU"=round(cputotal/num_pods, 4)
)
cpustats=rbind(cpustats, local_cpus)
# launch (boot) stats
local_boots = c(
"Test"=testname,
"n"=num_pods,
"median"=median(na.omit(local_bootdata)$launch_time)/1000,
"min"=min(na.omit(local_bootdata)$launch_time)/1000,
"max"=max(na.omit(local_bootdata)$launch_time)/1000,
"sd"=round(sd(na.omit(local_bootdata)$launch_time)/1000, 4)
)
bootstats=rbind(bootstats, local_boots)
# inode stats
local_inodes = c(
"Test"=testname,
"n"=num_pods,
"Tot_inode"=round(inodetotal, 3),
"avg_inode"=round(inodetotal/num_pods, 4)
)
inodestats=rbind(inodestats, local_inodes)
# And collect up our rows into our global table of all results
# These two tables *should* be the source of all the data we need to
# process and plot (apart from the stats....)
bootdata=rbind(bootdata, local_bootdata, make.row.names=FALSE)
nodedata=rbind(nodedata, local_nodedata, make.row.names=FALSE)
}
}
}
# Check if we got any stats at all by checking the memstats data. If we found no data,
# abort early and nicely
if ( length(memstats) == 0 ) {
cat("No results files found for scaling tests\n\n")
return()
}
# It's nice to show the graphs in Gb, at least for any decent sized test
# run, so make a new column with that pre-divided data in it for us to use.
nodedata$mem_free_gb = nodedata$mem_free/(1024*1024)
nodedata$mem_used_gb = nodedata$mem_used/(1024*1024)
# And show the boot times in seconds, not mS
bootdata$launch_time_s = bootdata$launch_time/1000
# The labels get messed up by us using an 'if' in the aes() - correct it by
# using the same 'if' to assign what we really want to use for the labels.
colour_label=(if(length(resultdirs)> 1) "testname" else "node")
########## Output memory page ##############
mem_stats_plot = suppressWarnings(ggtexttable(data.frame(memstats),
theme=ttheme(base_size=10),
rows=NULL
))
mem_line_plot <- ggplot(data=nodedata, aes(n_pods,
mem_free_gb,
colour=(if (length(resultdirs) > 1) testname else node),
group=interaction(testname, node))) +
labs(colour=colour_label) +
geom_line(alpha=0.2) +
geom_point(aes(shape=node), alpha=0.3, size=0.5) +
xlab("pods") +
ylab("System Avail (Gb)") +
scale_y_continuous(labels=comma) +
ggtitle("System Memory free") +
theme(legend.position="bottom") +
theme(axis.text.x=element_text(angle=90))
page1 = grid.arrange(
mem_line_plot,
mem_stats_plot,
ncol=1
)
# pagebreak, as the graphs overflow the page otherwise
cat("\n\n\\pagebreak\n")
########## Output cpu page ##############
cpu_stats_plot = suppressWarnings(ggtexttable(data.frame(cpustats),
theme=ttheme(base_size=10),
rows=NULL
))
cpu_line_plot <- ggplot(data=nodedata, aes(n_pods,
idle,
colour=(if (length(resultdirs) > 1) testname else node),
group=interaction(testname, node))) +
labs(colour=colour_label) +
geom_line(alpha=0.2) +
geom_point(aes(shape=node), alpha=0.3, size=0.5) +
xlab("pods") +
ylab("System CPU Idle (%)") +
ggtitle("System CPU usage") +
theme(legend.position="bottom") +
theme(axis.text.x=element_text(angle=90))
page2 = grid.arrange(
cpu_line_plot,
cpu_stats_plot,
ncol=1
)
# pagebreak, as the graphs overflow the page otherwise
cat("\n\n\\pagebreak\n")
########## Output boot page ##############
boot_stats_plot = suppressWarnings(ggtexttable(data.frame(bootstats),
theme=ttheme(base_size=10),
rows=NULL
))
boot_line_plot <- ggplot() +
geom_line( data=bootdata, aes(n_pods, launch_time_s, colour=testname, group=testname), alpha=0.2) +
geom_point( data=bootdata, aes(n_pods, launch_time_s, colour=interaction(testname, node), group=testname), alpha=0.6, size=0.6, stroke=0, shape=16) +
xlab("pods") +
ylab("Boot time (s)") +
ggtitle("Pod boot time") +
theme(legend.position="bottom") +
theme(axis.text.x=element_text(angle=90))
page3 = grid.arrange(
boot_line_plot,
boot_stats_plot,
ncol=1
)
# pagebreak, as the graphs overflow the page otherwise
cat("\n\n\\pagebreak\n")
########## Output inode page ##############
inode_stats_plot = suppressWarnings(ggtexttable(data.frame(inodestats),
theme=ttheme(base_size=10),
rows=NULL
))
inode_line_plot <- ggplot(data=nodedata, aes(n_pods,
inode_free,
colour=(if (length(resultdirs) > 1) testname else node),
group=interaction(testname, node))) +
labs(colour=colour_label) +
geom_line(alpha=0.2) +
geom_point(aes(shape=node), alpha=0.3, size=0.5) +
xlab("pods") +
ylab("inodes free") +
scale_y_continuous(labels=comma) +
ggtitle("inodes free") +
theme(legend.position="bottom") +
theme(axis.text.x=element_text(angle=90))
page4 = grid.arrange(
inode_line_plot,
inode_stats_plot,
ncol=1
)
}
render_tidy_scaling()