Example #1
0
def _get_recent_run_data(weeks,
                         group_field,
                         project=None,
                         additional_filters=None):
    """Get all the data from the time period and aggregate the results"""
    data = {
        "passed": {},
        "skipped": {},
        "error": {},
        "failed": {},
        "xfailed": {},
        "xpassed": {}
    }
    delta = timedelta(weeks=weeks).total_seconds()
    current_time = time.time()
    time_period_in_sec = current_time - delta

    # create filters for start time and that the group_field exists
    filters = [
        f"start_time>{datetime.utcfromtimestamp(time_period_in_sec)}",
        f"{group_field}@y"
    ]
    if additional_filters:
        filters.extend(additional_filters.split(","))
    if project:
        filters.append(f"project_id={project}")

    # generate the group field
    group_field = string_to_column(group_field, Run)

    # create the query
    query = session.query(
        group_field,
        func.sum(Run.summary["failures"].cast(Float)),
        func.sum(Run.summary["errors"].cast(Float)),
        func.sum(Run.summary["skips"].cast(Float)),
        func.sum(Run.summary["tests"].cast(Float)),
        func.sum(Run.summary["xpassed"].cast(Float)),
        func.sum(Run.summary["xfailed"].cast(Float)),
    ).group_by(group_field)

    # filter the query
    query = apply_filters(query, filters, Run)

    # make the query
    query_data = query.all()

    # parse the data
    for group, failed, error, skipped, total, xpassed, xfailed in query_data:
        # convert all data to percentages
        data["failed"][group] = int(round((failed / total) * 100.0))
        data["error"][group] = int(round((error / total) * 100.0))
        data["skipped"][group] = int(round((skipped / total) * 100.0))
        data["xpassed"][group] = int(round((xpassed or 0.0 / total) * 100.0))
        data["xfailed"][group] = int(round((xfailed or 0.0 / total) * 100.0))
        data["passed"][group] = int(
            100 - (data["failed"][group] + data["error"][group] +
                   data["skipped"][group] + data["xfailed"][group] +
                   data["xpassed"][group]))
    return data
Example #2
0
def get_result_summary(source=None,
                       env=None,
                       job_name=None,
                       project=None,
                       additional_filters=None):
    """Get a summary of results"""
    summary = {
        "error": 0,
        "skipped": 0,
        "failed": 0,
        "passed": 0,
        "total": 0,
        "xfailed": 0,
        "xpassed": 0,
    }
    query = session.query(
        func.sum(Run.summary["errors"].cast(Integer)),
        func.sum(Run.summary["skips"].cast(Integer)),
        func.sum(Run.summary["failures"].cast(Integer)),
        func.sum(Run.summary["tests"].cast(Integer)),
        func.sum(Run.summary["xfailures"].cast(Integer)),
        func.sum(Run.summary["xpasses"].cast(Integer)),
    )

    # parse any filters
    filters = []
    if source:
        filters.append(f"source={source}")
    if env:
        filters.append(f"env={env}")
    if job_name:
        filters.append(f"metadata.jenkins.job_name={job_name}")
    if project:
        filters.append(f"project_id={project}")
    if additional_filters:
        filters.extend(additional_filters.split(","))

    # TODO: implement some page size here?
    if filters:
        query = apply_filters(query, filters, Run)

    # get the total number
    query_data = query.all()

    # parse the data
    for error, skipped, failed, total, xfailed, xpassed in query_data:
        summary["error"] += error
        summary["skipped"] += skipped
        summary["failed"] += failed
        summary["total"] += total
        summary["xfailed"] += xfailed or 0.0
        summary["xpassed"] += xpassed or 0.0
        summary["passed"] += total - (error + skipped + failed + xpassed +
                                      xfailed)

    return summary
Example #3
0
def _create_result(tar,
                   run_id,
                   result,
                   artifacts,
                   project_id=None,
                   metadata=None):
    """Create a result with artifacts, used in the archive importer"""
    old_id = None
    result_id = result.get("id")
    if is_uuid(result_id):
        result_record = session.query(Result).get(result_id)
    else:
        result_record = None
    if result_record:
        result_record.run_id = run_id
    else:
        old_id = result["id"]
        if "id" in result:
            result.pop("id")
        result["run_id"] = run_id
        if project_id:
            result["project_id"] = project_id
        if metadata:
            result["metadata"] = result.get("metadata", {})
            result["metadata"].update(metadata)
        result["env"] = result.get("metadata", {}).get("env")
        result["component"] = result.get("metadata", {}).get("component")
        result_record = Result.from_dict(**result)
    session.add(result_record)
    session.commit()
    result = result_record.to_dict()
    for artifact in artifacts:
        session.add(
            Artifact(
                filename=artifact.name.split("/")[-1],
                result_id=result["id"],
                data={
                    "contentType": "text/plain",
                    "resultId": result["id"]
                },
                content=tar.extractfile(artifact).read(),
            ))
    session.commit()
    return old_id
Example #4
0
def _get_builds(job_name, builds, project=None, additional_filters=None):
    filters = [
        f"metadata.jenkins.job_name={job_name}",
        "metadata.jenkins.build_number@y"
    ]
    if additional_filters:
        filters.extend(additional_filters.split(","))
    if project:
        filters.append(f"project_id={project}")

    # generate the group_field
    group_field = string_to_column("metadata.jenkins.build_number", Run)

    # get the runs on which to run the aggregation, we select from a subset of runs to improve
    # performance, otherwise we'd be aggregating over ALL runs
    heatmap_run_limit = int((HEATMAP_RUN_LIMIT / HEATMAP_MAX_BUILDS) * builds)
    sub_query = (apply_filters(Run.query, filters, Run).order_by(
        desc("start_time")).limit(heatmap_run_limit).subquery())

    # create the query
    query = (session.query(
        func.min(Run.start_time).label("min_start_time"),
        group_field.cast(Integer).label("build_number"),
    ).select_entity_from(sub_query).group_by("build_number").order_by(
        desc("min_start_time")))

    # add filters to the query
    query = apply_filters(query, filters, Run)

    # make the query
    builds = [build for build in query.limit(builds)]
    min_start_times = [build.min_start_time for build in builds]
    if min_start_times:
        min_start_time = min(build.min_start_time for build in builds)
    else:
        min_start_time = None
    return min_start_time, [str(build.build_number) for build in builds]
Example #5
0
def run_archive_import(import_):
    """Import a test run from an Ibutsu archive file"""
    # Update the status of the import
    import_record = Import.query.get(str(import_["id"]))
    metadata = {}
    if import_record.data.get("metadata"):
        # metadata is expected to be a json dict
        metadata = import_record.data["metadata"]
    _update_import_status(import_record, "running")
    # Fetch the file contents
    import_file = ImportFile.query.filter(
        ImportFile.import_id == import_["id"]).first()
    if not import_file:
        _update_import_status(import_record, "error")
        return

    # First open the tarball and pull in the results
    run = None
    run_artifacts = []
    results = []
    result_artifacts = {}
    start_time = None
    file_object = BytesIO(import_file.content)
    with tarfile.open(fileobj=file_object) as tar:
        for member in tar.getmembers():
            # We don't care about directories, skip them
            if member.isdir():
                continue
            # Grab the run id
            run_id, rest = member.name.split("/", 1)
            if "/" not in rest:
                if member.name.endswith("run.json"):
                    run = json.loads(tar.extractfile(member).read())
                else:
                    run_artifacts.append(member)
                continue
            result_id, file_name = rest.split("/")
            if member.name.endswith("result.json"):
                result = json.loads(tar.extractfile(member).read())
                result_start_time = result.get("start_time")
                if not start_time or start_time > result_start_time:
                    start_time = result_start_time
                results.append(result)
            else:
                try:
                    result_artifacts[result_id].append(member)
                except KeyError:
                    result_artifacts[result_id] = [member]
        run_dict = run or {
            "duration": 0,
            "summary": {
                "errors": 0,
                "failures": 0,
                "skips": 0,
                "xfailures": 0,
                "xpasses": 0,
                "tests": 0,
            },
        }
        # patch things up a bit, if necessary
        run_dict["metadata"] = run_dict.get("metadata", {})
        run_dict["metadata"].update(metadata)
        _populate_metadata(run_dict, import_record)
        _populate_created_times(run_dict, start_time)

        # If this run has a valid ID, check if this run exists
        if is_uuid(run_dict.get("id")):
            run = session.query(Run).get(run_dict["id"])
        if run:
            run.update(run_dict)
        else:
            run = Run.from_dict(**run_dict)
        session.add(run)
        session.commit()
        import_record.run_id = run.id
        import_record.data["run_id"] = [run.id]
        # Loop through any artifacts associated with the run and upload them
        for artifact in run_artifacts:

            session.add(
                Artifact(
                    filename=artifact.name.split("/")[-1],
                    run_id=run.id,
                    data={
                        "contentType": "text/plain",
                        "runId": run.id
                    },
                    content=tar.extractfile(artifact).read(),
                ))
        # Now loop through all the results, and create or update them
        for result in results:
            artifacts = result_artifacts.get(result["id"], [])
            _create_result(
                tar,
                run.id,
                result,
                artifacts,
                project_id=run_dict.get("project_id")
                or import_record.data.get("project_id"),
                metadata=metadata,
            )
    # Update the import record
    _update_import_status(import_record, "done")
    if run:
        update_run.delay(run.id)
