/*******************************************************************************
 * Copyright (c) 2016 École Polytechnique de Montréal
 *
 * All rights reserved. This program and the accompanying materials are
 * made available under the terms of the Eclipse Public License 2.0 which
 * accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *******************************************************************************/

package org.eclipse.tracecompass.analysis.os.linux.core.tests.kernelmemoryusage;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.io.File;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.tracecompass.analysis.os.linux.core.kernelmemoryusage.KernelMemoryAnalysisModule;
import org.eclipse.tracecompass.analysis.os.linux.core.kernelmemoryusage.KernelMemoryStateProvider;
import org.eclipse.tracecompass.analysis.os.linux.core.tests.Activator;
import org.eclipse.tracecompass.analysis.os.linux.core.tests.stubs.trace.TmfXmlKernelTraceStub;
import org.eclipse.tracecompass.analysis.os.linux.core.tid.TidAnalysisModule;
import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem;
import org.eclipse.tracecompass.statesystem.core.exceptions.AttributeNotFoundException;
import org.eclipse.tracecompass.statesystem.core.exceptions.StateSystemDisposedException;
import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval;
import org.eclipse.tracecompass.tmf.core.analysis.IAnalysisModule;
import org.eclipse.tracecompass.tmf.core.event.TmfEvent;
import org.eclipse.tracecompass.tmf.core.exceptions.TmfTraceException;
import org.eclipse.tracecompass.tmf.core.signal.TmfTraceOpenedSignal;
import org.eclipse.tracecompass.tmf.core.tests.shared.TmfTestHelper;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
import org.eclipse.tracecompass.tmf.core.trace.TmfTrace;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

/**
 * Test suite for the {@link KernelMemoryAnalysisModule} class
 *
 * @author Julien Daoust
 * @author Najib Arbaoui
 */
public class KernelMemoryStateProviderTest {

    private static final String KERNEL_MEMORY_USAGE_FILE = "testfiles/KernelMemoryAnalysis_testTrace.xml";
    private static final long PAGE_SIZE = 4096;
    private ITmfTrace fTrace;
    private KernelMemoryAnalysisModule fModule = null;
    private SortedMap<Long, Long> threadEvent = new TreeMap<>();

    private static void deleteSuppFiles(ITmfTrace trace) {
        /* Remove supplementary files */
        File suppDir = new File(TmfTraceManager.getSupplementaryFileDir(trace));
        for (File file : suppDir.listFiles()) {
            file.delete();
        }
    }

    /**
     * Setup the trace for the tests
     */
    @Before
    public void setUp() {
        ITmfTrace trace = new TmfXmlKernelTraceStub();
        IPath filePath = Activator.getAbsoluteFilePath(KERNEL_MEMORY_USAGE_FILE);
        IStatus status = trace.validate(null, filePath.toOSString());
        if (!status.isOK()) {
            fail(status.getException().getMessage());
        }
        try {
            trace.initTrace(null, filePath.toOSString(), TmfEvent.class);
        } catch (TmfTraceException e) {
            fail(e.getMessage());
        }
        deleteSuppFiles(trace);

        ((TmfTrace) trace).traceOpened(new TmfTraceOpenedSignal(this, trace, null));
        fModule = TmfTraceUtils.getAnalysisModuleOfClass(trace, KernelMemoryAnalysisModule.class, KernelMemoryAnalysisModule.ID);
        assertNotNull(fModule);
        fTrace = trace;
    }

    /**
     * Clean up
     */
    @After
    public void tearDown() {
        deleteSuppFiles(fTrace);
        fTrace.dispose();
    }

    /**
     * Test that the analysis executes without problems
     */
    @Test
    public void testAnalysisExecution() {
        /* Make sure the analysis hasn't run yet */
        assertNull(fModule.getStateSystem());

        /* Make sure dependent analysis is executed for so that it ready for KernelMemoryAnalysisModule */
        ITmfTrace trace = fTrace;
        assertNotNull(trace);
        IAnalysisModule module = TmfTraceUtils.getAnalysisModuleOfClass(trace, TidAnalysisModule.class, TidAnalysisModule.ID);
        assertNotNull(module);
        module.schedule();
        module.waitForCompletion();

        /* Execute the analysis */
        assertTrue(TmfTestHelper.executeAnalysis(fModule));
        assertNotNull(fModule.getStateSystem());
    }

    /**
     * Test allocation and deallocation of kernel Memory
     */
    @Test
    public void testAllocationDeallocationMemory() {
        fModule.schedule();
        fModule.waitForCompletion();
        ITmfStateSystem ss = fModule.getStateSystem();
        assertNotNull(ss);
        assertEquals(1L, ss.getStartTime());
        assertEquals(30L, ss.getCurrentEndTime());
        long totalMemory = 0;
        threadEvent.put(1L, PAGE_SIZE); // kmem_mm_page_alloc at timestamp = 1
        threadEvent.put(2L, -PAGE_SIZE); // kmem_mm_page_free at timestamp = 2
        threadEvent.put(3L, -PAGE_SIZE); // kmem_mm_page_free at timestamp = 3
        threadEvent.put(17L, PAGE_SIZE << 2); // kmem_mm_page_alloc at timestamp = 17
        threadEvent.put(22L, -PAGE_SIZE); // kmem_mm_page_free at timestamp = 22
        threadEvent.put(28L, -PAGE_SIZE); // kmem_mm_page_free at timestamp = 28
        threadEvent.put(29L, PAGE_SIZE); // kmem_mm_page_alloc at timestamp = 29
        threadEvent.put(30L, PAGE_SIZE); // kmem_mm_page_alloc at timestamp = 30

        // loop a Map and check if all allocation and deallocation of kernel
        // memory are done proprely
        for (Map.Entry<Long, Long> entry : threadEvent.entrySet()) {
            try {
                int tidQuark = ss.getQuarkAbsolute(KernelMemoryStateProvider.OTHER_TID);
                ITmfStateInterval kernelState = ss.querySingleState(entry.getKey(), tidQuark);
                long value = kernelState.getStateValue().unboxLong();
                totalMemory += entry.getValue();
                assertEquals(totalMemory, value);

            } catch (StateSystemDisposedException | AttributeNotFoundException e) {
                fail(e.getMessage());
            }
        }
    }
}