Home Reference Source

cables_dev/cables_ui/src/ui/components/tabs/tab_profiler.js

import { ele } from "cables-shared-client";
import Tab from "../../elements/tabpanel/tab.js";
import { getHandleBarHtml } from "../../utils/handlebars.js";

/**
 * cpu profile the running patch, what is most expensive?
 *
 * @export
 * @class Profiler
 */
export default class Profiler
{
    constructor(tabs)
    {
        this._tab = new Tab("Profiler", { "icon": "pie-chart", "singleton": true, "infotext": "tab_profiler", "padding": true });
        tabs.addTab(this._tab, true);
        this.show();

        this.colors = ["#7AC4E0", "#D183BF", "#9091D6", "#FFC395", "#F0D165", "#63A8E8", "#CF5D9D", "#66C984", "#D66AA6", "#515151"];
        this.intervalId = null;
        this.lastPortTriggers = 0;
        this._subTab = 0;

        gui.corePatch().on("onLink", () => { if (gui.corePatch().profiler) gui.corePatch().profiler.clear(); this.update(); });
        gui.corePatch().on("onOpAdd", () => { if (gui.corePatch().profiler) gui.corePatch().profiler.clear(); this.update(); });
        gui.corePatch().on("onOpDelete", () => { if (gui.corePatch().profiler) gui.corePatch().profiler.clear(); this.update(); });
        gui.corePatch().on("onUnLink", () => { if (gui.corePatch().profiler) gui.corePatch().profiler.clear(); this.update(); });
    }

    setTab(which)
    {
        ele.byId("profilerTabOpsCum").classList.remove("tabActiveSubtab");
        ele.byId("profilerTabOps").classList.remove("tabActiveSubtab");
        ele.byId("profilerTabSubpatches").classList.remove("tabActiveSubtab");
        ele.byId("profilerTabPeaks").classList.remove("tabActiveSubtab");
        ele.byId("profilerTabEvents").classList.remove("tabActiveSubtab");

        if (which == 0) ele.byId("profilerTabOpsCum").classList.add("tabActiveSubtab");
        if (which == 3) ele.byId("profilerTabOps").classList.add("tabActiveSubtab");
        if (which == 1) ele.byId("profilerTabSubpatches").classList.add("tabActiveSubtab");
        if (which == 2) ele.byId("profilerTabPeaks").classList.add("tabActiveSubtab");
        if (which == 4) ele.byId("profilerTabEvents").classList.add("tabActiveSubtab");

        gui.corePatch().profiler.clear();
        this._subTab = which;
        this.update();
    }

    show()
    {
        const html = getHandleBarHtml("meta_profiler", {});
        this._tab.html(html);

        ele.byId("profilerstartbutton").addEventListener("click", function ()
        {
            this.start();
        }.bind(this));
    }

