// Copyright (c) 2019-present, iQIYI, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//

// Created by caikelun on 2019-03-22.

#include <stdint.h>
#include <sys/types.h>
#include <inttypes.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include "xcc_util.h"
#include "xcc_libc_support.h"
#include "xcc_meminfo.h"

#define XCC_MEMINFO_DELETE_STR     " (deleted)"
#define XCC_MEMINFO_DELETE_STR_LEN 10
#define XCC_MEMINFO_HEAD_FMT       "%13s %8s %8s %8s %8s %8s %8s %8s\n"
#define XCC_MEMINFO_DATA_FMT       "%13s %8zu %8zu %8zu %8zu %8zu %8zu %8zu\n"
#define XCC_MEMINFO_SUM_HEAD_FMT   "%21s %8s\n"
#define XCC_MEMINFO_SUM_DATA_FMT   "%21s %8zu\n"
#define XCC_MEMINFO_SUM_DATA2_FMT  "%21s %8zu %21s %8zu\n"

typedef struct
{
    size_t pss;
    size_t swappable_pss;
    size_t private_dirty;
    size_t shared_dirty;
    size_t private_clean;
    size_t shared_clean;
    size_t swapped_out;
    size_t swapped_out_pss;
} xcc_meminfo_t;

static const char *xcc_meminfo_label[] =
{
    "Native Heap",
    "Dalvik Heap",
    "Dalvik Other",
    "Stack",
    "Cursor",
    "Ashmem",
    "Gfx dev",
    "Other dev",
    ".so mmap",
    ".jar mmap",
    ".apk mmap",
    ".ttf mmap",
    ".dex mmap",
    ".oat mmap",
    ".art mmap",
    "Other mmap",
    //"EGL mtrack",
    //"GL mtrack",
    //"Other mtrack",
    "Unknown",

    ".Heap",
    ".LOS",
    ".Zygote",
    ".NonMoving",

    ".LinearAlloc",
    ".GC",
    ".JITCache",
    ".CompilerMetadata",
    ".IndirectRef",

    ".Boot vdex",
    ".App dex",
    ".App vdex",

    ".App art",
    ".Boot art"
};

enum {
    HEAP_NATIVE = 0,
    HEAP_DALVIK,
    HEAP_DALVIK_OTHER,
    HEAP_STACK,
    HEAP_CURSOR,
    HEAP_ASHMEM,
    HEAP_GL_DEV,
    HEAP_UNKNOWN_DEV,
    HEAP_SO,
    HEAP_JAR,
    HEAP_APK,
    HEAP_TTF,
    HEAP_DEX,
    HEAP_OAT,
    HEAP_ART,
    HEAP_UNKNOWN_MAP,
    //HEAP_GRAPHICS,
    //HEAP_GL,
    //HEAP_OTHER_MEMTRACK,
    HEAP_UNKNOWN,

    // Dalvik extra sections (heap).
    HEAP_DALVIK_NORMAL,
    HEAP_DALVIK_LARGE,
    HEAP_DALVIK_ZYGOTE,
    HEAP_DALVIK_NON_MOVING,

    // Dalvik other extra sections.
    HEAP_DALVIK_OTHER_LINEARALLOC,
    HEAP_DALVIK_OTHER_ACCOUNTING,
    HEAP_DALVIK_OTHER_CODE_CACHE,
    HEAP_DALVIK_OTHER_COMPILER_METADATA,
    HEAP_DALVIK_OTHER_INDIRECT_REFERENCE_TABLE,

    // Boot vdex / app dex / app vdex
    HEAP_DEX_BOOT_VDEX,
    HEAP_DEX_APP_DEX,
    HEAP_DEX_APP_VDEX,

    // App art, boot art.
    HEAP_ART_APP,
    HEAP_ART_BOOT,
    
    _NUM_HEAP,
    _NUM_EXCLUSIVE_HEAP = HEAP_UNKNOWN + 1
};

