├── .gitignore ├── CHANGELOG.md ├── CHANGELOG.md.meta ├── Documentation.meta ├── Documentation ├── Performance-Studio.md └── Performance-Studio.md.meta ├── LICENSE.md ├── LICENSE.md.meta ├── LICENSE_UNITY.md ├── LICENSE_UNITY.md.meta ├── Native~ ├── Build.bee.cs ├── IUnityInterface.h ├── streamline_annotate.c ├── streamline_annotate.h └── streamline_annotate_logging.h ├── Plugins.meta ├── Plugins ├── arm64-v8a.meta ├── arm64-v8a │ ├── libmobilestudio.so │ └── libmobilestudio.so.meta ├── armeabi-v7a.meta └── armeabi-v7a │ ├── libmobilestudio.so │ └── libmobilestudio.so.meta ├── README.md ├── README.md.meta ├── Runtime.meta ├── Runtime ├── MobileStudio.Runtime.asmdef ├── MobileStudio.Runtime.asmdef.meta ├── MobileStudio.cs ├── MobileStudio.cs.meta ├── UnityStatsProxy.cs └── UnityStatsProxy.cs.meta ├── package.json └── package.json.meta /.gitignore: -------------------------------------------------------------------------------- 1 | # Build artifacts 2 | /Native~/artifacts/ 3 | 4 | # Visual Studio cache directory 5 | .vs/ 6 | 7 | # Gradle cache directory 8 | .gradle/ 9 | 10 | # Autogenerated solution and project files 11 | ExportedObj/ 12 | .consulo/ 13 | *.csproj 14 | *.unityproj 15 | *.sln 16 | *.suo 17 | *.tmp 18 | *.user 19 | *.userprefs 20 | *.pidb 21 | *.booproj 22 | *.svd 23 | *.pdb 24 | *.mdb 25 | *.opendb 26 | *.VC.db -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this package will be documented in this file. 4 | 5 | ## [1.0.0] 6 | 7 | - Initial release. 8 | 9 | ## [1.1.0] 10 | 11 | - **Improvement:** Improve the counter annotation API. 12 | - **Fix:** Added binding synchronization for multi-threaded usage. 13 | 14 | ## [1.2.0] 15 | 16 | - **Improvement:** Added Unity profiler metrics as counters. 17 | 18 | ## [1.3.0] 19 | 20 | - **Fix:** Added static annotation ID namespacing to avoid collisions with 21 | other Arm tooling, such as the Performance Advisor layer driver. 22 | 23 | ## [1.4.0] 24 | 25 | - **Fix:** Added 64-bit counter value support in the annotation API. 26 | 27 | ## [1.5.0] 28 | 29 | - **Improvement:** Improved CAM annotation API to support track nesting. 30 | - **Improvement:** Improved CAM annotation API to support job dependencies. 31 | - **Fix:** Renamed counter `set_value()` to `setValue()`. 32 | 33 | - - - 34 | 35 | _Copyright © 2021-2024, Arm Limited and contributors. All rights reserved._ 36 | -------------------------------------------------------------------------------- /CHANGELOG.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 82bb30776aa4df641bd90eb3a7097141 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Documentation.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e7a1ee641b9303448a3654052a0631d3 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Documentation/Performance-Studio.md: -------------------------------------------------------------------------------- 1 | Unity Bindings for Performance Studio 2 | ===================================== 3 | 4 | Introduction 5 | ------------ 6 | 7 | This package contains C# bindings needed to export application-generated 8 | counters and event annotations to the Performance Studio Streamline profiler. 9 | 10 | The Streamline profiler can collect and present application software profiling 11 | information alongside sample-based hardware performance counter data from both 12 | Arm CPUs and GPUs. Streamline has an *annotations* feature which allows the 13 | application being profiled to emit additional information that can be displayed 14 | alongside the other captured performance information. 15 | 16 | > **Note:** Performance Studio was formerly known as Mobile Studio. For API 17 | > backwards compatibility the package continues to use the `MobileStudio` file 18 | > name prefix and C# namespace. 19 | 20 | Installation 21 | ------------ 22 | 23 | 1) Open the package manager in Unity. 24 | 2) Click `+` in the toolbar and select `Add package from git URL`. 25 | 3) Import the Performance Studio package from GitHub into your project. 26 | 27 | It is recommended that you set up a define so you can easily remove the 28 | package from release builds without leaving errors in your code from package 29 | usage. To set up the define, follow these steps: 30 | 31 | 4) If you do not have an asmdef file for scripts that reference the package 32 | API, create one. 33 | 5) In the asmdef file, under `Assembly Definition References`, 34 | add `MobileStudio.Runtime`. 35 | 6) In the asmdef file, under `Version Defines`, add a rule: 36 | a) Set `Resource` to `com.arm.mobile-studio`. 37 | b) Set `Define` to `MOBILE_STUDIO`. 38 | c) Set `Expression` to `1.0.0` 39 | This rule makes Unity define `MOBILE_STUDIO` if the `com.arm.mobile-studio` 40 | package is present in the project and if its version is greater than `1.0.0`. 41 | 7) In your code, wrap `MOBILE_STUDIO` around the package API use: 42 | 43 | #if MOBILE_STUDIO 44 | // Package usage 45 | #endif 46 | 47 | You can now easily add and remove the package without breaking your project, 48 | which avoids errors in release builds. 49 | 50 | Usage 51 | ----- 52 | 53 | ### Markers 54 | 55 | Markers are the simplest annotations, emitting a timestamped point indicator 56 | that will be shown along the top of the Streamline timeline view. 57 | 58 | To emit a marker, e.g: 59 | 60 | MobileStudio.Annotations.marker("Frame"); 61 | 62 | You can also specify the color of the marker by passing an optional Unity 63 | `Color` object, e.g: 64 | 65 | MobileStudio.Annotations.marker("Frame", Color.green); 66 | 67 | ### Channels 68 | 69 | Channels are custom event swimlanes that are associated with a software thread 70 | in the Streamline timeline. Once a channel has been created, you can place 71 | job annotations within it. A channel job annotation has a text label and a 72 | color but, unlike markers, they span a range of time. 73 | 74 | To create a channel, e.g.: 75 | 76 | channel = new MobileStudio.Annotations.Channel("AI"); 77 | 78 | To insert an annotation time-box into the channel, e.g.: 79 | 80 | // Trigger the start of the annotation 81 | channel.annotate("NPC AI", Color.red); 82 | 83 | // Do the work you want to time ... 84 | 85 | // Trigger the end of the annotation 86 | channel.end(); 87 | 88 | ### Counters 89 | 90 | Counters are numerical data points that can be plotted as a chart series in the 91 | Streamline timeline view. Counters can be created as either absolute counters, 92 | where every value is an absolute value, or as a delta counter, where values are 93 | the number of instances of an event since the last value was emitted. All 94 | values are floats and will be presented to 2 decimal places. 95 | 96 | When charts are first defined you can specify a title and a series name. The 97 | title names the chart, the series names the data series. Multiple counter 98 | series can use the same title, which means that they will be plotted on the 99 | same chart in the Streamline timeline. 100 | 101 | To create a counter, e.g.: 102 | 103 | counter = new MobileStudio.Annotations.Counter( 104 | "Title", "Series", MobileStudio.Annotations.CounterType.Absolute); 105 | 106 | To set a value for a counter, e.g.: 107 | 108 | counter.setValue(42.2f); 109 | 110 | ### Custom Activity Maps 111 | 112 | Custom Activity Map (CAM) views allow job execution information to be plotted 113 | on a hierarchical set of swimlane tracks. Each CAM view defines a standalone 114 | visualization and, unlike channel annotations, tracks are not associated with 115 | with a specific calling thread. Dependency information between jobs within a 116 | single CAM view can also be defined, allowing the visualization to show 117 | control flow information if it is provided. 118 | 119 | To create a CAM view, e.g: 120 | 121 | gameCAM = new MobileStudio.Annotations.CAM("Game Activity"); 122 | 123 | To add tracks to the CAM, e.g: 124 | 125 | // Create root tracks in the view 126 | aiTrack = gameCAM.createTrack("AI activity"); 127 | terrainTrack = gameCAM.createTrack("Terrain generation"); 128 | 129 | // Create a nested track inside another track 130 | windTrack = terrainTrack.createTrack("Wind activity"); 131 | 132 | To create a job within a track, there are two methods. The first is an 133 | immediate-mode API which starts a job when it is created, and stops it when 134 | the job's `stop()` method is called. 135 | 136 | 137 | job = aiTrack.makeJob("NPC AI", Color.blue); 138 | // Do work ... 139 | job.stop(); 140 | 141 | The other method is to store the start and end times of your work, and then 142 | add them to the track later. 143 | 144 | // Run the work 145 | startTime = MobileStudio.Annotations.getTime(); 146 | // Do work ... 147 | endTime = MobileStudio.Annotations.getTime(); 148 | 149 | // Register the work done earlier 150 | aiTrack.registerJob("NPC AI", Color.blue, startTime, endTime); 151 | 152 | The advantage of this second approach is that the `getTime()` method is very 153 | cheap in terms of CPU cycles, and can also be safely invoked from jobs running 154 | within the Unity Job Scheduler. 155 | 156 | To allow dependencies between Jobs to be expressed, both `makeJob()` and 157 | `registerJob()` accept an optional list of `CAMJob` objects, which indicate the 158 | producers that the new Job consumes from. Dependency links will be shown in the 159 | Streamline visualization. 160 | 161 | Further Reading 162 | --------------- 163 | 164 | If you'd like to know more or raise any questions, please see the Performance 165 | Studio developer pages at: 166 | 167 | * https://developer.arm.com/performance-studio 168 | 169 | Community support is available from Arm's graphics forum at: 170 | 171 | * https://community.arm.com/graphics 172 | 173 | - - - 174 | 175 | _Copyright © 2021-2024, Arm Limited and contributors. All rights reserve 176 | -------------------------------------------------------------------------------- /Documentation/Performance-Studio.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 508fe920daf213f40aa18200b71f4ee8 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2021, Arm Limited 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 22 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 25 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 26 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 27 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 28 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /LICENSE.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 13891517a823b984e9c3f9b1ee871320 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /LICENSE_UNITY.md: -------------------------------------------------------------------------------- 1 | Unity Native Plugin API copyright © 2015 Unity Technologies ApS 2 | 3 | Licensed under the Unity Companion License for Unity-dependent projects -- see 4 | [Unity Companion License](http://www.unity3d.com/legal/licenses/Unity_Companion_License). 5 | 6 | Unless expressly provided otherwise, the Software under this license is made 7 | available strictly on an “AS IS” BASIS WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 8 | IMPLIED. Please review the license for details on these and other terms and 9 | conditions. 10 | -------------------------------------------------------------------------------- /LICENSE_UNITY.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 39543846009c6f54caf645e58ebc8e16 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Native~/Build.bee.cs: -------------------------------------------------------------------------------- 1 | /** 2 | * SPDX-License-Identifier: BSD-3-Clause 3 | * 4 | * Copyright (c) 2021, Arm Limited 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 14 | * 2. Redistributions in binary form must reproduce the above copyright 15 | * notice, this list of conditions and the following disclaimer in the 16 | * documentation and/or other materials provided with the distribution. 17 | * 18 | * 3. Neither the name of the copyright holder nor the names of its 19 | * contributors may be used to endorse or promote products derived from 20 | * this software without specific prior written permission. 21 | * 22 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 25 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 26 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 27 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 28 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 29 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 30 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 31 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 32 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | */ 34 | 35 | using System; 36 | using System.Linq; 37 | using System.Collections.Generic; 38 | using NiceIO; 39 | using Bee.Core; 40 | using Bee.NativeProgramSupport; 41 | using Bee.Toolchain.Android; 42 | 43 | class BuildProgram 44 | { 45 | static void Main() 46 | { 47 | if(String.IsNullOrEmpty(Environment.GetEnvironmentVariable("ANDROID_NDK_ROOT"))) 48 | { 49 | throw new ArgumentException("ANDROID_NDK_ROOT is not set, please set it to the path of your NDK."); 50 | } 51 | 52 | // Target platforms 53 | List android = new List(); 54 | 55 | // Update these paths to NDK location 56 | android.Add(BuildCommand.Create(new AndroidNdkToolchain(new AndroidNdkR19( 57 | Architecture.Armv7, Environment.GetEnvironmentVariable("ANDROID_NDK_ROOT"), false)), "android", "armeabi-v7a")); 58 | android.Add(BuildCommand.Create(new AndroidNdkToolchain(new AndroidNdkR19( 59 | Architecture.Arm64, Environment.GetEnvironmentVariable("ANDROID_NDK_ROOT"), false)), "android", "arm64-v8a")); 60 | 61 | NativeProgram pluginProgram = new NativeProgram("MobileStudio"); 62 | pluginProgram.Sources.Add("./streamline_annotate.c"); 63 | pluginProgram.Exceptions.Set(true); 64 | pluginProgram.RTTI.Set(true); 65 | pluginProgram.Libraries.Add(new SystemLibrary("log")); 66 | ProcessProgram(pluginProgram, "mobilestudio", android); 67 | } 68 | 69 | private static void ProcessProgram(NativeProgram plugin, string targetDir, List commands) 70 | { 71 | foreach (var command in commands) 72 | { 73 | var toolchain = command.ToolChain; 74 | var config = new NativeProgramConfiguration(CodeGen.Release, toolchain, false); 75 | var builtProgram = plugin.SetupSpecificConfiguration(config, toolchain.DynamicLibraryFormat); 76 | var artefact = builtProgram.Path; 77 | if (command.PostProcess != null) 78 | artefact = command.PostProcess(artefact, toolchain, targetDir, command.PluginSubFolder); 79 | Backend.Current.AddAliasDependency(command.Alias, artefact); 80 | } 81 | } 82 | 83 | class BuildCommand 84 | { 85 | public ToolChain ToolChain; 86 | public string Alias; 87 | public string PluginSubFolder; 88 | public Func PostProcess = PostProcessDefault; 89 | public static BuildCommand Create (ToolChain chain, string alias, string pluginSubFolder = "") 90 | { 91 | return new BuildCommand() { Alias = alias, ToolChain = chain, PluginSubFolder = pluginSubFolder }; 92 | } 93 | } 94 | 95 | private static NPath PostProcessDefault(NPath builtProgram, ToolChain toolchain, string pluginDir, string subFolderDir) 96 | { 97 | return Copy(builtProgram, builtProgram, toolchain, pluginDir, subFolderDir); 98 | } 99 | 100 | private static NPath Copy(NPath from, NPath to, ToolChain toolchain, string pluginDir, string subFolderDir) 101 | { 102 | string fileName = "mobilestudio"; 103 | if ((toolchain.Platform is LinuxPlatform) || (toolchain.Platform is AndroidPlatform)) 104 | fileName = "libmobilestudio"; 105 | to = new NPath($"../Plugins/{subFolderDir}/{fileName}.{to.Extension}"); 106 | CopyTool.Instance().Setup(to, from); 107 | return to; 108 | } 109 | } -------------------------------------------------------------------------------- /Native~/IUnityInterface.h: -------------------------------------------------------------------------------- 1 | // Unity Native Plugin API copyright © 2015 Unity Technologies ApS 2 | // 3 | // Licensed under the Unity Companion License for Unity - dependent projects--see[Unity Companion License](http://www.unity3d.com/legal/licenses/Unity_Companion_License). 4 | // 5 | // Unless expressly provided otherwise, the Software under this license is made available strictly on an “AS IS” BASIS WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED.Please review the license for details on these and other terms and conditions. 6 | 7 | #pragma once 8 | 9 | // Unity native plugin API 10 | // Compatible with C99 11 | 12 | #if defined(__CYGWIN32__) 13 | #define UNITY_INTERFACE_API __stdcall 14 | #define UNITY_INTERFACE_EXPORT __declspec(dllexport) 15 | #elif defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(_WIN64) || defined(WINAPI_FAMILY) 16 | #define UNITY_INTERFACE_API __stdcall 17 | #define UNITY_INTERFACE_EXPORT __declspec(dllexport) 18 | #elif defined(__MACH__) || defined(__ANDROID__) || defined(__linux__) || defined(LUMIN) 19 | #define UNITY_INTERFACE_API 20 | #define UNITY_INTERFACE_EXPORT __attribute__ ((visibility ("default"))) 21 | #else 22 | #define UNITY_INTERFACE_API 23 | #define UNITY_INTERFACE_EXPORT 24 | #endif 25 | 26 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 27 | // IUnityInterface is a registry of interfaces we choose to expose to plugins. 28 | // 29 | // USAGE: 30 | // --------- 31 | // To retrieve an interface a user can do the following from a plugin, assuming they have the header file for the interface: 32 | // 33 | // IMyInterface * ptr = registry->Get(); 34 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 35 | 36 | // Unity Interface GUID 37 | // Ensures global uniqueness. 38 | // 39 | // Template specialization is used to produce a means of looking up a GUID from its interface type at compile time. 40 | // The net result should compile down to passing around the GUID. 41 | // 42 | // UNITY_REGISTER_INTERFACE_GUID should be placed in the header file of any interface definition outside of all namespaces. 43 | // The interface structure and the registration GUID are all that is required to expose the interface to other systems. 44 | struct UnityInterfaceGUID 45 | { 46 | #ifdef __cplusplus 47 | UnityInterfaceGUID(unsigned long long high, unsigned long long low) 48 | : m_GUIDHigh(high) 49 | , m_GUIDLow(low) 50 | { 51 | } 52 | 53 | UnityInterfaceGUID(const UnityInterfaceGUID& other) 54 | { 55 | m_GUIDHigh = other.m_GUIDHigh; 56 | m_GUIDLow = other.m_GUIDLow; 57 | } 58 | 59 | UnityInterfaceGUID& operator=(const UnityInterfaceGUID& other) 60 | { 61 | m_GUIDHigh = other.m_GUIDHigh; 62 | m_GUIDLow = other.m_GUIDLow; 63 | return *this; 64 | } 65 | 66 | bool Equals(const UnityInterfaceGUID& other) const { return m_GUIDHigh == other.m_GUIDHigh && m_GUIDLow == other.m_GUIDLow; } 67 | bool LessThan(const UnityInterfaceGUID& other) const { return m_GUIDHigh < other.m_GUIDHigh || (m_GUIDHigh == other.m_GUIDHigh && m_GUIDLow < other.m_GUIDLow); } 68 | #endif 69 | unsigned long long m_GUIDHigh; 70 | unsigned long long m_GUIDLow; 71 | }; 72 | #ifdef __cplusplus 73 | inline bool operator==(const UnityInterfaceGUID& left, const UnityInterfaceGUID& right) { return left.Equals(right); } 74 | inline bool operator!=(const UnityInterfaceGUID& left, const UnityInterfaceGUID& right) { return !left.Equals(right); } 75 | inline bool operator<(const UnityInterfaceGUID& left, const UnityInterfaceGUID& right) { return left.LessThan(right); } 76 | inline bool operator>(const UnityInterfaceGUID& left, const UnityInterfaceGUID& right) { return right.LessThan(left); } 77 | inline bool operator>=(const UnityInterfaceGUID& left, const UnityInterfaceGUID& right) { return !operator<(left, right); } 78 | inline bool operator<=(const UnityInterfaceGUID& left, const UnityInterfaceGUID& right) { return !operator>(left, right); } 79 | #else 80 | typedef struct UnityInterfaceGUID UnityInterfaceGUID; 81 | #endif 82 | 83 | 84 | #ifdef __cplusplus 85 | #define UNITY_DECLARE_INTERFACE(NAME) \ 86 | struct NAME : IUnityInterface 87 | 88 | // Generic version of GetUnityInterfaceGUID to allow us to specialize it 89 | // per interface below. The generic version has no actual implementation 90 | // on purpose. 91 | // 92 | // If you get errors about return values related to this method then 93 | // you have forgotten to include UNITY_REGISTER_INTERFACE_GUID with 94 | // your interface, or it is not visible at some point when you are 95 | // trying to retrieve or add an interface. 96 | template 97 | inline const UnityInterfaceGUID GetUnityInterfaceGUID(); 98 | 99 | // This is the macro you provide in your public interface header 100 | // outside of a namespace to allow us to map between type and GUID 101 | // without the user having to worry about it when attempting to 102 | // add or retrieve and interface from the registry. 103 | #define UNITY_REGISTER_INTERFACE_GUID(HASHH, HASHL, TYPE) \ 104 | template<> \ 105 | inline const UnityInterfaceGUID GetUnityInterfaceGUID() \ 106 | { \ 107 | return UnityInterfaceGUID(HASHH,HASHL); \ 108 | } 109 | 110 | // Same as UNITY_REGISTER_INTERFACE_GUID but allows the interface to live in 111 | // a particular namespace. As long as the namespace is visible at the time you call 112 | // GetUnityInterfaceGUID< INTERFACETYPE >() or you explicitly qualify it in the template 113 | // calls this will work fine, only the macro here needs to have the additional parameter 114 | #define UNITY_REGISTER_INTERFACE_GUID_IN_NAMESPACE(HASHH, HASHL, TYPE, NAMESPACE) \ 115 | const UnityInterfaceGUID TYPE##_GUID(HASHH, HASHL); \ 116 | template<> \ 117 | inline const UnityInterfaceGUID GetUnityInterfaceGUID< NAMESPACE :: TYPE >() \ 118 | { \ 119 | return UnityInterfaceGUID(HASHH,HASHL); \ 120 | } 121 | 122 | // These macros allow for C compatibility in user code. 123 | #define UNITY_GET_INTERFACE_GUID(TYPE) GetUnityInterfaceGUID< TYPE >() 124 | 125 | 126 | #else 127 | #define UNITY_DECLARE_INTERFACE(NAME) \ 128 | typedef struct NAME NAME; \ 129 | struct NAME 130 | 131 | // NOTE: This has the downside that one some compilers it will not get stripped from all compilation units that 132 | // can see a header containing this constant. However, it's only for C compatibility and thus should have 133 | // minimal impact. 134 | #define UNITY_REGISTER_INTERFACE_GUID(HASHH, HASHL, TYPE) \ 135 | const UnityInterfaceGUID TYPE##_GUID = {HASHH, HASHL}; 136 | 137 | // In general namespaces are going to be a problem for C code any interfaces we expose in a namespace are 138 | // not going to be usable from C. 139 | #define UNITY_REGISTER_INTERFACE_GUID_IN_NAMESPACE(HASHH, HASHL, TYPE, NAMESPACE) 140 | 141 | // These macros allow for C compatibility in user code. 142 | #define UNITY_GET_INTERFACE_GUID(TYPE) TYPE##_GUID 143 | #endif 144 | 145 | // Using this in user code rather than INTERFACES->Get() will be C compatible for those places in plugins where 146 | // this may be needed. Unity code itself does not need this. 147 | #define UNITY_GET_INTERFACE(INTERFACES, TYPE) (TYPE*)INTERFACES->GetInterfaceSplit (UNITY_GET_INTERFACE_GUID(TYPE).m_GUIDHigh, UNITY_GET_INTERFACE_GUID(TYPE).m_GUIDLow); 148 | 149 | 150 | #ifdef __cplusplus 151 | struct IUnityInterface 152 | { 153 | }; 154 | #else 155 | typedef void IUnityInterface; 156 | #endif 157 | 158 | 159 | typedef struct IUnityInterfaces 160 | { 161 | // Returns an interface matching the guid. 162 | // Returns nullptr if the given interface is unavailable in the active Unity runtime. 163 | IUnityInterface* (UNITY_INTERFACE_API * GetInterface)(UnityInterfaceGUID guid); 164 | 165 | // Registers a new interface. 166 | void(UNITY_INTERFACE_API * RegisterInterface)(UnityInterfaceGUID guid, IUnityInterface * ptr); 167 | 168 | // Split APIs for C 169 | IUnityInterface* (UNITY_INTERFACE_API * GetInterfaceSplit)(unsigned long long guidHigh, unsigned long long guidLow); 170 | void(UNITY_INTERFACE_API * RegisterInterfaceSplit)(unsigned long long guidHigh, unsigned long long guidLow, IUnityInterface * ptr); 171 | 172 | #ifdef __cplusplus 173 | // Helper for GetInterface. 174 | template 175 | INTERFACE* Get() 176 | { 177 | return static_cast(GetInterface(GetUnityInterfaceGUID())); 178 | } 179 | 180 | // Helper for RegisterInterface. 181 | template 182 | void Register(IUnityInterface* ptr) 183 | { 184 | RegisterInterface(GetUnityInterfaceGUID(), ptr); 185 | } 186 | 187 | #endif 188 | } IUnityInterfaces; 189 | 190 | 191 | #ifdef __cplusplus 192 | extern "C" { 193 | #endif 194 | 195 | // If exported by a plugin, this function will be called when the plugin is loaded. 196 | void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API UnityPluginLoad(IUnityInterfaces* unityInterfaces); 197 | // If exported by a plugin, this function will be called when the plugin is about to be unloaded. 198 | void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API UnityPluginUnload(); 199 | 200 | #ifdef __cplusplus 201 | } 202 | #endif 203 | 204 | struct RenderSurfaceBase; 205 | typedef struct RenderSurfaceBase* UnityRenderBuffer; 206 | typedef unsigned int UnityTextureID; 207 | -------------------------------------------------------------------------------- /Native~/streamline_annotate.c: -------------------------------------------------------------------------------- 1 | /** 2 | * SPDX-License-Identifier: BSD-3-Clause 3 | * 4 | * Copyright (c) 2014-2022, Arm Limited 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 14 | * 2. Redistributions in binary form must reproduce the above copyright 15 | * notice, this list of conditions and the following disclaimer in the 16 | * documentation and/or other materials provided with the distribution. 17 | * 18 | * 3. Neither the name of the copyright holder nor the names of its 19 | * contributors may be used to endorse or promote products derived from 20 | * this software without specific prior written permission. 21 | * 22 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 25 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 26 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 27 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 28 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 29 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 30 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 31 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 32 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | */ 34 | #ifdef __cplusplus 35 | extern "C" { 36 | #endif 37 | 38 | #ifndef _GNU_SOURCE 39 | #define _GNU_SOURCE 40 | #endif 41 | 42 | #include "streamline_annotate.h" 43 | #include "streamline_annotate_logging.h" 44 | 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | #include 60 | #include 61 | #include 62 | 63 | #define THREAD_BUFFER_SIZE (1<<16) 64 | #define THREAD_BUFFER_MASK (THREAD_BUFFER_SIZE - 1) 65 | 66 | #define STREAMLINE_ANNOTATE_PARENT "\0streamline-annotate-parent" 67 | #define STREAMLINE_ANNOTATE "\0streamline-annotate" 68 | 69 | static const char gator_annotate_handshake[] = "ANNOTATE 4\n"; 70 | static const int gator_minimum_version = 24; 71 | 72 | static const uint8_t HEADER_UTF8 = 0x01; 73 | static const uint8_t HEADER_UTF8_COLOR = 0x02; 74 | static const uint8_t HEADER_CHANNEL_NAME = 0x03; 75 | static const uint8_t HEADER_GROUP_NAME = 0x04; 76 | static const uint8_t HEADER_VISUAL = 0x05; 77 | static const uint8_t HEADER_MARKER = 0x06; 78 | static const uint8_t HEADER_MARKER_COLOR = 0x07; 79 | static const uint8_t HEADER_COUNTER = 0x08; 80 | static const uint8_t HEADER_COUNTER_VALUE = 0x09; 81 | static const uint8_t HEADER_ACTIVITY_SWITCH = 0x0a; 82 | static const uint8_t HEADER_CAM_TRACK = 0x0b; 83 | static const uint8_t HEADER_CAM_JOB = 0x0c; 84 | static const uint8_t HEADER_CAM_VIEW_NAME = 0x0d; 85 | static const uint8_t HEADER_CAM_JOB_START = 0x0e; 86 | static const uint8_t HEADER_CAM_JOB_SET_DEPS = 0x0f; 87 | static const uint8_t HEADER_CAM_JOB_STOP = 0x10; 88 | 89 | static const uint32_t SIZE_COLOR = 4; 90 | static const uint32_t MAXSIZE_PACK_INT = 5; 91 | static const uint32_t MAXSIZE_PACK_LONG = 10; 92 | 93 | static const uint64_t NS_PER_S = 1000000000; 94 | 95 | struct gator_thread { 96 | struct gator_thread *next; 97 | const char *oob_data; 98 | /* oob_data must be written before oob_length */ 99 | size_t oob_length; 100 | /* Posted when data is sent */ 101 | sem_t sem; 102 | int fd; 103 | int tid; 104 | uint32_t write_pos; 105 | uint32_t read_pos; 106 | bool exited; 107 | char buf[THREAD_BUFFER_SIZE]; 108 | }; 109 | 110 | struct gator_counter { 111 | struct gator_counter *next; 112 | const char *title; 113 | const char *name; 114 | const char *units; 115 | const char *description; 116 | const char **activities; 117 | uint32_t *activity_colors; 118 | size_t activity_count; 119 | int per_cpu; 120 | int average_selection; 121 | int average_cores; 122 | int percentage; 123 | enum gator_annotate_counter_class counter_class; 124 | enum gator_annotate_display display; 125 | enum gator_annotate_series_composition series_composition; 126 | enum gator_annotate_rendering_type rendering_type; 127 | uint32_t id; 128 | uint32_t modifier; 129 | uint32_t cores; 130 | uint32_t color; 131 | }; 132 | 133 | struct gator_cam_track { 134 | struct gator_cam_track *next; 135 | const char *name; 136 | uint32_t view_uid; 137 | uint32_t track_uid; 138 | uint32_t parent_track; 139 | }; 140 | 141 | struct gator_cam_name { 142 | struct gator_cam_name *next; 143 | const char *name; 144 | uint32_t view_uid; 145 | }; 146 | 147 | struct gator_state { 148 | struct gator_thread *threads; 149 | struct gator_counter *counters; 150 | struct gator_cam_track *cam_tracks; 151 | struct gator_cam_name *cam_names; 152 | /* Post to request asynchronous send of data */ 153 | sem_t sender_sem; 154 | /* Post to request synchronous send of data */ 155 | sem_t sync_sem; 156 | /* Posted when synchronous send of data has completed */ 157 | sem_t sync_waiter_sem; 158 | pthread_key_t key; 159 | int parent_fd; 160 | bool initialized; 161 | bool capturing; 162 | bool forked; 163 | bool resend_state; 164 | }; 165 | 166 | static struct gator_state gator_state; 167 | /* Intentionally exported */ 168 | uint8_t gator_dont_mangle_keys; 169 | 170 | static int gator_socket_cloexec(int domain, int type, int protocol) { 171 | #ifdef SOCK_CLOEXEC 172 | return socket(domain, type | SOCK_CLOEXEC, protocol); 173 | #else 174 | const int sock = socket(domain, type, protocol); 175 | if (sock < 0) 176 | return -1; 177 | 178 | const int fdf = fcntl(sock, F_GETFD); 179 | if ((fdf == -1) || (fcntl(sock, F_SETFD, fdf | FD_CLOEXEC) != 0)) { 180 | close(sock); 181 | return -1; 182 | } 183 | return sock; 184 | #endif 185 | } 186 | 187 | static void gator_destructor(void *value) 188 | { 189 | struct gator_thread *const thread = (struct gator_thread *)value; 190 | if (thread != NULL) 191 | thread->exited = true; 192 | } 193 | 194 | void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API gator_annotate_fork_child(void) 195 | { 196 | /* Single threaded at this point */ 197 | struct gator_thread *thread; 198 | pthread_setspecific(gator_state.key, NULL); 199 | for (thread = gator_state.threads; thread != NULL; thread = thread->next) { 200 | thread->exited = true; 201 | thread->read_pos = thread->write_pos; 202 | } 203 | 204 | gator_state.forked = true; 205 | } 206 | 207 | void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API gator_annotate_flush(void) 208 | { 209 | if (gator_state.capturing) { 210 | /* Request synchronous send of data */ 211 | sem_post(&gator_state.sync_sem); 212 | /* Wake up sender */ 213 | sem_post(&gator_state.sender_sem); 214 | /* Wait for completion */ 215 | sem_wait(&gator_state.sync_waiter_sem); 216 | } 217 | } 218 | 219 | static void gator_set_ts(struct timespec *const ts, const uint64_t time) 220 | { 221 | ts->tv_sec = time / NS_PER_S; 222 | ts->tv_nsec = time % NS_PER_S; 223 | } 224 | 225 | static uint64_t gator_time(const clockid_t clk_id) 226 | { 227 | struct timespec ts; 228 | if (clock_gettime(clk_id, &ts) != 0) 229 | return ~0; 230 | 231 | return NS_PER_S*ts.tv_sec + ts.tv_nsec; 232 | } 233 | 234 | uint64_t UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API gator_get_time(void) 235 | { 236 | #ifndef CLOCK_MONOTONIC_RAW 237 | /* Android doesn't have this defined but it was added in Linux 2.6.28 */ 238 | #define CLOCK_MONOTONIC_RAW 4 239 | #endif 240 | return gator_time(CLOCK_MONOTONIC_RAW); 241 | } 242 | 243 | static bool gator_parent_connect(void) 244 | { 245 | const int fd = gator_socket_cloexec(PF_UNIX, SOCK_STREAM, 0); 246 | if (fd < 0) 247 | return false; 248 | 249 | struct sockaddr_un addr = { 0 }; 250 | addr.sun_family = AF_UNIX; 251 | memcpy(addr.sun_path, STREAMLINE_ANNOTATE_PARENT, sizeof(STREAMLINE_ANNOTATE_PARENT)); 252 | if (connect(fd, (const struct sockaddr *)&addr, 253 | offsetof(struct sockaddr_un, sun_path) + sizeof(STREAMLINE_ANNOTATE_PARENT) - 1) != 0) { 254 | close(fd); 255 | return false; 256 | } 257 | 258 | gator_state.parent_fd = fd; 259 | return true; 260 | } 261 | 262 | static uint32_t gator_buf_pos(const uint32_t pos) { 263 | return pos & THREAD_BUFFER_MASK; 264 | } 265 | 266 | static uint32_t gator_buf_write_byte(char *const buf, uint32_t *const write_pos_ptr, char b) 267 | { 268 | const uint32_t write_pos = *write_pos_ptr; 269 | buf[write_pos] = b; 270 | *write_pos_ptr = gator_buf_pos(write_pos + 1); 271 | return 1; 272 | } 273 | 274 | static uint32_t gator_buf_write_uint32(char *const buf, uint32_t *const write_pos_ptr, uint32_t i) 275 | { 276 | const uint32_t write_pos = *write_pos_ptr; 277 | buf[gator_buf_pos(write_pos + 0)] = i & 0xff; 278 | buf[gator_buf_pos(write_pos + 1)] = (i >> 8) & 0xff; 279 | buf[gator_buf_pos(write_pos + 2)] = (i >> 16) & 0xff; 280 | buf[gator_buf_pos(write_pos + 3)] = (i >> 24) & 0xff; 281 | *write_pos_ptr = gator_buf_pos(write_pos + sizeof(uint32_t)); 282 | return sizeof(uint32_t); 283 | } 284 | 285 | static uint32_t gator_buf_write_color(char *const buf, uint32_t *const write_pos_ptr, const uint32_t color) 286 | { 287 | const uint32_t write_pos = *write_pos_ptr; 288 | buf[gator_buf_pos(write_pos + 0)] = (color >> 8) & 0xff; 289 | buf[gator_buf_pos(write_pos + 1)] = (color >> 16) & 0xff; 290 | buf[gator_buf_pos(write_pos + 2)] = (color >> 24) & 0xff; 291 | buf[gator_buf_pos(write_pos + 3)] = (color >> 0) & 0xff; 292 | *write_pos_ptr = gator_buf_pos(write_pos + 4); 293 | return 4; 294 | } 295 | 296 | static uint32_t gator_buf_write_int(char *const buf, uint32_t *const write_pos_ptr, int32_t x) 297 | { 298 | const uint32_t write_pos = *write_pos_ptr; 299 | int packed_bytes = 0; 300 | int more = true; 301 | 302 | while (more) { 303 | /* low order 7 bits of x */ 304 | char b = x & 0x7f; 305 | 306 | x >>= 7; 307 | 308 | if ((x == 0 && (b & 0x40) == 0) || (x == -1 && (b & 0x40) != 0)) 309 | more = false; 310 | else 311 | b |= 0x80; 312 | 313 | buf[gator_buf_pos(write_pos + packed_bytes)] = b; 314 | packed_bytes++; 315 | } 316 | 317 | *write_pos_ptr = gator_buf_pos(write_pos + packed_bytes); 318 | return packed_bytes; 319 | } 320 | 321 | static uint32_t gator_buf_write_long(char *const buf, uint32_t *const write_pos_ptr, int64_t x) 322 | { 323 | const uint32_t write_pos = *write_pos_ptr; 324 | int packed_bytes = 0; 325 | int more = true; 326 | 327 | while (more) { 328 | /* low order 7 bits of x */ 329 | char b = x & 0x7f; 330 | 331 | x >>= 7; 332 | 333 | if ((x == 0 && (b & 0x40) == 0) || (x == -1 && (b & 0x40) != 0)) 334 | more = false; 335 | else 336 | b |= 0x80; 337 | 338 | buf[gator_buf_pos(write_pos + packed_bytes)] = b; 339 | packed_bytes++; 340 | } 341 | 342 | *write_pos_ptr = gator_buf_pos(write_pos + packed_bytes); 343 | return packed_bytes; 344 | } 345 | 346 | static uint32_t gator_buf_write_time(char *const buf, uint32_t *const write_pos_ptr) 347 | { 348 | return gator_buf_write_long(buf, write_pos_ptr, gator_get_time()); 349 | } 350 | 351 | static uint32_t gator_buf_write_bytes(char *const buf, uint32_t *const write_pos_ptr, const char *const data, 352 | const uint32_t count) 353 | { 354 | const uint32_t write_pos = *write_pos_ptr; 355 | if (write_pos + count <= THREAD_BUFFER_SIZE) { 356 | memcpy(buf + write_pos, data, count); 357 | } else { 358 | const uint32_t first = THREAD_BUFFER_SIZE - write_pos; 359 | const uint32_t second = count - first; 360 | memcpy(buf + write_pos, data, first); 361 | memcpy(buf, data + first, second); 362 | } 363 | *write_pos_ptr = gator_buf_pos(write_pos + count); 364 | return count; 365 | } 366 | 367 | static int gator_connect(const int tid) 368 | { 369 | const int fd = gator_socket_cloexec(PF_UNIX, SOCK_STREAM, 0); 370 | if (fd < 0) 371 | return -1; 372 | 373 | struct sockaddr_un addr = { 0 }; 374 | addr.sun_family = AF_UNIX; 375 | memcpy(addr.sun_path, STREAMLINE_ANNOTATE, sizeof(STREAMLINE_ANNOTATE)); 376 | if (connect(fd, (const struct sockaddr *)&addr, 377 | offsetof(struct sockaddr_un, sun_path) + sizeof(STREAMLINE_ANNOTATE) - 1) != 0) { 378 | close(fd); 379 | return -1; 380 | } 381 | 382 | /* Send tid as gatord cannot autodiscover it and the per process unique id */ 383 | uint32_t write_pos = 0; 384 | char buf[sizeof(gator_annotate_handshake) + 2*sizeof(uint32_t) + 1]; 385 | gator_buf_write_bytes(buf, &write_pos, gator_annotate_handshake, sizeof(gator_annotate_handshake) - 1); 386 | gator_buf_write_uint32(buf, &write_pos, tid); 387 | gator_buf_write_uint32(buf, &write_pos, getpid()); 388 | gator_buf_write_byte(buf, &write_pos, gator_dont_mangle_keys); 389 | const ssize_t bytes = send(fd, buf, write_pos, MSG_NOSIGNAL); 390 | if (bytes != (ssize_t)write_pos) { 391 | close(fd); 392 | return -1; 393 | } 394 | 395 | return fd; 396 | } 397 | 398 | static void gator_start_capturing(void) 399 | { 400 | if (__sync_bool_compare_and_swap(&gator_state.capturing, false, true)) 401 | gator_state.resend_state = true; 402 | } 403 | 404 | static void gator_stop_capturing(void) 405 | { 406 | if (__sync_bool_compare_and_swap(&gator_state.capturing, true, false)) { 407 | struct gator_thread *thread; 408 | for (thread = gator_state.threads; thread != NULL; thread = thread->next) { 409 | thread->read_pos = thread->write_pos; 410 | thread->oob_length = 0; 411 | sem_post(&thread->sem); 412 | if (thread->fd > 0) { 413 | close(thread->fd); 414 | thread->fd = -1; 415 | } 416 | } 417 | } 418 | } 419 | 420 | static bool gator_send(struct gator_thread *const thread, const uint32_t write_pos) 421 | { 422 | size_t write; 423 | ssize_t bytes; 424 | 425 | if (write_pos > thread->read_pos) { 426 | write = write_pos - thread->read_pos; 427 | bytes = send(thread->fd, thread->buf + thread->read_pos, write, MSG_NOSIGNAL); 428 | if (bytes == 0) { 429 | //not an error reattempt. 430 | return true; 431 | } 432 | if (bytes < 0) { 433 | return false; 434 | } 435 | thread->read_pos = gator_buf_pos(thread->read_pos + bytes); 436 | } else { 437 | write = THREAD_BUFFER_SIZE - thread->read_pos; 438 | bytes = send(thread->fd, thread->buf + thread->read_pos, write, MSG_NOSIGNAL); 439 | if (bytes == 0) { 440 | //not an error reattempt. 441 | return true; 442 | } 443 | if (bytes < 0) { 444 | return false; 445 | } 446 | thread->read_pos = gator_buf_pos(thread->read_pos + bytes); 447 | 448 | if (write == (size_t)bytes) { 449 | /* Don't write more on a short write to be fair to other threads */ 450 | write = write_pos; 451 | bytes = send(thread->fd, thread->buf, write, MSG_NOSIGNAL); 452 | if (bytes == 0) { 453 | //not an error reattempt. 454 | return true; 455 | } 456 | if (bytes < 0) { 457 | return false; 458 | } 459 | thread->read_pos = gator_buf_pos(thread->read_pos + bytes); 460 | } 461 | } 462 | 463 | if (write == (size_t)bytes && thread->oob_length > 0) { 464 | __sync_synchronize(); 465 | /* Don't write more on a short write to be fair to other threads */ 466 | bytes = send(thread->fd, thread->oob_data, thread->oob_length, MSG_NOSIGNAL); 467 | if (bytes == 0) { 468 | //not an error reattempt. 469 | return true; 470 | } 471 | if (bytes < 0) { 472 | return false; 473 | } 474 | thread->oob_data += bytes; 475 | thread->oob_length -= bytes; 476 | } 477 | 478 | return true; 479 | } 480 | 481 | static void* gator_func(void *arg) 482 | { 483 | bool print = true; 484 | uint64_t last = 0; 485 | 486 | prctl(PR_SET_NAME, (unsigned long)&"gator-annotate", 0, 0, 0); 487 | 488 | if (arg != NULL) { 489 | /* Forked */ 490 | if (gator_state.parent_fd >= 0) { 491 | close(gator_state.parent_fd); 492 | gator_state.parent_fd = -1; 493 | } 494 | } 495 | 496 | for (;;) { 497 | if (gator_state.parent_fd < 0) { 498 | if (gator_parent_connect()) { 499 | /* Optimistically begin capturing data */ 500 | gator_start_capturing(); 501 | } else { 502 | gator_stop_capturing(); 503 | if (print) { 504 | LOG(LOG_WARN, 505 | "Warning : Not connected to gatord. The application will run " // 506 | "normally but Streamline will not collect annotations."); 507 | print = false; 508 | } 509 | sleep(1); 510 | continue; 511 | } 512 | } 513 | 514 | if (!gator_state.capturing) { 515 | char temp; 516 | const ssize_t bytes = read(gator_state.parent_fd, &temp, sizeof(temp)); 517 | if (bytes <= 0) { 518 | close(gator_state.parent_fd); 519 | gator_state.parent_fd = -1; 520 | continue; 521 | } 522 | gator_start_capturing(); 523 | gator_state.resend_state = true; 524 | } 525 | 526 | if (gator_state.capturing) { 527 | /* Iterate every 100ms */ 528 | const uint64_t freq = NS_PER_S/10; 529 | const uint64_t now = gator_time(CLOCK_REALTIME); 530 | const uint64_t wakeup = last + freq; 531 | if (wakeup < now) { 532 | /* Already timed out, save the current time and iterate again */ 533 | last = now; 534 | } else { 535 | struct timespec ts; 536 | gator_set_ts(&ts, wakeup); 537 | sem_timedwait(&gator_state.sender_sem, &ts); 538 | last = wakeup; 539 | while (sem_trywait(&gator_state.sender_sem) == 0) { 540 | /* Ignore multiple posts */ 541 | } 542 | } 543 | } 544 | 545 | int sync_count = 0; 546 | while (sem_trywait(&gator_state.sync_sem) == 0) 547 | ++sync_count; 548 | 549 | struct gator_thread **prev = &gator_state.threads; 550 | struct gator_thread *thread = *prev; 551 | while (thread != NULL) { 552 | if (gator_state.capturing) { 553 | if (thread->fd < 0 && (thread->fd = gator_connect(thread->tid)) < 0) { 554 | gator_stop_capturing(); 555 | } else { 556 | const uint32_t write_pos = thread->write_pos; 557 | if (write_pos != thread->read_pos || thread->oob_length > 0) { 558 | if (!gator_send(thread, write_pos)) { 559 | LOG(LOG_ERROR, 560 | "Failed to send bytes, " // 561 | "gator_thread = (exited:%s, fd:%d, oob_length:%zd, read_pos:%d, tid:%d), " // 562 | "write_pos = %u", // 563 | thread->exited ? "true" : "false", 564 | thread->fd, 565 | thread->oob_length, 566 | thread->read_pos, 567 | thread->tid, 568 | write_pos); 569 | gator_stop_capturing(); 570 | } 571 | else 572 | sem_post(&thread->sem); 573 | } 574 | } 575 | } 576 | 577 | struct gator_thread *const next = thread->next; 578 | if (!thread->exited || !__sync_bool_compare_and_swap(prev, thread, next)) { 579 | /* If the cas fails, the linked list has changed, get it next time */ 580 | prev = &thread->next; 581 | thread = *prev; 582 | } else { 583 | if (thread->fd > 0) 584 | close(thread->fd); 585 | sem_destroy(&thread->sem); 586 | free(thread); 587 | thread = next; 588 | } 589 | } 590 | 591 | for (; sync_count > 0; --sync_count) 592 | sem_post(&gator_state.sync_waiter_sem); 593 | } 594 | 595 | return NULL; 596 | } 597 | 598 | void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API gator_annotate_setup(void) 599 | { 600 | /* Support calling gator_annotate_setup more than once, but not at the same time on different cores */ 601 | if (__sync_bool_compare_and_swap(&gator_state.initialized, false, true)) { 602 | gator_state.parent_fd = -1; 603 | /* Optimistically begin capturing data */ 604 | gator_state.capturing = true; 605 | 606 | int err = sem_init(&gator_state.sender_sem, 0, 0); 607 | if (err != 0) { 608 | LOG(LOG_ERROR, "sem_init failed, with error %s", strerror(err)); 609 | return; 610 | } 611 | 612 | err = sem_init(&gator_state.sync_sem, 0, 0); 613 | if (err != 0) { 614 | LOG(LOG_ERROR, "sem_init failed, with error %s", strerror(err)); 615 | return; 616 | } 617 | 618 | err = sem_init(&gator_state.sync_waiter_sem, 0, 0); 619 | if (err != 0) { 620 | LOG(LOG_ERROR, "sem_init failed, with error %s", strerror(err)); 621 | return; 622 | } 623 | 624 | err = pthread_key_create(&gator_state.key, gator_destructor); 625 | if (err != 0) { 626 | LOG(LOG_ERROR, "pthread_key_create failed, with error %s", strerror(err)); 627 | return; 628 | } 629 | 630 | #if !defined(__ANDROID_API__) || __ANDROID_API__ >= 21 631 | err = pthread_atfork(NULL, NULL, gator_annotate_fork_child); 632 | if (err != 0) { 633 | LOG(LOG_ERROR, "pthread_atfork failed, with error %s", strerror(err)); 634 | return; 635 | } 636 | #endif 637 | 638 | if (atexit(gator_annotate_flush) != 0) { 639 | LOG(LOG_ERROR, "atexit failed"); 640 | return; 641 | } 642 | 643 | pthread_t thread; 644 | err = pthread_create(&thread, NULL, gator_func, NULL); 645 | if (err != 0) { 646 | LOG(LOG_ERROR, "pthread_create failed, with error %s", strerror(err)); 647 | return; 648 | } 649 | } 650 | } 651 | 652 | static void gator_annotate_write_counter(struct gator_thread *const thread, const struct gator_counter *const counter); 653 | static void gator_annotate_write_cam_track(struct gator_thread *const thread, 654 | const struct gator_cam_track *const cam_track); 655 | static void gator_annotate_write_cam_name(struct gator_thread *const thread, const struct gator_cam_name *const cam_name); 656 | 657 | static struct gator_thread *gator_get_thread(void) 658 | { 659 | if (__sync_bool_compare_and_swap(&gator_state.forked, true, false)) { 660 | pthread_t thread; 661 | int err = pthread_create(&thread, NULL, gator_func, (void *)1); 662 | if (err != 0) { 663 | LOG(LOG_ERROR, "pthread_create failed, with error %s", strerror(err)); 664 | return NULL; 665 | } 666 | } 667 | 668 | if (!gator_state.capturing) 669 | return NULL; 670 | 671 | struct gator_thread *thread = (struct gator_thread *)pthread_getspecific(gator_state.key); 672 | int err; 673 | if (thread != NULL) 674 | goto success; 675 | 676 | thread = (struct gator_thread *)malloc(sizeof(*thread)); 677 | if (thread == NULL) { 678 | LOG(LOG_ERROR, "malloc failed, with error %s", strerror(errno)); 679 | return NULL; 680 | } 681 | 682 | thread->oob_data = NULL; 683 | thread->oob_length = 0; 684 | thread->fd = -1; 685 | thread->tid = syscall(__NR_gettid); 686 | thread->write_pos = 0; 687 | thread->read_pos = 0; 688 | thread->exited = false; 689 | 690 | err = sem_init(&thread->sem, 0, 0); 691 | if (err != 0) { 692 | LOG(LOG_ERROR, "sem_init failed, with error %s", strerror(err)); 693 | goto fail_free_thread; 694 | } 695 | 696 | err = pthread_setspecific(gator_state.key, thread); 697 | if (err != 0) { 698 | LOG(LOG_ERROR, "pthread_setspecific failed, with error %s", strerror(err)); 699 | goto fail_sem_destroy; 700 | } 701 | 702 | do 703 | thread->next = gator_state.threads; 704 | while (!__sync_bool_compare_and_swap(&gator_state.threads, thread->next, thread)); 705 | 706 | success: 707 | if (__sync_bool_compare_and_swap(&gator_state.resend_state, true, false)) { 708 | struct gator_counter *counter; 709 | for (counter = gator_state.counters; counter != NULL; counter = counter->next) 710 | gator_annotate_write_counter(thread, counter); 711 | struct gator_cam_track *cam_track; 712 | for (cam_track = gator_state.cam_tracks; cam_track != NULL; cam_track = cam_track->next) 713 | gator_annotate_write_cam_track(thread, cam_track); 714 | struct gator_cam_name *cam_name; 715 | for (cam_name = gator_state.cam_names; cam_name != NULL; cam_name = cam_name->next) 716 | gator_annotate_write_cam_name(thread, cam_name); 717 | } 718 | 719 | return thread; 720 | 721 | fail_sem_destroy: 722 | sem_destroy(&thread->sem); 723 | fail_free_thread: 724 | free(thread); 725 | return NULL; 726 | } 727 | 728 | static uint32_t gator_buf_free(const struct gator_thread *const thread) 729 | { 730 | return (thread->read_pos - thread->write_pos - 1) & THREAD_BUFFER_MASK; 731 | } 732 | 733 | static uint32_t gator_buf_used(const struct gator_thread *const thread) 734 | { 735 | return (thread->write_pos - thread->read_pos) & THREAD_BUFFER_MASK; 736 | } 737 | 738 | #define gator_buf_wait_bytes(thread, bytes) \ 739 | const uint32_t __bytes = (bytes); \ 740 | if (__bytes > THREAD_BUFFER_SIZE / 2) { \ 741 | /* Large annotations won't fit in the buffer */ \ 742 | LOG(LOG_ERROR, "message is too large"); \ 743 | return; \ 744 | } \ 745 | __gator_buf_wait_bytes((thread), __bytes); 746 | 747 | static void __gator_buf_wait_bytes(struct gator_thread *const thread, const uint32_t bytes) 748 | { 749 | while (gator_buf_free(thread) < bytes) { 750 | sem_post(&gator_state.sender_sem); 751 | sem_wait(&thread->sem); 752 | } 753 | } 754 | 755 | static void gator_msg_begin(const char marker, struct gator_thread *const thread, uint32_t *const write_pos_ptr, 756 | uint32_t *const size_pos_ptr, uint32_t *const length_ptr) 757 | { 758 | *write_pos_ptr = thread->write_pos; 759 | gator_buf_write_byte(thread->buf, write_pos_ptr, marker); 760 | *size_pos_ptr = *write_pos_ptr; 761 | *write_pos_ptr = gator_buf_pos(*write_pos_ptr + sizeof(uint32_t)); 762 | *length_ptr = 0; 763 | } 764 | 765 | static void gator_msg_end(struct gator_thread *const thread, const uint32_t write_pos, uint32_t size_pos, 766 | const uint32_t length) 767 | { 768 | gator_buf_write_uint32(thread->buf, &size_pos, length); 769 | thread->write_pos = write_pos; 770 | 771 | /* Wakeup the sender thread if 3/4 full */ 772 | if (gator_buf_used(thread) >= 3*THREAD_BUFFER_SIZE/4) 773 | sem_post(&gator_state.sender_sem); 774 | } 775 | 776 | void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API gator_annotate_str(const uint32_t channel, const char *const str) 777 | { 778 | struct gator_thread *const thread = gator_get_thread(); 779 | if (thread == NULL) 780 | return; 781 | 782 | const int str_size = (str == NULL) ? 0 : strlen(str); 783 | gator_buf_wait_bytes(thread, 1 + sizeof(uint32_t) + MAXSIZE_PACK_LONG + MAXSIZE_PACK_INT + str_size); 784 | 785 | uint32_t write_pos; 786 | uint32_t size_pos; 787 | uint32_t length; 788 | gator_msg_begin(HEADER_UTF8, thread, &write_pos, &size_pos, &length); 789 | 790 | length += gator_buf_write_time(thread->buf, &write_pos); 791 | length += gator_buf_write_int(thread->buf, &write_pos, channel); 792 | length += gator_buf_write_bytes(thread->buf, &write_pos, str, str_size); 793 | 794 | gator_msg_end(thread, write_pos, size_pos, length); 795 | } 796 | 797 | void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API gator_annotate_color(const uint32_t channel, const uint32_t color, const char *const str) 798 | { 799 | struct gator_thread *const thread = gator_get_thread(); 800 | if (thread == NULL) { 801 | return; 802 | } 803 | 804 | const int str_size = (str == NULL) ? 0 : strlen(str); 805 | gator_buf_wait_bytes(thread, 1 + sizeof(uint32_t) + MAXSIZE_PACK_LONG + MAXSIZE_PACK_INT + SIZE_COLOR + str_size); 806 | 807 | uint32_t write_pos; 808 | uint32_t size_pos; 809 | uint32_t length; 810 | gator_msg_begin(HEADER_UTF8_COLOR, thread, &write_pos, &size_pos, &length); 811 | 812 | length += gator_buf_write_time(thread->buf, &write_pos); 813 | length += gator_buf_write_int(thread->buf, &write_pos, channel); 814 | length += gator_buf_write_color(thread->buf, &write_pos, color); 815 | length += gator_buf_write_bytes(thread->buf, &write_pos, str, str_size); 816 | 817 | gator_msg_end(thread, write_pos, size_pos, length); 818 | } 819 | 820 | void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API gator_annotate_name_channel(const uint32_t channel, const uint32_t group, const char *const str) 821 | { 822 | struct gator_thread *const thread = gator_get_thread(); 823 | if (thread == NULL) { 824 | return; 825 | } 826 | 827 | const int str_size = (str == NULL) ? 0 : strlen(str); 828 | gator_buf_wait_bytes(thread, 1 + sizeof(uint32_t) + MAXSIZE_PACK_LONG + 2*MAXSIZE_PACK_INT + str_size); 829 | 830 | uint32_t write_pos; 831 | uint32_t size_pos; 832 | uint32_t length; 833 | gator_msg_begin(HEADER_CHANNEL_NAME, thread, &write_pos, &size_pos, &length); 834 | 835 | length += gator_buf_write_time(thread->buf, &write_pos); 836 | length += gator_buf_write_int(thread->buf, &write_pos, channel); 837 | length += gator_buf_write_int(thread->buf, &write_pos, group); 838 | length += gator_buf_write_bytes(thread->buf, &write_pos, str, str_size); 839 | 840 | gator_msg_end(thread, write_pos, size_pos, length); 841 | } 842 | 843 | void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API gator_annotate_name_group(const uint32_t group, const char *const str) 844 | { 845 | struct gator_thread *const thread = gator_get_thread(); 846 | if (thread == NULL) { 847 | return; 848 | } 849 | 850 | const int str_size = (str == NULL) ? 0 : strlen(str); 851 | gator_buf_wait_bytes(thread, 1 + sizeof(uint32_t) + MAXSIZE_PACK_LONG + MAXSIZE_PACK_INT + str_size); 852 | 853 | uint32_t write_pos; 854 | uint32_t size_pos; 855 | uint32_t length; 856 | gator_msg_begin(HEADER_GROUP_NAME, thread, &write_pos, &size_pos, &length); 857 | 858 | length += gator_buf_write_time(thread->buf, &write_pos); 859 | length += gator_buf_write_int(thread->buf, &write_pos, group); 860 | length += gator_buf_write_bytes(thread->buf, &write_pos, str, str_size); 861 | 862 | gator_msg_end(thread, write_pos, size_pos, length); 863 | } 864 | 865 | void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API gator_annotate_visual(const void *const data, const uint32_t data_length, const char *const str) 866 | { 867 | struct gator_thread *const thread = gator_get_thread(); 868 | if (thread == NULL) { 869 | return; 870 | } 871 | 872 | const int str_size = (str == NULL) ? 0 : strlen(str); 873 | /* Don't include data_length because it doesn't end up in the buffer */ 874 | gator_buf_wait_bytes(thread, 1 + sizeof(uint32_t) + MAXSIZE_PACK_LONG + str_size + 1); 875 | 876 | uint32_t write_pos; 877 | uint32_t size_pos; 878 | uint32_t length; 879 | gator_msg_begin(HEADER_VISUAL, thread, &write_pos, &size_pos, &length); 880 | 881 | length += gator_buf_write_time(thread->buf, &write_pos); 882 | length += gator_buf_write_bytes(thread->buf, &write_pos, str, str_size); 883 | length += gator_buf_write_byte(thread->buf, &write_pos, '\0'); 884 | /* Calculate the length, but don't write the image */ 885 | length += data_length; 886 | /* Write the length and commit the first part of the message */ 887 | gator_buf_write_uint32(thread->buf, &size_pos, length); 888 | thread->write_pos = write_pos; 889 | 890 | thread->oob_data = (const char *)data; 891 | __sync_synchronize(); 892 | thread->oob_length = data_length; 893 | while (thread->oob_length > 0) { 894 | sem_post(&gator_state.sender_sem); 895 | sem_wait(&thread->sem); 896 | } 897 | } 898 | 899 | void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API gator_annotate_marker(const char *const str) 900 | { 901 | struct gator_thread *const thread = gator_get_thread(); 902 | if (thread == NULL) { 903 | return; 904 | } 905 | const int str_size = (str == NULL) ? 0 : strlen(str); 906 | gator_buf_wait_bytes(thread, 1 + sizeof(uint32_t) + MAXSIZE_PACK_LONG + str_size); 907 | 908 | uint32_t write_pos; 909 | uint32_t size_pos; 910 | uint32_t length; 911 | gator_msg_begin(HEADER_MARKER, thread, &write_pos, &size_pos, &length); 912 | 913 | length += gator_buf_write_time(thread->buf, &write_pos); 914 | length += gator_buf_write_bytes(thread->buf, &write_pos, str, str_size); 915 | 916 | gator_msg_end(thread, write_pos, size_pos, length); 917 | } 918 | 919 | void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API gator_annotate_marker_color(const uint32_t color, const char *const str) 920 | { 921 | struct gator_thread *const thread = gator_get_thread(); 922 | if (thread == NULL) 923 | return; 924 | 925 | const int str_size = (str == NULL) ? 0 : strlen(str); 926 | gator_buf_wait_bytes(thread, 1 + sizeof(uint32_t) + MAXSIZE_PACK_LONG + SIZE_COLOR + str_size); 927 | 928 | uint32_t write_pos; 929 | uint32_t size_pos; 930 | uint32_t length; 931 | gator_msg_begin(HEADER_MARKER_COLOR, thread, &write_pos, &size_pos, &length); 932 | 933 | length += gator_buf_write_time(thread->buf, &write_pos); 934 | length += gator_buf_write_color(thread->buf, &write_pos, color); 935 | length += gator_buf_write_bytes(thread->buf, &write_pos, str, str_size); 936 | 937 | gator_msg_end(thread, write_pos, size_pos, length); 938 | } 939 | 940 | static void gator_annotate_write_counter(struct gator_thread *const thread, const struct gator_counter *const counter) 941 | { 942 | const int title_size = (counter->title == NULL) ? 0 : strlen(counter->title); 943 | const int name_size = (counter->name == NULL) ? 0 : strlen(counter->name); 944 | const int units_size = (counter->units == NULL) ? 0 : strlen(counter->units); 945 | const int description_size = (counter->description == NULL) ? 0 : strlen(counter->description); 946 | int activity_size = 0; 947 | size_t i; 948 | for (i = 0; i < counter->activity_count; ++i) { 949 | activity_size += (counter->activities[i] == NULL) ? 0 : strlen(counter->activities[i]); 950 | activity_size += SIZE_COLOR; 951 | } 952 | gator_buf_wait_bytes(thread, 1 + sizeof(uint32_t) + 12*MAXSIZE_PACK_INT + SIZE_COLOR + activity_size + 953 | title_size + 1 + name_size + 1 + units_size + 1 + description_size); 954 | 955 | uint32_t write_pos; 956 | uint32_t size_pos; 957 | uint32_t length; 958 | gator_msg_begin(HEADER_COUNTER, thread, &write_pos, &size_pos, &length); 959 | 960 | length += gator_buf_write_int(thread->buf, &write_pos, counter->id); 961 | length += gator_buf_write_int(thread->buf, &write_pos, counter->per_cpu); 962 | length += gator_buf_write_int(thread->buf, &write_pos, counter->counter_class); 963 | length += gator_buf_write_int(thread->buf, &write_pos, counter->display); 964 | length += gator_buf_write_int(thread->buf, &write_pos, counter->modifier); 965 | length += gator_buf_write_int(thread->buf, &write_pos, counter->series_composition); 966 | length += gator_buf_write_int(thread->buf, &write_pos, counter->rendering_type); 967 | length += gator_buf_write_int(thread->buf, &write_pos, counter->average_selection); 968 | length += gator_buf_write_int(thread->buf, &write_pos, counter->average_cores); 969 | length += gator_buf_write_int(thread->buf, &write_pos, counter->percentage); 970 | length += gator_buf_write_int(thread->buf, &write_pos, counter->activity_count); 971 | length += gator_buf_write_int(thread->buf, &write_pos, counter->cores); 972 | length += gator_buf_write_color(thread->buf, &write_pos, counter->color); 973 | for (i = 0; i < counter->activity_count; ++i) { 974 | length += gator_buf_write_bytes(thread->buf, &write_pos, counter->activities[i], 975 | (counter->activities[i] == NULL) ? 0 : strlen(counter->activities[i])); 976 | length += gator_buf_write_byte(thread->buf, &write_pos, '\0'); 977 | length += gator_buf_write_color(thread->buf, &write_pos, counter->activity_colors[i]); 978 | } 979 | length += gator_buf_write_bytes(thread->buf, &write_pos, counter->title, title_size); 980 | length += gator_buf_write_byte(thread->buf, &write_pos, '\0'); 981 | length += gator_buf_write_bytes(thread->buf, &write_pos, counter->name, name_size); 982 | length += gator_buf_write_byte(thread->buf, &write_pos, '\0'); 983 | length += gator_buf_write_bytes(thread->buf, &write_pos, counter->units, units_size); 984 | length += gator_buf_write_byte(thread->buf, &write_pos, '\0'); 985 | length += gator_buf_write_bytes(thread->buf, &write_pos, counter->description, description_size); 986 | 987 | gator_msg_end(thread, write_pos, size_pos, length); 988 | } 989 | 990 | void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API gator_annotate_counter(const uint32_t id, const char *const title, const char *const name, const int per_cpu, 991 | const enum gator_annotate_counter_class counter_class, 992 | const enum gator_annotate_display display, const char *const units, const uint32_t modifier, 993 | const enum gator_annotate_series_composition series_composition, 994 | const enum gator_annotate_rendering_type rendering_type, const int average_selection, 995 | const int average_cores, const int percentage, const size_t activity_count, 996 | const char *const *const activities, const uint32_t *const activity_colors, 997 | const uint32_t cores, const uint32_t color, const char *const description) 998 | { 999 | struct gator_counter *const counter = (struct gator_counter *)malloc(sizeof(*counter)); 1000 | if (counter == NULL) { 1001 | LOG(LOG_ERROR, "malloc failed, with error %s", strerror(errno)); 1002 | return; 1003 | } 1004 | 1005 | /* Save off this counter so it can be resent if needed */ 1006 | counter->id = id; 1007 | counter->title = (title == NULL) ? NULL : strdup(title); 1008 | counter->name = (name == NULL) ? NULL : strdup(name); 1009 | counter->per_cpu = per_cpu; 1010 | counter->counter_class = counter_class; 1011 | counter->display = display; 1012 | counter->units = (units == NULL) ? NULL : strdup(units); 1013 | counter->modifier = modifier; 1014 | counter->series_composition = series_composition; 1015 | counter->rendering_type = rendering_type; 1016 | counter->average_selection = average_selection; 1017 | counter->average_cores = average_cores; 1018 | counter->percentage = percentage; 1019 | counter->activity_count = activity_count; 1020 | if (activity_count == 0) { 1021 | counter->activities = NULL; 1022 | counter->activity_colors = NULL; 1023 | } else { 1024 | counter->activities = (const char **)malloc(activity_count*sizeof(activities[0])); 1025 | if (counter->activities == NULL) { 1026 | LOG(LOG_ERROR, "malloc failed, with error %s", strerror(errno)); 1027 | goto free_counter; 1028 | } 1029 | counter->activity_colors = (uint32_t *)malloc(activity_count*sizeof(activity_colors[0])); 1030 | if (counter->activity_colors == NULL) { 1031 | LOG(LOG_ERROR, "malloc failed, with error %s", strerror(errno)); 1032 | goto free_activities; 1033 | } 1034 | size_t i; 1035 | for (i = 0; i < activity_count; ++i) { 1036 | counter->activities[i] = (activities[i] == NULL) ? NULL : strdup(activities[i]); 1037 | counter->activity_colors[i] = activity_colors[i]; 1038 | } 1039 | } 1040 | counter->cores = cores; 1041 | counter->color = color; 1042 | counter->description = (description == NULL) ? NULL : strdup(description); 1043 | 1044 | do 1045 | counter->next = gator_state.counters; 1046 | while (!__sync_bool_compare_and_swap(&gator_state.counters, counter->next, counter)); 1047 | 1048 | { 1049 | struct gator_thread *const thread = gator_get_thread(); 1050 | if (thread == NULL) { 1051 | return; 1052 | } 1053 | 1054 | gator_annotate_write_counter(thread, counter); 1055 | } 1056 | 1057 | return; 1058 | 1059 | free_activities: 1060 | free(counter->activities); 1061 | free_counter: 1062 | free(counter); 1063 | return; 1064 | } 1065 | 1066 | void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API gator_annotate_counter_value(const uint32_t core, const uint32_t id, const int64_t value) 1067 | { 1068 | struct gator_thread *const thread = gator_get_thread(); 1069 | if (thread == NULL) { 1070 | return; 1071 | } 1072 | 1073 | gator_buf_wait_bytes(thread, 1 + sizeof(uint32_t) + 2 * MAXSIZE_PACK_LONG + 2 * MAXSIZE_PACK_INT); 1074 | 1075 | uint32_t write_pos; 1076 | uint32_t size_pos; 1077 | uint32_t length; 1078 | gator_msg_begin(HEADER_COUNTER_VALUE, thread, &write_pos, &size_pos, &length); 1079 | 1080 | length += gator_buf_write_time(thread->buf, &write_pos); 1081 | length += gator_buf_write_int(thread->buf, &write_pos, core); 1082 | length += gator_buf_write_int(thread->buf, &write_pos, id); 1083 | length += gator_buf_write_long(thread->buf, &write_pos, value); 1084 | 1085 | gator_msg_end(thread, write_pos, size_pos, length); 1086 | } 1087 | 1088 | void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API gator_annotate_activity_switch(const uint32_t core, const uint32_t id, const uint32_t activity, const uint32_t tid) 1089 | { 1090 | struct gator_thread *const thread = gator_get_thread(); 1091 | if (thread == NULL) { 1092 | return; 1093 | } 1094 | 1095 | gator_buf_wait_bytes(thread, 1 + sizeof(uint32_t) + MAXSIZE_PACK_LONG + 4*MAXSIZE_PACK_INT); 1096 | 1097 | uint32_t write_pos; 1098 | uint32_t size_pos; 1099 | uint32_t length; 1100 | gator_msg_begin(HEADER_ACTIVITY_SWITCH, thread, &write_pos, &size_pos, &length); 1101 | 1102 | length += gator_buf_write_time(thread->buf, &write_pos); 1103 | length += gator_buf_write_int(thread->buf, &write_pos, core); 1104 | length += gator_buf_write_int(thread->buf, &write_pos, id); 1105 | length += gator_buf_write_int(thread->buf, &write_pos, activity); 1106 | length += gator_buf_write_int(thread->buf, &write_pos, tid); 1107 | 1108 | gator_msg_end(thread, write_pos, size_pos, length); 1109 | } 1110 | 1111 | static void gator_annotate_write_cam_track(struct gator_thread *const thread, 1112 | const struct gator_cam_track *const cam_track) 1113 | { 1114 | const int name_size = (cam_track->name == NULL) ? 0 : strlen(cam_track->name); 1115 | gator_buf_wait_bytes(thread, 1 + sizeof(uint32_t) + 3*MAXSIZE_PACK_INT + name_size); 1116 | 1117 | uint32_t write_pos; 1118 | uint32_t size_pos; 1119 | uint32_t length; 1120 | gator_msg_begin(HEADER_CAM_TRACK, thread, &write_pos, &size_pos, &length); 1121 | 1122 | length += gator_buf_write_int(thread->buf, &write_pos, cam_track->view_uid); 1123 | length += gator_buf_write_int(thread->buf, &write_pos, cam_track->track_uid); 1124 | length += gator_buf_write_int(thread->buf, &write_pos, cam_track->parent_track); 1125 | length += gator_buf_write_bytes(thread->buf, &write_pos, cam_track->name, name_size); 1126 | 1127 | gator_msg_end(thread, write_pos, size_pos, length); 1128 | } 1129 | 1130 | void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API gator_cam_track(const uint32_t view_uid, const uint32_t track_uid, const uint32_t parent_track, 1131 | const char *const name) 1132 | { 1133 | struct gator_cam_track *const cam_track = (struct gator_cam_track *)malloc(sizeof(*cam_track)); 1134 | if (cam_track == NULL) { 1135 | LOG(LOG_ERROR, "malloc failed, with error %s", strerror(errno)); 1136 | return; 1137 | } 1138 | 1139 | /* Save off this track so it can be resent if needed */ 1140 | cam_track->view_uid = view_uid; 1141 | cam_track->track_uid = track_uid; 1142 | cam_track->parent_track = parent_track; 1143 | cam_track->name = (name == NULL) ? NULL : strdup(name); 1144 | 1145 | do 1146 | cam_track->next = gator_state.cam_tracks; 1147 | while (!__sync_bool_compare_and_swap(&gator_state.cam_tracks, cam_track->next, cam_track)); 1148 | 1149 | struct gator_thread *const thread = gator_get_thread(); 1150 | if (thread == NULL) { 1151 | return; 1152 | } 1153 | 1154 | gator_annotate_write_cam_track(thread, cam_track); 1155 | } 1156 | 1157 | void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API gator_cam_job(const uint32_t view_uid, const uint32_t job_uid, const char *const name, const uint32_t track, 1158 | const uint64_t start_time, const uint64_t duration, const uint32_t color, 1159 | const uint32_t primary_dependency, const size_t dependency_count, const uint32_t *const dependencies) 1160 | { 1161 | struct gator_thread *const thread = gator_get_thread(); 1162 | if (thread == NULL) { 1163 | return; 1164 | } 1165 | 1166 | const int name_size = (name == NULL) ? 0 : strlen(name); 1167 | gator_buf_wait_bytes(thread, 1 + sizeof(uint32_t) + 5*MAXSIZE_PACK_INT + 2*MAXSIZE_PACK_LONG + SIZE_COLOR + 1168 | dependency_count*MAXSIZE_PACK_INT + name_size); 1169 | 1170 | uint32_t write_pos; 1171 | uint32_t size_pos; 1172 | uint32_t length; 1173 | gator_msg_begin(HEADER_CAM_JOB, thread, &write_pos, &size_pos, &length); 1174 | 1175 | length += gator_buf_write_int(thread->buf, &write_pos, view_uid); 1176 | length += gator_buf_write_int(thread->buf, &write_pos, job_uid); 1177 | length += gator_buf_write_int(thread->buf, &write_pos, track); 1178 | length += gator_buf_write_long(thread->buf, &write_pos, start_time); 1179 | length += gator_buf_write_long(thread->buf, &write_pos, duration); 1180 | length += gator_buf_write_color(thread->buf, &write_pos, color); 1181 | length += gator_buf_write_int(thread->buf, &write_pos, primary_dependency); 1182 | length += gator_buf_write_int(thread->buf, &write_pos, dependency_count); 1183 | size_t i; 1184 | for (i = 0; i < dependency_count; ++i) 1185 | length += gator_buf_write_int(thread->buf, &write_pos, dependencies[i]); 1186 | length += gator_buf_write_bytes(thread->buf, &write_pos, name, name_size); 1187 | 1188 | gator_msg_end(thread, write_pos, size_pos, length); 1189 | } 1190 | 1191 | void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API gator_cam_job_start(const uint32_t view_uid, const uint32_t job_uid, const char *const name, const uint32_t track, 1192 | const uint64_t time, const uint32_t color) 1193 | { 1194 | struct gator_thread *const thread = gator_get_thread(); 1195 | if (thread == NULL) { 1196 | return; 1197 | } 1198 | 1199 | const int name_size = (name == NULL) ? 0 : strlen(name); 1200 | gator_buf_wait_bytes(thread, 1 + sizeof(uint32_t) + 3*MAXSIZE_PACK_INT + MAXSIZE_PACK_LONG + SIZE_COLOR + name_size); 1201 | 1202 | uint32_t write_pos; 1203 | uint32_t size_pos; 1204 | uint32_t length; 1205 | gator_msg_begin(HEADER_CAM_JOB_START, thread, &write_pos, &size_pos, &length); 1206 | 1207 | length += gator_buf_write_int(thread->buf, &write_pos, view_uid); 1208 | length += gator_buf_write_int(thread->buf, &write_pos, job_uid); 1209 | length += gator_buf_write_int(thread->buf, &write_pos, track); 1210 | length += gator_buf_write_long(thread->buf, &write_pos, time); 1211 | length += gator_buf_write_color(thread->buf, &write_pos, color); 1212 | length += gator_buf_write_bytes(thread->buf, &write_pos, name, name_size); 1213 | 1214 | gator_msg_end(thread, write_pos, size_pos, length); 1215 | } 1216 | 1217 | void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API gator_cam_job_set_dependencies(const uint32_t view_uid, const uint32_t job_uid, const uint64_t time, 1218 | const uint32_t primary_dependency, const size_t dependency_count, 1219 | const uint32_t *const dependencies) 1220 | { 1221 | struct gator_thread *const thread = gator_get_thread(); 1222 | if (thread == NULL) { 1223 | return; 1224 | } 1225 | 1226 | gator_buf_wait_bytes(thread, 1 + sizeof(uint32_t) + 4*MAXSIZE_PACK_INT + MAXSIZE_PACK_LONG + 1227 | dependency_count*MAXSIZE_PACK_INT); 1228 | 1229 | uint32_t write_pos; 1230 | uint32_t size_pos; 1231 | uint32_t length; 1232 | gator_msg_begin(HEADER_CAM_JOB_SET_DEPS, thread, &write_pos, &size_pos, &length); 1233 | 1234 | length += gator_buf_write_int(thread->buf, &write_pos, view_uid); 1235 | length += gator_buf_write_int(thread->buf, &write_pos, job_uid); 1236 | length += gator_buf_write_long(thread->buf, &write_pos, time); 1237 | length += gator_buf_write_int(thread->buf, &write_pos, primary_dependency); 1238 | length += gator_buf_write_int(thread->buf, &write_pos, dependency_count); 1239 | size_t i; 1240 | for (i = 0; i < dependency_count; ++i) 1241 | length += gator_buf_write_int(thread->buf, &write_pos, dependencies[i]); 1242 | 1243 | gator_msg_end(thread, write_pos, size_pos, length); 1244 | } 1245 | 1246 | void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API gator_cam_job_stop(const uint32_t view_uid, const uint32_t job_uid, const uint64_t time) 1247 | { 1248 | struct gator_thread *const thread = gator_get_thread(); 1249 | if (thread == NULL) { 1250 | return; 1251 | } 1252 | 1253 | gator_buf_wait_bytes(thread, 1 + sizeof(uint32_t) + 2*MAXSIZE_PACK_INT + MAXSIZE_PACK_LONG); 1254 | 1255 | uint32_t write_pos; 1256 | uint32_t size_pos; 1257 | uint32_t length; 1258 | gator_msg_begin(HEADER_CAM_JOB_STOP, thread, &write_pos, &size_pos, &length); 1259 | 1260 | length += gator_buf_write_int(thread->buf, &write_pos, view_uid); 1261 | length += gator_buf_write_int(thread->buf, &write_pos, job_uid); 1262 | length += gator_buf_write_long(thread->buf, &write_pos, time); 1263 | 1264 | gator_msg_end(thread, write_pos, size_pos, length); 1265 | } 1266 | 1267 | static void gator_annotate_write_cam_name(struct gator_thread *const thread, const struct gator_cam_name *const cam_name) 1268 | { 1269 | const int name_size = (cam_name->name == NULL) ? 0 : strlen(cam_name->name); 1270 | gator_buf_wait_bytes(thread, 1 + sizeof(uint32_t) + MAXSIZE_PACK_INT + name_size); 1271 | 1272 | uint32_t write_pos; 1273 | uint32_t size_pos; 1274 | uint32_t length; 1275 | gator_msg_begin(HEADER_CAM_VIEW_NAME, thread, &write_pos, &size_pos, &length); 1276 | 1277 | length += gator_buf_write_int(thread->buf, &write_pos, cam_name->view_uid); 1278 | length += gator_buf_write_bytes(thread->buf, &write_pos, cam_name->name, name_size); 1279 | 1280 | gator_msg_end(thread, write_pos, size_pos, length); 1281 | } 1282 | 1283 | void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API gator_cam_view_name(const uint32_t view_uid, const char *const name) 1284 | { 1285 | struct gator_cam_name *const cam_name = (struct gator_cam_name *)malloc(sizeof(*cam_name)); 1286 | if (cam_name == NULL) { 1287 | return; 1288 | } 1289 | 1290 | /* Save off this name so it can be resent if needed */ 1291 | cam_name->next = NULL; 1292 | cam_name->view_uid = view_uid; 1293 | cam_name->name = (name == NULL) ? NULL : strdup(name); 1294 | 1295 | do 1296 | cam_name->next = gator_state.cam_names; 1297 | while (!__sync_bool_compare_and_swap(&gator_state.cam_names, cam_name->next, cam_name)); 1298 | 1299 | struct gator_thread *const thread = gator_get_thread(); 1300 | if (thread == NULL) { 1301 | return; 1302 | } 1303 | 1304 | gator_annotate_write_cam_name(thread, cam_name); 1305 | } 1306 | 1307 | #ifdef __cplusplus 1308 | } 1309 | #endif -------------------------------------------------------------------------------- /Native~/streamline_annotate.h: -------------------------------------------------------------------------------- 1 | /** 2 | * SPDX-License-Identifier: BSD-3-Clause 3 | * 4 | * Copyright (c) 2014-2022, Arm Limited 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 14 | * 2. Redistributions in binary form must reproduce the above copyright 15 | * notice, this list of conditions and the following disclaimer in the 16 | * documentation and/or other materials provided with the distribution. 17 | * 18 | * 3. Neither the name of the copyright holder nor the names of its 19 | * contributors may be used to endorse or promote products derived from 20 | * this software without specific prior written permission. 21 | * 22 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 25 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 26 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 27 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 28 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 29 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 30 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 31 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 32 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | */ 34 | 35 | #ifndef STREAMLINE_ANNOTATE_H 36 | #define STREAMLINE_ANNOTATE_H 37 | 38 | /* 39 | * User-space only macros: 40 | * ANNOTATE_DEFINE Deprecated and no longer used 41 | * ANNOTATE_SETUP Execute at the start of the program before other ANNOTATE macros are called 42 | * ANNOTATE_DELTA_COUNTER Define a delta counter for use 43 | * ANNOTATE_ABSOLUTE_COUNTER Define an absolute counter for use 44 | * ANNOTATE_COUNTER_VALUE Emit a counter value 45 | * CAM_TRACK Create a new custom activity map track 46 | * CAM_JOB Add a new job to a CAM track, use gator_get_time() to obtain the time in nanoseconds 47 | * CAM_VIEW_NAME Name the custom activity map view 48 | * 49 | * User-space and Kernel-space macros: 50 | * ANNOTATE(str) String annotation 51 | * ANNOTATE_CHANNEL(channel, str) String annotation on a channel 52 | * ANNOTATE_COLOR(color, str) String annotation with color 53 | * ANNOTATE_CHANNEL_COLOR(channel, color, str) String annotation on a channel with color 54 | * ANNOTATE_END() Terminate an annotation 55 | * ANNOTATE_CHANNEL_END(channel) Terminate an annotation on a channel 56 | * ANNOTATE_NAME_CHANNEL(channel, group, str) Name a channel and link it to a group 57 | * ANNOTATE_NAME_GROUP(group, str) Name a group 58 | * ANNOTATE_VISUAL(data, length, str) Image annotation with optional string 59 | * ANNOTATE_MARKER() Marker annotation 60 | * ANNOTATE_MARKER_STR(str) Marker annotation with a string 61 | * ANNOTATE_MARKER_COLOR(color) Marker annotation with a color 62 | * ANNOTATE_MARKER_COLOR_STR(color, str) Marker annotation with a string and color 63 | * 64 | * Channels and groups are defined per thread. This means that if the same 65 | * channel number is used on different threads they are in fact separate 66 | * channels. A channel can belong to only one group per thread. This means 67 | * channel 1 cannot be part of both group 1 and group 2 on the same thread. 68 | * 69 | * NOTE: Kernel annotations are not supported in interrupt context. 70 | */ 71 | 72 | /* ESC character, hex RGB (little endian) */ 73 | #define ANNOTATE_RED 0x0000ff1b 74 | #define ANNOTATE_BLUE 0xff00001b 75 | #define ANNOTATE_GREEN 0x00ff001b 76 | #define ANNOTATE_PURPLE 0xff00ff1b 77 | #define ANNOTATE_YELLOW 0x00ffff1b 78 | #define ANNOTATE_CYAN 0xffff001b 79 | #define ANNOTATE_WHITE 0xffffff1b 80 | #define ANNOTATE_LTGRAY 0xbbbbbb1b 81 | #define ANNOTATE_DKGRAY 0x5555551b 82 | #define ANNOTATE_BLACK 0x0000001b 83 | 84 | #define ANNOTATE_COLOR_CYCLE 0x00000000 85 | #define ANNOTATE_COLOR_T1 0x00000001 86 | #define ANNOTATE_COLOR_T2 0x00000002 87 | #define ANNOTATE_COLOR_T3 0x00000003 88 | #define ANNOTATE_COLOR_T4 0x00000004 89 | 90 | #ifdef __KERNEL__ /* Start of kernel-space macro definitions */ 91 | 92 | #include 93 | 94 | void gator_annotate(const char* str); 95 | void gator_annotate_channel(int channel, const char* str); 96 | void gator_annotate_color(int color, const char* str); 97 | void gator_annotate_channel_color(int channel, int color, const char* str); 98 | void gator_annotate_end(void); 99 | void gator_annotate_channel_end(int channel); 100 | void gator_annotate_name_channel(int channel, int group, const char* str); 101 | void gator_annotate_name_group(int group, const char* str); 102 | void gator_annotate_visual(const char* data, unsigned int length, const char* str); 103 | void gator_annotate_marker(void); 104 | void gator_annotate_marker_str(const char* str); 105 | void gator_annotate_marker_color(int color); 106 | void gator_annotate_marker_color_str(int color, const char* str); 107 | 108 | #define ANNOTATE_INVOKE(func, args) \ 109 | func##_ptr = symbol_get(gator_##func); \ 110 | if (func##_ptr) { \ 111 | func##_ptr args; \ 112 | symbol_put(gator_##func); \ 113 | } \ 114 | 115 | #define ANNOTATE(str) do { \ 116 | void (*annotate_ptr)(const char*); \ 117 | ANNOTATE_INVOKE(annotate, (str)); \ 118 | } while(0) 119 | 120 | #define ANNOTATE_CHANNEL(channel, str) do { \ 121 | void (*annotate_channel_ptr)(int, const char*); \ 122 | ANNOTATE_INVOKE(annotate_channel, (channel, str)); \ 123 | } while(0) 124 | 125 | #define ANNOTATE_COLOR(color, str) do { \ 126 | void (*annotate_color_ptr)(int, const char*); \ 127 | ANNOTATE_INVOKE(annotate_color, (color, str)); \ 128 | } while(0) 129 | 130 | #define ANNOTATE_CHANNEL_COLOR(channel, color, str) do { \ 131 | void (*annotate_channel_color_ptr)(int, int, const char*); \ 132 | ANNOTATE_INVOKE(annotate_channel_color, (channel, color, str)); \ 133 | } while(0) 134 | 135 | #define ANNOTATE_END() do { \ 136 | void (*annotate_end_ptr)(void); \ 137 | ANNOTATE_INVOKE(annotate_end, ()); \ 138 | } while(0) 139 | 140 | #define ANNOTATE_CHANNEL_END(channel) do { \ 141 | void (*annotate_channel_end_ptr)(int); \ 142 | ANNOTATE_INVOKE(annotate_channel_end, (channel)); \ 143 | } while(0) 144 | 145 | #define ANNOTATE_NAME_CHANNEL(channel, group, str) do { \ 146 | void (*annotate_name_channel_ptr)(int, int, const char*); \ 147 | ANNOTATE_INVOKE(annotate_name_channel, (channel, group, str)); \ 148 | } while(0) 149 | 150 | #define ANNOTATE_NAME_GROUP(group, str) do { \ 151 | void (*annotate_name_group_ptr)(int, const char*); \ 152 | ANNOTATE_INVOKE(annotate_name_group, (group, str)); \ 153 | } while(0) 154 | 155 | #define ANNOTATE_VISUAL(data, length, str) do { \ 156 | void (*annotate_visual_ptr)(const char*, unsigned int, const char*); \ 157 | ANNOTATE_INVOKE(annotate_visual, (data, length, str)); \ 158 | } while(0) 159 | 160 | #define ANNOTATE_MARKER() do { \ 161 | void (*annotate_marker_ptr)(void); \ 162 | ANNOTATE_INVOKE(annotate_marker, ()); \ 163 | } while(0) 164 | 165 | #define ANNOTATE_MARKER_STR(str) do { \ 166 | void (*annotate_marker_str_ptr)(const char*); \ 167 | ANNOTATE_INVOKE(annotate_marker_str, (str)); \ 168 | } while(0) 169 | 170 | #define ANNOTATE_MARKER_COLOR(color) do { \ 171 | void (*annotate_marker_color_ptr)(int); \ 172 | ANNOTATE_INVOKE(annotate_marker_color, (color)); \ 173 | } while(0) 174 | 175 | #define ANNOTATE_MARKER_COLOR_STR(color, str) do { \ 176 | void (*annotate_marker_color_str_ptr)(int, const char*); \ 177 | ANNOTATE_INVOKE(annotate_marker_color_str, (color, str)); \ 178 | } while(0) 179 | 180 | #else /* Start of user-space macro definitions */ 181 | 182 | #include 183 | #include 184 | 185 | #ifdef __cplusplus 186 | extern "C" { 187 | #endif 188 | 189 | enum gator_annotate_counter_class { 190 | ANNOTATE_DELTA = 1, 191 | ANNOTATE_ABSOLUTE, 192 | ANNOTATE_ACTIVITY 193 | }; 194 | 195 | enum gator_annotate_display { 196 | ANNOTATE_AVERAGE = 1, 197 | ANNOTATE_ACCUMULATE, 198 | ANNOTATE_HERTZ, 199 | ANNOTATE_MAXIMUM, 200 | ANNOTATE_MINIMUM 201 | }; 202 | 203 | enum gator_annotate_series_composition { 204 | ANNOTATE_STACKED = 1, 205 | ANNOTATE_OVERLAY, 206 | ANNOTATE_LOG10 207 | }; 208 | 209 | enum gator_annotate_rendering_type { 210 | ANNOTATE_FILL = 1, 211 | ANNOTATE_LINE, 212 | ANNOTATE_BAR 213 | }; 214 | 215 | void gator_annotate_setup(void); 216 | uint64_t gator_get_time(void); 217 | void gator_annotate_fork_child(void); 218 | void gator_annotate_flush(void); 219 | void gator_annotate_str(const uint32_t channel, const char *const str); 220 | void gator_annotate_color(const uint32_t channel, const uint32_t color, const char *const str); 221 | void gator_annotate_name_channel(const uint32_t channel, const uint32_t group, const char *const str); 222 | void gator_annotate_name_group(const uint32_t group, const char *const str); 223 | void gator_annotate_visual(const void *const data, const uint32_t length, const char *const str); 224 | void gator_annotate_marker(const char *const str); 225 | void gator_annotate_marker_color(const uint32_t color, const char *const str); 226 | void gator_annotate_counter(const uint32_t id, const char *const title, const char *const name, const int per_cpu, const enum gator_annotate_counter_class counter_class, const enum gator_annotate_display display, const char *const units, const uint32_t modifier, const enum gator_annotate_series_composition series_composition, const enum gator_annotate_rendering_type rendering_type, const int average_selection, const int average_cores, const int percentage, const size_t activity_count, const char *const *const activities, const uint32_t *const activity_colors, const uint32_t cores, const uint32_t color, const char *const description); 227 | void gator_annotate_counter_value(const uint32_t core, const uint32_t id, const int64_t value); 228 | void gator_annotate_activity_switch(const uint32_t core, const uint32_t id, const uint32_t activity, const uint32_t tid); 229 | void gator_cam_track(const uint32_t view_uid, const uint32_t track_uid, const uint32_t parent_track, const char *const name); 230 | void gator_cam_job(const uint32_t view_uid, const uint32_t job_uid, const char *const name, const uint32_t track, const uint64_t start_time, const uint64_t duration, const uint32_t color, const uint32_t primary_dependency, const size_t dependency_count, const uint32_t *const dependencies); 231 | void gator_cam_job_start(const uint32_t view_uid, const uint32_t job_uid, const char *const name, const uint32_t track, const uint64_t time, const uint32_t color); 232 | void gator_cam_job_set_dependencies(const uint32_t view_uid, const uint32_t job_uid, const uint64_t time, const uint32_t primary_dependency, const size_t dependency_count, const uint32_t *const dependencies); 233 | void gator_cam_job_stop(const uint32_t view_uid, const uint32_t job_uid, const uint64_t time); 234 | void gator_cam_view_name(const uint32_t view_uid, const char *const name); 235 | 236 | #define ANNOTATE_DEFINE extern int gator_annotate_unused 237 | 238 | #define ANNOTATE_SETUP gator_annotate_setup() 239 | 240 | #define ANNOTATE(str) gator_annotate_str(0, str) 241 | #define ANNOTATE_CHANNEL(channel, str) gator_annotate_str(channel, str) 242 | #define ANNOTATE_COLOR(color, str) gator_annotate_color(0, color, str) 243 | #define ANNOTATE_CHANNEL_COLOR(channel, color, str) gator_annotate_color(channel, color, str) 244 | #define ANNOTATE_END() gator_annotate_str(0, NULL) 245 | #define ANNOTATE_CHANNEL_END(channel) gator_annotate_str(channel, NULL) 246 | #define ANNOTATE_NAME_CHANNEL(channel, group, str) gator_annotate_name_channel(channel, group, str) 247 | #define ANNOTATE_NAME_GROUP(group, str) gator_annotate_name_group(group, str) 248 | #define ANNOTATE_VISUAL(data, length, str) gator_annotate_visual(data, length, str) 249 | #define ANNOTATE_MARKER() gator_annotate_marker(NULL) 250 | #define ANNOTATE_MARKER_STR(str) gator_annotate_marker(str) 251 | #define ANNOTATE_MARKER_COLOR(color) gator_annotate_marker_color(color, NULL) 252 | #define ANNOTATE_MARKER_COLOR_STR(color, str) gator_annotate_marker_color(color, str) 253 | #define ANNOTATE_DELTA_COUNTER(id, title, name) gator_annotate_counter(id, title, name, 0, ANNOTATE_DELTA, ANNOTATE_ACCUMULATE, NULL, 1, ANNOTATE_STACKED, ANNOTATE_FILL, 0, 0, 0, 0, NULL, NULL, 0, ANNOTATE_COLOR_CYCLE, NULL) 254 | #define ANNOTATE_ABSOLUTE_COUNTER(id, title, name) gator_annotate_counter(id, title, name, 0, ANNOTATE_ABSOLUTE, ANNOTATE_MAXIMUM, NULL, 1, ANNOTATE_STACKED, ANNOTATE_FILL, 0, 0, 0, 0, NULL, NULL, 0, ANNOTATE_COLOR_CYCLE, NULL) 255 | #define ANNOTATE_COUNTER_VALUE(id, value) gator_annotate_counter_value(0, id, value) 256 | #define CAM_TRACK(view_uid, track_uid, parent_track, name) gator_cam_track(view_uid, track_uid, parent_track, name) 257 | #define CAM_JOB(view_uid, job_uid, name, track, start_time, duration, color) gator_cam_job(view_uid, job_uid, name, track, start_time, duration, color, -1, 0, 0) 258 | #define CAM_JOB_DEP(view_uid, job_uid, name, track, start_time, duration, color, dependency) { \ 259 | uint32_t __dependency = dependency; \ 260 | gator_cam_job(view_uid, job_uid, name, track, start_time, duration, color, -1, 1, &__dependency); \ 261 | } 262 | #define CAM_JOB_DEPS(view_uid, job_uid, name, track, start_time, duration, color, dependency_count, dependencies) gator_cam_job(view_uid, job_uid, name, track, start_time, duration, color, -1, dependency_count, dependencies) 263 | #define CAM_JOB_START(view_uid, job_uid, name, track, time, color) gator_cam_job_start(view_uid, job_uid, name, track, time, color) 264 | #define CAM_JOB_SET_DEP(view_uid, job_uid, time, dependency) { \ 265 | uint32_t __dependency = dependency; \ 266 | gator_cam_job_set_dependencies(view_uid, job_uid, time, -1, 1, &__dependency); \ 267 | } 268 | #define CAM_JOB_SET_DEPS(view_uid, job_uid, time, dependency_count, dependencies) gator_cam_job_set_dependencies(view_uid, job_uid, time, -1, dependency_count, dependencies) 269 | #define CAM_JOB_STOP(view_uid, job_uid, time) gator_cam_job_stop(view_uid, job_uid, time) 270 | #define CAM_VIEW_NAME(view_uid, name) gator_cam_view_name(view_uid, name) 271 | 272 | #ifdef __cplusplus 273 | } 274 | #endif 275 | 276 | #endif /* _KERNEL_ */ 277 | #endif /* STREAMLINE_ANNOTATE_H */ 278 | -------------------------------------------------------------------------------- /Native~/streamline_annotate_logging.h: -------------------------------------------------------------------------------- 1 | /** 2 | * SPDX-License-Identifier: BSD-3-Clause 3 | * 4 | * Copyright (c) 2021-2022, Arm Limited 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 14 | * 2. Redistributions in binary form must reproduce the above copyright 15 | * notice, this list of conditions and the following disclaimer in the 16 | * documentation and/or other materials provided with the distribution. 17 | * 18 | * 3. Neither the name of the copyright holder nor the names of its 19 | * contributors may be used to endorse or promote products derived from 20 | * this software without specific prior written permission. 21 | * 22 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 25 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 26 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 27 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 28 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 29 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 30 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 31 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 32 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | */ 34 | 35 | #ifndef STREAMLINE_ANNOTATE_LOGGING_H 36 | #define STREAMLINE_ANNOTATE_LOGGING_H 37 | 38 | //Mapped to android log levels - android_LogPriority 39 | enum log_levels { 40 | LOG_UNKNOWN = 0, 41 | LOG_DEFAULT, 42 | LOG_VERBOSE, 43 | LOG_DEBUG, 44 | LOG_INFO, 45 | LOG_WARN, 46 | LOG_ERROR, 47 | LOG_FATAL, 48 | LOG_SILENT 49 | }; 50 | 51 | /* ANDROID IMPLEMENTATION */ 52 | #if defined(ANDROID) || defined(__ANDROID__) 53 | #include 54 | 55 | #define LOG_TAG "AnnotationLog" 56 | 57 | #define LOGGING(LOG_LEVEL, fmt, ...) \ 58 | __android_log_print(LOG_LEVEL, LOG_TAG, "%s:%d " fmt, __FUNCTION__, __LINE__, ##__VA_ARGS__); 59 | 60 | /* LINUX IMPLEMENTATION */ 61 | #elif defined(linux) || defined(__linux) || defined(__linux__) 62 | // clang-format off 63 | char *log_levels[] = { "UNKNOWN", 64 | "DEFAULT", 65 | "VERBOSE", 66 | "DEBUG", 67 | "INFO", 68 | "WARN", 69 | "ERROR", 70 | "FATAL", 71 | "SILENT"}; 72 | // clang-format on 73 | #define LOGGING(LOG_LEVEL, fmt, ...) \ 74 | printf("%s:%d [%s] " fmt " \n", __func__, __LINE__, log_levels[LOG_LEVEL], ##__VA_ARGS__); 75 | 76 | #endif 77 | //Use to do logging, if not needed un-define this variable 78 | #define ENABLE_LOG 79 | 80 | #ifdef ENABLE_LOG 81 | #define LOG(LOG_LEVEL, fmt, ...) LOGGING(LOG_LEVEL, fmt, ##__VA_ARGS__) 82 | #else 83 | #define LOG(LOG_LEVEL, fmt, ...) // nothing 84 | #endif 85 | 86 | #endif /* STREAMLINE_ANNOTATE_LOGGING_H */ -------------------------------------------------------------------------------- /Plugins.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d7982eaa35bd9b74cb90a6abece9d3c4 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Plugins/arm64-v8a.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b44fddb518743cf45b5e792cba5e2e4d 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Plugins/arm64-v8a/libmobilestudio.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARM-software/performance-studio-integration-for-unity/c379353ea01aa64bcc0a56028a4c20fd8743a1e6/Plugins/arm64-v8a/libmobilestudio.so -------------------------------------------------------------------------------- /Plugins/arm64-v8a/libmobilestudio.so.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 61598d34f4dd3774aa4fe94e8c2aa04e 3 | PluginImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | iconMap: {} 7 | executionOrder: {} 8 | defineConstraints: [] 9 | isPreloaded: 0 10 | isOverridable: 1 11 | isExplicitlyReferenced: 0 12 | validateReferences: 1 13 | platformData: 14 | - first: 15 | '': Any 16 | second: 17 | enabled: 0 18 | settings: 19 | Exclude Android: 0 20 | Exclude Editor: 1 21 | Exclude Linux: 1 22 | Exclude Linux64: 1 23 | Exclude LinuxUniversal: 1 24 | Exclude OSXUniversal: 1 25 | Exclude Win: 1 26 | Exclude Win64: 1 27 | - first: 28 | Android: Android 29 | second: 30 | enabled: 1 31 | settings: 32 | CPU: ARM64 33 | - first: 34 | Any: 35 | second: 36 | enabled: 0 37 | settings: {} 38 | - first: 39 | Editor: Editor 40 | second: 41 | enabled: 0 42 | settings: 43 | CPU: AnyCPU 44 | DefaultValueInitialized: true 45 | OS: AnyOS 46 | - first: 47 | Facebook: Win 48 | second: 49 | enabled: 0 50 | settings: 51 | CPU: AnyCPU 52 | - first: 53 | Facebook: Win64 54 | second: 55 | enabled: 0 56 | settings: 57 | CPU: AnyCPU 58 | - first: 59 | Standalone: Linux 60 | second: 61 | enabled: 0 62 | settings: 63 | CPU: x86 64 | - first: 65 | Standalone: Linux64 66 | second: 67 | enabled: 0 68 | settings: 69 | CPU: x86_64 70 | - first: 71 | Standalone: LinuxUniversal 72 | second: 73 | enabled: 0 74 | settings: 75 | CPU: None 76 | - first: 77 | Standalone: OSXUniversal 78 | second: 79 | enabled: 0 80 | settings: 81 | CPU: AnyCPU 82 | - first: 83 | Standalone: Win 84 | second: 85 | enabled: 0 86 | settings: 87 | CPU: AnyCPU 88 | - first: 89 | Standalone: Win64 90 | second: 91 | enabled: 0 92 | settings: 93 | CPU: AnyCPU 94 | userData: 95 | assetBundleName: 96 | assetBundleVariant: 97 | -------------------------------------------------------------------------------- /Plugins/armeabi-v7a.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 052fc7286bf3978499068f6f0606504b 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Plugins/armeabi-v7a/libmobilestudio.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARM-software/performance-studio-integration-for-unity/c379353ea01aa64bcc0a56028a4c20fd8743a1e6/Plugins/armeabi-v7a/libmobilestudio.so -------------------------------------------------------------------------------- /Plugins/armeabi-v7a/libmobilestudio.so.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6a581d82624939e40952afd4f6be1fef 3 | PluginImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | iconMap: {} 7 | executionOrder: {} 8 | defineConstraints: [] 9 | isPreloaded: 0 10 | isOverridable: 1 11 | isExplicitlyReferenced: 0 12 | validateReferences: 1 13 | platformData: 14 | - first: 15 | '': Any 16 | second: 17 | enabled: 0 18 | settings: 19 | Exclude Android: 0 20 | Exclude Editor: 1 21 | Exclude Linux: 1 22 | Exclude Linux64: 1 23 | Exclude LinuxUniversal: 1 24 | Exclude OSXUniversal: 1 25 | Exclude Win: 1 26 | Exclude Win64: 1 27 | - first: 28 | Android: Android 29 | second: 30 | enabled: 1 31 | settings: 32 | CPU: ARMv7 33 | - first: 34 | Any: 35 | second: 36 | enabled: 0 37 | settings: {} 38 | - first: 39 | Editor: Editor 40 | second: 41 | enabled: 0 42 | settings: 43 | CPU: AnyCPU 44 | DefaultValueInitialized: true 45 | OS: AnyOS 46 | - first: 47 | Facebook: Win 48 | second: 49 | enabled: 0 50 | settings: 51 | CPU: AnyCPU 52 | - first: 53 | Facebook: Win64 54 | second: 55 | enabled: 0 56 | settings: 57 | CPU: AnyCPU 58 | - first: 59 | Standalone: Linux 60 | second: 61 | enabled: 0 62 | settings: 63 | CPU: x86 64 | - first: 65 | Standalone: Linux64 66 | second: 67 | enabled: 0 68 | settings: 69 | CPU: x86_64 70 | - first: 71 | Standalone: LinuxUniversal 72 | second: 73 | enabled: 0 74 | settings: 75 | CPU: None 76 | - first: 77 | Standalone: OSXUniversal 78 | second: 79 | enabled: 0 80 | settings: 81 | CPU: AnyCPU 82 | - first: 83 | Standalone: Win 84 | second: 85 | enabled: 0 86 | settings: 87 | CPU: AnyCPU 88 | - first: 89 | Standalone: Win64 90 | second: 91 | enabled: 0 92 | settings: 93 | CPU: AnyCPU 94 | userData: 95 | assetBundleName: 96 | assetBundleVariant: 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Performance Studio Package 2 | ========================== 3 | 4 | This project is a Unity package for integrating the Performance Studio tool 5 | suite into game development workflows. This version of the package has the 6 | following features for integrating with the Streamline profiler. 7 | 8 | * C# language bindings for emitting event annotations. 9 | * C# language bindings for emitting software counters. 10 | 11 | > **Note:** Performance Studio was formerly known as Mobile Studio. For API 12 | > backwards compatibility the package continues to use the `MobileStudio` file 13 | > name prefix and C# namespace. 14 | 15 | License 16 | ======= 17 | 18 | Most files in this library are licensed under the BSD-3 Clause License (see 19 | [LICENSE.md](LICENSE.md)). 20 | 21 | The Unity native plugin interface header, `IUnityInterface.h`, is licensed 22 | under the Unity Companion License (see [LICENSE_UNITY.md](LICENSE_UNITY.md)). 23 | 24 | Technical details 25 | ================= 26 | 27 | Requirements 28 | ------------ 29 | 30 | This version of the package is compatible with the Unity Editor version 2020.3 31 | LTS and later. 32 | 33 | Building 34 | -------- 35 | 36 | This package is built using the Unity bee compiler. 37 | 38 | 1) Set the environment variable `ANDROID_NDK_ROOT` to a local Android NDK 39 | install. Android NDK can usually be found in: 40 | `\Editor\Data\PlaybackEngines\AndroidPlayer\NDK`. 41 | 2) Locate the bee compiler in your Unity install. It is usually found in: 42 | `\Editor\Data\il2cpp\build\BeeSettings\offline\bee.exe`. 43 | 3) To trigger a build, run `bee.exe` in the `Native~` directory. 44 | 45 | Compatibility 46 | ------------- 47 | 48 | This package uses application-generated Streamline annotations to add more 49 | context to captured data. It allocates annotation handles in the ID range 50 | 16384-32767; other users of Streamline in the same process should avoid using 51 | this range to avoid collisions. 52 | 53 | Installing and using 54 | -------------------- 55 | 56 | For instructions on how to install and use this package, see the 57 | [full documentation page](Documentation/Performance-Studio.md). 58 | 59 | - - - 60 | 61 | _Copyright © 2021-2024, Arm Limited and contributors. All rights reserved._ 62 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 60c9baf42bd81e44a8bb998e8bc6893e 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e97c0803001d45f4eb84d787f91a4af9 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/MobileStudio.Runtime.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "MobileStudio.Runtime", 3 | "references": [], 4 | "optionalUnityReferences": [], 5 | "includePlatforms": [], 6 | "excludePlatforms": [], 7 | "allowUnsafeCode": false, 8 | "overrideReferences": false, 9 | "precompiledReferences": [ 10 | "" 11 | ], 12 | "autoReferenced": true, 13 | "defineConstraints": [] 14 | } -------------------------------------------------------------------------------- /Runtime/MobileStudio.Runtime.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 76d1720d20ddf9244861e7467185a304 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime/MobileStudio.cs: -------------------------------------------------------------------------------- 1 | /** 2 | * SPDX-License-Identifier: BSD-3-Clause 3 | * 4 | * Copyright (c) 2021-2022, Arm Limited 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 14 | * 2. Redistributions in binary form must reproduce the above copyright 15 | * notice, this list of conditions and the following disclaimer in the 16 | * documentation and/or other materials provided with the distribution. 17 | * 18 | * 3. Neither the name of the copyright holder nor the names of its 19 | * contributors may be used to endorse or promote products derived from 20 | * this software without specific prior written permission. 21 | * 22 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 25 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 26 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 27 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 28 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 29 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 30 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 31 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 32 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | */ 34 | 35 | using System; 36 | using System.Collections.Generic; 37 | using System.Diagnostics; 38 | using System.Runtime.CompilerServices; 39 | using System.Runtime.InteropServices; 40 | using UnityEngine; 41 | 42 | namespace MobileStudio 43 | { 44 | public class Annotations 45 | { 46 | private static readonly object _locker = new object(); 47 | 48 | // Global state as to whether annotations are available for use. 49 | private enum AnnotationState { Active, Inactive }; 50 | 51 | // Counter type 52 | public enum CounterType { Absolute, Delta }; 53 | 54 | private static AnnotationState state = getAnnotationState(); 55 | 56 | internal static bool Active => state == AnnotationState.Active; 57 | 58 | #if UNITY_ANDROID && !UNITY_EDITOR 59 | [DllImport("mobilestudio")] 60 | private static extern void gator_annotate_setup(); 61 | 62 | [DllImport("mobilestudio")] 63 | private static extern void gator_annotate_marker( 64 | string str); 65 | 66 | [DllImport("mobilestudio")] 67 | private static extern void gator_annotate_marker_color( 68 | UInt32 color, string str); 69 | 70 | [DllImport("mobilestudio")] 71 | private static extern void gator_annotate_color( 72 | UInt32 channel, UInt32 color, string str); 73 | 74 | [DllImport("mobilestudio")] 75 | private static extern void gator_annotate_str( 76 | UInt32 channel, string str); 77 | 78 | [DllImport("mobilestudio")] 79 | private static extern void gator_annotate_name_channel( 80 | UInt32 channel, UInt32 group, string str); 81 | 82 | [DllImport("mobilestudio")] 83 | private static extern void gator_cam_view_name( 84 | UInt32 view_uid, string name); 85 | 86 | [DllImport("mobilestudio")] 87 | private static extern void gator_cam_track( 88 | UInt32 view_uid, UInt32 track_uid, UInt32 parent_track, string name); 89 | 90 | [DllImport("mobilestudio")] 91 | private static extern UInt64 gator_get_time(); 92 | 93 | [DllImport("mobilestudio")] 94 | private static extern void gator_cam_job_start( 95 | UInt32 view_uid, UInt32 job_uid, string name, UInt32 track, UInt64 time, UInt32 color); 96 | 97 | [DllImport("mobilestudio")] 98 | private static extern void gator_cam_job_stop( 99 | UInt32 view_uid, UInt32 job_uid, UInt64 time); 100 | 101 | [DllImport("mobilestudio")] 102 | private static extern void gator_cam_job_set_dependencies( 103 | UInt32 view_uid, UInt32 job_uid, UInt64 time, 104 | UInt32 primary_dependency, UInt32 dependency_count, [In, Out] UInt32[] dependencies); 105 | 106 | [DllImport("mobilestudio")] 107 | private static extern void gator_cam_job( 108 | UInt32 view_uid, UInt32 job_uid, string name, UInt32 track, UInt64 startTime, 109 | UInt64 duration, UInt32 color, UInt32 primary_dependency, UInt32 dependency_count, 110 | [In, Out] UInt32[] dependencies); 111 | 112 | [DllImport("mobilestudio")] 113 | private static extern void gator_annotate_counter( 114 | UInt32 counter_id, string title, string name, UInt32 per_cpu, UInt32 counter_class, 115 | UInt32 display_class, string units, UInt32 modifier, UInt32 display_composition, 116 | UInt32 display_renderer, UInt32 avg_selection, UInt32 avg_cores, UInt32 percentage, 117 | IntPtr activity_count, IntPtr activity_names, IntPtr activity_colors, UInt32 cores, 118 | UInt32 color, string description); 119 | 120 | [DllImport("mobilestudio")] 121 | private static extern void gator_annotate_counter_value( 122 | UInt32 cpu, UInt32 counter_id, Int64 value); 123 | #endif 124 | 125 | /* 126 | * Converts a Unity Color32 into a 32-bit Int used by gator to represent 127 | * the color. Gator's format is little-endian with a 0x1b escape code. 128 | */ 129 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 130 | private static UInt32 colorToGatorInt(Color32 color) 131 | { 132 | UInt32 colorInt = ((uint)(color.b) << 24) + 133 | ((uint)(color.g) << 16) + 134 | ((uint)(color.r) << 8) + 135 | ((uint)0x1b); 136 | 137 | return colorInt; 138 | } 139 | 140 | /* 141 | * Returns the active state if annotations are supported (we are running on Android 142 | * and successfully initialized the library), inactive otherwise 143 | */ 144 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 145 | private static AnnotationState getAnnotationState() 146 | { 147 | #if UNITY_ANDROID && !UNITY_EDITOR 148 | try 149 | { 150 | gator_annotate_setup(); 151 | return AnnotationState.Active; 152 | } 153 | catch (System.EntryPointNotFoundException) 154 | { 155 | return AnnotationState.Inactive; 156 | } 157 | #else 158 | return AnnotationState.Inactive; 159 | #endif 160 | } 161 | 162 | /* 163 | * Emit a simple marker that is displayed along the top of Streamline's 164 | * timeline. 165 | */ 166 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 167 | [Conditional("UNITY_ANDROID")] 168 | public static void marker(string str) 169 | { 170 | #if UNITY_ANDROID && !UNITY_EDITOR 171 | if (state == AnnotationState.Active) 172 | { 173 | gator_annotate_marker(str); 174 | } 175 | #endif 176 | } 177 | 178 | /* 179 | * Emit a colored marker that is displayed along the top of Streamline's 180 | * timeline. 181 | */ 182 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 183 | [Conditional("UNITY_ANDROID")] 184 | public static void marker(string str, Color32 color) 185 | { 186 | #if UNITY_ANDROID && !UNITY_EDITOR 187 | if (state == AnnotationState.Active) 188 | { 189 | UInt32 col = colorToGatorInt(color); 190 | gator_annotate_marker_color(col, str); 191 | } 192 | #endif 193 | } 194 | 195 | /* 196 | * Return a timestamp in a format that can later be used to register 197 | * a job on a Custom Activity Map's Track. 198 | */ 199 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 200 | public static UInt64 getTime() 201 | { 202 | #if UNITY_ANDROID && !UNITY_EDITOR 203 | if (state == AnnotationState.Active) 204 | { 205 | return gator_get_time(); 206 | } 207 | #endif 208 | return 0; 209 | } 210 | 211 | /* 212 | * Represents a single counter in the timeline. 213 | */ 214 | public class Counter 215 | { 216 | // Maintain a unique ID for each counter. 217 | // Allocate in a namespace block starting from 16384 to reduce 218 | // chances of collisions with other users of annotations. 219 | static UInt32 counterCount = 0x4000; 220 | 221 | // The scaling modifier used to display series as float to 2dp. 222 | const UInt32 modifier = 100; 223 | 224 | // The counter classes used by Streamline's native interface. 225 | const UInt32 CC_DELTA = 1; 226 | const UInt32 CC_ABSOLUTE = 2; 227 | 228 | // The display classes used by Streamline's native interface. 229 | const UInt32 DC_ACCUMULATE = 2; 230 | const UInt32 DC_MAXIMUM = 4; 231 | 232 | // The renderer classes used by Streamline's native interface. 233 | const UInt32 RC_OVERLAY = 2; 234 | const UInt32 RC_LINE = 2; 235 | 236 | // Our counter ID. 237 | UInt32 counter; 238 | 239 | /* 240 | * Specify the counter chart title, series name, and value type. 241 | */ 242 | public Counter(string title, string name, CounterType type, string unit = null) 243 | { 244 | lock(_locker) 245 | { 246 | counterCount++; 247 | counter = counterCount; 248 | } 249 | 250 | #if UNITY_ANDROID && !UNITY_EDITOR 251 | if (state == AnnotationState.Active) 252 | { 253 | UInt32 counterClass = type == CounterType.Delta ? CC_DELTA : CC_ABSOLUTE; 254 | UInt32 displayClass = type == CounterType.Delta ? DC_ACCUMULATE : DC_MAXIMUM; 255 | 256 | gator_annotate_counter( 257 | counter, title, name, 0, counterClass, displayClass, 258 | unit, modifier, RC_OVERLAY, RC_LINE, 0, 0, 0, 259 | IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, 0, 0, null); 260 | } 261 | #endif 262 | } 263 | 264 | /* 265 | * Update the counter value. 266 | */ 267 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 268 | [Conditional("UNITY_ANDROID")] 269 | public void setValue(float value) 270 | { 271 | #if UNITY_ANDROID && !UNITY_EDITOR 272 | if (state == AnnotationState.Active) 273 | { 274 | Int64 ivalue = (Int64)(value * (float)modifier); 275 | gator_annotate_counter_value(0, counter, ivalue); 276 | } 277 | #endif 278 | } 279 | } 280 | 281 | /* 282 | * Represents a channel of activity for the thread in which the channel 283 | * was created. Displayed as a row in Streamline's Heat Map view, 284 | * inside the process. 285 | */ 286 | public class Channel 287 | { 288 | // Maintain a unique ID for each channel. 289 | // Allocate in a namespace block starting from 16384 to reduce 290 | // chances of collisions with other users of annotations. 291 | static UInt32 channelCount = 0x4000; 292 | 293 | // Our channel ID. 294 | UInt32 channel; 295 | 296 | /* 297 | * Specify a name, which will be displayed in Streamline's Heat 298 | * Map view. 299 | */ 300 | public Channel(string name) 301 | { 302 | lock(_locker) 303 | { 304 | channelCount++; 305 | channel = channelCount; 306 | } 307 | 308 | #if UNITY_ANDROID && !UNITY_EDITOR 309 | if (state == AnnotationState.Active) 310 | { 311 | gator_annotate_name_channel(channel, 0, name); 312 | } 313 | #endif 314 | } 315 | 316 | /* 317 | * Starts an annotation in the channel, which will be labelled as 318 | * specified. This will appear in the Channel as an activity that 319 | * begins at the point in time where this method was called. It 320 | * will end when the next annotate() call is made, or the next end() 321 | * call. 322 | */ 323 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 324 | [Conditional("UNITY_ANDROID")] 325 | public void annotate(String str) 326 | { 327 | #if UNITY_ANDROID && !UNITY_EDITOR 328 | if (state == AnnotationState.Active) 329 | { 330 | gator_annotate_str(channel, str); 331 | } 332 | #endif 333 | } 334 | 335 | /* 336 | * As above, but with a specific color. 337 | */ 338 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 339 | [Conditional("UNITY_ANDROID")] 340 | public void annotate(String str, Color32 color) 341 | { 342 | #if UNITY_ANDROID && !UNITY_EDITOR 343 | if (state == AnnotationState.Active) 344 | { 345 | UInt32 intColor = colorToGatorInt(color); 346 | gator_annotate_color(channel, intColor, str); 347 | } 348 | #endif 349 | } 350 | 351 | /* 352 | * Marks the end of an annotation. The other way to end an 353 | * annotation is to start a new one in the channel. 354 | */ 355 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 356 | [Conditional("UNITY_ANDROID")] 357 | public void end() 358 | { 359 | #if UNITY_ANDROID && !UNITY_EDITOR 360 | if (state == AnnotationState.Active) 361 | { 362 | gator_annotate_str(channel, null); 363 | } 364 | #endif 365 | } 366 | } 367 | 368 | /* 369 | * Custom Activity Maps (CAMs) are each special views, displayed in the 370 | * bottom half of the Streamline UI. Each CAM consists of several 371 | * Tracks, and each Track can have Jobs placed on it (like annotations 372 | * in channels, but more flexible). 373 | */ 374 | public class CAM 375 | { 376 | /* 377 | * Represents a Track within a CAM 378 | */ 379 | public interface CAMTrack 380 | { 381 | /* 382 | * Creates a track with this track as its parent. 383 | */ 384 | CAMTrack createTrack(string name); 385 | 386 | /** 387 | * Creates an immediate-mode \c CAMJob with dependencies. 388 | * 389 | * This Job will be marked as starting immediately, and the caller must mark 390 | * completion with a call to \c stop() on the returned \c CAMJob. 391 | */ 392 | CAMJob makeJob(string name, Color32 color, List dependencies=null); 393 | 394 | /** 395 | * Registers a deferred-mode \c CAMJob with dependencies. 396 | * 397 | * This Job will use the caller-provided start and stop times. This is useful if you 398 | * are unable to register jobs as they happen, for example when they are created as 399 | * part of a job running in the Unity Job Scheduler. 400 | */ 401 | CAMJob registerJob(string name, Color32 color, UInt64 startTime, UInt64 stopTime, List dependencies=null); 402 | 403 | /* 404 | * Get the UID of this track. 405 | */ 406 | UInt32 getUid(); 407 | } 408 | 409 | /* 410 | * Represents a Job within a Track in a CAM. 411 | */ 412 | public interface CAMJob 413 | { 414 | /* 415 | * Registers that a job has completed at this point in time. 416 | */ 417 | void stop(); 418 | 419 | /* 420 | * Get the UID of this job. 421 | */ 422 | UInt32 getUid(); 423 | } 424 | 425 | 426 | // Maintain a unique ID for each CAM. 427 | // Allocate in a namespace block starting from 16384 to reduce 428 | // chances of collisions with other users of annotations. 429 | private static UInt32 camCount = 0x4000; 430 | 431 | private UInt32 trackCount; 432 | private UInt32 jobCount; 433 | private UInt32 viewUid; 434 | 435 | /* 436 | * When creating a Custom Activity Map, you must specify a name, 437 | * which is used to name the view in the Streamline UI. 438 | */ 439 | public CAM(string name) 440 | { 441 | // Tracks and Jobs need unique IDs. 442 | this.trackCount = 0; 443 | this.jobCount = 0; 444 | 445 | lock(_locker) 446 | { 447 | // Each CAM needs a unique ID. 448 | this.viewUid = camCount++; 449 | } 450 | 451 | #if UNITY_ANDROID && !UNITY_EDITOR 452 | if (state == AnnotationState.Active) 453 | { 454 | gator_cam_view_name(this.viewUid, name); 455 | } 456 | #endif 457 | } 458 | 459 | /** 460 | * Creates a track as a root track in the CAM view. 461 | */ 462 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 463 | public CAMTrack createTrack(string name) 464 | { 465 | CAMTrack newTrack; 466 | lock(_locker) 467 | { 468 | newTrack = new CAMTrackImp(this, name, ++trackCount); 469 | } 470 | return newTrack; 471 | } 472 | 473 | /* 474 | * Private implementation of the CAMTrack. 475 | */ 476 | private class CAMTrackImp : CAMTrack 477 | { 478 | // Parent CAM view - needed because Jobs must reference the CAM ID. 479 | private CAM view; 480 | 481 | // Maintain unique UIDs for each Track. 482 | private UInt32 trackUid; 483 | 484 | /* 485 | * Create a track. 486 | */ 487 | public CAMTrackImp(CAM view, String name, UInt32 trackUid, CAMTrack parent=null) 488 | { 489 | this.view = view; 490 | this.trackUid = trackUid; 491 | 492 | #if UNITY_ANDROID && !UNITY_EDITOR 493 | if (state == AnnotationState.Active) 494 | { 495 | if (parent != null) 496 | { 497 | gator_cam_track(view.viewUid, this.trackUid, parent.getUid(), name); 498 | } 499 | else 500 | { 501 | gator_cam_track(view.viewUid, this.trackUid, 0xffffffff, name); 502 | } 503 | } 504 | #endif 505 | } 506 | 507 | /* See CAMTrack interface for documentation. */ 508 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 509 | public UInt32 getUid() 510 | { 511 | return this.trackUid; 512 | } 513 | 514 | /* See CAMTrack interface for documentation. */ 515 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 516 | public CAMTrack createTrack(string name) 517 | { 518 | CAMTrack newTrack; 519 | lock(_locker) 520 | { 521 | newTrack = new CAMTrackImp(this.view, name, ++view.trackCount, this); 522 | } 523 | return newTrack; 524 | } 525 | 526 | /* See CAMTrack interface for documentation. */ 527 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 528 | public CAMJob makeJob(string name, Color32 color, List dependencies=null) 529 | { 530 | UInt32 intColor = colorToGatorInt(color); 531 | 532 | CAMJob newJob; 533 | lock(_locker) 534 | { 535 | newJob = new CAMJobImp(view, trackUid, view.jobCount++, name, intColor, dependencies); 536 | } 537 | 538 | return newJob; 539 | } 540 | 541 | /* See CAMJob interface for documentation. */ 542 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 543 | public CAMJob registerJob(string name, Color32 color, UInt64 startTime, UInt64 stopTime, List dependencies=null) 544 | { 545 | UInt32 intColor = colorToGatorInt(color); 546 | 547 | CAMJob newJob; 548 | lock (_locker) 549 | { 550 | newJob = new CAMJobImp(view, trackUid, view.jobCount++, name, intColor, startTime, stopTime, dependencies); 551 | } 552 | 553 | return newJob; 554 | } 555 | } 556 | 557 | /* 558 | * Private implementation of the CAMJob. 559 | */ 560 | private class CAMJobImp : CAMJob 561 | { 562 | // These are all maintained by the CAM and the Track. 563 | private UInt32 viewUid; 564 | private UInt32 jobUid; 565 | 566 | /* 567 | * Retrieves the existing time and uses it to register the start 568 | * of a job. Finish it with a call to stop(). If you want to 569 | * register a job after the fact, use Track.registerJob() to 570 | * register a Job with a specific start and end time. 571 | */ 572 | public CAMJobImp(CAM view, UInt32 trackUid, UInt32 jobUid, string name, UInt32 color, List dependencies) 573 | { 574 | this.viewUid = view.viewUid; 575 | this.jobUid = jobUid; 576 | 577 | 578 | #if UNITY_ANDROID && !UNITY_EDITOR 579 | if (state == AnnotationState.Active) 580 | { 581 | UInt64 startTime = gator_get_time(); 582 | gator_cam_job_start(this.viewUid, this.jobUid, name, trackUid, startTime, color); 583 | 584 | if (dependencies != null) 585 | { 586 | // Create the dependencies list 587 | int depCount = dependencies.Count; 588 | UInt32[] depsList = new UInt32[depCount]; 589 | 590 | int i = 0; 591 | foreach (CAMJob job in dependencies) 592 | { 593 | depsList[i] = job.getUid(); 594 | i++; 595 | } 596 | 597 | gator_cam_job_set_dependencies(this.viewUid, this.jobUid, startTime, 0xFFFFFFFF, (UInt32)depCount, depsList); 598 | } 599 | } 600 | 601 | #endif 602 | } 603 | 604 | /* 605 | * Retrieves the existing time and uses it to register the start 606 | * of a job. Finish it with a call to stop(). If you want to 607 | * register a job after the fact, use Track.registerJob() to 608 | * register a Job with a specific start and end time. 609 | */ 610 | public CAMJobImp(CAM view, UInt32 trackUid, UInt32 jobUid, string name, UInt32 color, UInt64 startTime, UInt64 stopTime, List dependencies) 611 | { 612 | this.viewUid = view.viewUid; 613 | this.jobUid = jobUid; 614 | 615 | #if UNITY_ANDROID && !UNITY_EDITOR 616 | if (state == AnnotationState.Active) 617 | { 618 | if (dependencies == null) 619 | { 620 | gator_cam_job(view.viewUid, jobUid, name, trackUid, startTime, stopTime - startTime, color, 0xffffffff, 0, null); 621 | } 622 | else 623 | { 624 | int depCount = dependencies.Count; 625 | UInt32[] depsList = new UInt32[depCount]; 626 | 627 | int i = 0; 628 | foreach (CAMJob job in dependencies) 629 | { 630 | depsList[i] = job.getUid(); 631 | i++; 632 | } 633 | 634 | gator_cam_job(view.viewUid, jobUid, name, trackUid, startTime, stopTime - startTime, color, 0xffffffff, (UInt32)depCount, depsList); 635 | } 636 | } 637 | #endif 638 | } 639 | 640 | /* See CAMJob interface for documentation. */ 641 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 642 | public UInt32 getUid() 643 | { 644 | return this.jobUid; 645 | } 646 | 647 | /* See CAMJob interface for documentation. */ 648 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 649 | public void stop() 650 | { 651 | #if UNITY_ANDROID && !UNITY_EDITOR 652 | if (state == AnnotationState.Active) 653 | { 654 | UInt64 stopTime = gator_get_time(); 655 | gator_cam_job_stop(this.viewUid, this.jobUid, stopTime); 656 | } 657 | #endif 658 | } 659 | } 660 | } 661 | } 662 | } 663 | -------------------------------------------------------------------------------- /Runtime/MobileStudio.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0ec00e53c7d7242eb9dcf238d3c57ba1 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/UnityStatsProxy.cs: -------------------------------------------------------------------------------- 1 | /** 2 | * SPDX-License-Identifier: BSD-3-Clause 3 | * 4 | * Copyright (c) 2022, Arm Limited 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 14 | * 2. Redistributions in binary form must reproduce the above copyright 15 | * notice, this list of conditions and the following disclaimer in the 16 | * documentation and/or other materials provided with the distribution. 17 | * 18 | * 3. Neither the name of the copyright holder nor the names of its 19 | * contributors may be used to endorse or promote products derived from 20 | * this software without specific prior written permission. 21 | * 22 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 25 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 26 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 27 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 28 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 29 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 30 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 31 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 32 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | */ 34 | 35 | // Only active in Android Players 36 | #if UNITY_ANDROID && !UNITY_EDITOR 37 | 38 | using System.Collections.Generic; 39 | using System.Runtime.InteropServices; 40 | using Unity.Profiling; 41 | using UnityEngine; 42 | using UnityEngine.LowLevel; 43 | using UnityEngine.Scripting; 44 | 45 | [assembly: AlwaysLinkAssembly] 46 | 47 | namespace MobileStudio 48 | { 49 | class UnityStatsProxy 50 | { 51 | static UnityStatsProxy Instance { get; set; } 52 | 53 | struct CounterObjectPair 54 | { 55 | public CounterObjectPair(Annotations.Counter mobileStudioCounter, ProfilerRecorder unityCounter, bool convertToMiB) 56 | { 57 | this.mobileStudioCounter = mobileStudioCounter; 58 | this.unityCounter = unityCounter; 59 | this.convertToMiB = convertToMiB; 60 | } 61 | 62 | public Annotations.Counter mobileStudioCounter; 63 | public ProfilerRecorder unityCounter; 64 | public bool convertToMiB; 65 | } 66 | List counters; 67 | 68 | struct CounterMapping 69 | { 70 | public CounterMapping(string mobileStudioTitle, string mobileStudioCounterName, string mobileStudioCounterUnit, string unityCounterName) 71 | { 72 | this.mobileStudioTitle = mobileStudioTitle; 73 | this.mobileStudioCounterName = mobileStudioCounterName; 74 | this.mobileStudioCounterUnit = mobileStudioCounterUnit; 75 | this.unityCounterName = unityCounterName; 76 | } 77 | 78 | public string mobileStudioTitle; 79 | public string mobileStudioCounterName; 80 | public string mobileStudioCounterUnit; 81 | public string unityCounterName; 82 | } 83 | 84 | // Memory counters are captured based on https://docs.unity3d.com/Manual/ProfilerMemory.html information. 85 | static readonly string memoryStatsTitle = "Unity Memory Usage"; 86 | static readonly string objectStatsTitle = "Unity Objects"; 87 | static readonly string unitBytes = "MiB"; 88 | static readonly string unitObjects = "objects"; 89 | const double toMiB = 1.0 / (1024.0 * 1024.0); 90 | static readonly CounterMapping[] mobileStudioDefaultCounters = 91 | { 92 | new CounterMapping(memoryStatsTitle, "Total Memory Usage", unitBytes, "Total Reserved Memory"), 93 | new CounterMapping(memoryStatsTitle, "Scripting Memory Usage", unitBytes, "GC Reserved Memory"), 94 | // Development player memory stats 95 | new CounterMapping(memoryStatsTitle, "Graphics Memory Usage", unitBytes, "Gfx Reserved Memory"), 96 | // Development player object stats 97 | new CounterMapping(objectStatsTitle, "Object Count", unitObjects, "Object Count"), 98 | new CounterMapping(objectStatsTitle, "Game Object Count", unitObjects, "Game Object Count"), 99 | new CounterMapping(objectStatsTitle, "Texture Count", unitObjects, "Texture Count"), 100 | new CounterMapping(objectStatsTitle, "Mesh Count", unitObjects, "Mesh Count"), 101 | new CounterMapping(objectStatsTitle, "Material Count", unitObjects, "Material Count"), 102 | new CounterMapping(objectStatsTitle, "AnimationClip Count", unitObjects, "AnimationClip Count"), 103 | }; 104 | 105 | [RuntimeInitializeOnLoadMethod] 106 | [Preserve] 107 | static void InitializeUnityStatsProxy() 108 | { 109 | if (Instance == null && Annotations.Active) 110 | { 111 | Instance = new UnityStatsProxy(); 112 | } 113 | } 114 | 115 | UnityStatsProxy() 116 | { 117 | if (!InstallUpdateCallback()) 118 | { 119 | UnityEngine.Debug.LogError("UnityStatsProxy: Failed to inject Playerloop callback!"); 120 | return; 121 | } 122 | 123 | InitializeCounters(); 124 | } 125 | 126 | ~UnityStatsProxy() 127 | { 128 | // Cleanup Unity counters 129 | DisposeCounters(); 130 | } 131 | 132 | bool InstallUpdateCallback() 133 | { 134 | var root = PlayerLoop.GetCurrentPlayerLoop(); 135 | var injectionPoint = typeof(UnityEngine.PlayerLoop.PreLateUpdate); 136 | var result = InjectPlayerLoopCallback(ref root, injectionPoint, Update); 137 | PlayerLoop.SetPlayerLoop(root); 138 | return result; 139 | } 140 | 141 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)] 142 | delegate uint PlayerLoopDelegate(); 143 | 144 | static bool InjectPlayerLoopCallback(ref PlayerLoopSystem system, System.Type injectedSystem, PlayerLoopSystem.UpdateFunction injectedFnc) 145 | { 146 | // Have we found the system we're looking for? 147 | if (system.type == injectedSystem) 148 | { 149 | // If system has updateFunction set, updateDelegate won't be called 150 | // We wrap native update function in something we can call in c# 151 | // Reset updateFunction and call wrapped updateFunction in our delegate 152 | PlayerLoopDelegate systemDelegate = null; 153 | if (system.updateFunction.ToInt64() != 0) 154 | { 155 | var intPtr = Marshal.ReadIntPtr(system.updateFunction); 156 | if (intPtr.ToInt64() != 0) 157 | { 158 | systemDelegate = (PlayerLoopDelegate)Marshal.GetDelegateForFunctionPointer(intPtr, typeof(PlayerLoopDelegate)); 159 | } 160 | } 161 | 162 | // Install the new delegate and keep the system function call 163 | system.updateDelegate = () => { injectedFnc(); if (systemDelegate != null) _ = systemDelegate(); }; 164 | system.updateFunction = new System.IntPtr(0); 165 | 166 | return true; 167 | } 168 | 169 | if (system.subSystemList == null) 170 | { 171 | return false; 172 | } 173 | 174 | // Iterate all subsystems 175 | for (int i = 0; i < system.subSystemList.Length; ++i) 176 | { 177 | if (InjectPlayerLoopCallback(ref system.subSystemList[i], injectedSystem, injectedFnc)) 178 | { 179 | return true; 180 | } 181 | } 182 | 183 | return false; 184 | } 185 | 186 | void InitializeCounters() 187 | { 188 | // Map Unity counters to Mobile Studio 189 | counters = new List(); 190 | for (var i = 0; i < mobileStudioDefaultCounters.Length; ++i) 191 | { 192 | var recorder = new ProfilerRecorder(mobileStudioDefaultCounters[i].unityCounterName, 0); 193 | if (!recorder.Valid) 194 | { 195 | recorder.Dispose(); 196 | continue; 197 | } 198 | 199 | var mobileStudioCounter = new Annotations.Counter(mobileStudioDefaultCounters[i].mobileStudioTitle, mobileStudioDefaultCounters[i].mobileStudioCounterName, Annotations.CounterType.Absolute, mobileStudioDefaultCounters[i].mobileStudioCounterUnit); 200 | counters.Add(new CounterObjectPair(mobileStudioCounter, recorder, mobileStudioDefaultCounters[i].mobileStudioCounterUnit == unitBytes)); 201 | } 202 | } 203 | 204 | void DisposeCounters() 205 | { 206 | if (counters == null) 207 | { 208 | return; 209 | } 210 | 211 | for (var i = 0; i < counters.Count; ++i) 212 | { 213 | counters[i].unityCounter.Dispose(); 214 | } 215 | 216 | counters = null; 217 | } 218 | 219 | void Update() 220 | { 221 | // Report Unity counters to MobileStudio on a frame basis 222 | for (var i = 0; i < counters.Count; ++i) 223 | { 224 | var value = counters[i].unityCounter.CurrentValueAsDouble; 225 | float valueToReport = counters[i].convertToMiB ? (float)(value * toMiB) : (float)value; 226 | counters[i].mobileStudioCounter.setValue(valueToReport); 227 | } 228 | } 229 | } 230 | } 231 | 232 | #endif 233 | -------------------------------------------------------------------------------- /Runtime/UnityStatsProxy.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9c045f7abd691ff42b9703630c61b284 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.arm.mobile-studio", 3 | "description": "Unity C# bindings for Mobile Studio.", 4 | "version": "1.5.0", 5 | "unity": "2020.3", 6 | "displayName": "Mobile Studio" 7 | } -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e7dd05f3d1f33b440a07360f90372d16 3 | PackageManifestImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | --------------------------------------------------------------------------------