Merge lp:~didrocks/ubuntu-release-upgrader/add_telemetry into lp:ubuntu-release-upgrader

Proposed by Didier Roche on 2018-05-04
Status: Merged
Approved by: Didier Roche on 2018-05-29
Approved revision: 3135
Merged at revision: 3131
Proposed branch: lp:~didrocks/ubuntu-release-upgrader/add_telemetry
Merge into: lp:ubuntu-release-upgrader
Diff against target: 549 lines (+176/-55)
8 files modified
DistUpgrade/DistUpgradeController.py (+16/-20)
DistUpgrade/DistUpgradeView.py (+11/-7)
DistUpgrade/DistUpgradeViewGtk3.py (+20/-16)
DistUpgrade/DistUpgradeViewKDE.py (+14/-10)
DistUpgrade/DistUpgradeViewNonInteractive.py (+3/-0)
DistUpgrade/DistUpgradeViewText.py (+5/-2)
DistUpgrade/telemetry.py (+101/-0)
debian/changelog (+6/-0)
To merge this branch: bzr merge lp:~didrocks/ubuntu-release-upgrader/add_telemetry
Reviewer Review Type Date Requested Status
Didier Roche Approve on 2018-05-29
Brian Murray 2018-05-04 Approve on 2018-05-23
Review via email: mp+345088@code.launchpad.net

Commit message

Add upgrade telemetry data for later collect in ubuntu-report.

Description of the change

Add upgrade telemetry data for later collect in ubuntu-report.

This was tested with 17.04 -> 18.04 upgrade, in text mode, GTK3 and KDE.

To post a comment you must log in.
Brian Murray (brian-murray) wrote :

Keep in mind that it is possible to from one release to multiple releases e.g. from xenial to artful or bionic. So without knowing the database structure I think it would make sense to also record the "To" release. You can find this in DistUpgradeController.py.

I'm also curious about how this information will be used because the use cases might benefit from gathering additional information like the version of release upgrader being used or environmental variables set.

Otherwise there are a couple of typos and one nitpick to have a look at.

review: Needs Information
Didier Roche (didrocks) wrote :

Thanks for the review!
There isn't a fixed database structure schema. However, keep in mind that ubuntu-report gather this results (as well as installers one) under some subfields on the json, alongside collecting a lot of others information.
So basically, we have the "To" and a lot of other details. Moreinfo on https://github.com/ubuntu/ubuntu-report.

For now, we collect those details, similarly than for ubiquity, and we'll organize them next cycle if nobody on IS step up first.

Typos fixed and pushed. Thanks!

3132. By Didier Roche on 2018-05-11

Fix some typos

3133. By Didier Roche on 2018-05-18

Use uptime time instead of time.time(), which is sensitive to system
clock reset by NTP or others…

3134. By Didier Roche on 2018-05-18

Fetch if users enabled third party repos before upgrade

3135. By Didier Roche on 2018-05-18

Fetch original install media

Brian Murray (brian-murray) wrote :

This looks good to me to merge and upload after the one change is made.

While this may be more useful for the failure case you might consider including what components (-updates, -security, main, universe, etc...) are enabled on the system that was upgraded. Here's an example from an upgrade log file.

2018-05-12 20:32:36,997 DEBUG found components: {'bionic-updates': {'main', 'universe', 'multiverse', 'restricted'}, 'bionic-security': {'main', 'universe', 'multiverse', 'restricted'}, 'bionic': {'main', 'universe', 'multiverse', 'restricted'}}

Didier Roche (didrocks) wrote :

We don't want to include the enabled components for now, but thanks for the suggestion! I'll fix the current uptime comment.

Thanks again for the review :)

review: Approve
Didier Roche (didrocks) wrote :

