<commit_before>// TODO rm allow(unused)
#![allow(unused)]
#![allow(unsafe_code)]

use std::{
    convert::{TryFrom, TryInto},
    fmt::{self, Debug},
    fs::File,
    mem::{transmute, MaybeUninit},
    path::Path,
    sync::{
        atomic::{AtomicU32, Ordering::Acquire},
        Arc,
    },
};

use crossbeam_epoch::pin;

use crate::{
    pagecache::{pread_exact, pwrite_all, MessageKind},
    stack::Stack,
    Error, Lsn, Result,
};

#[cfg(not(feature = "testing"))]
const MIN_SZ: u64 = 64 * 1024;

#[cfg(feature = "testing")]
const MIN_SZ: u64 = 32;

const MIN_TRAILING_ZEROS: u64 = MIN_SZ.trailing_zeros() as u64;

pub type SlabId = u8;
pub type SlabIdx = u32;

/// A unique identifier for a particular slot in the heap
#[allow(clippy::module_name_repetitions)]
#[derive(Clone, Copy, PartialOrd, Ord, Eq, PartialEq, Hash)]
pub struct HeapId {
    pub location: u64,
    pub original_lsn: Lsn,
}

impl Debug for HeapId {
    fn fmt(
        &self,
        f: &mut fmt::Formatter<'_>,
    ) -> std::result::Result<(), fmt::Error> {
        let (slab, idx, original_lsn) = self.decompose();
        f.debug_struct("HeapId")
            .field("slab", &slab)
            .field("idx", &idx)
            .field("original_lsn", &original_lsn)
            .finish()
    }
}

impl HeapId {
    pub fn decompose(&self) -> (SlabId, SlabIdx, Lsn) {
        const IDX_MASK: u64 = (1 << 32) - 1;
        let slab_id =
            u8::try_from((self.location >> 32).trailing_zeros()).unwrap();
        let slab_idx = u32::try_from(self.location & IDX_MASK).unwrap();
        (slab_id, slab_idx, self.original_lsn)
    }

    pub fn compose(
        slab_id: SlabId,
        slab_idx: SlabIdx,
        original_lsn: Lsn,
    ) -> HeapId {
        let slab = 1 << (32 + u64::from(slab_id));
        let heap_id = slab | u64::from(slab_idx);
        HeapId { location: heap_id, original_lsn }
    }

    fn offset(&self) -> u64 {
        let (slab_id, idx, _) = self.decompose();
        slab_id_to_size(slab_id) * u64::from(idx)
    }

    fn slab_size(&self) -> u64 {
        let (slab_id, idx, _) = self.decompose();
        slab_id_to_size(slab_id)
    }
}

pub(crate) fn slab_size(size: u64) -> u64 {
    slab_id_to_size(size_to_slab_id(size))
}

fn slab_id_to_size(slab_id: u8) -> u64 {
    1 << (MIN_TRAILING_ZEROS + u64::from(slab_id))
}

fn size_to_slab_id(size: u64) -> SlabId {
    // find the power of 2 that is at least 64k
    let normalized_size = std::cmp::max(MIN_SZ, size.next_power_of_two());

    // drop the lowest unused bits
    let rebased_size = normalized_size >> MIN_TRAILING_ZEROS;

    u8::try_from(rebased_size.trailing_zeros()).unwrap()
}

pub(crate) struct Reservation {
    slab_free: Arc<Stack<u32>>,
    completed: bool,
    file: File,
    pub heap_id: HeapId,
}

impl Drop for Reservation {
    fn drop(&mut self) {
        if !self.completed {
            let (slab_id, idx, _) = self.heap_id.decompose();
            self.slab_free.push(idx, &pin());
        }
    }
}

impl Reservation {
    pub fn complete(mut self, data: &[u8]) -> Result<HeapId> {
        log::trace!(
            "Heap::complete({:?}) to offset {} in file {:?}",
            self.heap_id,
            self.heap_id.offset(),
            self.file
        );
        assert_eq!(data.len() as u64, self.heap_id.slab_size());

        // write data
        pwrite_all(&self.file, data, self.heap_id.offset())?;

        // sync data
        #[cfg(target_os = "linux")]
        {
            use std::os::unix::io::AsRawFd;
            let ret = unsafe {
                libc::sync_file_range(
                    self.file.as_raw_fd(),
                    i64::try_from(self.heap_id.offset()).unwrap(),
                    i64::try_from(data.len()).unwrap(),
                    libc::SYNC_FILE_RANGE_WAIT_BEFORE
                        | libc::SYNC_FILE_RANGE_WRITE
                        | libc::SYNC_FILE_RANGE_WAIT_AFTER,
                )
            };
            if ret < 0 {
                let err = std::io::Error::last_os_error();
                if let Some(libc::ENOSYS) = err.raw_os_error() {
                    self.file.sync_all()?;
                } else {
                    return Err(err.into());
                }
            }
        }

        #[cfg(not(target_os = "linux"))]
        self.file.sync_all()?;

        // if this is not reached due to an IO error,
        // the offset will be returned to the Slab in Drop
        self.completed = true;

        Ok(self.heap_id)
    }

    pub fn abort(self) {
        // actual logic in Drop
    }
}

#[derive(Debug)]
pub(crate) struct Heap {
    // each slab stores
    // items that are double
    // the size of the previous,
    // ranging from 64k in the
    // smallest slab to 2^48 in
    // the last.
    slabs: [Slab; 32],
}

impl Heap {
    pub fn start<P: AsRef<Path>>(p: P) -> Result<Heap> {
        let mut slabs: [MaybeUninit<Slab>; 32] = unsafe { std::mem::zeroed() };

        for slab_id in 0..32 {
            let slab = Slab::start(&p, slab_id)?;
            slabs[slab_id as usize] = MaybeUninit::new(slab);
        }

        Ok(Heap { slabs: unsafe { transmute(slabs) } })
    }

    pub fn gc_unknown_items(&self, snapshot: &crate::pagecache::Snapshot) {
        let mut bitmaps = vec![];
        for slab in &self.slabs {
            let tip = slab.tip.load(Acquire) as usize;
            bitmaps.push(vec![0_u64; 1 + (tip / 64)]);
        }

        for page_state in &snapshot.pt {
            for heap_id in page_state.heap_ids() {
                let (sid, idx, _lsn) = heap_id.decompose();

                // set the bit for this slot
                let block = idx / 64;
                let bit = idx % 64;
                let bitmask = 1 << bit;
                bitmaps[sid as usize][block as usize] |= bitmask;
            }
        }

        let iter = self.slabs.iter().zip(bitmaps.into_iter());

        for (slab, bitmap) in iter {
            let tip = slab.tip.load(Acquire);

            for idx in 0..tip {
                let block = idx / 64;
                let bit = idx % 64;
                let bitmask = 1 << bit;
                let free = bitmap[block as usize] & bitmask == 0;

                if free {
                    slab.free(idx);
                }
            }
        }
    }

    pub fn read(
        &self,
        heap_id: HeapId,
        use_compression: bool,
    ) -> Result<(MessageKind, Vec<u8>)> {
        log::trace!("Heap::read({:?})", heap_id);
        let (slab_id, slab_idx, original_lsn) = heap_id.decompose();
        self.slabs[slab_id as usize].read(
            slab_idx,
            original_lsn,
            use_compression,
        )
    }

    pub fn free(&self, heap_id: HeapId) {
        log::trace!("Heap::free({:?})", heap_id);
        let (slab_id, slab_idx, _) = heap_id.decompose();
        self.slabs[slab_id as usize].free(slab_idx)
    }

    pub fn reserve(&self, size: u64, original_lsn: Lsn) -> Reservation {
        assert!(size < 1 << 48);
        let slab_id = size_to_slab_id(size);
        let ret = self.slabs[slab_id as usize].reserve(size, original_lsn);
        log::trace!("Heap::reserve({}) -> {:?}", size, ret.heap_id);
        ret
    }
}

#[derive(Debug)]
struct Slab {
    file: File,
    slab_id: u8,
    tip: AtomicU32,
    free: Arc<Stack<u32>>,
}

impl Slab {
    pub fn start<P: AsRef<Path>>(directory: P, slab_id: u8) -> Result<Slab> {
        let bs = slab_id_to_size(slab_id);
        let free = Arc::new(Stack::default());

        let mut options = std::fs::OpenOptions::new();
        options.create(true);
        options.read(true);
        options.write(true);

        let file =
            options.open(directory.as_ref().join(format!("{:02}", slab_id)))?;
        let len = file.metadata()?.len();
        let max_idx = len / bs;
        log::trace!(
            "starting heap slab for sizes of {}. tip: {} max idx: {}",
            bs,
            len,
            max_idx
        );
        let tip = AtomicU32::new(u32::try_from(max_idx).unwrap());

        Ok(Slab { file, slab_id, tip, free })
    }

