########################################################################
##
## Copyright (C) 2004-2020 The Octave Project Developers
##
## See the file COPYRIGHT.md in the top-level directory of this
## distribution or <https://octave.org/copyright/>.
##
## This file is part of Octave.
##
## Octave is free software: you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License, or
## (at your option) any later version.
##
## Octave is distributed in the hope that it will be useful, but
## WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with Octave; see the file COPYING.  If not, see
## <https://www.gnu.org/licenses/>.
##
########################################################################

## -*- texinfo -*-
## @deftypefn  {} {} memory ()
## @deftypefnx {} {@var{userinfo} =} memory ()
## @deftypefnx {} {[@var{userinfo}, @var{systeminfo}] =} memory ()
##
## @deftypefnx {} {} memory ("linux")
## @deftypefnx {} {@var{procstatus} =} memory ("linux")
## @deftypefnx {} {[@var{procstatus}, @var{meminfo}] =} memory ("linux")
##
## Display or return information about memory usage by Octave.
##
## @example
## @group
## memory ()
##    @result{} System    RAM: 3934008 KiB,  swap: 4087804 KiB
##       Octave    RAM:  170596 KiB,  virt: 1347944 KiB
##       Free      RAM: 1954940 KiB,  swap: 4087804 KiB
##       Available RAM: 2451948 KiB, total: 6042744 KiB
##
## [userinfo, systeminfo] = memory ()
##    @result{} userinfo =
##      scalar structure containing the fields:
##      MaxPossibleArrayBytes = 6.1622e+09
##      MemAvailableAllArrays = 6.1622e+09
##      RamAvailableAllArrays = 2.4883e+09
##      MemUsedMATLAB = 1.3825e+09
##      MemUsedOctave = 1.3825e+09
##      RamUsedOctave = 1.7824e+08
##
##    systeminfo =
##      scalar structure containing the fields:
##      PhysicalMemory =
##        scalar structure containing the fields:
##          Total = 4.0284e+09
##          Available = 2.4954e+09
##      SystemMemory =
##        scalar structure containing the fields:
##          Total = 8.2143e+09
##          Available = 6.6813e+09
##      VirtualAddressSpace =
##        scalar structure containing the fields:
##          Total = 2.8147e+14
##          Available = 2.8147e+14
## @end group
## @end example
##
## On non-Linux platforms this function fails with an error.
##
## If the argument "linux" is specified, a low level
## version of the command returns structs representing the
## /proc/self/status and /proc/meminfo pseudofiles.
##
## @seealso{computer, isunix, getpid, getrusage, nproc, uname}
## @end deftypefn

