Skip to content

Commit 23ee081

Browse files
committed
[ui] Add some primitive database diagnostics to UI.
- The most imporant part of this is a diagnostic which finds any cycles which are present in the database. Although this isn't supposed to happen, currently it is possible to end up with database in this situation when discovered dependencies induce a cycle.
1 parent 6674dab commit 23ee081

File tree

4 files changed

+112
-0
lines changed

4 files changed

+112
-0
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import orderedset
2+
3+
def find_cycle(nodes, successors):
4+
path = orderedset.orderedset()
5+
visited = set()
6+
7+
def visit(node):
8+
# If the node is already in the current path, we have found a cycle.
9+
if not path.add(node):
10+
return (path, node)
11+
12+
# If we have otherwise already visited this node, we don't need to visit
13+
# it again.
14+
if node in visited:
15+
item = path.pop()
16+
assert item == node
17+
return
18+
visited.add(node)
19+
20+
# Otherwise, visit all the successors.
21+
for succ in successors(node):
22+
cycle = visit(succ)
23+
if cycle is not None:
24+
return cycle
25+
26+
item = path.pop()
27+
assert item == node
28+
return None
29+
30+
for node in nodes:
31+
cycle = visit(node)
32+
if cycle is not None:
33+
return cycle
34+
else:
35+
assert not path.items
36+
37+
return None

products/ui/llbuildui/orderedset.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
class orderedset(object):
2+
def __init__(self):
3+
self.items = []
4+
self.set = set()
5+
6+
def add(self, item):
7+
if item in self.set:
8+
return False
9+
self.items.append(item)
10+
self.set.add(item)
11+
return True
12+
13+
def pop(self):
14+
item = self.items.pop()
15+
self.set.remove(item)
16+
return item
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{% extends "layout.html" %}
2+
{% block title %}Database Diagnostics{% endblock %}
3+
{% block body %}
4+
5+
<p>
6+
Key Names: {{ "{:,d}".format(session.query(model.KeyName).count()) }}<br/>
7+
Key Names Size: {{ "{:,d}".format(session.query(sql.functions.sum(sql.functions.char_length(model.KeyName.name))).first()[0]) }}<br/>
8+
Rule Results: {{ "{:,d}".format(session.query(model.RuleResult).count()) }}<br/>
9+
Rule Dependencies: {{ "{:,d}".format(session.query(model.RuleDependency).count()) }}<br/>
10+
</p>
11+
12+
{% if cycle %}
13+
<p>
14+
<font color="red">WARNING: Database contains a cycle!</font><br>
15+
<ul>
16+
{% for node in cycle %}
17+
<li>{{node}}</li>
18+
{% endfor %}
19+
</ul>
20+
</p>
21+
{% endif %}
22+
23+
{% endblock %}

products/ui/llbuildui/views.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import flask
2+
import sqlalchemy.sql
23

34
from flask import Flask, current_app, g, redirect, request, session, url_for
45

6+
import graphalgorithms
57
import model
68

79
main = flask.Blueprint('main', __name__)
@@ -33,6 +35,40 @@ def config():
3335
return redirect(url_for('main.index'))
3436
return flask.render_template("config.html", db_path=session.get("db"))
3537

38+
@main.route('/diagnostics', methods=['GET', 'POST'])
39+
def diagnostics():
40+
s = current_app.database_session
41+
42+
# Find all the rules.
43+
rules = {}
44+
key_names = {}
45+
for result in s.query(model.KeyName):
46+
key_names[result.id] = result.name
47+
for result in s.query(model.RuleResult):
48+
rules[result.key_id] = result
49+
50+
# Find information on any cycles in the database.
51+
def successors(id):
52+
# Get the rule.
53+
rule = rules[id]
54+
55+
# Get the dependencies.
56+
succs = []
57+
for dependency in rule.dependencies:
58+
succs.append(dependency.key_id)
59+
return succs
60+
keys = sorted(rules.keys())
61+
cycle = graphalgorithms.find_cycle(keys, successors)
62+
if cycle is not None:
63+
cycle = cycle[0].items + [cycle[1]]
64+
cycle = [key_names[id] for id in cycle]
65+
66+
return flask.render_template("diagnostics.html",
67+
db_path=session.get("db"),
68+
session=s, model=model, sql=sqlalchemy.sql,
69+
cycle=cycle)
70+
71+
3672
# MARK: Model Object Views
3773

3874
@main.route('/rule_result/<path:name>')

0 commit comments

Comments
 (0)