    fn read(
        &self,
        slab_idx: SlabIdx,
        original_lsn: Lsn,
        use_compression: bool,
    ) -> Result<(MessageKind, Vec<u8>)> {
        let bs = slab_id_to_size(self.slab_id);
        let offset = u64::from(slab_idx) * bs;

        log::trace!("reading heap slab slot {} at offset {}", slab_idx, offset);

        let mut heap_buf = vec![0; usize::try_from(bs).unwrap()];

        pread_exact(&self.file, &mut heap_buf, offset)?;

        let stored_crc =
            u32::from_le_bytes(heap_buf[1..5].as_ref().try_into().unwrap());

        let mut hasher = crc32fast::Hasher::new();
        hasher.update(&heap_buf[0..1]);
        hasher.update(&heap_buf[5..]);
        let actual_crc = hasher.finalize();

        if actual_crc == stored_crc {
            let actual_lsn = Lsn::from_le_bytes(
                heap_buf[5..13].as_ref().try_into().unwrap(),
            );
            if actual_lsn != original_lsn {
                log::error!(
                    "heap slot lsn {} does not match expected original lsn {}",
                    actual_lsn,
                    original_lsn
                );
                return Err(Error::corruption(None));
            }
            let buf = heap_buf[13..].to_vec();
            let buf = if use_compression {
                crate::pagecache::decompress(buf)
            } else {
                buf
            };
            Ok((MessageKind::from(heap_buf[0]), buf))
        } else {
            log::error!(
                "heap message CRC does not match contents. stored: {} actual: {}",
                stored_crc,
                actual_crc
            );
            Err(Error::corruption(None))
        }
    }

    fn reserve(&self, size: u64, original_lsn: Lsn) -> Reservation {
        let idx = if let Some(idx) = self.free.pop(&pin()) {
            log::trace!(
                "reusing heap index {} in slab for sizes of {}",
                idx,
                slab_id_to_size(self.slab_id),
            );
            idx
        } else {
            log::trace!(
                "no free heap slots in slab for sizes of {}",
                slab_id_to_size(self.slab_id),
            );
            self.tip.fetch_add(1, Acquire)
        };

        log::trace!(
            "heap reservation for slot {} in the slab for sizes of {}",
            idx,
            slab_id_to_size(self.slab_id),
        );

        let heap_id = HeapId::compose(self.slab_id, idx, original_lsn);

        Reservation {
            slab_free: self.free.clone(),
            completed: false,
            file: self.file.try_clone().unwrap(),
            heap_id,
        }
    }

    fn free(&self, idx: u32) {
        self.punch_hole(idx);
        self.free.push(idx, &pin());
    }

    fn punch_hole(&self, idx: u32) {
        #[cfg(target_os = "linux")]
        {
            use std::{
                os::unix::io::AsRawFd,
                sync::atomic::{AtomicBool, Ordering::Relaxed},
            };

            use libc::{fallocate, FALLOC_FL_KEEP_SIZE, FALLOC_FL_PUNCH_HOLE};

            static HOLE_PUNCHING_ENABLED: AtomicBool = AtomicBool::new(true);
            const MODE: i32 = FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE;

            #[cfg(miri)]
            {
                // fallocate is not yet supported
                HOLE_PUNCHING_ENABLED.store(false, Relaxed);
            }

            if HOLE_PUNCHING_ENABLED.load(Relaxed) {
                let bs = i64::try_from(slab_id_to_size(self.slab_id)).unwrap();
                let offset = i64::from(idx) * bs;

                let fd = self.file.as_raw_fd();

                let ret = unsafe {
                    fallocate(
                        fd,
                        MODE,
                        #[allow(clippy::useless_conversion)]
                        offset.try_into().unwrap(),
                        #[allow(clippy::useless_conversion)]
                        bs.try_into().unwrap(),
                    )
                };

                if ret != 0 {
                    let err = std::io::Error::last_os_error();
                    log::error!(
                        "failed to punch hole in heap file: {:?}. disabling hole punching",
                        err
                    );
                    HOLE_PUNCHING_ENABLED.store(false, Relaxed);
                }
            }
        }
    }
}
<commit_msg>small clean-up of heap.rs, including consolidation of conditional compilation<commit_after>// TODO rm allow(unused)
#![allow(unsafe_code)]

use std::{
    convert::{TryFrom, TryInto},
    fmt::{self, Debug},
    fs::File,
    mem::{transmute, MaybeUninit},
    path::Path,
    sync::{
        atomic::{AtomicU32, Ordering::Acquire},
        Arc,
    },
};

use crossbeam_epoch::pin;

use crate::{
    pagecache::{pread_exact, pwrite_all, MessageKind},
    stack::Stack,
    Error, Lsn, Result,
};

#[cfg(not(feature = "testing"))]
const MIN_SZ: u64 = 64 * 1024;

#[cfg(feature = "testing")]
const MIN_SZ: u64 = 32;

const MIN_TRAILING_ZEROS: u64 = MIN_SZ.trailing_zeros() as u64;

pub type SlabId = u8;
pub type SlabIdx = u32;

/// A unique identifier for a particular slot in the heap
#[allow(clippy::module_name_repetitions)]
#[derive(Clone, Copy, PartialOrd, Ord, Eq, PartialEq, Hash)]
pub struct HeapId {
    pub location: u64,
    pub original_lsn: Lsn,
}

impl Debug for HeapId {
    fn fmt(
        &self,
        f: &mut fmt::Formatter<'_>,
    ) -> std::result::Result<(), fmt::Error> {
        let (slab, idx, original_lsn) = self.decompose();
        f.debug_struct("HeapId")
            .field("slab", &slab)
            .field("idx", &idx)
            .field("original_lsn", &original_lsn)
            .finish()
    }
}

impl HeapId {
    pub fn decompose(&self) -> (SlabId, SlabIdx, Lsn) {
        const IDX_MASK: u64 = (1 << 32) - 1;
        let slab_id =
            u8::try_from((self.location >> 32).trailing_zeros()).unwrap();
        let slab_idx = u32::try_from(self.location & IDX_MASK).unwrap();
        (slab_id, slab_idx, self.original_lsn)
    }

    pub fn compose(
        slab_id: SlabId,
        slab_idx: SlabIdx,
        original_lsn: Lsn,
    ) -> HeapId {
        let slab = 1 << (32 + u64::from(slab_id));
        let heap_id = slab | u64::from(slab_idx);
        HeapId { location: heap_id, original_lsn }
    }

    fn offset(&self) -> u64 {
        let (slab_id, idx, _) = self.decompose();
        slab_id_to_size(slab_id) * u64::from(idx)
    }

    fn slab_size(&self) -> u64 {
        let (slab_id, _idx, _lsn) = self.decompose();
        slab_id_to_size(slab_id)
    }
}

pub(crate) fn slab_size(size: u64) -> u64 {
    slab_id_to_size(size_to_slab_id(size))
}

fn slab_id_to_size(slab_id: u8) -> u64 {
    1 << (MIN_TRAILING_ZEROS + u64::from(slab_id))
}

fn size_to_slab_id(size: u64) -> SlabId {
    // find the power of 2 that is at least 64k
    let normalized_size = std::cmp::max(MIN_SZ, size.next_power_of_two());

    // drop the lowest unused bits
    let rebased_size = normalized_size >> MIN_TRAILING_ZEROS;

    u8::try_from(rebased_size.trailing_zeros()).unwrap()
}

pub(crate) struct Reservation {
    slab_free: Arc<Stack<u32>>,
    completed: bool,
    file: File,
    pub heap_id: HeapId,
}

impl Drop for Reservation {
    fn drop(&mut self) {
        if !self.completed {
            let (_slab_id, idx, _) = self.heap_id.decompose();
            self.slab_free.push(idx, &pin());
        }
    }
}

impl Reservation {
    pub fn complete(mut self, data: &[u8]) -> Result<HeapId> {
        log::trace!(
            "Heap::complete({:?}) to offset {} in file {:?}",
            self.heap_id,
            self.heap_id.offset(),
            self.file
        );
        assert_eq!(data.len() as u64, self.heap_id.slab_size());

        // write data
        pwrite_all(&self.file, data, self.heap_id.offset())?;

        // sync data
        #[cfg(target_os = "linux")]
        {
            use std::os::unix::io::AsRawFd;
            let ret = unsafe {
                libc::sync_file_range(
                    self.file.as_raw_fd(),
                    i64::try_from(self.heap_id.offset()).unwrap(),
                    i64::try_from(data.len()).unwrap(),
                    libc::SYNC_FILE_RANGE_WAIT_BEFORE
                        | libc::SYNC_FILE_RANGE_WRITE
                        | libc::SYNC_FILE_RANGE_WAIT_AFTER,
                )
            };
            if ret < 0 {
                let err = std::io::Error::last_os_error();
                if let Some(libc::ENOSYS) = err.raw_os_error() {
                    self.file.sync_all()?;
                } else {
                    return Err(err.into());
                }
            }
        }

        #[cfg(not(target_os = "linux"))]
        self.file.sync_all()?;

        // if this is not reached due to an IO error,
        // the offset will be returned to the Slab in Drop
        self.completed = true;

        Ok(self.heap_id)
    }
}

#[derive(Debug)]
pub(crate) struct Heap {
    // each slab stores
    // items that are double
    // the size of the previous,
    // ranging from 64k in the
    // smallest slab to 2^48 in
    // the last.
    slabs: [Slab; 32],
}

impl Heap {
    pub fn start<P: AsRef<Path>>(p: P) -> Result<Heap> {
        let mut slabs: [MaybeUninit<Slab>; 32] = unsafe { std::mem::zeroed() };

        for slab_id in 0..32 {
            let slab = Slab::start(&p, slab_id)?;
            slabs[slab_id as usize] = MaybeUninit::new(slab);
        }

        Ok(Heap { slabs: unsafe { transmute(slabs) } })
    }

    pub fn gc_unknown_items(&self, snapshot: &crate::pagecache::Snapshot) {
        let mut bitmaps = vec![];
        for slab in &self.slabs {
            let tip = slab.tip.load(Acquire) as usize;
            bitmaps.push(vec![0_u64; 1 + (tip / 64)]);
        }

        for page_state in &snapshot.pt {
            for heap_id in page_state.heap_ids() {
                let (slab_id, idx, _lsn) = heap_id.decompose();

                // set the bit for this slot
                let block = idx / 64;
                let bit = idx % 64;
                let bitmask = 1 << bit;
                bitmaps[slab_id as usize][block as usize] |= bitmask;
            }
        }

        let iter = self.slabs.iter().zip(bitmaps.into_iter());

        for (slab, bitmap) in iter {
            let tip = slab.tip.load(Acquire);

            for idx in 0..tip {
                let block = idx / 64;
                let bit = idx % 64;
                let bitmask = 1 << bit;
                let free = bitmap[block as usize] & bitmask == 0;

                if free {
                    slab.free(idx);
                }
            }
        }
    }

    pub fn read(
        &self,
        heap_id: HeapId,
        use_compression: bool,
    ) -> Result<(MessageKind, Vec<u8>)> {
        log::trace!("Heap::read({:?})", heap_id);
        let (slab_id, slab_idx, original_lsn) = heap_id.decompose();
        self.slabs[slab_id as usize].read(
            slab_idx,
            original_lsn,
            use_compression,
        )
    }

    pub fn free(&self, heap_id: HeapId) {
        log::trace!("Heap::free({:?})", heap_id);
        let (slab_id, slab_idx, _) = heap_id.decompose();
        self.slabs[slab_id as usize].free(slab_idx)
    }

    pub fn reserve(&self, size: u64, original_lsn: Lsn) -> Reservation {
        assert!(size < 1 << 48);
        let slab_id = size_to_slab_id(size);
        let ret = self.slabs[slab_id as usize].reserve(original_lsn);
        log::trace!("Heap::reserve({}) -> {:?}", size, ret.heap_id);
        ret
    }
}

#[derive(Debug)]
struct Slab {
    file: File,
    slab_id: u8,
    tip: AtomicU32,
    free: Arc<Stack<u32>>,
}

impl Slab {
    pub fn start<P: AsRef<Path>>(directory: P, slab_id: u8) -> Result<Slab> {
        let bs = slab_id_to_size(slab_id);
        let free = Arc::new(Stack::default());

        let mut options = std::fs::OpenOptions::new();
        options.create(true);
        options.read(true);
        options.write(true);

        let file =
            options.open(directory.as_ref().join(format!("{:02}", slab_id)))?;
        let len = file.metadata()?.len();
        let max_idx = len / bs;
        log::trace!(
            "starting heap slab for sizes of {}. tip: {} max idx: {}",
            bs,
            len,
            max_idx
        );
        let tip = AtomicU32::new(u32::try_from(max_idx).unwrap());

        Ok(Slab { file, slab_id, tip, free })
    }

    fn read(
        &self,
        slab_idx: SlabIdx,
        original_lsn: Lsn,
        use_compression: bool,
    ) -> Result<(MessageKind, Vec<u8>)> {
        let bs = slab_id_to_size(self.slab_id);
        let offset = u64::from(slab_idx) * bs;

        log::trace!("reading heap slab slot {} at offset {}", slab_idx, offset);

        let mut heap_buf = vec![0; usize::try_from(bs).unwrap()];

        pread_exact(&self.file, &mut heap_buf, offset)?;

        let stored_crc =
            u32::from_le_bytes(heap_buf[1..5].as_ref().try_into().unwrap());

        let mut hasher = crc32fast::Hasher::new();
        hasher.update(&heap_buf[0..1]);
        hasher.update(&heap_buf[5..]);
        let actual_crc = hasher.finalize();

        if actual_crc == stored_crc {
            let actual_lsn = Lsn::from_le_bytes(
                heap_buf[5..13].as_ref().try_into().unwrap(),
            );
            if actual_lsn != original_lsn {
                log::error!(
                    "heap slot lsn {} does not match expected original lsn {}",
                    actual_lsn,
                    original_lsn
                );
                return Err(Error::corruption(None));
            }
            let buf = heap_buf[13..].to_vec();
            let buf = if use_compression {
                crate::pagecache::decompress(buf)
            } else {
                buf
            };
            Ok((MessageKind::from(heap_buf[0]), buf))
        } else {
            log::error!(
                "heap message CRC does not match contents. stored: {} actual: {}",
                stored_crc,
                actual_crc
            );
            Err(Error::corruption(None))
        }
    }

    fn reserve(&self, original_lsn: Lsn) -> Reservation {
        let idx = if let Some(idx) = self.free.pop(&pin()) {
            log::trace!(
                "reusing heap index {} in slab for sizes of {}",
                idx,
                slab_id_to_size(self.slab_id),
            );
            idx
        } else {
            log::trace!(
                "no free heap slots in slab for sizes of {}",
                slab_id_to_size(self.slab_id),
            );
            self.tip.fetch_add(1, Acquire)
        };

        log::trace!(
            "heap reservation for slot {} in the slab for sizes of {}",
            idx,
            slab_id_to_size(self.slab_id),
        );

        let heap_id = HeapId::compose(self.slab_id, idx, original_lsn);

        Reservation {
            slab_free: self.free.clone(),
            completed: false,
            file: self.file.try_clone().unwrap(),
            heap_id,
        }
    }

    fn free(&self, idx: u32) {
        self.punch_hole(idx);
        self.free.push(idx, &pin());
    }

    fn punch_hole(&self, #[allow(unused)] idx: u32) {
        #[cfg(all(target_os = "linux", not(miri)))]
        {
            use std::{
                os::unix::io::AsRawFd,
                sync::atomic::{AtomicBool, Ordering::Relaxed},
            };

            use libc::{fallocate, FALLOC_FL_KEEP_SIZE, FALLOC_FL_PUNCH_HOLE};

            static HOLE_PUNCHING_ENABLED: AtomicBool = AtomicBool::new(true);
            const MODE: i32 = FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE;

            if HOLE_PUNCHING_ENABLED.load(Relaxed) {
                let bs = i64::try_from(slab_id_to_size(self.slab_id)).unwrap();
                let offset = i64::from(idx) * bs;

                let fd = self.file.as_raw_fd();

                let ret = unsafe {
                    fallocate(
                        fd,
                        MODE,
                        #[allow(clippy::useless_conversion)]
                        offset.try_into().unwrap(),
                        #[allow(clippy::useless_conversion)]
                        bs.try_into().unwrap(),
                    )
                };

                if ret != 0 {
                    let err = std::io::Error::last_os_error();
                    log::error!(
                        "failed to punch hole in heap file: {:?}. disabling hole punching",
                        err
                    );
                    HOLE_PUNCHING_ENABLED.store(false, Relaxed);
                }
            }
        }
    }
}
<|endoftext|>


<commit_before><commit_msg>Tweak console_player text output<commit_after><|endoftext|>


<commit_before>use game::*;
use behaviour::LeafResolution;
use direction::Direction;
use coord::{Coord, StraightLine};

pub fn player_input(input_source: InputSourceRef) -> BehaviourLeaf {
    BehaviourLeaf::new(move |input| {
        loop {
            if let Some(meta_action) = get_meta_action(input, input_source) {
                return LeafResolution::Yield(meta_action);
            }
        }
    })
}

fn get_direction(map: &ControlMap, input_source: InputSourceRef) -> Option<Direction> {
    input_source.next_input().and_then(|event| {
        map.control(event).and_then(|control| {
            control_to_direction(control)
        })
    })
}

fn control_to_direction(control: Control) -> Option<Direction> {
    match control {
        Control::Direction(d) => Some(d),
        _ => None,
    }
}

fn aim(input: BehaviourInput, map: &ControlMap, input_source: InputSourceRef) -> Option<Coord> {
    let start = input.entity.position().unwrap();
    let mut knowledge = input.entity.drawable_knowledge_borrow_mut().unwrap();
    let level_knowledge = knowledge.level_mut_or_insert_size(input.level_id,
                                                             input.spatial_hash.width(),
                                                             input.spatial_hash.height());

    let targets = level_knowledge.sort_targets(start);
    let mut target_idx = 0;

    let mut end = if !targets.is_empty() {
        targets[target_idx]
    } else {
        start
    };

    let mut renderer = input.renderer.borrow_mut();

    loop {

        let overlay = RenderOverlay {
            aim_line: Some(StraightLine::new(start, end)),
        };

        renderer.draw_with_overlay(&overlay);

        if let Some(event) = input_source.next_input() {
            if let Some(control) = map.control(event) {
                if let Some(direction) = control_to_direction(control) {
                    let next_end = end + direction.vector();
                    if renderer.contains_world_coord(next_end) {
                        end = next_end;
                    }
                } else if control == Control::NextTarget {
                    if !targets.is_empty() {
                        target_idx = (target_idx + 1) % targets.len();
                        end = targets[target_idx];
                    }
                } else if control == Control::PrevTarget {
                    if !targets.is_empty() {
                        target_idx = (target_idx + targets.len() - 1) % targets.len();
                        end = targets[target_idx];
                    }
                } else if control == Control::Fire {
                    renderer.draw();
                    return Some(end);
                } else {
                    break;
                }
            }
        }
    }

    renderer.draw();
    None
}

fn get_meta_action(input: BehaviourInput, input_source: InputSourceRef) -> Option<MetaAction> {
    input_source.next_input().and_then(|event| {
        input.entity.control_map().and_then(|map| {
            map.control(event).and_then(|control| {
                match control {
                    Control::Direction(d) => Some(MetaAction::ActionArgs(ActionArgs::Walk(input.entity.id(), d))),
                    Control::Close => {
                        println!("getting direction");
                        get_direction(map, input_source).map(|d| MetaAction::ActionArgs(ActionArgs::Close(input.entity.id(), d)))
                    }
                    Control::Fire => {
                        aim(input, map, input_source).map(|coord| {
                            let delta = coord - input.entity.position().unwrap();

                            MetaAction::ActionArgs(ActionArgs::FireBullet(input.entity.id(), delta))
                        })
                    }
                    Control::Wait => {
                        Some(MetaAction::ActionArgs(ActionArgs::Null))
                    }
                    Control::Quit => Some(MetaAction::External(External::Quit)),
                    Control::NextTarget => None,
                    Control::PrevTarget => None,
                }
            })
        })
    })
}

<commit_msg>Removed debug printout<commit_after>use game::*;
use behaviour::LeafResolution;
use direction::Direction;
use coord::{Coord, StraightLine};

pub fn player_input(input_source: InputSourceRef) -> BehaviourLeaf {
    BehaviourLeaf::new(move |input| {
        loop {
            if let Some(meta_action) = get_meta_action(input, input_source) {
                return LeafResolution::Yield(meta_action);
            }
        }
    })
}

fn get_direction(map: &ControlMap, input_source: InputSourceRef) -> Option<Direction> {
    input_source.next_input().and_then(|event| {
        map.control(event).and_then(|control| {
            control_to_direction(control)
        })
    })
}

fn control_to_direction(control: Control) -> Option<Direction> {
    match control {
        Control::Direction(d) => Some(d),
        _ => None,
    }
}

fn aim(input: BehaviourInput, map: &ControlMap, input_source: InputSourceRef) -> Option<Coord> {
    let start = input.entity.position().unwrap();
    let mut knowledge = input.entity.drawable_knowledge_borrow_mut().unwrap();
    let level_knowledge = knowledge.level_mut_or_insert_size(input.level_id,
                                                             input.spatial_hash.width(),
                                                             input.spatial_hash.height());

    let targets = level_knowledge.sort_targets(start);
    let mut target_idx = 0;

    let mut end = if !targets.is_empty() {
        targets[target_idx]
    } else {
        start
    };

    let mut renderer = input.renderer.borrow_mut();

    loop {

        let overlay = RenderOverlay {
            aim_line: Some(StraightLine::new(start, end)),
        };

        renderer.draw_with_overlay(&overlay);

        if let Some(event) = input_source.next_input() {
            if let Some(control) = map.control(event) {
                if let Some(direction) = control_to_direction(control) {
                    let next_end = end + direction.vector();
                    if renderer.contains_world_coord(next_end) {
                        end = next_end;
                    }
                } else if control == Control::NextTarget {
                    if !targets.is_empty() {
                        target_idx = (target_idx + 1) % targets.len();
                        end = targets[target_idx];
                    }
                } else if control == Control::PrevTarget {
                    if !targets.is_empty() {
                        target_idx = (target_idx + targets.len() - 1) % targets.len();
                        end = targets[target_idx];
                    }
                } else if control == Control::Fire {
                    renderer.draw();
                    return Some(end);
                } else {
                    break;
                }
            }
        }
    }

    renderer.draw();
    None
}

fn get_meta_action(input: BehaviourInput, input_source: InputSourceRef) -> Option<MetaAction> {
    input_source.next_input().and_then(|event| {
        input.entity.control_map().and_then(|map| {
            map.control(event).and_then(|control| {
                match control {
                    Control::Direction(d) => Some(MetaAction::ActionArgs(ActionArgs::Walk(input.entity.id(), d))),
                    Control::Close => {
                        get_direction(map, input_source).map(|d| MetaAction::ActionArgs(ActionArgs::Close(input.entity.id(), d)))
                    }
                    Control::Fire => {
                        aim(input, map, input_source).map(|coord| {
                            let delta = coord - input.entity.position().unwrap();

                            MetaAction::ActionArgs(ActionArgs::FireBullet(input.entity.id(), delta))
                        })
                    }
                    Control::Wait => {
                        Some(MetaAction::ActionArgs(ActionArgs::Null))
                    }
                    Control::Quit => Some(MetaAction::External(External::Quit)),
                    Control::NextTarget => None,
                    Control::PrevTarget => None,
                }
            })
        })
    })
}

<|endoftext|>


<commit_before>use gtk::{
    self, AdjustmentExt, BoxExt, ButtonExt, ContainerExt, LabelExt, ScrolledWindowExt
};
use gtk::{WidgetExt, GtkWindowExt};
use pango;
use sysinfo::{self, Pid, ProcessExt};

use std::cell::RefCell;
use std::fmt;
use std::iter;
use std::rc::Rc;

use graph::{Connecter, Graph};
use notebook::NoteBook;
use utils::{get_main_window, connect_graph, format_number, RotateVec};

#[allow(dead_code)]
pub struct ProcDialog {
    working_directory: gtk::Label,
    memory_usage: gtk::Label,
    cpu_usage: gtk::Label,
    run_time: gtk::Label,
    pub popup: gtk::Window,
    pub pid: Pid,
    notebook: NoteBook,
    ram_usage_history: Rc<RefCell<Graph>>,
    cpu_usage_history: Rc<RefCell<Graph>>,
    memory_peak: RefCell<u64>,
    memory_peak_label: gtk::Label,
}

impl fmt::Debug for ProcDialog {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "ProcDialog {{ pid: {} }}", self.pid)
    }
}

impl ProcDialog {
    pub fn update(&self, process: &sysinfo::Process, start_time: u64) {
        self.working_directory.set_text(&process.cwd().display().to_string());
        let memory = process.memory() << 10; // * 1024
        let memory_s = format_number(memory);
        self.memory_usage.set_text(&memory_s);
        if memory > *self.memory_peak.borrow() {
            *self.memory_peak.borrow_mut() = memory;
            self.memory_peak_label.set_text(&memory_s);
        }
        self.cpu_usage.set_text(&format!("{:.1}%", process.cpu_usage()));
        let running_since = compute_running_since(process, start_time);
        self.run_time.set_text(&format_time(running_since));

        let mut t = self.ram_usage_history.borrow_mut();
        t.data[0].move_start();
        *t.data[0].get_mut(0).expect("cannot get data 0") = process.memory() as f64;
        t.invalidate();
        let mut t = self.cpu_usage_history.borrow_mut();
        t.data[0].move_start();
        *t.data[0].get_mut(0).expect("cannot get data 0") = f64::from(process.cpu_usage());
        t.invalidate();
    }
}

fn format_time(t: u64) -> String {
    format!("{}{}{}{}s",
            {
                let days = t / 86_400;
                if days > 0 {
                    format!("{}d ", days)
                } else {
                    "".to_owned()
                }
            },
            {
                let hours = t / 3_600 % 24;
                if hours > 0 {
                    format!("{}h ", hours)
                } else {
                    "".to_owned()
                }
            },
            {
                let minutes = t / 60 % 60;
                if minutes > 0 {
                    format!("{}m ", minutes)
                } else {
                    "".to_owned()
                }
            },
            t % 60)
}

fn create_and_add_new_label(scroll: &gtk::Box, title: &str, text: &str) -> gtk::Label {
    let horizontal_layout = gtk::Box::new(gtk::Orientation::Horizontal, 0);

    horizontal_layout.set_margin_top(5);
    horizontal_layout.set_margin_bottom(5);
    horizontal_layout.set_margin_end(5);
    horizontal_layout.set_margin_start(5);

    let label = gtk::Label::new(None);
    label.set_justify(gtk::Justification::Left);
    label.set_markup(&format!("{}: ", title));

    let text = gtk::Label::new(Some(text));
    text.set_selectable(true);
    text.set_justify(gtk::Justification::Left);
    text.set_line_wrap(true);
    text.set_line_wrap_mode(pango::WrapMode::Char);

    horizontal_layout.add(&label);
    horizontal_layout.add(&text);
    scroll.add(&horizontal_layout);
    text
}

fn compute_running_since(
    process: &sysinfo::Process,
    running_since: u64,
) -> u64 {
    if running_since > process.start_time() {
        running_since - process.start_time()
    } else {
        process.start_time() - running_since
    }
}

