Chris@168: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
Chris@168: 
Chris@168: /*
Chris@168:     Sonic Visualiser
Chris@168:     An audio file viewer and annotation editor.
Chris@168:     Centre for Digital Music, Queen Mary, University of London.
Chris@202:     This file copyright 2006 QMUL.
Chris@168:     
Chris@168:     This program is free software; you can redistribute it and/or
Chris@168:     modify it under the terms of the GNU General Public License as
Chris@168:     published by the Free Software Foundation; either version 2 of the
Chris@168:     License, or (at your option) any later version.  See the file
Chris@168:     COPYING included with this distribution for more information.
Chris@168: */
Chris@168: 
Chris@168: #include "StorageAdviser.h"
Chris@168: 
Chris@168: #include "Exceptions.h"
Chris@168: #include "TempDirectory.h"
Chris@168: 
Chris@168: #include "system/System.h"
Chris@168: 
Chris@168: #include <iostream>
Chris@168: 
Chris@1276: QString
Chris@1276: StorageAdviser::criteriaToString(int criteria)
Chris@1276: {
Chris@1276:     QStringList labels;
Chris@1276:     if (criteria & SpeedCritical) labels.push_back("SpeedCritical");
Chris@1276:     if (criteria & PrecisionCritical) labels.push_back("PrecisionCritical");
Chris@1276:     if (criteria & LongRetentionLikely) labels.push_back("LongRetentionLikely");
Chris@1276:     if (criteria & FrequentLookupLikely) labels.push_back("FrequentLookupLikely");
Chris@1276:     if (labels.empty()) return "None";
Chris@1276:     else return labels.join("+");
Chris@1276: }
Chris@1276: 
Chris@1276: QString
Chris@1276: StorageAdviser::recommendationToString(int recommendation)
Chris@1276: {
Chris@1276:     QStringList labels;
Chris@1276:     if (recommendation & UseMemory) labels.push_back("UseMemory");
Chris@1276:     if (recommendation & PreferMemory) labels.push_back("PreferMemory");
Chris@1276:     if (recommendation & PreferDisc) labels.push_back("PreferDisc");
Chris@1276:     if (recommendation & UseDisc) labels.push_back("UseDisc");
Chris@1276:     if (recommendation & ConserveSpace) labels.push_back("ConserveSpace");
Chris@1276:     if (recommendation & UseAsMuchAsYouLike) labels.push_back("UseAsMuchAsYouLike");
Chris@1276:     if (labels.empty()) return "None";
Chris@1276:     else return labels.join("+");
Chris@1276: }
Chris@1276: 
Chris@1276: QString
Chris@1276: StorageAdviser::storageStatusToString(StorageStatus status)
Chris@1276: {
Chris@1276:     if (status == Insufficient) return "Insufficient";
Chris@1276:     if (status == Marginal) return "Marginal";
Chris@1276:     if (status == Sufficient) return "Sufficient";
Chris@1276:     return "Unknown";
Chris@1276: }
Chris@374: 
Chris@1038: size_t StorageAdviser::m_discPlanned = 0;
Chris@1038: size_t StorageAdviser::m_memoryPlanned = 0;
Chris@205: 
Chris@168: StorageAdviser::Recommendation
Chris@411: StorageAdviser::m_baseRecommendation = StorageAdviser::NoRecommendation;
Chris@411: 
Chris@411: StorageAdviser::Recommendation
Chris@168: StorageAdviser::recommend(Criteria criteria,
Chris@1429:                           size_t minimumSize,
Chris@1429:                           size_t maximumSize)
Chris@168: {
Chris@1276:     SVDEBUG << "StorageAdviser::recommend: criteria " << criteria
Chris@1276:             << " (" + criteriaToString(criteria) + ")"
Chris@1276:             << ", minimumSize " << minimumSize
Chris@1276:             << ", maximumSize " << maximumSize << endl;
Chris@170: 
Chris@411:     if (m_baseRecommendation != NoRecommendation) {
Chris@1276:         SVDEBUG << "StorageAdviser::recommend: Returning fixed recommendation "
Chris@1276:                 << m_baseRecommendation << " ("
Chris@1276:                 << recommendationToString(m_baseRecommendation) << ")" << endl;
Chris@411:         return m_baseRecommendation; // for now
Chris@411:     }
Chris@411: 
Chris@541:     QString path;
Chris@541:     try {
Chris@541:         path = TempDirectory::getInstance()->getPath();
Chris@1465:     } catch (const std::exception &e) {
Chris@1276:         SVDEBUG << "StorageAdviser::recommend: ERROR: Failed to get temporary directory path: " << e.what() << endl;
Chris@1276:         int r = UseMemory | ConserveSpace;
Chris@1276:         SVDEBUG << "StorageAdviser: returning fallback " << r
Chris@1276:                 << " (" << recommendationToString(r) << ")" << endl;
Chris@1276:         return Recommendation(r);
Chris@541:     }
Chris@1038:     ssize_t discFree = GetDiscSpaceMBAvailable(path.toLocal8Bit());
Chris@1038:     ssize_t memoryFree, memoryTotal;
Chris@170:     GetRealMemoryMBAvailable(memoryFree, memoryTotal);
Chris@168: 
Chris@1276:     SVDEBUG << "StorageAdviser: disc space: " << discFree
Chris@1276:             << "M, memory free: " << memoryFree
Chris@1276:             << "M, memory total: " << memoryTotal << "M" << endl;
Chris@1405: 
Chris@1405:     // In 32-bit addressing mode we can't address more than 4Gb.
Chris@1405:     // If the total memory is reported as more than 4Gb, we should
Chris@1405:     // reduce the available amount by the difference between 4Gb
Chris@1405:     // and the total. This won't give us an accurate idea of the
Chris@1405:     // amount of memory available any more, but it should be enough
Chris@1405:     // to prevent us from trying to allocate more for our own use
Chris@1405:     // than can be addressed at all!
Chris@1405:     if (sizeof(void *) < 8) {
Chris@1405:         if (memoryTotal > 4096) {
Chris@1405:             ssize_t excess = memoryTotal - 4096;
Chris@1405:             if (memoryFree > excess) {
Chris@1405:                 memoryFree -= excess;
Chris@1405:             } else {
Chris@1405:                 memoryFree = 0;
Chris@1405:             }
Chris@1405:             SVDEBUG << "StorageAdviser: more real memory found than we "
Chris@1405:                     << "can address in a 32-bit process, reducing free "
Chris@1405:                     << "estimate to " << memoryFree << "M accordingly" << endl;
Chris@1405:         }
Chris@1405:     }
Chris@1405: 
Chris@1276:     SVDEBUG << "StorageAdviser: disc planned: " << (m_discPlanned / 1024)
Chris@1276:             << "K, memory planned: " << (m_memoryPlanned / 1024) << "K" << endl;
Chris@1276:     SVDEBUG << "StorageAdviser: min requested: " << minimumSize
Chris@1276:             << "K, max requested: " << maximumSize << "K" << endl;
Chris@1276: 
Chris@1038:     if (discFree > ssize_t(m_discPlanned / 1024 + 1)) {
Chris@205:         discFree -= m_discPlanned / 1024 + 1;
Chris@205:     } else if (discFree > 0) { // can also be -1 for unknown
Chris@205:         discFree = 0;
Chris@205:     }
Chris@205: 
Chris@1038:     if (memoryFree > ssize_t(m_memoryPlanned / 1024 + 1)) {
Chris@205:         memoryFree -= m_memoryPlanned / 1024 + 1;
Chris@205:     } else if (memoryFree > 0) { // can also be -1 for unknown
Chris@205:         memoryFree = 0;
Chris@205:     }
Chris@205: 
Chris@192:     //!!! We have a potentially serious problem here if multiple
Chris@192:     //recommendations are made in advance of any of the resulting
Chris@192:     //allocations, as the allocations that have been recommended for
Chris@192:     //won't be taken into account in subsequent recommendations.
Chris@192: 
Chris@170:     StorageStatus memoryStatus = Unknown;
Chris@170:     StorageStatus discStatus = Unknown;
Chris@170: 
Chris@1038:     ssize_t minmb = ssize_t(minimumSize / 1024 + 1);
Chris@1038:     ssize_t maxmb = ssize_t(maximumSize / 1024 + 1);
Chris@170: 
Chris@170:     if (memoryFree == -1) memoryStatus = Unknown;
Chris@1103:     else if (memoryFree < memoryTotal / 3 && memoryFree < 512) memoryStatus = Insufficient;
Chris@170:     else if (minmb > (memoryFree * 3) / 4) memoryStatus = Insufficient;
Chris@170:     else if (maxmb > (memoryFree * 3) / 4) memoryStatus = Marginal;
Chris@170:     else if (minmb > (memoryFree / 3)) memoryStatus = Marginal;
Chris@170:     else if (memoryTotal == -1 ||
Chris@170:              minmb > (memoryTotal / 10)) memoryStatus = Marginal;
Chris@170:     else memoryStatus = Sufficient;
Chris@170: 
Chris@170:     if (discFree == -1) discStatus = Unknown;
Chris@170:     else if (minmb > (discFree * 3) / 4) discStatus = Insufficient;
Chris@173:     else if (maxmb > (discFree / 4)) discStatus = Marginal;
Chris@173:     else if (minmb > (discFree / 10)) discStatus = Marginal;
Chris@170:     else discStatus = Sufficient;
Chris@170: 
Chris@1276:     SVDEBUG << "StorageAdviser: memory status: " << memoryStatus
Chris@1276:             << " (" << storageStatusToString(memoryStatus) << ")"
Chris@1276:             << ", disc status " << discStatus
Chris@1276:             << " (" << storageStatusToString(discStatus) << ")" << endl;
Chris@170: 
Chris@170:     int recommendation = NoRecommendation;
Chris@170: 
Chris@170:     if (memoryStatus == Insufficient || memoryStatus == Unknown) {
Chris@170: 
Chris@170:         recommendation |= UseDisc;
Chris@170: 
Chris@170:         if (discStatus == Insufficient && minmb > discFree) {
Chris@170:             throw InsufficientDiscSpace(path, minmb, discFree);
Chris@170:         }
Chris@170: 
Chris@170:         if (discStatus == Insufficient || discStatus == Marginal) {
Chris@170:             recommendation |= ConserveSpace;
Chris@170:         } else if (discStatus == Unknown && !(criteria & PrecisionCritical)) {
Chris@170:             recommendation |= ConserveSpace;
Chris@170:         } else {
Chris@170:             recommendation |= UseAsMuchAsYouLike;
Chris@170:         }
Chris@170: 
Chris@170:     } else if (memoryStatus == Marginal) {
Chris@170: 
Chris@170:         if (((criteria & SpeedCritical) ||
Chris@170:              (criteria & FrequentLookupLikely)) &&
Chris@170:             !(criteria & PrecisionCritical) &&
Chris@170:             !(criteria & LongRetentionLikely)) {
Chris@170: 
Chris@170:             // requirements suggest a preference for memory
Chris@170: 
Chris@170:             if (discStatus != Insufficient) {
Chris@170:                 recommendation |= PreferMemory;
Chris@170:             } else {
Chris@170:                 recommendation |= UseMemory;
Chris@170:             }
Chris@170: 
Chris@170:             recommendation |= ConserveSpace;
Chris@170: 
Chris@170:         } else {
Chris@170: 
Chris@170:             if (discStatus == Insufficient) {
Chris@170:                 recommendation |= (UseMemory | ConserveSpace);
Chris@170:             } else if (discStatus == Marginal) {
Chris@170:                 recommendation |= (PreferMemory | ConserveSpace);
Chris@170:             } else if (discStatus == Unknown) {
Chris@170:                 recommendation |= (PreferDisc | ConserveSpace);
Chris@170:             } else {
Chris@170:                 recommendation |= (UseDisc | UseAsMuchAsYouLike);
Chris@170:             }
Chris@170:         }    
Chris@170: 
Chris@170:     } else {
Chris@170: 
Chris@170:         if (discStatus == Insufficient) {
Chris@170:             recommendation |= (UseMemory | ConserveSpace);
Chris@170:         } else if (discStatus != Sufficient) {
Chris@170:             recommendation |= (PreferMemory | ConserveSpace);
Chris@170:         } else {
Chris@170: 
Chris@170:             if ((criteria & SpeedCritical) ||
Chris@170:                 (criteria & FrequentLookupLikely)) {
Chris@170:                 recommendation |= PreferMemory;
Chris@170:                 if (criteria & PrecisionCritical) {
Chris@170:                     recommendation |= UseAsMuchAsYouLike;
Chris@170:                 } else {
Chris@170:                     recommendation |= ConserveSpace;
Chris@170:                 }
Chris@170:             } else {
Chris@170:                 recommendation |= PreferDisc;
Chris@170:                 recommendation |= UseAsMuchAsYouLike;
Chris@170:             }
Chris@170:         }
Chris@170:     }
Chris@170: 
Chris@1276:     SVDEBUG << "StorageAdviser: returning recommendation " << recommendation
Chris@1276:             << " (" << recommendationToString(recommendation) << ")" << endl;
Chris@1098:     
Chris@170:     return Recommendation(recommendation);
Chris@168: }
Chris@168: 
Chris@205: void
Chris@1038: StorageAdviser::notifyPlannedAllocation(AllocationArea area, size_t size)
Chris@205: {
Chris@205:     if (area == MemoryAllocation) m_memoryPlanned += size;
Chris@205:     else if (area == DiscAllocation) m_discPlanned += size;
Chris@1276:     SVDEBUG << "StorageAdviser: storage planned up: now memory: " << m_memoryPlanned << ", disc "
Chris@1276:             << m_discPlanned << endl;
Chris@205: }
Chris@205: 
Chris@205: void
Chris@1038: StorageAdviser::notifyDoneAllocation(AllocationArea area, size_t size)
Chris@205: {
Chris@205:     if (area == MemoryAllocation) {
Chris@205:         if (m_memoryPlanned > size) m_memoryPlanned -= size;
Chris@205:         else m_memoryPlanned = 0;
Chris@205:     } else if (area == DiscAllocation) {
Chris@205:         if (m_discPlanned > size) m_discPlanned -= size; 
Chris@205:         else m_discPlanned = 0;
Chris@205:     }
Chris@1276:     SVDEBUG << "StorageAdviser: storage planned down: now memory: " << m_memoryPlanned << ", disc "
Chris@1276:             << m_discPlanned << endl;
Chris@205: }
Chris@205: 
Chris@411: void
Chris@411: StorageAdviser::setFixedRecommendation(Recommendation recommendation)
Chris@411: {
Chris@411:     m_baseRecommendation = recommendation;
Chris@411: }
Chris@411: