summaryrefslogtreecommitdiffstats
path: root/chromium/third_party/catapult/tracing/tracing/value/diagnostics/generic_set.py
blob: 8fd24f5e6c23d0804462ff6fc62b743ab6324294 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# Copyright 2018 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import json
import six

from tracing.proto import histogram_proto
from tracing.value.diagnostics import diagnostic


class GenericSet(diagnostic.Diagnostic):
  """Contains any Plain-Ol'-Data objects.

  Contents are serialized using json.dumps(): None, boolean, number, string,
  list, dict. Dicts, lists, and booleans are deduplicated by their JSON
  representation. Dicts and lists are not hashable.  (1 == True) and (0 ==
  False) in Python, but not in JSON.
  """
  __slots__ = '_values', '_comparable_set'

  def __init__(self, values):
    super(GenericSet, self).__init__()

    self._values = list(values)
    self._comparable_set = None

  def __contains__(self, value):
    return value in self._values

  def __iter__(self):
    for value in self._values:
      yield value

  def __len__(self):
    return len(self._values)

  def __eq__(self, other):
    return self._GetComparableSet() == other._GetComparableSet()

  def __repr__(self):
    return str(self._GetComparableSet())

  def __hash__(self):
    return id(self)

  def SetValues(self, values):
    # Use a list because Python sets cannot store dicts or lists because they
    # are not hashable.
    self._values = list(values)

    # Cache a set to facilitate comparing and merging GenericSets.
    # Dicts, lists, and booleans are serialized; other types are not.
    self._comparable_set = None

  def _GetComparableSet(self):
    if self._comparable_set is None:
      self._comparable_set = set()
      for value in self:
        if isinstance(value, (dict, list, bool)):
          self._comparable_set.add(json.dumps(value, sort_keys=True))
        else:
          self._comparable_set.add(value)
    return self._comparable_set

  def CanAddDiagnostic(self, other_diagnostic):
    return isinstance(other_diagnostic, GenericSet)

  def AddDiagnostic(self, other_diagnostic):
    comparable_set = self._GetComparableSet()
    for value in other_diagnostic:
      if isinstance(value, (dict, list, bool)):
        json_value = json.dumps(value, sort_keys=True)
        if json_value not in comparable_set:
          self._values.append(value)
          self._comparable_set.add(json_value)
      elif value not in comparable_set:
        self._values.append(value)
        self._comparable_set.add(value)

  def Serialize(self, serializer):
    if len(self) == 1:
      return serializer.GetOrAllocateId(self._values[0])
    return [serializer.GetOrAllocateId(v) for v in self]

  def _AsDictInto(self, dct):
    dct['values'] = list(self)

  def _AsProto(self):
    proto = histogram_proto.Pb2().Diagnostic()
    proto.generic_set.values.extend([json.dumps(v) for v in list(self)])
    return proto

  @staticmethod
  def Deserialize(data, deserializer):
    if not isinstance(data, list):
      data = [data]
    return GenericSet([deserializer.GetObject(i) for i in data])

  @staticmethod
  def FromDict(dct):
    return GenericSet(dct['values'])

  @staticmethod
  def FromProto(d):
    values = []
    for value_json in d.values:
      try:
        values.append(json.loads(value_json))
      except (TypeError, ValueError) as e:
        six.raise_from(
            TypeError('The value %s is not valid JSON. You cannot pass naked '
                      'strings as a GenericSet value, for instance; they '
                      'have to be quoted. Therefore, 1234 is a valid value '
                      '(int), "abcd" is a valid value (string), but abcd is '
                      'not valid.' % value_json), e)

    return GenericSet(values)

  def GetOnlyElement(self):
    assert len(self) == 1
    return self._values[0]