Coverage for mlos_bench/mlos_bench/tests/environments/local/local_env_telemetry_test.py: 100%

51 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-07-14 00:55 +0000

1# 

2# Copyright (c) Microsoft Corporation. 

3# Licensed under the MIT License. 

4# 

5"""Unit tests for telemetry and status of LocalEnv benchmark environment.""" 

6from datetime import datetime, timedelta, tzinfo 

7 

8import pytest 

9from pytz import UTC 

10 

11from mlos_bench.tests import ZONE_INFO 

12from mlos_bench.tests.environments import check_env_fail_telemetry, check_env_success 

13from mlos_bench.tests.environments.local import create_local_env 

14from mlos_bench.tunables.tunable_groups import TunableGroups 

15 

16 

17def _format_str(zone_info: tzinfo | None) -> str: 

18 if zone_info is not None: 

19 return "%Y-%m-%d %H:%M:%S.%f %z" 

20 return "%Y-%m-%d %H:%M:%S.%f" 

21 

22 

23# FIXME: This fails with zone_info = None when run with `TZ="America/Chicago pytest -n0 ...` 

24@pytest.mark.parametrize(("zone_info"), ZONE_INFO) 

25def test_local_env_telemetry(tunable_groups: TunableGroups, zone_info: tzinfo | None) -> None: 

26 """Produce benchmark and telemetry data in a local script and read it.""" 

27 ts1 = datetime.now(zone_info) 

28 ts2 = ts1 + timedelta(minutes=1) 

29 

30 format_str = _format_str(zone_info) 

31 time_str1 = ts1.strftime(format_str) 

32 time_str2 = ts2.strftime(format_str) 

33 

34 local_env = create_local_env( 

35 tunable_groups, 

36 { 

37 "run": [ 

38 "echo 'metric,value' > output.csv", 

39 "echo 'latency,4.1' >> output.csv", 

40 "echo 'throughput,512' >> output.csv", 

41 "echo 'score,0.95' >> output.csv", 

42 "echo '-------------------'", # This output does not go anywhere 

43 "echo 'timestamp,metric,value' > telemetry.csv", 

44 f"echo {time_str1},cpu_load,0.65 >> telemetry.csv", 

45 f"echo {time_str1},mem_usage,10240 >> telemetry.csv", 

46 f"echo {time_str2},cpu_load,0.8 >> telemetry.csv", 

47 f"echo {time_str2},mem_usage,20480 >> telemetry.csv", 

48 ], 

49 "read_results_file": "output.csv", 

50 "read_telemetry_file": "telemetry.csv", 

51 }, 

52 ) 

53 

54 check_env_success( 

55 local_env, 

56 tunable_groups, 

57 expected_results={ 

58 "latency": 4.1, 

59 "throughput": 512.0, 

60 "score": 0.95, 

61 }, 

62 expected_telemetry=[ 

63 (ts1.astimezone(UTC), "cpu_load", 0.65), 

64 (ts1.astimezone(UTC), "mem_usage", 10240.0), 

65 (ts2.astimezone(UTC), "cpu_load", 0.8), 

66 (ts2.astimezone(UTC), "mem_usage", 20480.0), 

67 ], 

68 ) 

69 

70 

71# FIXME: This fails with zone_info = None when run with `TZ="America/Chicago pytest -n0 ...` 

72@pytest.mark.parametrize(("zone_info"), ZONE_INFO) 

73def test_local_env_telemetry_no_header( 

74 tunable_groups: TunableGroups, 

75 zone_info: tzinfo | None, 

76) -> None: 

77 """Read the telemetry data with no header.""" 

78 ts1 = datetime.now(zone_info) 

79 ts2 = ts1 + timedelta(minutes=1) 

80 

81 format_str = _format_str(zone_info) 

82 time_str1 = ts1.strftime(format_str) 

83 time_str2 = ts2.strftime(format_str) 

84 

85 local_env = create_local_env( 

86 tunable_groups, 

87 { 

88 "run": [ 

89 f"echo {time_str1},cpu_load,0.65 > telemetry.csv", 

90 f"echo {time_str1},mem_usage,10240 >> telemetry.csv", 

91 f"echo {time_str2},cpu_load,0.8 >> telemetry.csv", 

92 f"echo {time_str2},mem_usage,20480 >> telemetry.csv", 

93 ], 

94 "read_telemetry_file": "telemetry.csv", 

95 }, 

96 ) 

