3

Task:

stdout the load avg from top in a decimal form like (e.g 0.23)

Details:

I need to parse this script to Chef's inspec and check if its result is bigger/smaller than something, for example:

describe command("top -b -n  1  | awk  '/load average/ { sub(/,/,\".\",$10); printf \"%f\n\",$10}'") do
  its("stdout") { should eq 0.00 }    
end

This example returns ""

But now when I think about it, I could compare with a file in /proc/loadavg

Progress

Used this resource: Grab the load average with top

With this command, I get a good representation of the output, but it's a string and I can't do any mathematical operations with it:

martin@martinv-pc:~$ top -b -n  1  | awk '/load average/ { printf "%s\n", $10}'
0,63,

But when I try to change the printf to decimal/float, I get an error:

martin@martinv-pc:~$ top -b -n  1  | awk '/load average/ { printf "%f\n", $10}'
0.000000
martin@martinv-pc:~$ top -b -n  1  | awk '/load average/ { printf "%d\n", $10}'
0

Can't echo, tried with cut, bad idea -- not working:

martin@martinv-pc:~$ top -b -n  1  | awk '/load average/ { printf "%s\n", $10}'|cut -c1-4
0,15
martin@martinv-pc:~$ top -b -n  1  | awk '/load average/ { printf "%s\n", $10}'|$((cut -c1-4))
-4: command not found

Another attempt:

martin@martinv-pc:~$ top -b -n  1  | awk '/load average/ BEGIN { printf "%.f\n", $10};'
awk: line 1: syntax error at or near BEGIN

Question:

How can I convert the string value to decimal/float/integer ?

ps -o user,rss output:

[vagrant@localhost ~]$ ps -o user,rss
USER       RSS
vagrant    736
vagrant   1080
  • I don't think you can rely on the load average always being in field #10 - it will vary depending on the machine's uptime (since the uptime string will go from H:M to something like d days, H:M) – steeldriver Jul 25 '18 at 10:54
  • Which load average, the 1 minute, 5 minute or 15 minute average? Why are you parsing the output of top? Wouldn't uptime be better, or if your system supports it, one of the sysctl variables (sysctl vm.loadavg on OpenBSD). – Kusalananda Jul 25 '18 at 11:01
  • I need all the all the load averages for 1,5,15 mins – murloc Jul 25 '18 at 11:07
  • @steeldriver What do you suggest then ? – murloc Jul 25 '18 at 11:21
  • OK, so what values do you need? You seem to be comparing its("stdout") expecting a single value. Which one? And you really don't need %f if you remove the comma. But if you show us what output you want, we can give it to you. – terdon Jul 25 '18 at 11:40
1

Task:

sdout the load avg from top in a decimal form like(e.g 0.23)

Solution:

top -b -n  1  | perl -lane 'print "$1.$2" if /load average: (\d+)[,.](\d+)/'

Notes:

This retrieves the 1m load average. It looks like this is what you want, but you should state it clearly. If needed, the code can be easily modified to retrieve the load averages over 5 minutes or 15 minutes, or even all three.

As pointed out by @terdon, uptime might be a better starting point than top in this case.

After the first two lines, you obscurely describe what you want to do with the result. Subsequent steps you want to take should be the subject of new questions.

In Perl, numbers are auto-casted to strings and vice-versa. Any numerical operation can be performed on a string representing a number. e.g. print "$1.$2"+11.11


Question 2:

This part is about the second question, which is totally unrelated to the first one.
I urge the OP to post this question separately.

How Can I convert the string value to decimal/float/integer ?

Better written as: Performing numeric comparisons on strings with Chef's InSpec.

Solution:

Convert the string to a numeric format, with either to_i or to_f.

Example:

describe command("echo 1.00") do
    its("stdout.to_f") { should be < 1.03 }
end

Explanation:

