浏览代码

simple storage performance test

 - assets are selected randomly
 - user can select number of assets and (re)start the test
 - TODO assets should be loaded after each other
 - TODO stop downloading for chain data during the test
 - TODO show size and measure download speed
 - TODO display latency
 - TODO show graphs with results for each provider
Joystream Stats 3 年之前
父节点
当前提交
901a3c02f8

+ 64 - 0
src/App.tsx

@@ -33,6 +33,7 @@ const version = 5;
 const userLink = `${domain}/#/members/joystreamstats`;
 
 const initialState = {
+  assets: [],
   connected: false,
   fetching: "",
   tasks: 0,
@@ -50,6 +51,7 @@ const initialState = {
   handles: {},
   members: [],
   proposalPosts: [],
+  providers: [],
   reports: {},
   stakes: {},
   stashes: [],
@@ -85,6 +87,66 @@ class App extends React.Component<IProps, IState> {
     this.save("status", status);
   }
 
+  async fetchAssets() {
+    const url = "https://hydra.joystream.org/graphql";
+    const request = {
+      query: "query {\n dataObjects(where: {}) { joystreamContentId }\n}",
+    };
+    console.debug(`Fetching data IDs (from ${url})`);
+    const { data } = await axios.post(url, request);
+    let assets = [];
+    data.data.dataObjects.forEach((p) => assets.push(p.joystreamContentId));
+    //console.log(`assets`, data);
+    this.save(`assets`, assets);
+  }
+
+  async fetchStorageProviders() {
+    const url = "https://hydra.joystream.org/graphql";
+    const request = {
+      query:
+        'query {\n  workers(where: {metadata_contains: "http", isActive_eq: true, type_eq: STORAGE}){\n    metadata\n  }\n}',
+    };
+    console.debug(`Fetching storage providers (from ${url})`);
+    const { data } = await axios.post(url, request);
+    const providers = data.data.workers.map((p) => {
+      return {
+        url: p.metadata,
+      };
+    });
+    this.save(`providers`, providers);
+  }
+
+  async getStorageProviders(api: Api) {
+    console.debug(`Fetching storage providers (from chain)`);
+    let providers = [];
+    const worker = await api.query.storageWorkingGroup.nextWorkerId();
+    console.log(`next provider: ${worker}`);
+
+    for (let i = 0; i < Number(worker); ++i) {
+      let storageProvider = (await api.query.storageWorkingGroup.workerById(
+        i
+      )) as WorkerOf;
+      if (storageProvider.is_active) {
+        const storage = (await api.query.storageWorkingGroup.workerStorage(
+          i
+        )) as Bytes;
+        const url = Buffer.from(storage.toString().substr(2), "hex").toString();
+
+        let membership = (await api.query.members.membershipById(
+          storageProvider.member_id
+        )) as Membership;
+
+        providers[i] = {
+          owner: membership.handle,
+          account: membership.root_account,
+          storage,
+          url,
+        };
+      }
+      this.save(`providers`, providers);
+    }
+  }
+
   async handleBlock(api, header: Header) {
     let { blocks, status, queue } = this.state;
     const id = header.number.toNumber();
@@ -716,6 +778,8 @@ class App extends React.Component<IProps, IState> {
   componentDidMount() {
     this.loadData();
     this.connectEndpoint();
+    this.fetchStorageProviders();
+    this.fetchAssets();
     setTimeout(() => this.fetchTokenomics(), 30000);
     //this.initializeSocket();
   }

+ 1 - 0
src/components/Dashboard/index.tsx

@@ -39,6 +39,7 @@ const Dashboard = (props: IProps) => {
           <Link to={`/timeline`}>Timeline</Link>
           <Link to={`/tokenomics`}>Reports</Link>
           <Link to={`/validators`}>Validators</Link>
+          <Link to={`/storage`}>Storage</Link>
           <Link to={`/spending`}>Spending</Link>
           <Link to="/mint">Toolbox</Link>
         </div>

+ 53 - 0
src/components/Storage/Asset.tsx

@@ -0,0 +1,53 @@
+import React, { useEffect } from "react";
+import moment from "moment";
+
+import { Link } from "react-router-dom";
+
+const Asset = (props: {
+  loadAsset: (string, any, string) => void;
+  provider: string;
+  id: string;
+  status: string;
+  startedAt: any;
+  finishedAt: any;
+}) => {
+  const { loadAsset, provider, id, status, startedAt, finishedAt } = props;
+  const bg = {
+    undefined: "warning",
+    loading: "warning",
+    loaded: "success",
+    failed: "danger",
+  };
+  //console.log(status, bg[status]);
+  useEffect(() => loadAsset(id, provider, "loading"), []);
+
+  const url = `${provider}asset/v0/${id}`;
+
+  return (
+    <div
+      className={`display-block bg-${bg[status]} p-1 mr-1`}
+      style={{ width: `50px` }}
+    >
+      <a href={url} title={id}>
+        {finishedAt
+          ? moment(finishedAt).diff(startedAt) / 1000
+          : moment().diff(startedAt) / 1000}
+        s
+        <img
+          onLoad={() =>
+            status === "loaded" || loadAsset(id, provider, "loaded")
+          }
+          onError={() =>
+            status === "failed" || loadAsset(id, provider, "failed")
+          }
+          className="hidden"
+          alt={id}
+          style={{ display: "none" }}
+          src={url}
+        />
+      </a>
+    </div>
+  );
+};
+
+export default Asset;

+ 37 - 0
src/components/Storage/Provider.tsx

@@ -0,0 +1,37 @@
+import React from "react";
+import Asset from "./Asset";
+
+const Provider = (props: {
+  loadAsset: (id: string, provider: string, status: string) => void;
+  loading: any[];
+  startedAt: string | boolean;
+  test: string[];
+  url: string;
+}) => {
+  const { loadAsset, loading, test, url, startedAt } = props;
+
+  return (
+    <div key={url} className="m-2 d-flex flex-row">
+      <div
+        className="text-info p-1 mr-2"
+        style={{ minWidth: `30%`, fontSize: `1.1em` }}
+      >
+        <a href={url}>{url}</a>
+      </div>
+
+      {loading &&
+        test.map((a) => (
+          <Asset
+            key={`${url}-${a}`}
+            loadAsset={loadAsset}
+            id={a}
+            provider={url}
+            startedAt={startedAt}
+            {...loading[`${url}-${a}`]}
+          />
+        ))}
+    </div>
+  );
+};
+
+export default Provider;

+ 107 - 0
src/components/Storage/index.tsx

@@ -0,0 +1,107 @@
+import React from "react";
+import { Button } from "react-bootstrap";
+import Loading from "../Loading";
+import moment from "moment";
+import Provider from "./Provider";
+
+interface IProps {
+  assets: string[];
+  providers: any[];
+}
+interface IState {}
+
+class Storage extends React.Component<IProps, IState> {
+  constructor(props: IProps) {
+    super(props);
+    this.state = {
+      startedAt: false,
+      selectedAssets: [],
+      number: 1,
+      loading: {},
+      assets: [],
+    };
+    this.startTest = this.startTest.bind(this);
+    this.loadAsset = this.loadAsset.bind(this);
+    this.handleChange = this.handleChange.bind(this);
+  }
+
+  componentDidMount() {
+    //setInterval(this.forceUpdate, 5000);
+  }
+
+  startTest() {
+    const { number } = this.state;
+    const { assets } = this.props;
+    const selectedAssets = [];
+    for (let i = 0; i < number; i++) {
+      const id = Math.round(Math.random() * assets.length);
+      const asset = assets.slice(id, id + 1)[0];
+
+      console.log(id, asset, selectedAssets);
+
+      if (selectedAssets.find((a) => a === asset)) i--;
+      else selectedAssets.push(asset);
+    }
+    this.setState({ loading: {}, startedAt: moment(), selectedAssets });
+  }
+
+  loadAsset(id: string, provider: string, status: string) {
+    let { startedAt } = this.state;
+    const tag = `${provider}-${id}`;
+    const value =
+      status === `loading`
+        ? { provider, id, status, startedAt }
+        : { provider, id, status, finishedAt: moment() };
+
+    const { loading } = this.state;
+    loading[tag] = value;
+    this.setState({ loading });
+  }
+
+  handleChange(e) {
+    this.setState({ [e.target.name]: e.target.value });
+  }
+
+  render() {
+    const { selectedAssets, number, loading, startedAt } = this.state;
+    const { providers, assets } = this.props;
+
+    if (!providers.length) return <Loading target="storage providers" />;
+    if (!assets.length) return <Loading target="assets" />;
+
+    return (
+      <div className="p-3 text-light">
+        <h2>Storage Providers</h2>
+        <div className="d-flex flex-row mb-2">
+          <input
+            className="mr-1"
+            name="number"
+            value={number}
+            onChange={this.handleChange}
+          />
+          <Button variant="warning" onClick={this.startTest}>
+            Test {number} out of {assets.length} assets
+          </Button>
+        </div>
+
+        {(providers.length &&
+          providers.map((p) => (
+            <Provider
+              key={p.url}
+              loadAsset={this.loadAsset}
+              test={selectedAssets}
+              loading={loading}
+              startedAt={startedAt}
+              {...p}
+            />
+          ))) || <Loading target="provider list" />}
+
+        <div className="position-fixed" style={{ right: "0px", top: "0px" }}>
+          <a href="/">Back</a>
+        </div>
+      </div>
+    );
+  }
+}
+
+export default Storage;

+ 2 - 0
src/types.ts

@@ -36,10 +36,12 @@ export interface Status {
 }
 
 export interface IState {
+  assets: string[];
   connecting: boolean;
   loading: string;
   processingTasks: number;
   fetching: string;
+  providers: any[];
   queue: { key: string; action: any }[];
   status: Status;
   blocks: Block[];