Tested artful -> bionic upgrade with this code and it reports the expected telemetry data.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'DistUpgrade/DistUpgradeController.py'
2--- DistUpgrade/DistUpgradeController.py 2018-03-16 17:56:15 +0000
3+++ DistUpgrade/DistUpgradeController.py 2018-05-18 10:01:29 +0000
4@@ -33,6 +33,7 @@
5 import copy
6 from configparser import NoOptionError
7 from configparser import ConfigParser as SafeConfigParser
8+from .telemetry import get as get_telemetry
9 from .utils import (country_mirror,
10 url_downloadable,
11 check_and_fix_xbit,
12@@ -45,14 +46,7 @@
13 from string import Template
14 from urllib.parse import urlsplit
15
16-from .DistUpgradeView import (
17- STEP_PREPARE,
18- STEP_MODIFY_SOURCES,
19- STEP_FETCH,
20- STEP_INSTALL,
21- STEP_CLEANUP,
22- STEP_REBOOT,
23-)
24+from .DistUpgradeView import Step
25 from .DistUpgradeCache import MyCache
26 from .DistUpgradeConfigParser import DistUpgradeConfig
27 from .DistUpgradeQuirks import DistUpgradeQuirks
28@@ -866,6 +860,7 @@
29 "'software-properties' tool or "
30 "your package manager."
31 ))
32+ get_telemetry().set_using_third_party_sources(self.sources_disabled)
33 return True
34
35 def _logChanges(self):
36@@ -1760,7 +1755,7 @@
37 def fullUpgrade(self):
38 # sanity check (check for ubuntu-desktop, brokenCache etc)
39 self._view.updateStatus(_("Checking package manager"))
40- self._view.setStep(STEP_PREPARE)
41+ self._view.setStep(Step.PREPARE)
42
43 if not self.prepare():
44 logging.error("self.prepare() failed")
45@@ -1829,7 +1824,7 @@
46 self.abort()
47
48 # update sources.list
49- self._view.setStep(STEP_MODIFY_SOURCES)
50+ self._view.setStep(Step.MODIFY_SOURCES)
51 self._view.updateStatus(_("Updating repository information"))
52 if not self.updateSourcesList():
53 self.abort()
54@@ -1901,14 +1896,14 @@
55 self.abort()
56
57 # fetch the stuff
58- self._view.setStep(STEP_FETCH)
59+ self._view.setStep(Step.FETCH)
60 self._view.updateStatus(_("Fetching"))
61 if not self.doDistUpgradeFetching():
62 self._enableAptCronJob()
63 self.abort()
64
65 # now do the upgrade
66- self._view.setStep(STEP_INSTALL)
67+ self._view.setStep(Step.INSTALL)
68 self._view.updateStatus(_("Upgrading"))
69 if not self.doDistUpgrade():
70 # run the post install scripts (for stuff like UUID conversion)
71@@ -1922,7 +1917,7 @@
72 sys.exit(1)
73
74 # do post-upgrade stuff
75- self._view.setStep(STEP_CLEANUP)
76+ self._view.setStep(Step.CLEANUP)
77 self._view.updateStatus(_("Searching for obsolete software"))
78 self.doPostUpgrade()
79
80@@ -1935,8 +1930,9 @@
81 os.unlink("/var/lib/ubuntu-release-upgrader/release-upgrade-available")
82
83 # done, ask for reboot
84- self._view.setStep(STEP_REBOOT)
85+ self._view.setStep(Step.REBOOT)
86 self._view.updateStatus(_("System upgrade is complete."))
87+ get_telemetry().done()
88 # FIXME should we look into /var/run/reboot-required here?
89 if (not inside_chroot() and
90 self._view.confirmRestart()):
91@@ -1950,20 +1946,20 @@
92
93 def doPartialUpgrade(self):
94 " partial upgrade mode, useful for repairing "
95- self._view.setStep(STEP_PREPARE)
96- self._view.hideStep(STEP_MODIFY_SOURCES)
97- self._view.hideStep(STEP_REBOOT)
98+ self._view.setStep(Step.PREPARE)
99+ self._view.hideStep(Step.MODIFY_SOURCES)
100+ self._view.hideStep(Step.REBOOT)
101 self._partialUpgrade = True
102 self.prepare()
103 if not self.doPostInitialUpdate():
104 return False
105 if not self.askDistUpgrade():
106 return False
107- self._view.setStep(STEP_FETCH)
108+ self._view.setStep(Step.FETCH)
109 self._view.updateStatus(_("Fetching"))
110 if not self.doDistUpgradeFetching():
111 return False
112- self._view.setStep(STEP_INSTALL)
113+ self._view.setStep(Step.INSTALL)
114 self._view.updateStatus(_("Upgrading"))
115 if not self.doDistUpgrade():
116 self._view.information(_("Upgrade complete"),
117@@ -1971,7 +1967,7 @@
118 "were errors during the upgrade "
119 "process."))
120 return False
121- self._view.setStep(STEP_CLEANUP)
122+ self._view.setStep(Step.CLEANUP)
123 if not self.doPostUpgrade():
124 self._view.information(_("Upgrade complete"),
125 _("The upgrade has completed but there "
126
127=== modified file 'DistUpgrade/DistUpgradeView.py'
128--- DistUpgrade/DistUpgradeView.py 2017-05-18 17:33:48 +0000
129+++ DistUpgrade/DistUpgradeView.py 2018-05-18 10:01:29 +0000
130@@ -21,7 +21,9 @@
131
132 from .DistUpgradeGettext import gettext as _
133 from .DistUpgradeGettext import ngettext
134+from .telemetry import get as get_telemetry
135 import apt
136+from enum import Enum
137 import errno
138 import os
139 import apt_pkg
140@@ -252,13 +254,14 @@
141 def hide(self):
142 pass
143
144-(STEP_PREPARE,
145- STEP_MODIFY_SOURCES,
146- STEP_FETCH,
147- STEP_INSTALL,
148- STEP_CLEANUP,
149- STEP_REBOOT,
150- STEP_N) = range(1,8)
151+class Step(Enum):
152+ PREPARE = 1
153+ MODIFY_SOURCES = 2
154+ FETCH = 3
155+ INSTALL = 4
156+ CLEANUP = 5
157+ REBOOT = 6
158+ N = 7
159
160 # Declare these translatable strings from the .ui files here so that
161 # xgettext picks them up.
162@@ -304,6 +307,7 @@
163 4. Post upgrade stuff
164 5. Complete
165 """
166+ get_telemetry().add_stage(step.name)
167 pass
168 def hideStep(self, step):
169 " hide a certain step from the GUI "
170
171=== modified file 'DistUpgrade/DistUpgradeViewGtk3.py'
172--- DistUpgrade/DistUpgradeViewGtk3.py 2016-02-23 17:06:42 +0000
173+++ DistUpgrade/DistUpgradeViewGtk3.py 2018-05-18 10:01:29 +0000
174@@ -52,6 +52,7 @@
175 from .DistUpgradeApport import run_apport, apport_crash
176
177 from .DistUpgradeView import DistUpgradeView, FuzzyTimeToStr, InstallProgress, AcquireProgress
178+from .telemetry import get as get_telemetry
179 from .SimpleGtk3builderApp import SimpleGtkbuilderApp
180
181 import gettext
182@@ -70,7 +71,7 @@
183 """ update is called regularly so that the gui can be redrawn """
184 if text:
185 self.status.set_text(text)
186- self.progress.set_fraction(step/float(self.totalSteps))
187+ self.progress.set_fraction(step.value/float(self.totalSteps))
188 while Gtk.events_pending():
189 Gtk.main_iteration()
190 def ask_cdrom_name(self):
191@@ -452,6 +453,8 @@
192 # check if we have a display etc
193 Gtk.init_check(sys.argv)
194
195+ get_telemetry().set_updater_type('GTK')
196+
197 try:
198 locale.bindtextdomain("ubuntu-release-upgrader",localedir)
199 gettext.textdomain("ubuntu-release-upgrader")
200@@ -472,7 +475,7 @@
201 # terminal stuff
202 self.create_terminal()
203
204- self.prev_step = 0 # keep a record of the latest step
205+ self.prev_step = None # keep a record of the latest step
206 # we don't use this currently
207 #self.window_main.set_keep_above(True)
208 self.icontheme = Gtk.IconTheme.get_default()
209@@ -598,44 +601,45 @@
210 def updateStatus(self, msg):
211 self.label_status.set_text("%s" % msg)
212 def hideStep(self, step):
213- image = getattr(self,"image_step%i" % step)
214- label = getattr(self,"label_step%i" % step)
215- #arrow = getattr(self,"arrow_step%i" % step)
216+ image = getattr(self,"image_step%i" % step.value)
217+ label = getattr(self,"label_step%i" % step.value)
218+ #arrow = getattr(self,"arrow_step%i" % step.value)
219 image.hide()
220 label.hide()
221 def showStep(self, step):
222- image = getattr(self,"image_step%i" % step)
223- label = getattr(self,"label_step%i" % step)
224+ image = getattr(self,"image_step%i" % step.value)
225+ label = getattr(self,"label_step%i" % step.value)
226 image.show()
227 label.show()
228 def abort(self):
229 size = Gtk.IconSize.MENU
230 step = self.prev_step
231- if step > 0:
232- image = getattr(self,"image_step%i" % step)
233- arrow = getattr(self,"arrow_step%i" % step)
234+ if step:
235+ image = getattr(self,"image_step%i" % step.value)
236+ arrow = getattr(self,"arrow_step%i" % step.value)
237 image.set_from_stock(Gtk.STOCK_CANCEL, size)
238 image.show()
239 arrow.hide()
240 def setStep(self, step):
241+ super(DistUpgradeViewGtk3, self).setStep(step)
242 if self.icontheme.rescan_if_needed():
243 logging.debug("icon theme changed, re-reading")
244 # first update the "previous" step as completed
245 size = Gtk.IconSize.MENU
246 attrlist=Pango.AttrList()
247 if self.prev_step:
248- image = getattr(self,"image_step%i" % self.prev_step)
249- label = getattr(self,"label_step%i" % self.prev_step)
250- arrow = getattr(self,"arrow_step%i" % self.prev_step)
251+ image = getattr(self,"image_step%i" % self.prev_step.value)
252+ label = getattr(self,"label_step%i" % self.prev_step.value)
253+ arrow = getattr(self,"arrow_step%i" % self.prev_step.value)
254 label.set_property("attributes",attrlist)
255 image.set_from_stock(Gtk.STOCK_APPLY, size)
256 image.show()
257 arrow.hide()
258 self.prev_step = step
259 # show the an arrow for the current step and make the label bold
260- image = getattr(self,"image_step%i" % step)
261- label = getattr(self,"label_step%i" % step)
262- arrow = getattr(self,"arrow_step%i" % step)
263+ image = getattr(self,"image_step%i" % step.value)
264+ label = getattr(self,"label_step%i" % step.value)
265+ arrow = getattr(self,"arrow_step%i" % step.value)
266 # check if that step was not hidden with hideStep()
267 if not label.get_property("visible"):
268 return
269
270=== modified file 'DistUpgrade/DistUpgradeViewKDE.py'
271--- DistUpgrade/DistUpgradeViewKDE.py 2018-01-22 11:12:39 +0000
272+++ DistUpgrade/DistUpgradeViewKDE.py 2018-05-18 10:01:29 +0000
273@@ -62,6 +62,7 @@
274 from .DistUpgradeApport import run_apport, apport_crash
275
276 from .DistUpgradeView import DistUpgradeView, FuzzyTimeToStr, InstallProgress, AcquireProgress
277+from .telemetry import get as get_telemetry
278
279 import select
280 import gettext
281@@ -251,7 +252,7 @@
282 """ update is called regularly so that the gui can be redrawn """
283 if text:
284 self.status.setText(text)
285- self.progressbar.setValue(step/float(self.totalSteps))
286+ self.progressbar.setValue(step.value/float(self.totalSteps))
287 QApplication.processEvents()
288
289 def ask_cdrom_name(self):
290@@ -560,6 +561,8 @@
291 """KDE frontend of the distUpgrade tool"""
292 def __init__(self, datadir=None, logdir=None):
293 DistUpgradeView.__init__(self)
294+
295+ get_telemetry().set_updater_type('KDE')
296 # silence the PyQt4 logger
297 logger = logging.getLogger("PyQt4")
298 logger.setLevel(logging.INFO)
299@@ -613,7 +616,7 @@
300 self.window_main.setParent(self)
301 self.window_main.show()
302
303- self.prev_step = 0 # keep a record of the latest step
304+ self.prev_step = None # keep a record of the latest step
305
306 self._opCacheProgress = KDEOpProgress(self.window_main.progressbar_cache, self.window_main.progress_text)
307 self._acquireProgress = KDEAcquireProgressAdapter(self)
308@@ -733,15 +736,15 @@
309 self.window_main.label_status.setText(msg)
310
311 def hideStep(self, step):
312- image = getattr(self.window_main,"image_step%i" % step)
313- label = getattr(self.window_main,"label_step%i" % step)
314+ image = getattr(self.window_main,"image_step%i" % step.value)
315+ label = getattr(self.window_main,"label_step%i" % step.value)
316 image.hide()
317 label.hide()
318
319 def abort(self):
320 step = self.prev_step
321- if step > 0:
322- image = getattr(self.window_main,"image_step%i" % step)
323+ if step:
324+ image = getattr(self.window_main,"image_step%i" % step.value)
325 cancelIcon = _icon("dialog-cancel",
326 fallbacks=["/usr/share/icons/oxygen/16x16/actions/dialog-cancel.png",
327 "/usr/lib/kde4/share/icons/oxygen/16x16/actions/dialog-cancel.png",
328@@ -750,6 +753,7 @@
329 image.show()
330
331 def setStep(self, step):
332+ super(DistUpgradeViewKDE , self).setStep(step)
333 okIcon = _icon("dialog-ok",
334 fallbacks=["/usr/share/icons/oxygen/16x16/actions/dialog-ok.png",
335 "/usr/lib/kde4/share/icons/oxygen/16x16/actions/dialog-ok.png",
336@@ -760,15 +764,15 @@
337 "/usr/share/icons/crystalsvg/16x16/actions/1rightarrow.png"])
338
339 if self.prev_step:
340- image = getattr(self.window_main,"image_step%i" % self.prev_step)
341- label = getattr(self.window_main,"label_step%i" % self.prev_step)
342+ image = getattr(self.window_main,"image_step%i" % self.prev_step.value)
343+ label = getattr(self.window_main,"label_step%i" % self.prev_step.value)
344 image.setPixmap(okIcon.pixmap(16, 16))
345 image.show()
346 ##arrow.hide()
347 self.prev_step = step
348 # show the an arrow for the current step and make the label bold
349- image = getattr(self.window_main,"image_step%i" % step)
350- label = getattr(self.window_main,"label_step%i" % step)
351+ image = getattr(self.window_main,"image_step%i" % step.value)
352+ label = getattr(self.window_main,"label_step%i" % step.value)
353 image.setPixmap(arrowIcon.pixmap(16, 16))
354 image.show()
355 label.setText("<b>" + label.text() + "</b>")
356
357=== modified file 'DistUpgrade/DistUpgradeViewNonInteractive.py'
358--- DistUpgrade/DistUpgradeViewNonInteractive.py 2016-10-10 18:05:08 +0000
359+++ DistUpgrade/DistUpgradeViewNonInteractive.py 2018-05-18 10:01:29 +0000
360@@ -36,6 +36,7 @@
361 from subprocess import PIPE, Popen
362
363 from .DistUpgradeView import DistUpgradeView, InstallProgress, AcquireProgress
364+from .telemetry import get as get_telemetry
365 from .DistUpgradeConfigParser import DistUpgradeConfig
366
367
368@@ -251,6 +252,7 @@
369 " non-interactive version of the upgrade view "
370 def __init__(self, datadir=None, logdir=None):
371 DistUpgradeView.__init__(self)
372+ get_telemetry().set_updater_type('NonInteractive')
373 self.config = DistUpgradeConfig(".")
374 self._acquireProgress = NonInteractiveAcquireProgress()
375 self._installProgress = NonInteractiveInstallProgress(logdir)
376@@ -286,6 +288,7 @@
377 4. Post upgrade stuff
378 5. Complete
379 """
380+ super(DistUpgradeViewNonInteractive, self).setStep(step)
381 pass
382 def confirmChanges(self, summary, changes, demotions, downloadSize,
383 actions=None, removal_bold=True):
384
385=== modified file 'DistUpgrade/DistUpgradeViewText.py'
386--- DistUpgrade/DistUpgradeViewText.py 2016-04-22 19:33:04 +0000
387+++ DistUpgrade/DistUpgradeViewText.py 2018-05-18 10:01:29 +0000
388@@ -36,6 +36,7 @@
389 ENCODING,
390 InstallProgress,
391 )
392+from .telemetry import get as get_telemetry
393 import apt.progress
394
395 import gettext
396@@ -88,7 +89,7 @@
397 def update(self, text, step):
398 """ update is called regularly so that the gui can be redrawn """
399 if text:
400- print("%s (%f)" % (text, step/float(self.totalSteps)*100))
401+ print("%s (%f)" % (text, step.value/float(self.totalSteps)*100))
402 def ask_cdrom_name(self):
403 return (False, "")
404 def change_cdrom(self):
405@@ -101,6 +102,7 @@
406 def __init__(self, datadir=None, logdir=None):
407 # indicate that we benefit from using gnu screen
408 self.needs_screen = True
409+ get_telemetry().set_updater_type('Text')
410 # its important to have a debconf frontend for
411 # packages like "quagga"
412 if "DEBIAN_FRONTEND" not in os.environ:
413@@ -116,7 +118,7 @@
414 except Exception as e:
415 logging.warning("Error setting locales (%s)" % e)
416
417- self.last_step = 0 # keep a record of the latest step
418+ self.last_step = None # keep a record of the latest step
419 self._opCacheProgress = apt.progress.text.OpProgress()
420 self._acquireProgress = TextAcquireProgress()
421 self._cdromProgress = TextCdromProgressAdapter()
422@@ -162,6 +164,7 @@
423 print()
424 print(_("Aborting"))
425 def setStep(self, step):
426+ super(DistUpgradeViewText, self).setStep(step)
427 self.last_step = step
428 def showDemotions(self, summary, msg, demotions):
429 self.information(summary, msg,
430
431=== added file 'DistUpgrade/telemetry.py'
432--- DistUpgrade/telemetry.py 1970-01-01 00:00:00 +0000
433+++ DistUpgrade/telemetry.py 2018-05-18 10:01:29 +0000
434@@ -0,0 +1,101 @@
435+# -*- coding: utf-8; Mode: Python; indent-tabs-mode: nil; tab-width: 4 -*-
436+
437+# Copyright (C) 2018 Canonical Ltd.
438+#
439+# Functions useful for the final install.py script and for ubiquity
440+# plugins to use
441+#
442+# This program is free software; you can redistribute it and/or modify
443+# it under the terms of the GNU General Public License as published by
444+# the Free Software Foundation; either version 2 of the License, or
445+# (at your option) any later version.
446+#
447+# This program is distributed in the hope that it will be useful,
448+# but WITHOUT ANY WARRANTY; without even the implied warranty of
449+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
450+# GNU General Public License for more details.
451+#
452+# You should have received a copy of the GNU General Public License
453+# along with this program; if not, write to the Free Software
454+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
455+
456+
457+import logging
458+import json
459+import os
460+import stat
461+import subprocess
462+
463+
464+def get():
465+ """Return a singleton _Telemetry instance."""
466+ if _Telemetry._telemetry is None:
467+ _Telemetry._telemetry = _Telemetry()
468+ return _Telemetry._telemetry
469+
470+
471+class _Telemetry():
472+
473+ _telemetry = None
474+
475+ def __init__(self):
476+ self._metrics = {}
477+ self._stages_hist = {}
478+ self._start_time = self._get_current_uptime()
479+ self._metrics["From"] = subprocess.Popen(
480+ ["lsb_release", "-r", "-s"], stdout=subprocess.PIPE,
481+ universal_newlines=True).communicate()[0].strip()
482+ self.add_stage('start')
483+ self._dest_path = '/var/log/upgrade/telemetry'
484+ try:
485+ with open('/var/log/installer/media-info') as f:
486+ self._metrics['InstallMedia'] = f.readline()
487+ except FileNotFoundError:
488+ pass
489+
490+ def _get_current_uptime(self):
491+ """Get current uptime info. None if we couldn't fetch it."""
492+ uptime = None
493+ try:
494+ with open('/proc/uptime') as f:
495+ uptime = float(f.read().split()[0])
496+ except (FileNotFoundError, OSError, ValueError) as e:
497+ logging.warning("Exception while fetching current uptime: " + str(e))
498+ return uptime
499+
500+ def add_stage(self, stage_name):
501+ """Record installer stage with current time"""
502+ now = self._get_current_uptime()
503+ if self._start_time is None or now is None:
504+ return
505+ self._stages_hist[int(now-self._start_time)] = stage_name
506+
507+ def set_updater_type(self, updater_type):
508+ """Record updater type"""
509+ self._metrics['Type'] = updater_type
510+
511+ def set_using_third_party_sources(self, using):
512+ """Record if the user had third party sources"""
513+ self._metrics['ThirdPartySources'] = using
514+
515+ def done(self):
516+ """Close telemetry collection
517+
518+ Save to destination file"""
519+
520+ self._metrics['Stages'] = self._stages_hist
521+
522+ target_dir = os.path.dirname(self._dest_path)
523+ try:
524+ if not os.path.exists(target_dir):
525+ os.makedirs(target_dir)
526+ with open(self._dest_path, 'w') as f:
527+ json.dump(self._metrics, f)
528+ os.chmod(self._dest_path,
529+ stat.S_IRUSR | stat.S_IWUSR |
530+ stat.S_IRGRP | stat.S_IROTH)
531+ except OSError as e:
532+ logging.warning("Exception while storing telemetry data: " +
533+ str(e))
534+
535+# vim:ai:et:sts=4:tw=80:sw=4:
536
537=== modified file 'debian/changelog'
538--- debian/changelog 2018-05-02 19:43:50 +0000
539+++ debian/changelog 2018-05-18 10:01:29 +0000
540@@ -1,3 +1,9 @@
541+ubuntu-release-upgrader (1:18.04.19) UNRELEASED; urgency=medium
542+
543+ * Add upgrade telemetry information (LP: #1755456)
544+
545+ -- Didier Roche <didrocks@ubuntu.com> Thu, 03 May 2018 15:55:34 +0200
546+
547 ubuntu-release-upgrader (1:18.04.18) bionic; urgency=medium
548
549 * data/removal_blacklist.cfg: Drop unity, ubuntu-gnome-desktop, and

Subscribers

People subscribed via source and target branches