| // Copyright (c) 2011 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <unistd.h> |
| |
| #if !NV_IS_LDK |
| #include <utils/Log.h> |
| #undef LOG_TAG |
| #define LOG_TAG "TegraStats" |
| #else |
| #include <string.h> |
| #define LOGE(...) \ |
| do { \ |
| printf(__VA_ARGS__); \ |
| printf("\n"); \ |
| } while (0) |
| |
| #define LOGI(...) \ |
| do { \ |
| printf(__VA_ARGS__); \ |
| printf("\n"); \ |
| } while (0) |
| #endif |
| |
| #define MONITOR_MINIMUM_INTERVAL_MS 100 |
| |
| #define NVMAP_BASE_PATH "/sys/devices/platform/tegra-nvmap/misc/nvmap/" |
| #define CARVEOUT(x) NVMAP_BASE_PATH "heap-generic-0/" # x |
| #define IRAM(x) NVMAP_BASE_PATH "heap-iram/" # x |
| |
| #define DVFS_CLOCKS_BASE_PATH "/sys/kernel/debug/clock/" |
| #define EMCCLK DVFS_CLOCKS_BASE_PATH "emc/rate" |
| #define AVPCLK DVFS_CLOCKS_BASE_PATH "avp.sclk/rate" |
| #define VDECLK DVFS_CLOCKS_BASE_PATH "vde/rate" |
| |
| #define CPU_TEMPERATURE_PATH "/sys/class/hwmon/hwmon0/device/ext_temperature" |
| |
| #define READ_VALUE(path, pvalue) { \ |
| f = fopen(path, "r"); \ |
| if (f) { \ |
| (void) fscanf(f, "%d", pvalue); \ |
| fclose(f); \ |
| } else { \ |
| LOGE("Failed to open %s", path); \ |
| } \ |
| } |
| |
| /* Prototypes. */ |
| |
| int main(int argc, char *argv[]); |
| |
| static void logFlush(void); |
| |
| static int B2MB(int bytes); |
| static int kB2MB(int kiloBytes); |
| |
| /* Store clk values to restore later */ |
| // Assuming clk frequencies are same for both CPUs |
| unsigned int cpuclk[2]; |
| |
| /* Functions. */ |
| |
| |
| static void logFlush(void) |
| { |
| #if NV_IS_LDK |
| //need to fflush on LDK to make output redirectable |
| fflush(stdout); |
| #endif |
| } |
| |
| static int B2MB(int bytes) |
| { |
| bytes += (1<<19)-1; //rounding |
| return bytes >> 20; |
| } |
| |
| static int kB2MB(int kiloBytes) |
| { |
| kiloBytes += (1<<9)-1; //rounding |
| return kiloBytes >> 10; |
| } |
| |
| static int B2kB(int bytes) |
| { |
| bytes += (1<<9)-1; //rounding |
| return bytes >> 10; |
| } |
| |
| static int SmartB2Str(char* str, size_t size, int bytes) |
| { |
| if (bytes < 1024) |
| return snprintf(str, size, "%dB", bytes); |
| else if (bytes < 1024*1024) |
| return snprintf(str, size, "%dkB", B2kB(bytes)); |
| else |
| return snprintf(str, size, "%dMB", B2MB(bytes)); |
| } |
| |
| static void setFreq(int setMax) |
| { |
| FILE* f; |
| LOGI("setFreq %d", setMax); |
| |
| if(!cpuclk[0]) { |
| f = fopen("/sys/devices/system/cpu/cpu0/cpufreq" |
| "/scaling_available_frequencies", "r"); |
| if(f) { |
| fscanf(f, "%u", &cpuclk[0]); |
| while(fscanf(f, "%u", &cpuclk[1]) != EOF); |
| |
| LOGI("cpuclk: minfreq = %u maxfreq = %u\n", cpuclk[0], cpuclk[1]); |
| fclose(f); |
| } |
| else { |
| LOGE("Error opening file scaling_available_frequencies"); |
| } |
| } |
| if(setMax) { |
| // set CPU frequency to highest value |
| f = fopen("/sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq", "w"); |
| if(f) { |
| fprintf(f, "%u", cpuclk[1]); |
| fclose(f); |
| } |
| else { |
| LOGE("Error opening file scaling_min_freq\n"); |
| } |
| } |
| else { |
| // set default |
| f = fopen("/sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq", "w"); |
| if(f) { |
| fprintf(f, "%u", cpuclk[0]); |
| fclose(f); |
| } |
| else { |
| LOGE("Error opening file scaling_min_freq\n"); |
| } |
| } |
| } |
| |
| void printUsage() |
| { |
| LOGI( "NVIDIA TegraStats Utility" ); |
| LOGI( "" ); |
| LOGI( "Monitor CPU loading, frequency and temperature, Core DVFS data, " |
| "Memory information, etc." ); |
| LOGI( "Specify CPU scaling in full range or no scaling." ); |
| LOGI( "" ); |
| LOGI( "./tegrastats [interval] [-total_sec <sec>] [-default] [-max] " |
| "[-help]" ); |
| LOGI( " interval Specify the decimal value for polling " |
| "interval in milliseconds;" ); |
| LOGI( " the default is 1000ms if you don't set the " |
| "parameter." ); |
| LOGI( " -total_sec <sec> Specify the decimal value for total " |
| "duration in seconds;" ); |
| LOGI( " the default is infinity." ); |
| LOGI( " -default Specify scaling in full range." ); |
| LOGI( " -max Specify no scaling." ); |
| LOGI( " -help Show this usage." ); |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| int i; |
| unsigned int sleepMS = 1000, totalSec = 0, loopCount = 0; |
| int isCpu0Active, isCpu1Active, hasCPUTemp = 0; |
| int cpuLoadPrev[3*10], cpuLoad = 0, cpu0Load = 0, cpu1Load = 0; |
| int debug = 0; |
| |
| memset(cpuLoadPrev, 0, 3*10*sizeof(int)); |
| |
| for ( i=1; i<argc; i++ ) |
| { |
| char * endptr; |
| unsigned int tmp; |
| |
| if ( argv[i][0] == '-' ) |
| { |
| if ( !strcmp( argv[i], "-total_sec" ) ) |
| { |
| i++; |
| if ( i < argc ) |
| { |
| tmp = strtoul( argv[i], &endptr, 0 ); |
| if ( !strcmp( endptr, "" ) ) |
| { |
| if ( tmp ) |
| { |
| totalSec = tmp; |
| continue; |
| } |
| } |
| } |
| |
| LOGE( "Error: Bad test time parameter" ); |
| return 0; |
| } |
| else if ( !strcmp( argv[i], "-default" ) ) |
| { |
| setFreq( 0 ); |
| return 0; |
| } |
| else if ( !strcmp( argv[i], "-max" ) ) |
| { |
| setFreq( 1 ); |
| LOGI( "Set all components to max frequency" ); |
| return 0; |
| } |
| else if ( !strcmp( argv[i], "-help" ) ) |
| { |
| printUsage(); |
| return 0; |
| } |
| else if ( !strcmp( argv[i], "-debug" ) ) |
| { |
| debug = 1; |
| } |
| else |
| { |
| LOGE( "Error: Invalid parameter" ); |
| return 0; |
| } |
| } |
| else |
| { |
| tmp = strtoul( argv[i], &endptr, 0 ); |
| if ( !strcmp( endptr, "" ) ) |
| { |
| if ( tmp >= MONITOR_MINIMUM_INTERVAL_MS ) |
| sleepMS = tmp; |
| else |
| sleepMS = MONITOR_MINIMUM_INTERVAL_MS; |
| |
| continue; |
| } |
| |
| LOGE( "Bad interval parameter" ); |
| return 0; |
| } |
| } |
| |
| // If the input sleepMS is more than total_sec, it is invalid parameter |
| if ( totalSec && ( sleepMS > ( totalSec * 1000 ) ) ) |
| { |
| LOGE( "Error: The interval can't be more than total duration" ); |
| return 0; |
| } |
| |
| loopCount = ( totalSec * 1000 ) / sleepMS; |
| while ( 1 ) |
| { |
| int totalRAMkB = -1, freeRAMkB = -1, largestFreeRAMBlockB = -1; |
| int numLargestRAMBlock = -1, buffersRAMkB = -1, cachedRAMkB = -1; |
| int totalCarveoutB = -1, freeCarveoutB = -1; |
| int largestFreeCarveoutBlockB = -1, totalGARTkB = -1, freeGARTkB = -1; |
| int largestFreeGARTBlockkB = -1, totalIRAMB = -1, freeIRAMB = -1; |
| int largestFreeIRAMBlockB = -1, currCpuFreq = -1; |
| int emcClk = -1, avpClk = -1, vdeClk = -1; |
| float currCpuTemp = 0.0; |
| |
| if ( totalSec ) |
| loopCount--; |
| |
| // RAM |
| FILE* f = fopen("/proc/meminfo", "r"); |
| if(f) |
| { |
| // add if (blah) {} to get around compiler warning |
| if (fscanf(f, "MemTotal: %d kB\n", &totalRAMkB)) {} |
| if (fscanf(f, "MemFree: %d kB\n", &freeRAMkB)) {} |
| if (fscanf(f, "Buffers: %d kB\n", &buffersRAMkB)) {} |
| if (fscanf(f, "Cached: %d kB\n", &cachedRAMkB)) {} |
| fclose(f); |
| } |
| else |
| LOGE("Failed to open /proc/meminfo"); |
| f = fopen("/proc/buddyinfo", "r"); |
| if(f) |
| { |
| #define NUM_SLOTS 11 |
| #define PAGE_SIZE 4096 |
| |
| char line[256]; |
| int lineNum = 0; |
| int slots[NUM_SLOTS]; |
| int i; |
| |
| // Get the number of free blocks for each size. |
| // Separation into nodes and zones is not kept. |
| while (fgets(line, sizeof(line), f)) |
| { |
| int j = 0; |
| int n; |
| int tmpSlots[NUM_SLOTS]; |
| char* buf = line; |
| |
| if (sscanf(buf, "Node %*d, zone %*s%n", &n)) {} |
| buf += n; |
| |
| while (sscanf(buf, "%d%n", &tmpSlots[j], &n) == 1) |
| { |
| buf += n; |
| slots[j] = lineNum ? slots[j] + tmpSlots[j] : tmpSlots[j]; |
| j++; |
| } |
| |
| lineNum++; |
| } |
| |
| fclose(f); |
| |
| // Extract info about the largest available blocks |
| i = NUM_SLOTS - 1; |
| while (slots[i] == 0 && i > 0) |
| i--; |
| numLargestRAMBlock = slots[i]; |
| largestFreeRAMBlockB = (1 << i) * PAGE_SIZE; |
| } |
| else |
| LOGE("Failed to open /proc/buddyinfo"); |
| |
| // CPU 0/1 On/Off |
| f = fopen("/sys/devices/system/cpu/cpu0/online", "r"); |
| if(f) |
| { |
| // add if (blah) {} to get around compiler warning |
| if (fscanf(f, "%d", &isCpu0Active)) {} |
| fclose(f); |
| } |
| else |
| LOGE("Failed to open /proc/meminfo"); |
| |
| f = fopen("/sys/devices/system/cpu/cpu1/online", "r"); |
| if(f) |
| { |
| // add if (blah) {} to get around compiler warning |
| if (fscanf(f, "%d", &isCpu1Active)) {} |
| fclose(f); |
| } |
| else |
| LOGE("Failed to open /proc/meminfo"); |
| |
| // CPU load |
| f = fopen("/proc/stat", "r"); |
| if(f) |
| { |
| int c[30], l[30],i; |
| |
| //from http://www.mjmwired.net/kernel/Documentation/filesystems/proc.txt |
| //Various pieces of information about kernel activity are available in the |
| // /proc/stat file. All of the numbers reported in this file are aggregates |
| //since the system first booted. |
| //The very first "cpu" line aggregates the numbers in all of the other "cpuN" |
| //lines. These numbers identify the amount of time the CPU has spent performing |
| //different kinds of work. Time units are in USER_HZ (typically hundredths of a |
| //second). The meanings of the columns are as follows, from left to right: |
| //- user: normal processes executing in user mode |
| //- nice: niced processes executing in user mode |
| //- system: processes executing in kernel mode |
| //- idle: twiddling thumbs |
| //- iowait: waiting for I/O to complete |
| //- irq: servicing interrupts |
| //- softirq: servicing softirqs |
| //- steal: involuntary wait |
| //- guest: running a normal guest |
| //- guest_nice: running a niced guest |
| |
| // add if (blah) {} to get around compiler warning |
| if (fscanf(f, "cpu %d %d %d %d %d %d %d %d %d %d\n", |
| c+0, c+1, c+2, c+3, c+4, c+5, c+6, c+7, c+8, c+9)) {} |
| if (fscanf(f, "cpu0 %d %d %d %d %d %d %d %d %d %d\n", |
| c+10, c+11, c+12, c+13, c+14, c+15, c+16, c+17, c+18, c+19)) {} |
| if(isCpu1Active) { |
| if (fscanf(f, "cpu1 %d %d %d %d %d %d %d %d %d %d\n", |
| c+20, c+21, c+22, c+23, c+24, c+25, c+26, c+27, c+28, c+29)) {} |
| } else |
| memset(c+20,0,10*sizeof(int)); |
| fclose(f); |
| |
| // cpu load = (time spent on something else but idle since the |
| // last update) / (total time spent since the last update) |
| cpuLoad = 0; |
| cpu0Load = 0; |
| cpu1Load = 0; |
| for(i=0;i<30;i++) |
| { |
| l[i] = c[i] - cpuLoadPrev[i]; |
| if(i<10) |
| cpuLoad += l[i]; |
| else if(i<20) |
| cpu0Load += l[i]; |
| else |
| cpu1Load += l[i]; |
| cpuLoadPrev[i] = c[i]; |
| } |
| /*if(debug) |
| LOGE("total0 %d idle0 %d | total1 %d idle1 %d", |
| cpu0Load, l[3+9], cpu1Load, l[3+18]);*/ |
| if(cpuLoad) |
| cpuLoad = 100*(cpuLoad-l[3])/cpuLoad; |
| else |
| cpuLoad = 0; |
| if(cpu0Load) |
| cpu0Load = 100*(cpu0Load-l[3+10])/cpu0Load; |
| else |
| cpu0Load = 0; |
| if(cpu1Load) |
| cpu1Load = 100*(cpu1Load-l[3+20])/cpu1Load; |
| else |
| cpu1Load = 0; |
| } |
| else |
| LOGE("Failed to open /proc/stat"); |
| |
| // Carveout |
| READ_VALUE(CARVEOUT(total_size), &totalCarveoutB); |
| READ_VALUE(CARVEOUT(free_size), &freeCarveoutB); |
| READ_VALUE(CARVEOUT(free_max), &largestFreeCarveoutBlockB); |
| |
| // GART |
| f = fopen("/proc/iovmminfo", "r"); |
| if(f) |
| { |
| // add if (blah) {} to get around compiler warning |
| if (fscanf(f, "\ngroups\n\t<unnamed> (device: iovmm-gart)" |
| "\n\t\tsize: %dKiB free: %dKiB largest: %dKiB", |
| &totalGARTkB, &freeGARTkB, &largestFreeGARTBlockkB)) {} |
| fclose(f); |
| } |
| else |
| LOGE("Failed to open /proc/iovmminfo"); |
| |
| // IRAM |
| READ_VALUE(IRAM(total_size), &totalIRAMB); |
| READ_VALUE(IRAM(free_size), &freeIRAMB); |
| READ_VALUE(IRAM(free_max), &largestFreeIRAMBlockB); |
| |
| // CPU Frequency |
| f = fopen("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_cur_freq", "r"); |
| if(f) |
| { |
| (void) fscanf(f, "%d", &currCpuFreq); |
| fclose(f); |
| } |
| |
| // CPU Temperature |
| f = fopen( CPU_TEMPERATURE_PATH, "r"); |
| if ( f ) |
| { |
| hasCPUTemp = 1; |
| (void) fscanf( f, "%f", &currCpuTemp ); |
| fclose( f ); |
| } |
| |
| // DFS |
| READ_VALUE(EMCCLK, &emcClk); |
| READ_VALUE(AVPCLK, &avpClk); |
| READ_VALUE(VDECLK, &vdeClk); |
| { |
| char cpu0String[5], cpu1String[5]; |
| char lfbRAM[10], lfbCarveout[10], lfbGART[10], lfbIRAM[10]; |
| if(isCpu0Active) |
| snprintf(cpu0String, 5, "%d%%", cpu0Load); |
| else |
| snprintf(cpu0String, 5, "off"); |
| if(isCpu1Active) |
| snprintf(cpu1String, 5, "%d%%", cpu1Load); |
| else |
| snprintf(cpu1String, 5, "off"); |
| SmartB2Str(lfbRAM, 10, largestFreeRAMBlockB); |
| SmartB2Str(lfbCarveout, 10, largestFreeCarveoutBlockB); |
| SmartB2Str(lfbGART, 10, largestFreeGARTBlockkB * 1024); |
| SmartB2Str(lfbIRAM, 10, largestFreeIRAMBlockB); |
| |
| if ( hasCPUTemp ) |
| { |
| LOGE( "RAM %d/%dMB (lfb %dx%s) Carveout %d/%dMB (lfb %s) " |
| "GART %d/%dMB (lfb %s) IRAM %d/%dkB (lfb %s) " |
| "cpu [%s,%s]@%d (%.2fC) EMC %d AVP %d VDE %d", |
| kB2MB( totalRAMkB - freeRAMkB - buffersRAMkB - cachedRAMkB), |
| kB2MB( totalRAMkB ), |
| numLargestRAMBlock, |
| lfbRAM, |
| B2MB( totalCarveoutB - freeCarveoutB ), |
| B2MB( totalCarveoutB ), |
| lfbCarveout, |
| kB2MB( totalGARTkB - freeGARTkB ), |
| kB2MB( totalGARTkB ), |
| lfbGART, |
| B2kB( totalIRAMB - freeIRAMB ), |
| B2kB( totalIRAMB ), |
| lfbIRAM, |
| cpu0String, cpu1String, |
| currCpuFreq, currCpuTemp, |
| emcClk, avpClk, vdeClk ); |
| } |
| else |
| { |
| LOGE( "RAM %d/%dMB (lfb %dx%s) Carveout %d/%dMB (lfb %s) " |
| "GART %d/%dMB (lfb %s) IRAM %d/%dkB (lfb %s) " |
| "cpu [%s,%s]@%d EMC %d AVP %d VDE %d", |
| kB2MB( totalRAMkB - freeRAMkB - buffersRAMkB - cachedRAMkB), |
| kB2MB( totalRAMkB ), |
| numLargestRAMBlock, |
| lfbRAM, |
| B2MB( totalCarveoutB - freeCarveoutB ), |
| B2MB( totalCarveoutB ), |
| lfbCarveout, |
| kB2MB( totalGARTkB - freeGARTkB ), |
| kB2MB( totalGARTkB ), |
| lfbGART, |
| B2kB( totalIRAMB - freeIRAMB ), |
| B2kB( totalIRAMB ), |
| lfbIRAM, |
| cpu0String, cpu1String, |
| currCpuFreq, |
| emcClk, avpClk, vdeClk ); |
| } |
| } |
| |
| // fflush stdout (on LDK) to make the output redirectable. |
| logFlush(); |
| |
| // Break if the totalSec is not infinity and loopCount is zero |
| if ( !loopCount && totalSec ) |
| break; |
| |
| usleep( sleepMS * 1000 ); |
| } |
| |
| return 0; |
| } |
| |
| /* example contents of /proc/meminfo |
| MemTotal: 450164 kB |
| MemFree: 269628 kB |
| Buffers: 2320 kB |
| Cached: 69008 kB |
| SwapCached: 0 kB |
| Active: 89476 kB |
| Inactive: 63612 kB |
| Active(anon): 82272 kB |
| Inactive(anon): 0 kB |
| Active(file): 7204 kB |
| Inactive(file): 63612 kB |
| Unevictable: 0 kB |
| Mlocked: 0 kB |
| SwapTotal: 0 kB |
| SwapFree: 0 kB |
| Dirty: 0 kB |
| Writeback: 0 kB |
| AnonPages: 81764 kB |
| Mapped: 35148 kB |
| Slab: 5204 kB |
| SReclaimable: 1760 kB |
| SUnreclaim: 3444 kB |
| PageTables: 4316 kB |
| NFS_Unstable: 0 kB |
| Bounce: 0 kB |
| WritebackTmp: 0 kB |
| CommitLimit: 225080 kB |
| Committed_AS: 1054316 kB |
| VmallocTotal: 450560 kB |
| VmallocUsed: 45964 kB |
| VmallocChunk: 340056 kB |
| |
| http://www.linuxweblog.com/meminfo |
| * MemTotal: Total usable ram (i.e. physical ram minus a few reserved bits |
| * and the kernel binary code) |
| * MemFree: Is sum of LowFree+HighFree (overall * stat) |
| * MemShared: 0 is here for compat reasons but always zero. |
| * Buffers: * Memory in buffer cache. mostly useless as metric nowadays |
| * Cached: Memory in the pagecache (diskcache) minus SwapCache |
| * SwapCache: Memory that once was swapped out, is swapped back in but |
| * still also is in the swapfile (if memory is needed it doesn't need to |
| * be swapped out AGAIN because it is already in the swapfile. This saves |
| * I/O) |
| |
| VM splits the cache pages into "active" and "inactive" memory. The idea is that |
| if you need memory and some cache needs to be sacrificed for that, you take it |
| from inactive since that's expected to be not used. The vm checks what is used |
| on a regular basis and moves stuff around. When you use memory, the CPU sets a |
| bit in the pagetable and the VM checks that bit occasionally, and based on |
| that, it can move pages back to active. And within active there's an order of |
| "longest ago not used" (roughly, it's a little more complex in reality). |
| |
| * Active: Memory that has been used more recently and usually not reclaimed |
| * unless absolutely necessary. |
| * Inact_dirty: Dirty means "might need writing to disk or swap." Takes more |
| * work to free. Examples might be files that have not been written to yet. |
| * They aren't written to memory too soon in order to keep the I/O down. For |
| * instance, if you're writing logs, it might be better to wait until you |
| * have a complete log ready before sending it to disk. |
| * Inact_clean: Assumed to be easily freeable. The kernel will try to keep |
| * some clean stuff around always to have a bit of breathing room. |
| * Inact_target: Just a goal metric the kernel uses for making sure there are |
| * enough inactive pages around. When exceeded, the kernel will not do work |
| * to move pages from active to inactive. A page can also get inactive in a |
| * few other ways, e.g. if you do a long sequential I/O, the kernel assumes |
| * you're not going to use that memory and makes it inactive preventively. So |
| * you can get more inactive pages than the target because the kernel marks |
| * some cache as "more likely to be never used" and lets it cheat in the |
| * "last used" order. |
| * HighTotal: is the total amount of memory in the high region. Highmem is |
| * all memory above (approx) 860MB of physical RAM. Kernel uses indirect |
| * tricks to access the high memory region. Data cache can go in this memory |
| * region. |
| * LowTotal: The total amount of non-highmem memory. |
| * LowFree: The amount of free memory of the low memory region. This is the |
| * memory the kernel can address directly. All kernel datastructures need to |
| * go into low memory. |
| * SwapTotal: Total amount of physical swap memory. |
| * SwapFree: Total amount of swap memory free. |
| * Committed_AS: An estimate of how much RAM you would need to make a 99.99% |
| * guarantee that there never is OOM (out of memory) for this workload. |
| * Normally the kernel will overcommit memory. The Committed_AS is a |
| * guesstimate of how much RAM/swap you would need worst-case. |
| */ |
| |