source: trunk/lib/Stats.cc @ 1122

Last change on this file since 1122 was 1122, checked in by Peter Johansson, 11 years ago

added line in config file describing config used to create cache (refs
#433). If config is different from the current one, the cache is
ignored and stats are retrieved from repo. Restructured code a bit to
allow loading old cache files; the first line (in cache) is used to
decide which function to use for the loading. Cache files VERSION 7
and newer are supported and older are ignored (refs #289).

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