crashclouseau/models.py (1,192 lines of code) (raw):
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
from collections import defaultdict, OrderedDict
from datetime import datetime
from dateutil.relativedelta import relativedelta
from libmozdata.hgmozilla import Mercurial
import sqlalchemy.dialects.postgresql as pg
from sqlalchemy import inspect, func
import pytz
from . import config, db, utils
from .logger import logger
CHANNEL_TYPE = db.Enum(*config.get_channels(), name="CHANNEL_TYPE")
PRODUCT_TYPE = db.Enum(*config.get_products(), name="PRODUCT_TYPE")
class LastDate(db.Model):
__tablename__ = "lastdate"
channel = db.Column(CHANNEL_TYPE, primary_key=True)
mindate = db.Column(db.DateTime(timezone=True))
maxdate = db.Column(db.DateTime(timezone=True))
def __init__(self, channel, mindate, maxdate):
self.channel = channel
self.mindate = mindate
self.maxdate = maxdate
@staticmethod
def update(mindate, maxdate, channel):
q = db.session.query(LastDate).filter(LastDate.channel == channel)
q = q.first()
if q:
if mindate:
q.mindate = mindate
q.maxdate = maxdate
db.session.add(q)
else:
db.session.add(LastDate(channel, mindate, maxdate))
db.session.commit()
return mindate, maxdate
@staticmethod
def get(channel):
d = db.session.query(LastDate).filter(LastDate.channel == channel)
d = d.first()
if d:
return d.mindate.astimezone(pytz.utc), d.maxdate.astimezone(pytz.utc)
return None, None
class File(db.Model):
__tablename__ = "files"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(512), unique=True)
def __init__(self, name):
self.name = name
@staticmethod
def get_id(name):
sel = db.select([db.literal(name)]).where(
~db.exists([File.name]).where(File.name == name)
)
ins = (
db.insert(File)
.from_select([File.name], sel)
.returning(File.id)
.cte("inserted")
)
rs = (
db.session.query(File.id)
.filter(File.name == name)
.union_all(
db.session.query(File.id).select_from(ins).filter(File.id == ins.c.id)
)
)
first = rs.first()
if first is None:
first = rs.first()
id = first[0]
db.session.commit()
return id
@staticmethod
def get_ids(names):
rs = db.session.query(File).filter(File.name.in_(names))
ids = {f.name: f.id for f in rs}
newnames = set(names) - set(ids.keys())
news = []
for n in newnames:
f = File(n)
news.append(f)
db.session.add(f)
db.session.commit()
for n in news:
ids[n.name] = n.id
return ids
@staticmethod
def get_full_path(name):
m = db.session.query(File.name).filter(File.name.like("%/" + name)).first()
if m:
return m[0]
return name
@staticmethod
def populate(files, check=False):
if check:
for f in files:
File.get_id(f)
else:
for f in files:
db.session.add(File(f))
db.session.commit()
class Node(db.Model):
__tablename__ = "nodes"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
channel = db.Column(CHANNEL_TYPE)
node = db.Column(db.String(12))
pushdate = db.Column(db.DateTime(timezone=True))
backedout = db.Column(db.Boolean)
merge = db.Column(db.Boolean)
bug = db.Column(db.Integer)
hgauthor = db.Column(db.Integer, db.ForeignKey("hgauthors.id", ondelete="CASCADE"))
def __init__(self, channel, info):
self.channel = channel
self.node = info["node"]
self.pushdate = info["date"]
self.backedout = info["backedout"]
self.merge = info["merge"]
self.bug = info["bug"]
self.hgauthor = HGAuthor.get_id(info["author"])
@staticmethod
def get_min_date(channel):
m = (
db.session.query(db.func.min(Node.pushdate))
.filter(Node.channel == channel)
.first()[0]
)
return m.astimezone(pytz.utc)
@staticmethod
def get_max_date(channel):
m = (
db.session.query(db.func.max(Node.pushdate))
.filter(Node.channel == channel)
.first()[0]
)
return m.astimezone(pytz.utc)
@staticmethod
def get_bugid(node, channel):
m = (
db.session.query(Node.bug)
.filter(Node.channel == channel, Node.node == node)
.first()[0]
)
return m if m > 0 else 0
@staticmethod
def clean(date, channel):
ndays_ago = date - relativedelta(days=config.get_ndays_of_data())
db.session.query(Node).filter(
Node.pushdate <= ndays_ago, Node.channel == channel
).delete()
db.session.commit()
return LastDate.update(Node.get_min_date(channel), date, channel)
@staticmethod
def get_ids(revs, channel):
res = {}
if revs:
qs = db.session.query(Node.id, Node.node).filter(
Node.node.in_(list(revs)), Node.channel == channel
)
for q in qs:
res[q.node] = q.id
return res
@staticmethod
def get_id(rev, channel):
if rev:
qs = db.session.query(Node.id).filter(
Node.node == rev, Node.channel == channel
)
return qs.first()
return None
@staticmethod
def has_channel(channel):
q = db.session.query(Node.channel).filter(Node.channel == channel).first()
return bool(q)
class Changeset(db.Model):
__tablename__ = "changesets"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
nodeid = db.Column(db.Integer, db.ForeignKey("nodes.id", ondelete="CASCADE"))
fileid = db.Column(db.Integer, db.ForeignKey("files.id", ondelete="CASCADE"))
added_lines = db.Column(pg.ARRAY(db.Integer), default=[])
deleted_lines = db.Column(pg.ARRAY(db.Integer), default=[])
touched_lines = db.Column(pg.ARRAY(db.Integer), default=[])
isnew = db.Column(db.Boolean, default=False)
analyzed = db.Column(db.Boolean, default=False)
def __init__(self, nodeid, fileid):
self.nodeid = nodeid
self.fileid = fileid
@staticmethod
def reset(revs):
q = db.session.query(Changeset).join(Node)
q = q.filter(Node.node.in_(revs)).update(
{
"analyzed": False,
"isnew": False,
"added_lines": [],
"deleted_lines": [],
"touched_lines": [],
},
synchronize_session="fetch",
)
db.session.commit()
@staticmethod
def to_analyze(chgsets=[], channel=""):
if not channel:
fl = (
db.session.query(Changeset.nodeid, Node.node, Node.channel)
.select_from(Changeset)
.join(Node)
)
fl = (
fl.filter(Node.merge.is_(False), Changeset.analyzed.is_(False))
.distinct(Node.id)
.first()
)
return (fl.nodeid, fl.node, fl.channel) if fl else (None, None, None)
if not chgsets:
return []
chgsets = list(chgsets)
fls = (
db.session.query(Changeset.id, Node.id, Node.node)
.select_from(Changeset)
.join(Node)
)
fls = fls.filter(
Node.node.in_(chgsets),
Node.channel == channel,
Node.merge.is_(False),
Changeset.analyzed.is_(False),
).distinct(Node.id)
res = [(nodeid, node) for _, nodeid, node in fls]
return res
@staticmethod
def add(chgsets, date, channel):
if not chgsets:
return None, None
nodes = []
files = set()
for chgset in chgsets:
node = Node(channel, chgset)
db.session.add(node)
nodes.append((node, chgset))
files |= set(chgset["files"])
db.session.commit()
if files:
ids = File.get_ids(files)
for node, chgset in nodes:
nodeid = node.id
for f in chgset["files"]:
c = Changeset(nodeid, ids[f])
db.session.add(c)
db.session.commit()
return Node.clean(date, channel)
@staticmethod
def add_analyzis(data, nodeid, channel, commit=True):
db.session.query(Changeset).filter(Changeset.nodeid == nodeid).update(
{"analyzed": True}
)
if data:
chgs = (
db.session.query(Changeset, File.name)
.select_from(Changeset)
.join(File)
.filter(Changeset.nodeid == nodeid)
)
for chg, name in chgs:
# if the filename is not in data,
# then it means that the file has been deleted
info = data.get(name)
if info:
added = info.get("added")
if added:
chg.added_lines = added
deleted = info.get("deleted")
if deleted:
chg.deleted_lines = deleted
touched = info.get("touched")
if touched:
chg.touched_lines = touched
new = info.get("new")
if new:
chg.isnew = True
db.session.add(chg)
if commit:
db.session.commit()
@staticmethod
def find(filenames, mindate, maxdate, channel):
if not filenames:
return None
chgs = (
db.session.query(Changeset.id, File.name, Node.node)
.select_from(Changeset)
.join(Node)
.join(File)
)
chgs = chgs.filter(
File.name.in_(filenames),
mindate <= Node.pushdate,
Node.pushdate <= maxdate,
Node.channel == channel,
Node.merge.is_(False),
)
res = {}
for _, fname, node in chgs:
if fname not in res:
res[fname] = []
res[fname].append(node)
return res
@staticmethod
def get_scores(filename, line, chgsets, csid):
chgs = db.session.query(Changeset).select_from(Changeset).join(Node).join(File)
chgs = chgs.filter(
Node.node.in_(chgsets), File.name == filename, Changeset.analyzed.is_(True)
)
res = []
M = config.get_max_score()
for chg in chgs:
if chg.isnew:
res.append((chg.id, csid, M))
else:
added = chg.added_lines
deleted = chg.deleted_lines
touched = chg.touched_lines
sc = max(
utils.get_line_score(line, touched),
utils.get_line_score(line, added),
)
if sc < 5:
sc = max(sc, utils.get_line_score(line, deleted))
res.append((chg.id, csid, sc))
return res
class Build(db.Model):
__tablename__ = "builds"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
buildid = db.Column(db.DateTime(timezone=True))
product = db.Column(PRODUCT_TYPE)
channel = db.Column(CHANNEL_TYPE)
version = db.Column(db.String(10))
nodeid = db.Column(db.Integer, db.ForeignKey("nodes.id", ondelete="CASCADE"))
__table_args__ = (
db.UniqueConstraint("buildid", "product", "channel", name="uix_builds"),
)
def __init__(self, buildid, product, channel, version, nodeid):
self.buildid = buildid
self.product = product
self.channel = channel
self.version = version
self.nodeid = nodeid
@staticmethod
def put_data(data):
revs = defaultdict(lambda: set())
for prod, i in data.items():
for chan, j in i.items():
revs_c = revs[chan]
for k in j.values():
revs_c.add(k["revision"])
for chan, r in revs.items():
revs[chan] = Node.get_ids(r, chan)
for prod, i in data.items():
for chan, j in i.items():
revs_c = revs[chan]
for bid, k in j.items():
rev = k["revision"]
if rev in revs_c:
version = k["version"]
ins = pg.insert(Build).values(
buildid=bid,
product=prod,
channel=chan,
version=version,
nodeid=revs_c[rev],
)
upd = ins.on_conflict_do_nothing()
db.session.execute(upd)
db.session.commit()
@staticmethod
def put_build(buildid, nodeid, product, channel, version):
db.session.add(Build(buildid, product, channel, version, nodeid))
db.session.commit()
@staticmethod
def get_two_last(buildid, channel, product):
qs = (
db.session.query(Build.buildid, Build.version, Node.node)
.select_from(Build)
.filter(
Build.buildid <= buildid,
Build.product == product,
Build.channel == channel,
)
)
qs = qs.join(Node).order_by(Build.buildid.desc()).limit(2)
res = [
{
"buildid": utils.get_buildid(q.buildid),
"revision": q.node,
"version": q.version,
}
for q in qs
]
if len(res) == 2:
x = res[0]
res[0] = res[1]
res[1] = x
return res
@staticmethod
def get_last_versions(date, channel, product, n=0):
qs = (
db.session.query(Build.buildid, Build.version, Node.node)
.select_from(Build)
.filter(
Build.buildid <= date,
Build.product == product,
Build.channel == channel,
)
)
qs = qs.join(Node).order_by(Build.buildid.desc())
if n >= 1:
qs = qs.limit(n)
res = []
major = 0
for q in qs:
if major == 0:
major = utils.get_major(q.version)
elif major != utils.get_major(q.version):
break
res.append(
{
"buildid": utils.get_buildid(q.buildid),
"revision": q.node,
"version": q.version,
}
)
if len(res) >= 2:
return res
return []
@staticmethod
def get_pushdate_before(buildid, channel, product):
qs = (
db.session.query(Build.buildid, Node.pushdate).select_from(Build).join(Node)
)
qs = (
qs.filter(
Build.buildid < buildid,
Build.product == product,
Build.channel == channel,
)
.order_by(Build.buildid.desc())
.first()
)
return qs.pushdate
@staticmethod
def get_id(bid, channel, product):
q = (
db.session.query(Build.id)
.filter(
Build.buildid == bid, Build.product == product, Build.channel == channel
)
.first()
)
if q:
return q[0]
return None
@staticmethod
def get_products(channel):
prods = db.session.query(Build.product).filter(Build.channel == channel)
prods = prods.distinct().order_by(Build.product.desc())
res = [p.product for p in prods]
return res
@staticmethod
def get_changeset(bid, channel, product):
q = db.session.query(Build.id, Node.node).select_from(Build).join(Node)
q = q.filter(
Build.buildid == bid, Build.product == product, Build.channel == channel
).first()
if q:
return q[1]
return None
class HGAuthor(db.Model):
__tablename__ = "hgauthors"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
email = db.Column(db.String(254))
real = db.Column(db.String(128))
nick = db.Column(db.String(64))
bucketid = db.Column(db.Integer, default=-1)
__table_args__ = (
db.UniqueConstraint("email", "real", "nick", name="uix_hgauthors"),
)
def __init__(self, *args):
self.email = args[0]
self.real = args[1]
self.nick = args[2]
@staticmethod
def get_id(info):
if not info:
return 1
info = info[0]
email, real, nick = info
sel = db.select(db.literal(email), db.literal(real), db.literal(nick)).where(
~db.exists().where(
db.and_(
HGAuthor.email == email,
HGAuthor.real == real,
HGAuthor.nick == nick,
)
)
)
ins = (
db.insert(HGAuthor)
.from_select([HGAuthor.email, HGAuthor.real, HGAuthor.nick], sel)
.returning(HGAuthor.id)
.cte("inserted")
)
rs = (
db.session.query(HGAuthor.id)
.filter(
HGAuthor.email == email, HGAuthor.real == real, HGAuthor.nick == nick
)
.union_all(
db.session.query(HGAuthor.id)
.select_from(ins)
.filter(Signature.id == ins.c.id)
)
)
first = rs.first()
if first is None:
first = rs.first()
id = first[0]
db.session.commit()
return id
@staticmethod
def put(data):
db.session.add(HGAuthor("", "", ""))
if data:
for info in sorted(data):
db.session.add(HGAuthor(*info))
db.session.commit()
class Signature(db.Model):
__tablename__ = "signatures"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
signature = db.Column(db.String(512))
def __init__(self, signature):
self.signature = signature
@staticmethod
def get_id(signature):
sel = db.select(db.literal(signature)).where(
~db.exists().where(Signature.signature == signature)
)
ins = (
db.insert(Signature)
.from_select([Signature.signature], sel)
.returning(Signature.id)
.cte("inserted")
)
rs = (
db.session.query(Signature.id)
.filter(Signature.signature == signature)
.union_all(
db.session.query(Signature.id)
.select_from(ins)
.filter(Signature.id == ins.c.id)
)
)
first = rs.first()
if first is None:
first = rs.first()
id = first[0]
db.session.commit()
return id
@staticmethod
def get_reports(signatures, product=None, channel=None):
reports = (
db.session.query(
Build.buildid,
Build.product,
Build.channel,
Signature.signature,
UUID.id,
UUID.uuid,
UUID.max_score,
)
.select_from(Signature)
.join(UUID)
.join(Build)
.filter(
Signature.signature.in_(signatures),
UUID.useless.is_(False),
UUID.analyzed.is_(True),
)
)
if product is not None:
reports = reports.filter(Build.product == product)
if channel is not None:
reports = reports.filter(Build.channel == channel)
reports_map = {
report.id: {
"uuid": report.uuid,
"build_id": int(utils.get_buildid(report.buildid)),
"product": report.product,
"channel": report.channel,
"signature": report.signature,
"max_score": report.max_score,
"changesets": [],
}
for report in reports
}
if not reports_map:
# No reports found, no need to continue
return []
changeset_aggregated_columns = (
CrashStack.uuidid,
Node.node,
Node.channel,
Node.pushdate,
Node.backedout,
Node.merge,
Node.bug,
)
changesets = (
db.session.query(
*changeset_aggregated_columns,
func.max(Score.score).label("max_score"),
)
.select_from(CrashStack)
.join(Score)
.join(Changeset)
.join(Node)
.filter(
CrashStack.uuidid.in_(reports_map.keys()),
)
.group_by(*changeset_aggregated_columns)
)
for changeset in changesets:
reports_map[changeset.uuidid]["changesets"].append(
{
"changeset": changeset.node,
"channel": changeset.channel,
"push_date": changeset.pushdate,
"is_backedout": changeset.backedout,
"is_merge": changeset.merge,
"bug_id": changeset.bug,
"max_score": changeset.max_score,
}
)
return list(reports_map.values())
class Stats(db.Model):
__tablename__ = "stats"
signatureid = db.Column(
db.Integer, db.ForeignKey("signatures.id", ondelete="CASCADE"), primary_key=True
)
buildid = db.Column(
db.Integer, db.ForeignKey("builds.id", ondelete="CASCADE"), primary_key=True
)
number = db.Column(db.Integer, default=0)
installs = db.Column(db.Integer, default=-1)
def __init__(self, signatureid, buildid, number, installs):
self.signatureid = signatureid
self.buildid = buildid
self.number = number
self.installs = installs
@staticmethod
def add(signatureid, buildid, number, installs, commit=True):
ins = pg.insert(Stats).values(
signatureid=signatureid, buildid=buildid, number=number, installs=installs
)
upd = ins.on_conflict_do_update(
index_elements=["signatureid", "buildid"],
set_=dict(number=number, installs=installs),
)
db.session.execute(upd)
if commit:
db.session.commit()
class UUID(db.Model):
__tablename__ = "uuids"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
uuid = db.Column(db.String(36), unique=True)
buildid = db.Column(db.Integer, db.ForeignKey("builds.id", ondelete="CASCADE"))
signatureid = db.Column(
db.Integer, db.ForeignKey("signatures.id", ondelete="CASCADE")
)
protohash = db.Column(db.String(56))
stackhash = db.Column(db.String(56))
jstackhash = db.Column(db.String(56))
analyzed = db.Column(db.Boolean, default=False)
useless = db.Column(db.Boolean, default=False)
max_score = db.Column(db.Integer, default=0)
error = db.Column(db.Boolean, default=False)
created = db.Column(
db.DateTime(timezone=True), nullable=False, server_default=db.func.now()
)
def __init__(self, uuid, signatureid, protohash, buildid):
self.uuid = uuid
self.signatureid = signatureid
self.protohash = protohash
self.buildid = buildid
@staticmethod
def get_info(uuid):
q = (
db.session.query(
UUID.id,
Build.buildid,
Build.product,
Build.channel,
Build.version,
Signature.signature,
)
.select_from(UUID)
.join(Build)
.join(Signature)
)
q = q.filter(UUID.uuid == uuid).first()
return {
"buildid": utils.get_buildid(q.buildid),
"product": q.product,
"channel": q.channel,
"version": q.version,
"signature": q.signature,
}
@staticmethod
def reset(uuids):
qs = db.session.query(UUID.id).filter(UUID.uuid.in_(uuids))
qs.update(
{"analyzed": False, "useless": False, "stackhash": "", "jstackhash": ""},
synchronize_session="fetch",
)
res = [q.id for q in qs]
db.session.commit()
return res
@staticmethod
def set_max_score(uuidid, score, commit=True):
q = db.session.query(UUID).filter(UUID.id == uuidid)
q.update({"max_score": score})
if commit:
db.session.commit()
@staticmethod
def set_error(uuid, commit=True):
q = db.session.query(UUID).filter(UUID.uuid == uuid)
q.update({"error": True})
if commit:
db.session.commit()
@staticmethod
def add(uuid, signatureid, proto, buildid, commit=True):
ret = True
protohash = utils.hash(proto)
q = (
db.session.query(UUID)
.filter(
UUID.signatureid == signatureid,
UUID.protohash == protohash,
UUID.buildid == buildid,
)
.first()
)
ret = not bool(q)
if ret:
ins = pg.insert(UUID).values(
uuid=uuid, signatureid=signatureid, protohash=protohash, buildid=buildid
)
upd = ins.on_conflict_do_update(
index_elements=["uuid"],
set_=dict(
signatureid=signatureid, protohash=protohash, buildid=buildid
),
)
db.session.execute(upd)
if commit:
db.session.commit()
return ret
@staticmethod
def add_stack_hash(uuid, sh, jsh, commit=True):
q = db.session.query(UUID).filter(UUID.uuid == uuid)
if sh:
q.update({"stackhash": sh})
elif jsh:
q.update({"jstackhash": jsh})
if commit:
db.session.commit()
@staticmethod
def set_analyzed(uuid, useless, commit=True):
q = db.session.query(UUID).filter(UUID.uuid == uuid)
q.update({"useless": useless, "analyzed": True})
if commit:
db.session.commit()
@staticmethod
def to_analyze(report_uuid):
uuid = (
db.session.query(
UUID.uuid, Build.buildid, Build.channel, Build.product, Node.node
)
.select_from(UUID)
.join(Build)
.join(Node)
)
if report_uuid:
uuid = uuid.filter(UUID.uuid == report_uuid).first()
else:
uuid = uuid.filter(UUID.analyzed.is_(False)).first()
return uuid
@staticmethod
def get_bid_chan(uuid):
r = (
db.session.query(UUID.id, Build.buildid, Build.channel)
.select_from(UUID)
.join(Build)
)
r = r.filter(UUID.uuid == uuid, UUID.useless.is_(False)).first()
return r.buildid.astimezone(pytz.utc), r.channel
@staticmethod
def get_bid_chan_by_id(uuidid):
r = (
db.session.query(
UUID.uuid, Signature.signature, Build.buildid, Build.channel, Node.node
)
.select_from(UUID)
.join(Build)
.join(Node)
.join(Signature)
)
r = r.filter(UUID.id == uuidid).first()
if r:
return {
"uuid": r.uuid,
"signature": r.signature,
"buildid": r.buildid.astimezone(pytz.utc),
"channel": r.channel,
"node": r.node,
}
return {}
@staticmethod
def get_bid_chan_by_uuid(uuid):
r = (
db.session.query(
UUID.id,
UUID.jstackhash,
Signature.signature,
Build.buildid,
Build.product,
Build.channel,
Node.node,
)
.select_from(UUID)
.join(Build)
.join(Node)
.join(Signature)
)
r = r.filter(
UUID.uuid == uuid, UUID.useless.is_(False), UUID.analyzed.is_(True)
).first()
if r:
return {
"uuid": uuid,
"id": r.id,
"signature": r.signature,
"buildid": r.buildid.astimezone(pytz.utc),
"channel": r.channel,
"product": r.product,
"java": bool(r.jstackhash),
"node": r.node,
}
return {}
@staticmethod
def get_uuids_from_buildid(buildid, product, channel):
sbid = buildid
buildid = utils.get_build_date(buildid)
uuids = db.session.query(
UUID.uuid, UUID.max_score, Signature.signature, Stats.number, Stats.installs
).select_from(UUID)
uuids = uuids.join(Signature).join(Build)
uuids = uuids.join(
Stats, db.and_(Signature.id == Stats.signatureid, Build.id == Stats.buildid)
)
uuids = (
uuids.filter(
Build.buildid == buildid,
Build.product == product,
Build.channel == channel,
UUID.useless.is_(False),
UUID.analyzed.is_(True),
)
.distinct(UUID.id)
.order_by(UUID.id)
)
_res = {}
for uuid in uuids:
t = (uuid.uuid, uuid.max_score)
if uuid.signature in _res:
_res[uuid.signature]["uuids"].append(t)
else:
_res[uuid.signature] = {
"uuids": [t],
"number": uuid.number,
"installs": uuid.installs,
"url": utils.make_url_for_signature(
uuid.signature, buildid, sbid, channel, product
),
}
res = sorted(
_res.items(),
key=lambda p: (-p[1]["number"], -p[1]["installs"], p[0].lower()),
)
return res
@staticmethod
def get_uuids_from_buildid_no_score(buildid, product, channel):
sbid = buildid
buildid = utils.get_build_date(buildid)
uuids = db.session.query(
UUID.uuid, Signature.signature, Stats.number, Stats.installs
).select_from(UUID)
uuids = uuids.join(Signature).join(Build)
uuids = uuids.join(
Stats, db.and_(Signature.id == Stats.signatureid, Build.id == Stats.buildid)
)
uuids = (
uuids.filter(
Build.buildid == buildid,
Build.product == product,
Build.channel == channel,
UUID.useless.is_(True),
UUID.analyzed.is_(True),
)
.distinct(UUID.id)
.order_by(UUID.id)
)
_res = {}
for uuid in uuids:
if uuid.signature in _res:
_res[uuid.signature]["uuids"].append(uuid.uuid)
else:
_res[uuid.signature] = {
"uuids": [uuid.uuid],
"number": uuid.number,
"installs": uuid.installs,
"url": utils.make_url_for_signature(
uuid.signature, buildid, sbid, channel, product
),
}
res = sorted(
_res.items(),
key=lambda p: (-p[1]["number"], -p[1]["installs"], p[0].lower()),
)
return res
@staticmethod
def clean(date, channel):
date = datetime(date.year, date.month, date.day)
date += relativedelta(days=config.get_ndays())
db.session.query(UUID).filter(
UUID.buildid <= date, UUID.channel == channel
).delete()
db.session.commit()
@staticmethod
def get_id(uuid):
return db.session.query(UUID.id).filter(UUID.uuid == uuid).first()[0]
@staticmethod
def is_stackhash_existing(stackhash, buildid, channel, product, java):
if java:
r = (
db.session.query(UUID.id)
.join(Build)
.filter(
UUID.jstackhash == stackhash,
Build.buildid == buildid,
Build.channel == channel,
Build.product == product,
)
.first()
)
else:
r = (
db.session.query(UUID.id)
.join(Build)
.filter(
UUID.stackhash == stackhash,
Build.buildid == buildid,
Build.channel == channel,
Build.product == product,
)
.first()
)
return r is not None
@staticmethod
def get_buildids_from_pc(product, channel):
bids = db.session.query(UUID.id, Build.buildid).select_from(UUID).join(Build)
bids = (
bids.filter(
Build.product == product,
Build.channel == channel,
UUID.useless.is_(False),
UUID.analyzed.is_(True),
)
.distinct(Build.buildid)
.order_by(Build.buildid.desc())
)
res = [utils.get_buildid(bid.buildid) for bid in bids]
return res
@staticmethod
def get_buildids(no_score=False):
bids = (
db.session.query(
UUID.id, Build.product, Build.channel, Build.buildid, Build.version
)
.select_from(UUID)
.join(Build)
)
bids = (
bids.filter(UUID.useless.is_(no_score), UUID.analyzed.is_(True))
.distinct(Build.product, Build.channel, Build.buildid)
.order_by(Build.buildid.desc())
)
res = {}
for bid in bids:
b = utils.get_buildid(bid.buildid)
if bid.product in res:
r = res[bid.product]
if bid.channel in r:
r[bid.channel].append([b, bid.version])
else:
r[bid.channel] = [[b, bid.version]]
else:
res[bid.product] = {bid.channel: [[b, bid.version]]}
return res
class Score(db.Model):
__tablename__ = "scores"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
changesetid = db.Column(
db.Integer, db.ForeignKey("changesets.id", ondelete="CASCADE")
)
crashstackid = db.Column(
db.Integer, db.ForeignKey("crashstack.id", ondelete="CASCADE")
)
score = db.Column(db.Integer)
def __init__(self, changesetid, crashstackid, score):
self.changesetid = changesetid
self.crashstackid = crashstackid
self.score = score
@staticmethod
def set(data):
for changesetid, crashstackid, score in data:
db.session.add(Score(changesetid, crashstackid, score))
db.session.commit()
@staticmethod
def get_by_score(score):
qs = (
db.session.query(Score, UUID.uuid)
.select_from(Score)
.join(CrashStack)
.join(UUID)
)
qs = qs.filter(Score.score == score).distinct(UUID.id)
res = [uuid for _, uuid in qs]
return res
class CrashStack(db.Model):
__tablename__ = "crashstack"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
uuidid = db.Column(db.Integer, db.ForeignKey("uuids.id", ondelete="CASCADE"))
java = db.Column(db.Boolean)
stackpos = db.Column(db.Integer)
original = db.Column(db.String(512))
module = db.Column(db.String(128))
filename = db.Column(db.String(512))
function = db.Column(db.Text)
line = db.Column(db.Integer)
node = db.Column(db.String(12))
internal = db.Column(db.Boolean)
def __init__(
self,
uuidid,
stackpos,
java,
original,
module,
filename,
function,
line,
node,
internal,
):
self.uuidid = uuidid
self.stackpos = stackpos
self.java = java
self.original = original
self.module = module
self.filename = filename
self.function = function
self.line = line
self.node = node
self.internal = internal
@staticmethod
def delete(ids):
db.session.query(CrashStack).filter(CrashStack.uuidid.in_(ids)).delete(
synchronize_session=False
)
db.session.commit()
@staticmethod
def put_frames(uuid, frames, java, commit=True):
css = []
uuidid = UUID.get_id(uuid)
for frame in frames["frames"]:
cs = CrashStack(
uuidid,
frame["stackpos"],
java,
frame["original"],
frame["module"],
frame["filename"],
frame["function"],
frame["line"],
frame["node"],
frame["internal"],
)
db.session.add(cs)
css.append((cs, frame))
db.session.commit()
max_score = 0
for cs, frame in css:
csets = frame["changesets"]
if csets:
scores = Changeset.get_scores(
frame["filename"], frame["line"], csets, cs.id
)
if scores:
Score.set(scores)
scores = max(s for _, _, s in scores)
max_score = max(max_score, scores)
else:
logger.warning(
"No scores for {} at line {} and changesets {} (uuid {})".format(
frame["filename"], frame["line"], csets, uuid
)
)
UUID.set_max_score(uuidid, max_score)
@staticmethod
def get_by_uuid(uuid):
uuid_info = UUID.get_bid_chan_by_uuid(uuid)
if not uuid_info:
return {}, {}
uuidid = uuid_info["id"]
repo_url = Mercurial.get_repo_url(uuid_info["channel"])
is_java = uuid_info["java"]
iframes = (
db.session.query(
CrashStack.stackpos,
Node.node,
Node.backedout,
Node.pushdate,
Node.bug,
Node.id,
Score.score,
)
.select_from(CrashStack)
.join(Score)
.join(Changeset)
.join(Node)
)
iframes = iframes.filter(
CrashStack.uuidid == uuidid, CrashStack.java.is_(is_java)
).order_by(CrashStack.stackpos, Node.id.desc())
frames = (
db.session.query(CrashStack)
.filter(CrashStack.uuidid == uuidid, CrashStack.java.is_(is_java))
.order_by(CrashStack.stackpos)
)
stack = []
res = {"frames": stack}
for frame in frames:
url, filename = utils.get_file_url(
repo_url, frame.filename, frame.node, frame.line, frame.original
)
stack.append(
{
"stackpos": frame.stackpos,
"filename": filename,
"function": frame.function,
"changesets": OrderedDict(),
"line": frame.line,
"node": frame.node,
"original": frame.original,
"internal": frame.internal,
"url": url,
}
)
for stackpos, node, bout, pdate, bugid, nodeid, score in iframes:
stack[stackpos]["changesets"][node] = {
"score": score,
"backedout": bout,
"pushdate": pdate,
"bugid": bugid,
}
return res, uuid_info
def commit():
db.session.commit()
def create():
engine = db.engine
if not inspect(engine).has_table("lastdate"):
db.create_all()
db.session.commit()
return True
return False
def clear():
db.drop_all()
db.session.commit()