static void xcc_meminfo_load(FILE *fp, xcc_meminfo_t *stats, int *found_swap_pss)
{
    char       line[1024];
    size_t     len;
    size_t     temp;
    
    uintptr_t  start = 0, end = 0, prev_end = 0;
    char      *name;
    size_t     name_len, name_pos;
    int        pos = 0;

    size_t     pss;
    size_t     swappable_pss;
    size_t     private_dirty;
    size_t     shared_dirty;
    size_t     private_clean;
    size_t     shared_clean;
    size_t     swapped_out;
    size_t     swapped_out_pss;
    float      sharing_proportion = 0.0;

    int        which_heap = HEAP_UNKNOWN;
    int        sub_heap = HEAP_UNKNOWN;
    int        prev_heap = HEAP_UNKNOWN;

    int        skip = 0;
    int        done = 0;
    int        is_swappable = 0;

    if(NULL == fgets(line, sizeof(line), fp)) return;
    
    while(!done)
    {
        prev_heap = which_heap;
        prev_end = end;
        which_heap = HEAP_UNKNOWN;
        sub_heap = HEAP_UNKNOWN;
        skip = 0;
        is_swappable = 0;

        len = strlen(line);
        if (len < 1) return;
        line[--len] = '\0';
        
        if(sscanf(line, "%"SCNxPTR"-%"SCNxPTR" %*s %*x %*x:%*x %*d%n", &start, &end, &pos) != 2)
        {
            skip = 1;
        }
        else
        {
            name_pos = (size_t)pos;
            
            //get name and name length
            while(isspace(line[name_pos]))
                name_pos += 1;
            name = line + name_pos;
            name_len = strlen(name);
            
            //trim the end of the line if it is " (deleted)"
            if(name_len > XCC_MEMINFO_DELETE_STR_LEN &&
               0 == strcmp(name + name_len - XCC_MEMINFO_DELETE_STR_LEN, XCC_MEMINFO_DELETE_STR))
            {
                name_len -= XCC_MEMINFO_DELETE_STR_LEN;
                name[name_len] = '\0';
            }

            if(0 == strncmp(name, "[heap]", 6) ||
               0 == strncmp(name, "[anon:libc_malloc]", 18))
            {
                which_heap = HEAP_NATIVE;
            }
            else if(0 == strncmp(name, "[stack", 6))
            {
                which_heap = HEAP_STACK;
            }
            else if(name_len > 3 && 0 == strcmp(name + name_len - 3, ".so"))
            {
                which_heap = HEAP_SO;
                is_swappable = 1;
            }
            else if(name_len > 4 && 0 == strcmp(name + name_len - 4, ".jar"))
            {
                which_heap = HEAP_JAR;
                is_swappable = 1;
            }
            else if(name_len > 4 && 0 == strcmp(name + name_len - 4, ".apk"))
            {
                which_heap = HEAP_APK;
                is_swappable = 1;
            }
            else if(name_len > 4 && 0 == strcmp(name + name_len - 4, ".ttf"))
            {
                which_heap = HEAP_TTF;
                is_swappable = 1;
            }
            else if((name_len > 4 && NULL != strstr(name, ".dex")) ||
                    (name_len > 5 && 0 == strcmp(name + name_len - 5, ".odex")))
            {
                which_heap = HEAP_DEX;
                sub_heap = HEAP_DEX_APP_DEX;
                is_swappable = 1;
            }
            else if(name_len > 5 && 0 == strcmp(name + name_len - 5, ".vdex"))
            {
                which_heap = HEAP_DEX;
                if(NULL != strstr(name, "@boot") || NULL != strstr(name, "/boot"))
                {
                    sub_heap = HEAP_DEX_BOOT_VDEX;
                }
                else
                {
                    sub_heap = HEAP_DEX_APP_VDEX;
                }
                is_swappable = 1;
            }
            else if(name_len > 4 && 0 == strcmp(name + name_len - 4, ".oat"))
            {
                which_heap = HEAP_OAT;
                is_swappable = 1;
            }
            else if(name_len > 4 && 0 == strcmp(name + name_len - 4, ".art"))
            {
                which_heap = HEAP_ART;
                if(NULL != strstr(name, "@boot") || NULL != strstr(name, "/boot"))
                {
                    sub_heap = HEAP_ART_BOOT;
                }
                else
                {
                    sub_heap = HEAP_ART_APP;
                }
                is_swappable = 1;
            }
            else if(0 == strncmp(name, "/dev/", 5))
            {
                if(0 == strncmp(name, "/dev/kgsl-3d0", 13))
                {
                    which_heap = HEAP_GL_DEV;
                }
                else if(0 == strncmp(name, "/dev/ashmem", 11))
                {
                    if(0 == strncmp(name, "/dev/ashmem/dalvik-", 19))
                    {
                        if(0 == strncmp(name, "/dev/ashmem/dalvik-LinearAlloc", 30))
                        {
                            which_heap = HEAP_DALVIK_OTHER;
                            sub_heap = HEAP_DALVIK_OTHER_LINEARALLOC;
                        }
                        else if(0 == strncmp(name, "/dev/ashmem/dalvik-alloc space", 30) ||
                                0 == strncmp(name, "/dev/ashmem/dalvik-main space", 29))
                        {
                            which_heap = HEAP_DALVIK;
                            sub_heap = HEAP_DALVIK_NORMAL;
                        }
                        else if(0 == strncmp(name, "/dev/ashmem/dalvik-large object space", 37) ||
                                0 == strncmp(name, "/dev/ashmem/dalvik-free list large object space", 47))
                        {
                            which_heap = HEAP_DALVIK;
                            sub_heap = HEAP_DALVIK_LARGE;
                        }
                        else if(0 == strncmp(name, "/dev/ashmem/dalvik-non moving space", 35))
                        {
                            which_heap = HEAP_DALVIK;
                            sub_heap = HEAP_DALVIK_NON_MOVING;
                        }
                        else if(0 == strncmp(name, "/dev/ashmem/dalvik-zygote space", 31))
                        {
                            which_heap = HEAP_DALVIK;
                            sub_heap = HEAP_DALVIK_ZYGOTE;
                        }
                        else if(0 == strncmp(name, "/dev/ashmem/dalvik-indirect ref", 31))
                        {
                            which_heap = HEAP_DALVIK_OTHER;
                            sub_heap = HEAP_DALVIK_OTHER_INDIRECT_REFERENCE_TABLE;
                        }
                        else if(0 == strncmp(name, "/dev/ashmem/dalvik-jit-code-cache", 33) ||
                                0 == strncmp(name, "/dev/ashmem/dalvik-data-code-cache", 34))
                        {
                            which_heap = HEAP_DALVIK_OTHER;
                            sub_heap = HEAP_DALVIK_OTHER_CODE_CACHE;
                        }
                        else if(0 == strncmp(name, "/dev/ashmem/dalvik-CompilerMetadata", 35))
                        {
                            which_heap = HEAP_DALVIK_OTHER;
                            sub_heap = HEAP_DALVIK_OTHER_COMPILER_METADATA;
                        }
                        else
                        {
                            which_heap = HEAP_DALVIK_OTHER;
                            sub_heap = HEAP_DALVIK_OTHER_ACCOUNTING;
                        }
                    }
                    else if(0 == strncmp(name, "/dev/ashmem/CursorWindow", 24))
                    {
                        which_heap = HEAP_CURSOR;
                    }
                    else if(0 == strncmp(name, "/dev/ashmem/libc malloc", 23))
                    {
                        which_heap = HEAP_NATIVE;
                    }
                    else
                    {
                        which_heap = HEAP_ASHMEM;
                    }
                }
                else
                {
                    which_heap = HEAP_UNKNOWN_DEV;
                }
            }
            else if(0 == strncmp(name, "[anon:", 6))
            {
                which_heap = HEAP_UNKNOWN;
            }
            else if(name_len > 0)
            {
                which_heap = HEAP_UNKNOWN_MAP;
            }
            else if(start == prev_end && prev_heap == HEAP_SO)
            {
                which_heap = HEAP_SO; //bss section of a shared library
            }
        }

        pss = 0;
        shared_clean = 0;
        shared_dirty = 0;
        private_clean = 0;
        private_dirty = 0;
        swapped_out = 0;
        swapped_out_pss = 0;

        while(1)
        {
            if(NULL == fgets(line, sizeof(line), fp))
            {
                done = 1;
                break;
            }

            if(line[0] == 'P' && sscanf(line, "Pss: %zu kB", &temp) == 1)
                pss = temp;
            else if(line[0] == 'S' && sscanf(line, "Shared_Clean: %zu kB", &temp) == 1)
                shared_clean = temp;
            else if (line[0] == 'S' && sscanf(line, "Shared_Dirty: %zu kB", &temp) == 1)
                shared_dirty = temp;
            else if (line[0] == 'P' && sscanf(line, "Private_Clean: %zu kB", &temp) == 1)
                private_clean = temp;
            else if (line[0] == 'P' && sscanf(line, "Private_Dirty: %zu kB", &temp) == 1)
                private_dirty = temp;
            else if (line[0] == 'S' && sscanf(line, "Swap: %zu kB", &temp) == 1)
                swapped_out = temp;
            else if (line[0] == 'S' && sscanf(line, "SwapPss: %zu kB", &temp) == 1)
            {
                *found_swap_pss = 1;
                swapped_out_pss = temp;
            }
            else if(sscanf(line, "%"SCNxPTR"-%"SCNxPTR" %*s %*x %*x:%*x %*d", &start, &end) == 2)
                break; // looks like a new mapping
        }

        if(!skip)
        {
            //get swappable_pss
            if(is_swappable && (pss > 0))
            {
                sharing_proportion = 0.0;
                if((shared_clean > 0) || (shared_dirty > 0))
                {
                    sharing_proportion = (pss - private_clean - private_dirty) / (shared_clean + shared_dirty);
                }
                swappable_pss = (size_t)((sharing_proportion * shared_clean) + private_clean);
            }
            else
            {
                swappable_pss = 0;
            }

            stats[which_heap].pss += pss;
            stats[which_heap].swappable_pss += swappable_pss;
            stats[which_heap].private_dirty += private_dirty;
            stats[which_heap].shared_dirty += shared_dirty;
            stats[which_heap].private_clean += private_clean;
            stats[which_heap].shared_clean += shared_clean;
            stats[which_heap].swapped_out += swapped_out;
            stats[which_heap].swapped_out_pss += swapped_out_pss;
            if (which_heap == HEAP_DALVIK ||
                which_heap == HEAP_DALVIK_OTHER ||
                which_heap == HEAP_DEX ||
                which_heap == HEAP_ART)
            {
                stats[sub_heap].pss += pss;
                stats[sub_heap].swappable_pss += swappable_pss;
                stats[sub_heap].private_dirty += private_dirty;
                stats[sub_heap].shared_dirty += shared_dirty;
                stats[sub_heap].private_clean += private_clean;
                stats[sub_heap].shared_clean += shared_clean;
                stats[sub_heap].swapped_out += swapped_out;
                stats[sub_heap].swapped_out_pss += swapped_out_pss;
            }
        }
    }
}

