source: XIOS3/trunk/src/client.cpp @ 2407

Last change on this file since 2407 was 2407, checked in by ymipsl, 21 months ago

Implement separate "reader" and "writer" service. Default reader live on same ressources that "writer" or "gatherer" services.

YM

  • Property copyright set to
    Software name : XIOS (Xml I/O Server)
    http://forge.ipsl.jussieu.fr/ioserver
    Creation date : January 2009
    Licence : CeCCIL version2
    see license file in root directory : Licence_CeCILL_V2-en.txt
    or http://www.cecill.info/licences/Licence_CeCILL_V2-en.html
    Holder : CEA/LSCE (Laboratoire des Sciences du CLimat et de l'Environnement)
    CNRS/IPSL (Institut Pierre Simon Laplace)
    Project Manager : Yann Meurdesoif
    yann.meurdesoif@cea.fr
  • Property svn:eol-style set to native
File size: 20.9 KB
Line 
1#include "globalScopeData.hpp"
2#include "xios_spl.hpp"
3#include "cxios.hpp"
4#include "client.hpp"
5#include <boost/functional/hash.hpp>
6#include "type.hpp"
7#include "context.hpp"
8#include "context_client.hpp"
9#include "oasis_cinterface.hpp"
10#include "mpi.hpp"
11#include "timer.hpp"
12#include "buffer_client.hpp"
13#include "string_tools.hpp"
14#include "ressources_manager.hpp"
15#include "services_manager.hpp"
16#include <functional>
17#include <cstdio>
18#include "workflow_graph.hpp"
19#include "release_static_allocation.hpp"
20
21namespace xios
22{
23
24    const double serverPublishDefaultTimeout=10;
25
26    MPI_Comm CClient::intraComm_ ;
27    MPI_Comm CClient::interComm_ ;
28    MPI_Comm CClient::clientsComm_ ;
29
30    std::list<MPI_Comm> CClient::contextInterComms;
31    int CClient::serverLeader ;
32    bool CClient::is_MPI_Initialized ;
33    int CClient::rank_ = INVALID_RANK;
34    StdOFStream CClient::m_infoStream;
35    StdOFStream CClient::m_errorStream;
36    CPoolRessource* CClient::poolRessource_=nullptr ;
37
38    MPI_Comm& CClient::getInterComm(void)   { return (interComm_); }
39     
40///---------------------------------------------------------------
41/*!
42 * \fn void CClient::initialize(const string& codeId, MPI_Comm& localComm, MPI_Comm& returnComm)
43 * Function creates intraComm (CClient::intraComm) for client group with id=codeId and interComm (CClient::interComm) between client and server groups.
44 * \param [in] codeId identity of context.
45 * \param [in/out] localComm local communicator.
46 * \param [in/out] returnComm (intra)communicator of client group.
47 */
48
49    void CClient::initialize(const string& codeId, MPI_Comm& localComm, MPI_Comm& returnComm)
50    {
51   
52       MPI_Comm clientComm ;
53      // initialize MPI if not initialized
54      int initialized ;
55      MPI_Initialized(&initialized) ;
56      if (initialized) is_MPI_Initialized=true ;
57      else is_MPI_Initialized=false ;
58     
59      MPI_Comm globalComm=CXios::getGlobalComm() ;
60
61      /////////////////////////////////////////
62      ///////////// PART 1 ////////////////////
63      /////////////////////////////////////////
64     
65
66      // localComm isn't given
67      if (localComm == MPI_COMM_NULL)
68      {
69         
70        // don't use OASIS
71        if (!CXios::usingOasis)
72        {
73
74          if (!is_MPI_Initialized)
75          {
76            MPI_Init(NULL, NULL);
77          }
78          CTimer::get("XIOS").resume() ;
79          CTimer::get("XIOS init/finalize",false).resume() ;
80         
81          // split the global communicator
82          // get hash from all model to attribute a unique color (int) and then split to get client communicator
83          // every mpi process of globalComm (MPI_COMM_WORLD) must participate
84
85          int commRank, commSize ;
86          MPI_Comm_rank(globalComm,&commRank) ;
87          MPI_Comm_size(globalComm,&commSize) ;
88
89          std::hash<string> hashString ;
90          size_t hashClient=hashString(codeId) ;
91         
92          size_t* hashAll = new size_t[commSize] ;
93          MPI_Allgather(&hashClient,1,MPI_SIZE_T,hashAll,1,MPI_SIZE_T,globalComm) ;
94         
95          int color=0 ;
96          map<size_t,int> listHash ;
97          for(int i=0 ; i<=commSize ; i++) 
98            if (listHash.count(hashAll[i])==0) 
99            {
100              listHash[hashAll[i]]=color ;
101              color=color+1 ;
102            }
103            color=listHash[hashClient] ;
104          delete[] hashAll ;
105
106          MPI_Comm_split(globalComm, color, commRank, &clientComm) ;
107        }
108        else
109        {
110          ERROR("void CClient::initialize(const string& codeId, MPI_Comm& localComm, MPI_Comm& returnComm)", <<"OASIS usage is set. In these conditions, XIOS initialization needs the local_comm created by OASIS."<<endl) ;
111        }
112      }
113      else // localComm is given
114      {
115        MPI_Comm_dup(localComm,&clientComm) ;
116        MPI_Comm_dup(localComm,&intraComm_) ;
117
118        if (CXios::usingServer)
119        {
120          MPI_Comm_rank(intraComm_,&rank_) ;
121        }
122
123      }
124     
125     
126      /////////////////////////////////////////
127      ///////////// PART 2 ////////////////////
128      /////////////////////////////////////////
129     
130
131      // Create the XIOS communicator for every process which is related
132      // to XIOS, as well on client side as on server side
133     
134      MPI_Comm xiosGlobalComm ;
135      string strIds=CXios::getin<string>("clients_code_id","") ;
136      vector<string> clientsCodeId=splitRegex(strIds,"\\s*,\\s*") ;
137      if (strIds.empty())
138      {
139         // no code Ids given, suppose XIOS initialisation is global           
140         int commRank, commGlobalRank, serverLeader, clientLeader,serverRemoteLeader,clientRemoteLeader ;
141         MPI_Comm splitComm,interComm ;
142         MPI_Comm_rank(globalComm,&commGlobalRank) ;
143         MPI_Comm_split(globalComm, 0, commGlobalRank, &splitComm) ;
144         int splitCommSize, globalCommSize ;
145       
146         MPI_Comm_size(splitComm,&splitCommSize) ;
147         MPI_Comm_size(globalComm,&globalCommSize) ;
148         if (splitCommSize==globalCommSize) // no server
149         {
150           MPI_Comm_dup(globalComm,&xiosGlobalComm) ;
151           CXios::setXiosComm(xiosGlobalComm) ;
152         }
153         else
154         {
155           MPI_Comm_rank(splitComm,&commRank) ;
156           if (commRank==0) clientLeader=commGlobalRank ;
157           else clientLeader=0 ;
158           serverLeader=0 ;
159           MPI_Allreduce(&clientLeader,&clientRemoteLeader,1,MPI_INT,MPI_SUM,globalComm) ;
160           MPI_Allreduce(&serverLeader,&serverRemoteLeader,1,MPI_INT,MPI_SUM,globalComm) ;
161           MPI_Intercomm_create(splitComm, 0, globalComm, serverRemoteLeader,1341,&interComm) ;
162           MPI_Intercomm_merge(interComm,true,&xiosGlobalComm) ;
163           CXios::setXiosComm(xiosGlobalComm) ;
164         }
165      }
166      else
167      {
168
169        xiosGlobalCommByFileExchange(clientComm, codeId) ;
170     
171      }
172
173      int commRank ;
174      MPI_Comm_rank(CXios::getXiosComm(), &commRank) ;
175      MPI_Comm_split(CXios::getXiosComm(),false,commRank, &clientsComm_) ;
176     
177      // is using server or not ?
178      int xiosCommSize, clientsCommSize ; 
179      MPI_Comm_size(CXios::getXiosComm(), &xiosCommSize) ;
180      MPI_Comm_size(clientsComm_, &clientsCommSize) ;
181      if (xiosCommSize==clientsCommSize) CXios::setUsingServer() ;
182      else CXios::setNotUsingServer() ;
183
184      /////////////////////////////////////////
185      ///////////// PART 3 ////////////////////
186      /////////////////////////////////////////
187     
188      CXios::launchDaemonsManager(false) ;
189      poolRessource_ = new CPoolRessource(clientComm, codeId) ;
190
191      /////////////////////////////////////////
192      ///////////// PART 4 ////////////////////
193      /////////////////////////////////////////     
194     
195      returnComm = clientComm ;
196    }
197
198
199    void CClient::xiosGlobalCommByFileExchange(MPI_Comm clientComm, const string& codeId)
200    {
201 
202      MPI_Comm globalComm=CXios::getGlobalComm() ;
203      MPI_Comm xiosGlobalComm ;
204
205      string strIds=CXios::getin<string>("clients_code_id","") ;
206      vector<string> clientsCodeId=splitRegex(strIds,"\\s*,\\s*") ;
207
208      int commRank, globalRank, clientRank, serverRank ;
209      MPI_Comm_rank(clientComm, &commRank) ;
210      MPI_Comm_rank(globalComm, &globalRank) ;
211      string clientFileName("__xios_publisher::"+codeId+"__to_remove__") ;
212           
213      int error ;
214
215      if (commRank==0) // if root process publish name
216      { 
217        std::ofstream ofs (clientFileName, std::ofstream::out);
218        ofs<<globalRank ;
219        ofs.close();
220       
221  // get server root rank
222
223        std::ifstream ifs ;
224        string fileName=("__xios_publisher::"+CXios::xiosCodeId+"__to_remove__") ;
225     
226        double timeout = CXios::getin<double>("server_puplish_timeout",serverPublishDefaultTimeout) ;
227        double time ;
228         
229        do
230        {
231          CTimer::get("server_publish_timeout").resume() ; 
232          ifs.clear() ;
233          ifs.open(fileName, std::ifstream::in) ;
234          CTimer::get("server_publish_timeout").suspend() ;
235        } while (ifs.fail() && CTimer::get("server_publish_timeout").getCumulatedTime()<timeout) ;
236       
237        if (CTimer::get("server_publish_timeout").getCumulatedTime()>=timeout || ifs.fail())
238        {
239          ifs.clear() ;
240          ifs.close() ;
241          ifs.clear() ;
242          error=true ;           
243        }
244        else 
245        {
246          ifs>>serverRank ;
247          ifs.close() ;
248          error=false ;
249        } 
250
251      } 
252      MPI_Bcast(&error,1,MPI_INT,0,clientComm) ;
253     
254      if (error==false)  // you have a server
255      {
256        MPI_Comm intraComm ;
257        MPI_Comm_dup(clientComm,&intraComm) ;
258        MPI_Comm interComm ;
259       
260        int pos=0 ;
261        for(int i=0 ; codeId!=clientsCodeId[i]; i++) pos=pos+1 ;
262
263        bool high=true ;
264        for(int i=pos ; i<clientsCodeId.size(); i++)
265        { 
266          MPI_Intercomm_create(intraComm, 0, globalComm, serverRank, 3141, &interComm);
267          MPI_Comm_free(&intraComm) ;
268          MPI_Intercomm_merge(interComm,high, &intraComm ) ;
269          high=false ;
270          if (i==pos) {
271            interComm_=interComm ;
272          }
273        }
274        xiosGlobalComm=intraComm ;
275      }
276      else  // no server detected
277      {
278        vector<int> clientsRank(clientsCodeId.size()) ;
279       
280        if (commRank==0)
281        { 
282          for(int i=0;i<clientsRank.size();i++)
283          {
284            std::ifstream ifs ;
285            string fileName=("__xios_publisher::"+clientsCodeId[i]+"__to_remove__") ;
286            do
287            {
288              ifs.clear() ;
289              ifs.open(fileName, std::ifstream::in) ;
290            } while (ifs.fail()) ;
291            ifs>>clientsRank[i] ;
292            ifs.close() ;
293          }
294        }
295         
296        int client ;
297        MPI_Comm intraComm ;
298        MPI_Comm_dup(clientComm,&intraComm) ;
299        MPI_Comm interComm ;
300       
301        int pos=0 ;
302        for(int i=0 ; codeId!=clientsCodeId[i]; i++) pos=pos+1 ;
303       
304        bool high=true ;
305        for(int i=pos+1 ; i<clientsCodeId.size(); i++)
306        { 
307          if (codeId==clientsCodeId[0])   // first model play the server rule
308          {         
309            MPI_Intercomm_create(intraComm, 0, globalComm, clientsRank[i], 3141, &interComm);
310            MPI_Intercomm_merge(interComm,false, &intraComm ) ;
311          }
312          else
313          {         
314            MPI_Intercomm_create(intraComm, 0, globalComm, clientsRank[0], 3141, &interComm);
315            MPI_Intercomm_merge(interComm,high, &intraComm ) ;
316            high=false ;
317          }
318          if (i==pos) {
319            interComm_=interComm ; // NOT TESTED !
320          }
321        }
322        xiosGlobalComm=intraComm ;
323      }
324
325      MPI_Barrier(xiosGlobalComm);
326      if (commRank==0) std::remove(clientFileName.c_str()) ;         
327      MPI_Barrier(xiosGlobalComm);
328 
329      CXios::setXiosComm(xiosGlobalComm) ;
330
331      MPI_Comm commUnfree ;
332      MPI_Comm_dup(clientComm, &commUnfree ) ;
333 
334    }
335
336// to check on other architecture
337    void CClient::xiosGlobalCommByPublishing(MPI_Comm clientComm, const string& codeId)
338    {
339
340      // untested. need to be developped an a true MPI compliant library
341
342/*
343        // try to discover other client/server
344        // do you have a xios server ?
345        char portName[MPI_MAX_PORT_NAME];
346        int ierr ;
347        int commRank ;
348        MPI_Comm_rank(clientComm,&commRank) ;
349
350        MPI_Barrier(globalComm) ;
351        if (commRank==0)
352        {
353             
354          MPI_Comm_set_errhandler(MPI_COMM_WORLD, MPI_ERRORS_RETURN );
355          const char* serviceName=CXios::xiosCodeId.c_str() ;
356          ierr=MPI_Lookup_name(CXios::xiosCodeId.c_str(), MPI_INFO_NULL, portName);
357          MPI_Comm_set_errhandler(MPI_COMM_WORLD, MPI_ERRORS_ARE_FATAL );
358        }
359        ierr=MPI_SUCCESS ;
360        MPI_Bcast(&ierr,1,MPI_INT,0,clientComm) ;
361
362        if (ierr==MPI_SUCCESS) // you have a server
363        { 
364          MPI_Comm intraComm=clientComm ;
365          MPI_Comm interComm ;
366          for(int i=0 ; i<clientsCodeId.size(); i++)
367          { 
368            MPI_Comm_connect(portName, MPI_INFO_NULL, 0, intraComm, &interComm);
369            MPI_Intercomm_merge(interComm, true, &intraComm ) ;
370          }
371          xiosGlobalComm=intraComm ;
372        }
373        else  // you don't have any server
374        {
375          if (codeId==clientsCodeId[0]) // first code will publish his name
376          {
377
378            if (commRank==0) // if root process publish name
379            { 
380              MPI_Open_port(MPI_INFO_NULL, portName);
381              MPI_Publish_name(CXios::xiosCodeId.c_str(), MPI_INFO_NULL, portName);
382            }
383
384            MPI_Comm intraComm=clientComm ;
385            MPI_Comm interComm ;
386            for(int i=0 ; i<clientsCodeId.size()-1; i++)
387            { 
388              MPI_Comm_accept(portName, MPI_INFO_NULL, 0, intraComm, &interComm);
389              MPI_Intercomm_merge(interComm,false, &intraComm ) ;
390            }
391          }
392          else  // other clients are connecting to the first one
393          {
394            if (commRank==0)
395            {
396
397              MPI_Comm_set_errhandler(MPI_COMM_WORLD, MPI_ERRORS_RETURN );
398              ierr=MPI_Lookup_name(CXios::xiosCodeId.c_str(), MPI_INFO_NULL, portName);
399              MPI_Comm_set_errhandler(MPI_COMM_WORLD, MPI_ERRORS_ARE_FATAL );
400             }
401
402            MPI_Bcast(&ierr,1,MPI_INT,0,clientComm) ;
403
404            if (ierr==MPI_SUCCESS) // you can connect
405            { 
406              MPI_Comm intraComm=clientComm ;
407              MPI_Comm interComm ;
408              for(int i=0 ; i<clientsCodeId.size()-1; i++)
409              { 
410                MPI_Comm_connect(portName, MPI_INFO_NULL, 0, intraComm, &interComm);
411                MPI_Intercomm_merge(interComm, true, &intraComm ) ;
412              }
413              xiosGlobalComm=intraComm ;
414            }
415          }
416        } 
417      */
418    }
419
420
421///---------------------------------------------------------------
422/*!
423 * \fn void CClient::registerContext(const string& id, MPI_Comm contextComm)
424 * \brief Sends a request to create a context to server. Creates client/server contexts.
425 * \param [in] id id of context.
426 * \param [in] contextComm.
427 * Function is only called by client.
428 */
429    void CClient::registerContext(const string& id, MPI_Comm contextComm)
430    {
431      int commRank, commSize ;
432      MPI_Comm_rank(contextComm,&commRank) ;
433      MPI_Comm_size(contextComm,&commSize) ;
434
435      getPoolRessource()->createService(contextComm, id, 0, CServicesManager::CLIENT, 1) ;
436      getPoolRessource()->createService(contextComm, id+"_"+CXios::defaultWriterId, 0, CServicesManager::WRITER, 1) ;
437      getPoolRessource()->createService(contextComm, id+"_"+CXios::defaultReaderId, 0, CServicesManager::READER, 1) ;
438
439      if (commRank==0) while (!CXios::getServicesManager()->hasService(getPoolRessource()->getId(), id, 0)) { CXios::getDaemonsManager()->eventLoop();}
440
441      if (commRank==0) CXios::getContextsManager()->createServerContext(getPoolRessource()->getId(), id, 0, id) ;
442      int type=CServicesManager::CLIENT ;
443      string name = CXios::getContextsManager()->getServerContextName(getPoolRessource()->getId(), id, 0, type, id) ;
444      double time ;
445      double lastTime=0 ;
446      double latency=0 ;
447      bool out=false ;
448      while (!out)
449      {
450        time=MPI_Wtime() ;
451        if (time-lastTime > latency) 
452        {
453          out=CXios::getContextsManager()->hasContext(name, contextComm);
454          lastTime=time ;
455        }
456        if (!out) CXios::getDaemonsManager()->eventLoop() ;
457      }
458
459    }
460
461
462
463/*!
464 * \fn void CClient::callOasisEnddef(void)
465 * \brief Send the order to the servers to call "oasis_enddef". It must be done by each compound of models before calling oasis_enddef on client side
466 * Function is only called by client.
467 */
468    void CClient::callOasisEnddef(void)
469    {
470      bool oasisEnddef=CXios::getin<bool>("call_oasis_enddef",true) ;
471      if (!oasisEnddef) ERROR("void CClient::callOasisEnddef(void)", <<"Function xios_oasis_enddef called but variable <call_oasis_enddef> is set to false."<<endl
472                                                                     <<"Variable <call_oasis_enddef> must be set to true"<<endl) ;
473      if (!CXios::isClient) // != isServer (change recently )
474      // Attached mode
475      {
476        // nothing to do   
477      }
478      else
479      {
480        int rank ;
481        int msg=0 ;
482
483        MPI_Comm_rank(intraComm_,&rank) ;
484        if (rank==0) 
485        {
486          MPI_Send(&msg,1,MPI_INT,0,5,interComm_) ; // tags oasis_endded = 5
487        }
488
489      }
490    }
491
492    void CClient::finalize(void)
493    {
494     
495      MPI_Barrier(clientsComm_) ;
496      int commRank ;
497      MPI_Comm_rank(clientsComm_, &commRank) ;
498      if (commRank==0) CXios::getRessourcesManager()->finalize() ;
499     
500      CTimer::get("XIOS init/finalize",false).suspend() ;
501      CTimer::get("XIOS").suspend() ;
502      CXios::finalizeDaemonsManager() ;
503      finalizePoolRessource() ;
504      CContext::removeAllContexts() ; // free memory for related context
505
506      CXios::getMpiGarbageCollector().release() ; // release unfree MPI ressources
507      if (!is_MPI_Initialized)
508      {
509        if (!CXios::usingOasis) MPI_Finalize() ;
510      }
511     
512      info(20) << "Client side context is finalized"<<endl ;
513      report(0) <<" Performance report : Whole time from XIOS init and finalize: "<< CTimer::get("XIOS init/finalize").getCumulatedTime()<<" s"<<endl ;
514      report(0) <<" Performance report : total time spent for XIOS : "<< CTimer::get("XIOS").getCumulatedTime()<<" s"<<endl ;
515      report(0)<< " Performance report : time spent for waiting free buffer : "<< CTimer::get("Blocking time").getCumulatedTime()<<" s"<<endl ;
516      report(0)<< " Performance report : Ratio : "<< CTimer::get("Blocking time").getCumulatedTime()/CTimer::get("XIOS init/finalize").getCumulatedTime()*100.<<" %"<<endl ;
517      report(0)<< " Performance report : This ratio must be close to zero. Otherwise it may be usefull to increase buffer size or numbers of server"<<endl ;
518//      report(0)<< " Memory report : Current buffer_size : "<<CXios::bufferSize<<endl ;
519      report(0)<< " Memory report : Minimum buffer size required : " << CClientBuffer::maxRequestSize << " bytes" << endl ;
520      report(0)<< " Memory report : increasing it by a factor will increase performance, depending of the volume of data wrote in file at each time step of the file"<<endl ;
521      report(100)<<CTimer::getAllCumulatedTime()<<endl ;
522      CWorkflowGraph::drawWorkFlowGraph_client();
523
524      xios::releaseStaticAllocation() ;
525
526    }
527   
528    void CClient::finalizePoolRessource() 
529    { 
530      delete poolRessource_ ; poolRessource_=nullptr ;
531    }
532
533    /*!
534    * Return global rank without oasis and current rank in model intraComm in case of oasis
535    */
536   int CClient::getRank()
537   {
538     return rank_;
539   }
540
541    /*!
542    * Open a file specified by a suffix and an extension and use it for the given file buffer.
543    * The file name will be suffix+rank+extension.
544    *
545    * \param fileName[in] protype file name
546    * \param ext [in] extension of the file
547    * \param fb [in/out] the file buffer
548    */
549    void CClient::openStream(const StdString& fileName, const StdString& ext, std::filebuf* fb)
550    {
551      StdStringStream fileNameClient;
552      int numDigit = 0;
553      int size = 0;
554      int rank;
555      MPI_Comm_size(CXios::getGlobalComm(), &size);
556      MPI_Comm_rank(CXios::getGlobalComm(),&rank);
557      while (size)
558      {
559        size /= 10;
560        ++numDigit;
561      }
562
563      fileNameClient << fileName << "_" << std::setfill('0') << std::setw(numDigit) << rank << ext;
564
565      fb->open(fileNameClient.str().c_str(), std::ios::out);
566      if (!fb->is_open())
567        ERROR("void CClient::openStream(const StdString& fileName, const StdString& ext, std::filebuf* fb)",
568              << std::endl << "Can not open <" << fileNameClient.str() << "> file to write the client log(s).");
569    }
570
571    /*!
572    * \brief Open a file stream to write the info logs
573    * Open a file stream with a specific file name suffix+rank
574    * to write the info logs.
575    * \param fileName [in] protype file name
576    */
577    void CClient::openInfoStream(const StdString& fileName)
578    {
579      std::filebuf* fb = m_infoStream.rdbuf();
580      openStream(fileName, ".out", fb);
581
582      info.write2File(fb);
583      report.write2File(fb);
584    }
585
586    //! Write the info logs to standard output
587    void CClient::openInfoStream()
588    {
589      info.write2StdOut();
590      report.write2StdOut();
591    }
592
593    //! Close the info logs file if it opens
594    void CClient::closeInfoStream()
595    {
596      if (m_infoStream.is_open()) m_infoStream.close();
597    }
598
599    /*!
600    * \brief Open a file stream to write the error log
601    * Open a file stream with a specific file name suffix+rank
602    * to write the error log.
603    * \param fileName [in] protype file name
604    */
605    void CClient::openErrorStream(const StdString& fileName)
606    {
607      std::filebuf* fb = m_errorStream.rdbuf();
608      openStream(fileName, ".err", fb);
609
610      error.write2File(fb);
611    }
612
613    //! Write the error log to standard error output
614    void CClient::openErrorStream()
615    {
616      error.write2StdErr();
617    }
618
619    //! Close the error log file if it opens
620    void CClient::closeErrorStream()
621    {
622      if (m_errorStream.is_open()) m_errorStream.close();
623    }
624}
Note: See TracBrowser for help on using the repository browser.