blob: 71e7e7c7452b781582a3d6b927676d516f1a983b [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This file implements outputLanguage for JSON.
package source_generator
import (
"config"
"encoding/json"
"privacy"
)
type jsonOutputter struct {
errorCalculator *privacy.ErrorCalculator
}
// JSON export structure
// go tags set the field name when exported to JSON data.
type jsonRegistry struct {
Customers []jsonCustomer `json:"customers"`
}
type jsonCustomer struct {
Name string `json:"name"`
Id uint32 `json:"id"`
Projects []jsonProject `json:"projects"`
}
type jsonProject struct {
Name string `json:"name"`
Id uint32 `json:"id"`
Contact string `json:"project_contact"`
Metrics []jsonMetric `json:"metrics"`
}
type jsonMetric struct {
Name string `json:"name"`
Id uint32 `json:"id"`
MetricType string `json:"metric_type"`
Owners []string `json:"owners"`
ExpirationDate string `json:"expiration_date"`
Dimensions []string `json:"dimensions"`
MaxEventCode []uint32 `json:"max_event_code"`
Semantics []string `json:"metric_semantics"`
Units string `json:"metric_units"`
UnitsOther string `json:"metric_units_other"`
StringCandidateFile string `json:"string_candidate_file"`
ProtoName string `json:"proto_name"`
ReplacementMetricId uint32 `json:"replacement_metric_id"`
Reports []jsonReport `json:"reports"`
}
type jsonError map[string]jsonErrorEstimate
type jsonReport struct {
Name string `json:"name"`
Id uint32 `json:"id"`
ReportType string `json:"report_type"`
ReportTypeId int32 `json:"report_type_id"`
LocalPrivacyNoiseLevel string `json:"local_privacy_noise_level"`
PrivacyLevel string `json:"privacy_level"`
ErrorEstimates jsonError `json:"error_estimates"`
CandidateFile string `json:"candidate_file"`
CandidateList []string `json:"candidate_list"`
EventVectorBufferMax uint64 `json:"event_vector_buffer_max"`
StringBufferMax uint32 `json:"string_buffer_max"`
SystemProfileField []string `json:"system_profile_field"`
AggregationType string `json:"aggregation_type"`
WindowSize []string `json:"window_size"`
LocalAggregationPeriod int32 `json:"local_aggregation_period"`
LocalAggregationProcedure string `json:"local_aggregation_procedure"`
LocalAggregationProcedurePercentileN uint32 `json:"local_aggregation_procedure_percentile_n"`
}
type jsonErrorEstimate struct {
Epsilon float64 `json:"epsilon"`
Estimates map[uint64]float64 `json:"estimates"`
}
// Generates a list of evenly distributed integer values
// between |start| and |stop| of length |num|.
func linspace(start, stop, num uint64) []uint64 {
step := float64(stop-start) / float64(num-1)
elements := make([]uint64, num)
for x := range elements {
elements[x] = uint64(float64(start) + step*float64(x))
}
return elements
}
func (jo *jsonOutputter) makeErrorEstimates(report *config.ReportDefinition, metric *config.MetricDefinition, populations []uint64) (estimate jsonError) {
if jo.errorCalculator == nil {
// Error calculator not provided; error estimation skipped.
return nil
}
var estimates = jsonError{}
for l, n := range config.ReportDefinition_PrivacyLevel_name {
if n == "PRIVACY_LEVEL_UNKNOWN" || n == "NO_ADDED_PRIVACY" {
continue
}
level := config.ReportDefinition_PrivacyLevel(l)
epsilon := jo.errorCalculator.ParamsCalc.Constants.EpsilonForPrivacyLevel[level]
var err error
values := make(map[uint64]float64, len(populations))
for _, population := range populations {
// TODO(fxbug.dev/87113): Support minDenominatorEstimates. Currently any report type requiring
// a denominator estimate will return an error and not be included.
var errorValue float64
errorValue, err = jo.errorCalculator.Estimate(metric, report, epsilon, population, 0)
if err != nil {
break
}
values[population] = errorValue
}
if err != nil {
// Error estimates must compute successfully for every population value otherwise the
// estimates for this epsilon are not included.
continue
}
estimates[level.String()] = jsonErrorEstimate{
Epsilon: epsilon,
Estimates: values,
}
}
if len(estimates) == 0 {
return nil
}
return estimates
}
// JSON struct constructors
func (jo *jsonOutputter) makeJSONReport(report *config.ReportDefinition, metric *config.MetricDefinition) jsonReport {
if report == nil {
return jsonReport{}
}
var systemProfileField []string
for _, f := range report.GetSystemProfileField() {
systemProfileField = append(systemProfileField, f.String())
}
var windowSize []string
for _, ws := range report.GetWindowSize() {
windowSize = append(windowSize, ws.String())
}
populations := linspace(10000, 10000000, 50)
estimates := jo.makeErrorEstimates(report, metric, populations)
return jsonReport{
Name: report.GetReportName(),
Id: report.GetId(),
ReportType: report.GetReportType().String(),
ReportTypeId: int32(report.GetReportType()),
LocalPrivacyNoiseLevel: report.GetLocalPrivacyNoiseLevel().String(),
PrivacyLevel: report.GetPrivacyLevel().String(),
ErrorEstimates: estimates,
CandidateFile: report.GetCandidateFile(),
CandidateList: report.GetCandidateList(),
EventVectorBufferMax: report.GetEventVectorBufferMax(),
StringBufferMax: report.GetStringBufferMax(),
SystemProfileField: systemProfileField,
AggregationType: report.GetAggregationType().String(),
WindowSize: windowSize,
LocalAggregationPeriod: int32(report.GetLocalAggregationPeriod()),
LocalAggregationProcedure: report.GetLocalAggregationProcedure().String(),
LocalAggregationProcedurePercentileN: report.GetLocalAggregationProcedurePercentileN(),
}
}
func (jo *jsonOutputter) makeJSONMetric(metric *config.MetricDefinition) jsonMetric {
if metric == nil {
return jsonMetric{}
}
var expirationDate string
var owners []string
if metric.GetMetaData() != nil {
expirationDate = metric.GetMetaData().GetExpirationDate()
owners = metric.GetMetaData().GetOwner()
}
var reports []jsonReport
for _, r := range metric.GetReports() {
reports = append(reports, jo.makeJSONReport(r, metric))
}
var dimensions []string
var maxEventCodes []uint32
for _, d := range metric.GetMetricDimensions() {
dimensions = append(dimensions, d.GetDimension())
maxEventCodes = append(maxEventCodes, d.GetMaxEventCode())
}
var semantics []string
for _, s := range metric.GetMetricSemantics() {
semantics = append(semantics, s.String())
}
return jsonMetric{
Name: metric.GetMetricName(),
Id: metric.GetId(),
MetricType: metric.GetMetricType().String(),
Owners: owners,
ExpirationDate: expirationDate,
Dimensions: dimensions,
MaxEventCode: maxEventCodes,
Semantics: semantics,
Units: metric.GetMetricUnits().String(),
UnitsOther: metric.GetMetricUnitsOther(),
StringCandidateFile: metric.GetStringCandidateFile(),
ProtoName: metric.GetProtoName(),
ReplacementMetricId: metric.GetReplacementMetricId(),
Reports: reports,
}
}
func (jo *jsonOutputter) makeJSONProject(project *config.ProjectConfig) jsonProject {
if project == nil {
return jsonProject{}
}
var metrics []jsonMetric
for _, m := range project.GetMetrics() {
metrics = append(metrics, jo.makeJSONMetric(m))
}
return jsonProject{
Name: project.GetProjectName(),
Id: project.GetProjectId(),
Contact: project.GetProjectContact(),
Metrics: metrics,
}
}
func (jo *jsonOutputter) makeJSONCustomer(customer *config.CustomerConfig) jsonCustomer {
if customer == nil {
return jsonCustomer{}
}
var projects []jsonProject
for _, p := range customer.GetProjects() {
projects = append(projects, jo.makeJSONProject(p))
}
return jsonCustomer{
Name: customer.GetCustomerName(),
Id: customer.GetCustomerId(),
Projects: projects,
}
}
func (jo *jsonOutputter) makeJSONRegistry(registry *config.CobaltRegistry) jsonRegistry {
if registry == nil {
return jsonRegistry{}
}
var customers []jsonCustomer
for _, c := range registry.GetCustomers() {
customers = append(customers, jo.makeJSONCustomer(c))
}
return jsonRegistry{
Customers: customers,
}
}
// Outputs the registry contents in JSON
func (jo *jsonOutputter) JSONOutput(_, filtered *config.CobaltRegistry) (outputBytes []byte, err error) {
jsonRegistry := jo.makeJSONRegistry(filtered)
prefix := ""
indent := " "
jsonBytes, err := json.MarshalIndent(jsonRegistry, prefix, indent)
if err != nil {
return nil, err
}
return jsonBytes, nil
}
// Returns an output formatter for JSON
//
// privacyParamsPath is the string path of the privacy params file to be used for error estimation.
func JSONOutputFactory(errorCalculator *privacy.ErrorCalculator) OutputFormatter {
jo := jsonOutputter{errorCalculator}
return jo.JSONOutput
}