-
Notifications
You must be signed in to change notification settings - Fork 3
/
a10-tpr3-html-report
executable file
·373 lines (333 loc) · 15.8 KB
/
a10-tpr3-html-report
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
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
#!/usr/bin/env ruby
#
# Copyright © 2014 Siarhei Siamashka <[email protected]>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
raise "Please upgrade ruby to at least version 1.9" if RUBY_VERSION =~ /^1\.8/
require_relative 'a10-tpr3-common.rb'
require "erb"
include ERB::Util
$notitle = false
$dirname = nil
ARGV.each {|arg|
if arg =~ /\-\-notitle/ then
$notitle = true
elsif File.directory?(arg) then
$dirname = arg
end
}
if not $dirname then
printf("Usage: #{$PROGRAM_NAME} [options] results_directory > report.html\n")
printf("\n")
printf("Where:\n")
printf(" --notitle - an option to just generate tables and omit\n")
printf(" the title header with links and explanations\n")
printf(" results_directory - is the name of the directory populated by\n")
printf(" the 'a10-tpr3-scan' script\n")
printf(" report.html - is the output of this script\n")
exit(1)
end
def parse_subtest_dir(dir, adj)
mfxdly_list = [0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00,
0x08, 0x10, 0x18, 0x20, 0x28, 0x30, 0x38]
sdphase_list = [36, 54, 72, 90, 108, 126]
tpr3_status_logs = {}
memtester_logs = {}
tpr3_print_name = {}
score_per_sdphase_label = {}
stability_score = 0
memtester_subtest_stats = {}
lane_r_fail_stats = [0] * 4
lane_w_fail_stats = [0] * 4
lane_r_fail_list = [{}, {}, {}, {}]
lane_w_fail_list = [{}, {}, {}, {}]
bit_fail_stats = [0] * 32
tpr3_generator(adj).each {|tpr3_info|
tpr3 = tpr3_info[:tpr3]
tpr3_status_logs[tpr3] = read_file(dir, sprintf(
"tpr3_0x%08X", tpr3))
memtester_logs[tpr3] = (read_file(dir, sprintf(
"tpr3_0x%08X.memtester", tpr3)) or "")
if memtester_logs[tpr3] == "" then
memtester_logs[tpr3] = (read_file(dir,
sprintf("tpr3_0x%08X.hardening.memtester", tpr3)) or "")
end
tpr3_status_log = tpr3_status_logs[tpr3]
tpr3_print_name[tpr3] = sprintf("0x%06X", tpr3)
if tpr3_status_log =~ /memtester success rate: (\d+)\/(\d+)/ then
stability_score += $1.to_i
tmp = tpr3_info[:sdphase_deg]
score_per_sdphase_label[tmp] = (score_per_sdphase_label[tmp] or 0) + $1.to_i
end
memtester_log = memtester_logs[tpr3]
if memtester_log =~ /FAILURE: 0x([0-9a-f]{8}) != 0x([0-9a-f]{8}).*?\((.*)\)/ then
val1 = $1.to_i(16)
val2 = $2.to_i(16)
memtester_subtest_stats[$3] = (memtester_subtest_stats[$3] or 0) + 1
print_name = sprintf("0x%02X", tpr3 >> 16)
3.downto(0) {|lane|
mask = 0xFF << (lane * 8)
if (val1 & mask) != (val2 & mask) then
if memtester_log =~ /WRITE FAILURE/ then
lane_w_fail_list[lane][tpr3] = true
lane_w_fail_stats[lane] += 1
elsif memtester_log =~ /READ FAILURE/ then
lane_r_fail_list[lane][tpr3] = true
lane_r_fail_stats[lane] += 1
end
print_name += sprintf("<b>%X</b>", (tpr3 >> (lane * 4)) & 0xF)
else
print_name += sprintf("%X", (tpr3 >> (lane * 4)) & 0xF)
end
}
tpr3_print_name[tpr3] = print_name
0.upto(31) {|bit|
mask = 1 << bit
if (val1 & mask) != (val2 & mask) then
bit_fail_stats[bit] += 1
end
}
end
}
def get_nice_color(dir, tpr3, data, memtester_log)
workdata = (read_file(dir, "_current_work_item.txt") or "")
if workdata.include?(sprintf("%08X", tpr3)) then
return "#C0C0C0" # gray marker - work is still in progress
end
tpr3_hardened_log = read_file(dir, sprintf("tpr3_0x%08X.hardening", tpr3))
if tpr3_hardened_log then
if tpr3_hardened_log == "FINISHED, memtester success rate: 100/100" then
return "#008000"
else
return "#60FF60"
end
end
passed_tests = 0
total_tests = 1
if data =~ /memtester success rate: (\d+)\/(\d+)/ then
passed_tests = $1.to_i
total_tests = $2.to_i
end
if passed_tests >= 10 then
color = "#40C040"
else
def lerp(a, b, ratio) return (a + (b - a) * ratio).to_i end
red_part = 0xFF
if memtester_log =~ /READ FAILURE/ then
green_part = lerp(0x50, 0xD0, Math.sqrt(passed_tests + total_tests) / 4.5)
blue_part = 0
elsif memtester_log =~ /WRITE FAILURE/ then
blue_part = lerp(0x50, 0xD0, Math.sqrt(passed_tests + total_tests) / 4.5)
green_part = 0
else
blue_part = lerp(0x0, 0x50, Math.sqrt(passed_tests + total_tests) / 4.5)
green_part = lerp(0x0, 0x50, Math.sqrt(passed_tests + total_tests) / 4.5)
end
color = sprintf("#%02X%02X%02X", red_part, green_part, blue_part)
end
return color
end
html_report = sprintf("<table border=1 style='border-collapse: collapse;")
html_report << sprintf(" empty-cells: show; font-family: arial; font-size: small;")
html_report << sprintf(" white-space: nowrap; background: #F0F0F0;'>\n")
html_report << sprintf("<tr><th>mfxdly")
sdphase_list.each {|sdphase|
# next if not gen_tpr3(0, sdphase, adj)
score_per_sdphase_label[sdphase] = 0 if not score_per_sdphase_label[sdphase]
html_report << sprintf("<th>phase=%d", sdphase)
}
mfxdly_list.each {|mfxdly|
html_report << sprintf("<tr><th><b>0x%02X</b>", mfxdly)
sdphase_list.each {|sdphase|
tpr3 = gen_tpr3(mfxdly, sdphase, adj)
if not tpr3 then
html_report << "<td>"
next
end
data = tpr3_status_logs[tpr3]
if not data then
html_report << "<td>"
next
end
memtester_log = memtester_logs[tpr3]
color = get_nice_color(dir, tpr3, data, memtester_log)
escaped_memtester_log = html_escape(memtester_log)
escaped_memtester_log.gsub!(/\'/, "'")
escaped_memtester_log.gsub!(/\"/, """)
html_report << sprintf("<td bgcolor=%s title='%s'>%s",
color, data + "\n" + escaped_memtester_log,
tpr3_print_name[tpr3])
}
}
html_report << sprintf("</table>\n")
return {
:html_report => html_report,
:memtester_subtest_stats => memtester_subtest_stats,
:lane_r_fail_stats => lane_r_fail_stats,
:lane_w_fail_stats => lane_w_fail_stats,
:lane_r_fail_list => lane_r_fail_list,
:lane_w_fail_list => lane_w_fail_list,
:bit_fail_stats => bit_fail_stats,
:stability_score => stability_score,
:score_per_sdphase_label => score_per_sdphase_label,
}
end
print "
<p>
This is a DRAM tuning/overclocking stability report for various <a href='http://linux-sunxi.org'>Allwinner
A10/A13/A20 based devices</a>. It can be automatically generated using the tools from
<a href='https://github.com/ssvb/a10-dram-tools'>https://github.com/ssvb/a10-dram-tools</a>.
Check the <a href='http://linux-sunxi.org/A10_DRAM_Controller_Calibration'>A10_DRAM_Controller_Calibration</a>
linux-sunxi wiki page for more details.
</p>
<p>
A table is generated for each DRAM configuration, with the configuration details
shown on the left side. Each cell in this table represents the <b>'tpr3'</b> parameter
value. Moving between cells in the horizontal direction changes read delays (of all byte
lanes as a whole), and moving between cells in the vertical direction changes write
delays (of all byte lanes as a whole). Naturally, some tpr3 values are better than the
others. The most stable configuration is likely to be located in the middle of a large
green colored isle in the table. An attempt to assign some score to the overall stability
of the DRAM configuration based on different heuristic metrics is also presented on the
right side. Different DRAM configurations may be compared using this score.
</p>
<p>
Results interpretation:
<ul>
<li>The shades of pure RED mean hardware deadlocks or major failures when we don't live
long enough to detect the data corruption.
<li>The shades of RED/ORANGE/YELLOW mean data corruption on read operations.
<li>The shades of RED/PURPLE/MAGENTA mean data corruption on write operations (actually
this is not a reliable fact, but just a guess).
<li>LIGHTGREEN - no problems detected during only a few minutes of running
<a href='https://github.com/ssvb/lima-memtester/'>lima-memtester</a>.
<li>DARKGREEN - no problems detected during up to roughly half an hour run of lima-memtester
(these extra 'hardening' tests are performed after all the preliminary data has been collected).
<li>Some digits of the tpr3 values in the table(s) below may be shown using bold font.
This indicates that data corruption has been detected in the corresponding byte lane(s).
</ul>
</p>
" if not $notitle
dirlist = []
Dir.glob(File.join($dirname, "*")).each {|f|
next if not File.directory?(f)
dirlist.push(f)
}
def strip_html_tags(text)
return text.gsub(/\<[\/]?a.*?\>/, "")
end
# Group results from the same device/configuration/description
tmp = {}
dirlist.sort.each {|f|
if File.basename(f) =~ /(.*MHz\-\d+\.\d+V-[0-9a-fA-FxX\-]+)/ then
id = $1
id = (read_file(f, "_description.txt") or "") + " : " + id
tmp[id] = [] if not tmp.has_key?(id)
tmp[id].push(f)
end
}
dirlist = []
tmp.to_a.sort {|x,y| strip_html_tags(x[0]) <=> strip_html_tags(y[0]) }.each {|x|
dirlist.push(x[1])
}
dirlist.each {|a|
a10_meminfo = read_file(a[0], "_a10_meminfo.txt")
if not a10_meminfo =~ /dram_bus_width\s*=\s*(\d+)/ then
raise("Error: dram_bus_width is not found in the a10-meminfo log\n")
end
$number_of_lanes = $1.to_i / 8
printf("<p><h2><b>%s</b></h2>\n",
(read_file(a[0], "_description.txt") or "Unknown device"))
printf("<table border=1 style='border-collapse: collapse;")
printf(" empty-cells: show; font-family: Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace; font-size: small;")
printf(" white-space: nowrap; background: #F0F0F0;'>\n")
a.each {|f|
jobs_finder_generator(f, {:sorted => true}).each {|job_info|
adj = job_info[:lane_phase_adjustments]
printf("<tr>")
printf("<td><table border=0 style='border-collapse: collapse;")
printf(" empty-cells: show; font-family: Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace; font-size: small;")
printf(" white-space: nowrap; background: #F0F0F0;'>\n")
printf("<tr><td>%s", a10_meminfo.gsub("\n", "<br>"))
printf("</table>")
subtest_results = parse_subtest_dir(f, adj)
printf("<td>%s", subtest_results[:html_report])
printf("<td>")
printf("Lane phase adjustments: [%s]<br>", adj.reverse.join(", "))
memtester_subtest_stats = subtest_results[:memtester_subtest_stats].to_a
memtester_subtest_stats.sort! {|x, y| y[1] <=> x[1] }
printf("Error statistics from memtester: [%s]<br>",
memtester_subtest_stats.map {|a|
sprintf("%s=%d", a[0], a[1])
}.join(", "))
column_scores = subtest_results[:score_per_sdphase_label].sort.map {|a| a[1] }
printf("<br>")
printf("Total number of successful memtester runs: %d<br>", subtest_results[:stability_score])
printf("<br>")
luminance_tpr3_info = tpr3_reordered_generator(adj, 0, f, 0.5).to_a[0]
printf("Best luminance at the height 0.5 is above 0x%06X, score = %.3f<br>",
luminance_tpr3_info[:tpr3], luminance_tpr3_info[:score])
luminance_tpr3_info = tpr3_reordered_generator(adj, 0, f, 1).to_a[0]
printf("Best luminance at the height 1.0 is above 0x%06X, score = %.3f<br>",
luminance_tpr3_info[:tpr3], luminance_tpr3_info[:score])
luminance_tpr3_info = tpr3_reordered_generator(adj, 0, f, 2).to_a[0]
printf("Best luminance at the height 2.0 is above 0x%06X, score = %.3f<br>",
luminance_tpr3_info[:tpr3], luminance_tpr3_info[:score])
luminance_tpr3_info = tpr3_reordered_generator(adj, 0, f, 4).to_a[0]
printf("Best luminance at the height 4.0 is above 0x%06X, score = %.3f<br>",
luminance_tpr3_info[:tpr3], luminance_tpr3_info[:score])
def print_lane_err_stats(prefix, lane_fail_stats, lane_fail_list)
printf("<br>%s errors per lane: [%s]. ", prefix,
lane_fail_stats.reverse.join(", "))
worst_lane_id = lane_fail_stats.each_with_index.max[1]
worst_lane_fail_list = lane_fail_list[worst_lane_id]
printf("Lane %d is the most noisy/problematic.<br>", worst_lane_id)
something_is_still_bad = false
lane_fail_list.each_with_index {|fail_list, lane_id|
matched_cnt = 0
total_cnt = 0
fail_list.each {|tpr3, dummy|
matched_cnt += 1 if worst_lane_fail_list.has_key?(tpr3)
total_cnt += 1
}
if total_cnt > 0 and lane_id != worst_lane_id then
something_is_still_bad = true if matched_cnt != total_cnt
if matched_cnt > 0 then
printf("Errors from the lane %d are %.1f%% eclipsed by the worst lane %d.<br>",
lane_id, (matched_cnt.to_f / total_cnt.to_f) * 100,
worst_lane_id)
else
printf("Errors from the lane %d are not intersecting with the errors from the worst lane %d.<br>",
lane_id, worst_lane_id)
end
end
}
return worst_lane_id
end
print_lane_err_stats("Read", subtest_results[:lane_r_fail_stats],
subtest_results[:lane_r_fail_list])
print_lane_err_stats("Write", subtest_results[:lane_w_fail_stats],
subtest_results[:lane_w_fail_list])
# if something_is_still_bad then
# adj1 = adj.each_with_index.map {|x, i| i == worst_lane_id ? x : x - 1}
# adj2 = adj.each_with_index.map {|x, i| i == worst_lane_id ? x : x + 1}
# printf("<br>Need to try lane phase adjustments <b>[%s]</b> and <b>[%s]</b> for further analysis.<br>",
# adj1.reverse.join(", "), adj2.reverse.join(", "))
# end
}
}
printf("</table>")
printf("</p>\n")
}