瀏覽代碼

show upload errors

Joystream Stats 2 年之前
父節點
當前提交
ade95b427e

+ 88 - 0
src/components/Distribution/ProviderLatency.tsx

@@ -0,0 +1,88 @@
+import { useState } from "react";
+import { Button } from "react-bootstrap";
+
+const Provider = (props: { results: any[] }) => {
+  const [expand, setExpand] = useState(false);
+  const results = props.results.filter(
+    (r) => moment(r.timestamp).valueOf() + 24 * 3600 * 1000 > moment().valueOf()
+  );
+  if (!results.length) return <div />;
+  const totalLatency = results.reduce((sum, r) => +sum + +r.latency, 0);
+  const avgLatency = totalLatency / results.length;
+  return (
+    <div className="d-flex flex-column" onClick={() => setExpand(!expand)}>
+      <div className="d-flex flex-row mx-2 p-1">
+        <Button
+          variant={expand ? "light" : "dark"}
+          className="col-5"
+          title="click to expand"
+        >
+          {props.url}
+        </Button>
+        <Button variant="danger" className="col-1 mx-1">
+          {results.length} errors
+        </Button>
+        <Button variant="success" className="col-1">
+          {avgLatency.toFixed()} ms
+        </Button>
+      </div>
+
+      <div className="d-flex flex-column px-2">
+        {expand &&
+          results.slice(0, 1000).map((r) => (
+            <div key={r.id} className="d-flex flex-row">
+              <Button
+                variant="dark"
+                className="col-2 m-1"
+                title={`lifetime: ${moment(r.timestamp)
+                  .add(24 * 3600 * 1000)
+                  .fromNow(true)}`}
+              >
+                {moment(r.createdAt).format(`DD-MMM-YY HH:mm:ss.SSS`)}
+              </Button>
+              <Button
+                variant="danger"
+                className="col-1 p-0 m-1"
+                title="open in a new tab to see original response"
+              >
+                <a href={r.url}>{r.objectId}</a>
+              </Button>
+              <Button
+                variant="warning"
+                className="col-4 m-1"
+                title="error message"
+              >
+                {r.status}
+              </Button>
+              <Button
+                variant="success"
+                className="col-1 m-1"
+                title="server responded within this time"
+              >
+                {r.latency}ms
+              </Button>
+              <Button
+                variant="light"
+                className="col-2 m-1"
+                title="click to see on the map where requests where made from (not yet implemented)"
+              >
+                {r.origin}
+              </Button>
+
+              <Button
+                variant="light"
+                className="p-0 m-1"
+                title="click to open channel"
+              >
+                <a href={`https://play.joystream.org/channel/${r.channelId}`}>
+                  Channel {r.channelId}
+                </a>
+              </Button>
+            </div>
+          ))}
+      </div>
+    </div>
+  );
+};
+
+export default Provider

+ 29 - 97
src/components/Distribution/TestResults.tsx

@@ -1,20 +1,36 @@
-import { useState } from "react";
 import moment from "moment";
