source: trunk/lib/Stats.cc @ 1199

Last change on this file since 1199 was 1199, checked in by Peter Johansson, 12 years ago

prefer operator+= rather than operator+

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 16.3 KB
Line 
1// $Id: Stats.cc 1199 2010-10-04 12:37:10Z peter $
2
3/*
4  Copyright (C) 2005 Peter Johansson
5  Copyright (C) 2006, 2007, 2008, 2009 Jari Häkkinen, Peter Johansson
6  Copyright (C) 2010 Peter Johansson
7
8  This file is part of svndigest, http://dev.thep.lu.se/svndigest
9
10  svndigest is free software; you can redistribute it and/or modify it
11  under the terms of the GNU General Public License as published by
12  the Free Software Foundation; either version 3 of the License, or
13  (at your option) any later version.
14
15  svndigest is distributed in the hope that it will be useful, but
16  WITHOUT ANY WARRANTY; without even the implied warranty of
17  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18  General Public License for more details.
19
20  You should have received a copy of the GNU General Public License
21  along with svndigest. If not, see <http://www.gnu.org/licenses/>.
22*/
23
24#include "Stats.h"
25
26#include "Colors.h"
27#include "Configuration.h"
28#include "Functor.h"
29#include "Graph.h"
30#include "SVNblame.h"
31#include "SVNinfo.h"
32#include "utility.h"
33
34#include <algorithm>
35#include <cassert>
36#include <cstdlib>
37#include <fstream>
38#include <iostream>
39#include <iterator>
40#include <map>
41#include <numeric>
42#include <string>
43#include <sstream>
44#include <unistd.h>
45#include <utility>
46#include <vector>
47
48namespace theplu{
49namespace svndigest{
50
51
52  Stats::Stats(const std::string& path)
53    : stats_(std::vector<Author2Vector>(LineTypeParser::total+1))
54  {
55    // Make sure latest revision is set properly
56    SVNinfo svn_info(path);
57    revision_=svn_info.rev();
58    last_changed_rev_=svn_info.last_changed_rev();
59    reset();
60
61    std::ostringstream ss;
62    if (const std::vector<std::pair<std::string, std::string> >* 
63        codons = Configuration::instance().codon(path)) {
64      using std::string;
65      typedef std::vector<std::pair<string, string> >::const_iterator citerator;
66      for ( citerator codon=codons->begin(); codon!=codons->end(); ++codon) 
67        ss << codon->first << codon->second;
68    }
69    config_code_ = ss.str();
70    // we don't allow newline in codon because we parse the string
71    // with newline and thus want the string to be a one-liner.
72    std::replace(config_code_.begin(), config_code_.end(), '\n', ' ');
73  }
74
75
76  Stats::~Stats(void)
77  {
78  }
79
80 
81  void Stats::add(const std::vector<std::map<std::string, SparseVector> >& data)
82  {
83    // loop over line types
84    for (size_t lt = 0; lt<data.size(); ++lt) {
85      std::map<std::string, SparseVector>::const_iterator iter=data[lt].begin();
86      std::map<std::string, SparseVector>::const_iterator last=data[lt].end();
87      // loop over users
88      for ( ; iter!=last; ++iter) {
89        add_author(iter->first);
90        SumVector tmpvec;
91        accumulate(iter->second, tmpvec);
92        stats_[lt][iter->first] += tmpvec;
93      }
94    }
95  }
96
97
98  /*
99  void Stats::accumulate(std::vector<unsigned int>& vec,
100                         svn_revnum_t rev) const
101  {
102    assert(rev>0);
103    if (vec.empty()){
104      // just to allow call to vec.back() below
105      vec.resize(1,0);
106    }
107    else if (vec.begin()+rev-1 < vec.end())
108      std::partial_sum(vec.begin()+rev-1,vec.end(),vec.begin()+rev-1);
109    // static_cast to remove annoying compiler warning
110    if (vec.size() < static_cast<size_t>(revision()+1))
111      vec.resize(revision()+1, vec.back());
112  }
113  */
114  /*
115  void Stats::accumulate_stats(svn_revnum_t rev)
116  {
117    if (!rev)
118      rev = 1;
119    for (std::set<std::string>::const_iterator iter(authors().begin());
120         iter!=authors().end(); ++iter) {
121      std::vector<unsigned int>& code = code_stats()[*iter];
122      accumulate(code, rev);
123      std::vector<unsigned int>& comments = comment_stats()[*iter];
124      accumulate(comments, rev);
125      std::vector<unsigned int>& other = other_stats()[*iter];
126      accumulate(other, rev);
127      std::vector<unsigned int>& copyright = copyright_stats()[*iter];
128      accumulate(copyright, rev);
129    }
130  }
131  */
132  /*
133  void Stats::add(const std::string& user, svn_revnum_t rev,
134                  LineTypeParser::line_type lt, unsigned int n)
135  {
136    assert(user.size());
137    add_author(user);
138
139    if (lt==LineTypeParser::code)
140      add(code_stats()[user], rev, n);
141    else if (lt==LineTypeParser::comment)
142      add(comment_stats()[user], rev, n);
143    else if (lt==LineTypeParser::other)
144      add(other_stats()[user], rev, n);
145    else if (lt==LineTypeParser::copyright)
146      add(copyright_stats()[user], rev, n);
147  }
148
149  void Stats::add(SumVector& vec, svn_revnum_t rev, unsigned int n)
150  {
151    vec.set(rev, vec[rev] + n);
152  }
153  */
154
155
156  void Stats::add_author(std::string name)
157  {
158    authors_.insert(name);
159  }
160
161
162  void Stats::add_authors(std::set<std::string>::const_iterator first, 
163                          std::set<std::string>::const_iterator last)
164  {
165    authors_.insert(first, last);
166  }
167
168
169  const std::set<std::string>& Stats::authors(void) const
170  {
171    return authors_;
172  }
173
174
175  void Stats::calc_all(void)
176  {
177    for (int lt=0; lt <= 4; ++lt) {
178      stats_[lt]["all"].clear();
179      for (std::map<std::string, SumVector>::iterator i = stats_[lt].begin();
180           i!=stats_[lt].end(); ++i) {
181        stats_[lt]["all"] += i->second;
182      }
183    }
184
185    comment_or_copy_stats()["all"] = comment_stats()["all"];
186    comment_or_copy_stats()["all"] += copyright_stats()["all"];
187
188    total_stats()["all"] = comment_or_copy_stats()["all"];
189    total_stats()["all"] += code_stats()["all"];
190    total_stats()["all"] += other_stats()["all"];
191  }
192
193
194  void Stats::calc_total(void)
195  {
196    for (std::set<std::string>::const_iterator iter(authors().begin());
197         iter!=authors().end(); ++iter) {
198      // FIXME: we should uer operator+=
199      const SumVector& code = code_stats()[*iter];
200      const SumVector& comments = comment_stats()[*iter];
201      const SumVector& other = other_stats()[*iter];
202      const SumVector& copy = copyright_stats()[*iter];
203
204      total_stats()[*iter] = code + comments + other + copy;
205      assert(total_stats()[*iter][100] == 
206             code[100]+comments[100]+other[100]+copy[100]);
207    }
208
209  }
210
211
212  void Stats::calc_comment_or_copy(void)
213  {
214    for (std::set<std::string>::const_iterator iter(authors().begin());
215         iter!=authors().end(); ++iter) {
216      const SumVector& comments = comment_stats()[*iter];
217      const SumVector& copy = copyright_stats()[*iter];
218
219      // FIXME: we should use operator+=
220      comment_or_copy_stats()[*iter] = comments + copy;
221    }
222
223  }
224
225
226  unsigned int Stats::code(const std::string& user) const
227  {
228    return get_back(code_stats(), user);
229  }
230
231
232  unsigned int Stats::comments(const std::string& user) const
233  {
234    return get_back(comment_or_copy_stats(), user);
235  }
236
237
238  unsigned int Stats::empty(const std::string& user) const
239  {
240    return get_back(other_stats(), user);
241  }
242
243
244  unsigned int Stats::get_back(const Author2Vector& m, std::string user) const
245  {
246    A2VConstIter iter(m.find(std::string(user)));
247    if (iter==m.end() || iter->second.empty()) 
248      return 0;
249    return iter->second.back();
250  }
251
252
253  const SumVector& Stats::get_vector(const Author2Vector& m, 
254                                     const std::string& user) const
255  {
256    A2VConstIter iter(m.find(std::string(user)));
257    if (iter==m.end()) 
258      throw std::runtime_error(user+std::string(" not found i Stats"));
259    return iter->second;
260  }
261
262
263  svn_revnum_t Stats::last_changed_rev(void) const
264  {
265    return last_changed_rev_;
266  }
267
268
269  unsigned int Stats::lines(const std::string& user) const
270  {
271    return get_back(total_stats(), user);
272  }
273
274
275  void Stats::load(std::istream& is, Author2Vector& m)
276  {
277    while (m.size() < authors().size()+1 && is.good()) {
278      std::string name;
279      std::getline(is, name);
280      SparseVector vec;
281      std::string line;
282      std::getline(is, line);
283      std::stringstream ss(line);
284      while (ss.good()) {
285        svn_revnum_t rev=0;
286        unsigned int count=0;
287        ss >> rev;
288        ss >> count;
289        assert(rev<=revision_);
290        if (!count)
291          break;
292        vec.set(rev, count);
293      }
294      SumVector& sumvec = m[name];
295      accumulate(vec, sumvec);
296    }
297  }
298
299
300  svn_revnum_t Stats::load_cache(std::istream& is, bool& latest_ver)
301  {
302    std::string str;
303    getline(is, str);
304    if (str == "CACHE FILE VERSION 8") {
305      latest_ver = true;
306      return load_cache8(is);
307    }
308    latest_ver = false;
309    if (str == "CACHE FILE VERSION 7")
310      return load_cache7(is);
311
312    if (str == "CACHE FILE VERSION 6")
313      std::cout << "cache file is obsolete; "
314                << "retrieving statistics from repository.\n";
315    return 0;
316  }
317
318
319  svn_revnum_t Stats::load_cache8(std::istream& is)
320  {
321    std::string line;
322    getline(is, line);
323    if (line!=config_code_) {
324      std::cout << "cache file is for different configuration.\n"
325                << "config code: '" << config_code_ << "'\n"
326                << "config code in cache file: '" << line << "'\n"
327                << "retrieving statistics from repository.\n";
328      return 0;
329    }
330    return load_cache7(is);
331  }
332
333
334  svn_revnum_t Stats::load_cache7(std::istream& is)
335  {
336    svn_revnum_t rev;
337    is >> rev;
338    reset();
339    size_t a_size=0;
340    is >> a_size;
341    std::string str;
342    while (authors().size()<a_size && is.good()){
343      getline(is, str);
344      if (str.size())
345        add_author(str);
346    }
347    getline(is, str);
348    if (str!=cache_check_str()) {
349      return 0;
350    }
351    for (size_t i=0; i<stats_.size(); ++i){
352      load(is, stats_[i]);
353      getline(is, str);
354      if (str!=cache_check_str()) {
355        return 0;
356      }
357    }
358    return rev;
359  }
360
361
362  void Stats::map_add(A2VConstIter first1, A2VConstIter last1, 
363                      Author2Vector& map)
364  {
365    A2VIter first2(map.begin());
366    Author2Vector::key_compare compare;
367    while ( first1 != last1) { 
368      // key of first1 less than key of first2
369      if (first2==map.end() || compare(first1->first,first2->first)) {
370        first2 = map.insert(first2, *first1);
371        ++first1;
372      }
373      // key of first2 less than key of first1
374      else if ( compare(first2->first, first1->first)) {
375        ++first2;
376      }
377      // keys are equivalent
378      else {
379        first2->second = first1->second + first2->second;
380        ++first1;
381        ++first2;
382      }
383    }
384  }
385
386
387  unsigned int Stats::max_element(const SumVector& vec) const
388  {
389    return std::max_element(vec.begin(), vec.end(), 
390       pair_value_compare<const svn_revnum_t, unsigned int>())->second;
391  }
392
393
394  void Stats::parse(const std::string& path, svn_revnum_t rev)
395  {
396    // reset stats to zero for [rev, inf)
397    for (size_t i=0; i<stats_.size(); ++i)
398      for (A2VIter iter=stats_[i].begin(); iter!=stats_[i].end(); ++iter) {
399        //iter->second.resize(rev,0);
400        //iter->second.resize(revision(),0);
401      }
402    do_parse(path, rev);
403    calc_comment_or_copy();
404    calc_total();
405    calc_all();
406   
407    assert(total_stats().size());
408    assert(code_stats().size());
409    assert(comment_or_copy_stats().size());
410    assert(other_stats().size());
411  }
412
413
414  std::string Stats::plot(const std::string& filename,
415                          const std::string& linetype) const
416  {
417    const std::string& format = Configuration::instance().image_format();
418    if (format=="none")
419      return filename;
420    plot(filename, linetype, format);
421    const std::string& anchor_format = 
422      Configuration::instance().image_anchor_format();
423
424    if (format!=anchor_format)
425      plot(filename, linetype, anchor_format);
426    return filename;
427  }
428
429
430  void Stats::plot(const std::string& filename,
431                   const std::string& linetype,
432                   const std::string& format) const
433  {
434    Graph gp(filename+"."+format, format);
435    const Author2Vector* stat=NULL;
436    if (linetype=="total")
437      stat = &total_stats();
438    else if (linetype=="code")
439      stat = &code_stats();
440    else if (linetype=="comments")
441      stat = &comment_or_copy_stats();
442    else if (linetype=="empty")
443      stat = &other_stats();
444    assert(stat);
445    assert(stat->size());
446    assert(stat->find("all")!=stat->end());
447    // FIXME: try keep a const& to avoid copying
448    SumVector total=get_vector(*stat, "all");   
449    total.resize(revision());
450    double yrange_max=1.03 * max_element(total) +1.0;
451    gp.ymax(yrange_max);
452
453    typedef std::vector<std::pair<std::string, SumVector> > vec_type;
454    vec_type author_cont;
455    author_cont.reserve(stat->size());
456    for (std::set<std::string>::const_iterator i=authors_.begin(); 
457         i != authors_.end(); ++i) {
458      assert(stat->find(*i)!=stat->end());
459      // FIXME: avoid this copying?
460      SumVector vec = get_vector(*stat,*i);
461      vec.resize(revision());
462      if (max_element(vec)) {
463        author_cont.push_back(std::make_pair(*i,vec));
464      }
465    }
466
467    LessReversed<SumVector> lr;
468    PairSecondCompare<std::string, SumVector, LessReversed<SumVector> > 
469      compare(lr);
470    std::sort(author_cont.begin(), author_cont.end(), compare);
471
472    vec_type::iterator end(author_cont.end());
473    vec_type::iterator i(author_cont.begin());
474    const vec_type::size_type maxauthors=8;
475    int authskip=author_cont.size()-maxauthors;
476    if (authskip>1) {
477      // only use others if there is more than 1 author to be skipped,
478      // there is no reason to add only 1 author to others.
479      vec_type::iterator j(i);
480      i+=authskip;
481      SumVector init;
482      SumVector others =
483        std::accumulate(j, i, init, PairValuePlus<std::string, SumVector>());
484      unsigned char r, g, b;
485      std::string label("others");
486      Colors::instance().get_color(label, r,g,b);
487      gp.current_color(r,g,b);
488      gp.plot(others, label, others.back());
489    }
490    for ( ; i!=end; ++i) {
491      unsigned char r, g, b;
492      Colors::instance().get_color(i->first,r,g,b);
493      gp.current_color(r,g,b);
494      gp.plot(i->second, i->first, get_back(*stat, i->first));
495    }
496    gp.current_color(255,0,0);
497    gp.plot(total, "total", get_back(*stat, "all"));
498  }
499
500
501  void Stats::plot_summary(const std::string& filename) const
502  {
503    const std::string& format = Configuration::instance().image_format();
504    if (format=="none")
505      return;
506    plot_summary(filename, format);
507    const std::string& anchor_format = 
508      Configuration::instance().image_anchor_format();
509
510    if (format!=anchor_format)
511      plot_summary(filename, anchor_format);
512  }
513
514
515  void Stats::plot_summary(const std::string& filename, 
516                           const std::string& format) const
517  {
518    Graph gp(filename+"."+format, format);
519    // FIXME: why not const&
520    SumVector total = get_vector(total_stats(), "all");
521    double yrange_max=1.03*total.back()+1;
522    gp.ymax(yrange_max);
523   
524    // FIXME: why not const&
525    SumVector x(get_vector(code_stats(), "all"));
526    gp.current_color(255,255,0);
527    gp.plot(x, "code", x.back());
528
529    x = get_vector(comment_or_copy_stats(), "all");
530    gp.current_color(0,0,255);
531    gp.plot(x, "comment", x.back());
532
533    x = get_vector(other_stats(), "all");
534    gp.current_color(0,255,0);
535    gp.plot(x, "other", x.back());
536
537    gp.current_color(255,0,0);
538    gp.plot(total, "total", total.back());
539  }
540
541
542  void Stats::print(std::ostream& os) const
543  {
544    // indicate that cache file is version 8 here , but keep remaning
545    // 'CACHE FILE VERSION' as 'VERSION 7' to allow load_cache7() to
546    // be used from load_cache8().
547    os << "CACHE FILE VERSION 8\n";
548    os << config_code_ << "\n";
549    os << last_changed_rev() << " ";
550    os << authors().size() << "\n";
551
552    std::copy(authors().begin(), authors().end(), 
553              std::ostream_iterator<std::string>(os, "\n"));
554    os << cache_check_str() << "\n";
555    for (size_t i=0; i<stats_.size(); ++i){
556      print(os, stats_[i]);
557      os << cache_check_str() << "\n";
558    }
559  }
560
561
562  void Stats::print(std::ostream& os, const Author2Vector& m) const
563  {
564    for (A2VConstIter i(m.begin()); i!=m.end(); ++i){
565      os << i->first << "\n";
566      if (i->second[0])
567        os << 0 << " " << i->second[0] << " ";
568      // FIXME: ise Vector::iterator
569      for (size_t j=1; j<i->second.size(); ++j) {
570        // only print if stats changes in this rev
571        if (i->second[j] != i->second[j-1]) {
572          os << j << " " << i->second[j] - i->second[j-1] << " ";
573        }
574      }
575      os << "\n";
576    }
577  }
578
579  void Stats::reset(void)
580  {
581    for (size_t i=0; i<stats_.size(); ++i){
582      stats_[i].clear();
583      stats_[i]["all"].resize(revision()+1);
584    }
585    authors_.clear();
586  }
587
588
589  void Stats::resize(svn_revnum_t rev)
590  {
591    // set size on vectors
592    for (size_t i=0; i<stats_.size(); ++i) {
593      for (A2VIter iter=stats_[i].begin(); iter!=stats_[i].end(); ++iter) {
594        iter->second.resize(rev);
595      }
596    }
597  }
598
599
600  svn_revnum_t Stats::revision(void) const 
601  { 
602    return revision_; 
603  }
604
605
606  Stats& Stats::operator+=(const Stats& rhs)
607  {
608    revision_ = std::max(revision_, rhs.revision_);
609    last_changed_rev_ = std::max(last_changed_rev_, rhs.last_changed_rev_);
610    add_authors(rhs.authors().begin(), rhs.authors().end());
611    assert(stats_.size()==rhs.stats_.size());
612    for (size_t i=0; i<stats_.size(); ++i)
613      map_add(rhs.stats_[i].begin(), rhs.stats_[i].end(), stats_[i]);
614   
615    return *this;
616  }
617
618 
619  size_t Stats::operator()(int linetype, std::string author, 
620                           svn_revnum_t rev) const
621  {
622    assert(linetype<=LineTypeParser::total);
623    assert(static_cast<size_t>(linetype) < stats_.size());
624    assert(rev>=0);
625    A2VConstIter i = stats_[linetype].find(author);
626    if (i==stats_[linetype].end()){
627      std::stringstream msg;
628      msg << __FILE__ << ": author: " << author << " does not exist"; 
629      throw std::runtime_error(msg.str());
630    }
631    assert(rev <= revision());
632    //    assert(rev < static_cast<svn_revnum_t>(i->second.size()));
633    return i->second[rev];
634  }
635
636}} // end of namespace svndigest and namespace theplu
Note: See TracBrowser for help on using the repository browser.