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()