-import { Button } from "react-bootstrap";
-
-const Results = (props: { results: any[] }) => {
-  const { results } = props;
-  if (!results.length) return <div />;
-
-  // TODO move to parent
-  let providers = [];
-  results.forEach((r) => {
-    if (!providers[r.endpoint]) providers[r.endpoint] = [];
-    providers[r.endpoint].push(r);
-  });
+import Provider from "./ProviderLatency";
 
+const Results = (props: { providers: any[]; uploadErrors: any[] }) => {
+  const { uploadErrors, providers } = props;
   return (
     <div className="mt-2">
+      <h2>Upload errors</h2>
+      <div
+        key={`upload-errors-head`}
+        className="font-weight-bold bg-info d-flex flex-row"
+      >
+        <div className="col-2">Date</div>
+        <div className="col-4">Endpoint</div>
+        <div className="col-4">Error</div>
+        <div className="col-1">Channel</div>
+        <div className="col-1">Video</div>
+      </div>
+
+      {uploadErrors.length
+        ? uploadErrors.map((e, i) => (
+            <div key={`upload-error-${i}`} className="d-flex flex-row">
+              <div className="col-2">
+                {moment(e.date).format(`MMM DD HH:mm`)}
+              </div>
+              <div className="col-4">{e.uploadOperatorEndpoint}</div>
+              <div className="col-4">{e.errorMessage}</div>
+              <div className="col-1">{e.channelId}</div>
+              <div className="col-1">{e.dataObjectId}</div>
+            </div>
+          ))
+        : ""}
+
       <h2>Failing Objects</h2>
       <div className="d-flex flex-column">
         {Object.keys(providers)
@@ -28,87 +44,3 @@ const Results = (props: { results: any[] }) => {
 };
 
 export default Results;
-
-const Provider = (props: { results: any[] }) => {
-  const [expand, setExpand] = useState(false);
-  const results = props.results.filter(
-    (r) => moment(r.timestamp).valueOf() + 24 * 3600 * 1000 > moment().valueOf()
-  );
-  if (!results.length) return <div />;
-  const totalLatency = results.reduce((sum, r) => +sum + +r.latency, 0);
-  const avgLatency = totalLatency / results.length;
-  return (
-    <div className="d-flex flex-column" onClick={() => setExpand(!expand)}>
-      <div className="d-flex flex-row mx-2 p-1">
-        <Button
-          variant={expand ? "light" : "dark"}
-          className="col-5"
-          title="click to expand"
-        >
-          {props.url}
-        </Button>
-        <Button variant="danger" className="col-1 mx-1">
-          {results.length} errors
-        </Button>
-        <Button variant="success" className="col-1">
-          {avgLatency.toFixed()} ms
-        </Button>
-      </div>
-
-      <div className="d-flex flex-column px-2">
-        {expand &&
-          results.slice(0, 1000).map((r) => (
-            <div key={r.id} className="d-flex flex-row">
-              <Button
-                variant="dark"
-                className="col-2 m-1"
-                title={`lifetime: ${moment(r.timestamp)
-                  .add(24 * 3600 * 1000)
-                  .fromNow(true)}`}
-              >
-                {moment(r.createdAt).format(`DD-MMM-YY HH:mm:ss.SSS`)}
-              </Button>
-              <Button
-                variant="danger"
-                className="col-1 p-0 m-1"
-                title="open in a new tab to see original response"
-              >
-                <a href={r.url}>{r.objectId}</a>
-              </Button>
-              <Button
-                variant="warning"
-                className="col-4 m-1"
-                title="error message"
-              >
-                {r.status}
-              </Button>
-              <Button
-                variant="success"
-                className="col-1 m-1"
-                title="server responded within this time"
-              >
-                {r.latency}ms
-              </Button>
-              <Button
-                variant="light"
-                className="col-2 m-1"
-                title="click to see on the map where requests where made from (not yet implemented)"
-              >
-                {r.origin}
-              </Button>
-
-              <Button
-                variant="light"
-                className="p-0 m-1"
-                title="click to open channel"
-              >
-                <a href={`https://play.joystream.org/channel/${r.channelId}`}>
-                  Channel {r.channelId}
-                </a>
-              </Button>
-            </div>
-          ))}
-      </div>
-    </div>
-  );
-};

+ 40 - 15
src/components/Distribution/index.tsx

@@ -5,30 +5,55 @@ import Bucket from "./Bucket";
 import TestResults from "./TestResults";
 import { getBuckets } from "./util";
 
+const uploadErrorsUrl = "https://joystreamstats.live/static/upload-errors.json";
+
 const Distribution = (props: {
   workers: { distributionWorkingGroup?: Worker[] };
 }) => {
   const { workers } = props;
   const [sBuckets, setSBuckets] = useState([]);
   const [dBuckets, setDBuckets] = useState([]);
-  const [results, setResults] = useState([]);
-  useEffect(() => {
-    const update = () =>
-      getBuckets([
-        workers?.storageWorkingGroup,
-        workers?.distributionWorkingGroup,
-      ]).then((buckets) => {
-        if (buckets[0].length) setSBuckets(buckets[0]);
-        if (buckets[1].length) setDBuckets(buckets[1]);
-      });
-    update();
-    setTimeout(update, 10000);
+  const [providers, setProviders] = useState([]);
+  const [uploadErrors, setUploadErrors] = useState([]);
+
+  const fetchErrors = () =>
+    axios
+      .get(uploadErrorsUrl)
+      .then(({ data }) => setUploadErrors(data.slice(0, 25)))
+      .catch((e) => console.error(e.message, uploadErrorsUrl));
+
+  const updateBuckets = () =>
+    getBuckets([
+      workers?.storageWorkingGroup,
+      workers?.distributionWorkingGroup,
+    ]).then((buckets) => {
+      if (buckets[0].length) setSBuckets(buckets[0]);
+      if (buckets[1].length) setDBuckets(buckets[1]);
+    });
+
+  const processResults = (results) => {
+    let providers = [];
+    results.forEach((r) => {
+      if (!providers[r.endpoint]) providers[r.endpoint] = [];
+      providers[r.endpoint].push(r);
+    });
+    setProviders(providers);
+  };
+
+  const getFailingAssets = () =>
     axios
       .get(`https://joystreamstats.live/api/v1/bags/errors`)
       .then(({ data }) =>
-        setResults(data.sort((a, b) => a.timestamp - b.timestamp))
+        processResults(data.sort((a, b) => a.timestamp - b.timestamp))
       );
-  }, [workers?.storageWorkingGroup, workers?.distributionWorkingGroup]);
+  useEffect(() => {
+    updateBuckets();
+    fetchErrors();
+    getFailingAssets();
+  });
+
+  setTimeout(updateBuckets, 10000);
+
   return (
     <div className="m-2 p-2 bg-light">
       <h2>Storage Buckets</h2>
@@ -46,7 +71,7 @@ const Distribution = (props: {
       ) : (
         <Loading target="distribution buckets" />
       )}
-      <TestResults results={results} />
+      <TestResults uploadErrors={uploadErrors} providers={providers} />
     </div>
   );
 };