Example #6
0
def _get_heatmap(job_name,
                 builds,
                 group_field,
                 count_skips,
                 project=None,
                 additional_filters=None):
    """Get Jenkins Heatmap Data."""

    # Get the distinct builds that exist in the DB
    min_start_time, builds = _get_builds(job_name, builds, project,
                                         additional_filters)

    # Create the filters for the query
    filters = [
        f"metadata.jenkins.job_name={job_name}",
        f"metadata.jenkins.build_number*{';'.join(builds)}",
        f"{group_field}@y",
    ]
    if additional_filters:
        filters.extend(additional_filters.split(","))
    if min_start_time:
        filters.append(f"start_time){min_start_time}")
    if project:
        filters.append(f"project_id={project}")

    # generate the group_fields
    group_field = string_to_column(group_field, Run)
    job_name = string_to_column("metadata.jenkins.job_name", Run)
    build_number = string_to_column("metadata.jenkins.build_number", Run)
    annotations = string_to_column("metadata.annotations", Run)

    # create the base query
    query = session.query(
        Run.id.label("run_id"),
        annotations.label("annotations"),
        group_field.label("group_field"),
        job_name.label("job_name"),
        build_number.label("build_number"),
        func.sum(Run.summary["failures"].cast(Float)).label("failures"),
        func.sum(Run.summary["errors"].cast(Float)).label("errors"),
        func.sum(Run.summary["skips"].cast(Float)).label("skips"),
        func.sum(Run.summary["xfailures"].cast(Float)).label("xfailures"),
        func.sum(Run.summary["xpasses"].cast(Float)).label("xpasses"),
        func.sum(Run.summary["tests"].cast(Float)).label("total"),
    ).group_by(group_field, job_name, build_number, Run.id)

    # add filters to the query
    query = apply_filters(query, filters, Run)

    # convert the base query to a sub query
    subquery = query.subquery()

    # create the main query (this allows us to do math on the SQL side)
    if count_skips:
        passes = subquery.c.total - (subquery.c.errors + subquery.c.skips +
                                     subquery.c.failures + subquery.c.xpasses +
                                     subquery.c.xfailures)
    else:
        passes = subquery.c.total - (subquery.c.errors + subquery.c.failures +
                                     subquery.c.xpasses + subquery.c.xfailures)

    query = session.query(
        subquery.c.group_field,
        subquery.c.build_number,
        subquery.c.run_id,
        subquery.c.annotations,
        # handle potential division by 0 errors, if the total is 0, set the pass_percent to 0
        case(
            [
                (subquery.c.total == 0, 0),
            ],
            else_=(100 * passes / subquery.c.total),
        ).label("pass_percent"),
    )

    # parse the data for the frontend
    query_data = query.all()
    data = {datum.group_field: [] for datum in query_data}
    for datum in query_data:
        data[datum.group_field].append([
            round(datum.pass_percent, 2), datum.run_id, datum.annotations,
            datum.build_number
        ])
    # compute the slope for each component
    data_with_slope = data.copy()
    for key, value in data.items():
        slope_info = _calculate_slope([v[0] for v in value])
        data_with_slope[key].insert(0, [slope_info, 0])

    return data_with_slope, builds
