void StandardSensors::OnThreadExecute(void)
    {
        // Pre-load what files we can... reduces open/close overhead (which is significant)

        // Setup CPU Clocks Support...
        FileHandle cpuOnlineFiles[g_maxCpus];
        FileHandle cpuFreqFiles[g_maxCpus];
        for(unsigned int i=0; i<g_maxCpus; i++)
        {
            cpuOnlineFiles[i] = cpuFreqFiles[i] = NullFileHandle;
        }
        if(CheckConnectionFlag(Enable_CPU_Zones))
        {
            for(unsigned int i=0; i<g_maxCpus; i++)
            {
                const CpuSensorDesc &desc = g_cpuDescs[i];
                cpuOnlineFiles[i] = OpenFile(desc.onlinePath);
                cpuFreqFiles[i]   = NullFileHandle;
                if(cpuOnlineFiles[i] != NullFileHandle)
                {
                    const int maxFreq = ReadIntFile(desc.maxFreqPath);
                    SensorSetRange(desc.label, 0, (float)maxFreq, Sensor_Interp_Nearest, Sensor_Unit_KHz);
                }
            }
        }

        // Setup GPU Clocks Support...
        FileHandle gpuFreqFile = NullFileHandle;
        if(CheckConnectionFlag(Enable_GPU_Clocks))
        {
            gpuFreqFile = OpenFile("/sys/class/kgsl/kgsl-3d0/gpuclk");
        }
        if(gpuFreqFile != NullFileHandle)
        {
            const int maxFreq = ReadIntFile("/sys/class/kgsl/kgsl-3d0/max_gpuclk");
            SensorSetRange(g_gpuLabel, 0, (float)maxFreq, Sensor_Interp_Nearest, Sensor_Unit_Hz);
        }

        // Setup Memory Clocks Support...
        FileHandle memFreqFile = NullFileHandle;
        //memFreqFile = OpenFile("/sys/class/devfreq/0.qcom,cpubw/cur_freq");
        if(memFreqFile != NullFileHandle)
        {
            const int maxFreq = ReadIntFile("/sys/class/devfreq/0.qcom,cpubw/max_freq");
            SensorSetRange(g_memLabel, 0, (float)maxFreq, Sensor_Interp_Nearest, Sensor_Unit_MByte_Second);
        }

        // Setup thermal sensors...
        static const unsigned int maxThermalSensors = 20;
        static ThermalSensorDesc  thermalDescs[maxThermalSensors];
        FileHandle                thermalFiles[maxThermalSensors];
        for(unsigned int i=0; i<maxThermalSensors; i++)
        {
            thermalFiles[i] = NullFileHandle;
        }
        if(CheckConnectionFlag(Enable_Thermal_Sensors))
        {
            for(unsigned int i=0; i<maxThermalSensors; i++)
            {
                ThermalSensorDesc &desc = thermalDescs[i];

                char typePath[64] = {0};
                char tempPath[64] = {0};
                char modePath[64] = {0};
                sprintf(typePath, "/sys/devices/virtual/thermal/thermal_zone%d/type", i);
                sprintf(tempPath, "/sys/devices/virtual/thermal/thermal_zone%d/temp", i);
                sprintf(modePath, "/sys/devices/virtual/thermal/thermal_zone%d/mode", i);

                // If either of these files don't exist, then we got to the end of the thermal zone list...
                if(!CheckFileExists(typePath) || !CheckFileExists(tempPath))
                    break;

                // check to see if the zone is disabled... its okay if there is no mode file...
                char mode[16] = {0};
                if(ReadFileLine(modePath, mode, sizeof(mode))>0 && !strcmp(mode, "disabled"))
                    continue;

                if(!desc.initialized)
                {
                    // Read the sensor name in...
                    ReadFileLine(typePath, desc.name, sizeof(desc.name));

                    // Initialize the Label...
                    desc.initialized = desc.label.ConditionalInit(desc.name);
                }

                // Finally... open the file.
                thermalFiles[i] = OpenFile(tempPath);
        
                if(thermalFiles[i] != NullFileHandle)
                {
                    // by default 0 to 100 degrees...
                    SensorSetRange(desc.label, 0, 100, Sensor_Interp_Linear);
                }
            }
        }

        // For clocks, we store the last value and only send updates when it changes since we
        // use blocking chart rendering.
        int lastCpuFreq[g_maxCpus] = {0};
        int lastGpuFreq            = 0;
        int lastMemValue           = 0;

        unsigned int sampleCount = 0;

        while(!QuitSignaled() && IsConnected())
        {
            // Sample CPU Frequencies...
            for(unsigned int i=0; i<g_maxCpus; i++)
            {
                // If the 'online' file can't be found, then we just assume this CPU doesn't even exist
                if(cpuOnlineFiles[i] == NullFileHandle)
                    continue;

                const CpuSensorDesc &desc   = g_cpuDescs[i];
                const bool           online = ReadIntFile(desc.onlinePath);
                if(online && cpuFreqFiles[i]==NullFileHandle)
                {
                    // Open the frequency file if we are online and its not already open...
                    cpuFreqFiles[i] = OpenFile(desc.freqPath);
                }
                else if(!online && cpuFreqFiles[i]!=NullFileHandle)
                {
                    // close the frequency file if we are no longer online
                    CloseFile(cpuFreqFiles[i]);
                    cpuFreqFiles[i] = NullFileHandle;
                }
                const int freq = cpuFreqFiles[i]==NullFileHandle ? 0 : ReadIntFile(cpuFreqFiles[i]);
                if(freq != lastCpuFreq[i])
                {
                    // Convert from KHz to Hz
                    SensorSetValue(desc.label, (float)freq);
                    lastCpuFreq[i] = freq;
                }
                ThreadYield();
            }

            // Sample GPU Frequency...
            if(gpuFreqFile != NullFileHandle)
            {
                const int freq = ReadIntFile(gpuFreqFile);
                if(freq != lastGpuFreq)
                {
                    SensorSetValue(g_gpuLabel, (float)freq);
                    lastGpuFreq = freq;
                }
            }

            // Sample Memory Bandwidth
            if(memFreqFile != NullFileHandle)
            {
                const int value = ReadIntFile(memFreqFile);
                if(value != lastMemValue)
                {
                    SensorSetValue(g_memLabel, (float)value);
                    lastMemValue = value;
                }
            }

            // Sample thermal sensors...
            if((sampleCount&15) == 0) // sample temperature at a much lower frequency as clocks... thermals don't change that fast.
            {
                for(unsigned int i=0; i<maxThermalSensors; i++)
                {
                    FileHandle file = thermalFiles[i];
                    if(file != NullFileHandle)
                    {
                        SensorSetValue(thermalDescs[i].label, (float)ReadIntFile(file));
                    }
                }
                ThreadYield();
            }

            // Sleep 5ms between samples...
            ThreadSleepMicroseconds(5000);
            sampleCount++;
        }

        // Close down cached file handles...
        for(unsigned int i=0; i<g_maxCpus; i++)
        {
            if(cpuOnlineFiles[i] != NullFileHandle) CloseFile(cpuOnlineFiles[i]);
            if(cpuFreqFiles[i]   != NullFileHandle) CloseFile(cpuFreqFiles[i]);
        }
        if(gpuFreqFile != NullFileHandle) CloseFile(gpuFreqFile);
        if(memFreqFile != NullFileHandle) CloseFile(memFreqFile);
        for(unsigned int i=0; i<maxThermalSensors; i++)
        {
            if(thermalFiles[i] != NullFileHandle) CloseFile(thermalFiles[i]);
        }
    }
	void StandardSensors::OnThreadExecute(void)
	{
		SetThreadName("CaptureSensors");

		// Pre-load what files we can... reduces open/close overhead (which is significant)

		// Setup CPU Clocks Support...
		static const UInt32 maxCpus = 8;
		static SensorLabelDesc cpuDescs[maxCpus];
		FileHandle cpuOnlineFiles[maxCpus];
		FileHandle cpuFreqFiles[maxCpus];
		for(UInt32 i=0; i<maxCpus; i++)
		{
			cpuOnlineFiles[i] = cpuFreqFiles[i] = NullFileHandle;
			SensorLabelDesc &desc = cpuDescs[i];
			FormatString(desc.name, sizeof(desc.name), "CPU%u Clocks", i);
			desc.label.ConditionalInit(desc.name);
		}
		if(CheckConnectionFlag(Enable_CPU_Zones))
		{
			int maxFreq = 0;
			for(UInt32 i=0; i<maxCpus; i++)
			{
				char onlinePath[64] = {0};
				FormatString(onlinePath, sizeof(onlinePath), "/sys/devices/system/cpu/cpu%u/online", i);
				cpuOnlineFiles[i] = OpenFile(onlinePath);
				cpuFreqFiles[i]   = NullFileHandle;
				if(cpuOnlineFiles[i] != NullFileHandle)
				{
					char maxFreqPath[64] = {0};
					FormatString(maxFreqPath, sizeof(maxFreqPath), "/sys/devices/system/cpu/cpu%u/cpufreq/cpuinfo_max_freq", i);
					maxFreq = std::max(maxFreq, ReadIntFile(maxFreqPath));
				}
			}
			for(UInt32 i=0; i<maxCpus; i++)
			{
				const SensorLabelDesc &desc = cpuDescs[i];
				SensorSetRange(desc.label, 0, (float)maxFreq, Sensor_Interp_Nearest, Sensor_Unit_KHz);
			}
		}

		// Setup GPU Clocks Support...
		FileHandle gpuFreqFile = NullFileHandle;
		if(CheckConnectionFlag(Enable_GPU_Clocks))
		{
			if(gpuFreqFile == NullFileHandle) // Adreno
			{
				gpuFreqFile = OpenFile("/sys/class/kgsl/kgsl-3d0/gpuclk");
				if(gpuFreqFile != NullFileHandle)
				{
					const int maxFreq = ReadIntFile("/sys/class/kgsl/kgsl-3d0/max_gpuclk");
					SensorSetRange(g_gpuLabel, 0, (float)maxFreq, Sensor_Interp_Nearest, Sensor_Unit_Hz);
				}
			}
			if(gpuFreqFile == NullFileHandle) // Mali
			{
				gpuFreqFile = OpenFile("/sys/devices/14ac0000.mali/clock");
				if(gpuFreqFile != NullFileHandle)
				{
					// TODO: query max GPU clocks on Mali, for now hacked to what we know the S6 is
					SensorSetRange(g_gpuLabel, 0, 0, Sensor_Interp_Nearest, Sensor_Unit_MHz);
				}
			}
		}

		// Setup Memory Clocks Support...
		FileHandle memFreqFile = NullFileHandle;
		//memFreqFile = OpenFile("/sys/class/devfreq/0.qcom,cpubw/cur_freq");
		if(memFreqFile != NullFileHandle)
		{
			const int maxFreq = ReadIntFile("/sys/class/devfreq/0.qcom,cpubw/max_freq");
			SensorSetRange(g_memLabel, 0, (float)maxFreq, Sensor_Interp_Nearest, Sensor_Unit_MByte_Second);
		}

		// Setup thermal sensors...
		static const UInt32 maxThermalSensors = 20;
		static SensorLabelDesc  thermalDescs[maxThermalSensors];
		FileHandle              thermalFiles[maxThermalSensors];
		for(UInt32 i=0; i<maxThermalSensors; i++)
		{
			thermalFiles[i] = NullFileHandle;
		}
		if(CheckConnectionFlag(Enable_Thermal_Sensors))
		{
			for(UInt32 i=0; i<maxThermalSensors; i++)
			{
				SensorLabelDesc &desc = thermalDescs[i];

				char typePath[64]      = {0};
				char tempPath[64]      = {0};
				char tripPointPath[64] = {0};
				FormatString(typePath,      sizeof(typePath),      "/sys/devices/virtual/thermal/thermal_zone%u/type", i);
				FormatString(tempPath,      sizeof(tempPath),      "/sys/devices/virtual/thermal/thermal_zone%u/temp", i);
				FormatString(tripPointPath, sizeof(tripPointPath), "/sys/devices/virtual/thermal/thermal_zone%u/trip_point_0_temp", i);

				// If either of these files don't exist, then we got to the end of the thermal zone list...
				if(!CheckFileExists(typePath) || !CheckFileExists(tempPath) || !CheckFileExists(tripPointPath))
					break;

				// Initialize the Label...
				if(ReadFileLine(typePath, desc.name, sizeof(desc.name)) <= 0)
					continue; // failed to read sensor name...
				desc.label.ConditionalInit(desc.name);

				char modePath[64] = {0};
				FormatString(modePath, sizeof(modePath), "/sys/devices/virtual/thermal/thermal_zone%d/mode", i);

				// check to see if the zone is disabled... its okay if there is no mode file...
				char mode[16] = {0};
				if(ReadFileLine(modePath, mode, sizeof(mode))>0 && !strcmp(mode, "disabled"))
					continue;

				// Finally... open the file.
				thermalFiles[i] = OpenFile(tempPath);

				// Check to see if the temperature file was found...
				if(thermalFiles[i] == NullFileHandle)
					continue;

				// Read in the critical temperature value.
				const int tripPoint = ReadIntFile(tripPointPath);
				if(tripPoint > 0)
				{
					SensorSetRange(desc.label, 0, (float)tripPoint, Sensor_Interp_Linear);
				}
			}
		}

		// For clocks, we store the last value and only send updates when it changes since we
		// use blocking chart rendering.
		int lastCpuFreq[maxCpus] = {0};
		int lastGpuFreq          = 0;
		int lastMemValue         = 0;

		UInt32 sampleCount = 0;

		while(!QuitSignaled() && IsConnected())
		{
			// Sample CPU Frequencies...
			for(UInt32 i=0; i<maxCpus; i++)
			{
				// If the 'online' file can't be found, then we just assume this CPU doesn't even exist
				if(cpuOnlineFiles[i] == NullFileHandle)
					continue;

				const SensorLabelDesc &desc   = cpuDescs[i];
				const bool             online = ReadIntFile(cpuOnlineFiles[i]) ? true : false;
				if(online && cpuFreqFiles[i]==NullFileHandle)
				{
					// Open the frequency file if we are online and its not already open...
					char freqPath[64] = {0};
					FormatString(freqPath, sizeof(freqPath), "/sys/devices/system/cpu/cpu%u/cpufreq/scaling_cur_freq", i);
					cpuFreqFiles[i] = OpenFile(freqPath);
				}
				else if(!online && cpuFreqFiles[i]!=NullFileHandle)
				{
					// close the frequency file if we are no longer online
					CloseFile(cpuFreqFiles[i]);
					cpuFreqFiles[i] = NullFileHandle;
				}
				const int freq = cpuFreqFiles[i]==NullFileHandle ? 0 : ReadIntFile(cpuFreqFiles[i]);
				if(freq != lastCpuFreq[i])
				{
					// Convert from KHz to Hz
					SensorSetValue(desc.label, (float)freq);
					lastCpuFreq[i] = freq;
				}
				ThreadYield();
			}

			// Sample GPU Frequency...
			if(gpuFreqFile != NullFileHandle)
			{
				const int freq = ReadIntFile(gpuFreqFile);
				if(freq != lastGpuFreq)
				{
					SensorSetValue(g_gpuLabel, (float)freq);
					lastGpuFreq = freq;
				}
			}

			// Sample Memory Bandwidth
			if(memFreqFile != NullFileHandle)
			{
				const int value = ReadIntFile(memFreqFile);
				if(value != lastMemValue)
				{
					SensorSetValue(g_memLabel, (float)value);
					lastMemValue = value;
				}
			}

			// Sample thermal sensors...
			if((sampleCount&15) == 0) // sample temperature at a much lower frequency as clocks... thermals don't change that fast.
			{
				for(UInt32 i=0; i<maxThermalSensors; i++)
				{
					FileHandle file = thermalFiles[i];
					if(file != NullFileHandle)
					{
						SensorSetValue(thermalDescs[i].label, (float)ReadIntFile(file));
					}
				}
				ThreadYield();
			}

			// Sleep 5ms between samples...
			ThreadSleepMicroseconds(5000);
			sampleCount++;
		}

		// Close down cached file handles...
		for(UInt32 i=0; i<maxCpus; i++)
		{
			if(cpuOnlineFiles[i] != NullFileHandle) CloseFile(cpuOnlineFiles[i]);
			if(cpuFreqFiles[i]   != NullFileHandle) CloseFile(cpuFreqFiles[i]);
		}
		if(gpuFreqFile != NullFileHandle) CloseFile(gpuFreqFile);
		if(memFreqFile != NullFileHandle) CloseFile(memFreqFile);
		for(UInt32 i=0; i<maxThermalSensors; i++)
		{
			if(thermalFiles[i] != NullFileHandle) CloseFile(thermalFiles[i]);
		}
	}