< Summary

Class:DCL.PerformanceMeterController
Assembly:PerformanceMeterController
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/WorldRuntime/Debugging/Performance/PerformanceMeterController/PerformanceMeterController.cs
Covered lines:0
Uncovered lines:104
Coverable lines:104
Total lines:357
Line coverage:0% (0 of 104)
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%42600%
UpdateAllocations()0%12300%
ProcessSamples()0%6200%
ReportData()0%2100%
CalculatePerformanceScore()0%12300%
CalculateHiccupsPercentage()0%2100%
Log(...)0%2100%

File(s)

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

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Linq;
 4using UnityEngine;
 5using DCL.FPSDisplay;
 6using DCL.SettingsCommon;
 7using Newtonsoft.Json;
 8using Unity.Profiling;
 9
 10namespace DCL
 11{
 12    /// <summary>
 13    /// Performance Meter Tool
 14    ///
 15    /// It samples frames performance data for the target duration and prints a complete report when finished.
 16    ///
 17    /// There are 2 ways to trigger this tool usage:
 18    /// A: While the client is running in the browser, open the browser console and run "clientDebug.RunPerformanceMeter
 19    /// B: In Unity Editor select the "Main" gameobject and right-click on its DebugBridge Monobehaviour, from there a d
 20    /// </summary>
 21    public class PerformanceMeterController
 22    {
 23        private class SampleData : IComparable
 24        {
 25            public int frameNumber;
 26            public float millisecondsConsumed;
 27            public bool isHiccup = false;
 28            public float currentTime;
 29            public float frameTimeMs;
 30
 31            public override string ToString()
 32            {
 033                return "frame number: " + frameNumber
 34                                        + "\n frame consumed milliseconds: " + millisecondsConsumed
 35                                        + "\n is hiccup: " + isHiccup
 36                                        + "\n frame time: " + frameTimeMs;
 37            }
 38
 39            public int CompareTo(object obj)
 40            {
 41                // 0    -> this and otherSample are equal
 42                // 1    -> this is greater
 43                // -1   -> otherSample is greater
 44
 045                SampleData otherSample = obj as SampleData;
 46
 047                if (otherSample == null)
 048                    return 1;
 49
 050                if (Math.Abs(this.frameTimeMs - otherSample.frameTimeMs) < float.Epsilon)
 051                    return 0;
 52
 053                return this.frameTimeMs > otherSample.frameTimeMs ? 1 : -1;
 54            }
 55        }
 56
 57        private PerformanceMetricsDataVariable metricsData;
 58        private float currentDurationInSeconds = 0f;
 59        private float targetDurationInSeconds = 0f;
 060        private List<SampleData> samples = new List<SampleData>();
 61
 62        // auxiliar data
 63        private SampleData lastSavedSample;
 64
 65        // reported data
 66        private double highestFrameTime;
 67        private double lowestFrameTime;
 68        private double averageFrameTime;
 69        private double marginOfError;
 70
 71        private float percentile1FrameTime;
 72        private float percentile50FrameTime;
 73        private float percentile99FrameTime;
 74        private int totalHiccupFrames;
 75        private float totalHiccupsTimeInSeconds;
 76        private int totalFrames;
 77        private float totalFramesTimeInSeconds;
 78        private long lowestAllocation;
 79        private long highestAllocation;
 80        private long averageAllocation;
 81        private long totalAllocation;
 82        private ProfilerRecorder gcAllocatedInFrameRecorder;
 83
 84        private bool justStarted = false;
 85
 086        public PerformanceMeterController() { metricsData = Resources.Load<PerformanceMetricsDataVariable>("ScriptableOb
 87
 88        private void ResetDataValues()
 89        {
 090            samples.Clear();
 091            currentDurationInSeconds = 0f;
 092            targetDurationInSeconds = 0f;
 93
 094            lastSavedSample = null;
 95
 096            highestFrameTime = 0;
 097            lowestFrameTime = 0;
 098            averageFrameTime = 0;
 099            percentile50FrameTime = 0;
 0100            percentile99FrameTime = 0;
 0101            totalHiccupFrames = 0;
 0102            totalHiccupsTimeInSeconds = 0;
 0103            totalFrames = 0;
 0104            totalFramesTimeInSeconds = 0;
 0105            lowestAllocation = long.MaxValue;
 0106            highestAllocation = 0;
 0107            averageAllocation = 0;
 0108            totalAllocation = 0;
 0109        }
 110
 111        /// <summary>
 112        /// Starts the Performance Meter Tool sampling.
 113        /// </summary>
 114        /// <param name="durationInSeconds">The target duration for the running of the tool, after which a report will b
 115        public void StartSampling(float durationInSeconds)
 116        {
 0117            Log("Start running... target duration: " + durationInSeconds + " seconds");
 118
 0119            ResetDataValues();
 120
 0121            targetDurationInSeconds = durationInSeconds;
 0122            justStarted = true;
 0123            gcAllocatedInFrameRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Memory, "GC Allocated In Frame");
 0124            metricsData.OnChange += OnMetricsChange;
 0125        }
 126
 127        /// <summary>
 128        /// Stops the Performance Meter Tool sampling, processes the data gathered and prints a full report in the conso
 129        /// </summary>
 130        public void StopSampling()
 131        {
 0132            Log("Stopped running.");
 133
 0134            metricsData.OnChange -= OnMetricsChange;
 135
 0136            if (samples.Count == 0)
 137            {
 0138                Log("No samples were gathered, the duration time in seconds set is probably too small");
 139
 0140                return;
 141            }
 142
 0143            ProcessSamples();
 144
 0145            ReportData();
 0146        }
 147
 148        /// <summary>
 149        /// Callback triggered on every update made to the PerformanceMetricsDataVariable ScriptableObject, done every f
 150        /// </summary>
 151        /// /// <param name="newData">NEW version of the PerformanceMetricsDataVariable ScriptableObject</param>
 152        /// /// <param name="oldData">OLD version of the PerformanceMetricsDataVariable ScriptableObject</param>
 153        private void OnMetricsChange(PerformanceMetricsData newData, PerformanceMetricsData oldData)
 154        {
 155            // we avoid the first frame as when we are in editor, the context menu pauses everything and the next frame 
 0156            if (justStarted)
 157            {
 0158                justStarted = false;
 0159                return;
 160            }
 161
 0162            float secondsConsumed = 0;
 163
 0164            if (lastSavedSample != null)
 165            {
 0166                if (lastSavedSample.frameNumber == Time.frameCount)
 167                {
 0168                    Log("PerformanceMetricsDataVariable changed more than once in the same frame!");
 169
 0170                    return;
 171                }
 172
 0173                secondsConsumed = Time.timeSinceLevelLoad - lastSavedSample.currentTime;
 174            }
 175
 0176            float frameTimeMs = Time.deltaTime * 1000f;
 177
 0178            SampleData newSample = new SampleData
 179            {
 180                frameTimeMs = frameTimeMs,
 181                frameNumber = Time.frameCount,
 182                millisecondsConsumed = secondsConsumed * 1000,
 183                currentTime = Time.timeSinceLevelLoad,
 184                isHiccup = secondsConsumed > FPSEvaluation.HICCUP_THRESHOLD_IN_SECONDS
 185            };
 186
 0187            samples.Add(newSample);
 0188            lastSavedSample = newSample;
 189
 0190            if (newSample.isHiccup)
 191            {
 0192                totalHiccupFrames++;
 0193                totalHiccupsTimeInSeconds += secondsConsumed;
 194            }
 195
 0196            UpdateAllocations();
 197
 0198            totalFrames++;
 199
 0200            currentDurationInSeconds += Time.deltaTime;
 201
 0202            if (currentDurationInSeconds > targetDurationInSeconds)
 203            {
 0204                totalFramesTimeInSeconds = currentDurationInSeconds;
 0205                StopSampling();
 206            }
 0207        }
 208
 209        private void UpdateAllocations()
 210        {
 0211            long lastAllocation = gcAllocatedInFrameRecorder.LastValue;
 212
 0213            if (highestAllocation < lastAllocation)
 214            {
 0215                highestAllocation = lastAllocation;
 216            }
 217
 0218            if (lowestAllocation > lastAllocation)
 219            {
 0220                lowestAllocation = lastAllocation;
 221            }
 222
 0223            totalAllocation += lastAllocation;
 0224        }
 225
 226        /// <summary>
 227        /// Process the data gathered from every frame sample to calculate the final highestFPS, lowestFPS, averageFPS, 
 228        /// </summary>
 229        private void ProcessSamples()
 230        {
 231            // Sort the samples based on FPS count of each one, to be able to calculate the percentiles later
 0232            var sortedSamples = new List<SampleData>(samples);
 0233            sortedSamples.Sort();
 234
 0235            int samplesCount = sortedSamples.Count;
 236
 0237            var benchmark = new BenchmarkResult(sortedSamples.Select(sample => (double)sample.frameTimeMs).ToArray());
 238
 0239            highestFrameTime = benchmark.max;
 0240            lowestFrameTime = benchmark.min;
 0241            averageFrameTime = benchmark.mean;
 0242            marginOfError = benchmark.rme;
 243
 0244            percentile1FrameTime = sortedSamples[Mathf.Min(Mathf.CeilToInt(samplesCount * 0.01f), sortedSamples.Count-1)
 0245            percentile50FrameTime = sortedSamples[Mathf.Min(Mathf.CeilToInt(samplesCount * 0.5f), sortedSamples.Count-1)
 0246            percentile99FrameTime = sortedSamples[Mathf.Min(Mathf.CeilToInt(samplesCount * 0.99f), sortedSamples.Count-1
 247
 0248            averageAllocation = totalAllocation / sortedSamples.Count;
 0249        }
 250
 251        /// <summary>
 252        /// Formats and prints the final data report following the 3 steps: system info, processed values and frame samp
 253        /// </summary>
 254        private void ReportData()
 255        {
 256            // TODO: We could build a text file (or html) template with replaceable tags like #OPERATING_SYSTEM, #GRAPHI
 257
 258            // Step 1 - report relevant system info: hardware, cappedFPS, OS, sampling duration, etc.
 0259            Log("Data report step 1 - System and Graphics info:"
 260                + "\n * Sampling duration in seconds -> " + targetDurationInSeconds
 261                + "\n * System Info -> Operating System -> " + SystemInfo.operatingSystem
 262                + "\n * System Info -> Device Name -> " + SystemInfo.deviceName
 263                + "\n * System Info -> Graphics Device Name -> " + SystemInfo.graphicsDeviceName
 264                + "\n * System Info -> System RAM Size -> " + SystemInfo.systemMemorySize
 265                + "\n * General Settings -> Scenes Load Radius -> " + Settings.i.generalSettings.Data.scenesLoadRadius
 266                + "\n * Quality Settings -> FPSCap -> " + Settings.i.qualitySettings.Data.fpsCap
 267                + "\n * Quality Settings -> Bloom -> " + Settings.i.qualitySettings.Data.bloom
 268                + "\n * Quality Settings -> Shadow -> " + Settings.i.qualitySettings.Data.shadows
 269                + "\n * Quality Settings -> Antialising -> " + Settings.i.qualitySettings.Data.antiAliasing
 270                + "\n * Quality Settings -> Base Resolution -> " + Settings.i.qualitySettings.Data.baseResolution
 271                + "\n * Quality Settings -> Display Name -> " + Settings.i.qualitySettings.Data.displayName
 272                + "\n * Quality Settings -> Render Scale -> " + Settings.i.qualitySettings.Data.renderScale
 273                + "\n * Quality Settings -> Shadow Distance -> " + Settings.i.qualitySettings.Data.shadowDistance
 274                + "\n * Quality Settings -> Shadow Resolution -> " + Settings.i.qualitySettings.Data.shadowResolution
 275                + "\n * Quality Settings -> Soft Shadows -> " + Settings.i.qualitySettings.Data.softShadows
 276                + "\n * Quality Settings -> SSAO Quality -> " + Settings.i.qualitySettings.Data.ssaoQuality
 277                + "\n * Quality Settings -> Camera Draw Distance -> " +
 278                Settings.i.qualitySettings.Data.cameraDrawDistance
 279                + "\n * Quality Settings -> Detail Object Culling Enabled -> " +
 280                Settings.i.qualitySettings.Data.enableDetailObjectCulling
 281                + "\n * Quality Settings -> Detail Object Culling Limit -> " +
 282                Settings.i.qualitySettings.Data.detailObjectCullingLimit
 283                + "\n * Quality Settings -> Reflection Quality -> " +
 284                Settings.i.qualitySettings.Data.reflectionResolution
 285            );
 286
 287            // Step 2 - report processed data
 0288            string format = "F1";
 289
 0290            Log($"Data report step 2 - Processed values:" +
 291                $"\n * PERFORMANCE SCORE (0-100) -> {CalculatePerformanceScore()}" +
 292                $"\n * lowest frame time -> {lowestFrameTime.ToString(format)}ms" +
 293                $"\n * average frame time -> {averageFrameTime.ToString(format)}ms" +
 294                $"\n * highest frame time -> {highestFrameTime.ToString(format)}ms" +
 295                $"\n * 1 percentile frame time -> {percentile1FrameTime.ToString(format)}ms" +
 296                $"\n * 50 percentile frame time -> {percentile50FrameTime.ToString(format)}ms" +
 297                $"\n * 99 percentile frame time -> {percentile99FrameTime.ToString(format)}ms" +
 298                $"\n * error percentage -> ±{marginOfError.ToString(format)}%" +
 299                $"\n * total hiccups (>{FPSEvaluation.HICCUP_THRESHOLD_IN_SECONDS}ms frames) -> {totalHiccupFrames} ({Ca
 300                $"\n * total hiccups time (seconds) -> {totalHiccupsTimeInSeconds}" +
 301                $"\n * total frames -> {totalFrames}" +
 302                $"\n * total frames time (seconds) -> {totalFramesTimeInSeconds}" +
 303                $"\n * lowest allocations (kb) -> {lowestAllocation / 1000.0}" +
 304                $"\n * highest allocations (kb) -> {highestAllocation / 1000.0}" +
 305                $"\n * average allocations (kb) -> {averageAllocation / 1000.0}" +
 306                $"\n * total allocations (kb) -> {totalAllocation / 1000.0}"
 307            );
 308
 309            // Step 3 - report all samples data
 0310            string rawSamplesJSON = "Data report step 3 - Raw samples:"
 311                                    + "\n "
 312                                    + "{\"frame-samples\": " + JsonConvert.SerializeObject(samples) + "}";
 313
 314#if !UNITY_WEBGL && UNITY_EDITOR
 315            string targetFilePath = Application.persistentDataPath + "/PerformanceMeterRawFrames.txt";
 316            Log("Data report step 3 - Trying to dump raw samples JSON at: " + targetFilePath);
 317            System.IO.File.WriteAllText(targetFilePath, rawSamplesJSON);
 318#endif
 319
 0320            Log(rawSamplesJSON);
 0321        }
 322
 323        /// <summary>
 324        /// Calculates a performance score from 0 to 100 based on the average frame time (compared to the closest frame 
 325        /// </summary>
 326        private int CalculatePerformanceScore()
 327        {
 0328            double desiredFrameTime = Settings.i.qualitySettings.Data.fpsCap ? 1000/30.0 : 1000/60.0;
 0329            double frameScore = Mathf.Min((float)(desiredFrameTime/ averageFrameTime), 1); // from 0 to 1
 0330            double hiccupsScore = 1 - (float) totalHiccupFrames / samples.Count; // from 0 to 1
 0331            double performanceScore = (frameScore + hiccupsScore) / 2 * 100; // scores sum / amount of scores * 100 to h
 332
 0333            return Mathf.RoundToInt((float)performanceScore * 100f) / 100;
 334        }
 335
 336        private float CalculateHiccupsPercentage()
 337        {
 0338            float percentage = ((float) totalHiccupFrames / totalFrames) * 100;
 0339            percentage = Mathf.Round(percentage * 100f) / 100f; // to have 2 decimals
 340
 0341            return percentage;
 342        }
 343
 344        /// <summary>
 345        /// Logs the tool messages in console regardless of the "Debug.unityLogger.logEnabled" value.
 346        /// </summary>
 347        private void Log(string message)
 348        {
 0349            bool originalLogEnabled = Debug.unityLogger.logEnabled;
 0350            Debug.unityLogger.logEnabled = true;
 351
 0352            Debug.Log("PerformanceMeter - " + message);
 353
 0354            Debug.unityLogger.logEnabled = originalLogEnabled;
 0355        }
 356    }
 357}