Skip to content

Commit 4d2d1fa

Browse files
committed
Experimental Tableau support
A web data connector to support exporting LNT data to the Tableau analytics platform.
1 parent ec6ab2b commit 4d2d1fa

File tree

4 files changed

+261
-0
lines changed

4 files changed

+261
-0
lines changed

docs/api.rst

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,43 @@ The API Auth token can be set by adding `api_auth_token` to the instances lnt.cf
6565
Example::
6666

6767
curl --request DELETE --header "AuthToken: SomeSecret" http://localhost:8000/api/db_default/v4/nts/runs/1
68+
69+
Accessing Data outside of LNT: Tableau Web Data Connector
70+
=========================================================
71+
72+
`Tableau Analytics <https://www.tableau.com>`_ is a popular data analytics platform. LNT has a builtin Tableau Web Data
73+
Connector (WDC) to make it easy to get LNT data into Tableau.
74+
75+
In Tableau, create a new data source of the Web Data Connector type. When prompted for the URL, use the standard
76+
database and suite url, followed by /tableau/.
77+
78+
Examples::
79+
80+
# WDC for a public server
81+
https://lnt.llvm.org/db_default/v4/nts/tableau/
82+
83+
# WDC for a local instance
84+
http://localhost:5000/db_default/v4/nts/tableau/
85+
86+
# WDC for a different database and suite
87+
http://localhost:5000/db_my_perf/v4/size/tableau/
88+
89+
The WDC exports all the data submitted for a collection of machines. The WDC will prompt for a machine regular
90+
expression. The regexp matches against the machine names in this database/suite. You can see those machine names at a
91+
url like `<base>/db_default/v4/nts/machine/`.
92+
93+
The regular expression is a `JavaScript regular expression <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Cheatsheet>`_.
94+
95+
The regexes will depend on your machine names. Some hypothetical examples with a machine name format of clang-arch-branch::
96+
97+
.* # All machines.
98+
clang-.* # All clang machines.
99+
clang-arm(64|32)-branch # Arm64 and Arm32
100+
clang-arm64-.* # All the branches.
101+
102+
The WDC will then populate all the data for the selected machines.
103+
104+
Note: to improve performance the WDC has incremental support. Once results are downloaded, they should refresh and get
105+
new results quickly.
106+
107+
You can have more than one WDC connection to a LNT server.