Very reasonably, stdout is treated as a string. Also very reasonably, numeric comparisons require the two numbers to be...numbers. Luckily, conversion can be done with the handy Ruby string methods: to_i, to_f, to_r and to_c.

  • The problem is that it doesn't go to Perl by rather Ruby and inspec, the EDIT I've maid is crucial and it's the issue I am experiencing. I need all the load avg scores { 1, 5, 15} if I have one of them, the rest are clear. As you've said the problem is in the subsequent question, meaning that I've asked a bad question – murloc Jul 26 '18 at 7:09
  • @murloc Actually, you posed a perfectly valid question and it has been answered: therefore, I think you should accept one of the proposed solutions that satisfy the requirements. Then, you are invited to ask a new question about the next step, providing all the needed details. – simlev Jul 26 '18 at 7:58
  • Yeah it works, thank you but how can I do this for the other variables ( for 1 and 5 mins) – murloc Jul 27 '18 at 9:16
  • @murloc You could create three InSpec controls, one for each of the three values, with a regex similar to (\d+)[,.](\d+)\D+(\d+)[,.](\d+)\D+(\d+)[,.](\d+). If you need help with regular expressions and capturing groups, that would be a third question. – simlev Jul 27 '18 at 10:12
2

From gawk: Summing floats formatted with comma separator :

The answer is to use the --use-lc-numeric gawk option.

--use-lc-numeric

This forces gawk to use the locale's decimal point character when parsing input data. Although the POSIX standard requires this behavior, and gawk does so when --posix is in effect, the default is to follow traditional behavior and use a period as the decimal point, even in locales where the period is not the decimal point character. This option overrides the default behavior, without the full draconian strictness of the --posix option.

In your case, this command should work:

top -b -n 1 | awk --use-lc-numeric '/load average/ { printf "%f\n", $10}'

  • Doesn't seem to work, the problem is that I am parsing this command to chef's inspec and it's returning "" – murloc Jul 25 '18 at 11:17
  • So if you need the output using dot instead of colon use the option 1 of @terdon answer – Leo Jul 25 '18 at 11:21
  • maybe I should try another approach – murloc Jul 25 '18 at 11:22
  • @murloc you might want to explain what is doing the "arithmetical operations". Is it awk itself? Some other program? The printf shouldn't be relevant in most cases if the problem is the comma. – terdon Jul 25 '18 at 11:22
  • I will do an edit explaining – murloc Jul 25 '18 at 11:23
2

You are presumably using a locale that has , as the decimal separator. You can try any of these approaches:

  1. Use the C locale for top:

    LC_ALL=C top -b -n  1  | awk  '/load average/ { printf "%f\n",$10}'
    

    That not only addresses the period versus comma issue but would also avoid problems with text like load average translated into the user's language.

  2. Replace the comma with a dot:

    top -b -n  1  | awk  '/load average/ { sub(/,/,".",$10); printf "%f\n",$10}'
    
  3. With GNU awk, use the --use-lc-numeric flag suggested by @Leo or use POSIXLY_CORRECT=1 awk.

  4. Or use a POSIX compliant awk implementation like the One True awk which should parse and print numbers according to locale rules by default.

Note that a more portable command to get the load average is uptime.

  • I've made an edit explaining my problem, these are all working but not for my case – murloc Jul 25 '18 at 11:27
  • I think I will try to make something with this uptime function, should be easier – murloc Jul 26 '18 at 7:06
1

Your problem is that the field extracted is using the comma as the decimal separator and awk expects floats to use a dot.

This reproduces your problem:

$ LC_ALL=de_DE top -bn 1 | awk 'NR==1'
top - 08:37:07 up 1 day, 10:22,  5 users,  load average: 0,17, 0,24, 0,26

As you can see, the numbers use a comma as decimal separator. You need something similar to:

$ LC_ALL=en_US top -bn 1 | awk 'NR==1'
top - 08:38:28 up 1 day, 10:23,  5 users,  load average: 0.56, 0.34, 0.30

But that will depend of which locales have been installed (check with locale -a) on your system. A C locale is always available.

$ LC_ALL=C top -bn 1 | awk 'NR==1'
top - 08:40:35 up 1 day, 10:25,  5 users,  load average: 0.50, 0.39, 0.31

But using top to only extract the first line is overkill. Better use uptime:

$ LC_ALL=C uptime
08:42:08 up 1 day, 10:27,  5 users,  load average: 0.35, 0.37, 0.31

But even better, read the /proc/loadavg file

$ cat /proc/loadavg 
0.29 0.34 0.30 1/468 15084

Which, by the way, is not affected by the locale. The first number is the 1 minute load average, just select it and print in any format:

$ awk '{printf( "%s\n %f\n %d\n", $1, $1, $1 )}' /proc/loadavg
0.35
0.350000
0

For the 1, 5 and 15 minutes load average use:

$ awk '{printf( "%7.3f %7.3f %5.2f\n", $1, $2, $3 )}' /proc/loadavg
 0.150   0.340  0.33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.