[config_parser] Use value range size as num_index_points when small
When a report has a small range of valid integer values
(determined by some combination of the report's min_value,
max_value, and max_count fields, depending on the report
type), the precomputed discretization parameter may be
unnecessarily large. After this change, the config parser
sets the effective number of index points for a report
equal to the max of the report's integer range size and
the precomputed number of index points.
Bug: 69287
Change-Id: I79f2d57167cad815bb0ea2b2a8c1996e830d02c9
Reviewed-on: https://fuchsia-review.googlesource.com/c/cobalt/+/485978
Commit-Queue: Laura Peskin <pesk@google.com>
Reviewed-by: Jared Weinstein <jaredweinstein@google.com>
diff --git a/src/bin/config_parser/src/config_parser/populate_privacy_params_test.go b/src/bin/config_parser/src/config_parser/populate_privacy_params_test.go
index 385dd04..d1b7fc1 100644
--- a/src/bin/config_parser/src/config_parser/populate_privacy_params_test.go
+++ b/src/bin/config_parser/src/config_parser/populate_privacy_params_test.go
@@ -27,9 +27,15 @@
{"10.0", "20000", "10", "0.0004855785518884659", "15"},
}
+// Test cases where the report's integer range size is larger than the number of index
+// points given by |testParamRecords|.
func TestPopulateParamsForReport(t *testing.T) {
var eventCodeBufferMax uint64 = 10
+ var minValue int64 = 0
+ var maxValue int64 = 100
+ var maxCount uint64 = 100
+
occurrenceMetric := config.MetricDefinition{
MetricName: "OccurrenceMetric",
MetricType: config.MetricDefinition_OCCURRENCE,
@@ -43,16 +49,22 @@
ReportName: "HighPrivacyFleetwideOccurrenceCounts",
ReportType: config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS,
PrivacyLevel: config.ReportDefinition_HIGH_PRIVACY,
+ MinValue: minValue,
+ MaxValue: maxValue,
}
mediumPrivacyFleetwideOccurrenceCountsReport := config.ReportDefinition{
ReportName: "MediumPrivacyFleetwideOccurrenceCounts",
ReportType: config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS,
PrivacyLevel: config.ReportDefinition_MEDIUM_PRIVACY,
+ MinValue: minValue,
+ MaxValue: maxValue,
}
lowPrivacyFleetwideOccurrenceCountsReport := config.ReportDefinition{
ReportName: "LowPrivacyFleetwideOccurrenceCounts",
ReportType: config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS,
PrivacyLevel: config.ReportDefinition_LOW_PRIVACY,
+ MinValue: minValue,
+ MaxValue: maxValue,
}
noAddedPrivacyFleetwideOccurrenceCountsReport := config.ReportDefinition{
ReportName: "NoAddedPrivacyFleetwideOccurrenceCounts",
@@ -63,6 +75,7 @@
ReportName: "HighPrivacyStringCounts",
ReportType: config.ReportDefinition_STRING_COUNTS,
PrivacyLevel: config.ReportDefinition_HIGH_PRIVACY,
+ MaxCount: maxCount,
}
noAddedPrivacyStringCountsReport := config.ReportDefinition{
ReportName: "NoAddedPrivacyStringCounts",
@@ -134,6 +147,95 @@
}
}
+// Test cases where the report's integer range size is smaller than the number of index
+// points given by |testParamRecords|. In this case the integer range size should be used
+// as the number of index points.
+func TestPopulateParamsForSmallRangeReport(t *testing.T) {
+ var eventCodeBufferMax uint64 = 5
+ var minValue int64 = 0
+ var maxValue int64 = 1
+ var maxCount uint64 = 1
+ var numLinearBuckets uint32 = 2
+
+ linearBuckets := config.LinearIntegerBuckets{NumBuckets: numLinearBuckets}
+ buckets := config.IntegerBuckets{
+ Buckets: &config.IntegerBuckets_Linear{&linearBuckets},
+ }
+
+ occurrenceMetric := config.MetricDefinition{
+ MetricName: "OccurrenceMetric",
+ MetricType: config.MetricDefinition_OCCURRENCE,
+ EventCodeBufferMax: eventCodeBufferMax,
+ }
+ integerMetric := config.MetricDefinition{
+ MetricName: "IntegerMetric",
+ MetricType: config.MetricDefinition_INTEGER,
+ EventCodeBufferMax: eventCodeBufferMax,
+ }
+ highPrivacyFleetwideOccurrenceCountsReport := config.ReportDefinition{
+ ReportName: "HighPrivacyFleetwideOccurrenceCounts",
+ ReportType: config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS,
+ PrivacyLevel: config.ReportDefinition_HIGH_PRIVACY,
+ MinValue: minValue,
+ MaxValue: maxValue,
+ }
+ highPrivacyFleetwideHistogramsReport := config.ReportDefinition{
+ ReportName: "HighPrivacyFleetwideHistograms",
+ ReportType: config.ReportDefinition_FLEETWIDE_HISTOGRAMS,
+ IntBuckets: &buckets,
+ PrivacyLevel: config.ReportDefinition_HIGH_PRIVACY,
+ MaxCount: maxCount,
+ }
+
+ calc, err := privacy.NewPrivacyEncodingParamsCalculatorForTesting(testParamRecords)
+ if err != nil {
+ t.Errorf("Failed precondition: failed to create PrivacyEncodingParamsCalculator. Error: %v", err)
+ }
+
+ type args struct {
+ metric *config.MetricDefinition
+ report *config.ReportDefinition
+ }
+ type expectedParams struct {
+ probBitFlip float64
+ numIndexPoints uint32
+ }
+ var tests = []struct {
+ input args
+ valid bool
+ expected expectedParams
+ }{
+ // Valid inputs:
+ // For each report, the target population is 15000.
+ //
+ // The sparsity is |eventCodeBufferMax| = 5.
+ // The best-match record has key (1.0, 10000, 10).
+ {args{&occurrenceMetric, &highPrivacyFleetwideOccurrenceCountsReport}, true,
+ expectedParams{0.019537480548024178, uint32(maxValue)}},
+ // The sparsity is |eventCodeBufferMax| * |numLinearBuckets| = 10.
+ // The best-match record has key (1.0, 10000, 10).
+ {args{&integerMetric, &highPrivacyFleetwideHistogramsReport}, true,
+ expectedParams{0.019537480548024178, uint32(maxCount)}},
+ }
+ for _, test := range tests {
+ err := populateParamsForReport(calc, test.input.metric, test.input.report)
+ if test.valid && err != nil {
+ t.Errorf("populateParamsForReport() failed for report %s: %v", test.input.report.ReportName, err)
+ } else if !test.valid && err == nil {
+ t.Errorf("populateParamsForReport() accepted invalid input: metric %s, report %s", test.input.metric.MetricName, test.input.report.ReportName)
+ } else {
+ if test.input.report.ProbBitFlip != test.expected.probBitFlip {
+ t.Errorf("populateParamsForReport() wrote incorrect ProbBitFlip for report %s: expected %f, got %f",
+ test.input.report.ReportName, test.expected.probBitFlip, test.input.report.ProbBitFlip)
+ }
+ if test.input.report.NumIndexPoints != test.expected.numIndexPoints {
+ t.Errorf("populateParamsForReport() wrote incorrect NumIndexPoints for report %s: expected %d, got %d",
+ test.input.report.ReportName, test.expected.numIndexPoints, test.input.report.NumIndexPoints)
+ }
+ }
+ }
+}
+
func TestPopulateParamsForConfig(t *testing.T) {
cobalt10Report := config.ReportDefinition{
ReportName: "Cobalt1.0Report",
@@ -148,6 +250,8 @@
ReportName: "Cobalt1.1Report",
ReportType: config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS,
PrivacyLevel: config.ReportDefinition_HIGH_PRIVACY,
+ MinValue: 0,
+ MaxValue: 10,
}
cobalt11Metric := config.MetricDefinition{
MetricName: "Cobalt1.1Metric",
diff --git a/src/bin/config_parser/src/privacy/privacy_encoding_params.go b/src/bin/config_parser/src/privacy/privacy_encoding_params.go
index 95c4e3d..df864c1 100644
--- a/src/bin/config_parser/src/privacy/privacy_encoding_params.go
+++ b/src/bin/config_parser/src/privacy/privacy_encoding_params.go
@@ -156,7 +156,7 @@
// {epsilon, population, sparsity, prob_bit_flip, num_index_points}
func parsePrivacyEncodingRecord(record []string) (key paramsMapKey, params PrivacyEncodingParams, err error) {
if len(record) != 5 {
- return key, params, fmt.Errorf("Wrong record size: %d", len(record))
+ return key, params, fmt.Errorf("wrong record size: %d", len(record))
}
epsilon, err := strconv.ParseFloat(record[0], 64)
if err != nil {
@@ -206,15 +206,18 @@
sort.Slice(*vals, func(i, j int) bool { return (*vals)[i] < (*vals)[j] })
}
-// Given a |metric| and |report|, looks up the corresponding PrivacyEncodingParams from |calc|'s paramMap.
+// Given a |metric| and |report|, looks up the corresponding PrivacyEncodingParams from |calc|'s
+// paramMap. If |report| has an integer range size which is smaller than the number of index points
+// computed by |calc|, this function uses that integer range size as the NumIndexPoints field of the
+// returned PrivacyEncodingParams in order to avoid incurring unnecessary rounding error.
//
-// If paramMap does not have a key which exactly matches the values drawn from |metric|, |report|, and |calc.constants|,
-// then parameters are returned for the closest key which provides at least as much privacy as is required by
-// |report|'s PrivacyLevel. See getBestMappedKey for more details.
+// If paramMap does not have a key which exactly matches the values drawn from |metric|, |report|,
+// and |calc.constants|, then parameters are returned for the closest key which provides at least as
+// much privacy as is required by |report|'s PrivacyLevel. See getBestMappedKey for more details.
func (calc *PrivacyEncodingParamsCalculator) GetPrivacyEncodingParamsForReport(metric *config.MetricDefinition, report *config.ReportDefinition) (params PrivacyEncodingParams, err error) {
epsilon, ok := calc.Constants.EpsilonForPrivacyLevel[report.PrivacyLevel]
if !ok {
- return params, fmt.Errorf("No epsilon found for privacy level: %v", report.PrivacyLevel)
+ return params, fmt.Errorf("no epsilon found for privacy level: %v", report.PrivacyLevel)
}
sparsity, err := getSparsityForReport(metric, report)
@@ -222,7 +225,21 @@
return params, err
}
- return calc.GetPrivacyEncodingParams(epsilon, calc.Constants.population, sparsity)
+ range_size, err := getIntegerRangeSizeForReport(report)
+ if err != nil {
+ return params, err
+ }
+
+ params, err = calc.GetPrivacyEncodingParams(epsilon, calc.Constants.population, sparsity)
+ if err != nil {
+ return params, err
+ }
+
+ if range_size < uint64(params.NumIndexPoints) {
+ params.NumIndexPoints = uint32(range_size)
+ }
+
+ return params, err
}
// Given an |epsilon|, |population|, and |sparsity|, looks up the corresponding PrivacyEncodingParams from |calc|'s paramMap.
@@ -238,12 +255,61 @@
params, ok := calc.paramMap[key]
if !ok {
- return params, fmt.Errorf("No params found for key: (epsilon=%f, population=%d, sparsity=%d)", key.epsilon, key.population, key.sparsity)
+ return params, fmt.Errorf("no params found for key: (epsilon=%f, population=%d, sparsity=%d)", key.epsilon, key.population, key.sparsity)
}
return calc.paramMap[key], nil
}
+// Returns the number of valid integer values for |report|. For FleetwideOccurrenceCounts,
+// UniqueDeviceNumericStats, and HourlyValueNumericStats reports, this is the difference between
+// the report's MaxValue and MinValue fields. For FleetwideHistograms and StringCounts reports,
+// it is the report's MaxCount field.
+//
+// For UniqueDeviceCounts, UniqueDeviceHistograms, and HourlyValueHistograms reports, this notion
+// doesn't apply and the returned range size is 0.
+//
+// A FleetwideMeans report has two separate configured ranges: one for sum values and another for
+// count values. The returned range size is the maximum of the two range sizes.
+func getIntegerRangeSizeForReport(report *config.ReportDefinition) (range_size uint64, err error) {
+ switch report.ReportType {
+ case config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS,
+ config.ReportDefinition_UNIQUE_DEVICE_NUMERIC_STATS,
+ config.ReportDefinition_HOURLY_VALUE_NUMERIC_STATS:
+ {
+ size := report.MaxValue - report.MinValue
+ if size >= 0 {
+ range_size = uint64(size)
+ } else {
+ return range_size, fmt.Errorf("min value %d is larger than max value %d", report.MinValue, report.MaxValue)
+ }
+ }
+ case config.ReportDefinition_FLEETWIDE_HISTOGRAMS,
+ config.ReportDefinition_STRING_COUNTS:
+ range_size, err = report.MaxCount, nil
+ case config.ReportDefinition_FLEETWIDE_MEANS:
+ {
+ sum_size := report.MaxValue - report.MinValue
+ if sum_size >= 0 {
+ range_size = uint64(sum_size)
+ } else {
+ return range_size, fmt.Errorf("min value %d is larger than max value %d", report.MinValue, report.MaxValue)
+ }
+ if range_size < report.MaxCount {
+ range_size = report.MaxCount
+ }
+ }
+ case config.ReportDefinition_UNIQUE_DEVICE_COUNTS,
+ config.ReportDefinition_UNIQUE_DEVICE_HISTOGRAMS,
+ config.ReportDefinition_HOURLY_VALUE_HISTOGRAMS:
+ range_size = 0
+ default:
+ return range_size, fmt.Errorf("unsupported ReportType: %v", report.ReportType)
+ }
+ return range_size, nil
+
+}
+
// Returns the sparsity for a given |metric| and |report|. This is the max number of elements in the index vector representation
// of a contribution for |report|.
func getSparsityForReport(metric *config.MetricDefinition, report *config.ReportDefinition) (sparsity uint64, err error) {
@@ -272,7 +338,7 @@
case config.ReportDefinition_AT_LEAST_ONCE:
numEventVectors, err = metric.EventCodeBufferMax, nil
default:
- err = fmt.Errorf("Unexpected LocalAggregationProcedure: %v", report.LocalAggregationProcedure)
+ err = fmt.Errorf("unexpected LocalAggregationProcedure: %v", report.LocalAggregationProcedure)
}
case
config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS,
@@ -286,7 +352,7 @@
numEventVectors, err = metric.EventCodeBufferMax, nil
default:
- err = fmt.Errorf("Unsupported ReportType: %v", report.ReportType)
+ err = fmt.Errorf("unsupported ReportType: %v", report.ReportType)
}
return numEventVectors, err
}
@@ -317,7 +383,7 @@
case config.MetricDefinition_STRING:
numBuckets, err = numBuckets, fmt.Errorf("STRING metrics are not supported yet")
default:
- err = fmt.Errorf("Unsupported metric type %v", metric.MetricType)
+ err = fmt.Errorf("unsupported metric type %v", metric.MetricType)
}
return numBuckets, err
}
@@ -332,7 +398,7 @@
case nil:
err = fmt.Errorf("IntegerBuckets type not set")
default:
- err = fmt.Errorf("Unexpected IntegerBuckets type")
+ err = fmt.Errorf("unexpected IntegerBuckets type")
}
return numBuckets, err
}
@@ -367,14 +433,14 @@
// Otherwise, the returned |mappedEpsilon| is the greatest epsilon value in |lists| which is less than or equal to |epsilon|.
func (lists *paramsKeyLists) getBestMappedEpsilon(epsilon float64) (mappedEpsilon float64, err error) {
if len(lists.epsilons) == 0 {
- return mappedEpsilon, fmt.Errorf("List of mapped epsilon values is empty")
+ return mappedEpsilon, fmt.Errorf("list of mapped epsilon values is empty")
}
i := sort.Search(len(lists.epsilons), func(i int) bool { return lists.epsilons[i] > epsilon })
if i > 0 {
return lists.epsilons[i-1], nil
} else {
- return mappedEpsilon, fmt.Errorf("Input epsilon %v is outside the valid range", epsilon)
+ return mappedEpsilon, fmt.Errorf("input epsilon %v is outside the valid range", epsilon)
}
}
@@ -382,14 +448,14 @@
// Otherwise, the returned |mappedPopulation| is the greatest population value in |lists| which is less than or equal to |population|.
func (lists *paramsKeyLists) getBestMappedPopulation(population uint64) (mappedPopulation uint64, err error) {
if len(lists.populations) == 0 {
- return mappedPopulation, fmt.Errorf("List of mapped population values is empty")
+ return mappedPopulation, fmt.Errorf("list of mapped population values is empty")
}
i := sort.Search(len(lists.populations), func(i int) bool { return lists.populations[i] > population })
if i > 0 {
return lists.populations[i-1], nil
} else {
- return mappedPopulation, fmt.Errorf("Input population %v is outside the valid range", population)
+ return mappedPopulation, fmt.Errorf("input population %v is outside the valid range", population)
}
}
@@ -397,13 +463,13 @@
// Otherwise, the returned |mappedSparsity| is the least sparsity value in |lists| which is greater than or equal to |sparsity|.
func (lists *paramsKeyLists) getBestMappedSparsity(sparsity uint64) (mappedSparsity uint64, err error) {
if len(lists.sparsities) == 0 {
- return mappedSparsity, fmt.Errorf("List of mapped sparsity values is empty")
+ return mappedSparsity, fmt.Errorf("list of mapped sparsity values is empty")
}
i := sort.Search(len(lists.sparsities), func(i int) bool { return lists.sparsities[i] >= sparsity })
if i < len(lists.sparsities) {
return lists.sparsities[i], nil
} else {
- return mappedSparsity, fmt.Errorf("Input sparsity %v is outside the valid range", sparsity)
+ return mappedSparsity, fmt.Errorf("input sparsity %v is outside the valid range", sparsity)
}
}
diff --git a/src/bin/config_parser/src/privacy/privacy_encoding_params_test.go b/src/bin/config_parser/src/privacy/privacy_encoding_params_test.go
index 48e380a..9db1d5d 100644
--- a/src/bin/config_parser/src/privacy/privacy_encoding_params_test.go
+++ b/src/bin/config_parser/src/privacy/privacy_encoding_params_test.go
@@ -82,6 +82,105 @@
}
}
+func TestGetIntegerRangeForReport(t *testing.T) {
+ var minValue int64 = -5
+ var maxValue int64 = 10
+ var maxCount uint64 = 20
+ var smallMaxCount uint64 = 5
+
+ // Reports with MinValue and MaxValue set
+ fleetwideOccurrenceCountsReport := config.ReportDefinition{
+ ReportName: "FleetwideOccurrenceCounts",
+ ReportType: config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS,
+ MinValue: minValue,
+ MaxValue: maxValue,
+ }
+ uniqueDeviceNumericStatsReport := config.ReportDefinition{
+ ReportName: "UniqueDeviceNumericStats",
+ ReportType: config.ReportDefinition_UNIQUE_DEVICE_NUMERIC_STATS,
+ MinValue: minValue,
+ MaxValue: maxValue,
+ }
+ hourlyValueNumericStatsReport := config.ReportDefinition{
+ ReportName: "HourlyValueNumericStats",
+ ReportType: config.ReportDefinition_HOURLY_VALUE_NUMERIC_STATS,
+ MinValue: minValue,
+ MaxValue: maxValue,
+ }
+ // Reports with MaxCount set
+ fleetwideHistogramsReport := config.ReportDefinition{
+ ReportName: "FleetwideHistograms",
+ ReportType: config.ReportDefinition_FLEETWIDE_HISTOGRAMS,
+ MaxCount: maxCount,
+ }
+ stringCountsReport := config.ReportDefinition{
+ ReportName: "StringCounts",
+ ReportType: config.ReportDefinition_STRING_COUNTS,
+ MaxCount: maxCount,
+ }
+ // Reports with MinValue, MaxValue, and MaxCount set
+ fleetwideMeansLargerSumRangeReport := config.ReportDefinition{
+ ReportName: "FleetwideMeansLargerSumRange",
+ ReportType: config.ReportDefinition_FLEETWIDE_MEANS,
+ MinValue: minValue,
+ MaxValue: maxValue,
+ MaxCount: smallMaxCount,
+ }
+ fleetwideMeansLargerCountRangeReport := config.ReportDefinition{
+ ReportName: "FleetwideMeansLargerCountRange",
+ ReportType: config.ReportDefinition_FLEETWIDE_MEANS,
+ MinValue: minValue,
+ MaxValue: maxValue,
+ MaxCount: maxCount,
+ }
+ // Reports without an integer range
+ uniqueDeviceCountsReport := config.ReportDefinition{
+ ReportName: "UniqueDeviceCounts",
+ ReportType: config.ReportDefinition_UNIQUE_DEVICE_COUNTS,
+ }
+ uniqueDeviceHistogramsReport := config.ReportDefinition{
+ ReportName: "UniqueDeviceHistograms",
+ ReportType: config.ReportDefinition_UNIQUE_DEVICE_HISTOGRAMS,
+ }
+ hourlyValueHistogramsReport := config.ReportDefinition{
+ ReportName: "HourlyValueHistograms",
+ ReportType: config.ReportDefinition_HOURLY_VALUE_HISTOGRAMS,
+ }
+ // Invalid report type
+ unsetReportTypeReport := config.ReportDefinition{
+ ReportName: "UnsetReportType",
+ }
+ var tests = []struct {
+ report *config.ReportDefinition
+ valid bool
+ expected uint64
+ }{
+ // Valid input:
+ {&fleetwideOccurrenceCountsReport, true, uint64(maxValue - minValue)},
+ {&uniqueDeviceNumericStatsReport, true, uint64(maxValue - minValue)},
+ {&hourlyValueNumericStatsReport, true, uint64(maxValue - minValue)},
+ {&fleetwideHistogramsReport, true, maxCount},
+ {&stringCountsReport, true, maxCount},
+ {&fleetwideMeansLargerSumRangeReport, true, uint64(maxValue - minValue)},
+ {&fleetwideMeansLargerCountRangeReport, true, maxCount},
+ {&uniqueDeviceCountsReport, true, 0},
+ {&uniqueDeviceHistogramsReport, true, 0},
+ {&hourlyValueHistogramsReport, true, 0},
+ // Invalid input:
+ {&unsetReportTypeReport, false, 0},
+ }
+ for _, test := range tests {
+ result, err := getIntegerRangeSizeForReport(test.report)
+ if test.valid && err != nil {
+ t.Errorf("getIntegerRangeSizeForReport() failed for report %s: %v", test.report.ReportName, err)
+ } else if !test.valid && err == nil {
+ t.Errorf("getIntegerRangeSizeForReport() accepted invalid report: %s", test.report.ReportName)
+ } else if test.valid && result != test.expected {
+ t.Errorf("getIntegerRangeSizeForReport() for report %s: expected %d, got %d", test.report.ReportName, test.expected, result)
+ }
+ }
+}
+
func TestGetSparsityForReport(t *testing.T) {
var eventCodeBufferMax uint64 = 5
var stringBufferMax uint32 = 3
@@ -417,11 +516,30 @@
t.Errorf("NewPrivacyEncodingParamsCalculatorForTesting failed with valid input. Error message: %v", err)
}
- metric := config.MetricDefinition{
- MetricType: config.MetricDefinition_OCCURRENCE,
- EventCodeBufferMax: 5,
+ var eventCodeBufferMax uint64 = 5
+ var minValue int64 = 0
+ var smallMaxValue int64 = 1
+ var largeMaxValue int64 = 100
+ var smallMaxCount uint64 = 1
+ var largeMaxCount uint64 = 100
+ var numLinearBuckets uint32 = 2
+
+ linearBuckets := config.LinearIntegerBuckets{NumBuckets: numLinearBuckets}
+ buckets := config.IntegerBuckets{
+ Buckets: &config.IntegerBuckets_Linear{&linearBuckets},
}
+ // Metrics
+ occurrenceMetric := config.MetricDefinition{
+ MetricType: config.MetricDefinition_OCCURRENCE,
+ EventCodeBufferMax: eventCodeBufferMax,
+ }
+ integerMetric := config.MetricDefinition{
+ MetricType: config.MetricDefinition_INTEGER,
+ EventCodeBufferMax: eventCodeBufferMax,
+ }
+
+ // Reports
highPrivacyAtLeastOnceReport := config.ReportDefinition{
ReportName: "HighPrivacyAtLeastOnceReport",
ReportType: config.ReportDefinition_UNIQUE_DEVICE_COUNTS,
@@ -434,6 +552,34 @@
PrivacyLevel: config.ReportDefinition_LOW_PRIVACY,
LocalAggregationProcedure: config.ReportDefinition_SELECT_FIRST,
}
+ smallRangeSizeFleetwideOccurrenceCountsReport := config.ReportDefinition{
+ ReportName: "smallRangeSizeFleetwideOccurrenceCountsReport",
+ ReportType: config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS,
+ PrivacyLevel: config.ReportDefinition_LOW_PRIVACY,
+ MinValue: minValue,
+ MaxValue: smallMaxValue,
+ }
+ largeRangeSizeFleetwideOccurrenceCountsReport := config.ReportDefinition{
+ ReportName: "largeRangeSizeFleetwideOccurrenceCountsReport",
+ ReportType: config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS,
+ PrivacyLevel: config.ReportDefinition_LOW_PRIVACY,
+ MinValue: minValue,
+ MaxValue: largeMaxValue,
+ }
+ smallRangeSizeFleetwideHistogramsReport := config.ReportDefinition{
+ ReportName: "smallRangeSizeFleetwideHistogramsReport",
+ ReportType: config.ReportDefinition_FLEETWIDE_HISTOGRAMS,
+ IntBuckets: &buckets,
+ PrivacyLevel: config.ReportDefinition_LOW_PRIVACY,
+ MaxCount: smallMaxCount,
+ }
+ largeRangeSizeFleetwideHistogramsReport := config.ReportDefinition{
+ ReportName: "largeRangeSizeFleetwideHistogramsReport",
+ ReportType: config.ReportDefinition_FLEETWIDE_HISTOGRAMS,
+ IntBuckets: &buckets,
+ PrivacyLevel: config.ReportDefinition_LOW_PRIVACY,
+ MaxCount: largeMaxCount,
+ }
unsetReportTypeReport := config.ReportDefinition{
ReportName: "UnsetReportTypeReport",
}
@@ -448,14 +594,45 @@
expected PrivacyEncodingParams
}{
// Valid input:
- // The target epsilon is 1.0 and the sparsity is metric.EventCodeBufferMax. The best-match key is {1.0, 10000, 10}.
- {args{&metric, &highPrivacyAtLeastOnceReport}, true, PrivacyEncodingParams{0.019537480548024178, 4}},
+ // The target epsilon is 1.0 and the sparsity is |eventCodeBufferMax|. The best-match key is
+ // {1.0, 10000, 10}. The report has type UniqueDeviceCounts, so the NumIndexPoints field of
+ // PrivacyEncodingParams is set to 0.
+ {args{&occurrenceMetric, &highPrivacyAtLeastOnceReport}, true,
+ PrivacyEncodingParams{0.019537480548024178, 0}},
// The target epsilon is 10.0 and the sparsity is 1. The best-match key is {5.0, 10000, 1}.
- {args{&metric, &lowPrivacySelectFirstReport}, true, PrivacyEncodingParams{0.000906200148165226, 12}},
+ // The report has type UniqueDeviceCounts, so the NumIndexPoints field of PrivacyEncodingParams is
+ // set to 0.
+ {args{&occurrenceMetric, &lowPrivacySelectFirstReport}, true,
+ PrivacyEncodingParams{0.000906200148165226, 0}},
+ // The target epsilon is 10.0 and the sparsity is |eventCodeBufferMax|.
+ // The best-match key is {5.0, 10000, 10}.
+ // The integer range size of the report (= |smallMaxValue|) is smaller than the number of index
+ // points computed by |calc|, so the returned NumIndexPoints should be equal to |smallMaxValue|.
+ {args{&occurrenceMetric, &smallRangeSizeFleetwideOccurrenceCountsReport}, true,
+ PrivacyEncodingParams{0.0014028092846274376, uint32(smallMaxValue)}},
+ // The target epsilon is 10.0 and the sparsity is |eventCodeBufferMax|.
+ // The best-match key is {5.0, 10000, 10}.
+ // The integer range size of the report (= largeMaxValue) is larger than the number of index points
+ // computed by |calc|, so the returned NumIndexPoints is given by the value at the best-match key.
+ {args{&occurrenceMetric, &largeRangeSizeFleetwideOccurrenceCountsReport}, true,
+ PrivacyEncodingParams{0.0014028092846274376, 10}},
+ // The target epsilon is 10.0 and the sparsity is |eventCodeBufferMax| * |numLinearBuckets|.
+ // The best-match key is {5.0, 10000, 10}.
+ // The integer range size of the report (= |smallMaxCount|) is smaller than the number of index
+ // points computed by |calc|, so the returned NumIndexPoints should be equal to |smallMaxCount|.
+ {args{&integerMetric, &smallRangeSizeFleetwideHistogramsReport}, true,
+ PrivacyEncodingParams{0.0014028092846274376, uint32(smallMaxCount)}},
+ // The target epsilon is 10.0 and the sparsity is |eventCodeBufferMax| * |numLinearBuckets|.
+ // The best-match key is {5.0, 10000, 10}.
+ // The integer range size of the report (= |largeMaxCount|) is larger than the number of index
+ // points computed by |calc|, so the returned NumIndexPoints is given by the value at the best-match
+ // key.
+ {args{&integerMetric, &largeRangeSizeFleetwideHistogramsReport}, true,
+ PrivacyEncodingParams{0.0014028092846274376, 10}},
// Invalid input:
// This report does not have a report type set.
- {args{&metric, &unsetReportTypeReport}, false, PrivacyEncodingParams{}},
+ {args{&occurrenceMetric, &unsetReportTypeReport}, false, PrivacyEncodingParams{}},
}
for _, test := range tests {
result, err := calc.GetPrivacyEncodingParamsForReport(test.input.metric, test.input.report)