def _get_jenkins_aggregation(filters=None,
                             project=None,
                             page=1,
                             page_size=25,
                             run_limit=None):
    """Get a list of Jenkins jobs"""
    offset = (page * page_size) - page_size

    # first create the filters
    query_filters = [
        "metadata.jenkins.build_number@y", "metadata.jenkins.job_name@y"
    ]
    if filters:
        for idx, filter in enumerate(filters):
            if "job_name" in filter or "build_number" in filter:
                filters[idx] = f"metadata.jenkins.{filter}"
        query_filters.extend(filters)
    if project:
        query_filters.append(f"project_id={project}")
    filters = query_filters

    # generate the group_fields
    job_name = string_to_column("metadata.jenkins.job_name", Run)
    build_number = string_to_column("metadata.jenkins.build_number", Run)
    build_url = string_to_column("metadata.jenkins.build_url", Run)
    env = string_to_column("env", Run)

    # get the runs on which to run the aggregation, we select from a subset of runs to improve
    # performance, otherwise we'd be aggregating over ALL runs
    sub_query = (apply_filters(Run.query, filters, Run).order_by(
        desc("start_time")).limit(run_limit or JJV_RUN_LIMIT).subquery())

    # create the base query
    query = (session.query(
        job_name.label("job_name"),
        build_number.label("build_number"),
        func.min(build_url.cast(Text)).label("build_url"),
        func.min(env).label("env"),
        func.min(Run.source).label("source"),
        func.sum(Run.summary["xfailures"].cast(Integer)).label("xfailures"),
        func.sum(Run.summary["xpasses"].cast(Integer)).label("xpasses"),
        func.sum(Run.summary["failures"].cast(Integer)).label("failures"),
        func.sum(Run.summary["errors"].cast(Integer)).label("errors"),
        func.sum(Run.summary["skips"].cast(Integer)).label("skips"),
        func.sum(Run.summary["tests"].cast(Integer)).label("tests"),
        func.min(Run.start_time).label("min_start_time"),
        func.max(Run.start_time).label("max_start_time"),
        func.sum(Run.duration).label("total_execution_time"),
        func.max(Run.duration).label("max_duration"),
    ).select_entity_from(sub_query).group_by(job_name, build_number).order_by(
        desc("max_start_time")))

    # apply filters to the query
    query = apply_filters(query, filters, Run)

    # apply pagination and get data
    query_data = query.offset(offset).limit(page_size).all()

    # parse the data for the frontend
    data = {
        "jobs": [],
        "pagination": {
            "page": page,
            "pageSize": page_size,
            "totalItems": query.count(),  # TODO: examine performance here
        },
    }
    for datum in query_data:
        data["jobs"].append({
            "_id":
            f"{datum.job_name}-{datum.build_number}",
            "build_number":
            datum.build_number,
            "build_url":
            datum.build_url,
            "duration":
            (datum.max_start_time.timestamp() -
             datum.min_start_time.timestamp()) + datum.max_duration,  # noqa
            "env":
            datum.env,
            "job_name":
            datum.job_name,
            "source":
            datum.source,
            "start_time":
            datum.min_start_time,
            "summary": {
                "xfailures":
                datum.xfailures,
                "xpasses":
                datum.xpasses,
                "errors":
                datum.errors,
                "failures":
                datum.failures,
                "skips":
                datum.skips,
                "tests":
                datum.tests,
                "passes":
                datum.tests - (datum.errors + datum.failures + datum.skips),
            },
            "total_execution_time":
            datum.total_execution_time,
        })

    return data