add mixed load test, other fixes
diff --git a/wally/report.py b/wally/report.py
index 2aa5338..e893c25 100644
--- a/wally/report.py
+++ b/wally/report.py
@@ -128,14 +128,13 @@
             num_res += len(result.raw_result['jobs'])
             for job_info in result.raw_result['jobs']:
                 for k, v in job_info['latency_ms'].items():
-                    if isinstance(k, str):
-                        assert k[:2] == '>='
+                    if isinstance(k, basestring) and k.startswith('>='):
                         lat_mks[int(k[2:]) * 1000] += v
                     else:
-                        lat_mks[k * 1000] += v
+                        lat_mks[int(k) * 1000] += v
 
                 for k, v in job_info['latency_us'].items():
-                    lat_mks[k] += v
+                    lat_mks[int(k)] += v
 
         for k, v in lat_mks.items():
             lat_mks[k] = float(v) / num_res
@@ -675,6 +674,68 @@
     return render_all_html(comment, di, lab_info, images, "report_ceph.html")
 
 
+@report('mixed', 'mixed')
+def make_mixed_report(processed_results, lab_info, comment):
+    #
+    # IOPS(X% read) = 100 / ( X / IOPS_W + (100 - X) / IOPS_R )
+    #
+    is_ssd = True
+    mixed = collections.defaultdict(lambda: [])
+    for res in processed_results.values():
+        if res.name.startswith('mixed'):
+            if res.name.startswith('mixed-ssd'):
+                is_ssd = True
+            mixed[res.concurence].append((res.p.rwmixread,
+                                          res.lat.average / 1000.0,
+                                          res.lat.deviation / 1000.0,
+                                          res.iops.average,
+                                          res.iops.deviation))
+
+    if len(mixed) == 0:
+        raise ValueError("No mixed load found")
+
+    fig, p1 = plt.subplots()
+    p2 = p1.twinx()
+
+    colors = ['red', 'green', 'blue', 'orange', 'magenta', "teal"]
+    colors_it = iter(colors)
+    for conc, mix_lat_iops in sorted(mixed.items()):
+        mix_lat_iops = sorted(mix_lat_iops)
+        read_perc, lat, dev, iops, iops_dev = zip(*mix_lat_iops)
+        p1.errorbar(read_perc, iops, color=next(colors_it),
+                    yerr=iops_dev, label=str(conc) + " th")
+
+        p2.errorbar(read_perc, lat, color=next(colors_it),
+                    ls='--', yerr=dev, label=str(conc) + " th lat")
+
+    if is_ssd:
+        p1.set_yscale('log')
+        p2.set_yscale('log')
+
+    p1.set_xlim(-5, 105)
+
+    read_perc = set(read_perc)
+    read_perc.add(0)
+    read_perc.add(100)
+    read_perc = sorted(read_perc)
+
+    plt.xticks(read_perc, map(str, read_perc))
+
+    p1.grid(True)
+    p1.set_xlabel("% of reads")
+    p1.set_ylabel("Mixed IOPS")
+    p2.set_ylabel("Latency, ms")
+
+    handles1, labels1 = p1.get_legend_handles_labels()
+    handles2, labels2 = p2.get_legend_handles_labels()
+    plt.subplots_adjust(top=0.85)
+    plt.legend(handles1 + handles2, labels1 + labels2,
+               bbox_to_anchor=(0.5, 1.15),
+               loc='upper center',
+               prop={'size': 12}, ncol=3)
+    plt.show()
+
+
 def make_load_report(idx, results_dir, fname):
     dpath = os.path.join(results_dir, "io_" + str(idx))
     files = sorted(os.listdir(dpath))
@@ -751,13 +812,16 @@
                     logger.exception("Diring {0} report generation".format(name))
                     continue
 
-                try:
-                    with open(hpath, "w") as fd:
-                        fd.write(report)
-                except:
-                    logger.exception("Diring saving {0} report".format(name))
-                    continue
-                logger.info("Report {0} saved into {1}".format(name, hpath))
+                if report is not None:
+                    try:
+                        with open(hpath, "w") as fd:
+                            fd.write(report)
+                    except:
+                        logger.exception("Diring saving {0} report".format(name))
+                        continue
+                    logger.info("Report {0} saved into {1}".format(name, hpath))
+                else:
+                    logger.warning("No report produced by {0!r}".format(name))
 
         if not found:
             logger.warning("No report generator found for this load")