Skip to content

Commit

Permalink
Initial commit of santametricservice.
Browse files Browse the repository at this point in the history
The santametricservice is an XPC helper service to write metrics. It consists of Formatters and Writers. This initial commit only has support for the rawJSON format and writing to a file.

This is a new daemon to be included. Docs and packaging will be updated in future PRs.
  • Loading branch information
pmarkowsky committed Sep 21, 2021
1 parent e569a68 commit c26aff3
Show file tree
Hide file tree
Showing 18 changed files with 1,191 additions and 1 deletion.
54 changes: 54 additions & 0 deletions Source/santametricservice/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
load("@build_bazel_rules_apple//apple:macos.bzl", "macos_command_line_application")
load("//:helper.bzl", "santa_unit_test")

package(default_visibility = ["//:santa_package_group"])

licenses(["notice"]) # Apache 2.0

objc_library(
name = "SNTMetricServiceLib",
srcs = [
"SNTMetricService.h",
"SNTMetricService.m",
"main.m",
],
deps = [
"//Source/common:SNTXPCMetricServiceInterface",
"//Source/common:SNTLogging",
"//Source/common:SNTConfigurator",
"//Source/common:SNTMetricSet",
"//Source/santametricservice/Formats:SNTMetricRawJsonFormat",
"//Source/santametricservice/Writers:SNTMetricFileWriter",
"@MOLCodesignChecker",
"@MOLXPCConnection",
],
)

santa_unit_test(
name="SNTMetricServiceTest",
srcs = ["SNTMetricServiceTest.m"],
deps = [
":SNTMetricServiceLib",
"@OCMock",
],
)

test_suite(
name="unit_tests",
tests = [
":SNTMetricServiceTest",
"//Source/santametricservice/Formats:SNTMetricRawJsonFormatTest",
"//Source/santametricservice/Writers:SNTMetricFileWriterTest",
],
)


macos_command_line_application(
name = "santametricservice",
bundle_id = "com.google.santa.metricservice",
infoplists = ["Info.plist"],
minimum_os_version = "10.15",
version = "//:version",
visibility = ["//:santa_package_group"],
deps = [":SNTMetricServiceLib"],
)
45 changes: 45 additions & 0 deletions Source/santametricservice/Formats/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
load("@build_bazel_rules_apple//apple:macos.bzl", "macos_command_line_application")
load("//:helper.bzl", "santa_unit_test")

package(default_visibility = ["//:santa_package_group"])

licenses(["notice"]) # Apache 2.0

objc_library(
name = "SNTMetricFormat",
hdrs = ["SNTMetricFormat.h"],
)

objc_library(
name = "SNTMetricRawJsonFormat",
srcs = [
"SNTMetricRawJsonFormat.h",
"SNTMetricRawJsonFormat.m",
"SNTMetricFormat.h",

],
deps = [
":SNTMetricFormat",
"//Source/common:SNTLogging",
],
)

santa_unit_test(
name = "SNTMetricRawJsonFormatTest",
srcs = [
"SNTMetricRawJsonFormatTest.m",
],
structured_resources = glob(["testdata/**"]),
deps = [
":SNTMetricRawJsonFormat",
"//Source/common:SNTMetricSet",
],
)


test_suite(
name = "format_tests",
tests = [
":SNTMetricRawJsonFormatTest",
],
)
19 changes: 19 additions & 0 deletions Source/santametricservice/Formats/SNTMetricFormat.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/// Copyright 2021 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.

#import <Foundation/Foundation.h>

@protocol SNTMetricFormat
- (NSArray<NSData *> *)convert:(NSDictionary *)metrics error:(NSError **)err;
@end
21 changes: 21 additions & 0 deletions Source/santametricservice/Formats/SNTMetricRawJsonFormat.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/// Copyright 2021 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.

#import <Foundation/Foundation.h>

#import "Source/santametricservice/Formats/SNTMetricFormat.h"

@interface SNTMetricRawJsonFormat : NSObject <SNTMetricFormat>
- (NSArray <NSData *> *) convert:(NSDictionary *)metrics error:(NSError **)err;
@end
98 changes: 98 additions & 0 deletions Source/santametricservice/Formats/SNTMetricRawJsonFormat.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/// Copyright 2021 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "Source/common/SNTLogging.h"

#import "Source/santametricservice/Formats/SNTMetricRawJsonFormat.h"

@implementation SNTMetricRawJsonFormat {
NSDateFormatter *dateFormatter;
}

- (instancetype)init
{
self = [super init];
if (self) {
dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"];
}
return self;
}

- (NSArray *)normalizeArray:(NSArray *)arr
{
NSMutableArray *normalized = [NSMutableArray arrayWithArray: arr];

for (int i = 0; i < [arr count];i++) {
if ([arr[i] isKindOfClass: [NSArray class]]) {
normalized[i] = [self normalizeArray: (NSArray *)arr[i]];
} else if ([arr[i] isKindOfClass: [NSDictionary class]]) {
normalized[i] = [self normalize: (NSDictionary *)arr[i]];
}
}

return normalized;
}

/**
* Normalizes the metrics dictionary for exporting to JSON
**/
- (NSDictionary *)normalize: (NSDictionary *)metrics
{
// Convert NSDate's to RFC3339 in strings as NSDate's cannot be serialized
// to JSON.
NSMutableDictionary *normalizedMetrics = [NSMutableDictionary dictionaryWithDictionary:metrics];

for (NSString *key in metrics) {
const id object = [metrics objectForKey: key];
if ([object isKindOfClass: [NSDate class]]) {
normalizedMetrics[key] = [self->dateFormatter stringFromDate: (NSDate *)object];
} else if ([object isKindOfClass: [NSDictionary class]]) {
normalizedMetrics[key] = [self normalize: metrics[key]];
} else if ([object isKindOfClass: [NSArray class]]) {
normalizedMetrics[key] = [self normalizeArray: (NSArray *)object];
}
}

return (NSDictionary *)normalizedMetrics;
}

