source: trunk/lib/Stats.cc @ 1194

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

refs #475. First version using Sparse Vector class. This change is obviously quite invasive and thus the size of this commit is larger than what is preferred. I've tried to limit as much as possible and left optimization and style issues behind with a note 'FIXME'.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 16.3 KB
Line 
1// $Id: Stats.cc 1194 2010-10-03 23:39:12Z 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    // FIXME: we should use operator+=
178    SumVector init;
179    for (int lt=0; lt <= 4; ++lt) {
180      stats_[lt]["all"].clear();
181      stats_[lt]["all"] =
182        std::accumulate(stats_[lt].begin(), 
183                        stats_[lt].end(), init,
184                        PairValuePlus<std::string, SumVector>());
185    }
186
187    comment_or_copy_stats()["all"] = 
188      comment_stats()["all"] + copyright_stats()["all"];
189
190    total_stats()["all"] = code_stats()["all"] + 
191      comment_or_copy_stats()["all"] + other_stats()["all"];
192  }
193
194
195  void Stats::calc_total(void)
196  {
197    for (std::set<std::string>::const_iterator iter(authors().begin());
198         iter!=authors().end(); ++iter) {
199      // FIXME: we should uer operator+=
200      const SumVector& code = code_stats()[*iter];
201      const SumVector& comments = comment_stats()[*iter];
202      const SumVector& other = other_stats()[*iter];
203      const SumVector& copy = copyright_stats()[*iter];
204
205      total_stats()[*iter] = code + comments + other + copy;
206      assert(total_stats()[*iter][100] == 
207             code[100]+comments[100]+other[100]+copy[100]);
208    }
209
210  }
211
212
213  void Stats::calc_comment_or_copy(void)
214  {
215    for (std::set<std::string>::const_iterator iter(authors().begin());
216         iter!=authors().end(); ++iter) {
217      const SumVector& comments = comment_stats()[*iter];
218      const SumVector& copy = copyright_stats()[*iter];
219
220      // FIXME: we should use operator+=
221      comment_or_copy_stats()[*iter] = comments + copy;
222    }
223
224  }
225
226
227  unsigned int Stats::code(const std::string& user) const
228  {
229    return get_back(code_stats(), user);
230  }
231
232
233  unsigned int Stats::comments(const std::string& user) const
234  {
235    return get_back(comment_or_copy_stats(), user);
236  }
237
238
239  unsigned int Stats::empty(const std::string& user) const
240  {
241    return get_back(other_stats(), user);
242  }
243
244
245  unsigned int Stats::get_back(const Author2Vector& m, std::string user) const
246  {
247    A2VConstIter iter(m.find(std::string(user)));
248    if (iter==m.end() || iter->second.empty()) 
249      return 0;
250    return iter->second.back();
251  }
252
253
254  const SumVector& Stats::get_vector(const Author2Vector& m, 
255                                     const std::string& user) const
256  {
257    A2VConstIter iter(m.find(std::string(user)));
258    if (iter==m.end()) 
259      throw std::runtime_error(user+std::string(" not found i Stats"));
260    return iter->second;
261  }
262
263
264  svn_revnum_t Stats::last_changed_rev(void) const
265  {
266    return last_changed_rev_;
267  }
268
269
270  unsigned int Stats::lines(const std::string& user) const
271  {
272    return get_back(total_stats(), user);
273  }
274
275
276  void Stats::load(std::istream& is, Author2Vector& m)
277  {
278    while (m.size() < authors().size()+1 && is.good()) {
279      std::string name;
280      std::getline(is, name);
281      SparseVector vec;
282      std::string line;
283      std::getline(is, line);
284      std::stringstream ss(line);
285      while (ss.good()) {
286        svn_revnum_t rev=0;
287        unsigned int count=0;
288        ss >> rev;
289        ss >> count;
290        assert(rev<=revision_);
291        if (!count)
292          break;
293        vec.set(rev, count);
294      }
295      SumVector& sumvec = m[name];
296      accumulate(vec, sumvec);
297    }
298  }
299
300
301  svn_revnum_t Stats::load_cache(std::istream& is, bool& latest_ver)
302  {
303    std::string str;
304    getline(is, str);
305    if (str == "CACHE FILE VERSION 8") {
306      latest_ver = true;
307      return load_cache8(is);
308    }
309    latest_ver = false;
310    if (str == "CACHE FILE VERSION 7")
311      return load_cache7(is);
312
313    if (str == "CACHE FILE VERSION 6")
314      std::cout << "cache file is obsolete; "
315                << "retrieving statistics from repository.\n";
316    return 0;
317  }
318
319
320  svn_revnum_t Stats::load_cache8(std::istream& is)
321  {
322    std::string line;
323    getline(is, line);
324    if (line!=config_code_) {
325      std::cout << "cache file is for different configuration.\n"
326                << "config code: '" << config_code_ << "'\n"
327                << "config code in cache file: '" << line << "'\n"
328                << "retrieving statistics from repository.\n";
329      return 0;
330    }
331    return load_cache7(is);
332  }
333
334
335  svn_revnum_t Stats::load_cache7(std::istream& is)
336  {
337    svn_revnum_t rev;
338    is >> rev;
339    reset();
340    size_t a_size=0;
341    is >> a_size;
342    std::string str;
343    while (authors().size()<a_size && is.good()){
344      getline(is, str);
345      if (str.size())
346        add_author(str);
347    }
348    getline(is, str);
349    if (str!=cache_check_str()) {
350      return 0;
351    }
352    for (size_t i=0; i<stats_.size(); ++i){
353      load(is, stats_[i]);
354      getline(is, str);
355      if (str!=cache_check_str()) {
356        return 0;
357      }
358    }
359    return rev;
360  }
361
362
363  void Stats::map_add(A2VConstIter first1, A2VConstIter last1, 
364                      Author2Vector& map)
365  {
366    A2VIter first2(map.begin());
367    Author2Vector::key_compare compare;
368    while ( first1 != last1) { 
369      // key of first1 less than key of first2
370      if (first2==map.end() || compare(first1->first,first2->first)) {
371        first2 = map.insert(first2, *first1);
372        ++first1;
373      }
374      // key of first2 less than key of first1
375      else if ( compare(first2->first, first1->first)) {
376        ++first2;
377      }
378      // keys are equivalent
379      else {
380        first2->second = first1->second + first2->second;
381        ++first1;
382        ++first2;
383      }
384    }
385  }
386
387
388  unsigned int Stats::max_element(const SumVector& vec) const
389  {
390    return std::max_element(vec.begin(), vec.end(), 
391       pair_value_compare<const svn_revnum_t, unsigned int>())->second;
392  }
393
394
395  void Stats::parse(const std::string& path, svn_revnum_t rev)
396  {
397    // reset stats to zero for [rev, inf)
398    for (size_t i=0; i<stats_.size(); ++i)
399      for (A2VIter iter=stats_[i].begin(); iter!=stats_[i].end(); ++iter) {
400        //iter->second.resize(rev,0);
401        //iter->second.resize(revision(),0);
402      }
403    do_parse(path, rev);
404    calc_comment_or_copy();
405    calc_total();
406    calc_all();
407   
408    assert(total_stats().size());
409    assert(code_stats().size());
410    assert(comment_or_copy_stats().size());
411    assert(other_stats().size());
412  }
413
414
415  std::string Stats::plot(const std::string& filename,
416                          const std::string& linetype) const
417  {
418    const std::string& format = Configuration::instance().image_format();
419    if (format=="none")
420      return filename;
421    plot(filename, linetype, format);
422    const std::string& anchor_format = 
423      Configuration::instance().image_anchor_format();
424
425    if (format!=anchor_format)
426      plot(filename, linetype, anchor_format);
427    return filename;
428  }
429
430
431  void Stats::plot(const std::string& filename,
432                   const std::string& linetype,
433                   const std::string& format) const
434  {
435    Graph gp(filename+"."+format, format);
436    const Author2Vector* stat=NULL;
437    if (linetype=="total")
438      stat = &total_stats();
439    else if (linetype=="code")
440      stat = &code_stats();
441    else if (linetype=="comments")
442      stat = &comment_or_copy_stats();
443    else if (linetype=="empty")
444      stat = &other_stats();
445    assert(stat);
446    assert(stat->size());
447    assert(stat->find("all")!=stat->end());
448    // FIXME: try keep a const& to avoid copying
449    SumVector total=get_vector(*stat, "all");   
450    total.resize(revision());
451    double yrange_max=1.03 * max_element(total) +1.0;
452    gp.ymax(yrange_max);
453
454    typedef std::vector<std::pair<std::string, SumVector> > vec_type;
455    vec_type author_cont;
456    author_cont.reserve(stat->size());
457    for (std::set<std::string>::const_iterator i=authors_.begin(); 
458         i != authors_.end(); ++i) {
459      assert(stat->find(*i)!=stat->end());
460      // FIXME: avoid this copying?
461      SumVector vec = get_vector(*stat,*i);
462      vec.resize(revision());
463      if (max_element(vec)) {
464        author_cont.push_back(std::make_pair(*i,vec));
465      }
466    }
467
468    LessReversed<SumVector> lr;
469    PairSecondCompare<std::string, SumVector, LessReversed<SumVector> > 
470      compare(lr);
471    std::sort(author_cont.begin(), author_cont.end(), compare);
472
473    vec_type::iterator end(author_cont.end());
474    vec_type::iterator i(author_cont.begin());
475    const vec_type::size_type maxauthors=8;
476    int authskip=author_cont.size()-maxauthors;
477    if (authskip>1) {
478      // only use others if there is more than 1 author to be skipped,
479      // there is no reason to add only 1 author to others.
480      vec_type::iterator j(i);
481      i+=authskip;
482      SumVector init;
483      SumVector others =
484        std::accumulate(j, i, init, PairValuePlus<std::string, SumVector>());
485      unsigned char r, g, b;
486      std::string label("others");
487      Colors::instance().get_color(label, r,g,b);
488      gp.current_color(r,g,b);
489      gp.plot(others, label, others.back());
490    }
491    for ( ; i!=end; ++i) {
492      unsigned char r, g, b;
493      Colors::instance().get_color(i->first,r,g,b);
494      gp.current_color(r,g,b);
495      gp.plot(i->second, i->first, get_back(*stat, i->first));
496    }
497    gp.current_color(255,0,0);
498    gp.plot(total, "total", get_back(*stat, "all"));
499  }
500
501
502  void Stats::plot_summary(const std::string& filename) const
503  {
504    const std::string& format = Configuration::instance().image_format();
505    if (format=="none")
506      return;
507    plot_summary(filename, format);
508    const std::string& anchor_format = 
509      Configuration::instance().image_anchor_format();
510
511    if (format!=anchor_format)
512      plot_summary(filename, anchor_format);
513  }
514
515
516  void Stats::plot_summary(const std::string& filename, 
517                           const std::string& format) const
518  {
519    Graph gp(filename+"."+format, format);
520    // FIXME: why not const&
521    SumVector total = get_vector(total_stats(), "all");
522    double yrange_max=1.03*total.back()+1;
523    gp.ymax(yrange_max);
524   
525    // FIXME: why not const&
526    SumVector x(get_vector(code_stats(), "all"));
527    gp.current_color(255,255,0);
528    gp.plot(x, "code", x.back());
529
530    x = get_vector(comment_or_copy_stats(), "all");
531    gp.current_color(0,0,255);
532    gp.plot(x, "comment", x.back());
533
534    x = get_vector(other_stats(), "all");
535    gp.current_color(0,255,0);
536    gp.plot(x, "other", x.back());
537
538    gp.current_color(255,0,0);
539    gp.plot(total, "total", total.back());
540  }
541
542
543  void Stats::print(std::ostream& os) const
544  {
545    // indicate that cache file is version 8 here , but keep remaning
546    // 'CACHE FILE VERSION' as 'VERSION 7' to allow load_cache7() to
547    // be used from load_cache8().
548    os << "CACHE FILE VERSION 8\n";
549    os << config_code_ << "\n";
550    os << last_changed_rev() << " ";
551    os << authors().size() << "\n";
552
553    std::copy(authors().begin(), authors().end(), 
554              std::ostream_iterator<std::string>(os, "\n"));
555    os << cache_check_str() << "\n";
556    for (size_t i=0; i<stats_.size(); ++i){
557      print(os, stats_[i]);
558      os << cache_check_str() << "\n";
559    }
560  }
561
562
563  void Stats::print(std::ostream& os, const Author2Vector& m) const
564  {
565    for (A2VConstIter i(m.begin()); i!=m.end(); ++i){
566      os << i->first << "\n";
567      if (i->second[0])
568        os << 0 << " " << i->second[0] << " ";
569      // FIXME: ise Vector::iterator
570      for (size_t j=1; j<i->second.size(); ++j) {
571        // only print if stats changes in this rev
572        if (i->second[j] != i->second[j-1]) {
573          os << j << " " << i->second[j] - i->second[j-1] << " ";
574        }
575      }
576      os << "\n";
577    }
578  }
579
580  void Stats::reset(void)
581  {
582    for (size_t i=0; i<stats_.size(); ++i){
583      stats_[i].clear();
584      stats_[i]["all"].resize(revision()+1);
585    }
586    authors_.clear();
587  }
588
589
590  void Stats::resize(svn_revnum_t rev)
591  {
592    // set size on vectors
593    for (size_t i=0; i<stats_.size(); ++i) {
594      for (A2VIter iter=stats_[i].begin(); iter!=stats_[i].end(); ++iter) {
595        iter->second.resize(rev);
596      }
597    }
598  }
599
600
601  svn_revnum_t Stats::revision(void) const 
602  { 
603    return revision_; 
604  }
605
606
607  Stats& Stats::operator+=(const Stats& rhs)
608  {
609    revision_ = std::max(revision_, rhs.revision_);
610    last_changed_rev_ = std::max(last_changed_rev_, rhs.last_changed_rev_);
611    add_authors(rhs.authors().begin(), rhs.authors().end());
612    assert(stats_.size()==rhs.stats_.size());
613    for (size_t i=0; i<stats_.size(); ++i)
614      map_add(rhs.stats_[i].begin(), rhs.stats_[i].end(), stats_[i]);
615   
616    return *this;
617  }
618
619 
620  size_t Stats::operator()(int linetype, std::string author, 
621                           svn_revnum_t rev) const
622  {
623    assert(linetype<=LineTypeParser::total);
624    assert(static_cast<size_t>(linetype) < stats_.size());
625    assert(rev>=0);
626    A2VConstIter i = stats_[linetype].find(author);
627    if (i==stats_[linetype].end()){
628      std::stringstream msg;
629      msg << __FILE__ << ": author: " << author << " does not exist"; 
630      throw std::runtime_error(msg.str());
631    }
632    assert(rev <= revision());
633    //    assert(rev < static_cast<svn_revnum_t>(i->second.size()));
634    return i->second[rev];
635  }
636
637}} // end of namespace svndigest and namespace theplu
Note: See TracBrowser for help on using the repository browser.