    update()
    {
        const profiler = gui.corePatch().profiler;
        if (!profiler) return;

        const items = profiler.getItems();
        let html = "";
        let htmlBar = "";
        let allTimes = 0;
        const sortedItems = [];
        let htmlData = "";

        let cumulate = true;
        if (this._subTab == 1 || this._subTab == 3) cumulate = false;

        const cumulated = {};
        const cumulatedSubPatches = {};
        const opids = {};

        for (const i in items)
        {
            const item = items[i];
            allTimes += item.timeUsed;

            opids[item.opid] = 1;

            if (cumulatedSubPatches[item.subPatch])
            {
                cumulatedSubPatches[item.subPatch].timeUsed += item.timeUsed;
            }
            else
            {
                cumulatedSubPatches[item.subPatch] = {};
                cumulatedSubPatches[item.subPatch].timeUsed = item.timeUsed;
                cumulatedSubPatches[item.subPatch].subPatch = item.subPatch;
            }

            if (cumulate)
            {
                if (cumulated[item.title])
                {
                    cumulated[item.title].timeUsed += item.timeUsed;
                    cumulated[item.title].numTriggers += item.numTriggers;
                    cumulated[item.title].numCumulated++;
                }
                else
                {
                    cumulated[item.title] = item;
                    cumulated[item.title].numCumulated = 1;
                    sortedItems.push(cumulated[item.title]);
                }
            }
            else
            {
                sortedItems.push(item);
            }
        }

        let allPortTriggers = 0;
        for (const i in sortedItems)
        {
            sortedItems[i].percent = sortedItems[i].timeUsed / allTimes * 100;
            allPortTriggers += sortedItems[i].numTriggers;
        }
        this.lastPortTriggers = allPortTriggers;

        let colorCounter = 0;

        htmlData += "Active Ops: " + Object.keys(opids).length + "<br/><br/>";

        sortedItems.sort(function (a, b) { return b.percent - a.percent; });

        if (!ele.byId("profilerdata"))
        {
            clearInterval(this.intervalId);
            this.intervalId = null;
            return;
        }
        ele.byId("profilerdata").innerHTML = htmlData;

        let item = null;
        let pad = "";
        const cgl = gui.corePatch().cgl;

        if (this._subTab == 4)
        {
            html += "<table>";
            for (let i = 0; i < cgl.profileData.heavyEvents.length; i++)
            {
                html += "<tr><td>" + cgl.profileData.heavyEvents[i].event + "</td><td>" + cgl.profileData.heavyEvents[i].name + "</td><td>" + (cgl.profileData.heavyEvents[i].info || "") + "</td></tr>";
            }
            html += "</table>";
        }
        if (this._subTab == 0 || this._subTab == 3)
        {
            html += "<h3>Ops</h3>";
            html += "<table>";

            html += "<tr>";
            html += "<td class=\"colname\">%</td>";
            html += "<td class=\"colname\">Port Name</td>";
            html += "<td class=\"colname\">Per Frame</td>";
            html += "<td class=\"colname\">Time used</td>";
            html += "<td class=\"colname\">Ops</td>";
            html += "</td>";

            for (let i in sortedItems)
            {
                item = sortedItems[i];
                pad = "";

                html += "<tr><td><span>";
                if (sortedItems.length > 0)
                    for (i = 0; i < 2 - (item.percent + "").length; i++)
                        pad += "&nbsp;";

                html += pad + Math.floor(item.percent * 100) / 100 + "% </span></td><td>";

                html += "<span>";


                html += item.title;
                html += "</span></td><td><span> " + Math.round(item.numTriggers * 10) / 10 + "x</span></td><td><span> " + Math.round(item.timeUsed) + "ms </span></td>";

                if (cumulate && item.numCumulated)html += "<td><span>" + item.numCumulated + "</span></td>";
                if (!cumulate) html += "<td ><a class=\"button-small\" onclick=\"gui.patchView.centerSelectOp('" + item.opid + "')\">op</a></td>";


                html += "</tr>";

                if (item.percent > 0)
                {
                    htmlBar += "<div class=\"tt\" data-tt=\"" + item.title + "\" style=\"height:20px;background-color:" + this.colors[colorCounter] + ";float:left;padding:0px;overflow:hidden;min-width:0px;width:" + item.percent + "%\"></div>";
                    colorCounter++;
                    if (colorCounter > this.colors.length - 1)colorCounter = 0;
                }
            }

            html += "</table>";
        }

        // peak list
        const htmlPeaks = "";
        sortedItems.sort(function (a, b) { return b.peak - a.peak; });


        // if (Object.keys(cumulatedSubPatches).length > 1)
        if (this._subTab == 1)
        {
            const subPatches = [];
            let allTimesUsed = 0;
            for (const i in cumulatedSubPatches)
            {
                allTimesUsed += cumulatedSubPatches[i].timeUsed;
                subPatches.push(cumulatedSubPatches[i]);
            }
            for (let i = 0; i < subPatches.length; i++)
            {
                subPatches[i].name = gui.patchView.getSubPatchName(subPatches[i].subPatch);
                subPatches[i].percent = subPatches[i].timeUsed / allTimesUsed * 100;
            }
            subPatches.sort(function (a, b) { return b.percent - a.percent; });

            html += "<h3>Subpatches</h3>";
            html += "<table>";
            for (let i = 0; i < subPatches.length; i++)
            {
                html += "<tr>";
                html += "<td><span>" + Math.floor(subPatches[i].percent * 100) / 100 + "%</span></td>";
                html += "<td><span><a onclick=\"gui.patchView.setCurrentSubPatch('" + subPatches[i].subPatch + "')\">" + subPatches[i].name + "</span></td>";
                html += "</tr>";
            }
            html += "</table>";
        }


        if (this._subTab == 2)
        {
            html += "<h3>Peaks</h3>";
            for (let i in sortedItems)
            {
                item = sortedItems[i];
                pad = "";

                if (sortedItems.length > 0) for (i = 0; i < 2 - (item.peak + "").length; i++)pad += "&nbsp;";
                html += pad + (Math.round(96 * item.peak) / 100) + "ms " + item.title + "<br/>";
            }
        }



        let pauseStr = "Pause";
        if (gui.corePatch().profiler)
        {
            if (gui.corePatch().profiler.paused)pauseStr = "Resume";
            ele.byId("profiler_pause").innerHTML = pauseStr;
        }


        ele.byId("profilerui").style.display = "block";
        // ele.byId("profilerlistPeaks").innerHTML = htmlPeaks;
        ele.byId("profilerbar").innerHTML = htmlBar;
        ele.byId("profilerlist").innerHTML = html;
        ele.byId("profilerstartbutton").style.display = "none";
    }

    start()
    {
        gui.corePatch().profile(true);
        this.update();
        ele.byId("profilerTabOpsCum").addEventListener("click", () => { this.setTab(0); });
        ele.byId("profilerTabOps").addEventListener("click", () => { this.setTab(3); });
        ele.byId("profilerTabSubpatches").addEventListener("click", () => { this.setTab(1); });
        ele.byId("profilerTabPeaks").addEventListener("click", () => { this.setTab(2); });
        ele.byId("profilerTabEvents").addEventListener("click", () => { this.setTab(4); });

        if (!this.intervalId) this.intervalId = setInterval(this.update.bind(this), 1000);
    }
}