function [userdata, systemdata] = memory (flag)

  if (!isunix)
    if (nargout)
      error ('memory function not yet implemented for this archiecture')
    else
      warning ('memory function not yet implemented for this archiecture')
    endif
    return
  endif

  linux_flag = nargin && strcmp (flag, 'linux');

  # read the /proc/pid/statm pseudofile
  # see https://linuxwiki.de/proc/pid#A.2Fproc.2Fpid.2Fstatm
  # it contains 7 numbers in total
  # 1st: total memory used by the process
  # 2nd: currently used RAM
  # Values are in pages of size 4096 Bytes
  #
  f = fopen ('/proc/self/statm');
  buffer = textscan (f);
  fclose (f);
  statm = buffer{:};
  UsedRAM = statm(2) * 4096;
  UsedVirtual = statm(1) * 4096;

  # read the proc/self/status pseudofile
  # see https://linuxwiki.de/proc/pid#A.2Fproc.2Fpid.2Fstatus
  # it contains a variable number of lines with Name - Value pairs
  
  f = fopen ('/proc/self/status');
  buffer = textscan (f, '%s %s', 'delimiter', ':\n');
  fclose (f);
  for i = 1:size (buffer{1},1)
    status.(buffer{1}{i}) = buffer{2}{i};
    status_numeric.(buffer{1}{i}) = textscan (buffer{2}{i}){:};
  endfor

  # read the /proc/meminfo pseudofile
  # see https://linuxwiki.de/proc/meminfo
  # it contains a variable number of lines with Name - Value pairs

  f = fopen ('/proc/meminfo');
  buffer = textscan (f, '%s %s', 'delimiter', ':\n');
  fclose (f);
  for i=1:size (buffer{1},1)
    meminfo.(buffer{1}{i}) = buffer{2}{i};
    meminfo_numeric.(buffer{1}{i}) = textscan (buffer{2}{i}){:};
  endfor

  kiB = 1024;

  AddressSpace_64Bit = 2^48;     # 256TB
  AddressSpace_32Bit = 2^30 * 3; # 3GB

  [architecture, bits] = computer;
  if (log2(bits) > 32)
    AddressSpace = AddressSpace_64Bit;
  else
    AddressSpace = AddressSpace_32Bit;
  endif

  TotalRAM = meminfo_numeric.MemTotal * kiB;
  TotalSwap = meminfo_numeric.SwapTotal * kiB;
  AvailableRAM = meminfo_numeric.MemAvailable * kiB;
  FreeRAM = meminfo_numeric.MemFree * kiB;
  FreeSwap = meminfo_numeric.SwapFree * kiB;
  UsedRAM = status_numeric.VmRSS * kiB;
  UsedVirtual = status_numeric.VmSize * kiB;
  #UsedShared = status_numeric.RssShmem * kiB;
  Available = min (FreeRAM + FreeSwap, AddressSpace - UsedVirtual);

  user.MaxPossibleArrayBytes = Available;
  user.MemAvailableAllArrays = Available;
  user.RamAvailableAllArrays = AvailableRAM; # Octave only = top Mem Avail
  user.MemUsedMATLAB = UsedVirtual;          # For Compatibility
  user.MemUsedOctave = UsedVirtual;          # Octave only = top VIRT
  user.RamUsedOctave = UsedRAM;              # Octave only = top RES
  #user.ShmUsedOctave = UsedShared;          # Octave only = top SHR

  syst.PhysicalMemory.Total = TotalRAM;
  syst.PhysicalMemory.Available = AvailableRAM;
  syst.SystemMemory.Total = TotalRAM + TotalSwap;  #octave only
  syst.SystemMemory.Available = AvailableRAM + FreeSwap;
  syst.VirtualAddressSpace.Total = AddressSpace;
  syst.VirtualAddressSpace.Available = AddressSpace - UsedVirtual;

  if (!linux_flag)

    if (nargout)
      userdata = user;
      systemdata = syst;
    else
      MiB = kiB * kiB;
      unitsize = kiB;
      unitname = 'KiB';
      disp (sprintf ('Octave is running on %s', architecture))
      disp (sprintf ('System    RAM: %7.0f %s,  swap: %7.0f %s',
                     round (TotalRAM / unitsize), unitname,
                     round (TotalSwap / unitsize), unitname ))
      disp (sprintf ('Octave    RAM: %7.0f %s,  virt: %7.0f %s',
                     round (user.RamUsedOctave / unitsize), unitname,
                     round (user.MemUsedOctave / unitsize), unitname))
      disp (sprintf ('Free      RAM: %7.0f %s,  swap: %7.0f %s',
                     round (FreeRAM / unitsize), unitname,
                     round (FreeSwap / unitsize), unitname))
      disp (sprintf ('Available RAM: %7.0f %s, total: %7.0f %s',
                     round ( user.RamAvailableAllArrays / unitsize), unitname,
                     round ( user.MemAvailableAllArrays / unitsize), unitname))
    endif

  else #linux_flag

    if (nargout)
      userdata = status;
      systemdata = meminfo;
    else
      disp ('')
      disp (['  contents of /proc/self/status'])
      disp (status)
      disp ('')
      disp (['  contents of /proc/meminfo'])
      disp (meminfo)
      #disp ('')
      #disp (['  results in userdata'])
      #disp (user)
      #disp ('')
      #disp (['  results in systemdata'])
      #disp (syst)
    endif

  endif

endfunction


%!test
%! [user syst] = memory ();
%! assert (!isunix || user.MemUsedOctave > 0);
%! assert (!isunix || user.RamUsedOctave < user.MemUsedOctave);
%! assert (!isunix || user.MemUsedOctave < syst.SystemMemory.Total);
%! assert (!isunix || user.MemAvailableAllArrays <= syst.SystemMemory.Available);