pub fn create_process_dialog(
    process: &sysinfo::Process,
    start_time: u64,
    total_memory: u64,
) -> ProcDialog {
    let mut notebook = NoteBook::new();

    let popup = gtk::Window::new(gtk::WindowType::Toplevel);

    popup.set_title(&format!("Information about {}", process.name()));
    popup.set_transient_for(get_main_window().as_ref());
    popup.set_destroy_with_parent(true);

    //
    // PROCESS INFO TAB
    //
    let scroll = gtk::ScrolledWindow::new(None::<&gtk::Adjustment>, None::<&gtk::Adjustment>);
    let close_button = gtk::Button::new_with_label("Close");
    let vertical_layout = gtk::Box::new(gtk::Orientation::Vertical, 0);
    scroll.set_policy(gtk::PolicyType::Automatic, gtk::PolicyType::Automatic);

    let running_since = compute_running_since(process, start_time);

    let labels = gtk::Box::new(gtk::Orientation::Vertical, 0);

    create_and_add_new_label(&labels, "name", process.name());
    create_and_add_new_label(&labels, "pid", &process.pid().to_string());
    let memory_peak = process.memory() << 10; // * 1024
    let memory_usage = create_and_add_new_label(&labels,
                                                "memory usage",
                                                &format_number(memory_peak));
    let memory_peak_label = create_and_add_new_label(&labels,
                                                     "memory usage peak",
                                                     &format_number(memory_peak));
    let cpu_usage = create_and_add_new_label(&labels,
                                             "cpu usage",
                                             &format!("{:.1}%", process.cpu_usage()));
    let run_time = create_and_add_new_label(&labels,
                                            "Running since",
                                            &format_time(running_since));
    create_and_add_new_label(&labels, "command", &format!("{:?}", process.cmd()));
    create_and_add_new_label(&labels, "executable path", &process.exe().display().to_string());
    let working_directory = create_and_add_new_label(&labels, "current working directory",
                                                     &process.cwd().display().to_string());
    create_and_add_new_label(&labels, "root directory", &process.root().display().to_string());
    let mut text = String::with_capacity(100);
    for env in process.environ() {
        text.push_str(&format!("\n{:?}", env));
    }
    create_and_add_new_label(&labels, "environment", &text);

    scroll.add(&labels);

    vertical_layout.pack_start(&scroll, true, true, 0);
    vertical_layout.pack_start(&close_button, false, true, 0);

    notebook.create_tab("Information", &vertical_layout);

    //
    // GRAPH TAB
    //
    let vertical_layout = gtk::Box::new(gtk::Orientation::Vertical, 0);
    vertical_layout.set_spacing(5);
    vertical_layout.set_margin_top(10);
    vertical_layout.set_margin_bottom(10);
    vertical_layout.set_margin_start(5);
    vertical_layout.set_margin_end(5);
    let scroll = gtk::ScrolledWindow::new(None::<&gtk::Adjustment>, None::<&gtk::Adjustment>);
    let mut cpu_usage_history = Graph::new(Some(100.), false);
    let mut ram_usage_history = Graph::new(Some(total_memory as f64), true);

    cpu_usage_history.set_display_labels(false);
    ram_usage_history.set_display_labels(false);

    cpu_usage_history.push(RotateVec::new(iter::repeat(0f64).take(61).collect()),
                           "", None);
    cpu_usage_history.set_label_callbacks(Some(Box::new(|_| {
        ["100".to_string(), "50".to_string(), "0".to_string(), "%".to_string()]
    })));
    vertical_layout.add(&gtk::Label::new(Some("Process usage")));
    cpu_usage_history.attach_to(&vertical_layout);
    cpu_usage_history.invalidate();
    let cpu_usage_history = connect_graph(cpu_usage_history);

    ram_usage_history.push(RotateVec::new(iter::repeat(0f64).take(61).collect()),
                           "", None);
    ram_usage_history.set_label_callbacks(Some(Box::new(|v| {
        if v < 100_000. {
            [v.to_string(),
             format!("{}", v / 2.),
             "0".to_string(),
             "kB".to_string()]
        } else if v < 10_000_000. {
            [format!("{:.1}", v / 1_024f64),
             format!("{:.1}", v / 2_048f64),
             "0".to_string(),
             "MB".to_string()]
        } else if v < 10_000_000_000. {
            [format!("{:.1}", v / 1_048_576f64),
             format!("{:.1}", v / 2_097_152f64),
             "0".to_string(),
             "GB".to_string()]
        } else {
            [format!("{:.1}", v / 1_073_741_824f64),
             format!("{:.1}", v / 1_073_741_824f64),
             "0".to_string(),
             "TB".to_string()]
        }
    })));
    vertical_layout.add(&gtk::Label::new(Some("Memory usage")));
    ram_usage_history.attach_to(&vertical_layout);
    ram_usage_history.invalidate();
    let ram_usage_history = connect_graph(ram_usage_history);

    scroll.add(&vertical_layout);
    scroll.connect_show(clone!(ram_usage_history, cpu_usage_history => move |_| {
        ram_usage_history.borrow().show_all();
        cpu_usage_history.borrow().show_all();
    }));
    notebook.create_tab("Resources usage", &scroll);

    /*let area = popup.get_content_area();
    area.set_margin_top(0);
    area.set_margin_bottom(0);
    area.set_margin_start(0);
    area.set_margin_end(0);
    area.pack_start(&notebook.notebook, true, true, 0);*/
    popup.add(&notebook.notebook);
    // To silence the annoying warning:
    // "(.:2257): Gtk-WARNING **: Allocating size to GtkWindow 0x7f8a31038290 without
    // calling gtk_widget_get_preferred_width/height(). How does the code know the size to
    // allocate?"
    popup.get_preferred_width();
    popup.set_size_request(500, 600);

    close_button.connect_clicked(clone!(popup => move |_| {
        popup.destroy();
    }));
    popup.set_resizable(true);
    popup.show_all();

    if let Some(adjust) = scroll.get_vadjustment() {
        adjust.set_value(0.);
        scroll.set_vadjustment(&adjust);
    }
    ram_usage_history.connect_to_window_events();
    cpu_usage_history.connect_to_window_events();

    ProcDialog {
        working_directory,
        memory_usage,
        cpu_usage,
        run_time,
        popup,
        pid: process.pid(),
        notebook,
        ram_usage_history,
        cpu_usage_history,
        memory_peak: RefCell::new(memory_peak),
        memory_peak_label,
    }
}
<commit_msg>Improve process dialog graphs<commit_after>use gtk::{
    self, AdjustmentExt, BoxExt, ButtonExt, ContainerExt, LabelExt, ScrolledWindowExt
};
use gtk::{WidgetExt, GtkWindowExt};
use pango;
use sysinfo::{self, Pid, ProcessExt};

use std::cell::RefCell;
use std::fmt;
use std::iter;
use std::rc::Rc;

use graph::{Connecter, Graph};
use notebook::NoteBook;
use utils::{get_main_window, connect_graph, format_number, RotateVec};

#[allow(dead_code)]
pub struct ProcDialog {
    working_directory: gtk::Label,
    memory_usage: gtk::Label,
    cpu_usage: gtk::Label,
    run_time: gtk::Label,
    pub popup: gtk::Window,
    pub pid: Pid,
    notebook: NoteBook,
    ram_usage_history: Rc<RefCell<Graph>>,
    cpu_usage_history: Rc<RefCell<Graph>>,
    memory_peak: RefCell<u64>,
    memory_peak_label: gtk::Label,
}

impl fmt::Debug for ProcDialog {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "ProcDialog {{ pid: {} }}", self.pid)
    }
}

impl ProcDialog {
    pub fn update(&self, process: &sysinfo::Process, start_time: u64) {
        self.working_directory.set_text(&process.cwd().display().to_string());
        let memory = process.memory() << 10; // * 1024
        let memory_s = format_number(memory);
        self.memory_usage.set_text(&memory_s);
        if memory > *self.memory_peak.borrow() {
            *self.memory_peak.borrow_mut() = memory;
            self.memory_peak_label.set_text(&memory_s);
        }
        self.cpu_usage.set_text(&format!("{:.1}%", process.cpu_usage()));
        let running_since = compute_running_since(process, start_time);
        self.run_time.set_text(&format_time(running_since));

        let mut t = self.ram_usage_history.borrow_mut();
        t.data[0].move_start();
        *t.data[0].get_mut(0).expect("cannot get data 0") = process.memory() as f64;
        t.invalidate();
        let mut t = self.cpu_usage_history.borrow_mut();
        t.data[0].move_start();
        *t.data[0].get_mut(0).expect("cannot get data 0") = f64::from(process.cpu_usage());
        t.invalidate();
    }
}

fn format_time(t: u64) -> String {
    format!("{}{}{}{}s",
            {
                let days = t / 86_400;
                if days > 0 {
                    format!("{}d ", days)
                } else {
                    "".to_owned()
                }
            },
            {
                let hours = t / 3_600 % 24;
                if hours > 0 {
                    format!("{}h ", hours)
                } else {
                    "".to_owned()
                }
            },
            {
                let minutes = t / 60 % 60;
                if minutes > 0 {
                    format!("{}m ", minutes)
                } else {
                    "".to_owned()
                }
            },
            t % 60)
}

