< Summary

Class:DCL.PerformanceMeterController
Assembly:PerformanceMeterController
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/Debugging/Performance/PerformanceMeterController/PerformanceMeterController.cs
Covered lines:0
Uncovered lines:88
Coverable lines:88
Total lines:295
Line coverage:0% (0 of 88)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
ToString()0%2100%
CompareTo(...)0%20400%
PerformanceMeterController()0%2100%
ResetDataValues()0%2100%
StartSampling(...)0%2100%
StopSampling()0%6200%
OnMetricsChange(...)0%30500%
ProcessSamples()0%2100%
ReportData()0%2100%
CalculatePerformanceScore()0%12300%
CalculateHiccupsPercentage()0%2100%
Log(...)0%2100%

File(s)

/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/Debugging/Performance/PerformanceMeterController/PerformanceMeterController.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using UnityEngine;
 4using DCL.FPSDisplay;
 5using DCL.SettingsCommon;
 6using Newtonsoft.Json;
 7
 8namespace DCL
 9{
 10    /// <summary>
 11    /// Performance Meter Tool
 12    ///
 13    /// It samples frames performance data for the target duration and prints a complete report when finished.
 14    ///
 15    /// There are 2 ways to trigger this tool usage:
 16    /// A: While the client is running in the browser, open the browser console and run "clientDebug.RunPerformanceMeter
 17    /// B: In Unity Editor select the "Main" gameobject and right-click on its DebugBridge Monobehaviour, from there a d
 18    /// </summary>
 19    public class PerformanceMeterController
 20    {
 21        private class SampleData : IComparable
 22        {
 23            public int frameNumber;
 24            public float millisecondsConsumed;
 25            public bool isHiccup = false;
 26            public float currentTime;
 27            public float fpsAtThisFrameInTime;
 28
 29            public override string ToString()
 30            {
 031                return "frame number: " + frameNumber
 32                                        + "\n frame consumed milliseconds: " + millisecondsConsumed
 33                                        + "\n is hiccup: " + isHiccup
 34                                        + "\n fps at this frame: " + fpsAtThisFrameInTime;
 35            }
 36
 37            public int CompareTo(object obj)
 38            {
 39                // 0    -> this and otherSample are equal
 40                // 1    -> this is greater
 41                // -1   -> otherSample is greater
 42
 043                SampleData otherSample = obj as SampleData;
 44
 045                if (otherSample == null)
 046                    return 1;
 47
 048                if (this.fpsAtThisFrameInTime == otherSample.fpsAtThisFrameInTime)
 049                    return 0;
 50
 051                return this.fpsAtThisFrameInTime > otherSample.fpsAtThisFrameInTime ? 1 : -1;
 52            }
 53        }
 54
 55        private PerformanceMetricsDataVariable metricsData;
 56        private float currentDurationInSeconds = 0f;
 57        private float targetDurationInSeconds = 0f;
 058        private List<SampleData> samples = new List<SampleData>();
 59
 60        // auxiliar data
 61        private SampleData lastSavedSample;
 62        private float fpsSum = 0;
 63
 64        // reported data
 65        private float highestFPS;
 66        private float lowestFPS;
 67        private float averageFPS;
 68        private float percentile50FPS;
 69        private float percentile95FPS;
 70        private int totalHiccupFrames;
 71        private float totalHiccupsTimeInSeconds;
 72        private int totalFrames;
 73        private float totalFramesTimeInSeconds;
 74
 075        public PerformanceMeterController() { metricsData = Resources.Load<PerformanceMetricsDataVariable>("ScriptableOb
 76
 77        private void ResetDataValues()
 78        {
 079            samples.Clear();
 080            currentDurationInSeconds = 0f;
 081            targetDurationInSeconds = 0f;
 82
 083            lastSavedSample = null;
 084            fpsSum = 0;
 85
 086            highestFPS = 0;
 087            lowestFPS = 0;
 088            averageFPS = 0;
 089            percentile50FPS = 0;
 090            percentile95FPS = 0;
 091            totalHiccupFrames = 0;
 092            totalHiccupsTimeInSeconds = 0;
 093            totalFrames = 0;
 094            totalFramesTimeInSeconds = 0;
 095        }
 96
 97        /// <summary>
 98        /// Starts the Performance Meter Tool sampling.
 99        /// </summary>
 100        /// <param name="durationInSeconds">The target duration for the running of the tool, after which a report will b
 101        public void StartSampling(float durationInSeconds)
 102        {
 0103            Log("Start running... target duration: " + durationInSeconds + " seconds");
 104
 0105            ResetDataValues();
 106
 0107            targetDurationInSeconds = durationInSeconds;
 108
 0109            metricsData.OnChange += OnMetricsChange;
 0110        }
 111
 112        /// <summary>
 113        /// Stops the Performance Meter Tool sampling, processes the data gathered and prints a full report in the conso
 114        /// </summary>
 115        public void StopSampling()
 116        {
 0117            Log("Stopped running.");
 118
 0119            metricsData.OnChange -= OnMetricsChange;
 120
 0121            if (samples.Count == 0)
 122            {
 0123                Log("No samples were gathered, the duration time in seconds set is probably too small");
 0124                return;
 125            }
 126
 0127            ProcessSamples();
 128
 0129            ReportData();
 0130        }
 131
 132        /// <summary>
 133        /// Callback triggered on every update made to the PerformanceMetricsDataVariable ScriptableObject, done every f
 134        /// </summary>
 135        /// /// <param name="newData">NEW version of the PerformanceMetricsDataVariable ScriptableObject</param>
 136        /// /// <param name="oldData">OLD version of the PerformanceMetricsDataVariable ScriptableObject</param>
 137        private void OnMetricsChange(PerformanceMetricsData newData, PerformanceMetricsData oldData)
 138        {
 0139            float secondsConsumed = 0;
 140
 0141            if (lastSavedSample != null)
 142            {
 0143                if (lastSavedSample.frameNumber == Time.frameCount)
 144                {
 0145                    Log("PerformanceMetricsDataVariable changed more than once in the same frame!");
 0146                    return;
 147                }
 148
 0149                secondsConsumed = Time.timeSinceLevelLoad - lastSavedSample.currentTime;
 150            }
 151
 0152            SampleData newSample = new SampleData()
 153            {
 154                frameNumber = Time.frameCount,
 155                fpsAtThisFrameInTime = newData.fpsCount,
 156                millisecondsConsumed = secondsConsumed * 1000,
 157                currentTime = Time.timeSinceLevelLoad
 158            };
 0159            newSample.isHiccup = secondsConsumed > FPSEvaluation.HICCUP_THRESHOLD_IN_SECONDS;
 0160            samples.Add(newSample);
 0161            lastSavedSample = newSample;
 162
 0163            if (newSample.isHiccup)
 164            {
 0165                totalHiccupFrames++;
 0166                totalHiccupsTimeInSeconds += secondsConsumed;
 167            }
 168
 0169            fpsSum += newData.fpsCount;
 170
 0171            totalFrames++;
 172
 0173            currentDurationInSeconds += Time.deltaTime;
 0174            if (currentDurationInSeconds > targetDurationInSeconds)
 175            {
 0176                totalFramesTimeInSeconds = currentDurationInSeconds;
 0177                StopSampling();
 178            }
 0179        }
 180
 181        /// <summary>
 182        /// Process the data gathered from every frame sample to calculate the final highestFPS, lowestFPS, averageFPS, 
 183        /// </summary>
 184        private void ProcessSamples()
 185        {
 186            // Sort the samples based on FPS count of each one, to be able to calculate the percentiles later
 0187            var sortedSamples = new List<SampleData>(samples);
 0188            sortedSamples.Sort();
 189
 0190            int samplesCount = sortedSamples.Count;
 191
 0192            highestFPS = sortedSamples[samplesCount - 1].fpsAtThisFrameInTime;
 0193            lowestFPS = sortedSamples[0].fpsAtThisFrameInTime;
 194
 0195            averageFPS = fpsSum / sortedSamples.Count;
 196
 0197            percentile50FPS = sortedSamples[Mathf.CeilToInt(samplesCount * 0.5f)].fpsAtThisFrameInTime;
 0198            percentile95FPS = sortedSamples[Mathf.CeilToInt(samplesCount * 0.95f)].fpsAtThisFrameInTime;
 0199        }
 200
 201        /// <summary>
 202        /// Formats and prints the final data report following the 3 steps: system info, processed values and frame samp
 203        /// </summary>
 204        private void ReportData()
 205        {
 206            // TODO: We could build a text file (or html) template with replaceable tags like #OPERATING_SYSTEM, #GRAPHI
 207
 208            // Step 1 - report relevant system info: hardware, cappedFPS, OS, sampling duration, etc.
 0209            Log("Data report step 1 - System and Graphics info:"
 210                + "\n * Sampling duration in seconds -> " + targetDurationInSeconds
 211                + "\n * System Info -> Operating System -> " + SystemInfo.operatingSystem
 212                + "\n * System Info -> Device Name -> " + SystemInfo.deviceName
 213                + "\n * System Info -> Graphics Device Name -> " + SystemInfo.graphicsDeviceName
 214                + "\n * System Info -> System RAM Size -> " + SystemInfo.systemMemorySize
 215                + "\n * General Settings -> Scenes Load Radius -> " + Settings.i.generalSettings.Data.scenesLoadRadius
 216                + "\n * Quality Settings -> FPSCap -> " + Settings.i.qualitySettings.Data.fpsCap
 217                + "\n * Quality Settings -> Bloom -> " + Settings.i.qualitySettings.Data.bloom
 218                + "\n * Quality Settings -> Shadow -> " + Settings.i.qualitySettings.Data.shadows
 219                + "\n * Quality Settings -> Antialising -> " + Settings.i.qualitySettings.Data.antiAliasing
 220                + "\n * Quality Settings -> Base Resolution -> " + Settings.i.qualitySettings.Data.baseResolution
 221                + "\n * Quality Settings -> Display Name -> " + Settings.i.qualitySettings.Data.displayName
 222                + "\n * Quality Settings -> Render Scale -> " + Settings.i.qualitySettings.Data.renderScale
 223                + "\n * Quality Settings -> Shadow Distance -> " + Settings.i.qualitySettings.Data.shadowDistance
 224                + "\n * Quality Settings -> Shadow Resolution -> " + Settings.i.qualitySettings.Data.shadowResolution
 225                + "\n * Quality Settings -> Soft Shadows -> " + Settings.i.qualitySettings.Data.softShadows
 226                + "\n * Quality Settings -> SSAO Quality -> " + Settings.i.qualitySettings.Data.ssaoQuality
 227                + "\n * Quality Settings -> Camera Draw Distance -> " + Settings.i.qualitySettings.Data.cameraDrawDistan
 228                + "\n * Quality Settings -> Detail Object Culling Enabled -> " + Settings.i.qualitySettings.Data.enableD
 229                + "\n * Quality Settings -> Detail Object Culling Limit -> " + Settings.i.qualitySettings.Data.detailObj
 230                + "\n * Quality Settings -> Reflection Quality -> " + Settings.i.qualitySettings.Data.reflectionResoluti
 231            );
 232
 233            // Step 2 - report processed data
 0234            Log("Data report step 2 - Processed values:"
 235                + "\n * PERFORMANCE SCORE (0-100) -> " + CalculatePerformanceScore()
 236                + "\n * average FPS -> " + averageFPS
 237                + "\n * highest FPS -> " + highestFPS
 238                + "\n * lowest FPS -> " + lowestFPS
 239                + "\n * 50 percentile (median) FPS -> " + percentile50FPS
 240                + "\n * 95 percentile FPS -> " + percentile95FPS
 241                + $"\n * total hiccups (>{FPSEvaluation.HICCUP_THRESHOLD_IN_SECONDS}ms frames) -> {totalHiccupFrames} ({
 242                + "\n * total hiccups time (seconds) -> " + totalHiccupsTimeInSeconds
 243                + "\n * total frames -> " + totalFrames
 244                + "\n * total frames time (seconds) -> " + totalFramesTimeInSeconds
 245            );
 246
 247            // Step 3 - report all samples data
 0248            string rawSamplesJSON = "Data report step 3 - Raw samples:"
 249                                    + "\n "
 250                                    + "{\"frame-samples\": " + JsonConvert.SerializeObject(samples) + "}";
 251
 252#if !UNITY_WEBGL && UNITY_EDITOR
 0253            string targetFilePath = Application.persistentDataPath + "/PerformanceMeterRawFrames.txt";
 0254            Log("Data report step 3 - Trying to dump raw samples JSON at: " + targetFilePath);
 0255            System.IO.File.WriteAllText(targetFilePath, rawSamplesJSON);
 256#endif
 257
 0258            Log(rawSamplesJSON);
 0259        }
 260
 261        /// <summary>
 262        /// Calculates a performance score from 0 to 100 based on the average FPS (compared to the max possible FPS) and
 263        /// </summary>
 264        private float CalculatePerformanceScore()
 265        {
 0266            float topFPS = Settings.i.qualitySettings.Data.fpsCap ? 30f : 60f;
 0267            float fpsScore = Mathf.Min(averageFPS / topFPS, 1); // from 0 to 1
 0268            float hiccupsScore = 1 - ((float)totalHiccupFrames / samples.Count); // from 0 to 1
 0269            float performanceScore = (fpsScore + hiccupsScore) / 2 * 100; // scores sum / amount of scores * 100 to have
 0270            performanceScore = Mathf.Round(performanceScore * 100f) / 100f; // to save only 2 decimals
 271
 0272            return performanceScore;
 273        }
 274
 275        private float CalculateHiccupsPercentage()
 276        {
 0277            float percentage = ((float)totalHiccupFrames / totalFrames) * 100;
 0278            percentage = Mathf.Round(percentage * 100f) / 100f; // to have 2 decimals
 0279            return percentage;
 280        }
 281
 282        /// <summary>
 283        /// Logs the tool messages in console regardless of the "Debug.unityLogger.logEnabled" value.
 284        /// </summary>
 285        private void Log(string message)
 286        {
 0287            bool originalLogEnabled = Debug.unityLogger.logEnabled;
 0288            Debug.unityLogger.logEnabled = true;
 289
 0290            Debug.Log("PerformanceMeter - " + message);
 291
 0292            Debug.unityLogger.logEnabled = originalLogEnabled;
 0293        }
 294    }
 295}