int main(int argc, char* argv[]) { std::string map; std::string style; double latTop,latBottom,lonLeft,lonRight; unsigned int startZoom; unsigned int endZoom; unsigned int tileWidth; unsigned int tileHeight; std::string driver; bool heapProfile; std::string heapProfilePrefix; if (argc<12) { std::cerr << "DrawMap " << std::endl; std::cerr << " <map directory> <style-file> " << std::endl; std::cerr << " <lat_top> <lon_left> <lat_bottom> <lon_right> " << std::endl; std::cerr << " <start zoom> <end zoom>" << std::endl; std::cerr << " <tile width> <tile height>" << std::endl; std::cerr << " <cairo|Qt|noop|none>" << std::endl; #if defined(HAVE_LIB_GPERFTOOLS) std::cerr << " [heap profile prefix]" << std::endl; #endif return 1; } heapProfile = false; #if defined(HAVE_LIB_GPERFTOOLS) if (argc>12) { heapProfile = true; heapProfilePrefix = argv[12]; } #endif map=argv[1]; style=argv[2]; if (sscanf(argv[3],"%lf",&latTop)!=1) { std::cerr << "lon is not numeric!" << std::endl; return 1; } if (sscanf(argv[4],"%lf",&lonLeft)!=1) { std::cerr << "lat is not numeric!" << std::endl; return 1; } if (sscanf(argv[5],"%lf",&latBottom)!=1) { std::cerr << "lon is not numeric!" << std::endl; return 1; } if (sscanf(argv[6],"%lf",&lonRight)!=1) { std::cerr << "lat is not numeric!" << std::endl; return 1; } if (sscanf(argv[7],"%u",&startZoom)!=1) { std::cerr << "start zoom is not numeric!" << std::endl; return 1; } if (sscanf(argv[8],"%u",&endZoom)!=1) { std::cerr << "end zoom is not numeric!" << std::endl; return 1; } if (sscanf(argv[9],"%u",&tileWidth)!=1) { std::cerr << "tile width is not numeric!" << std::endl; return 1; } if (sscanf(argv[10],"%u",&tileHeight)!=1) { std::cerr << "tile height is not numeric!" << std::endl; return 1; } driver=argv[11]; #if defined(HAVE_LIB_OSMSCOUTMAPCAIRO) cairo_surface_t * cairoSurface=NULL; cairo_t *cairo=NULL; #endif #if defined(HAVE_LIB_OSMSCOUTMAPQT) QPixmap *qtPixmap=NULL; QPainter *qtPainter=NULL; QApplication application(argc,argv,true); #endif if (driver=="cairo") { std::cout << "Using driver 'cairo'..." << std::endl; #if defined(HAVE_LIB_OSMSCOUTMAPCAIRO) cairoSurface=cairo_image_surface_create(CAIRO_FORMAT_RGB24,tileWidth,tileHeight); if (cairoSurface==NULL) { std::cerr << "Cannot create cairo image cairoSurface" << std::endl; return 1; } cairo=cairo_create(cairoSurface); if (cairo==NULL) { std::cerr << "Cannot create cairo_t for image cairoSurface" << std::endl; return 1; } #else std::cerr << "Driver 'cairo' is not enabled" << std::endl; return 1; #endif } else if (driver=="Qt") { std::cout << "Using driver 'Qt'..." << std::endl; #if defined(HAVE_LIB_OSMSCOUTMAPQT) qtPixmap=new QPixmap(tileWidth,tileHeight); if (qtPixmap==NULL) { std::cerr << "Cannot create QPixmap" << std::endl; return 1; } qtPainter=new QPainter(qtPixmap); if (qtPainter==NULL) { std::cerr << "Cannot create QPainter image cairoSurface" << std::endl; return 1; } #else std::cerr << "Driver 'Qt' is not enabled" << std::endl; return 1; #endif } else if (driver=="noop") { std::cout << "Using driver 'noop'..." << std::endl; } else if (driver=="none") { std::cout << "Using driver 'none'..." << std::endl; } else { std::cerr << "Unsupported driver '" << driver << "'" << std::endl; return 1; } osmscout::DatabaseParameter databaseParameter; //databaseParameter.SetDebugPerformance(true); osmscout::DatabaseRef database=std::make_shared<osmscout::Database>(databaseParameter); osmscout::MapServiceRef mapService=std::make_shared<osmscout::MapService>(database); if (!database->Open(map.c_str())) { std::cerr << "Cannot open database" << std::endl; return 1; } osmscout::StyleConfigRef styleConfig=std::make_shared<osmscout::StyleConfig>(database->GetTypeConfig()); if (!styleConfig->Load(style)) { std::cerr << "Cannot open style" << std::endl; return 1; } #if defined(HAVE_LIB_GPERFTOOLS) if (heapProfile){ HeapProfilerStart(heapProfilePrefix.c_str()); } #endif osmscout::TileProjection projection; osmscout::MapParameter drawParameter; osmscout::AreaSearchParameter searchParameter; std::list<LevelStats> statistics; searchParameter.SetUseMultithreading(true); for (uint32_t level=std::min(startZoom,endZoom); level<=std::max(startZoom,endZoom); level++) { LevelStats stats(level); osmscout::Magnification magnification; int xTileStart,xTileEnd,xTileCount,yTileStart,yTileEnd,yTileCount; magnification.SetLevel(level); xTileStart=osmscout::LonToTileX(std::min(lonLeft,lonRight), magnification); xTileEnd=osmscout::LonToTileX(std::max(lonLeft,lonRight), magnification); xTileCount=xTileEnd-xTileStart+1; yTileStart=osmscout::LatToTileY(std::max(latTop,latBottom), magnification); yTileEnd=osmscout::LatToTileY(std::min(latTop,latBottom), magnification); yTileCount=yTileEnd-yTileStart+1; std::cout << "----------" << std::endl; std::cout << "Drawing level " << level << ", " << (xTileCount)*(yTileCount) << " tiles [" << xTileStart << "," << yTileStart << " - " << xTileEnd << "," << yTileEnd << "]" << std::endl; #if defined(HAVE_LIB_OSMSCOUTMAPCAIRO) osmscout::MapPainterCairo cairoMapPainter(styleConfig); #endif #if defined(HAVE_LIB_OSMSCOUTMAPQT) osmscout::MapPainterQt qtMapPainter(styleConfig); #endif osmscout::MapPainterNoOp noOpMapPainter(styleConfig); size_t current=1; size_t tileCount=(yTileEnd-yTileStart+1)*(xTileEnd-xTileStart+1); size_t delta=tileCount/20; if (delta==0) { delta=1; } for (int y=yTileStart; y<=yTileEnd; y++) { for (int x=xTileStart; x<=xTileEnd; x++) { osmscout::MapData data; osmscout::GeoBox boundingBox; if ((current % delta)==0) { std::cout << current*100/tileCount << "% " << current; if (stats.tileCount>0) { std::cout << " " << stats.dbTotalTime/stats.tileCount; std::cout << " " << stats.drawTotalTime/stats.tileCount; } std::cout << std::endl; } projection.Set(x-1,y-1, x+1,y+1, magnification, DPI, tileWidth, tileHeight); projection.GetDimensions(boundingBox); osmscout::StopClock dbTimer; osmscout::GeoBox dataBoundingBox(osmscout::GeoCoord(osmscout::TileYToLat(y-1,magnification),osmscout::TileXToLon(x-1,magnification)), osmscout::GeoCoord(osmscout::TileYToLat(y+1,magnification),osmscout::TileXToLon(x+1,magnification))); std::list<osmscout::TileRef> tiles; // set cache size almost unlimited, // for better estimate of peak memory usage by tile loading mapService->SetCacheSize(10000000); mapService->LookupTiles(magnification,dataBoundingBox,tiles); mapService->LoadMissingTileData(searchParameter,*styleConfig,tiles); mapService->ConvertTilesToMapData(tiles,data); stats.nodeCount+=data.nodes.size(); stats.wayCount+=data.ways.size(); stats.areaCount+=data.areas.size(); #if defined(HAVE_LIB_GPERFTOOLS) if (heapProfile){ std::ostringstream buff; buff << "load-" << level << "-" << x << "-" << y; HeapProfilerDump(buff.str().c_str()); } struct mallinfo alloc_info = tc_mallinfo(); #else #if defined(HAVE_MALLINFO) struct mallinfo alloc_info = mallinfo(); #endif #endif #if defined(HAVE_MALLINFO) || defined(HAVE_LIB_GPERFTOOLS) std::cout << "memory usage: " << formatAlloc(alloc_info.uordblks) << std::endl; stats.allocMax = std::max(stats.allocMax, (double)alloc_info.uordblks); stats.allocSum = stats.allocSum + (double)alloc_info.uordblks; #endif // set cache size back to default mapService->SetCacheSize(25); dbTimer.Stop(); double dbTime=dbTimer.GetMilliseconds(); stats.dbMinTime=std::min(stats.dbMinTime,dbTime); stats.dbMaxTime=std::max(stats.dbMaxTime,dbTime); stats.dbTotalTime+=dbTime; osmscout::StopClock drawTimer; #if defined(HAVE_LIB_OSMSCOUTMAPCAIRO) if (driver=="cairo") { //std::cout << data.nodes.size() << " " << data.ways.size() << " " << data.areas.size() << std::endl; cairoMapPainter.DrawMap(projection, drawParameter, data, cairo); } #endif #if defined(HAVE_LIB_OSMSCOUTMAPQT) if (driver=="Qt") { //std::cout << data.nodes.size() << " " << data.ways.size() << " " << data.areas.size() << std::endl; qtMapPainter.DrawMap(projection, drawParameter, data, qtPainter); } #endif if (driver=="noop") { noOpMapPainter.DrawMap(projection, drawParameter, data); } if (driver=="none") { // Do nothing } drawTimer.Stop(); stats.tileCount++; double drawTime=drawTimer.GetMilliseconds(); stats.drawMinTime=std::min(stats.drawMinTime,drawTime); stats.drawMaxTime=std::max(stats.drawMaxTime,drawTime); stats.drawTotalTime+=drawTime; current++; } } statistics.push_back(stats); } #if defined(HAVE_LIB_GPERFTOOLS) if (heapProfile){ HeapProfilerStop(); } #endif std::cout << "==========" << std::endl; for (const auto& stats : statistics) { std::cout << "Level: " << stats.level << std::endl; #if defined(HAVE_MALLINFO) || defined(HAVE_LIB_GPERFTOOLS) std::cout << " Used memory: "; std::cout << "max: " << formatAlloc(stats.allocMax) << " "; std::cout << "avg: " << formatAlloc(stats.allocSum / stats.tileCount) << std::endl; #endif std::cout << " Tot. data : "; std::cout << "nodes: " << stats.nodeCount << " "; std::cout << "way: " << stats.wayCount << " "; std::cout << "areas: " << stats.areaCount << std::endl; std::cout << " Avg. data : "; std::cout << "nodes: " << stats.nodeCount/stats.tileCount << " "; std::cout << "way: " << stats.wayCount/stats.tileCount << " "; std::cout << "areas: " << stats.areaCount/stats.tileCount << std::endl; std::cout << " DB : "; std::cout << "total: " << stats.dbTotalTime << " "; std::cout << "min: " << stats.dbMinTime << " "; std::cout << "avg: " << stats.dbTotalTime/stats.tileCount << " "; std::cout << "max: " << stats.dbMaxTime << " " << std::endl; std::cout << " Map : "; std::cout << "total: " << stats.drawTotalTime << " "; std::cout << "min: " << stats.drawMinTime << " "; std::cout << "avg: " << stats.drawTotalTime/stats.tileCount << " "; std::cout << "max: " << stats.drawMaxTime << std::endl; } database->Close(); #if defined(HAVE_LIB_OSMSCOUTMAPCAIRO) if (driver=="cairo") { cairo_destroy(cairo); cairo_surface_destroy(cairoSurface); } #endif return 0; }
int main(int argc, char* argv[]) { osmscout::CmdLineParser argParser("PerformanceTest", argc,argv); Arguments args; osmscout::DatabaseParameter databaseParameter; argParser.AddOption(osmscout::CmdLineFlag([&args](const bool& value) { args.help=value; }), std::vector<std::string>{"h","help"}, "Display help", true); argParser.AddOption(osmscout::CmdLineFlag([&args](const bool& value) { args.debug=value; }), "debug", "Enable debug output", false); argParser.AddOption(osmscout::CmdLineUIntOption([&args](const unsigned int& value) { args.startZoom=osmscout::MagnificationLevel(value); }), "start-zoom", "Start zoom, default: " + std::to_string(args.startZoom.Get()), false); argParser.AddOption(osmscout::CmdLineUIntOption([&args](const unsigned int& value) { args.endZoom=osmscout::MagnificationLevel(value); }), "end-zoom", "End zoom, default: " + std::to_string(args.endZoom.Get()), false); argParser.AddOption(osmscout::CmdLineUIntOption([&args](const unsigned int& value) { args.tileDimension=std::make_tuple(value, std::get<1>(args.tileDimension)); }), "tile-width", "Tile width, default: " + std::to_string(std::get<0>(args.tileDimension)), false); argParser.AddOption(osmscout::CmdLineUIntOption([&args](const unsigned int& value) { args.tileDimension=std::make_tuple(std::get<0>(args.tileDimension), value); }), "tile-height", "Tile height, default: " + std::to_string(std::get<1>(args.tileDimension)), false); argParser.AddOption(osmscout::CmdLineStringOption([&args](const std::string& value) { args.driver = value; }), "driver", "Rendering driver (cairo|Qt|ag|opengl|noop|none), default: " + args.driver, false); argParser.AddOption(osmscout::CmdLineDoubleOption([&args](const double& value) { if (value > 0) { args.dpi = value; } else { std::cerr << "DPI can't be negative or zero" << std::endl; } }), "dpi", "Rendering DPI, default: " + std::to_string(args.dpi), false); argParser.AddOption(osmscout::CmdLineUIntOption([&args](const unsigned int& value) { args.drawRepeat = value; }), "draw-repeat", "Repeat every draw call, default: " + std::to_string(args.drawRepeat), false); argParser.AddOption(osmscout::CmdLineUIntOption([&args](const unsigned int& value) { args.loadRepeat = value; }), "load-repeat", "Repeat every load call, default: " + std::to_string(args.loadRepeat), false); argParser.AddOption(osmscout::CmdLineFlag([&args](const bool& value) { args.flushCache=value; }), "flush-cache", "Flush data caches after each data load, default: " + std::to_string(args.flushCache), false); argParser.AddOption(osmscout::CmdLineFlag([&args](const bool& value) { args.flushDiskCache=value; }), "flush-disk", "Flush system disk caches after each data load, default: " + std::to_string(args.flushDiskCache) + " (It work just on Linux with admin rights.)", false); argParser.AddOption(osmscout::CmdLineUIntOption([&databaseParameter](const unsigned int& value) { databaseParameter.SetNodeDataCacheSize(value); }), "cache-nodes", "Cache size for nodes, default: " + std::to_string(databaseParameter.GetNodeDataCacheSize()), false); argParser.AddOption(osmscout::CmdLineUIntOption([&databaseParameter](const unsigned int& value) { databaseParameter.SetWayDataCacheSize(value); }), "cache-ways", "Cache size for ways, default: " + std::to_string(databaseParameter.GetWayDataCacheSize()), false); argParser.AddOption(osmscout::CmdLineUIntOption([&databaseParameter](const unsigned int& value) { databaseParameter.SetAreaDataCacheSize(value); }), "cache-areas", "Cache size for areas, default: " + std::to_string(databaseParameter.GetAreaDataCacheSize()), false); #if defined(HAVE_LIB_GPERFTOOLS) argParser.AddOption(osmscout::CmdLineStringOption([&args](const std::string& value) { args.heapProfilePrefix = value; args.heapProfile = !args.heapProfilePrefix.empty(); }), "heap-profile", "GPerf heap profile prefix, profiler is disabled by default", false); #endif argParser.AddPositional(osmscout::CmdLineStringOption([&args](const std::string& value) { args.databaseDirectory=value; }), "databaseDir", "Database directory"); argParser.AddPositional(osmscout::CmdLineStringOption([&args](const std::string& value) { args.style=value; }), "stylesheet", "Map stylesheet"); argParser.AddPositional(osmscout::CmdLineGeoCoordOption([&args](const osmscout::GeoCoord& coord) { args.coordTopLeft = coord; }), "lat_top lon_left", "Bounding box top-left coordinate"); argParser.AddPositional(osmscout::CmdLineGeoCoordOption([&args](const osmscout::GeoCoord& coord) { args.coordBottomRight = coord; }), "lat_bottom lon_right", "Bounding box bottom-right coordinate"); osmscout::CmdLineParseResult argResult=argParser.Parse(); if (argResult.HasError()) { std::cerr << "ERROR: " << argResult.GetErrorDescription() << std::endl; std::cout << argParser.GetHelp() << std::endl; return 1; } else if (args.help) { std::cout << argParser.GetHelp() << std::endl; return 0; } osmscout::log.Debug(args.debug); //databaseParameter.SetDebugPerformance(true); osmscout::DatabaseRef database=std::make_shared<osmscout::Database>(databaseParameter); osmscout::MapServiceRef mapService=std::make_shared<osmscout::MapService>(database); if (!database->Open(args.databaseDirectory)) { std::cerr << "Cannot open database" << std::endl; return 1; } osmscout::StyleConfigRef styleConfig=std::make_shared<osmscout::StyleConfig>(database->GetTypeConfig()); if (!styleConfig->Load(args.style)) { std::cerr << "Cannot open style" << std::endl; return 1; } PerformanceTestBackendPtr backendPtr = PrepareBackend(argc, argv, args, styleConfig); if (!backendPtr){ return 1; } #if defined(HAVE_LIB_GPERFTOOLS) if (args.heapProfile){ HeapProfilerStart(args.heapProfilePrefix.c_str()); } #endif osmscout::TileProjection projection; osmscout::MapParameter drawParameter; osmscout::AreaSearchParameter searchParameter; std::list<LevelStats> statistics; // TODO: Use some way to find a valid font on the system (Agg display a ton of messages otherwise) drawParameter.SetFontName("/usr/share/fonts/TTF/DejaVuSans.ttf"); searchParameter.SetUseMultithreading(true); for (osmscout::MagnificationLevel level=osmscout::MagnificationLevel(std::min(args.startZoom,args.endZoom)); level<=osmscout::MagnificationLevel(std::max(args.startZoom,args.endZoom)); level++) { LevelStats stats(level.Get()); osmscout::Magnification magnification(level); osmscout::OSMTileId tileA(osmscout::OSMTileId::GetOSMTile(magnification, osmscout::GeoCoord(args.LatBottom(),args.LonLeft()))); osmscout::OSMTileId tileB(osmscout::OSMTileId::GetOSMTile(magnification, osmscout::GeoCoord(args.LatTop(),args.LonRight()))); osmscout::OSMTileIdBox tileArea(tileA,tileB); std::cout << "----------" << std::endl; std::cout << "Drawing level " << level << ", " << tileArea.GetCount() << " tiles " << tileArea.GetDisplayText() << std::endl; size_t current=1; size_t tileCount=tileArea.GetCount(); size_t delta=tileCount/20; if (delta==0) { delta=1; } for (const auto& tile : tileArea) { osmscout::MapData data; osmscout::OSMTileIdBox tileBox(osmscout::OSMTileId(tile.GetX()-1,tile.GetY()-1), osmscout::OSMTileId(tile.GetX()+1,tile.GetY()+1)); osmscout::GeoBox boundingBox; if ((current % delta)==0) { std::cout << current*100/tileCount << "% " << current; if (stats.tileCount>0) { std::cout << " " << stats.dbTotalTime/stats.tileCount; std::cout << " " << stats.drawTotalTime/stats.tileCount; } std::cout << std::endl; } projection.Set(tile, magnification, args.dpi, args.TileWidth(), args.TileHeight()); projection.GetDimensions(boundingBox); projection.SetLinearInterpolationUsage(level.Get() >= 10); for (size_t i=0; i<args.loadRepeat; i++) { data.nodes.clear(); data.ways.clear(); data.areas.clear(); osmscout::StopClock dbTimer; osmscout::GeoBox dataBoundingBox(tileBox.GetBoundingBox(magnification)); std::list<osmscout::TileRef> tiles; // set cache size almost unlimited, // for better estimate of peak memory usage by tile loading mapService->SetCacheSize(10000000); mapService->LookupTiles(magnification, dataBoundingBox, tiles); mapService->LoadMissingTileData(searchParameter, *styleConfig, tiles); mapService->AddTileDataToMapData(tiles, data); #if defined(HAVE_LIB_GPERFTOOLS) if (args.heapProfile) { std::ostringstream buff; buff << "load-" << level << "-" << tile.GetX() << "-" << tile.GetY(); HeapProfilerDump(buff.str().c_str()); } struct mallinfo alloc_info = tc_mallinfo(); #else #if defined(HAVE_MALLINFO) struct mallinfo alloc_info = mallinfo(); #endif #endif #if defined(HAVE_MALLINFO) || defined(HAVE_LIB_GPERFTOOLS) std::cout << "memory usage: " << formatAlloc(alloc_info.uordblks) << std::endl; stats.allocMax = std::max(stats.allocMax, (double) alloc_info.uordblks); stats.allocSum = stats.allocSum + (double) alloc_info.uordblks; #endif // set cache size back to default mapService->SetCacheSize(25); dbTimer.Stop(); double dbTime = dbTimer.GetMilliseconds(); stats.dbMinTime = std::min(stats.dbMinTime, dbTime); stats.dbMaxTime = std::max(stats.dbMaxTime, dbTime); stats.dbTotalTime += dbTime; if (args.flushCache) { tiles.clear(); // following flush method removes only tiles with use_count() == 1 mapService->FlushTileCache(); // simplest way howto flush database caches is close it and open again database->Close(); if (!database->Open(args.databaseDirectory)) { std::cerr << "Cannot open database" << std::endl; return 1; } } if (args.flushDiskCache) { // Linux specific if (osmscout::ExistsInFilesystem("/proc/sys/vm/drop_caches")){ osmscout::FileWriter f; try { f.Open("/proc/sys/vm/drop_caches"); f.Write(std::string("3")); f.Close(); }catch(const osmscout::IOException &e){ std::cerr << "Can't flush disk cache: " << e.what() << std::endl; } }else{ std::cerr << "Can't flush disk cache, \"/proc/sys/vm/drop_caches\" file don't exists" << std::endl; } } } stats.nodeCount+=data.nodes.size(); stats.wayCount+=data.ways.size(); stats.areaCount+=data.areas.size(); stats.tileCount++; for (size_t i=0; i<args.drawRepeat; i++) { osmscout::StopClock drawTimer; backendPtr->DrawMap(projection, drawParameter, data); drawTimer.Stop(); double drawTime = drawTimer.GetMilliseconds(); stats.drawMinTime = std::min(stats.drawMinTime, drawTime); stats.drawMaxTime = std::max(stats.drawMaxTime, drawTime); stats.drawTotalTime += drawTime; } current++; } statistics.push_back(stats); } #if defined(HAVE_LIB_GPERFTOOLS) if (args.heapProfile){ HeapProfilerStop(); } #endif std::cout << "==========" << std::endl; for (const auto& stats : statistics) { std::cout << "Level: " << stats.level << std::endl; std::cout << "Tiles: " << stats.tileCount << " (load " << args.loadRepeat << "x, drawn " << args.drawRepeat << "x)" << std::endl; #if defined(HAVE_MALLINFO) || defined(HAVE_LIB_GPERFTOOLS) std::cout << " Used memory: "; std::cout << "max: " << formatAlloc(stats.allocMax) << " "; std::cout << "avg: " << formatAlloc(stats.allocSum / (stats.tileCount * args.loadRepeat)) << std::endl; #endif std::cout << " Tot. data : "; std::cout << "nodes: " << stats.nodeCount << " "; std::cout << "way: " << stats.wayCount << " "; std::cout << "areas: " << stats.areaCount << std::endl; if (stats.tileCount>0) { std::cout << " Avg. data : "; std::cout << "nodes: " << stats.nodeCount/stats.tileCount << " "; std::cout << "way: " << stats.wayCount/stats.tileCount << " "; std::cout << "areas: " << stats.areaCount/stats.tileCount << std::endl; } std::cout << " DB : "; std::cout << "total: " << stats.dbTotalTime << " "; std::cout << "min: " << stats.dbMinTime << " "; if (stats.tileCount>0) { std::cout << "avg: " << stats.dbTotalTime/(stats.tileCount * args.loadRepeat) << " "; } std::cout << "max: " << stats.dbMaxTime << " " << std::endl; std::cout << " Map : "; std::cout << "total: " << stats.drawTotalTime << " "; std::cout << "min: " << stats.drawMinTime << " "; if (stats.tileCount>0) { std::cout << "avg: " << stats.drawTotalTime/(stats.tileCount * args.drawRepeat) << " "; } std::cout << "max: " << stats.drawMaxTime << std::endl; } database->Close(); return 0; }