fn create_and_add_new_label(scroll: &gtk::Box, title: &str, text: &str) -> gtk::Label {
    let horizontal_layout = gtk::Box::new(gtk::Orientation::Horizontal, 0);

    horizontal_layout.set_margin_top(5);
    horizontal_layout.set_margin_bottom(5);
    horizontal_layout.set_margin_end(5);
    horizontal_layout.set_margin_start(5);

    let label = gtk::Label::new(None);
    label.set_justify(gtk::Justification::Left);
    label.set_markup(&format!("{}: ", title));

    let text = gtk::Label::new(Some(text));
    text.set_selectable(true);
    text.set_justify(gtk::Justification::Left);
    text.set_line_wrap(true);
    text.set_line_wrap_mode(pango::WrapMode::Char);

    horizontal_layout.add(&label);
    horizontal_layout.add(&text);
    scroll.add(&horizontal_layout);
    text
}

fn compute_running_since(
    process: &sysinfo::Process,
    running_since: u64,
) -> u64 {
    if running_since > process.start_time() {
        running_since - process.start_time()
    } else {
        process.start_time() - running_since
    }
}

pub fn create_process_dialog(
    process: &sysinfo::Process,
    start_time: u64,
    total_memory: u64,
) -> ProcDialog {
    let mut notebook = NoteBook::new();

    let popup = gtk::Window::new(gtk::WindowType::Toplevel);

    popup.set_title(&format!("Information about {}", process.name()));
    popup.set_transient_for(get_main_window().as_ref());
    popup.set_destroy_with_parent(true);

    //
    // PROCESS INFO TAB
    //
    let scroll = gtk::ScrolledWindow::new(None::<&gtk::Adjustment>, None::<&gtk::Adjustment>);
    let close_button = gtk::Button::new_with_label("Close");
    let vertical_layout = gtk::Box::new(gtk::Orientation::Vertical, 0);
    scroll.set_policy(gtk::PolicyType::Automatic, gtk::PolicyType::Automatic);

    let running_since = compute_running_since(process, start_time);

    let labels = gtk::Box::new(gtk::Orientation::Vertical, 0);

    create_and_add_new_label(&labels, "name", process.name());
    create_and_add_new_label(&labels, "pid", &process.pid().to_string());
    let memory_peak = process.memory() << 10; // * 1024
    let memory_usage = create_and_add_new_label(&labels,
                                                "memory usage",
                                                &format_number(memory_peak));
    let memory_peak_label = create_and_add_new_label(&labels,
                                                     "memory usage peak",
                                                     &format_number(memory_peak));
    let cpu_usage = create_and_add_new_label(&labels,
                                             "cpu usage",
                                             &format!("{:.1}%", process.cpu_usage()));
    let run_time = create_and_add_new_label(&labels,
                                            "Running since",
                                            &format_time(running_since));
    create_and_add_new_label(&labels, "command", &format!("{:?}", process.cmd()));
    create_and_add_new_label(&labels, "executable path", &process.exe().display().to_string());
    let working_directory = create_and_add_new_label(&labels, "current working directory",
                                                     &process.cwd().display().to_string());
    create_and_add_new_label(&labels, "root directory", &process.root().display().to_string());
    let mut text = String::with_capacity(100);
    for env in process.environ() {
        text.push_str(&format!("\n{:?}", env));
    }
    create_and_add_new_label(&labels, "environment", &text);

    scroll.add(&labels);

    vertical_layout.pack_start(&scroll, true, true, 0);
    vertical_layout.pack_start(&close_button, false, true, 0);

    notebook.create_tab("Information", &vertical_layout);

    //
    // GRAPH TAB
    //
    let vertical_layout = gtk::Box::new(gtk::Orientation::Vertical, 0);
    vertical_layout.set_spacing(5);
    vertical_layout.set_margin_top(10);
    vertical_layout.set_margin_bottom(10);
    vertical_layout.set_margin_start(5);
    vertical_layout.set_margin_end(5);
    let scroll = gtk::ScrolledWindow::new(None::<&gtk::Adjustment>, None::<&gtk::Adjustment>);
    let mut cpu_usage_history = Graph::new(Some(100.), false); // In case a process uses more than 100%
    let mut ram_usage_history = Graph::new(Some(total_memory as f64), false);

    cpu_usage_history.set_display_labels(false);
    ram_usage_history.set_display_labels(false);

    cpu_usage_history.push(RotateVec::new(iter::repeat(0f64).take(61).collect()),
                           "", None);
    cpu_usage_history.set_label_callbacks(Some(Box::new(|v| {
        if v > 100. {
            let nb = v.ceil() as u64;
            [nb.to_string(), (nb / 2).to_string(), "0".to_string(), "%".to_string()]
        } else {
            ["100".to_string(), "50".to_string(), "0".to_string(), "%".to_string()]
        }
    })));
    vertical_layout.add(&gtk::Label::new(Some("Process usage")));
    cpu_usage_history.attach_to(&vertical_layout);
    cpu_usage_history.invalidate();
    let cpu_usage_history = connect_graph(cpu_usage_history);

    ram_usage_history.push(RotateVec::new(iter::repeat(0f64).take(61).collect()),
                           "", None);
    ram_usage_history.set_label_callbacks(Some(Box::new(|v| {
        if v < 100_000. {
            [v.to_string(),
             format!("{}", v / 2.),
             "0".to_string(),
             "kB".to_string()]
        } else if v < 10_000_000. {
            [format!("{:.1}", v / 1_024f64),
             format!("{:.1}", v / 2_048f64),
             "0".to_string(),
             "MB".to_string()]
        } else if v < 10_000_000_000. {
            [format!("{:.1}", v / 1_048_576f64),
             format!("{:.1}", v / 2_097_152f64),
             "0".to_string(),
             "GB".to_string()]
        } else {
            [format!("{:.1}", v / 1_073_741_824f64),
             format!("{:.1}", v / 1_073_741_824f64),
             "0".to_string(),
             "TB".to_string()]
        }
    })));
    vertical_layout.add(&gtk::Label::new(Some("Memory usage")));
    ram_usage_history.attach_to(&vertical_layout);
    ram_usage_history.invalidate();
    let ram_usage_history = connect_graph(ram_usage_history);

    scroll.add(&vertical_layout);
    scroll.connect_show(clone!(ram_usage_history, cpu_usage_history => move |_| {
        ram_usage_history.borrow().show_all();
        cpu_usage_history.borrow().show_all();
    }));
    notebook.create_tab("Resources usage", &scroll);

    popup.add(&notebook.notebook);
    // To silence the annoying warning:
    // "(.:2257): Gtk-WARNING **: Allocating size to GtkWindow 0x7f8a31038290 without
    // calling gtk_widget_get_preferred_width/height(). How does the code know the size to
    // allocate?"
    popup.get_preferred_width();
    popup.set_size_request(500, 600);

    close_button.connect_clicked(clone!(popup => move |_| {
        popup.destroy();
    }));
    popup.set_resizable(true);
    popup.show_all();

    if let Some(adjust) = scroll.get_vadjustment() {
        adjust.set_value(0.);
        scroll.set_vadjustment(&adjust);
    }
    ram_usage_history.connect_to_window_events();
    cpu_usage_history.connect_to_window_events();

    ProcDialog {
        working_directory,
        memory_usage,
        cpu_usage,
        run_time,
        popup,
        pid: process.pid(),
        notebook,
        ram_usage_history,
        cpu_usage_history,
        memory_peak: RefCell::new(memory_peak),
        memory_peak_label,
    }
}
<|endoftext|>


<commit_before>// Copyright 2016 Pierre Talbot (IRCAM)

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

//     http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use logic::*;
use propagators::*;
use term::*;
use concept::*;

pub struct Cumulative<VStore>
{
  starts: Vec<Var<VStore>>,
  durations: Vec<Var<VStore>>,
  resources: Vec<Var<VStore>>,
  capacity: Var<VStore>,
  intermediate: Vec<Vec<usize>>, // Given intermediate[j][i], if i left-overlap j, then it contains the number of resources used by i.
}

impl<VStore> Cumulative<VStore>
{
  pub fn new(starts: Vec<Var<VStore>>, durations: Vec<Var<VStore>>,
   resources: Vec<Var<VStore>>, capacity: Var<VStore>) -> Self
  {
    let tasks = starts.len();
    assert_eq!(tasks, durations.len());
    assert_eq!(tasks, resources.len());
    Cumulative {
      starts: starts,
      durations: durations,
      resources: resources,
      capacity: capacity,
      intermediate: vec![]
    }
  }
}

impl<VStore, Domain, Bound> Cumulative<VStore> where
  VStore: VStoreConcept<Item=Domain> + 'static,
  Domain: IntDomain<Item=Bound> + 'static,
  Bound: IntBound + 'static,
{
  // Decomposition described in `Why cumulative decomposition is not as bad as it sounds`, Schutt and al., 2009.
  // Intuitively, it says that for each task j, the sum of the resources used by the other tasks overlapping with j must not exceed the capacity.
  // forall( j in tasks ) (
  //   c >= r[j] + sum( i in tasks where i != j ) (
  //     bool2int( s[i] <= s[j] /\ s[j] < s[i] + d[i] ) * r[i]));
  pub fn join<CStore>(&mut self, vstore: &mut VStore, cstore: &mut CStore) where
    CStore: IntCStore<VStore> + 'static
  {
    let tasks = self.starts.len();
    // forall( j in tasks ) (...)
    for j in 0..tasks {
      let mut resource_vars = vec![];
      self.intermediate.push(vec![]);
      for i in 0..tasks {
        if i != j {
          // conj <-> s[i] <= s[j] /\ s[j] < s[i] + d[i]
          let conj = box Conjunction::new(vec![
            // s[i] <= s[j]
            box x_leq_y(self.start_at(i), self.start_at(j)),
            // s[j] < s[i] + d[i]
            box XLessYPlusZ::new(self.start_at(j), self.start_at(i), self.duration_at(i))]);

          // bi <-> conj
          let bi = Boolean::new(vstore);
          let equiv = equivalence(box bi.clone(), conj);
          cstore.alloc(equiv);

          // r = bi * r[i]
          let ri = self.resource_at(i);
          let ri_ub = ri.read(vstore).upper();
          let r_dom = Domain::new(Bound::zero(), ri_ub);
          // let hole = Domain::new(Bound::one(), ri_ub.clone() - Bound::one());
          let r = vstore.alloc(r_dom);
          self.intermediate.last_mut().unwrap().push(r.index());
          let r = box r as Var<VStore>;
          cstore.alloc(box XEqYMulZ::new(r.bclone(), box bi, ri));
          resource_vars.push(r);
        }
      }
      //  sum( i in tasks where i != j )(...)
      let sum = box Sum::new(resource_vars);
      // c >= r[j] + sum
      cstore.alloc(box x_geq_y_plus_z(self.capacity_var(), self.resource_at(j), sum));
    }
  }

  pub fn intermediate_vars(&self) -> Vec<Vec<usize>> {
    self.intermediate.clone()
  }

  fn start_at(&self, i: usize) -> Var<VStore> {
    self.starts[i].bclone()
  }
  fn duration_at(&self, i: usize) -> Var<VStore> {
    self.durations[i].bclone()
  }
  fn resource_at(&self, i: usize) -> Var<VStore> {
    self.resources[i].bclone()
  }
  fn capacity_var(&self) -> Var<VStore> {
    self.capacity.bclone()
  }
}

// #[cfg(test)]
// mod test {
//   use super::*;
//   use kernel::*;
//   use kernel::Trilean::*;
//   use variable::VStoreCopy;
//   use propagation::CStoreFD;
//   use interval::interval::*;
//   use interval::ops::Range;
//   use gcollections::ops::*;
//   use model::*;

//   type Dom = Interval<i32>;
//   type VStoreFD = VStoreCopy<Dom>;

//   struct CumulativeTest {
//     starts: Vec<Interval<i32>>,
//     durations: Vec<Interval<i32>>,
//     resources: Vec<Interval<i32>>,
//     capacity: Interval<i32>,
//   }

//   impl CumulativeTest {
//     fn new(starts: Vec<Interval<i32>>, durations: Vec<Interval<i32>>,
//       resources: Vec<Interval<i32>>, capacity: Interval<i32>) -> Self
//     {
//       CumulativeTest {
//         starts: starts,
//         durations: durations,
//         resources: resources,
//         capacity: capacity
//       }
//     }

//     fn new_assignment(starts: Vec<i32>, durations: Vec<i32>,
//       resources: Vec<i32>, capacity: i32) -> Self
//     {
//       CumulativeTest::new(
//         starts.into_iter().map(|s| Interval::new(s, s)).collect(),
//         durations.into_iter().map(|d| Interval::new(d, d)).collect(),
//         resources.into_iter().map(|r| Interval::new(r, r)).collect(),
//         Interval::new(capacity, capacity)
//       )
//     }

//     fn instantiate(self, model: &mut Model, vstore: &mut VStoreFD,
//       cstore: &mut CStoreFD<VStoreFD>)
//     {
//       model.open_group("s");
//       let starts = self.starts.into_iter()
//         .map(|s| box model.alloc_var(vstore, s)).collect();
//       model.close_group();
//       model.open_group("d");
//       let durations = self.durations.into_iter()
//         .map(|d| box model.alloc_var(vstore, d)).collect();
//       model.close_group();
//       model.open_group("r");
//       let resources = self.resources.into_iter()
//         .map(|r| box model.alloc_var(vstore, r)).collect();
//       model.close_group();
//       let capacity = box vstore.alloc(self.capacity);
//       model.register_var(capacity.index(), String::from("c"));

//       let mut cumulative = Cumulative::new(starts, durations, resources, capacity);
//       cumulative.join(vstore, cstore);
//     }

//     fn test(self, test_num: usize, before: Trilean, after: Trilean, propagate_success: bool) {
//       println!("Test number {}", test_num);
//       let mut vstore = VStoreFD::empty();
//       let mut cstore = CStoreFD::empty();
//       let mut model = Model::new();
//       self.instantiate(&mut model, &mut vstore, &mut cstore);
//       cstore.display(&(model, vstore.clone()));
//       assert_eq!(cstore.is_subsumed(&vstore), before);
//       assert_eq!(cstore.propagate(&mut vstore), propagate_success);
//       assert_eq!(cstore.is_subsumed(&vstore), after);
//     }

//     fn test_assignment(self, test_num: usize, expected: Trilean) {
//       let propagate = match expected {
//         True => true,
//         False => false,
//         Unknown => panic!("Assignment must always be either subsumed or refuted.")
//       };
//       /// Unknown because cumulative introduces new variables not fixed.
//       self.test(test_num, Unknown, expected, propagate);
//     }
//   }

// #[test]
// fn disjunctive_test() {
//   CumulativeTest::new_assignment(
//     vec![0,0], vec![0,0], vec![1,1], 1
//   )
//   .test_assignment(1, True);
// }

  // #[test]
  // fn cumulative_assignment_test() {
  //   // The task 2 and 3 overlaps and consume 4 resources altogether.
  //   let test = CumulativeTest::new_assignment(
  //     vec![0,1,4], vec![3,4,2], vec![1,2,2], 3);
  //   test.test(1, Unknown, False, false);

  //   // We can delay the task 3 to fix the problem.
  //   let test = CumulativeTest::new_assignment(
  //     vec![0,1,5], vec![3,4,2], vec![1,2,2], 3);
  //   test.test(2, Unknown, True, true);

  //   // Another possibility is to reduce the resource of task 3.
  //   let test = CumulativeTest::new_assignment(
  //     vec![0,1,4], vec![3,4,2], vec![1,2,1], 3);
  //   test.test(3, Unknown, True, true);

  //   // Or augment the total amount of resources available.
  //   let test = CumulativeTest::new_assignment(
  //     vec![0,1,4], vec![3,4,2], vec![1,2,2], 4);
  //   test.test(4, Unknown, True, true);

  //   // Or reduce the duration of task 2.
  //   let test = CumulativeTest::new_assignment(
  //     vec![0,1,4], vec![3,3,2], vec![1,2,2], 3);
  //   test.test(4, Unknown, True, true);
  // }

  // #[test]
  // fn cumulative_test() {
  //   let mut test = CumulativeTest::new_assignment(
  //     vec![0,1,4], vec![3,4,2], vec![1,2,2], 3);
  //   // Widden the start date of task 1, should fail anyway.
  //   test.starts[0] = Interval::new(0,4);
  //   test.test(1, Unknown, False, false);

  //   let mut test = CumulativeTest::new_assignment(
  //     vec![0,1,4], vec![3,4,2], vec![1,2,2], 3);
  //   // Widden the start date of task 2, succeed when schedule at start=0.
  //   test.starts[1] = Interval::new(0,1);
  //   test.test(2, Unknown, Unknown, true);

  //   let mut test = CumulativeTest::new_assignment(
  //     vec![0,1,4], vec![3,4,2], vec![1,2,2], 3);
  //   // Widden the start date of task 3, succeed when schedule at start=5.
  //   test.starts[2] = Interval::new(4,5);
  //   test.test(3, Unknown, Unknown, true);
  // }
// }
<commit_msg>[Cumulative] Fix a bug when the arrays have a single element and add additional tests for constant domains.<commit_after>// Copyright 2016 Pierre Talbot (IRCAM)

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

//     http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use logic::*;
use propagators::*;
use term::*;
use concept::*;

pub struct Cumulative<VStore>
{
  starts: Vec<Var<VStore>>,
  durations: Vec<Var<VStore>>,
  resources: Vec<Var<VStore>>,
  capacity: Var<VStore>,
  intermediate: Vec<Vec<usize>>, // Given intermediate[j][i], if i left-overlap j, then it contains the number of resources used by i.
}

impl<VStore> Cumulative<VStore>
{
  pub fn new(starts: Vec<Var<VStore>>, durations: Vec<Var<VStore>>,
   resources: Vec<Var<VStore>>, capacity: Var<VStore>) -> Self
  {
    let tasks = starts.len();
    assert_eq!(tasks, durations.len());
    assert_eq!(tasks, resources.len());
    Cumulative {
      starts: starts,
      durations: durations,
      resources: resources,
      capacity: capacity,
      intermediate: vec![]
    }
  }
}

