#!/usr/bin/env perl use strict; #use warnings; use Getopt::Long qw(:config no_ignore_case); use Data::Dumper; # # Code released under GPLv3: http://www.gnu.org/licenses/gpl.html # v0.3 - Adrian Popa (2016.03.11) # #we get our data mainly from /sys/devices/system/cpu my %options = (); my $cls = "\033[2J\033[0;0H"; #fancy ANSI escape string GetOptions(\%options, 'help|h', 'quiet|q', 'list|l', 'set|s', 'governor|g:s', 'cpu|c=s', 'min|m:s', 'max|M:s', 'frequency|f', 'interactive|i=f', 'temperature|t'); #print Dumper(\%options); if(defined $options{'help'}){ usage(); exit(0); } if(defined $options{'set'}){ #interpret the parameters as a set command #user must be root die "The script must be run as root in order to apply changes!" if($> != 0); #see on which cpus you need to set things my @cpu = getCPUs(); my %before; if(! exists $options{'quiet'}){ %before = getListing(@cpu); } #set the data foreach my $core (@cpu){ if(defined $options{'governor'}){ open FILE, ">/sys/devices/system/cpu/cpu${core}/cpufreq/scaling_governor" or die "$!"; print FILE $options{'governor'}; close FILE; } if(defined $options{'min'}){ open FILE, ">/sys/devices/system/cpu/cpu${core}/cpufreq/scaling_min_freq" or die "$!"; my $number = $options{'min'}; if($options{'min'}=~/([0-9\.]+)(M|G)?/){ $number = $1; my $scale = $2; $number = $number * 1000 if($scale eq 'M'); $number = $number * 1000_000 if($scale eq 'G'); } print FILE $number; close FILE; } if(exists $options{'max'}){ open FILE, ">/sys/devices/system/cpu/cpu${core}/cpufreq/scaling_max_freq" or die "$!"; my $number = $options{'max'}; if($options{'max'}=~/([0-9\.]+)(M|G)?/){ $number = $1; my $scale = $2; #print "DBG: $options{'max'}\n"; $number = $number * 1000 if(defined $scale && $scale eq 'M'); $number = $number * 1000_000 if(defined $scale && $scale eq 'G'); } print FILE $number; close FILE; } if(exists $options{'frequency'}){ #can't set current frequency } } #display results (if any) if(! exists $options{'quiet'}){ my %after = getListing(@cpu); # print "DBG: ".Dumper(\%after); foreach my $core (@cpu){ if(defined $after{$core}{'governor'}){ print "CPU$core: governor $before{$core}{'governor'} -> $after{$core}{'governor'}\n"; } if(defined $after{$core}{'scale_min'}){ print "CPU$core: min ".humanReadable($before{$core}{'scale_min'})." [".humanReadable($before{$core}{'cpuinfo_min'}) ."] -> ".humanReadable($after{$core}{'scale_min'})." [".humanReadable($after{$core}{'cpuinfo_min'}) ."]\n"; } if(defined $after{$core}{'scale_max'}){ print "CPU$core: max ".humanReadable($before{$core}{'scale_max'})." [".humanReadable($before{$core}{'cpuinfo_max'}) ."] -> ".humanReadable($after{$core}{'scale_max'})." [".humanReadable($after{$core}{'cpuinfo_max'}) ."]\n"; } } } } elsif(defined $options{'list'}){ #select all things to display if none is specified if(! exists $options{'governor'} && ! exists $options{'min'} && ! exists $options{'max'} && ! exists $options{'frequency'} && ! exists $options{'temperature'}){ $options{'governor'} = undef; $options{'min'} = undef; $options{'max'} = undef; $options{'frequency'} = undef; $options{'temperature'} = undef; } #start an infinte loop in case we're in interactive mode while(1){ my @cpu = getCPUs(); #make output buffered $|=0; if(defined $options{'interactive'}){ print $cls; print "Type CTRL+C to exit.\n"; } my $temp = 0; if(defined $options{'temperature'}){ $temp = getTemperature(); } #print Dumper(\%results); if(exists $options{'governor'} || exists $options{'frequency'} || exists $options{'min'} || exists $options{'max'}){ my %results = getListing(@cpu); #display the data -- redundant for, but we might expand/decouple the display later foreach my $core (@cpu){ my $output.= "CPU$core: "; $output.="governor $results{$core}{'governor'}\t" if (exists $results{$core}{'governor'}); $output.="current ".humanReadable($results{$core}{'cur'})."\t" if (exists $results{$core}{'cur'}); $output.="min ".humanReadable($results{$core}{'scale_min'})." [".humanReadable($results{$core}{'cpuinfo_min'}) ."]\t" if (exists $results{$core}{'scale_min'}); $output.="max ".humanReadable($results{$core}{'scale_max'})." [".humanReadable($results{$core}{'cpuinfo_max'}) ."] \t" if (exists $results{$core}{'scale_max'}); print $output."\n"; } } if(defined $options{'temperature'}){ print "TEMP: ${temp}C\n"; } if(defined $options{'interactive'}){ #sleep($options{'interactive'}); #sleep fractional seconds without the need of Time::HiRes select(undef, undef, undef, $options{'interactive'}); } else{ last; #break the infinite while } $|=1; } } else{ usage(); exit(1); } sub humanReadable{ my $value = shift; my %scaleMapping = ( '3' => 'MHz', '6' => 'GHz', '9' => 'THz' ); my $result = $value; my $scale = 0; my $break = 0; while($result >= 1000){ $result = $result/1000; $scale+=3; } return sprintf("%.2f", $result) . $scaleMapping{$scale}; } sub usage{ my $governors = getData("/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors"); print "Usage: $0 [options] Options: -l, --list\t\t\t List a parameter -s, --set\t\t\t Set a parameter -g, --governor \t Select a governor \t\t\t\t ($governors) -c, --cpu \t The CPU to edit/query. Leave blank for all CPUs. Valid syntax: \t\t\t\t 0; 0,4; 0,4,5-7; 0-7 -m, --min \t\t The minimum CPU frequency (must be supported by governor and CPU) -M, --max \t\t The maximum CPU frequency (must be supported by governor and CPU) -f, --frequency\t\t The current CPU frequency -t, --temperature\t\t The current CPU temperature -q, --quiet\t\t\t Don't display much output when setting a parameter -i, --interactive \t Keep running the list command every number seconds -h, --help\t\t\t Show this help screen Examples: $0 -l\t\t\t\tLists current frequency/governor for all cores $0 -l -g -f\t\t\t\tLists just governor and current frequency for all cores $0 -l -c 0,4\t\t\t\tLists all information only for cores 0 and 4 $0 -l -c 0-4\t\t\t\tLists all information for cores 0 to 4 $0 -s -g powersave -m 300M -M 600M\tSets governor, minimum and maximum frequency for all cores $0 -s -M 1.2G -c 0,4\t\t\tSets maximum frequency on cores 0 and 4 " } sub getCPUs{ my @cpu = (); #see if the user specified a CPU if(defined $options{'cpu'}){ #parse the CPU option. Could be a number or a sequence # -c 1 # -c 1,4 # -c 1,4,5-8 my @commas = split(/,/, $options{'cpu'}); foreach my $comma (@commas){ #see if there are any ranges if($comma=~/([0-9]+)-([0-9]+)/){ my $start = $1; my $stop = $2; for($start..$stop){ push @cpu, $_; } } else{ push @cpu, $comma; } } #push @cpu, $options{'cpu'}; } else{ #get all cpus from the system my $content = getData("/sys/devices/system/cpu/online"); if($content=~/([0-9]+)(?:-([0-9]+))?/){ my $minCPU = $1; my $maxCPU = $minCPU; $maxCPU = $2 if(defined $2); for($minCPU..$maxCPU){ push @cpu, $_; } } else{ die "Unable to get the list of CPUs"; } } return @cpu; } sub getData{ my $filename = shift; my $line = 'N/A'; if(open FILE, "$filename"){ $line = ; $line=~s/\r|\n//g; close FILE; } return $line; } sub getTemperature{ my $temperatureFile = '/sys/devices/virtual/thermal/thermal_zone0/temp'; my $temp = 0; if(-f "$temperatureFile"){ $temp = getData($temperatureFile); $temp = $temp/1000; } return $temp; } sub getListing{ my %results; my @cpu = @_; #get the data foreach my $core (@cpu){ if(exists $options{'governor'}){ $results{$core}{'governor'} = getData("/sys/devices/system/cpu/cpu${core}/cpufreq/scaling_governor"); } if(exists $options{'min'}){ $results{$core}{'scale_min'} = getData("/sys/devices/system/cpu/cpu${core}/cpufreq/scaling_min_freq"); $results{$core}{'cpuinfo_min'} = getData("/sys/devices/system/cpu/cpu${core}/cpufreq/cpuinfo_min_freq"); } if(exists $options{'max'}){ $results{$core}{'scale_max'} = getData("/sys/devices/system/cpu/cpu${core}/cpufreq/scaling_max_freq"); $results{$core}{'cpuinfo_max'} = getData("/sys/devices/system/cpu/cpu${core}/cpufreq/cpuinfo_max_freq"); } if(exists $options{'frequency'}){ $results{$core}{'cur'} = getData("/sys/devices/system/cpu/cpu${core}/cpufreq/scaling_cur_freq"); } } return %results; }