< Summary

Class:DCL.PerformanceMeterController
Assembly:PerformanceMeterController
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/Debugging/Performance/PerformanceMeterController/PerformanceMeterController.cs
Covered lines:2
Uncovered lines:83
Coverable lines:85
Total lines:295
Line coverage:2.3% (2 of 85)
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%110100%
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 Newtonsoft.Json;
 6
 7namespace DCL
 8{
 9    /// <summary>
 10    /// Performance Meter Tool
 11    ///
 12    /// It samples frames performance data for the target duration and prints a complete report when finished.
 13    ///
 14    /// There are 2 ways to trigger this tool usage:
 15    /// A: While the client is running in the browser, open the browser console and run "clientDebug.RunPerformanceMeter
 16    /// B: In Unity Editor select the "Main" gameobject and right-click on its DebugBridge Monobehaviour, from there a d
 17    /// </summary>
 18    public class PerformanceMeterController
 19    {
 20        private class SampleData : IComparable
 21        {
 22            public int frameNumber;
 23            public float millisecondsConsumed;
 24            public bool isHiccup = false;
 25            public float currentTime;
 26            public float fpsAtThisFrameInTime;
 27
 28            public override string ToString()
 29            {
 030                return "frame number: " + frameNumber
 31                                        + "\n frame consumed milliseconds: " + millisecondsConsumed
 32                                        + "\n is hiccup: " + isHiccup
 33                                        + "\n fps at this frame: " + fpsAtThisFrameInTime;
 34            }
 35
 36            public int CompareTo(object obj)
 37            {
 38                // 0    -> this and otherSample are equal
 39                // 1    -> this is greater
 40                // -1   -> otherSample is greater
 41
 042                SampleData otherSample = obj as SampleData;
 43
 044                if (otherSample == null)
 045                    return 1;
 46
 047                if (this.fpsAtThisFrameInTime == otherSample.fpsAtThisFrameInTime)
 048                    return 0;
 49
 050                return this.fpsAtThisFrameInTime > otherSample.fpsAtThisFrameInTime ? 1 : -1;
 51            }
 52        }
 53
 54        private PerformanceMetricsDataVariable metricsData;
 55        private float currentDurationInSeconds = 0f;
 56        private float targetDurationInSeconds = 0f;
 66657        private List<SampleData> samples = new List<SampleData>();
 58
 59        // auxiliar data
 60        private SampleData lastSavedSample;
 61        private float fpsSum = 0;
 62
 63        // reported data
 64        private float highestFPS;
 65        private float lowestFPS;
 66        private float averageFPS;
 67        private float percentile50FPS;
 68        private float percentile95FPS;
 69        private int totalHiccupFrames;
 70        private float totalHiccupsTimeInSeconds;
 71        private int totalFrames;
 72        private float totalFramesTimeInSeconds;
 73
 199874        public PerformanceMeterController() { metricsData = Resources.Load<PerformanceMetricsDataVariable>("ScriptableOb
 75
 76        private void ResetDataValues()
 77        {
 078            samples.Clear();
 079            currentDurationInSeconds = 0f;
 080            targetDurationInSeconds = 0f;
 81
 082            lastSavedSample = null;
 083            fpsSum = 0;
 84
 085            highestFPS = 0;
 086            lowestFPS = 0;
 087            averageFPS = 0;
 088            percentile50FPS = 0;
 089            percentile95FPS = 0;
 090            totalHiccupFrames = 0;
 091            totalHiccupsTimeInSeconds = 0;
 092            totalFrames = 0;
 093            totalFramesTimeInSeconds = 0;
 094        }
 95
 96        /// <summary>
 97        /// Starts the Performance Meter Tool sampling.
 98        /// </summary>
 99        /// <param name="durationInSeconds">The target duration for the running of the tool, after which a report will b
 100        public void StartSampling(float durationInSeconds)
 101        {
 0102            Log("Start running... target duration: " + durationInSeconds + " seconds");
 103
 0104            ResetDataValues();
 105
 0106            targetDurationInSeconds = durationInSeconds;
 107
 0108            metricsData.OnChange += OnMetricsChange;
 0109        }
 110
 111        /// <summary>
 112        /// Stops the Performance Meter Tool sampling, processes the data gathered and prints a full report in the conso
 113        /// </summary>
 114        public void StopSampling()
 115        {
 0116            Log("Stopped running.");
 117
 0118            metricsData.OnChange -= OnMetricsChange;
 119
 0120            if (samples.Count == 0)
 121            {
 0122                Log("No samples were gathered, the duration time in seconds set is probably too small");
 0123                return;
 124            }
 125
 0126            ProcessSamples();
 127
 0128            ReportData();
 0129        }
 130
 131        /// <summary>
 132        /// Callback triggered on every update made to the PerformanceMetricsDataVariable ScriptableObject, done every f
 133        /// </summary>
 134        /// /// <param name="newData">NEW version of the PerformanceMetricsDataVariable ScriptableObject</param>
 135        /// /// <param name="oldData">OLD version of the PerformanceMetricsDataVariable ScriptableObject</param>
 136        private void OnMetricsChange(PerformanceMetricsData newData, PerformanceMetricsData oldData)
 137        {
 0138            float secondsConsumed = 0;
 139
 0140            if (lastSavedSample != null)
 141            {
 0142                if (lastSavedSample.frameNumber == Time.frameCount)
 143                {
 0144                    Log("PerformanceMetricsDataVariable changed more than once in the same frame!");
 0145                    return;
 146                }
 147
 0148                secondsConsumed = Time.timeSinceLevelLoad - lastSavedSample.currentTime;
 149            }
 150
 0151            SampleData newSample = new SampleData()
 152            {
 153                frameNumber = Time.frameCount,
 154                fpsAtThisFrameInTime = newData.fpsCount,
 155                millisecondsConsumed = secondsConsumed * 1000,
 156                currentTime = Time.timeSinceLevelLoad
 157            };
 0158            newSample.isHiccup = secondsConsumed > FPSEvaluation.HICCUP_THRESHOLD_IN_SECONDS;
 0159            samples.Add(newSample);
 0160            lastSavedSample = newSample;
 161
 0162            if (newSample.isHiccup)
 163            {
 0164                totalHiccupFrames++;
 0165                totalHiccupsTimeInSeconds += secondsConsumed;
 166            }
 167
 0168            fpsSum += newData.fpsCount;
 169
 0170            totalFrames++;
 171
 0172            currentDurationInSeconds += Time.deltaTime;
 0173            if (currentDurationInSeconds > targetDurationInSeconds)
 174            {
 0175                totalFramesTimeInSeconds = currentDurationInSeconds;
 0176                StopSampling();
 177            }
 0178        }
 179
 180        /// <summary>
 181        /// Process the data gathered from every frame sample to calculate the final highestFPS, lowestFPS, averageFPS, 
 182        /// </summary>
 183        private void ProcessSamples()
 184        {
 185            // Sort the samples based on FPS count of each one, to be able to calculate the percentiles later
 0186            var sortedSamples = new List<SampleData>(samples);
 0187            sortedSamples.Sort();
 188
 0189            int samplesCount = sortedSamples.Count;
 190
 0191            highestFPS = sortedSamples[samplesCount - 1].fpsAtThisFrameInTime;
 0192            lowestFPS = sortedSamples[0].fpsAtThisFrameInTime;
 193
 0194            averageFPS = fpsSum / sortedSamples.Count;
 195
 0196            percentile50FPS = sortedSamples[Mathf.CeilToInt(samplesCount * 0.5f)].fpsAtThisFrameInTime;
 0197            percentile95FPS = sortedSamples[Mathf.CeilToInt(samplesCount * 0.95f)].fpsAtThisFrameInTime;
 0198        }
 199
 200        /// <summary>
 201        /// Formats and prints the final data report following the 3 steps: system info, processed values and frame samp
 202        /// </summary>
 203        private void ReportData()
 204        {
 205            // TODO: We could build a text file (or html) template with replaceable tags like #OPERATING_SYSTEM, #GRAPHI
 206
 207            // Step 1 - report relevant system info: hardware, cappedFPS, OS, sampling duration, etc.
 0208            Log("Data report step 1 - System and Graphics info:"
 209                + "\n * Sampling duration in seconds -> " + targetDurationInSeconds
 210                + "\n * System Info -> Operating System -> " + SystemInfo.operatingSystem
 211                + "\n * System Info -> Device Name -> " + SystemInfo.deviceName
 212                + "\n * System Info -> Graphics Device Name -> " + SystemInfo.graphicsDeviceName
 213                + "\n * System Info -> System RAM Size -> " + SystemInfo.systemMemorySize
 214                + "\n * General Settings -> Auto Quality ON -> " + Settings.i.generalSettings.autoqualityOn
 215                + "\n * General Settings -> Scenes Load Radius -> " + Settings.i.generalSettings.scenesLoadRadius
 216                + "\n * Quality Settings -> FPSCap -> " + Settings.i.currentQualitySettings.fpsCap
 217                + "\n * Quality Settings -> Bloom -> " + Settings.i.currentQualitySettings.bloom
 218                + "\n * Quality Settings -> Shadow -> " + Settings.i.currentQualitySettings.shadows
 219                + "\n * Quality Settings -> Antialising -> " + Settings.i.currentQualitySettings.antiAliasing
 220                + "\n * Quality Settings -> Base Resolution -> " + Settings.i.currentQualitySettings.baseResolution
 221                + "\n * Quality Settings -> Color Grading -> " + Settings.i.currentQualitySettings.colorGrading
 222                + "\n * Quality Settings -> Display Name -> " + Settings.i.currentQualitySettings.displayName
 223                + "\n * Quality Settings -> Render Scale -> " + Settings.i.currentQualitySettings.renderScale
 224                + "\n * Quality Settings -> Shadow Distance -> " + Settings.i.currentQualitySettings.shadowDistance
 225                + "\n * Quality Settings -> Shadow Resolution -> " + Settings.i.currentQualitySettings.shadowResolution
 226                + "\n * Quality Settings -> Soft Shadows -> " + Settings.i.currentQualitySettings.softShadows
 227                + "\n * Quality Settings -> SSAO Quality -> " + Settings.i.currentQualitySettings.ssaoQuality
 228                + "\n * Quality Settings -> Camera Draw Distance -> " + Settings.i.currentQualitySettings.cameraDrawDist
 229                + "\n * Quality Settings -> Detail Object Culling Enabled -> " + Settings.i.currentQualitySettings.enabl
 230                + "\n * Quality Settings -> Detail Object Culling Limit -> " + Settings.i.currentQualitySettings.detailO
 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
 253            string targetFilePath = Application.persistentDataPath + "/PerformanceMeterRawFrames.txt";
 254            Log("Data report step 3 - Trying to dump raw samples JSON at: " + targetFilePath);
 255            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.currentQualitySettings.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}