1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 """Generate and process backup statistics"""
23
24 import re, time, os
25
26 from duplicity import dup_time
27
28
31
33 """Contains various statistics, provide string conversion functions"""
34
35 space_regex = re.compile(" ")
36
37 stat_file_attrs = ('SourceFiles',
38 'SourceFileSize',
39 'NewFiles',
40 'NewFileSize',
41 'DeletedFiles',
42 'ChangedFiles',
43 'ChangedFileSize',
44 'ChangedDeltaSize',
45 'DeltaEntries',
46 'RawDeltaSize')
47 stat_misc_attrs = ('Errors',
48 'TotalDestinationSizeChange')
49 stat_time_attrs = ('StartTime',
50 'EndTime',
51 'ElapsedTime')
52 stat_attrs = (('Filename',) + stat_time_attrs +
53 stat_misc_attrs + stat_file_attrs)
54
55
56
57 stat_file_pairs = (('SourceFiles', False),
58 ('SourceFileSize', True),
59 ('NewFiles', False),
60 ('NewFileSize', True),
61 ('DeletedFiles', False),
62 ('ChangedFiles', False),
63 ('ChangedFileSize', True),
64 ('ChangedDeltaSize', True),
65 ('DeltaEntries', False),
66 ('RawDeltaSize', True))
67
68
69 byte_abbrev_list = ((1024*1024*1024*1024, "TB"),
70 (1024*1024*1024, "GB"),
71 (1024*1024, "MB"),
72 (1024, "KB"))
73
75 """Set attributes to None"""
76 for attr in self.stat_attrs:
77 self.__dict__[attr] = None
78
80 """Get a statistic"""
81 return self.__dict__[attribute]
82
84 """Set attribute to given value"""
85 self.__dict__[attr] = value
86
88 """Add 1 to value of attribute"""
89 self.__dict__[attr] += 1
90
92 """Return total destination size change
93
94 This represents the total increase in the size of the
95 duplicity destination directory, or None if not available.
96
97 """
98 return 0
99
101 """Return one line abbreviated version of full stats string"""
102 file_attrs = map(lambda attr: str(self.get_stat(attr)),
103 self.stat_file_attrs)
104 if not index:
105 filename = "."
106 else:
107 filename = apply(os.path.join, index)
108 if use_repr:
109
110
111 filename = self.space_regex.sub("\\x20", repr(filename)[1:-1])
112 return " ".join([filename,] + file_attrs)
113
115 """Set statistics from given line"""
116 def error():
117 raise StatsException("Bad line '%s'" % line)
118 if line[-1] == "\n":
119 line = line[:-1]
120 lineparts = line.split(" ")
121 if len(lineparts) < len(self.stat_file_attrs):
122 error()
123 for attr, val_string in zip(self.stat_file_attrs,
124 lineparts[-len(self.stat_file_attrs):]):
125 try:
126 val = long(val_string)
127 except ValueError:
128 try:
129 val = float(val_string)
130 except ValueError:
131 error()
132 self.set_stat(attr, val)
133 return self
134
140
142 """Return portion of statistics string dealing with time"""
143 timelist = []
144 if self.StartTime is not None:
145 timelist.append("StartTime %.2f (%s)\n" %
146 (self.StartTime, dup_time.timetopretty(self.StartTime)))
147 if self.EndTime is not None:
148 timelist.append("EndTime %.2f (%s)\n" %
149 (self.EndTime, dup_time.timetopretty(self.EndTime)))
150 if self.ElapsedTime or (self.StartTime is not None and
151 self.EndTime is not None):
152 if self.ElapsedTime is None:
153 self.ElapsedTime = self.EndTime - self.StartTime
154 timelist.append("ElapsedTime %.2f (%s)\n" %
155 (self.ElapsedTime, dup_time.inttopretty(self.ElapsedTime)))
156 return "".join(timelist)
157
159 """Return portion of statistics string about files and bytes"""
160 def fileline(stat_file_pair):
161 """Return zero or one line of the string"""
162 attr, in_bytes = stat_file_pair
163 val = self.get_stat(attr)
164 if val is None:
165 return ""
166 if in_bytes:
167 return "%s %s (%s)\n" % (attr, val,
168 self.get_byte_summary_string(val))
169 else:
170 return "%s %s\n" % (attr, val)
171
172 return "".join(map(fileline, self.stat_file_pairs))
173
175 """Return portion of extended stat string about misc attributes"""
176 misc_string = ""
177 tdsc = self.TotalDestinationSizeChange
178 if tdsc is not None:
179 misc_string += ("TotalDestinationSizeChange %s (%s)\n" %
180 (tdsc, self.get_byte_summary_string(tdsc)))
181 if self.Errors is not None:
182 misc_string += "Errors %d\n" % self.Errors
183 return misc_string
184
186 """Turn byte count into human readable string like "7.23GB" """
187 if byte_count < 0:
188 sign = "-"
189 byte_count = -byte_count
190 else:
191 sign = ""
192
193 for abbrev_bytes, abbrev_string in self.byte_abbrev_list:
194 if byte_count >= abbrev_bytes:
195
196 abbrev_count = float(byte_count)/abbrev_bytes
197 if abbrev_count >= 100:
198 precision = 0
199 elif abbrev_count >= 10:
200 precision = 1
201 else:
202 precision = 2
203 return "%s%%.%df %s" % (sign, precision, abbrev_string) \
204 % (abbrev_count,)
205 byte_count = round(byte_count)
206 if byte_count == 1:
207 return sign + "1 byte"
208 else:
209 return "%s%d bytes" % (sign, byte_count)
210
212 """Like get_stats_string, but add header and footer"""
213 header = "--------------[ %s ]--------------" % title
214 footer = "-" * len(header)
215 return "%s\n%s%s\n" % (header, self.get_stats_string(), footer)
216
218 """Initialize attributes from string, return self for convenience"""
219 def error(line):
220 raise StatsException("Bad line '%s'" % line)
221
222 for line in s.split("\n"):
223 if not line:
224 continue
225 line_parts = line.split()
226 if len(line_parts) < 2:
227 error(line)
228 attr, value_string = line_parts[:2]
229 if not attr in self.stat_attrs:
230 error(line)
231 try:
232 try:
233 val1 = long(value_string)
234 except ValueError:
235 val1 = None
236 val2 = float(value_string)
237 if val1 == val2:
238 self.set_stat(attr, val1)
239 else:
240 self.set_stat(attr, val2)
241 except ValueError:
242 error(line)
243 return self
244
250
257
259 """Return true if s has same statistics as self"""
260 assert isinstance(s, StatsObj)
261 for attr in self.stat_file_attrs:
262 if self.get_stat(attr) != s.get_stat(attr):
263 return None
264 return 1
265
267 """Set self's attributes to average of those in statobj_list"""
268 for attr in self.stat_attrs:
269 self.set_stat(attr, 0)
270 for statobj in statobj_list:
271 for attr in self.stat_attrs:
272 if statobj.get_stat(attr) is None:
273 self.set_stat(attr, None)
274 elif self.get_stat(attr) is not None:
275 self.set_stat(attr, statobj.get_stat(attr) +
276 self.get_stat(attr))
277
278
279 self.StartTime = None
280 self.EndTime = None
281
282 for attr in self.stat_attrs:
283 if self.get_stat(attr) is not None:
284 self.set_stat(attr,
285 self.get_stat(attr)/float(len(statobj_list)))
286 return self
287
294
295
297 """Keep track of statistics during DirDelta process"""
299 """StatsDeltaProcess initializer - zero file attributes"""
300 StatsObj.__init__(self)
301 for attr in StatsObj.stat_file_attrs:
302 self.__dict__[attr] = 0
303 self.Errors = 0
304 self.StartTime = time.time()
305
307 """Add stats of new file path to statistics"""
308 filesize = path.getsize()
309 self.SourceFiles += 1
310
311 self.NewFiles += 1
312 self.NewFileSize += filesize
313 self.DeltaEntries += 1
314
316 """Add stats of file that has changed since last backup"""
317 filesize = path.getsize()
318 self.SourceFiles += 1
319
320 self.ChangedFiles += 1
321 self.ChangedFileSize += filesize
322 self.DeltaEntries += 1
323
325 """Add stats of file no longer in source directory"""
326 self.DeletedFiles += 1
327 self.DeltaEntries += 1
328
330 """Add stats of file that hasn't changed since last backup"""
331 filesize = path.getsize()
332 self.SourceFiles += 1
333 self.SourceFileSize += filesize
334
336 """End collection of data, set EndTime"""
337 self.EndTime = time.time()
338