Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  reftest-analyzer-structured.xhtml   Sprache: unbekannt

 
<?xml version="1.0" encoding="UTF-8"?>
<!-- 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/. -->
<!--

Features to add:
* make the left and right parts of the viewer independently scrollable
* make the test list filterable
** default to only showing unexpecteds
* add other ways to highlight differences other than circling?
* add zoom/pan to images
* Add ability to load log via XMLHttpRequest (also triggered via URL param)
* color the test list based on pass/fail and expected/unexpected/random/skip
* ability to load multiple logs ?
** rename them by clicking on the name and editing
** turn the test list into a collapsing tree view
** move log loading into popup from viewer UI

-->
<!DOCTYPE html>
<html lang="en-US" xml:lang="en-US" xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title>Reftest analyzer</title>
    <style type="text/css">
      <![CDATA[

        html, body { margin: 0; }
        html { padding: 0; }
        body { padding: 4px; }

        #pixelarea, #itemlist, #images { position: absolute; }
        #itemlist, #images { overflow: auto; }
        #pixelarea { top: 0; left: 0; width: 320px; height: 84px; overflow: visible }
        #itemlist { top: 84px; left: 0; width: 320px; bottom: 0; }
        #images { top: 0; bottom: 0; left: 320px; right: 0; }

        #leftpane { width: 320px; }
        #images { position: fixed; top: 10px; left: 340px; }

        form#imgcontrols { margin: 0; display: block; }

        #itemlist > table { border-collapse: collapse; }
        #itemlist > table > tbody > tr > td { border: 1px solid; padding: 1px; }
        #itemlist td.activeitem { background-color: yellow; }

        /*
        #itemlist > table > tbody > tr.pass > td.url { background: lime; }
        #itemlist > table > tbody > tr.fail > td.url { background: red; }
        */

        #magnification > svg { display: block; width: 84px; height: 84px; }

        #pixelinfo { font: small sans-serif; position: absolute; width: 200px; left: 84px; }
        #pixelinfo table { border-collapse: collapse; }
        #pixelinfo table th { white-space: nowrap; text-align: left; padding: 0; }
        #pixelinfo table td { font-family: monospace; padding: 0 0 0 0.25em; }

        #pixelhint { display: inline; color: #88f; cursor: help; }
        #pixelhint > * { display: none; position: absolute; margin: 8px 0 0 8px; padding: 4px; width: 400px; background: #ffa; color: black; box-shadow: 3px 3px 2px #888; z-index: 1; }
        #pixelhint:hover { color: #000; }
        #pixelhint:hover > * { display: block; }
        #pixelhint p { margin: 0; }
        #pixelhint p + p { margin-top: 1em; }

        ]]>
    </style>
    <script type="text/javascript">
      <![CDATA[

      var XLINK_NS = "http://www.w3.org/1999/xlink";
      var SVG_NS = "http://www.w3.org/2000/svg";
      var IMAGE_NOT_AVAILABLE = "data:image/png;base64,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";

      var gPhases = null;

      var gIDCache = {};

      var gMagPixPaths = [];     // 2D array of array-of-two <path> objects used in the pixel magnifier
      var gMagWidth = 5;         // number of zoomed in pixels to show horizontally
      var gMagHeight = 5;        // number of zoomed in pixels to show vertically
      var gMagZoom = 16;         // size of the zoomed in pixels
      var gImage1Data;           // ImageData object for the reference image
      var gImage2Data;           // ImageData object for the test output image
      var gFlashingPixels = [];  // array of <path> objects that should be flashed due to pixel color mismatch
      var gParams;

      function ID(id) {
        if (!(id in gIDCache))
          gIDCache[id] = document.getElementById(id);
        return gIDCache[id];
      }

      function hash_parameters() {
        var result = { };
        var params = window.location.hash.substr(1).split(/[&;]/);
        for (var i = 0; i < params.length; i++) {
          var parts = params[i].split("=");
          result[parts[0]] = unescape(unescape(parts[1]));
        }
        return result;
      }

      function load() {
        gPhases = [ ID("entry"), ID("loading"), ID("viewer") ];
        build_mag();
        gParams = hash_parameters();
        if (gParams.log) {
          show_phase("loading");
          process_log(gParams.log);
        } else if (gParams.logurl) {
          show_phase("loading");
          var req = new XMLHttpRequest();
          req.onreadystatechange = function() {
            if (req.readyState === 4) {
              process_log(req.responseText);
            }
          };
          req.open('GET', gParams.logurl, true);
          req.send();
        }
        window.addEventListener('keypress', handle_keyboard_shortcut);
        ID("image1").addEventListener('error', image_load_error);
        ID("image2").addEventListener('error', image_load_error);
      }

      function image_load_error(e) {
        e.target.setAttributeNS(XLINK_NS, "xlink:href", IMAGE_NOT_AVAILABLE);
      }

      function build_mag() {
        var mag = ID("mag");

        var r = document.createElementNS(SVG_NS, "rect");
        r.setAttribute("x", gMagZoom * -gMagWidth / 2);
        r.setAttribute("y", gMagZoom * -gMagHeight / 2);
        r.setAttribute("width", gMagZoom * gMagWidth);
        r.setAttribute("height", gMagZoom * gMagHeight);
        mag.appendChild(r);

        mag.setAttribute("transform", "translate(" + (gMagZoom * (gMagWidth / 2) + 1) + "," + (gMagZoom * (gMagHeight / 2) + 1) + ")");

        for (var x = 0; x < gMagWidth; x++) {
          gMagPixPaths[x] = [];
          for (var y = 0; y < gMagHeight; y++) {
            var p1 = document.createElementNS(SVG_NS, "path");
            p1.setAttribute("d", "M" + ((x - gMagWidth / 2) + 1) * gMagZoom + "," + (y - gMagHeight / 2) * gMagZoom + "h" + -gMagZoom + "v" + gMagZoom);
            p1.setAttribute("stroke", "black");
            p1.setAttribute("stroke-width", "1px");
            p1.setAttribute("fill", "#aaa");

            var p2 = document.createElementNS(SVG_NS, "path");
            p2.setAttribute("d", "M" + ((x - gMagWidth / 2) + 1) * gMagZoom + "," + (y - gMagHeight / 2) * gMagZoom + "v" + gMagZoom + "h" + -gMagZoom);
            p2.setAttribute("stroke", "black");
            p2.setAttribute("stroke-width", "1px");
            p2.setAttribute("fill", "#888");

            mag.appendChild(p1);
            mag.appendChild(p2);
            gMagPixPaths[x][y] = [p1, p2];
          }
        }

        var flashedOn = false;
        setInterval(function() {
          flashedOn = !flashedOn;
          flash_pixels(flashedOn);
        }, 500);
      }

      function show_phase(phaseid) {
        for (var i in gPhases) {
          var phase = gPhases[i];
          phase.style.display = (phase.id == phaseid) ? "" : "none";
        }

        if (phase == "viewer")
          ID("images").style.display = "none";
      }

      function fileentry_changed() {
        show_phase("loading");
        var input = ID("fileentry");
        var files = input.files;
        if (files.length) {
          // Only handle the first file; don't handle multiple selection.
          // The parts of the log we care about are ASCII-only.  Since we
          // can ignore lines we don't care about, best to read in as
          // iso-8859-1, which guarantees we don't get decoding errors.
          var fileReader = new FileReader();
          fileReader.onload = function(e) {
            var log = null;

            log = e.target.result;

            if (log)
              process_log(log);
            else
              show_phase("entry");
          }
          fileReader.readAsText(files[0], "iso-8859-1");
        }
        // So the user can process the same filename again (after
        // overwriting the log), clear the value on the form input so we
        // will always get an onchange event.
        input.value = "";
      }

      function log_pasted() {
        show_phase("loading");
        var entry = ID("logentry");
        var log = entry.value;
        entry.value = "";
        process_log(log);
      }

      var gTestItems;

      function process_log(contents) {
        var lines = contents.split(/[\r\n]+/);
        gTestItems = [];
        for (var j in lines) {
          var line = lines[j];
          try {
            var data = JSON.parse(line);
          } catch(e) {
            continue;
          }
          // Ignore duplicated output in logcat.
          if (!data.action == "test_end" && data.status != "FAIL")
            continue;

          if (!data.hasOwnProperty("extra") ||
              !data.extra.hasOwnProperty("reftest_screenshots")) {
            continue;
          }

          var url = data.test;
          var screenshots = data.extra.reftest_screenshots;
          gTestItems.push(
            {
              pass: data.status === "PASS",
              // only one of the following three should ever be true
              unexpected: data.hasOwnProperty("expected"),
              random: false,
              skip: data.status == "SKIP",
              url,
              images: [],
              imageLabels: []
            });

          var item = gTestItems[gTestItems.length - 1];
          item.images.push("data:image/png;base64," + screenshots[0].screenshot);
          item.imageLabels.push(screenshots[0].url);
          if (screenshots.length > 1) {
            item.images.push("data:image/png;base64," + screenshots[2].screenshot);
            item.imageLabels.push(screenshots[2].url);
          }
        }
        build_viewer();
      }

      function build_viewer() {
        if (!gTestItems.length) {
          show_phase("entry");
          return;
        }

        var cell = ID("itemlist");
        while (cell.childNodes.length)
          cell.removeChild(cell.childNodes[cell.childNodes.length - 1]);

        var table = document.createElement("table");
        var tbody = document.createElement("tbody");
        table.appendChild(tbody);

        for (var i in gTestItems) {
          var item = gTestItems[i];

          // optional url filter for only showing unexpected results
          if (parseInt(gParams.only_show_unexpected) && !item.unexpected)
            continue;

          // XXX regardless skip expected pass items until we have filtering UI
          if (item.pass && !item.unexpected)
            continue;

          var tr = document.createElement("tr");
          var td;
          var text;

          td = document.createElement("td");
          text = "";
          if (item.unexpected) { text += "!"; }
          if (item.random) { text += "R"; }
          if (item.skip) { text += "S"; }
          td.appendChild(document.createTextNode(text));
          tr.appendChild(td);

          td = document.createElement("td");
          td.id = "item" + i;
          td.className = "url";
          // Only display part of URL after "/mozilla/".
          var match = item.url.match(/\/mozilla\/(.*)/);
          text = document.createTextNode(match ? match[1] : item.url);
          if (item.images.length) {
            var a = document.createElement("a");
            a.href = "javascript:show_images(" + i + ")";
            a.appendChild(text);
            td.appendChild(a);
          } else {
            td.appendChild(text);
          }
          tr.appendChild(td);

          tbody.appendChild(tr);
        }

        cell.appendChild(table);

        show_phase("viewer");
      }

      function get_image_data(src, whenReady) {
        var img = new Image();
        img.onload = function() {
          var canvas = document.createElement("canvas");
          canvas.width = img.naturalWidth;
          canvas.height = img.naturalHeight;

          var ctx = canvas.getContext("2d");
          ctx.drawImage(img, 0, 0);

          whenReady(ctx.getImageData(0, 0, img.naturalWidth, img.naturalHeight));
        };
        img.src = src;
      }

      function sync_svg_size(imageData) {
        // We need the size of the 'svg' and its 'image' elements to match the size
        // of the ImageData objects that we're going to read pixels from or else our
        // magnify() function will be very broken.
        ID("svg").setAttribute("width", imageData.width);
        ID("svg").setAttribute("height", imageData.height);
      }

      function show_images(i) {
        var item = gTestItems[i];
        var cell = ID("images");

        // Remove activeitem class from any existing elements
        var activeItems = document.querySelectorAll(".activeitem");
        for (var activeItemIdx = activeItems.length; activeItemIdx-- != 0;) {
          activeItems[activeItemIdx].classList.remove("activeitem");
        }

        ID("item" + i).classList.add("activeitem");
        ID("image1").style.display = "";
        ID("image2").style.display = "none";
        ID("diffrect").style.display = "none";
        ID("imgcontrols").reset();

        ID("image1").setAttributeNS(XLINK_NS, "xlink:href", item.images[0]);
        // Making the href be #image1 doesn't seem to work
        ID("feimage1").setAttributeNS(XLINK_NS, "xlink:href", item.images[0]);
        if (item.images.length == 1) {
          ID("imgcontrols").style.display = "none";
        } else {
          ID("imgcontrols").style.display = "";

          ID("image2").setAttributeNS(XLINK_NS, "xlink:href", item.images[1]);
          // Making the href be #image2 doesn't seem to work
          ID("feimage2").setAttributeNS(XLINK_NS, "xlink:href", item.images[1]);

          ID("label1").textContent = 'Image ' + item.imageLabels[0];
          ID("label2").textContent = 'Image ' + item.imageLabels[1];
        }

        cell.style.display = "";

        get_image_data(item.images[0], function(data) { gImage1Data = data; sync_svg_size(gImage1Data); });
        get_image_data(item.images[1], function(data) { gImage2Data = data });
      }

      function show_image(i) {
        if (i == 1) {
          ID("image1").style.display = "";
          ID("image2").style.display = "none";
        } else {
          ID("image1").style.display = "none";
          ID("image2").style.display = "";
        }
      }

      function handle_keyboard_shortcut(event) {
        switch (event.charCode) {
        case 49: // "1" key
          document.getElementById("radio1").checked = true;
          show_image(1);
          break;
        case 50: // "2" key
          document.getElementById("radio2").checked = true;
          show_image(2);
          break;
        case 100: // "d" key
          document.getElementById("differences").click();
          break;
        case 112: // "p" key
          shift_images(-1);
          break;
        case 110: // "n" key
          shift_images(1);
          break;
        }
      }

      function shift_images(dir) {
        var activeItem = document.querySelector(".activeitem");
        if (!activeItem) {
          return;
        }
        for (var elm = activeItem; elm; elm = elm.parentElement) {
          if (elm.tagName != "tr") {
            continue;
          }
          elm = dir > 0 ? elm.nextElementSibling : elm.previousElementSibling;
          if (elm) {
            elm.getElementsByTagName("a")[0].click();
          }
          return;
        }
      }

      function show_differences(cb) {
        ID("diffrect").style.display = cb.checked ? "" : "none";
      }

      function flash_pixels(on) {
        var stroke = on ? "red" : "black";
        var strokeWidth = on ? "2px" : "1px";
        for (var i = 0; i < gFlashingPixels.length; i++) {
          gFlashingPixels[i].setAttribute("stroke", stroke);
          gFlashingPixels[i].setAttribute("stroke-width", strokeWidth);
        }
      }

      function cursor_point(evt) {
        var m = evt.target.getScreenCTM().inverse();
        var p = ID("svg").createSVGPoint();
        p.x = evt.clientX;
        p.y = evt.clientY;
        p = p.matrixTransform(m);
        return { x: Math.floor(p.x), y: Math.floor(p.y) };
      }

      function hex2(i) {
        return (i < 16 ? "0" : "") + i.toString(16);
      }

      function canvas_pixel_as_hex(data, x, y) {
        var offset = (y * data.width + x) * 4;
        var r = data.data[offset];
        var g = data.data[offset + 1];
        var b = data.data[offset + 2];
        return "#" + hex2(r) + hex2(g) + hex2(b);
      }

      function hex_as_rgb(hex) {
        return "rgb(" + [parseInt(hex.substring(1, 3), 16), parseInt(hex.substring(3, 5), 16), parseInt(hex.substring(5, 7), 16)] + ")";
      }

      function magnify(evt) {
        var { x: x, y: y } = cursor_point(evt);
        var centerPixelColor1, centerPixelColor2;

        var dx_lo = -Math.floor(gMagWidth / 2);
        var dx_hi = Math.floor(gMagWidth / 2);
        var dy_lo = -Math.floor(gMagHeight / 2);
        var dy_hi = Math.floor(gMagHeight / 2);

        flash_pixels(false);
        gFlashingPixels = [];
        for (var j = dy_lo; j <= dy_hi; j++) {
          for (var i = dx_lo; i <= dx_hi; i++) {
            var px = x + i;
            var py = y + j;
            var p1 = gMagPixPaths[i + dx_hi][j + dy_hi][0];
            var p2 = gMagPixPaths[i + dx_hi][j + dy_hi][1];
            // Here we just use the dimensions of gImage1Data since we expect test
            // and reference to have the same dimensions.
            if (px < 0 || py < 0 || px >= gImage1Data.width || py >= gImage1Data.height) {
              p1.setAttribute("fill", "#aaa");
              p2.setAttribute("fill", "#888");
            } else {
              var color1 = canvas_pixel_as_hex(gImage1Data, x + i, y + j);
              var color2 = canvas_pixel_as_hex(gImage2Data, x + i, y + j);
              p1.setAttribute("fill", color1);
              p2.setAttribute("fill", color2);
              if (color1 != color2) {
                gFlashingPixels.push(p1, p2);
                p1.parentNode.appendChild(p1);
                p2.parentNode.appendChild(p2);
              }
              if (i == 0 && j == 0) {
                centerPixelColor1 = color1;
                centerPixelColor2 = color2;
              }
            }
          }
        }
        flash_pixels(true);
        show_pixelinfo(x, y, centerPixelColor1, hex_as_rgb(centerPixelColor1), centerPixelColor2, hex_as_rgb(centerPixelColor2));
      }

      function show_pixelinfo(x, y, pix1rgb, pix1hex, pix2rgb, pix2hex) {
        ID("coords").textContent = [x, y];
        ID("pix1hex").textContent = pix1hex;
        ID("pix1rgb").textContent = pix1rgb;
        ID("pix2hex").textContent = pix2hex;
        ID("pix2rgb").textContent = pix2rgb;
      }

        ]]>
    </script>
  </head>
  <body onload="load()">
    <div id="entry">
      <h1>Reftest analyzer: load raw structured log</h1>

      <p>
        Either paste your log into this textarea:<br />
        <textarea cols="80" rows="10" id="logentry" /><br />
        <input
          type="button"
          value="Process pasted log"
          onclick="log_pasted()"
        />
      </p>

      <p>
        ... or load it from a file:<br />
        <input type="file" id="fileentry" onchange="fileentry_changed()" />
      </p>
    </div>

    <div id="loading" style="display: none">Loading log...</div>

    <div id="viewer" style="display: none">
      <div id="pixelarea">
        <div id="pixelinfo">
          <table>
            <tbody>
              <tr>
                <th>Pixel at:</th>
                <td colspan="2" id="coords" />
              </tr>
              <tr>
                <th>Image 1:</th>
                <td id="pix1rgb"></td>
                <td id="pix1hex"></td>
              </tr>
              <tr>
                <th>Image 2:</th>
                <td id="pix2rgb"></td>
                <td id="pix2hex"></td>
              </tr>
            </tbody>
          </table>
          <div>
            <div id="pixelhint">
              ★
              <div>
                <p>
                  Move the mouse over the reftest image on the right to show
                  magnified pixels on the left. The color information above is
                  for the pixel centered in the magnified view.
                </p>
                <p>
                  Image 1 is shown in the upper triangle of each pixel and Image
                  2 is shown in the lower triangle.
                </p>
              </div>
            </div>
          </div>
        </div>
        <div id="magnification">
          <svg
            xmlns="http://www.w3.org/2000/svg"
            width="84"
            height="84"
            shape-rendering="optimizeSpeed"
          >
            <g id="mag" />
          </svg>
        </div>
      </div>
      <div id="itemlist"></div>
      <div id="images" style="display: none">
        <form id="imgcontrols">
          <input
            id="radio1"
            type="radio"
            name="which"
            value="0"
            onchange="show_image(1)"
            checked="checked"
          /><label id="label1" title="1" for="radio1">Image 1</label>
          <input
            id="radio2"
            type="radio"
            name="which"
            value="1"
            onchange="show_image(2)"
          /><label id="label2" title="2" for="radio2">Image 2</label>
          <label
            ><input
              id="differences"
              type="checkbox"
              onchange="show_differences(this)"
            />Circle differences</label
          >
        </form>
        <svg
          xmlns="http://www.w3.org/2000/svg"
          xmlns:xlink="http://www.w3.org/1999/xlink"
          version="1.1"
          width="800"
          height="1000"
          id="svg"
        >
          <defs>
            <!-- use sRGB to avoid loss of data -->
            <filter
              id="showDifferences"
              x="0%"
              y="0%"
              width="100%"
              height="100%"
              style="color-interpolation-filters: sRGB"
            >
              <feImage id="feimage1" result="img1" xlink:href="#image1" />
              <feImage id="feimage2" result="img2" xlink:href="#image2" />
              <!-- inv1 and inv2 are the images with RGB inverted -->
              <feComponentTransfer result="inv1" in="img1">
                <feFuncR type="linear" slope="-1" intercept="1" />
                <feFuncG type="linear" slope="-1" intercept="1" />
                <feFuncB type="linear" slope="-1" intercept="1" />
              </feComponentTransfer>
              <feComponentTransfer result="inv2" in="img2">
                <feFuncR type="linear" slope="-1" intercept="1" />
                <feFuncG type="linear" slope="-1" intercept="1" />
                <feFuncB type="linear" slope="-1" intercept="1" />
              </feComponentTransfer>
              <!-- w1 will have non-white pixels anywhere that img2
               is brighter than img1, and w2 for the reverse.
               It would be nice not to have to go through these
               intermediate states, but feComposite
               type="arithmetic" can't transform the RGB channels
               and leave the alpha channel untouched. -->
              <feComposite
                result="w1"
                in="img1"
                in2="inv2"
                operator="arithmetic"
                k2="1"
                k3="1"
              />
              <feComposite
                result="w2"
                in="img2"
                in2="inv1"
                operator="arithmetic"
                k2="1"
                k3="1"
              />
              <!-- c1 will have non-black pixels anywhere that img2
               is brighter than img1, and c2 for the reverse -->
              <feComponentTransfer result="c1" in="w1">
                <feFuncR type="linear" slope="-1" intercept="1" />
                <feFuncG type="linear" slope="-1" intercept="1" />
                <feFuncB type="linear" slope="-1" intercept="1" />
              </feComponentTransfer>
              <feComponentTransfer result="c2" in="w2">
                <feFuncR type="linear" slope="-1" intercept="1" />
                <feFuncG type="linear" slope="-1" intercept="1" />
                <feFuncB type="linear" slope="-1" intercept="1" />
              </feComponentTransfer>
              <!-- c will be nonblack (and fully on) for every pixel+component where there are differences -->
              <feComposite
                result="c"
                in="c1"
                in2="c2"
                operator="arithmetic"
                k2="255"
                k3="255"
              />
              <!-- a will be opaque for every pixel with differences and transparent for all others -->
              <feColorMatrix
                result="a"
                type="matrix"
                values="0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  1 1 1 0 0"
              />

              <!-- a, dilated by 1 pixel -->
              <feMorphology
                result="dila1"
                in="a"
                operator="dilate"
                radius="1"
              />
              <!-- a, dilated by 2 pixels -->
              <feMorphology
                result="dila2"
                in="dila1"
                operator="dilate"
                radius="1"
              />

              <!-- all the pixels in the 2-pixel dilation of a but not in the 1-pixel dilation, to highlight the diffs -->
              <feComposite
                result="highlight"
                in="dila2"
                in2="dila1"
                operator="out"
              />

              <feFlood result="red" flood-color="red" />
              <feComposite
                result="redhighlight"
                in="red"
                in2="highlight"
                operator="in"
              />
              <feFlood result="black" flood-color="black" flood-opacity="0.5" />
              <feMerge>
                <feMergeNode in="black" />
                <feMergeNode in="redhighlight" />
              </feMerge>
            </filter>
          </defs>
          <g onmousemove="magnify(evt)">
            <image x="0" y="0" width="100%" height="100%" id="image1" />
            <image x="0" y="0" width="100%" height="100%" id="image2" />
          </g>
          <rect
            id="diffrect"
            filter="url(#showDifferences)"
            pointer-events="none"
            x="0"
            y="0"
            width="100%"
            height="100%"
          />
        </svg>
      </div>
    </div>
  </body>
</html>

[ Dauer der Verarbeitung: 0.16 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge