github.com/pachyderm/pachyderm@v1.13.4/etc/testing/introspect/introspect.ipynb (about)

     1  {
     2   "cells": [
     3    {
     4     "cell_type": "code",
     5     "execution_count": 415,
     6     "metadata": {},
     7     "outputs": [],
     8     "source": [
     9      "from circleci.api import Api\n",
    10      "import pprint\n",
    11      "from collections import defaultdict\n",
    12      "\n",
    13      "token = open(\".env\").readlines()[0].split(\"=\")[1].strip()\n",
    14      "circleci = Api(token)\n",
    15      "\n",
    16      "# get info about your user\n",
    17      "#pprint.pprint(circleci.get_user_info())\n",
    18      "\n",
    19      "# get list of all of your projects\n",
    20      "# --> build_num --> get_build_info() -> steps -> output_url -> fetch it -> x[0][\"message\"] is newline delim string\n",
    21      "# --> .outcome == \"failed\" e.g.\n",
    22      "\n",
    23      "results = defaultdict(lambda: defaultdict(int))\n",
    24      "\n",
    25      "# get results 100 at a time\n",
    26      "\n",
    27      "get_how_many = 32\n",
    28      "per_page = 100\n",
    29      "pages = max(1, int(get_how_many / per_page))\n",
    30      "builds = []\n",
    31      "\n",
    32      "for i in range(pages):\n",
    33      "    for build in circleci.get_project_build_summary(\n",
    34      "            \"pachyderm\", \"pachyderm\", limit=min(100, get_how_many), offset=i*per_page,\n",
    35      "        ):\n",
    36      "        builds.append(build)\n",
    37      "        outcome = build[\"outcome\"]\n",
    38      "        build_num = build[\"build_num\"]\n",
    39      "\n",
    40      "        if not outcome == \"failed\" and not outcome == \"success\":\n",
    41      "            continue\n",
    42      "\n",
    43      "        job = build[\"workflows\"][\"job_name\"]\n",
    44      "        #print(f\"build {build_num} {outcome} {job}\")\n",
    45      "        if job.startswith(\"test-\"):\n",
    46      "            results[job][outcome] += 1\n",
    47      "\n",
    48      "#builds = builds[:12]"
    49     ]
    50    },
    51    {
    52     "cell_type": "markdown",
    53     "metadata": {},
    54     "source": [
    55      "# Finding flakiest test suites\n",
    56      "\n",
    57      "The following produces a table of test suites ordered by flakiest suite first."
    58     ]
    59    },
    60    {
    61     "cell_type": "code",
    62     "execution_count": 416,
    63     "metadata": {},
    64     "outputs": [
    65      {
    66       "data": {
    67        "text/html": [
    68         "<div>\n",
    69         "<style scoped>\n",
    70         "    .dataframe tbody tr th:only-of-type {\n",
    71         "        vertical-align: middle;\n",
    72         "    }\n",
    73         "\n",
    74         "    .dataframe tbody tr th {\n",
    75         "        vertical-align: top;\n",
    76         "    }\n",
    77         "\n",
    78         "    .dataframe thead th {\n",
    79         "        text-align: right;\n",
    80         "    }\n",
    81         "</style>\n",
    82         "<table border=\"1\" class=\"dataframe\">\n",
    83         "  <thead>\n",
    84         "    <tr style=\"text-align: right;\">\n",
    85         "      <th></th>\n",
    86         "      <th>failed</th>\n",
    87         "      <th>success</th>\n",
    88         "      <th>pass_rate</th>\n",
    89         "    </tr>\n",
    90         "  </thead>\n",
    91         "  <tbody>\n",
    92         "    <tr>\n",
    93         "      <th>test-MISC</th>\n",
    94         "      <td>1.0</td>\n",
    95         "      <td>1.0</td>\n",
    96         "      <td>0.5</td>\n",
    97         "    </tr>\n",
    98         "    <tr>\n",
    99         "      <th>test-PFS</th>\n",
   100         "      <td>1.0</td>\n",
   101         "      <td>1.0</td>\n",
   102         "      <td>0.5</td>\n",
   103         "    </tr>\n",
   104         "    <tr>\n",
   105         "      <th>test-PPS4</th>\n",
   106         "      <td>1.0</td>\n",
   107         "      <td>1.0</td>\n",
   108         "      <td>0.5</td>\n",
   109         "    </tr>\n",
   110         "    <tr>\n",
   111         "      <th>test-PPS2</th>\n",
   112         "      <td>0.0</td>\n",
   113         "      <td>2.0</td>\n",
   114         "      <td>1.0</td>\n",
   115         "    </tr>\n",
   116         "    <tr>\n",
   117         "      <th>test-PPS3</th>\n",
   118         "      <td>0.0</td>\n",
   119         "      <td>1.0</td>\n",
   120         "      <td>1.0</td>\n",
   121         "    </tr>\n",
   122         "    <tr>\n",
   123         "      <th>test-PPS5</th>\n",
   124         "      <td>0.0</td>\n",
   125         "      <td>2.0</td>\n",
   126         "      <td>1.0</td>\n",
   127         "    </tr>\n",
   128         "    <tr>\n",
   129         "      <th>test-PPS1</th>\n",
   130         "      <td>0.0</td>\n",
   131         "      <td>2.0</td>\n",
   132         "      <td>1.0</td>\n",
   133         "    </tr>\n",
   134         "    <tr>\n",
   135         "      <th>test-ADMIN</th>\n",
   136         "      <td>0.0</td>\n",
   137         "      <td>2.0</td>\n",
   138         "      <td>1.0</td>\n",
   139         "    </tr>\n",
   140         "    <tr>\n",
   141         "      <th>test-EXAMPLES</th>\n",
   142         "      <td>0.0</td>\n",
   143         "      <td>2.0</td>\n",
   144         "      <td>1.0</td>\n",
   145         "    </tr>\n",
   146         "    <tr>\n",
   147         "      <th>test-AUTH1</th>\n",
   148         "      <td>0.0</td>\n",
   149         "      <td>2.0</td>\n",
   150         "      <td>1.0</td>\n",
   151         "    </tr>\n",
   152         "    <tr>\n",
   153         "      <th>test-PPS6</th>\n",
   154         "      <td>0.0</td>\n",
   155         "      <td>2.0</td>\n",
   156         "      <td>1.0</td>\n",
   157         "    </tr>\n",
   158         "    <tr>\n",
   159         "      <th>test-AUTH2</th>\n",
   160         "      <td>0.0</td>\n",
   161         "      <td>1.0</td>\n",
   162         "      <td>1.0</td>\n",
   163         "    </tr>\n",
   164         "  </tbody>\n",
   165         "</table>\n",
   166         "</div>"
   167        ],
   168        "text/plain": [
   169         "               failed  success  pass_rate\n",
   170         "test-MISC         1.0      1.0        0.5\n",
   171         "test-PFS          1.0      1.0        0.5\n",
   172         "test-PPS4         1.0      1.0        0.5\n",
   173         "test-PPS2         0.0      2.0        1.0\n",
   174         "test-PPS3         0.0      1.0        1.0\n",
   175         "test-PPS5         0.0      2.0        1.0\n",
   176         "test-PPS1         0.0      2.0        1.0\n",
   177         "test-ADMIN        0.0      2.0        1.0\n",
   178         "test-EXAMPLES     0.0      2.0        1.0\n",
   179         "test-AUTH1        0.0      2.0        1.0\n",
   180         "test-PPS6         0.0      2.0        1.0\n",
   181         "test-AUTH2        0.0      1.0        1.0"
   182        ]
   183       },
   184       "execution_count": 416,
   185       "metadata": {},
   186       "output_type": "execute_result"
   187      }
   188     ],
   189     "source": [
   190      "import pandas as pd\n",
   191      "df = pd.DataFrame.from_dict(results)\n",
   192      "\n",
   193      "# Transpose\n",
   194      "df = df.T\n",
   195      "\n",
   196      "# NaN -> 0\n",
   197      "df = df.fillna(0)\n",
   198      "\n",
   199      "df['pass_rate'] = df['success'] / (df['failed'] + df['success'])\n",
   200      "\n",
   201      "# sort by failed\n",
   202      "df = df.sort_values(by=[\"pass_rate\"])\n",
   203      "\n",
   204      "df"
   205     ]
   206    },
   207    {
   208     "cell_type": "markdown",
   209     "metadata": {},
   210     "source": [
   211      "# Finding flakiest individual tests\n",
   212      "Now we fetch the logs for each individual test and find the flakiest individual tests."
   213     ]
   214    },
   215    {
   216     "cell_type": "code",
   217     "execution_count": 417,
   218     "metadata": {},
   219     "outputs": [],
   220     "source": [
   221      "import requests\n",
   222      "cache = {}\n",
   223      "build_info_cache = {}"
   224     ]
   225    },
   226    {
   227     "cell_type": "code",
   228     "execution_count": 418,
   229     "metadata": {},
   230     "outputs": [],
   231     "source": [
   232      "build_map = {}"
   233     ]
   234    },
   235    {
   236     "cell_type": "code",
   237     "execution_count": 419,
   238     "metadata": {},
   239     "outputs": [],
   240     "source": [
   241      "def parse_build(build):\n",
   242      "    tests = []\n",
   243      "    failed = set()\n",
   244      "    passed = set()\n",
   245      "    skipped = set()\n",
   246      "    try:\n",
   247      "        output_url = build[\"steps\"][5][\"actions\"][0][\"output_url\"]\n",
   248      "    except Exception as e:\n",
   249      "        print(f\"Got error: {e}, continuing...\")\n",
   250      "        return set(), set(), set()\n",
   251      "    \n",
   252      "    if output_url in cache:\n",
   253      "        lines = cache[output_url]\n",
   254      "    else:\n",
   255      "        lines = requests.get(output_url).json()\n",
   256      "        cache[output_url] = lines\n",
   257      "\n",
   258      "    for line in lines[0][\"message\"].split(\"\\n\"):\n",
   259      "        #print(line)\n",
   260      "        if \"RUN\" in line:\n",
   261      "            parts = line.split(\"RUN\")\n",
   262      "            if len(parts) != 2:\n",
   263      "                continue\n",
   264      "            preamble, test = parts\n",
   265      "            test = test.strip()\n",
   266      "            # ignore docker RUN lines which contain e.g. \"#10\"\n",
   267      "            if \"#\" in preamble or \"Step\" in preamble:\n",
   268      "                continue\n",
   269      "            if len(test) > 100:\n",
   270      "                # some base64 gunk\n",
   271      "                continue\n",
   272      "            if \"AcceptEnv\" in test or \"cd pachyderm\" in test \\\n",
   273      "                    or \"git clone\" in test or \"NING\" in test or \"_BAD_TESTS\" in test:\n",
   274      "                continue\n",
   275      "            tests.append(test)\n",
   276      "        if \"FAIL\" in line:\n",
   277      "            test = line.split(\"FAIL\")[1].replace(\":\", \"\").strip().split(\"(\")[0].strip()\n",
   278      "            if \"github.com\" in test or test == \"\":\n",
   279      "                # filter out some more noise\n",
   280      "                continue\n",
   281      "            if len(test) > 100:\n",
   282      "                # some base64 gunk\n",
   283      "                continue\n",
   284      "            failed.add(test)\n",
   285      "        if \"PASS\" in line:\n",
   286      "            test = line.split(\"PASS\")[1].replace(\":\", \"\").strip().split(\"(\")[0].strip()\n",
   287      "            if test == \"\":\n",
   288      "                # This happens when all the tests pass, we get a \"PASS\" on its own.\n",
   289      "                continue\n",
   290      "            if len(test) > 100:\n",
   291      "                # some base64 gunk\n",
   292      "                continue\n",
   293      "            if \"\\\\n\" in test:\n",
   294      "                continue\n",
   295      "            passed.add(test)\n",
   296      "        if \"SKIP\" in line:\n",
   297      "            test = line.split(\"SKIP\")[1].replace(\":\", \"\").strip().split(\"(\")[0].strip()\n",
   298      "            if test == \"\":\n",
   299      "                # This happens when all the tests pass, we get a \"PASS\" on its own.\n",
   300      "                continue\n",
   301      "            if len(test) > 100:\n",
   302      "                # some base64 gunk\n",
   303      "                continue\n",
   304      "            if \"\\\\n\" in test:\n",
   305      "                continue\n",
   306      "            skipped.add(test)\n",
   307      "\n",
   308      "    all_tests = set(tests)\n",
   309      "    for test in all_tests:\n",
   310      "        if test not in build_map:\n",
   311      "            build_map[test] = build[\"workflows\"][\"job_name\"], \\\n",
   312      "            f\"<a target='_blank' href='{build['build_url']}'>{build['build_num']}</a>\"\n",
   313      "    hung = all_tests - failed - passed - skipped\n",
   314      "    assert all_tests == (failed | passed | hung | skipped), \\\n",
   315      "        f\"all={all_tests}, failed={failed}, passed={passed}, hung={hung}, skipped={skipped}\"\n",
   316      "    return passed, failed, hung"
   317     ]
   318    },
   319    {
   320     "cell_type": "code",
   321     "execution_count": 420,
   322     "metadata": {},
   323     "outputs": [
   324      {
   325       "name": "stdout",
   326       "output_type": "stream",
   327       "text": [
   328        ".Got error: list index out of range, continuing...\n",
   329        ".Got error: list index out of range, continuing...\n",
   330        ".Got error: list index out of range, continuing...\n",
   331        ".Got error: list index out of range, continuing...\n",
   332        ".Got error: list index out of range, continuing...\n",
   333        ".Got error: list index out of range, continuing...\n",
   334        ".Got error: list index out of range, continuing...\n",
   335        ".Got error: list index out of range, continuing...\n",
   336        ".Got error: list index out of range, continuing...\n",
   337        ".Got error: list index out of range, continuing...\n",
   338        "......................"
   339       ]
   340      }
   341     ],
   342     "source": [
   343      "build_results = defaultdict(lambda: defaultdict(int))\n",
   344      "\n",
   345      "for build in builds:\n",
   346      "    print(\".\", end=\"\")\n",
   347      "    if build[\"build_num\"] in build_info_cache:\n",
   348      "        build_info = build_info_cache[build[\"build_num\"]]\n",
   349      "    else:\n",
   350      "        build_info = circleci.get_build_info(\"pachyderm\", \"pachyderm\", build[\"build_num\"])\n",
   351      "        build_info_cache[build[\"build_num\"]] = build_info\n",
   352      "    passed, failed, hung = parse_build(build_info)\n",
   353      "    for b in passed:\n",
   354      "        build_results[b][\"pass\"] += 1\n",
   355      "        build_results[b][\"bucket\"] = build_map[b][0]\n",
   356      "    for b in failed:\n",
   357      "        build_results[b][\"fail\"] += 1\n",
   358      "        build_results[b][\"bucket\"] = build_map[b][0]\n",
   359      "        build_results[b][\"example\"] = build_map[b][1]\n",
   360      "    for b in hung:\n",
   361      "        build_results[b][\"hung\"] += 1\n",
   362      "        build_results[b][\"bucket\"] = build_map[b][0]\n",
   363      "        build_results[b][\"example\"] = build_map[b][1]"
   364     ]
   365    },
   366    {
   367     "cell_type": "code",
   368     "execution_count": 421,
   369     "metadata": {},
   370     "outputs": [
   371      {
   372       "ename": "KeyError",
   373       "evalue": "'fail'",
   374       "output_type": "error",
   375       "traceback": [
   376        "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
   377        "\u001b[0;31mKeyError\u001b[0m                                  Traceback (most recent call last)",
   378        "\u001b[0;32m~/.local/lib/python3.8/site-packages/pandas/core/indexes/base.py\u001b[0m in \u001b[0;36mget_loc\u001b[0;34m(self, key, method, tolerance)\u001b[0m\n\u001b[1;32m   2888\u001b[0m             \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 2889\u001b[0;31m                 \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_engine\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_loc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcasted_key\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m   2890\u001b[0m             \u001b[0;32mexcept\u001b[0m \u001b[0mKeyError\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0merr\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
   379        "\u001b[0;32mpandas/_libs/index.pyx\u001b[0m in \u001b[0;36mpandas._libs.index.IndexEngine.get_loc\u001b[0;34m()\u001b[0m\n",
   380        "\u001b[0;32mpandas/_libs/index.pyx\u001b[0m in \u001b[0;36mpandas._libs.index.IndexEngine.get_loc\u001b[0;34m()\u001b[0m\n",
   381        "\u001b[0;32mpandas/_libs/hashtable_class_helper.pxi\u001b[0m in \u001b[0;36mpandas._libs.hashtable.PyObjectHashTable.get_item\u001b[0;34m()\u001b[0m\n",
   382        "\u001b[0;32mpandas/_libs/hashtable_class_helper.pxi\u001b[0m in \u001b[0;36mpandas._libs.hashtable.PyObjectHashTable.get_item\u001b[0;34m()\u001b[0m\n",
   383        "\u001b[0;31mKeyError\u001b[0m: 'fail'",
   384        "\nThe above exception was the direct cause of the following exception:\n",
   385        "\u001b[0;31mKeyError\u001b[0m                                  Traceback (most recent call last)",
   386        "\u001b[0;32m<ipython-input-421-d2968774b94e>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m      8\u001b[0m \u001b[0mdf\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mdf\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfillna\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      9\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 10\u001b[0;31m \u001b[0mdf\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'hang_rate'\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mdf\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'hung'\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m/\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mdf\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'pass'\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mdf\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'fail'\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mdf\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'hung'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m     11\u001b[0m \u001b[0mdf\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'fail_rate'\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mdf\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'fail'\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m/\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mdf\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'pass'\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mdf\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'fail'\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mdf\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'hung'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     12\u001b[0m \u001b[0mdf\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'hang_or_fail'\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mdf\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'hang_rate'\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mdf\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'fail_rate'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
   387        "\u001b[0;32m~/.local/lib/python3.8/site-packages/pandas/core/frame.py\u001b[0m in \u001b[0;36m__getitem__\u001b[0;34m(self, key)\u001b[0m\n\u001b[1;32m   2897\u001b[0m             \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcolumns\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnlevels\u001b[0m \u001b[0;34m>\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   2898\u001b[0m                 \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_getitem_multilevel\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mkey\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 2899\u001b[0;31m             \u001b[0mindexer\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcolumns\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_loc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mkey\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m   2900\u001b[0m             \u001b[0;32mif\u001b[0m \u001b[0mis_integer\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mindexer\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   2901\u001b[0m                 \u001b[0mindexer\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0mindexer\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
   388        "\u001b[0;32m~/.local/lib/python3.8/site-packages/pandas/core/indexes/base.py\u001b[0m in \u001b[0;36mget_loc\u001b[0;34m(self, key, method, tolerance)\u001b[0m\n\u001b[1;32m   2889\u001b[0m                 \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_engine\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_loc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcasted_key\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   2890\u001b[0m             \u001b[0;32mexcept\u001b[0m \u001b[0mKeyError\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0merr\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 2891\u001b[0;31m                 \u001b[0;32mraise\u001b[0m \u001b[0mKeyError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mkey\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0merr\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m   2892\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   2893\u001b[0m         \u001b[0;32mif\u001b[0m \u001b[0mtolerance\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
   389        "\u001b[0;31mKeyError\u001b[0m: 'fail'"
   390       ]
   391      }
   392     ],
   393     "source": [
   394      "import pandas as pd\n",
   395      "df = pd.DataFrame.from_dict(build_results)\n",
   396      "\n",
   397      "# Transpose\n",
   398      "df = df.T\n",
   399      "\n",
   400      "# NaN -> 0\n",
   401      "df = df.fillna(0)\n",
   402      "\n",
   403      "df['hang_rate'] = df['hung'] / (df['pass'] + df['fail'] + df['hung'])\n",
   404      "df['fail_rate'] = df['fail'] / (df['pass'] + df['fail'] + df['hung'])\n",
   405      "df['hang_or_fail'] = df['hang_rate'] + df['fail_rate']\n",
   406      "\n",
   407      "hangy = df[df[\"hang_rate\"] > 0]\n",
   408      "faily = df[df[\"fail_rate\"] > 0]\n",
   409      "\n",
   410      "# sort by failed\n",
   411      "hangy = hangy.sort_values(by=[\"hang_rate\"], ascending=False)\n",
   412      "faily = hangy.sort_values(by=[\"fail_rate\"], ascending=False)\n",
   413      "\n",
   414      "bad = df[df[\"hang_or_fail\"] > 0]\n",
   415      "bad = bad.sort_values(by=[\"hang_or_fail\"], ascending=False)"
   416     ]
   417    },
   418    {
   419     "cell_type": "code",
   420     "execution_count": null,
   421     "metadata": {},
   422     "outputs": [],
   423     "source": [
   424      "pandas.set_option('display.max_rows', None)"
   425     ]
   426    },
   427    {
   428     "cell_type": "code",
   429     "execution_count": null,
   430     "metadata": {},
   431     "outputs": [],
   432     "source": [
   433      "from IPython.display import HTML\n",
   434      "HTML(bad.to_html(escape=False))"
   435     ]
   436    },
   437    {
   438     "cell_type": "code",
   439     "execution_count": null,
   440     "metadata": {},
   441     "outputs": [],
   442     "source": []
   443    }
   444   ],
   445   "metadata": {
   446    "kernelspec": {
   447     "display_name": "Python 3",
   448     "language": "python",
   449     "name": "python3"
   450    },
   451    "language_info": {
   452     "codemirror_mode": {
   453      "name": "ipython",
   454      "version": 3
   455     },
   456     "file_extension": ".py",
   457     "mimetype": "text/x-python",
   458     "name": "python",
   459     "nbconvert_exporter": "python",
   460     "pygments_lexer": "ipython3",
   461     "version": "3.8.5"
   462    }
   463   },
   464   "nbformat": 4,
   465   "nbformat_minor": 4
   466  }