diff --git a/src/html_files/analytics.ts b/src/html_files/analytics.ts new file mode 100644 index 00000000..380d9afb --- /dev/null +++ b/src/html_files/analytics.ts @@ -0,0 +1,68 @@ +class Rule { + name: string; + single_run_rule?: RuleCall; + all_run_rule?: RuleCall; + per_run_rule?: RuleCall; +} + +interface RuleCall { + (opts: RuleOpts): Generator; +} + +class Rules { + data_type: string; + pretty_name: string; + rules: Array; +} + +class RuleOpts { + data_type: string; + runs: Array; + key: string; + all_data: any; + base_run: string; + base_run_data: any; + this_run: any; + this_run_data: any; + other_run_data: Map; + per_run_data: Array; +} + +enum Status { + Good = '✅', + NotGood = '❌', +} + +class Analytics { + name: string; + analysis: Array; +} + +class Finding { + text: string; + status: Status; + recommendation: string; + + constructor(text: string = '', status: Status = Status.NotGood, recommendation: string = '') { + this.text = text; + this.status = status; + this.recommendation = recommendation; + } + + is_good() { + this.status = Status.Good; + } + + is_not_good() { + this.status = Status.NotGood; + } +} + +function is_unique(values_array) { + return new Set(values_array).size == 1; +} + +let all_rules: Rules[] = [ + system_info_rules, + cpu_utilization_rules, +]; \ No newline at end of file diff --git a/src/html_files/cpu_utilization.ts b/src/html_files/cpu_utilization.ts index 4e7f1c11..927ff374 100644 --- a/src/html_files/cpu_utilization.ts +++ b/src/html_files/cpu_utilization.ts @@ -1,6 +1,50 @@ let got_cpu_util_data = false; let util_cpu_list: Map = new Map(); +let cpu_utilization_rules = { + data_type: "cpu_utilization", + pretty_name: "CPU Utilization", + rules: [ + { + name: "User", + single_run_rule: function* (opts): Generator { + let system_util = get_data_key(opts.data_type, "System"); + let total_util: number = opts.base_run_data + system_util.get(opts.base_run); + if (total_util < 50) { + yield new Finding( + `Average CPU Utilization for '${opts.base_run}' is less than 50%.`, + Status.NotGood, + ); + } + }, + per_run_rule: function* (opts): Generator { + let system_util = get_data_key(opts.data_type, "System"); + let init_total_util: number = opts.base_run_data + system_util.get(opts.base_run); + let run_total_util: number = opts.this_run_data + system_util.get(opts.this_run); + let cpu_diff = Math.ceil(Math.abs(run_total_util - init_total_util)); + yield new Finding( + `Average CPU Utilization difference between '${opts.base_run}' and '${opts.this_run}' is ${cpu_diff}%.`, + cpu_diff > 10 ? Status.NotGood : Status.Good, + ); + }, + }, + { + name: "Idle", + single_run_rule: function* (opts): Generator { + if (opts.base_run_data > 50) { + yield new Finding(`Average Idle time for '${opts.base_run}' is greater than 50%.`, Status.NotGood); + } + }, + per_run_rule: function* (opts): Generator { + let idle_diff = Math.ceil(Math.abs(opts.this_run_data - opts.base_run_data)); + yield new Finding( + `Average Idle time difference between '${opts.base_run}' and '${opts.this_run}' is ${idle_diff}%.`, + idle_diff > 10 ? Status.NotGood : Status.Good, + ) + }, + } + ] +} function getUtilizationType(run, elem, type, run_data) { var cpu_type_datas = []; var type_data; diff --git a/src/html_files/flamegraphs.ts b/src/html_files/flamegraphs.ts index f2e69585..38ff5252 100644 --- a/src/html_files/flamegraphs.ts +++ b/src/html_files/flamegraphs.ts @@ -34,11 +34,9 @@ function getFlamegraphInfo(run, container_id) { addElemToNode(container_id, div); } -function flamegraphs(set: boolean|string) { +function flamegraphs(set) { if (set == got_flamegraphs_data) { return; - } else if (typeof(set) == "boolean") { - set = "flamegraphs"; } got_flamegraphs_data = set; clear_and_create('flamegraphs'); diff --git a/src/html_files/index.html b/src/html_files/index.html index 90b464a4..3ee83d66 100644 --- a/src/html_files/index.html +++ b/src/html_files/index.html @@ -11,7 +11,7 @@
APerf
- + @@ -30,6 +30,12 @@
+
+ Findings + System Info +

+
+
@@ -131,6 +137,7 @@

Hide N/A and all-zero graphs:

+ @@ -149,6 +156,7 @@

Hide N/A and all-zero graphs:

+ diff --git a/src/html_files/index.ts b/src/html_files/index.ts index cfb99670..cda3c86d 100644 --- a/src/html_files/index.ts +++ b/src/html_files/index.ts @@ -14,7 +14,7 @@ DataTypes.set('meminfo', {name: 'meminfo', hideClass: 'meminfoHide', trueId: 'me DataTypes.set('netstat', {name: 'netstat', hideClass: 'netstatHide', trueId: 'netstat_hide_yes', callback: netStat}); DataTypes.set('interrupts', {name: 'interrupts', hideClass: '', trueId: '', callback: interrupts}); DataTypes.set('cpu_utilization', {name: 'cpuutilization', hideClass: '', trueId: '', callback: cpuUtilization}); -DataTypes.set('system_info', {name: 'systeminfo', hideClass: '', trueId: '', callback: systemInfo}); +DataTypes.set('system_info', {name: 'systeminfo', hideClass: 'landingChoice', trueId: '', callback: systemInfo}); DataTypes.set('flamegraphs', {name: 'flamegraphs', hideClass: 'flamegraphsSelection', trueId: '', callback: flamegraphs}); DataTypes.set('top_functions', {name: 'topfunctions', hideClass: '', trueId: '', callback: topFunctions}); DataTypes.set('processes', {name: 'processes', hideClass: '', trueId: '', callback: processes}); @@ -44,7 +44,11 @@ function display_tab(name) { if (datatype.hideClass != "") { let queryInput = `input[name="${datatype.hideClass}"]:checked`; let checkedId = document.querySelector(queryInput).id; - datatype.callback(checkedId == datatype.trueId); + if (datatype.trueId != "") { + datatype.callback(checkedId == datatype.trueId); + } else { + datatype.callback(checkedId); + } } else { datatype.callback(); } diff --git a/src/html_files/system_info.ts b/src/html_files/system_info.ts index b7f7efd9..e5852620 100644 --- a/src/html_files/system_info.ts +++ b/src/html_files/system_info.ts @@ -1,4 +1,49 @@ -let got_system_info_data = false; +let got_system_info_data = "none"; +let system_info_rules = { + data_type: "system_info", + pretty_name: "System Info", + rules: [ + { + name: "System Name", + all_run_rule: function* (ruleOpts: RuleOpts): Generator { + let os_version = get_data_key(ruleOpts.data_type, "OS Version").values(); + if (is_unique(ruleOpts.per_run_data) && is_unique(os_version)) { + yield new Finding("Same OS across runs.", Status.Good); + } else { + yield new Finding("Different OS and/or version across runs."); + } + }, + }, + { + name: "Total CPUs", + all_run_rule: function* (ruleOpts: RuleOpts) : Generator{ + if (is_unique(ruleOpts.per_run_data)) { + yield new Finding("Total CPUs are the same across runs.", Status.Good); + } else { + yield new Finding("Total CPUs are not the same across runs."); + } + }, + }, + { + name: "Kernel Version", + all_run_rule: function* (ruleOpts: RuleOpts) : Generator{ + let versions = ruleOpts.per_run_data; + for (let i = 0; i < versions.length; i++) { + if (versions[i].split(".").length > 2) { + versions[i] = versions[i].split(".").slice(0, 2).join("."); + } else if (versions[i].split("-").length > 1) { + versions[i] = versions[i].split("-")[0]; + } + } + if (is_unique(versions)) { + yield new Finding("Kernel versions (major, minor) are the same across all runs.", Status.Good); + } else { + yield new Finding("Kernel versions (major, minor) are not the same across all runs."); + } + }, + } + ] +} function getSystemInfo(run, container_id, run_data) { var data = JSON.parse(run_data); @@ -19,11 +64,101 @@ function getSystemInfo(run, container_id, run_data) { }) } -function systemInfo() { - if (got_system_info_data) { - return; +function formRuleOpts(data_type, rule) { + let per_run_data = get_data_key(data_type, rule.name); + let base_run_data = per_run_data.get(runs_raw[0]); + let other_run_data = new Map(per_run_data); + other_run_data.delete(runs_raw[0]); + let ruleOpts: RuleOpts = { + data_type: data_type, + runs: runs_raw, + key: rule.name, + all_data: raw_analytics, + this_run: undefined, + this_run_data: undefined, + base_run: runs_raw[0], + base_run_data: base_run_data, + other_run_data: other_run_data, + per_run_data: [...per_run_data.values()], } - clear_and_create('systeminfo'); + return ruleOpts; +} +function analytics() { + let all_analytics = []; + for (var i = 0; i < all_rules.length; i++) { + let rules_group = all_rules[i]; + let data_type = rules_group.data_type; + let analytics = { + name: rules_group.pretty_name, + analysis: [], + } + for (var j = 0; j < rules_group.rules.length; j++) { + let rule = rules_group.rules[j]; + let opts = formRuleOpts(data_type, rule); + if (runs_raw.length > 1) { + for (const [key, value] of opts.other_run_data) { + opts.this_run = key; + opts.this_run_data = value; + let gen = rule.per_run_rule?.(opts); + if (gen) { + let result = gen.next(); + while (!result.done) { + analytics.analysis = analytics.analysis.concat(result.value); + result = gen.next(); + } + } + } + let gen = rule.all_run_rule?.(opts); + if (gen) { + let result = gen.next(); + while (!result.done) { + analytics.analysis = analytics.analysis.concat(result.value); + result = gen.next(); + } + } + } + for (var k = 0; k < runs_raw.length; k++) { + let run = runs_raw[k]; + let per_run_data = get_data_key(data_type, rule.name); + let run_data = per_run_data.get(run); + opts.base_run = run; + opts.base_run_data = run_data; + let gen = rule.single_run_rule?.(opts); + if (gen) { + let result = gen.next(); + while (!result.done) { + analytics.analysis = analytics.analysis.concat(result.value); + result = gen.next(); + } + } + } + } + all_analytics.push(analytics); + } + var table = document.createElement('table'); + table.style.border = 'none'; + table.id = 'analytics-table'; + addElemToNode("findings-data", table); + for (let j = 0; j < all_analytics.length; j++) { + let analytics = all_analytics[j]; + for (let k = 0; k < analytics.analysis.length; k++) { + const row = table.insertRow(); + if (k == 0) { + const key = row.insertCell(); + key.textContent = `${analytics.name}`; + } else { + const key = row.insertCell(); + key.textContent = ''; + } + const tick = row.insertCell(); + tick.textContent = `${analytics.analysis[k].status}`; + const data = row.insertCell(); + data.textContent = `${analytics.analysis[k].text}`; + } + } +} + +function sutconfig() { for (let i = 0; i < system_info_raw_data['runs'].length; i++) { let run_name = system_info_raw_data['runs'][i]['name']; let elem_id = `${run_name}-systeminfo-per-data`; @@ -32,5 +167,25 @@ function systemInfo() { getSystemInfo(run_name, elem_id, this_run_data['key_values']['values']); }, 0); } - got_system_info_data = true; +} + +function systemInfo(set) { + if (set == got_system_info_data) { + return; + } + clear_and_create('systeminfo'); + clearElements("findings-data"); + got_system_info_data = set; + switch (set) { + case 'findings': + document.getElementById('landing-text').innerHTML = 'Findings'; + analytics(); + break; + case 'sutconfig': + document.getElementById('landing-text').innerHTML = 'System Info'; + sutconfig(); + break; + default: + return; + } } diff --git a/src/html_files/utils.ts b/src/html_files/utils.ts index efc7fe74..9f1a7df4 100644 --- a/src/html_files/utils.ts +++ b/src/html_files/utils.ts @@ -15,7 +15,9 @@ declare let flamegraph_raw_data; declare let aperf_run_stats_raw_data; declare let java_profile_raw_data; declare let aperf_runlog_raw_data; +declare let raw_analytics; +let comparator = 'mean'; let all_run_keys: Array = new Array(); let key_limits: Map = new Map(); @@ -171,3 +173,29 @@ function allRunCPUListUnchanged(cpu_list) { } return true; } + +function get_inside_value(data) { + if ('F64' in data) { + return data['F64'] ?? undefined; + } else if ('UInt64' in data) { + return data['UInt64'] ?? undefined; + } else if ('String' in data) { + return data['String'] ?? undefined; + } else if ('Stats' in data) { + return data['Stats'][comparator] ?? undefined; + } else { + return undefined; + } +} + +function get_data_key(data_type, key) { + let key_value_map = new Map(); + for (let run in raw_analytics) { + let run_data = raw_analytics[run].values; + if (key in run_data[data_type]) { + let v = run_data[data_type][key]; + key_value_map.set(run, get_inside_value(v)); + } + } + return key_value_map; +}