source: trunk/lib/Stats.cc @ 1280

Last change on this file since 1280 was 1280, checked in by Peter Johansson, 13 years ago

merged release 0.9 in to trunk

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