impl<VStore, Domain, Bound> Cumulative<VStore> where
  VStore: VStoreConcept<Item=Domain> + 'static,
  Domain: IntDomain<Item=Bound> + 'static,
  Bound: IntBound + 'static,
{
  // Decomposition described in `Why cumulative decomposition is not as bad as it sounds`, Schutt and al., 2009.
  // Intuitively, it says that for each task j, the sum of the resources used by the other tasks overlapping with j must not exceed the capacity.
  // forall( j in tasks ) (
  //   c >= r[j] + sum( i in tasks where i != j ) (
  //     bool2int( s[i] <= s[j] /\ s[j] < s[i] + d[i] ) * r[i]));
  pub fn join<CStore>(&mut self, vstore: &mut VStore, cstore: &mut CStore) where
    CStore: IntCStore<VStore> + 'static
  {
    let tasks = self.starts.len();
    // Special case where only one task needs to be scheduled.
    if tasks == 1 {
      // c >= r[j]
      cstore.alloc(box x_geq_y(self.capacity_var(), self.resource_at(0)));
    }
    else {
      // forall( j in tasks ) (...)
      for j in 0..tasks {
        let mut resource_vars = vec![];
        self.intermediate.push(vec![]);
        for i in 0..tasks {
          if i != j {
            // conj <-> s[i] <= s[j] /\ s[j] < s[i] + d[i]
            let conj = box Conjunction::new(vec![
              // s[i] <= s[j]
              box x_leq_y(self.start_at(i), self.start_at(j)),
              // s[j] < s[i] + d[i]
              box XLessYPlusZ::new(self.start_at(j), self.start_at(i), self.duration_at(i))]);

            // bi <-> conj
            let bi = Boolean::new(vstore);
            let equiv = equivalence(box bi.clone(), conj);
            cstore.alloc(equiv);

            // r = bi * r[i]
            let ri = self.resource_at(i);
            let ri_ub = ri.read(vstore).upper();
            let r_dom = Domain::new(Bound::zero(), ri_ub);
            // let hole = Domain::new(Bound::one(), ri_ub.clone() - Bound::one());
            let r = vstore.alloc(r_dom);
            self.intermediate.last_mut().unwrap().push(r.index());
            let r = box r as Var<VStore>;
            cstore.alloc(box XEqYMulZ::new(r.bclone(), box bi, ri));
            resource_vars.push(r);
          }
        }
        //  sum( i in tasks where i != j )(...)
        let sum = box Sum::new(resource_vars);
        // c >= r[j] + sum
        cstore.alloc(box x_geq_y_plus_z(self.capacity_var(), self.resource_at(j), sum));
      }
    }
  }

  pub fn intermediate_vars(&self) -> Vec<Vec<usize>> {
    self.intermediate.clone()
  }

  fn start_at(&self, i: usize) -> Var<VStore> {
    self.starts[i].bclone()
  }
  fn duration_at(&self, i: usize) -> Var<VStore> {
    self.durations[i].bclone()
  }
  fn resource_at(&self, i: usize) -> Var<VStore> {
    self.resources[i].bclone()
  }
  fn capacity_var(&self) -> Var<VStore> {
    self.capacity.bclone()
  }
}

#[cfg(test)]
mod test {
  use super::*;
  use kernel::*;
  use kernel::Trilean::*;
  use variable::VStoreCopy;
  use propagation::CStoreFD;
  use interval::interval::*;
  use interval::ops::Range;
  use gcollections::ops::*;
  use model::*;
  use propagation::ops::Subsumption;

  type Dom = Interval<i32>;
  type VStoreFD = VStoreCopy<Dom>;

  struct CumulativeTest {
    starts: Vec<Interval<i32>>,
    durations: Vec<Interval<i32>>,
    resources: Vec<Interval<i32>>,
    capacity: Interval<i32>,
  }

  impl CumulativeTest {
    fn new(starts: Vec<Interval<i32>>, durations: Vec<Interval<i32>>,
      resources: Vec<Interval<i32>>, capacity: Interval<i32>) -> Self
    {
      CumulativeTest {
        starts: starts,
        durations: durations,
        resources: resources,
        capacity: capacity
      }
    }

    fn new_assignment(starts: Vec<i32>, durations: Vec<i32>,
      resources: Vec<i32>, capacity: i32) -> Self
    {
      CumulativeTest::new(
        starts.into_iter().map(|s| Interval::new(s, s)).collect(),
        durations.into_iter().map(|d| Interval::new(d, d)).collect(),
        resources.into_iter().map(|r| Interval::new(r, r)).collect(),
        Interval::new(capacity, capacity)
      )
    }

    fn create_var(dom: Interval<i32>, model: &mut Model,
      vstore: &mut VStoreFD, constant: bool) -> Var<VStoreFD>
    {
      if dom.is_singleton() && constant {
        box Constant::new(dom.lower())
      }
      else {
        model.alloc_var(vstore, dom)
      }
    }

    fn instantiate(self, model: &mut Model, vstore: &mut VStoreFD,
      cstore: &mut CStoreFD<VStoreFD>, constant: bool)
    {
      model.open_group("s");
      let starts = self.starts.into_iter()
        .map(|s| Self::create_var(s,model,vstore,constant)).collect();
      model.close_group();
      model.open_group("d");
      let durations = self.durations.into_iter()
        .map(|d| Self::create_var(d,model,vstore,constant)).collect();
      model.close_group();
      model.open_group("r");
      let resources = self.resources.into_iter()
        .map(|r| Self::create_var(r,model,vstore,constant)).collect();
      model.close_group();
      let capacity = box vstore.alloc(self.capacity);
      model.register_var(capacity.index(), String::from("c"));

      let mut cumulative = Cumulative::new(starts, durations, resources, capacity);
      cumulative.join(vstore, cstore);
    }

    // The boolean "constant" indicates if we transform the singleton domains into constant terms or not.
    fn test(self, test_num: usize, before: Trilean, after: Trilean, constant: bool) {
      println!("Test number {}", test_num);
      let mut vstore = VStoreFD::empty();
      let mut cstore = CStoreFD::empty();
      let mut model = Model::new();
      self.instantiate(&mut model, &mut vstore, &mut cstore, constant);
      cstore.display(&(model, vstore.clone()));
      assert_eq!(cstore.is_subsumed(&vstore), before);
      assert_eq!(cstore.consistency(&mut vstore), after);
      assert_eq!(cstore.is_subsumed(&vstore), after);
    }

    fn test_assignment(self, test_num: usize, expected: Trilean, constant: bool) {
      // Unknown because cumulative introduces new variables not fixed.
      self.test(test_num, Unknown, expected, constant);
    }
  }

  #[test]
  fn disjunctive_test() {
    CumulativeTest::new_assignment(
      vec![0,0], vec![0,0], vec![1,1], 1
    )
    .test_assignment(1, True, false);
  }

  #[test]
  fn singleton_task() {
    CumulativeTest::new_assignment(vec![0], vec![0], vec![1], 1)
      .test(1, True, True, false);

    CumulativeTest::new_assignment(vec![0], vec![0], vec![1], 1)
      .test(1, True, True, true);
  }

  #[test]
  fn cumulative_assignment_test() {
    let constant = false;
    // The task 2 and 3 overlaps and consume 4 resources altogether.
    let test = CumulativeTest::new_assignment(
      vec![0,1,4], vec![3,4,2], vec![1,2,2], 3);
    test.test(1, Unknown, False, constant);

    // We can delay the task 3 to fix the problem.
    let test = CumulativeTest::new_assignment(
      vec![0,1,5], vec![3,4,2], vec![1,2,2], 3);
    test.test(2, Unknown, True, constant);

    // Another possibility is to reduce the resource of task 3.
    let test = CumulativeTest::new_assignment(
      vec![0,1,4], vec![3,4,2], vec![1,2,1], 3);
    test.test(3, Unknown, True, constant);

    // Or augment the total amount of resources available.
    let test = CumulativeTest::new_assignment(
      vec![0,1,4], vec![3,4,2], vec![1,2,2], 4);
    test.test(4, Unknown, True, constant);

    // Or reduce the duration of task 2.
    let test = CumulativeTest::new_assignment(
      vec![0,1,4], vec![3,3,2], vec![1,2,2], 3);
    test.test(4, Unknown, True, constant);
  }

  #[test]
  fn cumulative_test_constant() {
    cumulative_test_param(true);
  }

  #[test]
  fn cumulative_test_variable() {
    cumulative_test_param(false);
  }

  fn cumulative_test_param(constant: bool) {
    let mut test = CumulativeTest::new_assignment(
      vec![0,1,4], vec![3,4,2], vec![1,2,2], 3);
    // Widden the start date of task 1, should fail anyway.
    test.starts[0] = Interval::new(0,4);
    test.test(1, Unknown, False, constant);

    let mut test = CumulativeTest::new_assignment(
      vec![0,1,4], vec![3,4,2], vec![1,2,2], 3);
    // Widden the start date of task 2, succeed when schedule at start=0.
    test.starts[1] = Interval::new(0,1);
    test.test(2, Unknown, Unknown, constant);

    let mut test = CumulativeTest::new_assignment(
      vec![0,1,4], vec![3,4,2], vec![1,2,2], 3);
    // Widden the start date of task 3, succeed when schedule at start=5.
    test.starts[2] = Interval::new(4,5);
    test.test(3, Unknown, Unknown, constant);
  }
}
<|endoftext|>