using System; using Beefy; using Beefy.widgets; using Beefy.gfx; using Beefy.theme.dark; using Beefy.theme; using System.IO; using System.Threading; using System.Collections; using System.Diagnostics; using Beefy.events; using Beefy.sys; using System.Text; using System.Net; namespace BeefPerf { class BPApp : BFApp { public new static BPApp sApp; public float mTimePerFrame = 1.0f / 60; public bool mWantsFullscreen = false; public int32 mListenPort = 4208; //public int32 mListenPort = 135; WidgetWindow mMainWindow; Widget mRootWidget; public Widget mMainWidget; public List mLogLines = new List() ~ DeleteContainerAndItems!(_); //public PopupMenuManager mPopupMenuManager; //public Trainer mTrainer ~ delete _; public ScriptManager mScriptManager = new ScriptManager() ~ delete _; public DarkDockingFrame mDockingFrame; public MainFrame mMainFrame; public WorkspacePanel mWorkspacePanel; public Board mBoard; public ProfilePanel mProfilePanel; public FindPanel mFindPanel; public Windows.ProcessInformation mProcessInformation; public Socket mListenSocket ~ delete _; public Thread mSocketThread ~ delete _; public List mClients = new List() ~ delete _; public List mSessions = new List ~ DeleteContainerAndItems!(_); public Monitor mClientMonitor = new Monitor() ~ delete _; public WaitEvent mShutdownEvent = new WaitEvent() ~ delete _; public BpSession mCurSession; bool mEnableGCCollect = true; public bool mIsAutoOpened; public int32 mStatBytesReceived; public int32 mStatReportTick; public int32 mStatBytesPerSec; public bool mFailed; public int32 mFailIdx; public bool Listening { get { return mListenSocket.IsOpen; } set { if (value) { if (!mListenSocket.IsOpen) { if (mListenSocket.Listen(mListenPort) case .Err) { Fail(scope String()..AppendF("Failed to listen on port {0}", mListenPort)); } } } else { if (mListenSocket.IsOpen) { mListenSocket.Close(); } } } } public this() { sApp = this; gApp = this; //RefreshRate = 120; } public ~this() { if (mSocketThread != null) mSocketThread.Join(); for (var client in mClients) { if (!client.IsSessionInitialized) delete client; } Widget.RemoveAndDelete(mWorkspacePanel); Widget.RemoveAndDelete(mBoard); Widget.RemoveAndDelete(mProfilePanel); Widget.RemoveAndDelete(mFindPanel); if (!mLogLines.IsEmpty) { var fs = scope FileStream(); if (fs.Open("BeefPerf.txt", .Append, .ReadWrite, .ReadWrite, 4096, .None, null) case .Ok) { var streamWriter = scope StreamWriter(fs, Encoding.UTF8, 4096); for (var line in mLogLines) streamWriter.WriteLine(line); } } } static uint32 TimeToUnixTime(DateTime ft) { // takes the last modified date int64 date = ft.ToFileTime(); // 100-nanoseconds = milliseconds * 10000 int64 adjust = 11644473600000L * 10000; // removes the diff between 1970 and 1601 date -= adjust; // converts back from 100-nanoseconds to seconds return (uint32)(date / 10000000L); } public void LogLine(String text) { mLogLines.Add(new String(text)); } public void Fail(String text) { //MessageBeep(MessageBeepType.Error); // Always write to STDOUT even if we're running as a GUI, allowing cases like RunAndWait to pass us a stdout handle Console.WriteLine("ERROR: {0}", text); mFailIdx++; if (gApp.mScriptManager.mRunningCommand) return; if (mMainWindow == null) { LogLine(text); mFailed = true; if (!mShuttingDown) Shutdown(); return; } Dialog aDialog = ThemeFactory.mDefault.CreateDialog("ERROR", text, DarkTheme.sDarkTheme.mIconError); aDialog.mDefaultButton = aDialog.AddButton("OK"); aDialog.mEscButton = aDialog.mDefaultButton; aDialog.PopupWindow(mMainWindow); } void StartFakeClient() { return; #unwarn ProcessStartInfo procInfo = scope ProcessStartInfo(); procInfo.UseShellExecute = false; procInfo.RedirectStandardError = true; procInfo.SetFileName(@"c:\proj\BeefPerf\x64\Debug\BeefPerf.exe"); procInfo.CreateNoWindow = true; var spawnedProcess = scope SpawnedProcess(); spawnedProcess.Start(procInfo).IgnoreError(); //Process process = Process.Start(procInfo).GetValueOrDefault(); //delete process; } public void UpdateTitle() { String title = scope String(); if ((mBoard.mPerfView != null) && (mBoard.mPerfView.mSession.mClientName != null)) { title.Append(mBoard.mPerfView.mSession.mClientName); title.Append(" - "); } title.Append("BeefPerf"); mMainWindow.SetTitle(title); } public override void Init() { /*uint timeNow = GetCurrentUnixTime(); FitEntry[] fitEntries = new FitEntry[200]; for (int i = 0; i < fitEntries.Length; i++) { fitEntries[i].mElapsedTimeMS = i * 500; fitEntries[i].mPower = i; fitEntries[i].mCadence = 90; } int[] lapOffsets = scope int[] {100}; WriteFit("c:\\temp\\test.fit", UnixTimeToFitTime(timeNow), fitEntries.CArray(), fitEntries.Length, lapOffsets.CArray(), lapOffsets.Length);*/ base.Init(); mAutoDirty = false; Socket.Init(); mListenSocket = new Socket(); Listening = true; mSocketThread = new Thread(new => SocketThreadProc); mSocketThread.Start(false); DarkTheme aTheme = new DarkTheme(); aTheme.Init(); ThemeFactory.mDefault = aTheme; mMainFrame = new MainFrame(); mDockingFrame = mMainFrame.mDockingFrame; BFWindow.Flags windowFlags = BFWindow.Flags.Border | BFWindow.Flags.SysMenu | //| BFWindow.Flags.CaptureMediaKeys | BFWindow.Flags.Caption | BFWindow.Flags.Minimize | BFWindow.Flags.QuitOnClose | BFWindowBase.Flags.Resizable | BFWindow.Flags.Menu | BFWindow.Flags.SysMenu; if (mWantsFullscreen) windowFlags |= BFWindowBase.Flags.Fullscreen; //mRootWidget = new Widget(); mWorkspacePanel = new WorkspacePanel(); mBoard = new Board(); mProfilePanel = new ProfilePanel(); mFindPanel = new FindPanel(); //mRootWidget = mBoard; mMainWindow = new WidgetWindow(null, "BeefPerf", 0, 0, 1600, 1200, windowFlags, mMainFrame); mMainWindow.mOnWindowKeyDown.Add(new => SysKeyDown); mMainWindow.SetMinimumSize(480, 360); mMainWindow.mIsMainWindow = true; CreateMenu(); ShowWorkspacePanel(); ShowTimelinePanel(); ShowProfilePanel(); ShowFindPanel(); StartFakeClient(); GC.SetAutoCollectPeriod(mEnableGCCollect ? 20 : -1); GC.SetCollectFreeThreshold(mEnableGCCollect ? 64*1024*1024 : -1); } public override void Stop() { base.Stop(); mListenSocket.Close(); } void ShowWorkspacePanel() { if (mWorkspacePanel.mWidgetWindow != null) { return; } TabbedView tabbedView = CreateTabbedView(); SetupTab(tabbedView, "Workspace", mWorkspacePanel); tabbedView.SetRequestedSize(320, 320); if ((mDockingFrame.mSplitType == .Vert) || (mDockingFrame.mDockedWidgets.Count == 1)) mDockingFrame.AddDockedWidget(tabbedView, mDockingFrame.mDockedWidgets[0], DockingFrame.WidgetAlign.Left); else mDockingFrame.AddDockedWidget(tabbedView, null, DockingFrame.WidgetAlign.Top); } void ShowTimelinePanel() { if (mBoard.mWidgetWindow != null) { return; } TabbedView tabbedView = CreateTabbedView(); SetupTab(tabbedView, "Timeline", mBoard); tabbedView.SetRequestedSize(250, 250); tabbedView.mIsFillWidget = true; if ((mDockingFrame.mSplitType == .Vert) || (mDockingFrame.mDockedWidgets.Count == 1)) mDockingFrame.AddDockedWidget(tabbedView, mDockingFrame.mDockedWidgets[0], DockingFrame.WidgetAlign.Right); else mDockingFrame.AddDockedWidget(tabbedView, null, DockingFrame.WidgetAlign.Top); } void ShowProfilePanel() { if (mProfilePanel.mWidgetWindow != null) { return; } var tabbedView = CreateTabbedView(); SetupTab(tabbedView, "Profile", mProfilePanel); tabbedView.SetRequestedSize(300, 300); if ((mDockingFrame.mSplitType == .Vert) && (mDockingFrame.mDockedWidgets.Count > 0)) mDockingFrame.AddDockedWidget(tabbedView, mDockingFrame.mDockedWidgets.Back, DockingFrame.WidgetAlign.Left); else mDockingFrame.AddDockedWidget(tabbedView, null, DockingFrame.WidgetAlign.Bottom); } void ShowFindPanel() { if (mFindPanel.mWidgetWindow != null) { return; } var tabbedView = CreateTabbedView(); SetupTab(tabbedView, "Find", mFindPanel); tabbedView.SetRequestedSize(300, 300); if ((mDockingFrame.mSplitType == .Vert) && (mDockingFrame.mDockedWidgets.Count > 0)) mDockingFrame.AddDockedWidget(tabbedView, mDockingFrame.mDockedWidgets.Back, DockingFrame.WidgetAlign.Right); else mDockingFrame.AddDockedWidget(tabbedView, null, DockingFrame.WidgetAlign.Bottom); } void SetupTab(TabbedView tabbedView, String label, Widget widget) { var tabButton = tabbedView.AddTab(label, 0, widget, false); tabButton.mCloseClickedEvent.Add(new () => { var tabbedView = tabButton.mTabbedView; tabbedView.RemoveTab(tabButton); if (tabbedView.mTabs.IsEmpty) { tabbedView.mParentDockingFrame.RemoveDockedWidget(tabbedView); gApp.DeferDelete(tabbedView); } }); } void ToggleCheck(IMenu menu, ref bool checkVal) { checkVal = !checkVal; var sysMenu = (SysMenu)menu; sysMenu.Modify(null, null, null, true, checkVal ? 1 : 0); } void ShowInfo() { int streamSize = 0; int lodStreamSize = 0; for (var client in mClients) { for (var thread in client.mThreads) { for (var streamData in thread.mStreamDataList) { streamSize += streamData.mBuffer.Count; } for (var streamLOD in thread.mStreamLODs) { for (var streamData in streamLOD.mStreamDataList) { lodStreamSize += streamData.mBuffer.Count; } } } } Debug.WriteLine("Stream Size: {0}k", streamSize / 1024); Debug.WriteLine("LOD Stream Size: {0}k", lodStreamSize / 1024); } public void CloseSession() { SetSession(null); } public void OpenSession() { List iList = scope List(); iList.Add(123); var fileDialog = scope OpenFileDialog(); fileDialog.ShowReadOnly = false; fileDialog.Title = "Open Session"; fileDialog.Multiselect = true; fileDialog.ValidateNames = true; fileDialog.DefaultExt = ".bfps"; fileDialog.SetFilter("BeefPerf Session (*.bfps)|*.bfps|All files (*.*)|*.*"); if (fileDialog.ShowDialog() case .Ok) { for (String origProjFilePath in fileDialog.FileNames) { var session = new BpSession(false); switch (session.Load(origProjFilePath)) { case .Ok: mSessions.Add(session); MarkDirty(); case .Err(let err): switch (err) { case .FileNotFound, .FileError: Fail(scope String()..AppendF("Failed to open file '{0}'", origProjFilePath)); case .InvalidData: Fail(scope String()..AppendF("Invalid data in file '{0}'", origProjFilePath)); case .InvalidVersion: Fail(scope String()..AppendF("Unsupported version in file '{0}'", origProjFilePath)); } delete session; } } } } public void SaveSession() { if (mCurSession == null) return; var fileDialog = scope SaveFileDialog(); //fileDialog.ShowReadOnly = false; fileDialog.Title = "Save Session"; fileDialog.Multiselect = true; fileDialog.ValidateNames = true; fileDialog.DefaultExt = ".bfps"; fileDialog.SetFilter("BeefPerf Session (*.bfps)|*.bfps|All files (*.*)|*.*"); if (fileDialog.ShowDialog() case .Ok) { for (String origProjFilePath in fileDialog.FileNames) { if (mCurSession.Save(origProjFilePath) case .Err) { Fail(scope String()..AppendF("Failed to save file '{0}'", origProjFilePath)); } } } } public void RemoveSession() { if (mCurSession == null) return; var session = mCurSession; SetSession(null); mSessions.Remove(session); using (mClientMonitor.Enter()) { var client = session as BpClient; if (client != null) mClients.Remove(client); delete session; } MarkDirty(); } public void RemoveAllSessions() { SetSession(null); using (mClientMonitor.Enter()) { ClearAndDeleteItems(mSessions); mSessions.Clear(); mClients.Clear(); } MarkDirty(); } public void CreateMenu() { SysMenu root = mMainWindow.mSysMenu; SysMenu subMenu = root.AddMenuItem("&File"); subMenu.AddMenuItem("&Open Session", "Ctrl+O", new (menu) => { OpenSession(); }); subMenu.AddMenuItem("&Save Session", "Ctrl+S", new (menu) => { SaveSession(); }); subMenu.AddMenuItem("&Close Session", "Ctrl+W", new (menu) => { CloseSession(); }); subMenu.AddMenuItem("&Remove Session", null, new (menu) => { RemoveSession(); }); subMenu.AddMenuItem("Remove &All Sessions", null, new (menu) => { RemoveAllSessions(); }); subMenu.AddMenuItem("&Info", "Ctrl+I", new (menu) => { ShowInfo(); }); subMenu.AddMenuItem("E&xit", null, new (menu) => { mMainWindow.Close(); }); subMenu = root.AddMenuItem("&View"); subMenu.AddMenuItem("&Workspace", "Ctrl+Alt+W", new (menu) => { ShowWorkspacePanel(); }); subMenu.AddMenuItem("&Timeline", "Ctrl+Alt+T", new (menu) => { ShowTimelinePanel(); }); subMenu.AddMenuItem("&Profile", "Ctrl+Alt+P", new (menu) => { ShowProfilePanel(); }); subMenu.AddMenuItem("&Find", "Ctrl+Alt+F", new (menu) => { ShowFindPanel(); }); subMenu = root.AddMenuItem("&Debug"); subMenu.AddMenuItem("GC Collect", null, new (menu) => { if (Profiler.StartSampling() case .Ok(let id)) { GC.Collect(); id.Dispose(); } }); subMenu.AddMenuItem("Enable GC Collect", null, new (menu) => { ToggleCheck(menu, ref mEnableGCCollect); }, null, null, true, mEnableGCCollect ? 1 : 0); } public override void Shutdown() { base.Shutdown(); mShutdownEvent.Set(true); } public override bool HandleCommandLineParam(String key, String value) { switch (key) { case "-autoOpened": mIsAutoOpened = true; return true; case "-cmd": if (value == null) return false; Socket.Init(); LogLine(scope String()..AppendF("Cmd: {0}", value)); bool alreadyRunning = false; // { Socket socket = scope Socket(); if (socket.Listen(mListenPort) case .Err) alreadyRunning = true; } if (!alreadyRunning) { LogLine("Launching BeefPerf process"); var exePath = scope String(); var curProcess = scope Process(); curProcess.GetProcessById(Process.CurrentId); exePath.Append(curProcess.ProcessName); ProcessStartInfo procInfo = scope ProcessStartInfo(); procInfo.SetFileName(exePath); procInfo.SetArguments("-autoOpened"); SpawnedProcess spawnedProcess = scope SpawnedProcess(); spawnedProcess.Start(procInfo); } // Try for 10 seconds for (int i < 100) { Socket socket = scope Socket(); if (socket.Connect("127.0.0.1", mListenPort) case .Err) { Thread.Sleep(100); continue; } DynMemStream memStream = scope DynMemStream(); memStream.Write((int32)0); // Size placeholder memStream.Write((uint8)BpCmd.Cmd); String cmdStr = value; memStream.Write(cmdStr); memStream.Write((uint8)0); (*(int32*)memStream.Ptr) = (int32)memStream.Length - 4; // Try 10 seconds to send for (int sendItr < 100) { if (memStream.IsEmpty) break; switch (socket.Send(memStream.Ptr, memStream.Length)) { case .Ok(let sendLen): memStream.RemoveFromStart(sendLen); case .Err: Fail("Failed to send command"); } if (memStream.IsEmpty) break; Thread.Sleep(100); // Wait until we can send more... } // Try 10 seconds for a response bool gotResult = false; RecvLoop: for (int recvStr < 100) { int result = 0; switch (socket.Recv(&result, 1)) { case .Ok(let recvLen): if (recvLen > 0) { if (result != 1) Fail("Command failed"); gotResult = true; break RecvLoop; } case .Err: break; } Thread.Sleep(100); } if (!gotResult) { Fail("Failed to receive command response"); } if (!mShuttingDown) Shutdown(); return true; } Fail("Failed to connect to BeefPerf"); case "-fullscreen": mWantsFullscreen = true; case "-windowed": mWantsFullscreen = false; default: return base.HandleCommandLineParam(key, value); } return true; } public override void UnhandledCommandLine(String key, String value) { Fail(StackStringFormat!("Unhandled command line param: {0}", key)); } void SetupNewWindow(WidgetWindow window) { window.mOnWindowKeyDown.Add(new => SysKeyDown); //window.mWindowCloseQueryHandler.Add(new => SecondaryAllowClose); } DarkTabbedView CreateTabbedView() { var tabbedView = new DarkTabbedView(); tabbedView.mSharedData.mOpenNewWindowDelegate.Add(new (fromTabbedView, newWindow) => SetupNewWindow(newWindow)); return tabbedView; } public void PushMainWidget(Widget widget) { if (mMainWidget != null) { mMainWidget.RemoveSelf(); DeferDelete(mMainWidget); } mMainWidget = widget; mRootWidget.AddWidget(widget); widget.Resize(0, 0, mRootWidget.mWidth, mRootWidget.mHeight); widget.SetFocus(); } void SocketThreadProc() { while (true) { if (mShutdownEvent.WaitFor(0)) return; //TODO: Add client sockets to the select set var readSet = default(Socket.FDSet); var writeSet = default(Socket.FDSet); var exceptSet = default(Socket.FDSet); void Add(Socket socket) { readSet.Add(socket.[Friend]mHandle); exceptSet.Add(socket.[Friend]mHandle); } Add(mListenSocket); using (mClientMonitor.Enter()) { for (var client in mClients) Add(client.mSocket); } #unwarn int selectResult = Socket.Select(&readSet, &writeSet, &exceptSet, 20*1000); int clientIdx = 0; while (true) { BpClient client = null; using (mClientMonitor.Enter()) { if (clientIdx < mClients.Count) { client = mClients[clientIdx++]; } if (client == null) break; client.TryRecv(); } } } } public override void Update(bool batchStart) { base.Update(batchStart); /*if (!mListenSocket.IsConnected) { Listen(); }*/ Socket newConnection = new Socket(); if (newConnection.AcceptFrom(mListenSocket) case .Ok) { using (mClientMonitor.Enter()) { BpClient client = new BpClient(); //client.mProfileId = Profiler.StartSampling(); client.mConnectTime = DateTime.Now; client.mSocket = newConnection; mClients.Add(client); } /*{ // Kill all old clients - remove this when we support switching between clients ClearAndDeleteItems(mClients); mFindPanel.Clear(); mProfilePanel.Clear(); mFindPanel.mNeedsRestartSearch = true; BpClient client = new BpClient(); client.mSocket = newConnection; mClients.Add(client); SetClient(client); }*/ } else delete newConnection; using (mClientMonitor.Enter()) { for (var client in mClients) { client.Update(); if (client.mSessionOver) { if (!client.IsSessionInitialized) delete client; @client.Remove(); if (mClients.Count == 0) { gApp.mStatBytesPerSec = 0; } MarkDirty(); } if (mUpdateCnt % 20 == 0) MarkDirty(); } } if (mUpdateCnt - mStatReportTick >= 60) { mStatBytesPerSec = mStatBytesReceived; mStatBytesReceived = 0; mStatReportTick = mUpdateCnt; } } void SysKeyDown(KeyDownEvent evt) { var window = (WidgetWindow)evt.mSender; #unwarn Widget focusWidget = window.mFocusWidget; if (evt.mKeyFlags == 0) // No ctrl/shift/alt { switch (evt.mKeyCode) { default: } } if (evt.mKeyFlags == .Ctrl) { switch (evt.mKeyCode) { case (KeyCode)'F': ShowFind(); case (KeyCode)'C': if (DarkTooltipManager.sTooltip != null) { SetClipboardText(DarkTooltipManager.sTooltip.mText); } default: } } } enum ShowTabResult { Existing, OpenedNew } public void WithDocumentTabbedViews(Action func) { for (int32 windowIdx = 0; windowIdx < mWindows.Count; windowIdx++) { var window = mWindows[windowIdx]; var widgetWindow = window as WidgetWindow; if (widgetWindow != null) { var darkDockingFrame = widgetWindow.mRootWidget as DarkDockingFrame; if (widgetWindow == mMainWindow) darkDockingFrame = mDockingFrame; if (darkDockingFrame != null) { darkDockingFrame.WithAllDockedWidgets(scope (dockedWidget) => { var tabbedView = dockedWidget as DarkTabbedView; if (tabbedView != null) func(tabbedView); }); } } } } public void WithTabs(Action func) { WithDocumentTabbedViews(scope (documentTabbedView) => { documentTabbedView.WithTabs(func); }); } public TabbedView.TabButton GetTab(Widget content) { TabbedView.TabButton tab = null; WithTabs(scope [&] (checkTab) => { if (checkTab.mContent == content) tab = checkTab; }); return tab; } TabbedView FindTabbedView(DockingFrame dockingFrame, int32 xDir, int32 yDir) { bool useFirst = true; if (dockingFrame.mSplitType == DockingFrame.SplitType.Horz) { useFirst = xDir > 0; } else useFirst = yDir > 0; for (int32 pass = 0; pass < 2; pass++) { for (int32 i = 0; i < dockingFrame.mDockedWidgets.Count; i++) { if ((useFirst) && (i == 0) && (pass == 0)) continue; var widget = dockingFrame.mDockedWidgets[i]; if (widget is TabbedView) return (TabbedView)widget; DockingFrame childFrame = widget as DockingFrame; if (childFrame != null) { TabbedView tabbedView = FindTabbedView(childFrame, xDir, yDir); if (tabbedView != null) return tabbedView; } } } return null; } ShowTabResult ShowTab(Widget tabContent, String name, float width, bool ownsContent) { var result = ShowTabResult.Existing; var tabButton = GetTab(tabContent); if (tabButton == null) { TabbedView tabbedView = FindTabbedView(mDockingFrame, -1, 1); if (tabbedView != null) { tabButton = tabbedView.AddTab(name, width, tabContent, ownsContent); result = ShowTabResult.OpenedNew; } } if (tabButton != null) tabButton.Activate(); return result; } void ShowPanel(Widget panel, String label) { //RecordHistoryLocation(); ShowTab(panel, label, 150, false); //panel.FocusForKeyboard(); } public void ShowFind() { ShowPanel(mFindPanel, "Find"); mFindPanel.mEntryEdit.SetFocus(); } public void SetSession(BpSession session) { if (session == mCurSession) return; mFindPanel.Clear(); mProfilePanel.Clear(); mFindPanel.mNeedsRestartSearch = true; mCurSession = session; mBoard.ShowSession(session); mFindPanel.Show(mBoard.mPerfView); mFindPanel.Clear(); MarkDirty(); /*if (mBoard.mPerfView != null) { mBoard.mPerfView.SaveSummary("BfCompiler_Compile", @"c:\temp\save.txt"); }*/ } public void ZoneSelected(PerfView perfView, BPSelection selection) { mProfilePanel.Show(perfView, selection); } public void ReportBytesReceived(int32 count) { mStatBytesReceived += count; } public bool HandleClientCommand(BpClient client, StringView cmd) { int failIdx = mFailIdx; mScriptManager.Exec(cmd); return failIdx == mFailIdx; } public void SessionInitialized(BpClient bpClient) { mSessions.Add(bpClient); UpdateTitle(); MarkDirty(); } } } static { static BeefPerf.BPApp gApp; }