static int xcc_meminfo_record_sys(int log_fd)
{
    return xcc_util_record_sub_section_from(log_fd, "/proc/meminfo", " System Summary (From: /proc/meminfo)\n", 0);
}

static int xcc_meminfo_record_proc_status(int log_fd, pid_t pid)
{
    char  path[64];
    snprintf(path, sizeof(path), "/proc/%d/status", pid);

    return xcc_util_record_sub_section_from(log_fd, path, " Process Status (From: /proc/PID/status)\n", 0);
}

static int xcc_meminfo_record_proc_limits(int log_fd, pid_t pid)
{
    char  path[64];
    snprintf(path, sizeof(path), "/proc/%d/limits", pid);

    return xcc_util_record_sub_section_from(log_fd, path, " Process Limits (From: /proc/PID/limits)\n", 0);
}

int xcc_meminfo_record(int log_fd, pid_t pid)
{
    char           path[64];
    FILE          *fp = NULL;
    xcc_meminfo_t  stats[_NUM_HEAP];
    xcc_meminfo_t  total;
    int            found_swap_pss = 0;
    size_t         i;
    int            r = 0;

    xcc_libc_support_memset(stats, 0, sizeof(stats));
    xcc_libc_support_memset(&total, 0, sizeof(total));

    //load memory info from /proc/pid/smaps
    snprintf(path, sizeof(path), "/proc/%d/smaps", pid);
    if(NULL == (fp = fopen(path, "r"))) return 0;
    xcc_meminfo_load(fp, stats, &found_swap_pss);
    fclose(fp);

    for(i = 0; i < _NUM_EXCLUSIVE_HEAP; i++)
    {
        total.pss += (stats[i].pss + stats[i].swapped_out_pss);
        total.swappable_pss += stats[i].swappable_pss;
        total.private_dirty += stats[i].private_dirty;
        total.shared_dirty += stats[i].shared_dirty;
        total.private_clean += stats[i].private_clean;
        total.shared_clean += stats[i].shared_clean;
        total.swapped_out += stats[i].swapped_out;
        total.swapped_out_pss += stats[i].swapped_out_pss;
    }

    //dump
    if(0 != (r = xcc_util_write_str(log_fd, "memory info:\n"))) return r;
    if(0 != (r = xcc_meminfo_record_sys(log_fd))) return r;
    if(0 != (r = xcc_meminfo_record_proc_status(log_fd, pid))) return r;
    if(0 != (r = xcc_meminfo_record_proc_limits(log_fd, pid))) return r;
    if(0 != (r = xcc_util_write_str(log_fd, " Process Details (From: /proc/PID/smaps)\n"))) return r;
    if(0 != (r = xcc_util_write_format(log_fd, XCC_MEMINFO_HEAD_FMT, "", "Pss", "Pss", "Shared", "Private", "Shared", "Private", found_swap_pss ? "SwapPss" : "Swap"))) return r;
    if(0 != (r = xcc_util_write_format(log_fd, XCC_MEMINFO_HEAD_FMT, "", "Total", "Clean", "Dirty", "Dirty", "Clean", "Clean", "Dirty"))) return r;
    if(0 != (r = xcc_util_write_format(log_fd, XCC_MEMINFO_HEAD_FMT, "", "------", "------", "------", "------", "------", "------", "------"))) return r;
    for(i = 0; i < _NUM_EXCLUSIVE_HEAP; i++)
    {
        if(HEAP_NATIVE == i || HEAP_DALVIK == i || HEAP_UNKNOWN == i ||
           0 != stats[i].pss || 0 != stats[i].swappable_pss || 0 != stats[i].shared_dirty ||
           0 != stats[i].private_dirty || 0 != stats[i].shared_clean || 0 != stats[i].private_clean ||
           0 != (found_swap_pss ? stats[i].swapped_out_pss : stats[i].swapped_out))
        {
            if(0 != (r = xcc_util_write_format(log_fd, XCC_MEMINFO_DATA_FMT, xcc_meminfo_label[i],
                                               stats[i].pss,
                                               stats[i].swappable_pss,
                                               stats[i].shared_dirty,
                                               stats[i].private_dirty,
                                               stats[i].shared_clean,
                                               stats[i].private_clean,
                                               found_swap_pss ? stats[i].swapped_out_pss : stats[i].swapped_out))) return r;
        }
    }
    if(0 != (r = xcc_util_write_format(log_fd, XCC_MEMINFO_DATA_FMT, "TOTAL",
                                       total.pss,
                                       total.swappable_pss,
                                       total.shared_dirty,
                                       total.private_dirty,
                                       total.shared_clean,
                                       total.private_clean,
                                       found_swap_pss ? total.swapped_out_pss : total.swapped_out))) return r;
    if(0 != (r = xcc_util_write_str(log_fd, "-\n Process Dalvik Details (From: /proc/PID/smaps)\n"))) return r;
    for(i = _NUM_EXCLUSIVE_HEAP; i < _NUM_HEAP; i++)
    {
        if(0 != stats[i].pss || 0 != stats[i].swappable_pss || 0 != stats[i].shared_dirty ||
           0 != stats[i].private_dirty || 0 != stats[i].shared_clean || 0 != stats[i].private_clean ||
           0 != (found_swap_pss ? stats[i].swapped_out_pss : stats[i].swapped_out))
        {
            if(0 != (r = xcc_util_write_format(log_fd, XCC_MEMINFO_DATA_FMT, xcc_meminfo_label[i],
                                               stats[i].pss,
                                               stats[i].swappable_pss,
                                               stats[i].shared_dirty,
                                               stats[i].private_dirty,
                                               stats[i].shared_clean,
                                               stats[i].private_clean,
                                               found_swap_pss ? stats[i].swapped_out_pss : stats[i].swapped_out))) return r;
        }
    }
    if(0 != (r = xcc_util_write_str(log_fd, "-\n Process Summary (From: /proc/PID/smaps)\n"))) return r;
    if(0 != (r = xcc_util_write_format(log_fd, XCC_MEMINFO_SUM_HEAD_FMT, "", "Pss(KB)"))) return r;
    if(0 != (r = xcc_util_write_format(log_fd, XCC_MEMINFO_SUM_HEAD_FMT, "", "------"))) return r;
    if(0 != (r = xcc_util_write_format(log_fd, XCC_MEMINFO_SUM_DATA_FMT, "Java Heap:",
                                       stats[HEAP_DALVIK].private_dirty +
                                       stats[HEAP_ART].private_dirty + stats[HEAP_ART].private_clean))) return r;
    if(0 != (r = xcc_util_write_format(log_fd, XCC_MEMINFO_SUM_DATA_FMT, "Native Heap:",
                                       stats[HEAP_NATIVE].private_dirty))) return r;
    if(0 != (r = xcc_util_write_format(log_fd, XCC_MEMINFO_SUM_DATA_FMT, "Code:",
                                       stats[HEAP_SO].private_dirty + stats[HEAP_SO].private_clean +
                                       stats[HEAP_JAR].private_dirty + stats[HEAP_JAR].private_clean +
                                       stats[HEAP_APK].private_dirty + stats[HEAP_APK].private_clean +
                                       stats[HEAP_TTF].private_dirty + stats[HEAP_TTF].private_clean +
                                       stats[HEAP_DEX].private_dirty + stats[HEAP_DEX].private_clean +
                                       stats[HEAP_OAT].private_dirty + stats[HEAP_OAT].private_clean))) return r;
    if(0 != (r = xcc_util_write_format(log_fd, XCC_MEMINFO_SUM_DATA_FMT, "Stack:",
                                       stats[HEAP_STACK].private_dirty))) return r;
    if(0 != (r = xcc_util_write_format(log_fd, XCC_MEMINFO_SUM_DATA_FMT, "Private Other:", 
                                       stats[HEAP_DALVIK_OTHER].private_dirty + stats[HEAP_DALVIK_OTHER].private_clean +
                                       stats[HEAP_CURSOR].private_dirty + stats[HEAP_CURSOR].private_clean +
                                       stats[HEAP_ASHMEM].private_dirty + stats[HEAP_ASHMEM].private_clean +
                                       stats[HEAP_GL_DEV].private_dirty + stats[HEAP_GL_DEV].private_clean +
                                       stats[HEAP_UNKNOWN_DEV].private_dirty + stats[HEAP_UNKNOWN_DEV].private_clean +
                                       stats[HEAP_UNKNOWN_MAP].private_dirty + stats[HEAP_UNKNOWN_MAP].private_clean +
                                       stats[HEAP_UNKNOWN].private_dirty + stats[HEAP_UNKNOWN].private_clean))) return r;
    if(0 != (r = xcc_util_write_format(log_fd, XCC_MEMINFO_SUM_DATA_FMT, "System:",
                                       total.pss - total.private_dirty - total.private_clean))) return r;
    if(found_swap_pss)
    {
        if(0 != (r = xcc_util_write_format(log_fd, XCC_MEMINFO_SUM_DATA2_FMT,
                                           "TOTAL:",
                                           total.pss,
                                           "TOTAL SWAP PSS:",
                                           total.swapped_out_pss))) return r;
    }
    else
    {
        if(0 != (r = xcc_util_write_format(log_fd, XCC_MEMINFO_SUM_DATA2_FMT,
                                           "TOTAL:",
                                           total.pss,
                                           "TOTAL SWAP:",
                                           total.swapped_out))) return r;
    }
    if(0 != (r = xcc_util_write_str(log_fd, "-\n\n"))) return r;
    
    return 0;
}
