source: trunk/lib/Stats.cc @ 1124

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

detecting old cache files and re-writing them in new format (version 8). closes #443 and #289

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 15.8 KB
Line 
1// $Id: Stats.cc 1124 2010-07-07 05:35:17Z 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, bool& latest_ver)
292  {
293    std::string str;
294    getline(is, str);
295    if (str == "CACHE FILE VERSION 8") {
296      latest_ver = true;
297      return load_cache8(is);
298    }
299    latest_ver = false;
300    if (str == "CACHE FILE VERSION 7")
301      return load_cache7(is);
302
303    if (str == "CACHE FILE VERSION 6")
304      std::cout << "cache file is obsolete; "
305                << "retrieving statistics from repository.\n";
306    return 0;
307  }
308
309
310  svn_revnum_t Stats::load_cache8(std::istream& is)
311  {
312    std::string line;
313    getline(is, line);
314    if (line!=config_code_) {
315      std::cout << "cache file is for different configuration.\n"
316                << "config code: '" << config_code_ << "'\n"
317                << "config code in cache file: '" << line << "'\n"
318                << "retrieving statistics from repository.\n";
319      return 0;
320    }
321    return load_cache7(is);
322  }
323
324
325  svn_revnum_t Stats::load_cache7(std::istream& is)
326  {
327    svn_revnum_t rev;
328    is >> rev;
329    reset();
330    size_t a_size=0;
331    is >> a_size;
332    std::string str;
333    while (authors().size()<a_size && is.good()){
334      getline(is, str);
335      if (str.size())
336        add_author(str);
337    }
338    getline(is, str);
339    if (str!=cache_check_str()) {
340      return 0;
341    }
342    for (size_t i=0; i<stats_.size(); ++i){
343      load(is, stats_[i]);
344      getline(is, str);
345      if (str!=cache_check_str()) {
346        return 0;
347      }
348    }
349    return rev;
350  }
351
352
353  void Stats::map_add(A2VConstIter first1, A2VConstIter last1, 
354                      Author2Vector& map)
355  {
356    A2VIter first2(map.begin());
357    Author2Vector::key_compare compare;
358    while ( first1 != last1) { 
359      // key of first1 less than key of first2
360      if (first2==map.end() || compare(first1->first,first2->first)) {
361        first2 = map.insert(first2, *first1);
362        ++first1;
363      }
364      // key of first2 less than key of first1
365      else if ( compare(first2->first, first1->first)) {
366        ++first2;
367      }
368      // keys are equivalent
369      else {
370        VectorPlus<Author2Vector::mapped_type::value_type> vp;
371        first2->second = vp(first1->second, first2->second);
372        ++first1;
373        ++first2;
374      }
375    }
376  }
377
378
379  unsigned int Stats::max_element(const std::vector<unsigned int>& vec) const
380  {
381    return *std::max_element(vec.begin(), vec.end());
382  }
383
384
385  void Stats::parse(const std::string& path, svn_revnum_t rev)
386  {
387    // reset stats to zero for [rev, inf)
388    for (size_t i=0; i<stats_.size(); ++i)
389      for (A2VIter iter=stats_[i].begin(); iter!=stats_[i].end(); ++iter) {
390        iter->second.resize(rev,0);
391        iter->second.resize(revision(),0);
392      }
393    do_parse(path, rev);
394    calc_comment_or_copy();
395    calc_total();
396    calc_all();
397    assert(total_stats().size());
398    assert(code_stats().size());
399    assert(comment_or_copy_stats().size());
400    assert(other_stats().size());
401  }
402
403
404  std::string Stats::plot(const std::string& filename,
405                          const std::string& linetype) const
406  {
407    const std::string& format = Configuration::instance().image_format();
408    if (format=="none")
409      return filename;
410    plot(filename, linetype, format);
411    const std::string& anchor_format = 
412      Configuration::instance().image_anchor_format();
413
414    if (format!=anchor_format)
415      plot(filename, linetype, anchor_format);
416    return filename;
417  }
418
419
420  void Stats::plot(const std::string& filename,
421                   const std::string& linetype,
422                   const std::string& format) const
423  {
424    assert(total_stats().size());
425    Graph gp(filename+"."+format, format);
426    const Author2Vector* stat=NULL;
427    if (linetype=="total")
428      stat = &total_stats();
429    else if (linetype=="code")
430      stat = &code_stats();
431    else if (linetype=="comments")
432      stat = &comment_or_copy_stats();
433    else if (linetype=="empty")
434      stat = &other_stats();
435    assert(stat);
436    assert(stat->size());
437    assert(stat->find("all")!=stat->end());
438    std::vector<unsigned int> total=get_vector(*stat, "all");   
439    double yrange_max=1.03 * max_element(total) +1.0;
440    gp.ymax(yrange_max);
441
442    typedef std::vector<std::pair<std::string, std::vector<unsigned int> > > vec_type;
443    vec_type author_cont;
444    author_cont.reserve(stat->size());
445    for (std::set<std::string>::const_iterator i=authors_.begin(); 
446         i != authors_.end(); ++i) {
447      assert(stat->find(*i)!=stat->end());
448      const std::vector<unsigned int>& vec = get_vector(*stat,*i);
449      if (max_element(vec)) {
450        author_cont.push_back(std::make_pair(*i,vec));
451      }
452    }
453
454    LessReversed<std::vector<unsigned int> > lr;
455    PairSecondCompare<std::string, std::vector<unsigned int>, 
456      LessReversed<std::vector<unsigned int> > > compare(lr);
457    std::sort(author_cont.begin(), author_cont.end(), compare);
458
459    vec_type::iterator end(author_cont.end());
460    vec_type::iterator i(author_cont.begin());
461    const vec_type::size_type maxauthors=8;
462    int authskip=author_cont.size()-maxauthors;
463    if (authskip>1) {
464      // only use others if there is more than 1 author to be skipped,
465      // there is no reason to add only 1 author to others.
466      vec_type::iterator j(i);
467      i+=authskip;
468      std::vector<unsigned int> init(revision()+1);
469      std::vector<unsigned int> others =
470        std::accumulate(j, i, init, PairValuePlus<std::string,unsigned int>());
471      unsigned char r, g, b;
472      std::string label("others");
473      Colors::instance().get_color(label, r,g,b);
474      gp.current_color(r,g,b);
475      gp.plot(others, label, others.back());
476    }
477    for ( ; i!=end; ++i) {
478      unsigned char r, g, b;
479      Colors::instance().get_color(i->first,r,g,b);
480      gp.current_color(r,g,b);
481      gp.plot(i->second, i->first, get_back(*stat, i->first));
482    }
483    gp.current_color(255,0,0);
484    gp.plot(total, "total", get_back(*stat, "all"));
485  }
486
487
488  void Stats::plot_summary(const std::string& filename) const
489  {
490    const std::string& format = Configuration::instance().image_format();
491    if (format=="none")
492      return;
493    plot_summary(filename, format);
494    const std::string& anchor_format = 
495      Configuration::instance().image_anchor_format();
496
497    if (format!=anchor_format)
498      plot_summary(filename, anchor_format);
499  }
500
501
502  void Stats::plot_summary(const std::string& filename, 
503                           const std::string& format) const
504  {
505    Graph gp(filename+"."+format, format);
506    std::vector<unsigned int> total = get_vector(total_stats(), "all");
507    double yrange_max=1.03*total.back()+1;
508    gp.ymax(yrange_max);
509   
510    std::vector<unsigned int> x(get_vector(code_stats(), "all"));
511    gp.current_color(255,255,0);
512    gp.plot(x, "code", x.back());
513
514    x = get_vector(comment_or_copy_stats(), "all");
515    gp.current_color(0,0,255);
516    gp.plot(x, "comment", x.back());
517
518    x = get_vector(other_stats(), "all");
519    gp.current_color(0,255,0);
520    gp.plot(x, "other", x.back());
521
522    gp.current_color(255,0,0);
523    gp.plot(total, "total", total.back());
524  }
525
526
527  void Stats::print(std::ostream& os) const
528  {
529    // indicate that cache file is version 8 here , but keep remaning
530    // 'CACHE FILE VERSION' as 'VERSION 7' to allow load_cache7() to
531    // be used from load_cache8().
532    os << "CACHE FILE VERSION 8\n";
533    os << config_code_ << "\n";
534    os << last_changed_rev() << " ";
535    os << authors().size() << "\n";
536
537    std::copy(authors().begin(), authors().end(), 
538              std::ostream_iterator<std::string>(os, "\n"));
539    os << cache_check_str() << "\n";
540    for (size_t i=0; i<stats_.size(); ++i){
541      print(os, stats_[i]);
542      os << cache_check_str() << "\n";
543    }
544  }
545
546
547  void Stats::print(std::ostream& os, const Author2Vector& m) const
548  {
549    for (A2VConstIter i(m.begin()); i!=m.end(); ++i){
550      os << i->first << "\n";
551      assert(i->second.size());
552      if (i->second[0])
553        os << 0 << " " << i->second[0] << " ";
554      for (size_t j=1; j<i->second.size(); ++j) {
555        // only print if stats changes in this rev
556        if (i->second[j] != i->second[j-1]) {
557          os << j << " " << i->second[j] - i->second[j-1] << " ";
558        }
559      }
560      os << "\n";
561    }
562  }
563
564  void Stats::reset(void)
565  {
566    for (size_t i=0; i<stats_.size(); ++i){
567      stats_[i].clear();
568      std::vector<unsigned int>& tmp = stats_[i]["all"];
569      std::fill(tmp.begin(), tmp.end(), 0);
570      tmp.resize(revision_+1);
571    }
572    authors_.clear();
573  }
574
575
576  Stats& Stats::operator+=(const Stats& rhs)
577  {
578    revision_ = std::max(revision_, rhs.revision_);
579    last_changed_rev_ = std::max(last_changed_rev_, rhs.last_changed_rev_);
580    add_authors(rhs.authors().begin(), rhs.authors().end());
581    assert(stats_.size()==rhs.stats_.size());
582    for (size_t i=0; i<stats_.size(); ++i)
583      map_add(rhs.stats_[i].begin(), rhs.stats_[i].end(), stats_[i]);
584   
585    return *this;
586  }
587
588 
589  size_t Stats::operator()(int linetype, std::string author, 
590                           svn_revnum_t rev) const
591  {
592    assert(linetype<=LineTypeParser::total);
593    assert(static_cast<size_t>(linetype) < stats_.size());
594    assert(rev>=0);
595    A2VConstIter i = stats_[linetype].find(author);
596    if (i==stats_[linetype].end()){
597      std::stringstream msg;
598      msg << __FILE__ << ": author: " << author << " does not exist"; 
599      throw std::runtime_error(msg.str());
600    }
601    assert(rev < static_cast<svn_revnum_t>(i->second.size()));
602    return i->second[rev];
603  }
604
605}} // end of namespace svndigest and namespace theplu
Note: See TracBrowser for help on using the repository browser.