lnt/server/ui/static/lnt_tableau.js

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
/*jslint browser: true, devel: true*/
2+
/*global $, jQuery, tableau, ts_url */
3+
4+
(function() {
5+
// Create the connector object.
6+
var myConnector = tableau.makeConnector();
7+
8+
// TODO: make the server report types.
9+
// Map LNT types to Tableau datatypes.
10+
var col_type_mapper = {
11+
"compile_status": tableau.dataTypeEnum.int,
12+
"execution_status": tableau.dataTypeEnum.int,
13+
"compile_time": tableau.dataTypeEnum.float,
14+
"execution_time": tableau.dataTypeEnum.float,
15+
"score": tableau.dataTypeEnum.int,
16+
"mem_bytes": tableau.dataTypeEnum.int,
17+
"hash_status": tableau.dataTypeEnum.int,
18+
"hash": tableau.dataTypeEnum.string,
19+
"code_size": tableau.dataTypeEnum.int};
20+
21+
function getValue(payload_url) {
22+
var value = $.ajax({
23+
url: payload_url,
24+
async: false
25+
}).responseText;
26+
return JSON.parse(value);
27+
}
28+
29+
function get_matching_machines(regexp) {
30+
const name_regexp = new RegExp(regexp);
31+
var resp = getValue(ts_url + "/machines/");
32+
var machines = resp.machines;
33+
return machines.filter(function (name_ids) {
34+
console.log(name_ids.name + ", " + name_regexp.test(name_ids.name));
35+
return name_regexp.test(name_ids.name);
36+
});
37+
}
38+
39+
// Define the schema.
40+
myConnector.getSchema = function (schemaCallback) {
41+
var search_info = JSON.parse(tableau.connectionData);
42+
tableau.reportProgress("Getting Schema from LNT.");
43+
44+
// Lookup machines of interest, and gather run fields.
45+
var machine_names = get_matching_machines(search_info.machine_regexp);
46+
if (machine_names.length === 0) {
47+
tableau.abortWithError("Did not match any machine names matching: " +
48+
search_info.machine_regexp);
49+
}
50+
51+
$.getJSON(ts_url + "/fields/", function (resp) {
52+
var fields = resp.fields;
53+
var cols = [];
54+
cols.push({
55+
id: "machine_name",
56+
alias: "Machine Name",
57+
dataType: tableau.dataTypeEnum.string
58+
});
59+
cols.push({
60+
id: "run_id",
61+
alias: "Run ID",
62+
dataType: tableau.dataTypeEnum.int
63+
});
64+
cols.push({
65+
id: "run_order",
66+
alias: "Run Order",
67+
dataType: tableau.dataTypeEnum.string
68+
});
69+
cols.push({
70+
id: "run_date",
71+
alias: "Run DateTime",
72+
dataType: tableau.dataTypeEnum.datetime
73+
});
74+
cols.push({
75+
id: "test_name",
76+
alias: "Test",
77+
dataType: tableau.dataTypeEnum.string
78+
});
79+
80+
fields.forEach(function(field) {
81+
cols.push({
82+
id: field.column_name,
83+
alias: field.column_name,
84+
dataType: col_type_mapper[field.column_name]
85+
});
86+
});
87+
var tableSchema = {
88+
id: "lnt_machine_feed",
89+
alias: "Performance Data from " + resp.generated_by,
90+
columns: cols,
91+
incrementColumnId: "run_id"
92+
};
93+
schemaCallback([tableSchema]);
94+
});
95+
};
96+
97+
// Download the data.
98+
myConnector.getData = function (table, doneCallback) {
99+
var last_run_id = parseInt(table.incrementValue || 0);
100+
101+
// Get latest machines.
102+
var search_info = JSON.parse(tableau.connectionData);
103+
var machine_names = get_matching_machines(search_info.machine_regexp);
104+
if (machine_names.length === 0) {
105+
tableau.abortWithError("Did not match any machine names matching: " +
106+
search_info.machine_regexp);
107+
} else {
108+
tableau.reportProgress("Found " + machine_names.length +
109+
" machines to fetch.");
110+
}
111+
112+
machine_names.forEach(function (machine) {
113+
var url = ts_url + "/machines/" + machine.id;
114+
var machine_info = getValue(url);
115+
var machine_name = machine_info.machine.name;
116+
var tableData = [];
117+
118+
machine_info.runs.forEach(function(run, index) {
119+
var run_data;
120+
var runs_total = machine_info.runs.length;
121+
// Run based incremental refresh. If we have already seen data, skip it.
122+
if (run.id <= last_run_id) {
123+
return;
124+
}
125+
126+
var status_msg = "Getting Machine: " + machine_name
127+
+ " Run: " + run.id
128+
+ " (" + (index + 1) + "/" + runs_total + ")";
129+
130+
tableau.reportProgress(status_msg);
131+
run_data = getValue(ts_url + "/runs/" + run.id);
132+
133+
var date_str = run_data.run.end_time;
134+
var run_date = new Date(date_str);
135+
var derived_run_data = {
136+
"machine_name": machine_name,
137+
"run_id": run.id,
138+
"run_order": run[run.order_by],
139+
"run_date": run_date
140+
};
141+
run_data.tests.forEach(function (element) {
142+
element.test_name = element.name;
143+
delete element.name;
144+
var data = Object.assign({}, derived_run_data, element);
145+
tableData.push(data);
146+
147+
});
148+
run_data = null;
149+
});
150+
151+
table.appendRows(tableData);
152+
153+
});
154+
doneCallback();
155+
};
156+
157+
tableau.registerConnector(myConnector);
158+
159+
// Create event listeners for when the user submits the form.
160+
$(document)
161+
.ready(function () {
162+
$("#submitButton")
163+
.click(function () {
164+
var requested_machines = {
165+
machine_regexp: $("#machine-name")
166+
.val()
167+
.trim()
168+
};
169+
// This will be the data source name in Tableau.
170+
tableau.connectionName = requested_machines.machine_regexp + " (LNT)";
171+
tableau.connectionData = JSON.stringify(requested_machines);
172+
tableau.submit(); // This sends the connector object to Tableau
173+
});
174+
});
175+
})();
176+
177+
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{% set nosidebar = True %}
2+
{% extends "layout.html" %}
3+
{% set components = [] %}
4+
{% block title %}Tableau Machines Data Feed{% endblock %}
5+
{% block head %}
6+
<script>
7+
var ts_url = "/api/db_{{ request.view_args.db_name }}/v4/{{ request.view_args.testsuite_name }}";
8+
</script>
9+
<meta http-equiv="Cache-Control" content="no-store"/>
10+
<script type="text/javascript" src="https://connectors.tableau.com/libs/tableauwdc-2.3.latest.js"></script>
11+
<script type="text/javascript" src="{{ url_for('.static', filename='lnt_tableau.js') }}"></script>
12+
{% endblock %}
13+
{% block body %}
14+
15+
{% if error is defined %}
16+
<p><font color="#FF0000">{{ error }}</font></p>
17+
{% endif %}
18+
<p>This WDC exports all the data submitted for a collection of machines. Below is a prompt for a machine name regular
19+
expression. The regexp matches against the machine names in this database/suite. You can see those machine names <a href="{{ v4_url_for(".v4_machines") }}" target="_blank">here</a>.</p>
20+
21+
<p>The regular expression is a <a
22+
href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Cheatsheet" target="_blank">JavaScript
23+
regular expression</a>.</p>
24+
25+
<form>
26+
<div class="form-inline">
27+
<label for="machine-name" class="text-center">Machine Regexp</label>
28+
</div>
29+
<div class="form-inline">
30+
<input type="text" class="form-control" id="machine-name" value=".*">
31+
</div>
32+
<button type="button" id="submitButton" class="btn btn-success" style="margin: 10px;">Get LNT Data!</button>
33+
</form>
34+
{% endblock %}

lnt/server/ui/views.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -951,6 +951,16 @@ def load_geomean_data(field, machine, limit, xaxis_date, revision_cache=None):
951951
return data
952952

953953

954+
@v4_route("/tableau")
955+
def v4_tableau():
956+
""" Tableau WDC."""
957+
ts = request.get_testsuite()
958+
# TODO: fixup data type exporting to support all test suites.
959+
if ts.name != "nts":
960+
flash("Support for non-nts suites is experimental: suite is " + ts.name, FLASH_DANGER)
961+
return render_template("v4_tableau.html")
962+
963+
954964
@v4_route("/graph")
955965
def v4_graph():
956966

0 commit comments

Comments
 (0)