| // 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"` |
| EventCodeBufferMax uint64 `json:"event_code_buffer_max"` |
| StringCandidateFile string `json:"string_candidate_file"` |
| StringBufferMax uint32 `json:"string_buffer_max"` |
| 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"` |
| 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(jaredweinstein): 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(), |
| 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(), |
| EventCodeBufferMax: metric.GetEventCodeBufferMax(), |
| StringCandidateFile: metric.GetStringCandidateFile(), |
| StringBufferMax: metric.GetStringBufferMax(), |
| 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 |
| } |