/*
* Convert normalies and converts the metrics dictionary to a single JSON
* object.
*
* @param metrics an NSDictionary exported by the SNTMetricSet
* @param error a pointer to an NSError to allow errors to bubble up.
*
* Returns an NSArray containing one entry of all metrics serialized to JSON or
* nil on error.
*/
- (NSArray <NSData *> *) convert:(NSDictionary *)metrics error:(NSError **)err
{
NSDictionary *normalizedMetrics = [self normalize: metrics];

if (![NSJSONSerialization isValidJSONObject: normalizedMetrics]) {
LOGE(@"unable to convert metrics to JSON: invalid metrics");
return nil;
}

NSData *json = [NSJSONSerialization dataWithJSONObject: normalizedMetrics
options: NSJSONWritingPrettyPrinted
error: err];
if (json == nil && *err != nil) {
return nil;
}

return @[ json ];
}
@end
155 changes: 155 additions & 0 deletions Source/santametricservice/Formats/SNTMetricRawJsonFormatTest.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
#import <XCTest/XCTest.h>

#import "Source/common/SNTMetricSet.h"
#import "Source/santametricservice/Formats/SNTMetricRawJsonFormat.h"

NSDictionary *validMetricsDict = nil;

@interface SNTMetricRawJsonFormatTest : XCTestCase
@end

@implementation SNTMetricRawJsonFormatTest

- (void)initializeValidMetricsDict
{
NSDateFormatter *formatter = NSDateFormatter.new;
[formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"];
NSDate *fixedDate = [formatter dateFromString:@"2021-09-16T21:07:34.826Z"];

validMetricsDict = @{
@"root_labels" : @{@"hostname" : @"testHost", @"username" : @"testUser"},
@"metrics" : @{
@"/build/label" : @{
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantString],
@"fields" : @{
@"" : @[ @{
@"value" : @"",
@"created" : fixedDate,
@"last_updated" : fixedDate,
@"data" : @"20210809.0.1"
} ]
}
},
@"/santa/events" : @{
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeCounter],
@"fields" : @{
@"rule_type" : @[
@{
@"value" : @"binary",
@"created" : fixedDate,
@"last_updated" : fixedDate,
@"data" : @1,
},
@{
@"value" : @"certificate",
@"created" : fixedDate,
@"last_updated" : fixedDate,
@"data" : @2,
},
],
},
},
@"/santa/rules" : @{
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeInt64],
@"fields" : @{
@"rule_type" : @[
@{
@"value" : @"binary",
@"created" : fixedDate,
@"last_updated" : fixedDate,
@"data" : @1
},
@{
@"value" : @"certificate",
@"created" : fixedDate,
@"last_updated" : fixedDate,
@"data" : @3
}
]
},
},
@"/santa/using_endpoint_security_framework" : @{
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantBool],
@"fields" : @{
@"" : @[ @{
@"value" : @"",
@"created" : fixedDate,
@"last_updated" : fixedDate,
@"data" : [NSNumber numberWithBool:YES]
} ]
}
},
@"/proc/birth_timestamp" : @{
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantInt64],
@"fields" : @{
@"" : @[ @{
@"value" : @"",
@"created" : fixedDate,
@"last_updated" : fixedDate,
@"data" : [NSNumber numberWithLong:1250999830800]
} ]
},
},
@"/proc/memory/virtual_size" : @{
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeInt64],
@"fields" : @{
@"" : @[ @{
@"value" : @"",
@"created" : fixedDate,
@"last_updated" : fixedDate,
@"data" : [NSNumber numberWithInt:987654321]
} ]
}
},
@"/proc/memory/resident_size" : @{
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeInt64],
@"fields" : @{
@"" : @[ @{
@"value" : @"",
@"created" : fixedDate,
@"last_updated" : fixedDate,
@"data" : [NSNumber numberWithInt:123456789]
} ]
},
},
}
};
}

- (void)setUp
{
[self initializeValidMetricsDict];
}

- (void)testMetricsConversionToJSON
{
SNTMetricRawJsonFormat *formatter = [[SNTMetricRawJsonFormat alloc] init];
NSError *err = nil;
NSArray<NSData *> *output = [formatter convert:validMetricsDict error: &err];

XCTAssertEqual(1, [output count]);
XCTAssertNotNil(output[0]);
XCTAssertNil(err);

NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:output[0]
options:NSJSONReadingAllowFragments
error:&err];
XCTAssertNotNil(jsonDict);

NSString *path = [[NSBundle bundleForClass:[self class]] resourcePath];
path = [path stringByAppendingPathComponent:@"testdata/json/test.json"];

NSFileManager *filemgr = [NSFileManager defaultManager];
NSData *goldenFileData = [filemgr contentsAtPath: path];

XCTAssertNotNil(goldenFileData, @"unable to open / read golden file");

NSDictionary *expectedJsonDict = [NSJSONSerialization JSONObjectWithData:goldenFileData
options:NSJSONReadingAllowFragments
error: &err];

XCTAssertNotNil(expectedJsonDict);
XCTAssertEqualObjects(expectedJsonDict, jsonDict, @"generated JSON does not match golden file.");
}

@end
Loading

0 comments on commit c26aff3

Please sign in to comment.