97 

98 check_env_success( 

99 local_env, 

100 tunable_groups, 

101 expected_results={}, 

102 expected_telemetry=[ 

103 (ts1.astimezone(UTC), "cpu_load", 0.65), 

104 (ts1.astimezone(UTC), "mem_usage", 10240.0), 

105 (ts2.astimezone(UTC), "cpu_load", 0.8), 

106 (ts2.astimezone(UTC), "mem_usage", 20480.0), 

107 ], 

108 ) 

109 

110 

111@pytest.mark.filterwarnings( 

112 "ignore:.*(Could not infer format, so each element will be parsed individually, " 

113 "falling back to `dateutil`).*:UserWarning::0" 

114) # pylint: disable=line-too-long # noqa 

115@pytest.mark.parametrize(("zone_info"), ZONE_INFO) 

116def test_local_env_telemetry_wrong_header( 

117 tunable_groups: TunableGroups, 

118 zone_info: tzinfo | None, 

119) -> None: 

120 """Read the telemetry data with incorrect header.""" 

121 ts1 = datetime.now(zone_info) 

122 ts2 = ts1 + timedelta(minutes=1) 

123 

124 format_str = _format_str(zone_info) 

125 time_str1 = ts1.strftime(format_str) 

126 time_str2 = ts2.strftime(format_str) 

127 

128 local_env = create_local_env( 

129 tunable_groups, 

130 { 

131 "run": [ 

132 # Error: the data is correct, but the header has unexpected column names 

133 "echo 'ts,metric_name,metric_value' > telemetry.csv", 

134 f"echo {time_str1},cpu_load,0.65 >> telemetry.csv", 

135 f"echo {time_str1},mem_usage,10240 >> telemetry.csv", 

136 f"echo {time_str2},cpu_load,0.8 >> telemetry.csv", 

137 f"echo {time_str2},mem_usage,20480 >> telemetry.csv", 

138 ], 

139 "read_telemetry_file": "telemetry.csv", 

140 }, 

141 ) 

142 

143 check_env_fail_telemetry(local_env, tunable_groups) 

144 

145 

146def test_local_env_telemetry_invalid(tunable_groups: TunableGroups) -> None: 

147 """Fail when the telemetry data has wrong format.""" 

148 zone_info = UTC 

149 ts1 = datetime.now(zone_info) 

150 ts2 = ts1 + timedelta(minutes=1) 

151 

152 format_str = _format_str(zone_info) 

153 time_str1 = ts1.strftime(format_str) 

154 time_str2 = ts2.strftime(format_str) 

155 

156 local_env = create_local_env( 

157 tunable_groups, 

158 { 

159 "run": [ 

160 # Error: too many columns 

161 f"echo {time_str1},EXTRA,cpu_load,0.65 > telemetry.csv", 

162 f"echo {time_str1},EXTRA,mem_usage,10240 >> telemetry.csv", 

163 f"echo {time_str2},EXTRA,cpu_load,0.8 >> telemetry.csv", 

164 f"echo {time_str2},EXTRA,mem_usage,20480 >> telemetry.csv", 

165 ], 

166 "read_telemetry_file": "telemetry.csv", 

167 }, 

168 ) 

169 

170 check_env_fail_telemetry(local_env, tunable_groups) 

171 

172 

173def test_local_env_telemetry_invalid_ts(tunable_groups: TunableGroups) -> None: 

174 """Fail when the telemetry data has wrong format.""" 

175 local_env = create_local_env( 

176 tunable_groups, 

177 { 

178 "run": [ 

179 # Error: field 1 must be a timestamp 

180 "echo 1,cpu_load,0.65 > telemetry.csv", 

181 "echo 2,mem_usage,10240 >> telemetry.csv", 

182 "echo 3,cpu_load,0.8 >> telemetry.csv", 

183 "echo 4,mem_usage,20480 >> telemetry.csv", 

184 ], 

185 "read_telemetry_file": "telemetry.csv", 

186 }, 

187 ) 

188 

189 check_env_fail_telemetry(local_env, tunable_groups)